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):

  1. c593db47 — replace EntityFieldBasedPropExpressionInterface::hasSameStartingPointAs() with getStartingPointKey(). Returning a stable key lets callers group expressions by starting point without N×N pairwise comparisons. Includes testStartingPointKey covering the key format and the three implementing classes.
  2. 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.
  3. 718908f7 — add Coalescer (src/PropExpressions/StructuredData/). Public API: Coalescer::coalesce(list<string>) / Coalescer::expand(list<string>). Coalesces same-field-different-property picks into FieldObjectPropsExpression, same-chain-different-bundle references into ReferenceFieldPropExpression with ReferencedBundleSpecificBranches. 11 unit tests covering all three flavors + passthrough + roundtrip + idempotence. Adds 4 cspell entries.
  4. 8b9b6171Evaluator multi-bundle no-branch handling. When an entity's bundle has no matching branch and the prop is optional, omit; when required, fall through and let ReferencedBundleSpecificBranches::getBranch() throw \OutOfRange (with @todo to #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 phpcs is clean
  • composer phpstan is 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)
Edited by Christian López Espínola

Merge request reports

Loading