Fail closed on ambiguous import-style enum canonicalization

Stop rewriting OrderEvent.PAY to the machine package when the simple name is ambiguous, and apply the same guard when capping polymorphic events to configured transitions.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-14 05:52:37 +02:00
parent 26560fee6d
commit cef9dd1f9a
2 changed files with 73 additions and 9 deletions

View File

@@ -69,6 +69,13 @@ public final class MachineEnumCanonicalizer {
public static TriggerPoint canonicalizeTriggerPoint(
TriggerPoint trigger,
StateMachineTypeResolver.MachineTypes machineTypes) {
return canonicalizeTriggerPoint(trigger, machineTypes, null);
}
public static TriggerPoint canonicalizeTriggerPoint(
TriggerPoint trigger,
StateMachineTypeResolver.MachineTypes machineTypes,
CodebaseContext context) {
if (trigger == null) {
return null;
}
@@ -81,14 +88,14 @@ public final class MachineEnumCanonicalizer {
List<String> polymorphicEvents = trigger.getPolymorphicEvents() == null ? null
: trigger.getPolymorphicEvents().stream()
.map(event -> canonicalizeLabel(event, eventTypeFqn))
.map(event -> canonicalizeLabel(event, eventTypeFqn, context))
.collect(Collectors.toList());
return trigger.toBuilder()
.eventTypeFqn(eventTypeFqn)
.stateTypeFqn(stateTypeFqn)
.event(canonicalizeLabel(trigger.getEvent(), eventTypeFqn))
.sourceState(canonicalizeLabel(trigger.getSourceState(), stateTypeFqn))
.event(canonicalizeLabel(trigger.getEvent(), eventTypeFqn, context))
.sourceState(canonicalizeLabel(trigger.getSourceState(), stateTypeFqn, context))
.polymorphicEvents(polymorphicEvents)
.build();
}
@@ -162,7 +169,7 @@ public final class MachineEnumCanonicalizer {
TriggerPoint trigger,
StateMachineTypeResolver.MachineTypes machineTypes,
CodebaseContext context) {
TriggerPoint canonical = canonicalizeTriggerPoint(trigger, machineTypes);
TriggerPoint canonical = canonicalizeTriggerPoint(trigger, machineTypes, context);
if (canonical == null || machineTypes == null || machineTypes.eventTypeFqn() == null) {
return canonical;
}
@@ -404,7 +411,7 @@ public final class MachineEnumCanonicalizer {
}
if (!transitionEvents.isEmpty()) {
result = capToConfiguredTransitionEvents(result, transitionEvents, eventTypeFqn);
result = capToConfiguredTransitionEvents(result, transitionEvents, eventTypeFqn, context);
}
return result;
}
@@ -416,6 +423,14 @@ public final class MachineEnumCanonicalizer {
List<String> candidates,
List<String> transitionEvents,
String eventTypeFqn) {
return capToConfiguredTransitionEvents(candidates, transitionEvents, eventTypeFqn, null);
}
static List<String> capToConfiguredTransitionEvents(
List<String> candidates,
List<String> transitionEvents,
String eventTypeFqn,
CodebaseContext context) {
if (transitionEvents == null || transitionEvents.isEmpty()) {
return candidates == null ? List.of() : candidates;
}
@@ -440,12 +455,12 @@ public final class MachineEnumCanonicalizer {
String transitionType = enumTypeFromRef(transitionEvent);
if (candidate.contains(".") && transitionEvent.contains(".")) {
if (candidateType != null && transitionType != null
&& enumTypesMatch(candidateType, transitionType)) {
&& enumTypesMatch(candidateType, transitionType, context)) {
capped.add(transitionEvent);
break;
}
} else if (eventTypeFqn != null && transitionType != null
&& enumTypesMatch(eventTypeFqn, transitionType)) {
&& enumTypesMatch(eventTypeFqn, transitionType, context)) {
capped.add(transitionEvent);
break;
}
@@ -723,6 +738,10 @@ public final class MachineEnumCanonicalizer {
}
public static String canonicalizeLabel(String value, String enumTypeFqn) {
return canonicalizeLabel(value, enumTypeFqn, null);
}
public static String canonicalizeLabel(String value, String enumTypeFqn, CodebaseContext context) {
if (value == null || value.isBlank() || enumTypeFqn == null || enumTypeFqn.isBlank()) {
return value;
}
@@ -749,11 +768,15 @@ public final class MachineEnumCanonicalizer {
String constant = constantName(stripped);
String typePart = enumTypeFromRef(stripped);
if (typePart != null && enumTypesMatch(enumTypeFqn, typePart)) {
if (typePart != null && context != null && context.isAmbiguousSimpleName(typePart)) {
return stripped;
}
if (typePart != null && enumTypesMatch(enumTypeFqn, typePart, context)) {
return enumTypeFqn + "." + constant;
}
if (typePart != null && !typePart.contains(".") && !enumTypesMatch(enumTypeFqn, typePart)
if (typePart != null && !typePart.contains(".") && !enumTypesMatch(enumTypeFqn, typePart, context)
&& importStyleEnumTypeMatches(typePart, enumTypeFqn)
&& constant.matches("[A-Z_][A-Z0-9_]*")) {
return enumTypeFqn + "." + constant;

View File

@@ -1,6 +1,12 @@
package click.kamil.springstatemachineexporter.analysis.resolver;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
@@ -23,4 +29,39 @@ class MachineEnumCanonicalizerCrossPackageTest {
"OrderEvents.PAY", "com.bar.OrderEvents"))
.isEqualTo("com.bar.OrderEvents.PAY");
}
@Test
void shouldNotRewriteImportStyleWhenSimpleNameIsAmbiguous(@TempDir Path tempDir) throws Exception {
Files.createDirectories(tempDir.resolve("a"));
Files.createDirectories(tempDir.resolve("b"));
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 }");
CodebaseContext context = new CodebaseContext();
context.scan(tempDir);
assertThat(MachineEnumCanonicalizer.canonicalizeLabel("OrderEvent.PAY", "a.OrderEvent", context))
.isEqualTo("OrderEvent.PAY");
}
@Test
void shouldNotCapImportStyleWhenSimpleNameIsAmbiguous(@TempDir Path tempDir) throws Exception {
Files.createDirectories(tempDir.resolve("a"));
Files.createDirectories(tempDir.resolve("b"));
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 }");
CodebaseContext context = new CodebaseContext();
context.scan(tempDir);
assertThat(MachineEnumCanonicalizer.capToConfiguredTransitionEvents(
List.of("OrderEvent.PAY"),
List.of("a.OrderEvent.PAY"),
"a.OrderEvent",
context)).isEmpty();
}
}