diff --git a/src/language-js/parse/postprocess/index.js b/src/language-js/parse/postprocess/index.js index c4e421d6b..93b6163be 100644 --- a/src/language-js/parse/postprocess/index.js +++ b/src/language-js/parse/postprocess/index.js @@ -5,18 +5,9 @@ import isNonEmptyArray from "../../../utils/is-non-empty-array.js"; import isBlockComment from "../../utils/is-block-comment.js"; import isIndentableBlockComment from "../../utils/is-indentable-block-comment.js"; import visitNode from "./visit-node.js"; -import { throwErrorForInvalidNodes } from "./typescript.js"; import throwSyntaxError from "./throw-ts-syntax-error.js"; function postprocess(ast, options) { - 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. if ( options.parser !== "typescript" && diff --git a/src/language-js/parse/postprocess/typescript.js b/src/language-js/parse/postprocess/typescript.js index 2d9ffb3c2..1f86d936b 100644 --- a/src/language-js/parse/postprocess/typescript.js +++ b/src/language-js/parse/postprocess/typescript.js @@ -2,9 +2,28 @@ import isNonEmptyArray from "../../../utils/is-non-empty-array.js"; import visitNode from "./visit-node.js"; import throwTsSyntaxError from "./throw-ts-syntax-error.js"; +// Taken from `typescript` package +const SyntaxKind = { + AbstractKeyword: 126, + SourceFile: 305, + DeclareKeyword: 135, + PropertyDeclaration: 167, +}; + +function getTsNodeLocation(nodeOrToken) { + const sourceFile = getSourceFileOfNode(nodeOrToken); + const [start, end] = [nodeOrToken.pos, nodeOrToken.end].map((position) => { + const { line, character: column } = + sourceFile.getLineAndCharacterOfPosition(position); + return { line: line + 1, column }; + }); + + return { start, end }; +} + // Copied from https://unpkg.com/typescript@4.8.2/lib/typescript.js function getSourceFileOfNode(node) { - while (node && node.kind !== 305 /* SyntaxKind.SourceFile */) { + while (node && node.kind !== SyntaxKind.SourceFile) { node = node.parent; } return node; @@ -21,53 +40,91 @@ function throwErrorForInvalidDecorator(tsNode) { const [{ expression }] = illegalDecorators; - const sourceFile = getSourceFileOfNode(expression); - const [start, end] = [expression.pos, expression.end].map((position) => { - const { line, character: column } = - sourceFile.getLineAndCharacterOfPosition(position); - return { line: line + 1, column }; - }); - - throwTsSyntaxError({ loc: { start, end } }, "Decorators are not valid here."); + throwTsSyntaxError( + { loc: getTsNodeLocation(expression) }, + "Decorators are not valid here." + ); } // 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 = 167; - 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 - )) + !( + tsNode.kind === SyntaxKind.PropertyDeclaration && + tsNode.initializer && + esTreeNode.value === null && + tsNode.modifiers?.some( + (modifier) => modifier.kind === SyntaxKind.AbstractKeyword + ) + ) ) { return; } - if (tsNode.initializer && esTreeNode.value === null) { - throwTsSyntaxError( - esTreeNode, - "Abstract property cannot have an initializer" - ); - } + + throwTsSyntaxError( + esTreeNode, + "Abstract property cannot have an initializer" + ); } -function throwErrorForInvalidNodes(ast, options) { - const { esTreeNodeToTSNodeMap, tsNodeToESTreeNodeMap } = - options.tsParseResult; +function throwErrorForInvalidDeclare(tsNode, esTreeNode) { + if ( + !( + esTreeNode.type === "MethodDefinition" && + (esTreeNode.kind === "get" || esTreeNode.kind === "set") + ) + ) { + return; + } - visitNode(ast, (node) => { - const tsNode = esTreeNodeToTSNodeMap.get(node); + const declareKeyword = tsNode.modifiers?.find( + (modifier) => modifier.kind === SyntaxKind.DeclareKeyword + ); + + if (!declareKeyword) { + return; + } + + throwTsSyntaxError( + { loc: getTsNodeLocation(declareKeyword) }, + /* cspell:disable-next-line */ + `'declare' is not allowed in ${esTreeNode.kind}ters.` + ); +} + +function getTsNode(node, tsParseResult) { + const { esTreeNodeToTSNodeMap, tsNodeToESTreeNodeMap } = tsParseResult; + const tsNode = esTreeNodeToTSNodeMap.get(node); + if (!tsNode) { + return; + } + + const esTreeNode = tsNodeToESTreeNodeMap.get(tsNode); + if (esTreeNode !== node) { + return; + } + + return tsNode; +} + +function throwErrorForInvalidNodes(tsParseResult, options) { + if ( + // decorators + // abstract properties + // declare in accessor + !/@|abstract|declare/.test(options.originalText) + ) { + return; + } + + visitNode(tsParseResult.ast, (esTreeNode) => { + const tsNode = getTsNode(esTreeNode, tsParseResult); if (!tsNode) { return; } - const esTreeNode = tsNodeToESTreeNodeMap.get(tsNode); - if (esTreeNode !== node) { - return; - } - + throwErrorForInvalidDeclare(tsNode, esTreeNode); throwErrorForInvalidDecorator(tsNode); throwErrorForInvalidAbstractProperty(tsNode, esTreeNode); }); diff --git a/src/language-js/parse/typescript.js b/src/language-js/parse/typescript.js index 058807696..2bc351280 100644 --- a/src/language-js/parse/typescript.js +++ b/src/language-js/parse/typescript.js @@ -3,6 +3,7 @@ import tryCombinations from "../../utils/try-combinations.js"; import createParser from "./utils/create-parser.js"; import replaceHashbang from "./utils/replace-hashbang.js"; import postprocess from "./postprocess/index.js"; +import { throwErrorForInvalidNodes } from "./postprocess/typescript.js"; /** @type {import("@typescript-eslint/typescript-estree").TSESTreeOptions} */ const parseOptions = { @@ -55,7 +56,9 @@ async function parse(text, options = {}) { } options.originalText = text; - options.tsParseResult = result; + + throwErrorForInvalidNodes(result, options); + return postprocess(result.ast, options); } 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 e5586b5f9..5a1c8a384 100644 --- a/tests/format/misc/errors/typescript/__snapshots__/jsfmt.spec.js.snap +++ b/tests/format/misc/errors/typescript/__snapshots__/jsfmt.spec.js.snap @@ -1,5 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`declare-getter.ts [typescript] format 1`] = ` +"'declare' is not allowed in getters. (1:12) +> 1 | class Foo { + | ^ +> 2 | declare get foo() + | ^^^^^^^^^^ + 3 | } + 4 |" +`; + +exports[`declare-setter.ts [typescript] format 1`] = ` +"'declare' is not allowed in setters. (1:12) +> 1 | class Foo { + | ^ +> 2 | declare set foo(v) + | ^^^^^^^^^^ + 3 | } + 4 |" +`; + exports[`default-escaped.ts [typescript] format 1`] = ` "Declaration or statement expected. (1:1) > 1 | export asyn\\u{63} from "async"; diff --git a/tests/format/misc/errors/typescript/declare-getter.ts b/tests/format/misc/errors/typescript/declare-getter.ts new file mode 100644 index 000000000..4f346f403 --- /dev/null +++ b/tests/format/misc/errors/typescript/declare-getter.ts @@ -0,0 +1,3 @@ +class Foo { + declare get foo() +} diff --git a/tests/format/misc/errors/typescript/declare-setter.ts b/tests/format/misc/errors/typescript/declare-setter.ts new file mode 100644 index 000000000..8ff17fdc7 --- /dev/null +++ b/tests/format/misc/errors/typescript/declare-setter.ts @@ -0,0 +1,3 @@ +class Foo { + declare set foo(v) +}