Skip to content

Commit

Permalink
ts: allow non-null assertions in js decorators
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Mar 14, 2024
1 parent 4d997d9 commit 300eeb7
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 38 deletions.
86 changes: 48 additions & 38 deletions internal/js_parser/js_parser.go
Expand Up @@ -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<y>() 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<z>() 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<y>"
// "@x.y<z>"
if !p.skipTypeScriptTypeArguments(skipTypeScriptTypeArgumentsOpts{}) {
break loop
}
}
}

Expand Down
3 changes: 3 additions & 0 deletions internal/js_parser/js_parser_test.go
Expand Up @@ -2064,6 +2064,9 @@ func TestDecorators(t *testing.T) {
expectParseError(t, "@x export @y class Foo {}", "<stdin>: ERROR: Decorators are not valid here\n")
expectParseError(t, "@x export default abstract", "<stdin>: ERROR: Decorators are not valid here\n")
expectParseError(t, "@x export @y default class {}", "<stdin>: ERROR: Decorators are not valid here\n<stdin>: ERROR: Unexpected \"default\"\n")

// Disallow TypeScript syntax in JavaScript
expectParseError(t, "@x!.y!.z class Foo {}", "<stdin>: ERROR: Unexpected \"!\"\n")
}

func TestGenerator(t *testing.T) {
Expand Down
8 changes: 8 additions & 0 deletions internal/js_parser/ts_parser_test.go
Expand Up @@ -2051,6 +2051,10 @@ func TestTSExperimentalDecorator(t *testing.T) {
expectParseErrorExperimentalDecoratorTS(t, "@x export default abstract", "<stdin>: ERROR: Decorators are not valid here\n")
expectParseErrorExperimentalDecoratorTS(t, "@x export @y default class {}", "<stdin>: ERROR: Decorators are not valid here\n<stdin>: 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")
Expand Down Expand Up @@ -2132,6 +2136,10 @@ func TestTSDecorators(t *testing.T) {
expectParseErrorTS(t, "@x export default abstract", "<stdin>: ERROR: Decorators are not valid here\n")
expectParseErrorTS(t, "@x export @y default class {}", "<stdin>: ERROR: Decorators are not valid here\n<stdin>: 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 }",
"<stdin>: ERROR: Decorators are not valid here\n")
Expand Down

0 comments on commit 300eeb7

Please sign in to comment.