From 362cae86f435fd9dc823e6f355fcfa4ec92cb5b3 Mon Sep 17 00:00:00 2001 From: Evan Wallace Date: Tue, 31 Jan 2023 22:24:51 -0500 Subject: [PATCH] better JSX unexpected EOF error message --- internal/js_lexer/js_lexer.go | 10 +++------- internal/js_parser/js_parser.go | 14 ++++++++++++-- internal/js_parser/js_parser_test.go | 12 ++++++------ internal/js_parser/ts_parser_test.go | 27 +++++++++++++-------------- 4 files changed, 34 insertions(+), 29 deletions(-) diff --git a/internal/js_lexer/js_lexer.go b/internal/js_lexer/js_lexer.go index 87fb1ff8f2a..b40cc7030b4 100644 --- a/internal/js_lexer/js_lexer.go +++ b/internal/js_lexer/js_lexer.go @@ -703,19 +703,15 @@ func (lexer *Lexer) NextJSXElementChild() { stringLiteral: for { switch lexer.codePoint { - case -1: - // Reaching the end of the file without a closing element is an error - lexer.SyntaxError() + case -1, '{', '<': + // Stop when the string ends + break stringLiteral case '&', '\r', '\n', '\u2028', '\u2029': // This needs fixing if it has an entity or if it's a multi-line string needsFixing = true lexer.step() - case '{', '<': - // Stop when the string ends - break stringLiteral - case '}', '>': // These technically aren't valid JSX: https://facebook.github.io/jsx/ // diff --git a/internal/js_parser/js_parser.go b/internal/js_parser/js_parser.go index d166433734c..478ab896b71 100644 --- a/internal/js_parser/js_parser.go +++ b/internal/js_parser/js_parser.go @@ -5019,8 +5019,8 @@ func (p *parser) parseJSXElement(loc logger.Loc) js_ast.Expr { if startText != endText { msg := logger.Msg{ Kind: logger.Error, - Data: p.tracker.MsgData(endRange, fmt.Sprintf("Expected closing tag %q to match opening tag %q", endText, startText)), - Notes: []logger.MsgData{p.tracker.MsgData(startRange, fmt.Sprintf("The opening tag %q is here:", startText))}, + Data: p.tracker.MsgData(endRange, fmt.Sprintf("Expected closing %q tag to match opening %q tag", endText, startText)), + Notes: []logger.MsgData{p.tracker.MsgData(startRange, fmt.Sprintf("The opening %q tag is here:", startText))}, } msg.Data.Location.Suggestion = startText p.log.AddMsg(msg) @@ -5037,6 +5037,16 @@ func (p *parser) parseJSXElement(loc logger.Loc) js_ast.Expr { IsTagSingleLine: isSingleLine, }} + case js_lexer.TEndOfFile: + msg := logger.Msg{ + Kind: logger.Error, + Data: p.tracker.MsgData(p.lexer.Range(), fmt.Sprintf("Unexpected end of file before a closing %q tag", startText)), + Notes: []logger.MsgData{p.tracker.MsgData(startRange, fmt.Sprintf("The opening %q tag is here:", startText))}, + } + msg.Data.Location.Suggestion = fmt.Sprintf("", startText) + p.log.AddMsg(msg) + panic(js_lexer.LexerPanic{}) + default: p.lexer.Unexpected() } diff --git a/internal/js_parser/js_parser_test.go b/internal/js_parser/js_parser_test.go index b0a0e5c50fa..dd58bc50229 100644 --- a/internal/js_parser/js_parser_test.go +++ b/internal/js_parser/js_parser_test.go @@ -4948,11 +4948,11 @@ func TestJSX(t *testing.T) { expectParseErrorJSX(t, "", ": ERROR: Expected \"{\" but found \"true\"\n") expectParseErrorJSX(t, "", ": ERROR: Expected identifier but found \"/\"\n") - expectParseErrorJSX(t, "<>", ": ERROR: Expected closing tag \"b\" to match opening tag \"\"\n: NOTE: The opening tag \"\" is here:\n") - expectParseErrorJSX(t, "", ": ERROR: Expected closing tag \"\" to match opening tag \"a\"\n: NOTE: The opening tag \"a\" is here:\n") - expectParseErrorJSX(t, "", ": ERROR: Expected closing tag \"b\" to match opening tag \"a\"\n: NOTE: The opening tag \"a\" is here:\n") + expectParseErrorJSX(t, "<>", ": ERROR: Expected closing \"b\" tag to match opening \"\" tag\n: NOTE: The opening \"\" tag is here:\n") + expectParseErrorJSX(t, "", ": ERROR: Expected closing \"\" tag to match opening \"a\" tag\n: NOTE: The opening \"a\" tag is here:\n") + expectParseErrorJSX(t, "", ": ERROR: Expected closing \"b\" tag to match opening \"a\" tag\n: NOTE: The opening \"a\" tag is here:\n") expectParseErrorJSX(t, "<\na\n.\nb\n>\n<\n/\nc\n.\nd\n>", - ": ERROR: Expected closing tag \"c.d\" to match opening tag \"a.b\"\n: NOTE: The opening tag \"a.b\" is here:\n") + ": ERROR: Expected closing \"c.d\" tag to match opening \"a.b\" tag\n: NOTE: The opening \"a.b\" tag is here:\n") expectParseErrorJSX(t, "", ": ERROR: Expected \">\" but found \".\"\n") expectParseErrorJSX(t, "", ": ERROR: Unexpected \"-\"\n") @@ -4982,13 +4982,13 @@ func TestJSX(t *testing.T) { expectParseErrorJSX(t, "", ": ERROR: Expected \"*/\" to terminate multi-line comment\n: NOTE: The multi-line comment starts here:\n") expectParseErrorJSX(t, "", ": ERROR: Expected \"*/\" to terminate multi-line comment\n: NOTE: The multi-line comment starts here:\n") expectParseErrorJSX(t, "", ": ERROR: Expected \">\" but found end of file\n") - expectParseErrorJSX(t, "", ": ERROR: Unexpected end of file\n") + expectParseErrorJSX(t, "", ": ERROR: Unexpected end of file before a closing \"a\" tag\n: NOTE: The opening \"a\" tag is here:\n") expectParseErrorJSX(t, "", "") expectParseErrorJSX(t, "", "") expectParseErrorJSX(t, "", ": ERROR: Expected \"*/\" to terminate multi-line comment\n: NOTE: The multi-line comment starts here:\n") expectParseErrorJSX(t, "", ": ERROR: Expected \"*/\" to terminate multi-line comment\n: NOTE: The multi-line comment starts here:\n") expectParseErrorJSX(t, "", ": ERROR: Expected \">\" but found end of file\n") - expectParseErrorJSX(t, "", ": ERROR: Unexpected end of file\n") + expectParseErrorJSX(t, "", ": ERROR: Unexpected end of file before a closing \"a\" tag\n: NOTE: The opening \"a\" tag is here:\n") expectParseErrorJSX(t, "", "") expectParseErrorJSX(t, "", "") diff --git a/internal/js_parser/ts_parser_test.go b/internal/js_parser/ts_parser_test.go index 803159438f8..2c56ec9762a 100644 --- a/internal/js_parser/ts_parser_test.go +++ b/internal/js_parser/ts_parser_test.go @@ -296,8 +296,7 @@ func TestTSTypes(t *testing.T) { // TypeScript 4.7 jsxErrorArrow := ": ERROR: The character \">\" is not valid inside a JSX element\n" + - "NOTE: Did you mean to escape it as \"{'>'}\" instead?\n" + - ": ERROR: Unexpected end of file\n" + "NOTE: Did you mean to escape it as \"{'>'}\" instead?\n" expectPrintedTS(t, "type Foo = T", "") expectPrintedTS(t, "type Foo = T", "") expectPrintedTS(t, "type Foo = T", "") @@ -370,9 +369,9 @@ func TestTSTypes(t *testing.T) { expectParseErrorTSX(t, "() => {}", ": ERROR: Expected \">\" but found \",\"\n") expectParseErrorTSX(t, "() => {}", ": ERROR: Expected \">\" but found \",\"\n") expectParseErrorTSX(t, "() => {}", ": ERROR: Expected \">\" but found \",\"\n") - expectParseErrorTSX(t, "() => {}", jsxErrorArrow) - expectParseErrorTSX(t, "() => {}", jsxErrorArrow) - expectParseErrorTSX(t, "() => {}", jsxErrorArrow) + expectParseErrorTSX(t, "() => {}", jsxErrorArrow+": ERROR: Unexpected end of file before a closing \"in\" tag\n: NOTE: The opening \"in\" tag is here:\n") + expectParseErrorTSX(t, "() => {}", jsxErrorArrow+": ERROR: Unexpected end of file before a closing \"out\" tag\n: NOTE: The opening \"out\" tag is here:\n") + expectParseErrorTSX(t, "() => {}", jsxErrorArrow+": ERROR: Unexpected end of file before a closing \"in\" tag\n: NOTE: The opening \"in\" tag is here:\n") expectPrintedTS(t, "class Container { get data(): typeof this.#data {} }", "class Container {\n get data() {\n }\n}\n") expectPrintedTS(t, "const a: typeof this.#a = 1;", "const a = 1;\n") expectParseErrorTS(t, "const a: typeof #a = 1;", ": ERROR: Expected identifier but found \"#a\"\n") @@ -435,10 +434,10 @@ func TestTSTypes(t *testing.T) { expectPrintedTSX(t, "async () => {}", "async () => {\n};\n") expectPrintedTSX(t, "async () => {}", "async () => {\n};\n") expectPrintedTSX(t, "async () => {}", "async () => {\n};\n") - expectParseErrorTSX(t, "() => {}", jsxErrorArrow) - expectParseErrorTSX(t, "() => {}", jsxErrorArrow) + expectParseErrorTSX(t, "() => {}", jsxErrorArrow+": ERROR: Unexpected end of file before a closing \"const\" tag\n: NOTE: The opening \"const\" tag is here:\n") + expectParseErrorTSX(t, "() => {}", jsxErrorArrow+": ERROR: Unexpected end of file before a closing \"const\" tag\n: NOTE: The opening \"const\" tag is here:\n") expectParseErrorTSX(t, "() => {}", ": ERROR: Expected \">\" but found \",\"\n") - expectParseErrorTSX(t, "() => {}", jsxErrorArrow) + expectParseErrorTSX(t, "() => {}", jsxErrorArrow+": ERROR: Unexpected end of file before a closing \"const\" tag\n: NOTE: The opening \"const\" tag is here:\n") expectParseErrorTSX(t, "async () => {}", ": ERROR: Unexpected \"const\"\n") expectParseErrorTSX(t, "async () => {}", ": ERROR: Unexpected \"const\"\n") expectParseErrorTSX(t, "async () => {}", ": ERROR: Unexpected \"const\"\n") @@ -2078,9 +2077,9 @@ func TestTSInstantiationExpression(t *testing.T) { expectPrintedTS(t, "interface Foo { \n (a: number): typeof a \n (): void \n }", "") expectPrintedTSX(t, "interface Foo { \n (a: number): typeof a \n (): void \n }", "") expectParseErrorTS(t, "type x = y\n\nz\n", ": ERROR: Unterminated regular expression\n") - expectParseErrorTSX(t, "type x = y\n\nz", ": ERROR: Unexpected end of file\n") + expectParseErrorTSX(t, "type x = y\n\nz", ": ERROR: Unexpected end of file before a closing \"number\" tag\n: NOTE: The opening \"number\" tag is here:\n") expectParseErrorTS(t, "type x = typeof y\n\nz\n", ": ERROR: Unterminated regular expression\n") - expectParseErrorTSX(t, "type x = typeof y\n\nz", ": ERROR: Unexpected end of file\n") + expectParseErrorTSX(t, "type x = typeof y\n\nz", ": ERROR: Unexpected end of file before a closing \"number\" tag\n: NOTE: The opening \"number\" tag is here:\n") // See: https://github.com/microsoft/TypeScript/issues/48654 expectPrintedTS(t, "x\ny", "x < true > y;\n") @@ -2391,7 +2390,7 @@ func TestTSJSX(t *testing.T) { expectPrintedTS(t, "const x = 1", "const x = 1;\n") expectPrintedTSX(t, "const x = 1", "const x = /* @__PURE__ */ React.createElement(\"number\", null, \"1\");\n") - expectParseErrorTSX(t, "const x = 1", ": ERROR: Unexpected end of file\n") + expectParseErrorTSX(t, "const x = 1", ": ERROR: Unexpected end of file before a closing \"number\" tag\n: NOTE: The opening \"number\" tag is here:\n") expectPrintedTSX(t, "a{}c", "/* @__PURE__ */ React.createElement(\"x\", null, \"a\", \"c\");\n") expectPrintedTSX(t, "a{b}c", "/* @__PURE__ */ React.createElement(\"x\", null, \"a\", b, \"c\");\n") @@ -2460,9 +2459,9 @@ func TestTSJSX(t *testing.T) { expectPrintedTSX(t, "(() => {})", "() => {\n};\n") expectPrintedTSX(t, "((y) => {})", "(y) => {\n};\n") expectPrintedTSX(t, "((y): (() => {}) => {})", "(y) => {\n};\n") - expectParseErrorTSX(t, "(() => {})", invalidWithHint+": ERROR: Unexpected end of file\n") - expectParseErrorTSX(t, "((x: X) => {})", invalidWithHint+": ERROR: Unexpected end of file\n") - expectParseErrorTSX(t, "((x: X) => {})", invalidWithHint+": ERROR: Unexpected end of file\n") + expectParseErrorTSX(t, "(() => {})", invalidWithHint+": ERROR: Unexpected end of file before a closing \"T\" tag\n: NOTE: The opening \"T\" tag is here:\n") + expectParseErrorTSX(t, "((x: X) => {})", invalidWithHint+": ERROR: Unexpected end of file before a closing \"Y\" tag\n: NOTE: The opening \"Y\" tag is here:\n") + expectParseErrorTSX(t, "((x: X) => {})", invalidWithHint+": ERROR: Unexpected end of file before a closing \"T\" tag\n: NOTE: The opening \"T\" tag is here:\n") expectParseErrorTSX(t, "(<[]>(y))", ": ERROR: Expected identifier but found \"[\"\n") expectParseErrorTSX(t, "((y))", ": ERROR: Expected \">\" but found \"[\"\n") expectParseErrorTSX(t, "((y))", ": ERROR: Expected \"=>\" but found \")\"\n")