better enricher attempt

This commit is contained in:
2026-06-19 21:21:44 +02:00
parent 7077214c81
commit bf9208d529
4 changed files with 115 additions and 7 deletions

View File

@@ -51,10 +51,10 @@ public class TransitionLinkerEnricher implements AnalysisEnricher {
if (targetVar == null && chain.getTriggerPoint() != null) {
targetVar = chain.getTriggerPoint().getStateMachineId();
}
if (targetVar == null || targetVar.isEmpty()) {
// Wildcard without specific routing context is too broad, do not link
continue;
}
// We no longer hard-block wildcards without a specific routing context.
// If a project doesn't use standard SM persisters (e.g. restores state manually),
// contextMachineId will be null. We should still link the wildcard to provide SOME visibility,
// rather than completely hiding the endpoint.
}
if (isWildcard || smEvent.equals(triggerEvent) || triggerEvent.toLowerCase().contains(smEvent.toLowerCase()) || smEvent.toLowerCase().contains(triggerEvent.toLowerCase())) {

View File

@@ -84,6 +84,15 @@ public class CallGraphBuilder {
// Find parameter index in target method
int paramIndex = getParameterIndex(target, currentParamName);
if (paramIndex < 0) {
// Not a parameter. Maybe it's a local variable initialized from a parameter or method call?
String tracedVar = traceLocalVariable(target, currentParamName);
if (tracedVar != null && !tracedVar.equals(currentParamName)) {
currentParamName = tracedVar;
// We must try to find paramIndex again, because the traced variable might be a parameter of this same method
paramIndex = getParameterIndex(target, currentParamName);
}
}
if (paramIndex < 0) {
break; // Parameter name changed or not found, stop tracing
}
@@ -141,6 +150,51 @@ public class CallGraphBuilder {
return -1;
}
private String traceLocalVariable(String methodFqn, String varName) {
if (methodFqn == null || !methodFqn.contains(".")) return null;
String className = methodFqn.substring(0, methodFqn.lastIndexOf('.'));
String methodName = methodFqn.substring(methodFqn.lastIndexOf('.') + 1);
TypeDeclaration td = context.getTypeDeclaration(className);
if (td != null) {
MethodDeclaration md = context.findMethodDeclaration(td, methodName, true);
if (md != null && md.getBody() != null) {
final Expression[] initializer = new Expression[1];
md.getBody().accept(new ASTVisitor() {
@Override
public boolean visit(VariableDeclarationFragment node) {
if (node.getName().getIdentifier().equals(varName) && node.getInitializer() != null) {
initializer[0] = node.getInitializer();
}
return super.visit(node);
}
@Override
public boolean visit(Assignment node) {
if (node.getLeftHandSide() instanceof SimpleName asn && asn.getIdentifier().equals(varName)) {
initializer[0] = node.getRightHandSide();
}
return super.visit(node);
}
});
if (initializer[0] != null) {
Expression expr = traceVariable(initializer[0]);
if (expr instanceof MethodInvocation mi) {
// e.g. event.getType() -> return "event.getType()" or just "event"
if (mi.getExpression() instanceof SimpleName sn) {
return sn.getIdentifier() + "." + mi.getName().getIdentifier() + "()";
}
return mi.getName().getIdentifier() + "()";
}
if (expr instanceof SimpleName sn) {
return sn.getIdentifier();
}
return expr.toString();
}
}
}
return null;
}
private String extractContextMachineId(List<String> path, Map<String, List<CallEdge>> callGraph) {
for (String node : path) {
List<CallEdge> edges = callGraph.get(node);

View File

@@ -168,7 +168,7 @@ class TransitionLinkerEnricherTest {
}
@Test
void shouldIgnoreWildcardWhenContextMachineIdIsMissing() {
void shouldAllowWildcardWhenContextMachineIdIsMissing() {
Transition t1 = new Transition();
t1.setSourceStates(List.of(State.of("NEW", "NEW")));
t1.setTargetStates(List.of(State.of("PAID", "PAID")));
@@ -176,7 +176,7 @@ class TransitionLinkerEnricherTest {
CallChain chain = CallChain.builder()
.triggerPoint(TriggerPoint.builder().event("event").build())
// Intentionally NO contextMachineId or stateMachineId
// NO contextMachineId or stateMachineId
.build();
AnalysisResult result = AnalysisResult.builder()
@@ -188,6 +188,6 @@ class TransitionLinkerEnricherTest {
enricher.enrich(result, null, null);
CallChain updatedChain = result.getMetadata().getCallChains().get(0);
assertThat(updatedChain.getMatchedTransitions()).isNullOrEmpty();
assertThat(updatedChain.getMatchedTransitions()).hasSize(1);
}
}

View File

@@ -165,4 +165,58 @@ class CallGraphBuilderTest {
CallChain chain = chains.get(0);
assertThat(chain.getTriggerPoint().getEvent()).isEqualTo("OrderEvents.CREATE");
}
@Test
void shouldResolveTriggerPointLocalVariableAcrossChain() throws IOException {
String source = """
package com.example;
public class OrderController {
private OrderService service;
public void processOrderEvent(MyOrderEvent domainEvent) {
doProcessOrderEvent(domainEvent);
}
private void doProcessOrderEvent(MyOrderEvent domainEvent) {
OrderEvents eventType = domainEvent.getType();
service.updateOrderState(eventType);
}
}
class OrderService {
public void updateOrderState(OrderEvents event) {
// sendEvent
}
}
class MyOrderEvent {
public OrderEvents getType() { return OrderEvents.PAY; }
}
enum OrderEvents { CREATE, PAY }
""";
Path tempDir = Files.createTempDirectory("callgraph_test2");
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
CodebaseContext context = new CodebaseContext();
context.scan(tempDir);
CallGraphBuilder builder = new CallGraphBuilder(context);
EntryPoint entryPoint = EntryPoint.builder()
.className("com.example.OrderController")
.methodName("processOrderEvent")
.build();
TriggerPoint trigger = TriggerPoint.builder()
.className("com.example.OrderService")
.methodName("updateOrderState")
.event("event")
.build();
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
assertThat(chains).hasSize(1);
CallChain chain = chains.get(0);
assertThat(chain.getTriggerPoint().getEvent()).isEqualTo("domainEvent.getType()");
}
}