Explorar el Código

LibJS: Allow division after IdentifierNames in optional chain

The following syntax is valid:
```js
e?.example / 1.2
```

Previously, the `/` would be treated as a unterminated regex literal,
because it was calling the regular `consume` instead of
`consume_and_allow_division`.

This is what is done when parsing IdentifierNames in
parse_secondary_expression when a period is encountered.

Allows us to parse clients-main-[hash].js on https://ubereats.com/
Luke Wilde hace 8 meses
padre
commit
bd4c29322c
Se han modificado 2 ficheros con 17 adiciones y 2 borrados
  1. 2 2
      Libraries/LibJS/Parser.cpp
  2. 15 0
      Libraries/LibJS/Tests/syntax/slash-after-block.js

+ 2 - 2
Libraries/LibJS/Parser.cpp

@@ -3561,7 +3561,7 @@ NonnullRefPtr<OptionalChain const> Parser::parse_optional_chain(NonnullRefPtr<Ex
             default:
                 if (match_identifier_name()) {
                     auto start = position();
-                    auto identifier = consume();
+                    auto identifier = consume_and_allow_division();
                     chain.append(OptionalChain::MemberReference {
                         create_ast_node<Identifier>({ m_source_code, start, position() }, identifier.DeprecatedFlyString_value()),
                         OptionalChain::Mode::Optional,
@@ -3587,7 +3587,7 @@ NonnullRefPtr<OptionalChain const> Parser::parse_optional_chain(NonnullRefPtr<Ex
                 });
             } else if (match_identifier_name()) {
                 auto start = position();
-                auto identifier = consume();
+                auto identifier = consume_and_allow_division();
                 chain.append(OptionalChain::MemberReference {
                     create_ast_node<Identifier>({ m_source_code, start, position() }, identifier.DeprecatedFlyString_value()),
                     OptionalChain::Mode::NotOptional,

+ 15 - 0
Libraries/LibJS/Tests/syntax/slash-after-block.js

@@ -1,5 +1,6 @@
 test("slash token resolution in lexer", () => {
     expect(`{ blah.blah; }\n/foo/`).toEval();
+    expect(`{ blah?.blah.blah; }\n/foo/`).toEval();
     expect("``/foo/").not.toEval();
     expect("1/foo/").not.toEval();
     expect("1/foo").toEval();
@@ -16,24 +17,34 @@ test("slash token resolution in lexer", () => {
     expect("+a++ / 1").toEval();
     expect("+a-- / 1").toEval();
     expect("a.in / b").toEval();
+    expect("a?.in.in / b").toEval();
     expect("a.instanceof / b").toEval();
+    expect("a?.instanceof.instanceof / b").toEval();
     expect("class A { #name; d = a.#name / b; }").toEval();
+    expect("class A { #name; d = a?.#name / b; }").toEval();
 
     expect("async / b").toEval();
     expect("a.delete / b").toEval();
+    expect("a?.delete.delete / b").toEval();
     expect("delete / b/").toEval();
     expect("a.in / b").toEval();
+    expect("a?.in.in / b").toEval();
     expect("for (a in / b/) {}").toEval();
     expect("a.instanceof / b").toEval();
+    expect("a?.instanceof.instanceof / b").toEval();
     expect("a instanceof / b/").toEval();
+    expect("a?.instanceof instanceof / b/").toEval();
     expect("new / b/").toEval();
     expect("null / b").toEval();
     expect("for (a of / b/) {}").toEval();
     expect("a.return / b").toEval();
+    expect("a?.return.return / b").toEval();
     expect("function foo() { return / b/ }").toEval();
     expect("throw / b/").toEval();
     expect("a.typeof / b").toEval();
+    expect("a?.typeof.typeof / b").toEval();
     expect("a.void / b").toEval();
+    expect("a?.void.void / b").toEval();
     expect("void / b/").toEval();
 
     expect("await / b").toEval();
@@ -49,4 +60,8 @@ test("slash token resolution in lexer", () => {
     expect("this / 1").toEval();
     expect("this / 1 /").not.toEval();
     expect("this / 1 / 1").toEval();
+
+    expect("this?.a / 1").toEval();
+    expect("this?.a / 1 /").not.toEval();
+    expect("this?.a / 1 / 1").toEval();
 });