import * as t from "../../lib/index.js"; import stringifyValidator, { isValueType, } from "../utils/stringifyValidator.js"; const parentMaps = new Map([["File", new Set(["null"])]]); function registerParentMaps(parent, nodes) { for (const node of nodes) { if (!parentMaps.has(node)) { parentMaps.set(node, new Set()); } parentMaps.get(node).add(parent); } } function getNodeTypesFromValidator(validator) { if (validator === undefined) return []; if (validator.each) { return getNodeTypesFromValidator(validator.each); } if (validator.chainOf) { return getNodeTypesFromValidator(validator.chainOf[1]); } let nodeTypes = []; if (validator.oneOfNodeTypes) { nodeTypes = validator.oneOfNodeTypes; } if (validator.oneOfNodeOrValueTypes) { nodeTypes = validator.oneOfNodeOrValueTypes.filter( type => !isValueType(type) ); } return nodeTypes.flatMap(type => t.FLIPPED_ALIAS_KEYS[type] ?? type); } export default function generateAstTypes() { let code = `// NOTE: This file is autogenerated. Do not modify. // See packages/babel-types/scripts/generators/ast-types.js for script used. interface BaseComment { value: string; start?: number; end?: number; loc?: SourceLocation; // generator will skip the comment if ignore is true ignore?: boolean; 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 { type: Node["type"]; leadingComments?: Comment[] | null; innerComments?: Comment[] | null; trailingComments?: Comment[] | null; start?: number | null; end?: number | null; loc?: SourceLocation | null; range?: [number, number]; extra?: Record; } export type CommentTypeShorthand = "leading" | "inner" | "trailing"; export type Node = ${t.TYPES.filter(k => !t.FLIPPED_ALIAS_KEYS[k]) .sort() .join(" | ")};\n\n`; const deprecatedAlias = {}; for (const type in t.DEPRECATED_KEYS) { deprecatedAlias[t.DEPRECATED_KEYS[type]] = type; } for (const type in t.NODE_FIELDS) { const fields = t.NODE_FIELDS[type]; const fieldNames = sortFieldNames(Object.keys(t.NODE_FIELDS[type]), type); const struct = []; 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"; } const alphaNumeric = /^\w+$/; const optional = field.optional ? "?" : ""; if (t.isValidIdentifier(fieldName) || alphaNumeric.test(fieldName)) { struct.push(`${fieldName}${optional}: ${typeAnnotation};`); } else { struct.push(`"${fieldName}"${optional}: ${typeAnnotation};`); } registerParentMaps(type, getNodeTypesFromValidator(field.validate)); }); code += `export interface ${type} extends BaseNode { type: "${type}"; ${struct.join("\n ").trim()} }\n\n`; if (deprecatedAlias[type]) { code += `/** * @deprecated Use \`${type}\` */ export interface ${deprecatedAlias[type]} extends BaseNode { type: "${deprecatedAlias[type]}"; ${struct.join("\n ").trim()} }\n\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 += `export type DeprecatedAliases = ${Object.keys( t.DEPRECATED_KEYS ).join(" | ")}\n\n`; code += "export interface ParentMaps {\n"; registerParentMaps("null", [...Object.keys(t.DEPRECATED_KEYS)]); // todo: provide a better parent type for Placeholder, currently it acts // as a catch-all parent type for an abstract NodePath, s.t NodePath.parent must // be a Node if type has not been specified registerParentMaps("Node", ["Placeholder"]); const parentMapsKeys = [...parentMaps.keys()].sort(); for (const type of parentMapsKeys) { const deduplicated = [...parentMaps.get(type)].sort(); code += ` ${type}: ${deduplicated.join(" | ")};\n`; } code += "}\n\n"; return code; } 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; }); }