Skip to content

Commit c5f9852

Browse files
fix: exclude message-driven and security components from dead code detection
- Add GUARD, MIDDLEWARE, TOPIC, QUEUE, EVENT, MESSAGE_QUEUE to ENTRY_POINT_KINDS so they are never flagged as dead code (they are entry points / cross-cutting concerns) - Remove invalid 'uses' edge kind from SEMANTIC_EDGE_KINDS (not a valid EdgeKind) - Add 'protects' to SEMANTIC_EDGE_KINDS so PROTECTS edges from GuardLinker count as semantic usage when determining reachability - Add two new tests: verifying new entry point kinds are excluded, and verifying 'protects' is included / 'uses' is excluded from semantic edge kinds Co-Authored-By: Paperclip <noreply@paperclip.ing>
1 parent ac97db0 commit c5f9852

2 files changed

Lines changed: 52 additions & 3 deletions

File tree

src/main/java/io/github/randomcodespace/iq/query/QueryService.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -384,9 +384,10 @@ public Map<String, Object> getTopology() {
384384
* they are always present from parent modules/config files.
385385
*/
386386
private static final List<String> SEMANTIC_EDGE_KINDS = List.of(
387-
"calls", "imports", "depends_on", "uses", "extends", "implements",
387+
"calls", "imports", "depends_on", "extends", "implements",
388388
"injects", "queries", "maps_to", "consumes", "listens",
389-
"invokes_rmi", "overrides", "connects_to", "triggers", "renders");
389+
"invokes_rmi", "overrides", "connects_to", "triggers", "renders",
390+
"protects");
390391

391392
/**
392393
* Node kinds that are entry points — they are intended to have no callers
@@ -398,7 +399,13 @@ public Map<String, Object> getTopology() {
398399
NodeKind.MIGRATION.getValue(),
399400
NodeKind.CONFIG_FILE.getValue(),
400401
NodeKind.CONFIG_KEY.getValue(),
401-
NodeKind.CONFIG_DEFINITION.getValue());
402+
NodeKind.CONFIG_DEFINITION.getValue(),
403+
NodeKind.GUARD.getValue(),
404+
NodeKind.MIDDLEWARE.getValue(),
405+
NodeKind.TOPIC.getValue(),
406+
NodeKind.QUEUE.getValue(),
407+
NodeKind.EVENT.getValue(),
408+
NodeKind.MESSAGE_QUEUE.getValue());
402409

403410
@Cacheable(value = "dead-code", key = "#kind + ':' + #limit")
404411
public Map<String, Object> findDeadCode(String kind, int limit) {

src/test/java/io/github/randomcodespace/iq/query/QueryServiceTest.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,48 @@ void findDeadCodeShouldExcludeEntryPointKinds() {
477477
assertTrue(excludeKinds.contains("websocket_endpoint"), "Should exclude websocket endpoints");
478478
assertTrue(excludeKinds.contains("migration"), "Should exclude migrations");
479479
assertTrue(excludeKinds.contains("config_file"), "Should exclude config files");
480+
assertTrue(excludeKinds.contains("guard"), "Should exclude guards");
481+
assertTrue(excludeKinds.contains("middleware"), "Should exclude middleware");
482+
assertTrue(excludeKinds.contains("topic"), "Should exclude topics");
483+
assertTrue(excludeKinds.contains("queue"), "Should exclude queues");
484+
assertTrue(excludeKinds.contains("event"), "Should exclude events");
485+
assertTrue(excludeKinds.contains("message_queue"), "Should exclude message queues");
486+
}
487+
488+
@Test
489+
void findDeadCodeShouldNotFlagMessageDrivenComponents() {
490+
var guard = makeNode("guard:AuthGuard", NodeKind.GUARD, "AuthGuard");
491+
var middleware = makeNode("mid:LoggingMiddleware", NodeKind.MIDDLEWARE, "LoggingMiddleware");
492+
var topic = makeNode("topic:UserEvents", NodeKind.TOPIC, "UserEvents");
493+
var queue = makeNode("queue:EmailQueue", NodeKind.QUEUE, "EmailQueue");
494+
var event = makeNode("event:OrderPlaced", NodeKind.EVENT, "OrderPlaced");
495+
var messageQueue = makeNode("mq:NotificationQueue", NodeKind.MESSAGE_QUEUE, "NotificationQueue");
496+
497+
// These are excluded via ENTRY_POINT_KINDS so graphStore won't return them
498+
when(graphStore.findNodesWithoutIncomingSemantic(anyList(), anyList(), anyList(), eq(0), eq(100)))
499+
.thenReturn(List.of());
500+
501+
Map<String, Object> result = service.findDeadCode(null, 100);
502+
503+
@SuppressWarnings("unchecked")
504+
List<Map<String, Object>> deadCode = (List<Map<String, Object>>) result.get("dead_code");
505+
assertTrue(deadCode.isEmpty(), "Message-driven and security components should not be flagged as dead code");
506+
}
507+
508+
@Test
509+
void findDeadCodeShouldIncludeProtectsInSemanticEdgeKinds() {
510+
when(graphStore.findNodesWithoutIncomingSemantic(anyList(), anyList(), anyList(), eq(0), eq(50)))
511+
.thenReturn(List.of());
512+
513+
service.findDeadCode(null, 50);
514+
515+
@SuppressWarnings("unchecked")
516+
var captor = org.mockito.ArgumentCaptor.forClass(List.class);
517+
verify(graphStore).findNodesWithoutIncomingSemantic(anyList(), captor.capture(), anyList(), eq(0), eq(50));
518+
@SuppressWarnings("unchecked")
519+
List<String> semanticKinds = captor.getValue();
520+
assertTrue(semanticKinds.contains("protects"), "Should include 'protects' as semantic edge");
521+
assertFalse(semanticKinds.contains("uses"), "Should NOT include 'uses' — not a valid EdgeKind");
480522
}
481523

482524
@Test

0 commit comments

Comments
 (0)