fix(parser): inject params into one-line non-async arrow scenarios - #5680
fix(parser): inject params into one-line non-async arrow scenarios#5680mirao wants to merge 1 commit into
Conversation
parse-function@5.6.10 decides whether its input is an ES6 object method with
`/^\*?.+\([\S\W]*\)\s*{/`. The greedy `.+` combined with `[\S\W]*` matches any
source containing a `) {` sequence, so a non-async arrow whose body holds an
`if`, `for`, `while` or `switch` gets wrapped in braces as a fake object method
and acorn throws. getParams() then returns undefined and nothing is injected —
`I`, page objects and `current` are all undefined when the test runs.
Async arrows escape through the library's own isAsyncArrow check, and in plain
JavaScript a multi-line arrow escapes as well, because `.` does not cross a
newline so the greedy `.+` cannot reach past the first line. That second escape
does not exist under TypeScript: tsx/esbuild emit every function on one line, so
fn.toString() returns the one-line form however the source was written, and
every non-async scenario with destructured params and a conditional fails.
normalizeArrowFn() asks acorn whether the source really is an
ArrowFunctionExpression and, if so, hands parse-function `async <source>` so it
takes the isAsyncArrow branch. Anything acorn does not confirm as an arrow —
class methods, generators, function expressions, strings, unparseable input — is
returned untouched, so only input that fails today is affected. The prefix cannot
change a parameter list, and default values stay correct because their offsets
are sliced from the same prefixed string.
ecmaVersion becomes a shared const so the parse and the arrow check cannot drift
apart; its value is unchanged, so no syntax gains or loses parseability.
Fixes codeceptjs#5679
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
luantaraschi
left a comment
There was a problem hiding this comment.
I reproduced the parser case and reviewed the normalization boundary. Acorn confirms that the source is an arrow expression before the async prefix is added, so class methods, generators, function expressions, and invalid input continue through the existing parser unchanged. The shared ecmaVersion also keeps both parses on the same syntax level.
The focused parser file passes with 12 tests. I also ran the complete unit and runner suites, ESLint on both changed files, and git diff --check; all completed successfully.
The red Appium job does not reach this code. The fork run has empty Sauce Labs credentials, the application upload returns Authorization failed, and session creation is rejected as unauthorized. The parser-related checks and the remaining CI jobs are green.
Fixes #5679
Problem
Scenario('...', ({ I }) => { if (true) { I.say('hello') } })injects nothing —I, page objects andcurrentare allundefined, and the test dies withTypeError: Cannot read properties of undefined.parse-function@5.6.10decides whether its input is an ES6 object method with:The greedy
.+combined with[\S\W]*matches any source containing a) {sequence, so a non-async arrow whose body holds anif,for,whileorswitchis wrapped in braces as a fake object method and acorn throws.getParams()logs theSyntaxErrorand returnsundefined.Async arrows escape via the library's own
isAsyncArrowcheck. In plain JavaScript a multi-line arrow escapes too, because.does not cross a newline. That second escape does not exist under TypeScript —tsx/esbuildemit every function on one line, sofn.toString()returns the one-line form however the source was written. In a TypeScript suite every non-async scenario with destructured params and a conditional fails, which is why this surfaced as a 4.0 regression.Fix
normalizeArrowFn()asks acorn whether the source really is anArrowFunctionExpressionand, if so, handsparse-functionasync <source>so it takes theisAsyncArrowbranch instead of the method-wrapping one.Anything acorn does not confirm as an arrow — class methods, generators, function expressions, strings, unparseable input — is returned untouched, so only input that already fails is affected. The prefix cannot change a parameter list, and default values stay correct because their offsets are sliced from the same prefixed string.
ecmaVersionbecomes a shared const so the parse and the arrow check cannot drift apart. Its value is unchanged, so no syntax gains or loses parseability.Verification
lib/parser.jsrevertedgetParamsandgetParamsToStringnpm run test:unitnpm run test:runnerData().Scenario(({ current }) => ...)The differential corpus covered class methods, async/static methods, getters, generators and async generators, function expressions, expression-body arrows, rest params, default values (numeric, string, call expressions), nested arrows, strings containing
=>, sinon proxies, string inputs and garbage input.Not included
A one-line arrow whose body uses ES2021+ syntax (
??=,||=, numeric separators, class fields) still gets no params, becauseecmaVersion: 11pins the parser to ES2020. That is a separate, pre-existing defect with the same symptom — unrelated to theisMethodmisdetection and unchanged by this PR. Raising it to'latest'fixes it and was green on both suites, but it is deliberately left out to keep this PR to the reported bug. Happy to add it here or in a follow-up if maintainers prefer.🤖 Generated with Claude Code