diff --git a/internal/js_parser/js_parser.go b/internal/js_parser/js_parser.go index d45298ca97..547955424b 100644 --- a/internal/js_parser/js_parser.go +++ b/internal/js_parser/js_parser.go @@ -6713,50 +6713,60 @@ func (p *parser) parseDecorator() js_ast.Expr { memberExpr := js_ast.Expr{Loc: nameRange.Loc, Data: &js_ast.EIdentifier{Ref: p.storeNameInRef(name)}} - // "@x() class{}" - if p.options.ts.Parse { - p.skipTypeScriptTypeArguments(skipTypeScriptTypeArgumentsOpts{}) - } - - for p.lexer.Token == js_lexer.TDot { - p.lexer.Next() - - if p.lexer.Token == js_lexer.TPrivateIdentifier { - name := p.lexer.Identifier - memberExpr.Data = &js_ast.EIndex{ - Target: memberExpr, - Index: js_ast.Expr{Loc: p.lexer.Loc(), Data: &js_ast.EPrivateIdentifier{Ref: p.storeNameInRef(name)}}, +loop: + for { + switch p.lexer.Token { + case js_lexer.TExclamation: + // Skip over TypeScript non-null assertions + if p.lexer.HasNewlineBefore { + break loop + } + if !p.options.ts.Parse { + p.lexer.Unexpected() } - p.reportPrivateNameUsage(name.String) p.lexer.Next() - } else { - memberExpr.Data = &js_ast.EDot{ - Target: memberExpr, - Name: p.lexer.Identifier.String, - NameLoc: p.lexer.Loc(), + + case js_lexer.TDot: + p.lexer.Next() + + if p.lexer.Token == js_lexer.TPrivateIdentifier { + name := p.lexer.Identifier + memberExpr.Data = &js_ast.EIndex{ + Target: memberExpr, + Index: js_ast.Expr{Loc: p.lexer.Loc(), Data: &js_ast.EPrivateIdentifier{Ref: p.storeNameInRef(name)}}, + } + p.reportPrivateNameUsage(name.String) + p.lexer.Next() + } else { + memberExpr.Data = &js_ast.EDot{ + Target: memberExpr, + Name: p.lexer.Identifier.String, + NameLoc: p.lexer.Loc(), + } + p.lexer.Expect(js_lexer.TIdentifier) } - p.lexer.Expect(js_lexer.TIdentifier) - } - // "@x.y() class{}" - if p.options.ts.Parse { - p.skipTypeScriptTypeArguments(skipTypeScriptTypeArgumentsOpts{}) - } - } + case js_lexer.TQuestionDot: + // The grammar for "DecoratorMemberExpression" currently forbids "?." + p.lexer.Expect(js_lexer.TDot) - // The grammar for "DecoratorMemberExpression" currently forbids "?." - if p.lexer.Token == js_lexer.TQuestionDot { - p.lexer.Expect(js_lexer.TDot) - } + case js_lexer.TOpenParen: + args, closeParenLoc, isMultiLine := p.parseCallArgs() + memberExpr.Data = &js_ast.ECall{ + Target: memberExpr, + Args: args, + CloseParenLoc: closeParenLoc, + IsMultiLine: isMultiLine, + Kind: js_ast.TargetWasOriginallyPropertyAccess, + } + break loop - if p.lexer.Token == js_lexer.TOpenParen { - args, closeParenLoc, isMultiLine := p.parseCallArgs() - memberExpr.Data = &js_ast.ECall{ - Target: memberExpr, - Args: args, - CloseParenLoc: closeParenLoc, - IsMultiLine: isMultiLine, - Kind: js_ast.TargetWasOriginallyPropertyAccess, + default: + // "@x" + // "@x.y" + if !p.skipTypeScriptTypeArguments(skipTypeScriptTypeArgumentsOpts{}) { + break loop + } } } diff --git a/internal/js_parser/js_parser_test.go b/internal/js_parser/js_parser_test.go index 06507a0ea6..250fde4ff8 100644 --- a/internal/js_parser/js_parser_test.go +++ b/internal/js_parser/js_parser_test.go @@ -2064,6 +2064,9 @@ func TestDecorators(t *testing.T) { expectParseError(t, "@x export @y class Foo {}", ": ERROR: Decorators are not valid here\n") expectParseError(t, "@x export default abstract", ": ERROR: Decorators are not valid here\n") expectParseError(t, "@x export @y default class {}", ": ERROR: Decorators are not valid here\n: ERROR: Unexpected \"default\"\n") + + // Disallow TypeScript syntax in JavaScript + expectParseError(t, "@x!.y!.z class Foo {}", ": ERROR: Unexpected \"!\"\n") } func TestGenerator(t *testing.T) { diff --git a/internal/js_parser/ts_parser_test.go b/internal/js_parser/ts_parser_test.go index e02feae126..148e560baa 100644 --- a/internal/js_parser/ts_parser_test.go +++ b/internal/js_parser/ts_parser_test.go @@ -2051,6 +2051,10 @@ func TestTSExperimentalDecorator(t *testing.T) { expectParseErrorExperimentalDecoratorTS(t, "@x export default abstract", ": ERROR: Decorators are not valid here\n") expectParseErrorExperimentalDecoratorTS(t, "@x export @y default class {}", ": ERROR: Decorators are not valid here\n: ERROR: Unexpected \"default\"\n") + // From the TypeScript team: "We do allow postfix ! because it's TypeScript only." + // https://github.com/microsoft/TypeScript/issues/57756 + expectPrintedExperimentalDecoratorTS(t, "@x!.y!.z class Foo {}", "let Foo = class {\n};\nFoo = __decorateClass([\n x.y.z\n], Foo);\n") + // TypeScript experimental decorators are actually allowed on declared and abstract fields expectPrintedExperimentalDecoratorTS(t, "class Foo { @(() => {}) declare foo: any; @(() => {}) bar: any }", "class Foo {\n bar;\n}\n__decorateClass([\n () => {\n }\n], Foo.prototype, \"foo\", 2);\n__decorateClass([\n () => {\n }\n], Foo.prototype, \"bar\", 2);\n") @@ -2132,6 +2136,10 @@ func TestTSDecorators(t *testing.T) { expectParseErrorTS(t, "@x export default abstract", ": ERROR: Decorators are not valid here\n") expectParseErrorTS(t, "@x export @y default class {}", ": ERROR: Decorators are not valid here\n: ERROR: Unexpected \"default\"\n") + // From the TypeScript team: "We do allow postfix ! because it's TypeScript only." + // https://github.com/microsoft/TypeScript/issues/57756 + expectPrintedTS(t, "@x!.y!.z class Foo {}", "@x.y.z class Foo {\n}\n") + // JavaScript decorators are not allowed on declared or abstract fields expectParseErrorTS(t, "class Foo { @(() => {}) declare foo: any; @(() => {}) bar: any }", ": ERROR: Decorators are not valid here\n")