Skip to content

Commit 271c607

Browse files
committed
Fix parsing of 'await <jsx/>'
In async functions, and with top-level await allowed, 'await<x>y</x>/g' should behave like 'Promise.resolve(<x>y</x> / g)'. However, in top-level code, parsing is ambiguous. quick-lint-js always parses 'await' as an identifier in top-level code when it is followed by a JSX expression. Fix parsing of 'await<x>y</x>/g' to guess which parse was intended. Reuse the logic for parsing the ambiguity with 'await/re/g' (regular expressions).
1 parent a517edb commit 271c607

File tree

4 files changed

+85
-75
lines changed

4 files changed

+85
-75
lines changed

docs/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ Semantic Versioning.
1212

1313
* `x && <Element/>` no longer falsely reports [E0026][] (missing operand for
1414
operator). (Thanks to [Piotr Dąbrowski][] for reporting.)
15+
* In top-level code, `await <x/>` is now parsed as the `await` operator followed
16+
by a JSX element (rather than `await` less-than-compared to `x`, followed by
17+
jibberish).
1518

1619
### Changed
1720

src/quick-lint-js/parse-expression-inl.h

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -885,14 +885,11 @@ expression* parser::parse_await_expression(Visitor& v, token await_token,
885885
case token_type::semicolon:
886886
return true;
887887

888-
// await <x>y</x>/g // operator
889-
// await < other // identifier
888+
// await <x>y</x>/g; // operator
889+
// await < other; // identifier
890+
// await /regexp/; // operator
891+
// await / rhs; // identifier
890892
case token_type::less:
891-
// TODO(strager): Resolve the ambiguity.
892-
return true;
893-
894-
// await /regexp/;
895-
// await / rhs;
896893
case token_type::slash:
897894
case token_type::slash_equal: {
898895
parse_expression_cache_key cache_key = {
@@ -904,19 +901,19 @@ expression* parser::parse_await_expression(Visitor& v, token await_token,
904901
.in_switch_statement = this->in_switch_statement_,
905902
.in_class = this->in_class_,
906903
};
907-
auto cache_it =
908-
this->await_slash_is_identifier_divide_cache_.find(cache_key);
909-
if (cache_it != this->await_slash_is_identifier_divide_cache_.end()) {
904+
auto cache_it = this->await_is_identifier_cache_.find(cache_key);
905+
if (cache_it != this->await_is_identifier_cache_.end()) {
910906
return cache_it->second;
911907
}
912908

913909
parser_transaction transaction = this->begin_transaction();
914910

915911
if (this->in_top_level_) {
916-
// Try to parse the / as a regular expression literal.
912+
// Try to parse the / as a regular expression literal or the < as a
913+
// JSX element.
917914
[[maybe_unused]] expression* ast = this->parse_expression(v, prec);
918915
} else {
919-
// Try to parse the / as a binary division operator.
916+
// Try to parse the / or < as a binary operator.
920917
[[maybe_unused]] expression* ast = this->parse_expression_remainder(
921918
v,
922919
this->make_expression<expression::variable>(
@@ -931,15 +928,13 @@ expression* parser::parse_await_expression(Visitor& v, token await_token,
931928

932929
bool is_identifier_result;
933930
if (this->in_top_level_) {
934-
bool parsed_slash_as_regexp = parsed_ok;
935-
is_identifier_result = !parsed_slash_as_regexp;
931+
is_identifier_result = !parsed_ok;
936932
} else {
937-
bool parsed_slash_as_divide = parsed_ok;
938-
is_identifier_result = parsed_slash_as_divide;
933+
is_identifier_result = parsed_ok;
939934
}
940935
auto [_cache_it, inserted] =
941-
this->await_slash_is_identifier_divide_cache_.try_emplace(
942-
cache_key, is_identifier_result);
936+
this->await_is_identifier_cache_.try_emplace(cache_key,
937+
is_identifier_result);
943938
QLJS_ASSERT(inserted);
944939
return is_identifier_result;
945940
}

src/quick-lint-js/parse.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -571,13 +571,13 @@ class parser {
571571
// first `await` is an operator.)
572572
//
573573
// The value of each entry indicates the conclusion:
574-
// * true means 'await' looks like an identifier, thus '/' is the division
575-
// operator.
574+
// * true means 'await' looks like an identifier, thus the following '/' or
575+
// '/=' or '<' is the division operator.
576576
// * false means 'await' looks like an operator, thus '/' begins a regular
577-
// expression literal.
577+
// expression literal or '<' begins a JSX element.
578578
std::unordered_map<parse_expression_cache_key, bool,
579579
parse_expression_cache_key::hash>
580-
await_slash_is_identifier_divide_cache_;
580+
await_is_identifier_cache_;
581581

582582
#if QLJS_HAVE_SETJMP
583583
bool have_fatal_parse_error_jmp_buf_ = false;

test/test-parse-expression.cpp

Lines changed: 65 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -970,20 +970,31 @@ TEST_F(test_parse_expression, await_followed_by_arrow_function) {
970970

971971
TEST_F(test_parse_expression,
972972
await_in_normal_function_vs_async_function_vs_top_level) {
973+
static parser_options default_parser_options;
974+
975+
parser_options no_jsx;
976+
no_jsx.jsx = false;
977+
978+
parser_options jsx;
979+
jsx.jsx = true;
980+
973981
struct test_case {
974982
string8_view code;
975983
const char* expected_normal_function;
976984
const char* expected_async_function;
985+
986+
parser_options& options = default_parser_options;
977987
};
978988

979-
for (test_case test : {
989+
for (const test_case& test : {
980990
// clang-format off
981991
test_case
982992

983993
// 'await' is either an identifier or a unary operator:
984994
{u8"await/re/g"_sv, "binary(var await, var re, var g)", "await(literal)"},
985995
{u8"await+x"_sv, "binary(var await, var x)", "await(unary(var x))"},
986996
{u8"await-x"_sv, "binary(var await, var x)", "await(unary(var x))"},
997+
{u8"await<x>y</x>/g"_sv, "binary(var await, var x, var y, literal)", "await(binary(jsxelement(x), var g))", jsx},
987998
{u8"await(x)"_sv, "call(var await, var x)", "await(paren(var x))"},
988999
{u8"await[x]"_sv, "index(var await, var x)", "await(array(var x))"},
9891000
{u8"await++\nx"_sv, "rwunarysuffix(var await)", "await(rwunary(var x))"},
@@ -1025,55 +1036,56 @@ TEST_F(test_parse_expression,
10251036
{u8"await yield"_sv, nullptr, "await(var yield)"},
10261037

10271038
// 'await' must be an identifier:
1028-
{u8"[await]"_sv, "array(var await)", nullptr},
1029-
{u8"await => x"_sv, "arrowexpr(var await)", nullptr},
1030-
{u8"await = x"_sv, "assign(var await, var x)", nullptr},
1031-
{u8"await != x"_sv, "binary(var await, var x)", nullptr},
1032-
{u8"await !== x"_sv, "binary(var await, var x)", nullptr},
1033-
{u8"await % x"_sv, "binary(var await, var x)", nullptr},
1034-
{u8"await & x"_sv, "binary(var await, var x)", nullptr},
1035-
{u8"await && x"_sv, "binary(var await, var x)", nullptr},
1036-
{u8"await ** x"_sv, "binary(var await, var x)", nullptr},
1037-
{u8"await / x"_sv, "binary(var await, var x)", nullptr},
1038-
{u8"await < x"_sv, "binary(var await, var x)", nullptr},
1039-
{u8"await << x"_sv, "binary(var await, var x)", nullptr},
1040-
{u8"await <= x"_sv, "binary(var await, var x)", nullptr},
1041-
{u8"await == x"_sv, "binary(var await, var x)", nullptr},
1042-
{u8"await === x"_sv, "binary(var await, var x)", nullptr},
1043-
{u8"await > x"_sv, "binary(var await, var x)", nullptr},
1044-
{u8"await >= x"_sv, "binary(var await, var x)", nullptr},
1045-
{u8"await >> x"_sv, "binary(var await, var x)", nullptr},
1046-
{u8"await >>> x"_sv, "binary(var await, var x)", nullptr},
1047-
{u8"await ?? x"_sv, "binary(var await, var x)", nullptr},
1048-
{u8"await ^ x"_sv, "binary(var await, var x)", nullptr},
1049-
{u8"await in xs"_sv, "binary(var await, var xs)", nullptr},
1050-
{u8"await instanceof X"_sv, "binary(var await, var X)", nullptr},
1051-
{u8"await | x"_sv, "binary(var await, var x)", nullptr},
1052-
{u8"await || x"_sv, "binary(var await, var x)", nullptr},
1053-
{u8"await, x"_sv, "binary(var await, var x)", nullptr},
1054-
{u8"await ? x : y"_sv, "cond(var await, var x, var y)", nullptr},
1055-
{u8"x ? await : y"_sv, "cond(var x, var await, var y)", nullptr},
1056-
{u8"await &&= x"_sv, "condassign(var await, var x)", nullptr},
1057-
{u8"await ?\x3f= x"_sv, "condassign(var await, var x)", nullptr},
1058-
{u8"await ||= x"_sv, "condassign(var await, var x)", nullptr},
1059-
{u8"await.prop"_sv, "dot(var await, prop)", nullptr},
1060-
{u8"await?.prop"_sv, "dot(var await, prop)", nullptr},
1061-
{u8"{key: await}"_sv, "object(literal, var await)", nullptr},
1062-
{u8"await %= x"_sv, "upassign(var await, var x)", nullptr},
1063-
{u8"await &= x"_sv, "upassign(var await, var x)", nullptr},
1064-
{u8"await **= x"_sv, "upassign(var await, var x)", nullptr},
1065-
{u8"await *= x"_sv, "upassign(var await, var x)", nullptr},
1066-
{u8"await += x"_sv, "upassign(var await, var x)", nullptr},
1067-
{u8"await -= x"_sv, "upassign(var await, var x)", nullptr},
1068-
{u8"await /= x"_sv, "upassign(var await, var x)", nullptr},
1069-
{u8"await <<= x"_sv, "upassign(var await, var x)", nullptr},
1070-
{u8"await >>= x"_sv, "upassign(var await, var x)", nullptr},
1071-
{u8"await >>>= x"_sv, "upassign(var await, var x)", nullptr},
1072-
{u8"await ^= x"_sv, "upassign(var await, var x)", nullptr},
1073-
{u8"await |= x"_sv, "upassign(var await, var x)", nullptr},
1074-
{u8"await"_sv, "var await", nullptr},
1075-
{u8"(await)"_sv, "paren(var await)", nullptr},
1076-
{u8"await;"_sv, "var await", nullptr},
1039+
{u8"[await]"_sv, "array(var await)", nullptr},
1040+
{u8"await => x"_sv, "arrowexpr(var await)", nullptr},
1041+
{u8"await = x"_sv, "assign(var await, var x)", nullptr},
1042+
{u8"await != x"_sv, "binary(var await, var x)", nullptr},
1043+
{u8"await !== x"_sv, "binary(var await, var x)", nullptr},
1044+
{u8"await % x"_sv, "binary(var await, var x)", nullptr},
1045+
{u8"await & x"_sv, "binary(var await, var x)", nullptr},
1046+
{u8"await && x"_sv, "binary(var await, var x)", nullptr},
1047+
{u8"await ** x"_sv, "binary(var await, var x)", nullptr},
1048+
{u8"await / x"_sv, "binary(var await, var x)", nullptr},
1049+
{u8"await < x"_sv, "binary(var await, var x)", nullptr},
1050+
{u8"await << x"_sv, "binary(var await, var x)", nullptr},
1051+
{u8"await <= x"_sv, "binary(var await, var x)", nullptr},
1052+
{u8"await == x"_sv, "binary(var await, var x)", nullptr},
1053+
{u8"await === x"_sv, "binary(var await, var x)", nullptr},
1054+
{u8"await > x"_sv, "binary(var await, var x)", nullptr},
1055+
{u8"await >= x"_sv, "binary(var await, var x)", nullptr},
1056+
{u8"await >> x"_sv, "binary(var await, var x)", nullptr},
1057+
{u8"await >>> x"_sv, "binary(var await, var x)", nullptr},
1058+
{u8"await ?? x"_sv, "binary(var await, var x)", nullptr},
1059+
{u8"await ^ x"_sv, "binary(var await, var x)", nullptr},
1060+
{u8"await in xs"_sv, "binary(var await, var xs)", nullptr},
1061+
{u8"await instanceof X"_sv, "binary(var await, var X)", nullptr},
1062+
{u8"await | x"_sv, "binary(var await, var x)", nullptr},
1063+
{u8"await || x"_sv, "binary(var await, var x)", nullptr},
1064+
{u8"await, x"_sv, "binary(var await, var x)", nullptr},
1065+
{u8"await<x>y</x>/g"_sv, "binary(var await, var x, var y, literal)", nullptr, no_jsx},
1066+
{u8"await ? x : y"_sv, "cond(var await, var x, var y)", nullptr},
1067+
{u8"x ? await : y"_sv, "cond(var x, var await, var y)", nullptr},
1068+
{u8"await &&= x"_sv, "condassign(var await, var x)", nullptr},
1069+
{u8"await ?\x3f= x"_sv, "condassign(var await, var x)", nullptr},
1070+
{u8"await ||= x"_sv, "condassign(var await, var x)", nullptr},
1071+
{u8"await.prop"_sv, "dot(var await, prop)", nullptr},
1072+
{u8"await?.prop"_sv, "dot(var await, prop)", nullptr},
1073+
{u8"{key: await}"_sv, "object(literal, var await)", nullptr},
1074+
{u8"await %= x"_sv, "upassign(var await, var x)", nullptr},
1075+
{u8"await &= x"_sv, "upassign(var await, var x)", nullptr},
1076+
{u8"await **= x"_sv, "upassign(var await, var x)", nullptr},
1077+
{u8"await *= x"_sv, "upassign(var await, var x)", nullptr},
1078+
{u8"await += x"_sv, "upassign(var await, var x)", nullptr},
1079+
{u8"await -= x"_sv, "upassign(var await, var x)", nullptr},
1080+
{u8"await /= x"_sv, "upassign(var await, var x)", nullptr},
1081+
{u8"await <<= x"_sv, "upassign(var await, var x)", nullptr},
1082+
{u8"await >>= x"_sv, "upassign(var await, var x)", nullptr},
1083+
{u8"await >>>= x"_sv, "upassign(var await, var x)", nullptr},
1084+
{u8"await ^= x"_sv, "upassign(var await, var x)", nullptr},
1085+
{u8"await |= x"_sv, "upassign(var await, var x)", nullptr},
1086+
{u8"await"_sv, "var await", nullptr},
1087+
{u8"(await)"_sv, "paren(var await)", nullptr},
1088+
{u8"await;"_sv, "var await", nullptr},
10771089

10781090
// TODO(strager): Fix these test cases:
10791091
#if 0
@@ -1086,7 +1098,7 @@ TEST_F(test_parse_expression,
10861098

10871099
{
10881100
// Normal function:
1089-
test_parser p(test.code);
1101+
test_parser p(test.code, test.options);
10901102
auto guard = p.parser().enter_function(function_attributes::normal);
10911103
expression* ast = p.parse_expression();
10921104

@@ -1122,7 +1134,7 @@ TEST_F(test_parse_expression,
11221134

11231135
if (test.expected_async_function) {
11241136
// Async function:
1125-
test_parser p(test.code);
1137+
test_parser p(test.code, test.options);
11261138
auto guard = p.parser().enter_function(function_attributes::async);
11271139
expression* ast = p.parse_expression();
11281140
EXPECT_EQ(summarize(ast), test.expected_async_function);
@@ -1131,7 +1143,7 @@ TEST_F(test_parse_expression,
11311143

11321144
{
11331145
// Top level:
1134-
test_parser p(test.code);
1146+
test_parser p(test.code, test.options);
11351147
expression* ast = p.parse_expression();
11361148
EXPECT_EQ(summarize(ast), test.expected_async_function
11371149
? test.expected_async_function

0 commit comments

Comments
 (0)