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 <cursoragent@cursor.com>
This commit is contained in:
2026-07-14 06:31:11 +02:00
parent 4a8b6ae9d9
commit d589f65cc1
2 changed files with 51 additions and 5 deletions

View File

@@ -137,7 +137,7 @@ public final class AnalysisResultFinalizer {
: MachineEnumCanonicalizer.canonicalizeAndExpandTriggerPoint( : MachineEnumCanonicalizer.canonicalizeAndExpandTriggerPoint(
chain.getTriggerPoint(), machineTypes, context); chain.getTriggerPoint(), machineTypes, context);
List<MatchedTransition> matched = canonicalizeMatchedTransitions( List<MatchedTransition> matched = canonicalizeMatchedTransitions(
chain.getMatchedTransitions(), machineTypes); chain.getMatchedTransitions(), machineTypes, context);
return chain.toBuilder() return chain.toBuilder()
.triggerPoint(trigger) .triggerPoint(trigger)
.matchedTransitions(matched) .matchedTransitions(matched)
@@ -148,18 +148,19 @@ public final class AnalysisResultFinalizer {
private static List<MatchedTransition> canonicalizeMatchedTransitions( private static List<MatchedTransition> canonicalizeMatchedTransitions(
List<MatchedTransition> matchedTransitions, List<MatchedTransition> matchedTransitions,
StateMachineTypeResolver.MachineTypes machineTypes) { StateMachineTypeResolver.MachineTypes machineTypes,
CodebaseContext context) {
if (matchedTransitions == null) { if (matchedTransitions == null) {
return null; return null;
} }
return matchedTransitions.stream() return matchedTransitions.stream()
.map(matched -> MatchedTransition.builder() .map(matched -> MatchedTransition.builder()
.event(MachineEnumCanonicalizer.canonicalizeLabel( .event(MachineEnumCanonicalizer.canonicalizeLabel(
matched.getEvent(), machineTypes.eventTypeFqn())) matched.getEvent(), machineTypes.eventTypeFqn(), context))
.sourceState(MachineEnumCanonicalizer.canonicalizeLabel( .sourceState(MachineEnumCanonicalizer.canonicalizeLabel(
matched.getSourceState(), machineTypes.stateTypeFqn())) matched.getSourceState(), machineTypes.stateTypeFqn(), context))
.targetState(MachineEnumCanonicalizer.canonicalizeLabel( .targetState(MachineEnumCanonicalizer.canonicalizeLabel(
matched.getTargetState(), machineTypes.stateTypeFqn())) matched.getTargetState(), machineTypes.stateTypeFqn(), context))
.build()) .build())
.collect(Collectors.toList()); .collect(Collectors.toList());
} }

View File

@@ -123,6 +123,51 @@ class AnalysisResultFinalizerTest {
assertThat(resolved.getPolymorphicEvents()).containsExactly("com.example.order.OrderEvent.PAY"); 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<OrderState, OrderEvent> {
}
""");
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 { private static void writeSampleConfig(Path tempDir) throws Exception {
Path orderPkg = tempDir.resolve("com/example/order"); Path orderPkg = tempDir.resolve("com/example/order");
Path configPkg = tempDir.resolve("com/example/config"); Path configPkg = tempDir.resolve("com/example/config");