Skip to content

Commit 456e1f1

Browse files
committed
Detect and stop parsing on JSX
When quick-lint-js is run on code containing JSX, the parser and linter barf horribly all over the code. It'll take us a while to implement a full JSX parser. When JSX syntax is detected, emit a friendly diagnostic and stop parsing to avoid diagnostic spew. For valid JavaScript code, this commit should not change behavior.
1 parent e10bec6 commit 456e1f1

File tree

8 files changed

+138
-6
lines changed

8 files changed

+138
-6
lines changed

docs/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ Beta release.
4444
* The npm package and the manual builds are now much smaller and faster. (They
4545
were previously compiled in debug, unoptimized mode.)
4646
* `delete x` no longer reports a warning if `x` is a global variable.
47+
* JSX: Instead of reporting a bunch of errors, quick-lint-js now tells you that
48+
JSX syntax is not yet supported.
4749

4850
## 0.5.0 (2021-10-12)
4951

docs/errors/E177.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# E177: React/JSX is not yet implemented
2+
3+
```config-for-examples
4+
{
5+
"globals": {
6+
"React": true,
7+
"ReactDOM": true
8+
}
9+
}
10+
```
11+
12+
quick-lint-js does not yet support JSX syntax (used in React code):
13+
14+
```javascript-ignoring-extra-errors
15+
ReactDOM.render(
16+
<h1>Hello, world!</h1>,
17+
document.getElementById('root')
18+
);
19+
```
20+
21+
To fix this issue, stop using quick-lint-js for your code. Alternative, use
22+
`React.createElement` instead of JSX:
23+
24+
ReactDOM.render(
25+
React.createElement('h1', null, 'Hello, world!'),
26+
document.getElementById('root')
27+
);

src/parse.cpp

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -464,8 +464,20 @@ expression* parser::parse_primary_expression(precedence prec) {
464464
expression* ast =
465465
this->make_expression<expression::_invalid>(this->peek().span());
466466
if (prec.binary_operators) {
467-
this->error_reporter_->report(
468-
error_missing_operand_for_operator{this->peek().span()});
467+
if (this->peek().type == token_type::less) {
468+
// <MyComponent /> (JSX)
469+
this->error_reporter_->report(
470+
error_jsx_not_yet_implemented{.jsx_start = this->peek().span()});
471+
#if QLJS_HAVE_SETJMP
472+
if (this->have_fatal_parse_error_jmp_buf_) {
473+
std::longjmp(this->fatal_parse_error_jmp_buf_, 1);
474+
QLJS_UNREACHABLE();
475+
}
476+
#endif
477+
} else {
478+
this->error_reporter_->report(
479+
error_missing_operand_for_operator{this->peek().span()});
480+
}
469481
}
470482
return ast;
471483
}

src/quick-lint-js/error.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,10 @@
596596
error_invalid_utf_8_sequence, "E022", { source_code_span sequence; }, \
597597
ERROR(QLJS_TRANSLATABLE("invalid UTF-8 sequence"), sequence)) \
598598
\
599+
QLJS_ERROR_TYPE( \
600+
error_jsx_not_yet_implemented, "E177", { source_code_span jsx_start; }, \
601+
ERROR(QLJS_TRANSLATABLE("React/JSX is not yet implemented"), jsx_start)) \
602+
\
599603
QLJS_ERROR_TYPE( \
600604
error_keywords_cannot_contain_escape_sequences, "E023", \
601605
{ source_code_span escape_sequence; }, \

test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ quick_lint_js_add_executable(
7070
test-parse-expression-statement.cpp
7171
test-parse-expression.cpp
7272
test-parse-function.cpp
73+
test-parse-jsx.cpp
7374
test-parse-loop.cpp
7475
test-parse-module.cpp
7576
test-parse-statement.cpp

test/test-parse-expression-statement.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -235,11 +235,12 @@ TEST(test_parse, stray_right_parenthesis) {
235235
}
236236

237237
TEST(test_parse, statement_starting_with_binary_only_operator) {
238+
// '<' omitted. It is used for JSX.
238239
for (string8_view op : {
239-
u8"!=", u8"!==", u8"%", u8"&", u8"&&", u8"*",
240-
u8"**", u8",", u8"<", u8"<<", u8"<=", u8"=",
241-
u8"==", u8"===", u8">", u8">=", u8">>", u8">>>",
242-
u8"??", u8"^", u8"in", u8"instanceof", u8"|",
240+
u8"!=", u8"!==", u8"%", u8"&", u8"&&", u8"*",
241+
u8"**", u8",", u8"<<", u8"<=", u8"=", u8"==",
242+
u8"===", u8">", u8">=", u8">>", u8">>>", u8"??",
243+
u8"^", u8"in", u8"instanceof", u8"|",
243244
}) {
244245
padded_string code(string8(op) + u8" x");
245246
SCOPED_TRACE(code);

test/test-parse-expression.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3291,6 +3291,21 @@ TEST_F(test_parse_expression, generator_misplaced_star) {
32913291
EXPECT_EQ(p.range(ast).end_offset(), 16);
32923292
}
32933293

3294+
TEST_F(test_parse_expression, jsx_is_not_supported) {
3295+
// If parsing was not started with
3296+
// parse_and_visit_module_catching_fatal_parse_errors, then we can't halt
3297+
// parsing at the '<'. For error recovery, treat '<' as if it was a binary
3298+
// operator.
3299+
test_parser p(u8"<MyComponent attr={value}>hello</MyComponent>"_sv);
3300+
expression* ast = p.parse_expression();
3301+
EXPECT_THAT(p.errors(), ElementsAre(ERROR_TYPE_FIELD(
3302+
error_jsx_not_yet_implemented, jsx_start,
3303+
offsets_matcher(p.code(), 0, u8"<"))));
3304+
EXPECT_EQ(p.range(ast).begin_offset(), 0);
3305+
EXPECT_EQ(p.range(ast).end_offset(), strlen(u8"<MyComponent"));
3306+
EXPECT_EQ(summarize(ast), "binary(?, var MyComponent)");
3307+
}
3308+
32943309
std::string summarize(const expression& expression) {
32953310
auto children = [&] {
32963311
std::string result;

test/test-parse-jsx.cpp

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Copyright (C) 2020 Matthew "strager" Glazar
2+
// See end of file for extended copyright information.
3+
4+
#include <gmock/gmock.h>
5+
#include <gtest/gtest.h>
6+
#include <quick-lint-js/array.h>
7+
#include <quick-lint-js/char8.h>
8+
#include <quick-lint-js/cli-location.h>
9+
#include <quick-lint-js/error-collector.h>
10+
#include <quick-lint-js/error-matcher.h>
11+
#include <quick-lint-js/error.h>
12+
#include <quick-lint-js/language.h>
13+
#include <quick-lint-js/padded-string.h>
14+
#include <quick-lint-js/parse-support.h>
15+
#include <quick-lint-js/parse.h>
16+
#include <quick-lint-js/spy-visitor.h>
17+
#include <string>
18+
#include <string_view>
19+
#include <vector>
20+
21+
using ::testing::Contains;
22+
using ::testing::ElementsAre;
23+
24+
namespace quick_lint_js {
25+
namespace {
26+
TEST(test_parse, jsx_is_not_supported) {
27+
// If parsing was not started with
28+
// parse_and_visit_module_catching_fatal_parse_errors, then we can't halt
29+
// parsing at the '<'. Error recovery will do a bad job.
30+
padded_string code(u8"<MyComponent attr={value}>hello</MyComponent>"_sv);
31+
spy_visitor v;
32+
parser p(&code, &v);
33+
p.parse_and_visit_module(v);
34+
EXPECT_THAT(v.errors, Contains(ERROR_TYPE_FIELD(
35+
error_jsx_not_yet_implemented, jsx_start,
36+
offsets_matcher(&code, 0, u8"<"))));
37+
}
38+
39+
#if QLJS_HAVE_SETJMP
40+
TEST(test_parse, parsing_stops_on_jsx) {
41+
padded_string code(u8"<MyComponent attr={value}>hello</MyComponent>"_sv);
42+
spy_visitor v;
43+
parser p(&code, &v);
44+
bool ok = p.parse_and_visit_module_catching_fatal_parse_errors(v);
45+
EXPECT_FALSE(ok);
46+
EXPECT_THAT(v.errors, ElementsAre(ERROR_TYPE_FIELD(
47+
error_jsx_not_yet_implemented, jsx_start,
48+
offsets_matcher(&code, 0, u8"<"))));
49+
}
50+
#endif
51+
}
52+
}
53+
54+
// quick-lint-js finds bugs in JavaScript programs.
55+
// Copyright (C) 2020 Matthew "strager" Glazar
56+
//
57+
// This file is part of quick-lint-js.
58+
//
59+
// quick-lint-js is free software: you can redistribute it and/or modify
60+
// it under the terms of the GNU General Public License as published by
61+
// the Free Software Foundation, either version 3 of the License, or
62+
// (at your option) any later version.
63+
//
64+
// quick-lint-js is distributed in the hope that it will be useful,
65+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
66+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
67+
// GNU General Public License for more details.
68+
//
69+
// You should have received a copy of the GNU General Public License
70+
// along with quick-lint-js. If not, see <https://www.gnu.org/licenses/>.

0 commit comments

Comments
 (0)