Skip to content

Make exposed downgrade rules idempotent on repeated application - #409

Merged
TomasVotruba merged 1 commit into
mainfrom
tv-downgrade-idempotent
Oct 18, 2026
Merged

TomasVotruba merged 1 commit into
mainfrom
tv-downgrade-idempotent

Conversation

@TomasVotruba

Copy link
Copy Markdown
Member

A few downgrade rules were not idempotent - running the rule more than once over the same file kept transforming its own output (infinite re-wrapping or re-appending). This is harmless today, but the upcoming rector-src node-traverser simplification drops the created-by-rule reprocessing guard, so each rule must settle on its own.

Fixes:

  • DowngradeFlexibleHeredocSyntaxRector - left a marker on the doc indentation attribute once flattened, so a later pass skips the node instead of appending another newline.
  • DowngradeNewMethodCallWithoutParenthesesRector - the already-parenthesized guard checked the method-call start token, but the opening paren is prepended to the new var token; now both positions are checked.
  • DowngradeIsEnumRector - skip an isEnum() call already wrapped in a method_exists(...) ? ... : false ternary, using the same IS_IF_TERNARY idiom as DowngradeReflectionGetAttributesRector.
  • DowngradePhp73JsonConstRector - returning an array replaces the Expression, so the traverser never descended into the ConstFetch to clean the flag; the flag is now cleaned in place, so the error check is appended once.
  • DowngradeCovariantReturnTypeRector - only downgrade to a wider ancestor type; a multi-level hierarchy previously flipped the return type back and forth.
  • DowngradeProcOpenArrayCommandArgRector - skip a command arg already wrapped in the is_array(...) ? implode(...) : ... ternary (its type is string|array, so the string check did not catch it).

All affected rule tests pass, plus ECS and PHPStan.

These rules kept transforming their own output when a rule runs more
than once over the same file:

- DowngradeFlexibleHeredocSyntaxRector: marker so a flattened doc is skipped
- DowngradeNewMethodCallWithoutParenthesesRector: guard checked the wrong token position
- DowngradeIsEnumRector: skip isEnum() already wrapped in a method_exists ternary
- DowngradePhp73JsonConstRector: clean the flag in-place, so the error check is appended once
- DowngradeCovariantReturnTypeRector: only downgrade to a wider ancestor type
- DowngradeProcOpenArrayCommandArgRector: skip a command arg already wrapped
@TomasVotruba
TomasVotruba merged commit 6f458fe into main Oct 18, 2026
9 checks passed
@TomasVotruba
TomasVotruba deleted the tv-downgrade-idempotent branch October 18, 2026 13:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant