Skip to content

Commit 8c6c39a

Browse files
committed
improve the error message for generic arrow in tsx
1 parent 2c2ef24 commit 8c6c39a

File tree

3 files changed

+58
-18
lines changed

3 files changed

+58
-18
lines changed

internal/js_lexer/js_lexer.go

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ type Lexer struct {
253253
JSXFactoryPragmaComment logger.Span
254254
JSXFragmentPragmaComment logger.Span
255255
SourceMappingURL logger.Span
256+
BadArrowInTSXSuggestion string
256257

257258
// Escape sequences in string literals are decoded lazily because they are
258259
// not interpreted inside tagged templates, and tagged templates can contain
@@ -270,6 +271,8 @@ type Lexer struct {
270271
start int
271272
end int
272273
ApproximateNewlineCount int
274+
CouldBeBadArrowInTSX int
275+
BadArrowInTSXRange logger.Range
273276
LegacyOctalLoc logger.Loc
274277
AwaitKeywordLoc logger.Loc
275278
FnOrArrowStartLoc logger.Loc
@@ -945,23 +948,36 @@ func (lexer *Lexer) NextJSXElementChild() {
945948
} else {
946949
replacement = "{'>'}"
947950
}
948-
msg := logger.Msg{Kind: logger.Error, Data: lexer.tracker.MsgData(logger.Range{Loc: logger.Loc{Start: int32(lexer.end)}, Len: 1},
949-
fmt.Sprintf("The character \"%c\" is not valid inside a JSX element", lexer.codePoint)),
950-
Notes: []logger.MsgData{{Text: fmt.Sprintf("Did you mean to escape it as %q instead?", replacement)}}}
951-
msg.Data.Location.Suggestion = replacement
952-
if !lexer.ts.Parse {
953-
// TypeScript treats this as an error but Babel doesn't treat this
954-
// as an error yet, so allow this in JS for now. Babel version 8
955-
// was supposed to be released in 2021 but was never released. If
956-
// it's released in the future, this can be changed to an error too.
957-
//
958-
// More context:
959-
// * TypeScript change: https://github.com/microsoft/TypeScript/issues/36341
960-
// * Babel 8 change: https://github.com/babel/babel/issues/11042
961-
// * Babel 8 release: https://github.com/babel/babel/issues/10746
962-
//
963-
msg.Kind = logger.Warning
951+
msg := logger.Msg{
952+
Kind: logger.Error,
953+
Data: lexer.tracker.MsgData(logger.Range{Loc: logger.Loc{Start: int32(lexer.end)}, Len: 1},
954+
fmt.Sprintf("The character \"%c\" is not valid inside a JSX element", lexer.codePoint)),
964955
}
956+
957+
// Attempt to provide a better error message if this looks like an arrow function
958+
if lexer.CouldBeBadArrowInTSX > 0 && lexer.codePoint == '>' && lexer.source.Contents[lexer.end-1] == '=' {
959+
msg.Notes = []logger.MsgData{lexer.tracker.MsgData(lexer.BadArrowInTSXRange,
960+
"TypeScript's TSX syntax interprets arrow functions with a single generic type parameter as an opening JSX element. "+
961+
"If you want it to be interpreted as an arrow function instead, you need to add a trailing comma after the type parameter to disambiguate:")}
962+
msg.Notes[0].Location.Suggestion = lexer.BadArrowInTSXSuggestion
963+
} else {
964+
msg.Notes = []logger.MsgData{{Text: fmt.Sprintf("Did you mean to escape it as %q instead?", replacement)}}
965+
msg.Data.Location.Suggestion = replacement
966+
if !lexer.ts.Parse {
967+
// TypeScript treats this as an error but Babel doesn't treat this
968+
// as an error yet, so allow this in JS for now. Babel version 8
969+
// was supposed to be released in 2021 but was never released. If
970+
// it's released in the future, this can be changed to an error too.
971+
//
972+
// More context:
973+
// * TypeScript change: https://github.com/microsoft/TypeScript/issues/36341
974+
// * Babel 8 change: https://github.com/babel/babel/issues/11042
975+
// * Babel 8 release: https://github.com/babel/babel/issues/10746
976+
//
977+
msg.Kind = logger.Warning
978+
}
979+
}
980+
965981
lexer.log.AddMsg(msg)
966982
lexer.step()
967983

internal/js_parser/js_parser.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4597,6 +4597,24 @@ func (p *parser) parseJSXElement(loc logger.Loc) js_ast.Expr {
45974597
}}
45984598
}
45994599

4600+
// Attempt to provide a better error message for people incorrectly trying to
4601+
// use arrow functions in TSX (which doesn't work because they are JSX elements)
4602+
if p.options.ts.Parse && len(properties) == 0 && startText != "" && p.lexer.Token == js_lexer.TGreaterThan &&
4603+
strings.HasPrefix(p.source.Contents[p.lexer.Loc().Start:], ">(") {
4604+
badArrowInTSXRange := p.lexer.BadArrowInTSXRange
4605+
badArrowInTSXSuggestion := p.lexer.BadArrowInTSXSuggestion
4606+
4607+
p.lexer.CouldBeBadArrowInTSX++
4608+
p.lexer.BadArrowInTSXRange = logger.Range{Loc: loc, Len: p.lexer.Range().End() - loc.Start}
4609+
p.lexer.BadArrowInTSXSuggestion = fmt.Sprintf("<%s,>", startText)
4610+
4611+
defer func() {
4612+
p.lexer.CouldBeBadArrowInTSX--
4613+
p.lexer.BadArrowInTSXRange = badArrowInTSXRange
4614+
p.lexer.BadArrowInTSXSuggestion = badArrowInTSXSuggestion
4615+
}()
4616+
}
4617+
46004618
// Use ExpectJSXElementChild() so we parse child strings
46014619
p.lexer.ExpectJSXElementChild(js_lexer.TGreaterThan)
46024620

internal/js_parser/ts_parser_test.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2292,7 +2292,11 @@ func TestTSJSX(t *testing.T) {
22922292
expectPrintedTS(t, "const x = <[]>(y, z) => {}", "const x = (y, z) => {\n};\n")
22932293

22942294
invalid := "<stdin>: ERROR: The character \">\" is not valid inside a JSX element\nNOTE: Did you mean to escape it as \"{'>'}\" instead?\n"
2295-
expectParseErrorTSX(t, "(<T>(y) => {}</T>)", invalid)
2295+
invalidWithHint := "<stdin>: ERROR: The character \">\" is not valid inside a JSX element\n<stdin>: NOTE: TypeScript's TSX syntax interprets " +
2296+
"arrow functions with a single generic type parameter as an opening JSX element. If you want it to be interpreted as an arrow function instead, " +
2297+
"you need to add a trailing comma after the type parameter to disambiguate:\n"
2298+
expectParseErrorTSX(t, "(<T>(y) => {}</T>)", invalidWithHint)
2299+
expectParseErrorTSX(t, "(<T>(x: X<Y>) => {}</Y></T>)", invalidWithHint)
22962300
expectParseErrorTSX(t, "(<T extends>(y) => {}</T>)", invalid)
22972301
expectParseErrorTSX(t, "(<T extends={false}>(y) => {}</T>)", invalid)
22982302
expectPrintedTSX(t, "(<T = X>(y) => {})", "(y) => {\n};\n")
@@ -2301,7 +2305,9 @@ func TestTSJSX(t *testing.T) {
23012305
expectPrintedTSX(t, "(<T,>() => {})", "() => {\n};\n")
23022306
expectPrintedTSX(t, "(<T, X>(y) => {})", "(y) => {\n};\n")
23032307
expectPrintedTSX(t, "(<T, X>(y): (() => {}) => {})", "(y) => {\n};\n")
2304-
expectParseErrorTSX(t, "(<T>() => {})", invalid+"<stdin>: ERROR: Unexpected end of file\n")
2308+
expectParseErrorTSX(t, "(<T>() => {})", invalidWithHint+"<stdin>: ERROR: Unexpected end of file\n")
2309+
expectParseErrorTSX(t, "(<T>(x: X<Y>) => {})", invalidWithHint+"<stdin>: ERROR: Unexpected end of file\n")
2310+
expectParseErrorTSX(t, "(<T>(x: X<Y>) => {})</Y>", invalidWithHint+"<stdin>: ERROR: Unexpected end of file\n")
23052311
expectParseErrorTSX(t, "(<[]>(y))", "<stdin>: ERROR: Expected identifier but found \"[\"\n")
23062312
expectParseErrorTSX(t, "(<T[]>(y))", "<stdin>: ERROR: Expected \">\" but found \"[\"\n")
23072313
expectParseErrorTSX(t, "(<T = X>(y))", "<stdin>: ERROR: Expected \"=>\" but found \")\"\n")

0 commit comments

Comments
 (0)