Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .claude/skills/rector-live-test/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,9 @@ use Rector\Config\RectorConfig;

return static function (RectorConfig $rectorConfig): void {
$rectorConfig->fileExtensions(['php', 'module', 'theme', 'install', 'profile', 'inc']);
$rectorConfig->bootstrapFiles([
__DIR__.'/vendor/palantirnet/drupal-rector/config/drupal-phpunit-bootstrap-file.php',
]);
$rectorConfig->ruleWithConfiguration(<ClassName>::class, [
new DrupalIntroducedVersionConfiguration('<introduced-version>'),
]);
Expand All @@ -183,6 +186,9 @@ git -C ~/projects/drupal-rector-test checkout -- web/modules/contrib/
rm ~/projects/drupal-rector-test/rector-live-test.php
```

If the contrib modules are not git-tracked in the test project, `git checkout` won't restore them.
Use `ddev composer reinstall drupal/<module1> drupal/<module2> --no-interaction` instead.

### 5. Capture the PHPStan deprecation message

While the contrib module is still installed and the pre-transform code is on
Expand Down
160 changes: 160 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,89 @@ release-by-release.

### Added

- **`ReplaceNonBoolAccessRector`** — rewrites integer-literal `#access`
values inside render arrays to proper booleans: `1` (or any non-zero
integer) becomes `true`, and `0` becomes `false`. Passing non-boolean,
non-`AccessResultInterface` values to `#access` was deprecated in
drupal:11.4.0 and will be removed in drupal:13.0.0. The rule matches
on `ArrayItem` nodes whose key is the string literal `'#access'` and
whose value is an integer literal — it deliberately ignores booleans,
variables, function/method calls, and any other expression, because
the correct boolean replacement for those cannot be determined
statically. The replacement is pure PHP (`true` / `false`), so no BC
wrapping is needed; the transformed code runs on every Drupal version.
[#3526250](https://www.drupal.org/i/3526250).
- **`ReplaceDialogClassOptionRector`** — rewrites the removed
`$dialog_options['dialogClass']` key to
`$dialog_options['classes']['ui-dialog']` in `new OpenDialogCommand(...)` and
`new OpenOffCanvasDialogCommand(...)` calls. `dialogClass` was deprecated in
drupal:11.3.0 and removed in drupal:12.0.0. Receiver narrowing is by FQCN
match on the resolved `New_->class` (`Drupal\Core\Ajax\OpenDialogCommand` or
`Drupal\Core\Ajax\OpenOffCanvasDialogCommand`), so unrelated constructors
with similarly-shaped option arrays are left alone. Handles three array
shapes: (a) no existing `classes` key → adds `'classes' => ['ui-dialog' => $value]`;
(b) `classes` exists without `ui-dialog` → adds `'ui-dialog' => $value` inside
it; (c) `classes['ui-dialog']` already present and both old/new values are
string literals → concatenates with a space. Non-literal values (variables,
function calls, dynamic arrays) at any position cause the rule to skip
rather than guess. The replacement form `classes['ui-dialog']` has existed
in core since 10.3.x, so the transformed output runs on every
drupal-rector–supported Drupal minor (D10.3+) — no BC wrapping needed.
[#3571054](https://www.drupal.org/i/3571054) /
[CR](https://www.drupal.org/node/3440844).
- **`RemoveToolkitArgFromImageToolkitOperationConstructorRector`** —
removes the deprecated `ImageToolkitInterface $toolkit` 4th argument
from `ImageToolkitOperationBase` subclass constructors and strips it
from the matching `parent::__construct()` call. The parameter was
deprecated in drupal:11.4.0 and will be removed in drupal:13.0.0; the
plugin manager now injects the toolkit via `setToolkit()` after
instantiation, enabling constructor autowiring. The rector only fires
when the subclass constructor has at least five parameters, the 4th
is typed exactly as `\Drupal\Core\ImageToolkit\ImageToolkitInterface`,
and `$toolkit` appears exactly once in the constructor body (as the
4th argument of `parent::__construct()`). Classes that reference
`$toolkit` anywhere else in the constructor body are left untouched
to avoid breaking code that uses the variable before `setToolkit()`
can be called. No BC wrapping is needed: the parent signature
accepts the union `LoggerInterface|ImageToolkitInterface`, so the
transformed code runs on every drupal:11.4.x+ version.
[#3559481](https://www.drupal.org/i/3559481) /
[CR](https://www.drupal.org/node/3562304).
- **`RemoveRendererAddCacheableDependencyNonObjectRector`** — deletes calls
to `RendererInterface::addCacheableDependency($elements, $dependency)`
whose second argument is statically provable to be a non-object
(`bool`, `int`, `float`, `string`, `null`, or `array`). Passing such
values was deprecated in drupal:11.3.0 and will throw in
drupal:13.0.0; at runtime it silently sets `max-age = 0` on the
render array, making the page uncacheable for no useful gain. The
rector matches at statement level so the entire expression is
removed, never partially rewritten. The receiver is type-guarded to
`\Drupal\Core\Render\RendererInterface` (catching the concrete
`Renderer` and any other implementer), and the argument count must
be exactly two — this distinguishes the call from the single-argument
`addCacheableDependency()` defined on `BubbleableMetadata` and
`RefinableCacheableDependencyInterface`. The PHPStan type of the
dependency must satisfy `isObject()->no()`, so any call where the
argument might be an object at runtime (variables typed as configs,
entities, or `mixed`) is left untouched — the rector targets only
the unambiguous primitive-passing mistake the deprecation was added
to flag. No BC wrapping is needed because the removed call is a
silent uncacheability bug on every Drupal version.
[#3525388](https://www.drupal.org/i/3525388) /
[CR](https://www.drupal.org/node/3525389).
- **`RemoveInstallSchemaSystemSequencesRector`** — removes deprecated
`KernelTestBase::installSchema('system', 'sequences')` calls in test
classes. The `sequences` table was deprecated in drupal:10.2.0 and
fully removed in drupal:12.0.0 via [#3335756](https://www.drupal.org/i/3335756);
the call now throws a `LogicException` on Drupal 12. The rule removes
the entire statement when the second argument is the string
`'sequences'` or an array containing only `'sequences'`; when the
array also lists other tables, only the `'sequences'` entry is
stripped. Receiver is type-guarded to `Drupal\KernelTests\KernelTestBase`,
so unrelated `installSchema()` methods on other classes are left
untouched. See change record
[#3349345](https://www.drupal.org/node/3349345).

- **`RemovePhpUnitCompatibilityTraitRector`** — removes
`use Drupal\Tests\PhpUnitCompatibilityTrait;` from test class
declarations. The trait was a forward-compatibility shim for PHPUnit
Expand All @@ -36,6 +119,24 @@ release-by-release.
orphan top-of-file `use Drupal\Tests\PhpUnitCompatibilityTrait;`
import is left in place — PHP never resolves an unused alias, so it
remains harmless on D12; cleanup is optional and out of scope.
- **`GroupLegacyToIgnoreDeprecationsRector`** — replaces the
`@group legacy` PHPDoc annotation with PHPUnit 10's native
`#[\PHPUnit\Framework\Attributes\IgnoreDeprecations]` attribute on
both method- and class-level test declarations. Drupal 11 dropped the
`symfony/phpunit-bridge` dependency and adopted PHPUnit 10, whose
attribute supersedes the bridge's docblock annotation. A Drupal shim
preserves the annotation form for BC, so the rewrite is purely a
forward-compatibility cleanup rather than a hard requirement, but
test classes that already declare the attribute are skipped so the
rule is idempotent. The rector strips just the `@group legacy` line
from the docblock — surrounding annotations (`@covers`, `@dataProvider`,
description text) are preserved — and inserts the attribute
immediately above the method or class declaration. PHPStan cannot
surface this deprecation because `@group legacy` is a docblock
convention, not a code-level `@deprecated` symbol; this rule must be
applied proactively as part of a PHPUnit 10 migration.
[#3417066](https://www.drupal.org/i/3417066) /
[related: #3365413](https://www.drupal.org/i/3365413).
- **`RemoveAliasManagerCacheMethodCallsRector`** — deletes calls to
`AliasManager::setCacheKey()` and `AliasManager::writeCache()`. Both
methods were deprecated in drupal:11.3.0 and are removed in
Expand Down Expand Up @@ -145,6 +246,60 @@ release-by-release.
`RenameClassRector` in `drupal-11.3-deprecations.php`.
[#3571874](https://www.drupal.org/i/3571874) /
[CR](https://www.drupal.org/node/3527501).
- Class-rename entry for `I18nQueryTrait`:
`Drupal\content_translation\Plugin\migrate\source\I18nQueryTrait` →
`Drupal\migrate_drupal\Plugin\migrate\source\I18nQueryTrait`. The trait was
moved because it is only needed by `migrate_drupal` source plugins, not by
`content_translation` itself. Deprecated in drupal:11.2.0, removed in
drupal:12.0.0; registered via Rector's built-in `RenameClassRector` in
`drupal-11.2-deprecations.php`. When the trait is used via a short-name
`use I18nQueryTrait;` import, `RenameClassRector` rewrites the class-body
trait composition to the fully-qualified new name but leaves the old `use`
import statement as dead code — a separate dead-import pass (PHPStan or
PHP-CS-Fixer) can clean it up.
[#3258581](https://www.drupal.org/i/3258581) /
[CR](https://www.drupal.org/node/3439256).
- Class-rename entry for `LibraryDiscovery`:
`Drupal\Core\Asset\LibraryDiscovery` → `Drupal\Core\Asset\LibraryDiscoveryInterface`.
The concrete class was deprecated in drupal:11.1.0 and removed in drupal:12.0.0;
the `library.discovery` service is now backed by `LibraryDiscoveryCollector`,
so consumer code should type-hint the interface. Registered via Rector's
built-in `RenameClassRector` in `drupal-11.1-deprecations.php`.
`LibraryDiscoveryInterface` has existed since Drupal 10.0.x, so the rewrite is
safe across all supported Drupal 10 and 11 minors.
[#3462871](https://www.drupal.org/i/3462871) (deprecation) /
[#3571057](https://www.drupal.org/i/3571057) (removal) /
[CR](https://www.drupal.org/node/3462970).
- Class-rename entry for `EntityPermissionsRouteProviderWithCheck`:
`Drupal\user\Entity\EntityPermissionsRouteProviderWithCheck` →
`Drupal\user\Entity\EntityPermissionsRouteProvider`. The `WithCheck` variant was
deprecated in drupal:11.1.0 and removed in drupal:12.0.0; the base provider has
existed since Drupal 10.0.x, so the rewrite is safe across all supported Drupal
10 and 11 minors. Registered via Rector's built-in `RenameClassRector` in
`drupal-11.1-deprecations.php`. **Access-check semantics:** the `WithCheck`
variant layered a `_custom_access` requirement (`EntityPermissionsForm::access`,
also removed) on top of the base route to deny access when an entity type had
no entity-specific permissions; the base provider already enforces
`_permission: administer permissions`, so the security boundary is preserved
and only the "no permissions defined → deny" convenience check is dropped.
Subclass overrides that re-added the custom check are NOT rewritten —
`RenameClassRector` only updates the parent class reference, so owners of such
subclasses must port any remaining access logic into the route definition
(the new model adds permission requirements directly on the route).
**Limitation — doctrine annotation strings are not rewritten.** Real-world
contrib usage is almost exclusively the entity-annotation form
(`"permissions" = "Drupal\user\Entity\EntityPermissionsRouteProviderWithCheck"`
inside a `@ContentEntityType` / `@ConfigEntityType` docblock).
`RenameClassRector` only touches PHP `Name` nodes (`use`, `extends`,
`implements`, `::class`, typehints, `instanceof`) — it does **not** scan
string literals inside doctrine annotations. Audited against Drupal contrib
(api.tresbien.tech, 2026-05-27): three modules reference the class — all via
the annotation form — and zero contrib modules use it in PHP code. Owners of
those modules must hand-edit the annotation string; this rector is a safety
net for `use` / `extends` patterns and for entity types that switch to
PHP-attribute syntax.
[#3573870](https://www.drupal.org/i/3573870) /
[CR](https://www.drupal.org/node/3384745).

## [1.0.0-beta1] — 2026-05-25

Expand Down Expand Up @@ -250,6 +405,8 @@ distinct deprecations in the same minor.
|---|---|---|
| `PluginBaseIsConfigurableRector` | `PluginBase::isConfigurable()` → `instanceof ConfigurableInterface` check | [node/2946122](https://www.drupal.org/node/2946122) |
| `RenameClassRector` | `AliasWhitelist[Interface]` → `AliasPrefixList[Interface]`; `MatchingRouteNotFoundException` → `ResourceNotFoundException` | [node/3467559](https://www.drupal.org/node/3467559) |
| `RenameClassRector` | `Drupal\Core\Asset\LibraryDiscovery` → `LibraryDiscoveryInterface` | [node/3462970](https://www.drupal.org/node/3462970) |
| `RenameClassRector` | `EntityPermissionsRouteProviderWithCheck` → `EntityPermissionsRouteProvider` | [node/3384745](https://www.drupal.org/node/3384745) |
| `MethodToMethodWithCheckRector` | `AliasManager::pathAliasWhitelistRebuild()` → `pathAliasPrefixListRebuild()` | [node/3467559](https://www.drupal.org/node/3467559) |
| `RemoveModuleHandlerDeprecatedMethodsRector` | `ModuleHandler::writeCache()` removed; `getHookInfo()` returns `[]` | [node/3368812](https://www.drupal.org/node/3368812) |
| `ReplaceLocaleConfigBatchFunctionsRector` | Rename `locale_config_batch_set_config_langcodes()` and `locale_config_batch_refresh_name()` | [node/3575254](https://www.drupal.org/node/3575254) |
Expand Down Expand Up @@ -283,6 +440,7 @@ distinct deprecations in the same minor.
| `ReplaceEntityOriginalPropertyRector` | `$entity->original` magic property → `$entity->getOriginal()` / `setOriginal()`; nullsafe-aware | [node/3571065](https://www.drupal.org/node/3571065) |
| `RenameStopProceduralHookScanRector` | `#[StopProceduralHookScan]` → `#[ProceduralHookScanStop]` | [node/3495943](https://www.drupal.org/node/3495943) |
| `RenameClassRector` | `Drupal\Core\Entity\Query\Sql\pgsql\*` → `Drupal\pgsql\Entity\Query\*`; `jsonapi` ResourceResponseValidator move | [node/3488572](https://www.drupal.org/node/3488572) |
| `RenameClassRector` | `content_translation\Plugin\migrate\source\I18nQueryTrait` → `migrate_drupal\Plugin\migrate\source\I18nQueryTrait` | [node/3439256](https://www.drupal.org/node/3439256) |
| `RemoveCacheTagChecksumAssertionsRector` | Remove `CacheTagChecksumCount` / `CacheTagIsValidCount` performance-trait assertions | [node/3511149](https://www.drupal.org/node/3511149) |
| `RemoveRootFromCreateConnectionOptionsFromUrlRector` | `Connection::createConnectionOptionsFromUrl()` — drop `$root` parameter | [node/3511287](https://www.drupal.org/node/3511287) |
| `ClassConstantToClassConstantRector` | `SystemManager::REQUIREMENT_*` → `RequirementSeverity::*` | [node/3410939](https://www.drupal.org/node/3410939) |
Expand Down Expand Up @@ -313,6 +471,7 @@ distinct deprecations in the same minor.
| `ReplaceThemeGetSettingRector` | `theme_get_setting()` → `ThemeSettingsProvider` service; `_system_default_theme_features()` → `ThemeSettingsProvider::DEFAULT_THEME_FEATURES` | [node/3573896](https://www.drupal.org/node/3573896) |
| `RemoveRootFromConvertDbUrlRector` | `Database::convertDbUrlToConnectionInfo($url, $root, …)` — drop the `$root` arg | [node/3511287](https://www.drupal.org/node/3511287) |
| `RenameClassRector` | `workspaces.association` service / `WorkspaceAssociation*` → `workspaces.tracker` / `WorkspaceTracker*` | [node/3551450](https://www.drupal.org/node/3551450) |
| `ReplaceDialogClassOptionRector` | `OpenDialogCommand`/`OpenOffCanvasDialogCommand` `dialogClass` option → `classes['ui-dialog']` | [node/3440844](https://www.drupal.org/node/3440844) |

##### Drupal 11.4

Expand Down Expand Up @@ -352,6 +511,7 @@ distinct deprecations in the same minor.
| `RemoveFilterTipsLongParamRector` | `FilterInterface::tips()` — drop the `$long` parameter | [node/3567879](https://www.drupal.org/node/3567879) |
| `SystemSortThemesRector` | `'system_sort_themes'` string callback → `Closure` (closure callable) | [node/3566774](https://www.drupal.org/node/3566774) |
| `LocaleCompareIncToServiceRector` | `locale_translation_flush_projects()` / `locale_translation_build_projects()` / `locale_translation_check_projects*()` etc. → `locale.project` and `LocaleSource` services | [node/3037031](https://www.drupal.org/node/3037031) |
| `ReplaceNonBoolAccessRector` | Integer-literal `#access` render-array values → `true` / `false` | [node/3526250](https://www.drupal.org/node/3526250) |

#### Drupal 10.3 deprecation rules

Expand Down
4 changes: 4 additions & 0 deletions config/coverage-registry.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@
0 => 'Call to deprecated method setCacheKey() of class Drupal\\path_alias\\AliasManager. Deprecated in drupal:11.3.0 and is removed from drupal:13.0.0. There is no replacement.',
1 => 'Call to deprecated method writeCache() of class Drupal\\path_alias\\AliasManager. Deprecated in drupal:11.3.0 and is removed from drupal:13.0.0. There is no replacement.',
),
'RemoveRendererAddCacheableDependencyNonObjectRector' =>
array (
0 => 'Calling Drupal\\Core\\Render\\Renderer::addCacheableDependency() with an object that doesn\'t implement Drupal\\Core\\Cache\\CacheableDependencyInterface is deprecated in drupal:11.3.0 and will throw an error in drupal:13.0.0. See https://www.drupal.org/node/3525389',
),
'ReplaceExpectDeprecationRector' =>
array (
0 => 'Call to deprecated method expectDeprecation() of class Drupal\\KernelTests\\KernelTestBase. Deprecated in drupal:11.4.0 and is removed from drupal:12.0.0. Use $this->expectUserDeprecationMessage() or $this->expectUserDeprecationMessageMatches() instead.',
Expand Down
7 changes: 7 additions & 0 deletions config/drupal-11/drupal-11.0-deprecations.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use DrupalRector\Drupal10\Rector\Deprecation\ReplaceRequestTimeConstantRector;
use DrupalRector\Drupal11\Rector\Deprecation\GetNameToNameRector;
use DrupalRector\Drupal11\Rector\Deprecation\GroupLegacyToIgnoreDeprecationsRector;
use DrupalRector\Drupal11\Rector\Deprecation\MigrateSqlGetMigrationPluginManagerRector;
use DrupalRector\Drupal11\Rector\Deprecation\RemoveStateCacheSettingRector;
use DrupalRector\Drupal11\Rector\Deprecation\StripMigrationDependenciesExpandArgRector;
Expand Down Expand Up @@ -46,4 +47,10 @@
$rectorConfig->ruleWithConfiguration(MigrateSqlGetMigrationPluginManagerRector::class, [
new DrupalIntroducedVersionConfiguration('11.0.0'),
]);

// https://www.drupal.org/node/3417066
// @group legacy docblock annotation deprecated in drupal:10.3.0, removed in drupal:11.0.0.
// Drupal 11 drops symfony/phpunit-bridge in favour of PHPUnit 10, whose native
// #[\PHPUnit\Framework\Attributes\IgnoreDeprecations] attribute supersedes it.
$rectorConfig->rule(GroupLegacyToIgnoreDeprecationsRector::class);
};
Loading
Loading