Resolve path bindings through cross-class static helpers.

EntryPointBindingExpander follows single-arg normalize helpers declared on other types and accepts switch selectors wrapped by those helpers when extracting string case arms.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-14 17:47:45 +02:00
parent 8ef6e5ef8f
commit a70059331b
2 changed files with 164 additions and 9 deletions

View File

@@ -579,4 +579,112 @@ class EntryPointBindingExpanderTest {
assertThat(variants).extracting(map -> map.get("event"))
.containsExactlyInAnyOrder("PAY", "SHIP");
}
@Test
void shouldExpandEventWhenParameterIsPassedThroughCrossClassStaticHelper(@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) {
OrderEvent.valueOf(EventNormalizer.normalize(event));
}
}
class EventNormalizer {
static String normalize(String raw) {
return raw.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 shouldExpandPathVariableFromSwitchOnCrossClassStaticHelper(@TempDir Path tempDir) throws Exception {
String source = """
package com.example;
public class GenericCommandController {
CommandGateway gateway;
public void execute(String commandKey) {
gateway.executeViaMapper(commandKey);
}
}
class CommandGateway {
StringCommandMapper mapper;
void executeViaMapper(String commandKey) {
mapper.fromString(commandKey);
}
}
class StringCommandMapper {
DomainCommand fromString(String commandKey) {
return switch (CommandKeys.normalize(commandKey)) {
case "order.pay" -> DomainCommand.ORDER_PAY;
case "order.ship" -> DomainCommand.ORDER_SHIP;
default -> throw new IllegalArgumentException(commandKey);
};
}
}
class CommandKeys {
static String normalize(String raw) {
return raw.trim();
}
}
enum DomainCommand { ORDER_PAY, ORDER_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.GenericCommandController")
.methodName("execute")
.name("POST /api/commands/{commandKey}")
.parameters(List.of(EntryPoint.Parameter.builder()
.name("commandKey")
.type("String")
.annotations(List.of("PathVariable"))
.build()))
.build();
List<Map<String, String>> variants =
EntryPointBindingExpander.expandPathVariableBindings(entryPoint, context, graph);
assertThat(variants).extracting(map -> map.get("commandKey"))
.containsExactlyInAnyOrder("order.pay", "order.ship");
}
}