UNPKG

@typescript-eslint/typescript-estree

Version:

A parser that converts TypeScript source code into an ESTree compatible form

1,010 lines • 138 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.Converter = void 0; exports.convertError = convertError; // There's lots of funny stuff due to the typing of ts.Node /* eslint-disable @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-unnecessary-condition, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access */ const ts = __importStar(require("typescript")); const getModifiers_1 = require("./getModifiers"); const node_utils_1 = require("./node-utils"); const ts_estree_1 = require("./ts-estree"); const SyntaxKind = ts.SyntaxKind; /** * Extends and formats a given error object * @param error the error object * @returns converted error object */ function convertError(error) { return (0, node_utils_1.createError)(('message' in error && error.message) || error.messageText, error.file, error.start); } class Converter { allowPattern = false; ast; esTreeNodeToTSNodeMap = new WeakMap(); options; tsNodeToESTreeNodeMap = new WeakMap(); /** * Converts a TypeScript node into an ESTree node * @param ast the full TypeScript AST * @param options additional options for the conversion * @returns the converted ESTreeNode */ constructor(ast, options) { this.ast = ast; this.options = { ...options }; } #checkForStatementDeclaration(initializer, kind) { const loop = kind === ts.SyntaxKind.ForInStatement ? 'for...in' : 'for...of'; if (ts.isVariableDeclarationList(initializer)) { if (initializer.declarations.length !== 1) { this.#throwError(initializer, `Only a single variable declaration is allowed in a '${loop}' statement.`); } const declaration = initializer.declarations[0]; if (declaration.initializer) { this.#throwError(declaration, `The variable declaration of a '${loop}' statement cannot have an initializer.`); } else if (declaration.type) { this.#throwError(declaration, `The variable declaration of a '${loop}' statement cannot have a type annotation.`); } if (kind === ts.SyntaxKind.ForInStatement && initializer.flags & ts.NodeFlags.Using) { this.#throwError(initializer, "The left-hand side of a 'for...in' statement cannot be a 'using' declaration."); } } else if (!(0, node_utils_1.isValidAssignmentTarget)(initializer) && initializer.kind !== ts.SyntaxKind.ObjectLiteralExpression && initializer.kind !== ts.SyntaxKind.ArrayLiteralExpression) { this.#throwError(initializer, `The left-hand side of a '${loop}' statement must be a variable or a property access.`); } } #checkModifiers(node) { if (this.options.allowInvalidAST) { return; } // typescript<5.0.0 if ((0, node_utils_1.nodeHasIllegalDecorators)(node)) { this.#throwError(node.illegalDecorators[0], 'Decorators are not valid here.'); } for (const decorator of (0, getModifiers_1.getDecorators)(node, /* includeIllegalDecorators */ true) ?? []) { // `checkGrammarModifiers` function in typescript if (!(0, node_utils_1.nodeCanBeDecorated)(node)) { if (ts.isMethodDeclaration(node) && !(0, node_utils_1.nodeIsPresent)(node.body)) { this.#throwError(decorator, 'A decorator can only decorate a method implementation, not an overload.'); } else { this.#throwError(decorator, 'Decorators are not valid here.'); } } } for (const modifier of (0, getModifiers_1.getModifiers)(node, /* includeIllegalModifiers */ true) ?? []) { if (modifier.kind !== SyntaxKind.ReadonlyKeyword) { if (node.kind === SyntaxKind.PropertySignature || node.kind === SyntaxKind.MethodSignature) { this.#throwError(modifier, `'${ts.tokenToString(modifier.kind)}' modifier cannot appear on a type member`); } if (node.kind === SyntaxKind.IndexSignature && (modifier.kind !== SyntaxKind.StaticKeyword || !ts.isClassLike(node.parent))) { this.#throwError(modifier, `'${ts.tokenToString(modifier.kind)}' modifier cannot appear on an index signature`); } } if (modifier.kind !== SyntaxKind.InKeyword && modifier.kind !== SyntaxKind.OutKeyword && modifier.kind !== SyntaxKind.ConstKeyword && node.kind === SyntaxKind.TypeParameter) { this.#throwError(modifier, `'${ts.tokenToString(modifier.kind)}' modifier cannot appear on a type parameter`); } if ((modifier.kind === SyntaxKind.InKeyword || modifier.kind === SyntaxKind.OutKeyword) && (node.kind !== SyntaxKind.TypeParameter || !(ts.isInterfaceDeclaration(node.parent) || ts.isClassLike(node.parent) || ts.isTypeAliasDeclaration(node.parent)))) { this.#throwError(modifier, `'${ts.tokenToString(modifier.kind)}' modifier can only appear on a type parameter of a class, interface or type alias`); } if (modifier.kind === SyntaxKind.ReadonlyKeyword && node.kind !== SyntaxKind.PropertyDeclaration && node.kind !== SyntaxKind.PropertySignature && node.kind !== SyntaxKind.IndexSignature && node.kind !== SyntaxKind.Parameter) { this.#throwError(modifier, "'readonly' modifier can only appear on a property declaration or index signature."); } if (modifier.kind === SyntaxKind.DeclareKeyword && ts.isClassLike(node.parent) && !ts.isPropertyDeclaration(node)) { this.#throwError(modifier, `'${ts.tokenToString(modifier.kind)}' modifier cannot appear on class elements of this kind.`); } if (modifier.kind === SyntaxKind.DeclareKeyword && ts.isVariableStatement(node)) { const declarationKind = (0, node_utils_1.getDeclarationKind)(node.declarationList); if (declarationKind === 'using' || declarationKind === 'await using') { this.#throwError(modifier, `'declare' modifier cannot appear on a '${declarationKind}' declaration.`); } } if (modifier.kind === SyntaxKind.AbstractKeyword && node.kind !== SyntaxKind.ClassDeclaration && node.kind !== SyntaxKind.ConstructorType && node.kind !== SyntaxKind.MethodDeclaration && node.kind !== SyntaxKind.PropertyDeclaration && node.kind !== SyntaxKind.GetAccessor && node.kind !== SyntaxKind.SetAccessor) { this.#throwError(modifier, `'${ts.tokenToString(modifier.kind)}' modifier can only appear on a class, method, or property declaration.`); } if ((modifier.kind === SyntaxKind.StaticKeyword || modifier.kind === SyntaxKind.PublicKeyword || modifier.kind === SyntaxKind.ProtectedKeyword || modifier.kind === SyntaxKind.PrivateKeyword) && (node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.SourceFile)) { this.#throwError(modifier, `'${ts.tokenToString(modifier.kind)}' modifier cannot appear on a module or namespace element.`); } if (modifier.kind === SyntaxKind.AccessorKeyword && node.kind !== SyntaxKind.PropertyDeclaration) { this.#throwError(modifier, "'accessor' modifier can only appear on a property declaration."); } // `checkGrammarAsyncModifier` function in `typescript` if (modifier.kind === SyntaxKind.AsyncKeyword && node.kind !== SyntaxKind.MethodDeclaration && node.kind !== SyntaxKind.FunctionDeclaration && node.kind !== SyntaxKind.FunctionExpression && node.kind !== SyntaxKind.ArrowFunction) { this.#throwError(modifier, "'async' modifier cannot be used here."); } // `checkGrammarModifiers` function in `typescript` if (node.kind === SyntaxKind.Parameter && (modifier.kind === SyntaxKind.StaticKeyword || modifier.kind === SyntaxKind.ExportKeyword || modifier.kind === SyntaxKind.DeclareKeyword || modifier.kind === SyntaxKind.AsyncKeyword)) { this.#throwError(modifier, `'${ts.tokenToString(modifier.kind)}' modifier cannot appear on a parameter.`); } // `checkGrammarModifiers` function in `typescript` if (modifier.kind === SyntaxKind.PublicKeyword || modifier.kind === SyntaxKind.ProtectedKeyword || modifier.kind === SyntaxKind.PrivateKeyword) { for (const anotherModifier of (0, getModifiers_1.getModifiers)(node) ?? []) { if (anotherModifier !== modifier && (anotherModifier.kind === SyntaxKind.PublicKeyword || anotherModifier.kind === SyntaxKind.ProtectedKeyword || anotherModifier.kind === SyntaxKind.PrivateKeyword)) { this.#throwError(anotherModifier, `Accessibility modifier already seen.`); } } } // `checkParameter` function in `typescript` if (node.kind === SyntaxKind.Parameter && // In `typescript` package, it's `ts.hasSyntacticModifier(node, ts.ModifierFlags.ParameterPropertyModifier)` // https://github.com/typescript-eslint/typescript-eslint/pull/6615#discussion_r1136489935 (modifier.kind === SyntaxKind.PublicKeyword || modifier.kind === SyntaxKind.PrivateKeyword || modifier.kind === SyntaxKind.ProtectedKeyword || modifier.kind === SyntaxKind.ReadonlyKeyword || modifier.kind === SyntaxKind.OverrideKeyword)) { const func = (0, node_utils_1.getContainingFunction)(node); if (!(func.kind === SyntaxKind.Constructor && (0, node_utils_1.nodeIsPresent)(func.body))) { this.#throwError(modifier, 'A parameter property is only allowed in a constructor implementation.'); } } } } #throwError(node, message) { let start; let end; if (typeof node === 'number') { start = end = node; } else { start = node.getStart(this.ast); end = node.getEnd(); } throw (0, node_utils_1.createError)(message, this.ast, start, end); } #throwUnlessAllowInvalidAST(node, message) { if (!this.options.allowInvalidAST) { this.#throwError(node, message); } } /** * Creates a getter for a property under aliasKey that returns the value under * valueKey. If suppressDeprecatedPropertyWarnings is not enabled, the * getter also console warns about the deprecation. * * @see https://github.com/typescript-eslint/typescript-eslint/issues/6469 */ #withDeprecatedAliasGetter(node, aliasKey, valueKey, suppressWarnings = false) { let warned = suppressWarnings; Object.defineProperty(node, aliasKey, { configurable: true, get: this.options.suppressDeprecatedPropertyWarnings ? () => node[valueKey] : () => { if (!warned) { process.emitWarning(`The '${aliasKey}' property is deprecated on ${node.type} nodes. Use '${valueKey}' instead. See https://typescript-eslint.io/troubleshooting/faqs/general#the-key-property-is-deprecated-on-type-nodes-use-key-instead-warnings.`, 'DeprecationWarning'); warned = true; } return node[valueKey]; }, set(value) { Object.defineProperty(node, aliasKey, { enumerable: true, value, writable: true, }); }, }); return node; } #withDeprecatedGetter(node, deprecatedKey, preferredKey, value) { let warned = false; Object.defineProperty(node, deprecatedKey, { configurable: true, get: this.options.suppressDeprecatedPropertyWarnings ? () => value : () => { if (!warned) { process.emitWarning(`The '${deprecatedKey}' property is deprecated on ${node.type} nodes. Use ${preferredKey} instead. See https://typescript-eslint.io/troubleshooting/faqs/general#the-key-property-is-deprecated-on-type-nodes-use-key-instead-warnings.`, 'DeprecationWarning'); warned = true; } return value; }, set(value) { Object.defineProperty(node, deprecatedKey, { enumerable: true, value, writable: true, }); }, }); return node; } assertModuleSpecifier(node, allowNull) { if (!allowNull && node.moduleSpecifier == null) { this.#throwUnlessAllowInvalidAST(node, 'Module specifier must be a string literal.'); } if (node.moduleSpecifier && node.moduleSpecifier?.kind !== SyntaxKind.StringLiteral) { this.#throwUnlessAllowInvalidAST(node.moduleSpecifier, 'Module specifier must be a string literal.'); } } convertBindingNameWithTypeAnnotation(name, tsType, parent) { const id = this.convertPattern(name); if (tsType) { id.typeAnnotation = this.convertTypeAnnotation(tsType, parent); this.fixParentLocation(id, id.typeAnnotation.range); } return id; } /** * Coverts body Nodes and add a directive field to StringLiterals * @param nodes of ts.Node * @param parent parentNode * @returns Array of body statements */ convertBodyExpressions(nodes, parent) { let allowDirectives = (0, node_utils_1.canContainDirective)(parent); return (nodes .map(statement => { const child = this.convertChild(statement); if (allowDirectives) { if (child?.expression && ts.isExpressionStatement(statement) && ts.isStringLiteral(statement.expression)) { const raw = child.expression.raw; child.directive = raw.slice(1, -1); return child; // child can be null, but it's filtered below } allowDirectives = false; } return child; // child can be null, but it's filtered below }) // filter out unknown nodes for now .filter(statement => statement)); } convertChainExpression(node, tsNode) { const { child, isOptional } = (() => { if (node.type === ts_estree_1.AST_NODE_TYPES.MemberExpression) { return { child: node.object, isOptional: node.optional }; } if (node.type === ts_estree_1.AST_NODE_TYPES.CallExpression) { return { child: node.callee, isOptional: node.optional }; } return { child: node.expression, isOptional: false }; })(); const isChildUnwrappable = (0, node_utils_1.isChildUnwrappableOptionalChain)(tsNode, child); if (!isChildUnwrappable && !isOptional) { return node; } if (isChildUnwrappable && (0, node_utils_1.isChainExpression)(child)) { // unwrap the chain expression child const newChild = child.expression; if (node.type === ts_estree_1.AST_NODE_TYPES.MemberExpression) { node.object = newChild; } else if (node.type === ts_estree_1.AST_NODE_TYPES.CallExpression) { node.callee = newChild; } else { node.expression = newChild; } } return this.createNode(tsNode, { type: ts_estree_1.AST_NODE_TYPES.ChainExpression, expression: node, }); } /** * Converts a TypeScript node into an ESTree node. * @param child the child ts.Node * @param parent parentNode * @returns the converted ESTree node */ convertChild(child, parent) { return this.converter(child, parent, false); } /** * Converts a TypeScript node into an ESTree node. * @param child the child ts.Node * @param parent parentNode * @returns the converted ESTree node */ convertPattern(child, parent) { return this.converter(child, parent, true); } /** * Converts a child into a type annotation. This creates an intermediary * TypeAnnotation node to match what Flow does. * @param child The TypeScript AST node to convert. * @param parent parentNode * @returns The type annotation node. */ convertTypeAnnotation(child, parent) { // in FunctionType and ConstructorType typeAnnotation has 2 characters `=>` and in other places is just colon const offset = parent?.kind === SyntaxKind.FunctionType || parent?.kind === SyntaxKind.ConstructorType ? 2 : 1; const annotationStartCol = child.getFullStart() - offset; const range = [annotationStartCol, child.end]; const loc = (0, node_utils_1.getLocFor)(range, this.ast); return { type: ts_estree_1.AST_NODE_TYPES.TSTypeAnnotation, loc, range, typeAnnotation: this.convertChild(child), }; } /** * Converts a ts.Node's typeArguments to TSTypeParameterInstantiation node * @param typeArguments ts.NodeArray typeArguments * @param node parent used to create this node * @returns TypeParameterInstantiation node */ convertTypeArgumentsToTypeParameterInstantiation(typeArguments, node) { const greaterThanToken = (0, node_utils_1.findNextToken)(typeArguments, this.ast, this.ast); return this.createNode(node, { type: ts_estree_1.AST_NODE_TYPES.TSTypeParameterInstantiation, range: [typeArguments.pos - 1, greaterThanToken.end], params: typeArguments.map(typeArgument => this.convertChild(typeArgument)), }); } /** * Converts a ts.Node's typeParameters to TSTypeParameterDeclaration node * @param typeParameters ts.Node typeParameters * @returns TypeParameterDeclaration node */ convertTSTypeParametersToTypeParametersDeclaration(typeParameters) { const greaterThanToken = (0, node_utils_1.findNextToken)(typeParameters, this.ast, this.ast); const range = [ typeParameters.pos - 1, greaterThanToken.end, ]; return { type: ts_estree_1.AST_NODE_TYPES.TSTypeParameterDeclaration, loc: (0, node_utils_1.getLocFor)(range, this.ast), range, params: typeParameters.map(typeParameter => this.convertChild(typeParameter)), }; } /** * Converts an array of ts.Node parameters into an array of ESTreeNode params * @param parameters An array of ts.Node params to be converted * @returns an array of converted ESTreeNode params */ convertParameters(parameters) { if (!parameters?.length) { return []; } return parameters.map(param => { const convertedParam = this.convertChild(param); convertedParam.decorators = (0, getModifiers_1.getDecorators)(param)?.map(el => this.convertChild(el)) ?? []; return convertedParam; }); } /** * Converts a TypeScript node into an ESTree node. * @param node the child ts.Node * @param parent parentNode * @param allowPattern flag to determine if patterns are allowed * @returns the converted ESTree node */ converter(node, parent, allowPattern) { /** * Exit early for null and undefined */ if (!node) { return null; } this.#checkModifiers(node); const pattern = this.allowPattern; if (allowPattern != null) { this.allowPattern = allowPattern; } const result = this.convertNode(node, (parent ?? node.parent)); this.registerTSNodeInNodeMap(node, result); this.allowPattern = pattern; return result; } convertImportAttributes(node) { return node == null ? [] : node.elements.map(element => this.convertChild(element)); } convertJSXIdentifier(node) { const result = this.createNode(node, { type: ts_estree_1.AST_NODE_TYPES.JSXIdentifier, name: node.getText(), }); this.registerTSNodeInNodeMap(node, result); return result; } convertJSXNamespaceOrIdentifier(node) { // TypeScript@5.1 added in ts.JsxNamespacedName directly // We prefer using that if it's relevant for this node type if (node.kind === ts.SyntaxKind.JsxNamespacedName) { const result = this.createNode(node, { type: ts_estree_1.AST_NODE_TYPES.JSXNamespacedName, name: this.createNode(node.name, { type: ts_estree_1.AST_NODE_TYPES.JSXIdentifier, name: node.name.text, }), namespace: this.createNode(node.namespace, { type: ts_estree_1.AST_NODE_TYPES.JSXIdentifier, name: node.namespace.text, }), }); this.registerTSNodeInNodeMap(node, result); return result; } // TypeScript@<5.1 has to manually parse the JSX attributes const text = node.getText(); const colonIndex = text.indexOf(':'); // this is intentional we can ignore conversion if `:` is in first character if (colonIndex > 0) { const range = (0, node_utils_1.getRange)(node, this.ast); const result = this.createNode(node, { type: ts_estree_1.AST_NODE_TYPES.JSXNamespacedName, range, name: this.createNode(node, { type: ts_estree_1.AST_NODE_TYPES.JSXIdentifier, range: [range[0] + colonIndex + 1, range[1]], name: text.slice(colonIndex + 1), }), namespace: this.createNode(node, { type: ts_estree_1.AST_NODE_TYPES.JSXIdentifier, range: [range[0], range[0] + colonIndex], name: text.slice(0, colonIndex), }), }); this.registerTSNodeInNodeMap(node, result); return result; } return this.convertJSXIdentifier(node); } /** * Converts a TypeScript JSX node.tagName into an ESTree node.name * @param node the tagName object from a JSX ts.Node * @returns the converted ESTree name object */ convertJSXTagName(node, parent) { let result; switch (node.kind) { case SyntaxKind.PropertyAccessExpression: if (node.name.kind === SyntaxKind.PrivateIdentifier) { // This is one of the few times where TS explicitly errors, and doesn't even gracefully handle the syntax. // So we shouldn't ever get into this state to begin with. this.#throwError(node.name, 'Non-private identifier expected.'); } result = this.createNode(node, { type: ts_estree_1.AST_NODE_TYPES.JSXMemberExpression, object: this.convertJSXTagName(node.expression, parent), property: this.convertJSXIdentifier(node.name), }); break; case SyntaxKind.ThisKeyword: case SyntaxKind.Identifier: default: return this.convertJSXNamespaceOrIdentifier(node); } this.registerTSNodeInNodeMap(node, result); return result; } convertMethodSignature(node) { return this.createNode(node, { type: ts_estree_1.AST_NODE_TYPES.TSMethodSignature, accessibility: (0, node_utils_1.getTSNodeAccessibility)(node), computed: (0, node_utils_1.isComputedProperty)(node.name), key: this.convertChild(node.name), kind: (() => { switch (node.kind) { case SyntaxKind.GetAccessor: return 'get'; case SyntaxKind.SetAccessor: return 'set'; case SyntaxKind.MethodSignature: return 'method'; } })(), optional: (0, node_utils_1.isOptional)(node), params: this.convertParameters(node.parameters), readonly: (0, node_utils_1.hasModifier)(SyntaxKind.ReadonlyKeyword, node), returnType: node.type && this.convertTypeAnnotation(node.type, node), static: (0, node_utils_1.hasModifier)(SyntaxKind.StaticKeyword, node), typeParameters: node.typeParameters && this.convertTSTypeParametersToTypeParametersDeclaration(node.typeParameters), }); } /** * Uses the provided range location to adjust the location data of the given Node * @param result The node that will have its location data mutated * @param childRange The child node range used to expand location */ fixParentLocation(result, childRange) { if (childRange[0] < result.range[0]) { result.range[0] = childRange[0]; result.loc.start = (0, node_utils_1.getLineAndCharacterFor)(result.range[0], this.ast); } if (childRange[1] > result.range[1]) { result.range[1] = childRange[1]; result.loc.end = (0, node_utils_1.getLineAndCharacterFor)(result.range[1], this.ast); } } /** * Converts a TypeScript node into an ESTree node. * The core of the conversion logic: * Identify and convert each relevant TypeScript SyntaxKind * @returns the converted ESTree node */ convertNode(node, parent) { switch (node.kind) { case SyntaxKind.SourceFile: { return this.createNode(node, { type: ts_estree_1.AST_NODE_TYPES.Program, range: [node.getStart(this.ast), node.endOfFileToken.end], body: this.convertBodyExpressions(node.statements, node), comments: undefined, sourceType: node.externalModuleIndicator ? 'module' : 'script', tokens: undefined, }); } case SyntaxKind.Block: { return this.createNode(node, { type: ts_estree_1.AST_NODE_TYPES.BlockStatement, body: this.convertBodyExpressions(node.statements, node), }); } case SyntaxKind.Identifier: { if ((0, node_utils_1.isThisInTypeQuery)(node)) { // special case for `typeof this.foo` - TS emits an Identifier for `this` // but we want to treat it as a ThisExpression for consistency return this.createNode(node, { type: ts_estree_1.AST_NODE_TYPES.ThisExpression, }); } return this.createNode(node, { type: ts_estree_1.AST_NODE_TYPES.Identifier, decorators: [], name: node.text, optional: false, typeAnnotation: undefined, }); } case SyntaxKind.PrivateIdentifier: { return this.createNode(node, { type: ts_estree_1.AST_NODE_TYPES.PrivateIdentifier, // typescript includes the `#` in the text name: node.text.slice(1), }); } case SyntaxKind.WithStatement: return this.createNode(node, { type: ts_estree_1.AST_NODE_TYPES.WithStatement, body: this.convertChild(node.statement), object: this.convertChild(node.expression), }); // Control Flow case SyntaxKind.ReturnStatement: return this.createNode(node, { type: ts_estree_1.AST_NODE_TYPES.ReturnStatement, argument: this.convertChild(node.expression), }); case SyntaxKind.LabeledStatement: return this.createNode(node, { type: ts_estree_1.AST_NODE_TYPES.LabeledStatement, body: this.convertChild(node.statement), label: this.convertChild(node.label), }); case SyntaxKind.ContinueStatement: return this.createNode(node, { type: ts_estree_1.AST_NODE_TYPES.ContinueStatement, label: this.convertChild(node.label), }); case SyntaxKind.BreakStatement: return this.createNode(node, { type: ts_estree_1.AST_NODE_TYPES.BreakStatement, label: this.convertChild(node.label), }); // Choice case SyntaxKind.IfStatement: return this.createNode(node, { type: ts_estree_1.AST_NODE_TYPES.IfStatement, alternate: this.convertChild(node.elseStatement), consequent: this.convertChild(node.thenStatement), test: this.convertChild(node.expression), }); case SyntaxKind.SwitchStatement: if (node.caseBlock.clauses.filter(switchCase => switchCase.kind === SyntaxKind.DefaultClause).length > 1) { this.#throwError(node, "A 'default' clause cannot appear more than once in a 'switch' statement."); } return this.createNode(node, { type: ts_estree_1.AST_NODE_TYPES.SwitchStatement, cases: node.caseBlock.clauses.map(el => this.convertChild(el)), discriminant: this.convertChild(node.expression), }); case SyntaxKind.CaseClause: case SyntaxKind.DefaultClause: return this.createNode(node, { type: ts_estree_1.AST_NODE_TYPES.SwitchCase, // expression is present in case only consequent: node.statements.map(el => this.convertChild(el)), test: node.kind === SyntaxKind.CaseClause ? this.convertChild(node.expression) : null, }); // Exceptions case SyntaxKind.ThrowStatement: if (node.expression.end === node.expression.pos) { this.#throwUnlessAllowInvalidAST(node, 'A throw statement must throw an expression.'); } return this.createNode(node, { type: ts_estree_1.AST_NODE_TYPES.ThrowStatement, argument: this.convertChild(node.expression), }); case SyntaxKind.TryStatement: return this.createNode(node, { type: ts_estree_1.AST_NODE_TYPES.TryStatement, block: this.convertChild(node.tryBlock), finalizer: this.convertChild(node.finallyBlock), handler: this.convertChild(node.catchClause), }); case SyntaxKind.CatchClause: if (node.variableDeclaration?.initializer) { this.#throwError(node.variableDeclaration.initializer, 'Catch clause variable cannot have an initializer.'); } return this.createNode(node, { type: ts_estree_1.AST_NODE_TYPES.CatchClause, body: this.convertChild(node.block), param: node.variableDeclaration ? this.convertBindingNameWithTypeAnnotation(node.variableDeclaration.name, node.variableDeclaration.type) : null, }); // Loops case SyntaxKind.WhileStatement: return this.createNode(node, { type: ts_estree_1.AST_NODE_TYPES.WhileStatement, body: this.convertChild(node.statement), test: this.convertChild(node.expression), }); /** * Unlike other parsers, TypeScript calls a "DoWhileStatement" * a "DoStatement" */ case SyntaxKind.DoStatement: return this.createNode(node, { type: ts_estree_1.AST_NODE_TYPES.DoWhileStatement, body: this.convertChild(node.statement), test: this.convertChild(node.expression), }); case SyntaxKind.ForStatement: return this.createNode(node, { type: ts_estree_1.AST_NODE_TYPES.ForStatement, body: this.convertChild(node.statement), init: this.convertChild(node.initializer), test: this.convertChild(node.condition), update: this.convertChild(node.incrementor), }); case SyntaxKind.ForInStatement: this.#checkForStatementDeclaration(node.initializer, node.kind); return this.createNode(node, { type: ts_estree_1.AST_NODE_TYPES.ForInStatement, body: this.convertChild(node.statement), left: this.convertPattern(node.initializer), right: this.convertChild(node.expression), }); case SyntaxKind.ForOfStatement: { this.#checkForStatementDeclaration(node.initializer, node.kind); return this.createNode(node, { type: ts_estree_1.AST_NODE_TYPES.ForOfStatement, await: Boolean(node.awaitModifier && node.awaitModifier.kind === SyntaxKind.AwaitKeyword), body: this.convertChild(node.statement), left: this.convertPattern(node.initializer), right: this.convertChild(node.expression), }); } // Declarations case SyntaxKind.FunctionDeclaration: { const isDeclare = (0, node_utils_1.hasModifier)(SyntaxKind.DeclareKeyword, node); const isAsync = (0, node_utils_1.hasModifier)(SyntaxKind.AsyncKeyword, node); const isGenerator = !!node.asteriskToken; if (isDeclare) { if (node.body) { this.#throwError(node, 'An implementation cannot be declared in ambient contexts.'); } else if (isAsync) { this.#throwError(node, "'async' modifier cannot be used in an ambient context."); } else if (isGenerator) { this.#throwError(node, 'Generators are not allowed in an ambient context.'); } } else if (!node.body && isGenerator) { this.#throwError(node, 'A function signature cannot be declared as a generator.'); } const result = this.createNode(node, { // declare implies no body due to the invariant above type: !node.body ? ts_estree_1.AST_NODE_TYPES.TSDeclareFunction : ts_estree_1.AST_NODE_TYPES.FunctionDeclaration, async: isAsync, body: this.convertChild(node.body) || undefined, declare: isDeclare, expression: false, generator: isGenerator, id: this.convertChild(node.name), params: this.convertParameters(node.parameters), returnType: node.type && this.convertTypeAnnotation(node.type, node), typeParameters: node.typeParameters && this.convertTSTypeParametersToTypeParametersDeclaration(node.typeParameters), }); return this.fixExports(node, result); } case SyntaxKind.VariableDeclaration: { const definite = !!node.exclamationToken; const init = this.convertChild(node.initializer); const id = this.convertBindingNameWithTypeAnnotation(node.name, node.type, node); if (definite) { if (init) { this.#throwError(node, 'Declarations with initializers cannot also have definite assignment assertions.'); } else if (id.type !== ts_estree_1.AST_NODE_TYPES.Identifier || !id.typeAnnotation) { this.#throwError(node, 'Declarations with definite assignment assertions must also have type annotations.'); } } return this.createNode(node, { type: ts_estree_1.AST_NODE_TYPES.VariableDeclarator, definite, id, init, }); } case SyntaxKind.VariableStatement: { const result = this.createNode(node, { type: ts_estree_1.AST_NODE_TYPES.VariableDeclaration, declarations: node.declarationList.declarations.map(el => this.convertChild(el)), declare: (0, node_utils_1.hasModifier)(SyntaxKind.DeclareKeyword, node), kind: (0, node_utils_1.getDeclarationKind)(node.declarationList), }); if (!result.declarations.length) { this.#throwUnlessAllowInvalidAST(node, 'A variable declaration list must have at least one variable declarator.'); } if (result.kind === 'using' || result.kind === 'await using') { node.declarationList.declarations.forEach((declaration, i) => { if (result.declarations[i].init == null) { this.#throwError(declaration, `'${result.kind}' declarations must be initialized.`); } if (result.declarations[i].id.type !== ts_estree_1.AST_NODE_TYPES.Identifier) { this.#throwError(declaration.name, `'${result.kind}' declarations may not have binding patterns.`); } }); } // Definite assignment only allowed for non-declare let and var if (result.declare || ['await using', 'const', 'using'].includes(result.kind)) { node.declarationList.declarations.forEach((declaration, i) => { if (result.declarations[i].definite) { this.#throwError(declaration, `A definite assignment assertion '!' is not permitted in this context.`); } }); } if (result.declare) { node.declarationList.declarations.forEach((declaration, i) => { if (result.declarations[i].init && (['let', 'var'].includes(result.kind) || result.declarations[i].id.typeAnnotation)) { this.#throwError(declaration, `Initializers are not permitted in ambient contexts.`); } }); // Theoretically, only certain initializers are allowed for declare const, // (TS1254: A 'const' initializer in an ambient context must be a string // or numeric literal or literal enum reference.) but we just allow // all expressions } // Note! No-declare does not mean the variable is not ambient, because // it can be further nested in other declare contexts. Therefore we cannot // check for const initializers. /** * Semantically, decorators are not allowed on variable declarations, * Pre 4.8 TS would include them in the AST, so we did as well. * However as of 4.8 TS no longer includes it (as it is, well, invalid). * * So for consistency across versions, we no longer include it either. */ return this.fixExports(node, result); } // mostly for for-of, for-in case SyntaxKind.VariableDeclarationList: { const result = this.createNode(node, { type: ts_estree_1.AST_NODE_TYPES.VariableDeclaration, declarations: node.declarations.map(el => this.convertChild(el)), declare: false, kind: (0, node_utils_1.getDeclarationKind)(node), }); if (result.kind === 'using' || result.kind === 'await using') { node.declarations.forEach((declaration, i) => { if (result.declarations[i].init != null) { this.#throwError(declaration, `'${result.kind}' declarations may not be initialized in for statement.`); } if (result.declarations[i].id.type !== ts_estree_1.AST_NODE_TYPES.Identifier) { this.#throwError(declaration.name, `'${result.kind}' declarations may not have binding patterns.`); } }); } return result; } // Expressions case SyntaxKind.ExpressionStatement: return this.createNode(node, { type: ts_estree_1.AST_NODE_TYPES.ExpressionStatement, directive: undefined, expression: this.convertChild(node.expression), }); case SyntaxKind.ThisKeyword: return this.createNode(node, { type: ts_estree_1.AST_NODE_TYPES.ThisExpression, }); case SyntaxKind.ArrayLiteralExpression: { // TypeScript uses ArrayLiteralExpression in destructuring assignment, too if (this.allowPattern) { return this.createNode(node, { type: ts_estree_1.AST_NODE_TYPES.ArrayPattern, decorators: [], elements: node.elements.map(el => this.convertPattern(el)), optional: false, typeAnnotation: undefined, }); } return this.createNode(node, { type: ts_estree_1.AST_NODE_TYPES.ArrayExpression, elements: node.elements.map(el => this.convertChild(el)), }); } case SyntaxKind.ObjectLiteralExpression: { // TypeScript uses ObjectLiteralExpression in destructuring assignment, too if (this.allowPattern) { return this.createNode(node, { type: ts_estree_1.AST_NODE_TYPES.ObjectPattern, decorators: [], optional: false, properties: node.properties.map(el => this.convertPattern(el)), typeAnnotation: undefined, }); } const properties = []; for (const property of node.properties) { if ((property.kind === SyntaxKind.GetAccessor || property.kind === SyntaxKind.SetAccessor || property.kind === SyntaxKind.MethodDeclaration) && !property.body) { this.#throwUnlessAllowInvalidAST(property.end - 1, "'{' expected."); } properties.push(this.convertChild(property)); } return this.createNode(node, { type: ts_estree_1.AST_NODE_TYPES.ObjectExpression, properties, }); } case SyntaxKind.PropertyAssignment: { // eslint-disable-next-line @typescript-eslint/no-deprecated const { exclamationToken, questionToken } = node; if (questionToken) { this.#throwError(questionToken, 'A property assignment cannot have a question token.'); } if (exclamationToken) { this.#throwError(exclamationToken, 'A property assignment cannot have an exclamation token.'); } return this.createNode(node, { type: ts_estree_1.AST_NODE_TYPES.Property, computed: (0, node_utils_1.isComputedProperty)(node.name), key: this.convertChild(node.name), kind: 'init', method: false, optional: false, shorthand: false, value: this.converter(node.initializer, node, this.allowPattern), }); } case SyntaxKind.ShorthandPropertyAssignment: { // eslint-disable-next-line @typescript-eslint/no-deprecated const { exclamationToken, modifiers, questionToken } = node; if (modifiers) { this.#throwError(modifiers[0], 'A shorthand property assignment cannot have modifiers.'); } if (questionToken) { this.#throwError(questionToken, 'A shorthand property assignment cannot have a question token.'); } if (exclamationToken) { this.#throwError(exclamationToken, 'A shorthand property assignment cannot have an exclamation token.'); } if (node.objectAssignmentInitializer) { return this.createNode(node, { type: ts_estree_1.AST_NODE_TYPES.Property, computed: false, key: this.convertChild(node.name), kind: 'init', method: false, optional: false, shorthand: true, value: this.createNode(node, { type: ts_estree_1.AST_NODE_TYPES.AssignmentPattern, decorators: [], left: this.convertPattern(node.name), optional: false, right: this.convertChild(node.objectAssignmentInitiali