From 80569c794ca1b2ebddad12d1696d904d00c78deb Mon Sep 17 00:00:00 2001 From: James Henry Date: Sun, 2 Sep 2018 15:10:06 -0400 Subject: [PATCH 1/2] Breaking: Use typescript-estree for parsing --- Makefile.js | 2 +- README.md | 36 +- lib/ast-converter.js | 76 - lib/ast-node-types.js | 161 -- lib/convert-comments.js | 147 -- lib/convert.js | 2217 ----------------------- lib/node-utils.js | 748 -------- package.json | 10 +- parser.js | 190 +- tests/ast-alignment/.eslintrc.yml | 2 - tests/ast-alignment/fixtures-to-test.js | 523 ------ tests/ast-alignment/jest.config.js | 6 - tests/ast-alignment/parse.js | 96 - tests/ast-alignment/spec.js | 80 - tests/ast-alignment/utils.js | 154 -- tests/lib/__snapshots__/parse.js.snap | 190 -- tests/lib/parse.js | 64 - 17 files changed, 30 insertions(+), 4672 deletions(-) delete mode 100644 lib/ast-converter.js delete mode 100644 lib/ast-node-types.js delete mode 100644 lib/convert-comments.js delete mode 100644 lib/convert.js delete mode 100644 lib/node-utils.js delete mode 100644 tests/ast-alignment/.eslintrc.yml delete mode 100644 tests/ast-alignment/fixtures-to-test.js delete mode 100644 tests/ast-alignment/jest.config.js delete mode 100644 tests/ast-alignment/parse.js delete mode 100644 tests/ast-alignment/spec.js delete mode 100644 tests/ast-alignment/utils.js delete mode 100644 tests/lib/__snapshots__/parse.js.snap delete mode 100644 tests/lib/parse.js diff --git a/Makefile.js b/Makefile.js index a1cf6cd..2cd8c66 100644 --- a/Makefile.js +++ b/Makefile.js @@ -38,7 +38,7 @@ const NODE_MODULES = "./node_modules/", // Files MAKEFILE = "./Makefile.js", /* eslint-disable no-use-before-define */ - JS_FILES = `${find("lib/").filter(fileType("js")).join(" ")} parser.js`, + JS_FILES = "parser.js", TEST_FILES = find("tests/lib/").filter(fileType("js")).join(" "), TOOLS_FILES = find("tools/").filter(fileType("js")).join(" "); /* eslint-enable no-use-before-define */ diff --git a/README.md b/README.md index 2f76d80..061a53e 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,21 @@ # TypeScript ESLint Parser -A parser that converts TypeScript source code into an [ESTree](https://github.com/estree/estree)-compatible form. +An ESLint custom parser which leverages [TypeScript ESTree](https://github.com/JamesHenry/typescript-estree) to allow for ESLint to lint TypeScript source code. -## Usage -This parser is actually somewhat generic and robust - it could be used to power any use-case which requires taking TypeScript source code and producing an ESTree-compatiable AST. +## Installation: -In fact, that is exactly what it is used for in the popular open-source code formatter, [Prettier](https://prettier.io), to power its TypeScript support. +```sh +npm install --save-dev typescript-eslint-parser +``` -Nevertheless, the parser does have a special appreciation for ESLint-specific use-cases built in, and can even produce a slightly different AST for ESLint if needed (using the special `parseForESLint()` method). +## Usage -The majority of users of this parser use it to enable them to use ESLint on their TypeScript source files, so they will not actually be interacting with the parser directly. Instead they will configure ESLint to use it instead of its default parser, [espree](https://github.com/eslint/espree), which does not understand TypeScript. +In your ESLint configuration file, set the `parser` property: -## Usage with ESLint +```json +"parser": "typescript-eslint-parser" +``` There is sometimes an incorrect assumption that the parser itself is what does everything necessary to facilitate the use of ESLint with TypeScript. In actuality, it is the combination of the parser _and_ one or more plugins which allow you to maximize your usage of ESLint with TypeScript. @@ -24,18 +27,6 @@ Instead, you also need to make use of one more plugins which will add or extend By far the most common case will be installing the [eslint-plugin-typescript](https://github.com/nzakas/eslint-plugin-typescript) plugin, but there are also other relevant options available such a [eslint-plugin-tslint](https://github.com/JamesHenry/eslint-plugin-tslint). -Install: - -```sh -npm install --save-dev typescript-eslint-parser -``` - -And in your ESLint configuration file: - -```json -"parser": "typescript-eslint-parser" -``` - ## Supported TypeScript Version We will always endeavor to support the latest stable version of TypeScript. @@ -52,9 +43,7 @@ If you're familiar with TypeScript and ESLint, and you'd like to see this projec ## Reporting Issues -The vast majority of issues which are submitted here are not actually parsing bugs at all. They are integration issues with the ESLint ecosystem. - -This is not ideal, but users need a place to be able to report those things, so it has become accepted that that will also be done in this repo. +Please **do not report parsing/AST issues in this repo**, report them directly to [TypeScript ESTree](https://github.com/JamesHenry/typescript-estree). Please check the current list of open and known issues and ensure the issue has not been reported before. When creating a new issue provide as much information about your environment as possible. This includes: @@ -69,7 +58,7 @@ We have a very flexible way of running integration tests which connects all of t We run each test within its own docker container, and so each one has complete autonomy over what dependencies/plugins are installed and what versions are used. This also has the benefit of not bloating the `package.json` and `node_modules` of the parser project itself. -> If you are going to submit an issue related to the usage of this parser with ESLint, please consider creating a failing integration which clearly demonstrates the behavior. It's honestly super quick! +> If you are going to submit an issue related to the usage of this parser with ESLint, please consider creating a failing integration test which clearly demonstrates the behavior. It's honestly super quick! You just need to duplicate on of the existing test sub-directories found in `tests/integration/`, tweak the dependencies and ESLint config to match what you need, and add a new entry to the docker-compose.yml file which matches the format of the existing ones. @@ -93,7 +82,6 @@ Issues and pull requests will be triaged and responded to as quickly as possible - `npm test` - run all linting and tests - `npm run lint` - run all linting -- `npm run ast-alignment-tests` - run only Babylon AST alignment tests - `npm run integration-tests` - run only integration tests ## License diff --git a/lib/ast-converter.js b/lib/ast-converter.js deleted file mode 100644 index 779c154..0000000 --- a/lib/ast-converter.js +++ /dev/null @@ -1,76 +0,0 @@ -/** - * @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 - */ - -"use strict"; - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -const convert = require("./convert"), - convertComments = require("./convert-comments").convertComments, - nodeUtils = require("./node-utils"); - -//------------------------------------------------------------------------------ -// Private -//------------------------------------------------------------------------------ - -/** - * Extends and formats a given error object - * @param {Object} error the error object - * @returns {Object} converted error object - */ -function convertError(error) { - return nodeUtils.createError(error.file, error.start, error.message || error.messageText); -} - -//------------------------------------------------------------------------------ -// Public -//------------------------------------------------------------------------------ - -module.exports = (ast, extra) => { - - /** - * The TypeScript compiler produced fundamental parse errors when parsing the - * source. - */ - if (ast.parseDiagnostics.length) { - throw convertError(ast.parseDiagnostics[0]); - } - - /** - * Recursively convert the TypeScript AST into an ESTree-compatible AST - */ - const estree = convert({ - node: ast, - parent: null, - ast, - additionalOptions: { - errorOnUnknownASTType: extra.errorOnUnknownASTType || false, - useJSXTextNode: extra.useJSXTextNode || false, - parseForESLint: extra.parseForESLint - } - }); - - /** - * Optionally convert and include all tokens in the AST - */ - if (extra.tokens) { - estree.tokens = nodeUtils.convertTokens(ast); - } - - /** - * Optionally convert and include all comments in the AST - */ - if (extra.comment) { - estree.comments = convertComments(ast, extra.code); - } - - return estree; - -}; diff --git a/lib/ast-node-types.js b/lib/ast-node-types.js deleted file mode 100644 index 155abac..0000000 --- a/lib/ast-node-types.js +++ /dev/null @@ -1,161 +0,0 @@ -/** - * @fileoverview The AST node types produced by the parser. - * @author Nicholas C. Zakas - * @author James Henry - * @copyright jQuery Foundation and other contributors, https://jquery.org/ - * MIT License - */ - -"use strict"; - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -// None! - -//------------------------------------------------------------------------------ -// Public -//------------------------------------------------------------------------------ - -module.exports = { - ArrayExpression: "ArrayExpression", - ArrayPattern: "ArrayPattern", - ArrowFunctionExpression: "ArrowFunctionExpression", - AssignmentExpression: "AssignmentExpression", - AssignmentPattern: "AssignmentPattern", - AwaitExpression: "AwaitExpression", - BinaryExpression: "BinaryExpression", - BlockStatement: "BlockStatement", - BreakStatement: "BreakStatement", - CallExpression: "CallExpression", - CatchClause: "CatchClause", - ClassBody: "ClassBody", - ClassDeclaration: "ClassDeclaration", - ClassExpression: "ClassExpression", - ClassImplements: "ClassImplements", - ClassProperty: "ClassProperty", - ConditionalExpression: "ConditionalExpression", - ContinueStatement: "ContinueStatement", - DebuggerStatement: "DebuggerStatement", - DeclareFunction: "DeclareFunction", - Decorator: "Decorator", - DoWhileStatement: "DoWhileStatement", - EmptyStatement: "EmptyStatement", - ExportAllDeclaration: "ExportAllDeclaration", - ExportDefaultDeclaration: "ExportDefaultDeclaration", - ExportNamedDeclaration: "ExportNamedDeclaration", - ExportSpecifier: "ExportSpecifier", - ExpressionStatement: "ExpressionStatement", - ForInStatement: "ForInStatement", - ForOfStatement: "ForOfStatement", - ForStatement: "ForStatement", - FunctionDeclaration: "FunctionDeclaration", - FunctionExpression: "FunctionExpression", - GenericTypeAnnotation: "GenericTypeAnnotation", - Identifier: "Identifier", - IfStatement: "IfStatement", - Import: "Import", - ImportDeclaration: "ImportDeclaration", - ImportDefaultSpecifier: "ImportDefaultSpecifier", - ImportNamespaceSpecifier: "ImportNamespaceSpecifier", - ImportSpecifier: "ImportSpecifier", - JSXAttribute: "JSXAttribute", - JSXClosingElement: "JSXClosingElement", - JSXElement: "JSXElement", - JSXEmptyExpression: "JSXEmptyExpression", - JSXExpressionContainer: "JSXExpressionContainer", - JSXIdentifier: "JSXIdentifier", - JSXMemberExpression: "JSXMemberExpression", - JSXNamespacedName: "JSXNamespacedName", - JSXOpeningElement: "JSXOpeningElement", - JSXSpreadAttribute: "JSXSpreadAttribute", - JSXSpreadChild: "JSXSpreadChild", - JSXText: "JSXText", - LabeledStatement: "LabeledStatement", - Literal: "Literal", - LogicalExpression: "LogicalExpression", - MemberExpression: "MemberExpression", - MetaProperty: "MetaProperty", - MethodDefinition: "MethodDefinition", - NewExpression: "NewExpression", - ObjectExpression: "ObjectExpression", - ObjectPattern: "ObjectPattern", - Program: "Program", - Property: "Property", - RestElement: "RestElement", - ReturnStatement: "ReturnStatement", - SequenceExpression: "SequenceExpression", - SpreadElement: "SpreadElement", - Super: "Super", - SwitchCase: "SwitchCase", - SwitchStatement: "SwitchStatement", - TaggedTemplateExpression: "TaggedTemplateExpression", - TemplateElement: "TemplateElement", - TemplateLiteral: "TemplateLiteral", - ThisExpression: "ThisExpression", - ThrowStatement: "ThrowStatement", - TryStatement: "TryStatement", - /** - * TS-prefixed nodes - */ - TSAbstractClassProperty: "TSAbstractClassProperty", - TSAbstractKeyword: "TSAbstractKeyword", - TSAbstractMethodDefinition: "TSAbstractMethodDefinition", - TSAnyKeyword: "TSAnyKeyword", - TSArrayType: "TSArrayType", - TSAsyncKeyword: "TSAsyncKeyword", - TSBooleanKeyword: "TSBooleanKeyword", - TSConstructorType: "TSConstructorType", - TSConstructSignature: "TSConstructSignature", - TSDeclareKeyword: "TSDeclareKeyword", - TSEnumDeclaration: "TSEnumDeclaration", - TSEnumMember: "TSEnumMember", - TSExportAssignment: "TSExportAssignment", - TSExportKeyword: "TSExportKeyword", - TSImportType: "TSImportType", - TSLiteralType: "TSLiteralType", - TSIndexSignature: "TSIndexSignature", - TSInterfaceBody: "TSInterfaceBody", - TSInterfaceDeclaration: "TSInterfaceDeclaration", - TSInterfaceHeritage: "TSInterfaceHeritage", - TSFunctionType: "TSFunctionType", - TSMethodSignature: "TSMethodSignature", - TSModuleBlock: "TSModuleBlock", - TSModuleDeclaration: "TSModuleDeclaration", - TSNamespaceFunctionDeclaration: "TSNamespaceFunctionDeclaration", - TSNonNullExpression: "TSNonNullExpression", - TSNeverKeyword: "TSNeverKeyword", - TSNullKeyword: "TSNullKeyword", - TSNumberKeyword: "TSNumberKeyword", - TSObjectKeyword: "TSObjectKeyword", - TSParameterProperty: "TSParameterProperty", - TSPrivateKeyword: "TSPrivateKeyword", - TSPropertySignature: "TSPropertySignature", - TSProtectedKeyword: "TSProtectedKeyword", - TSPublicKeyword: "TSPublicKeyword", - TSQualifiedName: "TSQualifiedName", - TSQuestionToken: "TSQuestionToken", - TSReadonlyKeyword: "TSReadonlyKeyword", - TSStaticKeyword: "TSStaticKeyword", - TSStringKeyword: "TSStringKeyword", - TSSymbolKeyword: "TSSymbolKeyword", - TSTypeAnnotation: "TSTypeAnnotation", - TSTypeLiteral: "TSTypeLiteral", - TSTypeOperator: "TSTypeOperator", - TSTypeParameter: "TSTypeParameter", - TSTypeParameterDeclaration: "TSTypeParameterDeclaration", - TSTypeParameterInstantiation: "TSTypeParameterInstantiation", - TSTypePredicate: "TSTypePredicate", - TSTypeReference: "TSTypeReference", - TSUnionType: "TSUnionType", - TSUndefinedKeyword: "TSUndefinedKeyword", - TSVoidKeyword: "TSVoidKeyword", - UnaryExpression: "UnaryExpression", - UpdateExpression: "UpdateExpression", - VariableDeclaration: "VariableDeclaration", - VariableDeclarator: "VariableDeclarator", - WhileStatement: "WhileStatement", - WithStatement: "WithStatement", - YieldExpression: "YieldExpression" -}; diff --git a/lib/convert-comments.js b/lib/convert-comments.js deleted file mode 100644 index 866aa44..0000000 --- a/lib/convert-comments.js +++ /dev/null @@ -1,147 +0,0 @@ -/** - * @fileoverview Convert comment using TypeScript token scanner - * @author James Henry - * @copyright jQuery Foundation and other contributors, https://jquery.org/ - * MIT License - */ - -"use strict"; - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -const ts = require("typescript"), - nodeUtils = require("./node-utils"); - -//------------------------------------------------------------------------------ -// Private -//------------------------------------------------------------------------------ - -/** - * Converts a TypeScript comment to an Esprima comment. - * @param {boolean} block True if it's a block comment, false if not. - * @param {string} text The text of the comment. - * @param {int} start The index at which the comment starts. - * @param {int} end The index at which the comment ends. - * @param {Location} startLoc The location at which the comment starts. - * @param {Location} endLoc The location at which the comment ends. - * @returns {Object} The comment object. - * @private - */ -function convertTypeScriptCommentToEsprimaComment(block, text, start, end, startLoc, endLoc) { - const comment = { - type: block ? "Block" : "Line", - value: text - }; - - if (typeof start === "number") { - comment.range = [start, end]; - } - - if (typeof startLoc === "object") { - comment.loc = { - start: startLoc, - end: endLoc - }; - } - - return comment; -} - -/** - * Convert comment from TypeScript Triva Scanner. - * @param {Object} triviaScanner TS Scanner - * @param {Object} ast the AST object - * @param {string} code TypeScript code - * @returns {ESTreeComment} the converted ESTreeComment - * @private - */ -function getCommentFromTriviaScanner(triviaScanner, ast, code) { - const kind = triviaScanner.getToken(); - const isBlock = (kind === ts.SyntaxKind.MultiLineCommentTrivia); - const range = { - pos: triviaScanner.getTokenPos(), - end: triviaScanner.getTextPos(), - kind: triviaScanner.getToken() - }; - - const comment = code.substring(range.pos, range.end); - const text = (isBlock) ? comment.replace(/^\/\*/, "").replace(/\*\/$/, "") : comment.replace(/^\/\//, ""); - const loc = nodeUtils.getLocFor(range.pos, range.end, ast); - - const esprimaComment = convertTypeScriptCommentToEsprimaComment(isBlock, text, range.pos, range.end, loc.start, loc.end); - - return esprimaComment; -} - -//------------------------------------------------------------------------------ -// Public -//------------------------------------------------------------------------------ - -/* eslint-disable no-use-before-define */ -module.exports = { - convertComments -}; - - -/** - * Convert all comments for the given AST. - * @param {Object} ast the AST object - * @param {string} code the TypeScript code - * @returns {ESTreeComment[]} the converted ESTreeComment - * @private - */ -function convertComments(ast, code) { - const comments = []; - - /** - * Create a TypeScript Scanner, with skipTrivia set to false so that - * we can parse the comments - */ - const triviaScanner = ts.createScanner(ast.languageVersion, false, 0, code); - - let kind = triviaScanner.scan(); - while (kind !== ts.SyntaxKind.EndOfFileToken) { - const start = triviaScanner.getTokenPos(); - const end = triviaScanner.getTextPos(); - - let container = null; - switch (kind) { - case ts.SyntaxKind.SingleLineCommentTrivia: - case ts.SyntaxKind.MultiLineCommentTrivia: { - const comment = getCommentFromTriviaScanner(triviaScanner, ast, code); - - comments.push(comment); - break; - } - case ts.SyntaxKind.CloseBraceToken: - container = nodeUtils.getNodeContainer(ast, start, end); - - if ( - container.kind === ts.SyntaxKind.TemplateMiddle || - container.kind === ts.SyntaxKind.TemplateTail - ) { - kind = triviaScanner.reScanTemplateToken(); - continue; - } - break; - case ts.SyntaxKind.SlashToken: - case ts.SyntaxKind.SlashEqualsToken: - container = nodeUtils.getNodeContainer(ast, start, end); - - if ( - container.kind === ts.SyntaxKind.RegularExpressionLiteral - ) { - kind = triviaScanner.reScanSlashToken(); - continue; - } - break; - default: - break; - } - kind = triviaScanner.scan(); - } - - return comments; -} diff --git a/lib/convert.js b/lib/convert.js deleted file mode 100644 index ac295eb..0000000 --- a/lib/convert.js +++ /dev/null @@ -1,2217 +0,0 @@ -/** - * @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 - */ - -"use strict"; - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -const nodeUtils = require("./node-utils"), - AST_NODE_TYPES = require("./ast-node-types"); - -//------------------------------------------------------------------------------ -// Private -//------------------------------------------------------------------------------ - -const SyntaxKind = nodeUtils.SyntaxKind; - -//------------------------------------------------------------------------------ -// Public -//------------------------------------------------------------------------------ - -/** - * Converts a TypeScript node into an ESTree node - * @param {Object} config configuration options for the conversion - * @param {TSNode} config.node the TSNode - * @param {TSNode} config.parent the parent TSNode - * @param {TSNode} config.ast the full TypeScript AST - * @param {Object} config.additionalOptions additional options for the conversion - * @param {Object} config.additionalOptions.errorOnUnknownASTType whether whether or not to throw an error if an unknown AST Node Type is encountered - * @returns {ESTreeNode} the converted ESTreeNode - */ -module.exports = function convert(config) { - - const node = config.node; - const parent = config.parent; - const ast = config.ast; - const additionalOptions = config.additionalOptions || {}; - - /** - * Exit early for null and undefined - */ - if (!node) { - return null; - } - - /** - * Create a new ESTree node - */ - let result = { - type: "", - range: [node.getStart(), node.end], - loc: nodeUtils.getLoc(node, ast) - }; - - /** - * Copies the result object into an ESTree node with just a type property. - * This is used only for leaf nodes that have no other properties. - * @returns {void} - */ - function simplyCopy() { - Object.assign(result, { - type: SyntaxKind[node.kind] - }); - } - - /** - * If we are parsing for ESLint we need to perform a custom namespacing step - * on functions which have no body so that we do not break any ESLint rules which - * rely on them to have one. - * - * @param {ESTreeNode} functionNode the converted ESTreeNode - * @returns {void} - */ - function namespaceEmptyBodyFunctionForESLint(functionNode) { - if (!config.additionalOptions.parseForESLint || functionNode.body) { - return; - } - functionNode.type = `TSEmptyBody${functionNode.type}`; - } - - /** - * Converts a TypeScript node into an ESTree node. - * @param {TSNode} child the child TSNode - * @returns {ESTreeNode} the converted ESTree node - */ - function convertChild(child) { - return convert({ node: child, parent: node, ast, additionalOptions }); - } - - /** - * Converts a child into a type annotation. This creates an intermediary - * TypeAnnotation node to match what Flow does. - * @param {TSNode} child The TypeScript AST node to convert. - * @returns {ESTreeNode} The type annotation node. - */ - function convertTypeAnnotation(child) { - const annotation = convertChild(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 - }; - } - - /** - * Converts a TSNode's typeArguments array to a flow-like typeParameters node - * @param {TSNode[]} typeArguments TSNode typeArguments - * @returns {TypeParameterInstantiation} TypeParameterInstantiation node - */ - function convertTypeArgumentsToTypeParameters(typeArguments) { - /** - * Even if typeArguments is an empty array, TypeScript sets a `pos` and `end` - * property on the array object so we can safely read the values here - */ - const start = typeArguments.pos - 1; - let end = typeArguments.end + 1; - if (typeArguments && typeArguments.length) { - const firstTypeArgument = typeArguments[0]; - const typeArgumentsParent = firstTypeArgument.parent; - /** - * In the case of the parent being a CallExpression or a TypeReference we have to use - * slightly different logic to calculate the correct end position - */ - if (typeArgumentsParent && (typeArgumentsParent.kind === SyntaxKind.CallExpression || typeArgumentsParent.kind === SyntaxKind.TypeReference)) { - const lastTypeArgument = typeArguments[typeArguments.length - 1]; - const greaterThanToken = nodeUtils.findNextToken(lastTypeArgument, ast); - end = greaterThanToken.end; - } - } - return { - type: AST_NODE_TYPES.TSTypeParameterInstantiation, - range: [ - start, - end - ], - loc: nodeUtils.getLocFor(start, end, ast), - params: typeArguments.map(typeArgument => { - if (nodeUtils.isTypeKeyword(typeArgument.kind)) { - return { - type: AST_NODE_TYPES[`TS${SyntaxKind[typeArgument.kind]}`], - range: [ - typeArgument.getStart(), - typeArgument.getEnd() - ], - loc: nodeUtils.getLoc(typeArgument, ast) - }; - } - if (typeArgument.kind === SyntaxKind.ImportType) { - return convert({ node: typeArgument, parent: null, ast, additionalOptions }); - } - return { - type: AST_NODE_TYPES.TSTypeReference, - range: [ - typeArgument.getStart(), - typeArgument.getEnd() - ], - loc: nodeUtils.getLoc(typeArgument, ast), - typeName: convertChild(typeArgument.typeName || typeArgument), - typeParameters: (typeArgument.typeArguments) - ? convertTypeArgumentsToTypeParameters(typeArgument.typeArguments) - : undefined - }; - }) - }; - } - - /** - * Converts a TSNode's typeParameters array to a flow-like TypeParameterDeclaration node - * @param {TSNode[]} typeParameters TSNode typeParameters - * @returns {TypeParameterDeclaration} TypeParameterDeclaration node - */ - function convertTSTypeParametersToTypeParametersDeclaration(typeParameters) { - const firstTypeParameter = typeParameters[0]; - const lastTypeParameter = typeParameters[typeParameters.length - 1]; - - const greaterThanToken = nodeUtils.findNextToken(lastTypeParameter, ast); - - 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 => { - const name = typeParameter.name.text; - - const constraint = typeParameter.constraint - ? convert({ node: typeParameter.constraint, parent: typeParameter, ast, additionalOptions }) - : undefined; - - const defaultParameter = typeParameter.default - ? convert({ node: typeParameter.default, parent: typeParameter, ast, additionalOptions }) - : typeParameter.default; - - return { - type: AST_NODE_TYPES.TSTypeParameter, - range: [ - typeParameter.getStart(), - typeParameter.getEnd() - ], - loc: nodeUtils.getLoc(typeParameter, ast), - name, - constraint, - default: defaultParameter - }; - }) - }; - } - - /** - * Converts a child into a class implements node. This creates an intermediary - * ClassImplements node to match what Flow does. - * @param {TSNode} child The TypeScript AST node to convert. - * @returns {ESTreeNode} The type annotation node. - */ - function convertClassImplements(child) { - const id = convertChild(child.expression); - const classImplementsNode = { - 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; - } - - /** - * Converts a child into a interface heritage node. - * @param {TSNode} child The TypeScript AST node to convert. - * @returns {ESTreeNode} The type annotation node. - */ - function convertInterfaceHeritageClause(child) { - const id = convertChild(child.expression); - const classImplementsNode = { - type: AST_NODE_TYPES.TSInterfaceHeritage, - loc: id.loc, - range: id.range, - id - }; - - if (child.typeArguments && child.typeArguments.length) { - classImplementsNode.typeParameters = convertTypeArgumentsToTypeParameters(child.typeArguments); - } - return classImplementsNode; - } - - /** - * Converts an array of TSNode decorators into an array of ESTreeNode decorators - * @param {TSNode[]} decorators An array of TSNode decorators to be converted - * @returns {ESTreeNode[]} an array of converted ESTreeNode decorators - */ - function convertDecorators(decorators) { - if (!decorators || !decorators.length) { - return []; - } - return decorators.map(decorator => { - const expression = convertChild(decorator.expression); - return { - type: AST_NODE_TYPES.Decorator, - range: [decorator.getStart(), decorator.end], - loc: nodeUtils.getLoc(decorator, ast), - expression - }; - }); - } - - /** - * Converts an array of TSNode parameters into an array of ESTreeNode params - * @param {TSNode[]} parameters An array of TSNode params to be converted - * @returns {ESTreeNode[]} an array of converted ESTreeNode params - */ - function convertParameters(parameters) { - if (!parameters || !parameters.length) { - return []; - } - return parameters.map(param => { - const convertedParam = convertChild(param); - if (!param.decorators || !param.decorators.length) { - return convertedParam; - } - return Object.assign(convertedParam, { - decorators: convertDecorators(param.decorators) - }); - }); - } - - /** - * For nodes that are copied directly from the TypeScript AST into - * ESTree mostly as-is. The only difference is the addition of a type - * property instead of a kind property. Recursively copies all children. - * @returns {void} - */ - function deeplyCopy() { - const customType = `TS${SyntaxKind[node.kind]}`; - /** - * If the "errorOnUnknownASTType" option is set to true, throw an error, - * otherwise fallback to just inlcuding 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.type) ? convertTypeAnnotation(node.type) : null; - } else if (key === "typeArguments") { - result.typeParameters = (node.typeArguments) - ? convertTypeArgumentsToTypeParameters(node.typeArguments) - : null; - } else if (key === "typeParameters") { - result.typeParameters = (node.typeParameters) - ? convertTSTypeParametersToTypeParametersDeclaration(node.typeParameters) - : null; - } else if (key === "decorators") { - const decorators = convertDecorators(node.decorators); - if (decorators && decorators.length) { - result.decorators = decorators; - } - } else { - if (Array.isArray(node[key])) { - result[key] = node[key].map(convertChild); - } else if (node[key] && typeof node[key] === "object") { - result[key] = convertChild(node[key]); - } else { - result[key] = node[key]; - } - } - }); - } - - /** - * Converts a TypeScript JSX node.tagName into an ESTree node.name - * @param {Object} tagName the tagName object from a JSX TSNode - * @param {Object} ast the AST object - * @returns {Object} the converted ESTree name object - */ - function convertTypeScriptJSXTagNameToESTreeName(tagName) { - const tagNameToken = nodeUtils.convertToken(tagName, ast); - - if (tagNameToken.type === AST_NODE_TYPES.JSXMemberExpression) { - - const isNestedMemberExpression = (node.tagName.expression.kind === SyntaxKind.PropertyAccessExpression); - - // Convert TSNode left and right objects into ESTreeNode object - // and property objects - tagNameToken.object = convertChild(node.tagName.expression); - tagNameToken.property = convertChild(node.tagName.name); - - // Assign the appropriate types - tagNameToken.object.type = (isNestedMemberExpression) ? AST_NODE_TYPES.JSXMemberExpression : AST_NODE_TYPES.JSXIdentifier; - tagNameToken.property.type = AST_NODE_TYPES.JSXIdentifier; - if (tagName.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 {TSNode[]} modifiers original TSNodes from the node.modifiers array - * @returns {void} (the current result object will be mutated) - */ - function applyModifiersToResult(modifiers) { - if (!modifiers || !modifiers.length) { - return; - } - /** - * Some modifiers are explicitly handled by applying them as - * boolean values on the result node. As well as adding them - * to the result, we remove them from the array, so that they - * are not handled twice. - */ - const handledModifierIndices = {}; - for (let i = 0; i < modifiers.length; i++) { - const modifier = modifiers[i]; - switch (modifier.kind) { - /** - * Ignore ExportKeyword and DefaultKeyword, they are handled - * via the fixExports utility function - */ - case SyntaxKind.ExportKeyword: - case SyntaxKind.DefaultKeyword: - handledModifierIndices[i] = true; - break; - case SyntaxKind.ConstKeyword: - result.const = true; - handledModifierIndices[i] = true; - break; - case SyntaxKind.DeclareKeyword: - result.declare = true; - handledModifierIndices[i] = true; - break; - default: - } - } - /** - * If there are still valid modifiers available which have - * not been explicitly handled above, we just convert and - * add the modifiers array to the result node. - */ - const remainingModifiers = modifiers.filter((_, i) => !handledModifierIndices[i]); - if (!remainingModifiers || !remainingModifiers.length) { - return; - } - result.modifiers = remainingModifiers.map(convertChild); - } - - /** - * Uses the current TSNode's end location for its `type` to adjust the location data of the given - * ESTreeNode, which should be the parent of the final typeAnnotation node - * @param {ESTreeNode} typeAnnotationParent The node that will have its location data mutated - * @returns {void} - */ - function fixTypeAnnotationParentLocation(typeAnnotationParent) { - const end = node.type.getEnd(); - typeAnnotationParent.range[1] = end; - const loc = nodeUtils.getLocFor(typeAnnotationParent.range[0], typeAnnotationParent.range[1], ast); - typeAnnotationParent.loc = loc; - } - - /** - * 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: [], - sourceType: node.externalModuleIndicator ? "module" : "script" - }); - - // filter out unknown nodes for now - node.statements.forEach(statement => { - const convertedStatement = convertChild(statement); - if (convertedStatement) { - result.body.push(convertedStatement); - } - }); - - result.range[1] = node.endOfFileToken.end; - result.loc = nodeUtils.getLocFor(node.getStart(), result.range[1], ast); - break; - - case SyntaxKind.Block: - Object.assign(result, { - type: AST_NODE_TYPES.BlockStatement, - body: node.statements.map(convertChild) - }); - break; - - case SyntaxKind.Identifier: - Object.assign(result, { - type: AST_NODE_TYPES.Identifier, - name: node.text - }); - break; - - case SyntaxKind.WithStatement: - Object.assign(result, { - type: AST_NODE_TYPES.WithStatement, - object: convertChild(node.expression), - body: convertChild(node.statement) - }); - break; - - // Control Flow - - case SyntaxKind.ReturnStatement: - Object.assign(result, { - type: AST_NODE_TYPES.ReturnStatement, - argument: convertChild(node.expression) - }); - break; - - case SyntaxKind.LabeledStatement: - Object.assign(result, { - type: AST_NODE_TYPES.LabeledStatement, - label: convertChild(node.label), - body: convertChild(node.statement) - }); - break; - - case SyntaxKind.BreakStatement: - case SyntaxKind.ContinueStatement: - Object.assign(result, { - type: SyntaxKind[node.kind], - label: convertChild(node.label) - }); - break; - - // Choice - - case SyntaxKind.IfStatement: - Object.assign(result, { - type: AST_NODE_TYPES.IfStatement, - test: convertChild(node.expression), - consequent: convertChild(node.thenStatement), - alternate: convertChild(node.elseStatement) - }); - break; - - case SyntaxKind.SwitchStatement: - Object.assign(result, { - type: AST_NODE_TYPES.SwitchStatement, - discriminant: convertChild(node.expression), - cases: node.caseBlock.clauses.map(convertChild) - }); - break; - - case SyntaxKind.CaseClause: - case SyntaxKind.DefaultClause: - Object.assign(result, { - type: AST_NODE_TYPES.SwitchCase, - test: convertChild(node.expression), - consequent: node.statements.map(convertChild) - }); - break; - - // Exceptions - - case SyntaxKind.ThrowStatement: - Object.assign(result, { - type: AST_NODE_TYPES.ThrowStatement, - argument: convertChild(node.expression) - }); - 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) - }); - break; - - case SyntaxKind.CatchClause: - Object.assign(result, { - type: AST_NODE_TYPES.CatchClause, - param: node.variableDeclaration ? convertChild(node.variableDeclaration.name) : null, - body: convertChild(node.block) - }); - break; - - // Loops - - case SyntaxKind.WhileStatement: - Object.assign(result, { - type: AST_NODE_TYPES.WhileStatement, - test: convertChild(node.expression), - body: convertChild(node.statement) - }); - break; - - /** - * Unlike other parsers, TypeScript calls a "DoWhileStatement" - * a "DoStatement" - */ - case SyntaxKind.DoStatement: - Object.assign(result, { - type: AST_NODE_TYPES.DoWhileStatement, - test: convertChild(node.expression), - body: convertChild(node.statement) - }); - break; - - 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; - - case SyntaxKind.ForInStatement: - case SyntaxKind.ForOfStatement: { - const isAwait = !!(node.awaitModifier && node.awaitModifier.kind === SyntaxKind.AwaitKeyword); - Object.assign(result, { - type: SyntaxKind[node.kind], - left: convertChild(node.initializer), - right: convertChild(node.expression), - body: convertChild(node.statement), - await: isAwait - }); - break; - } - - // Declarations - - case SyntaxKind.FunctionDeclaration: { - - let functionDeclarationType = AST_NODE_TYPES.FunctionDeclaration; - - if (node.modifiers && node.modifiers.length) { - const isDeclareFunction = nodeUtils.hasModifier(SyntaxKind.DeclareKeyword, node); - if (isDeclareFunction) { - functionDeclarationType = AST_NODE_TYPES.DeclareFunction; - } - } - - Object.assign(result, { - type: functionDeclarationType, - id: convertChild(node.name), - generator: !!node.asteriskToken, - expression: false, - async: nodeUtils.hasModifier(SyntaxKind.AsyncKeyword, node), - params: convertParameters(node.parameters), - body: convertChild(node.body) - }); - - // Process returnType - if (node.type) { - result.returnType = convertTypeAnnotation(node.type); - } - - // Process typeParameters - if (node.typeParameters && node.typeParameters.length) { - result.typeParameters = convertTSTypeParametersToTypeParametersDeclaration(node.typeParameters); - } - - namespaceEmptyBodyFunctionForESLint(result); - - // check for exports - result = nodeUtils.fixExports(node, result, ast); - - break; - - } - - case SyntaxKind.VariableDeclaration: { - Object.assign(result, { - type: AST_NODE_TYPES.VariableDeclarator, - id: convertChild(node.name), - init: convertChild(node.initializer) - }); - - if (node.exclamationToken) { - result.definite = true; - } - - if (node.type) { - result.id.typeAnnotation = convertTypeAnnotation(node.type); - fixTypeAnnotationParentLocation(result.id); - } - break; - } - - case SyntaxKind.VariableStatement: - Object.assign(result, { - type: AST_NODE_TYPES.VariableDeclaration, - declarations: node.declarationList.declarations.map(convertChild), - kind: nodeUtils.getDeclarationKind(node.declarationList) - }); - - // check for exports - result = nodeUtils.fixExports(node, result, ast); - break; - - // 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; - - // Expressions - - case SyntaxKind.ExpressionStatement: - Object.assign(result, { - type: AST_NODE_TYPES.ExpressionStatement, - expression: convertChild(node.expression) - }); - break; - - case SyntaxKind.ThisKeyword: - Object.assign(result, { - type: AST_NODE_TYPES.ThisExpression - }); - break; - - 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 (arrayAssignNode.left === node) { - arrayIsInAssignment = true; - } else { - arrayIsInAssignment = (nodeUtils.findChildOfKind(arrayAssignNode.left, SyntaxKind.ArrayLiteralExpression, ast) === node); - } - } - - // 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; - - } - - case SyntaxKind.ObjectLiteralExpression: { - - const ancestorNode = nodeUtils.findFirstMatchingAncestor( - node, - parentNode => - (parentNode.kind === SyntaxKind.BinaryExpression || parentNode.kind === SyntaxKind.ArrowFunction) - ); - const objectAssignNode = ( - ancestorNode && - ancestorNode.kind === SyntaxKind.BinaryExpression && - ancestorNode.operatorToken.kind === SyntaxKind.FirstAssignment - ) ? ancestorNode : null; - - let objectIsInAssignment = false; - - if (objectAssignNode) { - if (objectAssignNode.left === node) { - objectIsInAssignment = true; - } else { - objectIsInAssignment = (nodeUtils.findChildOfKind(objectAssignNode.left, SyntaxKind.ObjectLiteralExpression, ast) === node); - } - } - - // TypeScript uses ObjectLiteralExpression in destructuring assignment, too - if (objectIsInAssignment) { - Object.assign(result, { - type: AST_NODE_TYPES.ObjectPattern, - properties: node.properties.map(convertChild) - }); - } else { - Object.assign(result, { - type: AST_NODE_TYPES.ObjectExpression, - properties: node.properties.map(convertChild) - }); - } - - 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" - }); - } else { - Object.assign(result, { - type: AST_NODE_TYPES.Property, - key: convertChild(node.name), - value: convertChild(node.initializer || node.name), - computed: false, - method: false, - shorthand: true, - kind: "init" - }); - } - break; - } - - case SyntaxKind.ComputedPropertyName: - - if (parent.kind === SyntaxKind.ObjectLiteralExpression) { - Object.assign(result, { - type: AST_NODE_TYPES.Property, - key: convertChild(node.name), - value: convertChild(node.name), - computed: false, - method: false, - shorthand: true, - kind: "init" - }); - } else { - return convertChild(node.expression); - } - break; - - case SyntaxKind.PropertyDeclaration: { - const isAbstract = nodeUtils.hasModifier(SyntaxKind.AbstractKeyword, node); - 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.hasStaticModifierFlag(node), - readonly: nodeUtils.hasModifier(SyntaxKind.ReadonlyKeyword, node) || undefined - }); - - if (node.type) { - result.typeAnnotation = convertTypeAnnotation(node.type); - } - - if (node.decorators) { - result.decorators = convertDecorators(node.decorators); - } - - const accessibility = nodeUtils.getTSNodeAccessibility(node); - if (accessibility) { - result.accessibility = accessibility; - } - - if (node.name.kind === SyntaxKind.Identifier && node.questionToken) { - result.optional = true; - } - - if (node.exclamationToken) { - result.definite = true; - } - - if (result.key.type === AST_NODE_TYPES.Literal && node.questionToken) { - result.optional = true; - } - break; - } - - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.MethodDeclaration: { - - const openingParen = nodeUtils.findFirstMatchingToken(node.name, ast, token => { - if (!token || !token.kind) { - return false; - } - return nodeUtils.getTextForTokenKind(token.kind) === "("; - }); - - const methodLoc = ast.getLineAndCharacterOfPosition(openingParen.getStart()), - 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.range[1]], - loc: { - start: { - line: methodLoc.line + 1, - column: methodLoc.character - }, - end: result.loc.end - } - }; - - if (node.type) { - method.returnType = convertTypeAnnotation(node.type); - } - - if (parent.kind === SyntaxKind.ObjectLiteralExpression) { - - method.params = node.parameters.map(convertChild); - - Object.assign(result, { - type: AST_NODE_TYPES.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.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.hasStaticModifierFlag(node), - kind: "method" - }); - - if (node.decorators) { - result.decorators = convertDecorators(node.decorators); - } - - const accessibility = nodeUtils.getTSNodeAccessibility(node); - if (accessibility) { - result.accessibility = accessibility; - } - - } - - if (result.key.type === AST_NODE_TYPES.Identifier && node.questionToken) { - result.key.optional = true; - } - - if (node.kind === SyntaxKind.GetAccessor) { - result.kind = "get"; - } else if (node.kind === SyntaxKind.SetAccessor) { - result.kind = "set"; - } else if (!result.static && node.name.kind === SyntaxKind.StringLiteral && node.name.text === "constructor") { - result.kind = "constructor"; - } - - // Process typeParameters - if (node.typeParameters && node.typeParameters.length) { - method.typeParameters = convertTSTypeParametersToTypeParametersDeclaration(node.typeParameters); - } - - namespaceEmptyBodyFunctionForESLint(result.value); - - break; - - } - - // TypeScript uses this even for static methods named "constructor" - case SyntaxKind.Constructor: { - - const constructorIsStatic = nodeUtils.hasStaticModifierFlag(node), - constructorIsAbstract = nodeUtils.hasModifier(SyntaxKind.AbstractKeyword, node), - firstConstructorToken = constructorIsStatic ? nodeUtils.findNextToken(node.getFirstToken(), 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.range[1]], - loc: { - start: { - line: constructorLoc.line + 1, - column: constructorLoc.character - }, - end: result.loc.end - } - }; - - const constructorIdentifierLocStart = ast.getLineAndCharacterOfPosition(firstConstructorToken.getStart()), - constructorIdentifierLocEnd = ast.getLineAndCharacterOfPosition(firstConstructorToken.getEnd()), - 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.getStart(), firstConstructorToken.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.getStart(), firstConstructorToken.end], - loc: { - start: { - line: constructorIdentifierLocStart.line + 1, - column: constructorIdentifierLocStart.character - }, - end: { - line: constructorIdentifierLocEnd.line + 1, - column: constructorIdentifierLocEnd.character - } - } - }; - } - - 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.accessibility = accessibility; - } - - namespaceEmptyBodyFunctionForESLint(result.value); - - break; - - } - - 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.returnType = convertTypeAnnotation(node.type); - } - - // Process typeParameters - if (node.typeParameters && node.typeParameters.length) { - result.typeParameters = convertTSTypeParametersToTypeParametersDeclaration(node.typeParameters); - } - break; - - case SyntaxKind.SuperKeyword: - Object.assign(result, { - type: AST_NODE_TYPES.Super - }); - break; - - case SyntaxKind.ArrayBindingPattern: - Object.assign(result, { - type: AST_NODE_TYPES.ArrayPattern, - elements: node.elements.map(convertChild) - }); - break; - - // occurs with missing array elements like [,] - case SyntaxKind.OmittedExpression: - return null; - - 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 (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.initializer) { - result.value = { - type: AST_NODE_TYPES.AssignmentPattern, - left: convertChild(node.name), - right: convertChild(node.initializer), - range: [node.name.getStart(), node.initializer.end], - loc: nodeUtils.getLocFor(node.name.getStart(), node.initializer.end, ast) - }; - } - } - break; - - - 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 - }); - - // Process returnType - if (node.type) { - result.returnType = convertTypeAnnotation(node.type); - } - - // Process typeParameters - if (node.typeParameters && node.typeParameters.length) { - result.typeParameters = convertTSTypeParametersToTypeParametersDeclaration(node.typeParameters); - } - break; - - case SyntaxKind.YieldExpression: - Object.assign(result, { - type: AST_NODE_TYPES.YieldExpression, - delegate: !!node.asteriskToken, - argument: convertChild(node.expression) - }); - break; - - case SyntaxKind.AwaitExpression: - Object.assign(result, { - type: AST_NODE_TYPES.AwaitExpression, - argument: convertChild(node.expression) - }); - break; - - // Template Literals - - case SyntaxKind.NoSubstitutionTemplateLiteral: - Object.assign(result, { - type: AST_NODE_TYPES.TemplateLiteral, - quasis: [ - { - type: AST_NODE_TYPES.TemplateElement, - value: { - raw: ast.text.slice(node.getStart() + 1, node.end - 1), - cooked: node.text - }, - tail: true, - range: result.range, - loc: result.loc - } - ], - expressions: [] - }); - break; - - case SyntaxKind.TemplateExpression: - Object.assign(result, { - type: AST_NODE_TYPES.TemplateLiteral, - quasis: [convertChild(node.head)], - expressions: [] - }); - - node.templateSpans.forEach(templateSpan => { - result.expressions.push(convertChild(templateSpan.expression)); - result.quasis.push(convertChild(templateSpan.literal)); - }); - break; - - case SyntaxKind.TaggedTemplateExpression: - Object.assign(result, { - type: AST_NODE_TYPES.TaggedTemplateExpression, - typeParameters: (node.typeArguments) - ? convertTypeArgumentsToTypeParameters(node.typeArguments) - : undefined, - tag: convertChild(node.tag), - quasi: convertChild(node.template) - }); - break; - - case SyntaxKind.TemplateHead: - case SyntaxKind.TemplateMiddle: - case SyntaxKind.TemplateTail: { - const tail = (node.kind === SyntaxKind.TemplateTail); - Object.assign(result, { - type: AST_NODE_TYPES.TemplateElement, - value: { - raw: ast.text.slice(node.getStart() + 1, node.end - (tail ? 1 : 2)), - cooked: node.text - }, - tail - }); - break; - } - - // Patterns - - case SyntaxKind.SpreadElement: { - let type = AST_NODE_TYPES.SpreadElement; - - if (node.parent && - node.parent.parent && - node.parent.parent.kind === SyntaxKind.BinaryExpression - ) { - if (node.parent.parent.left === node.parent) { - type = AST_NODE_TYPES.RestElement; - } else if (node.parent.parent.right === node.parent) { - type = AST_NODE_TYPES.SpreadElement; - } - } - - Object.assign(result, { - type, - argument: convertChild(node.expression) - }); - 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.right === node.parent) { - type = AST_NODE_TYPES.SpreadElement; - } else if (node.parent.parent.left === node.parent) { - type = AST_NODE_TYPES.RestElement; - } - } - - Object.assign(result, { - type, - argument: convertChild(node.expression) - }); - break; - } - - case SyntaxKind.Parameter: { - let parameter; - - 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 = parameter; - } - - if (node.type) { - parameter.typeAnnotation = convertTypeAnnotation(node.type); - fixTypeAnnotationParentLocation(parameter); - } - - if (node.questionToken) { - parameter.optional = true; - } - - if (node.modifiers) { - return { - type: AST_NODE_TYPES.TSParameterProperty, - range: [node.getStart(), 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 - }; - } - - break; - - } - - // Classes - - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: { - - const heritageClauses = node.heritageClauses || []; - - let classNodeType = SyntaxKind[node.kind]; - let lastClassToken = heritageClauses.length ? heritageClauses[heritageClauses.length - 1] : node.name; - - if (node.typeParameters && node.typeParameters.length) { - const lastTypeParameter = node.typeParameters[node.typeParameters.length - 1]; - - if (!lastClassToken || lastTypeParameter.pos > lastClassToken.pos) { - lastClassToken = nodeUtils.findNextToken(lastTypeParameter, ast); - } - result.typeParameters = convertTSTypeParametersToTypeParametersDeclaration(node.typeParameters); - } - - if (node.modifiers && node.modifiers.length) { - - /** - * TypeScript class declarations can be defined as "abstract" - */ - if (node.kind === SyntaxKind.ClassDeclaration) { - if (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); - } - - } else if (!lastClassToken) { // no name - lastClassToken = node.getFirstToken(); - } - - const openBrace = nodeUtils.findNextToken(lastClassToken, ast); - const superClass = heritageClauses.find(clause => clause.token === SyntaxKind.ExtendsKeyword); - - if (superClass) { - if (superClass.types.length > 1) { - throw nodeUtils.createError(ast, superClass.types[1].pos, "Classes can only extend a single class."); - } - - if (superClass.types[0] && superClass.types[0].typeArguments) { - result.superTypeParameters = convertTypeArgumentsToTypeParameters(superClass.types[0].typeArguments); - } - } - - const implementsClause = heritageClauses.find(clause => clause.token === SyntaxKind.ImplementsKeyword); - - Object.assign(result, { - type: classNodeType, - id: convertChild(node.name), - body: { - type: AST_NODE_TYPES.ClassBody, - body: [], - - // TODO: Fix location info - range: [openBrace.getStart(), result.range[1]], - loc: nodeUtils.getLocFor(openBrace.getStart(), node.end, ast) - }, - superClass: (superClass && superClass.types[0] ? convertChild(superClass.types[0].expression) : null) - }); - - if (implementsClause) { - result.implements = implementsClause.types.map(convertClassImplements); - } - - if (node.decorators) { - result.decorators = convertDecorators(node.decorators); - } - - const filteredMembers = node.members.filter(nodeUtils.isESTreeClassMember); - - if (filteredMembers.length) { - result.body.body = filteredMembers.map(convertChild); - } - - // check for exports - result = nodeUtils.fixExports(node, result, ast); - - break; - - } - - // Modules - case SyntaxKind.ModuleBlock: - Object.assign(result, { - type: AST_NODE_TYPES.TSModuleBlock, - body: node.statements.map(convertChild) - }); - break; - - case SyntaxKind.ImportDeclaration: - Object.assign(result, { - type: AST_NODE_TYPES.ImportDeclaration, - source: convertChild(node.moduleSpecifier), - specifiers: [] - }); - - if (node.importClause) { - if (node.importClause.name) { - result.specifiers.push(convertChild(node.importClause)); - } - - if (node.importClause.namedBindings) { - if (node.importClause.namedBindings.kind === SyntaxKind.NamespaceImport) { - result.specifiers.push(convertChild(node.importClause.namedBindings)); - } else { - result.specifiers = result.specifiers.concat(node.importClause.namedBindings.elements.map(convertChild)); - } - } - } - - break; - - case SyntaxKind.NamespaceImport: - Object.assign(result, { - type: AST_NODE_TYPES.ImportNamespaceSpecifier, - local: convertChild(node.name) - }); - break; - - case SyntaxKind.ImportSpecifier: - Object.assign(result, { - type: AST_NODE_TYPES.ImportSpecifier, - local: convertChild(node.name), - imported: convertChild(node.propertyName || node.name) - }); - break; - - case SyntaxKind.ImportClause: - Object.assign(result, { - type: AST_NODE_TYPES.ImportDefaultSpecifier, - local: convertChild(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], ast); - break; - - case SyntaxKind.NamedImports: - Object.assign(result, { - type: AST_NODE_TYPES.ImportDefaultSpecifier, - local: convertChild(node.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; - - // Unary Operations - - 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; - } - - case SyntaxKind.DeleteExpression: - Object.assign(result, { - type: AST_NODE_TYPES.UnaryExpression, - operator: "delete", - prefix: true, - argument: convertChild(node.expression) - }); - break; - - case SyntaxKind.VoidExpression: - Object.assign(result, { - type: AST_NODE_TYPES.UnaryExpression, - operator: "void", - prefix: true, - argument: convertChild(node.expression) - }); - break; - - case SyntaxKind.TypeOfExpression: - Object.assign(result, { - type: AST_NODE_TYPES.UnaryExpression, - operator: "typeof", - prefix: true, - argument: convertChild(node.expression) - }); - break; - - case SyntaxKind.TypeOperator: - Object.assign(result, { - type: AST_NODE_TYPES.TSTypeOperator, - operator: nodeUtils.getTextForTokenKind(node.operator), - typeAnnotation: convertChild(node.type) - }); - break; - - // Binary Operations - - case SyntaxKind.BinaryExpression: - - // TypeScript uses BinaryExpression for sequences as well - if (nodeUtils.isComma(node.operatorToken)) { - Object.assign(result, { - type: AST_NODE_TYPES.SequenceExpression, - expressions: [] - }); - - const left = convertChild(node.left), - right = convertChild(node.right); - - if (left.type === AST_NODE_TYPES.SequenceExpression) { - result.expressions = result.expressions.concat(left.expressions); - } else { - result.expressions.push(left); - } - - if (right.type === AST_NODE_TYPES.SequenceExpression) { - result.expressions = result.expressions.concat(right.expressions); - } else { - result.expressions.push(right); - } - - } else if (node.operatorToken && node.operatorToken.kind === SyntaxKind.AsteriskAsteriskEqualsToken) { - Object.assign(result, { - type: AST_NODE_TYPES.AssignmentExpression, - operator: nodeUtils.getTextForTokenKind(node.operatorToken.kind), - left: convertChild(node.left), - right: convertChild(node.right) - }); - } else { - Object.assign(result, { - type: nodeUtils.getBinaryExpressionType(node.operatorToken), - operator: nodeUtils.getTextForTokenKind(node.operatorToken.kind), - left: convertChild(node.left), - right: convertChild(node.right) - }); - - // if the binary expression is in a destructured array, switch it - if (result.type === AST_NODE_TYPES.AssignmentExpression) { - const upperArrayNode = nodeUtils.findAncestorOfKind(node, SyntaxKind.ArrayLiteralExpression), - upperArrayAssignNode = upperArrayNode && nodeUtils.findAncestorOfKind(upperArrayNode, SyntaxKind.BinaryExpression); - - let upperArrayIsInAssignment; - - if (upperArrayAssignNode) { - if (upperArrayAssignNode.left === upperArrayNode) { - upperArrayIsInAssignment = true; - } else { - upperArrayIsInAssignment = (nodeUtils.findChildOfKind(upperArrayAssignNode.left, SyntaxKind.ArrayLiteralExpression, ast) === upperArrayNode); - } - } - - if (upperArrayIsInAssignment) { - delete result.operator; - result.type = AST_NODE_TYPES.AssignmentPattern; - } - } - } - 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.object.name = "this"; - } - - jsxMemberExpression.object.type = (isNestedMemberExpression) ? AST_NODE_TYPES.MemberExpression : AST_NODE_TYPES.JSXIdentifier; - jsxMemberExpression.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; - } - - // Literals - - case SyntaxKind.StringLiteral: - Object.assign(result, { - type: AST_NODE_TYPES.Literal, - raw: ast.text.slice(result.range[0], result.range[1]) - }); - if (parent.name && parent.name === node) { - result.value = node.text; - } else { - result.value = nodeUtils.unescapeStringLiteralText(node.text); - } - break; - - case SyntaxKind.NumericLiteral: - Object.assign(result, { - type: AST_NODE_TYPES.Literal, - value: Number(node.text), - raw: ast.text.slice(result.range[0], result.range[1]) - }); - break; - - case SyntaxKind.RegularExpressionLiteral: { - 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; - } - - Object.assign(result, { - type: AST_NODE_TYPES.Literal, - value: regex, - raw: node.text, - regex: { - pattern, - flags - } - }); - break; - } - - case SyntaxKind.TrueKeyword: - Object.assign(result, { - type: AST_NODE_TYPES.Literal, - value: true, - raw: "true" - }); - break; - - case SyntaxKind.FalseKeyword: - Object.assign(result, { - type: AST_NODE_TYPES.Literal, - value: false, - raw: "false" - }); - break; - - case SyntaxKind.NullKeyword: { - if (nodeUtils.isWithinTypeAnnotation(node)) { - Object.assign(result, { - type: AST_NODE_TYPES.TSNullKeyword - }); - } else { - Object.assign(result, { - type: AST_NODE_TYPES.Literal, - value: null, - raw: "null" - }); - } - break; - } - - case SyntaxKind.ImportKeyword: - Object.assign(result, { - type: AST_NODE_TYPES.Import - }); - break; - - case SyntaxKind.EmptyStatement: - case SyntaxKind.DebuggerStatement: - simplyCopy(); - break; - - // JSX - - case SyntaxKind.JsxElement: - Object.assign(result, { - type: AST_NODE_TYPES.JSXElement, - openingElement: convertChild(node.openingElement), - closingElement: convertChild(node.closingElement), - children: node.children.map(convertChild) - }); - - break; - - 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.kind = SyntaxKind.JsxOpeningElement; - - const openingElement = convertChild(node); - openingElement.selfClosing = true; - - Object.assign(result, { - type: AST_NODE_TYPES.JSXElement, - openingElement, - closingElement: null, - children: [] - }); - - break; - - } - - 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.JsxExpression: { - const eloc = ast.getLineAndCharacterOfPosition(result.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.loc.end.line, - column: result.loc.end.column - 1 - } - }, - range: [result.range[0] + 1, result.range[1] - 1] - }; - - Object.assign(result, { - type: node.dotDotDotToken - ? AST_NODE_TYPES.JSXSpreadChild - : AST_NODE_TYPES.JSXExpressionContainer, - expression - }); - - break; - - } - - 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; - - } - - /** - * 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) - }); - - result.loc = nodeUtils.getLocFor(start, end, ast); - result.range = [start, end]; - - break; - } - - 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; - } - - // TypeScript specific - - case SyntaxKind.ParenthesizedExpression: - return convert({ node: node.expression, parent, ast, additionalOptions }); - - /** - * Convert TypeAliasDeclaration node into VariableDeclaration - * to allow core rules such as "semi" to work automatically - */ - case SyntaxKind.TypeAliasDeclaration: { - const typeAliasDeclarator = { - type: AST_NODE_TYPES.VariableDeclarator, - id: convertChild(node.name), - init: convertChild(node.type), - range: [node.name.getStart(), node.end] - }; - - typeAliasDeclarator.loc = nodeUtils.getLocFor(typeAliasDeclarator.range[0], typeAliasDeclarator.range[1], ast); - - // Process typeParameters - if (node.typeParameters && node.typeParameters.length) { - typeAliasDeclarator.typeParameters = convertTSTypeParametersToTypeParametersDeclaration(node.typeParameters); - } - - Object.assign(result, { - type: AST_NODE_TYPES.VariableDeclaration, - kind: nodeUtils.getDeclarationKind(node), - declarations: [typeAliasDeclarator] - }); - - // check for exports - result = nodeUtils.fixExports(node, result, ast); - - break; - - } - - 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.accessibility = accessibility; - } - - if (node.typeParameters) { - result.typeParameters = convertTSTypeParametersToTypeParametersDeclaration(node.typeParameters); - } - - break; - } - - 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.accessibility = accessibility; - } - - break; - } - - 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.accessibility = accessibility; - } - - break; - } - - case SyntaxKind.ConstructSignature: { - Object.assign(result, { - type: AST_NODE_TYPES.TSConstructSignature, - params: convertParameters(node.parameters), - typeAnnotation: (node.type) ? convertTypeAnnotation(node.type) : null - }); - - if (node.typeParameters) { - result.typeParameters = convertTSTypeParametersToTypeParametersDeclaration(node.typeParameters); - } - - break; - } - - 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); - } - result.typeParameters = convertTSTypeParametersToTypeParametersDeclaration(node.typeParameters); - } - - const hasImplementsClause = interfaceHeritageClauses.length > 0; - const hasAbstractKeyword = nodeUtils.hasModifier(SyntaxKind.AbstractKeyword, node); - const interfaceOpenBrace = nodeUtils.findNextToken(interfaceLastClassToken, ast); - - const interfaceBody = { - type: AST_NODE_TYPES.TSInterfaceBody, - body: node.members.map(member => convertChild(member)), - range: [interfaceOpenBrace.getStart(), result.range[1]], - loc: nodeUtils.getLocFor(interfaceOpenBrace.getStart(), node.end, ast) - }; - - Object.assign(result, { - abstract: hasAbstractKeyword, - 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); - } - // check for exports - result = nodeUtils.fixExports(node, result, ast); - - break; - - } - - 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.typeAnnotation.loc = result.typeAnnotation.typeAnnotation.loc; - result.typeAnnotation.range = result.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; - - 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, 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; - } - - case SyntaxKind.EnumMember: { - Object.assign(result, { - type: AST_NODE_TYPES.TSEnumMember, - id: convertChild(node.name) - }); - if (node.initializer) { - result.initializer = convertChild(node.initializer); - } - break; - } - - case SyntaxKind.AbstractKeyword: { - Object.assign(result, { - type: AST_NODE_TYPES.TSAbstractKeyword - }); - break; - } - - 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); - // ...then check for exports - result = nodeUtils.fixExports(node, result, ast); - break; - } - - default: - deeplyCopy(); - } - - return result; - -}; diff --git a/lib/node-utils.js b/lib/node-utils.js deleted file mode 100644 index bd0bee0..0000000 --- a/lib/node-utils.js +++ /dev/null @@ -1,748 +0,0 @@ -/** - * @fileoverview Utilities for finding and converting TSNodes into ESTreeNodes - * @author James Henry - * @copyright jQuery Foundation and other contributors, https://jquery.org/ - * MIT License - */ - -"use strict"; - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -const ts = require("typescript"), - unescape = require("lodash.unescape"); - -//------------------------------------------------------------------------------ -// Private -//------------------------------------------------------------------------------ - -const SyntaxKind = ts.SyntaxKind; - -const ASSIGNMENT_OPERATORS = [ - SyntaxKind.EqualsToken, - SyntaxKind.PlusEqualsToken, - SyntaxKind.MinusEqualsToken, - SyntaxKind.AsteriskEqualsToken, - SyntaxKind.SlashEqualsToken, - SyntaxKind.PercentEqualsToken, - SyntaxKind.LessThanLessThanEqualsToken, - SyntaxKind.GreaterThanGreaterThanEqualsToken, - SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken, - SyntaxKind.AmpersandEqualsToken, - SyntaxKind.BarEqualsToken, - SyntaxKind.CaretEqualsToken -]; - -const LOGICAL_OPERATORS = [ - SyntaxKind.BarBarToken, - SyntaxKind.AmpersandAmpersandToken -]; - -const TOKEN_TO_TEXT = {}; -TOKEN_TO_TEXT[SyntaxKind.OpenBraceToken] = "{"; -TOKEN_TO_TEXT[SyntaxKind.CloseBraceToken] = "}"; -TOKEN_TO_TEXT[SyntaxKind.OpenParenToken] = "("; -TOKEN_TO_TEXT[SyntaxKind.CloseParenToken] = ")"; -TOKEN_TO_TEXT[SyntaxKind.OpenBracketToken] = "["; -TOKEN_TO_TEXT[SyntaxKind.CloseBracketToken] = "]"; -TOKEN_TO_TEXT[SyntaxKind.DotToken] = "."; -TOKEN_TO_TEXT[SyntaxKind.DotDotDotToken] = "..."; -TOKEN_TO_TEXT[SyntaxKind.SemicolonToken] = ";"; -TOKEN_TO_TEXT[SyntaxKind.CommaToken] = ","; -TOKEN_TO_TEXT[SyntaxKind.LessThanToken] = "<"; -TOKEN_TO_TEXT[SyntaxKind.GreaterThanToken] = ">"; -TOKEN_TO_TEXT[SyntaxKind.LessThanEqualsToken] = "<="; -TOKEN_TO_TEXT[SyntaxKind.GreaterThanEqualsToken] = ">="; -TOKEN_TO_TEXT[SyntaxKind.EqualsEqualsToken] = "=="; -TOKEN_TO_TEXT[SyntaxKind.ExclamationEqualsToken] = "!="; -TOKEN_TO_TEXT[SyntaxKind.EqualsEqualsEqualsToken] = "==="; -TOKEN_TO_TEXT[SyntaxKind.InstanceOfKeyword] = "instanceof"; -TOKEN_TO_TEXT[SyntaxKind.ExclamationEqualsEqualsToken] = "!=="; -TOKEN_TO_TEXT[SyntaxKind.EqualsGreaterThanToken] = "=>"; -TOKEN_TO_TEXT[SyntaxKind.PlusToken] = "+"; -TOKEN_TO_TEXT[SyntaxKind.MinusToken] = "-"; -TOKEN_TO_TEXT[SyntaxKind.AsteriskToken] = "*"; -TOKEN_TO_TEXT[SyntaxKind.AsteriskAsteriskToken] = "**"; -TOKEN_TO_TEXT[SyntaxKind.SlashToken] = "/"; -TOKEN_TO_TEXT[SyntaxKind.PercentToken] = "%"; -TOKEN_TO_TEXT[SyntaxKind.PlusPlusToken] = "++"; -TOKEN_TO_TEXT[SyntaxKind.MinusMinusToken] = "--"; -TOKEN_TO_TEXT[SyntaxKind.LessThanLessThanToken] = "<<"; -TOKEN_TO_TEXT[SyntaxKind.LessThanSlashToken] = ">"; -TOKEN_TO_TEXT[SyntaxKind.GreaterThanGreaterThanGreaterThanToken] = ">>>"; -TOKEN_TO_TEXT[SyntaxKind.AmpersandToken] = "&"; -TOKEN_TO_TEXT[SyntaxKind.BarToken] = "|"; -TOKEN_TO_TEXT[SyntaxKind.CaretToken] = "^"; -TOKEN_TO_TEXT[SyntaxKind.ExclamationToken] = "!"; -TOKEN_TO_TEXT[SyntaxKind.TildeToken] = "~"; -TOKEN_TO_TEXT[SyntaxKind.AmpersandAmpersandToken] = "&&"; -TOKEN_TO_TEXT[SyntaxKind.BarBarToken] = "||"; -TOKEN_TO_TEXT[SyntaxKind.QuestionToken] = "?"; -TOKEN_TO_TEXT[SyntaxKind.ColonToken] = ":"; -TOKEN_TO_TEXT[SyntaxKind.EqualsToken] = "="; -TOKEN_TO_TEXT[SyntaxKind.PlusEqualsToken] = "+="; -TOKEN_TO_TEXT[SyntaxKind.MinusEqualsToken] = "-="; -TOKEN_TO_TEXT[SyntaxKind.AsteriskEqualsToken] = "*="; -TOKEN_TO_TEXT[SyntaxKind.AsteriskAsteriskEqualsToken] = "**="; -TOKEN_TO_TEXT[SyntaxKind.SlashEqualsToken] = "/="; -TOKEN_TO_TEXT[SyntaxKind.PercentEqualsToken] = "%="; -TOKEN_TO_TEXT[SyntaxKind.LessThanLessThanEqualsToken] = "<<="; -TOKEN_TO_TEXT[SyntaxKind.GreaterThanGreaterThanEqualsToken] = ">>="; -TOKEN_TO_TEXT[SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken] = ">>>="; -TOKEN_TO_TEXT[SyntaxKind.AmpersandEqualsToken] = "&="; -TOKEN_TO_TEXT[SyntaxKind.BarEqualsToken] = "|="; -TOKEN_TO_TEXT[SyntaxKind.CaretEqualsToken] = "^="; -TOKEN_TO_TEXT[SyntaxKind.AtToken] = "@"; -TOKEN_TO_TEXT[SyntaxKind.InKeyword] = "in"; -TOKEN_TO_TEXT[SyntaxKind.UniqueKeyword] = "unique"; -TOKEN_TO_TEXT[SyntaxKind.KeyOfKeyword] = "keyof"; -TOKEN_TO_TEXT[SyntaxKind.NewKeyword] = "new"; -TOKEN_TO_TEXT[SyntaxKind.ImportKeyword] = "import"; - -/** - * Find the first matching child based on the given sourceFile and predicate function. - * @param {TSNode} node The current TSNode - * @param {Object} sourceFile The full AST source file - * @param {Function} predicate The predicate function to apply to each checked child - * @returns {TSNode|undefined} a matching child TSNode - */ -function findFirstMatchingChild(node, sourceFile, predicate) { - const children = node.getChildren(sourceFile); - for (let i = 0; i < children.length; i++) { - const child = children[i]; - if (child && predicate(child)) { - return child; - } - - const grandChild = findFirstMatchingChild(child, sourceFile, predicate); - if (grandChild) { - return grandChild; - } - } - return undefined; -} - -//------------------------------------------------------------------------------ -// Public -//------------------------------------------------------------------------------ - -/* eslint-disable no-use-before-define */ -module.exports = { - /** - * Expose the enum of possible TSNode `kind`s. - */ - SyntaxKind, - isAssignmentOperator, - isLogicalOperator, - getTextForTokenKind, - isESTreeClassMember, - hasModifier, - isComma, - getBinaryExpressionType, - getLocFor, - getLoc, - isToken, - isJSXToken, - getDeclarationKind, - getTSNodeAccessibility, - hasStaticModifierFlag, - findNextToken, - findFirstMatchingToken, - findChildOfKind, - findFirstMatchingAncestor, - findAncestorOfKind, - hasJSXAncestor, - unescapeStringLiteralText, - isComputedProperty, - isOptional, - fixExports, - getTokenType, - convertToken, - convertTokens, - getNodeContainer, - isWithinTypeAnnotation, - isTypeKeyword, - isComment, - isJSDocComment, - createError -}; -/* eslint-enable no-use-before-define */ - -/** - * Returns true if the given TSToken is the assignment operator - * @param {TSToken} operator the operator token - * @returns {boolean} is assignment - */ -function isAssignmentOperator(operator) { - return ASSIGNMENT_OPERATORS.indexOf(operator.kind) > -1; -} - -/** - * Returns true if the given TSToken is a logical operator - * @param {TSToken} operator the operator token - * @returns {boolean} is a logical operator - */ -function isLogicalOperator(operator) { - return LOGICAL_OPERATORS.indexOf(operator.kind) > -1; -} - -/** - * Returns the string form of the given TSToken SyntaxKind - * @param {number} kind the token's SyntaxKind - * @returns {string} the token applicable token as a string - */ -function getTextForTokenKind(kind) { - return TOKEN_TO_TEXT[kind]; -} - -/** - * Returns true if the given TSNode is a valid ESTree class member - * @param {TSNode} node TypeScript AST node - * @returns {boolean} is valid ESTree class member - */ -function isESTreeClassMember(node) { - return node.kind !== SyntaxKind.SemicolonClassElement; -} - -/** - * Checks if a TSNode has a modifier - * @param {SyntaxKind} modifierKind TypeScript SyntaxKind modifier - * @param {TSNode} node TypeScript AST node - * @returns {boolean} has the modifier specified - */ -function hasModifier(modifierKind, node) { - return !!node.modifiers && !!node.modifiers.length && node.modifiers.some(modifier => modifier.kind === modifierKind); -} - -/** - * Returns true if the given TSToken is a comma - * @param {TSToken} token the TypeScript token - * @returns {boolean} is comma - */ -function isComma(token) { - return token.kind === SyntaxKind.CommaToken; -} - -/** - * Returns true if the given TSNode is a comment - * @param {TSNode} node the TypeScript node - * @returns {boolean} is commment - */ -function isComment(node) { - return node.kind === SyntaxKind.SingleLineCommentTrivia || node.kind === SyntaxKind.MultiLineCommentTrivia; -} - -/** - * Returns true if the given TSNode is a JSDoc comment - * @param {TSNode} node the TypeScript node - * @returns {boolean} is JSDoc comment - */ -function isJSDocComment(node) { - return node.kind === SyntaxKind.JSDocComment; -} - -/** - * Returns the binary expression type of the given TSToken - * @param {TSToken} operator the operator token - * @returns {string} the binary expression type - */ -function getBinaryExpressionType(operator) { - if (isAssignmentOperator(operator)) { - return "AssignmentExpression"; - } else if (isLogicalOperator(operator)) { - return "LogicalExpression"; - } - return "BinaryExpression"; -} - -/** - * Returns line and column data for the given start and end positions, - * for the given AST - * @param {Object} start start data - * @param {Object} end end data - * @param {Object} ast the AST object - * @returns {Object} the loc data - */ -function getLocFor(start, end, ast) { - const startLoc = ast.getLineAndCharacterOfPosition(start), - endLoc = ast.getLineAndCharacterOfPosition(end); - - return { - start: { - line: startLoc.line + 1, - column: startLoc.character - }, - end: { - line: endLoc.line + 1, - column: endLoc.character - } - }; -} - -/** - * Returns line and column data for the given ESTreeNode or ESTreeToken, - * for the given AST - * @param {ESTreeToken|ESTreeNode} nodeOrToken the ESTreeNode or ESTreeToken - * @param {Object} ast the AST object - * @returns {Object} the loc data - */ -function getLoc(nodeOrToken, ast) { - return getLocFor(nodeOrToken.getStart(), nodeOrToken.end, ast); -} - -/** - * Returns true if a given TSNode is a token - * @param {TSNode} node the TSNode - * @returns {boolean} is a token - */ -function isToken(node) { - return node.kind >= SyntaxKind.FirstToken && node.kind <= SyntaxKind.LastToken; -} - -/** - * Returns true if a given TSNode is a JSX token - * @param {TSNode} node TSNode to be checked - * @returns {boolean} is a JSX token - */ -function isJSXToken(node) { - return ( - node.kind >= SyntaxKind.JsxElement && - node.kind <= SyntaxKind.JsxAttribute - ); -} - -/** - * Returns true if the given TSNode.kind value corresponds to a type keyword - * @param {number} kind TypeScript SyntaxKind - * @returns {boolean} is a type keyword - */ -function isTypeKeyword(kind) { - switch (kind) { - case SyntaxKind.AnyKeyword: - case SyntaxKind.BooleanKeyword: - case SyntaxKind.NeverKeyword: - case SyntaxKind.NumberKeyword: - case SyntaxKind.ObjectKeyword: - case SyntaxKind.StringKeyword: - case SyntaxKind.SymbolKeyword: - case SyntaxKind.VoidKeyword: - return true; - default: - return false; - } -} - -/** - * Returns the declaration kind of the given TSNode - * @param {TSNode} node TypeScript AST node - * @returns {string} declaration kind - */ -function getDeclarationKind(node) { - switch (node.kind) { - case SyntaxKind.TypeAliasDeclaration: - return "type"; - 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."; - } -} - -/** - * Gets a TSNode's accessibility level - * @param {TSNode} node The TSNode - * @returns {string | null} accessibility "public", "protected", "private", or null - */ -function getTSNodeAccessibility(node) { - const modifiers = node.modifiers; - if (!modifiers) { - return null; - } - for (let i = 0; i < modifiers.length; i++) { - const modifier = modifiers[i]; - switch (modifier.kind) { - case SyntaxKind.PublicKeyword: - return "public"; - case SyntaxKind.ProtectedKeyword: - return "protected"; - case SyntaxKind.PrivateKeyword: - return "private"; - default: - continue; - } - } - return null; -} - -/** - * Returns true if the given TSNode has the modifier flag set which corresponds - * to the static keyword. - * @param {TSNode} node The TSNode - * @returns {boolean} whether or not the static modifier flag is set - */ -function hasStaticModifierFlag(node) { - /** - * TODO: Remove dependency on private TypeScript method - */ - return Boolean(ts.getModifierFlags(node) & ts.ModifierFlags.Static); -} - -/** - * Finds the next token based on the previous one and its parent - * @param {TSToken} previousToken The previous TSToken - * @param {TSNode} parent The parent TSNode - * @returns {TSToken} the next TSToken - */ -function findNextToken(previousToken, parent) { - /** - * TODO: Remove dependency on private TypeScript method - */ - return ts.findNextToken(previousToken, parent); -} - -/** - * Find the first matching token based on the given predicate function. - * @param {TSToken} previousToken The previous TSToken - * @param {TSNode} parent The parent TSNode - * @param {Function} predicate The predicate function to apply to each checked token - * @returns {TSToken|undefined} a matching TSToken - */ -function findFirstMatchingToken(previousToken, parent, predicate) { - while (previousToken) { - if (predicate(previousToken)) { - return previousToken; - } - previousToken = findNextToken(previousToken, parent); - } - return undefined; -} - -/** - * Finds the first child TSNode which matches the given kind - * @param {TSNode} node The parent TSNode - * @param {number} kind The TSNode kind to match against - * @param {Object} sourceFile The full AST source file - * @returns {TSNode|undefined} a matching TSNode - */ -function findChildOfKind(node, kind, sourceFile) { - return findFirstMatchingChild(node, sourceFile, child => child.kind === kind); -} - -/** - * Find the first matching ancestor based on the given predicate function. - * @param {TSNode} node The current TSNode - * @param {Function} predicate The predicate function to apply to each checked ancestor - * @returns {TSNode|undefined} a matching parent TSNode - */ -function findFirstMatchingAncestor(node, predicate) { - while (node) { - if (predicate(node)) { - return node; - } - node = node.parent; - } - return undefined; -} - -/** - * Finds the first parent TSNode which mastches the given kind - * @param {TSNode} node The current TSNode - * @param {number} kind The TSNode kind to match against - * @returns {TSNode|undefined} a matching parent TSNode - */ -function findAncestorOfKind(node, kind) { - return findFirstMatchingAncestor(node, parent => parent.kind === kind); -} - - -/** - * Returns true if a given TSNode has a JSX token within its hierarchy - * @param {TSNode} node TSNode to be checked - * @returns {boolean} has JSX ancestor - */ -function hasJSXAncestor(node) { - return !!findFirstMatchingAncestor(node, isJSXToken); -} - -/** - * Unescape the text content of string literals, e.g. & -> & - * @param {string} text The escaped string literal text. - * @returns {string} The unescaped string literal text. - */ -function unescapeStringLiteralText(text) { - return unescape(text); -} - -/** - * Returns true if a given TSNode is a computed property - * @param {TSNode} node TSNode to be checked - * @returns {boolean} is Computed Property - */ -function isComputedProperty(node) { - return node.kind === SyntaxKind.ComputedPropertyName; -} - -/** - * Returns true if a given TSNode is optional (has QuestionToken) - * @param {TSNode} node TSNode to be checked - * @returns {boolean} is Optional - */ -function isOptional(node) { - return (node.questionToken) - ? (node.questionToken.kind === SyntaxKind.QuestionToken) : false; -} - -/** - * Returns true if the given TSNode is within the context of a "typeAnnotation", - * which effectively means - is it coming from its parent's `type` or `types` property - * @param {TSNode} node TSNode to be checked - * @returns {boolean} is within "typeAnnotation context" - */ -function isWithinTypeAnnotation(node) { - return node.parent.type === node || (node.parent.types && node.parent.types.indexOf(node) > -1); -} - -/** - * Fixes the exports of the given TSNode - * @param {TSNode} node the TSNode - * @param {Object} result result - * @param {Object} ast the AST - * @returns {TSNode} the TSNode with fixed exports - */ -function fixExports(node, result, ast) { - // check for exports - if (node.modifiers && node.modifiers[0].kind === SyntaxKind.ExportKeyword) { - const exportKeyword = node.modifiers[0], - nextModifier = node.modifiers[1], - lastModifier = node.modifiers[node.modifiers.length - 1], - declarationIsDefault = nextModifier && (nextModifier.kind === SyntaxKind.DefaultKeyword), - varToken = findNextToken(lastModifier, ast); - - result.range[0] = varToken.getStart(); - result.loc = getLocFor(result.range[0], result.range[1], ast); - - const declarationType = declarationIsDefault ? "ExportDefaultDeclaration" : "ExportNamedDeclaration"; - - const newResult = { - type: declarationType, - declaration: result, - range: [exportKeyword.getStart(), result.range[1]], - loc: getLocFor(exportKeyword.getStart(), result.range[1], ast) - }; - - if (!declarationIsDefault) { - newResult.specifiers = []; - newResult.source = null; - } - - return newResult; - - } - - return result; -} - -/** - * Returns the type of a given ESTreeToken - * @param {ESTreeToken} token the ESTreeToken - * @returns {string} the token type - */ -function getTokenType(token) { - // Need two checks for keywords since some are also identifiers - if (token.originalKeywordKind) { - - switch (token.originalKeywordKind) { - case SyntaxKind.NullKeyword: - return "Null"; - - case SyntaxKind.GetKeyword: - case SyntaxKind.SetKeyword: - case SyntaxKind.TypeKeyword: - case SyntaxKind.ModuleKeyword: - return "Identifier"; - - default: - return "Keyword"; - } - } - - if (token.kind >= SyntaxKind.FirstKeyword && token.kind <= SyntaxKind.LastFutureReservedWord) { - if (token.kind === SyntaxKind.FalseKeyword || token.kind === SyntaxKind.TrueKeyword) { - return "Boolean"; - } - - return "Keyword"; - } - - if (token.kind >= SyntaxKind.FirstPunctuation && token.kind <= SyntaxKind.LastBinaryOperator) { - return "Punctuator"; - } - - if (token.kind >= SyntaxKind.NoSubstitutionTemplateLiteral && token.kind <= SyntaxKind.TemplateTail) { - return "Template"; - } - - switch (token.kind) { - case SyntaxKind.NumericLiteral: - return "Numeric"; - - case SyntaxKind.JsxText: - return "JSXText"; - - case SyntaxKind.StringLiteral: - - // A TypeScript-StringLiteral token with a TypeScript-JsxAttribute or TypeScript-JsxElement parent, - // must actually be an ESTree-JSXText token - if (token.parent && (token.parent.kind === SyntaxKind.JsxAttribute || token.parent.kind === SyntaxKind.JsxElement)) { - return "JSXText"; - } - - return "String"; - - case SyntaxKind.RegularExpressionLiteral: - return "RegularExpression"; - - case SyntaxKind.Identifier: - case SyntaxKind.ConstructorKeyword: - case SyntaxKind.GetKeyword: - case SyntaxKind.SetKeyword: - - // falls through - default: - } - - // Some JSX tokens have to be determined based on their parent - if (token.parent) { - if (token.kind === SyntaxKind.Identifier && token.parent.kind === SyntaxKind.PropertyAccessExpression && hasJSXAncestor(token)) { - return "JSXIdentifier"; - } - - if (isJSXToken(token.parent)) { - if (token.kind === SyntaxKind.PropertyAccessExpression) { - return "JSXMemberExpression"; - } - - if (token.kind === SyntaxKind.Identifier) { - return "JSXIdentifier"; - } - } - } - - return "Identifier"; -} - -/** - * Extends and formats a given ESTreeToken, for a given AST - * @param {ESTreeToken} token the ESTreeToken - * @param {Object} ast the AST object - * @returns {ESTreeToken} the converted ESTreeToken - */ -function convertToken(token, ast) { - const start = (token.kind === SyntaxKind.JsxText) ? token.getFullStart() : token.getStart(), - end = token.getEnd(), - value = ast.text.slice(start, end), - newToken = { - type: getTokenType(token), - value, - range: [start, end], - loc: getLocFor(start, end, ast) - }; - - if (newToken.type === "RegularExpression") { - newToken.regex = { - pattern: value.slice(1, value.lastIndexOf("/")), - flags: value.slice(value.lastIndexOf("/") + 1) - }; - } - - return newToken; -} - -/** - * Converts all tokens for the given AST - * @param {Object} ast the AST object - * @returns {ESTreeToken[]} the converted ESTreeTokens - */ -function convertTokens(ast) { - const result = []; - /** - * @param {TSNode} node the TSNode - * @returns {undefined} - */ - function walk(node) { - // TypeScript generates tokens for types in JSDoc blocks. Comment tokens - // and their children should not be walked or added to the resulting tokens list. - if (isComment(node) || isJSDocComment(node)) { - return; - } - - if (isToken(node) && node.kind !== SyntaxKind.EndOfFileToken) { - const converted = convertToken(node, ast); - - if (converted) { - result.push(converted); - } - } else { - node.getChildren().forEach(walk); - } - } - walk(ast); - return result; -} - -/** - * Get container token node between range - * @param {Object} ast the AST object - * @param {int} start The index at which the comment starts. - * @param {int} end The index at which the comment ends. - * @returns {TSToken} typescript container token - * @private - */ -function getNodeContainer(ast, start, end) { - let container = null; - - /** - * @param {TSNode} node the TSNode - * @returns {undefined} - */ - function walk(node) { - const nodeStart = node.pos; - const nodeEnd = node.end; - - if (start >= nodeStart && end <= nodeEnd) { - if (isToken(node)) { - container = node; - } else { - node.getChildren().forEach(walk); - } - } - } - walk(ast); - - return container; -} - -/** - * @param {Object} ast the AST object - * @param {int} start the index at which the error starts - * @param {string} message the error message - * @returns {Object} converted error object - */ -function createError(ast, start, message) { - const loc = ast.getLineAndCharacterOfPosition(start); - return { - index: start, - lineNumber: loc.line + 1, - column: loc.character, - message - }; -} diff --git a/package.json b/package.json index 0a2d52a..367189c 100644 --- a/package.json +++ b/package.json @@ -18,15 +18,11 @@ }, "license": "BSD-2-Clause", "devDependencies": { - "babel-code-frame": "6.26.0", - "babylon": "7.0.0-beta.39", "eslint": "4.19.1", "eslint-config-eslint": "4.0.0", "eslint-plugin-node": "6.0.1", "eslint-release": "0.11.1", - "glob": "7.1.2", "jest": "23.1.0", - "lodash.isplainobject": "4.0.6", "npm-license": "0.3.3", "shelljs": "0.8.2", "shelljs-nodecli": "0.1.1", @@ -42,9 +38,8 @@ "eslint" ], "scripts": { - "test": "node Makefile.js test && npm run ast-alignment-tests", + "test": "node Makefile.js test", "jest": "jest", - "ast-alignment-tests": "jest --config=./tests/ast-alignment/jest.config.js", "integration-tests": "docker-compose -f tests/integration/docker-compose.yml up", "kill-integration-test-containers": "docker-compose -f tests/integration/docker-compose.yml down -v --rmi local", "lint": "node Makefile.js lint", @@ -55,8 +50,7 @@ "betarelease": "eslint-prerelease beta" }, "dependencies": { - "lodash.unescape": "4.0.1", - "semver": "5.5.0" + "typescript-estree": "1.0.0" }, "peerDependencies": { "typescript": "*" diff --git a/parser.js b/parser.js index da4e0c5..8399020 100644 --- a/parser.js +++ b/parser.js @@ -8,176 +8,11 @@ "use strict"; -const astNodeTypes = require("./lib/ast-node-types"), - ts = require("typescript"), - convert = require("./lib/ast-converter"), - semver = require("semver"); - -const SUPPORTED_TYPESCRIPT_VERSIONS = require("./package.json").devDependencies.typescript; -const ACTIVE_TYPESCRIPT_VERSION = ts.version; -const isRunningSupportedTypeScriptVersion = semver.satisfies(ACTIVE_TYPESCRIPT_VERSION, SUPPORTED_TYPESCRIPT_VERSIONS); - -let extra; -let warnedAboutTSVersion = false; - -/** - * Resets the extra config object - * @returns {void} - */ -function resetExtra() { - extra = { - tokens: null, - range: false, - loc: false, - comment: false, - comments: [], - tolerant: false, - errors: [], - strict: false, - ecmaFeatures: {}, - useJSXTextNode: false, - log: console.log // eslint-disable-line no-console - }; -} - -//------------------------------------------------------------------------------ -// Parser -//------------------------------------------------------------------------------ - -/** - * Parses the given source code to produce a valid AST - * @param {mixed} code TypeScript code - * @param {Object} options configuration object for the parser - * @param {Object} additionalParsingContext additional internal configuration - * @returns {Object} the AST - */ -function generateAST(code, options, additionalParsingContext) { - additionalParsingContext = additionalParsingContext || {}; - - const toString = String; - - if (typeof code !== "string" && !(code instanceof String)) { - code = toString(code); - } - - resetExtra(); - - if (typeof options !== "undefined") { - extra.range = (typeof options.range === "boolean") && options.range; - extra.loc = (typeof options.loc === "boolean") && options.loc; - - if (extra.loc && options.source !== null && options.source !== undefined) { - extra.source = toString(options.source); - } - - if (typeof options.tokens === "boolean" && options.tokens) { - extra.tokens = []; - } - if (typeof options.comment === "boolean" && options.comment) { - extra.comment = true; - extra.comments = []; - } - if (typeof options.tolerant === "boolean" && options.tolerant) { - extra.errors = []; - } - - if (options.ecmaFeatures && typeof options.ecmaFeatures === "object") { - // pass through jsx option - extra.ecmaFeatures.jsx = options.ecmaFeatures.jsx; - } - - /** - * Allow the user to cause the parser to error if it encounters an unknown AST Node Type - * (used in testing). - */ - if (options.errorOnUnknownASTType) { - extra.errorOnUnknownASTType = true; - } - - if (typeof options.useJSXTextNode === "boolean" && options.useJSXTextNode) { - extra.useJSXTextNode = true; - } - - /** - * Allow the user to override the function used for logging - */ - if (typeof options.loggerFn === "function") { - extra.log = options.loggerFn; - } else if (options.loggerFn === false) { - extra.log = Function.prototype; - } - - /** - * Provide the context as to whether or not we are parsing for ESLint, - * specifically - */ - if (additionalParsingContext.isParseForESLint) { - extra.parseForESLint = true; - } - } - - if (!isRunningSupportedTypeScriptVersion && !warnedAboutTSVersion) { - const border = "============="; - const versionWarning = [ - border, - "WARNING: You are currently running a version of TypeScript which is not officially supported by typescript-eslint-parser.", - "You may find that it works just fine, or you may not.", - `SUPPORTED TYPESCRIPT VERSIONS: ${SUPPORTED_TYPESCRIPT_VERSIONS}`, - `YOUR TYPESCRIPT VERSION: ${ACTIVE_TYPESCRIPT_VERSION}`, - "Please only submit bug reports when using the officially supported version.", - border - ]; - extra.log(versionWarning.join("\n\n")); - warnedAboutTSVersion = true; - } - - // Even if jsx option is set in typescript compiler, filename still has to - // contain .tsx file extension - const FILENAME = (extra.ecmaFeatures.jsx) ? "eslint.tsx" : "eslint.ts"; - - const compilerHost = { - fileExists() { - return true; - }, - getCanonicalFileName() { - return FILENAME; - }, - getCurrentDirectory() { - return ""; - }, - getDefaultLibFileName() { - return "lib.d.ts"; - }, - - // TODO: Support Windows CRLF - getNewLine() { - return "\n"; - }, - getSourceFile(filename) { - return ts.createSourceFile(filename, code, ts.ScriptTarget.Latest, true); - }, - readFile() { - return null; - }, - useCaseSensitiveFileNames() { - return true; - }, - writeFile() { - return null; - } - }; - - const program = ts.createProgram([FILENAME], { - noResolve: true, - target: ts.ScriptTarget.Latest, - jsx: extra.ecmaFeatures.jsx ? "preserve" : undefined - }, compilerHost); - - const ast = program.getSourceFile(FILENAME); - - extra.code = code; - return convert(ast, extra); -} +const parse = require("typescript-estree").parse; +const astNodeTypes = require("typescript-estree").AST_NODE_TYPES; +/* eslint-disable */ +const traverser = require("eslint/lib/util/traverser"); +/* eslint-enable */ //------------------------------------------------------------------------------ // Public @@ -185,12 +20,17 @@ function generateAST(code, options, additionalParsingContext) { exports.version = require("./package.json").version; -exports.parse = function parse(code, options) { - return generateAST(code, options, { isParseForESLint: false }); -}; - exports.parseForESLint = function parseForESLint(code, options) { - const ast = generateAST(code, options, { isParseForESLint: true }); + const ast = parse(code, options); + traverser.traverse(ast, { + enter: node => { + if (node.type === "DeclareFunction" || node.type === "FunctionExpression" || node.type === "FunctionDeclaration") { + if (!node.body) { + node.type = `TSEmptyBody${node.type}`; + } + } + } + }); return { ast }; }; diff --git a/tests/ast-alignment/.eslintrc.yml b/tests/ast-alignment/.eslintrc.yml deleted file mode 100644 index e19b2cf..0000000 --- a/tests/ast-alignment/.eslintrc.yml +++ /dev/null @@ -1,2 +0,0 @@ -env: - jest: true diff --git a/tests/ast-alignment/fixtures-to-test.js b/tests/ast-alignment/fixtures-to-test.js deleted file mode 100644 index 338dd15..0000000 --- a/tests/ast-alignment/fixtures-to-test.js +++ /dev/null @@ -1,523 +0,0 @@ -"use strict"; - -const path = require("path"); -const glob = require("glob"); - -/** - * JSX fixtures which have known issues for typescript-eslint-parser - */ -const jsxFilesWithKnownIssues = require("../jsx-known-issues").map(f => f.replace("jsx/", "")); - -/** - * Current random error difference on jsx/invalid-no-tag-name.src.js - * TSEP - SyntaxError - * Babylon - RangeError - * - * Reported here: https://github.com/babel/babylon/issues/674 - */ -jsxFilesWithKnownIssues.push("invalid-no-tag-name"); - -/** - * Custom constructs/concepts used in this file: - * - * type Pattern = string; // used for node-glob matching - * - * interface FixturePatternConfig { - * pattern: Pattern, - * config?: { - * babylonParserOptions: any, - * typeScriptESLintParserOptions: any - * } - * } - */ - -/** - * Globally track which fixtures need to be parsed with sourceType: "module" - * so that they can be added with the correct FixturePatternConfig - */ -let fixturesRequiringSourceTypeModule = []; - -/** - * 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 {Object?} 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, config) { - if (!fixturesSubPath) { - return ""; - } - config = config || {}; - config.ignore = config.ignore || []; - config.fileType = config.fileType || "js"; - config.parseWithSourceTypeModule = config.parseWithSourceTypeModule || []; - /** - * The TypeScript compiler gives us the "externalModuleIndicator" to allow typescript-eslint-parser do dynamically detect the "sourceType". - * Babylon does not have an equivalent feature (although perhaps it might come in the future https://github.com/babel/babylon/issues/440), - * so we have to specify the "sourceType" we want to use. - * - * By default we have configured babylon to use "script", but for any fixtures specified in the parseWithSourceTypeModule array we need "module". - * - * 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.parseWithSourceTypeModule.length) { - config.ignore = [].concat(config.ignore, config.parseWithSourceTypeModule); - fixturesRequiringSourceTypeModule = [].concat(fixturesRequiringSourceTypeModule, config.parseWithSourceTypeModule.map( - fixture => ({ - // It needs to be the full path from within fixtures/ for the pattern - pattern: `${fixturesSubPath}/${fixture}.src.${config.fileType}`, - config: { babylonParserOptions: { sourceType: "module" } } - }) - )); - } - return { - pattern: `${fixturesSubPath}/!(${config.ignore.join("|")}).src.${config.fileType}` - }; -} - -/** - * An array of FixturePatternConfigs - */ -let fixturePatternConfigsToTest = [ - createFixturePatternConfigFor("basics"), - - createFixturePatternConfigFor("comments", { - ignore: [ - "export-default-anonymous-class", // needs to be parsed with `sourceType: "module"` - /** - * Template strings seem to also be affected by the difference in opinion between different parsers in: - * https://github.com/babel/babylon/issues/673 - */ - "no-comment-template", // Purely AST diffs - "template-string-block" // Purely AST diffs - ] - }), - - createFixturePatternConfigFor("ecma-features/templateStrings", { - ignore: [ - "**/*" - ] - }), - - createFixturePatternConfigFor("ecma-features/experimentalObjectRestSpread", { - ignore: [ - /** - * Trailing comma is not permitted after a "RestElement" in Babylon - */ - "invalid-rest-trailing-comma" - ] - }), - - createFixturePatternConfigFor("ecma-features/arrowFunctions", { - ignore: [ - /** - * Expected babylon 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-eslint-parser - * does not actually error on them and will produce an AST. - */ - "error-dup-params", // babylon parse errors - "error-dup-params", // babylon parse errors - "error-strict-dup-params", // babylon parse errors - "error-strict-octal", // babylon parse errors - "error-two-lines" // babylon parse errors - ] - }), - - createFixturePatternConfigFor("ecma-features/binaryLiterals"), - createFixturePatternConfigFor("ecma-features/blockBindings"), - - createFixturePatternConfigFor("ecma-features/classes", { - ignore: [ - /** - * super() is being used outside of constructor. Other parsers (e.g. espree, acorn) do not error on this. - */ - "class-one-method-super", // babylon parse errors - /** - * Expected babylon 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-eslint-parser - * does not actually error on them and will produce an AST. - */ - "invalid-class-declaration", // babylon parse errors - "invalid-class-setter-declaration" // babylon parse errors - ] - }), - - createFixturePatternConfigFor("ecma-features/defaultParams"), - - createFixturePatternConfigFor("ecma-features/destructuring", { - ignore: [ - /** - * Expected babylon 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-eslint-parser - * does not actually error on them and will produce an AST. - */ - "invalid-defaults-object-assign" // babylon parse errors - ] - }), - - createFixturePatternConfigFor("ecma-features/destructuring-and-arrowFunctions"), - createFixturePatternConfigFor("ecma-features/destructuring-and-blockBindings"), - createFixturePatternConfigFor("ecma-features/destructuring-and-defaultParams"), - createFixturePatternConfigFor("ecma-features/destructuring-and-forOf"), - - createFixturePatternConfigFor("ecma-features/destructuring-and-spread", { - ignore: [ - /** - * Expected babylon 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-eslint-parser - * does not actually error on them and will produce an AST. - */ - "error-complex-destructured-spread-first" // babylon parse errors - ] - }), - - createFixturePatternConfigFor("ecma-features/experimentalAsyncIteration"), - createFixturePatternConfigFor("ecma-features/experimentalDynamicImport"), - createFixturePatternConfigFor("ecma-features/exponentiationOperators"), - - createFixturePatternConfigFor("ecma-features/forOf", { - ignore: [ - /** - * TypeScript, espree and acorn parse this fine - esprima, flow and babylon do not... - */ - "for-of-with-function-initializer" // babylon parse errors - ] - }), - - createFixturePatternConfigFor("ecma-features/generators"), - createFixturePatternConfigFor("ecma-features/globalReturn"), - - createFixturePatternConfigFor("ecma-features/modules", { - ignore: [ - /** - * TypeScript, flow and babylon parse this fine - esprima, espree and acorn do not... - */ - "invalid-export-default", // typescript-eslint-parser parse errors - /** - * Expected babylon 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-eslint-parser - * does not actually error on them and will produce an AST. - */ - "invalid-export-named-default", // babylon parse errors - "invalid-import-default-module-specifier", // babylon parse errors - "invalid-import-module-specifier", // babylon parse errors - /** - * Deleting local variable in strict mode - */ - "error-delete", // babylon parse errors - /** - * 'with' in strict mode - */ - "error-strict" // babylon parse errors - ], - parseWithSourceTypeModule: [ - "export-default-array", - "export-default-class", - "export-default-expression", - "export-default-function", - "export-default-named-class", - "export-default-named-function", - "export-default-number", - "export-default-object", - "export-default-value", - "export-from-batch", - "export-from-default", - "export-from-named-as-default", - "export-from-named-as-specifier", - "export-from-named-as-specifiers", - "export-from-specifier", - "export-from-specifiers", - "export-function", - "export-named-as-default", - "export-named-as-specifier", - "export-named-as-specifiers", - "export-named-class", - "export-named-empty", - "export-named-specifier", - "export-named-specifiers-comma", - "export-named-specifiers", - "export-var-anonymous-function", - "export-var-number", - "export-var", - "import-default-and-named-specifiers", - "import-default-and-namespace-specifiers", - "import-default-as", - "import-default", - "import-jquery", - "import-module", - "import-named-as-specifier", - "import-named-as-specifiers", - "import-named-empty", - "import-named-specifier", - "import-named-specifiers-comma", - "import-named-specifiers", - "import-namespace-specifier", - "import-null-as-nil", - "invalid-await", - "invalid-class" - ] - }), - - createFixturePatternConfigFor("ecma-features/newTarget", { - ignore: [ - /** - * Expected babylon 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-eslint-parser - * does not actually error on them and will produce an AST. - */ - "invalid-new-target", // babylon parse errors - "invalid-unknown-property" // babylon parse errors - ] - }), - - createFixturePatternConfigFor("ecma-features/objectLiteralComputedProperties"), - - createFixturePatternConfigFor("ecma-features/objectLiteralDuplicateProperties", { - ignore: [ - /** - * Expected babylon 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-eslint-parser - * does not actually error on them and will produce an AST. - */ - "error-proto-property", // babylon parse errors - "error-proto-string-property" // babylon parse errors - ] - }), - - createFixturePatternConfigFor("ecma-features/objectLiteralShorthandMethods"), - createFixturePatternConfigFor("ecma-features/objectLiteralShorthandProperties"), - createFixturePatternConfigFor("ecma-features/octalLiterals"), - createFixturePatternConfigFor("ecma-features/regex"), - createFixturePatternConfigFor("ecma-features/regexUFlag"), - createFixturePatternConfigFor("ecma-features/regexYFlag"), - - createFixturePatternConfigFor("ecma-features/restParams", { - ignore: [ - /** - * Expected babylon 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-eslint-parser - * does not actually error on them and will produce an AST. - */ - "error-no-default", // babylon parse errors - "error-not-last" // babylon parse errors - ] - }), - - createFixturePatternConfigFor("ecma-features/spread"), - createFixturePatternConfigFor("ecma-features/unicodeCodePointEscapes"), - createFixturePatternConfigFor("jsx", { ignore: jsxFilesWithKnownIssues }), - createFixturePatternConfigFor("jsx-useJSXTextNode"), - - /* ================================================== */ - - /** - * TSX-SPECIFIC FILES - */ - - createFixturePatternConfigFor("tsx", { - fileType: "tsx", - ignore: [ - /** - * AST difference - */ - "react-typed-props", - /** - * currently babylon not supported - */ - "generic-jsx-element" - ] - }), - - /* ================================================== */ - - /** - * TYPESCRIPT-SPECIFIC FILES - */ - - createFixturePatternConfigFor("typescript/babylon-convergence", { fileType: "ts" }), - - createFixturePatternConfigFor("typescript/basics", { - fileType: "ts", - ignore: [ - /** - * Other babylon parse errors relating to invalid syntax. - */ - "abstract-class-with-abstract-constructor", // babylon parse errors - "class-with-export-parameter-properties", // babylon parse errors - "class-with-optional-methods", // babylon parse errors - "class-with-static-parameter-properties", // babylon parse errors - "interface-with-all-property-types", // babylon parse errors - "interface-with-construct-signature-with-parameter-accessibility", // babylon parse errors - "class-with-implements-and-extends", // babylon parse errors - "var-with-definite-assignment", // babylon parse errors - "class-with-definite-assignment", // babylon parse errors - /** - * typescript-eslint-parser erroring, but babylon not. - */ - "arrow-function-with-type-parameters", // typescript-eslint-parser parse errors - /** - * Babylon: ClassDeclaration + abstract: true - * tsep: TSAbstractClassDeclaration - */ - "abstract-class-with-abstract-properties", - /** - * Babylon: ClassProperty + abstract: true - * tsep: TSAbstractClassProperty - */ - "abstract-class-with-abstract-readonly-property", - /** - * Babylon: TSExpressionWithTypeArguments - * tsep: ClassImplements - */ - "class-with-implements-generic-multiple", - "class-with-implements-generic", - "class-with-implements", - "class-with-extends-and-implements", - /** - * Babylon: TSDeclareFunction + declare: true - * tsep: DeclareFunction - */ - "declare-function", - /** - * 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", - "type-alias-declaration-with-constrained-type-parameter", - "type-alias-declaration", - "type-alias-object-without-annotation", - "typed-this", - "export-type-function-declaration", - "export-type-class-declaration", - "abstract-interface", - "export-type-alias-declaration", - "unique-symbol", - "keyof-operator", - /** - * tsep bug - Program.body[0].expression.left.properties[0].value.right is currently showing up - * as `ArrayPattern`, babylon, acorn and espree say it should be `ArrayExpression` - * TODO: Fix this - */ - "destructuring-assignment", - /** - * Babylon bug for optional or abstract methods? - */ - "abstract-class-with-abstract-method", // babylon parse errors - "abstract-class-with-optional-method", // babylon parse errors - "declare-class-with-optional-method", // babylon parse errors - /** - * Awaiting feedback on Babylon issue https://github.com/babel/babylon/issues/700 - */ - "class-with-private-parameter-properties", - "class-with-protected-parameter-properties", - "class-with-public-parameter-properties", - "class-with-readonly-parameter-properties", - /** - * Not yet supported in Babylon https://github.com/babel/babel/issues/7749 - */ - "import-type", - "import-type-with-type-parameters-in-type-reference" - ], - parseWithSourceTypeModule: [ - "export-named-enum", - "export-assignment", - "export-default-class-with-generic", - "export-default-class-with-multiple-generics", - "export-named-class-with-generic", - "export-named-class-with-multiple-generics" - ] - }), - - 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: [ - /** - * currently babylon not supported - */ - "tagged-template-expression-type-arguments" - ] - }), - - createFixturePatternConfigFor("typescript/errorRecovery", { - fileType: "ts", - ignore: [ - /** - * AST difference - */ - "interface-empty-extends", - /** - * TypeScript-specific tests taken from "errorRecovery". Babylon is not being as forgiving as the TypeScript compiler here. - */ - "class-empty-extends-implements", // babylon parse errors - "class-empty-extends", // babylon parse errors - "decorator-on-enum-declaration", // babylon parse errors - "decorator-on-interface-declaration", // babylon parse errors - "interface-property-modifiers", // babylon parse errors - "enum-with-keywords" // babylon parse errors - ] - }), - - createFixturePatternConfigFor("typescript/namespaces-and-modules", { - fileType: "ts", - ignore: [ - /** - * Minor AST difference - */ - "nested-internal-module", - /** - * Babylon: TSDeclareFunction - * tsep: TSNamespaceFunctionDeclaration - */ - "declare-namespace-with-exported-function" - ] - }) -]; - -/** - * Add in all the fixtures which need to be parsed with sourceType: "module" - */ -fixturePatternConfigsToTest = [].concat(fixturePatternConfigsToTest, fixturesRequiringSourceTypeModule); - -/** - * interface Fixture { - * filename: string, - * config?: any - * } - */ -const fixturesToTest = []; -const fixturesDirPath = path.join(__dirname, "../fixtures"); - -/** - * Resolve the glob patterns into actual Fixture files that we can run assertions for... - */ -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, - config: fixturePatternConfig.config - }); - }); -}); - -module.exports = fixturesToTest; diff --git a/tests/ast-alignment/jest.config.js b/tests/ast-alignment/jest.config.js deleted file mode 100644 index 2a310d0..0000000 --- a/tests/ast-alignment/jest.config.js +++ /dev/null @@ -1,6 +0,0 @@ -"use strict"; - -module.exports = { - testEnvironment: "node", - testRegex: "spec\\.js$" -}; diff --git a/tests/ast-alignment/parse.js b/tests/ast-alignment/parse.js deleted file mode 100644 index 11614f6..0000000 --- a/tests/ast-alignment/parse.js +++ /dev/null @@ -1,96 +0,0 @@ -"use strict"; - -const codeFrame = require("babel-code-frame"); -const parseUtils = require("./utils"); - -function createError(message, line, column) { // eslint-disable-line - // Construct an error similar to the ones thrown by Babylon. - const error = new SyntaxError(`${message} (${line}:${column})`); - error.loc = { - line, - column - }; - return error; -} - -function parseWithBabylonPluginTypescript(text, parserOptions) { // eslint-disable-line - parserOptions = parserOptions || {}; - const babylon = require("babylon"); - return babylon.parse(text, Object.assign({ - sourceType: "script", - allowImportExportEverywhere: true, - allowReturnOutsideFunction: true, - ranges: true, - plugins: [ - "jsx", - "typescript", - "objectRestSpread", - "decorators", - "classProperties", - "asyncGenerators", - "dynamicImport", - "estree" - ] - }, parserOptions)); -} - -function parseWithTypeScriptESLintParser(text, parserOptions) { // eslint-disable-line - parserOptions = parserOptions || {}; - const parser = require("../../parser"); - try { - return parser.parse(text, Object.assign({ - loc: true, - range: true, - tokens: false, - comment: false, - useJSXTextNode: true, - errorOnUnknownASTType: true, - ecmaFeatures: { - jsx: true - } - }, parserOptions)); - } catch (e) { - throw createError( - e.message, - e.lineNumber, - e.column - ); - } -} - -module.exports = function parse(text, opts) { - - /** - * Always return a consistent interface, there will be times when we expect both - * parsers to fail to parse the invalid source. - */ - const result = { - parseError: null, - ast: null - }; - - try { - switch (opts.parser) { - case "typescript-eslint-parser": - result.ast = parseUtils.normalizeNodeTypes(parseWithTypeScriptESLintParser(text, opts.typeScriptESLintParserOptions)); - break; - case "babylon-plugin-typescript": - result.ast = parseUtils.normalizeNodeTypes(parseWithBabylonPluginTypescript(text, opts.babylonParserOptions)); - break; - default: - throw new Error("Please provide a valid parser: either \"typescript-eslint-parser\" or \"babylon-plugin-typescript\""); - } - } catch (error) { - const loc = error.loc; - if (loc) { - error.codeFrame = codeFrame(text, loc.line, loc.column + 1, { - highlightCode: true - }); - error.message += `\n${error.codeFrame}`; - } - result.parseError = error; - } - - return result; - -}; diff --git a/tests/ast-alignment/spec.js b/tests/ast-alignment/spec.js deleted file mode 100644 index 3c9114a..0000000 --- a/tests/ast-alignment/spec.js +++ /dev/null @@ -1,80 +0,0 @@ -"use strict"; - -const fs = require("fs"); - -const parse = require("./parse"); -const parseUtils = require("./utils"); -const fixturesToTest = require("./fixtures-to-test"); - -fixturesToTest.forEach(fixture => { - - const filename = fixture.filename; - const source = fs.readFileSync(filename, "utf8").replace(/\r\n/g, "\n"); - - /** - * Parse with typescript-eslint-parser - */ - const typeScriptESLintParserResult = parse(source, { - parser: "typescript-eslint-parser", - typeScriptESLintParserOptions: (fixture.config && fixture.config.typeScriptESLintParserOptions) ? fixture.config.typeScriptESLintParserOptions : null - }); - - /** - * Parse the source with babylon typescript-plugin - */ - const babylonTypeScriptPluginResult = parse(source, { - parser: "babylon-plugin-typescript", - babylonParserOptions: (fixture.config && fixture.config.babylonParserOptions) ? fixture.config.babylonParserOptions : null - }); - - /** - * If babylon fails to parse the source, ensure that typescript-eslint-parser has the same fundamental issue - */ - if (babylonTypeScriptPluginResult.parseError) { - /** - * FAIL: babylon errored but typescript-eslint-parser did not - */ - if (!typeScriptESLintParserResult.parseError) { - test(`TEST FAIL [BABYLON ERRORED, BUT TSEP DID NOT] - ${filename}`, () => { - expect(typeScriptESLintParserResult.parseError).toEqual(babylonTypeScriptPluginResult.parseError); - }); - return; - } - /** - * Both parsers errored - this is OK as long as the errors are of the same "type" - */ - test(`[Both parsers error as expected] - ${filename}`, () => { - expect(babylonTypeScriptPluginResult.parseError.name).toEqual(typeScriptESLintParserResult.parseError.name); - }); - return; - } - - /** - * FAIL: typescript-eslint-parser errored but babylon did not - */ - if (typeScriptESLintParserResult.parseError) { - test(`TEST FAIL [TSEP ERRORED, BUT BABYLON DID NOT] - ${filename}`, () => { - expect(babylonTypeScriptPluginResult.parseError).toEqual(typeScriptESLintParserResult.parseError); - }); - return; - } - - /** - * No errors, assert the two ASTs match - */ - test(`${filename}`, () => { - expect(babylonTypeScriptPluginResult.ast).toBeTruthy(); - expect(typeScriptESLintParserResult.ast).toBeTruthy(); - /** - * Perform some extra formatting steps on the babylon AST before comparing - */ - expect( - parseUtils.removeLocationDataFromProgramNode( - parseUtils.preprocessBabylonAST(babylonTypeScriptPluginResult.ast) - ) - ).toEqual( - parseUtils.removeLocationDataFromProgramNode(typeScriptESLintParserResult.ast) - ); - }); - -}); diff --git a/tests/ast-alignment/utils.js b/tests/ast-alignment/utils.js deleted file mode 100644 index 22cca72..0000000 --- a/tests/ast-alignment/utils.js +++ /dev/null @@ -1,154 +0,0 @@ -"use strict"; - -const isPlainObject = require("lodash.isplainobject"); - -/** - * By default, pretty-format (within Jest matchers) retains the names/types of nodes from the babylon AST, - * quick and dirty way to avoid that is to JSON.stringify and then JSON.parser the - * ASTs before comparing them with pretty-format - * - * @param {Object} ast raw AST - * @returns {Object} normalized AST - */ -function normalizeNodeTypes(ast) { - return JSON.parse(JSON.stringify(ast)); -} - -/** - * Removes the given keys from the given AST object recursively - * @param {Object} obj A JavaScript object to remove keys from - * @param {Object[]} keysToOmit Names and predicate functions use to determine what keys to omit from the final object - * @returns {Object} formatted object - */ -function omitDeep(obj, keysToOmit) { - keysToOmit = keysToOmit || []; - function shouldOmit(keyName, val) { // eslint-disable-line - if (!keysToOmit || !keysToOmit.length) { - return false; - } - for (const keyConfig of keysToOmit) { - if (keyConfig.key !== keyName) { - continue; - } - return keyConfig.predicate(val); - } - return false; - } - - for (const key in obj) { - if (!obj.hasOwnProperty(key)) { - continue; - } - const val = obj[key]; - if (isPlainObject(val)) { - if (shouldOmit(key, val)) { - delete obj[key]; - // re-run with the same arguments - // in case the object has multiple keys to omit - return omitDeep(obj, keysToOmit); - } - omitDeep(val, keysToOmit); - } else if (Array.isArray(val)) { - if (shouldOmit(key, val)) { - delete obj[key]; - // re-run with the same arguments - // in case the object has multiple keys to omit - return omitDeep(obj, keysToOmit); - } - for (const i of val) { - omitDeep(i, keysToOmit); - } - } else if (shouldOmit(key, val)) { - delete obj[key]; - // re-run with the same arguments - // in case the object has multiple keys to omit - return omitDeep(obj, keysToOmit); - } - } - return obj; -} - -/* eslint-disable */ -/** - * Common predicates for Babylon AST preprocessing - */ -const always = () => true; -const ifNumber = (val) => typeof val === "number"; -/* eslint-enable */ - -/** - * - Babylon wraps the "Program" node in an extra "File" node, normalize this for simplicity for now... - * - Remove "start" and "end" values from Babylon nodes to reduce unimportant noise in diffs ("loc" data will still be in - * each final AST and compared). - * - * @param {Object} ast raw babylon AST - * @returns {Object} processed babylon AST - */ -function preprocessBabylonAST(ast) { - return omitDeep(ast.program, [ - { - key: "start", - // only remove the "start" number (not the "start" object within loc) - predicate: ifNumber - }, - { - key: "end", - // only remove the "end" number (not the "end" object within loc) - predicate: ifNumber - }, - { - key: "identifierName", - predicate: always - }, - { - key: "extra", - predicate: always - }, - { - key: "directives", - predicate: always - }, - { - key: "directive", - predicate: always - }, - { - key: "innerComments", - predicate: always - }, - { - key: "leadingComments", - predicate: always - }, - { - key: "trailingComments", - predicate: always - }, - { - key: "guardedHandlers", - predicate: always - } - ]); -} - -/** - * There is currently a really awkward difference in location data for Program nodes - * between different parsers in the ecosystem. Hack around this by removing the data - * before comparing the ASTs. - * - * See: https://github.com/babel/babylon/issues/673 - * - * @param {Object} ast the raw AST with a Program node at its top level - * @returns {Object} the ast with the location data removed from the Program node - */ -function removeLocationDataFromProgramNode(ast) { - delete ast.loc; - delete ast.range; - return ast; -} - -module.exports = { - normalizeNodeTypes, - preprocessBabylonAST, - removeLocationDataFromProgramNode -}; diff --git a/tests/lib/__snapshots__/parse.js.snap b/tests/lib/__snapshots__/parse.js.snap deleted file mode 100644 index ae6a9a3..0000000 --- a/tests/lib/__snapshots__/parse.js.snap +++ /dev/null @@ -1,190 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`parse() general output tokens, comments, locs, and ranges when called with those options 1`] = ` -Object { - "body": Array [ - Object { - "declarations": Array [ - Object { - "id": Object { - "loc": Object { - "end": Object { - "column": 7, - "line": 1, - }, - "start": Object { - "column": 4, - "line": 1, - }, - }, - "name": "foo", - "range": Array [ - 4, - 7, - ], - "type": "Identifier", - }, - "init": Object { - "loc": Object { - "end": Object { - "column": 13, - "line": 1, - }, - "start": Object { - "column": 10, - "line": 1, - }, - }, - "name": "bar", - "range": Array [ - 10, - 13, - ], - "type": "Identifier", - }, - "loc": Object { - "end": Object { - "column": 13, - "line": 1, - }, - "start": Object { - "column": 4, - "line": 1, - }, - }, - "range": Array [ - 4, - 13, - ], - "type": "VariableDeclarator", - }, - ], - "kind": "let", - "loc": Object { - "end": Object { - "column": 14, - "line": 1, - }, - "start": Object { - "column": 0, - "line": 1, - }, - }, - "range": Array [ - 0, - 14, - ], - "type": "VariableDeclaration", - }, - ], - "comments": Array [], - "loc": Object { - "end": Object { - "column": 14, - "line": 1, - }, - "start": Object { - "column": 0, - "line": 1, - }, - }, - "range": Array [ - 0, - 14, - ], - "sourceType": "script", - "tokens": Array [ - Object { - "loc": Object { - "end": Object { - "column": 3, - "line": 1, - }, - "start": Object { - "column": 0, - "line": 1, - }, - }, - "range": Array [ - 0, - 3, - ], - "type": "Keyword", - "value": "let", - }, - Object { - "loc": Object { - "end": Object { - "column": 7, - "line": 1, - }, - "start": Object { - "column": 4, - "line": 1, - }, - }, - "range": Array [ - 4, - 7, - ], - "type": "Identifier", - "value": "foo", - }, - Object { - "loc": Object { - "end": Object { - "column": 9, - "line": 1, - }, - "start": Object { - "column": 8, - "line": 1, - }, - }, - "range": Array [ - 8, - 9, - ], - "type": "Punctuator", - "value": "=", - }, - Object { - "loc": Object { - "end": Object { - "column": 13, - "line": 1, - }, - "start": Object { - "column": 10, - "line": 1, - }, - }, - "range": Array [ - 10, - 13, - ], - "type": "Identifier", - "value": "bar", - }, - Object { - "loc": Object { - "end": Object { - "column": 14, - "line": 1, - }, - "start": Object { - "column": 13, - "line": 1, - }, - }, - "range": Array [ - 13, - 14, - ], - "type": "Punctuator", - "value": ";", - }, - ], - "type": "Program", -} -`; diff --git a/tests/lib/parse.js b/tests/lib/parse.js deleted file mode 100644 index c844992..0000000 --- a/tests/lib/parse.js +++ /dev/null @@ -1,64 +0,0 @@ -/** - * @fileoverview Tests for tokenize(). - * @author Nicholas C. Zakas - * @author James Henry - * @copyright jQuery Foundation and other contributors, https://jquery.org/ - * MIT License - */ - -"use strict"; - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -const parser = require("../../parser"), - testUtils = require("../../tools/test-utils"); - -//------------------------------------------------------------------------------ -// Tests -//------------------------------------------------------------------------------ - -describe("parse()", () => { - - - describe("basic functionality", () => { - - it("should parse an empty string", () => { - expect(parser.parse("").body).toEqual([]); - expect(parser.parse("", {}).body).toEqual([]); - }); - - }); - - describe("modules", () => { - - it("should have correct column number when strict mode error occurs", () => { - try { - parser.parse("function fn(a, a) {\n}", { sourceType: "module" }); - } catch (err) { - expect(err.column).toEqual(16); - } - }); - - }); - - describe("general", () => { - - const code = "let foo = bar;"; - const config = { - ecmaFeatures: { - blockBindings: true - }, - comment: true, - tokens: true, - range: true, - loc: true - }; - - test("output tokens, comments, locs, and ranges when called with those options", testUtils.createSnapshotTestBlock(code, config)); - - }); - - -}); From 1a46ab9b9fc73567b19b05efb79ece7f3c9c51b4 Mon Sep 17 00:00:00 2001 From: James Henry Date: Tue, 25 Sep 2018 08:54:49 -0400 Subject: [PATCH 2/2] Address feedback --- README.md | 2 +- package.json | 2 +- parser.js | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 061a53e..60a3cbb 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ We run each test within its own docker container, and so each one has complete a > If you are going to submit an issue related to the usage of this parser with ESLint, please consider creating a failing integration test which clearly demonstrates the behavior. It's honestly super quick! -You just need to duplicate on of the existing test sub-directories found in `tests/integration/`, tweak the dependencies and ESLint config to match what you need, and add a new entry to the docker-compose.yml file which matches the format of the existing ones. +You just need to duplicate one of the existing test sub-directories found in `tests/integration/`, tweak the dependencies and ESLint config to match what you need, and add a new entry to the docker-compose.yml file which matches the format of the existing ones. Then run: diff --git a/package.json b/package.json index 367189c..a86fc47 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,6 @@ }, "license": "BSD-2-Clause", "devDependencies": { - "eslint": "4.19.1", "eslint-config-eslint": "4.0.0", "eslint-plugin-node": "6.0.1", "eslint-release": "0.11.1", @@ -50,6 +49,7 @@ "betarelease": "eslint-prerelease beta" }, "dependencies": { + "eslint": "4.19.1", "typescript-estree": "1.0.0" }, "peerDependencies": { diff --git a/parser.js b/parser.js index 8399020..9f12905 100644 --- a/parser.js +++ b/parser.js @@ -10,9 +10,7 @@ const parse = require("typescript-estree").parse; const astNodeTypes = require("typescript-estree").AST_NODE_TYPES; -/* eslint-disable */ const traverser = require("eslint/lib/util/traverser"); -/* eslint-enable */ //------------------------------------------------------------------------------ // Public