Follow intra-method parameter aliases during path binding expansion.

Local renames like eventCode = event or normalized = event.trim() no longer break Enum.valueOf case extraction; add multi-hop and normalization coverage in EntryPointBindingExpanderTest.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-14 17:44:21 +02:00
parent a3789ceb3c
commit 5348dafb69
2 changed files with 255 additions and 6 deletions

View File

@@ -370,4 +370,166 @@ class EntryPointBindingExpanderTest {
"ORDER".equals(variant.get("machineType"))
&& List.of("PAY", "SHIP").contains(variant.get("event")))).isTrue();
}
@Test
void shouldExpandEventThroughMultiHopParameterRenames(@TempDir Path tempDir) throws Exception {
String source = """
package com.example;
public class MachineController {
RoutingGateway gateway;
public void transition(String machineType, String event) {
gateway.route(machineType, event);
}
}
class RoutingGateway {
void route(String domain, String actionKey) {
dispatcher.dispatch(domain, actionKey);
}
StateMachineDispatcher dispatcher;
}
class StateMachineDispatcher {
void dispatch(String machineType, String eventString) {
if ("ORDER".equalsIgnoreCase(machineType)) {
fireOrder(eventString);
} else if ("DOCUMENT".equalsIgnoreCase(machineType)) {
fireDocument(eventString);
}
}
void fireOrder(String eventString) {
OrderEvent.valueOf(eventString.toUpperCase());
}
void fireDocument(String eventString) {
DocumentEvent.valueOf(eventString.toUpperCase());
}
}
enum OrderEvent { PAY, SHIP }
enum DocumentEvent { SUBMIT, APPROVE }
""";
Files.writeString(tempDir.resolve("App.java"), source);
CodebaseContext context = new CodebaseContext();
context.setResolveBindings(true);
context.scan(tempDir);
HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context);
Map<String, List<click.kamil.springstatemachineexporter.analysis.model.CallEdge>> graph = engine.buildCallGraph();
EntryPoint entryPoint = EntryPoint.builder()
.className("com.example.MachineController")
.methodName("transition")
.name("POST /api/machine/{machineType}/transition/{event}")
.parameters(List.of(
EntryPoint.Parameter.builder()
.name("machineType")
.type("String")
.annotations(List.of("PathVariable"))
.build(),
EntryPoint.Parameter.builder()
.name("event")
.type("String")
.annotations(List.of("PathVariable"))
.build()))
.build();
List<Map<String, String>> variants =
EntryPointBindingExpander.expandPathVariableBindings(entryPoint, context, graph);
assertThat(variants).anySatisfy(variant -> {
assertThat(variant).containsEntry("machineType", "ORDER");
assertThat(variant.get("event")).isIn("PAY", "SHIP");
});
assertThat(EntryPointBindingExpander.withResolvedPath(
entryPoint, Map.of("machineType", "ORDER", "event", "PAY")).getName())
.isEqualTo("POST /api/machine/ORDER/transition/PAY");
}
@Test
void shouldExpandEventWhenParameterIsRenamedWithinMethodBody(@TempDir Path tempDir) throws Exception {
String source = """
package com.example;
public class OrderController {
OrderGateway gateway;
public void transition(String event) {
gateway.trigger(event);
}
}
class OrderGateway {
void trigger(String event) {
String eventCode = event;
OrderEvent.valueOf(eventCode.toUpperCase());
}
}
enum OrderEvent { PAY, SHIP }
""";
Files.writeString(tempDir.resolve("App.java"), source);
CodebaseContext context = new CodebaseContext();
context.setResolveBindings(true);
context.scan(tempDir);
HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context);
Map<String, List<click.kamil.springstatemachineexporter.analysis.model.CallEdge>> graph = engine.buildCallGraph();
EntryPoint entryPoint = EntryPoint.builder()
.className("com.example.OrderController")
.methodName("transition")
.name("POST /api/order/{event}")
.parameters(List.of(EntryPoint.Parameter.builder()
.name("event")
.type("String")
.annotations(List.of("PathVariable"))
.build()))
.build();
List<Map<String, String>> variants =
EntryPointBindingExpander.expandPathVariableBindings(entryPoint, context, graph);
assertThat(variants).extracting(map -> map.get("event"))
.containsExactlyInAnyOrder("PAY", "SHIP");
}
@Test
void shouldExpandEventWhenParameterIsNormalizedWithinMethodBody(@TempDir Path tempDir) throws Exception {
String source = """
package com.example;
public class OrderController {
OrderGateway gateway;
public void transition(String event) {
gateway.trigger(event);
}
}
class OrderGateway {
void trigger(String event) {
String normalized = event.trim();
OrderEvent.valueOf(normalized.toUpperCase());
}
}
enum OrderEvent { PAY, SHIP }
""";
Files.writeString(tempDir.resolve("App.java"), source);
CodebaseContext context = new CodebaseContext();
context.setResolveBindings(true);
context.scan(tempDir);
HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context);
Map<String, List<click.kamil.springstatemachineexporter.analysis.model.CallEdge>> graph = engine.buildCallGraph();
EntryPoint entryPoint = EntryPoint.builder()
.className("com.example.OrderController")
.methodName("transition")
.name("POST /api/order/{event}")
.parameters(List.of(EntryPoint.Parameter.builder()
.name("event")
.type("String")
.annotations(List.of("PathVariable"))
.build()))
.build();
List<Map<String, String>> variants =
EntryPointBindingExpander.expandPathVariableBindings(entryPoint, context, graph);
assertThat(variants).extracting(map -> map.get("event"))
.containsExactlyInAnyOrder("PAY", "SHIP");
}
}