Skip to content

JavaScript: Detect empty diffs in rewriteRun() test harness and fix surfaced issues#6904

Merged
knutwannheden merged 5 commits intomainfrom
fancy-whale
Mar 10, 2026
Merged

JavaScript: Detect empty diffs in rewriteRun() test harness and fix surfaced issues#6904
knutwannheden merged 5 commits intomainfrom
fancy-whale

Conversation

@knutwannheden
Copy link
Copy Markdown
Contributor

@knutwannheden knutwannheden commented Mar 10, 2026

Summary

  • Add empty diff detection to the JavaScript rewriteRun() test harness — throws when a recipe changes the AST but printed output is identical (allowEmptyDiff = false by default).
  • Fix produceTree() in TreeVisitor to preserve object identity when no changes occur.
  • Fix RemoveImport: use updateIfChanged and eliminate dead length check in filter guard.
  • Fix OrderImports: add early return when imports are already sorted.
  • Fix MinimumViableSpacingVisitor: remove line that incorrectly stripped the space before { in class declarations.
  • Fix WrappingAndBracesVisitor: skip produce() when leadingAnnotations is empty; rename blockNeedsOneLineblockIsMultiLine for clarity.
  • Fix SpacesVisitor: simplify import space handling — parser always places the space on importClause.prefix, so no ambiguity workaround needed.
  • Fix WhitespaceReconciler: compare Space objects structurally instead of by reference.

Test plan

  • All 1795 tests pass, 0 failures
  • Typecheck passes cleanly
  • New tests: "raises error on empty diff by default" and "allowEmptyDiff suppresses the error"
  • All 255 format + recipe tests pass
  • Verified ChangeText preserves identity for no-op (no allowEmptyDiff needed)

Add allowEmptyDiff option to RecipeSpec (default false) that catches
recipes which modify the AST without producing visible output changes.
This surfaces parser/formatter bugs where whitespace is placed on the
wrong AST element.

Fix produceTree() to preserve object identity when neither the recipe
nor marker visiting changed anything, preventing false positives from
the base visitor infrastructure.
The empty diff detection added in the previous commit surfaced real
identity-preservation bugs across several visitors and recipes:

- MinimumViableSpacingVisitor: remove unconditional body prefix clear
  that stripped the space before '{' in class declarations
- WrappingAndBracesVisitor: skip produce() when leadingAnnotations is
  empty; restructure visitBlock/visitElse to check conditions first
- SpacesVisitor: add guard to skip produce() on already-correct imports;
  avoid moving whitespace between equivalent AST positions
- WhitespaceReconciler: compare Space objects structurally instead of by
  reference so identical whitespace doesn't create new objects
- OrderImports: early return when imports are already sorted
- RemoveImport: use updateIfChanged instead of object spreads; only
  filter statements when removals actually occurred
@github-project-automation github-project-automation Bot moved this to In Progress in OpenRewrite Mar 10, 2026
@knutwannheden knutwannheden changed the title Detect empty diffs in rewriteRun() test harness and fix surfaced issues JavaScript: Detect empty diffs in rewriteRun() test harness and fix surfaced issues Mar 10, 2026
- Remove duplicated importNeedsSpaceChanges guard; Mutative natively
  detects same-value assignments and preserves object identity, so
  conditional guards are unnecessary for scalar property writes.
- Add TODO noting parser normalization issue for import keyword space.
- Add clarifying comments: Mutative structural sharing invariant in
  produceTree, SpacesVisitor body.prefix responsibility in MVS,
  TextComment-only caveat in spacesEqual.
- Fix test isolation: use local RecipeSpec instead of shared instance
  that leaked allowEmptyDiff between tests.
The parser always places the space after 'import' on importClause.prefix
(both ImportClause and NamedBindings share the same trivia span, but
ImportClause consumes it first). Remove the workaround that accepted
the space in either position and the associated TODO.

Also remove unused type parameter on ensurePrefixHasNewLine.
- Remove dead length check in remove-import.ts filter guard (mapAsync
  preserves array length, so the check was always equal)
- Rename blockNeedsOneLine to blockIsMultiLine for clarity
- Remove unnecessary allowEmptyDiff in test — ChangeText correctly
  preserves identity via produceTree when text is unchanged
- Add comment noting spread cost trade-off in produceTree
@knutwannheden knutwannheden merged commit 10aa10f into main Mar 10, 2026
1 check passed
@knutwannheden knutwannheden deleted the fancy-whale branch March 10, 2026 11:18
@github-project-automation github-project-automation Bot moved this from In Progress to Done in OpenRewrite Mar 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

1 participant