Skip to content

Commit 30bed2d

Browse files
committed
better errors for invalid js decorator syntax
1 parent 300eeb7 commit 30bed2d

3 files changed

Lines changed: 52 additions & 13 deletions

File tree

internal/js_parser/js_parser.go

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6713,6 +6713,10 @@ func (p *parser) parseDecorator() js_ast.Expr {
67136713

67146714
memberExpr := js_ast.Expr{Loc: nameRange.Loc, Data: &js_ast.EIdentifier{Ref: p.storeNameInRef(name)}}
67156715

6716+
// Custom error reporting for error recovery
6717+
var syntaxError logger.MsgData
6718+
wrapRange := nameRange
6719+
67166720
loop:
67176721
for {
67186722
switch p.lexer.Token {
@@ -6724,10 +6728,17 @@ loop:
67246728
if !p.options.ts.Parse {
67256729
p.lexer.Unexpected()
67266730
}
6731+
wrapRange.Len = p.lexer.Range().End() - wrapRange.Loc.Start
67276732
p.lexer.Next()
67286733

6729-
case js_lexer.TDot:
6734+
case js_lexer.TDot, js_lexer.TQuestionDot:
6735+
// The grammar for "DecoratorMemberExpression" currently forbids "?."
6736+
if p.lexer.Token == js_lexer.TQuestionDot && syntaxError.Location == nil {
6737+
syntaxError = p.tracker.MsgData(p.lexer.Range(), "JavaScript decorator syntax does not allow \"?.\" here")
6738+
}
6739+
67306740
p.lexer.Next()
6741+
wrapRange.Len = p.lexer.Range().End() - wrapRange.Loc.Start
67316742

67326743
if p.lexer.Token == js_lexer.TPrivateIdentifier {
67336744
name := p.lexer.Identifier
@@ -6746,10 +6757,6 @@ loop:
67466757
p.lexer.Expect(js_lexer.TIdentifier)
67476758
}
67486759

6749-
case js_lexer.TQuestionDot:
6750-
// The grammar for "DecoratorMemberExpression" currently forbids "?."
6751-
p.lexer.Expect(js_lexer.TDot)
6752-
67536760
case js_lexer.TOpenParen:
67546761
args, closeParenLoc, isMultiLine := p.parseCallArgs()
67556762
memberExpr.Data = &js_ast.ECall{
@@ -6759,6 +6766,15 @@ loop:
67596766
IsMultiLine: isMultiLine,
67606767
Kind: js_ast.TargetWasOriginallyPropertyAccess,
67616768
}
6769+
wrapRange.Len = closeParenLoc.Start + 1 - wrapRange.Loc.Start
6770+
6771+
// The grammar for "DecoratorCallExpression" currently forbids anything after it
6772+
if p.lexer.Token == js_lexer.TDot {
6773+
if syntaxError.Location == nil {
6774+
syntaxError = p.tracker.MsgData(p.lexer.Range(), "JavaScript decorator syntax does not allow \".\" after a call expression")
6775+
}
6776+
continue
6777+
}
67626778
break loop
67636779

67646780
default:
@@ -6770,6 +6786,21 @@ loop:
67706786
}
67716787
}
67726788

6789+
// Suggest that non-decorator expressions be wrapped in parentheses
6790+
if syntaxError.Location != nil {
6791+
var notes []logger.MsgData
6792+
if text := p.source.TextForRange(wrapRange); !strings.ContainsRune(text, '\n') {
6793+
note := p.tracker.MsgData(wrapRange, "Wrap this decorator in parentheses to allow arbitrary expressions:")
6794+
note.Location.Suggestion = fmt.Sprintf("(%s)", text)
6795+
notes = []logger.MsgData{note}
6796+
}
6797+
p.log.AddMsg(logger.Msg{
6798+
Kind: logger.Error,
6799+
Data: syntaxError,
6800+
Notes: notes,
6801+
})
6802+
}
6803+
67736804
return memberExpr
67746805
}
67756806

internal/js_parser/js_parser_test.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2028,15 +2028,19 @@ func TestDecorators(t *testing.T) {
20282028
expectPrinted(t, "class Foo { #x = @y.#x.y.#x class {} }", "class Foo {\n #x = @y.#x.y.#x class {\n };\n}\n")
20292029
expectParseError(t, "@123 class Foo {}", "<stdin>: ERROR: Expected identifier but found \"123\"\n")
20302030
expectParseError(t, "@x[y] class Foo {}", "<stdin>: ERROR: Expected \";\" but found \"class\"\n")
2031-
expectParseError(t, "@x?.() class Foo {}", "<stdin>: ERROR: Expected \".\" but found \"?.\"\n")
2032-
expectParseError(t, "@x?.y() class Foo {}", "<stdin>: ERROR: Expected \".\" but found \"?.\"\n")
2033-
expectParseError(t, "@x?.[y]() class Foo {}", "<stdin>: ERROR: Expected \".\" but found \"?.\"\n")
2031+
expectParseError(t, "@x?.() class Foo {}", "<stdin>: ERROR: Expected identifier but found \"(\"\n")
2032+
expectParseError(t, "@x?.y() class Foo {}",
2033+
"<stdin>: ERROR: JavaScript decorator syntax does not allow \"?.\" here\n"+
2034+
"<stdin>: NOTE: Wrap this decorator in parentheses to allow arbitrary expressions:\n")
2035+
expectParseError(t, "@x?.[y]() class Foo {}", "<stdin>: ERROR: Expected identifier but found \"[\"\n")
20342036
expectParseError(t, "@new Function() class Foo {}", "<stdin>: ERROR: Expected identifier but found \"new\"\n")
20352037
expectParseError(t, "@() => {} class Foo {}", "<stdin>: ERROR: Unexpected \")\"\n")
20362038
expectParseError(t, "x = @y function() {}", "<stdin>: ERROR: Expected \"class\" but found \"function\"\n")
20372039

20382040
// See: https://github.com/microsoft/TypeScript/issues/55336
2039-
expectParseError(t, "@x().y() class Foo {}", "<stdin>: ERROR: Unexpected \".\"\n")
2041+
expectParseError(t, "@x().y() class Foo {}",
2042+
"<stdin>: ERROR: JavaScript decorator syntax does not allow \".\" after a call expression\n"+
2043+
"<stdin>: NOTE: Wrap this decorator in parentheses to allow arbitrary expressions:\n")
20402044

20412045
errorText := "<stdin>: ERROR: Transforming JavaScript decorators to the configured target environment is not supported yet\n"
20422046
expectParseErrorWithUnsupportedFeatures(t, compat.Decorators, "@dec class Foo {}", errorText)

internal/js_parser/ts_parser_test.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2092,9 +2092,11 @@ func TestTSDecorators(t *testing.T) {
20922092
expectPrintedTS(t, "class Foo { #x = @y.#x.y.#x class {} }", "class Foo {\n #x = @y.#x.y.#x class {\n };\n}\n")
20932093
expectParseErrorTS(t, "@123 class Foo {}", "<stdin>: ERROR: Expected identifier but found \"123\"\n")
20942094
expectParseErrorTS(t, "@x[y] class Foo {}", "<stdin>: ERROR: Expected \";\" but found \"class\"\n")
2095-
expectParseErrorTS(t, "@x?.() class Foo {}", "<stdin>: ERROR: Expected \".\" but found \"?.\"\n")
2096-
expectParseErrorTS(t, "@x?.y() class Foo {}", "<stdin>: ERROR: Expected \".\" but found \"?.\"\n")
2097-
expectParseErrorTS(t, "@x?.[y]() class Foo {}", "<stdin>: ERROR: Expected \".\" but found \"?.\"\n")
2095+
expectParseErrorTS(t, "@x?.() class Foo {}", "<stdin>: ERROR: Expected identifier but found \"(\"\n")
2096+
expectParseErrorTS(t, "@x?.y() class Foo {}",
2097+
"<stdin>: ERROR: JavaScript decorator syntax does not allow \"?.\" here\n"+
2098+
"<stdin>: NOTE: Wrap this decorator in parentheses to allow arbitrary expressions:\n")
2099+
expectParseErrorTS(t, "@x?.[y]() class Foo {}", "<stdin>: ERROR: Expected identifier but found \"[\"\n")
20982100
expectParseErrorTS(t, "@new Function() class Foo {}", "<stdin>: ERROR: Expected identifier but found \"new\"\n")
20992101
expectParseErrorTS(t, "@() => {} class Foo {}", "<stdin>: ERROR: Unexpected \")\"\n")
21002102
expectParseErrorTS(t, "x = @y function() {}", "<stdin>: ERROR: Expected \"class\" but found \"function\"\n")
@@ -2107,7 +2109,9 @@ func TestTSDecorators(t *testing.T) {
21072109

21082110
// TypeScript 5.0+ allows this but Babel doesn't. I believe this is a bug
21092111
// with TypeScript: https://github.com/microsoft/TypeScript/issues/55336
2110-
expectParseErrorTS(t, "class Foo { @x<{}>().y<[], () => {}>() z: any }", "<stdin>: ERROR: Expected identifier but found \".\"\n")
2112+
expectParseErrorTS(t, "class Foo { @x<{}>().y<[], () => {}>() z: any }",
2113+
"<stdin>: ERROR: JavaScript decorator syntax does not allow \".\" after a call expression\n"+
2114+
"<stdin>: NOTE: Wrap this decorator in parentheses to allow arbitrary expressions:\n")
21112115

21122116
errorText := "<stdin>: ERROR: Transforming JavaScript decorators to the configured target environment is not supported yet\n"
21132117
expectParseErrorWithUnsupportedFeaturesTS(t, compat.Decorators, "@dec class Foo {}", errorText)

0 commit comments

Comments
 (0)