Skip to content

[composer-based] Register each rule only once, add --composer-based process filter - #8248

Merged
TomasVotruba merged 1 commit into
mainfrom
composer-based-dedupe-rules
Jul 31, 2026
Merged

[composer-based] Register each rule only once, add --composer-based process filter#8248
TomasVotruba merged 1 commit into
mainfrom
composer-based-dedupe-rules

Conversation

@TomasVotruba

@TomasVotruba TomasVotruba commented Jul 31, 2026

Copy link
Copy Markdown
Member

1. Each rule is registered only once

RectorConfig::rule() tagged the class on every call. A rule registered by more than one set got one tag entry per set, and giveTagged(RectorInterface::class) handed the same singleton back once per entry.

That means the rule was run repeatedly on every node, and listed repeatedly in list-rules / composer-based:

  AnnotationWithValueToAttributeRector           phpunit/phpunit   >=10.0     11.5.56.0   yes
- AnnotationWithValueToAttributeRector           phpunit/phpunit   >=10.0     11.5.56.0   yes
  MockObjectArgCreateStubToCreateMockRector      phpunit/phpunit   >=11.0     11.5.56.0   yes
- MockObjectArgCreateStubToCreateMockRector      phpunit/phpunit   >=11.0     11.5.56.0   yes
- MockObjectArgCreateStubToCreateMockRector      phpunit/phpunit   >=11.0     11.5.56.0   yes

Reproduced with 3 rule() calls for the same class - $container->tagged(RectorInterface::class) returned 3 instances before, 1 after.

 $this->singleton($rectorClass);
-$this->tag($rectorClass, RectorInterface::class);

-// for cache invalidation in case of change
-SimpleParameterProvider::addParameter(Option::REGISTERED_RECTOR_RULES, $rectorClass);
+// the same rule can be registered by multiple sets, tag it only once,
+// otherwise it is run twice on every node and listed twice in the reports
+if (! isset($this->registeredRectorClasses[$rectorClass])) {
+    $this->registeredRectorClasses[$rectorClass] = true;
+
+    $this->tag($rectorClass, RectorInterface::class);
+
+    // for cache invalidation in case of change
+    SimpleParameterProvider::addParameter(Option::REGISTERED_RECTOR_RULES, $rectorClass);
+}

Same for composer bound rule configurations - the same rule class + package + constraint + configuration is reported once, no matter how many sets register it.

2. --composer-based process option

Runs only the rules that are bound to an installed composer package version - rules implementing ComposerPackageConstraintInterface, and rules whose configuration was registered through ruleWithConfigurationComposerVersionBound().

vendor/bin/rector process src --composer-based --dry-run

Complements the composer-based command from #8247, which reports the same set of rules and configurations without running them.

…rocess filter

A rule registered by multiple sets was tagged repeatedly, so the same singleton
was handed out N times: run N times per node, and listed N times in reports.

Also adds the --composer-based process option and the composer-bound rule
configuration report.
@TomasVotruba
TomasVotruba force-pushed the composer-based-dedupe-rules branch from 00549b3 to 5fe8e35 Compare July 31, 2026 20:21
@TomasVotruba
TomasVotruba merged commit afaada8 into main Jul 31, 2026
65 checks passed
@TomasVotruba
TomasVotruba deleted the composer-based-dedupe-rules branch July 31, 2026 20:28
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