From 82f4e5c087218b216d0fb3e349c6706c1be6d7fa Mon Sep 17 00:00:00 2001 From: Kamil Patryk Kozakowski Date: Tue, 14 Jul 2026 05:45:23 +0200 Subject: [PATCH] Scope implementation and transition capping to package-qualified types When an interface simple name is ambiguous, resolve FQN implementation lookups within the requested package only, and stop capping polymorphic candidates to machine transitions by constant name across enum types. Co-authored-by: Cursor --- .../resolver/MachineEnumCanonicalizer.java | 19 +++++++++++++++--- .../ast/common/CodebaseContext.java | 20 ++++++++++++++++--- .../MachineEnumCanonicalizerTest.java | 10 ++++++++++ .../ast/common/CodebaseContextTest.java | 4 ++-- 4 files changed, 45 insertions(+), 8 deletions(-) diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/MachineEnumCanonicalizer.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/MachineEnumCanonicalizer.java index a66a435..e8e6bd2 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/MachineEnumCanonicalizer.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/MachineEnumCanonicalizer.java @@ -404,7 +404,7 @@ public final class MachineEnumCanonicalizer { } if (!transitionEvents.isEmpty()) { - result = capToConfiguredTransitionEvents(result, transitionEvents); + result = capToConfiguredTransitionEvents(result, transitionEvents, eventTypeFqn); } return result; } @@ -414,7 +414,8 @@ public final class MachineEnumCanonicalizer { */ static List capToConfiguredTransitionEvents( List candidates, - List transitionEvents) { + List transitionEvents, + String eventTypeFqn) { if (transitionEvents == null || transitionEvents.isEmpty()) { return candidates == null ? List.of() : candidates; } @@ -430,9 +431,21 @@ public final class MachineEnumCanonicalizer { capped.add(candidate); continue; } + String candidateType = enumTypeFromRef(candidate); String constant = constantName(candidate); for (String transitionEvent : transitionEvents) { - if (constantName(transitionEvent).equals(constant)) { + if (!constantName(transitionEvent).equals(constant)) { + continue; + } + String transitionType = enumTypeFromRef(transitionEvent); + if (candidate.contains(".") && transitionEvent.contains(".")) { + if (candidateType != null && transitionType != null + && enumTypesMatch(candidateType, transitionType)) { + capped.add(transitionEvent); + break; + } + } else if (eventTypeFqn != null && transitionType != null + && enumTypesMatch(eventTypeFqn, transitionType)) { capped.add(transitionEvent); break; } diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/common/CodebaseContext.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/common/CodebaseContext.java index 3edc9fd..6869e00 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/common/CodebaseContext.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/common/CodebaseContext.java @@ -460,9 +460,23 @@ public class CodebaseContext { } // Try simple name match if input was FQN - if (directImpls == null && typeName.contains(".")) { - String simpleName = typeName.substring(typeName.lastIndexOf('.') + 1); - directImpls = interfaceToImpls.get(simpleName); + if (directImpls == null && cleanName.contains(".")) { + String simpleName = cleanName.substring(cleanName.lastIndexOf('.') + 1); + List simpleImpls = interfaceToImpls.get(simpleName); + if (simpleImpls != null) { + if (ambiguousSimpleNames.contains(simpleName)) { + String pkg = cleanName.substring(0, cleanName.lastIndexOf('.')); + List pkgScoped = new ArrayList<>(); + for (String impl : simpleImpls) { + if (impl.startsWith(pkg + ".")) { + pkgScoped.add(impl); + } + } + directImpls = pkgScoped.isEmpty() ? null : pkgScoped; + } else { + directImpls = simpleImpls; + } + } } if (directImpls != null) { diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/resolver/MachineEnumCanonicalizerTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/resolver/MachineEnumCanonicalizerTest.java index 794ace5..68bd636 100644 --- a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/resolver/MachineEnumCanonicalizerTest.java +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/resolver/MachineEnumCanonicalizerTest.java @@ -263,6 +263,16 @@ class MachineEnumCanonicalizerTest { assertThat(enriched.getPolymorphicEvents()).isNullOrEmpty(); } + @Test + void shouldNotCapForeignPackageEnumConstantToMachineTransition() { + List capped = MachineEnumCanonicalizer.capToConfiguredTransitionEvents( + List.of("b.OrderEvent.PAY"), + List.of("a.OrderEvent.PAY"), + "a.OrderEvent"); + + assertThat(capped).isEmpty(); + } + @Test void shouldValidateRawNameConsistencyForCanonicalHelpers() { assertThat(MachineEnumCanonicalizer.isRawNameConsistentWithFullIdentifier( diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/ast/common/CodebaseContextTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/ast/common/CodebaseContextTest.java index 863040a..1e5c336 100644 --- a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/ast/common/CodebaseContextTest.java +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/ast/common/CodebaseContextTest.java @@ -271,8 +271,8 @@ class CodebaseContextTest { assertThat(context.getImplementations("Service")) .as("ambiguous simple interface name must not widen to arbitrary impls") .isEmpty(); - assertThat(context.getImplementations("a.Service")).contains("a.AServiceImpl"); - assertThat(context.getImplementations("b.Service")).contains("b.BServiceImpl"); + assertThat(context.getImplementations("a.Service")).containsExactly("a.AServiceImpl"); + assertThat(context.getImplementations("b.Service")).containsExactly("b.BServiceImpl"); } @Test