Skip to content

fix(snapshot): make inline comment-skip regex non-greedy#10594

Open
martypdx wants to merge 1 commit into
vitest-dev:mainfrom
martypdx:fix/inline-snapshot-greedy-comment-regex
Open

fix(snapshot): make inline comment-skip regex non-greedy#10594
martypdx wants to merge 1 commit into
vitest-dev:mainfrom
martypdx:fix/inline-snapshot-greedy-comment-regex

Conversation

@martypdx

Copy link
Copy Markdown

Description

Multiple toMatchInlineSnapshot / toThrowErrorMatchingInlineSnapshot calls in a single file that carry block-comment directives (e.g. /* HTML */) corrupt each other on --update: only the last snapshot is written — mangled — and the earlier ones are silently left empty.

Root cause. The comment-skip alternative in defaultStartObjectRegex and defaultStartRegex (packages/snapshot/src/port/inlineSnapshot.ts) uses a greedy \/\*[\s\S]*\*\/. When several such calls share a file, the per-call match runs from that call's /* to the last call's */, so each earlier call's computed start index lands inside the last call. MagicString then coalesces the overlapping overwrite()s onto that one location. The run still reports success, so the corruption is silent.

Repro (vitest --update):

test('x', () => {
  expect('a').toMatchInlineSnapshot(/* HTML */)
  expect('b').toMatchInlineSnapshot(/* HTML */)
  expect('c').toMatchInlineSnapshot(/* HTML */)
})

Before — only the last call writes, mangled; a/b are left empty:

  expect('a').toMatchInlineSnapshot(/* HTML */)
  expect('b').toMatchInlineSnapshot(/* HTML */)
  expect('c').toMatchInlineSnapshot(/* HTML */`"a"``"b"``"c"`)

After — each call writes on its own line:

  expect('a').toMatchInlineSnapshot(/* HTML */`"a"`)
  expect('b').toMatchInlineSnapshot(/* HTML */`"b"`)
  expect('c').toMatchInlineSnapshot(/* HTML */`"c"`)

Fix. Make the comment-skip lazy ([\s\S]*[\s\S]*?) in both regexes. A block comment cannot legally contain */, so the lazy form is equivalent for valid input — it simply stops at each call's own comment close.

Related: #8632 is a different comment-position case (a comment between the method name and (), single call); it is not addressed by this change.

Tests

Adds an e2e regression — test/e2e/snapshots/inline-comment-directive.test.ts with fixture test/e2e/snapshots/fixtures/inline-multiple-calls/comment-directive.test.ts. It fails before this change (only gamma written, mangled) and passes after.

Verified locally:

  • New regression: fails on main, passes with the fix.
  • Existing inline-multiple-calls, soft-inline, domain-inline e2e suites: pass.
  • Unit snapshot-inline suite: pass.
  • No pnpm-lock.yaml changes.

When multiple `toMatchInlineSnapshot` / `toThrowErrorMatchingInlineSnapshot`
calls in one file carry block-comment directives (e.g. `/* HTML */`), the
greedy comment-skip `\/\*[\s\S]*\*\/` in `defaultStartObjectRegex` and
`defaultStartRegex` matched from the first call's `/*` to the LAST call's
`*/`. On `--update`, every earlier call's write coalesced onto the last
call's location: only the final snapshot was written — mangled, e.g.
`toMatchInlineSnapshot(/* HTML */`"a"``"b"``"c"`)` — and the earlier calls
were left empty. Silently, since the run still reports success.

Make the comment-skip lazy (`[\s\S]*?`) in both regexes. A block comment
cannot contain `*/`, so the lazy form is equivalent for valid input.

Adds an e2e regression (fixtures/inline-multiple-calls/comment-directive
+ the inline-comment-directive driver) that fails before this change and
passes after.
@martypdx martypdx changed the title brewfix(snapshot): make inline comment-skip regex non-greedy fix(snapshot): make inline comment-skip regex non-greedy Jun 14, 2026
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.

1 participant