diff --git a/src/ast-converter.ts b/src/ast-converter.ts index 5ceece0..88710a7 100644 --- a/src/ast-converter.ts +++ b/src/ast-converter.ts @@ -5,7 +5,7 @@ * @copyright jQuery Foundation and other contributors, https://jquery.org/ * MIT License */ -import convert, { getASTMaps, resetASTMaps, convertError } from './convert'; +import Converter, { convertError } from './convert'; import { convertComments } from './convert-comments'; import nodeUtils from './node-utils'; import ts from 'typescript'; @@ -27,16 +27,12 @@ export default ( /** * Recursively convert the TypeScript AST into an ESTree-compatible AST */ - const estree: any = convert({ - node: ast, - parent: null, - ast, - additionalOptions: { - errorOnUnknownASTType: extra.errorOnUnknownASTType || false, - useJSXTextNode: extra.useJSXTextNode || false, - shouldProvideParserServices - } + const instance = new Converter(ast, { + errorOnUnknownASTType: extra.errorOnUnknownASTType || false, + useJSXTextNode: extra.useJSXTextNode || false, + shouldProvideParserServices }); + const estree: any = instance.convertProgram(ast); /** * Optionally convert and include all tokens in the AST @@ -54,8 +50,7 @@ export default ( let astMaps = undefined; if (shouldProvideParserServices) { - astMaps = getASTMaps(); - resetASTMaps(); + astMaps = instance.getASTMaps(); } return { estree, astMaps }; diff --git a/src/convert-base.ts b/src/convert-base.ts new file mode 100644 index 0000000..899bf7c --- /dev/null +++ b/src/convert-base.ts @@ -0,0 +1,511 @@ +/** + * @fileoverview Converts TypeScript AST into ESTree format. + * @author Nicholas C. Zakas + * @author James Henry + * @copyright jQuery Foundation and other contributors, https://jquery.org/ + * MIT License + */ +import ts from 'typescript'; +import nodeUtils from './node-utils'; +import { AST_NODE_TYPES } from './ast-node-types'; +import { ESTreeNode } from './temp-types-based-on-js-source'; + +const SyntaxKind = ts.SyntaxKind; + +export interface ConvertAdditionalOptions { + errorOnUnknownASTType: boolean; + useJSXTextNode: boolean; + shouldProvideParserServices: boolean; +} + +export abstract class AbstractConverter { + protected ast: ts.SourceFile; + protected additionalOptions: ConvertAdditionalOptions; + public esTreeNodeToTSNodeMap = new WeakMap(); + public tsNodeToESTreeNodeMap = new WeakMap(); + + [key: number]: ( + node: any, + parent: ts.Node, + inTypeMode?: boolean + ) => ESTreeNode | null; + + constructor(ast: ts.SourceFile, additionalOptions: ConvertAdditionalOptions) { + this.ast = ast; + this.additionalOptions = additionalOptions; + } + + public convertProgram(node: ts.SourceFile) { + return this.convert(node); + } + + public getASTMaps() { + return { + esTreeNodeToTSNodeMap: this.esTreeNodeToTSNodeMap, + tsNodeToESTreeNodeMap: this.tsNodeToESTreeNodeMap + }; + } + + /** + * Converts a TypeScript node into an ESTree node. + * @param node the child ts.Node + * @param parent parentNode + * @returns the converted ESTree node + */ + protected convertType(node?: ts.Node, parent?: ts.Node): ESTreeNode | null { + return this.convert(node, parent, true); + } + + /** + * Converts a TypeScript node into an ESTree node. + * @param node the child ts.Node + * @param parent parentNode + * @param inTypeMode flag to determine if we are in typeMode + * @returns the converted ESTree node + */ + protected convert( + node?: ts.Node, + parent?: ts.Node, + inTypeMode?: boolean + ): ESTreeNode | null { + if (!node) { + return null; + } + + let result: ESTreeNode | null = null; + + if (node.kind in this) { + result = this[node.kind].call( + this, + node, + parent || node.parent, + inTypeMode + ); + } else { + result = this.deeplyCopy(node); + } + + if (result && this.additionalOptions.shouldProvideParserServices) { + this.tsNodeToESTreeNodeMap.set(node, result); + this.esTreeNodeToTSNodeMap.set(result, node); + } + return result; + } + + getRange(node: ts.Node): [number, number] { + return [node.getStart(this.ast), node.getEnd()]; + } + + createNode(node: ts.Node, data: any): ESTreeNode { + const result: ESTreeNode = data; + if (!result.range) { + result.range = this.getRange(node); + } + if (!result.loc) { + result.loc = nodeUtils.getLoc(node, this.ast); + } + + return result; + } + + /** + * Converts a ts.Node's typeArguments ts.NodeArray to a flow-like typeParameters node + * @param {ts.NodeArray} typeArguments ts.Node typeArguments + * @returns {ESTreeNode} TypeParameterInstantiation node + */ + convertTypeArgumentsToTypeParameters( + typeArguments: ts.NodeArray + ): ESTreeNode { + /** + * 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 = nodeUtils.findNextToken( + lastTypeArgument, + this.ast, + this.ast + ); + end = greaterThanToken!.end; + } + } + return { + type: AST_NODE_TYPES.TSTypeParameterInstantiation, + range: [start, end], + loc: nodeUtils.getLocFor(start, end, this.ast), + params: typeArguments.map(typeArgument => this.convertType(typeArgument)) + }; + } + + createSimpleNode(node: ts.Node, name: string): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES[name] + }); + } + + /** + * 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 + */ + convertTSTypeParametersToTypeParametersDeclaration( + typeParameters: ts.NodeArray + ): ESTreeNode { + const firstTypeParameter = typeParameters[0]; + const lastTypeParameter = typeParameters[typeParameters.length - 1]; + + const greaterThanToken = nodeUtils.findNextToken( + lastTypeParameter, + this.ast, + this.ast + ); + + return { + type: AST_NODE_TYPES.TSTypeParameterDeclaration, + range: [firstTypeParameter.pos - 1, greaterThanToken!.end], + loc: nodeUtils.getLocFor( + firstTypeParameter.pos - 1, + greaterThanToken!.end, + this.ast + ), + params: typeParameters.map(typeParameter => + this.convertType(typeParameter) + ) + }; + } + + /** + * Coverts body ExpressionStatements to directives + */ + convertBodyExpressionsToDirectives( + node: ts.Node, + body: (ESTreeNode | null)[] + ) { + if (body && nodeUtils.canContainDirective(node)) { + const unique: string[] = []; + + // directives has to be unique, if directive is registered twice pick only first one + body + .filter( + child => + child && + child.type === AST_NODE_TYPES.ExpressionStatement && + child.expression && + child.expression.type === AST_NODE_TYPES.Literal && + (child.expression as any).value && + typeof (child.expression as any).value === 'string' + ) + .forEach(child => { + if (!unique.includes((child!.expression as any).raw)) { + child!.directive = (child!.expression as any).raw.slice(1, -1); + unique.push((child!.expression as any).raw); + } + }); + } + return body; + } + + /** + * 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. + */ + convertTypeAnnotation(child: ts.TypeNode): ESTreeNode { + const annotationStartCol = child.getFullStart() - 1; + return { + type: AST_NODE_TYPES.TSTypeAnnotation, + loc: nodeUtils.getLocFor(annotationStartCol, child.end, this.ast), + range: [annotationStartCol, child.end], + typeAnnotation: this.convertType(child as any) + }; + } + + /** + * Converts an array of ts.Node parameters into an array of ESTreeNode params + * @param {ts.NodeArray} parameters An array of ts.Node params to be converted + * @returns {ESTreeNode[]} an array of converted ESTreeNode params + */ + convertParameters(parameters: ts.NodeArray): ESTreeNode[] { + if (!parameters || !parameters.length) { + return []; + } + return parameters.map(param => { + const convertedParam = this.convert(param) as ESTreeNode; + if (!param.decorators || !param.decorators.length) { + return convertedParam; + } + return Object.assign(convertedParam, { + decorators: this.convertDecorators(param.decorators) + }); + }); + } + + /** + * Converts a ts.NodeArray of ts.Decorators into an array of ESTreeNode decorators + * @param {ts.NodeArray} decorators A ts.NodeArray of ts.Decorators to be converted + * @returns {ESTreeNode[]} an array of converted ESTreeNode decorators + */ + convertDecorators(decorators: ts.NodeArray): ESTreeNode[] { + if (!decorators || !decorators.length) { + return []; + } + return decorators.map(decorator => { + return this.createNode(decorator, { + type: AST_NODE_TYPES.Decorator, + expression: this.convert(decorator.expression) + }); + }); + } + + /** + * Converts a child into a interface heritage node. + * @param {ts.ExpressionWithTypeArguments} child The TypeScript AST node to convert. + * @returns {ESTreeNode} The type annotation node. + */ + convertInterfaceHeritageClause( + child: ts.ExpressionWithTypeArguments + ): ESTreeNode { + const id = this.convert(child.expression)!; + const classImplementsNode: ESTreeNode = { + type: AST_NODE_TYPES.TSInterfaceHeritage, + loc: id.loc, + range: id.range, + id + }; + + if (child.typeArguments && child.typeArguments.length) { + classImplementsNode.typeParameters = this.convertTypeArgumentsToTypeParameters( + child.typeArguments + ); + } + return classImplementsNode; + } + + /** + * 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 {ts.Node} node Node to be fixed + * @param {ESTreeNode} typeAnnotationParent The node that will have its location data mutated + * @returns {void} + */ + fixTypeAnnotationParentLocation( + node: ts.Node, + typeAnnotationParent: ESTreeNode + ): void { + typeAnnotationParent.range[1] = (node as any).type.getEnd(); + typeAnnotationParent.loc = nodeUtils.getLocFor( + typeAnnotationParent.range[0], + typeAnnotationParent.range[1], + this.ast + ); + } + + /** + * Converts a TypeScript JSX node.tagName into an ESTree node.name + * @param {ts.Node} node Node to be processed + * @param {ts.JsxTagNameExpression} tagName the tagName object from a JSX ts.Node + * @returns {Object} the converted ESTree name object + */ + convertTypeScriptJSXTagNameToESTreeName( + node: ts.Node, + tagName: ts.JsxTagNameExpression + ): ESTreeNode { + const tagNameToken = nodeUtils.convertToken(tagName, this.ast); + + if (tagNameToken.type === AST_NODE_TYPES.JSXMemberExpression) { + const isNestedMemberExpression = + (node as any).tagName.expression.kind === + SyntaxKind.PropertyAccessExpression; + + // Convert TSNode left and right objects into ESTreeNode object + // and property objects + tagNameToken.object = this.convert( + (node as any).tagName.expression, + node + ); + tagNameToken.property = this.convert((node as any).tagName.name, node); + + // Assign the appropriate types + tagNameToken.object.type = isNestedMemberExpression + ? AST_NODE_TYPES.JSXMemberExpression + : AST_NODE_TYPES.JSXIdentifier; + tagNameToken.property.type = AST_NODE_TYPES.JSXIdentifier; + if ((tagName as any).expression.kind === SyntaxKind.ThisKeyword) { + tagNameToken.object.name = 'this'; + } + } else { + tagNameToken.type = AST_NODE_TYPES.JSXIdentifier; + tagNameToken.name = tagNameToken.value; + } + + delete tagNameToken.value; + + return tagNameToken; + } + + /** + * Applies the given TS modifiers to the given result object. + * @param {ESTreeNode} result + * @param {ts.ModifiersArray} modifiers original ts.Nodes from the node.modifiers array + * @returns {void} the current result object will be mutated + * @deprecated + */ + applyModifiersToResult( + result: ESTreeNode, + modifiers?: ts.ModifiersArray + ): void { + 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: { [key: number]: boolean } = {}; + 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(el => this.convert(el)); + } + + /** + * Converts a child into a class implements node. This creates an intermediary + * ClassImplements node to match what Flow does. + * @param {ts.ExpressionWithTypeArguments} child The TypeScript AST node to convert. + * @returns {ESTreeNode} The type annotation node. + */ + convertClassImplements(child: ts.ExpressionWithTypeArguments): ESTreeNode { + const id = this.convert(child.expression) as ESTreeNode; + const classImplementsNode: ESTreeNode = { + type: AST_NODE_TYPES.ClassImplements, + loc: id.loc, + range: id.range, + id + }; + if (child.typeArguments && child.typeArguments.length) { + classImplementsNode.typeParameters = this.convertTypeArgumentsToTypeParameters( + child.typeArguments + ); + } + return classImplementsNode; + } + + /** + * 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} + * @deprecated + */ + deeplyCopy(node: ts.Node): ESTreeNode { + 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 ( + this.additionalOptions.errorOnUnknownASTType && + !AST_NODE_TYPES[customType] + ) { + throw new Error(`Unknown AST_NODE_TYPE: "${customType}"`); + } + const result = this.createNode(node, { + 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 as any).type + ? this.convertTypeAnnotation((node as any).type) + : null; + } else if (key === 'typeArguments') { + result.typeParameters = (node as any).typeArguments + ? this.convertTypeArgumentsToTypeParameters( + (node as any).typeArguments + ) + : null; + } else if (key === 'typeParameters') { + result.typeParameters = (node as any).typeParameters + ? this.convertTSTypeParametersToTypeParametersDeclaration( + (node as any).typeParameters + ) + : null; + } else if (key === 'decorators') { + const decorators = this.convertDecorators((node as any).decorators); + if (decorators && decorators.length) { + result.decorators = decorators; + } + } else { + if (Array.isArray((node as any)[key])) { + (result as any)[key] = (node as any)[key].map((el: any) => + this.convert(el) + ); + } else if ( + (node as any)[key] && + typeof (node as any)[key] === 'object' && + (node as any)[key].kind + ) { + // need to check node[key].kind to ensure we don't try to convert a symbol + (result as any)[key] = this.convert((node as any)[key]); + } else { + (result as any)[key] = (node as any)[key]; + } + } + }); + return result; + } +} diff --git a/src/convert.ts b/src/convert.ts index f03ae79..8383752 100644 --- a/src/convert.ts +++ b/src/convert.ts @@ -8,37 +8,11 @@ import ts from 'typescript'; import nodeUtils from './node-utils'; import { AST_NODE_TYPES } from './ast-node-types'; +import { AbstractConverter } from './convert-base'; import { ESTreeNode } from './temp-types-based-on-js-source'; -import { TSNode } from './ts-nodes'; const SyntaxKind = ts.SyntaxKind; -let esTreeNodeToTSNodeMap = new WeakMap(); -let tsNodeToESTreeNodeMap = new WeakMap(); - -export function resetASTMaps() { - esTreeNodeToTSNodeMap = new WeakMap(); - tsNodeToESTreeNodeMap = new WeakMap(); -} - -export function getASTMaps() { - return { esTreeNodeToTSNodeMap, tsNodeToESTreeNodeMap }; -} - -interface ConvertAdditionalOptions { - errorOnUnknownASTType: boolean; - useJSXTextNode: boolean; - shouldProvideParserServices: boolean; -} - -interface ConvertConfig { - node: ts.Node; - parent?: ts.Node | null; - inTypeMode?: boolean; - ast: ts.SourceFile; - additionalOptions: ConvertAdditionalOptions; -} - /** * Extends and formats a given error object * @param {Object} error the error object @@ -52,2653 +26,2304 @@ export function convertError(error: any) { ); } -/** - * 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 - */ -export default function convert(config: ConvertConfig): ESTreeNode | null { - const node: TSNode = config.node as TSNode; - const parent = config.parent; - const ast = config.ast; - const additionalOptions = config.additionalOptions || {}; +export default class Converter extends AbstractConverter { + [SyntaxKind.SourceFile](node: ts.SourceFile): ESTreeNode { + const result = this.createNode(node, { + type: AST_NODE_TYPES.Program, + body: [], + // externalModuleIndicator is internal field in TSC + sourceType: (node as any).externalModuleIndicator ? 'module' : 'script' + }); - /** - * Exit early for null and undefined - */ - if (!node) { - return null; + // filter out unknown nodes for now + node.statements.forEach(statement => { + const convertedStatement = this.convert(statement); + if (convertedStatement) { + result.body.push(convertedStatement); + } + }); + + result.body = this.convertBodyExpressionsToDirectives(node, result.body); + + result.range[1] = node.endOfFileToken.end; + result.loc = nodeUtils.getLocFor( + node.getStart(this.ast), + result.range[1], + this.ast + ); + return result; } - /** - * Create a new ESTree node - */ - let result: ESTreeNode = { - type: '', - range: [node.getStart(ast), node.end], - loc: nodeUtils.getLoc(node, ast) - }; + [SyntaxKind.Block](node: ts.Block): ESTreeNode { + const result = this.createNode(node, { + type: AST_NODE_TYPES.BlockStatement, + body: node.statements.map(el => this.convert(el)) + }); - function converter(child?: ts.Node, inTypeMode?: boolean): ESTreeNode | null { - if (!child) { - return null; - } - return convert({ - node: child, - parent: node, - inTypeMode, - ast, - additionalOptions + result.body = this.convertBodyExpressionsToDirectives(node, result.body); + return result; + } + + [SyntaxKind.Identifier](node: ts.Identifier): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.Identifier, + name: node.text }); } - /** - * 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?: ts.Node): ESTreeNode | null { - return converter(child, config.inTypeMode); + [SyntaxKind.WithStatement](node: ts.WithStatement): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.WithStatement, + object: this.convert(node.expression), + body: this.convert(node.statement) + }); } - /** - * 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?: ts.Node): ESTreeNode | null { - return converter(child, true); + // Control Flow + + [SyntaxKind.ReturnStatement](node: ts.ReturnStatement): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.ReturnStatement, + argument: this.convert(node.expression) + }); } - /** - * 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: ts.TypeNode): ESTreeNode { - const annotation = convertChildType(child); - const annotationStartCol = child.getFullStart() - 1; - const loc = nodeUtils.getLocFor(annotationStartCol, child.end, ast); - return { - type: AST_NODE_TYPES.TSTypeAnnotation, - loc, - range: [annotationStartCol, child.end], - typeAnnotation: annotation - }; + [SyntaxKind.LabeledStatement](node: ts.LabeledStatement): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.LabeledStatement, + label: this.convert(node.label), + body: this.convert(node.statement) + }); } - /** - * Coverts body ExpressionStatements to directives - */ - function convertBodyExpressionsToDirectives() { - if (result.body && nodeUtils.canContainDirective(node)) { - const unique: string[] = []; - - // directives has to be unique, if directive is registered twice pick only first one - result.body - .filter( - (child: ESTreeNode) => - child.type === AST_NODE_TYPES.ExpressionStatement && - child.expression && - child.expression.type === AST_NODE_TYPES.Literal && - (child.expression as any).value && - typeof (child.expression as any).value === 'string' - ) - .forEach( - (child: { directive: string; expression: { raw: string } }) => { - if (!unique.includes((child.expression as any).raw)) { - child.directive = child.expression.raw.slice(1, -1); - unique.push(child.expression.raw); - } - } - ); - } + [SyntaxKind.BreakStatement](node: ts.BreakStatement): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.BreakStatement, + label: this.convert(node.label) + }); } - /** - * Converts a ts.Node's typeArguments ts.NodeArray to a flow-like typeParameters node - * @param {ts.NodeArray} typeArguments ts.Node typeArguments - * @returns {ESTreeNode} TypeParameterInstantiation node - */ - function convertTypeArgumentsToTypeParameters( - typeArguments: ts.NodeArray - ): ESTreeNode { - /** - * 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 = nodeUtils.findNextToken( - lastTypeArgument, - ast, - ast - ); - end = greaterThanToken!.end; - } - } - return { - type: AST_NODE_TYPES.TSTypeParameterInstantiation, - range: [start, end], - loc: nodeUtils.getLocFor(start, end, ast), - params: typeArguments.map(typeArgument => convertChildType(typeArgument)) - }; + [SyntaxKind.ContinueStatement](node: ts.ContinueStatement): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.ContinueStatement, + label: this.convert(node.label) + }); } - /** - * 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: ts.NodeArray - ): ESTreeNode { - const firstTypeParameter = typeParameters[0]; - const lastTypeParameter = typeParameters[typeParameters.length - 1]; + // Choice - const greaterThanToken = nodeUtils.findNextToken( - lastTypeParameter, - ast, - ast - ); + [SyntaxKind.IfStatement](node: ts.IfStatement): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.IfStatement, + test: this.convert(node.expression), + consequent: this.convert(node.thenStatement), + alternate: this.convert(node.elseStatement) + }); + } - return { - type: AST_NODE_TYPES.TSTypeParameterDeclaration, - range: [firstTypeParameter.pos - 1, greaterThanToken!.end], - loc: nodeUtils.getLocFor( - firstTypeParameter.pos - 1, - greaterThanToken!.end, - ast - ), - params: typeParameters.map(typeParameter => - convertChildType(typeParameter) - ) - }; + [SyntaxKind.SwitchStatement](node: ts.SwitchStatement): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.SwitchStatement, + discriminant: this.convert(node.expression), + cases: node.caseBlock.clauses.map(el => this.convert(el)) + }); } - /** - * Converts a child into a class implements node. This creates an intermediary - * ClassImplements node to match what Flow does. - * @param {ts.ExpressionWithTypeArguments} child The TypeScript AST node to convert. - * @returns {ESTreeNode} The type annotation node. - */ - function convertClassImplements( - child: ts.ExpressionWithTypeArguments - ): ESTreeNode { - const id = convertChild(child.expression) as ESTreeNode; - const classImplementsNode: ESTreeNode = { - type: AST_NODE_TYPES.ClassImplements, - loc: id.loc, - range: id.range, - id - }; - if (child.typeArguments && child.typeArguments.length) { - classImplementsNode.typeParameters = convertTypeArgumentsToTypeParameters( - child.typeArguments - ); - } - return classImplementsNode; + [SyntaxKind.CaseClause](node: ts.CaseClause): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.SwitchCase, + // expression is present in case only + test: this.convert(node.expression), + consequent: node.statements.map(el => this.convert(el)) + }); } - /** - * Converts a child into a interface heritage node. - * @param {ts.ExpressionWithTypeArguments} child The TypeScript AST node to convert. - * @returns {ESTreeNode} The type annotation node. - */ - function convertInterfaceHeritageClause( - child: ts.ExpressionWithTypeArguments - ): ESTreeNode { - const id = convertChild(child.expression) as ESTreeNode; - const classImplementsNode: ESTreeNode = { - type: AST_NODE_TYPES.TSInterfaceHeritage, - loc: id.loc, - range: id.range, - id - }; + [SyntaxKind.DefaultClause](node: ts.DefaultClause): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.SwitchCase, + // expression is present in case only + test: null, + consequent: node.statements.map(el => this.convert(el)) + }); + } - if (child.typeArguments && child.typeArguments.length) { - classImplementsNode.typeParameters = convertTypeArgumentsToTypeParameters( - child.typeArguments - ); - } - return classImplementsNode; + // Exceptions + + [SyntaxKind.ThrowStatement](node: ts.ThrowStatement): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.ThrowStatement, + argument: this.convert(node.expression) + }); } - /** - * Converts a ts.NodeArray of ts.Decorators into an array of ESTreeNode decorators - * @param {ts.NodeArray} decorators A ts.NodeArray of ts.Decorators to be converted - * @returns {ESTreeNode[]} an array of converted ESTreeNode decorators - */ - function convertDecorators( - decorators: ts.NodeArray - ): ESTreeNode[] { - if (!decorators || !decorators.length) { - return []; - } - return decorators.map(decorator => { - const expression = convertChild(decorator.expression); - return { - type: AST_NODE_TYPES.Decorator, - range: [decorator.getStart(ast), decorator.end], - loc: nodeUtils.getLoc(decorator, ast), - expression - }; + [SyntaxKind.TryStatement](node: ts.TryStatement): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.TryStatement, + block: this.convert(node.tryBlock), + handler: this.convert(node.catchClause), + finalizer: this.convert(node.finallyBlock) }); } - /** - * 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: ts.NodeArray): ESTreeNode[] { - if (!parameters || !parameters.length) { - return []; - } - return parameters.map(param => { - const convertedParam = convertChild(param) as ESTreeNode; - if (!param.decorators || !param.decorators.length) { - return convertedParam; - } - return Object.assign(convertedParam, { - decorators: convertDecorators(param.decorators) - }); + [SyntaxKind.CatchClause](node: ts.CatchClause): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.CatchClause, + param: node.variableDeclaration + ? this.convert(node.variableDeclaration.name) + : null, + body: this.convert(node.block) }); } - /** - * 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(): void { - 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[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 as any).type - ? convertTypeAnnotation((node as any).type) - : null; - } else if (key === 'typeArguments') { - result.typeParameters = (node as any).typeArguments - ? convertTypeArgumentsToTypeParameters((node as any).typeArguments) - : null; - } else if (key === 'typeParameters') { - result.typeParameters = (node as any).typeParameters - ? convertTSTypeParametersToTypeParametersDeclaration( - (node as any).typeParameters - ) - : null; - } else if (key === 'decorators') { - const decorators = convertDecorators((node as any).decorators); - if (decorators && decorators.length) { - result.decorators = decorators; - } - } else { - if (Array.isArray((node as any)[key])) { - (result as any)[key] = (node as any)[key].map(convertChild); - } else if ( - (node as any)[key] && - typeof (node as any)[key] === 'object' && - (node as any)[key].kind - ) { - // need to check node[key].kind to ensure we don't try to convert a symbol - (result as any)[key] = convertChild((node as any)[key]); - } else { - (result as any)[key] = (node as any)[key]; - } - } - }); + // Loops + + [SyntaxKind.WhileStatement](node: ts.WhileStatement): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.WhileStatement, + test: this.convert(node.expression), + body: this.convert(node.statement) + }); } /** - * 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 + * Unlike other parsers, TypeScript calls a "DoWhileStatement" + * a "DoStatement" */ - function convertTypeScriptJSXTagNameToESTreeName( - tagName: ts.JsxTagNameExpression - ): ESTreeNode { - const tagNameToken = nodeUtils.convertToken(tagName, ast); + [SyntaxKind.DoStatement](node: ts.DoStatement): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.DoWhileStatement, + test: this.convert(node.expression), + body: this.convert(node.statement) + }); + } - if (tagNameToken.type === AST_NODE_TYPES.JSXMemberExpression) { - const isNestedMemberExpression = - (node as any).tagName.expression.kind === - SyntaxKind.PropertyAccessExpression; + [SyntaxKind.ForStatement](node: ts.ForStatement): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.ForStatement, + init: this.convert(node.initializer), + test: this.convert(node.condition), + update: this.convert(node.incrementor), + body: this.convert(node.statement) + }); + } - // Convert TSNode left and right objects into ESTreeNode object - // and property objects - tagNameToken.object = convertChild((node as any).tagName.expression); - tagNameToken.property = convertChild((node as any).tagName.name); + [SyntaxKind.ForInStatement](node: ts.ForInStatement): ESTreeNode { + return this.createNode(node, { + type: SyntaxKind[node.kind], + left: this.convert(node.initializer), + right: this.convert(node.expression), + body: this.convert(node.statement) + }); + } - // Assign the appropriate types - tagNameToken.object.type = isNestedMemberExpression - ? AST_NODE_TYPES.JSXMemberExpression - : AST_NODE_TYPES.JSXIdentifier; - tagNameToken.property.type = AST_NODE_TYPES.JSXIdentifier; - if ((tagName as any).expression.kind === SyntaxKind.ThisKeyword) { - tagNameToken.object.name = 'this'; - } - } else { - tagNameToken.type = AST_NODE_TYPES.JSXIdentifier; - tagNameToken.name = tagNameToken.value; - } + [SyntaxKind.ForOfStatement](node: ts.ForOfStatement): ESTreeNode { + return this.createNode(node, { + type: SyntaxKind[node.kind], + left: this.convert(node.initializer), + right: this.convert(node.expression), + body: this.convert(node.statement), + // await is only available in for of statement + await: Boolean( + node.awaitModifier && + node.awaitModifier.kind === SyntaxKind.AwaitKeyword + ) + }); + } - delete tagNameToken.value; + // Declarations - return tagNameToken; - } + [SyntaxKind.FunctionDeclaration](node: ts.FunctionDeclaration): ESTreeNode { + let functionDeclarationType = AST_NODE_TYPES.FunctionDeclaration; + if (nodeUtils.hasModifier(SyntaxKind.DeclareKeyword, node)) { + functionDeclarationType = AST_NODE_TYPES.TSDeclareFunction; + } - /** - * 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?: ts.ModifiersArray): void { - if (!modifiers || !modifiers.length) { - return; + const result = this.createNode(node, { + type: functionDeclarationType, + id: this.convert(node.name), + generator: !!node.asteriskToken, + expression: false, + async: nodeUtils.hasModifier(SyntaxKind.AsyncKeyword, node), + params: this.convertParameters(node.parameters), + body: this.convert(node.body) || undefined + }); + + // Process returnType + if (node.type) { + (result as any).returnType = this.convertTypeAnnotation(node.type); } - /** - * 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: { [key: number]: boolean } = {}; - 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 (functionDeclarationType === AST_NODE_TYPES.TSDeclareFunction) { + result.declare = true; } - /** - * 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; + + // Process typeParameters + if (node.typeParameters && node.typeParameters.length) { + result.typeParameters = this.convertTSTypeParametersToTypeParametersDeclaration( + node.typeParameters + ); } - 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: ESTreeNode - ): void { - typeAnnotationParent.range[1] = (node as any).type.getEnd(); - typeAnnotationParent.loc = nodeUtils.getLocFor( - typeAnnotationParent.range[0], - typeAnnotationParent.range[1], - ast - ); + // check for exports + return nodeUtils.fixExports(node, result, this.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.Program, - body: [], - // externalModuleIndicator is internal field in TSC - sourceType: (node as any).externalModuleIndicator ? 'module' : 'script' - }); + [SyntaxKind.VariableDeclaration](node: ts.VariableDeclaration): ESTreeNode { + const result = this.createNode(node, { + type: AST_NODE_TYPES.VariableDeclarator, + id: this.convert(node.name), + init: this.convert(node.initializer) + }); - // filter out unknown nodes for now - node.statements.forEach((statement: any) => { - const convertedStatement = convertChild(statement); - if (convertedStatement) { - result.body.push(convertedStatement); - } - }); + if (node.exclamationToken) { + (result as any).definite = true; + } - convertBodyExpressionsToDirectives(); + if (node.type) { + (result as any).id.typeAnnotation = this.convertTypeAnnotation(node.type); + this.fixTypeAnnotationParentLocation(node, (result as any).id); + } + return result; + } - (result as any).range[1] = node.endOfFileToken.end; - result.loc = nodeUtils.getLocFor( - node.getStart(ast), - (result as any).range[1], - ast - ); - break; + [SyntaxKind.VariableStatement](node: ts.VariableStatement): ESTreeNode { + const result = this.createNode(node, { + type: AST_NODE_TYPES.VariableDeclaration, + declarations: node.declarationList.declarations.map(el => + this.convert(el) + ), + kind: nodeUtils.getDeclarationKind(node.declarationList) + }); - case SyntaxKind.Block: - Object.assign(result, { - type: AST_NODE_TYPES.BlockStatement, - body: node.statements.map(convertChild) - }); + if (nodeUtils.hasModifier(SyntaxKind.DeclareKeyword, node)) { + result.declare = true; + } - convertBodyExpressionsToDirectives(); - break; + // check for exports + return nodeUtils.fixExports(node, result, this.ast); + } - case SyntaxKind.Identifier: - Object.assign(result, { - type: AST_NODE_TYPES.Identifier, - name: node.text - }); - break; + // mostly for for-of, for-in + [SyntaxKind.VariableDeclarationList]( + node: ts.VariableDeclarationList + ): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.VariableDeclaration, + declarations: node.declarations.map(el => this.convert(el)), + kind: nodeUtils.getDeclarationKind(node) + }); + } - case SyntaxKind.WithStatement: - Object.assign(result, { - type: AST_NODE_TYPES.WithStatement, - object: convertChild(node.expression), - body: convertChild(node.statement) - }); - break; + // Expressions - // Control Flow + [SyntaxKind.ExpressionStatement](node: ts.ExpressionStatement): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.ExpressionStatement, + expression: this.convert(node.expression) + }); + } - case SyntaxKind.ReturnStatement: - Object.assign(result, { - type: AST_NODE_TYPES.ReturnStatement, - argument: convertChild(node.expression) - }); - break; + [SyntaxKind.ThisKeyword](node: ts.ThisExpression): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.ThisExpression + }); + } - case SyntaxKind.LabeledStatement: - Object.assign(result, { - type: AST_NODE_TYPES.LabeledStatement, - label: convertChild(node.label), - body: convertChild(node.statement) - }); - break; + [SyntaxKind.ArrayLiteralExpression]( + node: ts.ArrayLiteralExpression + ): ESTreeNode { + const arrayAssignNode = nodeUtils.findAncestorOfKind( + node, + SyntaxKind.BinaryExpression + ); + const arrayIsInForOf = + node.parent && node.parent.kind === SyntaxKind.ForOfStatement; + const arrayIsInForIn = + node.parent && node.parent.kind === SyntaxKind.ForInStatement; + let arrayIsInAssignment; + + if (arrayAssignNode) { + if (node.parent.kind === SyntaxKind.ShorthandPropertyAssignment) { + arrayIsInAssignment = false; + } else if (node.parent.kind === SyntaxKind.CallExpression) { + arrayIsInAssignment = false; + } else if ( + nodeUtils.getBinaryExpressionType( + (arrayAssignNode as any).operatorToken + ) === AST_NODE_TYPES.AssignmentExpression + ) { + arrayIsInAssignment = + nodeUtils.findChildOfKind( + (arrayAssignNode as any).left, + SyntaxKind.ArrayLiteralExpression, + this.ast + ) === node || (arrayAssignNode as any).left === node; + } else { + arrayIsInAssignment = false; + } + } - case SyntaxKind.BreakStatement: - case SyntaxKind.ContinueStatement: - Object.assign(result, { - type: SyntaxKind[node.kind], - label: convertChild(node.label) + // TypeScript uses ArrayLiteralExpression in destructuring assignment, too + if (arrayIsInAssignment || arrayIsInForOf || arrayIsInForIn) { + return this.createNode(node, { + type: AST_NODE_TYPES.ArrayPattern, + elements: node.elements.map(el => this.convert(el)) }); - break; - - // Choice - - case SyntaxKind.IfStatement: - Object.assign(result, { - type: AST_NODE_TYPES.IfStatement, - test: convertChild(node.expression), - consequent: convertChild(node.thenStatement), - alternate: convertChild(node.elseStatement) + } else { + return this.createNode(node, { + type: AST_NODE_TYPES.ArrayExpression, + elements: node.elements.map(el => this.convert(el)) }); - break; + } + } - case SyntaxKind.SwitchStatement: - Object.assign(result, { - type: AST_NODE_TYPES.SwitchStatement, - discriminant: convertChild(node.expression), - cases: node.caseBlock.clauses.map(convertChild) - }); - break; + [SyntaxKind.ObjectLiteralExpression]( + node: ts.ObjectLiteralExpression + ): ESTreeNode { + const ancestorNode = nodeUtils.findFirstMatchingAncestor( + node, + parentNode => + parentNode.kind === SyntaxKind.BinaryExpression || + parentNode.kind === SyntaxKind.ArrowFunction + ); + const objectAssignNode = + ancestorNode && + ancestorNode.kind === SyntaxKind.BinaryExpression && + (ancestorNode as any).operatorToken.kind === SyntaxKind.FirstAssignment + ? ancestorNode + : null; + + let objectIsInAssignment = false; + + if (objectAssignNode) { + if (node.parent.kind === SyntaxKind.ShorthandPropertyAssignment) { + objectIsInAssignment = false; + } else if ((objectAssignNode as any).left === node) { + objectIsInAssignment = true; + } else if (node.parent.kind === SyntaxKind.CallExpression) { + objectIsInAssignment = false; + } else { + objectIsInAssignment = + nodeUtils.findChildOfKind( + (objectAssignNode as any).left, + SyntaxKind.ObjectLiteralExpression, + this.ast + ) === node; + } + } - case SyntaxKind.CaseClause: - case SyntaxKind.DefaultClause: - Object.assign(result, { - type: AST_NODE_TYPES.SwitchCase, - // expression is present in case only - test: - node.kind === SyntaxKind.CaseClause - ? convertChild(node.expression) - : null, - consequent: node.statements.map(convertChild) + // TypeScript uses ObjectLiteralExpression in destructuring assignment, too + if (objectIsInAssignment) { + return this.createNode(node, { + type: AST_NODE_TYPES.ObjectPattern, + properties: node.properties.map(el => this.convert(el)) + }); + } else { + return this.createNode(node, { + type: AST_NODE_TYPES.ObjectExpression, + properties: node.properties.map(el => this.convert(el)) }); - break; + } + } - // Exceptions + [SyntaxKind.PropertyAssignment](node: ts.PropertyAssignment): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.Property, + key: this.convert(node.name), + value: this.convert(node.initializer), + computed: nodeUtils.isComputedProperty(node.name), + method: false, + shorthand: false, + kind: 'init' + }); + } - case SyntaxKind.ThrowStatement: - Object.assign(result, { - type: AST_NODE_TYPES.ThrowStatement, - argument: convertChild(node.expression) + [SyntaxKind.ShorthandPropertyAssignment]( + node: ts.ShorthandPropertyAssignment + ) { + if (node.objectAssignmentInitializer) { + const result = this.createNode(node, { + type: AST_NODE_TYPES.Property, + key: this.convert(node.name), + computed: false, + method: false, + shorthand: true, + kind: 'init' }); - break; - - case SyntaxKind.TryStatement: - Object.assign(result, { - type: AST_NODE_TYPES.TryStatement, - block: convert({ - node: node.tryBlock, - parent: null, - ast, - additionalOptions - }), - handler: convertChild(node.catchClause), - finalizer: convertChild(node.finallyBlock) + (result as any).value = { + type: AST_NODE_TYPES.AssignmentPattern, + left: this.convert(node.name), + right: this.convert(node.objectAssignmentInitializer), + loc: result.loc, + range: result.range + }; + return result; + } else { + // TODO: this node has no initializer field + return this.createNode(node, { + type: AST_NODE_TYPES.Property, + key: this.convert(node.name), + value: this.convert((node as any).initializer || node.name), + computed: false, + method: false, + shorthand: true, + kind: 'init' }); - break; + } + } - case SyntaxKind.CatchClause: - Object.assign(result, { - type: AST_NODE_TYPES.CatchClause, - param: node.variableDeclaration - ? convertChild(node.variableDeclaration.name) - : null, - body: convertChild(node.block) + [SyntaxKind.ComputedPropertyName]( + node: ts.ComputedPropertyName, + parent: ts.Node + ): ESTreeNode | null { + if (parent.kind === SyntaxKind.ObjectLiteralExpression) { + // TODO: ComputedPropertyName has no name field + return this.createNode(node, { + type: AST_NODE_TYPES.Property, + key: this.convert((node as any).name), + value: this.convert((node as any).name), + computed: false, + method: false, + shorthand: true, + kind: 'init' }); - break; + } else { + return this.convert(node.expression); + } + } - // Loops + [SyntaxKind.PropertyDeclaration](node: ts.PropertyDeclaration): ESTreeNode { + const isAbstract = nodeUtils.hasModifier(SyntaxKind.AbstractKeyword, node); + const result = this.createNode(node, { + type: isAbstract + ? AST_NODE_TYPES.TSAbstractClassProperty + : AST_NODE_TYPES.ClassProperty, + key: this.convert(node.name), + value: this.convert(node.initializer), + computed: nodeUtils.isComputedProperty(node.name), + static: nodeUtils.hasModifier(SyntaxKind.StaticKeyword, node), + readonly: + nodeUtils.hasModifier(SyntaxKind.ReadonlyKeyword, node) || undefined + }); - case SyntaxKind.WhileStatement: - Object.assign(result, { - type: AST_NODE_TYPES.WhileStatement, - test: convertChild(node.expression), - body: convertChild(node.statement) - }); - break; + if (node.type) { + result.typeAnnotation = this.convertTypeAnnotation(node.type); + } - /** - * Unlike other parsers, TypeScript calls a "DoWhileStatement" - * a "DoStatement" - */ - case SyntaxKind.DoStatement: - Object.assign(result, { - type: AST_NODE_TYPES.DoWhileStatement, - test: convertChild(node.expression), - body: convertChild(node.statement) - }); - break; + if (node.decorators) { + result.decorators = this.convertDecorators(node.decorators); + } - case SyntaxKind.ForStatement: - Object.assign(result, { - type: AST_NODE_TYPES.ForStatement, - init: convertChild(node.initializer), - test: convertChild(node.condition), - update: convertChild(node.incrementor), - body: convertChild(node.statement) - }); - break; + const accessibility = nodeUtils.getTSNodeAccessibility(node); + if (accessibility) { + (result as any).accessibility = accessibility; + } - case SyntaxKind.ForInStatement: - case SyntaxKind.ForOfStatement: { - Object.assign(result, { - type: SyntaxKind[node.kind], - left: convertChild(node.initializer), - right: convertChild(node.expression), - body: convertChild(node.statement) - }); + if (node.name.kind === SyntaxKind.Identifier && node.questionToken) { + (result as any).optional = true; + } - // await is only available in for of statement - if (node.kind === SyntaxKind.ForOfStatement) { - (result as any).await = Boolean( - node.awaitModifier && - node.awaitModifier.kind === SyntaxKind.AwaitKeyword - ); - } - break; + if (node.exclamationToken) { + (result as any).definite = true; } - // Declarations + if ( + (result as any).key.type === AST_NODE_TYPES.Literal && + node.questionToken + ) { + (result as any).optional = true; + } + return result; + } - case SyntaxKind.FunctionDeclaration: { - let functionDeclarationType = AST_NODE_TYPES.FunctionDeclaration; - if (nodeUtils.hasModifier(SyntaxKind.DeclareKeyword, node)) { - functionDeclarationType = AST_NODE_TYPES.TSDeclareFunction; - } + convertMethodDeclaration( + node: + | ts.MethodDeclaration + | ts.SetAccessorDeclaration + | ts.GetAccessorDeclaration, + parent: ts.Node + ): ESTreeNode { + const result = this.createNode(node, {}); + + const openingParen = nodeUtils.findFirstMatchingToken( + node.name, + this.ast, + (token: any) => { + if (!token || !token.kind) { + return false; + } + return nodeUtils.getTextForTokenKind(token.kind) === '('; + }, + this.ast + ); - Object.assign(result, { - type: functionDeclarationType, - id: convertChild(node.name), + const methodLoc = this.ast.getLineAndCharacterOfPosition( + (openingParen as any).getStart(this.ast) + ), + nodeIsMethod = node.kind === SyntaxKind.MethodDeclaration, + method = { + type: AST_NODE_TYPES.FunctionExpression, + id: null, generator: !!node.asteriskToken, expression: false, async: nodeUtils.hasModifier(SyntaxKind.AsyncKeyword, node), - params: convertParameters(node.parameters), - body: convertChild(node.body) || undefined - }); + body: this.convert(node.body), + range: [node.parameters.pos - 1, result.range[1]], + loc: { + start: { + line: methodLoc.line + 1, + column: methodLoc.character + }, + end: result.loc.end + } + }; - // Process returnType - if (node.type) { - (result as any).returnType = convertTypeAnnotation(node.type); - } + if (node.type) { + (method as any).returnType = this.convertTypeAnnotation(node.type); + } - if (functionDeclarationType === AST_NODE_TYPES.TSDeclareFunction) { - result.declare = true; - } + if (parent.kind === SyntaxKind.ObjectLiteralExpression) { + (method as any).params = node.parameters.map(el => this.convert(el)); - // Process typeParameters - if (node.typeParameters && node.typeParameters.length) { - result.typeParameters = convertTSTypeParametersToTypeParametersDeclaration( - node.typeParameters - ); - } + // TODO: refactor me + Object.assign(result, { + type: AST_NODE_TYPES.Property, + key: this.convert(node.name), + value: method, + computed: nodeUtils.isComputedProperty(node.name), + method: nodeIsMethod, + shorthand: false, + kind: 'init' + }); + } else { + // class - // check for exports - result = nodeUtils.fixExports(node, result as any, ast); + /** + * Unlike in object literal methods, class method params can have decorators + */ + (method as any).params = this.convertParameters(node.parameters); - break; - } + /** + * TypeScript class methods can be defined as "abstract" + */ + const methodDefinitionType = nodeUtils.hasModifier( + SyntaxKind.AbstractKeyword, + node + ) + ? AST_NODE_TYPES.TSAbstractMethodDefinition + : AST_NODE_TYPES.MethodDefinition; - case SyntaxKind.VariableDeclaration: { + // TODO: refactor me Object.assign(result, { - type: AST_NODE_TYPES.VariableDeclarator, - id: convertChild(node.name), - init: convertChild(node.initializer) + type: methodDefinitionType, + key: this.convert(node.name), + value: method, + computed: nodeUtils.isComputedProperty(node.name), + static: nodeUtils.hasModifier(SyntaxKind.StaticKeyword, node), + kind: 'method' }); - if (node.exclamationToken) { - (result as any).definite = true; + if (node.decorators) { + result.decorators = this.convertDecorators(node.decorators); } - if (node.type) { - (result as any).id.typeAnnotation = convertTypeAnnotation(node.type); - fixTypeAnnotationParentLocation((result as any).id); + const accessibility = nodeUtils.getTSNodeAccessibility(node); + if (accessibility) { + (result as any).accessibility = accessibility; } - break; } - case SyntaxKind.VariableStatement: - Object.assign(result, { - type: AST_NODE_TYPES.VariableDeclaration, - declarations: node.declarationList.declarations.map(convertChild), - kind: nodeUtils.getDeclarationKind(node.declarationList) - }); + if ( + (result as any).key.type === AST_NODE_TYPES.Identifier && + node.questionToken + ) { + (result as any).key.optional = true; + } - if (nodeUtils.hasModifier(SyntaxKind.DeclareKeyword, node)) { - result.declare = true; - } + if (node.kind === SyntaxKind.GetAccessor) { + (result as any).kind = 'get'; + } else if (node.kind === SyntaxKind.SetAccessor) { + (result as any).kind = 'set'; + } else if ( + !(result as any).static && + node.name.kind === SyntaxKind.StringLiteral && + node.name.text === 'constructor' + ) { + (result as any).kind = 'constructor'; + } - // check for exports - result = nodeUtils.fixExports(node, result as any, ast); - break; + // Process typeParameters + if (node.typeParameters && node.typeParameters.length) { + (method as any).typeParameters = this.convertTSTypeParametersToTypeParametersDeclaration( + node.typeParameters + ); + } + return result; + } - // mostly for for-of, for-in - case SyntaxKind.VariableDeclarationList: - Object.assign(result, { - type: AST_NODE_TYPES.VariableDeclaration, - declarations: node.declarations.map(convertChild), - kind: nodeUtils.getDeclarationKind(node) - }); - break; + [SyntaxKind.GetAccessor]( + node: ts.GetAccessorDeclaration, + parent: ts.Node + ): ESTreeNode { + return this.convertMethodDeclaration(node, parent); + } - // Expressions + [SyntaxKind.SetAccessor]( + node: ts.SetAccessorDeclaration, + parent: ts.Node + ): ESTreeNode { + return this.convertMethodDeclaration(node, parent); + } - case SyntaxKind.ExpressionStatement: - Object.assign(result, { - type: AST_NODE_TYPES.ExpressionStatement, - expression: convertChild(node.expression) - }); - break; + [SyntaxKind.MethodDeclaration]( + node: ts.MethodDeclaration, + parent: ts.Node + ): ESTreeNode { + return this.convertMethodDeclaration(node, parent); + } - case SyntaxKind.ThisKeyword: - Object.assign(result, { - type: AST_NODE_TYPES.ThisExpression - }); - break; + // TypeScript uses this even for static methods named "constructor" + [SyntaxKind.Constructor](node: ts.ConstructorDeclaration): ESTreeNode { + const result = this.createNode(node, {}); - case SyntaxKind.ArrayLiteralExpression: { - const arrayAssignNode = nodeUtils.findAncestorOfKind( - node, - SyntaxKind.BinaryExpression - ); - const arrayIsInForOf = - node.parent && node.parent.kind === SyntaxKind.ForOfStatement; - const arrayIsInForIn = - node.parent && node.parent.kind === SyntaxKind.ForInStatement; - let arrayIsInAssignment; - - if (arrayAssignNode) { - if (node.parent.kind === SyntaxKind.ShorthandPropertyAssignment) { - arrayIsInAssignment = false; - } else if (node.parent.kind === SyntaxKind.CallExpression) { - arrayIsInAssignment = false; - } else if ( - nodeUtils.getBinaryExpressionType( - (arrayAssignNode as any).operatorToken - ) === AST_NODE_TYPES.AssignmentExpression - ) { - arrayIsInAssignment = - nodeUtils.findChildOfKind( - (arrayAssignNode as any).left, - SyntaxKind.ArrayLiteralExpression, - ast - ) === node || (arrayAssignNode as any).left === node; - } else { - arrayIsInAssignment = false; + const constructorIsStatic = nodeUtils.hasModifier( + SyntaxKind.StaticKeyword, + node + ), + constructorIsAbstract = nodeUtils.hasModifier( + SyntaxKind.AbstractKeyword, + node + ), + firstConstructorToken = constructorIsStatic + ? nodeUtils.findNextToken(node.getFirstToken()!, this.ast, this.ast) + : node.getFirstToken(), + constructorLoc = this.ast.getLineAndCharacterOfPosition( + node.parameters.pos - 1 + ), + constructor = { + type: AST_NODE_TYPES.FunctionExpression, + id: null, + params: this.convertParameters(node.parameters), + generator: false, + expression: false, + async: false, + body: this.convert(node.body), + range: [node.parameters.pos - 1, result.range[1]], + loc: { + start: { + line: constructorLoc.line + 1, + column: constructorLoc.character + }, + end: result.loc.end } - } + }; - // TypeScript uses ArrayLiteralExpression in destructuring assignment, too - if (arrayIsInAssignment || arrayIsInForOf || arrayIsInForIn) { - Object.assign(result, { - type: AST_NODE_TYPES.ArrayPattern, - elements: node.elements.map(convertChild) - }); - } else { - Object.assign(result, { - type: AST_NODE_TYPES.ArrayExpression, - elements: node.elements.map(convertChild) - }); - } - break; + const constructorIdentifierLocStart = this.ast.getLineAndCharacterOfPosition( + (firstConstructorToken as any).getStart(this.ast) + ), + constructorIdentifierLocEnd = this.ast.getLineAndCharacterOfPosition( + (firstConstructorToken as any).getEnd(this.ast) + ), + constructorIsComputed = + !!node.name && nodeUtils.isComputedProperty(node.name); + + let constructorKey; + + if (constructorIsComputed) { + constructorKey = { + type: AST_NODE_TYPES.Literal, + value: 'constructor', + raw: node.name!.getText(), + range: [ + (firstConstructorToken as any).getStart(this.ast), + (firstConstructorToken as any).end + ], + loc: { + start: { + line: constructorIdentifierLocStart.line + 1, + column: constructorIdentifierLocStart.character + }, + end: { + line: constructorIdentifierLocEnd.line + 1, + column: constructorIdentifierLocEnd.character + } + } + }; + } else { + constructorKey = { + type: AST_NODE_TYPES.Identifier, + name: 'constructor', + range: [ + (firstConstructorToken as any).getStart(this.ast), + (firstConstructorToken as any).end + ], + loc: { + start: { + line: constructorIdentifierLocStart.line + 1, + column: constructorIdentifierLocStart.character + }, + end: { + line: constructorIdentifierLocEnd.line + 1, + column: constructorIdentifierLocEnd.character + } + } + }; + } + + // TODO: refactor me + Object.assign(result, { + type: constructorIsAbstract + ? AST_NODE_TYPES.TSAbstractMethodDefinition + : AST_NODE_TYPES.MethodDefinition, + key: constructorKey, + value: constructor, + computed: constructorIsComputed, + static: constructorIsStatic, + kind: + constructorIsStatic || constructorIsComputed ? 'method' : 'constructor' + }); + + const accessibility = nodeUtils.getTSNodeAccessibility(node); + if (accessibility) { + (result as any).accessibility = accessibility; + } + return result; + } + + [SyntaxKind.FunctionExpression](node: ts.FunctionExpression): ESTreeNode { + const result = this.createNode(node, { + type: AST_NODE_TYPES.FunctionExpression, + id: this.convert(node.name), + generator: !!node.asteriskToken, + params: this.convertParameters(node.parameters), + body: this.convert(node.body), + async: nodeUtils.hasModifier(SyntaxKind.AsyncKeyword, node), + expression: false + }); + + // Process returnType + if (node.type) { + (result as any).returnType = this.convertTypeAnnotation(node.type); } - case SyntaxKind.ObjectLiteralExpression: { - const ancestorNode = nodeUtils.findFirstMatchingAncestor( - node, - parentNode => - parentNode.kind === SyntaxKind.BinaryExpression || - parentNode.kind === SyntaxKind.ArrowFunction + // Process typeParameters + if (node.typeParameters && node.typeParameters.length) { + result.typeParameters = this.convertTSTypeParametersToTypeParametersDeclaration( + node.typeParameters ); - const objectAssignNode = - ancestorNode && - ancestorNode.kind === SyntaxKind.BinaryExpression && - (ancestorNode as any).operatorToken.kind === SyntaxKind.FirstAssignment - ? ancestorNode - : null; - - let objectIsInAssignment = false; - - if (objectAssignNode) { - if (node.parent.kind === SyntaxKind.ShorthandPropertyAssignment) { - objectIsInAssignment = false; - } else if ((objectAssignNode as any).left === node) { - objectIsInAssignment = true; - } else if (node.parent.kind === SyntaxKind.CallExpression) { - objectIsInAssignment = false; - } else { - objectIsInAssignment = - nodeUtils.findChildOfKind( - (objectAssignNode as any).left, - SyntaxKind.ObjectLiteralExpression, - ast - ) === node; - } - } + } + return result; + } + + [SyntaxKind.SuperKeyword](node: ts.SuperExpression): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.Super + }); + } + + [SyntaxKind.ArrayBindingPattern](node: ts.ArrayBindingPattern): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.ArrayPattern, + elements: node.elements.map(el => this.convert(el)) + }); + } + + // occurs with missing array elements like [,] + [SyntaxKind.OmittedExpression](node: ts.OmittedExpression): null { + return null; + } + + [SyntaxKind.ObjectBindingPattern](node: ts.ObjectBindingPattern): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.ObjectPattern, + properties: node.elements.map(el => this.convert(el)) + }); + } + + [SyntaxKind.BindingElement]( + node: ts.BindingElement, + parent: ts.Node + ): ESTreeNode | null { + if (parent.kind === SyntaxKind.ArrayBindingPattern) { + const arrayItem = this.convert(node.name, parent); - // TypeScript uses ObjectLiteralExpression in destructuring assignment, too - if (objectIsInAssignment) { - Object.assign(result, { - type: AST_NODE_TYPES.ObjectPattern, - properties: node.properties.map(convertChild) + if (node.initializer) { + return this.createNode(node, { + type: AST_NODE_TYPES.AssignmentPattern, + left: arrayItem, + right: this.convert(node.initializer) }); - } else { - Object.assign(result, { - type: AST_NODE_TYPES.ObjectExpression, - properties: node.properties.map(convertChild) + } else if (node.dotDotDotToken) { + return this.createNode(node, { + type: AST_NODE_TYPES.RestElement, + argument: arrayItem }); + } else { + return arrayItem; } + } else if (parent.kind === SyntaxKind.ObjectBindingPattern) { + let result: ESTreeNode; - break; - } - - case SyntaxKind.PropertyAssignment: - Object.assign(result, { - type: AST_NODE_TYPES.Property, - key: convertChild(node.name), - value: convertChild(node.initializer), - computed: nodeUtils.isComputedProperty(node.name), - method: false, - shorthand: false, - kind: 'init' - }); - break; - - case SyntaxKind.ShorthandPropertyAssignment: { - if (node.objectAssignmentInitializer) { - Object.assign(result, { - type: AST_NODE_TYPES.Property, - key: convertChild(node.name), - value: { - type: AST_NODE_TYPES.AssignmentPattern, - left: convertChild(node.name), - right: convertChild(node.objectAssignmentInitializer), - loc: result.loc, - range: result.range - }, - computed: false, - method: false, - shorthand: true, - kind: 'init' + if (node.dotDotDotToken) { + result = this.createNode(node, { + type: AST_NODE_TYPES.RestElement, + argument: this.convert(node.propertyName || node.name) }); } else { - // TODO: this node has no initializer field - Object.assign(result, { + result = this.createNode(node, { type: AST_NODE_TYPES.Property, - key: convertChild(node.name), - value: convertChild((node as any).initializer || node.name), - computed: false, + key: this.convert(node.propertyName || node.name), + value: this.convert(node.name), + computed: Boolean( + node.propertyName && + node.propertyName.kind === SyntaxKind.ComputedPropertyName + ), method: false, - shorthand: true, + shorthand: !node.propertyName, kind: 'init' }); } - break; - } - case SyntaxKind.ComputedPropertyName: - if (parent!.kind === SyntaxKind.ObjectLiteralExpression) { - // TODO: ComputedPropertyName has no name field - Object.assign(result, { - type: AST_NODE_TYPES.Property, - key: convertChild((node as any).name), - value: convertChild((node as any).name), - computed: false, - method: false, - shorthand: true, - kind: 'init' - }); - } else { - return convertChild(node.expression); + if (node.initializer) { + (result as any).value = { + type: AST_NODE_TYPES.AssignmentPattern, + left: this.convert(node.name), + right: this.convert(node.initializer), + range: [node.name.getStart(this.ast), node.initializer.end], + loc: nodeUtils.getLocFor( + node.name.getStart(this.ast), + node.initializer.end, + this.ast + ) + }; } - break; + return result; + } + return null; + } - case SyntaxKind.PropertyDeclaration: { - const isAbstract = nodeUtils.hasModifier( - SyntaxKind.AbstractKeyword, - node + [SyntaxKind.ArrowFunction](node: ts.ArrowFunction): ESTreeNode { + const result = this.createNode(node, { + type: AST_NODE_TYPES.ArrowFunctionExpression, + generator: false, + id: null, + params: this.convertParameters(node.parameters), + body: this.convert(node.body), + async: nodeUtils.hasModifier(SyntaxKind.AsyncKeyword, node), + expression: node.body.kind !== SyntaxKind.Block + }); + + // Process returnType + if (node.type) { + (result as any).returnType = this.convertTypeAnnotation(node.type); + } + + // Process typeParameters + if (node.typeParameters && node.typeParameters.length) { + result.typeParameters = this.convertTSTypeParametersToTypeParametersDeclaration( + node.typeParameters ); - Object.assign(result, { - type: isAbstract - ? AST_NODE_TYPES.TSAbstractClassProperty - : AST_NODE_TYPES.ClassProperty, - key: convertChild(node.name), - value: convertChild(node.initializer), - computed: nodeUtils.isComputedProperty(node.name), - static: nodeUtils.hasModifier(SyntaxKind.StaticKeyword, node), - readonly: - nodeUtils.hasModifier(SyntaxKind.ReadonlyKeyword, node) || undefined - }); + } + return result; + } - if (node.type) { - result.typeAnnotation = convertTypeAnnotation(node.type); - } + [SyntaxKind.YieldExpression](node: ts.YieldExpression): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.YieldExpression, + delegate: !!node.asteriskToken, + argument: this.convert(node.expression) + }); + } - if (node.decorators) { - result.decorators = convertDecorators(node.decorators); - } + [SyntaxKind.AwaitExpression](node: ts.AwaitExpression): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.AwaitExpression, + argument: this.convert(node.expression) + }); + } - const accessibility = nodeUtils.getTSNodeAccessibility(node); - if (accessibility) { - (result as any).accessibility = accessibility; - } + // Template Literals - if (node.name.kind === SyntaxKind.Identifier && node.questionToken) { - (result as any).optional = true; - } + [SyntaxKind.NoSubstitutionTemplateLiteral]( + node: ts.NoSubstitutionTemplateLiteral + ) { + const result = this.createNode(node, { + type: AST_NODE_TYPES.TemplateLiteral, + quasis: [], + expressions: [] + }); - if (node.exclamationToken) { - (result as any).definite = true; - } + (result as any).quasis.push({ + type: AST_NODE_TYPES.TemplateElement, + value: { + raw: this.ast.text.slice(node.getStart(this.ast) + 1, node.end - 1), + cooked: node.text + }, + tail: true, + range: result.range, + loc: result.loc + }); + return result; + } - if ( - (result as any).key.type === AST_NODE_TYPES.Literal && - node.questionToken - ) { - (result as any).optional = true; - } - break; - } - - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.MethodDeclaration: { - const openingParen = nodeUtils.findFirstMatchingToken( - node.name, - ast, - (token: any) => { - if (!token || !token.kind) { - return false; - } - return nodeUtils.getTextForTokenKind(token.kind) === '('; - }, - ast - ); + [SyntaxKind.TemplateExpression](node: ts.TemplateExpression): ESTreeNode { + const result = this.createNode(node, { + type: AST_NODE_TYPES.TemplateLiteral, + quasis: [this.convert(node.head)], + expressions: [] + }); + + node.templateSpans.forEach(templateSpan => { + (result as any).expressions.push(this.convert(templateSpan.expression)); + (result as any).quasis.push(this.convert(templateSpan.literal)); + }); + return result; + } + + [SyntaxKind.TaggedTemplateExpression]( + node: ts.TaggedTemplateExpression + ): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.TaggedTemplateExpression, + typeParameters: node.typeArguments + ? this.convertTypeArgumentsToTypeParameters(node.typeArguments) + : undefined, + tag: this.convert(node.tag), + quasi: this.convert(node.template) + }); + } - const methodLoc = ast.getLineAndCharacterOfPosition( - (openingParen as any).getStart(ast) + convertTemplateParts( + node: ts.TemplateHead | ts.TemplateMiddle | ts.TemplateTail + ): ESTreeNode { + const tail = node.kind === SyntaxKind.TemplateTail; + return this.createNode(node, { + type: AST_NODE_TYPES.TemplateElement, + value: { + raw: this.ast.text.slice( + node.getStart(this.ast) + 1, + node.end - (tail ? 1 : 2) ), - nodeIsMethod = node.kind === SyntaxKind.MethodDeclaration, - method = { - type: AST_NODE_TYPES.FunctionExpression, - id: null, - generator: !!node.asteriskToken, - expression: false, - async: nodeUtils.hasModifier(SyntaxKind.AsyncKeyword, node), - body: convertChild(node.body), - range: [node.parameters.pos - 1, (result as any).range[1]], - loc: { - start: { - line: methodLoc.line + 1, - column: methodLoc.character - }, - end: (result as any).loc.end - } - }; + cooked: node.text + }, + tail + }); + } - if (node.type) { - (method as any).returnType = convertTypeAnnotation(node.type); - } + [SyntaxKind.TemplateHead](node: ts.TemplateHead): ESTreeNode { + return this.convertTemplateParts(node); + } - if (parent!.kind === SyntaxKind.ObjectLiteralExpression) { - (method as any).params = node.parameters.map(convertChild); + [SyntaxKind.TemplateMiddle](node: ts.TemplateMiddle): ESTreeNode { + return this.convertTemplateParts(node); + } - Object.assign(result, { - type: AST_NODE_TYPES.Property, - key: convertChild(node.name), - value: method, - computed: nodeUtils.isComputedProperty(node.name), - method: nodeIsMethod, - shorthand: false, - kind: 'init' - }); - } else { - // class - - /** - * Unlike in object literal methods, class method params can have decorators - */ - (method as any).params = convertParameters(node.parameters); - - /** - * TypeScript class methods can be defined as "abstract" - */ - const methodDefinitionType = nodeUtils.hasModifier( - SyntaxKind.AbstractKeyword, - node - ) - ? AST_NODE_TYPES.TSAbstractMethodDefinition - : AST_NODE_TYPES.MethodDefinition; - - Object.assign(result, { - type: methodDefinitionType, - key: convertChild(node.name), - value: method, - computed: nodeUtils.isComputedProperty(node.name), - static: nodeUtils.hasModifier(SyntaxKind.StaticKeyword, node), - kind: 'method' - }); + [SyntaxKind.TemplateTail](node: ts.TemplateTail): ESTreeNode { + return this.convertTemplateParts(node); + } - if (node.decorators) { - result.decorators = convertDecorators(node.decorators); - } + // Patterns - const accessibility = nodeUtils.getTSNodeAccessibility(node); - if (accessibility) { - (result as any).accessibility = accessibility; - } - } + [SyntaxKind.SpreadElement](node: ts.SpreadElement): ESTreeNode { + let type = AST_NODE_TYPES.SpreadElement; - if ( - (result as any).key.type === AST_NODE_TYPES.Identifier && - node.questionToken + if ( + node.parent && + node.parent.parent && + node.parent.parent.kind === SyntaxKind.BinaryExpression + ) { + if ((node.parent.parent as ts.BinaryExpression).left === node.parent) { + type = AST_NODE_TYPES.RestElement; + } else if ( + (node.parent.parent as ts.BinaryExpression).right === node.parent ) { - (result as any).key.optional = true; + type = AST_NODE_TYPES.SpreadElement; } + } + + return this.createNode(node, { + type, + argument: this.convert(node.expression) + }); + } + [SyntaxKind.SpreadAssignment](node: ts.SpreadAssignment): ESTreeNode { + let type = AST_NODE_TYPES.SpreadElement; - if (node.kind === SyntaxKind.GetAccessor) { - (result as any).kind = 'get'; - } else if (node.kind === SyntaxKind.SetAccessor) { - (result as any).kind = 'set'; + if ( + node.parent && + node.parent.parent && + node.parent.parent.kind === SyntaxKind.BinaryExpression + ) { + if ((node.parent.parent as ts.BinaryExpression).right === node.parent) { + type = AST_NODE_TYPES.SpreadElement; } else if ( - !(result as any).static && - node.name.kind === SyntaxKind.StringLiteral && - node.name.text === 'constructor' + (node.parent.parent as ts.BinaryExpression).left === node.parent ) { - (result as any).kind = 'constructor'; + type = AST_NODE_TYPES.RestElement; } - - // Process typeParameters - if (node.typeParameters && node.typeParameters.length) { - (method as any).typeParameters = convertTSTypeParametersToTypeParametersDeclaration( - node.typeParameters - ); - } - - break; } - // TypeScript uses this even for static methods named "constructor" - case SyntaxKind.Constructor: { - const constructorIsStatic = nodeUtils.hasModifier( - SyntaxKind.StaticKeyword, - node - ), - constructorIsAbstract = nodeUtils.hasModifier( - SyntaxKind.AbstractKeyword, - node - ), - firstConstructorToken = constructorIsStatic - ? nodeUtils.findNextToken(node.getFirstToken()!, ast, ast) - : node.getFirstToken(), - constructorLoc = ast.getLineAndCharacterOfPosition( - node.parameters.pos - 1 - ), - constructor = { - type: 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 as any).range[1]], - loc: { - start: { - line: constructorLoc.line + 1, - column: constructorLoc.character - }, - end: (result as any).loc.end - } - }; - - const constructorIdentifierLocStart = ast.getLineAndCharacterOfPosition( - (firstConstructorToken as any).getStart(ast) - ), - constructorIdentifierLocEnd = ast.getLineAndCharacterOfPosition( - (firstConstructorToken as any).getEnd(ast) - ), - constructorIsComputed = - !!node.name && nodeUtils.isComputedProperty(node.name); - - let constructorKey; - - if (constructorIsComputed) { - constructorKey = { - type: AST_NODE_TYPES.Literal, - value: 'constructor', - raw: node.name!.getText(), - range: [ - (firstConstructorToken as any).getStart(ast), - (firstConstructorToken as any).end - ], - loc: { - start: { - line: constructorIdentifierLocStart.line + 1, - column: constructorIdentifierLocStart.character - }, - end: { - line: constructorIdentifierLocEnd.line + 1, - column: constructorIdentifierLocEnd.character - } - } - }; - } else { - constructorKey = { - type: AST_NODE_TYPES.Identifier, - name: 'constructor', - range: [ - (firstConstructorToken as any).getStart(ast), - (firstConstructorToken as any).end - ], - loc: { - start: { - line: constructorIdentifierLocStart.line + 1, - column: constructorIdentifierLocStart.character - }, - end: { - line: constructorIdentifierLocEnd.line + 1, - column: constructorIdentifierLocEnd.character - } - } - }; - } + return this.createNode(node, { + type, + argument: this.convert(node.expression) + }); + } - Object.assign(result, { - type: constructorIsAbstract - ? AST_NODE_TYPES.TSAbstractMethodDefinition - : AST_NODE_TYPES.MethodDefinition, - key: constructorKey, - value: constructor, - computed: constructorIsComputed, - static: constructorIsStatic, - kind: - constructorIsStatic || constructorIsComputed - ? 'method' - : 'constructor' + [SyntaxKind.Parameter]( + node: ts.ParameterDeclaration, + parent: ts.Node + ): ESTreeNode | null { + let parameter; + let result: ESTreeNode | null; + + if (node.dotDotDotToken) { + parameter = this.convert(node.name); + result = this.createNode(node, { + type: AST_NODE_TYPES.RestElement, + argument: parameter + }); + } else if (node.initializer) { + parameter = this.convert(node.name); + result = this.createNode(node, { + type: AST_NODE_TYPES.AssignmentPattern, + left: parameter, + right: this.convert(node.initializer) }); + } else { + parameter = this.convert(node.name, parent); + result = parameter; + } - const accessibility = nodeUtils.getTSNodeAccessibility(node); - if (accessibility) { - (result as any).accessibility = accessibility; - } + if (node.type) { + (parameter as any).typeAnnotation = this.convertTypeAnnotation(node.type); + this.fixTypeAnnotationParentLocation(node, parameter!); + } - break; + if (node.questionToken) { + (parameter as any).optional = true; } - case SyntaxKind.FunctionExpression: - Object.assign(result, { - type: AST_NODE_TYPES.FunctionExpression, - id: convertChild(node.name), - generator: !!node.asteriskToken, - params: convertParameters(node.parameters), - body: convertChild(node.body), - async: nodeUtils.hasModifier(SyntaxKind.AsyncKeyword, node), - expression: false - }); - - // Process returnType - if (node.type) { - (result as any).returnType = convertTypeAnnotation(node.type); - } + if (node.modifiers) { + return { + type: AST_NODE_TYPES.TSParameterProperty, + range: [node.getStart(this.ast), node.end], + loc: nodeUtils.getLoc(node, this.ast), + accessibility: nodeUtils.getTSNodeAccessibility(node) || undefined, + readonly: + nodeUtils.hasModifier(SyntaxKind.ReadonlyKeyword, node) || undefined, + static: + nodeUtils.hasModifier(SyntaxKind.StaticKeyword, node) || undefined, + export: + nodeUtils.hasModifier(SyntaxKind.ExportKeyword, node) || undefined, + parameter: result + }; + } + return result; + } - // Process typeParameters - if (node.typeParameters && node.typeParameters.length) { - result.typeParameters = convertTSTypeParametersToTypeParametersDeclaration( - node.typeParameters - ); - } - break; + // Classes + convertClassLike(node: ts.ClassExpression | ts.ClassDeclaration): ESTreeNode { + const result = this.createNode(node, {}); - case SyntaxKind.SuperKeyword: - Object.assign(result, { - type: AST_NODE_TYPES.Super - }); - break; + const heritageClauses = node.heritageClauses || []; - case SyntaxKind.ArrayBindingPattern: - Object.assign(result, { - type: AST_NODE_TYPES.ArrayPattern, - elements: node.elements.map(convertChild) - }); - break; + let classNodeType = SyntaxKind[node.kind]; + let lastClassToken: any = heritageClauses.length + ? heritageClauses[heritageClauses.length - 1] + : node.name; - // occurs with missing array elements like [,] - case SyntaxKind.OmittedExpression: - return null; + if (node.typeParameters && node.typeParameters.length) { + const lastTypeParameter = + node.typeParameters[node.typeParameters.length - 1]; - case SyntaxKind.ObjectBindingPattern: - Object.assign(result, { - type: AST_NODE_TYPES.ObjectPattern, - properties: node.elements.map(convertChild) - }); - break; - - case SyntaxKind.BindingElement: - if (parent!.kind === SyntaxKind.ArrayBindingPattern) { - const arrayItem = convert({ - node: node.name, - parent, - ast, - additionalOptions - }); + if (!lastClassToken || lastTypeParameter.pos > lastClassToken.pos) { + lastClassToken = nodeUtils.findNextToken( + lastTypeParameter, + this.ast, + this.ast + ); + } + result.typeParameters = this.convertTSTypeParametersToTypeParametersDeclaration( + node.typeParameters + ); + } - if (node.initializer) { - Object.assign(result, { - type: AST_NODE_TYPES.AssignmentPattern, - left: arrayItem, - right: convertChild(node.initializer) - }); - } else if (node.dotDotDotToken) { - Object.assign(result, { - type: 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.RestElement, - argument: convertChild(node.propertyName || node.name) - }); - } else { - Object.assign(result, { - type: 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.modifiers && node.modifiers.length) { + /** + * TypeScript class declarations can be defined as "abstract" + */ + if (node.kind === SyntaxKind.ClassDeclaration) { + if (nodeUtils.hasModifier(SyntaxKind.AbstractKeyword, node)) { + classNodeType = `TSAbstract${classNodeType}`; } + } - if (node.initializer) { - (result as any).value = { - type: AST_NODE_TYPES.AssignmentPattern, - left: convertChild(node.name), - right: convertChild(node.initializer), - range: [node.name.getStart(ast), node.initializer.end], - loc: nodeUtils.getLocFor( - node.name.getStart(ast), - node.initializer.end, - ast - ) - }; - } + /** + * We need check for modifiers, and use the last one, as there + * could be multiple before the open brace + */ + const lastModifier = node.modifiers![node.modifiers!.length - 1]; + + if (!lastClassToken || lastModifier.pos > lastClassToken.pos) { + lastClassToken = nodeUtils.findNextToken( + lastModifier, + this.ast, + this.ast + ); } - break; + } else if (!lastClassToken) { + // no name + lastClassToken = node.getFirstToken(); + } - case SyntaxKind.ArrowFunction: - Object.assign(result, { - type: AST_NODE_TYPES.ArrowFunctionExpression, - generator: false, - id: null, - params: convertParameters(node.parameters), - body: convertChild(node.body), - async: nodeUtils.hasModifier(SyntaxKind.AsyncKeyword, node), - expression: node.body.kind !== SyntaxKind.Block - }); + const openBrace = nodeUtils.findNextToken( + lastClassToken, + this.ast, + this.ast + )!; + const superClass = heritageClauses.find( + clause => clause.token === SyntaxKind.ExtendsKeyword + ); - // Process returnType - if (node.type) { - (result as any).returnType = convertTypeAnnotation(node.type); + if (superClass) { + if (superClass.types.length > 1) { + throw nodeUtils.createError( + this.ast, + superClass.types[1].pos, + 'Classes can only extend a single class.' + ); } - // Process typeParameters - if (node.typeParameters && node.typeParameters.length) { - result.typeParameters = convertTSTypeParametersToTypeParametersDeclaration( - node.typeParameters + if (superClass.types[0] && superClass.types[0].typeArguments) { + (result as any).superTypeParameters = this.convertTypeArgumentsToTypeParameters( + superClass.types[0].typeArguments ); } - break; + } - case SyntaxKind.YieldExpression: - Object.assign(result, { - type: AST_NODE_TYPES.YieldExpression, - delegate: !!node.asteriskToken, - argument: convertChild(node.expression) - }); - break; + const implementsClause = heritageClauses.find( + clause => clause.token === SyntaxKind.ImplementsKeyword + ); - case SyntaxKind.AwaitExpression: - Object.assign(result, { - type: AST_NODE_TYPES.AwaitExpression, - argument: convertChild(node.expression) - }); - break; + Object.assign(result, { + type: classNodeType, + id: this.convert(node.name), + body: { + type: AST_NODE_TYPES.ClassBody, + body: [], - // Template Literals + // TODO: Fix location info + range: [openBrace.getStart(this.ast), result.range[1]], + loc: nodeUtils.getLocFor( + openBrace.getStart(this.ast), + node.end, + this.ast + ) + }, + superClass: + superClass && superClass.types[0] + ? this.convert(superClass.types[0].expression) + : null + }); - case SyntaxKind.NoSubstitutionTemplateLiteral: - Object.assign(result, { - type: AST_NODE_TYPES.TemplateLiteral, - quasis: [ - { - type: 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; + if (implementsClause) { + (result as any).implements = implementsClause.types.map(el => + this.convertClassImplements(el) + ); + } - case SyntaxKind.TemplateExpression: - Object.assign(result, { - type: AST_NODE_TYPES.TemplateLiteral, - quasis: [convertChild(node.head)], - expressions: [] - }); + if (nodeUtils.hasModifier(SyntaxKind.DeclareKeyword, node)) { + result.declare = true; + } - node.templateSpans.forEach((templateSpan: any) => { - (result as any).expressions.push(convertChild(templateSpan.expression)); - (result as any).quasis.push(convertChild(templateSpan.literal)); - }); - break; + if (node.decorators) { + result.decorators = this.convertDecorators(node.decorators); + } - case SyntaxKind.TaggedTemplateExpression: - Object.assign(result, { - type: AST_NODE_TYPES.TaggedTemplateExpression, - typeParameters: node.typeArguments - ? convertTypeArgumentsToTypeParameters(node.typeArguments) - : undefined, - tag: convertChild(node.tag), - quasi: convertChild(node.template) - }); - break; + const filteredMembers = node.members.filter(nodeUtils.isESTreeClassMember); - case SyntaxKind.TemplateHead: - case SyntaxKind.TemplateMiddle: - case SyntaxKind.TemplateTail: { - const tail = node.kind === SyntaxKind.TemplateTail; - Object.assign(result, { - type: AST_NODE_TYPES.TemplateElement, - value: { - raw: ast.text.slice( - node.getStart(ast) + 1, - node.end - (tail ? 1 : 2) - ), - cooked: node.text - }, - tail - }); - break; + if (filteredMembers.length) { + result.body.body = filteredMembers.map(el => this.convert(el)); } - // Patterns + // check for exports + return nodeUtils.fixExports(node, result, this.ast); + } - case SyntaxKind.SpreadElement: { - let type = AST_NODE_TYPES.SpreadElement; + [SyntaxKind.ClassDeclaration](node: ts.ClassDeclaration): ESTreeNode { + return this.convertClassLike(node); + } - if ( - node.parent && - node.parent.parent && - node.parent.parent.kind === SyntaxKind.BinaryExpression - ) { - if ((node.parent.parent as ts.BinaryExpression).left === node.parent) { - type = AST_NODE_TYPES.RestElement; - } else if ( - (node.parent.parent as ts.BinaryExpression).right === node.parent + [SyntaxKind.ClassExpression](node: ts.ClassExpression): ESTreeNode { + return this.convertClassLike(node); + } + + // Modules + [SyntaxKind.ModuleBlock](node: ts.ModuleBlock): ESTreeNode { + const result = this.createNode(node, { + type: AST_NODE_TYPES.TSModuleBlock, + body: node.statements.map(el => this.convert(el)) + }); + + result.body = this.convertBodyExpressionsToDirectives(node, result.body); + return result; + } + + [SyntaxKind.ImportDeclaration](node: ts.ImportDeclaration): ESTreeNode { + const result = this.createNode(node, { + type: AST_NODE_TYPES.ImportDeclaration, + source: this.convert(node.moduleSpecifier), + specifiers: [] + }); + + if (node.importClause) { + if (node.importClause.name) { + result.specifiers!.push(this.convert(node.importClause)); + } + + if (node.importClause.namedBindings) { + if ( + node.importClause.namedBindings.kind === SyntaxKind.NamespaceImport ) { - type = AST_NODE_TYPES.SpreadElement; + result.specifiers!.push( + this.convert(node.importClause.namedBindings) + ); + } else { + result.specifiers = result.specifiers!.concat( + node.importClause.namedBindings.elements.map(el => this.convert(el)) + ); } } + } + return result; + } - Object.assign(result, { - type, - argument: convertChild(node.expression) + [SyntaxKind.NamespaceImport](node: ts.NamespaceImport): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.ImportNamespaceSpecifier, + local: this.convert(node.name) + }); + } + + [SyntaxKind.ImportSpecifier](node: ts.ImportSpecifier): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.ImportSpecifier, + local: this.convert(node.name), + imported: this.convert(node.propertyName || node.name) + }); + } + + [SyntaxKind.ImportClause](node: ts.ImportClause): ESTreeNode { + const result = this.createNode(node, { + type: AST_NODE_TYPES.ImportDefaultSpecifier, + local: this.convert(node.name) + }); + + // have to adjust location information due to tree differences + result.range[1] = node.name!.end; + result.loc = nodeUtils.getLocFor( + result.range[0], + result.range[1], + this.ast + ); + return result; + } + + [SyntaxKind.NamedImports](node: ts.NamedImports): ESTreeNode { + // TODO: node has no name field + return this.createNode(node, { + type: AST_NODE_TYPES.ImportDefaultSpecifier, + local: this.convert((node as any).name) + }); + } + + [SyntaxKind.ExportDeclaration](node: ts.ExportDeclaration): ESTreeNode { + if (node.exportClause) { + return this.createNode(node, { + type: AST_NODE_TYPES.ExportNamedDeclaration, + source: this.convert(node.moduleSpecifier), + specifiers: node.exportClause.elements.map(el => this.convert(el)), + declaration: null + }); + } else { + return this.createNode(node, { + type: AST_NODE_TYPES.ExportAllDeclaration, + source: this.convert(node.moduleSpecifier) }); - break; } - case SyntaxKind.SpreadAssignment: { - let type = AST_NODE_TYPES.SpreadElement; + } - if ( - node.parent && - node.parent.parent && - node.parent.parent.kind === SyntaxKind.BinaryExpression - ) { - if ((node.parent.parent as ts.BinaryExpression).right === node.parent) { - type = AST_NODE_TYPES.SpreadElement; - } else if ( - (node.parent.parent as ts.BinaryExpression).left === node.parent - ) { - type = AST_NODE_TYPES.RestElement; - } - } + [SyntaxKind.ExportSpecifier](node: ts.ExportSpecifier): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.ExportSpecifier, + local: this.convert(node.propertyName || node.name), + exported: this.convert(node.name) + }); + } - Object.assign(result, { - type, - argument: convertChild(node.expression) + [SyntaxKind.ExportAssignment](node: ts.ExportAssignment): ESTreeNode { + if (node.isExportEquals) { + return this.createNode(node, { + type: AST_NODE_TYPES.TSExportAssignment, + expression: this.convert(node.expression) + }); + } else { + return this.createNode(node, { + type: AST_NODE_TYPES.ExportDefaultDeclaration, + declaration: this.convert(node.expression) }); - break; } + } - case SyntaxKind.Parameter: { - let parameter; + // Unary Operations + convertUnaryExpression( + node: ts.PrefixUnaryExpression | ts.PostfixUnaryExpression + ): ESTreeNode { + const operator = nodeUtils.getTextForTokenKind(node.operator) || ''; + return this.createNode(node, { + /** + * ESTree uses UpdateExpression for ++/-- + */ + type: /^(?:\+\+|--)$/.test(operator) + ? AST_NODE_TYPES.UpdateExpression + : AST_NODE_TYPES.UnaryExpression, + operator, + prefix: node.kind === SyntaxKind.PrefixUnaryExpression, + argument: this.convert(node.operand) + }); + } - if (node.dotDotDotToken) { - parameter = convertChild(node.name); - Object.assign(result, { - type: AST_NODE_TYPES.RestElement, - argument: parameter - }); - } else if (node.initializer) { - parameter = convertChild(node.name); - Object.assign(result, { - type: AST_NODE_TYPES.AssignmentPattern, - left: parameter, - right: convertChild(node.initializer) - }); - } else { - parameter = convert({ - node: node.name, - parent, - ast, - additionalOptions - }); - (result as any) = parameter; - } + [SyntaxKind.PrefixUnaryExpression]( + node: ts.PrefixUnaryExpression + ): ESTreeNode { + return this.convertUnaryExpression(node); + } - if (node.type) { - (parameter as any).typeAnnotation = convertTypeAnnotation(node.type); - fixTypeAnnotationParentLocation(parameter as any); - } + [SyntaxKind.PostfixUnaryExpression]( + node: ts.PostfixUnaryExpression + ): ESTreeNode { + return this.convertUnaryExpression(node); + } - if (node.questionToken) { - (parameter as any).optional = true; - } + [SyntaxKind.DeleteExpression](node: ts.DeleteExpression): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.UnaryExpression, + operator: 'delete', + prefix: true, + argument: this.convert(node.expression) + }); + } - if (node.modifiers) { - return { - type: AST_NODE_TYPES.TSParameterProperty, - range: [node.getStart(ast), node.end], - loc: nodeUtils.getLoc(node, ast), - accessibility: nodeUtils.getTSNodeAccessibility(node) || undefined, - readonly: - nodeUtils.hasModifier(SyntaxKind.ReadonlyKeyword, node) || - undefined, - static: - nodeUtils.hasModifier(SyntaxKind.StaticKeyword, node) || undefined, - export: - nodeUtils.hasModifier(SyntaxKind.ExportKeyword, node) || undefined, - parameter: result - }; - } + [SyntaxKind.VoidExpression](node: ts.VoidExpression): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.UnaryExpression, + operator: 'void', + prefix: true, + argument: this.convert(node.expression) + }); + } - break; - } + [SyntaxKind.TypeOfExpression](node: ts.TypeOfExpression): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.UnaryExpression, + operator: 'typeof', + prefix: true, + argument: this.convert(node.expression) + }); + } - // Classes + [SyntaxKind.TypeOperator](node: ts.TypeOperatorNode): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.TSTypeOperator, + operator: nodeUtils.getTextForTokenKind(node.operator), + typeAnnotation: this.convert(node.type) + }); + } - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: { - const heritageClauses = node.heritageClauses || []; + // Binary Operations - let classNodeType = SyntaxKind[node.kind]; - let lastClassToken: any = heritageClauses.length - ? heritageClauses[heritageClauses.length - 1] - : node.name; + [SyntaxKind.BinaryExpression](node: ts.BinaryExpression): ESTreeNode { + // TypeScript uses BinaryExpression for sequences as well + if (nodeUtils.isComma(node.operatorToken)) { + const result = this.createNode(node, { + type: AST_NODE_TYPES.SequenceExpression, + expressions: [] + }); - if (node.typeParameters && node.typeParameters.length) { - const lastTypeParameter = - node.typeParameters[node.typeParameters.length - 1]; + const left = this.convert(node.left), + right = this.convert(node.right); - if (!lastClassToken || lastTypeParameter.pos > lastClassToken.pos) { - lastClassToken = nodeUtils.findNextToken(lastTypeParameter, ast, ast); - } - result.typeParameters = convertTSTypeParametersToTypeParametersDeclaration( - node.typeParameters + if ((left as any).type === AST_NODE_TYPES.SequenceExpression) { + (result as any).expressions = (result as any).expressions.concat( + (left as any).expressions ); + } else { + (result as any).expressions.push(left); } - if (node.modifiers && node.modifiers.length) { - /** - * TypeScript class declarations can be defined as "abstract" - */ - if (node.kind === SyntaxKind.ClassDeclaration) { - if (nodeUtils.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 lastModifier = node.modifiers![node.modifiers!.length - 1]; - - if (!lastClassToken || lastModifier.pos > lastClassToken.pos) { - lastClassToken = nodeUtils.findNextToken(lastModifier, ast, ast); - } - } else if (!lastClassToken) { - // no name - lastClassToken = node.getFirstToken(); + if ((right as any).type === AST_NODE_TYPES.SequenceExpression) { + (result as any).expressions = (result as any).expressions.concat( + (right as any).expressions + ); + } else { + (result as any).expressions.push(right); } + return result; + } else { + const result = this.createNode(node, { + type: nodeUtils.getBinaryExpressionType(node.operatorToken), + operator: nodeUtils.getTextForTokenKind(node.operatorToken.kind), + left: this.convert(node.left), + right: this.convert(node.right) + }); + + // if the binary expression is in a destructured array, switch it + if (result.type === AST_NODE_TYPES.AssignmentExpression) { + const upperArrayNode = nodeUtils.findFirstMatchingAncestor( + node, + parent => + parent.kind === SyntaxKind.ArrayLiteralExpression || + parent.kind === SyntaxKind.ObjectLiteralExpression + ); + const upperArrayAssignNode = + upperArrayNode && + nodeUtils.findAncestorOfKind( + upperArrayNode, + SyntaxKind.BinaryExpression + ); - const openBrace = nodeUtils.findNextToken(lastClassToken, ast, ast)!; - const superClass = heritageClauses.find( - clause => clause.token === SyntaxKind.ExtendsKeyword - ); + let upperArrayIsInAssignment; - if (superClass) { - if (superClass.types.length > 1) { - throw nodeUtils.createError( - ast, - superClass.types[1].pos, - 'Classes can only extend a single class.' - ); + if (upperArrayAssignNode) { + if ((upperArrayAssignNode as any).left === upperArrayNode) { + upperArrayIsInAssignment = true; + } else { + upperArrayIsInAssignment = + nodeUtils.findChildOfKind( + (upperArrayAssignNode as any).left, + SyntaxKind.ArrayLiteralExpression, + this.ast + ) === upperArrayNode; + } } - if (superClass.types[0] && superClass.types[0].typeArguments) { - (result as any).superTypeParameters = convertTypeArgumentsToTypeParameters( - superClass.types[0].typeArguments - ); + if (upperArrayIsInAssignment) { + delete (result as any).operator; + result.type = AST_NODE_TYPES.AssignmentPattern; } } + return result; + } + } - const implementsClause = heritageClauses.find( - clause => clause.token === SyntaxKind.ImplementsKeyword - ); + [SyntaxKind.PropertyAccessExpression]( + node: ts.PropertyAccessExpression, + parent: ts.Node + ): ESTreeNode { + if (nodeUtils.isJSXToken(parent)) { + const result = this.createNode(node, { + type: AST_NODE_TYPES.MemberExpression, + object: this.convert(node.expression, parent), + property: this.convert(node.name, parent) + }); + const isNestedMemberExpression = + node.expression.kind === SyntaxKind.PropertyAccessExpression; + if (node.expression.kind === SyntaxKind.ThisKeyword) { + (result as any).object.name = 'this'; + } - Object.assign(result, { - type: classNodeType, - id: convertChild(node.name), - body: { - type: AST_NODE_TYPES.ClassBody, - body: [], - - // TODO: Fix location info - range: [openBrace.getStart(ast), (result as any).range[1]], - loc: nodeUtils.getLocFor(openBrace.getStart(ast), node.end, ast) - }, - superClass: - superClass && superClass.types[0] - ? convertChild(superClass.types[0].expression) - : null + (result as any).object.type = isNestedMemberExpression + ? AST_NODE_TYPES.MemberExpression + : AST_NODE_TYPES.JSXIdentifier; + (result as any).property.type = AST_NODE_TYPES.JSXIdentifier; + return result; + } else { + return this.createNode(node, { + type: AST_NODE_TYPES.MemberExpression, + object: this.convert(node.expression), + property: this.convert(node.name), + computed: false }); + } + } - if (implementsClause) { - (result as any).implements = implementsClause.types.map( - convertClassImplements - ); - } + [SyntaxKind.ElementAccessExpression]( + node: ts.ElementAccessExpression + ): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.MemberExpression, + object: this.convert(node.expression), + property: this.convert(node.argumentExpression), + computed: true + }); + } - if (nodeUtils.hasModifier(SyntaxKind.DeclareKeyword, node)) { - result.declare = true; - } + [SyntaxKind.ConditionalExpression]( + node: ts.ConditionalExpression + ): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.ConditionalExpression, + test: this.convert(node.condition), + consequent: this.convert(node.whenTrue), + alternate: this.convert(node.whenFalse) + }); + } - if (node.decorators) { - result.decorators = convertDecorators(node.decorators); - } + [SyntaxKind.CallExpression](node: ts.CallExpression): ESTreeNode { + const result = this.createNode(node, { + type: AST_NODE_TYPES.CallExpression, + callee: this.convert(node.expression), + arguments: node.arguments.map(el => this.convert(el)) + }); + if (node.typeArguments && node.typeArguments.length) { + result.typeParameters = this.convertTypeArgumentsToTypeParameters( + node.typeArguments + ); + } + return result; + } - const filteredMembers = node.members.filter( - nodeUtils.isESTreeClassMember + [SyntaxKind.NewExpression](node: ts.NewExpression): ESTreeNode { + const result = this.createNode(node, { + type: AST_NODE_TYPES.NewExpression, + callee: this.convert(node.expression), + arguments: node.arguments + ? node.arguments.map(el => this.convert(el)) + : [] + }); + if (node.typeArguments && node.typeArguments.length) { + result.typeParameters = this.convertTypeArgumentsToTypeParameters( + node.typeArguments ); + } + return result; + } - if (filteredMembers.length) { - result.body.body = filteredMembers.map(convertChild); - } + [SyntaxKind.MetaProperty](node: ts.MetaProperty): ESTreeNode { + const newToken = nodeUtils.convertToken(node.getFirstToken()!, this.ast); + return this.createNode(node, { + type: AST_NODE_TYPES.MetaProperty, + meta: { + type: AST_NODE_TYPES.Identifier, + range: newToken.range, + loc: newToken.loc, + name: nodeUtils.getTextForTokenKind(node.keywordToken) + }, + property: this.convert(node.name) + }); + } - // check for exports - result = nodeUtils.fixExports(node, result as any, ast); + // Literals - break; + [SyntaxKind.StringLiteral]( + node: ts.StringLiteral, + parent: ts.Node + ): ESTreeNode { + const result = this.createNode(node, { + type: AST_NODE_TYPES.Literal + }); + + (result as any).raw = this.ast.text.slice(result.range[0], result.range[1]); + + if ((parent as any).name && (parent as any).name === node) { + (result as any).value = node.text; + } else { + (result as any).value = nodeUtils.unescapeStringLiteralText(node.text); } + return result; + } - // Modules - case SyntaxKind.ModuleBlock: - Object.assign(result, { - type: AST_NODE_TYPES.TSModuleBlock, - body: node.statements.map(convertChild) - }); + [SyntaxKind.NumericLiteral](node: ts.NumericLiteral): ESTreeNode { + const result = this.createNode(node, { + type: AST_NODE_TYPES.Literal, + value: Number(node.text) + }); - convertBodyExpressionsToDirectives(); - break; + (result as any).raw = this.ast.text.slice(result.range[0], result.range[1]); + return result; + } - case SyntaxKind.ImportDeclaration: - Object.assign(result, { - type: AST_NODE_TYPES.ImportDeclaration, - source: convertChild(node.moduleSpecifier), - specifiers: [] - }); + [SyntaxKind.BigIntLiteral](node: ts.BigIntLiteral): ESTreeNode { + const result = this.createNode(node, { + type: AST_NODE_TYPES.BigIntLiteral + }); - if (node.importClause) { - if (node.importClause.name) { - (result as any).specifiers.push(convertChild(node.importClause)); - } + (result as any).raw = this.ast.text.slice(result.range[0], result.range[1]); + (result as any).value = (result as any).raw.slice(0, -1); // remove suffix `n` - if (node.importClause.namedBindings) { - if ( - node.importClause.namedBindings.kind === SyntaxKind.NamespaceImport - ) { - (result as any).specifiers.push( - convertChild(node.importClause.namedBindings) - ); - } else { - result.specifiers = (result as any).specifiers.concat( - node.importClause.namedBindings.elements.map(convertChild) - ); - } - } + return result; + } + + [SyntaxKind.RegularExpressionLiteral]( + node: ts.RegularExpressionLiteral + ): ESTreeNode { + const pattern = node.text.slice(1, node.text.lastIndexOf('/')); + const flags = node.text.slice(node.text.lastIndexOf('/') + 1); + + let regex = null; + try { + regex = new RegExp(pattern, flags); + } catch (exception) { + regex = null; + } + + return this.createNode(node, { + type: AST_NODE_TYPES.Literal, + value: regex, + raw: node.text, + regex: { + pattern, + flags } + }); + } - break; + [SyntaxKind.TrueKeyword](node: ts.BooleanLiteral): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.Literal, + value: true, + raw: 'true' + }); + } - case SyntaxKind.NamespaceImport: - Object.assign(result, { - type: AST_NODE_TYPES.ImportNamespaceSpecifier, - local: convertChild(node.name) - }); - break; + [SyntaxKind.FalseKeyword](node: ts.BooleanLiteral): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.Literal, + value: false, + raw: 'false' + }); + } - case SyntaxKind.ImportSpecifier: - Object.assign(result, { - type: AST_NODE_TYPES.ImportSpecifier, - local: convertChild(node.name), - imported: convertChild(node.propertyName || node.name) + [SyntaxKind.NullKeyword]( + node: ts.NullLiteral, + parent: ts.Node, + type?: boolean + ): ESTreeNode { + if (type) { + return this.createNode(node, { + type: AST_NODE_TYPES.TSNullKeyword }); - break; - - case SyntaxKind.ImportClause: - Object.assign(result, { - type: AST_NODE_TYPES.ImportDefaultSpecifier, - local: convertChild(node.name) + } else { + return this.createNode(node, { + type: AST_NODE_TYPES.Literal, + value: null, + raw: 'null' }); + } + } - // have to adjust location information due to tree differences - (result as any).range[1] = node.name!.end; - result.loc = nodeUtils.getLocFor( - (result as any).range[0], - (result as any).range[1], - ast - ); - break; - - case SyntaxKind.NamedImports: - // TODO: node has no name field - Object.assign(result, { - type: AST_NODE_TYPES.ImportDefaultSpecifier, - local: convertChild((node as any).name) - }); - break; - - case SyntaxKind.ExportDeclaration: - if (node.exportClause) { - Object.assign(result, { - type: AST_NODE_TYPES.ExportNamedDeclaration, - source: convertChild(node.moduleSpecifier), - specifiers: node.exportClause.elements.map(convertChild), - declaration: null - }); - } else { - Object.assign(result, { - type: AST_NODE_TYPES.ExportAllDeclaration, - source: convertChild(node.moduleSpecifier) - }); - } - break; - - case SyntaxKind.ExportSpecifier: - Object.assign(result, { - type: AST_NODE_TYPES.ExportSpecifier, - local: convertChild(node.propertyName || node.name), - exported: convertChild(node.name) - }); - break; - - case SyntaxKind.ExportAssignment: - if (node.isExportEquals) { - Object.assign(result, { - type: AST_NODE_TYPES.TSExportAssignment, - expression: convertChild(node.expression) - }); - } else { - Object.assign(result, { - type: AST_NODE_TYPES.ExportDefaultDeclaration, - declaration: convertChild(node.expression) - }); - } - break; + [SyntaxKind.ImportKeyword](node: ts.ImportExpression): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.Import + }); + } - // Unary Operations + [SyntaxKind.EmptyStatement](node: ts.EmptyStatement): ESTreeNode { + return this.createSimpleNode(node, AST_NODE_TYPES.EmptyStatement); + } - case SyntaxKind.PrefixUnaryExpression: - case SyntaxKind.PostfixUnaryExpression: { - const operator = nodeUtils.getTextForTokenKind(node.operator) || ''; - Object.assign(result, { - /** - * ESTree uses UpdateExpression for ++/-- - */ - type: /^(?:\+\+|--)$/.test(operator) - ? AST_NODE_TYPES.UpdateExpression - : AST_NODE_TYPES.UnaryExpression, - operator, - prefix: node.kind === SyntaxKind.PrefixUnaryExpression, - argument: convertChild(node.operand) - }); - break; - } + [SyntaxKind.DebuggerStatement](node: ts.DebuggerStatement): ESTreeNode { + return this.createSimpleNode(node, AST_NODE_TYPES.DebuggerStatement); + } - case SyntaxKind.DeleteExpression: - Object.assign(result, { - type: AST_NODE_TYPES.UnaryExpression, - operator: 'delete', - prefix: true, - argument: convertChild(node.expression) - }); - break; + // JSX - case SyntaxKind.VoidExpression: - Object.assign(result, { - type: AST_NODE_TYPES.UnaryExpression, - operator: 'void', - prefix: true, - argument: convertChild(node.expression) - }); - break; + [SyntaxKind.JsxElement](node: ts.JsxElement): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.JSXElement, + openingElement: this.convert(node.openingElement), + closingElement: this.convert(node.closingElement), + children: node.children.map(el => this.convert(el)) + }); + } - case SyntaxKind.TypeOfExpression: - Object.assign(result, { - type: AST_NODE_TYPES.UnaryExpression, - operator: 'typeof', - prefix: true, - argument: convertChild(node.expression) - }); - break; + [SyntaxKind.JsxFragment](node: ts.JsxFragment): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.JSXFragment, + openingFragment: this.convert(node.openingFragment), + closingFragment: this.convert(node.closingFragment), + children: node.children.map(el => this.convert(el)) + }); + } - case SyntaxKind.TypeOperator: - Object.assign(result, { - type: AST_NODE_TYPES.TSTypeOperator, - operator: nodeUtils.getTextForTokenKind(node.operator), - typeAnnotation: convertChild(node.type) - }); - break; + [SyntaxKind.JsxSelfClosingElement]( + node: ts.JsxSelfClosingElement + ): ESTreeNode { + /** + * Convert SyntaxKind.JsxSelfClosingElement to SyntaxKind.JsxOpeningElement, + * TypeScript does not seem to have the idea of openingElement when tag is self-closing + */ + (node as any).kind = SyntaxKind.JsxOpeningElement; - // Binary Operations + const openingElement = this.convert(node); + (openingElement as any).selfClosing = true; - case SyntaxKind.BinaryExpression: - // TypeScript uses BinaryExpression for sequences as well - if (nodeUtils.isComma(node.operatorToken)) { - Object.assign(result, { - type: AST_NODE_TYPES.SequenceExpression, - expressions: [] - }); + return this.createNode(node, { + type: AST_NODE_TYPES.JSXElement, + openingElement, + closingElement: null, + children: [] + }); + } - const left = convertChild(node.left), - right = convertChild(node.right); + [SyntaxKind.JsxOpeningElement](node: ts.JsxOpeningElement): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.JSXOpeningElement, + typeParameters: node.typeArguments + ? this.convertTypeArgumentsToTypeParameters(node.typeArguments) + : undefined, + selfClosing: false, + name: this.convertTypeScriptJSXTagNameToESTreeName(node, node.tagName), + attributes: node.attributes.properties.map(el => this.convert(el)) + }); + } - if ((left as any).type === AST_NODE_TYPES.SequenceExpression) { - (result as any).expressions = (result as any).expressions.concat( - (left as any).expressions - ); - } else { - (result as any).expressions.push(left); - } + [SyntaxKind.JsxClosingElement](node: ts.JsxClosingElement): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.JSXClosingElement, + name: this.convertTypeScriptJSXTagNameToESTreeName(node, node.tagName) + }); + } - if ((right as any).type === AST_NODE_TYPES.SequenceExpression) { - (result as any).expressions = (result as any).expressions.concat( - (right as any).expressions - ); - } else { - (result as any).expressions.push(right); - } - } else { - Object.assign(result, { - type: nodeUtils.getBinaryExpressionType(node.operatorToken), - operator: nodeUtils.getTextForTokenKind(node.operatorToken.kind), - left: convertChild(node.left), - right: convertChild(node.right) - }); + [SyntaxKind.JsxOpeningFragment](node: ts.JsxOpeningFragment): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.JSXOpeningFragment + }); + } + [SyntaxKind.JsxClosingFragment](node: ts.JsxClosingFragment): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.JSXClosingFragment + }); + } - // if the binary expression is in a destructured array, switch it - if (result.type === AST_NODE_TYPES.AssignmentExpression) { - const upperArrayNode = nodeUtils.findFirstMatchingAncestor( - node, - parent => - parent.kind === SyntaxKind.ArrayLiteralExpression || - parent.kind === SyntaxKind.ObjectLiteralExpression - ); - const upperArrayAssignNode = - upperArrayNode && - nodeUtils.findAncestorOfKind( - upperArrayNode, - SyntaxKind.BinaryExpression - ); - - let upperArrayIsInAssignment; - - if (upperArrayAssignNode) { - if ((upperArrayAssignNode as any).left === upperArrayNode) { - upperArrayIsInAssignment = true; - } else { - upperArrayIsInAssignment = - nodeUtils.findChildOfKind( - (upperArrayAssignNode as any).left, - SyntaxKind.ArrayLiteralExpression, - ast - ) === upperArrayNode; - } - } + [SyntaxKind.JsxExpression](node: ts.JsxExpression): ESTreeNode { + const result = this.createNode(node, { + type: node.dotDotDotToken + ? AST_NODE_TYPES.JSXSpreadChild + : AST_NODE_TYPES.JSXExpressionContainer + }); - if (upperArrayIsInAssignment) { - delete (result as any).operator; - result.type = AST_NODE_TYPES.AssignmentPattern; + if (node.expression) { + result.expression = this.convert(node.expression); + } else { + const eloc = this.ast.getLineAndCharacterOfPosition(result.range[0] + 1); + result.expression = { + type: AST_NODE_TYPES.JSXEmptyExpression, + loc: { + start: { + line: eloc.line + 1, + column: eloc.character + }, + end: { + line: result.loc.end.line, + column: result.loc.end.column - 1 } - } - } - break; - - case SyntaxKind.PropertyAccessExpression: - if (nodeUtils.isJSXToken(parent!)) { - const jsxMemberExpression = { - type: AST_NODE_TYPES.MemberExpression, - object: convertChild(node.expression), - property: convertChild(node.name) - }; - const isNestedMemberExpression = - node.expression.kind === SyntaxKind.PropertyAccessExpression; - if (node.expression.kind === SyntaxKind.ThisKeyword) { - (jsxMemberExpression as any).object.name = 'this'; - } - - (jsxMemberExpression as any).object.type = isNestedMemberExpression - ? AST_NODE_TYPES.MemberExpression - : AST_NODE_TYPES.JSXIdentifier; - (jsxMemberExpression as any).property.type = - AST_NODE_TYPES.JSXIdentifier; - Object.assign(result, jsxMemberExpression); - } else { - Object.assign(result, { - type: AST_NODE_TYPES.MemberExpression, - object: convertChild(node.expression), - property: convertChild(node.name), - computed: false - }); - } - break; - - case SyntaxKind.ElementAccessExpression: - Object.assign(result, { - type: AST_NODE_TYPES.MemberExpression, - object: convertChild(node.expression), - property: convertChild(node.argumentExpression), - computed: true - }); - break; - - case SyntaxKind.ConditionalExpression: - Object.assign(result, { - type: AST_NODE_TYPES.ConditionalExpression, - test: convertChild(node.condition), - consequent: convertChild(node.whenTrue), - alternate: convertChild(node.whenFalse) - }); - break; - - case SyntaxKind.CallExpression: - Object.assign(result, { - type: AST_NODE_TYPES.CallExpression, - callee: convertChild(node.expression), - arguments: node.arguments.map(convertChild) - }); - if (node.typeArguments && node.typeArguments.length) { - result.typeParameters = convertTypeArgumentsToTypeParameters( - node.typeArguments - ); - } - break; - - case SyntaxKind.NewExpression: - Object.assign(result, { - type: AST_NODE_TYPES.NewExpression, - callee: convertChild(node.expression), - arguments: node.arguments ? node.arguments.map(convertChild) : [] - }); - if (node.typeArguments && node.typeArguments.length) { - result.typeParameters = convertTypeArgumentsToTypeParameters( - node.typeArguments - ); - } - break; - - case SyntaxKind.MetaProperty: { - const newToken = nodeUtils.convertToken(node.getFirstToken()!, ast); - Object.assign(result, { - type: AST_NODE_TYPES.MetaProperty, - meta: { - type: AST_NODE_TYPES.Identifier, - range: newToken.range, - loc: newToken.loc, - name: nodeUtils.getTextForTokenKind(node.keywordToken) }, - property: convertChild(node.name) - }); - break; + range: [result.range[0] + 1, result.range[1] - 1] + }; } - // Literals - - case SyntaxKind.StringLiteral: - Object.assign(result, { - type: AST_NODE_TYPES.Literal, - raw: ast.text.slice((result as any).range[0], (result as any).range[1]) - }); - if ((parent as any).name && (parent as any).name === node) { - (result as any).value = node.text; - } else { - (result as any).value = nodeUtils.unescapeStringLiteralText(node.text); - } - break; + return result; + } - case SyntaxKind.NumericLiteral: - Object.assign(result, { - type: AST_NODE_TYPES.Literal, - value: Number(node.text), - raw: ast.text.slice((result as any).range[0], (result as any).range[1]) - }); - break; + [SyntaxKind.JsxAttribute](node: ts.JsxAttribute): ESTreeNode { + const attributeName = nodeUtils.convertToken(node.name, this.ast); + attributeName.type = AST_NODE_TYPES.JSXIdentifier; + attributeName.name = attributeName.value; + delete attributeName.value; - case SyntaxKind.BigIntLiteral: { - const raw = ast.text.slice( - (result as any).range[0], - (result as any).range[1] - ); - const value = raw.slice(0, -1); // remove suffix `n` - Object.assign(result, { - type: AST_NODE_TYPES.BigIntLiteral, - raw, - value - }); - break; - } + return this.createNode(node, { + type: AST_NODE_TYPES.JSXAttribute, + name: attributeName, + value: this.convert(node.initializer) + }); + } - case SyntaxKind.RegularExpressionLiteral: { - const pattern = node.text.slice(1, node.text.lastIndexOf('/')); - const flags = node.text.slice(node.text.lastIndexOf('/') + 1); + /** + * The JSX AST changed the node type for string literals + * inside a JSX Element from `Literal` to `JSXText`. We + * provide a flag to support both types until `Literal` + * node type is deprecated in ESLint v5. + */ + [SyntaxKind.JsxText](node: ts.JsxText): ESTreeNode { + const start = node.getFullStart(); + const end = node.getEnd(); + + const type = this.additionalOptions.useJSXTextNode + ? AST_NODE_TYPES.JSXText + : AST_NODE_TYPES.Literal; + + const result = this.createNode(node, { + type, + value: this.ast.text.slice(start, end), + raw: this.ast.text.slice(start, end) + }); - let regex = null; - try { - regex = new RegExp(pattern, flags); - } catch (exception) { - regex = null; - } + result.loc = nodeUtils.getLocFor(start, end, this.ast); + result.range = [start, end]; + return result; + } - Object.assign(result, { - type: AST_NODE_TYPES.Literal, - value: regex, - raw: node.text, - regex: { - pattern, - flags - } - }); - break; - } + [SyntaxKind.JsxSpreadAttribute](node: ts.JsxSpreadAttribute): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.JSXSpreadAttribute, + argument: this.convert(node.expression) + }); + } - case SyntaxKind.TrueKeyword: - Object.assign(result, { - type: AST_NODE_TYPES.Literal, - value: true, - raw: 'true' - }); - break; + [SyntaxKind.QualifiedName](node: ts.QualifiedName): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.TSQualifiedName, + left: this.convert(node.left), + right: this.convert(node.right) + }); + } - case SyntaxKind.FalseKeyword: - Object.assign(result, { - type: AST_NODE_TYPES.Literal, - value: false, - raw: 'false' - }); - break; + // TypeScript specific - case SyntaxKind.NullKeyword: { - if (config.inTypeMode) { - Object.assign(result, { - type: AST_NODE_TYPES.TSNullKeyword - }); - } else { - Object.assign(result, { - type: AST_NODE_TYPES.Literal, - value: null, - raw: 'null' - }); - } - break; - } + [SyntaxKind.TypeReference](node: ts.TypeReferenceNode): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.TSTypeReference, + typeName: this.convertType(node.typeName), + typeParameters: node.typeArguments + ? this.convertTypeArgumentsToTypeParameters(node.typeArguments) + : undefined + }); + } - case SyntaxKind.ImportKeyword: - Object.assign(result, { - type: AST_NODE_TYPES.Import - }); - break; + [SyntaxKind.TypeParameter](node: ts.TypeParameterDeclaration): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.TSTypeParameter, + name: node.name.text, + constraint: node.constraint + ? this.convertType(node.constraint) + : undefined, + default: node.default ? this.convertType(node.default) : undefined + }); + } - case SyntaxKind.EmptyStatement: - case SyntaxKind.DebuggerStatement: - Object.assign(result, { - type: SyntaxKind[node.kind] - }); - break; + [SyntaxKind.AnyKeyword](node: ts.KeywordTypeNode): ESTreeNode { + return this.createSimpleNode(node, AST_NODE_TYPES.TSAnyKeyword); + } + [SyntaxKind.BigIntKeyword](node: ts.KeywordTypeNode): ESTreeNode { + return this.createSimpleNode(node, AST_NODE_TYPES.TSBigIntKeyword); + } + [SyntaxKind.BooleanKeyword](node: ts.KeywordTypeNode): ESTreeNode { + return this.createSimpleNode(node, AST_NODE_TYPES.TSBooleanKeyword); + } + [SyntaxKind.NeverKeyword](node: ts.KeywordTypeNode): ESTreeNode { + return this.createSimpleNode(node, AST_NODE_TYPES.TSNeverKeyword); + } + [SyntaxKind.NumberKeyword](node: ts.KeywordTypeNode): ESTreeNode { + return this.createSimpleNode(node, AST_NODE_TYPES.TSNumberKeyword); + } + [SyntaxKind.ObjectKeyword](node: ts.KeywordTypeNode): ESTreeNode { + return this.createSimpleNode(node, AST_NODE_TYPES.TSObjectKeyword); + } + [SyntaxKind.StringKeyword](node: ts.KeywordTypeNode): ESTreeNode { + return this.createSimpleNode(node, AST_NODE_TYPES.TSStringKeyword); + } + [SyntaxKind.SymbolKeyword](node: ts.KeywordTypeNode): ESTreeNode { + return this.createSimpleNode(node, AST_NODE_TYPES.TSSymbolKeyword); + } + [SyntaxKind.UnknownKeyword](node: ts.KeywordTypeNode): ESTreeNode { + return this.createSimpleNode(node, AST_NODE_TYPES.TSUnknownKeyword); + } + [SyntaxKind.VoidKeyword](node: ts.KeywordTypeNode): ESTreeNode { + return this.createSimpleNode(node, AST_NODE_TYPES.TSVoidKeyword); + } + [SyntaxKind.UndefinedKeyword](node: ts.KeywordTypeNode): ESTreeNode { + return this.createSimpleNode(node, AST_NODE_TYPES.TSUndefinedKeyword); + } - // JSX + [SyntaxKind.NonNullExpression](node: ts.NonNullExpression): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.TSNonNullExpression, + expression: this.convert(node.expression) + }); + } - case SyntaxKind.JsxElement: - Object.assign(result, { - type: AST_NODE_TYPES.JSXElement, - openingElement: convertChild(node.openingElement), - closingElement: convertChild(node.closingElement), - children: node.children.map(convertChild) - }); + [SyntaxKind.TypeLiteral](node: ts.TypeLiteralNode): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.TSTypeLiteral, + members: node.members.map(el => this.convert(el)) + }); + } - break; + [SyntaxKind.ArrayType](node: ts.ArrayTypeNode): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.TSArrayType, + elementType: this.convertType(node.elementType) + }); + } - case SyntaxKind.JsxFragment: - Object.assign(result, { - type: AST_NODE_TYPES.JSXFragment, - openingFragment: convertChild((node as ts.JsxFragment).openingFragment), - closingFragment: convertChild((node as ts.JsxFragment).closingFragment), - children: node.children.map(convertChild) - }); - break; + [SyntaxKind.IndexedAccessType](node: ts.IndexedAccessTypeNode): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.TSIndexedAccessType, + objectType: this.convertType(node.objectType), + indexType: this.convertType(node.indexType) + }); + } - case SyntaxKind.JsxSelfClosingElement: { - /** - * Convert SyntaxKind.JsxSelfClosingElement to SyntaxKind.JsxOpeningElement, - * TypeScript does not seem to have the idea of openingElement when tag is self-closing - */ - (node as any).kind = SyntaxKind.JsxOpeningElement; + [SyntaxKind.ConditionalType](node: ts.ConditionalTypeNode): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.TSConditionalType, + checkType: this.convertType(node.checkType), + extendsType: this.convertType(node.extendsType), + trueType: this.convertType(node.trueType), + falseType: this.convertType(node.falseType) + }); + } - const openingElement = convertChild(node); - (openingElement as any).selfClosing = true; + [SyntaxKind.TypeQuery](node: ts.TypeQueryNode): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.TSTypeQuery, + exprName: this.convertType(node.exprName) + }); + } - Object.assign(result, { - type: AST_NODE_TYPES.JSXElement, - openingElement, - closingElement: null, - children: [] - }); + [SyntaxKind.MappedType](node: ts.MappedTypeNode): ESTreeNode { + const result = this.createNode(node, { + type: AST_NODE_TYPES.TSMappedType, + typeParameter: this.convertType(node.typeParameter) + }); - break; + if (node.readonlyToken) { + if (node.readonlyToken.kind === SyntaxKind.ReadonlyKeyword) { + (result as any).readonly = true; + } else { + (result as any).readonly = nodeUtils.getTextForTokenKind( + node.readonlyToken.kind + ); + } } - case SyntaxKind.JsxOpeningElement: - Object.assign(result, { - type: AST_NODE_TYPES.JSXOpeningElement, - typeParameters: node.typeArguments - ? convertTypeArgumentsToTypeParameters(node.typeArguments) - : undefined, - selfClosing: false, - name: convertTypeScriptJSXTagNameToESTreeName(node.tagName), - attributes: node.attributes.properties.map(convertChild) - }); - break; - - case SyntaxKind.JsxClosingElement: - Object.assign(result, { - type: AST_NODE_TYPES.JSXClosingElement, - name: convertTypeScriptJSXTagNameToESTreeName(node.tagName) - }); - break; - - case SyntaxKind.JsxOpeningFragment: - Object.assign(result, { - type: AST_NODE_TYPES.JSXOpeningFragment - }); - break; - case SyntaxKind.JsxClosingFragment: - Object.assign(result, { - type: AST_NODE_TYPES.JSXClosingFragment - }); - break; - - case SyntaxKind.JsxExpression: { - const eloc = ast.getLineAndCharacterOfPosition( - (result as any).range[0] + 1 - ); - const expression = node.expression - ? convertChild(node.expression) - : { - type: AST_NODE_TYPES.JSXEmptyExpression, - loc: { - start: { - line: eloc.line + 1, - column: eloc.character - }, - end: { - line: (result as any).loc.end.line, - column: (result as any).loc.end.column - 1 - } - }, - range: [(result as any).range[0] + 1, (result as any).range[1] - 1] - }; - - Object.assign(result, { - type: node.dotDotDotToken - ? AST_NODE_TYPES.JSXSpreadChild - : AST_NODE_TYPES.JSXExpressionContainer, - expression - }); - - break; + if (node.questionToken) { + if (node.questionToken.kind === SyntaxKind.QuestionToken) { + (result as any).optional = true; + } else { + (result as any).optional = nodeUtils.getTextForTokenKind( + node.questionToken.kind + ); + } } - case SyntaxKind.JsxAttribute: { - const attributeName = nodeUtils.convertToken(node.name, ast); - attributeName.type = AST_NODE_TYPES.JSXIdentifier; - attributeName.name = attributeName.value; - delete attributeName.value; - - Object.assign(result, { - type: AST_NODE_TYPES.JSXAttribute, - name: attributeName, - value: convertChild(node.initializer) - }); - - break; + if (node.type) { + result.typeAnnotation = this.convertType(node.type); } + return result; + } - /** - * The JSX AST changed the node type for string literals - * inside a JSX Element from `Literal` to `JSXText`. We - * provide a flag to support both types until `Literal` - * node type is deprecated in ESLint v5. - */ - case SyntaxKind.JsxText: { - const start = node.getFullStart(); - const end = node.getEnd(); - - const type = additionalOptions.useJSXTextNode - ? AST_NODE_TYPES.JSXText - : AST_NODE_TYPES.Literal; - - Object.assign(result, { - type, - value: ast.text.slice(start, end), - raw: ast.text.slice(start, end) - }); + [SyntaxKind.ParenthesizedExpression]( + node: ts.ParenthesizedExpression, + parent: ts.Node + ): ESTreeNode | null { + return this.convert(node.expression, parent); + } - result.loc = nodeUtils.getLocFor(start, end, ast); - result.range = [start, end]; + [SyntaxKind.TypeAliasDeclaration](node: ts.TypeAliasDeclaration): ESTreeNode { + const result = this.createNode(node, { + type: AST_NODE_TYPES.TSTypeAliasDeclaration, + id: this.convert(node.name), + typeAnnotation: this.convertType(node.type) + }); - break; + if (nodeUtils.hasModifier(SyntaxKind.DeclareKeyword, node)) { + result.declare = true; } - case SyntaxKind.JsxSpreadAttribute: - Object.assign(result, { - type: AST_NODE_TYPES.JSXSpreadAttribute, - argument: convertChild(node.expression) - }); - - break; - - case SyntaxKind.FirstNode: { - Object.assign(result, { - type: AST_NODE_TYPES.TSQualifiedName, - left: convertChild(node.left), - right: convertChild(node.right) - }); - - break; + // Process typeParameters + if (node.typeParameters && node.typeParameters.length) { + (result as any).typeParameters = this.convertTSTypeParametersToTypeParametersDeclaration( + node.typeParameters + ); } - // TypeScript specific - - case SyntaxKind.TypeReference: { - Object.assign(result, { - type: AST_NODE_TYPES.TSTypeReference, - typeName: convertChildType(node.typeName), - typeParameters: node.typeArguments - ? convertTypeArgumentsToTypeParameters(node.typeArguments) - : undefined - }); - break; - } + // check for exports + return nodeUtils.fixExports(node, result, this.ast); + } - case SyntaxKind.TypeParameter: { - Object.assign(result, { - type: AST_NODE_TYPES.TSTypeParameter, - name: node.name.text, - constraint: node.constraint - ? convertChildType(node.constraint) - : undefined, - default: node.default ? convertChildType(node.default) : undefined - }); - break; - } - - case SyntaxKind.AnyKeyword: - case SyntaxKind.BigIntKeyword: - case SyntaxKind.BooleanKeyword: - case SyntaxKind.NeverKeyword: - case SyntaxKind.NumberKeyword: - case SyntaxKind.ObjectKeyword: - case SyntaxKind.StringKeyword: - case SyntaxKind.SymbolKeyword: - case SyntaxKind.UnknownKeyword: - case SyntaxKind.VoidKeyword: - case SyntaxKind.UndefinedKeyword: { - Object.assign(result, { - type: `TS${SyntaxKind[node.kind]}` - }); - break; - } + [SyntaxKind.MethodSignature](node: ts.MethodSignature): ESTreeNode { + const result = this.createNode(node, { + type: AST_NODE_TYPES.TSMethodSignature, + optional: nodeUtils.isOptional(node), + computed: nodeUtils.isComputedProperty(node.name), + key: this.convert(node.name), + params: this.convertParameters(node.parameters), + typeAnnotation: node.type ? this.convertTypeAnnotation(node.type) : null, + readonly: + nodeUtils.hasModifier(SyntaxKind.ReadonlyKeyword, node) || undefined, + static: nodeUtils.hasModifier(SyntaxKind.StaticKeyword, node), + export: nodeUtils.hasModifier(SyntaxKind.ExportKeyword, node) || undefined + }); - case SyntaxKind.NonNullExpression: { - Object.assign(result, { - type: AST_NODE_TYPES.TSNonNullExpression, - expression: convertChild(node.expression) - }); - break; + const accessibility = nodeUtils.getTSNodeAccessibility(node); + if (accessibility) { + (result as any).accessibility = accessibility; } - case SyntaxKind.TypeLiteral: { - Object.assign(result, { - type: AST_NODE_TYPES.TSTypeLiteral, - members: node.members.map(convertChild) - }); - break; + if (node.typeParameters) { + (result as any).typeParameters = this.convertTSTypeParametersToTypeParametersDeclaration( + node.typeParameters + ); } + return result; + } - case SyntaxKind.ArrayType: { - Object.assign(result, { - type: AST_NODE_TYPES.TSArrayType, - elementType: convertChildType(node.elementType) - }); - break; - } + [SyntaxKind.PropertySignature](node: ts.PropertySignature): ESTreeNode { + const result = this.createNode(node, { + type: AST_NODE_TYPES.TSPropertySignature, + optional: nodeUtils.isOptional(node) || undefined, + computed: nodeUtils.isComputedProperty(node.name), + key: this.convert(node.name), + typeAnnotation: node.type + ? this.convertTypeAnnotation(node.type) + : undefined, + initializer: this.convert(node.initializer) || undefined, + readonly: + nodeUtils.hasModifier(SyntaxKind.ReadonlyKeyword, node) || undefined, + static: + nodeUtils.hasModifier(SyntaxKind.StaticKeyword, node) || undefined, + export: nodeUtils.hasModifier(SyntaxKind.ExportKeyword, node) || undefined + }); - case SyntaxKind.IndexedAccessType: { - Object.assign(result, { - type: AST_NODE_TYPES.TSIndexedAccessType, - objectType: convertChildType(node.objectType), - indexType: convertChildType(node.indexType) - }); - break; + const accessibility = nodeUtils.getTSNodeAccessibility(node); + if (accessibility) { + (result as any).accessibility = accessibility; } + return result; + } - case SyntaxKind.ConditionalType: { - Object.assign(result, { - type: AST_NODE_TYPES.TSConditionalType, - checkType: convertChildType(node.checkType), - extendsType: convertChildType(node.extendsType), - trueType: convertChildType(node.trueType), - falseType: convertChildType(node.falseType) - }); - break; - } + [SyntaxKind.IndexSignature](node: ts.IndexSignatureDeclaration): ESTreeNode { + const result = this.createNode(node, { + type: AST_NODE_TYPES.TSIndexSignature, + index: this.convert(node.parameters[0]), + typeAnnotation: node.type ? this.convertTypeAnnotation(node.type) : null, + readonly: + nodeUtils.hasModifier(SyntaxKind.ReadonlyKeyword, node) || undefined, + static: nodeUtils.hasModifier(SyntaxKind.StaticKeyword, node), + export: nodeUtils.hasModifier(SyntaxKind.ExportKeyword, node) || undefined + }); - case SyntaxKind.TypeQuery: { - Object.assign(result, { - type: AST_NODE_TYPES.TSTypeQuery, - exprName: convertChildType(node.exprName) - }); - break; + const accessibility = nodeUtils.getTSNodeAccessibility(node); + if (accessibility) { + (result as any).accessibility = accessibility; } + return result; + } - case SyntaxKind.MappedType: { - Object.assign(result, { - type: AST_NODE_TYPES.TSMappedType, - typeParameter: convertChildType(node.typeParameter) - }); - - if (node.readonlyToken) { - if (node.readonlyToken.kind === SyntaxKind.ReadonlyKeyword) { - (result as any).readonly = true; - } else { - (result as any).readonly = nodeUtils.getTextForTokenKind( - node.readonlyToken.kind - ); - } - } - - if (node.questionToken) { - if (node.questionToken.kind === SyntaxKind.QuestionToken) { - (result as any).optional = true; - } else { - (result as any).optional = nodeUtils.getTextForTokenKind( - node.questionToken.kind - ); - } - } + [SyntaxKind.ConstructSignature]( + node: ts.ConstructSignatureDeclaration + ): ESTreeNode { + const result = this.createNode(node, { + type: AST_NODE_TYPES.TSConstructSignature, + params: this.convertParameters(node.parameters), + typeAnnotation: node.type ? this.convertTypeAnnotation(node.type) : null + }); - if (node.type) { - result.typeAnnotation = convertChildType(node.type); - } - break; + if (node.typeParameters) { + result.typeParameters = this.convertTSTypeParametersToTypeParametersDeclaration( + node.typeParameters + ); } + return result; + } - case SyntaxKind.ParenthesizedExpression: - return convert({ - node: node.expression, - parent, - ast, - additionalOptions - }); + [SyntaxKind.InterfaceDeclaration](node: ts.InterfaceDeclaration): ESTreeNode { + const result = this.createNode(node, {}); - case SyntaxKind.TypeAliasDeclaration: { - Object.assign(result, { - type: AST_NODE_TYPES.TSTypeAliasDeclaration, - id: convertChild(node.name), - typeAnnotation: convertChildType(node.type) - }); + const interfaceHeritageClauses = node.heritageClauses || []; - if (nodeUtils.hasModifier(SyntaxKind.DeclareKeyword, node)) { - result.declare = true; - } + let interfaceLastClassToken = interfaceHeritageClauses.length + ? interfaceHeritageClauses[interfaceHeritageClauses.length - 1] + : node.name; - // Process typeParameters - if (node.typeParameters && node.typeParameters.length) { - (result as any).typeParameters = convertTSTypeParametersToTypeParametersDeclaration( - node.typeParameters - ); - } + if (node.typeParameters && node.typeParameters.length) { + const interfaceLastTypeParameter = + node.typeParameters[node.typeParameters.length - 1]; - // check for exports - result = nodeUtils.fixExports(node, result as any, ast); - break; + if ( + !interfaceLastClassToken || + interfaceLastTypeParameter.pos > interfaceLastClassToken.pos + ) { + interfaceLastClassToken = nodeUtils.findNextToken( + interfaceLastTypeParameter, + this.ast, + this.ast + ) as any; + } + result.typeParameters = this.convertTSTypeParametersToTypeParametersDeclaration( + node.typeParameters + ); } - case SyntaxKind.MethodSignature: { - Object.assign(result, { - type: AST_NODE_TYPES.TSMethodSignature, - optional: nodeUtils.isOptional(node), - computed: nodeUtils.isComputedProperty(node.name), - key: convertChild(node.name), - params: convertParameters(node.parameters), - typeAnnotation: node.type ? convertTypeAnnotation(node.type) : null, - readonly: - nodeUtils.hasModifier(SyntaxKind.ReadonlyKeyword, node) || undefined, - static: nodeUtils.hasModifier(SyntaxKind.StaticKeyword, node), - export: - nodeUtils.hasModifier(SyntaxKind.ExportKeyword, node) || undefined - }); - - const accessibility = nodeUtils.getTSNodeAccessibility(node); - if (accessibility) { - (result as any).accessibility = accessibility; - } - - if (node.typeParameters) { - (result as any).typeParameters = convertTSTypeParametersToTypeParametersDeclaration( - node.typeParameters - ); - } + const hasImplementsClause = interfaceHeritageClauses.length > 0; + const interfaceOpenBrace = nodeUtils.findNextToken( + interfaceLastClassToken, + this.ast, + this.ast + )!; + + const interfaceBody = { + type: AST_NODE_TYPES.TSInterfaceBody, + body: node.members.map((member: any) => this.convert(member)), + range: [interfaceOpenBrace.getStart(this.ast), result.range[1]], + loc: nodeUtils.getLocFor( + interfaceOpenBrace.getStart(this.ast), + node.end, + this.ast + ) + }; - break; + Object.assign(result, { + type: AST_NODE_TYPES.TSInterfaceDeclaration, + body: interfaceBody, + id: this.convert(node.name), + heritage: hasImplementsClause + ? interfaceHeritageClauses[0].types.map(el => + this.convertInterfaceHeritageClause(el) + ) + : [] + }); + /** + * Semantically, decorators are not allowed on interface declarations, + * but the TypeScript compiler will parse them and produce a valid AST, + * so we handle them here too. + */ + if (node.decorators) { + result.decorators = this.convertDecorators(node.decorators); } - - case SyntaxKind.PropertySignature: { - Object.assign(result, { - type: AST_NODE_TYPES.TSPropertySignature, - optional: nodeUtils.isOptional(node) || undefined, - computed: nodeUtils.isComputedProperty(node.name), - key: convertChild(node.name), - typeAnnotation: node.type - ? convertTypeAnnotation(node.type) - : undefined, - initializer: convertChild(node.initializer) || undefined, - readonly: - nodeUtils.hasModifier(SyntaxKind.ReadonlyKeyword, node) || undefined, - static: - nodeUtils.hasModifier(SyntaxKind.StaticKeyword, node) || undefined, - export: - nodeUtils.hasModifier(SyntaxKind.ExportKeyword, node) || undefined - }); - - const accessibility = nodeUtils.getTSNodeAccessibility(node); - if (accessibility) { - (result as any).accessibility = accessibility; - } - - break; + if (nodeUtils.hasModifier(SyntaxKind.AbstractKeyword, node)) { + result.abstract = true; } - - case SyntaxKind.IndexSignature: { - Object.assign(result, { - type: AST_NODE_TYPES.TSIndexSignature, - index: convertChild(node.parameters[0]), - typeAnnotation: node.type ? convertTypeAnnotation(node.type) : null, - readonly: - nodeUtils.hasModifier(SyntaxKind.ReadonlyKeyword, node) || undefined, - static: nodeUtils.hasModifier(SyntaxKind.StaticKeyword, node), - export: - nodeUtils.hasModifier(SyntaxKind.ExportKeyword, node) || undefined - }); - - const accessibility = nodeUtils.getTSNodeAccessibility(node); - if (accessibility) { - (result as any).accessibility = accessibility; - } - - break; + if (nodeUtils.hasModifier(SyntaxKind.DeclareKeyword, node)) { + result.declare = true; } + // check for exports + return nodeUtils.fixExports(node, result, this.ast); + } - case SyntaxKind.ConstructSignature: { - Object.assign(result, { - type: AST_NODE_TYPES.TSConstructSignature, - params: convertParameters(node.parameters), - typeAnnotation: node.type ? convertTypeAnnotation(node.type) : null - }); + [SyntaxKind.TypePredicate](node: ts.TypePredicateNode): ESTreeNode { + const result = this.createNode(node, { + type: AST_NODE_TYPES.TSTypePredicate, + parameterName: this.convert(node.parameterName), + typeAnnotation: this.convertTypeAnnotation(node.type) + }); + /** + * Specific fix for type-guard location data + */ + (result as any).typeAnnotation.loc = (result as any).typeAnnotation.typeAnnotation.loc; + (result as any).typeAnnotation.range = (result as any).typeAnnotation.typeAnnotation.range; + return result; + } - if (node.typeParameters) { - result.typeParameters = convertTSTypeParametersToTypeParametersDeclaration( - node.typeParameters - ); - } + [SyntaxKind.ImportType](node: ts.ImportTypeNode): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.TSImportType, + isTypeOf: !!node.isTypeOf, + parameter: this.convert(node.argument), + qualifier: this.convert(node.qualifier), + typeParameters: node.typeArguments + ? this.convertTypeArgumentsToTypeParameters(node.typeArguments) + : null + }); + } - break; + [SyntaxKind.EnumDeclaration](node: ts.EnumDeclaration): ESTreeNode { + let result = this.createNode(node, { + type: AST_NODE_TYPES.TSEnumDeclaration, + id: this.convert(node.name), + members: node.members.map(el => this.convert(el)) + }); + // apply modifiers first... + this.applyModifiersToResult(result, node.modifiers); + // ...then check for exports + result = nodeUtils.fixExports(node, result, this.ast); + /** + * Semantically, decorators are not allowed on enum declarations, + * but the TypeScript compiler will parse them and produce a valid AST, + * so we handle them here too. + */ + if (node.decorators) { + result.decorators = this.convertDecorators(node.decorators); } + return result; + } - case SyntaxKind.InterfaceDeclaration: { - const interfaceHeritageClauses = node.heritageClauses || []; - - let interfaceLastClassToken = interfaceHeritageClauses.length - ? interfaceHeritageClauses[interfaceHeritageClauses.length - 1] - : node.name; - - if (node.typeParameters && node.typeParameters.length) { - const interfaceLastTypeParameter = - node.typeParameters[node.typeParameters.length - 1]; - - if ( - !interfaceLastClassToken || - interfaceLastTypeParameter.pos > interfaceLastClassToken.pos - ) { - interfaceLastClassToken = nodeUtils.findNextToken( - interfaceLastTypeParameter, - ast, - ast - ) as any; - } - result.typeParameters = convertTSTypeParametersToTypeParametersDeclaration( - node.typeParameters - ); - } - - const hasImplementsClause = interfaceHeritageClauses.length > 0; - const interfaceOpenBrace = nodeUtils.findNextToken( - interfaceLastClassToken, - ast, - ast - )!; - - const interfaceBody = { - type: AST_NODE_TYPES.TSInterfaceBody, - body: node.members.map((member: any) => convertChild(member)), - range: [interfaceOpenBrace.getStart(ast), (result as any).range[1]], - loc: nodeUtils.getLocFor( - interfaceOpenBrace.getStart(ast), - node.end, - ast - ) - }; - - Object.assign(result, { - type: AST_NODE_TYPES.TSInterfaceDeclaration, - body: interfaceBody, - id: convertChild(node.name), - heritage: hasImplementsClause - ? interfaceHeritageClauses[0].types.map( - convertInterfaceHeritageClause - ) - : [] - }); - /** - * Semantically, decorators are not allowed on interface declarations, - * but the TypeScript compiler will parse them and produce a valid AST, - * so we handle them here too. - */ - if (node.decorators) { - result.decorators = convertDecorators(node.decorators); - } - if (nodeUtils.hasModifier(SyntaxKind.AbstractKeyword, node)) { - result.abstract = true; - } - if (nodeUtils.hasModifier(SyntaxKind.DeclareKeyword, node)) { - result.declare = true; - } - // check for exports - result = nodeUtils.fixExports(node, result as any, ast); - - break; + [SyntaxKind.EnumMember](node: ts.EnumMember): ESTreeNode { + const result = this.createNode(node, { + type: AST_NODE_TYPES.TSEnumMember, + id: this.convert(node.name) + }); + if (node.initializer) { + (result as any).initializer = this.convert(node.initializer); } + return result; + } - case SyntaxKind.FirstTypeNode: - Object.assign(result, { - type: AST_NODE_TYPES.TSTypePredicate, - parameterName: convertChild(node.parameterName), - typeAnnotation: convertTypeAnnotation(node.type) - }); - /** - * Specific fix for type-guard location data - */ - (result as any).typeAnnotation.loc = (result as any).typeAnnotation.typeAnnotation.loc; - (result as any).typeAnnotation.range = (result as any).typeAnnotation.typeAnnotation.range; - break; - - case SyntaxKind.ImportType: - Object.assign(result, { - type: AST_NODE_TYPES.TSImportType, - isTypeOf: !!node.isTypeOf, - parameter: convertChild(node.argument), - qualifier: convertChild(node.qualifier), - typeParameters: node.typeArguments - ? convertTypeArgumentsToTypeParameters(node.typeArguments) - : null - }); - break; + [SyntaxKind.AbstractKeyword]( + node: ts.Token + ): ESTreeNode { + return this.createSimpleNode(node, AST_NODE_TYPES.TSAbstractKeyword); + } - case SyntaxKind.EnumDeclaration: { - Object.assign(result, { - type: AST_NODE_TYPES.TSEnumDeclaration, - id: convertChild(node.name), - members: node.members.map(convertChild) - }); - // apply modifiers first... - applyModifiersToResult(node.modifiers); - // ...then check for exports - result = nodeUtils.fixExports(node, result as any, ast); - /** - * Semantically, decorators are not allowed on enum declarations, - * but the TypeScript compiler will parse them and produce a valid AST, - * so we handle them here too. - */ - if (node.decorators) { - result.decorators = convertDecorators(node.decorators); - } - break; + [SyntaxKind.ModuleDeclaration](node: ts.ModuleDeclaration): ESTreeNode { + const result = this.createNode(node, { + type: AST_NODE_TYPES.TSModuleDeclaration, + id: this.convert(node.name) + }); + if (node.body) { + result.body = this.convert(node.body); } - - case SyntaxKind.EnumMember: { - Object.assign(result, { - type: AST_NODE_TYPES.TSEnumMember, - id: convertChild(node.name) - }); - if (node.initializer) { - (result as any).initializer = convertChild(node.initializer); - } - break; + // apply modifiers first... + this.applyModifiersToResult(result, node.modifiers); + if (node.flags & ts.NodeFlags.GlobalAugmentation) { + result.global = true; } + // ...then check for exports + return nodeUtils.fixExports(node, result, this.ast); + } - case SyntaxKind.AbstractKeyword: { - Object.assign(result, { - type: AST_NODE_TYPES.TSAbstractKeyword - }); - break; - } + // TypeScript specific types + [SyntaxKind.OptionalType](node: ts.OptionalTypeNode): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.TSOptionalType, + typeAnnotation: this.convertType(node.type) + }); + } - case SyntaxKind.ModuleDeclaration: { - Object.assign(result, { - type: AST_NODE_TYPES.TSModuleDeclaration, - id: convertChild(node.name) - }); - if (node.body) { - result.body = convertChild(node.body); - } - // apply modifiers first... - applyModifiersToResult(node.modifiers); - if (node.flags & ts.NodeFlags.GlobalAugmentation) { - result.global = true; - } - // ...then check for exports - result = nodeUtils.fixExports(node, result as any, ast); - break; - } + [SyntaxKind.ParenthesizedType](node: ts.ParenthesizedTypeNode): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.TSParenthesizedType, + typeAnnotation: this.convertType(node.type) + }); + } - // TypeScript specific types - case SyntaxKind.OptionalType: { - Object.assign(result, { - type: AST_NODE_TYPES.TSOptionalType, - typeAnnotation: convertChildType(node.type) - }); - break; - } - case SyntaxKind.ParenthesizedType: { - Object.assign(result, { - type: AST_NODE_TYPES.TSParenthesizedType, - typeAnnotation: convertChildType(node.type) - }); - break; - } - case SyntaxKind.TupleType: { - Object.assign(result, { - type: AST_NODE_TYPES.TSTupleType, - elementTypes: node.elementTypes.map(convertChildType) - }); - break; - } - case SyntaxKind.UnionType: { - Object.assign(result, { - type: AST_NODE_TYPES.TSUnionType, - types: node.types.map(convertChildType) - }); - break; - } - case SyntaxKind.IntersectionType: { - Object.assign(result, { - type: AST_NODE_TYPES.TSIntersectionType, - types: node.types.map(convertChildType) - }); - break; - } - case SyntaxKind.RestType: { - Object.assign(result, { - type: AST_NODE_TYPES.TSRestType, - typeAnnotation: convertChildType(node.type) - }); - break; - } + [SyntaxKind.TupleType](node: ts.TupleTypeNode): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.TSTupleType, + elementTypes: node.elementTypes.map(type => this.convertType(type)) + }); + } - default: - deeplyCopy(); + [SyntaxKind.UnionType](node: ts.UnionTypeNode): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.TSUnionType, + types: node.types.map(type => this.convertType(type)) + }); } - if (additionalOptions.shouldProvideParserServices) { - tsNodeToESTreeNodeMap.set(node, result); - esTreeNodeToTSNodeMap.set(result, node); + [SyntaxKind.IntersectionType](node: ts.IntersectionTypeNode): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.TSIntersectionType, + types: node.types.map(type => this.convertType(type)) + }); } - return result as any; + [SyntaxKind.RestType](node: ts.RestTypeNode): ESTreeNode { + return this.createNode(node, { + type: AST_NODE_TYPES.TSRestType, + typeAnnotation: this.convertType(node.type) + }); + } } diff --git a/src/node-utils.ts b/src/node-utils.ts index 4b016cd..891b458 100644 --- a/src/node-utils.ts +++ b/src/node-utils.ts @@ -11,7 +11,6 @@ import { ESTreeNode, ESTreeToken } from './temp-types-based-on-js-source'; -import { TSNode } from './ts-nodes'; import { AST_NODE_TYPES } from './ast-node-types'; const SyntaxKind = ts.SyntaxKind; @@ -322,12 +321,12 @@ function canContainDirective(node: ts.Node): boolean { /** * Returns line and column data for the given ts.Node or ts.Token, * for the given AST - * @param {ts.Token|TSNode} nodeOrToken the ts.Node or ts.Token + * @param {ts.Token} nodeOrToken the ts.Node or ts.Token * @param {ts.SourceFile} ast the AST object * @returns {ESTreeLoc} the loc data */ function getLoc( - nodeOrToken: TSNode | ts.Token, + nodeOrToken: ts.Token, ast: ts.SourceFile ): ESTreeNodeLoc { return getLocFor(nodeOrToken.getStart(ast), nodeOrToken.end, ast); @@ -357,22 +356,19 @@ function isJSXToken(node: ts.Node): boolean { /** * Returns the declaration kind of the given ts.Node - * @param {ts.Node} node TypeScript AST node + * @param {ts.VariableDeclarationList} node TypeScript AST node * @returns {string} declaration kind */ -function getDeclarationKind(node: ts.Node): 'let' | 'const' | 'var' { - switch (node.kind) { - case SyntaxKind.VariableDeclarationList: - if (node.flags & ts.NodeFlags.Let) { - return 'let'; - } - if (node.flags & ts.NodeFlags.Const) { - return 'const'; - } - return 'var'; - default: - throw 'Unable to determine declaration kind.'; +function getDeclarationKind( + node: ts.VariableDeclarationList +): 'let' | 'const' | 'var' { + if (node.flags & ts.NodeFlags.Let) { + return 'let'; + } + if (node.flags & ts.NodeFlags.Const) { + return 'const'; } + return 'var'; } /** diff --git a/src/temp-types-based-on-js-source.ts b/src/temp-types-based-on-js-source.ts index 8ee48b9..f0634ff 100644 --- a/src/temp-types-based-on-js-source.ts +++ b/src/temp-types-based-on-js-source.ts @@ -22,14 +22,15 @@ export interface ESTreeNode { type: string; loc: ESTreeNodeLoc; range: number[]; + declaration?: ESTreeNode; - specifiers?: any[]; + specifiers?: (ESTreeNode | null)[]; source?: any; typeAnnotation?: ESTreeNode | null; typeParameters?: ESTreeNode | null; id?: ESTreeNode | null; expression?: ESTreeNode | null; - decorators?: any; + decorators?: (ESTreeNode | null)[]; const?: boolean; declare?: boolean; global?: boolean; @@ -42,6 +43,8 @@ export interface ESTreeNode { export?: boolean; parameter?: any; abstract?: boolean; + typeName?: ESTreeNode | null; + directive?: string; } export interface ESTreeComment extends ESTreeNode {} diff --git a/src/ts-nodes.ts b/src/ts-nodes.ts deleted file mode 100644 index 69682cb..0000000 --- a/src/ts-nodes.ts +++ /dev/null @@ -1,180 +0,0 @@ -import ts from 'typescript'; - -export type TSNode = ts.Node & - ( - | ts.Modifier - | ts.Identifier - | ts.QualifiedName - | ts.ComputedPropertyName - | ts.Decorator - | ts.TypeParameterDeclaration - // | ts.SignatureDeclarationBase -> CallSignatureDeclaration, ConstructSignatureDeclaration - | ts.CallSignatureDeclaration - | ts.ConstructSignatureDeclaration - | ts.VariableDeclaration - | ts.VariableDeclarationList - | ts.ParameterDeclaration - | ts.BindingElement - | ts.PropertySignature - | ts.PropertyDeclaration - | ts.PropertyAssignment - | ts.ShorthandPropertyAssignment - | ts.SpreadAssignment - | ts.ObjectBindingPattern - | ts.ArrayBindingPattern - | ts.FunctionDeclaration - | ts.MethodSignature - | ts.MethodDeclaration - | ts.ConstructorDeclaration - | ts.SemicolonClassElement - | ts.GetAccessorDeclaration - | ts.SetAccessorDeclaration - | ts.IndexSignatureDeclaration - | ts.KeywordTypeNode - | ts.ImportTypeNode - | ts.ThisTypeNode - // | ts.FunctionOrConstructorTypeNodeBase -> FunctionTypeNode, ConstructorTypeNode - | ts.ConstructorTypeNode - | ts.TypeReferenceNode - | ts.TypePredicateNode - | ts.TypeQueryNode - | ts.TypeLiteralNode - | ts.ArrayTypeNode - | ts.TupleTypeNode - | ts.OptionalTypeNode - | ts.RestTypeNode - | ts.UnionTypeNode - | ts.IntersectionTypeNode - | ts.ConditionalTypeNode - | ts.InferTypeNode - | ts.ParenthesizedTypeNode - | ts.TypeOperatorNode - | ts.IndexedAccessTypeNode - | ts.FunctionTypeNode - | ts.ConstructorTypeNode - | ts.MappedTypeNode - | ts.LiteralTypeNode - | ts.StringLiteral - | ts.OmittedExpression - | ts.PartiallyEmittedExpression - | ts.PrefixUnaryExpression - | ts.PostfixUnaryExpression - | ts.NullLiteral - | ts.BooleanLiteral - | ts.ThisExpression - | ts.SuperExpression - | ts.ImportExpression - | ts.DeleteExpression - | ts.TypeOfExpression - | ts.VoidExpression - | ts.AwaitExpression - | ts.YieldExpression - | ts.SyntheticExpression - | ts.BinaryExpression - | ts.ConditionalExpression - | ts.FunctionExpression - | ts.ArrowFunction - | ts.RegularExpressionLiteral - | ts.NoSubstitutionTemplateLiteral - | ts.NumericLiteral - | ts.BigIntLiteral - | ts.TemplateHead - | ts.TemplateMiddle - | ts.TemplateTail - | ts.TemplateExpression - | ts.TemplateSpan - | ts.ParenthesizedExpression - | ts.ArrayLiteralExpression - | ts.SpreadElement - | ts.ObjectLiteralExpression - | ts.PropertyAccessExpression - | ts.ElementAccessExpression - | ts.CallExpression - | ts.ExpressionWithTypeArguments - | ts.NewExpression - | ts.TaggedTemplateExpression - | ts.AsExpression - | ts.TypeAssertion - | ts.NonNullExpression - | ts.MetaProperty - | ts.JsxElement - | ts.JsxOpeningElement - | ts.JsxSelfClosingElement - | ts.JsxFragment - | ts.JsxOpeningFragment - | ts.JsxClosingFragment - | ts.JsxAttribute - | ts.JsxSpreadAttribute - | ts.JsxClosingElement - | ts.JsxExpression - | ts.JsxText - | ts.NotEmittedStatement - | ts.CommaListExpression - | ts.EmptyStatement - | ts.DebuggerStatement - | ts.MissingDeclaration - | ts.Block - | ts.VariableStatement - | ts.ExpressionStatement - | ts.IfStatement - | ts.DoStatement - | ts.WhileStatement - | ts.ForStatement - | ts.ForInStatement - | ts.ForOfStatement - | ts.BreakStatement - | ts.ContinueStatement - | ts.ReturnStatement - | ts.WithStatement - | ts.SwitchStatement - | ts.CaseBlock - | ts.CaseClause - | ts.DefaultClause - | ts.LabeledStatement - | ts.ThrowStatement - | ts.TryStatement - | ts.CatchClause - // | ts.ClassLikeDeclarationBase -> ClassDeclaration | ClassExpression - | ts.ClassDeclaration - | ts.ClassExpression - | ts.InterfaceDeclaration - | ts.HeritageClause - | ts.TypeAliasDeclaration - | ts.EnumMember - | ts.EnumDeclaration - | ts.ModuleDeclaration - | ts.ModuleBlock - | ts.ImportEqualsDeclaration - | ts.ExternalModuleReference - | ts.ImportDeclaration - | ts.ImportClause - | ts.NamespaceImport - | ts.NamespaceExportDeclaration - | ts.ExportDeclaration - | ts.NamedImports - | ts.NamedExports - | ts.ImportSpecifier - | ts.ExportSpecifier - | ts.ExportAssignment - | ts.CommentRange - | ts.JSDocTypeExpression - | ts.JSDoc - | ts.JSDocUnknownTag - | ts.JSDocAugmentsTag - | ts.JSDocClassTag - | ts.JSDocEnumTag - | ts.JSDocThisTag - | ts.JSDocTemplateTag - | ts.JSDocReturnTag - | ts.JSDocTypeTag - | ts.JSDocTypedefTag - | ts.JSDocCallbackTag - | ts.JSDocSignature - | ts.JSDocPropertyTag - | ts.JSDocParameterTag - | ts.JSDocTypeLiteral - | ts.SourceFile - | ts.Bundle - | ts.InputFiles - | ts.UnparsedSource - | ts.JsonMinusNumericLiteral); diff --git a/src/tsconfig-parser.ts b/src/tsconfig-parser.ts index 9995327..2212c4d 100644 --- a/src/tsconfig-parser.ts +++ b/src/tsconfig-parser.ts @@ -213,7 +213,7 @@ export function createProgram(code: string, filePath: string, extra: Extra) { return undefined; } - const compilerHost = ts.createCompilerHost(commandLine.options); + const compilerHost = ts.createCompilerHost(commandLine.options, true); const oldReadFile = compilerHost.readFile; compilerHost.readFile = (fileName: string) => path.normalize(fileName) === path.normalize(filePath) diff --git a/tests/ast-alignment/fixtures-to-test.ts b/tests/ast-alignment/fixtures-to-test.ts index b0cd1f8..7e1c00a 100644 --- a/tests/ast-alignment/fixtures-to-test.ts +++ b/tests/ast-alignment/fixtures-to-test.ts @@ -6,11 +6,13 @@ import jsxKnownIssues from '../jsx-known-issues'; interface Fixture { filename: string; + jsx: boolean; ignoreSourceType: boolean; } interface FixturePatternConfig { pattern: string; + jsx: boolean; ignoreSourceType: boolean; } @@ -22,6 +24,69 @@ interface CreateFixturePatternConfig { const fixturesDirPath = path.join(__dirname, '../fixtures'); +class FixturesTester { + protected fixtures: FixturePatternConfig[] = []; + + constructor() {} + + public addFixturePatternConfig( + fixturesSubPath: string, + config: CreateFixturePatternConfig = {} + ) { + if (!fs.existsSync(path.join(fixturesDirPath, fixturesSubPath))) { + throw new Error( + `Registered path '${path.join( + __dirname, + fixturesSubPath + )}' was not found` + ); + } + + const ignore = config.ignore || []; + const fileType = config.fileType || 'js'; + const ignoreSourceType = config.ignoreSourceType || []; + const jsx = fileType === 'js' || fileType === 'jsx' || fileType === 'tsx'; + + /** + * The TypeScript compiler gives us the "externalModuleIndicator" to allow typescript-estree do dynamically detect the "sourceType". + * Babel has similar feature sourceType='unambiguous' but its not perfect, and in some specific cases we sill have to enforce it. + * Known issues: + * - https://github.com/babel/babel/issues/9213 + */ + if (ignoreSourceType.length) { + ignore.push(...ignoreSourceType); + for (const fixture of ignoreSourceType) { + this.fixtures.push({ + // It needs to be the full path from within fixtures/ for the pattern + pattern: `${fixturesSubPath}/${fixture}.src.${fileType}`, + ignoreSourceType: true, + jsx + }); + } + } + + this.fixtures.push({ + pattern: `${fixturesSubPath}/!(${ignore.join('|')}).src.${fileType}`, + ignoreSourceType: false, + jsx + }); + } + + public getFixtures(): Fixture[] { + return this.fixtures + .map(fixture => + glob + .sync(`${fixturesDirPath}/${fixture.pattern}`, {}) + .map(filename => ({ + filename, + ignoreSourceType: fixture.ignoreSourceType, + jsx: fixture.jsx + })) + ) + .reduce((acc, x) => acc.concat(x), []); + } +} + /** * JSX fixtures which have known issues for typescript-estree */ @@ -37,490 +102,404 @@ const jsxFilesWithKnownIssues = jsxKnownIssues.map(f => f.replace('jsx/', '')); jsxFilesWithKnownIssues.push('invalid-no-tag-name'); /** - * Globally track which fixtures need to be parsed with sourceType: "module" - * so that they can be added with the correct FixturePatternConfig + * An class with FixturePatternConfigs */ -let fixturesRequiringSourceTypeModule: FixturePatternConfig[] = []; +const tester = new FixturesTester(); + +tester.addFixturePatternConfig('javascript/basics'); + +tester.addFixturePatternConfig('comments', { + ignore: [ + /** + * Template strings seem to also be affected by the difference in opinion between different parsers in: + * https://github.com/babel/babel/issues/6681 + */ + 'no-comment-template', // Purely AST diffs + 'template-string-block' // Purely AST diffs + ] +}); -/** - * Utility to generate a FixturePatternConfig object containing the glob pattern for specific subsections of the fixtures/ directory, - * including the capability to ignore specific nested patterns. - * - * @param {string} fixturesSubPath the sub-path within the fixtures/ directory - * @param {CreateFixturePatternConfig?} config an optional configuration object with optional sub-paths to ignore and/or parse with sourceType: module - * @returns {FixturePatternConfig} an object containing the glob pattern and optional additional config - */ -function createFixturePatternConfigFor( - fixturesSubPath: string, - config?: CreateFixturePatternConfig -): FixturePatternConfig { - if (!fixturesSubPath) { - throw new Error( - 'fixtureSubPath was not provided for the current fixture pattern' - ); - } - if (!fs.existsSync(path.join(fixturesDirPath, fixturesSubPath))) { - throw new Error( - `Registered path '${path.join(__dirname, fixturesSubPath)}' was not found` - ); - } +tester.addFixturePatternConfig('javascript/templateStrings', { + ignore: ['**/*'] +}); - config = config || ({} as CreateFixturePatternConfig); - config.ignore = config.ignore || []; - config.fileType = config.fileType || 'js'; - config.ignoreSourceType = config.ignoreSourceType || []; - /** - * The TypeScript compiler gives us the "externalModuleIndicator" to allow typescript-estree do dynamically detect the "sourceType". - * Babel has similar feature sourceType='unambiguous' but its not perfect, and in some specific cases we sill have to enforce it. - * - * First merge the fixtures which need to be parsed with sourceType: "module" into the - * ignore list, and then add their full config into the global array. - */ - if (config.ignoreSourceType.length) { - config.ignore = ([] as string[]).concat( - config.ignore, - config.ignoreSourceType - ); - for (const fixture of config.ignoreSourceType) { - fixturesRequiringSourceTypeModule.push({ - // It needs to be the full path from within fixtures/ for the pattern - pattern: `${fixturesSubPath}/${fixture}.src.${config.fileType}`, - ignoreSourceType: true - }); - } - } - return { - pattern: `${fixturesSubPath}/!(${config.ignore.join('|')}).src.${ - config.fileType - }`, - ignoreSourceType: false - }; -} +tester.addFixturePatternConfig('javascript/arrayLiteral'); -/** - * An array of FixturePatternConfigs - */ -let fixturePatternConfigsToTest = [ - createFixturePatternConfigFor('javascript/basics'), - - createFixturePatternConfigFor('comments', { - ignore: [ - /** - * Template strings seem to also be affected by the difference in opinion between different parsers in: - * https://github.com/babel/babel/issues/6681 - */ - 'no-comment-template', // Purely AST diffs - 'template-string-block' // Purely AST diffs - ] - }), - - createFixturePatternConfigFor('javascript/templateStrings', { - ignore: ['**/*'] - }), - - createFixturePatternConfigFor('javascript/simple-literals'), - - createFixturePatternConfigFor('javascript/directives'), - - createFixturePatternConfigFor('javascript/experimentalObjectRestSpread', { - ignore: [ - /** - * Trailing comma is not permitted after a "RestElement" in Babel - */ - 'invalid-rest-trailing-comma' - ] - }), - - createFixturePatternConfigFor('javascript/arrowFunctions', { - ignore: [ - /** - * Expected babel parse errors - all of these files below produce parse errors in espree - * as well, but the TypeScript compiler is so forgiving during parsing that typescript-estree - * does not actually error on them and will produce an AST. - */ - 'error-dup-params', // babel parse errors - 'error-strict-dup-params', // babel parse errors - 'error-strict-octal', // babel parse errors - 'error-two-lines' // babel parse errors - ] - }), - - createFixturePatternConfigFor('javascript/bigIntLiterals'), - createFixturePatternConfigFor('javascript/binaryLiterals'), - createFixturePatternConfigFor('javascript/blockBindings'), - - createFixturePatternConfigFor('javascript/classes', { - ignore: [ - /** - * super() is being used outside of constructor. Other parsers (e.g. espree, acorn) do not error on this. - */ - 'class-one-method-super', // babel parse errors - /** - * Expected babel parse errors - all of these files below produce parse errors in espree - * as well, but the TypeScript compiler is so forgiving during parsing that typescript-estree - * does not actually error on them and will produce an AST. - */ - 'invalid-class-declaration', // babel parse errors - 'invalid-class-setter-declaration' // babel parse errors - ] - }), - - createFixturePatternConfigFor('javascript/defaultParams'), - - createFixturePatternConfigFor('javascript/destructuring', { - ignore: [ - /** - * Expected babel parse errors - all of these files below produce parse errors in espree - * as well, but the TypeScript compiler is so forgiving during parsing that typescript-estree - * does not actually error on them and will produce an AST. - */ - 'invalid-defaults-object-assign' // babel parse errors - ] - }), - - createFixturePatternConfigFor('javascript/destructuring-and-arrowFunctions'), - createFixturePatternConfigFor('javascript/destructuring-and-blockBindings'), - createFixturePatternConfigFor('javascript/destructuring-and-defaultParams'), - createFixturePatternConfigFor('javascript/destructuring-and-forOf'), - - createFixturePatternConfigFor('javascript/destructuring-and-spread', { - ignore: [ - /** - * Expected babel parse errors - all of these files below produce parse errors in espree - * as well, but the TypeScript compiler is so forgiving during parsing that typescript-estree - * does not actually error on them and will produce an AST. - */ - 'error-complex-destructured-spread-first', // babel parse errors - 'not-final-array' // babel parse errors - ] - }), - - createFixturePatternConfigFor('javascript/experimentalAsyncIteration'), - createFixturePatternConfigFor('javascript/experimentalDynamicImport'), - createFixturePatternConfigFor('javascript/exponentiationOperators'), - createFixturePatternConfigFor('javascript/experimentalOptionalCatchBinding'), - - createFixturePatternConfigFor('javascript/forOf', { - ignore: [ - /** - * TypeScript, espree and acorn parse this fine - esprima, flow and babel do not... - */ - 'for-of-with-function-initializer' // babel parse errors - ] - }), - - createFixturePatternConfigFor('javascript/generators'), - createFixturePatternConfigFor('javascript/globalReturn'), - - createFixturePatternConfigFor('javascript/modules', { - ignore: [ - /** - * Expected babel parse errors - all of these files below produce parse errors in espree - * as well, but the TypeScript compiler is so forgiving during parsing that typescript-estree - * does not actually error on them and will produce an AST. - */ - 'invalid-export-named-default', // babel parse errors - 'invalid-import-default-module-specifier', // babel parse errors - 'invalid-import-module-specifier' // babel parse errors - ], - ignoreSourceType: ['error-function', 'error-strict', 'error-delete'] - }), - - createFixturePatternConfigFor('javascript/newTarget', { - ignore: [ - /** - * Expected babel parse errors - all of these files below produce parse errors in espree - * as well, but the TypeScript compiler is so forgiving during parsing that typescript-estree - * does not actually error on them and will produce an AST. - */ - 'invalid-new-target', // babel parse errors - 'invalid-unknown-property' // babel parse errors - ] - }), - - createFixturePatternConfigFor('javascript/objectLiteral'), - createFixturePatternConfigFor('javascript/objectLiteralComputedProperties'), - - createFixturePatternConfigFor('javascript/objectLiteralDuplicateProperties', { - ignore: [ - /** - * Expected babel parse errors - all of these files below produce parse errors in espree - * as well, but the TypeScript compiler is so forgiving during parsing that typescript-estree - * does not actually error on them and will produce an AST. - */ - 'error-proto-property', // babel parse errors - 'error-proto-string-property' // babel parse errors - ] - }), - - createFixturePatternConfigFor('javascript/objectLiteralShorthandMethods'), - createFixturePatternConfigFor('javascript/objectLiteralShorthandProperties'), - createFixturePatternConfigFor('javascript/octalLiterals'), - createFixturePatternConfigFor('javascript/regex'), - createFixturePatternConfigFor('javascript/regexUFlag'), - createFixturePatternConfigFor('javascript/regexYFlag'), - - createFixturePatternConfigFor('javascript/restParams', { - ignore: [ - /** - * Expected babel parse errors - all of these files below produce parse errors in espree - * as well, but the TypeScript compiler is so forgiving during parsing that typescript-estree - * does not actually error on them and will produce an AST. - */ - 'error-no-default', // babel parse errors - 'error-not-last' // babel parse errors - ] - }), - - createFixturePatternConfigFor('javascript/spread'), - createFixturePatternConfigFor('javascript/unicodeCodePointEscapes'), - - /* ================================================== */ - - createFixturePatternConfigFor('jsx', { - ignore: jsxFilesWithKnownIssues - }), - createFixturePatternConfigFor('jsx-useJSXTextNode'), - - /* ================================================== */ - - /** - * TSX-SPECIFIC FILES - */ - - createFixturePatternConfigFor('tsx', { - fileType: 'tsx' - }), - - /* ================================================== */ - - /** - * TYPESCRIPT-SPECIFIC FILES - */ - - createFixturePatternConfigFor('typescript/babylon-convergence', { - fileType: 'ts' - }), - - createFixturePatternConfigFor('typescript/basics', { - fileType: 'ts', - ignore: [ - /** - * Other babel parse errors relating to invalid syntax. - */ - 'abstract-class-with-abstract-constructor', // babel parse errors - 'class-with-export-parameter-properties', // babel parse errors - 'class-with-implements-and-extends', // babel parse errors - 'class-with-optional-methods', // babel parse errors - 'class-with-static-parameter-properties', // babel parse errors - 'interface-with-all-property-types', // babel parse errors - 'interface-with-construct-signature-with-parameter-accessibility', // babel parse errors - /** - * typescript-estree erroring, but babel not. - */ - 'arrow-function-with-type-parameters', // typescript-estree parse errors - /** - * Babel: ClassDeclaration + abstract: true - * tsep: TSAbstractClassDeclaration - */ - 'abstract-class-with-abstract-properties', - /** - * Babel: ClassProperty + abstract: true - * tsep: TSAbstractClassProperty - */ - 'abstract-class-with-abstract-readonly-property', - /** - * Babel: TSExpressionWithTypeArguments - * tsep: ClassImplements - */ - 'class-with-implements-generic-multiple', - 'class-with-implements-generic', - 'class-with-implements', - 'class-with-extends-and-implements', - /** - * Other major AST differences (e.g. fundamentally different node types) - */ - 'class-with-mixin', - 'function-with-types-assignation', - 'interface-extends-multiple', - 'interface-extends', - 'interface-type-parameters', - 'interface-with-extends-type-parameters', - 'interface-with-generic', - 'interface-with-jsdoc', - 'interface-with-optional-properties', - 'interface-without-type-annotation', - 'typed-this', - 'export-type-function-declaration', - 'abstract-interface', - /** - * Babel bug for optional or abstract methods? - */ - 'abstract-class-with-abstract-method', // babel parse errors - 'abstract-class-with-optional-method', // babel parse errors - 'declare-class-with-optional-method', // babel parse errors - /** - * Awaiting feedback on Babel issue https://github.com/babel/babel/issues/6679 - */ - 'class-with-private-parameter-properties', - 'class-with-protected-parameter-properties', - 'class-with-public-parameter-properties', - 'class-with-readonly-parameter-properties', - /** - * Not yet supported in Babel https://github.com/babel/babel/issues/7749 - */ - 'import-type', - 'import-type-with-type-parameters-in-type-reference', - /** - * babel is not supporting it yet https://github.com/babel/babel/pull/9230 - * Babel: TSTypeReference -> Identifier - * tsep: TSBigIntKeyword - */ - 'typed-keyword-bigint', - /** - * Awaiting feedback on Babel issue https://github.com/babel/babel/issues/9228 - * Babel: BooleanLiteral - * tsep: Literal - */ - 'typed-keyword-true', - /** - * Not yet supported in Babel https://github.com/babel/babel/issues/9228 - * Babel: BooleanLiteral - * tsep: Literal - */ - 'typed-keyword-false', - /** - * Not yet supported in Babel https://github.com/babel/babel/issues/9228 - * Directive field is not added to module and namespace - */ - 'directive-in-module', - /** - * Not yet supported in Babel https://github.com/babel/babel/issues/9228 - * Directive field is not added to module and namespace - */ - 'directive-in-namespace' - ], - ignoreSourceType: [ - // https://github.com/babel/babel/issues/9213 - 'export-assignment' - ] - }), - - createFixturePatternConfigFor('typescript/decorators/accessor-decorators', { - fileType: 'ts' - }), - createFixturePatternConfigFor('typescript/decorators/class-decorators', { - fileType: 'ts' - }), - createFixturePatternConfigFor('typescript/decorators/method-decorators', { - fileType: 'ts' - }), - createFixturePatternConfigFor('typescript/decorators/parameter-decorators', { - fileType: 'ts' - }), - createFixturePatternConfigFor('typescript/decorators/property-decorators', { - fileType: 'ts' - }), - - createFixturePatternConfigFor('typescript/expressions', { - fileType: 'ts', - ignore: [ - /** - * there is difference in range between babel and tsep - */ - 'tagged-template-expression-type-arguments' - ] - }), - - createFixturePatternConfigFor('typescript/errorRecovery', { - fileType: 'ts', - ignore: [ - /** - * AST difference - */ - 'interface-empty-extends', - /** - * TypeScript-specific tests taken from "errorRecovery". Babel is not being as forgiving as the TypeScript compiler here. - */ - 'class-empty-extends-implements', // babel parse errors - 'class-empty-extends', // babel parse errors - 'decorator-on-enum-declaration', // babel parse errors - 'decorator-on-interface-declaration', // babel parse errors - 'interface-property-modifiers', // babel parse errors - 'enum-with-keywords', // babel parse errors - 'solo-const' // babel parse errors - ] - }), - - createFixturePatternConfigFor('typescript/types', { - fileType: 'ts' - }), - - createFixturePatternConfigFor('typescript/declare', { - fileType: 'ts', - ignore: [ - /** - * AST difference - * tsep: heritage = [] - * babel: heritage = undefined - */ - 'interface', - /** - * AST difference - * tsep: TSAbstractClassDeclaration - * babel: ClassDeclaration[abstract=true] - */ - 'abstract-class' - ] - }), - - createFixturePatternConfigFor('typescript/namespaces-and-modules', { - fileType: 'ts', - ignore: [ - /** - * Minor AST difference - */ - 'nested-internal-module', - /** - * Babel: TSDeclareFunction - * tsep: TSNamespaceFunctionDeclaration - */ - 'declare-namespace-with-exported-function' - ], - ignoreSourceType: [ - 'module-with-default-exports', - 'ambient-module-declaration-with-import' - ] - }) -]; +tester.addFixturePatternConfig('javascript/simple-literals'); + +tester.addFixturePatternConfig('javascript/directives'); + +tester.addFixturePatternConfig('javascript/experimentalObjectRestSpread', { + ignore: [ + /** + * Trailing comma is not permitted after a "RestElement" in Babel + */ + 'invalid-rest-trailing-comma' + ] +}); + +tester.addFixturePatternConfig('javascript/arrowFunctions', { + ignore: [ + /** + * Expected babel parse errors - all of these files below produce parse errors in espree + * as well, but the TypeScript compiler is so forgiving during parsing that typescript-estree + * does not actually error on them and will produce an AST. + */ + 'error-dup-params', // babel parse errors + 'error-strict-dup-params', // babel parse errors + 'error-strict-octal', // babel parse errors + 'error-two-lines' // babel parse errors + ] +}); + +tester.addFixturePatternConfig('javascript/bigIntLiterals'); +tester.addFixturePatternConfig('javascript/binaryLiterals'); +tester.addFixturePatternConfig('javascript/blockBindings'); + +tester.addFixturePatternConfig('javascript/classes', { + ignore: [ + /** + * super() is being used outside of constructor. Other parsers (e.g. espree, acorn) do not error on this. + */ + 'class-one-method-super', // babel parse errors + /** + * Expected babel parse errors - all of these files below produce parse errors in espree + * as well, but the TypeScript compiler is so forgiving during parsing that typescript-estree + * does not actually error on them and will produce an AST. + */ + 'invalid-class-declaration', // babel parse errors + 'invalid-class-setter-declaration' // babel parse errors + ] +}); + +tester.addFixturePatternConfig('javascript/defaultParams'); + +tester.addFixturePatternConfig('javascript/destructuring', { + ignore: [ + /** + * Expected babel parse errors - all of these files below produce parse errors in espree + * as well, but the TypeScript compiler is so forgiving during parsing that typescript-estree + * does not actually error on them and will produce an AST. + */ + 'invalid-defaults-object-assign' // babel parse errors + ] +}); + +tester.addFixturePatternConfig('javascript/destructuring-and-arrowFunctions'); +tester.addFixturePatternConfig('javascript/destructuring-and-blockBindings'); +tester.addFixturePatternConfig('javascript/destructuring-and-defaultParams'); +tester.addFixturePatternConfig('javascript/destructuring-and-forOf'); + +tester.addFixturePatternConfig('javascript/destructuring-and-spread', { + ignore: [ + /** + * Expected babel parse errors - all of these files below produce parse errors in espree + * as well, but the TypeScript compiler is so forgiving during parsing that typescript-estree + * does not actually error on them and will produce an AST. + */ + 'error-complex-destructured-spread-first', // babel parse errors + 'not-final-array' // babel parse errors + ] +}); + +tester.addFixturePatternConfig('javascript/experimentalAsyncIteration'); +tester.addFixturePatternConfig('javascript/experimentalDynamicImport'); +tester.addFixturePatternConfig('javascript/exponentiationOperators'); +tester.addFixturePatternConfig('javascript/experimentalOptionalCatchBinding'); + +tester.addFixturePatternConfig('javascript/forOf', { + ignore: [ + /** + * TypeScript, espree and acorn parse this fine - esprima, flow and babel do not... + */ + 'for-of-with-function-initializer' // babel parse errors + ] +}); + +tester.addFixturePatternConfig('javascript/generators'); +tester.addFixturePatternConfig('javascript/globalReturn'); + +tester.addFixturePatternConfig('javascript/modules', { + ignore: [ + /** + * Expected babel parse errors - all of these files below produce parse errors in espree + * as well, but the TypeScript compiler is so forgiving during parsing that typescript-estree + * does not actually error on them and will produce an AST. + */ + 'invalid-export-named-default', // babel parse errors + 'invalid-import-default-module-specifier', // babel parse errors + 'invalid-import-module-specifier' // babel parse errors + ], + ignoreSourceType: ['error-function', 'error-strict', 'error-delete'] +}); + +tester.addFixturePatternConfig('javascript/newTarget', { + ignore: [ + /** + * Expected babel parse errors - all of these files below produce parse errors in espree + * as well, but the TypeScript compiler is so forgiving during parsing that typescript-estree + * does not actually error on them and will produce an AST. + */ + 'invalid-new-target', // babel parse errors + 'invalid-unknown-property' // babel parse errors + ] +}); + +tester.addFixturePatternConfig('javascript/objectLiteral'); +tester.addFixturePatternConfig('javascript/objectLiteralComputedProperties'); + +tester.addFixturePatternConfig('javascript/objectLiteralDuplicateProperties', { + ignore: [ + /** + * Expected babel parse errors - all of these files below produce parse errors in espree + * as well, but the TypeScript compiler is so forgiving during parsing that typescript-estree + * does not actually error on them and will produce an AST. + */ + 'error-proto-property', // babel parse errors + 'error-proto-string-property' // babel parse errors + ] +}); + +tester.addFixturePatternConfig('javascript/objectLiteralShorthandMethods'); +tester.addFixturePatternConfig('javascript/objectLiteralShorthandProperties'); +tester.addFixturePatternConfig('javascript/octalLiterals'); +tester.addFixturePatternConfig('javascript/regex'); +tester.addFixturePatternConfig('javascript/regexUFlag'); +tester.addFixturePatternConfig('javascript/regexYFlag'); + +tester.addFixturePatternConfig('javascript/restParams', { + ignore: [ + /** + * Expected babel parse errors - all of these files below produce parse errors in espree + * as well, but the TypeScript compiler is so forgiving during parsing that typescript-estree + * does not actually error on them and will produce an AST. + */ + 'error-no-default', // babel parse errors + 'error-not-last' // babel parse errors + ] +}); + +tester.addFixturePatternConfig('javascript/spread'); +tester.addFixturePatternConfig('javascript/unicodeCodePointEscapes'); + +/* ================================================== */ + +tester.addFixturePatternConfig('jsx', { + ignore: jsxFilesWithKnownIssues +}); +tester.addFixturePatternConfig('jsx-useJSXTextNode'); + +/* ================================================== */ /** - * Add in all the fixtures which need to be parsed with sourceType: "module" + * TSX-SPECIFIC FILES */ -fixturePatternConfigsToTest = ([] as FixturePatternConfig[]).concat( - fixturePatternConfigsToTest, - fixturesRequiringSourceTypeModule -); -const fixturesToTest: Fixture[] = []; +tester.addFixturePatternConfig('tsx', { + fileType: 'tsx' +}); + +/* ================================================== */ /** - * Resolve the glob patterns into actual Fixture files that we can run assertions for... + * TYPESCRIPT-SPECIFIC FILES */ -fixturePatternConfigsToTest.forEach(fixturePatternConfig => { - /** - * Find the fixture files which match the given pattern - */ - const matchingFixtures = glob.sync( - `${fixturesDirPath}/${fixturePatternConfig.pattern}`, - {} - ); - matchingFixtures.forEach(filename => { - fixturesToTest.push({ - filename, - ignoreSourceType: fixturePatternConfig.ignoreSourceType - }); - }); + +tester.addFixturePatternConfig('typescript/babylon-convergence', { + fileType: 'ts' +}); + +tester.addFixturePatternConfig('typescript/basics', { + fileType: 'ts', + ignore: [ + /** + * Other babel parse errors relating to invalid syntax. + */ + 'abstract-class-with-abstract-constructor', // babel parse errors + 'class-with-export-parameter-properties', // babel parse errors + 'class-with-implements-and-extends', // babel parse errors + 'class-with-optional-methods', // babel parse errors + 'class-with-static-parameter-properties', // babel parse errors + 'interface-with-all-property-types', // babel parse errors + 'interface-with-construct-signature-with-parameter-accessibility', // babel parse errors + /** + * typescript-estree erroring, but babel not. + */ + 'arrow-function-with-type-parameters', // typescript-estree parse errors + /** + * Babel: ClassDeclaration + abstract: true + * tsep: TSAbstractClassDeclaration + */ + 'abstract-class-with-abstract-properties', + /** + * Babel: ClassProperty + abstract: true + * tsep: TSAbstractClassProperty + */ + 'abstract-class-with-abstract-readonly-property', + /** + * Babel: TSExpressionWithTypeArguments + * tsep: ClassImplements + */ + 'class-with-implements-generic-multiple', + 'class-with-implements-generic', + 'class-with-implements', + 'class-with-extends-and-implements', + /** + * Other major AST differences (e.g. fundamentally different node types) + */ + 'class-with-mixin', + 'function-with-types-assignation', + 'interface-extends-multiple', + 'interface-extends', + 'interface-type-parameters', + 'interface-with-extends-type-parameters', + 'interface-with-generic', + 'interface-with-jsdoc', + 'interface-with-optional-properties', + 'interface-without-type-annotation', + 'typed-this', + 'export-type-function-declaration', + 'abstract-interface', + /** + * Babel bug for optional or abstract methods? + */ + 'abstract-class-with-abstract-method', // babel parse errors + 'abstract-class-with-optional-method', // babel parse errors + 'declare-class-with-optional-method', // babel parse errors + /** + * Awaiting feedback on Babel issue https://github.com/babel/babel/issues/6679 + */ + 'class-with-private-parameter-properties', + 'class-with-protected-parameter-properties', + 'class-with-public-parameter-properties', + 'class-with-readonly-parameter-properties', + /** + * Not yet supported in Babel https://github.com/babel/babel/issues/7749 + */ + 'import-type', + 'import-type-with-type-parameters-in-type-reference', + /** + * babel is not supporting it yet https://github.com/babel/babel/pull/9230 + * Babel: TSTypeReference -> Identifier + * tsep: TSBigIntKeyword + */ + 'typed-keyword-bigint', + /** + * Awaiting feedback on Babel issue https://github.com/babel/babel/issues/9228 + * Babel: BooleanLiteral + * tsep: Literal + */ + 'typed-keyword-true', + /** + * Not yet supported in Babel https://github.com/babel/babel/issues/9228 + * Babel: BooleanLiteral + * tsep: Literal + */ + 'typed-keyword-false', + /** + * Not yet supported in Babel https://github.com/babel/babel/issues/9228 + * Directive field is not added to module and namespace + */ + 'directive-in-module', + /** + * Not yet supported in Babel https://github.com/babel/babel/issues/9228 + * Directive field is not added to module and namespace + */ + 'directive-in-namespace' + ], + ignoreSourceType: [ + // https://github.com/babel/babel/issues/9213 + 'export-assignment' + ] }); +tester.addFixturePatternConfig('typescript/decorators/accessor-decorators', { + fileType: 'ts' +}); +tester.addFixturePatternConfig('typescript/decorators/class-decorators', { + fileType: 'ts' +}); +tester.addFixturePatternConfig('typescript/decorators/method-decorators', { + fileType: 'ts' +}); +tester.addFixturePatternConfig('typescript/decorators/parameter-decorators', { + fileType: 'ts' +}); +tester.addFixturePatternConfig('typescript/decorators/property-decorators', { + fileType: 'ts' +}); + +tester.addFixturePatternConfig('typescript/expressions', { + fileType: 'ts', + ignore: [ + /** + * there is difference in range between babel and tsep + */ + 'tagged-template-expression-type-arguments' + ] +}); + +tester.addFixturePatternConfig('typescript/errorRecovery', { + fileType: 'ts', + ignore: [ + /** + * AST difference + */ + 'interface-empty-extends', + /** + * TypeScript-specific tests taken from "errorRecovery". Babel is not being as forgiving as the TypeScript compiler here. + */ + 'class-empty-extends-implements', // babel parse errors + 'class-empty-extends', // babel parse errors + 'decorator-on-enum-declaration', // babel parse errors + 'decorator-on-interface-declaration', // babel parse errors + 'interface-property-modifiers', // babel parse errors + 'enum-with-keywords', // babel parse errors + 'solo-const' // babel parse errors + ] +}); + +tester.addFixturePatternConfig('typescript/types', { + fileType: 'ts' +}); + +tester.addFixturePatternConfig('typescript/declare', { + fileType: 'ts', + ignore: [ + /** + * AST difference + * tsep: heritage = [] + * babel: heritage = undefined + */ + 'interface', + /** + * AST difference + * tsep: TSAbstractClassDeclaration + * babel: ClassDeclaration[abstract=true] + */ + 'abstract-class' + ] +}); + +tester.addFixturePatternConfig('typescript/namespaces-and-modules', { + fileType: 'ts', + ignore: [ + /** + * Minor AST difference + */ + 'nested-internal-module', + /** + * Babel: TSDeclareFunction + * tsep: TSNamespaceFunctionDeclaration + */ + 'declare-namespace-with-exported-function' + ], + ignoreSourceType: [ + 'module-with-default-exports', + 'ambient-module-declaration-with-import' + ] +}); + +const fixturesToTest = tester.getFixtures(); + export { fixturesToTest }; diff --git a/tests/ast-alignment/parse.ts b/tests/ast-alignment/parse.ts index c5df123..c37f5dc 100644 --- a/tests/ast-alignment/parse.ts +++ b/tests/ast-alignment/parse.ts @@ -1,8 +1,7 @@ import codeFrame from 'babel-code-frame'; import * as parser from '../../src/parser'; import * as parseUtils from './utils'; -import { ParserOptions as BabelParserOptions } from '@babel/parser'; -import { ParserOptions } from '../../src/temp-types-based-on-js-source'; +import { ParserPlugin } from '@babel/parser'; function createError(message: string, line: number, column: number) { // Construct an error similar to the ones thrown by Babylon. @@ -14,55 +13,42 @@ function createError(message: string, line: number, column: number) { return error; } -function parseWithBabelParser( - text: string, - parserOptions?: BabelParserOptions -) { - parserOptions = parserOptions || {}; +function parseWithBabelParser(text: string, jsx: boolean = true) { const babel = require('@babel/parser'); - return babel.parse( - text, - Object.assign( - { - sourceType: 'unambiguous', - allowImportExportEverywhere: true, - allowReturnOutsideFunction: true, - ranges: true, - plugins: [ - 'jsx', - 'typescript', - 'objectRestSpread', - 'decorators-legacy', - 'classProperties', - 'asyncGenerators', - 'dynamicImport', - 'estree', - 'bigInt' - ] - }, - parserOptions - ) - ); + const plugins: ParserPlugin[] = [ + 'typescript', + 'objectRestSpread', + 'decorators-legacy', + 'classProperties', + 'asyncGenerators', + 'dynamicImport', + 'estree', + 'bigInt' + ]; + if (jsx) { + plugins.push('jsx'); + } + + return babel.parse(text, { + sourceType: 'unambiguous', + allowImportExportEverywhere: true, + allowReturnOutsideFunction: true, + ranges: true, + plugins + }); } -function parseWithTypeScriptESTree( - text: string, - parserOptions?: ParserOptions -) { - parserOptions = parserOptions || ({} as ParserOptions); +function parseWithTypeScriptESTree(text: string, jsx: boolean = true) { try { - return parser.parse(text, Object.assign( - { - loc: true, - range: true, - tokens: false, - comment: false, - useJSXTextNode: true, - errorOnUnknownASTType: true, - jsx: true - }, - parserOptions - ) as any); + return parser.parse(text, { + loc: true, + range: true, + tokens: false, + comment: false, + useJSXTextNode: true, + errorOnUnknownASTType: true, + jsx + }); } catch (e) { throw createError(e.message, e.lineNumber, e.column); } @@ -70,8 +56,7 @@ function parseWithTypeScriptESTree( interface ASTComparisonParseOptions { parser: string; - typeScriptESTreeOptions?: ParserOptions; - babelParserOptions?: BabelParserOptions; + jsx?: boolean; } export function parse(text: string, opts: ASTComparisonParseOptions) { @@ -88,12 +73,12 @@ export function parse(text: string, opts: ASTComparisonParseOptions) { switch (opts.parser) { case 'typescript-estree': result.ast = parseUtils.normalizeNodeTypes( - parseWithTypeScriptESTree(text, opts.typeScriptESTreeOptions) + parseWithTypeScriptESTree(text, opts.jsx) ); break; case '@babel/parser': result.ast = parseUtils.normalizeNodeTypes( - parseWithBabelParser(text, opts.babelParserOptions) + parseWithBabelParser(text, opts.jsx) ); break; default: diff --git a/tests/ast-alignment/spec.ts b/tests/ast-alignment/spec.ts index 31814b9..2f27fb2 100644 --- a/tests/ast-alignment/spec.ts +++ b/tests/ast-alignment/spec.ts @@ -11,14 +11,16 @@ fixturesToTest.forEach(fixture => { * Parse with typescript-estree */ const typeScriptESTreeResult = parse(source, { - parser: 'typescript-estree' + parser: 'typescript-estree', + jsx: fixture.jsx }); /** * Parse the source with @babel/parser typescript-plugin */ const babelParserResult = parse(source, { - parser: '@babel/parser' + parser: '@babel/parser', + jsx: fixture.jsx }); /** diff --git a/tests/lib/javascript.ts b/tests/lib/javascript.ts index 6635ae7..a8a1550 100644 --- a/tests/lib/javascript.ts +++ b/tests/lib/javascript.ts @@ -31,16 +31,13 @@ const testFiles = shelljs describe('javascript', () => { testFiles.forEach(filename => { const code = shelljs.cat(`${path.resolve(FIXTURES_DIR, filename)}.src.js`), - config = { + config: ParserOptions = { loc: true, range: true, tokens: true, errorOnUnknownASTType: true }; - it( - `fixtures/${filename}.src`, - createSnapshotTestBlock(code, config as ParserOptions) - ); + it(`fixtures/${filename}.src`, createSnapshotTestBlock(code, config)); }); }); diff --git a/tests/lib/semanticInfo.ts b/tests/lib/semanticInfo.ts index 96ae842..db1671c 100644 --- a/tests/lib/semanticInfo.ts +++ b/tests/lib/semanticInfo.ts @@ -143,6 +143,23 @@ describe('semanticInfo', () => { ); }); + test('non-existent file should provide parents nodes', () => { + const parseResult = parseCodeAndGenerateServices( + `function M() { return Base }`, + createOptions('') + ); + + // https://github.com/JamesHenry/typescript-estree/issues/77 + expect(parseResult.services.program).toBeDefined(); + expect( + parseResult.services.program!.getSourceFile('') + ).toBeDefined(); + expect( + parseResult.services.program!.getSourceFile('')!.statements[0] + .parent + ).toBeDefined(); + }); + test('non-existent project file', () => { const fileName = path.resolve(FIXTURES_DIR, 'isolated-file.src.ts'); const badConfig = createOptions(fileName);