UNPKG

typescript-estree

Version:

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

1,167 lines 93.7 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); /** * @fileoverview Converts TypeScript AST into ESTree format. * @author Nicholas C. Zakas * @author James Henry <https://github.com/JamesHenry> * @copyright jQuery Foundation and other contributors, https://jquery.org/ * MIT License */ const typescript_1 = __importDefault(require("typescript")); const node_utils_1 = require("./node-utils"); const ast_node_types_1 = require("./ast-node-types"); const SyntaxKind = typescript_1.default.SyntaxKind; let esTreeNodeToTSNodeMap = new WeakMap(); let tsNodeToESTreeNodeMap = new WeakMap(); function resetASTMaps() { esTreeNodeToTSNodeMap = new WeakMap(); tsNodeToESTreeNodeMap = new WeakMap(); } exports.resetASTMaps = resetASTMaps; function getASTMaps() { return { esTreeNodeToTSNodeMap, tsNodeToESTreeNodeMap }; } exports.getASTMaps = getASTMaps; /** * Extends and formats a given error object * @param {Object} error the error object * @returns {Object} converted error object */ function convertError(error) { return node_utils_1.createError(error.file, error.start, error.message || error.messageText); } exports.convertError = convertError; /** * Converts a TypeScript node into an ESTree node * @param {Object} config configuration options for the conversion * @param {TSNode} config.node the ts.Node * @param {ts.Node} config.parent the parent ts.Node * @param {ts.SourceFile} config.ast the full TypeScript AST * @param {Object} config.additionalOptions additional options for the conversion * @returns {ESTreeNode|null} the converted ESTreeNode */ function convert(config) { const node = config.node; const parent = config.parent; const ast = config.ast; const additionalOptions = config.additionalOptions || {}; /** * Exit early for null and undefined */ if (!node) { return null; } /** * Create a new ESTree node */ let result = { type: '', range: [node.getStart(ast), node.end], loc: node_utils_1.getLoc(node, ast) }; function converter(child, inTypeMode, allowPattern) { if (!child) { return null; } return convert({ node: child, parent: node, inTypeMode, allowPattern, ast, additionalOptions }); } /** * Converts a TypeScript node into an ESTree node. * @param {ts.Node} child the child ts.Node * @returns {ESTreeNode|null} the converted ESTree node */ function convertPattern(child) { return converter(child, config.inTypeMode, true); } /** * Converts a TypeScript node into an ESTree node. * @param {ts.Node} child the child ts.Node * @returns {ESTreeNode|null} the converted ESTree node */ function convertChild(child) { return converter(child, config.inTypeMode, false); } /** * Converts a TypeScript node into an ESTree node. * @param {ts.Node} child the child ts.Node * @returns {ESTreeNode|null} the converted ESTree node */ function convertChildType(child) { return converter(child, true, false); } /** * Converts a child into a type annotation. This creates an intermediary * TypeAnnotation node to match what Flow does. * @param {ts.TypeNode} child The TypeScript AST node to convert. * @returns {ESTreeNode} The type annotation node. */ function convertTypeAnnotation(child) { const annotation = convertChildType(child); // in FunctionType and ConstructorType typeAnnotation has 2 characters `=>` and in other places is just colon const offset = node.kind === SyntaxKind.FunctionType || node.kind === SyntaxKind.ConstructorType ? 2 : 1; const annotationStartCol = child.getFullStart() - offset; const loc = node_utils_1.getLocFor(annotationStartCol, child.end, ast); return { type: ast_node_types_1.AST_NODE_TYPES.TSTypeAnnotation, loc, range: [annotationStartCol, child.end], typeAnnotation: annotation }; } /** * Coverts body Nodes and add directive field to StringLiterals * @param {ts.NodeArray<ts.Statement>} nodes of ts.Node * @returns {ESTreeNode[]} Array of body statements */ function convertBodyExpressions(nodes) { // directives has to be unique, if directive is registered twice pick only first one const unique = []; const allowDirectives = node_utils_1.canContainDirective(node); return (nodes .map(statement => { const child = convertChild(statement); if (allowDirectives && child && child.expression && typescript_1.default.isExpressionStatement(statement) && typescript_1.default.isStringLiteral(statement.expression)) { const raw = child.expression.raw; if (!unique.includes(raw)) { child.directive = raw.slice(1, -1); unique.push(raw); } } return child; // child can be null but it's filtered below }) // filter out unknown nodes for now .filter(statement => statement)); } /** * Converts a ts.Node's typeArguments ts.NodeArray to a flow-like typeParameters node * @param {ts.NodeArray<any>} typeArguments ts.Node typeArguments * @returns {ESTreeNode} TypeParameterInstantiation node */ function convertTypeArgumentsToTypeParameters(typeArguments) { /** * Even if typeArguments is an empty array, TypeScript sets a `pos` and `end` * property on the array object so we can safely read the values here */ const start = typeArguments.pos - 1; let end = typeArguments.end + 1; if (typeArguments && typeArguments.length) { const firstTypeArgument = typeArguments[0]; const typeArgumentsParent = firstTypeArgument.parent; /** * In the case of the parent being a CallExpression or a TypeReference we have to use * slightly different logic to calculate the correct end position */ if (typeArgumentsParent && (typeArgumentsParent.kind === SyntaxKind.CallExpression || typeArgumentsParent.kind === SyntaxKind.TypeReference)) { const lastTypeArgument = typeArguments[typeArguments.length - 1]; const greaterThanToken = node_utils_1.findNextToken(lastTypeArgument, ast, ast); end = greaterThanToken.end; } } return { type: ast_node_types_1.AST_NODE_TYPES.TSTypeParameterInstantiation, range: [start, end], loc: node_utils_1.getLocFor(start, end, ast), params: typeArguments.map(typeArgument => convertChildType(typeArgument)) }; } /** * Converts a ts.Node's typeParameters ts.ts.NodeArray to a flow-like TypeParameterDeclaration node * @param {ts.NodeArray} typeParameters ts.Node typeParameters * @returns {ESTreeNode} TypeParameterDeclaration node */ function convertTSTypeParametersToTypeParametersDeclaration(typeParameters) { const firstTypeParameter = typeParameters[0]; const lastTypeParameter = typeParameters[typeParameters.length - 1]; const greaterThanToken = node_utils_1.findNextToken(lastTypeParameter, ast, ast); return { type: ast_node_types_1.AST_NODE_TYPES.TSTypeParameterDeclaration, range: [firstTypeParameter.pos - 1, greaterThanToken.end], loc: node_utils_1.getLocFor(firstTypeParameter.pos - 1, greaterThanToken.end, ast), params: typeParameters.map(typeParameter => convertChildType(typeParameter)) }; } /** * Converts a child into a specified heritage node. * @param {AST_NODE_TYPES} nodeType Type of node to be used * @param {ts.ExpressionWithTypeArguments} child The TypeScript AST node to convert. * @returns {ESTreeNode} The heritage node. */ function convertHeritageClause(nodeType, child) { const expression = convertChild(child.expression); const classImplementsNode = { type: nodeType, loc: expression.loc, range: expression.range, expression }; if (child.typeArguments && child.typeArguments.length) { classImplementsNode.typeParameters = convertTypeArgumentsToTypeParameters(child.typeArguments); } return classImplementsNode; } /** * Converts an array of ts.Node parameters into an array of ESTreeNode params * @param {ts.Node[]} parameters An array of ts.Node params to be converted * @returns {ESTreeNode[]} an array of converted ESTreeNode params */ function convertParameters(parameters) { if (!parameters || !parameters.length) { return []; } return parameters.map(param => { const convertedParam = convertChild(param); if (!param.decorators || !param.decorators.length) { return convertedParam; } return Object.assign(convertedParam, { decorators: param.decorators.map(convertChild) }); }); } /** * For nodes that are copied directly from the TypeScript AST into * ESTree mostly as-is. The only difference is the addition of a type * property instead of a kind property. Recursively copies all children. * @returns {void} */ function deeplyCopy() { const customType = `TS${SyntaxKind[node.kind]}`; /** * If the "errorOnUnknownASTType" option is set to true, throw an error, * otherwise fallback to just including the unknown type as-is. */ if (additionalOptions.errorOnUnknownASTType && !ast_node_types_1.AST_NODE_TYPES[customType]) { throw new Error(`Unknown AST_NODE_TYPE: "${customType}"`); } result.type = customType; Object.keys(node) .filter(key => !/^(?:_children|kind|parent|pos|end|flags|modifierFlagsCache|jsDoc)$/.test(key)) .forEach(key => { if (key === 'type') { result.typeAnnotation = node.type ? convertTypeAnnotation(node.type) : null; } else if (key === 'typeArguments') { result.typeParameters = node.typeArguments ? convertTypeArgumentsToTypeParameters(node.typeArguments) : null; } else if (key === 'typeParameters') { result.typeParameters = node.typeParameters ? convertTSTypeParametersToTypeParametersDeclaration(node.typeParameters) : null; } else if (key === 'decorators') { if (node.decorators && node.decorators.length) { result.decorators = node.decorators.map(convertChild); } } else { if (Array.isArray(node[key])) { result[key] = node[key].map(convertChild); } else if (node[key] && typeof node[key] === 'object' && node[key].kind) { // need to check node[key].kind to ensure we don't try to convert a symbol result[key] = convertChild(node[key]); } else { result[key] = node[key]; } } }); } /** * Converts a TypeScript JSX node.tagName into an ESTree node.name * @param {ts.JsxTagNameExpression} tagName the tagName object from a JSX ts.Node * @returns {Object} the converted ESTree name object */ function convertTypeScriptJSXTagNameToESTreeName(tagName) { const tagNameToken = node_utils_1.convertToken(tagName, ast); if (tagNameToken.type === ast_node_types_1.AST_NODE_TYPES.JSXMemberExpression) { const isNestedMemberExpression = node.tagName.expression.kind === SyntaxKind.PropertyAccessExpression; // Convert TSNode left and right objects into ESTreeNode object // and property objects tagNameToken.object = convertChild(node.tagName.expression); tagNameToken.property = convertChild(node.tagName.name); // Assign the appropriate types tagNameToken.object.type = isNestedMemberExpression ? ast_node_types_1.AST_NODE_TYPES.JSXMemberExpression : ast_node_types_1.AST_NODE_TYPES.JSXIdentifier; tagNameToken.property.type = ast_node_types_1.AST_NODE_TYPES.JSXIdentifier; if (tagName.expression.kind === SyntaxKind.ThisKeyword) { tagNameToken.object.name = 'this'; } } else { tagNameToken.type = ast_node_types_1.AST_NODE_TYPES.JSXIdentifier; tagNameToken.name = tagNameToken.value; } delete tagNameToken.value; return tagNameToken; } /** * Applies the given TS modifiers to the given result object. * @param {ts.ModifiersArray} modifiers original ts.Nodes from the node.modifiers array * @returns {void} (the current result object will be mutated) */ function applyModifiersToResult(modifiers) { if (!modifiers || !modifiers.length) { return; } /** * Some modifiers are explicitly handled by applying them as * boolean values on the result node. As well as adding them * to the result, we remove them from the array, so that they * are not handled twice. */ const handledModifierIndices = {}; for (let i = 0; i < modifiers.length; i++) { const modifier = modifiers[i]; switch (modifier.kind) { /** * Ignore ExportKeyword and DefaultKeyword, they are handled * via the fixExports utility function */ case SyntaxKind.ExportKeyword: case SyntaxKind.DefaultKeyword: handledModifierIndices[i] = true; break; case SyntaxKind.ConstKeyword: result.const = true; handledModifierIndices[i] = true; break; case SyntaxKind.DeclareKeyword: result.declare = true; handledModifierIndices[i] = true; break; default: } } /** * If there are still valid modifiers available which have * not been explicitly handled above, we just convert and * add the modifiers array to the result node. */ const remainingModifiers = modifiers.filter((_, i) => !handledModifierIndices[i]); if (!remainingModifiers || !remainingModifiers.length) { return; } result.modifiers = remainingModifiers.map(convertChild); } /** * Uses the current TSNode's end location for its `type` to adjust the location data of the given * ESTreeNode, which should be the parent of the final typeAnnotation node * @param {ESTreeNode} typeAnnotationParent The node that will have its location data mutated * @returns {void} */ function fixTypeAnnotationParentLocation(typeAnnotationParent) { typeAnnotationParent.range[1] = node.type.getEnd(); typeAnnotationParent.loc = node_utils_1.getLocFor(typeAnnotationParent.range[0], typeAnnotationParent.range[1], ast); } /** * The core of the conversion logic: * Identify and convert each relevant TypeScript SyntaxKind */ switch (node.kind) { case SyntaxKind.SourceFile: Object.assign(result, { type: ast_node_types_1.AST_NODE_TYPES.Program, body: convertBodyExpressions(node.statements), // externalModuleIndicator is internal field in TSC sourceType: node.externalModuleIndicator ? 'module' : 'script' }); result.range[1] = node.endOfFileToken.end; result.loc = node_utils_1.getLocFor(node.getStart(ast), result.range[1], ast); break; case SyntaxKind.Block: Object.assign(result, { type: ast_node_types_1.AST_NODE_TYPES.BlockStatement, body: convertBodyExpressions(node.statements) }); break; case SyntaxKind.Identifier: Object.assign(result, { type: ast_node_types_1.AST_NODE_TYPES.Identifier, name: node.text }); break; case SyntaxKind.WithStatement: Object.assign(result, { type: ast_node_types_1.AST_NODE_TYPES.WithStatement, object: convertChild(node.expression), body: convertChild(node.statement) }); break; // Control Flow case SyntaxKind.ReturnStatement: Object.assign(result, { type: ast_node_types_1.AST_NODE_TYPES.ReturnStatement, argument: convertChild(node.expression) }); break; case SyntaxKind.LabeledStatement: Object.assign(result, { type: ast_node_types_1.AST_NODE_TYPES.LabeledStatement, label: convertChild(node.label), body: convertChild(node.statement) }); break; case SyntaxKind.BreakStatement: case SyntaxKind.ContinueStatement: Object.assign(result, { type: SyntaxKind[node.kind], label: convertChild(node.label) }); break; // Choice case SyntaxKind.IfStatement: Object.assign(result, { type: ast_node_types_1.AST_NODE_TYPES.IfStatement, test: convertChild(node.expression), consequent: convertChild(node.thenStatement), alternate: convertChild(node.elseStatement) }); break; case SyntaxKind.SwitchStatement: Object.assign(result, { type: ast_node_types_1.AST_NODE_TYPES.SwitchStatement, discriminant: convertChild(node.expression), cases: node.caseBlock.clauses.map(convertChild) }); break; case SyntaxKind.CaseClause: case SyntaxKind.DefaultClause: Object.assign(result, { type: ast_node_types_1.AST_NODE_TYPES.SwitchCase, // expression is present in case only test: node.kind === SyntaxKind.CaseClause ? convertChild(node.expression) : null, consequent: node.statements.map(convertChild) }); break; // Exceptions case SyntaxKind.ThrowStatement: Object.assign(result, { type: ast_node_types_1.AST_NODE_TYPES.ThrowStatement, argument: convertChild(node.expression) }); break; case SyntaxKind.TryStatement: Object.assign(result, { type: ast_node_types_1.AST_NODE_TYPES.TryStatement, block: convert({ node: node.tryBlock, parent: null, ast, additionalOptions }), handler: convertChild(node.catchClause), finalizer: convertChild(node.finallyBlock) }); break; case SyntaxKind.CatchClause: Object.assign(result, { type: ast_node_types_1.AST_NODE_TYPES.CatchClause, param: node.variableDeclaration ? convertChild(node.variableDeclaration.name) : null, body: convertChild(node.block) }); break; // Loops case SyntaxKind.WhileStatement: Object.assign(result, { type: ast_node_types_1.AST_NODE_TYPES.WhileStatement, test: convertChild(node.expression), body: convertChild(node.statement) }); break; /** * Unlike other parsers, TypeScript calls a "DoWhileStatement" * a "DoStatement" */ case SyntaxKind.DoStatement: Object.assign(result, { type: ast_node_types_1.AST_NODE_TYPES.DoWhileStatement, test: convertChild(node.expression), body: convertChild(node.statement) }); break; case SyntaxKind.ForStatement: Object.assign(result, { type: ast_node_types_1.AST_NODE_TYPES.ForStatement, init: convertChild(node.initializer), test: convertChild(node.condition), update: convertChild(node.incrementor), body: convertChild(node.statement) }); break; case SyntaxKind.ForInStatement: case SyntaxKind.ForOfStatement: { Object.assign(result, { type: SyntaxKind[node.kind], left: convertPattern(node.initializer), right: convertChild(node.expression), body: convertChild(node.statement) }); // await is only available in for of statement if (node.kind === SyntaxKind.ForOfStatement) { result.await = Boolean(node.awaitModifier && node.awaitModifier.kind === SyntaxKind.AwaitKeyword); } break; } // Declarations case SyntaxKind.FunctionDeclaration: { const isDeclare = node_utils_1.hasModifier(SyntaxKind.DeclareKeyword, node); let functionDeclarationType = ast_node_types_1.AST_NODE_TYPES.FunctionDeclaration; if (isDeclare || !node.body) { functionDeclarationType = ast_node_types_1.AST_NODE_TYPES.TSDeclareFunction; } Object.assign(result, { type: functionDeclarationType, id: convertChild(node.name), generator: !!node.asteriskToken, expression: false, async: node_utils_1.hasModifier(SyntaxKind.AsyncKeyword, node), params: convertParameters(node.parameters), body: convertChild(node.body) || undefined }); // Process returnType if (node.type) { result.returnType = convertTypeAnnotation(node.type); } if (isDeclare) { result.declare = true; } // Process typeParameters if (node.typeParameters && node.typeParameters.length) { result.typeParameters = convertTSTypeParametersToTypeParametersDeclaration(node.typeParameters); } // check for exports result = node_utils_1.fixExports(node, result, ast); break; } case SyntaxKind.VariableDeclaration: { Object.assign(result, { type: ast_node_types_1.AST_NODE_TYPES.VariableDeclarator, id: convertPattern(node.name), init: convertChild(node.initializer) }); if (node.exclamationToken) { result.definite = true; } if (node.type) { result.id.typeAnnotation = convertTypeAnnotation(node.type); fixTypeAnnotationParentLocation(result.id); } break; } case SyntaxKind.VariableStatement: Object.assign(result, { type: ast_node_types_1.AST_NODE_TYPES.VariableDeclaration, declarations: node.declarationList.declarations.map(convertChild), kind: node_utils_1.getDeclarationKind(node.declarationList) }); if (node_utils_1.hasModifier(SyntaxKind.DeclareKeyword, node)) { result.declare = true; } // check for exports result = node_utils_1.fixExports(node, result, ast); break; // mostly for for-of, for-in case SyntaxKind.VariableDeclarationList: Object.assign(result, { type: ast_node_types_1.AST_NODE_TYPES.VariableDeclaration, declarations: node.declarations.map(convertChild), kind: node_utils_1.getDeclarationKind(node) }); break; // Expressions case SyntaxKind.ExpressionStatement: Object.assign(result, { type: ast_node_types_1.AST_NODE_TYPES.ExpressionStatement, expression: convertChild(node.expression) }); break; case SyntaxKind.ThisKeyword: Object.assign(result, { type: ast_node_types_1.AST_NODE_TYPES.ThisExpression }); break; case SyntaxKind.ArrayLiteralExpression: { // TypeScript uses ArrayLiteralExpression in destructuring assignment, too if (config.allowPattern) { Object.assign(result, { type: ast_node_types_1.AST_NODE_TYPES.ArrayPattern, elements: node.elements.map(convertPattern) }); } else { Object.assign(result, { type: ast_node_types_1.AST_NODE_TYPES.ArrayExpression, elements: node.elements.map(convertChild) }); } break; } case SyntaxKind.ObjectLiteralExpression: { // TypeScript uses ObjectLiteralExpression in destructuring assignment, too if (config.allowPattern) { Object.assign(result, { type: ast_node_types_1.AST_NODE_TYPES.ObjectPattern, properties: node.properties.map(convertPattern) }); } else { Object.assign(result, { type: ast_node_types_1.AST_NODE_TYPES.ObjectExpression, properties: node.properties.map(convertChild) }); } break; } case SyntaxKind.PropertyAssignment: Object.assign(result, { type: ast_node_types_1.AST_NODE_TYPES.Property, key: convertChild(node.name), value: converter(node.initializer, config.inTypeMode, config.allowPattern), computed: node_utils_1.isComputedProperty(node.name), method: false, shorthand: false, kind: 'init' }); break; case SyntaxKind.ShorthandPropertyAssignment: { if (node.objectAssignmentInitializer) { Object.assign(result, { type: ast_node_types_1.AST_NODE_TYPES.Property, key: convertChild(node.name), value: { type: ast_node_types_1.AST_NODE_TYPES.AssignmentPattern, left: convertPattern(node.name), right: convertChild(node.objectAssignmentInitializer), loc: result.loc, range: result.range }, computed: false, method: false, shorthand: true, kind: 'init' }); } else { // TODO: this node has no initializer field Object.assign(result, { type: ast_node_types_1.AST_NODE_TYPES.Property, key: convertChild(node.name), value: convertChild(node.initializer || node.name), computed: false, method: false, shorthand: true, kind: 'init' }); } break; } case SyntaxKind.ComputedPropertyName: if (parent.kind === SyntaxKind.ObjectLiteralExpression) { // TODO: ComputedPropertyName has no name field Object.assign(result, { type: ast_node_types_1.AST_NODE_TYPES.Property, key: convertChild(node.name), value: convertChild(node.name), computed: false, method: false, shorthand: true, kind: 'init' }); } else { return convertChild(node.expression); } break; case SyntaxKind.PropertyDeclaration: { const isAbstract = node_utils_1.hasModifier(SyntaxKind.AbstractKeyword, node); Object.assign(result, { type: isAbstract ? ast_node_types_1.AST_NODE_TYPES.TSAbstractClassProperty : ast_node_types_1.AST_NODE_TYPES.ClassProperty, key: convertChild(node.name), value: convertChild(node.initializer), computed: node_utils_1.isComputedProperty(node.name), static: node_utils_1.hasModifier(SyntaxKind.StaticKeyword, node), readonly: node_utils_1.hasModifier(SyntaxKind.ReadonlyKeyword, node) || undefined }); if (node.type) { result.typeAnnotation = convertTypeAnnotation(node.type); } if (node.decorators) { result.decorators = node.decorators.map(convertChild); } const accessibility = node_utils_1.getTSNodeAccessibility(node); if (accessibility) { result.accessibility = accessibility; } if (node.name.kind === SyntaxKind.Identifier && node.questionToken) { result.optional = true; } if (node.exclamationToken) { result.definite = true; } if (result.key.type === ast_node_types_1.AST_NODE_TYPES.Literal && node.questionToken) { result.optional = true; } break; } case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: case SyntaxKind.MethodDeclaration: { const openingParen = node_utils_1.findFirstMatchingToken(node.name, ast, (token) => { if (!token || !token.kind) { return false; } return node_utils_1.getTextForTokenKind(token.kind) === '('; }, ast); const methodLoc = ast.getLineAndCharacterOfPosition(openingParen.getStart(ast)), nodeIsMethod = node.kind === SyntaxKind.MethodDeclaration, method = { type: ast_node_types_1.AST_NODE_TYPES.FunctionExpression, id: null, generator: !!node.asteriskToken, expression: false, async: node_utils_1.hasModifier(SyntaxKind.AsyncKeyword, node), body: convertChild(node.body), range: [node.parameters.pos - 1, result.range[1]], loc: { start: { line: methodLoc.line + 1, column: methodLoc.character }, end: result.loc.end } }; if (node.type) { method.returnType = convertTypeAnnotation(node.type); } if (parent.kind === SyntaxKind.ObjectLiteralExpression) { method.params = node.parameters.map(convertChild); Object.assign(result, { type: ast_node_types_1.AST_NODE_TYPES.Property, key: convertChild(node.name), value: method, computed: node_utils_1.isComputedProperty(node.name), method: nodeIsMethod, shorthand: false, kind: 'init' }); } else { // class /** * Unlike in object literal methods, class method params can have decorators */ method.params = convertParameters(node.parameters); /** * TypeScript class methods can be defined as "abstract" */ const methodDefinitionType = node_utils_1.hasModifier(SyntaxKind.AbstractKeyword, node) ? ast_node_types_1.AST_NODE_TYPES.TSAbstractMethodDefinition : ast_node_types_1.AST_NODE_TYPES.MethodDefinition; Object.assign(result, { type: methodDefinitionType, key: convertChild(node.name), value: method, computed: node_utils_1.isComputedProperty(node.name), static: node_utils_1.hasModifier(SyntaxKind.StaticKeyword, node), kind: 'method' }); if (node.decorators) { result.decorators = node.decorators.map(convertChild); } const accessibility = node_utils_1.getTSNodeAccessibility(node); if (accessibility) { result.accessibility = accessibility; } } if (result.key.type === ast_node_types_1.AST_NODE_TYPES.Identifier && node.questionToken) { result.key.optional = true; } if (node.kind === SyntaxKind.GetAccessor) { result.kind = 'get'; } else if (node.kind === SyntaxKind.SetAccessor) { result.kind = 'set'; } else if (!result.static && node.name.kind === SyntaxKind.StringLiteral && node.name.text === 'constructor' && result.type !== ast_node_types_1.AST_NODE_TYPES.Property) { result.kind = 'constructor'; } // Process typeParameters if (node.typeParameters && node.typeParameters.length) { if (result.type !== ast_node_types_1.AST_NODE_TYPES.Property) { method.typeParameters = convertTSTypeParametersToTypeParametersDeclaration(node.typeParameters); } else { result.typeParameters = convertTSTypeParametersToTypeParametersDeclaration(node.typeParameters); } } break; } // TypeScript uses this even for static methods named "constructor" case SyntaxKind.Constructor: { const lastModifier = node_utils_1.getLastModifier(node); const constructorToken = (lastModifier && node_utils_1.findNextToken(lastModifier, node, ast)) || node.getFirstToken(); const constructorTokenRange = [ constructorToken.getStart(ast), constructorToken.end ]; const constructorLoc = ast.getLineAndCharacterOfPosition(node.parameters.pos - 1); const constructor = { type: ast_node_types_1.AST_NODE_TYPES.FunctionExpression, id: null, params: convertParameters(node.parameters), generator: false, expression: false, async: false, body: convertChild(node.body), range: [node.parameters.pos - 1, result.range[1]], loc: { start: { line: constructorLoc.line + 1, column: constructorLoc.character }, end: result.loc.end } }; // Process typeParameters if (node.typeParameters && node.typeParameters.length) { constructor.typeParameters = convertTSTypeParametersToTypeParametersDeclaration(node.typeParameters); } // Process returnType if (node.type) { constructor.returnType = convertTypeAnnotation(node.type); } const constructorKey = { type: ast_node_types_1.AST_NODE_TYPES.Identifier, name: 'constructor', range: constructorTokenRange, loc: node_utils_1.getLocFor(constructorTokenRange[0], constructorTokenRange[1], ast) }; const isStatic = node_utils_1.hasModifier(SyntaxKind.StaticKeyword, node); Object.assign(result, { type: node_utils_1.hasModifier(SyntaxKind.AbstractKeyword, node) ? ast_node_types_1.AST_NODE_TYPES.TSAbstractMethodDefinition : ast_node_types_1.AST_NODE_TYPES.MethodDefinition, key: constructorKey, value: constructor, computed: false, static: isStatic, kind: isStatic ? 'method' : 'constructor' }); const accessibility = node_utils_1.getTSNodeAccessibility(node); if (accessibility) { result.accessibility = accessibility; } break; } case SyntaxKind.FunctionExpression: Object.assign(result, { type: ast_node_types_1.AST_NODE_TYPES.FunctionExpression, id: convertChild(node.name), generator: !!node.asteriskToken, params: convertParameters(node.parameters), body: convertChild(node.body), async: node_utils_1.hasModifier(SyntaxKind.AsyncKeyword, node), expression: false }); // Process returnType if (node.type) { result.returnType = convertTypeAnnotation(node.type); } // Process typeParameters if (node.typeParameters && node.typeParameters.length) { result.typeParameters = convertTSTypeParametersToTypeParametersDeclaration(node.typeParameters); } break; case SyntaxKind.SuperKeyword: Object.assign(result, { type: ast_node_types_1.AST_NODE_TYPES.Super }); break; case SyntaxKind.ArrayBindingPattern: Object.assign(result, { type: ast_node_types_1.AST_NODE_TYPES.ArrayPattern, elements: node.elements.map(convertPattern) }); break; // occurs with missing array elements like [,] case SyntaxKind.OmittedExpression: return null; case SyntaxKind.ObjectBindingPattern: Object.assign(result, { type: ast_node_types_1.AST_NODE_TYPES.ObjectPattern, properties: node.elements.map(convertPattern) }); break; case SyntaxKind.BindingElement: if (parent.kind === SyntaxKind.ArrayBindingPattern) { const arrayItem = convert({ node: node.name, parent, ast, additionalOptions }); if (node.initializer) { Object.assign(result, { type: ast_node_types_1.AST_NODE_TYPES.AssignmentPattern, left: arrayItem, right: convertChild(node.initializer) }); } else if (node.dotDotDotToken) { Object.assign(result, { type: ast_node_types_1.AST_NODE_TYPES.RestElement, argument: arrayItem }); } else { return arrayItem; } } else if (parent.kind === SyntaxKind.ObjectBindingPattern) { if (node.dotDotDotToken) { Object.assign(result, { type: ast_node_types_1.AST_NODE_TYPES.RestElement, argument: convertChild(node.propertyName || node.name) }); } else { Object.assign(result, { type: ast_node_types_1.AST_NODE_TYPES.Property, key: convertChild(node.propertyName || node.name), value: convertChild(node.name), computed: Boolean(node.propertyName && node.propertyName.kind === SyntaxKind.ComputedPropertyName), method: false, shorthand: !node.propertyName, kind: 'init' }); } if (node.initializer) { result.value = { type: ast_node_types_1.AST_NODE_TYPES.AssignmentPattern, left: convertChild(node.name), right: convertChild(node.initializer), range: [node.name.getStart(ast), node.initializer.end], loc: node_utils_1.getLocFor(node.name.getStart(ast), node.initializer.end, ast) }; } } break; case SyntaxKind.ArrowFunction: Object.assign(result, { type: ast_node_types_1.AST_NODE_TYPES.ArrowFunctionExpression, generator: false, id: null, params: convertParameters(node.parameters), body: convertChild(node.body), async: node_utils_1.hasModifier(SyntaxKind.AsyncKeyword, node), expression: node.body.kind !== SyntaxKind.Block }); // Process returnType if (node.type) { result.returnType = convertTypeAnnotation(node.type); } // Process typeParameters if (node.typeParameters && node.typeParameters.length) { result.typeParameters = convertTSTypeParametersToTypeParametersDeclaration(node.typeParameters); } break; case SyntaxKind.YieldExpression: Object.assign(result, { type: ast_node_types_1.AST_NODE_TYPES.YieldExpression, delegate: !!node.asteriskToken, argument: convertChild(node.expression) }); break; case SyntaxKind.AwaitExpression: Object.assign(result, { type: ast_node_types_1.AST_NODE_TYPES.AwaitExpression, argument: convertChild(node.expression) }); break; // Template Literals case SyntaxKind.NoSubstitutionTemplateLiteral: Object.assign(result, { type: ast_node_types_1.AST_NODE_TYPES.TemplateLiteral, quasis: [ { type: ast_node_types_1.AST_NODE_TYPES.TemplateElement, value: { raw: ast.text.slice(node.getStart(ast) + 1, node.end - 1), cooked: node.text }, tail: true, range: result.range, loc: result.loc } ], expressions: [] }); break; case SyntaxKind.TemplateExpression: Object.assign(result, { type: ast_node_types_1.AST_NODE_TYPES.TemplateLiteral, quasis: [convertChild(node.head)], expressions: [] }); node.templateSpans.forEach((templateSpan) => { result.expressions.push(convertChild(templateSpan.expression)); result.quasis.push(convertChild(templateSpan.literal)); }); break; case SyntaxKind.TaggedTemplateExpression: Object.assign(result, { type: ast_node_types_1.AST_NODE_TYPES.TaggedTemplateExpression, typeParameters: node.typeArguments ? convertTypeArgumentsToTypeParameters(node.typeArguments) : undefined, tag: convertChild(node.tag), quasi: convertChild(node.template) }); break; case SyntaxKind.TemplateHead: case SyntaxKind.TemplateMiddle: case SyntaxKind.TemplateTail: { const tail = node.kind === SyntaxKind.TemplateTail; Object.assign(result, { type: ast_node_types_1.AST_NODE_TYPES.TemplateElement, value: { raw: ast.text.slice(node.getStart(ast) + 1, node.end - (tail ? 1 : 2)), cooked: node.text }, tail }); break; } // Patterns case SyntaxKind.SpreadAssignment: case SyntaxKind.SpreadElement: { if (config.allowPattern) { Object.assign(result, { type: ast_node_types_1.AST_NODE_TYPES.RestElement, argument: convertPattern(node.expression) }); } else { Object.assign(result, { type: ast_node_types_1.AST_NODE_TYPES.SpreadElement, argument: convertChild(node.expression) }); } break; } case SyntaxKind.Parameter: { let parameter; if (node.dotDotDotToken) { Object.assign(result, { type: ast_node_types_1.AST_NODE_TYPES.RestElement, argument: convertChild(node.name) }); parameter = result; } else if (node.initializer) { parameter = convertChild(node.name); Object.assign(result, { type: ast_node_types_1.AST_NODE_TYPES.AssignmentPattern, left: parameter, right: convertChild(node.initializer) }); } else { parameter = result = convert({ node: node.name, parent, ast, additionalOptions }); } if (node.type) { parameter.typeAnnotation = convertTypeAnnotation(node.type); fixTypeAnnotationParentLocation(parameter); } if (node.questionToken) { parameter.optional = true; } if (node.modifiers) { return { type: ast_node_types_1.AST_NODE_TYPES.TSParameterProperty, range: [node.getStart(ast), node.end], loc: node_utils_1.getLoc(node, ast), accessibility: node_utils_1.getTSNodeAccessibility(node) || undefined, readonly: node_utils_1.hasModifier(SyntaxKind.ReadonlyKeyword, node) || undefined, static: node_utils_1.hasModifier(SyntaxKind.StaticKeyword, node) || undefined, export: node_utils_1.hasModifier(SyntaxKind.ExportKeyword, node) || undefined, parameter: result }; } break; } // Classes case SyntaxKind.ClassDeclaration: case SyntaxKind.ClassExpression: { const heritageClauses = node.heritageClauses || []; let classNodeType = SyntaxKind[node.kind]; let lastClassToken = heritageClauses.length ? heritageClauses[heritageClauses.length - 1] : node.name; if (node.typeParameters && node.typeParameters.length) { const lastTypeParameter = node.typeParameters[node.typeParameters.length - 1]; if (!lastClassToken || lastTypeParameter.pos > lastClassToken.pos) { lastClassToken = node_utils_1.findNextToken(lastTypeParameter, ast, ast); } result.typeParameters = convertTSTypeParametersToTypeParametersDeclaration(node.typeParameters); } if (node.modifiers && node.modifiers.length) { /** * TypeScript class declarations can be defined as "abstract" */ if (node.kind === SyntaxKind.ClassDeclaration) { if (node_utils_1.hasModifier(SyntaxKind.AbstractKeyword, node)) { classNodeType = `TSAbstract${classNodeType}`; } } /** * We need check for modifiers, and use the last one, as there * could be multiple before the open brace */ const lastModifie