import t from "../../lib/index.js"; import definitions from "../../lib/definitions/index.js"; import formatBuilderName from "../utils/formatBuilderName.js"; import lowerFirst from "../utils/lowerFirst.js"; import stringifyValidator from "../utils/stringifyValidator.js"; 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; }); } function generateBuilderArgs(type) { const fields = t.NODE_FIELDS[type]; const fieldNames = sortFieldNames(Object.keys(t.NODE_FIELDS[type]), type); const builderNames = t.BUILDER_KEYS[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, "t."); if (isNullable(field) && !hasDefault(field)) { typeAnnotation += " | null"; } if (builderNames.includes(fieldName)) { const bindingIdentifierName = t.toBindingIdentifierName(fieldName); if (areAllRemainingFieldsNullable(fieldName, builderNames, fields)) { args.push( `${bindingIdentifierName}${ isNullable(field) ? "?:" : ":" } ${typeAnnotation}` ); } else { args.push( `${bindingIdentifierName}: ${typeAnnotation}${ isNullable(field) ? " | undefined" : "" }` ); } } }); return args; } export default function generateBuilders(kind) { return kind === "uppercase.js" ? generateUppercaseBuilders() : generateLowercaseBuilders(); } function generateLowercaseBuilders() { let output = `/* * This file is auto-generated! Do not modify it directly. * To re-generate run 'make build' */ import builder from "../builder"; import type * as t from "../.."; /* eslint-disable @typescript-eslint/no-unused-vars */ `; const reservedNames = new Set(["super", "import"]); Object.keys(definitions.BUILDER_KEYS).forEach(type => { const defArgs = generateBuilderArgs(type); const formatedBuilderName = formatBuilderName(type); const formatedBuilderNameLocal = reservedNames.has(formatedBuilderName) ? `_${formatedBuilderName}` : formatedBuilderName; output += `${ formatedBuilderNameLocal === formatedBuilderName ? "export " : "" }function ${formatedBuilderNameLocal}(${defArgs.join( ", " )}): t.${type} { return builder.apply("${type}", arguments); }\n`; if (formatedBuilderNameLocal !== formatedBuilderName) { output += `export { ${formatedBuilderNameLocal} as ${formatedBuilderName} };\n`; } // This is needed for backwards compatibility. // It should be removed in the next major version. // JSXIdentifier -> jSXIdentifier if (/^[A-Z]{2}/.test(type)) { output += `export { ${formatedBuilderNameLocal} as ${lowerFirst( type )} }\n`; } }); Object.keys(definitions.DEPRECATED_KEYS).forEach(type => { const newType = definitions.DEPRECATED_KEYS[type]; const formatedBuilderName = formatBuilderName(type); output += `/** @deprecated */ function ${type}(${generateBuilderArgs(newType).join(", ")}): t.${type} { console.trace("The node type ${type} has been renamed to ${newType}"); return builder.apply("${type}", arguments); } export { ${type} as ${formatedBuilderName} };\n`; // This is needed for backwards compatibility. // It should be removed in the next major version. // JSXIdentifier -> jSXIdentifier if (/^[A-Z]{2}/.test(type)) { output += `export { ${type} as ${lowerFirst(type)} }\n`; } }); return output; } function generateUppercaseBuilders() { let output = `/* * This file is auto-generated! Do not modify it directly. * To re-generate run 'make build' */ /** * This file is written in JavaScript and not TypeScript because uppercase builders * conflict with AST types. TypeScript reads the uppercase.d.ts file instead. */ export {\n`; Object.keys(definitions.BUILDER_KEYS).forEach(type => { const formatedBuilderName = formatBuilderName(type); output += ` ${formatedBuilderName} as ${type},\n`; }); Object.keys(definitions.DEPRECATED_KEYS).forEach(type => { const formatedBuilderName = formatBuilderName(type); output += ` ${formatedBuilderName} as ${type},\n`; }); output += ` } from './index';\n`; return output; }