diff --git a/.claude/skills/rector-live-test/SKILL.md b/.claude/skills/rector-live-test/SKILL.md index 8a4ac94f..ba4e6ffe 100644 --- a/.claude/skills/rector-live-test/SKILL.md +++ b/.claude/skills/rector-live-test/SKILL.md @@ -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(::class, [ new DrupalIntroducedVersionConfiguration(''), ]); @@ -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/ drupal/ --no-interaction` instead. + ### 5. Capture the PHPStan deprecation message While the contrib module is still installed and the pre-transform code is on diff --git a/CHANGELOG.md b/CHANGELOG.md index 349976df..98deb101 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 @@ -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 @@ -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 @@ -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) | @@ -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) | @@ -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 @@ -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 diff --git a/config/coverage-registry.php b/config/coverage-registry.php index d64254c5..8db7e1df 100644 --- a/config/coverage-registry.php +++ b/config/coverage-registry.php @@ -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.', diff --git a/config/drupal-11/drupal-11.0-deprecations.php b/config/drupal-11/drupal-11.0-deprecations.php index e85c68df..68a33925 100644 --- a/config/drupal-11/drupal-11.0-deprecations.php +++ b/config/drupal-11/drupal-11.0-deprecations.php @@ -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; @@ -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); }; diff --git a/config/drupal-11/drupal-11.1-deprecations.php b/config/drupal-11/drupal-11.1-deprecations.php index 37765b8f..3eaf0e13 100644 --- a/config/drupal-11/drupal-11.1-deprecations.php +++ b/config/drupal-11/drupal-11.1-deprecations.php @@ -34,10 +34,50 @@ // Replaced by AliasPrefixList and AliasPrefixListInterface. // AliasManager::pathAliasWhitelistRebuild() deprecated in drupal:11.1.0, removed in drupal:12.0.0. // Replaced by pathAliasPrefixListRebuild(). + // https://www.drupal.org/node/3462871 (deprecation issue) + // https://www.drupal.org/node/3571057 (removal issue, drupal:12.0.0) + // https://www.drupal.org/node/3462970 (change record) + // Drupal\Core\Asset\LibraryDiscovery deprecated in drupal:11.1.0, removed in drupal:12.0.0. + // Consumer code should type-hint LibraryDiscoveryInterface; the library.discovery + // service is now backed by LibraryDiscoveryCollector. + // + // TODO PHPSTAN_MESSAGES RenameClassRector: class-deprecation messages from + // phpstan-deprecation-rules include consumer-specific context, so they + // can't be exact-matched by upgrade_status. Captured shape against + // drupal/style_selector 2.0.x: + // "Parameter $library_discovery of method ::__construct() has + // typehint with deprecated class Drupal\Core\Asset\LibraryDiscovery: + // \Drupal\Core\Asset\LibraryDiscoveryCollector instead." + // The stable suffix is "deprecated class Drupal\Core\Asset\LibraryDiscovery: …". + // + // https://www.drupal.org/node/3573870 + // https://www.drupal.org/node/3384745 (change record) + // Drupal\user\Entity\EntityPermissionsRouteProviderWithCheck deprecated in + // drupal:11.1.0, removed in drupal:12.0.0. Use EntityPermissionsRouteProvider + // instead. The WithCheck variant layered an `_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. Owners of such subclasses must port any required + // access logic into the new model (route requirement on the route + // definition). + // + // Limitation: doctrine annotation-string references (the dominant real-world + // pattern, e.g. `"permissions" = "Drupal\user\Entity\Entity…"` inside an + // entity-type docblock) are NOT rewritten — RenameClassRector only touches + // PHP Name nodes (use/extends/implements/::class/typehints), not strings + // inside annotations. Contrib audit (api.tresbien.tech, 2026-05-27) found + // three modules using the class and zero PHP-code references; this entry + // is therefore mostly a safety net for use/extends/::class patterns. $rectorConfig->ruleWithConfiguration(RenameClassRector::class, [ 'Drupal\path_alias\AliasWhitelist' => 'Drupal\path_alias\AliasPrefixList', 'Drupal\path_alias\AliasWhitelistInterface' => 'Drupal\path_alias\AliasPrefixListInterface', 'Drupal\Core\Routing\MatchingRouteNotFoundException' => 'Symfony\Component\Routing\Exception\ResourceNotFoundException', + 'Drupal\Core\Asset\LibraryDiscovery' => 'Drupal\Core\Asset\LibraryDiscoveryInterface', + 'Drupal\user\Entity\EntityPermissionsRouteProviderWithCheck' => 'Drupal\user\Entity\EntityPermissionsRouteProvider', ]); $rectorConfig->ruleWithConfiguration(MethodToMethodWithCheckRector::class, [ new MethodToMethodWithCheckConfiguration('Drupal\path_alias\AliasManager', 'pathAliasWhitelistRebuild', 'pathAliasPrefixListRebuild'), diff --git a/config/drupal-11/drupal-11.2-deprecations.php b/config/drupal-11/drupal-11.2-deprecations.php index 371c111f..a60b01b4 100644 --- a/config/drupal-11/drupal-11.2-deprecations.php +++ b/config/drupal-11/drupal-11.2-deprecations.php @@ -228,12 +228,17 @@ // https://www.drupal.org/node/3498916 (change record) // Drupal\migrate_drupal\Plugin\migrate\source\ContentEntity/ContentEntityDeriver deprecated in drupal:11.2.0, // removed in drupal:12.0.0. Moved to Drupal\migrate namespace. + // https://www.drupal.org/node/3258581 + // https://www.drupal.org/node/3439256 (change record) + // Drupal\content_translation\Plugin\migrate\source\I18nQueryTrait deprecated in drupal:11.2.0, + // removed in drupal:12.0.0. Moved to Drupal\migrate_drupal namespace. $rectorConfig->ruleWithConfiguration(RenameClassRector::class, [ 'Drupal\Core\Entity\Query\Sql\pgsql\QueryFactory' => 'Drupal\pgsql\EntityQuery\QueryFactory', 'Drupal\Core\Entity\Query\Sql\pgsql\Condition' => 'Drupal\pgsql\EntityQuery\Condition', 'Drupal\jsonapi\EventSubscriber\ResourceResponseValidator' => 'Drupal\jsonapi_response_validator\EventSubscriber\ResourceResponseValidator', 'Drupal\migrate_drupal\Plugin\migrate\source\ContentEntity' => 'Drupal\migrate\Plugin\migrate\source\ContentEntity', 'Drupal\migrate_drupal\Plugin\migrate\source\ContentEntityDeriver' => 'Drupal\migrate\Plugin\migrate\source\ContentEntityDeriver', + 'Drupal\content_translation\Plugin\migrate\source\I18nQueryTrait' => 'Drupal\migrate_drupal\Plugin\migrate\source\I18nQueryTrait', ]); // https://www.drupal.org/node/3511123 diff --git a/config/drupal-11/drupal-11.3-deprecations.php b/config/drupal-11/drupal-11.3-deprecations.php index 894e3c2a..a577298b 100644 --- a/config/drupal-11/drupal-11.3-deprecations.php +++ b/config/drupal-11/drupal-11.3-deprecations.php @@ -8,9 +8,11 @@ use DrupalRector\Drupal11\Rector\Deprecation\LoadAllIncludesRector; use DrupalRector\Drupal11\Rector\Deprecation\NodeStorageDeprecatedMethodsRector; use DrupalRector\Drupal11\Rector\Deprecation\RemoveAliasManagerCacheMethodCallsRector; +use DrupalRector\Drupal11\Rector\Deprecation\RemoveRendererAddCacheableDependencyNonObjectRector; use DrupalRector\Drupal11\Rector\Deprecation\RemoveRootFromConvertDbUrlRector; use DrupalRector\Drupal11\Rector\Deprecation\ReplaceCommentManagerGetCountNewCommentsRector; use DrupalRector\Drupal11\Rector\Deprecation\ReplaceCommentPreviewConstantsRector; +use DrupalRector\Drupal11\Rector\Deprecation\ReplaceDialogClassOptionRector; use DrupalRector\Drupal11\Rector\Deprecation\ReplaceNodeAccessViewAllNodesRector; use DrupalRector\Drupal11\Rector\Deprecation\ReplaceNodeAddBodyFieldRector; use DrupalRector\Drupal11\Rector\Deprecation\ReplaceNodeModuleProceduralFunctionsRector; @@ -268,4 +270,21 @@ // AliasManager::setCacheKey() and AliasManager::writeCache() deprecated in drupal:11.3.0, // removed in drupal:13.0.0 with no replacement (they are no-ops). $rectorConfig->rule(RemoveAliasManagerCacheMethodCallsRector::class); + + // https://www.drupal.org/node/3525388 + // https://www.drupal.org/node/3525389 (change record) + // RendererInterface::addCacheableDependency() deprecated in drupal:11.3.0, + // throws in drupal:13.0.0, when the dependency cannot implement + // CacheableDependencyInterface. Removes calls whose dependency argument is + // provably a primitive/array (bool, int, float, string, null, array). + $rectorConfig->rule(RemoveRendererAddCacheableDependencyNonObjectRector::class); + + // https://www.drupal.org/node/3571054 + // https://www.drupal.org/node/3440844 (change record) + // OpenDialogCommand / OpenOffCanvasDialogCommand $dialog_options['dialogClass'] + // deprecated in drupal:11.3.0, removed in drupal:12.0.0. Replaced by + // $dialog_options['classes']['ui-dialog']. The replacement form has existed in + // core since 10.3.x, so the transformed output is safe on every drupal-rector– + // supported Drupal minor (D10.3+); no BC wrapper needed. + $rectorConfig->rule(ReplaceDialogClassOptionRector::class); }; diff --git a/config/drupal-11/drupal-11.4-deprecations.php b/config/drupal-11/drupal-11.4-deprecations.php index 91186e68..7fe53266 100644 --- a/config/drupal-11/drupal-11.4-deprecations.php +++ b/config/drupal-11/drupal-11.4-deprecations.php @@ -14,15 +14,18 @@ use DrupalRector\Drupal11\Rector\Deprecation\RemoveCacheExpireOverrideRector; use DrupalRector\Drupal11\Rector\Deprecation\RemoveConfigSaveTrustedDataArgRector; use DrupalRector\Drupal11\Rector\Deprecation\RemoveFilterTipsLongParamRector; +use DrupalRector\Drupal11\Rector\Deprecation\RemoveInstallSchemaSystemSequencesRector; use DrupalRector\Drupal11\Rector\Deprecation\RemoveLinkWidgetValidateTitleElementRector; use DrupalRector\Drupal11\Rector\Deprecation\RemovePhpUnitCompatibilityTraitRector; use DrupalRector\Drupal11\Rector\Deprecation\RemoveSetUriCallbackRector; +use DrupalRector\Drupal11\Rector\Deprecation\RemoveToolkitArgFromImageToolkitOperationConstructorRector; use DrupalRector\Drupal11\Rector\Deprecation\RemoveTrustDataCallRector; use DrupalRector\Drupal11\Rector\Deprecation\RemoveViewsRowCacheKeysRector; use DrupalRector\Drupal11\Rector\Deprecation\ReplaceEntityReferenceRecursiveLimitRector; use DrupalRector\Drupal11\Rector\Deprecation\ReplaceExpectDeprecationRector; use DrupalRector\Drupal11\Rector\Deprecation\ReplaceHideShowWithPrintedRector; use DrupalRector\Drupal11\Rector\Deprecation\ReplaceLocaleTranslationPathConfigRector; +use DrupalRector\Drupal11\Rector\Deprecation\ReplaceNonBoolAccessRector; use DrupalRector\Drupal11\Rector\Deprecation\ReplaceRecipeRunnerInstallModuleRector; use DrupalRector\Drupal11\Rector\Deprecation\ReplaceSessionManagerDeleteRector; use DrupalRector\Drupal11\Rector\Deprecation\ReplaceSystemPerformanceGzipKeyRector; @@ -300,6 +303,12 @@ // Subclass overrides are dead code; remove them. $rectorConfig->rule(RemoveCacheExpireOverrideRector::class); + // https://www.drupal.org/node/3559481 + // https://www.drupal.org/node/3562304 (change record) + // ImageToolkitOperationBase::__construct() $toolkit argument deprecated in drupal:11.4.0, + // removed in drupal:13.0.0. Plugin manager now injects via setToolkit() for autowiring. + $rectorConfig->rule(RemoveToolkitArgFromImageToolkitOperationConstructorRector::class); + // https://www.drupal.org/node/3347842 // https://www.drupal.org/node/3348180 (change record) // trustData() deprecated in drupal:11.4.0, removed in drupal:13.0.0. Remove from fluent chains. @@ -475,6 +484,13 @@ // Replaced by direct $element['#printed'] = TRUE/FALSE assignment. $rectorConfig->rule(ReplaceHideShowWithPrintedRector::class); + // https://www.drupal.org/node/3526250 + // Integer values for #access render array key deprecated in drupal:11.4.0, + // removed in drupal:13.0.0. Replaced by boolean or AccessResultInterface. + // Only integer literals are rewritten (1 → true, 0 → false); variables and + // typed expressions are left for manual review. + $rectorConfig->rule(ReplaceNonBoolAccessRector::class); + // https://www.drupal.org/node/3550268 // https://www.drupal.org/node/3545276 (change record) // ExpectDeprecationTrait deprecated in drupal:11.4.0, removed in drupal:12.0.0. @@ -522,4 +538,12 @@ $rectorConfig->ruleWithConfiguration(RemovePhpUnitCompatibilityTraitRector::class, [ new DrupalIntroducedVersionConfiguration('12.0.0'), ]); + + // https://www.drupal.org/node/3335756 + // https://www.drupal.org/node/3349345 (change record) + // KernelTestBase::installSchema('system', 'sequences') deprecated in + // drupal:10.2.0 and removed in drupal:12.0.0. The sequences table no + // longer exists in core; the call throws a LogicException on D12 and + // must be removed (or have 'sequences' stripped from its array form). + $rectorConfig->rule(RemoveInstallSchemaSystemSequencesRector::class); }; diff --git a/src/Drupal11/Rector/Deprecation/GroupLegacyToIgnoreDeprecationsRector.php b/src/Drupal11/Rector/Deprecation/GroupLegacyToIgnoreDeprecationsRector.php new file mode 100644 index 00000000..ab790233 --- /dev/null +++ b/src/Drupal11/Rector/Deprecation/GroupLegacyToIgnoreDeprecationsRector.php @@ -0,0 +1,113 @@ +> */ + public function getNodeTypes(): array + { + return [ClassMethod::class, Class_::class]; + } + + /** @param ClassMethod|Class_ $node */ + public function refactor(Node $node): ?Node + { + $docComment = $node->getDocComment(); + if ($docComment === null) { + return null; + } + + $docText = $docComment->getText(); + if (!str_contains($docText, '@group legacy')) { + return null; + } + + foreach ($node->attrGroups as $attrGroup) { + foreach ($attrGroup->attrs as $attr) { + $name = $this->getName($attr->name); + if ($name === 'PHPUnit\\Framework\\Attributes\\IgnoreDeprecations' + || $name === 'IgnoreDeprecations' + ) { + return null; + } + } + } + + $node->attrGroups[] = new AttributeGroup([ + new Attribute(new FullyQualified('PHPUnit\\Framework\\Attributes\\IgnoreDeprecations')), + ]); + + $newDocText = preg_replace('/^[ \t]*\*[ \t]*@group legacy[ \t]*\r?\n/m', '', $docText); + $newDocText = preg_replace('/\n[ \t]*\*[ \t]*\n([ \t]*\*\/)$/', "\n$1", $newDocText); + + if (preg_match('/^\/\*\*\s*\*\/\s*$/', trim($newDocText))) { + $node->setAttribute('comments', []); + } else { + $node->setAttribute('comments', [new Doc($newDocText)]); + } + + return $node; + } +} diff --git a/src/Drupal11/Rector/Deprecation/RemoveInstallSchemaSystemSequencesRector.php b/src/Drupal11/Rector/Deprecation/RemoveInstallSchemaSystemSequencesRector.php new file mode 100644 index 00000000..48938c86 --- /dev/null +++ b/src/Drupal11/Rector/Deprecation/RemoveInstallSchemaSystemSequencesRector.php @@ -0,0 +1,138 @@ +installSchema('system', ['sequences']); +CODE_BEFORE, + <<<'CODE_AFTER' + +CODE_AFTER + ), + ] + ); + } + + /** @return array> */ + public function getNodeTypes(): array + { + return [Expression::class]; + } + + /** @param Expression $node */ + public function refactor(Node $node): int|Node|null + { + if (!$node->expr instanceof MethodCall) { + return null; + } + + $methodCall = $node->expr; + + if (!$this->isName($methodCall->name, 'installSchema')) { + return null; + } + + if (!$this->isObjectType($methodCall->var, new ObjectType('Drupal\KernelTests\KernelTestBase'))) { + return null; + } + + $args = $methodCall->args; + if (count($args) < 2) { + return null; + } + + $firstArg = $args[0]; + if (!$firstArg instanceof Arg) { + return null; + } + if (!$firstArg->value instanceof String_) { + return null; + } + if ($firstArg->value->value !== 'system') { + return null; + } + + $secondArg = $args[1]; + if (!$secondArg instanceof Arg) { + return null; + } + + $tablesExpr = $secondArg->value; + + // Case 1: single string 'sequences' — remove the whole statement. + if ($tablesExpr instanceof String_ && $tablesExpr->value === 'sequences') { + return NodeVisitor::REMOVE_NODE; + } + + // Case 2: array containing 'sequences'. + if ($tablesExpr instanceof Array_) { + $newItems = []; + $foundSequences = false; + + foreach ($tablesExpr->items as $item) { + if ($item->value instanceof String_ && $item->value->value === 'sequences') { + $foundSequences = true; + continue; + } + $newItems[] = $item; + } + + if (!$foundSequences) { + return null; + } + + // Array is now empty — remove the whole statement. + if (count($newItems) === 0) { + return NodeVisitor::REMOVE_NODE; + } + + $tablesExpr->items = $newItems; + + return $node; + } + + return null; + } +} diff --git a/src/Drupal11/Rector/Deprecation/RemoveRendererAddCacheableDependencyNonObjectRector.php b/src/Drupal11/Rector/Deprecation/RemoveRendererAddCacheableDependencyNonObjectRector.php new file mode 100644 index 00000000..9fc7e346 --- /dev/null +++ b/src/Drupal11/Rector/Deprecation/RemoveRendererAddCacheableDependencyNonObjectRector.php @@ -0,0 +1,83 @@ +renderer->addCacheableDependency($build, false);', + '' + ), + ] + ); + } + + /** @return array> */ + public function getNodeTypes(): array + { + return [Expression::class]; + } + + public function refactor(Node $node): ?int + { + assert($node instanceof Expression); + if (!$node->expr instanceof MethodCall) { + return null; + } + + $methodCall = $node->expr; + if (!$this->isName($methodCall->name, 'addCacheableDependency')) { + return null; + } + + // RendererInterface::addCacheableDependency() takes exactly 2 arguments + // (array &$elements, $dependency). The 1-argument variant on + // RefinableCacheableDependencyInterface/BubbleableMetadata is unrelated. + if (count($methodCall->args) !== 2) { + return null; + } + + if (!$this->isObjectType($methodCall->var, new ObjectType('Drupal\Core\Render\RendererInterface'))) { + return null; + } + + // Only remove when PHPStan can prove the dependency is not an object. + $dependencyType = $this->getType($methodCall->args[1]->value); + if (!$dependencyType->isObject()->no()) { + return null; + } + + return NodeVisitor::REMOVE_NODE; + } +} diff --git a/src/Drupal11/Rector/Deprecation/RemoveToolkitArgFromImageToolkitOperationConstructorRector.php b/src/Drupal11/Rector/Deprecation/RemoveToolkitArgFromImageToolkitOperationConstructorRector.php new file mode 100644 index 00000000..b3ddc3f7 --- /dev/null +++ b/src/Drupal11/Rector/Deprecation/RemoveToolkitArgFromImageToolkitOperationConstructorRector.php @@ -0,0 +1,147 @@ +> */ + public function getNodeTypes(): array + { + return [Class_::class]; + } + + /** @param Class_ $node */ + public function refactor(Node $node): ?Node + { + $constructor = $node->getMethod('__construct'); + if ($constructor === null) { + return null; + } + + $params = $constructor->params; + + // We need at least 5 params (configuration, plugin_id, plugin_definition, toolkit, logger). + if (count($params) < 5) { + return null; + } + + // Verify the 4th param (index 3) is typed as ImageToolkitInterface. + $toolkitParam = $params[3]; + if ($toolkitParam->type === null) { + return null; + } + + $typeName = $this->getName($toolkitParam->type); + if ($typeName !== self::TOOLKIT_INTERFACE) { + return null; + } + + $toolkitVarName = $this->getName($toolkitParam->var); + if ($toolkitVarName === null) { + return null; + } + + // Count all usages of the $toolkit variable inside the constructor body + // to ensure it is only passed to parent::__construct(). + $toolkitUsageCount = 0; + $this->traverseNodesWithCallable($constructor->stmts ?? [], function (Node $innerNode) use ($toolkitVarName, &$toolkitUsageCount): ?Node { + if ($innerNode instanceof Variable && $this->isName($innerNode, $toolkitVarName)) { + ++$toolkitUsageCount; + } + + return null; + }); + + // If $toolkit is used more than once or not at all in body, skip. + if ($toolkitUsageCount !== 1) { + return null; + } + + // Locate parent::__construct() and confirm $toolkit is its 4th arg, + // then remove that arg. + $parentCallUpdated = false; + $this->traverseNodesWithCallable($constructor->stmts ?? [], function (Node $innerNode) use ($toolkitVarName, &$parentCallUpdated): ?Node { + if (!$innerNode instanceof StaticCall) { + return null; + } + if (!$this->isName($innerNode->class, 'parent') || !$this->isName($innerNode->name, '__construct')) { + return null; + } + if (!isset($innerNode->args[3]) || !$innerNode->args[3] instanceof Arg) { + return null; + } + $arg3Value = $innerNode->args[3]->value; + if (!$arg3Value instanceof Variable || !$this->isName($arg3Value, $toolkitVarName)) { + return null; + } + array_splice($innerNode->args, 3, 1); + $parentCallUpdated = true; + + return $innerNode; + }); + + if (!$parentCallUpdated) { + return null; + } + + // Remove the $toolkit parameter from the constructor signature. + array_splice($constructor->params, 3, 1); + + return $node; + } +} diff --git a/src/Drupal11/Rector/Deprecation/ReplaceDialogClassOptionRector.php b/src/Drupal11/Rector/Deprecation/ReplaceDialogClassOptionRector.php new file mode 100644 index 00000000..50ee5cc4 --- /dev/null +++ b/src/Drupal11/Rector/Deprecation/ReplaceDialogClassOptionRector.php @@ -0,0 +1,203 @@ + ...] array trigger the same deprecation + * notice from the parent constructor. + * + * @see https://www.drupal.org/node/3571054 + * @see https://www.drupal.org/node/3440844 + */ +final class ReplaceDialogClassOptionRector extends AbstractRector +{ + /** + * Map: FQCN => zero-based index of the $dialog_options argument. + */ + private const CLASS_ARG_INDEX = [ + 'Drupal\\Core\\Ajax\\OpenDialogCommand' => 3, + 'Drupal\\Core\\Ajax\\OpenModalDialogCommand' => 2, + 'Drupal\\Core\\Ajax\\OpenOffCanvasDialogCommand' => 2, + ]; + + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition( + "Replace removed \$dialog_options['dialogClass'] with \$dialog_options['classes']['ui-dialog'] in OpenDialogCommand / OpenModalDialogCommand / OpenOffCanvasDialogCommand constructors (removed in drupal:12.0.0)", + [ + new CodeSample( + <<<'CODE_BEFORE' +new \Drupal\Core\Ajax\OpenDialogCommand('#my-dialog', 'Title', $content, ['dialogClass' => 'my-class', 'width' => 600]); +CODE_BEFORE, + <<<'CODE_AFTER' +new \Drupal\Core\Ajax\OpenDialogCommand('#my-dialog', 'Title', $content, ['width' => 600, 'classes' => ['ui-dialog' => 'my-class']]); +CODE_AFTER + ), + ] + ); + } + + /** @return array> */ + public function getNodeTypes(): array + { + return [New_::class]; + } + + /** @param New_ $node */ + public function refactor(Node $node): ?Node + { + $className = $this->getResolvedClassName($node); + if ($className === null || !isset(self::CLASS_ARG_INDEX[$className])) { + return null; + } + + $argIndex = self::CLASS_ARG_INDEX[$className]; + + if (!isset($node->args[$argIndex])) { + return null; + } + + $arg = $node->args[$argIndex]; + if (!$arg instanceof Node\Arg) { + return null; + } + + $optionsArray = $arg->value; + if (!$optionsArray instanceof Array_) { + return null; + } + + $dialogClassIdx = null; + $classesIdx = null; + $uiDialogIdx = null; + + foreach ($optionsArray->items as $idx => $item) { + if (!$item->key instanceof String_) { + continue; + } + + if ($item->key->value === 'dialogClass') { + $dialogClassIdx = $idx; + } elseif ($item->key->value === 'classes') { + $classesIdx = $idx; + if ($item->value instanceof Array_) { + foreach ($item->value->items as $subIdx => $subItem) { + if ($subItem->key instanceof String_ + && $subItem->key->value === 'ui-dialog' + ) { + $uiDialogIdx = $subIdx; + } + } + } + } + } + + if ($dialogClassIdx === null) { + return null; + } + + $dialogClassValue = $optionsArray->items[$dialogClassIdx]->value; + + unset($optionsArray->items[$dialogClassIdx]); + $optionsArray->items = array_values($optionsArray->items); + + if ($classesIdx === null) { + $optionsArray->items[] = new ArrayItem( + new Array_([ + new ArrayItem($dialogClassValue, new String_('ui-dialog')), + ]), + new String_('classes') + ); + } elseif ($uiDialogIdx !== null) { + // dialogClass deletion shifted the indexes — recompute the classes index. + $classesIdx = $this->findKeyIndex($optionsArray, 'classes'); + if ($classesIdx === null) { + return null; + } + $classesItem = $optionsArray->items[$classesIdx]; + if (!$classesItem->value instanceof Array_) { + return null; + } + + $uiDialogItem = $classesItem->value->items[$uiDialogIdx]; + + if (!($uiDialogItem->value instanceof String_) || !($dialogClassValue instanceof String_)) { + return null; + } + + $uiDialogItem->value = new String_( + $uiDialogItem->value->value.' '.$dialogClassValue->value + ); + } else { + $classesIdx = $this->findKeyIndex($optionsArray, 'classes'); + if ($classesIdx === null) { + return null; + } + $classesItem = $optionsArray->items[$classesIdx]; + if (!$classesItem->value instanceof Array_) { + return null; + } + + $classesItem->value->items[] = new ArrayItem( + $dialogClassValue, + new String_('ui-dialog') + ); + } + + return $node; + } + + private function getResolvedClassName(New_ $node): ?string + { + $class = $node->class; + + if ($class instanceof FullyQualified) { + return $class->toString(); + } + + if ($class instanceof Node\Name) { + $resolved = $class->getAttribute('resolvedName'); + if ($resolved instanceof FullyQualified) { + return $resolved->toString(); + } + } + + return null; + } + + private function findKeyIndex(Array_ $array, string $keyName): ?int + { + foreach ($array->items as $idx => $item) { + if ($item->key instanceof String_ && $item->key->value === $keyName) { + return $idx; + } + } + + return null; + } +} diff --git a/src/Drupal11/Rector/Deprecation/ReplaceNonBoolAccessRector.php b/src/Drupal11/Rector/Deprecation/ReplaceNonBoolAccessRector.php new file mode 100644 index 00000000..0d37e858 --- /dev/null +++ b/src/Drupal11/Rector/Deprecation/ReplaceNonBoolAccessRector.php @@ -0,0 +1,75 @@ + 'foo', '#access' => 1]; +CODE_BEFORE, + <<<'CODE_AFTER' +$build = ['#markup' => 'foo', '#access' => true]; +CODE_AFTER + ), + ] + ); + } + + /** @return array> */ + public function getNodeTypes(): array + { + return [ArrayItem::class]; + } + + /** @param ArrayItem $node */ + public function refactor(Node $node): ?Node + { + if ($node->key === null) { + return null; + } + if (!$node->key instanceof String_) { + return null; + } + if ($node->key->value !== '#access') { + return null; + } + // Only act on integer literals; leave booleans, variables, and + // AccessResultInterface expressions untouched. + if (!$node->value instanceof Int_) { + return null; + } + $node->value = $node->value->value === 0 + ? $this->nodeFactory->createFalse() + : $this->nodeFactory->createTrue(); + + return $node; + } +} diff --git a/tests/src/Drupal11/Rector/Deprecation/GroupLegacyToIgnoreDeprecationsRector/GroupLegacyToIgnoreDeprecationsRectorTest.php b/tests/src/Drupal11/Rector/Deprecation/GroupLegacyToIgnoreDeprecationsRector/GroupLegacyToIgnoreDeprecationsRectorTest.php new file mode 100644 index 00000000..e8a0562a --- /dev/null +++ b/tests/src/Drupal11/Rector/Deprecation/GroupLegacyToIgnoreDeprecationsRector/GroupLegacyToIgnoreDeprecationsRectorTest.php @@ -0,0 +1,26 @@ +doTestFile($filePath); + } + + public static function provideData(): \Iterator + { + return self::yieldFilesFromDirectory(__DIR__.'/fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__.'/config/configured_rule.php'; + } +} diff --git a/tests/src/Drupal11/Rector/Deprecation/GroupLegacyToIgnoreDeprecationsRector/config/configured_rule.php b/tests/src/Drupal11/Rector/Deprecation/GroupLegacyToIgnoreDeprecationsRector/config/configured_rule.php new file mode 100644 index 00000000..9ea2b777 --- /dev/null +++ b/tests/src/Drupal11/Rector/Deprecation/GroupLegacyToIgnoreDeprecationsRector/config/configured_rule.php @@ -0,0 +1,11 @@ + +----- + diff --git a/tests/src/Drupal11/Rector/Deprecation/GroupLegacyToIgnoreDeprecationsRector/fixture/class_level.php.inc b/tests/src/Drupal11/Rector/Deprecation/GroupLegacyToIgnoreDeprecationsRector/fixture/class_level.php.inc new file mode 100644 index 00000000..9cb2440f --- /dev/null +++ b/tests/src/Drupal11/Rector/Deprecation/GroupLegacyToIgnoreDeprecationsRector/fixture/class_level.php.inc @@ -0,0 +1,34 @@ + +----- + diff --git a/tests/src/Drupal11/Rector/Deprecation/GroupLegacyToIgnoreDeprecationsRector/fixture/no_change_already_has_attribute.php.inc b/tests/src/Drupal11/Rector/Deprecation/GroupLegacyToIgnoreDeprecationsRector/fixture/no_change_already_has_attribute.php.inc new file mode 100644 index 00000000..d819019d --- /dev/null +++ b/tests/src/Drupal11/Rector/Deprecation/GroupLegacyToIgnoreDeprecationsRector/fixture/no_change_already_has_attribute.php.inc @@ -0,0 +1,18 @@ + diff --git a/tests/src/Drupal11/Rector/Deprecation/GroupLegacyToIgnoreDeprecationsRector/fixture/no_change_no_legacy.php.inc b/tests/src/Drupal11/Rector/Deprecation/GroupLegacyToIgnoreDeprecationsRector/fixture/no_change_no_legacy.php.inc new file mode 100644 index 00000000..13182699 --- /dev/null +++ b/tests/src/Drupal11/Rector/Deprecation/GroupLegacyToIgnoreDeprecationsRector/fixture/no_change_no_legacy.php.inc @@ -0,0 +1,18 @@ + diff --git a/tests/src/Drupal11/Rector/Deprecation/RemoveInstallSchemaSystemSequencesRector/RemoveInstallSchemaSystemSequencesRectorTest.php b/tests/src/Drupal11/Rector/Deprecation/RemoveInstallSchemaSystemSequencesRector/RemoveInstallSchemaSystemSequencesRectorTest.php new file mode 100644 index 00000000..e6978ed8 --- /dev/null +++ b/tests/src/Drupal11/Rector/Deprecation/RemoveInstallSchemaSystemSequencesRector/RemoveInstallSchemaSystemSequencesRectorTest.php @@ -0,0 +1,26 @@ +doTestFile($filePath); + } + + public static function provideData(): \Iterator + { + return self::yieldFilesFromDirectory(__DIR__.'/fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__.'/config/configured_rule.php'; + } +} diff --git a/tests/src/Drupal11/Rector/Deprecation/RemoveInstallSchemaSystemSequencesRector/config/configured_rule.php b/tests/src/Drupal11/Rector/Deprecation/RemoveInstallSchemaSystemSequencesRector/config/configured_rule.php new file mode 100644 index 00000000..6ca323ac --- /dev/null +++ b/tests/src/Drupal11/Rector/Deprecation/RemoveInstallSchemaSystemSequencesRector/config/configured_rule.php @@ -0,0 +1,11 @@ +installSchema('system', ['sequences', 'router']); + } +} +?> +----- +installSchema('system', ['router']); + } +} +?> diff --git a/tests/src/Drupal11/Rector/Deprecation/RemoveInstallSchemaSystemSequencesRector/fixture/basic_array.php.inc b/tests/src/Drupal11/Rector/Deprecation/RemoveInstallSchemaSystemSequencesRector/fixture/basic_array.php.inc new file mode 100644 index 00000000..59c392d9 --- /dev/null +++ b/tests/src/Drupal11/Rector/Deprecation/RemoveInstallSchemaSystemSequencesRector/fixture/basic_array.php.inc @@ -0,0 +1,30 @@ +installSchema('system', ['sequences']); + } +} +?> +----- + diff --git a/tests/src/Drupal11/Rector/Deprecation/RemoveInstallSchemaSystemSequencesRector/fixture/no_change_other_module.php.inc b/tests/src/Drupal11/Rector/Deprecation/RemoveInstallSchemaSystemSequencesRector/fixture/no_change_other_module.php.inc new file mode 100644 index 00000000..13f46832 --- /dev/null +++ b/tests/src/Drupal11/Rector/Deprecation/RemoveInstallSchemaSystemSequencesRector/fixture/no_change_other_module.php.inc @@ -0,0 +1,33 @@ +installSchema('node', ['sequences']); + } +} +?> +----- +installSchema('node', ['sequences']); + } +} +?> diff --git a/tests/src/Drupal11/Rector/Deprecation/RemoveInstallSchemaSystemSequencesRector/fixture/no_change_other_tables.php.inc b/tests/src/Drupal11/Rector/Deprecation/RemoveInstallSchemaSystemSequencesRector/fixture/no_change_other_tables.php.inc new file mode 100644 index 00000000..2830caf6 --- /dev/null +++ b/tests/src/Drupal11/Rector/Deprecation/RemoveInstallSchemaSystemSequencesRector/fixture/no_change_other_tables.php.inc @@ -0,0 +1,33 @@ +installSchema('system', ['router', 'queue']); + } +} +?> +----- +installSchema('system', ['router', 'queue']); + } +} +?> diff --git a/tests/src/Drupal11/Rector/Deprecation/RemoveInstallSchemaSystemSequencesRector/fixture/no_change_unrelated.php.inc b/tests/src/Drupal11/Rector/Deprecation/RemoveInstallSchemaSystemSequencesRector/fixture/no_change_unrelated.php.inc new file mode 100644 index 00000000..4a5457da --- /dev/null +++ b/tests/src/Drupal11/Rector/Deprecation/RemoveInstallSchemaSystemSequencesRector/fixture/no_change_unrelated.php.inc @@ -0,0 +1,19 @@ +installSchema('system', 'sequences'); + $obj->installSchema('system', ['sequences']); +} +?> +----- +installSchema('system', 'sequences'); + $obj->installSchema('system', ['sequences']); +} +?> diff --git a/tests/src/Drupal11/Rector/Deprecation/RemoveInstallSchemaSystemSequencesRector/fixture/string_form.php.inc b/tests/src/Drupal11/Rector/Deprecation/RemoveInstallSchemaSystemSequencesRector/fixture/string_form.php.inc new file mode 100644 index 00000000..03dd27dd --- /dev/null +++ b/tests/src/Drupal11/Rector/Deprecation/RemoveInstallSchemaSystemSequencesRector/fixture/string_form.php.inc @@ -0,0 +1,30 @@ +installSchema('system', 'sequences'); + } +} +?> +----- + diff --git a/tests/src/Drupal11/Rector/Deprecation/RemoveRendererAddCacheableDependencyNonObjectRector/RemoveRendererAddCacheableDependencyNonObjectRectorTest.php b/tests/src/Drupal11/Rector/Deprecation/RemoveRendererAddCacheableDependencyNonObjectRector/RemoveRendererAddCacheableDependencyNonObjectRectorTest.php new file mode 100644 index 00000000..88e679ce --- /dev/null +++ b/tests/src/Drupal11/Rector/Deprecation/RemoveRendererAddCacheableDependencyNonObjectRector/RemoveRendererAddCacheableDependencyNonObjectRectorTest.php @@ -0,0 +1,26 @@ +doTestFile($filePath); + } + + public static function provideData(): \Iterator + { + return self::yieldFilesFromDirectory(__DIR__.'/fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__.'/config/configured_rule.php'; + } +} diff --git a/tests/src/Drupal11/Rector/Deprecation/RemoveRendererAddCacheableDependencyNonObjectRector/config/configured_rule.php b/tests/src/Drupal11/Rector/Deprecation/RemoveRendererAddCacheableDependencyNonObjectRector/config/configured_rule.php new file mode 100644 index 00000000..1280abd2 --- /dev/null +++ b/tests/src/Drupal11/Rector/Deprecation/RemoveRendererAddCacheableDependencyNonObjectRector/config/configured_rule.php @@ -0,0 +1,11 @@ +addCacheableDependency($build, false); + $renderer->addCacheableDependency($build, 42); + $renderer->addCacheableDependency($build, 'a string'); + $renderer->addCacheableDependency($build, null); + $renderer->addCacheableDependency($build, ['x' => 1]); + return $build; +} +?> +----- + diff --git a/tests/src/Drupal11/Rector/Deprecation/RemoveRendererAddCacheableDependencyNonObjectRector/fixture/concrete_class.php.inc b/tests/src/Drupal11/Rector/Deprecation/RemoveRendererAddCacheableDependencyNonObjectRector/fixture/concrete_class.php.inc new file mode 100644 index 00000000..2486c317 --- /dev/null +++ b/tests/src/Drupal11/Rector/Deprecation/RemoveRendererAddCacheableDependencyNonObjectRector/fixture/concrete_class.php.inc @@ -0,0 +1,20 @@ +addCacheableDependency($build, true); +} +?> +----- + diff --git a/tests/src/Drupal11/Rector/Deprecation/RemoveRendererAddCacheableDependencyNonObjectRector/fixture/no_change_object_arg.php.inc b/tests/src/Drupal11/Rector/Deprecation/RemoveRendererAddCacheableDependencyNonObjectRector/fixture/no_change_object_arg.php.inc new file mode 100644 index 00000000..ca58b7c5 --- /dev/null +++ b/tests/src/Drupal11/Rector/Deprecation/RemoveRendererAddCacheableDependencyNonObjectRector/fixture/no_change_object_arg.php.inc @@ -0,0 +1,23 @@ +addCacheableDependency($build, $dep); + $renderer->addCacheableDependency($build, new \stdClass()); +} +?> +----- +addCacheableDependency($build, $dep); + $renderer->addCacheableDependency($build, new \stdClass()); +} +?> diff --git a/tests/src/Drupal11/Rector/Deprecation/RemoveRendererAddCacheableDependencyNonObjectRector/fixture/no_change_unrelated.php.inc b/tests/src/Drupal11/Rector/Deprecation/RemoveRendererAddCacheableDependencyNonObjectRector/fixture/no_change_unrelated.php.inc new file mode 100644 index 00000000..0898a88f --- /dev/null +++ b/tests/src/Drupal11/Rector/Deprecation/RemoveRendererAddCacheableDependencyNonObjectRector/fixture/no_change_unrelated.php.inc @@ -0,0 +1,19 @@ +addCacheableDependency($build, false); +} +?> +----- +addCacheableDependency($build, false); +} +?> diff --git a/tests/src/Drupal11/Rector/Deprecation/RemoveToolkitArgFromImageToolkitOperationConstructorRector/RemoveToolkitArgFromImageToolkitOperationConstructorRectorTest.php b/tests/src/Drupal11/Rector/Deprecation/RemoveToolkitArgFromImageToolkitOperationConstructorRector/RemoveToolkitArgFromImageToolkitOperationConstructorRectorTest.php new file mode 100644 index 00000000..82a8919f --- /dev/null +++ b/tests/src/Drupal11/Rector/Deprecation/RemoveToolkitArgFromImageToolkitOperationConstructorRector/RemoveToolkitArgFromImageToolkitOperationConstructorRectorTest.php @@ -0,0 +1,29 @@ +doTestFile($filePath); + } + + /** + * @return \Iterator<> + */ + public static function provideData(): \Iterator + { + return self::yieldFilesFromDirectory(__DIR__.'/fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__.'/config/configured_rule.php'; + } +} diff --git a/tests/src/Drupal11/Rector/Deprecation/RemoveToolkitArgFromImageToolkitOperationConstructorRector/config/configured_rule.php b/tests/src/Drupal11/Rector/Deprecation/RemoveToolkitArgFromImageToolkitOperationConstructorRector/config/configured_rule.php new file mode 100644 index 00000000..52ca1127 --- /dev/null +++ b/tests/src/Drupal11/Rector/Deprecation/RemoveToolkitArgFromImageToolkitOperationConstructorRector/config/configured_rule.php @@ -0,0 +1,10 @@ +rule(RemoveToolkitArgFromImageToolkitOperationConstructorRector::class); +}; diff --git a/tests/src/Drupal11/Rector/Deprecation/RemoveToolkitArgFromImageToolkitOperationConstructorRector/fixture/basic.php.inc b/tests/src/Drupal11/Rector/Deprecation/RemoveToolkitArgFromImageToolkitOperationConstructorRector/fixture/basic.php.inc new file mode 100644 index 00000000..6aa52672 --- /dev/null +++ b/tests/src/Drupal11/Rector/Deprecation/RemoveToolkitArgFromImageToolkitOperationConstructorRector/fixture/basic.php.inc @@ -0,0 +1,31 @@ + +----- + diff --git a/tests/src/Drupal11/Rector/Deprecation/RemoveToolkitArgFromImageToolkitOperationConstructorRector/fixture/no_change_no_constructor.php.inc b/tests/src/Drupal11/Rector/Deprecation/RemoveToolkitArgFromImageToolkitOperationConstructorRector/fixture/no_change_no_constructor.php.inc new file mode 100644 index 00000000..2c00f67d --- /dev/null +++ b/tests/src/Drupal11/Rector/Deprecation/RemoveToolkitArgFromImageToolkitOperationConstructorRector/fixture/no_change_no_constructor.php.inc @@ -0,0 +1,37 @@ + +----- + diff --git a/tests/src/Drupal11/Rector/Deprecation/RemoveToolkitArgFromImageToolkitOperationConstructorRector/fixture/no_change_toolkit_used_in_body.php.inc b/tests/src/Drupal11/Rector/Deprecation/RemoveToolkitArgFromImageToolkitOperationConstructorRector/fixture/no_change_toolkit_used_in_body.php.inc new file mode 100644 index 00000000..59541c1d --- /dev/null +++ b/tests/src/Drupal11/Rector/Deprecation/RemoveToolkitArgFromImageToolkitOperationConstructorRector/fixture/no_change_toolkit_used_in_body.php.inc @@ -0,0 +1,33 @@ +customToolkit = $toolkit; + } +} + +?> +----- +customToolkit = $toolkit; + } +} + +?> diff --git a/tests/src/Drupal11/Rector/Deprecation/RemoveToolkitArgFromImageToolkitOperationConstructorRector/fixture/no_change_unrelated_class.php.inc b/tests/src/Drupal11/Rector/Deprecation/RemoveToolkitArgFromImageToolkitOperationConstructorRector/fixture/no_change_unrelated_class.php.inc new file mode 100644 index 00000000..469ff7c1 --- /dev/null +++ b/tests/src/Drupal11/Rector/Deprecation/RemoveToolkitArgFromImageToolkitOperationConstructorRector/fixture/no_change_unrelated_class.php.inc @@ -0,0 +1,27 @@ + +----- + diff --git a/tests/src/Drupal11/Rector/Deprecation/ReplaceDialogClassOptionRector/ReplaceDialogClassOptionRectorTest.php b/tests/src/Drupal11/Rector/Deprecation/ReplaceDialogClassOptionRector/ReplaceDialogClassOptionRectorTest.php new file mode 100644 index 00000000..fd52e8a0 --- /dev/null +++ b/tests/src/Drupal11/Rector/Deprecation/ReplaceDialogClassOptionRector/ReplaceDialogClassOptionRectorTest.php @@ -0,0 +1,26 @@ +doTestFile($filePath); + } + + public static function provideData(): \Iterator + { + return self::yieldFilesFromDirectory(__DIR__.'/fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__.'/config/configured_rule.php'; + } +} diff --git a/tests/src/Drupal11/Rector/Deprecation/ReplaceDialogClassOptionRector/config/configured_rule.php b/tests/src/Drupal11/Rector/Deprecation/ReplaceDialogClassOptionRector/config/configured_rule.php new file mode 100644 index 00000000..2faa0f92 --- /dev/null +++ b/tests/src/Drupal11/Rector/Deprecation/ReplaceDialogClassOptionRector/config/configured_rule.php @@ -0,0 +1,11 @@ + 'extra', 'classes' => ['ui-dialog' => 'base']]); +} + +} +?> +----- + ['ui-dialog' => 'base extra']]); +} + +} +?> diff --git a/tests/src/Drupal11/Rector/Deprecation/ReplaceDialogClassOptionRector/fixture/basic.php.inc b/tests/src/Drupal11/Rector/Deprecation/ReplaceDialogClassOptionRector/fixture/basic.php.inc new file mode 100644 index 00000000..f805a892 --- /dev/null +++ b/tests/src/Drupal11/Rector/Deprecation/ReplaceDialogClassOptionRector/fixture/basic.php.inc @@ -0,0 +1,23 @@ + 'my-class', 'width' => 600]); +} + +} +?> +----- + 600, 'classes' => ['ui-dialog' => 'my-class']]); +} + +} +?> diff --git a/tests/src/Drupal11/Rector/Deprecation/ReplaceDialogClassOptionRector/fixture/merge_into_existing_classes.php.inc b/tests/src/Drupal11/Rector/Deprecation/ReplaceDialogClassOptionRector/fixture/merge_into_existing_classes.php.inc new file mode 100644 index 00000000..774b0ec7 --- /dev/null +++ b/tests/src/Drupal11/Rector/Deprecation/ReplaceDialogClassOptionRector/fixture/merge_into_existing_classes.php.inc @@ -0,0 +1,23 @@ + 'extra', 'classes' => ['ui-dialog-content' => 'pad']]); +} + +} +?> +----- + ['ui-dialog-content' => 'pad', 'ui-dialog' => 'extra']]); +} + +} +?> diff --git a/tests/src/Drupal11/Rector/Deprecation/ReplaceDialogClassOptionRector/fixture/modal_dialog.php.inc b/tests/src/Drupal11/Rector/Deprecation/ReplaceDialogClassOptionRector/fixture/modal_dialog.php.inc new file mode 100644 index 00000000..be4ded12 --- /dev/null +++ b/tests/src/Drupal11/Rector/Deprecation/ReplaceDialogClassOptionRector/fixture/modal_dialog.php.inc @@ -0,0 +1,23 @@ + 'modal-class', 'width' => 800]); +} + +} +?> +----- + 800, 'classes' => ['ui-dialog' => 'modal-class']]); +} + +} +?> diff --git a/tests/src/Drupal11/Rector/Deprecation/ReplaceDialogClassOptionRector/fixture/no_change_no_dialog_class.php.inc b/tests/src/Drupal11/Rector/Deprecation/ReplaceDialogClassOptionRector/fixture/no_change_no_dialog_class.php.inc new file mode 100644 index 00000000..a82a7a4f --- /dev/null +++ b/tests/src/Drupal11/Rector/Deprecation/ReplaceDialogClassOptionRector/fixture/no_change_no_dialog_class.php.inc @@ -0,0 +1,11 @@ + 600, 'classes' => ['ui-dialog' => 'already-correct']]); +} + +} +?> diff --git a/tests/src/Drupal11/Rector/Deprecation/ReplaceDialogClassOptionRector/fixture/no_change_non_literal_options.php.inc b/tests/src/Drupal11/Rector/Deprecation/ReplaceDialogClassOptionRector/fixture/no_change_non_literal_options.php.inc new file mode 100644 index 00000000..fb87dedb --- /dev/null +++ b/tests/src/Drupal11/Rector/Deprecation/ReplaceDialogClassOptionRector/fixture/no_change_non_literal_options.php.inc @@ -0,0 +1,11 @@ + diff --git a/tests/src/Drupal11/Rector/Deprecation/ReplaceDialogClassOptionRector/fixture/no_change_unrelated_class.php.inc b/tests/src/Drupal11/Rector/Deprecation/ReplaceDialogClassOptionRector/fixture/no_change_unrelated_class.php.inc new file mode 100644 index 00000000..4a8566fa --- /dev/null +++ b/tests/src/Drupal11/Rector/Deprecation/ReplaceDialogClassOptionRector/fixture/no_change_unrelated_class.php.inc @@ -0,0 +1,16 @@ + 'should-not-change']); +} + +} +?> diff --git a/tests/src/Drupal11/Rector/Deprecation/ReplaceDialogClassOptionRector/fixture/off_canvas.php.inc b/tests/src/Drupal11/Rector/Deprecation/ReplaceDialogClassOptionRector/fixture/off_canvas.php.inc new file mode 100644 index 00000000..74785ba1 --- /dev/null +++ b/tests/src/Drupal11/Rector/Deprecation/ReplaceDialogClassOptionRector/fixture/off_canvas.php.inc @@ -0,0 +1,23 @@ + 'side-panel', 'width' => 400]); +} + +} +?> +----- + 400, 'classes' => ['ui-dialog' => 'side-panel']]); +} + +} +?> diff --git a/tests/src/Drupal11/Rector/Deprecation/ReplaceDialogClassOptionRector/fixture/use_imported_class.php.inc b/tests/src/Drupal11/Rector/Deprecation/ReplaceDialogClassOptionRector/fixture/use_imported_class.php.inc new file mode 100644 index 00000000..ca200705 --- /dev/null +++ b/tests/src/Drupal11/Rector/Deprecation/ReplaceDialogClassOptionRector/fixture/use_imported_class.php.inc @@ -0,0 +1,27 @@ + 'imported-class']); +} + +} +?> +----- + ['ui-dialog' => 'imported-class']]); +} + +} +?> diff --git a/tests/src/Drupal11/Rector/Deprecation/ReplaceNonBoolAccessRector/ReplaceNonBoolAccessRectorTest.php b/tests/src/Drupal11/Rector/Deprecation/ReplaceNonBoolAccessRector/ReplaceNonBoolAccessRectorTest.php new file mode 100644 index 00000000..dcecedf1 --- /dev/null +++ b/tests/src/Drupal11/Rector/Deprecation/ReplaceNonBoolAccessRector/ReplaceNonBoolAccessRectorTest.php @@ -0,0 +1,26 @@ +doTestFile($filePath); + } + + public static function provideData(): \Iterator + { + return self::yieldFilesFromDirectory(__DIR__.'/fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__.'/config/configured_rule.php'; + } +} diff --git a/tests/src/Drupal11/Rector/Deprecation/ReplaceNonBoolAccessRector/config/configured_rule.php b/tests/src/Drupal11/Rector/Deprecation/ReplaceNonBoolAccessRector/config/configured_rule.php new file mode 100644 index 00000000..6f623c1d --- /dev/null +++ b/tests/src/Drupal11/Rector/Deprecation/ReplaceNonBoolAccessRector/config/configured_rule.php @@ -0,0 +1,11 @@ + 'foo', '#access' => 1]; + $other = ['#markup' => 'bar', '#access' => 0]; + return [$build, $other]; +} +?> +----- + 'foo', '#access' => true]; + $other = ['#markup' => 'bar', '#access' => false]; + return [$build, $other]; +} +?> diff --git a/tests/src/Drupal11/Rector/Deprecation/ReplaceNonBoolAccessRector/fixture/no_change_non_integer.php.inc b/tests/src/Drupal11/Rector/Deprecation/ReplaceNonBoolAccessRector/fixture/no_change_non_integer.php.inc new file mode 100644 index 00000000..70002f77 --- /dev/null +++ b/tests/src/Drupal11/Rector/Deprecation/ReplaceNonBoolAccessRector/fixture/no_change_non_integer.php.inc @@ -0,0 +1,18 @@ + 'foo', '#access' => true]; + $b = ['#markup' => 'foo', '#access' => false]; + + // Variable — cannot determine boolean replacement statically. + $c = ['#markup' => 'foo', '#access' => $variable]; + + // AccessResultInterface expression — leave alone. + $d = ['#markup' => 'foo', '#access' => $access_result]; + + // Unrelated key — even integer values must be ignored. + $e = ['#weight' => 1, '#markup' => 'foo']; + + return [$a, $b, $c, $d, $e]; +} diff --git a/tests/src/Rector/Convert/HookConvertRector/HookConvertRectorTest.php b/tests/src/Rector/Convert/HookConvertRector/HookConvertRectorTest.php index d05b5807..95390375 100644 --- a/tests/src/Rector/Convert/HookConvertRector/HookConvertRectorTest.php +++ b/tests/src/Rector/Convert/HookConvertRector/HookConvertRectorTest.php @@ -12,9 +12,7 @@ use PhpParser\Node\Stmt\Function_; use PhpParser\ParserFactory; use PHPUnit\Framework\TestCase; -use Rector\NodeAnalyzer\ExprAnalyzer; use Rector\PhpParser\Printer\BetterStandardPrinter; -use Rector\Util\Reflection\PrivatesAccessor; class HookConvertRectorTest extends TestCase { @@ -22,7 +20,11 @@ class HookConvertRectorTest extends TestCase protected function setUp(): void { - $printer = new BetterStandardPrinter(new ExprAnalyzer(), new PrivatesAccessor()); + // getLegacyHookFunction() doesn't use the printer; bypassing the + // constructor avoids rector's internal dependency chain (>=2.4.5 + // ExprAnalyzer now requires NodeNameResolver, which transitively + // needs ReflectionProvider). + $printer = (new \ReflectionClass(BetterStandardPrinter::class))->newInstanceWithoutConstructor(); $this->rector = new HookConvertRector($printer); $ref = new \ReflectionClass($this->rector);