chore(Data model): #3591600 "Add infrastructure to support finding commonalities in a set of scalar prop expressions"
Splits ~660 LoC of generic scalar PropExpression infrastructure out of !1112 so it can land independently and give that MR a much smaller, easier-to-review diff.
Per Wim's review of !1112 (notes 939464 and 939546 §B.2): the JsComponent config entity had bloated by ~50% with PropExpression-domain logic that wasn't intrinsic to it and had no unit-test surface. Moving it into its own class next to Evaluator/Labeler solves both. Also closes the Evaluator is_required: TRUE coverage gap from notes 939465/939466 here (rather than deferring to #3563309) so the multi-bundle behavior change doesn't land without a regression test.
Closes #3591600 (closed)
Commits (each leaves the branch green):
- c593db47 — replace
EntityFieldBasedPropExpressionInterface::hasSameStartingPointAs()withgetStartingPointKey(). Returning a stable key lets callers group expressions by starting point without N×N pairwise comparisons. IncludestestStartingPointKeycovering the key format and the three implementing classes. - f840768f — add
ReferenceFieldPropExpression::withFinalTargetReplaced(). Returns a clone with the deepest non-reference leaf swapped, preserving every referencer in the chain. Throws on multi-bundle (ambiguous branch). +6-assertion unit test. - 718908f7 — add
Coalescer(src/PropExpressions/StructuredData/). Public API:Coalescer::coalesce(list<string>)/Coalescer::expand(list<string>). Coalesces same-field-different-property picks intoFieldObjectPropsExpression, same-chain-different-bundle references intoReferenceFieldPropExpressionwithReferencedBundleSpecificBranches. 11 unit tests covering all three flavors + passthrough + roundtrip + idempotence. Adds 4 cspell entries. - 8b9b6171 —
Evaluatormulti-bundle no-branch handling. When an entity's bundle has no matching branch and the prop is optional, omit; when required, fall through and letReferencedBundleSpecificBranches::getBranch()throw\OutOfRange(with@todoto #3563309 for the full solution). Adds kernel coverage for both paths.
AI-Generated: Yes (Used Claude Code to lift methods from !1112 and write the Coalescer/Coalescer test/Evaluator kernel tests; final review and commits by the author).
Testing instructions
-
composer phpcsis clean -
composer phpstanis clean -
vendor/bin/phpunit -c web/core/phpunit.xml.dist web/modules/contrib/canvas/tests/src/Unit/CoalescerTest.php→ 11/11 pass -
vendor/bin/phpunit -c web/core/phpunit.xml.dist web/modules/contrib/canvas/tests/src/Unit/PropExpressionTest.php→ 159 tests pass (covers the rename +withFinalTargetReplaced) -
vendor/bin/phpunit -c web/core/phpunit.xml.dist web/modules/contrib/canvas/tests/src/Kernel/PropExpressionKernelTest.php→ 5/5 pass (covers the Evaluator multi-bundle + is_required behavior); the one deprecation warning is intentional (the tests deliberately construct expressions whose branches don't cover all of the field's target bundles, to exercise the new guard)