TypeScript: Throw errors for 'declare' in accessors (#13770)
This commit is contained in:
1 parent
68a13957f4
commit
a66236a3e1
6 files changed
+119
-42
No files matched your search
@@ -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" &&
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
class Foo {
|
||||
declare get foo()
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
class Foo {
|
||||
declare set foo(v)
|
||||
}
|
||||
Reference in new issue
Block a user