feat: add composer-based set binding every rule to an exact drupal/core version - #419
feat: add composer-based set binding every rule to an exact drupal/core version#419TomasVotruba wants to merge 14 commits into
Conversation
…re version
Replaces `DrupalSetProvider` — Rector deprecated `SetProviderInterface` and
`ComposerTriggeredSet` in favour of binding rules to a package version
directly — with a single generated set, `config/composer-based.php`, exposed
as `DrupalSetList::COMPOSER_BASED`.
Every configurable rule is registered through Rector 2.6's
`RectorConfig::ruleWithConfigurationComposerVersionBound()` with the exact
version its deprecation was introduced in, taken from the value object it is
already configured with:
-$rectorConfig->ruleWithConfiguration(FileSystemBasenameToNativeRector::class, [
- new DrupalIntroducedVersionConfiguration('11.3.0'),
-]);
+$rectorConfig->ruleWithConfigurationComposerVersionBound(FileSystemBasenameToNativeRector::class, [
+ new DrupalIntroducedVersionConfiguration('11.3.0'),
+], 'drupal/core', '>=11.3.0');
Rules that take no configuration cannot use that API, so they are registered
behind a `$ruleSince()` helper that checks the same constraint against the
installed `drupal/core`:
-$rectorConfig->rule(LoadAllIncludesRector::class);
+$ruleSince(LoadAllIncludesRector::class, '>=11.3.0');
Rector then activates only the rules the installed core satisfies — 34 of the
124 bound registrations on core 10.3, 63 on 11.2, 124 on 12.0 — and
`vendor/bin/rector composer-based` lists them with their constraint. Because
the installed version is known exactly, the opt-in "breaking" renames are
included; they cannot fatal on a core guaranteed to have the replacement.
The per-minor `Drupal*SetList` constants are unchanged and remain the way to
look ahead at a Drupal version that is not installed yet.
The set is generated by `scripts/generate-composer-based.php` from the
per-minor configs, so a rule only has to be registered once;
`ComposerBasedSetTest` asserts the committed file is in sync, that every
registration carries an exact `>=X.Y.Z` constraint, and that no rule of the
per-minor sets is missing from it.
…rupal: true) Restores `DrupalSetProvider` so existing `withSetProviders(DrupalSetProvider::class)` configs keep working; its docblock now points at `DrupalSetList::COMPOSER_BASED` as the successor. Drops the explicit `withSets([DrupalSetList::COMPOSER_BASED])` from the README and from the generated set's docblock — `withComposerBased(drupal: true)` is the documented entry point.
Adds the `@deprecated` annotation and a CHANGELOG "Deprecated" entry for `DrupalSetProvider` and the `drupal` / `drupal (breaking)` set groups it provides. The class stays registered so existing configs keep working, and is removed in a later release.
…hConfigurationComposerVersionBound()
|
Still WIP... need a bit config tuning. But check the linked PRs in the description to get the idea ahead 👍 |
The generator rewrote the per-minor configs by walking their PHP tokens and patching the registration calls, which made the set a build artifact of a parser rather than a config you can read. Keeps the set as a plain config of real rules, in the shape rector-doctrine uses (rectorphp/rector-doctrine#497), and accepts that a rule is registered both here and in its per-minor config. `ComposerBasedSetTest` fails when a rule of a per-minor config is missing here, so the duplication cannot drift unnoticed.
…e shape Follows rectorphp/rector-doctrine#497: a rule that takes no configuration declares the version its target API landed in on the rule class, instead of being gated by the set that registers it. -$ruleSince(LoadAllIncludesRector::class, '>=11.3.0'); +$rectorConfig->rule(LoadAllIncludesRector::class); +final class LoadAllIncludesRector extends AbstractRector implements ComposerPackageConstraintInterface, DocumentedRuleInterface +{ + public function provideComposerPackageConstraint(): ComposerPackageConstraint + { + return new ComposerPackageConstraint('drupal/core', '>=11.3.0'); + } 76 rules are bonded this way, which drops the `$ruleSince()` helper and the `Semver` lookup from the set. The three rector-phpunit rules the Drupal 10.1 config registers are dropped from the set as well: they are already bonded to `phpunit/phpunit` by rector-phpunit's own composer-based set, which is the accurate constraint for them. Rector applies the constraint filter globally rather than per set, so these rules no longer run against a core older than the deprecation, including when a `Drupal*SetList` set is loaded by hand. Verified against a project on 11.2.3 and one on 11.3.0: `LoadAllIncludesRector` fires only on the latter. Rule tests point `provideComposerJsonFilePath()` at a stub composer.json that requires drupal/core, as this package does not — without it every bonded rule would be filtered out of its own test.
- `ComposerBasedSetTest` used `new ReflectionClass(…)->…`, which only parses on PHP 8.4; the phpunit and phpstan workflows run 8.3. - Both workflows installed `rector/rector:^2` before running, which can resolve below the 2.6 that ships `ruleWithConfigurationComposerVersionBound()` and `ComposerPackageConstraintInterface`. Bumped the matrix to `^2.6`, matching the constraint in composer.json. - `ShouldCallParentMethodsRector` and `AddSymfonyConstraintValidatorTypeDeclarationsRector` were each registered by two per-minor configs. A bonded rule carries one constraint, so each is bonded to the lower of its two versions and registered once, under that version. Verified on PHP 8.3 with `composer require rector/rector:^2.6 --dev` applied the way the workflows do it: php-cs-fixer 0/523, PHPStan no errors, PHPUnit 699 tests.
The checks are static properties of the config and the rule classes, so PHPStan is where they belong — they now report at the offending line rather than as a regex assertion over the file contents, and cover every rule class rather than only what the set file happens to reference. - BoundRuleConfigurationRule — a ruleWithConfigurationComposerVersionBound() call must name "drupal/core" and an exact ">=X.Y.Z" version. - ComposerPackageConstraintRule — the same for every ComposerPackageConstraint a rule class constructs. - PlainlyRegisteredRuleRule — a rule registered with a plain rule() call in the composer-based set must implement ComposerPackageConstraintInterface, otherwise it runs on every Drupal version. - ComposerBasedSetCoverageRule — with RegisteredRectorClassCollector, fails when a per-minor config registers a rule the composer-based set does not. Each rule is covered by a RuleTestCase, and all four were verified against a deliberately broken tree: a ">=8.0" constraint, a rule class stripped of the interface, a "drupal/coder" package name, and a registration deleted from the set each produce the expected error.
|
Ready now 👍 |
|
Ping @bbrala |
|
We should bump the required rector version and make So that Or all sets needs to check that the constant like |
* fix: conflict with rector/rector >=2.6.2 (#420) Rector 2.6.2 removed every version-specific set constant from its first-party extension packages in favour of the new composer-based sets (rectorphp/rector-phpunit#760). The Drupal 8, 9 and 10 configs reference those constants directly, so from 2.6.2 on they abort before any rule runs. Bisected against real installs; 2.6.1 is the last release on which every Drupal set loads: 2.5.9 / 2.6.0 / 2.6.1 ok 2.6.2 / 2.6.3 / 2.6.6 fail Breakage map on 2.6.6: DRUPAL_90, DRUPAL_100 Could not detect twig set. DRUPAL_91, DRUPAL_92 Undefined constant PHPUnitSetList::PHPUNIT_90 DRUPAL_101 Undefined constant SymfonySetList::SYMFONY_63 DRUPAL_102 Undefined constant SymfonySetList::SYMFONY_64 plus the DRUPAL_8 / DRUPAL_9 / DRUPAL_10 aggregates that include them. The Drupal 11 and 12 sets reference no third-party sets and are unaffected. A conflict rather than a narrowed require constraint, so the supported range stays documented as ^2 and composer reports the incompatibility by name. This is a stopgap to keep installs working. The real fix is porting the sets to the composer-based mechanism (#419), which Rector 2.6.6 already expects: withComposerBased(drupal: true) resolves DrupalRector\Set\DrupalSetList:: COMPOSER_BASED, and SetGroup::DRUPAL is now marked deprecated upstream in favour of a composer-based.php set. The conflict is lifted once that lands. Reported by ptmkenny in #420 * style: use ?int over null|int in RemoveToolkitArgFromImageToolkitOperationConstructor Unrelated to #420, but the codestyle job is failing on main's code and blocks this branch. The @symfony ruleset in php-cs-fixer 3.95.24 enforces nullable_type_declaration with the question_mark style. The dev constraint is ^3.95.1, so the rule arrived by a floating minor rather than by any change here — the file has been unchanged since it was merged green. Only the php-cs-fixer job would have caught it, and the scheduled runs cover phpstan, phpunit and functional tests only, so it went unnoticed. No behavior change; ?int and null|int are the same type.
|
Ok, back from vacation. I did a ninja release for this package to lock it out of >2.6.1 so it doesnt break while this is worked on. There is another path in this, and that is removing the specific rules for twig and phpunit maybe. Also kinda interested in how this works. A rule tells us: ">=10.1.0", does that mean it is selected on anything above, or anything above but belore next major? So does I wouldn't mind to much to drop the set references, but am a little worried to throw things around again with how the sets work. |
|
Welcome back :) many news in Rector. Just the >=. Checkout my PRs in Laravel Rector package, where I did the same upgrade. |
Hey, I'm improving the way Rector find composer-based rules and sets. All you need is:
ComposerPackageConstraintInterfaceruleWithConfigurationComposerVersionBound- e.g. for generic rules like rename class:The set provider, SetList, versioned set list can be dropped (in one of next PRs, not now).
It's agent generated, but it works well on Doctrine/Symfony/PHPUnit sets, see e.g.
I'll open this for feedback if it's all done correctly :) do not merge yet.
I'll make sure it's part of next Rector release and everything works as smoothly as before.
Feedback welcomed 👍
I've also added custom PHPStan rule to make sure all the rules are registered in
composer-based.phpconfig and nothing is missed by accident 😉Replaces
DrupalSetProvider— Rector deprecatedSetProviderInterfaceandComposerTriggeredSetin favour of binding rules to a package version directly - with a single generated set,config/composer-based.php, exposed asDrupalSetList::COMPOSER_BASED.