Repository navigation
fix(parser): parse / after an await identifier as division - #659
Merged
3cp merged 1 commit intoSep 15, 2026
Merged
Conversation
`parseIdentifier` used to scan the next token with `Context.AllowRegExp` whenever the identifier text was an unescaped `await`, regardless of context. In sloppy script code, and for any property named `await`, that made a following `/` open a regular expression instead of acting as a division operator, so `await/x` failed with `Unterminated regular expression` and `a.await/2/g` with `Unexpected token: 'regular expression'`. The regex scan now travels with the one caller that can actually start an await expression. `parseAwaitExpressionOrIdentifier` computes it from the context that decides how `await` reads, and passes it through `parseIdentifierOrArrow` into `parseIdentifier`, which no longer decides from the token text. Property names, object keys, labels and bindings therefore never request a regex scan, while `await /x/` inside an async function or at module top level is unchanged.
commit: |
Member
|
v7.4.0 is released. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
meriyah.parseScript('await/x')throws[1:5-1:7]: Unterminated regular expression. So dofunction f(){ await/x },function* g(){ await/x },let x = await/2,var await; await/xandlabel: await/x, whileawait/x/gandx = await/2/gthrowUnexpected token: 'regular expression'. In script codeawaitis an ordinary identifier, so the/after it is a division operator, and V8 and acorn parse every one of those inputs; strict script code is hit the same way, since"use strict"; await/xfails with the sameUnterminated regular expression. The same hole reaches a property namedawaitin any mode, soa.await/2/g,function f(){ x.await/2/g }andasync function f(){ x.await/2/g }fail too, in script and in module code alike, even thoughawaitthere is only a property name and can never start an await expression.The cause is in
parseIdentifierinsrc/parser.ts, which decided by token text alone:const allowRegex = tokenValue === 'await' && (parser.getToken() & Token.IsEscaped) === 0. Every identifier whose text happens to beawaittherefore made the lexer read the following token withContext.AllowRegExp, no matter what the surrounding context was and no matter whether the identifier was an expression, a binding, a label or a member property. Whetherawaitintroduces anAwaitExpressionis fully determined by the context, so no token text sniffing and no lookahead is needed for that choice.The fix moves the decision to the one caller that can start an await expression.
parseAwaitExpressionOrIdentifiercomputesallowRegExpas not escaped andcontext & (Context.InAwaitContext | Context.Module), which is the same testsrc/common.tsalready uses at lines 308, 340 and 359 to decide whetherawaitis a keyword, and hands it throughparseIdentifierOrArrowintoparseIdentifier, which no longer decides fromtokenValue. Both new parameters default to0, so property names, object keys and bindings never ask for a regex scan through them; thebreakandcontinuelabel references keep requesting one throughContext.AllowRegExpin the context, as before. Await expressions in module code are untouched:await/xat module top level still fails,y = await/x/gin a module andasync function f(){ await/x/g }still parse the regular expression, andasync function f(){ await/x }still fails.sourceType: 'commonjs'sets neitherContext.ModulenorContext.InAwaitContext, so it reads/as division, which matches Node. One nearby gap is untouched because it is not this scan:function f(a = await/2/g) {}is accepted by V8 and acorn but still reportsAwait expression not allowed in formal parameter, from the parameter await tracking (firstAwaitLocation,DestructuringKind.Await).Seventeen pass cases go into
test/parser/expressions/await.ts: fourteen fail onmain, one of them undersourceType: 'commonjs'and one in strict script code, and three guard the await expression side that must keep scanning a regular expression. Two new fail cases pin that side so it cannot quietly flip to division later. Seven existing fail inputs change their message, filling eight snapshot entries because one is listed twice, and all still error. Four now point at the right token:async(a = await/r/g) => {}moves from[1:15-1:19]: Expected ')'under/r/gto[1:10-1:15]: Await expression not allowed in formal parameterunderawait. Two become less pointed but state the correct complaint, since division is the right reading in script code and the real fault is the target on the left. The seventh,a = (b = await/r/g) => {}) => {}, now points at the stray).On node v22.23.2 over base 683b03e:
npx vitest runis 91979 passed in 139 files with 0 failures,npx tsc --noEmitexits 0,npx prettier --check .is clean andnode scripts/lint-snapshots.mjspasses. Every input named above was cross checked against acorn, and the script ones againstnew vm.Script; after the change the parser agrees with both on all of them, apart from the formal parameter case called out in the third paragraph. I could not run the test262 conformance harness locally, so that pass deserves a look in CI.