typescript-legacy.js 16.7 KB
Newer Older
Rosanny Sihombing's avatar
Rosanny Sihombing committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
import * as t from "../../lib/index.js";
import stringifyValidator from "../utils/stringifyValidator.js";
import toFunctionName from "../utils/toFunctionName.js";

let code = `// NOTE: This file is autogenerated. Do not modify.
// See packages/babel-types/scripts/generators/typescript-legacy.js for script used.

interface BaseComment {
  value: string;
  start: number;
  end: number;
  loc: SourceLocation;
  type: "CommentBlock" | "CommentLine";
}

export interface CommentBlock extends BaseComment {
  type: "CommentBlock";
}

export interface CommentLine extends BaseComment {
  type: "CommentLine";
}

export type Comment = CommentBlock | CommentLine;

export interface SourceLocation {
  start: {
    line: number;
    column: number;
  };

  end: {
    line: number;
    column: number;
  };
}

interface BaseNode {
  leadingComments: ReadonlyArray<Comment> | null;
  innerComments: ReadonlyArray<Comment> | null;
  trailingComments: ReadonlyArray<Comment> | null;
  start: number | null;
  end: number | null;
  loc: SourceLocation | null;
  type: Node["type"];
  extra?: Record<string, unknown>;
}

export type Node = ${t.TYPES.sort().join(" | ")};\n\n`;

//

const lines = [];

for (const type in t.NODE_FIELDS) {
  const fields = t.NODE_FIELDS[type];
  const fieldNames = sortFieldNames(Object.keys(t.NODE_FIELDS[type]), type);
  const builderNames = t.BUILDER_KEYS[type];

  const struct = ['type: "' + type + '";'];
  const args = [];

  fieldNames.forEach(fieldName => {
    const field = fields[fieldName];
    // Future / annoying TODO:
    // MemberExpression.property, ObjectProperty.key and ObjectMethod.key need special cases; either:
    // - convert the declaration to chain() like ClassProperty.key and ClassMethod.key,
    // - declare an alias type for valid keys, detect the case and reuse it here,
    // - declare a disjoint union with, for example, ObjectPropertyBase,
    //   ObjectPropertyLiteralKey and ObjectPropertyComputedKey, and declare ObjectProperty
    //   as "ObjectPropertyBase & (ObjectPropertyLiteralKey | ObjectPropertyComputedKey)"
    let typeAnnotation = stringifyValidator(field.validate, "");

    if (isNullable(field) && !hasDefault(field)) {
      typeAnnotation += " | null";
    }

    if (builderNames.includes(fieldName)) {
      if (areAllRemainingFieldsNullable(fieldName, builderNames, fields)) {
        args.push(
          `${t.toBindingIdentifierName(fieldName)}${
            isNullable(field) ? "?:" : ":"
          } ${typeAnnotation}`
        );
      } else {
        args.push(
          `${t.toBindingIdentifierName(fieldName)}: ${typeAnnotation}${
            isNullable(field) ? " | undefined" : ""
          }`
        );
      }
    }

    const alphaNumeric = /^\w+$/;

    if (t.isValidIdentifier(fieldName) || alphaNumeric.test(fieldName)) {
      struct.push(`${fieldName}: ${typeAnnotation};`);
    } else {
      struct.push(`"${fieldName}": ${typeAnnotation};`);
    }
  });

  code += `export interface ${type} extends BaseNode {
  ${struct.join("\n  ").trim()}
}\n\n`;

  // super and import are reserved words in JavaScript
  if (type !== "Super" && type !== "Import") {
    lines.push(
      `export function ${toFunctionName(type)}(${args.join(", ")}): ${type};`
    );
  } else {
    const functionName = toFunctionName(type);
    lines.push(
      `declare function _${functionName}(${args.join(", ")}): ${type};`,
      `export { _${functionName} as ${functionName}}`
    );
  }
}

for (const typeName of t.TYPES) {
  const isDeprecated = !!t.DEPRECATED_KEYS[typeName];
  const realName = isDeprecated ? t.DEPRECATED_KEYS[typeName] : typeName;

  const result =
    t.NODE_FIELDS[realName] || t.FLIPPED_ALIAS_KEYS[realName]
      ? `node is ${realName}`
      : "boolean";

  if (isDeprecated) {
    lines.push(`/** @deprecated Use \`is${realName}\` */`);
  }
  lines.push(
    `export function is${typeName}(node: object | null | undefined, opts?: object | null): ${result};`
  );

  if (isDeprecated) {
    lines.push(`/** @deprecated Use \`assert${realName}\` */`);
  }
  lines.push(
    `export function assert${typeName}(node: object | null | undefined, opts?: object | null): void;`
  );
}

lines.push(
  // assert/
  `export function assertNode(obj: any): void`,

  // builders/
  // eslint-disable-next-line max-len
  `export function createTypeAnnotationBasedOnTypeof(type: 'string' | 'number' | 'undefined' | 'boolean' | 'function' | 'object' | 'symbol'): StringTypeAnnotation | VoidTypeAnnotation | NumberTypeAnnotation | BooleanTypeAnnotation | GenericTypeAnnotation`,
  `export function createUnionTypeAnnotation<T extends FlowType>(types: [T]): T`,
  `export function createFlowUnionType<T extends FlowType>(types: [T]): T`,
  // this probably misbehaves if there are 0 elements, and it's not a UnionTypeAnnotation if there's only 1
  // it is possible to require "2 or more" for this overload ([T, T, ...T[]]) but it requires typescript 3.0
  `export function createUnionTypeAnnotation(types: ReadonlyArray<FlowType>): UnionTypeAnnotation`,
  `export function createFlowUnionType(types: ReadonlyArray<FlowType>): UnionTypeAnnotation`,
  // this smells like "internal API"
  // eslint-disable-next-line max-len
  `export function buildChildren(node: { children: ReadonlyArray<JSXText | JSXExpressionContainer | JSXSpreadChild | JSXElement | JSXFragment | JSXEmptyExpression> }): JSXElement['children']`,

  // clone/
  `export function clone<T extends Node>(n: T): T;`,
  `export function cloneDeep<T extends Node>(n: T): T;`,
  `export function cloneDeepWithoutLoc<T extends Node>(n: T): T;`,
  `export function cloneNode<T extends Node>(n: T, deep?: boolean, withoutLoc?: boolean): T;`,
  `export function cloneWithoutLoc<T extends Node>(n: T): T;`,

  // comments/
  `export type CommentTypeShorthand = 'leading' | 'inner' | 'trailing'`,
  // eslint-disable-next-line max-len
  `export function addComment<T extends Node>(node: T, type: CommentTypeShorthand, content: string, line?: boolean): T`,
  // eslint-disable-next-line max-len
  `export function addComments<T extends Node>(node: T, type: CommentTypeShorthand, comments: ReadonlyArray<Comment>): T`,
  `export function inheritInnerComments(node: Node, parent: Node): void`,
  `export function inheritLeadingComments(node: Node, parent: Node): void`,
  `export function inheritsComments<T extends Node>(node: T, parent: Node): void`,
  `export function inheritTrailingComments(node: Node, parent: Node): void`,
  `export function removeComments<T extends Node>(node: T): T`,

  // converters/
  // eslint-disable-next-line max-len
  `export function ensureBlock(node: Extract<Node, { body: BlockStatement | Statement | Expression }>): BlockStatement`,
  // too complex?
  // eslint-disable-next-line max-len
  `export function ensureBlock<K extends keyof Extract<Node, { body: BlockStatement | Statement | Expression }> = 'body'>(node: Extract<Node, Record<K, BlockStatement | Statement | Expression>>, key: K): BlockStatement`,
  // gatherSequenceExpressions is not exported
  `export function toBindingIdentifierName(name: { toString(): string } | null | undefined): string`,
  `export function toBlock(node: Statement | Expression, parent?: Function | null): BlockStatement`,
  // it is possible for `node` to be an arbitrary object if `key` is always provided,
  // but that doesn't look like intended API
  // eslint-disable-next-line max-len
  `export function toComputedKey<T extends Extract<Node, { computed: boolean | null }>>(node: T, key?: Expression | Identifier): Expression`,
  `export function toExpression(node: Function): FunctionExpression`,
  `export function toExpression(node: Class): ClassExpression`,
  `export function toExpression(node: ExpressionStatement | Expression | Class | Function): Expression`,
  `export function toIdentifier(name: { toString(): string } | null | undefined): string`,
  `export function toKeyAlias(node: Method | Property, key?: Node): string`,
  // NOTE: this actually uses Scope from @babel/traverse, but we can't add a dependency on its types,
  // as they live in @types. Declare the structural subset that is required.
  // eslint-disable-next-line max-len
  `export function toSequenceExpression(nodes: ReadonlyArray<Node>, scope: { push(value: { id: LVal; kind: 'var'; init?: Expression}): void; buildUndefinedNode(): Node }): SequenceExpression | undefined`,
  `export function toStatement(node: AssignmentExpression, ignore?: boolean): ExpressionStatement`,
  `export function toStatement(node: Statement | AssignmentExpression, ignore?: boolean): Statement`,
  `export function toStatement(node: Class, ignore: true): ClassDeclaration | undefined`,
  `export function toStatement(node: Class, ignore?: boolean): ClassDeclaration`,
  `export function toStatement(node: Function, ignore: true): FunctionDeclaration | undefined`,
  `export function toStatement(node: Function, ignore?: boolean): FunctionDeclaration`,
  // eslint-disable-next-line max-len
  `export function toStatement(node: Statement | Class | Function | AssignmentExpression, ignore: true): Statement | undefined`,
  // eslint-disable-next-line max-len
  `export function toStatement(node: Statement | Class | Function | AssignmentExpression, ignore?: boolean): Statement`,
  // eslint-disable-next-line max-len
  `export function valueToNode(value: undefined): Identifier`, // (should this not be a UnaryExpression to avoid shadowing?)
  `export function valueToNode(value: boolean): BooleanLiteral`,
  `export function valueToNode(value: null): NullLiteral`,
  `export function valueToNode(value: string): StringLiteral`,
  // Infinities and NaN need to use a BinaryExpression; negative values must be wrapped in UnaryExpression
  `export function valueToNode(value: number): NumericLiteral | BinaryExpression | UnaryExpression`,
  `export function valueToNode(value: RegExp): RegExpLiteral`,
  // eslint-disable-next-line max-len
  `export function valueToNode(value: ReadonlyArray<undefined | boolean | null | string | number | RegExp | object>): ArrayExpression`,
  // this throws with objects that are not PlainObject according to lodash,
  // or if there are non-valueToNode-able values
  `export function valueToNode(value: object): ObjectExpression`,
  // eslint-disable-next-line max-len
  `export function valueToNode(value: undefined | boolean | null | string | number | RegExp | object): Expression`,

  // modifications/
  // eslint-disable-next-line max-len
  `export function removeTypeDuplicates(types: ReadonlyArray<FlowType | false | null | undefined>): FlowType[]`,
  // eslint-disable-next-line max-len
  `export function appendToMemberExpression<T extends Pick<MemberExpression, 'object' | 'property'>>(member: T, append: MemberExpression['property'], computed?: boolean): T`,
  // eslint-disable-next-line max-len
  `export function inherits<T extends Node | null | undefined>(child: T, parent: Node | null | undefined): T`,
  // eslint-disable-next-line max-len
  `export function prependToMemberExpression<T extends Pick<MemberExpression, 'object' | 'property'>>(member: T, prepend: MemberExpression['object']): T`,
  `export function removeProperties(
  n: Node,
  opts?: { preserveComments: boolean } | null
): void;`,
  `export function removePropertiesDeep<T extends Node>(
  n: T,
  opts?: { preserveComments: boolean } | null
): T;`,

  // retrievers/
  // eslint-disable-next-line max-len
  `export function getBindingIdentifiers(node: Node, duplicates: true, outerOnly?: boolean): Record<string, Array<Identifier>>`,
  // eslint-disable-next-line max-len
  `export function getBindingIdentifiers(node: Node, duplicates?: false, outerOnly?: boolean): Record<string, Identifier>`,
  // eslint-disable-next-line max-len
  `export function getBindingIdentifiers(node: Node, duplicates: boolean, outerOnly?: boolean): Record<string, Identifier | Array<Identifier>>`,
  // eslint-disable-next-line max-len
  `export function getOuterBindingIdentifiers(node: Node, duplicates: true): Record<string, Array<Identifier>>`,
  `export function getOuterBindingIdentifiers(node: Node, duplicates?: false): Record<string, Identifier>`,
  // eslint-disable-next-line max-len
  `export function getOuterBindingIdentifiers(node: Node, duplicates: boolean): Record<string, Identifier | Array<Identifier>>`,

  // traverse/
  `export type TraversalAncestors = ReadonlyArray<{
    node: Node,
    key: string,
    index?: number,
  }>;
  export type TraversalHandler<T> = (
    this: undefined, node: Node, parent: TraversalAncestors, type: T
  ) => void;
  export type TraversalHandlers<T> = {
    enter?: TraversalHandler<T>,
    exit?: TraversalHandler<T>,
  };`.replace(/(^|\n) {2}/g, "$1"),
  // eslint-disable-next-line
  `export function traverse<T>(n: Node, h: TraversalHandler<T> | TraversalHandlers<T>, state?: T): void;`,
  `export function traverseFast<T>(n: Node, h: TraversalHandler<T>, state?: T): void;`,

  // utils/
  // cleanJSXElementLiteralChild is not exported
  // inherit is not exported
  `export function shallowEqual<T extends object>(actual: object, expected: T): actual is T`,

  // validators/
  // eslint-disable-next-line max-len
  `export function buildMatchMemberExpression(match: string, allowPartial?: boolean): (node: Node | null | undefined) => node is MemberExpression`,
  // eslint-disable-next-line max-len
  `export function is<T extends Node['type']>(type: T, n: Node | null | undefined, required?: undefined): n is Extract<Node, { type: T }>`,
  // eslint-disable-next-line max-len
  `export function is<T extends Node['type'], P extends Extract<Node, { type: T }>>(type: T, n: Node | null | undefined, required: Partial<P>): n is P`,
  // eslint-disable-next-line max-len
  `export function is<P extends Node>(type: string, n: Node | null | undefined, required: Partial<P>): n is P`,
  `export function is(type: string, n: Node | null | undefined, required?: Partial<Node>): n is Node`,
  `export function isBinding(node: Node, parent: Node, grandparent?: Node): boolean`,
  // eslint-disable-next-line max-len
  `export function isBlockScoped(node: Node): node is FunctionDeclaration | ClassDeclaration | VariableDeclaration`,
  `export function isImmutable(node: Node): node is Immutable`,
  `export function isLet(node: Node): node is VariableDeclaration`,
  `export function isNode(node: object | null | undefined): node is Node`,
  `export function isNodesEquivalent<T extends Partial<Node>>(a: T, b: any): b is T`,
  `export function isNodesEquivalent(a: any, b: any): boolean`,
  `export function isPlaceholderType(placeholderType: Node['type'], targetType: Node['type']): boolean`,
  `export function isReferenced(node: Node, parent: Node, grandparent?: Node): boolean`,
  `export function isScope(node: Node, parent: Node): node is Scopable`,
  `export function isSpecifierDefault(specifier: ModuleSpecifier): boolean`,
  `export function isType<T extends Node['type']>(nodetype: string, targetType: T): nodetype is T`,
  `export function isType(nodetype: string | null | undefined, targetType: string): boolean`,
  `export function isValidES3Identifier(name: string): boolean`,
  `export function isValidIdentifier(name: string): boolean`,
  `export function isVar(node: Node): node is VariableDeclaration`,
  // the MemberExpression implication is incidental, but it follows from the implementation
  // eslint-disable-next-line max-len
  `export function matchesPattern(node: Node | null | undefined, match: string | ReadonlyArray<string>, allowPartial?: boolean): node is MemberExpression`,
  // eslint-disable-next-line max-len
  `export function validate<T extends Node, K extends keyof T>(n: Node | null | undefined, key: K, value: T[K]): void;`,
  `export function validate(n: Node, key: string, value: any): void;`
);

for (const type in t.DEPRECATED_KEYS) {
  code += `/**
 * @deprecated Use \`${t.DEPRECATED_KEYS[type]}\`
 */
export type ${type} = ${t.DEPRECATED_KEYS[type]};\n
`;
}

for (const type in t.FLIPPED_ALIAS_KEYS) {
  const types = t.FLIPPED_ALIAS_KEYS[type];
  code += `export type ${type} = ${types
    .map(type => `${type}`)
    .join(" | ")};\n`;
}
code += "\n";

code += "export interface Aliases {\n";
for (const type in t.FLIPPED_ALIAS_KEYS) {
  code += `  ${type}: ${type};\n`;
}
code += "}\n\n";

code += lines.join("\n") + "\n";

//

process.stdout.write(code);

//

function areAllRemainingFieldsNullable(fieldName, fieldNames, fields) {
  const index = fieldNames.indexOf(fieldName);
  return fieldNames.slice(index).every(_ => isNullable(fields[_]));
}

function hasDefault(field) {
  return field.default != null;
}

function isNullable(field) {
  return field.optional || hasDefault(field);
}

function sortFieldNames(fields, type) {
  return fields.sort((fieldA, fieldB) => {
    const indexA = t.BUILDER_KEYS[type].indexOf(fieldA);
    const indexB = t.BUILDER_KEYS[type].indexOf(fieldB);
    if (indexA === indexB) return fieldA < fieldB ? -1 : 1;
    if (indexA === -1) return 1;
    if (indexB === -1) return -1;
    return indexA - indexB;
  });
}