Skip to content

fix(parser): inject params into one-line non-async arrow scenarios - #5680

Open
mirao wants to merge 1 commit into
codeceptjs:4.xfrom
mirao:fix/5679-oneline-arrow-params
Open

fix(parser): inject params into one-line non-async arrow scenarios#5680
mirao wants to merge 1 commit into
codeceptjs:4.xfrom
mirao:fix/5679-oneline-arrow-params

Conversation

@mirao

@mirao mirao commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Fixes #5679

Problem

Scenario('...', ({ I }) => { if (true) { I.say('hello') } }) injects nothing — I, page objects and current are all undefined, and the test dies with TypeError: Cannot read properties of undefined.

parse-function@5.6.10 decides whether its input is an ES6 object method with:

const isMethod = /^\*?.+\([\S\W]*\)\s*{/i.test(result.value)
if (!(isFunction || isAsyncFn || isAsyncArrow) && isMethod) result.value = `{ ${result.value} }`

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 is wrapped in braces as a fake object method and acorn throws. getParams() logs the SyntaxError and returns undefined.

Async arrows escape via the library's own isAsyncArrow check. In plain JavaScript a multi-line arrow escapes too, because . does not cross a newline. That second escape does not exist under TypeScripttsx/esbuild emit every function on one line, so fn.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 an ArrowFunctionExpression and, if so, hands parse-function async <source> so it takes the isAsyncArrow branch 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.

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.

Verification

Check Result
New tests with lib/parser.js reverted 4 failing
New tests with the fix 12 passing (8 pre-existing + 4 new)
Differential vs. the old parser, 47 function shapes, both getParams and getParamsToString 0 regressions, 15 fixes
npm run test:unit 773 passed, 0 failed
npm run test:runner 276 passed, 0 failed
Repro from the issue, incl. Data().Scenario(({ current }) => ...) 4 passed, params injected

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, because ecmaVersion: 11 pins the parser to ES2020. That is a separate, pre-existing defect with the same symptom — unrelated to the isMethod misdetection 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

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 luantaraschi left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Since 4.0: Scenario with a non-async one-line arrow gets no injected parameters (parse-function isMethod misdetection)

2 participants