diff --git a/.eslintrc.js b/.eslintrc.js index b2932680e..88d21bf78 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -235,7 +235,7 @@ module.exports = { functions: ["hasComment", "getComments"], }, "src/language-js/pragma.js", - "src/language-js/parse/postprocess.js", + "src/language-js/parse/postprocess/*.js", "src/language-js/parse/babel.js", "src/language-js/parse/meriyah.js", "src/language-js/parse/json.js", diff --git a/package.json b/package.json index eec595ef9..04ef618a3 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "@babel/parser": "7.15.8", "@glimmer/syntax": "0.82.0", "@iarna/toml": "2.2.5", - "@typescript-eslint/typescript-estree": "4.32.0", + "@typescript-eslint/typescript-estree": "5.1.0", "angular-estree-parser": "2.5.0", "angular-html-parser": "1.8.0", "camelcase": "6.2.0", diff --git a/src/language-js/comments.js b/src/language-js/comments.js index 5bac9b1a8..d1eb48b03 100644 --- a/src/language-js/comments.js +++ b/src/language-js/comments.js @@ -460,7 +460,7 @@ function handleMethodNameComments({ (enclosingNode.type === "ClassMethod" || enclosingNode.type === "ClassProperty" || enclosingNode.type === "PropertyDefinition" || - enclosingNode.type === "TSAbstractClassProperty" || + enclosingNode.type === "TSAbstractPropertyDefinition" || enclosingNode.type === "TSAbstractMethodDefinition" || enclosingNode.type === "TSDeclareMethod" || enclosingNode.type === "MethodDefinition") diff --git a/src/language-js/parse/babel.js b/src/language-js/parse/babel.js index 31f7b7a07..806021042 100644 --- a/src/language-js/parse/babel.js +++ b/src/language-js/parse/babel.js @@ -7,7 +7,7 @@ const { } = require("../../common/util.js"); const createParser = require("./utils/create-parser.js"); const createBabelParseError = require("./utils/create-babel-parse-error.js"); -const postprocess = require("./postprocess.js"); +const postprocess = require("./postprocess/index.js"); const jsonParsers = require("./json.js"); /** diff --git a/src/language-js/parse/espree.js b/src/language-js/parse/espree.js index 3aa3b79b0..2d3a558d7 100644 --- a/src/language-js/parse/espree.js +++ b/src/language-js/parse/espree.js @@ -4,7 +4,7 @@ const createError = require("../../common/parser-create-error.js"); const tryCombinations = require("../../utils/try-combinations.js"); const createParser = require("./utils/create-parser.js"); const replaceHashbang = require("./utils/replace-hashbang.js"); -const postprocess = require("./postprocess.js"); +const postprocess = require("./postprocess/index.js"); /** @type {import("espree").Options} */ const parseOptions = { diff --git a/src/language-js/parse/flow.js b/src/language-js/parse/flow.js index 5604ee1aa..c97d98447 100644 --- a/src/language-js/parse/flow.js +++ b/src/language-js/parse/flow.js @@ -3,7 +3,7 @@ const createError = require("../../common/parser-create-error.js"); const createParser = require("./utils/create-parser.js"); const replaceHashbang = require("./utils/replace-hashbang.js"); -const postprocess = require("./postprocess.js"); +const postprocess = require("./postprocess/index.js"); // https://github.com/facebook/flow/tree/master/packages/flow-parser#options const parseOptions = { diff --git a/src/language-js/parse/meriyah.js b/src/language-js/parse/meriyah.js index cb5d2c3ce..08f46b872 100644 --- a/src/language-js/parse/meriyah.js +++ b/src/language-js/parse/meriyah.js @@ -3,7 +3,7 @@ const createError = require("../../common/parser-create-error.js"); const tryCombinations = require("../../utils/try-combinations.js"); const createParser = require("./utils/create-parser.js"); -const postprocess = require("./postprocess.js"); +const postprocess = require("./postprocess/index.js"); // https://github.com/meriyah/meriyah/blob/4676f60b6c149d7082bde2c9147f9ae2359c8075/src/parser.ts#L185 const parseOptions = { diff --git a/src/language-js/parse/postprocess.js b/src/language-js/parse/postprocess/index.js similarity index 64% rename from src/language-js/parse/postprocess.js rename to src/language-js/parse/postprocess/index.js index b4fb50708..cdf537a9d 100644 --- a/src/language-js/parse/postprocess.js +++ b/src/language-js/parse/postprocess/index.js @@ -1,54 +1,18 @@ "use strict"; -const { - getLast, - getNextNonSpaceNonCommentCharacter, -} = require("../../common/util.js"); -const createError = require("../../common/parser-create-error.js"); -const { locStart, locEnd } = require("../loc.js"); -const { isTypeCastComment } = require("../comments.js"); +const { getLast } = require("../../../common/util.js"); +const { locStart, locEnd } = require("../../loc.js"); +const { isTypeCastComment } = require("../../comments.js"); +const visitNode = require("./visitNode.js"); +const { throwErrorForInvalidNodes } = require("./typescript.js"); function postprocess(ast, options) { - // Invalid decorators are removed since `@typescript-eslint/typescript-estree` v4 - // https://github.com/typescript-eslint/typescript-eslint/pull/2375 - if (options.parser === "typescript" && options.originalText.includes("@")) { - const { esTreeNodeToTSNodeMap, tsNodeToESTreeNodeMap } = - options.tsParseResult; - ast = visitNode(ast, (node) => { - const tsNode = esTreeNodeToTSNodeMap.get(node); - if (!tsNode) { - return; - } - const tsDecorators = tsNode.decorators; - if (!Array.isArray(tsDecorators)) { - return; - } - // `esTreeNodeToTSNodeMap.get(ClassBody)` and `esTreeNodeToTSNodeMap.get(ClassDeclaration)` has the same tsNode - const esTreeNode = tsNodeToESTreeNodeMap.get(tsNode); - if (esTreeNode !== node) { - return; - } - const esTreeDecorators = esTreeNode.decorators; - if ( - !Array.isArray(esTreeDecorators) || - esTreeDecorators.length !== tsDecorators.length || - tsDecorators.some((tsDecorator) => { - const esTreeDecorator = tsNodeToESTreeNodeMap.get(tsDecorator); - return ( - !esTreeDecorator || !esTreeDecorators.includes(esTreeDecorator) - ); - }) - ) { - const { start, end } = esTreeNode.loc; - throw createError( - "Leading decorators must be attached to a class declaration", - { - start: { line: start.line, column: start.column + 1 }, - end: { line: end.line, column: end.column + 1 }, - } - ); - } - }); + if ( + options.parser === "typescript" && + // decorators or abstract properties + /@|abstract/.test(options.originalText) + ) { + throwErrorForInvalidNodes(ast, options); } // Keep Babel's non-standard ParenthesizedExpression nodes only if they have Closure-style type cast comments. @@ -138,20 +102,6 @@ function postprocess(ast, options) { ]; break; } - case "ClassProperty": - // TODO: Temporary auto-generated node type. To remove when typescript-estree has proper support for private fields. - if ( - node.key && - node.key.type === "TSPrivateIdentifier" && - getNextNonSpaceNonCommentCharacter( - options.originalText, - node.key, - locEnd - ) === "?" - ) { - node.optional = true; - } - break; // For hack-style pipeline case "TopicReference": options.__isUsingHackPipeline = true; @@ -195,32 +145,6 @@ function transformChainExpression(node) { return node; } -function visitNode(node, fn) { - let entries; - - if (Array.isArray(node)) { - entries = node.entries(); - } else if ( - node && - typeof node === "object" && - typeof node.type === "string" - ) { - entries = Object.entries(node); - } else { - return node; - } - - for (const [key, child] of entries) { - node[key] = visitNode(child, fn); - } - - if (Array.isArray(node)) { - return node; - } - - return fn(node) || node; -} - function isUnbalancedLogicalTree(node) { return ( node.type === "LogicalExpression" && diff --git a/src/language-js/parse/postprocess/typescript.js b/src/language-js/parse/postprocess/typescript.js new file mode 100644 index 000000000..2ee9bcac2 --- /dev/null +++ b/src/language-js/parse/postprocess/typescript.js @@ -0,0 +1,80 @@ +"use strict"; + +const createError = require("../../../common/parser-create-error.js"); +const visitNode = require("./visitNode.js"); + +function throwSyntaxError(node, message) { + const { start, end } = node.loc; + throw createError(message, { + start: { line: start.line, column: start.column + 1 }, + end: { line: end.line, column: end.column + 1 }, + }); +} + +// Invalid decorators are removed since `@typescript-eslint/typescript-estree` v4 +// https://github.com/typescript-eslint/typescript-eslint/pull/2375 +function throwErrorForInvalidDecorator( + tsNode, + esTreeNode, + tsNodeToESTreeNodeMap +) { + const tsDecorators = tsNode.decorators; + if (!Array.isArray(tsDecorators)) { + return; + } + const esTreeDecorators = esTreeNode.decorators; + if ( + !Array.isArray(esTreeDecorators) || + esTreeDecorators.length !== tsDecorators.length || + tsDecorators.some((tsDecorator) => { + const esTreeDecorator = tsNodeToESTreeNodeMap.get(tsDecorator); + return !esTreeDecorator || !esTreeDecorators.includes(esTreeDecorator); + }) + ) { + throwSyntaxError( + esTreeNode, + "Leading decorators must be attached to a class declaration" + ); + } +} + +// Values of abstract property is removed since `@typescript-eslint/typescript-estree` v5 +// https://github.com/typescript-eslint/typescript-eslint/releases/tag/v5.0.0 +function throwErrorForInvalidAbstractProperty(tsNode, esTreeNode) { + const SYNTAX_KIND_PROPERTY_DEFINITION = 165; + const SYNTAX_KIND_ABSTRACT_KEYWORD = 126; + if ( + tsNode.kind !== SYNTAX_KIND_PROPERTY_DEFINITION || + (tsNode.modifiers && + !tsNode.modifiers.some( + (modifier) => modifier.kind === SYNTAX_KIND_ABSTRACT_KEYWORD + )) + ) { + return; + } + if (tsNode.initializer && esTreeNode.value === null) { + throwSyntaxError( + esTreeNode, + "Abstract property cannot have an initializer" + ); + } +} + +function throwErrorForInvalidNodes(ast, options) { + const { esTreeNodeToTSNodeMap, tsNodeToESTreeNodeMap } = + options.tsParseResult; + ast = visitNode(ast, (node) => { + const tsNode = esTreeNodeToTSNodeMap.get(node); + if (!tsNode) { + return; + } + const esTreeNode = tsNodeToESTreeNodeMap.get(tsNode); + if (esTreeNode !== node) { + return; + } + throwErrorForInvalidDecorator(tsNode, esTreeNode, tsNodeToESTreeNodeMap); + throwErrorForInvalidAbstractProperty(tsNode, esTreeNode); + }); +} + +module.exports = { throwErrorForInvalidNodes }; diff --git a/src/language-js/parse/postprocess/visitNode.js b/src/language-js/parse/postprocess/visitNode.js new file mode 100644 index 000000000..d916157e5 --- /dev/null +++ b/src/language-js/parse/postprocess/visitNode.js @@ -0,0 +1,29 @@ +"use strict"; + +function visitNode(node, fn) { + let entries; + + if (Array.isArray(node)) { + entries = node.entries(); + } else if ( + node && + typeof node === "object" && + typeof node.type === "string" + ) { + entries = Object.entries(node); + } else { + return node; + } + + for (const [key, child] of entries) { + node[key] = visitNode(child, fn); + } + + if (Array.isArray(node)) { + return node; + } + + return fn(node) || node; +} + +module.exports = visitNode; diff --git a/src/language-js/parse/typescript.js b/src/language-js/parse/typescript.js index fb15bf4e5..b3240918b 100644 --- a/src/language-js/parse/typescript.js +++ b/src/language-js/parse/typescript.js @@ -4,7 +4,7 @@ const createError = require("../../common/parser-create-error.js"); const tryCombinations = require("../../utils/try-combinations.js"); const createParser = require("./utils/create-parser.js"); const replaceHashbang = require("./utils/replace-hashbang.js"); -const postprocess = require("./postprocess.js"); +const postprocess = require("./postprocess/index.js"); /** @type {import("@typescript-eslint/typescript-estree").TSESTreeOptions} */ const parseOptions = { @@ -13,7 +13,6 @@ const parseOptions = { loc: true, range: true, comment: true, - useJSXTextNode: true, jsx: true, tokens: true, loggerFn: false, diff --git a/src/language-js/print/binaryish.js b/src/language-js/print/binaryish.js index e5f259e9b..1b71976c6 100644 --- a/src/language-js/print/binaryish.js +++ b/src/language-js/print/binaryish.js @@ -112,7 +112,7 @@ function printBinaryishExpression(path, options, print) { parent.type === "VariableDeclarator" || parent.type === "ClassProperty" || parent.type === "PropertyDefinition" || - parent.type === "TSAbstractClassProperty" || + parent.type === "TSAbstractPropertyDefinition" || parent.type === "ClassPrivateProperty" || isObjectProperty(parent); diff --git a/src/language-js/print/class.js b/src/language-js/print/class.js index 017f9a36d..66ad25052 100644 --- a/src/language-js/print/class.js +++ b/src/language-js/print/class.js @@ -204,7 +204,7 @@ function printClassProperty(path, options, print) { if (node.static) { parts.push("static "); } - if (node.type === "TSAbstractClassProperty" || node.abstract) { + if (node.type === "TSAbstractPropertyDefinition" || node.abstract) { parts.push("abstract "); } if (node.override) { diff --git a/src/language-js/print/statement.js b/src/language-js/print/statement.js index c7573faf7..a5527760b 100644 --- a/src/language-js/print/statement.js +++ b/src/language-js/print/statement.js @@ -215,7 +215,7 @@ function shouldPrintSemicolonAfterClassProperty(node, nextNode) { switch (nextNode.type) { case "ClassProperty": case "PropertyDefinition": - case "TSAbstractClassProperty": + case "TSAbstractPropertyDefinition": return nextNode.computed; case "MethodDefinition": // Flow case "TSAbstractMethodDefinition": // TypeScript diff --git a/src/language-js/print/typescript.js b/src/language-js/print/typescript.js index 748166ad7..9d5547775 100644 --- a/src/language-js/print/typescript.js +++ b/src/language-js/print/typescript.js @@ -113,7 +113,7 @@ function printTypescript(path, options, print) { case "TSAbstractMethodDefinition": case "TSDeclareMethod": return printClassMethod(path, options, print); - case "TSAbstractClassProperty": + case "TSAbstractPropertyDefinition": return printClassProperty(path, options, print); case "TSInterfaceHeritage": case "TSExpressionWithTypeArguments": // Babel AST @@ -486,9 +486,6 @@ function printTypescript(path, options, print) { return parts; } - // TODO: Temporary auto-generated node type. To remove when typescript-estree has proper support for private fields. - case "TSPrivateIdentifier": - return node.escapedText; case "TSConditionalType": return printTernary(path, options, print); diff --git a/src/language-js/printer-estree.js b/src/language-js/printer-estree.js index 2db75d1e2..faf2aa6af 100644 --- a/src/language-js/printer-estree.js +++ b/src/language-js/printer-estree.js @@ -96,7 +96,7 @@ function genericPrint(path, options, print, args) { type === "ClassPrivateMethod" || type === "ClassProperty" || type === "PropertyDefinition" || - type === "TSAbstractClassProperty" || + type === "TSAbstractPropertyDefinition" || type === "ClassPrivateProperty" || type === "MethodDefinition" || type === "TSAbstractMethodDefinition" || diff --git a/src/language-js/utils.js b/src/language-js/utils.js index df1b02889..c7946bef7 100644 --- a/src/language-js/utils.js +++ b/src/language-js/utils.js @@ -708,8 +708,8 @@ function isStringPropSafeToUnquote(node, options) { // With `--strictPropertyInitialization`, TS treats properties with quoted names differently than unquoted ones. // See https://github.com/microsoft/TypeScript/pull/20075 !( - (options.parser === "typescript" || options.parser === "babel-ts") && - node.type === "ClassProperty" + (options.parser === "babel-ts" && node.type === "ClassProperty") || + (options.parser === "typescript" && node.type === "PropertyDefinition") )) || (isSimpleNumber(node.key.value) && String(Number(node.key.value)) === node.key.value && diff --git a/tests/format/misc/errors/typescript/__snapshots__/jsfmt.spec.js.snap b/tests/format/misc/errors/typescript/__snapshots__/jsfmt.spec.js.snap index bea1eaebd..703b695cd 100644 --- a/tests/format/misc/errors/typescript/__snapshots__/jsfmt.spec.js.snap +++ b/tests/format/misc/errors/typescript/__snapshots__/jsfmt.spec.js.snap @@ -89,3 +89,12 @@ exports[`snippet: #5 [typescript] format 1`] = ` > 1 | let x6 =