Resolve path bindings through single-arg string helper methods.
Enum.valueOf(normalize(event)) now expands when normalize trivially forwards its parameter; add expander regression test and assert flow highlight helper in enterprise HTML export. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -506,7 +506,7 @@ public final class EntryPointBindingExpander {
|
|||||||
return super.visit(node);
|
return super.visit(node);
|
||||||
}
|
}
|
||||||
Expression argument = (Expression) node.arguments().get(0);
|
Expression argument = (Expression) node.arguments().get(0);
|
||||||
if (!argumentReferencesParameter(argument, paramName)) {
|
if (!argumentReferencesParameter(argument, paramName, method, context)) {
|
||||||
return super.visit(node);
|
return super.visit(node);
|
||||||
}
|
}
|
||||||
if (!isValueOfSiteCompatibleWithBindings(node, partialBindings)) {
|
if (!isValueOfSiteCompatibleWithBindings(node, partialBindings)) {
|
||||||
@@ -551,24 +551,91 @@ public final class EntryPointBindingExpander {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static boolean argumentReferencesParameter(Expression argument, String paramName) {
|
private static boolean argumentReferencesParameter(Expression argument, String paramName) {
|
||||||
|
return argumentReferencesParameter(argument, paramName, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean argumentReferencesParameter(
|
||||||
|
Expression argument,
|
||||||
|
String paramName,
|
||||||
|
MethodDeclaration ownerMethod,
|
||||||
|
CodebaseContext context) {
|
||||||
if (argument instanceof SimpleName simpleName) {
|
if (argument instanceof SimpleName simpleName) {
|
||||||
return paramName.equals(simpleName.getIdentifier());
|
return paramName.equals(simpleName.getIdentifier());
|
||||||
}
|
}
|
||||||
if (argument instanceof MethodInvocation invocation
|
if (argument instanceof MethodInvocation invocation
|
||||||
&& "toUpperCase".equals(invocation.getName().getIdentifier())
|
|
||||||
&& invocation.arguments().isEmpty()
|
&& invocation.arguments().isEmpty()
|
||||||
&& invocation.getExpression() instanceof SimpleName simpleName) {
|
&& invocation.getExpression() instanceof SimpleName simpleName) {
|
||||||
return paramName.equals(simpleName.getIdentifier());
|
String methodName = invocation.getName().getIdentifier();
|
||||||
|
if (("toUpperCase".equals(methodName) || "toLowerCase".equals(methodName) || "trim".equals(methodName))
|
||||||
|
&& paramName.equals(simpleName.getIdentifier())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (argument instanceof MethodInvocation invocation
|
if (argument instanceof MethodInvocation invocation
|
||||||
&& "toLowerCase".equals(invocation.getName().getIdentifier())
|
&& ownerMethod != null
|
||||||
&& invocation.arguments().isEmpty()
|
&& context != null
|
||||||
&& invocation.getExpression() instanceof SimpleName simpleName) {
|
&& isSingleArgHelperForwardingParameter(invocation, paramName, ownerMethod, context)) {
|
||||||
return paramName.equals(simpleName.getIdentifier());
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean isSingleArgHelperForwardingParameter(
|
||||||
|
MethodInvocation helperInvocation,
|
||||||
|
String paramName,
|
||||||
|
MethodDeclaration ownerMethod,
|
||||||
|
CodebaseContext context) {
|
||||||
|
if (helperInvocation.arguments().size() != 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Expression forwarded = (Expression) helperInvocation.arguments().get(0);
|
||||||
|
if (!argumentReferencesParameter(forwarded, paramName, ownerMethod, context)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
MethodDeclaration helper = findHelperMethod(ownerMethod, helperInvocation, context);
|
||||||
|
return helper != null && isTrivialSingleArgStringHelper(helper);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MethodDeclaration findHelperMethod(
|
||||||
|
MethodDeclaration ownerMethod,
|
||||||
|
MethodInvocation helperInvocation,
|
||||||
|
CodebaseContext context) {
|
||||||
|
ASTNode parent = ownerMethod.getParent();
|
||||||
|
if (!(parent instanceof TypeDeclaration typeDeclaration)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String helperName = helperInvocation.getName().getIdentifier();
|
||||||
|
return context.findMethodDeclaration(typeDeclaration, helperName, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isTrivialSingleArgStringHelper(MethodDeclaration helper) {
|
||||||
|
if (helper.parameters().size() != 1 || helper.getBody() == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String helperParam = null;
|
||||||
|
Object paramObj = helper.parameters().get(0);
|
||||||
|
if (paramObj instanceof SingleVariableDeclaration param) {
|
||||||
|
helperParam = param.getName().getIdentifier();
|
||||||
|
}
|
||||||
|
if (helperParam == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Set<String> known = new LinkedHashSet<>();
|
||||||
|
known.add(helperParam);
|
||||||
|
known.addAll(collectIntraMethodParameterAliases(helper, helperParam));
|
||||||
|
final boolean[] returnsKnownBinding = {false};
|
||||||
|
helper.getBody().accept(new ASTVisitor() {
|
||||||
|
@Override
|
||||||
|
public boolean visit(ReturnStatement node) {
|
||||||
|
if (expressionReferencesKnownBinding(node.getExpression(), known)) {
|
||||||
|
returnsKnownBinding[0] = true;
|
||||||
|
}
|
||||||
|
return super.visit(node);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return returnsKnownBinding[0];
|
||||||
|
}
|
||||||
|
|
||||||
private static String resolveValueOfEnumType(MethodInvocation valueOfInvocation, CodebaseContext context) {
|
private static String resolveValueOfEnumType(MethodInvocation valueOfInvocation, CodebaseContext context) {
|
||||||
Expression receiver = valueOfInvocation.getExpression();
|
Expression receiver = valueOfInvocation.getExpression();
|
||||||
if (receiver != null) {
|
if (receiver != null) {
|
||||||
|
|||||||
@@ -532,4 +532,51 @@ class EntryPointBindingExpanderTest {
|
|||||||
assertThat(variants).extracting(map -> map.get("event"))
|
assertThat(variants).extracting(map -> map.get("event"))
|
||||||
.containsExactlyInAnyOrder("PAY", "SHIP");
|
.containsExactlyInAnyOrder("PAY", "SHIP");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExpandEventWhenParameterIsPassedThroughSingleArgHelper(@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(normalize(event));
|
||||||
|
}
|
||||||
|
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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ class EnterpriseDedicatedEndpointHtmlTest {
|
|||||||
assertThat(html).contains("function highlightFlow(flow)");
|
assertThat(html).contains("function highlightFlow(flow)");
|
||||||
assertThat(html).contains("\"source\" : \"click.kamil.enterprise.machines.order.OrderState.NEW\"");
|
assertThat(html).contains("\"source\" : \"click.kamil.enterprise.machines.order.OrderState.NEW\"");
|
||||||
assertThat(html).contains("\"source\" : \"click.kamil.enterprise.machines.order.OrderState.PENDING\"");
|
assertThat(html).contains("\"source\" : \"click.kamil.enterprise.machines.order.OrderState.PENDING\"");
|
||||||
|
assertThat(html).contains("function highlightEvents(steps)");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Path findOrderMachineHtml(Path outputDir) throws Exception {
|
private static Path findOrderMachineHtml(Path outputDir) throws Exception {
|
||||||
|
|||||||
Reference in New Issue
Block a user