<commit_before>/**
 * Copyright 2018 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import {createEnumDeclaration, createEnumMember, createIntersectionTypeNode, createKeywordTypeNode, createModifiersFromModifierFlags, createParenthesizedType, createStringLiteral, createTypeAliasDeclaration, createTypeLiteralNode, createTypeReferenceNode, createUnionTypeNode, EnumDeclaration, ModifierFlags, SyntaxKind, TypeNode} from 'typescript';

import {withComments} from './comments';
import {toClassName, toEnumName, toScopedName} from './names';
import {Property, PropertyType} from './toProperty';
import {ObjectPredicate, TObject, TPredicate, TSubject} from './triple';
import {SchemaObject, SchemaString} from './types';
import {GetComment, GetSubClassOf, GetType, IsClass, IsProperty, TTypeName} from './wellKnown';

export type ClassMap = Map<string, Class>;

export interface BySubject {
  Subject: TSubject;
  values: ObjectPredicate[];
}
export interface ByType {
  type: TTypeName;
  decls: BySubject[];
}

export class Class {
  private comment?: string;
  private readonly parents: string[] = [];
  private readonly _props: Property[] = [];
  private readonly _enums: EnumValue[] = [];
  private isLeaf = true;

  properties() {
    return this.isLeaf ?
        [
          new Property(
              '@type',
              new PropertyType(
                  this.subject, new SchemaString(toScopedName(this.subject)))),
          ...this._props
        ] :
        this._props;
  }

  constructor(readonly subject: TSubject) {}
  add(value: {Predicate: TPredicate; Object: TObject},
      classMap: ClassMap): boolean {
    const c = GetComment(value);
    if (c) {
      if (this.comment) {
        throw new Error(`Trying to add comment on class ${
            this.subject.toString()} but one already exists.`);
      }
      this.comment = c.comment;
      return true;
    }
    const s = GetSubClassOf(value);
    if (s) {
      this.parents.push(toClassName(s.subClassOf));
      const parentClass = classMap.get(s.subClassOf.toString());
      if (parentClass) {
        parentClass.isLeaf = false;
      } else {
        throw new Error(`Couldn't find parent of ${this.subject.toString()}, ${
            s.subClassOf.toString()}`);
      }
      return true;
    }

    if (GetType(value)) return true;  // We used types already.

    return false;
  }
  addProp(p: Property) {
    this._props.push(p);
  }
  addEnum(e: EnumValue) {
    this._enums.push(e);
  }

  private nonEnumType(): TypeNode {
    // Parent Part.
    const parentTypes =
        this.parents.map(parent => createTypeReferenceNode(parent, []));
    const parentNode = parentTypes.length === 0 ?
        null :
        parentTypes.length === 1 ? parentTypes[0] :
                                   createUnionTypeNode(parentTypes);

    // Properties part.
    const propLiteral =
        createTypeLiteralNode(this.properties().map(prop => prop.toNode()));

    if (parentNode && propLiteral.members.length > 0) {
      return createIntersectionTypeNode([parentNode, propLiteral]);
    } else if (parentNode) {
      return parentNode;
    } else if (propLiteral.members.length > 0) {
      return propLiteral;
    } else {
      return createKeywordTypeNode(SyntaxKind.NeverKeyword);
    }
  }

  private totalType(): TypeNode {
    const isEnum = this._enums.length > 0;

    if (isEnum) {
      return createUnionTypeNode([
        createTypeReferenceNode(toClassName(this.subject) + 'Enum', []),
        createParenthesizedType(this.nonEnumType()),
      ]);
    } else {
      return this.nonEnumType();
    }
  }

  private enumType(): EnumDeclaration|undefined {
    if (this._enums.length === 0) return undefined;

    return createEnumDeclaration(
        /* decorators= */[],
        createModifiersFromModifierFlags(ModifierFlags.Export),
        toClassName(this.subject) + 'Enum', this._enums.map(e => e.toNode()));
  }

  toNode() {
    const typeValue: TypeNode = this.totalType();
    const enumType = this.enumType();
    const declaration = withComments(
        this.comment,
        createTypeAliasDeclaration(
            /* decorators = */[],
            createModifiersFromModifierFlags(ModifierFlags.Export),
            toClassName(this.subject),
            [],
            typeValue,
            ));

    return enumType ? [enumType, declaration] : [declaration];
  }
}

export class Builtin extends Class {
  constructor(
      private readonly name: string, private readonly equivTo: string,
      private readonly doc?: string) {
    super(new SchemaObject(name));
  }

  toNode() {
    return [createTypeAliasDeclaration(
        [], createModifiersFromModifierFlags(ModifierFlags.Export), this.name,
        [], createTypeReferenceNode(this.equivTo, []))];
  }
}

export class EnumValue {<commit_msg>Redo generated type structure to better support type inference.<commit_after>/**
 * Copyright 2018 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import {createEnumDeclaration, createEnumMember, createIntersectionTypeNode, createKeywordTypeNode, createModifiersFromModifierFlags, createParenthesizedType, createStringLiteral, createTypeAliasDeclaration, createTypeLiteralNode, createTypeReferenceNode, createUnionTypeNode, EnumDeclaration, ModifierFlags, Statement, SyntaxKind, TypeAliasDeclaration, TypeNode} from 'typescript';

import {withComments} from './comments';
import {toClassName, toEnumName, toScopedName} from './names';
import {Property, PropertyType} from './toProperty';
import {ObjectPredicate, TObject, TPredicate, TSubject} from './triple';
import {SchemaObject, SchemaString} from './types';
import {GetComment, GetSubClassOf, GetType, IsClass, IsProperty, TTypeName} from './wellKnown';

export type ClassMap = Map<string, Class>;

export interface BySubject {
  Subject: TSubject;
  values: ObjectPredicate[];
}
export interface ByType {
  type: TTypeName;
  decls: BySubject[];
}

function arrayOf<T>(...args: Array<T|undefined|null>): T[] {
  return args.filter(
      (elem): elem is T => elem !== null && typeof elem !== 'undefined');
}

export class Class {
  private comment?: string;
  private readonly children: Class[] = [];
  private readonly parents: Class[] = [];
  private readonly _props: Property[] = [];
  private readonly _enums: EnumValue[] = [];

  private aliasesBuiltin(): boolean {
    for (const parent of this.parents) {
      if (parent instanceof Builtin || parent.aliasesBuiltin()) {
        return true;
      }
    }
    return false;
  }

  isLeaf(): boolean {
    return this.children.length === 0 && !this.aliasesBuiltin();
  }

  properties() {
    return this.isLeaf() ?
        [
          new Property(
              '@type',
              new PropertyType(
                  this.subject, new SchemaString(toScopedName(this.subject)))),
          ...this._props
        ] :
        this._props;
  }

  protected baseName() {
    return toClassName(this.subject) + 'Base';
  }
  private enumName() {
    return toClassName(this.subject) + 'Enum';
  }
  private className() {
    return toClassName(this.subject);
  }

  constructor(readonly subject: TSubject) {}
  add(value: {Predicate: TPredicate; Object: TObject},
      classMap: ClassMap): boolean {
    const c = GetComment(value);
    if (c) {
      if (this.comment) {
        throw new Error(`Trying to add comment on class ${
            this.subject.toString()} but one already exists.`);
      }
      this.comment = c.comment;
      return true;
    }
    const s = GetSubClassOf(value);
    if (s) {
      const parentClass = classMap.get(s.subClassOf.toString());
      if (parentClass) {
        this.parents.push(parentClass);
        parentClass.children.push(this);
      } else {
        throw new Error(`Couldn't find parent of ${this.subject.toString()}, ${
            s.subClassOf.toString()}`);
      }
      return true;
    }

    if (GetType(value)) return true;  // We used types already.

    return false;
  }
  addProp(p: Property) {
    this._props.push(p);
  }
  addEnum(e: EnumValue) {
    this._enums.push(e);
  }

  private baseNode(): TypeNode {
    // Properties part.
    const propLiteral =
        createTypeLiteralNode(this.properties().map(prop => prop.toNode()));

    const parentTypes = this.parents.map(
        parent => createTypeReferenceNode(parent.baseName(), []));
    const parentNode = parentTypes.length === 0 ?
        null :
        parentTypes.length === 1 ?
        parentTypes[0] :
        createParenthesizedType(createIntersectionTypeNode(parentTypes));

    if (parentNode && propLiteral.members.length > 0) {
      return createIntersectionTypeNode([parentNode, propLiteral]);
    } else if (parentNode) {
      return parentNode;
    } else if (propLiteral.members.length > 0) {
      return propLiteral;
    } else {
      return createKeywordTypeNode(SyntaxKind.NeverKeyword);
    }
  }

  private baseDecl(): TypeAliasDeclaration {
    const baseNode = this.baseNode();

    return createTypeAliasDeclaration(
        /*decorators=*/[], /*modifiers=*/[], this.baseName(),
        /*typeParameters=*/[], baseNode);
  }

  private nonEnumType(): TypeNode {
    const children = this.children.map(
        child =>
            createTypeReferenceNode(child.className(), /*typeArguments=*/[]));

    const childrenNode = children.length === 0 ?
        null :
        children.length === 1 ?
        children[0] :
        createParenthesizedType(createUnionTypeNode(children));

    if (childrenNode) {
      return childrenNode;
    } else {
      return createTypeReferenceNode(this.baseName(), /*typeArguments=*/[]);
    }
  }

  private totalType(): TypeNode {
    const isEnum = this._enums.length > 0;

    if (isEnum) {
      return createUnionTypeNode([
        createTypeReferenceNode(this.enumName(), []),
        createParenthesizedType(this.nonEnumType()),
      ]);
    } else {
      return this.nonEnumType();
    }
  }

  private enumDecl(): EnumDeclaration|undefined {
    if (this._enums.length === 0) return undefined;

    return createEnumDeclaration(
        /* decorators= */[],
        createModifiersFromModifierFlags(ModifierFlags.Export), this.enumName(),
        this._enums.map(e => e.toNode()));
  }

  toNode() {
    const typeValue: TypeNode = this.totalType();
    const declaration = withComments(
        this.comment,
        createTypeAliasDeclaration(
            /* decorators = */[],
            createModifiersFromModifierFlags(ModifierFlags.Export),
            this.className(),
            [],
            typeValue,
            ));

    return arrayOf<Statement>(this.enumDecl(), this.baseDecl(), declaration);
  }
}

export class Builtin extends Class {
  constructor(
      private readonly name: string, private readonly equivTo: string,
      private readonly doc?: string) {
    super(new SchemaObject(name));
  }

  toNode() {
    return [createTypeAliasDeclaration(
        [], createModifiersFromModifierFlags(ModifierFlags.Export), this.name,
        [], createTypeReferenceNode(this.equivTo, []))];
  }

  protected baseName() {
    return this.name;
  }
}

export class EnumValue {
{'fraction_non_alphanumeric': 0.11793188346327452, 'fraction_numerical': 0.0027903159622486664, 'mean_word_length': 3.072860962566845, 'pattern_counts': {'":': 0, '<': 8, '<?xml version=': 0, '>': 22, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2837018', 'n_tokens_mistral': 3393, 'n_tokens_neox': 3370, 'n_words': 886}