Skip to content

Commit

Permalink
Report grammar errors for invalid decorator grammar (#57749)
Browse files Browse the repository at this point in the history
  • Loading branch information
rbuckton committed Mar 13, 2024
1 parent c1f0f7c commit 5e8f900
Show file tree
Hide file tree
Showing 34 changed files with 1,550 additions and 47 deletions.
75 changes: 75 additions & 0 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,7 @@ import {
isNewExpression,
isNodeDescendantOf,
isNonNullAccess,
isNonNullExpression,
isNullishCoalesce,
isNumericLiteral,
isNumericLiteralName,
Expand Down Expand Up @@ -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);
Expand Down
16 changes: 16 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions src/services/_namespaces/ts.codefix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
35 changes: 35 additions & 0 deletions src/services/codefixes/wrapDecoratorInParentheses.ts
Original file line number Diff line number Diff line change
@@ -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);
}
53 changes: 27 additions & 26 deletions src/testRunner/compilerRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
"allowSyntheticDefaultImports",
"alwaysStrict",
"downlevelIteration",
"experimentalDecorators",
"emitDecoratorMetadata",
"esModuleInterop",
"exactOptionalPropertyTypes",
"importHelpers",
"importHelpers",
"downlevelIteration",
"isolatedModules",
"verbatimModuleSyntax",
"strict",
"jsx",
"module",
"moduleDetection",
"moduleResolution",
"noEmit",
"noImplicitAny",
"strictNullChecks",
"strictFunctionTypes",
"strictBindCallApply",
"strictPropertyInitialization",
"noImplicitThis",
"alwaysStrict",
"allowSyntheticDefaultImports",
"esModuleInterop",
"emitDecoratorMetadata",
"skipDefaultLibCheck",
"noPropertyAccessFromIndexSignature",
"noUncheckedIndexedAccess",
"preserveConstEnums",
"removeComments",
"resolveJsonModule",
"resolvePackageJsonExports",
"resolvePackageJsonImports",
"skipDefaultLibCheck",
"skipLibCheck",
"exactOptionalPropertyTypes",
"strict",
"strictBindCallApply",
"strictFunctionTypes",
"strictNullChecks",
"strictPropertyInitialization",
"target",
"useDefineForClassFields",
"useUnknownInCatchVariables",
"noUncheckedIndexedAccess",
"noPropertyAccessFromIndexSignature",
"resolvePackageJsonExports",
"resolvePackageJsonImports",
"resolveJsonModule",
"allowArbitraryExtensions",
"verbatimModuleSyntax",
];
private fileName: string;
private justName: string;
Expand Down
6 changes: 3 additions & 3 deletions tests/baselines/reference/decoratorOnClassMethod11.errors.txt
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
decoratorOnClassMethod11.ts(5,10): error TS2331: 'this' cannot be referenced in a module or namespace body.
decoratorOnClassMethod11.ts(5,11): error TS2331: 'this' cannot be referenced in a module or namespace body.


==== decoratorOnClassMethod11.ts (1 errors) ====
module M {
class C {
decorator(target: Object, key: string): void { }

@this.decorator
~~~~
@(this.decorator)
~~~~
!!! error TS2331: 'this' cannot be referenced in a module or namespace body.
method() { }
}
Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/decoratorOnClassMethod11.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ module M {
class C {
decorator(target: Object, key: string): void { }

@this.decorator
@(this.decorator)
method() { }
}
}
Expand All @@ -25,7 +25,7 @@ var M;
C.prototype.decorator = function (target, key) { };
C.prototype.method = function () { };
__decorate([
this.decorator
(this.decorator)
], C.prototype, "method", null);
return C;
}());
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/decoratorOnClassMethod11.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ module M {
>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>key : Symbol(key, Decl(decoratorOnClassMethod11.ts, 2, 33))

@this.decorator
@(this.decorator)
method() { }
>method : Symbol(C.method, Decl(decoratorOnClassMethod11.ts, 2, 56))
}
Expand Down
3 changes: 2 additions & 1 deletion tests/baselines/reference/decoratorOnClassMethod11.types
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ module M {
>target : Object
>key : string

@this.decorator
@(this.decorator)
>(this.decorator) : any
>this.decorator : any
>this : any
>decorator : any
Expand Down
6 changes: 3 additions & 3 deletions tests/baselines/reference/decoratorOnClassMethod12.errors.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
decoratorOnClassMethod12.ts(6,10): error TS2660: 'super' can only be referenced in members of derived classes or object literal expressions.
decoratorOnClassMethod12.ts(6,11): error TS2660: 'super' can only be referenced in members of derived classes or object literal expressions.


==== decoratorOnClassMethod12.ts (1 errors) ====
Expand All @@ -7,8 +7,8 @@ decoratorOnClassMethod12.ts(6,10): error TS2660: 'super' can only be referenced
decorator(target: Object, key: string): void { }
}
class C extends S {
@super.decorator
~~~~~
@(super.decorator)
~~~~~
!!! error TS2660: 'super' can only be referenced in members of derived classes or object literal expressions.
method() { }
}
Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/decoratorOnClassMethod12.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ module M {
decorator(target: Object, key: string): void { }
}
class C extends S {
@super.decorator
@(super.decorator)
method() { }
}
}
Expand Down Expand Up @@ -48,7 +48,7 @@ var M;
}
C.prototype.method = function () { };
__decorate([
_super.decorator
(_super.decorator)
], C.prototype, "method", null);
return C;
}(S));
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/decoratorOnClassMethod12.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ module M {
>C : Symbol(C, Decl(decoratorOnClassMethod12.ts, 3, 5))
>S : Symbol(S, Decl(decoratorOnClassMethod12.ts, 0, 10))

@super.decorator
@(super.decorator)
method() { }
>method : Symbol(C.method, Decl(decoratorOnClassMethod12.ts, 4, 23))
}
Expand Down
3 changes: 2 additions & 1 deletion tests/baselines/reference/decoratorOnClassMethod12.types
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ module M {
>C : C
>S : S

@super.decorator
@(super.decorator)
>(super.decorator) : any
>super.decorator : any
>super : any
>decorator : any
Expand Down
Loading

0 comments on commit 5e8f900

Please sign in to comment.