UNPKG

eslint-codemod-utils

Version:

A collection of AST helper functions for more complex ESLint rule fixes.

1,204 lines (1,203 loc) 37.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.literal = exports.nullLiteral = exports.numberLiteral = exports.booleanLiteral = exports.stringLiteral = exports.regExpLiteral = exports.bigIntLiteral = exports.importDeclaration = exports.templateElement = exports.importNamespaceSpecifier = exports.variableDeclaration = exports.variableDeclarator = exports.logicalExpression = exports.memberExpression = exports.emptyStatement = exports.objectExpression = exports.restElement = exports.spreadElement = exports.objectPattern = exports.property = exports.newExpression = exports.expressionStatement = exports.updateExpression = exports.arrayPattern = exports.arrayExpression = exports.yieldExpression = exports.importSpecifier = exports.exportSpecifier = exports.exportAllDeclaration = exports.exportDefaultDeclaration = exports.exportNamedDeclaration = exports.importDefaultSpecifier = exports.importExpression = exports.withStatement = exports.tryStatement = exports.catchClause = exports.ifStatement = exports.thisExpression = exports.unaryExpression = exports.throwStatement = exports.returnStatement = exports.blockStatement = exports.functionExpression = exports.taggedTemplateExpression = exports.arrowFunctionExpression = exports.sequenceExpression = exports.binaryExpression = exports.chainExpression = exports.superCallExpression = exports.callExpression = void 0; exports.decorator = exports.privateIdentifier = exports.program = exports.classExpression = exports.classDeclaration = exports.classBody = exports.propertyDefinition = exports.methodDefinition = exports.methodOrPropertyFn = exports.functionDeclaration = exports.staticBlock = exports.awaitExpression = exports.assignmentExpression = exports.conditionalExpression = exports.debuggerStatement = exports.breakStatement = exports.continueStatement = exports.forOfStatement = exports.forInStatement = exports.forStatement = exports.templateLiteral = exports.switchStatement = exports.switchCase = exports.whileStatement = exports.doWhileStatement = exports.identifier = void 0; const types_1 = require("@typescript-eslint/types"); const node_1 = require("./utils/node"); const constants_1 = require("./constants"); const utils_1 = require("./utils"); /** * __CallExpression__ * * @example * * Usage * ``` * const call = callExpression({ callee: identifier({ name: 'normalCallExpression' }) }) * ``` * * Produces * * @example * * ```js * normalCallExpression() * ``` * * @returns {ESTree.CallExpression} */ const callExpression = ({ arguments: calleeArgs, callee, optional = false, ...other }) => { return { ...other, arguments: calleeArgs, callee, optional, type: types_1.AST_NODE_TYPES.CallExpression, toString: () => `${(0, node_1.node)(callee)}${optional ? '?.' : ''}(${calleeArgs .map(node_1.node) .join(', ')})`, }; }; exports.callExpression = callExpression; /** * __Super__ * * @example * * ``` * // note the whole expression is a `CallExpression` * // super is simply the callee / identifier * super() * ^^^^^ * ``` * * @returns {ESTree.Super} */ const superCallExpression = ({ ...other }) => { return { ...other, type: types_1.AST_NODE_TYPES.Super, toString: () => `super`, }; }; exports.superCallExpression = superCallExpression; const chainExpression = ({ expression, ...other }) => { return { ...other, expression, type: types_1.AST_NODE_TYPES.ChainExpression, toString: () => `${(0, node_1.node)(expression)}`, }; }; exports.chainExpression = chainExpression; /** * __BinaryExpression__ * * @example * ```ts * const x = 'left' + 'right' * ^^^^^^^^^^^^^^^^ * ``` */ const binaryExpression = ({ left, right, operator, ...other }) => { return { ...other, left, right, operator, type: types_1.AST_NODE_TYPES.BinaryExpression, toString: () => `${(0, node_1.node)(left)} ${operator} ${(0, node_1.node)(right)}`, }; }; exports.binaryExpression = binaryExpression; /** * __SequenceExpression__ * * @example * ```ts * const x = (4, 8) * ^^^^^^ * ``` */ const sequenceExpression = ({ expressions, ...other }) => { return { ...other, expressions, type: types_1.AST_NODE_TYPES.SequenceExpression, toString: () => `(${expressions.map(node_1.node).map(String).join(', ')})`, }; }; exports.sequenceExpression = sequenceExpression; /** * __ArrowFunctionExpression__ * * @example * ```js * const arrow = () => 42 * ⌃⌃⌃⌃⌃⌃⌃⌃ * ``` * @returns {ESTree.ArrowFunctionExpression} */ const arrowFunctionExpression = ({ async = false, generator = false, body, expression, params, ...other }) => { return { ...other, generator, async, expression, body, params, type: types_1.AST_NODE_TYPES.ArrowFunctionExpression, toString: () => `${async ? 'async ' : ''}(${params .map(node_1.node) .map(String) .join(', ')}) => ${(0, node_1.node)(body)}`, }; }; exports.arrowFunctionExpression = arrowFunctionExpression; /** * __TaggedTemplateExpression__ * * @example * ```ts * const style = css`color: red;` * ^^^^^^^^^^^ * ``` */ const taggedTemplateExpression = ({ quasi, tag, ...other }) => { return { ...other, quasi, tag, type: types_1.AST_NODE_TYPES.TaggedTemplateExpression, toString: () => `${(0, node_1.node)(tag)}${(0, node_1.node)(quasi)}`, }; }; exports.taggedTemplateExpression = taggedTemplateExpression; const functionExpression = ({ async = false, generator = false, body, params, id, ...other }) => { return { ...other, id, async, generator, body, params, type: types_1.AST_NODE_TYPES.FunctionExpression, toString: () => `${async ? 'async ' : ''}function ${id ? (0, node_1.node)(id) : ''}(${params .map(node_1.node) .join(', ')}) ${(0, node_1.node)(body)}`, }; }; exports.functionExpression = functionExpression; const blockStatement = ({ body, ...other }) => { return { ...other, body, type: types_1.AST_NODE_TYPES.BlockStatement, toString: () => `{${body.length ? constants_1.DEFAULT_WHITESPACE + body.map(node_1.node).map(String).join(constants_1.DEFAULT_WHITESPACE) + '\n' : ''}}`, }; }; exports.blockStatement = blockStatement; const returnStatement = ({ argument, ...other }) => { return { ...other, argument, type: types_1.AST_NODE_TYPES.ReturnStatement, toString: () => `return${argument ? argument.type === 'JSXElement' ? ` (${constants_1.DEFAULT_WHITESPACE}${(0, node_1.node)(argument)}${constants_1.DEFAULT_WHITESPACE})` : ` ${(0, node_1.node)(argument)}` : ''};`, }; }; exports.returnStatement = returnStatement; const throwStatement = ({ argument, ...other }) => { return { ...other, argument, type: types_1.AST_NODE_TYPES.ThrowStatement, toString: () => { if (!argument) { return `throw;`; } // `TSESTree.ThrowStatement.argument` is typed as // `Statement | TSAsExpression | null` in `@typescript-eslint/types@5.45` // which notably excludes JSX nodes, even though `espree` happily emits // `JSXElement`/`JSXFragment` here at runtime for `throw (<el />)`. Cast // the discriminant to the full `AST_NODE_TYPES` enum so the comparison // reflects the real parser output rather than the narrow declared type. const argumentType = argument.type; const needsParens = argumentType === types_1.AST_NODE_TYPES.JSXElement || argumentType === types_1.AST_NODE_TYPES.JSXFragment; return `throw${needsParens ? ` (${constants_1.DEFAULT_WHITESPACE}${(0, node_1.node)(argument)}${constants_1.DEFAULT_WHITESPACE})` : ` ${(0, node_1.node)(argument)}`};`; }, }; }; exports.throwStatement = throwStatement; /** * __UnaryExpression__ * * @example * * ```ts * const y = typeof x * ^^^^^^ * ++x * ^^ * ``` * * @returns {ESTree.UnaryExpression} */ const unaryExpression = ({ operator, argument, prefix, ...other }) => { return { ...other, operator, prefix, argument, type: types_1.AST_NODE_TYPES.UnaryExpression, toString: () => `${operator} ${(0, node_1.node)(argument)}`, }; }; exports.unaryExpression = unaryExpression; /** * __ThisExpression__ * * @example * * ```js * // In `this.self` 'this' is a ThisExpression. * this.self * ⌃⌃⌃⌃ * ``` * * @returns {ESTree.ThisExpression} */ const thisExpression = (node) => ({ ...node, type: types_1.AST_NODE_TYPES.ThisExpression, toString: () => `this`, }); exports.thisExpression = thisExpression; /** * __IfStatement__ * * @example * * ```ts * if (test) { * // consequant * } else { * // alternate * } * ⌃⌃⌃⌃^^^^^^^^ * ``` * * @returns {ESTree.IfStatement} */ const ifStatement = ({ test, alternate, consequent, ...other }) => ({ ...other, test, alternate, consequent, type: types_1.AST_NODE_TYPES.IfStatement, toString: () => `if (${(0, node_1.node)(test)}) ${(0, node_1.node)(consequent)} ${alternate ? `else ${(0, node_1.node)(alternate)}` : ''}`, }); exports.ifStatement = ifStatement; /** * __CatchClause__ * * @example * * ```ts * // always inside a try statement * catch (e) {} * ⌃⌃⌃⌃^^^^^^^^ * ``` * * @returns {ESTree.CatchClause} */ const catchClause = ({ body, param, ...other }) => ({ ...other, body, param, type: types_1.AST_NODE_TYPES.CatchClause, toString: () => `catch${param ? ` (${(0, node_1.node)(param)})` : ''} ${(0, node_1.node)(body)}`, }); exports.catchClause = catchClause; /** * __TryStatement__ * * @example * * ```ts * try { * // block * } catch(e) { // <--- handler * * } finally {} // <--- finalizer * ⌃⌃⌃⌃^^^^^^^^ * ``` * * @returns {ESTree.TryStatement} */ const tryStatement = ({ block, finalizer, handler, ...other }) => ({ ...other, block, finalizer, handler, type: types_1.AST_NODE_TYPES.TryStatement, toString: () => `try ${(0, node_1.node)(block)} ${handler ? (0, node_1.node)(handler) : ''} ${finalizer ? `finally ${(0, node_1.node)(finalizer)}` : ''}`, }); exports.tryStatement = tryStatement; /** * __WithStatement__ * * @example * * ```ts * with (Math) { * a = PI * r * r; * x = r * cos(PI); * y = r * sin(PI / 2); * } * ``` * * @returns {ESTree.WithStatement} */ const withStatement = ({ object, body, ...other }) => ({ ...other, type: types_1.AST_NODE_TYPES.WithStatement, object, body, toString: () => `with (${(0, node_1.node)(object)}) ${(0, node_1.node)(body)}`, }); exports.withStatement = withStatement; /** * __ImportExpression__ * * @example * * ```ts * import('some-path') * ⌃⌃⌃⌃^^^^^^^^^^^^^^^ * ``` * * @returns {ESTree.ImportExpression} */ const importExpression = ({ source, ...other }) => ({ ...other, type: types_1.AST_NODE_TYPES.ImportExpression, source, toString: () => `import(${(0, node_1.node)(source)})`, }); exports.importExpression = importExpression; /** * __ImportDefaultSpecifier__ * * @example * * ```ts * import Hello from 'world' * ^^^^^ * ``` * * @returns {ESTree.ImportDefaultSpecifier} */ const importDefaultSpecifier = ({ local, ...other }) => ({ ...other, local, type: types_1.AST_NODE_TYPES.ImportDefaultSpecifier, toString: () => local.name, }); exports.importDefaultSpecifier = importDefaultSpecifier; /** * __ExportNamedDeclaration__ * * @example * * ```ts * export { Hello } from 'world' * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * ``` * * @returns {ESTree.ExportNamedDeclaration} */ const exportNamedDeclaration = ({ declaration, specifiers, source, assertions = [], exportKind = 'value', ...other }) => { // `ExportNamedDeclaration` is a discriminated union with mutually-exclusive // `source`/`declaration`/`specifiers` invariants that depend on runtime // values. TypeScript can't narrow to one variant here, so fall back to a // structural cast — the runtime shape is always valid for exactly one // variant depending on the inputs the caller provided. return { ...other, declaration, specifiers, source, assertions, exportKind, type: types_1.AST_NODE_TYPES.ExportNamedDeclaration, toString: () => `export ${declaration ? (0, node_1.node)(declaration) : ''}${specifiers.length ? `{ ${specifiers.map(node_1.node).map(String).join(', ')} }` : ''}${source ? `from ${(0, node_1.node)(source)}` : ''}`, }; }; exports.exportNamedDeclaration = exportNamedDeclaration; /** * __ExportDefaultDeclaration__ * * @example * * ```ts * export default HelloWorld * ^^^^^^^^^^^^^^^^^^^^^^^^^ * ``` * * @returns {ESTree.ExportDefaultDeclaration} */ const exportDefaultDeclaration = ({ declaration, ...other }) => { return { ...other, type: types_1.AST_NODE_TYPES.ExportDefaultDeclaration, declaration, toString: () => `export default ${(0, node_1.node)(declaration)}`, }; }; exports.exportDefaultDeclaration = exportDefaultDeclaration; /** * __ExportAllDeclaration__ * * @example * * ```ts * export * from 'world' * ^^^^^^^^^^^^^^^^^^^^^^^^^ * ``` * ```ts * export * as Hello from 'world' * ^^^^^^^^^^^^^^^^^^^^^^^^^ * ``` * * @returns {ESTree.ExportAllDeclaration} */ /** * `exported` is made optional here (defaults to `null`) via a narrowed input * type so callers don't have to write `exported: null` for the common * `export * from '…'` case. It stays required on the underlying TSESTree type * (and on `ExportSpecifier`, which is why it's deliberately excluded from the * shared `DefaultableFields` list). */ const exportAllDeclaration = ({ source, exported = null, assertions = [], exportKind = 'value', ...other }) => { return { ...other, type: types_1.AST_NODE_TYPES.ExportAllDeclaration, source, exported, assertions, exportKind, toString: () => `export * ${exported ? `as ${(0, node_1.node)(exported)} ` : ''}from ${(0, node_1.node)(source)}`, }; }; exports.exportAllDeclaration = exportAllDeclaration; const exportSpecifier = ({ exported, local, exportKind = 'value', ...other }) => { return { ...other, exported, local, exportKind, type: types_1.AST_NODE_TYPES.ExportSpecifier, toString: () => local.name !== exported.name ? `${(0, node_1.node)(exported)} as ${(0, node_1.node)(local)}` : String((0, node_1.node)(local)), }; }; exports.exportSpecifier = exportSpecifier; const importSpecifier = ({ imported, local, importKind = 'value', ...other }) => ({ ...other, type: types_1.AST_NODE_TYPES.ImportSpecifier, imported, importKind, local, toString: () => `${importKind === 'type' ? 'type ' : ''}${local.name === imported.name ? imported.name : `${imported.name} as ${local.name}`}`, }); exports.importSpecifier = importSpecifier; /** * __YieldExpression__ * * @example * * ```ts * const thing = yield someYieldExpression * ⌃⌃⌃⌃⌃⌃⌃⌃⌃⌃⌃⌃^^^^^^^^^^^^^ * ``` * * @returns {ESTree.YieldExpression} */ const yieldExpression = ({ argument, delegate, ...other }) => { return { ...other, argument, delegate, type: types_1.AST_NODE_TYPES.YieldExpression, toString: () => `yield ${argument ? (0, node_1.node)(argument) : ''}`, }; }; exports.yieldExpression = yieldExpression; const arrayExpression = ({ elements, ...other }) => { return { ...other, type: types_1.AST_NODE_TYPES.ArrayExpression, elements, toString: () => `[${elements .filter((n) => Boolean(n)) .map(node_1.node) .map(String) .join(', ')}]`, }; }; exports.arrayExpression = arrayExpression; const arrayPattern = ({ elements, ...other }) => { return { ...other, type: types_1.AST_NODE_TYPES.ArrayPattern, elements, toString: () => `[${elements .filter((node) => node !== null) .map(node_1.node) .map(String) .join(', ')}]`, }; }; exports.arrayPattern = arrayPattern; const updateExpression = ({ argument, operator, prefix, ...other }) => { return { ...other, argument, operator, prefix, type: types_1.AST_NODE_TYPES.UpdateExpression, toString: () => `${prefix ? `${operator}${(0, node_1.node)(argument)}` : `${(0, node_1.node)(argument)}${operator}`}`, }; }; exports.updateExpression = updateExpression; const expressionStatement = ({ expression, ...other }) => ({ ...other, expression, type: types_1.AST_NODE_TYPES.ExpressionStatement, toString: () => String((0, node_1.node)(expression)), }); exports.expressionStatement = expressionStatement; /** * __NewExpression__ * * @example * ```ts * new SomeThing() * ^^^^^^^^^^^^^^^ * ``` */ const newExpression = ({ callee, arguments: argumentsParam, ...other }) => ({ ...other, callee, arguments: argumentsParam, type: types_1.AST_NODE_TYPES.NewExpression, toString: () => `new ${(0, node_1.node)(callee)}(${argumentsParam.map(node_1.node).join(', ')})`, }); exports.newExpression = newExpression; const property = ({ kind = 'init', key, value, method = false, computed = false, shorthand = false, ...other }) => { // `Property` is a union discriminated on `computed: boolean`. The runtime // value of `computed` chooses the variant, but TS can't narrow a runtime // boolean so structurally cast the return. return { ...other, key, kind, value, method, shorthand, computed, type: types_1.AST_NODE_TYPES.Property, toString: () => `${kind === 'init' ? '' : kind + ' '}${computed ? '[' : ''}${(0, node_1.node)(key)}${computed ? ']' : ''}${kind !== 'init' ? '' : ': '}${kind !== 'init' && (0, utils_1.isNodeOfType)(value, types_1.AST_NODE_TYPES.FunctionExpression) ? (0, exports.methodOrPropertyFn)(value) : (0, node_1.node)(value)}`, }; }; exports.property = property; /** * __ObjectPattern__ * * @example * ```ts * function App({ a }) {} * ^^^^^ * ``` * @returns */ const objectPattern = ({ properties, ...other }) => { return { ...other, properties, type: types_1.AST_NODE_TYPES.ObjectPattern, toString: () => `{${properties.map(node_1.node).map(String).join(', ')}}`, }; }; exports.objectPattern = objectPattern; /** * __SpreadElement__ * * @example * ```ts * const obj = { * ...spread * ^^^^^^^^^ * } * ``` * * @returns {ESTree.SpreadElement} */ const spreadElement = ({ argument, ...other }) => { return { ...other, argument, type: types_1.AST_NODE_TYPES.SpreadElement, toString: () => `...${(0, node_1.node)(argument)}`, }; }; exports.spreadElement = spreadElement; /** * __RestElement__ * * @example * ```ts * const [a, ...b] = c * ^^^^ * ``` * * * @example * ```ts * const { a, ...b } = c * ^^^^ * ``` * * @returns {ESTree.RestElement} */ const restElement = ({ argument, ...other }) => { return { ...other, argument, type: types_1.AST_NODE_TYPES.RestElement, toString: () => `...${(0, node_1.node)(argument)}`, }; }; exports.restElement = restElement; /** * __ObjectExpression__ * @example * ```ts * const x = { * key: value, * get x() { return 1 }, * } * ^^^^^^^^^^^^ * ``` */ const objectExpression = ({ properties, ...other }) => { return { ...other, properties, type: types_1.AST_NODE_TYPES.ObjectExpression, toString: () => `{${constants_1.DEFAULT_WHITESPACE}${properties .map(node_1.node) .map(String) .join(`,${constants_1.DEFAULT_WHITESPACE}`)}\n}`, }; }; exports.objectExpression = objectExpression; const emptyStatement = ({ ...other }) => ({ ...other, type: types_1.AST_NODE_TYPES.EmptyStatement, toString: () => `;`, }); exports.emptyStatement = emptyStatement; const memberExpression = ({ object, property, computed = false, optional = false, ...other }) => // `MemberExpression` is a union discriminated on `computed` — see the note // on `property` above for why a structural cast is needed here. ({ ...other, type: types_1.AST_NODE_TYPES.MemberExpression, computed, optional, object, property, toString: () => { const translatedNode = (0, node_1.node)(property); return `${(0, node_1.node)(object)}${computed ? `[${translatedNode}]` : `.${translatedNode}`}`; }, }); exports.memberExpression = memberExpression; const logicalExpression = ({ left, right, operator, ...other }) => { return { ...other, left, right, operator, type: types_1.AST_NODE_TYPES.LogicalExpression, toString: () => `${(0, node_1.node)(left)} ${operator} ${(0, node_1.node)(right)}`, }; }; exports.logicalExpression = logicalExpression; const variableDeclarator = ({ id, init, ...other }) => { return { ...other, id, init, type: types_1.AST_NODE_TYPES.VariableDeclarator, toString: () => `${(0, node_1.node)(id)}${init ? ` = ${(0, node_1.node)(init)}` : ''}`, }; }; exports.variableDeclarator = variableDeclarator; const variableDeclaration = ({ declarations, kind, ...other }) => { return { ...other, declarations, kind, type: types_1.AST_NODE_TYPES.VariableDeclaration, toString: () => `${kind ? `${kind} ` : ''}${declarations .map(exports.variableDeclarator) .map(String) .join()}`, }; }; exports.variableDeclaration = variableDeclaration; const importNamespaceSpecifier = ({ local, ...other }) => { return { ...other, type: types_1.AST_NODE_TYPES.ImportNamespaceSpecifier, local, toString: () => `* as ${local.name}`, }; }; exports.importNamespaceSpecifier = importNamespaceSpecifier; const templateElement = ({ value, ...other }) => { return { ...other, value, type: types_1.AST_NODE_TYPES.TemplateElement, toString: () => `${value.raw}`, }; }; exports.templateElement = templateElement; const importDeclaration = ({ specifiers, source, importKind = 'value', assertions = [], ...other }) => ({ ...other, type: types_1.AST_NODE_TYPES.ImportDeclaration, specifiers, source, importKind, assertions, toString: () => { if (!specifiers.length) { return `import '${source.value}'`; } const defaultSpecifier = specifiers.find((spec) => (0, utils_1.isNodeOfType)(spec, types_1.AST_NODE_TYPES.ImportDefaultSpecifier)); const otherSpecifiers = specifiers.filter((spec) => (0, utils_1.isNodeOfType)(spec, types_1.AST_NODE_TYPES.ImportSpecifier)); const nameSpaceSpecifier = specifiers.find((node) => (0, utils_1.isNodeOfType)(node, types_1.AST_NODE_TYPES.ImportNamespaceSpecifier)); const seperator = otherSpecifiers.length > 4 ? `,${constants_1.DEFAULT_WHITESPACE}` : ', '; const leadOrEndSpecifier = otherSpecifiers.length > 4 ? '\n' : ' '; return `import ${importKind === 'type' ? 'type ' : ''}${defaultSpecifier ? defaultSpecifier.local.name : ''}${otherSpecifiers.length ? defaultSpecifier ? `, {${leadOrEndSpecifier}${otherSpecifiers .map(exports.importSpecifier) .join(seperator)}${leadOrEndSpecifier}}` : `{${leadOrEndSpecifier}${otherSpecifiers .map(exports.importSpecifier) .join(seperator)}${leadOrEndSpecifier}}` : ''}${(otherSpecifiers.length || defaultSpecifier) && nameSpaceSpecifier ? ', ' : ''}${nameSpaceSpecifier ? (0, exports.importNamespaceSpecifier)(nameSpaceSpecifier) : ''} from '${source.value}'`; }, }); exports.importDeclaration = importDeclaration; const bigIntLiteral = ({ value, raw, bigint, ...other }) => ({ ...other, value, raw, bigint, type: types_1.AST_NODE_TYPES.Literal, toString: () => raw || String(value), }); exports.bigIntLiteral = bigIntLiteral; const regExpLiteral = ({ value, raw, regex, ...other }) => ({ ...other, value, raw, regex, type: types_1.AST_NODE_TYPES.Literal, toString: () => raw || String(value), }); exports.regExpLiteral = regExpLiteral; const stringLiteral = ({ raw, ...node }) => { return { ...node, raw, type: types_1.AST_NODE_TYPES.Literal, toString: () => raw, }; }; exports.stringLiteral = stringLiteral; const booleanLiteral = (n) => { if (typeof n === 'boolean') { return { value: n, raw: String(n), type: types_1.AST_NODE_TYPES.Literal, toString: () => String(n), }; } else { return { ...n, type: types_1.AST_NODE_TYPES.Literal, toString: () => n.raw || String(n.value), }; } }; exports.booleanLiteral = booleanLiteral; const numberLiteral = (n) => { if (typeof n === 'number') { return { value: n, raw: String(n), type: types_1.AST_NODE_TYPES.Literal, toString: () => String(n), }; } return { ...n, type: types_1.AST_NODE_TYPES.Literal, toString: () => n.raw || String(n.value), }; }; exports.numberLiteral = numberLiteral; const nullLiteral = (n) => { if (n === null) { return { raw: 'null', value: null, type: types_1.AST_NODE_TYPES.Literal, toString: () => 'null', }; } return { ...n, type: types_1.AST_NODE_TYPES.Literal, toString: () => 'null', }; }; exports.nullLiteral = nullLiteral; function literal(n) { if (typeof n === 'string') { return (0, exports.stringLiteral)({ value: n, raw: `'${n}'` }); } if (typeof n === 'boolean') { return (0, exports.booleanLiteral)(n); } if (typeof n === 'number') { return (0, exports.numberLiteral)(n); } if (n === null) { return (0, exports.nullLiteral)(n); } // At this point `n` is `WithoutType<Literal>` — a discriminated union across // the concrete literal variants. Dispatch by the discriminator field. The // `in` narrowing loses `WithoutType<…>` through the distributive intersection // produced by `Loose<…>`, so re-assert the concrete shape. if ('bigint' in n) { return (0, exports.bigIntLiteral)(n); } if ('regex' in n) { return (0, exports.regExpLiteral)(n); } if (typeof n.value === 'string') { return (0, exports.stringLiteral)(n); } if (typeof n.value === 'number') { return (0, exports.numberLiteral)(n); } if (typeof n.value === 'boolean') { return (0, exports.booleanLiteral)(n); } if (n.value === null) { return (0, exports.nullLiteral)(n); } // Unreachable in well-typed callers — fall back to stringifying as a string // literal so this degrades gracefully at runtime. return (0, exports.stringLiteral)(n); } exports.literal = literal; const identifier = (param) => { const name = typeof param === 'string' ? param : param.name; const other = typeof param === 'object' ? param : {}; return { ...other, type: types_1.AST_NODE_TYPES.Identifier, name, toString: () => name, }; }; exports.identifier = identifier; const doWhileStatement = ({ test, body, ...other }) => ({ ...other, test, body, type: types_1.AST_NODE_TYPES.DoWhileStatement, toString() { return `do ${(0, node_1.node)(body)} while (${(0, node_1.node)(test)})`; }, }); exports.doWhileStatement = doWhileStatement; const whileStatement = ({ test, body, ...other }) => ({ ...other, test, body, type: types_1.AST_NODE_TYPES.WhileStatement, toString() { return `while (${(0, node_1.node)(test)}) ${(0, node_1.node)(body)}`; }, }); exports.whileStatement = whileStatement; const switchCase = ({ consequent, test, ...other }) => { return { ...other, consequent, test, type: types_1.AST_NODE_TYPES.SwitchCase, toString: () => `${!test ? 'default' : `case ${(0, node_1.node)(test)}`}: ${consequent .map(node_1.node) .map(String) .join('; ')};`, }; }; exports.switchCase = switchCase; const switchStatement = ({ cases, discriminant, ...other }) => ({ ...other, toString: () => `switch (${(0, node_1.node)(discriminant)}) { ${cases.map(exports.switchCase).join(constants_1.DEFAULT_WHITESPACE)}\n}`, cases, discriminant, type: types_1.AST_NODE_TYPES.SwitchStatement, }); exports.switchStatement = switchStatement; const templateLiteral = ({ expressions, quasis, ...other }) => { if (quasis.length < expressions.length) { throw new Error('invariant: quasis should always outnumber expressions in a TemplateLiteral'); } return { ...other, type: types_1.AST_NODE_TYPES.TemplateLiteral, quasis, expressions, toString: () => { const range = Array.from({ length: quasis.length + expressions.length }); return ('`' + range .map((_, index) => { if (index % 2 === 0) { return (0, node_1.node)(quasis[Math.floor(index / 2)]); } else { return `\${${(0, node_1.node)(expressions[Math.floor(index / 2)])}}`; } }) .map(String) .join('') + '`'); }, }; }; exports.templateLiteral = templateLiteral; const forStatement = ({ body, init, test, update, ...other }) => ({ ...other, init, body, test, update, type: types_1.AST_NODE_TYPES.ForStatement, toString: () => `for (${init ? (0, node_1.node)(init) : ''};${test ? (0, node_1.node)(test) : ''};${update ? (0, node_1.node)(update) : ''}) ${(0, node_1.node)(body)}`, }); exports.forStatement = forStatement; const forInStatement = ({ body, left, right, ...other }) => ({ ...other, body, left, right, type: types_1.AST_NODE_TYPES.ForInStatement, toString: () => `for (${(0, node_1.node)(left)} in ${(0, node_1.node)(right)}) ${(0, node_1.node)(body)}`, }); exports.forInStatement = forInStatement; const forOfStatement = ({ body, left, right, ...other }) => ({ ...other, body, left, right, type: types_1.AST_NODE_TYPES.ForOfStatement, toString: () => `for (${(0, node_1.node)(left)} of ${(0, node_1.node)(right)}) ${(0, node_1.node)(body)}`, }); exports.forOfStatement = forOfStatement; const continueStatement = ({ label, ...other }) => ({ ...other, toString: () => `continue${label ? ` ${(0, node_1.node)(label)}` : ''}`, label, type: types_1.AST_NODE_TYPES.ContinueStatement, }); exports.continueStatement = continueStatement; const breakStatement = ({ label, ...other }) => ({ ...other, toString: () => `break${label ? ` ${(0, node_1.node)(label)}` : ''}`, label, type: types_1.AST_NODE_TYPES.BreakStatement, }); exports.breakStatement = breakStatement; const debuggerStatement = (node) => ({ ...node, toString: () => `debugger`, type: types_1.AST_NODE_TYPES.DebuggerStatement, }); exports.debuggerStatement = debuggerStatement; const conditionalExpression = ({ consequent, alternate, test, ...other }) => ({ ...other, toString: () => `${(0, node_1.node)(test)} ? ${(0, node_1.node)(consequent)} : ${(0, node_1.node)(alternate)}`, consequent, alternate, test, type: types_1.AST_NODE_TYPES.ConditionalExpression, }); exports.conditionalExpression = conditionalExpression; const assignmentExpression = ({ left, right, operator, ...other }) => { return { ...other, type: types_1.AST_NODE_TYPES.AssignmentExpression, left, right, operator, toString: () => `${(0, node_1.node)(left)}${operator}${(0, node_1.node)(right)}`, }; }; exports.assignmentExpression = assignmentExpression; const awaitExpression = ({ argument, ...other }) => ({ ...other, toString: () => `await ${(0, node_1.node)(argument)}`, argument, type: types_1.AST_NODE_TYPES.AwaitExpression, }); exports.awaitExpression = awaitExpression; /** * __StaticBlock__ * * @example * ```ts * class A { * // only applicable inside a class * static { } * ^^^^^^^^^^ * } * ``` */ const staticBlock = ({ body, ...other }) => { return { ...other, body, type: types_1.AST_NODE_TYPES.StaticBlock, toString: () => `static {\n${body.map(node_1.node).map(String).join(constants_1.DEFAULT_WHITESPACE)}\n}`, }; }; exports.staticBlock = staticBlock; const functionDeclaration = ({ body, async = false, id, generator = false, params, ...other }) => ({ ...other, type: types_1.AST_NODE_TYPES.FunctionDeclaration, body, async, id, generator, params, toString: () => `${async ? 'async ' : ''}function ${id ? (0, node_1.node)(id) : ''}(${params .map(node_1.node) .map(String) .join(', ')}) ${(0, node_1.node)(body)}`, }); exports.functionDeclaration = functionDeclaration; const methodOrPropertyFn = ({ params, body, }) => { return `(${params.map(node_1.node).join(', ')}) ${body ? (0, node_1.node)(body) : `{}`}`; }; exports.methodOrPropertyFn = methodOrPropertyFn; const methodDefinition = ({ computed, key, kind, value, ...other }) => { // `MethodDefinition` is a union discriminated on `computed: boolean` — TS // can't narrow a runtime boolean to one variant, so cast the result. return { ...other, computed, key, kind, value, type: types_1.AST_NODE_TYPES.MethodDefinition, toString: () => `${computed ? `[${(0, node_1.node)(key)}]` : (0, node_1.node)(key)} ${(0, exports.methodOrPropertyFn)(value)}`, }; }; exports.methodDefinition = methodDefinition; const propertyDefinition = ({ computed, key, static: staticKeyWord, value, ...other }) => { // `PropertyDefinition` is a union discriminated on `computed: boolean` — // cast the structural return to satisfy the union. return { ...other, computed, key, static: staticKeyWord, value, type: types_1.AST_NODE_TYPES.PropertyDefinition, toString: () => `UNIMPLEMENTED`, }; }; exports.propertyDefinition = propertyDefinition; const classBody = ({ body, ...other }) => { return { ...other, type: types_1.AST_NODE_TYPES.ClassBody, body, toString: () => body.length ? `${constants_1.DEFAULT_WHITESPACE}${body .map(node_1.node) .map(String) .join(constants_1.DEFAULT_WHITESPACE)}\n` : '', }; }; exports.classBody = classBody; const classDeclaration = ({ superClass, id, body, ...other }) => { return { ...other, type: types_1.AST_NODE_TYPES.ClassDeclaration, superClass, body, id, toString: () => `class${id ? ` ${(0, node_1.node)(id)}` : ''}${superClass ? ` extends ${(0, node_1.node)(superClass)}` : ''} {${(0, node_1.node)(body)}}`, }; }; exports.classDeclaration = classDeclaration; const classExpression = ({ superClass, id, body, ...other }) => { return { ...other, type: types_1.AST_NODE_TYPES.ClassExpression, superClass, body, id, toString: () => String((0, exports.classDeclaration)({ superClass, id: id || null, body, ...other })), }; }; exports.classExpression = classExpression; const program = ({ body, ...other }) => ({ ...other, type: types_1.AST_NODE_TYPES.Program, toString: () => body.map(node_1.node).map(String).join('\n'), body, }); exports.program = program; const privateIdentifier = ({ name, ...other }) => ({ ...other, type: types_1.AST_NODE_TYPES.PrivateIdentifier, name, toString: () => `#${name}`, }); exports.privateIdentifier = privateIdentifier; const decorator = ({ expression, ...other }) => ({ expression, ...other, type: types_1.AST_NODE_TYPES.Decorator, toString: () => `@${(0, node_1.node)(expression)}`, }); exports.decorator = decorator;