From fe89b4ddc2b933baa58b717d1f519dd142cd9490 Mon Sep 17 00:00:00 2001 From: James Henry Date: Sat, 6 May 2017 16:39:51 +0100 Subject: [PATCH] Chore: Refactor the codebase (fixes #220) --- .eslintrc.yml | 13 +- .travis.yml | 7 +- Makefile.js | 52 +- lib/ast-converter.js | 2132 +--------------------------------- lib/ast-node-types.js | 91 +- lib/convert.js | 1731 +++++++++++++++++++++++++++ lib/features.js | 94 -- lib/node-utils.js | 635 ++++++++++ lib/token-translator.js | 252 ---- package.json | 30 +- parser.js | 92 +- tests/lib/attach-comments.js | 33 +- tests/lib/basics.js | 38 +- tests/lib/ecma-features.js | 50 +- tests/lib/parse.js | 18 +- tests/lib/tester.js | 10 +- tests/lib/typescript.js | 38 +- 17 files changed, 2646 insertions(+), 2670 deletions(-) create mode 100644 lib/convert.js delete mode 100644 lib/features.js create mode 100644 lib/node-utils.js delete mode 100644 lib/token-translator.js diff --git a/.eslintrc.yml b/.eslintrc.yml index 49ee699..b706d16 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -1,6 +1,11 @@ root: true -env: - node: true - -extends: eslint +plugins: + - node +extends: + - "eslint" + - "plugin:node/recommended" +rules: + no-undefined: "off" + lines-around-comment: "off" + newline-after-var: "off" diff --git a/.travis.yml b/.travis.yml index accaab3..c7e7112 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,9 @@ language: node_js sudo: false node_js: - - "0.12" - - 4 - - 6 + - "4" + - "5" + - "6" + - "7" after_success: - npm run coveralls diff --git a/Makefile.js b/Makefile.js index 823c651..50d6cfa 100644 --- a/Makefile.js +++ b/Makefile.js @@ -15,14 +15,14 @@ require("shelljs/make"); -var checker = require("npm-license"), +const checker = require("npm-license"), nodeCLI = require("shelljs-nodecli"); //------------------------------------------------------------------------------ // Settings //------------------------------------------------------------------------------ -var OPEN_SOURCE_LICENSES = [ +const OPEN_SOURCE_LICENSES = [ /MIT/, /BSD/, /Apache/, /ISC/, /WTF/, /Public Domain/ ]; @@ -30,15 +30,15 @@ var OPEN_SOURCE_LICENSES = [ // Data //------------------------------------------------------------------------------ -var NODE_MODULES = "./node_modules/", +const NODE_MODULES = "./node_modules/", // Utilities - intentional extra space at the end of each string - MOCHA = NODE_MODULES + "mocha/bin/_mocha ", + MOCHA = `${NODE_MODULES}mocha/bin/_mocha `, // Files MAKEFILE = "./Makefile.js", /* eslint-disable no-use-before-define */ - JS_FILES = find("lib/").filter(fileType("js")).join(" ") + " parser.js", + JS_FILES = `${find("lib/").filter(fileType("js")).join(" ")} parser.js`, TEST_FILES = find("tests/lib/").filter(fileType("js")).join(" "); /* eslint-enable no-use-before-define */ @@ -67,7 +67,7 @@ target.all = function() { }; target.lint = function() { - var errors = 0, + let errors = 0, lastReturn; echo("Validating Makefile.js"); @@ -96,10 +96,8 @@ target.lint = function() { target.test = function() { target.lint(); - var errors = 0, - lastReturn; - - lastReturn = nodeCLI.exec("istanbul", "cover", MOCHA, "-- -c", TEST_FILES); + const lastReturn = nodeCLI.exec("istanbul", "cover", MOCHA, "-- -c", TEST_FILES); + let errors = 0; if (lastReturn.code !== 0) { errors++; @@ -122,42 +120,34 @@ target.checkLicenses = function() { /** * Returns true if the given dependency's licenses are all permissable for use in OSS - * @param {object} dependency object containing the name and licenses of the given dependency + * @param {Object} dependency object containing the name and licenses of the given dependency * @returns {boolean} is permissable dependency */ function isPermissible(dependency) { - var licenses = dependency.licenses; + const licenses = dependency.licenses; if (Array.isArray(licenses)) { - return licenses.some(function(license) { - return isPermissible({ - name: dependency.name, - licenses: license - }); - }); + return licenses.some(license => isPermissible({ + name: dependency.name, + licenses: license + })); } - return OPEN_SOURCE_LICENSES.some(function(license) { - return license.test(licenses); - }); + return OPEN_SOURCE_LICENSES.some(license => license.test(licenses)); } echo("Validating licenses"); checker.init({ start: __dirname - }, function(deps) { - var impermissible = Object.keys(deps).map(function(dependency) { - return { - name: dependency, - licenses: deps[dependency].licenses - }; - }).filter(function(dependency) { - return !isPermissible(dependency); - }); + }, deps => { + const impermissible = Object.keys(deps).map(dependency => ({ + name: dependency, + licenses: deps[dependency].licenses + })).filter(dependency => !isPermissible(dependency)); if (impermissible.length) { - impermissible.forEach(function(dependency) { + impermissible.forEach(dependency => { console.error("%s license for %s is impermissible.", dependency.licenses, dependency.name diff --git a/lib/ast-converter.js b/lib/ast-converter.js index 3d0fbaf..363eb84 100644 --- a/lib/ast-converter.js +++ b/lib/ast-converter.js @@ -11,298 +11,20 @@ // Requirements //------------------------------------------------------------------------------ -var ts = require("typescript"), - assign = require("object-assign"), - unescape = require("lodash.unescape"); +const convert = require("./convert"), + nodeUtils = require("./node-utils"); //------------------------------------------------------------------------------ // Private //------------------------------------------------------------------------------ -var SyntaxKind = ts.SyntaxKind; - -var 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 -]; - -var LOGICAL_OPERATORS = [ - SyntaxKind.BarBarToken, - SyntaxKind.AmpersandAmpersandToken -]; - -var 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.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.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"; - -/** - * 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(function(modifier) { - return 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 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 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"; - } else { - 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) { - var 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); - // var start = nodeOrToken.getStart(), - // startLoc = ast.getLineAndCharacterOfPosition(start), - // endLoc = ast.getLineAndCharacterOfPosition(nodeOrToken.end); - - // return { - // start: { - // line: startLoc.line + 1, - // column: startLoc.character - // }, - // end: { - // line: endLoc.line + 1, - // column: endLoc.character - // } - // }; -} - -/** - * 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) { - var exportKeyword = node.modifiers[0], - nextModifier = node.modifiers[1], - lastModifier = node.modifiers[node.modifiers.length - 1], - declarationIsDefault = nextModifier && (nextModifier.kind === SyntaxKind.DefaultKeyword), - varToken = ts.findNextToken(lastModifier, ast); - - result.range[0] = varToken.getStart(); - result.loc = getLocFor(result.range[0], result.range[1], ast); - - var declarationType = declarationIsDefault ? "ExportDefaultDeclaration" : "ExportNamedDeclaration"; - - /** - * Prefix exports from TypeScript namespaces with "TS" to distinguish - * them from ES2015 exports - */ - if (node.parent && node.parent.kind === SyntaxKind.ModuleBlock) { - declarationType = "TSNamespaceExportDeclaration"; - } - - var 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 true if a given TSNode is a token - * @param {TSNode} node the TSNode - * @returns {boolean} is a token - */ -function isToken(node) { - return node.kind >= ts.SyntaxKind.FirstToken && node.kind <= ts.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 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) { - while (node) { - if (isJSXToken(node)) { - return true; - } - node = node.parent; - } - return false; -} - - /** * Extends and formats a given error object * @param {Object} error the error object * @returns {Object} converted error object */ function convertError(error) { - - var loc = error.file.getLineAndCharacterOfPosition(error.start); - + const loc = error.file.getLineAndCharacterOfPosition(error.start); return { index: error.start, lineNumber: loc.line + 1, @@ -311,1855 +33,41 @@ function convertError(error) { }; } -/** - * 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) { - - var start = token.getStart(), - value = ast.text.slice(start, token.end), - newToken = { - type: getTokenType(token), - value: value, - start: start, - end: token.end, - range: [start, token.end], - loc: getLoc(token, 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) { - var result = []; - /** - * @param {TSNode} node the TSNode - * @returns {undefined} - */ - function walk(node) { - if (isToken(node) && node.kind !== ts.SyntaxKind.EndOfFileToken) { - var converted = convertToken(node, ast); - if (converted) { - result.push(converted); - } - } else { - node.getChildren().forEach(walk); - } - } - walk(ast); - return result; -} - //------------------------------------------------------------------------------ // Public //------------------------------------------------------------------------------ -module.exports = function(ast, extra) { +module.exports = (ast, extra) => { + /** + * The TypeScript compiler produced fundamental parse errors when parsing the + * source. + */ if (ast.parseDiagnostics.length) { throw convertError(ast.parseDiagnostics[0]); } /** - * Converts a TypeScript node into an ESTree node - * @param {TSNode} node the TSNode - * @param {TSNode} parent the parent TSNode - * @returns {ESTreeNode} the converted ESTreeNode + * Recursively convert the TypeScript AST into an ESTree-compatible AST */ - function convert(node, parent) { - - // exit early for null and undefined - if (!node) { - return null; - } - - var result = { - type: "", - range: [node.getStart(), node.end], - loc: 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() { - assign(result, { - type: SyntaxKind[node.kind] - }); - } - - /** - * 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(child, node); - } - - /** - * 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) { - var annotation = convertChild(child); - return { - type: "TypeAnnotation", - loc: annotation.loc, - range: annotation.range, - 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) { - var firstTypeArgument = typeArguments[0]; - var lastTypeArgument = typeArguments[typeArguments.length - 1]; - return { - type: "TypeParameterInstantiation", - range: [ - firstTypeArgument.pos - 1, - lastTypeArgument.end + 1 - ], - loc: getLocFor(firstTypeArgument.pos - 1, lastTypeArgument.end + 1, ast), - params: typeArguments.map(function(typeArgument) { - /** - * Have to manually calculate the start of the range, - * because TypeScript includes leading whitespace but Flow does not - */ - var typeArgumentStart = (typeArgument.typeName && typeArgument.typeName.text) - ? typeArgument.end - typeArgument.typeName.text.length - : typeArgument.pos; - return { - type: "GenericTypeAnnotation", - range: [ - typeArgumentStart, - typeArgument.end - ], - loc: getLocFor(typeArgumentStart, typeArgument.end, ast), - id: convertChild(typeArgument.typeName || typeArgument), - typeParameters: (typeArgument.typeArguments) - ? convertTypeArgumentsToTypeParameters(typeArgument.typeArguments) - : null - }; - }) - }; - } - - /** - * Gets a TSNode's accessibility level - * @param {TSNode} tsNode The TSNode - * @returns {string | null} accessibility "public", "protected", "private", or null - */ - function getTSNodeAccessibility(tsNode) { - var modifiers = tsNode.modifiers; - if (!modifiers) { - return null; - } - for (var i = 0; i < modifiers.length; i++) { - var 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 the declaration kind of the given TSNode - * @param {TSNode} tsNode TypeScript AST node - * @returns {string} declaration kind - */ - function getDeclarationKind(tsNode) { - var varDeclarationKind; - - switch (tsNode.kind) { - case SyntaxKind.TypeAliasDeclaration: - varDeclarationKind = "type"; - break; - case SyntaxKind.VariableDeclarationList: - if (ts.isLet(tsNode)) { - varDeclarationKind = "let"; - } else if (ts.isConst(tsNode)) { - varDeclarationKind = "const"; - } else { - varDeclarationKind = "var"; - } - break; - default: - throw "Unable to determine declaration kind."; - } - - return varDeclarationKind; - } - - /** - * Converts a TSNode's typeParameters array to a flow-like TypeParameterDeclaration node - * @param {TSNode[]} typeParameters TSNode typeParameters - * @returns {TypeParameterDeclaration} TypeParameterDeclaration node - */ - function convertTSTypeParametersToTypeParametersDeclaration(typeParameters) { - var firstTypeParameter = typeParameters[0]; - var lastTypeParameter = typeParameters[typeParameters.length - 1]; - - return { - type: "TypeParameterDeclaration", - range: [ - firstTypeParameter.pos - 1, - lastTypeParameter.end + 1 - ], - loc: getLocFor(firstTypeParameter.pos - 1, lastTypeParameter.end + 1, ast), - params: typeParameters.map(function(typeParameter) { - /** - * Have to manually calculate the start of the range, - * because TypeScript includes leading whitespace but Flow does not - */ - var typeParameterStart = (typeParameter.name && typeParameter.name.text) - ? typeParameter.name.end - typeParameter.name.text.length - : typeParameter.pos; - - var defaultParameter = typeParameter.default - ? convert(typeParameter.default, typeParameter) - : typeParameter.default; - - return { - type: "TypeParameter", - range: [ - typeParameterStart, - typeParameter.end - ], - loc: getLocFor(typeParameterStart, typeParameter.end, ast), - name: typeParameter.name.text, - constraint: (typeParameter.constraint) - ? convertTypeAnnotation(typeParameter.constraint) - : null, - 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) { - var id = convertChild(child.expression); - var classImplementsNode = { - type: "ClassImplements", - loc: id.loc, - range: id.range, - id: 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) { - var id = convertChild(child.expression); - var classImplementsNode = { - type: "TSInterfaceHeritage", - loc: id.loc, - range: id.range, - id: id - }; - if (child.typeArguments && child.typeArguments.length) { - classImplementsNode.typeParameters = convertTypeArgumentsToTypeParameters(child.typeArguments); - } - return classImplementsNode; - } - - /** - * For nodes that are copied directly from the TypeScript AST into - * ESTree mostly as-is. The only difference is the addition of a type - * property instead of a kind property. Recursively copies all children. - * @returns {void} - */ - function deeplyCopy() { - result.type = "TS" + SyntaxKind[node.kind]; - Object.keys(node).filter(function(key) { - return !(/^(?:kind|parent|pos|end|flags|modifierFlagsCache|jsDoc)$/.test(key)); - }).forEach(function(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 (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) { - var tagNameToken = convertToken(tagName, ast); - - if (tagNameToken.type === "JSXMemberExpression") { - - var 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) ? "JSXMemberExpression" : "JSXIdentifier"; - tagNameToken.property.type = "JSXIdentifier"; - - } else { - - tagNameToken.name = tagNameToken.value; - } - - delete tagNameToken.value; - - return tagNameToken; - } - - switch (node.kind) { - case SyntaxKind.SourceFile: - assign(result, { - type: "Program", - body: [], - sourceType: node.externalModuleIndicator ? "module" : "script" - }); - - // filter out unknown nodes for now - node.statements.forEach(function(statement) { - var convertedStatement = convertChild(statement); - if (convertedStatement) { - result.body.push(convertedStatement); - } - }); - - // fix end location - result.range[1] = node.endOfFileToken.pos; - result.loc = getLocFor(node.getStart(), result.range[1], ast); - break; - - case SyntaxKind.Block: - assign(result, { - type: "BlockStatement", - body: node.statements.map(convertChild) - }); - break; - - case SyntaxKind.Identifier: - assign(result, { - type: "Identifier", - name: ts.unescapeIdentifier(node.text) - }); - if (node.parent.questionToken && ( - SyntaxKind.Parameter === node.parent.kind || - SyntaxKind.PropertyDeclaration === node.parent.kind || - SyntaxKind.PropertySignature === node.parent.kind || - SyntaxKind.MethodDeclaration === node.parent.kind || - SyntaxKind.MethodSignature === node.parent.kind - )) { - result.optional = true; - } - break; - - case SyntaxKind.WithStatement: - assign(result, { - type: "WithStatement", - object: convertChild(node.expression), - body: convertChild(node.statement) - }); - break; - - // Control Flow - - case SyntaxKind.ReturnStatement: - assign(result, { - type: "ReturnStatement", - argument: convertChild(node.expression) - }); - break; - - case SyntaxKind.LabeledStatement: - assign(result, { - type: "LabeledStatement", - label: convertChild(node.label), - body: convertChild(node.statement) - }); - break; - - case SyntaxKind.BreakStatement: - case SyntaxKind.ContinueStatement: - assign(result, { - type: SyntaxKind[node.kind], - label: convertChild(node.label) - }); - break; - - // Choice - - case SyntaxKind.IfStatement: - assign(result, { - type: "IfStatement", - test: convertChild(node.expression), - consequent: convertChild(node.thenStatement), - alternate: convertChild(node.elseStatement) - }); - break; - - case SyntaxKind.SwitchStatement: - assign(result, { - type: "SwitchStatement", - discriminant: convertChild(node.expression), - cases: node.caseBlock.clauses.map(convertChild) - }); - break; - - case SyntaxKind.CaseClause: - case SyntaxKind.DefaultClause: - assign(result, { - type: "SwitchCase", - test: convertChild(node.expression), - consequent: node.statements.map(convertChild) - }); - break; - - // Exceptions - - case SyntaxKind.ThrowStatement: - assign(result, { - type: "ThrowStatement", - argument: convertChild(node.expression) - }); - break; - - case SyntaxKind.TryStatement: - assign(result, { - type: "TryStatement", - block: convert(node.tryBlock), - handler: convertChild(node.catchClause), - finalizer: convertChild(node.finallyBlock) - }); - break; - - case SyntaxKind.CatchClause: - assign(result, { - type: "CatchClause", - param: convertChild(node.variableDeclaration.name), - body: convertChild(node.block) - }); - break; - - - // Loops - - case SyntaxKind.WhileStatement: - assign(result, { - type: "WhileStatement", - test: convertChild(node.expression), - body: convertChild(node.statement) - }); - break; - - /** - * Unlike other parsers, TypeScript calls a "DoWhileStatement" - * a "DoStatement" - */ - case SyntaxKind.DoStatement: - assign(result, { - type: "DoWhileStatement", - test: convertChild(node.expression), - body: convertChild(node.statement) - }); - break; - - case SyntaxKind.ForStatement: - assign(result, { - type: "ForStatement", - init: convertChild(node.initializer), - test: convertChild(node.condition), - update: convertChild(node.incrementor), - body: convertChild(node.statement) - }); - break; - - case SyntaxKind.ForInStatement: - case SyntaxKind.ForOfStatement: - var isAwait = node.awaitModifier - && node.awaitModifier.kind === ts.SyntaxKind.AwaitKeyword; - - 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: - - var functionDeclarationType = "FunctionDeclaration"; - if (node.modifiers && node.modifiers.length) { - var isDeclareFunction = hasModifier(ts.SyntaxKind.DeclareKeyword, node); - if (isDeclareFunction) { - functionDeclarationType = "DeclareFunction"; - } - } - - /** - * Prefix FunctionDeclarations within TypeScript namespaces with "TS" - */ - if (node.parent && node.parent.kind === SyntaxKind.ModuleBlock) { - functionDeclarationType = "TSNamespaceFunctionDeclaration"; - } - - assign(result, { - type: functionDeclarationType, - id: convertChild(node.name), - generator: !!node.asteriskToken, - expression: false, - async: hasModifier(SyntaxKind.AsyncKeyword, node), - params: node.parameters.map(convertChild), - 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); - } - - // check for exports - result = fixExports(node, result, ast); - - break; - - case SyntaxKind.VariableDeclaration: - assign(result, { - type: "VariableDeclarator", - id: convertChild(node.name), - init: convertChild(node.initializer) - }); - - if (node.type) { - result.id.typeAnnotation = convertTypeAnnotation(node.type); - } - break; - - case SyntaxKind.VariableStatement: - assign(result, { - type: "VariableDeclaration", - declarations: node.declarationList.declarations.map(convertChild), - kind: getDeclarationKind(node.declarationList) - }); - - // check for exports - result = fixExports(node, result, ast); - break; - - // mostly for for-of, for-in - case SyntaxKind.VariableDeclarationList: - assign(result, { - type: "VariableDeclaration", - declarations: node.declarations.map(convertChild), - kind: getDeclarationKind(node) - }); - break; - - // Expressions - - case SyntaxKind.ExpressionStatement: - assign(result, { - type: "ExpressionStatement", - expression: convertChild(node.expression) - }); - break; - - case SyntaxKind.ThisKeyword: - assign(result, { - type: "ThisExpression" - }); - break; - - case SyntaxKind.ArrayLiteralExpression: - - var arrayAssignNode = ts.getAncestor(node, SyntaxKind.BinaryExpression), - arrayIsInAssignment; - - if (arrayAssignNode) { - if (arrayAssignNode.left === node) { - arrayIsInAssignment = true; - } else { - arrayIsInAssignment = (ts.findChildOfKind(arrayAssignNode.left, SyntaxKind.ArrayLiteralExpression, ast) === node); - } - } - - // TypeScript uses ArrayLiteralExpression in destructuring assignment, too - if (arrayIsInAssignment) { - assign(result, { - type: "ArrayPattern", - elements: node.elements.map(convertChild) - }); - } else { - assign(result, { - type: "ArrayExpression", - elements: node.elements.map(convertChild) - }); - } - break; - - case SyntaxKind.ObjectLiteralExpression: - - var objectAssignNode = ts.getAncestor(node, SyntaxKind.BinaryExpression), - objectIsInAssignment; - - if (objectAssignNode) { - if (objectAssignNode.left === node) { - objectIsInAssignment = true; - } else { - objectIsInAssignment = (ts.findChildOfKind(objectAssignNode.left, SyntaxKind.ObjectLiteralExpression, ast) === node); - } - } - - // TypeScript uses ObjectLiteralExpression in destructuring assignment, too - if (objectIsInAssignment) { - assign(result, { - type: "ObjectPattern", - properties: node.properties.map(convertChild) - }); - } else { - assign(result, { - type: "ObjectExpression", - properties: node.properties.map(convertChild) - }); - } - break; - - case SyntaxKind.PropertyAssignment: - assign(result, { - type: "Property", - key: convertChild(node.name), - value: convertChild(node.initializer), - computed: (node.name.kind === SyntaxKind.ComputedPropertyName), - method: false, - shorthand: false, - kind: "init" - }); - break; - - case SyntaxKind.ShorthandPropertyAssignment: - assign(result, { - type: "Property", - key: convertChild(node.name), - value: convertChild(node.name), - computed: false, - method: false, - shorthand: true, - kind: "init" - }); - break; - - case SyntaxKind.ComputedPropertyName: - - if (parent.kind === SyntaxKind.ObjectLiteralExpression) { - assign(result, { - type: "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: - var isAbstract = hasModifier(SyntaxKind.AbstractKeyword, node); - - assign(result, { - type: (isAbstract) ? "TSAbstractClassProperty" : "ClassProperty", - key: convertChild(node.name), - value: convertChild(node.initializer), - computed: (node.name.kind === SyntaxKind.ComputedPropertyName), - static: Boolean(ts.getModifierFlags(node) & ts.ModifierFlags.Static), - accessibility: getTSNodeAccessibility(node), - decorators: (node.decorators) ? node.decorators.map(function(d) { - return convertChild(d.expression); - }) : [], - typeAnnotation: (node.type) ? convertTypeAnnotation(node.type) : null - }); - break; - - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.MethodDeclaration: - - // TODO: double-check that these positions are correct - var methodLoc = ast.getLineAndCharacterOfPosition(node.name.end + 1), - nodeIsMethod = (node.kind === SyntaxKind.MethodDeclaration), - method = { - type: "FunctionExpression", - id: null, - generator: false, - expression: false, - async: 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 - 1 - }, - end: result.loc.end - } - }; - - if (node.type) { - method.returnType = convertTypeAnnotation(node.type); - } - - if (parent.kind === SyntaxKind.ObjectLiteralExpression) { - - method.params = node.parameters.map(convertChild); - - assign(result, { - type: "Property", - key: convertChild(node.name), - value: method, - computed: (node.name.kind === SyntaxKind.ComputedPropertyName), - method: nodeIsMethod, - shorthand: false, - kind: "init" - }); - - } else { // class - - /** - * Unlike in object literal methods, class method params can have decorators - */ - method.params = node.parameters.map(function(param) { - var convertedParam = convertChild(param); - convertedParam.decorators = (param.decorators) ? param.decorators.map(function(d) { - return convertChild(d.expression); - }) : []; - return convertedParam; - }); - - var isMethodNameComputed = (node.name.kind === SyntaxKind.ComputedPropertyName); - - /** - * TypeScript class methods can be defined as "abstract" - */ - var methodDefinitionType = hasModifier(SyntaxKind.AbstractKeyword, node) - ? "TSAbstractMethodDefinition" - : "MethodDefinition"; - - assign(result, { - type: methodDefinitionType, - key: convertChild(node.name), - value: method, - computed: isMethodNameComputed, - static: Boolean(ts.getModifierFlags(node) & ts.ModifierFlags.Static), - kind: "method", - accessibility: getTSNodeAccessibility(node), - decorators: (node.decorators) ? node.decorators.map(function(d) { - return convertChild(d.expression); - }) : [] - }); - - } - - 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); - } - - break; - - // TypeScript uses this even for static methods named "constructor" - case SyntaxKind.Constructor: - - var constructorIsStatic = Boolean(ts.getModifierFlags(node) & ts.ModifierFlags.Static), - constructorIsAbstract = hasModifier(SyntaxKind.AbstractKeyword, node), - firstConstructorToken = constructorIsStatic ? ts.findNextToken(node.getFirstToken(), ast) : node.getFirstToken(), - constructorLoc = ast.getLineAndCharacterOfPosition(node.parameters.pos - 1), - constructor = { - type: "FunctionExpression", - id: null, - params: node.parameters.map(function(param) { - var convertedParam = convertChild(param); - var decorators = (param.decorators) ? param.decorators.map(function(d) { - return convertChild(d.expression); - }) : []; - - return assign(convertedParam, { - decorators: decorators - }); - }), - 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 - } - }; - - var constructorIdentifierLoc = ast.getLineAndCharacterOfPosition(firstConstructorToken.getStart()), - constructorIsComputed = !!node.name && (node.name.kind === SyntaxKind.ComputedPropertyName), - constructorKey; - - if (constructorIsComputed) { - constructorKey = { - type: "Literal", - value: "constructor", - raw: node.name.getText(), - range: [ firstConstructorToken.getStart(), firstConstructorToken.end ], - loc: { - start: { - line: constructorIdentifierLoc.line + 1, - column: constructorIdentifierLoc.character - }, - end: { - line: constructor.loc.start.line, - column: constructor.loc.start.column - } - } - }; - } else { - constructorKey = { - type: "Identifier", - name: "constructor", - range: [ firstConstructorToken.getStart(), firstConstructorToken.end ], - loc: { - start: { - line: constructorIdentifierLoc.line + 1, - column: constructorIdentifierLoc.character - }, - end: { - line: constructor.loc.start.line, - column: constructor.loc.start.column - } - } - }; - } - - assign(result, { - type: constructorIsAbstract ? "TSAbstractMethodDefinition" : "MethodDefinition", - key: constructorKey, - value: constructor, - computed: constructorIsComputed, - accessibility: getTSNodeAccessibility(node), - static: constructorIsStatic, - kind: (constructorIsStatic || constructorIsComputed) ? "method" : "constructor" - }); - break; - - case SyntaxKind.FunctionExpression: - assign(result, { - type: "FunctionExpression", - id: convertChild(node.name), - generator: !!node.asteriskToken, - params: node.parameters.map(convertChild), - body: convertChild(node.body), - async: 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: - assign(result, { - type: "Super" - }); - break; - - case SyntaxKind.ArrayBindingPattern: - assign(result, { - type: "ArrayPattern", - elements: node.elements.map(convertChild) - }); - break; - - // occurs with missing array elements like [,] - case SyntaxKind.OmittedExpression: - return null; - - case SyntaxKind.ObjectBindingPattern: - assign(result, { - type: "ObjectPattern", - properties: node.elements.map(convertChild) - }); - break; - - case SyntaxKind.BindingElement: - - if (parent.kind === SyntaxKind.ArrayBindingPattern) { - var arrayItem = convert(node.name, parent); - if (node.initializer) { - assign(result, { - type: "AssignmentPattern", - left: arrayItem, - right: convertChild(node.initializer) - }); - } else { - return arrayItem; - } - } else { - - assign(result, { - type: "Property", - key: convertChild(node.propertyName || node.name), - value: convertChild(node.name), - computed: false, - method: false, - shorthand: !node.propertyName, - kind: "init" - }); - - if (node.initializer) { - result.value = { - type: "AssignmentPattern", - left: convertChild(node.name), - right: convertChild(node.initializer), - range: [ node.name.getStart(), node.initializer.end ], - loc: getLocFor(node.name.getStart(), node.initializer.end, ast) - }; - } - - } - break; - - - case SyntaxKind.ArrowFunction: - assign(result, { - type: "ArrowFunctionExpression", - generator: false, - id: null, - params: node.parameters.map(convertChild), - body: convertChild(node.body), - async: 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: - assign(result, { - type: "YieldExpression", - delegate: !!node.asteriskToken, - argument: convertChild(node.expression) - }); - break; - - case SyntaxKind.AwaitExpression: - assign(result, { - type: "AwaitExpression", - argument: convertChild(node.expression) - }); - break; - - // Template Literals - - case SyntaxKind.NoSubstitutionTemplateLiteral: - assign(result, { - type: "TemplateLiteral", - quasis: [ - { - type: "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: - assign(result, { - type: "TemplateLiteral", - quasis: [ convertChild(node.head) ], - expressions: [] - }); - - node.templateSpans.forEach(function(templateSpan) { - result.expressions.push(convertChild(templateSpan.expression)); - result.quasis.push(convertChild(templateSpan.literal)); - }); - break; - - case SyntaxKind.TaggedTemplateExpression: - assign(result, { - type: "TaggedTemplateExpression", - tag: convertChild(node.tag), - quasi: convertChild(node.template) - }); - break; - - case SyntaxKind.TemplateHead: - case SyntaxKind.TemplateMiddle: - case SyntaxKind.TemplateTail: - var tail = (node.kind === SyntaxKind.TemplateTail); - assign(result, { - type: "TemplateElement", - value: { - raw: ast.text.slice(node.getStart() + 1, node.end - (tail ? 1 : 2)), - cooked: node.text - }, - tail: tail - }); - break; - - // Patterns - - case SyntaxKind.SpreadElement: - case SyntaxKind.SpreadAssignment: - assign(result, { - type: "SpreadElement", - argument: convertChild(node.expression) - }); - break; - - case SyntaxKind.Parameter: - var parameter; - if (node.dotDotDotToken) { - parameter = convertChild(node.name); - assign(result, { - type: "RestElement", - argument: parameter - }); - } else if (node.initializer) { - parameter = convertChild(node.name); - assign(result, { - type: "AssignmentPattern", - left: parameter, - right: convertChild(node.initializer) - }); - } else { - parameter = convert(node.name, parent); - result = parameter; - } - - if (node.type) { - assign(parameter, { - typeAnnotation: convertTypeAnnotation(node.type) - }); - } - - if (node.modifiers) { - return { - type: "TSParameterProperty", - range: [node.getStart(), node.end], - loc: getLoc(node, ast), - accessibility: getTSNodeAccessibility(node), - isReadonly: hasModifier(SyntaxKind.ReadonlyKeyword, node), - parameter: result - }; - } - - break; - - // Classes - - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - - var heritageClauses = node.heritageClauses || []; - var lastClassToken = heritageClauses.length ? heritageClauses[heritageClauses.length - 1] : node.name; - var classNodeType = SyntaxKind[node.kind]; - - if (node.typeParameters && node.typeParameters.length) { - var lastTypeParameter = node.typeParameters[node.typeParameters.length - 1]; - if (!lastClassToken || lastTypeParameter.pos > lastClassToken.pos) { - lastClassToken = ts.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 (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 - */ - var lastModifier = node.modifiers[node.modifiers.length - 1]; - if (!lastClassToken || lastModifier.pos > lastClassToken.pos) { - lastClassToken = ts.findNextToken(lastModifier, ast); - } - - } else if (!lastClassToken) { // no name - lastClassToken = node.getFirstToken(); - } - - var openBrace = ts.findNextToken(lastClassToken, ast); - var hasExtends = (heritageClauses.length && node.heritageClauses[0].token === SyntaxKind.ExtendsKeyword), - superClass, - hasImplements = false; - - if (hasExtends && heritageClauses[0].types.length > 0) { - superClass = heritageClauses.shift(); - if (superClass.types[0] && superClass.types[0].typeArguments) { - result.superTypeParameters = convertTypeArgumentsToTypeParameters(superClass.types[0].typeArguments); - } - } - - hasImplements = heritageClauses.length > 0; - - assign(result, { - type: classNodeType, - id: convertChild(node.name), - body: { - type: "ClassBody", - body: [], - - // TODO: Fix location info - range: [ openBrace.getStart(), result.range[1] ], - loc: getLocFor(openBrace.getStart(), node.end, ast) - }, - superClass: (superClass ? convertChild(superClass.types[0].expression) : null), - implements: hasImplements ? heritageClauses[0].types.map(convertClassImplements) : [], - decorators: (node.decorators) ? node.decorators.map(function(d) { - return convertChild(d.expression); - }) : [] - }); - - var filteredMembers = node.members.filter(isESTreeClassMember); - if (filteredMembers.length) { - result.body.body = filteredMembers.map(convertChild); - } - - // check for exports - result = fixExports(node, result, ast); - - break; - - // Modules - case SyntaxKind.ModuleBlock: - assign(result, { - type: "TSModuleBlock", - body: node.statements.map(convertChild) - }); - break; - - case SyntaxKind.ImportDeclaration: - assign(result, { - type: "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: - assign(result, { - type: "ImportNamespaceSpecifier", - local: convertChild(node.name) - }); - break; - - case SyntaxKind.ImportSpecifier: - assign(result, { - type: "ImportSpecifier", - local: convertChild(node.name), - imported: convertChild(node.propertyName || node.name) - }); - break; - - case SyntaxKind.ImportClause: - assign(result, { - type: "ImportDefaultSpecifier", - local: convertChild(node.name) - }); - - // have to adjust location information due to tree differences - result.range[1] = node.name.end; - result.loc = getLocFor(result.range[0], result.range[1], ast); - break; - - case SyntaxKind.NamedImports: - assign(result, { - type: "ImportDefaultSpecifier", - local: convertChild(node.name) - }); - break; - - case SyntaxKind.ExportDeclaration: - if (node.exportClause) { - assign(result, { - type: "ExportNamedDeclaration", - source: convertChild(node.moduleSpecifier), - specifiers: node.exportClause.elements.map(convertChild), - declaration: null - }); - } else { - assign(result, { - type: "ExportAllDeclaration", - source: convertChild(node.moduleSpecifier) - }); - } - break; - - case SyntaxKind.ExportSpecifier: - assign(result, { - type: "ExportSpecifier", - local: convertChild(node.propertyName || node.name), - exported: convertChild(node.name) - }); - break; - - case SyntaxKind.ExportAssignment: - assign(result, { - type: "ExportDefaultDeclaration", - declaration: convertChild(node.expression) - }); - break; - - // Unary Operations - - case SyntaxKind.PrefixUnaryExpression: - case SyntaxKind.PostfixUnaryExpression: - var operator = TOKEN_TO_TEXT[node.operator]; - assign(result, { - // ESTree uses UpdateExpression for ++/-- - type: /^(?:\+\+|\-\-)$/.test(operator) ? "UpdateExpression" : "UnaryExpression", - operator: operator, - prefix: node.kind === SyntaxKind.PrefixUnaryExpression, - argument: convertChild(node.operand) - }); - break; - - case SyntaxKind.DeleteExpression: - assign(result, { - type: "UnaryExpression", - operator: "delete", - prefix: true, - argument: convertChild(node.expression) - }); - break; - - case SyntaxKind.VoidExpression: - assign(result, { - type: "UnaryExpression", - operator: "void", - prefix: true, - argument: convertChild(node.expression) - }); - break; - - case SyntaxKind.TypeOfExpression: - assign(result, { - type: "UnaryExpression", - operator: "typeof", - prefix: true, - argument: convertChild(node.expression) - }); - break; - - // Binary Operations - - case SyntaxKind.BinaryExpression: - - // TypeScript uses BinaryExpression for sequences as well - if (isComma(node.operatorToken)) { - assign(result, { - type: "SequenceExpression", - expressions: [] - }); - - var left = convertChild(node.left), - right = convertChild(node.right); - - if (left.type === "SequenceExpression") { - result.expressions = result.expressions.concat(left.expressions); - } else { - result.expressions.push(left); - } - - if (right.type === "SequenceExpression") { - result.expressions = result.expressions.concat(right.expressions); - } else { - result.expressions.push(right); - } - - } else { - assign(result, { - type: getBinaryExpressionType(node.operatorToken), - operator: TOKEN_TO_TEXT[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 === "AssignmentExpression") { - var upperArrayNode = ts.getAncestor(node, SyntaxKind.ArrayLiteralExpression), - upperArrayAssignNode = upperArrayNode && ts.getAncestor(upperArrayNode, SyntaxKind.BinaryExpression), - upperArrayIsInAssignment; - - if (upperArrayAssignNode) { - if (upperArrayAssignNode.left === upperArrayNode) { - upperArrayIsInAssignment = true; - } else { - upperArrayIsInAssignment = (ts.findChildOfKind(upperArrayAssignNode.left, SyntaxKind.ArrayLiteralExpression, ast) === upperArrayNode); - } - } - - if (upperArrayIsInAssignment) { - delete result.operator; - result.type = "AssignmentPattern"; - } - } - } - break; - - case SyntaxKind.PropertyAccessExpression: - if (isJSXToken(parent)) { - var jsxMemberExpression = { - type: "MemberExpression", - object: convertChild(node.expression), - property: convertChild(node.name) - }; - var isNestedMemberExpression = (node.expression.kind === SyntaxKind.PropertyAccessExpression); - jsxMemberExpression.object.type = (isNestedMemberExpression) ? "MemberExpression" : "JSXIdentifier"; - jsxMemberExpression.property.type = "JSXIdentifier"; - assign(result, jsxMemberExpression); - } else { - assign(result, { - type: "MemberExpression", - object: convertChild(node.expression), - property: convertChild(node.name), - computed: false - }); - } - break; - - case SyntaxKind.ElementAccessExpression: - assign(result, { - type: "MemberExpression", - object: convertChild(node.expression), - property: convertChild(node.argumentExpression), - computed: true - }); - break; - - case SyntaxKind.ConditionalExpression: - assign(result, { - type: "ConditionalExpression", - test: convertChild(node.condition), - consequent: convertChild(node.whenTrue), - alternate: convertChild(node.whenFalse) - }); - break; - - case SyntaxKind.CallExpression: - assign(result, { - type: "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: - assign(result, { - type: "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: - var newToken = convertToken(node.getFirstToken(), ast); - - assign(result, { - type: "MetaProperty", - meta: { - type: "Identifier", - range: newToken.range, - loc: newToken.loc, - name: "new" - }, - property: convertChild(node.name) - }); - break; - - - // Literals - - case SyntaxKind.StringLiteral: - assign(result, { - type: "Literal", - value: unescape(node.text), - raw: ast.text.slice(result.range[0], result.range[1]) - }); - break; - - case SyntaxKind.NumericLiteral: - assign(result, { - type: "Literal", - value: Number(node.text), - raw: ast.text.slice(result.range[0], result.range[1]) - }); - break; - - case SyntaxKind.RegularExpressionLiteral: - assign(result, { - type: "Literal", - value: Number(node.text), - raw: node.text, - regex: { - pattern: node.text.slice(1, node.text.lastIndexOf("/")), - flags: node.text.slice(node.text.lastIndexOf("/") + 1) - } - }); - break; - - case SyntaxKind.TrueKeyword: - assign(result, { - type: "Literal", - value: true, - raw: "true" - }); - break; - - case SyntaxKind.FalseKeyword: - assign(result, { - type: "Literal", - value: false, - raw: "false" - }); - break; - - case SyntaxKind.NullKeyword: - assign(result, { - type: "Literal", - value: null, - raw: "null" - }); - break; - - case SyntaxKind.EmptyStatement: - case SyntaxKind.DebuggerStatement: - simplyCopy(); - break; - - // JSX - - case SyntaxKind.JsxElement: - assign(result, { - type: "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; - var openingElement = convertChild(node); - openingElement.selfClosing = true; - assign(result, { - type: "JSXElement", - openingElement: openingElement, - closingElement: null, - children: [] - }); - - break; - - case SyntaxKind.JsxOpeningElement: - var openingTagName = convertTypeScriptJSXTagNameToESTreeName(node.tagName); - assign(result, { - type: "JSXOpeningElement", - selfClosing: false, - name: openingTagName, - attributes: node.attributes.properties.map(convertChild) - }); - - break; - - case SyntaxKind.JsxClosingElement: - var closingTagName = convertTypeScriptJSXTagNameToESTreeName(node.tagName); - assign(result, { - type: "JSXClosingElement", - name: closingTagName - }); - - break; - - case SyntaxKind.JsxExpression: - var eloc = ast.getLineAndCharacterOfPosition(result.range[0] + 1); - var expression = (node.expression) ? convertChild(node.expression) : { - type: "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] - }; - - assign(result, { - type: "JSXExpressionContainer", - expression: expression - }); - - break; - - case SyntaxKind.JsxAttribute: - var attributeName = convertToken(node.name, ast); - attributeName.type = "JSXIdentifier"; - attributeName.name = attributeName.value; - delete attributeName.value; - - assign(result, { - type: "JSXAttribute", - name: attributeName, - value: convertChild(node.initializer) - }); - - break; - - case SyntaxKind.JsxText: - assign(result, { - type: "Literal", - value: ast.text.slice(node.pos, node.end), - raw: ast.text.slice(node.pos, node.end) - }); - - result.loc.start.column = node.pos; - result.range[0] = node.pos; - - break; - - case SyntaxKind.JsxSpreadAttribute: - assign(result, { - type: "JSXSpreadAttribute", - argument: convertChild(node.expression) - }); - - break; - - case SyntaxKind.FirstNode: - var jsxMemberExpressionObject = convertChild(node.left); - jsxMemberExpressionObject.type = "JSXIdentifier"; - delete jsxMemberExpressionObject.value; - - var jsxMemberExpressionProperty = convertChild(node.right); - jsxMemberExpressionProperty.type = "JSXIdentifier"; - delete jsxMemberExpressionObject.value; - - assign(result, { - type: "JSXMemberExpression", - object: jsxMemberExpressionObject, - property: jsxMemberExpressionProperty - }); - - break; - - // TypeScript specific - - case SyntaxKind.ParenthesizedExpression: - return convert(node.expression, parent); - - /** - * Convert TypeAliasDeclaration node into VariableDeclaration - * to allow core rules such as "semi" to work automatically - */ - case SyntaxKind.TypeAliasDeclaration: - var typeAliasDeclarator = { - type: "VariableDeclarator", - id: convertChild(node.name), - init: convertChild(node.type), - range: [node.name.getStart(), node.end] - }; - - typeAliasDeclarator.loc = getLocFor(typeAliasDeclarator.range[0], typeAliasDeclarator.range[1], ast); - - // Process typeParameters - if (node.typeParameters && node.typeParameters.length) { - typeAliasDeclarator.typeParameters = convertTSTypeParametersToTypeParametersDeclaration(node.typeParameters); - } - - assign(result, { - type: "VariableDeclaration", - kind: getDeclarationKind(node), - declarations: [typeAliasDeclarator] - }); - - // check for exports - result = fixExports(node, result, ast); - - break; - - case SyntaxKind.InterfaceDeclaration: - var interfaceHeritageClauses = node.heritageClauses || []; - var interfaceLastClassToken = interfaceHeritageClauses.length ? interfaceHeritageClauses[interfaceHeritageClauses.length - 1] : node.name; - - if (node.typeParameters && node.typeParameters.length) { - var interfaceLastTypeParameter = node.typeParameters[node.typeParameters.length - 1]; - if (!interfaceLastClassToken || interfaceLastTypeParameter.pos > interfaceLastClassToken.pos) { - interfaceLastClassToken = ts.findNextToken(interfaceLastTypeParameter, ast); - } - result.typeParameters = convertTSTypeParametersToTypeParametersDeclaration(node.typeParameters); - } - - var hasImplementsClause = interfaceHeritageClauses.length > 0; - var interfaceOpenBrace = ts.findNextToken(interfaceLastClassToken, ast); - - var interfaceBody = { - type: "TSInterfaceBody", - body: node.members.map(function(member) { - return convertChild(member); - }), - range: [ interfaceOpenBrace.getStart(), result.range[1] ], - loc: getLocFor(interfaceOpenBrace.getStart(), node.end, ast) - }; - - assign(result, { - type: "TSInterfaceDeclaration", - body: interfaceBody, - id: convertChild(node.name), - heritage: hasImplementsClause ? interfaceHeritageClauses[0].types.map(convertInterfaceHeritageClause) : [] - }); - break; - - default: - deeplyCopy(); + const estree = convert({ + node: ast, + parent: null, + ast, + additionalOptions: { + errorOnUnknownASTType: extra.errorOnUnknownASTType || false } + }); - return result; - } - - var estree = convert(ast); - + /** + * Optionally convert and include all tokens in the AST + */ if (extra.tokens) { - estree.tokens = convertTokens(ast); + estree.tokens = nodeUtils.convertTokens(ast); } /** * Add the comment nodes to the AST (that were parsed separately in parser.js) - * TODO: Track the progress of https://github.com/eslint/eslint/issues/6724 - * regarding ESLint itself becoming responsible for attributing comment nodes */ if (extra.comment || extra.attachComment) { estree.comments = extra.comments || []; diff --git a/lib/ast-node-types.js b/lib/ast-node-types.js index 3265712..90f9fa0 100644 --- a/lib/ast-node-types.js +++ b/lib/ast-node-types.js @@ -18,37 +18,60 @@ //------------------------------------------------------------------------------ module.exports = { - AssignmentExpression: "AssignmentExpression", - AssignmentPattern: "AssignmentPattern", ArrayExpression: "ArrayExpression", ArrayPattern: "ArrayPattern", ArrowFunctionExpression: "ArrowFunctionExpression", + AssignmentExpression: "AssignmentExpression", + AssignmentPattern: "AssignmentPattern", AwaitExpression: "AwaitExpression", - BlockStatement: "BlockStatement", BinaryExpression: "BinaryExpression", + BlockStatement: "BlockStatement", BreakStatement: "BreakStatement", CallExpression: "CallExpression", CatchClause: "CatchClause", ClassBody: "ClassBody", ClassDeclaration: "ClassDeclaration", ClassExpression: "ClassExpression", + ClassImplements: "ClassImplements", + ClassProperty: "ClassProperty", ConditionalExpression: "ConditionalExpression", ContinueStatement: "ContinueStatement", - DoWhileStatement: "DoWhileStatement", DebuggerStatement: "DebuggerStatement", + DeclareFunction: "DeclareFunction", + DoWhileStatement: "DoWhileStatement", EmptyStatement: "EmptyStatement", ExperimentalRestProperty: "ExperimentalRestProperty", ExperimentalSpreadProperty: "ExperimentalSpreadProperty", + ExportAllDeclaration: "ExportAllDeclaration", + ExportDefaultDeclaration: "ExportDefaultDeclaration", + ExportNamedDeclaration: "ExportNamedDeclaration", + ExportSpecifier: "ExportSpecifier", ExpressionStatement: "ExpressionStatement", - ForStatement: "ForStatement", ForInStatement: "ForInStatement", ForOfStatement: "ForOfStatement", + ForStatement: "ForStatement", FunctionDeclaration: "FunctionDeclaration", FunctionExpression: "FunctionExpression", + GenericTypeAnnotation: "GenericTypeAnnotation", Identifier: "Identifier", IfStatement: "IfStatement", - Literal: "Literal", + 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", + JSXText: "JSXText", LabeledStatement: "LabeledStatement", + Literal: "Literal", LogicalExpression: "LogicalExpression", MemberExpression: "MemberExpression", MetaProperty: "MetaProperty", @@ -71,30 +94,46 @@ module.exports = { ThisExpression: "ThisExpression", ThrowStatement: "ThrowStatement", TryStatement: "TryStatement", + + /** + * TS-prefixed nodes + */ + TSAbstractClassProperty: "TSAbstractClassProperty", + TSAbstractMethodDefinition: "TSAbstractMethodDefinition", + TSAnyKeyword: "TSAnyKeyword", + TSArrayType: "TSArrayType", + TSBooleanKeyword: "TSBooleanKeyword", + TSConstructorType: "TSConstructorType", + TSConstructSignature: "TSConstructSignature", + TSDeclareKeyword: "TSDeclareKeyword", + TSInterfaceBody: "TSInterfaceBody", + TSInterfaceDeclaration: "TSInterfaceDeclaration", + TSInterfaceHeritage: "TSInterfaceHeritage", + TSFunctionType: "TSFunctionType", + TSMethodSignature: "TSMethodSignature", + TSModuleBlock: "TSModuleBlock", + TSModuleDeclaration: "TSModuleDeclaration", + TSNamespaceFunctionDeclaration: "TSNamespaceFunctionDeclaration", + TSNonNullExpression: "TSNonNullExpression", + TSNumberKeyword: "TSNumberKeyword", + TSParameterProperty: "TSParameterProperty", + TSPropertySignature: "TSPropertySignature", + TSQuestionToken: "TSQuestionToken", + TSStringKeyword: "TSStringKeyword", + TSTypeLiteral: "TSTypeLiteral", + TSTypeReference: "TSTypeReference", + TSUnionType: "TSUnionType", + TSVoidKeyword: "TSVoidKeyword", + + TypeAnnotation: "TypeAnnotation", + TypeParameter: "TypeParameter", + TypeParameterDeclaration: "TypeParameterDeclaration", + TypeParameterInstantiation: "TypeParameterInstantiation", UnaryExpression: "UnaryExpression", UpdateExpression: "UpdateExpression", VariableDeclaration: "VariableDeclaration", VariableDeclarator: "VariableDeclarator", WhileStatement: "WhileStatement", WithStatement: "WithStatement", - YieldExpression: "YieldExpression", - JSXIdentifier: "JSXIdentifier", - JSXNamespacedName: "JSXNamespacedName", - JSXMemberExpression: "JSXMemberExpression", - JSXEmptyExpression: "JSXEmptyExpression", - JSXExpressionContainer: "JSXExpressionContainer", - JSXElement: "JSXElement", - JSXClosingElement: "JSXClosingElement", - JSXOpeningElement: "JSXOpeningElement", - JSXAttribute: "JSXAttribute", - JSXSpreadAttribute: "JSXSpreadAttribute", - JSXText: "JSXText", - ExportDefaultDeclaration: "ExportDefaultDeclaration", - ExportNamedDeclaration: "ExportNamedDeclaration", - ExportAllDeclaration: "ExportAllDeclaration", - ExportSpecifier: "ExportSpecifier", - ImportDeclaration: "ImportDeclaration", - ImportSpecifier: "ImportSpecifier", - ImportDefaultSpecifier: "ImportDefaultSpecifier", - ImportNamespaceSpecifier: "ImportNamespaceSpecifier" + YieldExpression: "YieldExpression" }; diff --git a/lib/convert.js b/lib/convert.js new file mode 100644 index 0000000..ce4ffa9 --- /dev/null +++ b/lib/convert.js @@ -0,0 +1,1731 @@ +/** + * @fileoverview Converts TypeScript AST into ESTree format. + * @author Nicholas C. Zakas + * @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] + }); + } + + /** + * 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); + return { + type: AST_NODE_TYPES.TypeAnnotation, + loc: annotation.loc, + range: annotation.range, + 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) { + const firstTypeArgument = typeArguments[0]; + const lastTypeArgument = typeArguments[typeArguments.length - 1]; + return { + type: AST_NODE_TYPES.TypeParameterInstantiation, + range: [ + firstTypeArgument.pos - 1, + lastTypeArgument.end + 1 + ], + loc: nodeUtils.getLocFor(firstTypeArgument.pos - 1, lastTypeArgument.end + 1, ast), + params: typeArguments.map(typeArgument => { + /** + * Have to manually calculate the start of the range, + * because TypeScript includes leading whitespace but Flow does not + */ + const typeArgumentStart = (typeArgument.typeName && typeArgument.typeName.text) + ? typeArgument.end - typeArgument.typeName.text.length + : typeArgument.pos; + return { + type: AST_NODE_TYPES.GenericTypeAnnotation, + range: [ + typeArgumentStart, + typeArgument.end + ], + loc: nodeUtils.getLocFor(typeArgumentStart, typeArgument.end, ast), + id: convertChild(typeArgument.typeName || typeArgument), + typeParameters: (typeArgument.typeArguments) + ? convertTypeArgumentsToTypeParameters(typeArgument.typeArguments) + : null + }; + }) + }; + } + + /** + * 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]; + return { + type: AST_NODE_TYPES.TypeParameterDeclaration, + range: [ + firstTypeParameter.pos - 1, + lastTypeParameter.end + 1 + ], + loc: nodeUtils.getLocFor(firstTypeParameter.pos - 1, lastTypeParameter.end + 1, ast), + params: typeParameters.map(typeParameter => { + + /** + * Have to manually calculate the start of the range, + * because TypeScript includes leading whitespace but Flow does not + */ + const typeParameterStart = (typeParameter.name && typeParameter.name.text) + ? typeParameter.name.end - typeParameter.name.text.length + : typeParameter.pos; + + const defaultParameter = typeParameter.default + ? convert({ node: typeParameter.default, parent: typeParameter, ast, additionalOptions }) + : typeParameter.default; + + return { + type: AST_NODE_TYPES.TypeParameter, + range: [ + typeParameterStart, + typeParameter.end + ], + loc: nodeUtils.getLocFor(typeParameterStart, typeParameter.end, ast), + name: typeParameter.name.text, + constraint: (typeParameter.constraint) + ? convertTypeAnnotation(typeParameter.constraint) + : null, + 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; + } + + /** + * 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 => !(/^(?: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 (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; + + } else { + + tagNameToken.name = tagNameToken.value; + } + + delete tagNameToken.value; + + return tagNameToken; + } + + /** + * 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 => convertChild(decorator.expression)); + } + + /** + * The core of the conervsion 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); + } + }); + + // fix end location + result.range[1] = node.endOfFileToken.pos; + 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: nodeUtils.unescapeIdentifier(node.text) + }); + if (node.parent.questionToken && ( + SyntaxKind.Parameter === node.parent.kind || + SyntaxKind.PropertyDeclaration === node.parent.kind || + SyntaxKind.PropertySignature === node.parent.kind || + SyntaxKind.MethodDeclaration === node.parent.kind || + SyntaxKind.MethodSignature === node.parent.kind + )) { + result.optional = true; + } + 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: convertChild(node.variableDeclaration.name), + 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; + } + } + + /** + * Prefix FunctionDeclarations within TypeScript namespaces with "TS" + */ + if (node.parent && node.parent.kind === SyntaxKind.ModuleBlock) { + functionDeclarationType = AST_NODE_TYPES.TSNamespaceFunctionDeclaration; + } + + Object.assign(result, { + type: functionDeclarationType, + id: convertChild(node.name), + generator: !!node.asteriskToken, + expression: false, + async: nodeUtils.hasModifier(SyntaxKind.AsyncKeyword, node), + params: node.parameters.map(convertChild), + 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); + } + + // 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.type) { + result.id.typeAnnotation = convertTypeAnnotation(node.type); + } + 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); + 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) { + 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 objectAssignNode = nodeUtils.findAncestorOfKind(node, SyntaxKind.BinaryExpression); + let objectIsInAssignment; + + 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: (node.name.kind === SyntaxKind.ComputedPropertyName), + method: false, + shorthand: false, + kind: "init" + }); + break; + + case SyntaxKind.ShorthandPropertyAssignment: + Object.assign(result, { + type: AST_NODE_TYPES.Property, + key: convertChild(node.name), + value: convertChild(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: (node.name.kind === SyntaxKind.ComputedPropertyName), + static: nodeUtils.hasStaticModifierFlag(node), + accessibility: nodeUtils.getTSNodeAccessibility(node), + decorators: convertDecorators(node.decorators), + typeAnnotation: (node.type) ? convertTypeAnnotation(node.type) : null + }); + break; + } + + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.MethodDeclaration: { + + // TODO: double-check that these positions are correct + const methodLoc = ast.getLineAndCharacterOfPosition(node.name.end + 1), + nodeIsMethod = (node.kind === SyntaxKind.MethodDeclaration), + method = { + type: AST_NODE_TYPES.FunctionExpression, + id: null, + generator: false, + 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 - 1 + }, + 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: (node.name.kind === SyntaxKind.ComputedPropertyName), + method: nodeIsMethod, + shorthand: false, + kind: "init" + }); + + } else { // class + + /** + * Unlike in object literal methods, class method params can have decorators + */ + method.params = node.parameters.map(param => { + const convertedParam = convertChild(param); + convertedParam.decorators = convertDecorators(param.decorators); + return convertedParam; + }); + + const isMethodNameComputed = (node.name.kind === SyntaxKind.ComputedPropertyName); + + /** + * 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: isMethodNameComputed, + static: nodeUtils.hasStaticModifierFlag(node), + kind: "method", + accessibility: nodeUtils.getTSNodeAccessibility(node), + decorators: convertDecorators(node.decorators) + }); + + } + + 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); + } + + 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: node.parameters.map(param => { + const convertedParam = convertChild(param); + const decorators = convertDecorators(param.decorators); + return Object.assign(convertedParam, { + decorators + }); + }), + 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 constructorIdentifierLoc = ast.getLineAndCharacterOfPosition(firstConstructorToken.getStart()), + constructorIsComputed = !!node.name && (node.name.kind === SyntaxKind.ComputedPropertyName); + + let constructorKey; + + if (constructorIsComputed) { + constructorKey = { + type: AST_NODE_TYPES.Literal, + value: "constructor", + raw: node.name.getText(), + range: [firstConstructorToken.getStart(), firstConstructorToken.end], + loc: { + start: { + line: constructorIdentifierLoc.line + 1, + column: constructorIdentifierLoc.character + }, + end: { + line: constructor.loc.start.line, + column: constructor.loc.start.column + } + } + }; + } else { + constructorKey = { + type: AST_NODE_TYPES.Identifier, + name: "constructor", + range: [firstConstructorToken.getStart(), firstConstructorToken.end], + loc: { + start: { + line: constructorIdentifierLoc.line + 1, + column: constructorIdentifierLoc.character + }, + end: { + line: constructor.loc.start.line, + column: constructor.loc.start.column + } + } + }; + } + + Object.assign(result, { + type: constructorIsAbstract ? AST_NODE_TYPES.TSAbstractMethodDefinition : AST_NODE_TYPES.MethodDefinition, + key: constructorKey, + value: constructor, + computed: constructorIsComputed, + accessibility: nodeUtils.getTSNodeAccessibility(node), + static: constructorIsStatic, + kind: (constructorIsStatic || constructorIsComputed) ? "method" : "constructor" + }); + break; + + } + + case SyntaxKind.FunctionExpression: + Object.assign(result, { + type: AST_NODE_TYPES.FunctionExpression, + id: convertChild(node.name), + generator: !!node.asteriskToken, + params: node.parameters.map(convertChild), + 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 { + return arrayItem; + } + } else { + + Object.assign(result, { + type: AST_NODE_TYPES.Property, + key: convertChild(node.propertyName || node.name), + value: convertChild(node.name), + computed: false, + 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: node.parameters.map(convertChild), + 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, + 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: + case SyntaxKind.SpreadAssignment: + Object.assign(result, { + type: AST_NODE_TYPES.SpreadElement, + 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) { + Object.assign(parameter, { + typeAnnotation: convertTypeAnnotation(node.type) + }); + } + + if (node.modifiers) { + return { + type: AST_NODE_TYPES.TSParameterProperty, + range: [node.getStart(), node.end], + loc: nodeUtils.getLoc(node, ast), + accessibility: nodeUtils.getTSNodeAccessibility(node), + isReadonly: nodeUtils.hasModifier(SyntaxKind.ReadonlyKeyword, node), + 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 hasExtends = (heritageClauses.length && node.heritageClauses[0].token === SyntaxKind.ExtendsKeyword); + + let hasImplements = false; + let superClass; + + if (hasExtends && heritageClauses[0].types.length > 0) { + superClass = heritageClauses.shift(); + if (superClass.types[0] && superClass.types[0].typeArguments) { + result.superTypeParameters = convertTypeArgumentsToTypeParameters(superClass.types[0].typeArguments); + } + } + + hasImplements = heritageClauses.length > 0; + + 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 ? convertChild(superClass.types[0].expression) : null), + implements: hasImplements ? heritageClauses[0].types.map(convertClassImplements) : [], + 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: + 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; + + // 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 { + 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); + + 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: "new" + }, + property: convertChild(node.name) + }); + break; + } + + // Literals + + case SyntaxKind.StringLiteral: + Object.assign(result, { + type: AST_NODE_TYPES.Literal, + value: nodeUtils.unescapeStringLiteralText(node.text), + raw: ast.text.slice(result.range[0], result.range[1]) + }); + 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: + Object.assign(result, { + type: AST_NODE_TYPES.Literal, + value: Number(node.text), + raw: node.text, + regex: { + pattern: node.text.slice(1, node.text.lastIndexOf("/")), + flags: node.text.slice(node.text.lastIndexOf("/") + 1) + } + }); + 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: + Object.assign(result, { + type: AST_NODE_TYPES.Literal, + value: null, + raw: "null" + }); + 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, + 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: 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; + + } + + case SyntaxKind.JsxText: + Object.assign(result, { + type: AST_NODE_TYPES.Literal, + value: ast.text.slice(node.pos, node.end), + raw: ast.text.slice(node.pos, node.end) + }); + + result.loc.start.column = node.pos; + result.range[0] = node.pos; + + break; + + case SyntaxKind.JsxSpreadAttribute: + Object.assign(result, { + type: AST_NODE_TYPES.JSXSpreadAttribute, + argument: convertChild(node.expression) + }); + + break; + + case SyntaxKind.FirstNode: { + const jsxMemberExpressionObject = convertChild(node.left); + jsxMemberExpressionObject.type = AST_NODE_TYPES.JSXIdentifier; + delete jsxMemberExpressionObject.value; + + const jsxMemberExpressionProperty = convertChild(node.right); + jsxMemberExpressionProperty.type = AST_NODE_TYPES.JSXIdentifier; + delete jsxMemberExpressionObject.value; + + Object.assign(result, { + type: AST_NODE_TYPES.JSXMemberExpression, + object: jsxMemberExpressionObject, + property: jsxMemberExpressionProperty + }); + + 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.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 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, { + type: AST_NODE_TYPES.TSInterfaceDeclaration, + body: interfaceBody, + id: convertChild(node.name), + heritage: hasImplementsClause ? interfaceHeritageClauses[0].types.map(convertInterfaceHeritageClause) : [] + }); + break; + + } + + default: + deeplyCopy(); + } + + return result; + +}; diff --git a/lib/features.js b/lib/features.js deleted file mode 100644 index 2a94e13..0000000 --- a/lib/features.js +++ /dev/null @@ -1,94 +0,0 @@ -/** - * @fileoverview The list of feature flags supported by the parser and their default - * settings. - * @author Nicholas C. Zakas - * @copyright jQuery Foundation and other contributors, https://jquery.org/ - * MIT License - */ - -"use strict"; - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -// None! - -//------------------------------------------------------------------------------ -// Public -//------------------------------------------------------------------------------ - -module.exports = { - - // enable parsing of arrow functions - arrowFunctions: false, - - // enable parsing of let and const - blockBindings: false, - - // enable parsing of destructured arrays and objects - destructuring: false, - - // enable parsing of regex u flag - regexUFlag: false, - - // enable parsing of regex y flag - regexYFlag: false, - - // enable parsing of template strings - templateStrings: false, - - // enable parsing binary literals - binaryLiterals: false, - - // enable parsing ES6 octal literals - octalLiterals: false, - - // enable parsing unicode code point escape sequences - unicodeCodePointEscapes: true, - - // enable parsing of default parameters - defaultParams: false, - - // enable parsing of rest parameters - restParams: false, - - // enable parsing of for-of statements - forOf: false, - - // enable parsing computed object literal properties - objectLiteralComputedProperties: false, - - // enable parsing of shorthand object literal methods - objectLiteralShorthandMethods: false, - - // enable parsing of shorthand object literal properties - objectLiteralShorthandProperties: false, - - // Allow duplicate object literal properties (except '__proto__') - objectLiteralDuplicateProperties: false, - - // enable parsing of generators/yield - generators: false, - - // support the spread operator - spread: false, - - // enable parsing of classes - classes: false, - - // enable parsing of new.target - newTarget: false, - - // enable parsing of modules - modules: false, - - // React JSX parsing - jsx: false, - - // allow return statement in global scope - globalReturn: false, - - // allow experimental object rest/spread - experimentalObjectRestSpread: false -}; diff --git a/lib/node-utils.js b/lib/node-utils.js new file mode 100644 index 0000000..c5585d3 --- /dev/null +++ b/lib/node-utils.js @@ -0,0 +1,635 @@ +/** + * @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.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.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"; + +/** + * 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; + } + } + return undefined; +} + +/** + * 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; +} + +/** + * Returns true if the given TSNode is a let variable declaration + * @param {TSNode} node The TSNode + * @returns {boolean} whether or not the given node is a let variable declaration + */ +function isLet(node) { + /** + * TODO: Remove dependency on private TypeScript method + */ + return ts.isLet(node); +} + +/** + * Returns true if the given TSNode is a const variable declaration + * @param {TSNode} node The TSNode + * @returns {boolean} whether or not the given node is a const variable declaration + */ +function isConst(node) { + /** + * TODO: Remove dependency on private TypeScript method + */ + return ts.isConst(node); +} + +//------------------------------------------------------------------------------ +// 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, + findChildOfKind, + findAncestorOfKind, + hasJSXAncestor, + unescapeIdentifier, + unescapeStringLiteralText, + fixExports, + getTokenType, + convertToken, + convertTokens +}; +/* 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 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 the declaration kind of the given TSNode + * @param {TSNode} node TypeScript AST node + * @returns {string} declaration kind + */ +function getDeclarationKind(node) { + let varDeclarationKind; + switch (node.kind) { + case SyntaxKind.TypeAliasDeclaration: + varDeclarationKind = "type"; + break; + case SyntaxKind.VariableDeclarationList: + if (isLet(node)) { + varDeclarationKind = "let"; + } else if (isConst(node)) { + varDeclarationKind = "const"; + } else { + varDeclarationKind = "var"; + } + break; + default: + throw "Unable to determine declaration kind."; + } + return varDeclarationKind; +} + +/** + * 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); +} + +/** + * 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); +} + +/** + * 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); +} + +/** + * Remove extra underscore from escaped identifier text content. + * @param {string} identifier The escaped identifier text. + * @returns {string} The unescaped identifier text. + */ +function unescapeIdentifier(identifier) { + return ts.unescapeIdentifier(identifier); +} + +/** + * 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); +} + +/** + * 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); + + let declarationType = declarationIsDefault ? "ExportDefaultDeclaration" : "ExportNamedDeclaration"; + + /** + * Prefix exports from TypeScript namespaces with "TS" to distinguish + * them from ES2015 exports + */ + if (node.parent && node.parent.kind === SyntaxKind.ModuleBlock) { + declarationType = "TSNamespaceExportDeclaration"; + } + + 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.getStart(), + value = ast.text.slice(start, token.end), + newToken = { + type: getTokenType(token), + value, + start, + end: token.end, + range: [start, token.end], + loc: getLoc(token, 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) { + 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; +} diff --git a/lib/token-translator.js b/lib/token-translator.js deleted file mode 100644 index a38d29f..0000000 --- a/lib/token-translator.js +++ /dev/null @@ -1,252 +0,0 @@ -/** - * @fileoverview Translates tokens between Acorn format and Esprima format. - * @author Nicholas C. Zakas - * @copyright jQuery Foundation and other contributors, https://jquery.org/ - * MIT License - */ -/* eslint no-underscore-dangle: 0 */ - -"use strict"; - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -// none! - -//------------------------------------------------------------------------------ -// Private -//------------------------------------------------------------------------------ - - -// Esprima Token Types -var Token = { - Boolean: "Boolean", - EOF: "", - Identifier: "Identifier", - Keyword: "Keyword", - Null: "Null", - Numeric: "Numeric", - Punctuator: "Punctuator", - String: "String", - RegularExpression: "RegularExpression", - Template: "Template", - JSXIdentifier: "JSXIdentifier", - JSXText: "JSXText" -}; - -/** - * Converts part of a template into an Esprima token. - * @param {AcornToken[]} tokens The Acorn tokens representing the template. - * @param {string} code The source code. - * @returns {EsprimaToken} The Esprima equivalent of the template token. - * @private - */ -function convertTemplatePart(tokens, code) { - var firstToken = tokens[0], - lastTemplateToken = tokens[tokens.length - 1]; - - var token = { - type: Token.Template, - value: code.slice(firstToken.start, lastTemplateToken.end) - }; - - if (firstToken.loc) { - token.loc = { - start: firstToken.loc.start, - end: lastTemplateToken.loc.end - }; - } - - if (firstToken.range) { - token.range = [firstToken.range[0], lastTemplateToken.range[1]]; - } - - return token; -} - -/** - * Contains logic to translate Acorn tokens into Esprima tokens. - * @param {Object} acornTokTypes The Acorn token types. - * @param {string} code The source code Acorn is parsing. This is necessary - * to correct the "value" property of some tokens. - * @constructor - */ -function TokenTranslator(acornTokTypes, code) { - - // token types - this._acornTokTypes = acornTokTypes; - - // token buffer for templates - this._tokens = []; - - // track the last curly brace - this._curlyBrace = null; - - // the source code - this._code = code; - -} - -TokenTranslator.prototype = { - constructor: TokenTranslator, - - /** - * Translates a single Esprima token to a single Acorn token. This may be - * inaccurate due to how templates are handled differently in Esprima and - * Acorn, but should be accurate for all other tokens. - * @param {AcornToken} token The Acorn token to translate. - * @param {Object} extra Espree extra object. - * @returns {EsprimaToken} The Esprima version of the token. - */ - translate: function(token, extra) { - - var type = token.type, - tt = this._acornTokTypes, - result = { - type: "", - value: token.value, - range: token.range, - loc: token.loc - }; - - if (type === tt.name) { - result.type = Token.Identifier; - - // TODO: See if this is an Acorn bug - if (result.value === "static") { - result.type = Token.Keyword; - } - - } else if (type === tt.semi || type === tt.comma || - type === tt.parenL || type === tt.parenR || - type === tt.braceL || type === tt.braceR || - type === tt.dot || type === tt.bracketL || - type === tt.colon || type === tt.question || - type === tt.bracketR || type === tt.ellipsis || - type === tt.arrow || type === tt.jsxTagStart || - type === tt.jsxTagEnd || (type.binop && !type.keyword) || - type.isAssign) { - - result.type = Token.Punctuator; - result.value = this._code.slice(token.start, token.end); - } else if (type === tt.jsxName) { - result.type = Token.JSXIdentifier; - } else if (type.label === "jsxText" || type === tt.jsxAttrValueToken) { - result.type = Token.JSXText; - } else if (type.keyword) { - if (type.keyword === "true" || type.keyword === "false") { - result.type = Token.Boolean; - } else if (type.keyword === "null") { - result.type = Token.Null; - } else { - result.type = Token.Keyword; - } - } else if (type === tt.num) { - result.type = Token.Numeric; - result.value = this._code.slice(token.start, token.end); - } else if (type === tt.string) { - - if (extra.jsxAttrValueToken) { - extra.jsxAttrValueToken = false; - result.type = Token.JSXText; - } else { - result.type = Token.String; - } - - result.value = this._code.slice(token.start, token.end); - } else if (type === tt.regexp) { - result.type = Token.RegularExpression; - var value = token.value; - result.regex = { - flags: value.flags, - pattern: value.pattern - }; - result.value = "/" + value.pattern + "/" + value.flags; - } - - return result; - }, - - /** - * Function to call during Acorn's onToken handler. - * @param {AcornToken} token The Acorn token. - * @param {Object} extra The Espree extra object. - * @returns {void} - */ - onToken: function(token, extra) { - - var that = this, - tt = this._acornTokTypes, - tokens = extra.tokens, - templateTokens = this._tokens; - - /** - * Flushes the buffered template tokens and resets the template - * tracking. - * @returns {void} - * @private - */ - function translateTemplateTokens() { - tokens.push(convertTemplatePart(that._tokens, that._code)); - that._tokens = []; - } - - if (token.type === tt.eof) { - - // might be one last curlyBrace - if (this._curlyBrace) { - tokens.push(this.translate(this._curlyBrace, extra)); - } - - return; - } - - if (token.type === tt.backQuote) { - templateTokens.push(token); - - // it's the end - if (templateTokens.length > 1) { - translateTemplateTokens(); - } - - return; - } else if (token.type === tt.dollarBraceL) { - templateTokens.push(token); - translateTemplateTokens(); - return; - } else if (token.type === tt.braceR) { - - // if there's already a curly, it's not part of the template - if (this._curlyBrace) { - - tokens.push(this.translate(this._curlyBrace, extra)); - } - - // store new curly for later - this._curlyBrace = token; - return; - } else if (token.type === tt.template) { - if (this._curlyBrace) { - templateTokens.push(this._curlyBrace); - this._curlyBrace = null; - } - - templateTokens.push(token); - return; - } - - if (this._curlyBrace) { - tokens.push(this.translate(this._curlyBrace, extra)); - this._curlyBrace = null; - } - - tokens.push(this.translate(token, extra)); - } -}; - -//------------------------------------------------------------------------------ -// Public -//------------------------------------------------------------------------------ - -module.exports = TokenTranslator; diff --git a/package.json b/package.json index e0f71ea..d306bcb 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "parser.js" ], "engines": { - "node": ">=0.10.0" + "node": ">=4" }, "repository": "eslint/typescript-eslint-parser", "bugs": { @@ -18,17 +18,18 @@ }, "license": "BSD-2-Clause", "devDependencies": { - "chai": "^1.10.0", - "dateformat": "^1.0.11", - "eslint": "^2.2.0", - "eslint-config-eslint": "^3.0.0", - "eslint-release": "^0.10.0", - "istanbul": "~0.2.6", - "leche": "^1.0.1", - "mocha": "^2.0.1", - "npm-license": "^0.2.3", - "shelljs": "^0.3.0", - "shelljs-nodecli": "^0.1.1", + "chai": "3.5.0", + "dateformat": "2.0.0", + "eslint": "3.19.0", + "eslint-config-eslint": "4.0.0", + "eslint-plugin-node": "4.2.2", + "eslint-release": "0.10.3", + "istanbul": "0.4.5", + "leche": "2.1.2", + "mocha": "3.3.0", + "npm-license": "0.3.3", + "shelljs": "0.7.7", + "shelljs-nodecli": "0.1.1", "typescript": "~2.3.2" }, "keywords": [ @@ -50,9 +51,8 @@ "betarelease": "eslint-prerelease beta" }, "dependencies": { - "lodash.unescape": "4.0.0", - "object-assign": "^4.0.1", - "semver": "^5.3.0" + "lodash.unescape": "4.0.1", + "semver": "5.3.0" }, "peerDependencies": { "typescript": "*" diff --git a/parser.js b/parser.js index c2f85a6..6958209 100644 --- a/parser.js +++ b/parser.js @@ -4,34 +4,34 @@ * @copyright jQuery Foundation and other contributors, https://jquery.org/ * MIT License */ -/* eslint no-undefined:0, no-use-before-define: 0 */ "use strict"; -var astNodeTypes = require("./lib/ast-node-types"), +const astNodeTypes = require("./lib/ast-node-types"), ts = require("typescript"), semver = require("semver"); -var SUPPORTED_TYPESCRIPT_VERSIONS = require("./package.json").devDependencies.typescript; -var ACTIVE_TYPESCRIPT_VERSION = ts.version; +const SUPPORTED_TYPESCRIPT_VERSIONS = require("./package.json").devDependencies.typescript; +const ACTIVE_TYPESCRIPT_VERSION = ts.version; -var isRunningSupportedTypeScriptVersion = semver.satisfies(ACTIVE_TYPESCRIPT_VERSION, SUPPORTED_TYPESCRIPT_VERSIONS); +const isRunningSupportedTypeScriptVersion = semver.satisfies(ACTIVE_TYPESCRIPT_VERSION, SUPPORTED_TYPESCRIPT_VERSIONS); if (!isRunningSupportedTypeScriptVersion) { - var border = "============="; - var versionWarning = [ + 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, + `SUPPORTED TYPESCRIPT VERSIONS: ${SUPPORTED_TYPESCRIPT_VERSIONS}`, + `YOUR TYPESCRIPT VERSION: ${ACTIVE_TYPESCRIPT_VERSION}`, "Please only submit bug reports when using the officially supported version.", border ]; - console.warn(versionWarning.join("\n\n")); + + console.warn(versionWarning.join("\n\n")); // eslint-disable-line no-console } -var extra; +let extra; /** * Resets the extra config object @@ -63,7 +63,7 @@ function resetExtra() { * @private */ function convertTypeScriptCommentToEsprimaComment(block, text, start, end, startLoc, endLoc) { - var comment = { + const comment = { type: block ? "Block" : "Line", value: text }; @@ -91,7 +91,7 @@ function convertTypeScriptCommentToEsprimaComment(block, text, start, end, start * @returns {Object} the loc data */ function getLocFor(start, end, ast) { - var startLoc = ast.getLineAndCharacterOfPosition(start), + const startLoc = ast.getLineAndCharacterOfPosition(start), endLoc = ast.getLineAndCharacterOfPosition(end); return { @@ -113,13 +113,12 @@ function getLocFor(start, end, ast) { /** * Parses the given source code to produce a valid AST * @param {mixed} code TypeScript code - * @param {object} options configuration object for the parser - * @returns {object} the AST + * @param {Object} options configuration object for the parser + * @returns {Object} the AST */ function parse(code, options) { - var program, - toString = String; + const toString = String; if (typeof code !== "string" && !(code instanceof String)) { code = toString(code); @@ -155,78 +154,89 @@ function parse(code, options) { // 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; + } + } // Even if jsx option is set in typescript compiler, filename still has to // contain .tsx file extension - var FILENAME = (extra.ecmaFeatures.jsx) ? "eslint.tsx" : "eslint.ts"; + const FILENAME = (extra.ecmaFeatures.jsx) ? "eslint.tsx" : "eslint.ts"; - var compilerHost = { - fileExists: function() { + const compilerHost = { + fileExists() { return true; }, - getCanonicalFileName: function() { + getCanonicalFileName() { return FILENAME; }, - getCurrentDirectory: function() { + getCurrentDirectory() { return ""; }, - getDefaultLibFileName: function() { + getDefaultLibFileName() { return "lib.d.ts"; }, // TODO: Support Windows CRLF - getNewLine: function() { + getNewLine() { return "\n"; }, - getSourceFile: function(filename) { + getSourceFile(filename) { return ts.createSourceFile(filename, code, ts.ScriptTarget.Latest, true); }, - readFile: function() { + readFile() { return null; }, - useCaseSensitiveFileNames: function() { + useCaseSensitiveFileNames() { return true; }, - writeFile: function() { + writeFile() { return null; } }; - program = ts.createProgram([FILENAME], { + const program = ts.createProgram([FILENAME], { noResolve: true, target: ts.ScriptTarget.Latest, jsx: extra.ecmaFeatures.jsx ? "preserve" : undefined }, compilerHost); - var ast = program.getSourceFile(FILENAME); + const ast = program.getSourceFile(FILENAME); if (extra.attachComment || extra.comment) { /** * Create a TypeScript Scanner, with skipTrivia set to false so that * we can parse the comments */ - var triviaScanner = ts.createScanner(ast.languageVersion, false, 0, code); + const triviaScanner = ts.createScanner(ast.languageVersion, false, 0, code); + + let kind = triviaScanner.scan(); - var kind = triviaScanner.scan(); while (kind !== ts.SyntaxKind.EndOfFileToken) { if (kind !== ts.SyntaxKind.SingleLineCommentTrivia && kind !== ts.SyntaxKind.MultiLineCommentTrivia) { kind = triviaScanner.scan(); continue; } - var isBlock = (kind === ts.SyntaxKind.MultiLineCommentTrivia); - var range = { + const isBlock = (kind === ts.SyntaxKind.MultiLineCommentTrivia); + const range = { pos: triviaScanner.getTokenPos(), end: triviaScanner.getTextPos(), kind: triviaScanner.getToken() }; - var comment = code.substring(range.pos, range.end); - var text = comment.replace("//", "").replace("/*", "").replace("*/", ""); - var loc = getLocFor(range.pos, range.end, ast); + const comment = code.substring(range.pos, range.end); + const text = comment.replace("//", "").replace("/*", "").replace("*/", ""); + const loc = getLocFor(range.pos, range.end, ast); + + const esprimaComment = convertTypeScriptCommentToEsprimaComment(isBlock, text, range.pos, range.end, loc.start, loc.end); - var esprimaComment = convertTypeScriptCommentToEsprimaComment(isBlock, text, range.pos, range.end, loc.start, loc.end); extra.comments.push(esprimaComment); kind = triviaScanner.scan(); @@ -234,9 +244,10 @@ function parse(code, options) { } - var convert = require("./lib/ast-converter"); + const convert = require("./lib/ast-converter"); return convert(ast, extra); + } //------------------------------------------------------------------------------ @@ -250,7 +261,8 @@ exports.parse = parse; // Deep copy. /* istanbul ignore next */ exports.Syntax = (function() { - var name, types = {}; + let name, + types = {}; if (typeof Object.create === "function") { types = Object.create(null); diff --git a/tests/lib/attach-comments.js b/tests/lib/attach-comments.js index b135969..28120a8 100644 --- a/tests/lib/attach-comments.js +++ b/tests/lib/attach-comments.js @@ -30,7 +30,7 @@ // Requirements //------------------------------------------------------------------------------ -var assert = require("chai").assert, +const assert = require("chai").assert, leche = require("leche"), path = require("path"), parser = require("../../parser"), @@ -41,23 +41,22 @@ var assert = require("chai").assert, // Setup //------------------------------------------------------------------------------ -var FIXTURES_DIR = "./tests/fixtures/attach-comments"; +const FIXTURES_DIR = "./tests/fixtures/attach-comments"; -var testFiles = shelljs.find(FIXTURES_DIR).filter(function(filename) { - return filename.indexOf(".src.js") > -1; -}).map(function(filename) { - return filename.substring(FIXTURES_DIR.length - 1, filename.length - 7); // strip off ".src.js" -}); +const testFiles = shelljs.find(FIXTURES_DIR) + .filter(filename => filename.indexOf(".src.js") > -1) + // strip off ".src.js" + .map(filename => filename.substring(FIXTURES_DIR.length - 1, filename.length - 7)); //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ -describe("attachComment: true", function() { +describe("attachComment: true", () => { - var config; + let config; - beforeEach(function() { + beforeEach(() => { config = { loc: true, range: true, @@ -67,12 +66,12 @@ describe("attachComment: true", function() { }; }); - leche.withData(testFiles, function(filename) { - var code = shelljs.cat(path.resolve(FIXTURES_DIR, filename) + ".src.js"); + leche.withData(testFiles, filename => { + const code = shelljs.cat(`${path.resolve(FIXTURES_DIR, filename)}.src.js`); - it("should produce correct AST when parsed with attachComment", function() { - var expected = require(path.resolve(__dirname, "../../", FIXTURES_DIR, filename) + ".result.js"); - var result; + it("should produce correct AST when parsed with attachComment", () => { + const expected = require(`${path.resolve(__dirname, "../../", FIXTURES_DIR, filename)}.result.js`); + let result; try { result = parser.parse(code, config); @@ -82,9 +81,9 @@ describe("attachComment: true", function() { // format of error isn't exactly the same, just check if it's expected if (expected.message) { return; - } else { - throw ex; } + throw ex; + } assert.deepEqual(result, expected); diff --git a/tests/lib/basics.js b/tests/lib/basics.js index db0719a..7ac087f 100644 --- a/tests/lib/basics.js +++ b/tests/lib/basics.js @@ -11,7 +11,7 @@ // Requirements //------------------------------------------------------------------------------ -var assert = require("chai").assert, +const assert = require("chai").assert, leche = require("leche"), path = require("path"), parser = require("../../parser"), @@ -22,39 +22,40 @@ var assert = require("chai").assert, // Setup //------------------------------------------------------------------------------ -var FIXTURES_DIR = "./tests/fixtures/basics"; +const FIXTURES_DIR = "./tests/fixtures/basics"; -var testFiles = shelljs.find(FIXTURES_DIR).filter(function(filename) { - return filename.indexOf(".src.js") > -1; -}).map(function(filename) { - return filename.substring(FIXTURES_DIR.length - 1, filename.length - 7); // strip off ".src.js" -}); +const testFiles = shelljs.find(FIXTURES_DIR) + .filter(filename => filename.indexOf(".src.js") > -1) + // strip off ".src.js" + .map(filename => filename.substring(FIXTURES_DIR.length - 1, filename.length - 7)); //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ -describe("basics", function() { +describe("basics", () => { - var config; + let config; - beforeEach(function() { + beforeEach(() => { config = { loc: true, range: true, tokens: true, - ecmaFeatures: {} + ecmaFeatures: {}, + errorOnUnknownASTType: true }; }); - leche.withData(testFiles, function(filename) { + leche.withData(testFiles, filename => { + // Uncomment and fill in filename to focus on a single file // var filename = "jsx/invalid-matching-placeholder-in-closing-tag"; - var code = shelljs.cat(path.resolve(FIXTURES_DIR, filename) + ".src.js"); + const code = shelljs.cat(`${path.resolve(FIXTURES_DIR, filename)}.src.js`); - it("should parse correctly", function() { - var expected = require(path.resolve(__dirname, "../../", FIXTURES_DIR, filename) + ".result.js"); - var result; + it("should parse correctly", () => { + const expected = require(`${path.resolve(__dirname, "../../", FIXTURES_DIR, filename)}.result.js`); + let result; try { result = parser.parse(code, config); @@ -64,11 +65,12 @@ describe("basics", function() { // format of error isn't exactly the same, just check if it's expected if (expected.message) { return; - } else { - throw ex; } + throw ex; + } + // console.log(JSON.stringify(result, null, 4)); assert.deepEqual(result, expected); diff --git a/tests/lib/ecma-features.js b/tests/lib/ecma-features.js index 402d8c9..f7104aa 100644 --- a/tests/lib/ecma-features.js +++ b/tests/lib/ecma-features.js @@ -11,7 +11,7 @@ // Requirements //------------------------------------------------------------------------------ -var assert = require("chai").assert, +const assert = require("chai").assert, leche = require("leche"), path = require("path"), parser = require("../../parser"), @@ -22,27 +22,23 @@ var assert = require("chai").assert, // Setup //------------------------------------------------------------------------------ -var FIXTURES_DIR = "./tests/fixtures/ecma-features"; +const FIXTURES_DIR = "./tests/fixtures/ecma-features"; + // var FIXTURES_MIX_DIR = "./tests/fixtures/ecma-features-mix"; -var filesWithOutsandingTSIssues = [ +const filesWithOutsandingTSIssues = [ "jsx/embedded-tags", // https://github.com/Microsoft/TypeScript/issues/7410 "jsx/namespaced-attribute-and-value-inserted", // https://github.com/Microsoft/TypeScript/issues/7411 "jsx/namespaced-name-and-attribute", // https://github.com/Microsoft/TypeScript/issues/7411 "jsx/multiple-blank-spaces" ]; -var testFiles = shelljs.find(FIXTURES_DIR).filter(function(filename) { - return filename.indexOf(".src.js") > -1; -}).filter(function(filename) { - return filesWithOutsandingTSIssues.every(function(fileName) { - return filename.indexOf(fileName) === -1; - }); -}).map(function(filename) { - return filename.substring(FIXTURES_DIR.length - 1, filename.length - 7); // strip off ".src.js" -}).filter(function(filename) { - return !(/error\-|invalid\-|globalReturn/.test(filename)); -}); +const testFiles = shelljs.find(FIXTURES_DIR) + .filter(filename => filename.indexOf(".src.js") > -1) + .filter(filename => filesWithOutsandingTSIssues.every(fileName => filename.indexOf(fileName) === -1)) + // strip off ".src.js" + .map(filename => filename.substring(FIXTURES_DIR.length - 1, filename.length - 7)) + .filter(filename => !(/error-|invalid-|globalReturn/.test(filename))); // var moduleTestFiles = testFiles.filter(function(filename) { // return !/jsx|globalReturn|invalid|experimental|generators|not\-strict/.test(filename); @@ -63,29 +59,31 @@ var testFiles = shelljs.find(FIXTURES_DIR).filter(function(filename) { // Tests //------------------------------------------------------------------------------ -describe("ecmaFeatures", function() { +describe("ecmaFeatures", () => { - var config; + let config; - beforeEach(function() { + beforeEach(() => { config = { loc: true, range: true, tokens: true, - ecmaFeatures: {} + ecmaFeatures: {}, + errorOnUnknownASTType: true }; }); - leche.withData(testFiles, function(filename) { + leche.withData(testFiles, filename => { + // Uncomment and fill in filename to focus on a single file // var filename = "jsx/invalid-matching-placeholder-in-closing-tag"; - var feature = path.dirname(filename), - code = shelljs.cat(path.resolve(FIXTURES_DIR, filename) + ".src.js"); + const feature = path.dirname(filename), + code = shelljs.cat(`${path.resolve(FIXTURES_DIR, filename)}.src.js`); - it("should parse correctly when " + feature + " is true", function() { + it(`should parse correctly when ${feature} is true`, () => { config.ecmaFeatures[feature] = true; - var expected = require(path.resolve(__dirname, "../../", FIXTURES_DIR, filename) + ".result.js"); - var result; + const expected = require(`${path.resolve(__dirname, "../../", FIXTURES_DIR, filename)}.result.js`); + let result; try { result = parser.parse(code, config); @@ -95,9 +93,9 @@ describe("ecmaFeatures", function() { // format of error isn't exactly the same, just check if it's expected if (expected.message) { return; - } else { - throw ex; } + throw ex; + } assert.deepEqual(result, expected); diff --git a/tests/lib/parse.js b/tests/lib/parse.js index 76310b8..a6606cc 100644 --- a/tests/lib/parse.js +++ b/tests/lib/parse.js @@ -11,7 +11,7 @@ // Requirements //------------------------------------------------------------------------------ -var assert = require("chai").assert, +const assert = require("chai").assert, parser = require("../../parser"), tester = require("./tester"); @@ -19,21 +19,21 @@ var assert = require("chai").assert, // Tests //------------------------------------------------------------------------------ -describe("parse()", function() { +describe("parse()", () => { - describe("basic functionality", function() { + describe("basic functionality", () => { - it("should parse an empty string", function() { + it("should parse an empty string", () => { assert.deepEqual(parser.parse("").body, []); assert.deepEqual(parser.parse("", {}).body, []); }); }); - describe("modules", function() { + describe("modules", () => { - it("should have correct column number when strict mode error occurs", function() { + it("should have correct column number when strict mode error occurs", () => { try { parser.parse("function fn(a, a) {\n}", { sourceType: "module" }); } catch (err) { @@ -43,10 +43,10 @@ describe("parse()", function() { }); - describe("general", function() { + describe("general", () => { - it("should output tokens, comments, locs, and ranges when called with those options", function() { - var ast = parser.parse("let foo = bar;", { + it("should output tokens, comments, locs, and ranges when called with those options", () => { + const ast = parser.parse("let foo = bar;", { ecmaFeatures: { blockBindings: true }, diff --git a/tests/lib/tester.js b/tests/lib/tester.js index d20003a..aa14f38 100644 --- a/tests/lib/tester.js +++ b/tests/lib/tester.js @@ -13,13 +13,13 @@ /** * Returns a raw copy of the given AST - * @param {object} ast the AST object - * @returns {object} copy of the AST object + * @param {Object} ast the AST object + * @returns {Object} copy of the AST object */ function getRaw(ast) { - return JSON.parse(JSON.stringify(ast, function(key, value) { + return JSON.parse(JSON.stringify(ast, (key, value) => { if ((key === "start" || key === "end") && typeof value === "number") { - return undefined; // eslint-disable-line no-undefined + return undefined; } return value; @@ -27,5 +27,5 @@ function getRaw(ast) { } module.exports = { - getRaw: getRaw + getRaw }; diff --git a/tests/lib/typescript.js b/tests/lib/typescript.js index 691985e..8c0b12a 100644 --- a/tests/lib/typescript.js +++ b/tests/lib/typescript.js @@ -11,7 +11,7 @@ // Requirements //------------------------------------------------------------------------------ -var assert = require("chai").assert, +const assert = require("chai").assert, leche = require("leche"), path = require("path"), parser = require("../../parser"), @@ -22,39 +22,40 @@ var assert = require("chai").assert, // Setup //------------------------------------------------------------------------------ -var FIXTURES_DIR = "./tests/fixtures/typescript"; +const FIXTURES_DIR = "./tests/fixtures/typescript"; -var testFiles = shelljs.find(FIXTURES_DIR).filter(function(filename) { - return filename.indexOf(".src.ts") > -1; -}).map(function(filename) { - return filename.substring(FIXTURES_DIR.length - 1, filename.length - 7); // strip off ".src.ts" -}); +const testFiles = shelljs.find(FIXTURES_DIR) + .filter(filename => filename.indexOf(".src.ts") > -1) + // strip off ".src.ts" + .map(filename => filename.substring(FIXTURES_DIR.length - 1, filename.length - 7)); //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ -describe("typescript", function() { +describe("typescript", () => { - var config; + let config; - beforeEach(function() { + beforeEach(() => { config = { loc: true, range: true, tokens: true, - ecmaFeatures: {} + ecmaFeatures: {}, + errorOnUnknownASTType: true }; }); - leche.withData(testFiles, function(filename) { + leche.withData(testFiles, filename => { + // Uncomment and fill in filename to focus on a single file // var filename = "jsx/invalid-matching-placeholder-in-closing-tag"; - var code = shelljs.cat(path.resolve(FIXTURES_DIR, filename) + ".src.ts"); + const code = shelljs.cat(`${path.resolve(FIXTURES_DIR, filename)}.src.ts`); - it("should parse correctly", function() { - var expected = require(path.resolve(__dirname, "../../", FIXTURES_DIR, filename) + ".result.js"); - var result; + it("should parse correctly", () => { + const expected = require(`${path.resolve(__dirname, "../../", FIXTURES_DIR, filename)}.result.js`); + let result; try { result = parser.parse(code, config); @@ -64,11 +65,12 @@ describe("typescript", function() { // format of error isn't exactly the same, just check if it's expected if (expected.message) { return; - } else { - throw ex; } + throw ex; + } + // console.log(JSON.stringify(result, null, 4)); assert.deepEqual(result, expected);