From d589f65cc1e1cd1f3193c655a7063e749249c5e3 Mon Sep 17 00:00:00 2001 From: Kamil Patryk Kozakowski Date: Tue, 14 Jul 2026 06:31:11 +0200 Subject: [PATCH] Use context when canonicalizing matched transitions Matched transition enum labels now respect ambiguous simple-name fail-closed during finalization instead of guessing package from machine types alone. Co-authored-by: Cursor --- .../service/AnalysisResultFinalizer.java | 11 ++--- .../service/AnalysisResultFinalizerTest.java | 45 +++++++++++++++++++ 2 files changed, 51 insertions(+), 5 deletions(-) diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/AnalysisResultFinalizer.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/AnalysisResultFinalizer.java index 99e1653..d3fad09 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/AnalysisResultFinalizer.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/AnalysisResultFinalizer.java @@ -137,7 +137,7 @@ public final class AnalysisResultFinalizer { : MachineEnumCanonicalizer.canonicalizeAndExpandTriggerPoint( chain.getTriggerPoint(), machineTypes, context); List matched = canonicalizeMatchedTransitions( - chain.getMatchedTransitions(), machineTypes); + chain.getMatchedTransitions(), machineTypes, context); return chain.toBuilder() .triggerPoint(trigger) .matchedTransitions(matched) @@ -148,18 +148,19 @@ public final class AnalysisResultFinalizer { private static List canonicalizeMatchedTransitions( List matchedTransitions, - StateMachineTypeResolver.MachineTypes machineTypes) { + StateMachineTypeResolver.MachineTypes machineTypes, + CodebaseContext context) { if (matchedTransitions == null) { return null; } return matchedTransitions.stream() .map(matched -> MatchedTransition.builder() .event(MachineEnumCanonicalizer.canonicalizeLabel( - matched.getEvent(), machineTypes.eventTypeFqn())) + matched.getEvent(), machineTypes.eventTypeFqn(), context)) .sourceState(MachineEnumCanonicalizer.canonicalizeLabel( - matched.getSourceState(), machineTypes.stateTypeFqn())) + matched.getSourceState(), machineTypes.stateTypeFqn(), context)) .targetState(MachineEnumCanonicalizer.canonicalizeLabel( - matched.getTargetState(), machineTypes.stateTypeFqn())) + matched.getTargetState(), machineTypes.stateTypeFqn(), context)) .build()) .collect(Collectors.toList()); } diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/AnalysisResultFinalizerTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/AnalysisResultFinalizerTest.java index f2f5fc3..558a7a3 100644 --- a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/AnalysisResultFinalizerTest.java +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/AnalysisResultFinalizerTest.java @@ -123,6 +123,51 @@ class AnalysisResultFinalizerTest { assertThat(resolved.getPolymorphicEvents()).containsExactly("com.example.order.OrderEvent.PAY"); } + @Test + void shouldPreserveAmbiguousImportStyleMatchedTransitions(@TempDir Path tempDir) throws Exception { + Files.createDirectories(tempDir.resolve("a")); + Files.createDirectories(tempDir.resolve("b")); + Files.createDirectories(tempDir.resolve("com/example/config")); + Files.writeString(tempDir.resolve("a/OrderEvent.java"), "package a; public enum OrderEvent { PAY }"); + Files.writeString(tempDir.resolve("b/OrderEvent.java"), "package b; public enum OrderEvent { CANCEL }"); + Files.writeString(tempDir.resolve("a/OrderState.java"), "package a; public enum OrderState { NEW, PAID }"); + Files.writeString(tempDir.resolve("com/example/config/OrderStateMachineConfiguration.java"), + """ + package com.example.config; + import a.OrderEvent; + import a.OrderState; + import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter; + public class OrderStateMachineConfiguration + extends EnumStateMachineConfigurerAdapter { + } + """); + + CodebaseContext context = new CodebaseContext(); + context.scan(tempDir); + + AnalysisResult result = AnalysisResult.builder() + .name("com.example.config.OrderStateMachineConfiguration") + .transitions(List.of()) + .metadata(CodebaseMetadata.builder() + .callChains(List.of(click.kamil.springstatemachineexporter.analysis.model.CallChain.builder() + .matchedTransitions(List.of( + click.kamil.springstatemachineexporter.analysis.model.MatchedTransition.builder() + .event("OrderEvent.PAY") + .sourceState("a.OrderState.NEW") + .targetState("a.OrderState.PAID") + .build())) + .build())) + .build()) + .build(); + + AnalysisResultFinalizer.applyCanonicalization(result, context, + new click.kamil.springstatemachineexporter.analysis.resolver.StateMachineTypeResolver.MachineTypes( + "a.OrderState", "a.OrderEvent")); + + assertThat(result.getMetadata().getCallChains().get(0).getMatchedTransitions().get(0).getEvent()) + .isEqualTo("OrderEvent.PAY"); + } + private static void writeSampleConfig(Path tempDir) throws Exception { Path orderPkg = tempDir.resolve("com/example/order"); Path configPkg = tempDir.resolve("com/example/config");