Skip to content

Feature/digest rectors#2

Open
bbrala wants to merge 133 commits intomainfrom
feature/digest-rectors
Open

Feature/digest rectors#2
bbrala wants to merge 133 commits intomainfrom
feature/digest-rectors

Conversation

@bbrala
Copy link
Copy Markdown
Owner

@bbrala bbrala commented Apr 30, 2026

An experiement rewriting rector rules from a digest to drupal-rector. Using confgiurable rectors where possible.

bbrala added 30 commits April 30, 2026 12:53
Creates src/Drupal11/Rector/Deprecation/ and tests/src/Drupal11/Rector/Deprecation/
directory structure for incoming Drupal 11 deprecation rules. Adds stub
Drupal11SetList.php following the established Drupal10SetList pattern.
Captures the structural differences between drupal-digests AI-generated rules
and drupal-rector conventions: namespace, base class selection, BC wrapping
decision tree, fixture format, test config patterns, and multi-node-type handling.
Reusable 14-step AI prompt that converts any drupal-digests rector rule into a
fully drupal-rector-compliant implementation: namespaced rule class, test class,
fixture file, and test config. Includes BC decision tree, class templates for both
AbstractRector and AbstractDrupalCoreRector patterns, and a verification checklist.
…ecationsRector

Validates the conversion workflow on two real drupal-digests rules:
- FormLocationRector (issue #3550054): ClassConstFetch replacement, no BC wrapping,
  replaces deprecated CommentItemInterface constants with FormLocation enum cases.
- LanguageModuleFunctionDeprecationsRector (issue #3574727): FuncCall → StaticCall/MethodCall,
  BC-wrapped via AbstractDrupalCoreRector, replaces deprecated language.module functions
  with OOP equivalents.

Updates Drupal stub VERSION from 10.99.x-dev to 11.99.x-dev so Drupal11 rules fire in
tests. All 95 tests pass (93 existing + 2 new).
Implements Drupal 11.4.0 deprecation fixes using existing data-driven rectors
rather than custom classes, following the pattern established by Drupal 8/9/10:

- CommentItemInterface::FORM_BELOW/FORM_SEPARATE_PAGE → Drupal\comment\FormLocation
  (ClassConstantToClassConstantRector, no BC wrap — ClassConstFetch is not CallLike)
- language_configuration_element_submit → LanguageConfiguration::submit
  (FunctionToStaticRector, BC-wrapped for 11.4.0)
- language_process_language_select → \Drupal::service('…')->processLanguageSelect()
  (FunctionToServiceRector, BC-wrapped for 11.4.0)

Also scaffolds src/Drupal11/ and tests/src/Drupal11/ directories and adds
Drupal11SetList stub, plus digest-to-rector conversion prompt and mapping docs.
Bumps Drupal VERSION stub to 11.99.x-dev so Drupal11 rules fire in tests.
… for version-specific test scenarios

Replaces the broken namespaced-class Drupal hack in BackwardsCompatibilityActionAnnotationToAttributeRectorTest
with a proper setUp/tearDown pattern. Updates docs to describe the new mechanism.
…Rector

Previously backwardsCompatibleCall wrapping only applied when both the
old and new nodes were CallLike (FuncCall, MethodCall, StaticCall, etc.).
The restriction was in AbstractDrupalCoreRector, not in DeprecationHelper
itself, which accepts any callable.

Broaden the guard to Node\Expr so that non-CallLike transformations such
as ClassConstFetch → ClassConstFetch and ClassConstFetch → PropertyFetch
also get BC-wrapped when the introduced version supports it.

Add a test stub rector (ClassConstFetchBCRector) and fixture to cover
the new Expr → Expr path.
…eplacements

FileSystemInterface::EXISTS_RENAME, EXISTS_REPLACE, and EXISTS_ERROR were
deprecated in drupal:10.3.0 and removed in drupal:12.0.0 (issue #3575575).
Replaced by the \Drupal\Core\File\FileExists backed enum.
- template_preprocess_{container,html,page,links,time,datetime_form,
  datetime_wrapper}() → ThemePreprocess/DatePreprocess service methods
  (issue #3501136)
- SystemManager::REQUIREMENT_{OK,WARNING,ERROR} → RequirementSeverity
  enum cases (issue #3575841)
node_mass_update() was deprecated in drupal:11.3.0 and removed in
drupal:13.0.0 (issue #3571623). Replaced by NodeBulkUpdate::process()
via the service container.
…rable rectors

FunctionToServiceRector (29 functions across 9 modules, issue numbers below):
- ckeditor5_filter_format_edit_form_submit, _update_ckeditor5_html_filter
  → Ckeditor5Hooks service (#3566792)
- _dblog_get_message_types, dblog_filters → DbLogFilters service (#3560398)
- contact_user_profile_form_submit, contact_form_user_admin_settings_submit
  → ContactFormHooks service (#3566888)
- 6× content_translation_* → content_translation.manager /
  ContentTranslationEnableTranslationPerBundle / ContentTranslationHooks (#3548571)
- locale_translation_batch_update_build, _batch_fetch_build → LocaleFetch (#3572339)
- 7× locale_translation_* → locale.project / LocaleSource (#3569328)
- 6× menu_ui_* → MenuUiUtility / MenuUiHooks (#3571400)
- text_summary → TextSummary::generate (#3568387)
- user_form_process_password_confirm → UserThemeHooks (#3582106)

FunctionToStaticRector (4 functions):
- views_ui_form_button_was_clicked → ViewsFormHelperTrait::formButtonWasClicked
- views_ui_add_limited_validation, _add_ajax_wrapper, _nojs_submit
  → ViewsFormAjaxHelperTrait (#3035340)

ClassConstantToClassConstantRector (6 constants):
- CommentItemInterface::HIDDEN/CLOSED/OPEN → CommentingStatus enum (#3574661)
- CommentInterface::ANONYMOUS_* → AnonymousContact enum (#3574661)
Replaces deprecated ModuleHandlerInterface::getName($module) calls with
\Drupal::service('extension.list.module')->getName($module), BC-wrapped
for sites running Drupal 10.3+.
…ist rename

Adds AliasManager::pathAliasWhitelistRebuild() -> pathAliasPrefixListRebuild()
rename (drupal:11.1.0, issue #3151086). Also registers DRUPAL_111/112/113
constants in Drupal11SetList.
…n rule

Replaces invalidateAll() with deleteAll() on CacheBackendInterface objects
(drupal:11.2.0, issue #3498947).
…r issue #3543035

Replaces CommentManagerInterface::getCountNewComments() with
\Drupal::service(\Drupal\history\HistoryManager::class)->getCountNewComments(),
BC-wrapped for drupal:11.3.0.
…6062

Replaces NodeStorage::revisionIds() and userRevisionIds() with equivalent
entity query chains (drupal:11.3.0, removed in drupal:13.0.0).
Replaces deprecated PluginBase::isConfigurable() calls with
instanceof \Drupal\Component\Plugin\ConfigurableInterface checks
(drupal:11.1.0, removed in drupal:12.0.0).
Replaces SessionManager::delete($uid) with
\Drupal::service(UserSessionRepositoryInterface::class)->deleteAll($uid),
BC-wrapped for drupal:11.4.0.
…sue #3490200

Renames fetchColumn() to fetchField() on database statements, skipping
PDO's native $this->clientStatement->fetchColumn() (drupal:11.2.0).
Replaces ModuleHandler::loadAllIncludes() with an explicit foreach loop
over getModuleList() + loadInclude() (drupal:11.3.0, removed in 13.0.0).
…Rector

- ReplaceRebuildThemeDataRector: replaces ThemeHandlerInterface::rebuildThemeData()
  with extension.list.theme service chain (drupal:10.3.0, issue #3571068)
- ViewsPluginHandlerManagerRector: replaces Views::pluginManager() and
  Views::handlerManager() with Drupal::service() equivalents (drupal:11.4.0, issue #3566424)
…all removal

Handles 11 deprecated Drupal functions across versions 11.2–11.4 that have been
removed with no direct replacement, using a configurable accumulation pattern
so multiple version config files can each add their function list.
…ting removal

$settings['state_cache'] deprecated in drupal:11.0.0 — state caching is permanently
enabled. Adds drupal-11.0-deprecations.php config file and DRUPAL_110 set list constant.
ModuleHandlerInterface::addModule() and addProfile() are no-ops since drupal:11.2.0
and removed in drupal:12.0.0. Uses isObjectType() type check to target only calls
on ModuleHandlerInterface implementors.
LinkWidget::validateTitleElement() deprecated in drupal:11.4.0, removed in drupal:12.0.0.
Validation is now handled by LinkTitleRequiredConstraint on the LinkItem field type.
\$form['#submit'][] = 'automated_cron_settings_submit' deprecated in drupal:11.4.0,
removed in drupal:13.0.0. Config saving is now handled automatically via
#config_target on the interval element.
- drupal-11.2: FunctionCallRemovalRector (template_preprocess, 5 update manager funcs),
  RemoveModuleHandlerAddModuleCallsRector
- drupal-11.3: FunctionCallRemovalRector (block_content_add_body_field)
- drupal-11.4: FunctionCallRemovalRector (views_ui contextual suppress funcs,
  automated_cron_settings_submit), RemoveLinkWidgetValidateTitleElementRector,
  RemoveAutomatedCronSubmitHandlerRector
REQUEST_TIME constant was deprecated in drupal:8.3.0 and removed in drupal:11.0.0.
Replaces all uses with \Drupal::time()->getRequestTime().
JSONAPI_FILTER_AMONG_* global constants deprecated in drupal:11.3.0, removed in
drupal:13.0.0. Replaced by \Drupal\jsonapi\JsonApiFilter::AMONG_* class constants.
bbrala added 30 commits May 3, 2026 10:52
…ample

Double-quoted string caused $editor to be interpreted as a PHP variable
interpolation. Changed to single-quoted string to fix PHPStan error
variable.undefined.
…rors

# Conflicts:
#	tests/src/Drupal10/Rector/Deprecation/ActionAnnotationToAttributeRector/BackwardsCompatibilityActionAnnotationToAttributeRectorTest.php
PHPUnit 12 dropped support for @dataProvider annotations. Replace with
#[\PHPUnit\Framework\Attributes\DataProvider('provideData')] attribute
across all 52 affected test files.
…ent instead of silently dropping it

Also add chained_call fixture for ViewsPluginHandlerManagerRector (no rector change needed, already worked).
…sRector to handle real-world patterns

PluginBaseIsConfigurableRector was restricted to $this->isConfigurable()
but missed delegation wrappers like $this->plugin->isConfigurable() where
the property is typed as PluginBase.  Remove the $this-only guard; the
existing isObjectType() check already verifies the receiver type.

RemoveViewsRowCacheKeysRector only handled the inline-array pattern
['keys' => $plugin->getRowCacheKeys($row)].  Two additional patterns seen
in the wild were silently skipped:
- Variable-first: $keys = $plugin->getRowCacheKeys($row) followed by an
  array item 'keys' => $keys.  Added Expression handler that removes the
  assignment and tracks the variable name; Array_ handler extended to also
  drop items whose value is a tracked variable.
- Method delegation: a ClassMethod whose entire body is
  return $plugin->getRowCacheKeys($row).  Added ClassMethod handler that
  removes the whole method.

beforeTraversal() resets the tracked variable list per file to avoid
false positives across file boundaries.
…ib search

ReplaceAlphadecimalToIntNullRector (case 3):
  search.tresbien.tech confirms 0 contrib modules call alphadecimalToInt()
  with a literal null or empty string. Pattern genuinely exhausted.
  Removed comment_mover; added to skipped list with explanation.

ReplaceDateTimeRangeConstantsRector (case 4):
  optional_end_date 8.x-1.4 already migrated away from
  DateTimeRangeConstantsInterface constants (version drift).
  For the datetime_type_field_views_data_helper() pattern, scheduler_field
  is confirmed to call it twice (D11-compatible ^8||^9||^10||^11).
  Added scheduler_field as second test module alongside optional_end_date.

ReplaceSessionManagerDeleteRector (case 5):
  entity_visibility_preview and session_inspector use their own custom
  SessionManager or raw DB queries — neither calls Drupal core's
  SessionManager::delete(). role_expire/src/RoleExpireApiService.php
  declares @var Drupal\Core\Session\SessionManager (concrete class) and
  calls $this->sessionManager->delete($uid), satisfying the rector's
  isObjectType check. D11-compatible (^10.2||^11).
  Replaced both modules with role_expire.

Also added search.tresbien.tech as step 1 in the investigation methodology.
…pal 11.4.0 version gate

role_expire is the correct test module (RoleExpireApiService.php:168 calls
$this->sessionManager->delete($uid) with the right concrete type), but
AbstractDrupalCoreRector gates on Drupal::VERSION >= 11.4.0 and 11.4.0
has not been released yet (latest stable: 11.3.9).

Disable the module in the test script with an explanatory comment.
Re-enable once the test site is upgraded to Drupal 11.4.0+.

Also add mglaman/phpstan-drupal to composer.json require.
Migrates 14 Drupal 11 rectors from AbstractRector to AbstractDrupalCoreRector
by renaming refactor() to refactorWithConfiguration(). This lets the parent
automatically wrap replacement expressions in DeprecationHelper::backwardsCompatibleCall()
so code emitted by rector works on both the old and new Drupal API during the
transition period.

Also adds DrupalIntroducedVersionConfiguration to the rector set configs for all
14 rectors (drupal-11.2/11.3/11.4-deprecations.php), and fixes two node-mutation
bugs in StatementPrefetchIteratorFetchColumnRector and ReplaceRecipeRunnerInstallModuleRector
where in-place node mutation would corrupt the BC "old callable" snapshot.

Fixes PHPStan errors in RemoveViewsRowCacheKeysRector (pre-existing): adds
@param/@return array<Node> to beforeTraversal() and @return NodeVisitor::REMOVE_NODE|null
to the two private helper methods.
…ectors

Migrates 7 Group B rectors from AbstractRector to AbstractDrupalCoreRector
so replacement expressions are wrapped in DeprecationHelper::backwardsCompatibleCall()
when the installed Drupal version supports it.

Rectors migrated:
- ReplaceLocaleConfigBatchFunctionsRector (11.1.0) — also fixes in-place mutation
  (clone node before renaming to preserve original for BC old-callable)
- ReplaceDateTimeRangeConstantsRector (11.2.0)
- ReplaceEntityOriginalPropertyRector (11.2.0) — uses IS_BEING_ASSIGNED attribute to
  skip PropertyFetch transformation when on assignment LHS, letting the Assign handler
  produce a BC-wrapped setOriginal() call with the original assignment as old-callable
- ReplaceNodeAccessViewAllNodesRector (11.3.0)
- ReplaceNodeModuleProceduralFunctionsRector (11.3.0)
- ReplaceThemeGetSettingRector (11.3.0)
- ReplaceViewsProceduralFunctionsRector (11.4.0)

All 339 tests pass, PHPStan clean.
…-wrapped rectors

Each rector test that uses DrupalIntroducedVersionConfiguration now has
two explicit version scenarios: testAboveVersion (99.99.99 override,
expects BC-wrapped output) and testBelowVersion (1.0.0 override, expects
no transformation). A fixture-below-version/ directory holds the
identity fixtures for the below-version path.
…emove group from one of the rectors since that hangs on one of the tests.
…-typed variables

The rector was checking ObjectType('SessionManager') (the concrete class),
which missed the common pattern of injecting SessionManagerInterface and
calling ->delete() on it (as seen in role_expire).

- Switch type guard to SessionManagerInterface so both the interface and
  concrete class are matched
- Add PHPStan stubs for SessionManagerInterface and SessionManager so the
  type hierarchy resolves correctly in tests
- Replace no_change_interface fixture (was documenting the limitation)
  with interface_variable and interface_property positive fixtures
…ackslash

Old Drupal contrib code commonly writes `@var Drupal\Core\Session\SessionManager`
without a leading `\`. PHPStan resolves this relative to the current namespace,
producing a mangled class name like `Vendor\Module\Drupal\Core\Session\SessionManager`
that neither isObjectType check can match, causing the rector to silently skip
the file (as seen in role_expire).

Extract `isSessionManagerType()` with three layers:
- isObjectType(SessionManagerInterface) — properly typed interface
- isObjectType(SessionManager) — properly typed concrete class
- str_ends_with fallback on raw PHPStan class names — catches the
  namespace-mangled case; still limited to the exact Drupal class paths

Add `class_property_phpdoc.php.inc` fixture (above and below version) that
reproduces the role_expire pattern: non-promoted property with a @var-only
annotation and no leading backslash.
- Document specific patterns to identify issues in rector test logs.
- Outline steps to produce a structured report, including key metrics table.
Adds four Claude Code skills and a supporting PHP script to reduce
friction in the drupal-digests → drupal-rector conversion workflow:

- rector-discover: lists pending rules by phase; regenerates the index
  when stale
- rector-implement: wraps docs/digest-to-rector-prompt.md steps 1–14
  with two quality gates (type guard audit QG-A, version-gating QG-B)
- rector-live-test: finds D11-compatible contrib modules and runs the
  rector against them using search.tresbien.tech
- rector-qa: four-pass review (type guards, fixtures, BC decision,
  @see URL accuracy)

scripts/generate-rector-index.php scans the digests repo and rector
src/ to write docs/rector-index.yml — a single source of truth for
which of the 107 in-scope rules are implemented, config-only, or
pending. The YAML is gitignored (always re-generated from source).

Gitignore updated to track .claude/skills/** so skills are shareable
via git with teammates.
…tion

- rector-live-test: add WebFetch/WebSearch to allowed-tools (search.tresbien.tech
  was unreachable with Read/Bash/Glob only); replace 'Navigate to' with concrete
  WebFetch instruction
- rector-discover: fix duplicate step numbers (1,2,2,3,4,5 → 1,2,3,4,5,6); move
  ddev tip from Apply-filters step to the path-discovery step where it belongs
- rector-implement QG-A: replace hardcoded ~/projects/drupal-core with
  ordered path probe (ddev mount first, then sibling, then home)
- rector-implement QG-B: replace magic '10.0.0' stub with derived
  <major>.<minor-1>.0 pattern so the agent uses the actual introduced version
- rector-qa: same drupal-core path probe; replace ~/.claude/ skill reference with
  in-scope path; replace ~/projects/drupal-digests with DIGESTS_PATH variable
… syntax

Use /search endpoint, -r:drupal filter (not -r:core), Go regexp syntax,
and standard filters (f:.php$, -f:test, lang:php). Includes example URL.
Clone drupal-digests and drupal-core into repos/ (gitignored) via
scripts/setup-repos.sh so skills work inside ddev without external
mounts. Updated all skill path references from ~/projects/... to
repos/... and adjusted generate-rector-index.php default digest path.
Replaces deprecated _filter_autop(), _filter_html_escape(), and
_filter_html_image_secure_process() with plugin.manager.filter
createInstance() chains. BC-wrapped for Drupal 11.4.0.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant