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:
@@ -69,6 +69,13 @@ public final class MachineEnumCanonicalizer {
|
|||||||
public static TriggerPoint canonicalizeTriggerPoint(
|
public static TriggerPoint canonicalizeTriggerPoint(
|
||||||
TriggerPoint trigger,
|
TriggerPoint trigger,
|
||||||
StateMachineTypeResolver.MachineTypes machineTypes) {
|
StateMachineTypeResolver.MachineTypes machineTypes) {
|
||||||
|
return canonicalizeTriggerPoint(trigger, machineTypes, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TriggerPoint canonicalizeTriggerPoint(
|
||||||
|
TriggerPoint trigger,
|
||||||
|
StateMachineTypeResolver.MachineTypes machineTypes,
|
||||||
|
CodebaseContext context) {
|
||||||
if (trigger == null) {
|
if (trigger == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -81,14 +88,14 @@ public final class MachineEnumCanonicalizer {
|
|||||||
|
|
||||||
List<String> polymorphicEvents = trigger.getPolymorphicEvents() == null ? null
|
List<String> polymorphicEvents = trigger.getPolymorphicEvents() == null ? null
|
||||||
: trigger.getPolymorphicEvents().stream()
|
: trigger.getPolymorphicEvents().stream()
|
||||||
.map(event -> canonicalizeLabel(event, eventTypeFqn))
|
.map(event -> canonicalizeLabel(event, eventTypeFqn, context))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
return trigger.toBuilder()
|
return trigger.toBuilder()
|
||||||
.eventTypeFqn(eventTypeFqn)
|
.eventTypeFqn(eventTypeFqn)
|
||||||
.stateTypeFqn(stateTypeFqn)
|
.stateTypeFqn(stateTypeFqn)
|
||||||
.event(canonicalizeLabel(trigger.getEvent(), eventTypeFqn))
|
.event(canonicalizeLabel(trigger.getEvent(), eventTypeFqn, context))
|
||||||
.sourceState(canonicalizeLabel(trigger.getSourceState(), stateTypeFqn))
|
.sourceState(canonicalizeLabel(trigger.getSourceState(), stateTypeFqn, context))
|
||||||
.polymorphicEvents(polymorphicEvents)
|
.polymorphicEvents(polymorphicEvents)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
@@ -162,7 +169,7 @@ public final class MachineEnumCanonicalizer {
|
|||||||
TriggerPoint trigger,
|
TriggerPoint trigger,
|
||||||
StateMachineTypeResolver.MachineTypes machineTypes,
|
StateMachineTypeResolver.MachineTypes machineTypes,
|
||||||
CodebaseContext context) {
|
CodebaseContext context) {
|
||||||
TriggerPoint canonical = canonicalizeTriggerPoint(trigger, machineTypes);
|
TriggerPoint canonical = canonicalizeTriggerPoint(trigger, machineTypes, context);
|
||||||
if (canonical == null || machineTypes == null || machineTypes.eventTypeFqn() == null) {
|
if (canonical == null || machineTypes == null || machineTypes.eventTypeFqn() == null) {
|
||||||
return canonical;
|
return canonical;
|
||||||
}
|
}
|
||||||
@@ -404,7 +411,7 @@ public final class MachineEnumCanonicalizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!transitionEvents.isEmpty()) {
|
if (!transitionEvents.isEmpty()) {
|
||||||
result = capToConfiguredTransitionEvents(result, transitionEvents, eventTypeFqn);
|
result = capToConfiguredTransitionEvents(result, transitionEvents, eventTypeFqn, context);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -416,6 +423,14 @@ public final class MachineEnumCanonicalizer {
|
|||||||
List<String> candidates,
|
List<String> candidates,
|
||||||
List<String> transitionEvents,
|
List<String> transitionEvents,
|
||||||
String eventTypeFqn) {
|
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()) {
|
if (transitionEvents == null || transitionEvents.isEmpty()) {
|
||||||
return candidates == null ? List.of() : candidates;
|
return candidates == null ? List.of() : candidates;
|
||||||
}
|
}
|
||||||
@@ -440,12 +455,12 @@ public final class MachineEnumCanonicalizer {
|
|||||||
String transitionType = enumTypeFromRef(transitionEvent);
|
String transitionType = enumTypeFromRef(transitionEvent);
|
||||||
if (candidate.contains(".") && transitionEvent.contains(".")) {
|
if (candidate.contains(".") && transitionEvent.contains(".")) {
|
||||||
if (candidateType != null && transitionType != null
|
if (candidateType != null && transitionType != null
|
||||||
&& enumTypesMatch(candidateType, transitionType)) {
|
&& enumTypesMatch(candidateType, transitionType, context)) {
|
||||||
capped.add(transitionEvent);
|
capped.add(transitionEvent);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else if (eventTypeFqn != null && transitionType != null
|
} else if (eventTypeFqn != null && transitionType != null
|
||||||
&& enumTypesMatch(eventTypeFqn, transitionType)) {
|
&& enumTypesMatch(eventTypeFqn, transitionType, context)) {
|
||||||
capped.add(transitionEvent);
|
capped.add(transitionEvent);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -723,6 +738,10 @@ public final class MachineEnumCanonicalizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static String canonicalizeLabel(String value, String enumTypeFqn) {
|
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()) {
|
if (value == null || value.isBlank() || enumTypeFqn == null || enumTypeFqn.isBlank()) {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
@@ -749,11 +768,15 @@ public final class MachineEnumCanonicalizer {
|
|||||||
String constant = constantName(stripped);
|
String constant = constantName(stripped);
|
||||||
String typePart = enumTypeFromRef(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;
|
return enumTypeFqn + "." + constant;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typePart != null && !typePart.contains(".") && !enumTypesMatch(enumTypeFqn, typePart)
|
if (typePart != null && !typePart.contains(".") && !enumTypesMatch(enumTypeFqn, typePart, context)
|
||||||
&& importStyleEnumTypeMatches(typePart, enumTypeFqn)
|
&& importStyleEnumTypeMatches(typePart, enumTypeFqn)
|
||||||
&& constant.matches("[A-Z_][A-Z0-9_]*")) {
|
&& constant.matches("[A-Z_][A-Z0-9_]*")) {
|
||||||
return enumTypeFqn + "." + constant;
|
return enumTypeFqn + "." + constant;
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
package click.kamil.springstatemachineexporter.analysis.resolver;
|
package click.kamil.springstatemachineexporter.analysis.resolver;
|
||||||
|
|
||||||
|
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||||
import org.junit.jupiter.api.Test;
|
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;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
@@ -23,4 +29,39 @@ class MachineEnumCanonicalizerCrossPackageTest {
|
|||||||
"OrderEvents.PAY", "com.bar.OrderEvents"))
|
"OrderEvents.PAY", "com.bar.OrderEvents"))
|
||||||
.isEqualTo("com.bar.OrderEvents.PAY");
|
.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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user