Skip to content

fix(testing): use surgical text replacement in Jest matcher alias migration#34350

Merged
jaysoo merged 2 commits intonrwl:masterfrom
baer:fix/jest-migration-surgical-text-replacement
Feb 26, 2026
Merged

fix(testing): use surgical text replacement in Jest matcher alias migration#34350
jaysoo merged 2 commits intonrwl:masterfrom
baer:fix/jest-migration-surgical-text-replacement

Conversation

@baer
Copy link
Contributor

@baer baer commented Feb 5, 2026

Current Behavior

The replace-removed-matcher-aliases migration uses tsquery.replace() which reprints the entire AST through TypeScript's Printer. This causes two problems:

  1. Syntax corruption: Valid TypeScript files are mangled:

    • Destructuring patterns: { result } becomes {result}:
    • Arrow functions: missing opening braces
    • Nested callbacks: collapsed/merged code blocks
  2. Unnecessary file changes: Every test file is written back to disk even when no matchers are replaced. This triggers formatFiles() to reformat unchanged files, creating large whitespace-only diffs. In large codebases, this can result in hundreds or thousands of files being modified unnecessarily, making the migration PR difficult to review.

Why I care a Lot

I was running this on a multi-million-LOC monorepo and ran into two issues:

  • I got ~10k modified files with whitespace-only changes from the removal of newlines. These changes couldn't be fixed with Prettier because it didn't care about the number of newlines, so the diff was unmergeable.
  • I got ~8 files with malformed Typescript, causing commit hooks, CI, etc. to fail without manual intervention.

Expected Behavior

The migration should:

  1. Only replace the deprecated matcher names (e.g., toBeCalledtoHaveBeenCalled)
  2. Preserve all surrounding code exactly as written
  3. Only touch files that actually contain deprecated matchers

Solution

Replace AST-reprinting with surgical text replacement:

  • Use tsquery.query() to find matching AST nodes
  • Collect text positions (start/end) for each node to replace
  • Apply replacements in reverse order using string slicing
  • Only write files that actually changed

This pattern is already used successfully in other Nx migrations (e.g., rename-cy-exec-code-property.ts in the Cypress package).

Additional improvements:

  • Single AST parse with regex selector vs. 11 separate passes
  • Quick string check skips parsing files without deprecated matchers
  • New regression test covers complex patterns that triggered corruption

Related Issue(s)

Fixes #32062

@baer baer requested a review from a team as a code owner February 5, 2026 18:12
@baer baer requested a review from FrozenPandaz February 5, 2026 18:12
@netlify
Copy link

netlify bot commented Feb 5, 2026

‼️ Deploy request for nx-docs rejected.

Name Link
🔨 Latest commit 4a65b65

@netlify
Copy link

netlify bot commented Feb 5, 2026

Deploy Preview for nx-dev ready!

Name Link
🔨 Latest commit 4a65b65
🔍 Latest deploy log https://app.netlify.com/projects/nx-dev/deploys/69a091906d942d0008e8db6e
😎 Deploy Preview https://deploy-preview-34350--nx-dev.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@baer baer force-pushed the fix/jest-migration-surgical-text-replacement branch from 38852f2 to 881b479 Compare February 5, 2026 18:23
jaysoo

This comment was marked as outdated.

@nx-cloud
Copy link
Contributor

nx-cloud bot commented Feb 26, 2026

View your CI Pipeline Execution ↗ for commit 4a65b65

Command Status Duration Result
nx affected --targets=lint,test,build,e2e,e2e-c... ✅ Succeeded 1h 36m 45s View ↗
nx run-many -t check-imports check-lock-files c... ✅ Succeeded 3m 21s View ↗
nx-cloud record -- nx-cloud conformance:check ✅ Succeeded 8s View ↗
nx-cloud record -- nx sync:check ✅ Succeeded <1s View ↗
nx-cloud record -- nx format:check ✅ Succeeded 1s View ↗

☁️ Nx Cloud last updated this comment at 2026-02-26 20:17:05 UTC

baer and others added 2 commits February 26, 2026 13:25
…ration

The `replace-removed-matcher-aliases` migration was corrupting TypeScript
files by using `tsquery.replace()`, which reprints the entire AST through
TypeScript's Printer. This caused several issues:

1. **Syntax corruption**: The Printer would mangle complex patterns like
   destructuring (`{ result }` → `{result}:`), arrow functions (missing
   opening braces), and nested callbacks (collapsed blocks).

2. **Unnecessary reformatting**: Every test file was written back to disk
   even if no matchers were replaced, causing `formatFiles()` to reformat
   files that didn't need changes.

This fix replaces the AST-reprinting approach with surgical text replacement:
- Use `tsquery.query()` to find matching AST nodes
- Collect text positions (start/end) for each node to replace
- Apply replacements in reverse order using string slicing
- Only write files that actually changed

This pattern is already used successfully in other Nx migrations (e.g.,
`rename-cy-exec-code-property.ts` in the Cypress package).

Additional improvements:
- Single AST parse with regex selector vs. 11 separate passes
- Quick string check skips parsing files without deprecated matchers
- New regression test covers complex patterns that triggered corruption

Fixes nrwl#32062
@jaysoo jaysoo force-pushed the fix/jest-migration-surgical-text-replacement branch from 881b479 to 4a65b65 Compare February 26, 2026 18:31
@jaysoo jaysoo merged commit b8b6ed8 into nrwl:master Feb 26, 2026
20 of 21 checks passed
FrozenPandaz pushed a commit that referenced this pull request Feb 26, 2026
…ration (#34350)

## Current Behavior

The `replace-removed-matcher-aliases` migration uses `tsquery.replace()`
which reprints the entire AST through TypeScript's Printer. This causes
two problems:

1. **Syntax corruption**: Valid TypeScript files are mangled:
   - Destructuring patterns: `{ result }` becomes `{result}:`
   - Arrow functions: missing opening braces
   - Nested callbacks: collapsed/merged code blocks

2. **Unnecessary file changes**: Every test file is written back to disk
even when no matchers are replaced. This triggers `formatFiles()` to
reformat unchanged files, creating large whitespace-only diffs. In large
codebases, this can result in hundreds or thousands of files being
modified unnecessarily, making the migration PR difficult to review.

**Why I care a Lot**

I was running this on a multi-million-LOC monorepo and ran into two
issues:

* I got ~10k modified files with whitespace-only changes from the
removal of newlines. These changes couldn't be fixed with Prettier
because it didn't care about the number of newlines, so the diff was
unmergeable.
* I got ~8 files with malformed Typescript, causing commit hooks, CI,
etc. to fail without manual intervention.

## Expected Behavior

The migration should:
1. Only replace the deprecated matcher names (e.g., `toBeCalled` →
`toHaveBeenCalled`)
2. Preserve all surrounding code exactly as written
5. Only touch files that actually contain deprecated matchers

## Solution

Replace AST-reprinting with surgical text replacement:
- Use `tsquery.query()` to find matching AST nodes
- Collect text positions (start/end) for each node to replace
- Apply replacements in reverse order using string slicing
- Only write files that actually changed

This pattern is already used successfully in other Nx migrations (e.g.,
`rename-cy-exec-code-property.ts` in the Cypress package).

**Additional improvements:**
- Single AST parse with regex selector vs. 11 separate passes
- Quick string check skips parsing files without deprecated matchers
- New regression test covers complex patterns that triggered corruption

## Related Issue(s)

Fixes #32062

---------

Co-authored-by: Jack Hsu <jack.hsu@gmail.com>
(cherry picked from commit b8b6ed8)
FrozenPandaz pushed a commit that referenced this pull request Feb 26, 2026
…ration (#34350)

## Current Behavior

The `replace-removed-matcher-aliases` migration uses `tsquery.replace()`
which reprints the entire AST through TypeScript's Printer. This causes
two problems:

1. **Syntax corruption**: Valid TypeScript files are mangled:
   - Destructuring patterns: `{ result }` becomes `{result}:`
   - Arrow functions: missing opening braces
   - Nested callbacks: collapsed/merged code blocks

2. **Unnecessary file changes**: Every test file is written back to disk
even when no matchers are replaced. This triggers `formatFiles()` to
reformat unchanged files, creating large whitespace-only diffs. In large
codebases, this can result in hundreds or thousands of files being
modified unnecessarily, making the migration PR difficult to review.

**Why I care a Lot**

I was running this on a multi-million-LOC monorepo and ran into two
issues:

* I got ~10k modified files with whitespace-only changes from the
removal of newlines. These changes couldn't be fixed with Prettier
because it didn't care about the number of newlines, so the diff was
unmergeable.
* I got ~8 files with malformed Typescript, causing commit hooks, CI,
etc. to fail without manual intervention.

## Expected Behavior

The migration should:
1. Only replace the deprecated matcher names (e.g., `toBeCalled` →
`toHaveBeenCalled`)
2. Preserve all surrounding code exactly as written
5. Only touch files that actually contain deprecated matchers

## Solution

Replace AST-reprinting with surgical text replacement:
- Use `tsquery.query()` to find matching AST nodes
- Collect text positions (start/end) for each node to replace
- Apply replacements in reverse order using string slicing
- Only write files that actually changed

This pattern is already used successfully in other Nx migrations (e.g.,
`rename-cy-exec-code-property.ts` in the Cypress package).

**Additional improvements:**
- Single AST parse with regex selector vs. 11 separate passes
- Quick string check skips parsing files without deprecated matchers
- New regression test covers complex patterns that triggered corruption

## Related Issue(s)

Fixes #32062

---------

Co-authored-by: Jack Hsu <jack.hsu@gmail.com>
(cherry picked from commit b8b6ed8)
@github-actions
Copy link
Contributor

github-actions bot commented Mar 4, 2026

This pull request has already been merged/closed. If you experience issues related to these changes, please open a new issue referencing this pull request.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Mar 4, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Running migration @nx/jest: replace-removed-matcher-aliases fails with typescript error

2 participants