Defer polymorphic qualification to linker with context
Stop rewriting polymorphic events in TriggerPoint construction without ambiguity context; canonicalize labels at link time while preserving type FQNs for shared-infrastructure routing. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -59,6 +59,7 @@ public class TransitionLinkerEnricher implements AnalysisEnricher {
|
||||
|
||||
tp = MachineEnumCanonicalizer.ensureCallChainPolymorphicEvents(
|
||||
tp, machineTypes, context, stateMachineTransitions, true);
|
||||
tp = MachineEnumCanonicalizer.canonicalizeTriggerLabelsForLinking(tp, machineTypes, context);
|
||||
tp = CallChainLinkPolicy.applyRestExternalPolicy(tp, chain, context);
|
||||
|
||||
if (LifecycleTriggerMarkers.isLifecycle(tp.getEvent())) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import click.kamil.springstatemachineexporter.analysis.model.CallChain;
|
||||
import click.kamil.springstatemachineexporter.analysis.model.CodebaseMetadata;
|
||||
import click.kamil.springstatemachineexporter.analysis.model.MatchedTransition;
|
||||
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
|
||||
import click.kamil.springstatemachineexporter.analysis.resolver.MachineEnumCanonicalizer;
|
||||
import click.kamil.springstatemachineexporter.analysis.resolver.StateMachineTypeResolver;
|
||||
import click.kamil.springstatemachineexporter.model.Transition;
|
||||
import lombok.Builder;
|
||||
@@ -139,6 +140,8 @@ public class AnalysisResult {
|
||||
List<String> polymorphicEvents = trigger.getPolymorphicEvents() == null ? null
|
||||
: trigger.getPolymorphicEvents().stream()
|
||||
.map(value -> resolver.resolveValue(value, properties))
|
||||
.map(value -> MachineEnumCanonicalizer.qualifyEventIdentifier(
|
||||
value, trigger.getEventTypeFqn()))
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
return trigger.toBuilder()
|
||||
.event(trigger.getEvent() != null ? resolver.resolveValue(trigger.getEvent(), properties) : null)
|
||||
|
||||
@@ -55,13 +55,7 @@ public class TriggerPoint {
|
||||
this.stateTypeFqn = stateTypeFqn;
|
||||
this.eventTypeFqn = eventTypeFqn;
|
||||
this.event = MachineEnumCanonicalizer.qualifyEventIdentifier(event, eventTypeFqn);
|
||||
if (polymorphicEvents != null) {
|
||||
this.polymorphicEvents = polymorphicEvents.stream()
|
||||
.map(pe -> MachineEnumCanonicalizer.qualifyEventIdentifier(pe, eventTypeFqn))
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
} else {
|
||||
this.polymorphicEvents = null;
|
||||
}
|
||||
this.polymorphicEvents = polymorphicEvents;
|
||||
this.external = external;
|
||||
this.constraint = constraint;
|
||||
this.ambiguous = ambiguous;
|
||||
|
||||
@@ -100,6 +100,37 @@ public final class MachineEnumCanonicalizer {
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Canonicalizes trigger labels for linking using machine types, but preserves the trigger's own
|
||||
* {@code eventTypeFqn}/{@code stateTypeFqn} so routing evidence (e.g. shared-infrastructure
|
||||
* detection) is not overwritten before machine affinity is resolved.
|
||||
*/
|
||||
public static TriggerPoint canonicalizeTriggerLabelsForLinking(
|
||||
TriggerPoint trigger,
|
||||
StateMachineTypeResolver.MachineTypes machineTypes,
|
||||
CodebaseContext context) {
|
||||
if (trigger == null) {
|
||||
return null;
|
||||
}
|
||||
String eventTypeFqnForLabels = preferFullTypeFqn(
|
||||
trigger.getEventTypeFqn(),
|
||||
machineTypes != null ? machineTypes.eventTypeFqn() : null);
|
||||
String stateTypeFqnForLabels = preferFullTypeFqn(
|
||||
trigger.getStateTypeFqn(),
|
||||
machineTypes != null ? machineTypes.stateTypeFqn() : null);
|
||||
|
||||
List<String> polymorphicEvents = trigger.getPolymorphicEvents() == null ? null
|
||||
: trigger.getPolymorphicEvents().stream()
|
||||
.map(event -> canonicalizeLabel(event, eventTypeFqnForLabels, context))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return trigger.toBuilder()
|
||||
.event(canonicalizeLabel(trigger.getEvent(), eventTypeFqnForLabels, context))
|
||||
.sourceState(canonicalizeLabel(trigger.getSourceState(), stateTypeFqnForLabels, context))
|
||||
.polymorphicEvents(polymorphicEvents)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Expands {@code <SYMBOLIC: EnumType.*>} placeholders into concrete package-canonical enum
|
||||
* constants for the owning machine's event type. Non-matching symbolic entries are dropped.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package click.kamil.springstatemachineexporter.analysis.resolver;
|
||||
|
||||
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
|
||||
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
@@ -64,4 +65,31 @@ class MachineEnumCanonicalizerCrossPackageTest {
|
||||
"a.OrderEvent",
|
||||
context)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCanonicalizeLabelsForLinkingWithoutOverwritingTypeFqns(@TempDir Path tempDir) throws Exception {
|
||||
Files.createDirectories(tempDir.resolve("com/example/order"));
|
||||
Files.writeString(tempDir.resolve("com/example/order/OrderEvent.java"),
|
||||
"package com.example.order; public enum OrderEvent { PAY, SHIP }");
|
||||
Files.writeString(tempDir.resolve("App.java"), "package com.example; class App {}");
|
||||
|
||||
CodebaseContext context = new CodebaseContext();
|
||||
context.scan(tempDir);
|
||||
|
||||
TriggerPoint trigger = TriggerPoint.builder()
|
||||
.event("OrderEvent.valueOf(action)")
|
||||
.polymorphicEvents(List.of("OrderEvent.PAY", "OrderEvent.SHIP"))
|
||||
.build();
|
||||
StateMachineTypeResolver.MachineTypes types = new StateMachineTypeResolver.MachineTypes(
|
||||
"com.example.order.OrderState", "com.example.order.OrderEvent");
|
||||
|
||||
TriggerPoint linked = MachineEnumCanonicalizer.canonicalizeTriggerLabelsForLinking(
|
||||
trigger, types, context);
|
||||
|
||||
assertThat(linked.getEventTypeFqn()).isNull();
|
||||
assertThat(linked.getStateTypeFqn()).isNull();
|
||||
assertThat(linked.getPolymorphicEvents()).containsExactly(
|
||||
"com.example.order.OrderEvent.PAY",
|
||||
"com.example.order.OrderEvent.SHIP");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user