Report grammar errors for invalid decorator grammar (#57749)

This commit is contained in:
Ron Buckton
2024-03-13 14:50:04 -04:00
committed by GitHub
parent c1f0f7cb58
commit 5e8f900afa
34 changed files with 1553 additions and 50 deletions

View File

@@ -651,6 +651,7 @@ import {
isNewExpression,
isNodeDescendantOf,
isNonNullAccess,
isNonNullExpression,
isNullishCoalesce,
isNumericLiteral,
isNumericLiteralName,
@@ -41572,8 +41573,82 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
}
function checkGrammarDecorator(decorator: Decorator): boolean {
const sourceFile = getSourceFileOfNode(decorator);
if (!hasParseDiagnostics(sourceFile)) {
let node: Expression = decorator.expression;
// DecoratorParenthesizedExpression :
// `(` Expression `)`
if (isParenthesizedExpression(node)) {
return false;
}
let canHaveCallExpression = true;
let errorNode: Node | undefined;
while (true) {
// Allow TS syntax such as non-null assertions and instantiation expressions
if (isExpressionWithTypeArguments(node) || isNonNullExpression(node)) {
node = node.expression;
continue;
}
// DecoratorCallExpression :
// DecoratorMemberExpression Arguments
if (isCallExpression(node)) {
if (!canHaveCallExpression) {
errorNode = node;
}
if (node.questionDotToken) {
// Even if we already have an error node, error at the `?.` token since it appears earlier.
errorNode = node.questionDotToken;
}
node = node.expression;
canHaveCallExpression = false;
continue;
}
// DecoratorMemberExpression :
// IdentifierReference
// DecoratorMemberExpression `.` IdentifierName
// DecoratorMemberExpression `.` PrivateIdentifier
if (isPropertyAccessExpression(node)) {
if (node.questionDotToken) {
// Even if we already have an error node, error at the `?.` token since it appears earlier.
errorNode = node.questionDotToken;
}
node = node.expression;
canHaveCallExpression = false;
continue;
}
if (!isIdentifier(node)) {
// Even if we already have an error node, error at this node since it appears earlier.
errorNode = node;
}
break;
}
if (errorNode) {
addRelatedInfo(
error(decorator.expression, Diagnostics.Expression_must_be_enclosed_in_parentheses_to_be_used_as_a_decorator),
createDiagnosticForNode(errorNode, Diagnostics.Invalid_syntax_in_decorator),
);
return true;
}
}
return false;
}
/** Check a decorator */
function checkDecorator(node: Decorator): void {
checkGrammarDecorator(node);
const signature = getResolvedSignature(node);
checkDeprecatedSignature(signature, node);
const returnType = getReturnTypeOfSignature(signature);

View File

@@ -1637,6 +1637,14 @@
"category": "Error",
"code": 1496
},
"Expression must be enclosed in parentheses to be used as a decorator.": {
"category": "Error",
"code": 1497
},
"Invalid syntax in decorator.": {
"category": "Error",
"code": 1498
},
"The types of '{0}' are incompatible between these types.": {
"category": "Error",
@@ -7792,6 +7800,14 @@
"category": "Message",
"code": 95193
},
"Wrap in parentheses": {
"category": "Message",
"code": 95194
},
"Wrap all invalid decorator expressions in parentheses": {
"category": "Message",
"code": 95195
},
"No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": {
"category": "Error",

View File

@@ -64,6 +64,7 @@ export * from "../codefixes/useDefaultImport";
export * from "../codefixes/useBigintLiteral";
export * from "../codefixes/fixAddModuleReferTypeMissingTypeof";
export * from "../codefixes/wrapJsxInFragment";
export * from "../codefixes/wrapDecoratorInParentheses";
export * from "../codefixes/convertToMappedObjectType";
export * from "../codefixes/removeAccidentalCallParentheses";
export * from "../codefixes/removeUnnecessaryAwait";

View File

@@ -0,0 +1,35 @@
import {
Debug,
Diagnostics,
factory,
findAncestor,
getTokenAtPosition,
isDecorator,
SourceFile,
textChanges,
} from "../_namespaces/ts";
import {
codeFixAll,
createCodeFixAction,
registerCodeFix,
} from "../_namespaces/ts.codefix";
const fixId = "wrapDecoratorInParentheses";
const errorCodes = [Diagnostics.Expression_must_be_enclosed_in_parentheses_to_be_used_as_a_decorator.code];
registerCodeFix({
errorCodes,
getCodeActions: function getCodeActionsToWrapDecoratorExpressionInParentheses(context) {
const changes = textChanges.ChangeTracker.with(context, t => makeChange(t, context.sourceFile, context.span.start));
return [createCodeFixAction(fixId, changes, Diagnostics.Wrap_in_parentheses, fixId, Diagnostics.Wrap_all_invalid_decorator_expressions_in_parentheses)];
},
fixIds: [fixId],
getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => makeChange(changes, diag.file, diag.start)),
});
function makeChange(changeTracker: textChanges.ChangeTracker, sourceFile: SourceFile, pos: number) {
const token = getTokenAtPosition(sourceFile, pos);
const decorator = findAncestor(token, isDecorator)!;
Debug.assert(!!decorator, "Expected position to be owned by a decorator.");
const replacement = factory.createParenthesizedExpression(decorator.expression);
changeTracker.replaceNode(sourceFile, decorator.expression, replacement);
}

View File

@@ -128,42 +128,43 @@ export class CompilerBaselineRunner extends RunnerBase {
class CompilerTest {
private static varyBy: readonly string[] = [
"module",
"moduleResolution",
"moduleDetection",
"allowArbitraryExtensions",
"allowImportingTsExtensions",
"target",
"jsx",
"noEmit",
"removeComments",
"importHelpers",
"importHelpers",
"downlevelIteration",
"isolatedModules",
"verbatimModuleSyntax",
"strict",
"noImplicitAny",
"strictNullChecks",
"strictFunctionTypes",
"strictBindCallApply",
"strictPropertyInitialization",
"noImplicitThis",
"alwaysStrict",
"allowSyntheticDefaultImports",
"esModuleInterop",
"alwaysStrict",
"downlevelIteration",
"experimentalDecorators",
"emitDecoratorMetadata",
"skipDefaultLibCheck",
"preserveConstEnums",
"skipLibCheck",
"esModuleInterop",
"exactOptionalPropertyTypes",
"useDefineForClassFields",
"useUnknownInCatchVariables",
"noUncheckedIndexedAccess",
"importHelpers",
"importHelpers",
"isolatedModules",
"jsx",
"module",
"moduleDetection",
"moduleResolution",
"noEmit",
"noImplicitAny",
"noImplicitThis",
"noPropertyAccessFromIndexSignature",
"noUncheckedIndexedAccess",
"preserveConstEnums",
"removeComments",
"resolveJsonModule",
"resolvePackageJsonExports",
"resolvePackageJsonImports",
"resolveJsonModule",
"allowArbitraryExtensions",
"skipDefaultLibCheck",
"skipLibCheck",
"strict",
"strictBindCallApply",
"strictFunctionTypes",
"strictNullChecks",
"strictPropertyInitialization",
"target",
"useDefineForClassFields",
"useUnknownInCatchVariables",
"verbatimModuleSyntax",
];
private fileName: string;
private justName: string;