Generalize the CanvasConfigUpdater preSave-wiring PHPStan enforcement beyond Component (JavaScriptComponent, ComponentTree config entities, FieldConfig)
### Overview In #3591611 we added attribute-driven static enforcement so that a `CanvasConfigUpdater` method which heals a **`Component`** on save cannot silently skip its `Component::preSave()` wiring. A `post_update` alone is not enough: it runs once per site and never for imported config, so an entity re-saved outside the update path keeps its outdated data unless an equivalent heal also runs in `::preSave()`. That enforcement is currently **`Component`-only**. `CanvasConfigUpdater` heals other config entities the same way, and those paths are still guarded by humans remembering to wire `::preSave()`: - `updateJavaScriptComponent()` → `JavaScriptComponent::preSave()` - `updateConfigEntityWithComponentTreeInputs()` / `updateConfigEntityWithComponentTreeInputsAsArrays()` → `ComponentTreeConfigEntityBase::preSave()` (ContentTemplate, PageRegion, Pattern) and `FieldConfig` Nothing stops a contributor from adding (or forgetting to wire) such a method for any of these. We want the same deterministic build-time guarantee for every config entity an updater can heal, not just `Component`. ### Proposed resolution Generalize the two attributes and two PHPStan rules introduced in #3591611 from `Component` to **any config entity** `CanvasConfigUpdater` heals. - Make the attributes entity-agnostic (today `#[ComponentPreSaveUpdate]` / `#[NotAppliedOnComponentPreSave]`), so the "must heal on save" / "explicitly opted out" decision is recorded for `JavaScriptComponent`, `ComponentTreeConfigEntityBase` subclasses, and `FieldConfig` updaters too. - The "must heal on save" attribute records **which** entity's `::preSave()` the method must be invoked from, rather than assuming `Component::preSave()`. The rule can infer the target from the updater method's parameter type. - Broaden the "must declare intent" rule's filter from `Component`-taking methods to any public, non-`needs*` updater that takes a config entity, so forgetting to even consider preSave fails the build for all of them. - Handle the awkward cases: updaters typed against a union (`ComponentTreeEntityInterface|FieldConfig`) heal in more than one place, and `FieldConfig` is a core entity with no Canvas `::preSave()` override — its heal-on-save runs from a presave hook, not an entity method, so the rule must accept a hook wiring site (or such methods declare the explicit opt-out with a reason). ### Remaining tasks - [ ] Make the two attributes and two rules entity-agnostic (carry the target entity / wiring site instead of hard-coding `Component`). - [ ] Annotate `updateJavaScriptComponent()` and assert it is wired into `JavaScriptComponent::preSave()`. - [ ] Cover the ComponentTree config entity updaters (`ComponentTreeConfigEntityBase::preSave()`), and decide how `FieldConfig` heal-on-save (presave hook) is recognised by the rule. - [ ] Decide the build-failure identifiers for the generalized rules (the current ones are named after `Component`). ### User interface changes None in this issue — developer-facing static analysis only. ### Related - Follow-up to #3591611. - Both follow-ups of #3591607.
issue