From bf286973e7241150dc63633ff3649a106832344d Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Sat, 4 Apr 2026 14:47:27 +0000 Subject: [PATCH 1/3] checkpoint: pre-yolo 20260404-144727 From 357567287452a844e078e60cfe009b08d9a23a9a Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Sat, 4 Apr 2026 14:50:54 +0000 Subject: [PATCH 2/3] checkpoint: pre-yolo 20260404-145054 From 20678421a0eb54e5dd470108a65b8e3214fd16c7 Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Sat, 4 Apr 2026 15:10:33 +0000 Subject: [PATCH 3/3] refactor: eliminate code duplication across detectors and CLI commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Extract DetectorDbHelper: shared ensureDbNode/addDbEdge utility used by TypeORM, Prisma, JPA, Repository detectors + AbstractPythonDbDetector delegates to it — removes ~150 lines of copy-paste across 5 files - Extract AbstractStructuredDetector.buildFileNode/addKeyNode: shared CONFIG_FILE node + CONTAINS edge boilerplate now used by JSON, YAML, TOML, IniStructureDetectors — removes ~50 lines of copy-paste - Extract CliOutput.printAnalysisStats/printBreakdowns: shared result output code now used by both AnalyzeCommand and IndexCommand — removes ~60 lines of copy-paste Co-Authored-By: Claude Sonnet 4.6 --- .../iq/cli/AnalyzeCommand.java | 30 +------- .../randomcodespace/iq/cli/CliOutput.java | 47 ++++++++++++ .../randomcodespace/iq/cli/IndexCommand.java | 31 +------- .../detector/AbstractStructuredDetector.java | 42 +++++++++++ .../iq/detector/DetectorDbHelper.java | 72 +++++++++++++++++++ .../detector/config/IniStructureDetector.java | 8 +-- .../config/JsonStructureDetector.java | 23 +----- .../config/TomlStructureDetector.java | 9 +-- .../config/YamlStructureDetector.java | 23 +----- .../iq/detector/java/JpaEntityDetector.java | 46 +----------- .../iq/detector/java/RepositoryDetector.java | 44 +----------- .../python/AbstractPythonDbDetector.java | 35 +-------- .../typescript/PrismaORMDetector.java | 44 +----------- .../typescript/TypeORMEntityDetector.java | 44 +----------- 14 files changed, 184 insertions(+), 314 deletions(-) create mode 100644 src/main/java/io/github/randomcodespace/iq/detector/DetectorDbHelper.java diff --git a/src/main/java/io/github/randomcodespace/iq/cli/AnalyzeCommand.java b/src/main/java/io/github/randomcodespace/iq/cli/AnalyzeCommand.java index 916b5f90..8de8322d 100644 --- a/src/main/java/io/github/randomcodespace/iq/cli/AnalyzeCommand.java +++ b/src/main/java/io/github/randomcodespace/iq/cli/AnalyzeCommand.java @@ -118,34 +118,8 @@ public Integer call() { + nf.format(result.nodeCount()) + " nodes, " + nf.format(result.edgeCount()) + " edges in " + timeStr); System.out.println(); - CliOutput.info(" Files: " + nf.format(result.totalFiles()) + " discovered, " - + nf.format(result.filesAnalyzed()) + " analyzed"); - CliOutput.cyan(" Nodes: " + nf.format(result.nodeCount())); - CliOutput.cyan(" Edges: " + nf.format(result.edgeCount())); - CliOutput.info(" Time: " + timeStr); - - if (!result.nodeBreakdown().isEmpty()) { - System.out.println(); - StringBuilder topNodes = new StringBuilder(" Top node kinds: "); - result.nodeBreakdown().entrySet().stream() - .sorted(Map.Entry.comparingByValue().reversed()) - .limit(10) - .forEach(e -> topNodes.append(e.getKey()).append(" (") - .append(nf.format(e.getValue())).append("), ")); - if (topNodes.length() > 2) topNodes.setLength(topNodes.length() - 2); - CliOutput.info(topNodes.toString()); - } - - if (!result.languageBreakdown().isEmpty()) { - StringBuilder langs = new StringBuilder(" Languages: "); - result.languageBreakdown().entrySet().stream() - .sorted(Map.Entry.comparingByValue().reversed()) - .limit(10) - .forEach(e -> langs.append(e.getKey()).append(" (") - .append(nf.format(e.getValue())).append("), ")); - if (langs.length() > 2) langs.setLength(langs.length() - 2); - CliOutput.info(langs.toString()); - } + CliOutput.printAnalysisStats(result, nf); + CliOutput.printBreakdowns(result, nf); if (result.frameworkBreakdown() != null && !result.frameworkBreakdown().isEmpty()) { StringBuilder fws = new StringBuilder(" Frameworks: "); diff --git a/src/main/java/io/github/randomcodespace/iq/cli/CliOutput.java b/src/main/java/io/github/randomcodespace/iq/cli/CliOutput.java index cb44f440..82a94972 100644 --- a/src/main/java/io/github/randomcodespace/iq/cli/CliOutput.java +++ b/src/main/java/io/github/randomcodespace/iq/cli/CliOutput.java @@ -1,8 +1,11 @@ package io.github.randomcodespace.iq.cli; +import io.github.randomcodespace.iq.analyzer.AnalysisResult; import picocli.CommandLine; import java.io.PrintStream; +import java.text.NumberFormat; +import java.util.Map; /** * Utility class for rich ANSI-colored CLI output. @@ -76,6 +79,50 @@ static String format(String ansiFormatted) { return ANSI.string(ansiFormatted); } + /** + * Print files/nodes/edges/time summary lines shared by analyze and index commands. + * Callers are responsible for printing the preceding success banner and any + * command-specific extra lines (e.g. "Store: H2…"). + */ + static void printAnalysisStats(AnalysisResult result, NumberFormat nf) { + long secs = result.elapsed().toSeconds(); + String timeStr = secs > 0 ? secs + "s" : result.elapsed().toMillis() + "ms"; + info(" Files: " + nf.format(result.totalFiles()) + " discovered, " + + nf.format(result.filesAnalyzed()) + " analyzed"); + cyan(" Nodes: " + nf.format(result.nodeCount())); + cyan(" Edges: " + nf.format(result.edgeCount())); + info(" Time: " + timeStr); + } + + /** + * Print node-kind and language breakdown lines shared by analyze and index commands. + * Prints a blank line before the node breakdown when it is non-empty. + */ + static void printBreakdowns(AnalysisResult result, NumberFormat nf) { + if (!result.nodeBreakdown().isEmpty()) { + System.out.println(); + StringBuilder topNodes = new StringBuilder(" Top node kinds: "); + result.nodeBreakdown().entrySet().stream() + .sorted(Map.Entry.comparingByValue().reversed()) + .limit(10) + .forEach(e -> topNodes.append(e.getKey()).append(" (") + .append(nf.format(e.getValue())).append("), ")); + if (topNodes.length() > 2) topNodes.setLength(topNodes.length() - 2); + info(topNodes.toString()); + } + + if (!result.languageBreakdown().isEmpty()) { + StringBuilder langs = new StringBuilder(" Languages: "); + result.languageBreakdown().entrySet().stream() + .sorted(Map.Entry.comparingByValue().reversed()) + .limit(10) + .forEach(e -> langs.append(e.getKey()).append(" (") + .append(nf.format(e.getValue())).append("), ")); + if (langs.length() > 2) langs.setLength(langs.length() - 2); + info(langs.toString()); + } + } + /** * Escape pipe characters that would break picocli ANSI markup. */ diff --git a/src/main/java/io/github/randomcodespace/iq/cli/IndexCommand.java b/src/main/java/io/github/randomcodespace/iq/cli/IndexCommand.java index ccf94ef1..3ab41c8d 100644 --- a/src/main/java/io/github/randomcodespace/iq/cli/IndexCommand.java +++ b/src/main/java/io/github/randomcodespace/iq/cli/IndexCommand.java @@ -12,7 +12,6 @@ import java.nio.file.Path; import java.text.NumberFormat; import java.util.Locale; -import java.util.Map; import java.util.concurrent.Callable; /** @@ -127,35 +126,9 @@ public Integer call() { + nf.format(result.nodeCount()) + " nodes, " + nf.format(result.edgeCount()) + " edges in " + timeStr); System.out.println(); - CliOutput.info(" Files: " + nf.format(result.totalFiles()) + " discovered, " - + nf.format(result.filesAnalyzed()) + " analyzed"); - CliOutput.cyan(" Nodes: " + nf.format(result.nodeCount())); - CliOutput.cyan(" Edges: " + nf.format(result.edgeCount())); - CliOutput.info(" Time: " + timeStr); + CliOutput.printAnalysisStats(result, nf); CliOutput.info(" Store: H2 (.code-intelligence/analysis-cache)"); - - if (!result.nodeBreakdown().isEmpty()) { - System.out.println(); - StringBuilder topNodes = new StringBuilder(" Top node kinds: "); - result.nodeBreakdown().entrySet().stream() - .sorted(Map.Entry.comparingByValue().reversed()) - .limit(10) - .forEach(e -> topNodes.append(e.getKey()).append(" (") - .append(nf.format(e.getValue())).append("), ")); - if (topNodes.length() > 2) topNodes.setLength(topNodes.length() - 2); - CliOutput.info(topNodes.toString()); - } - - if (!result.languageBreakdown().isEmpty()) { - StringBuilder langs = new StringBuilder(" Languages: "); - result.languageBreakdown().entrySet().stream() - .sorted(Map.Entry.comparingByValue().reversed()) - .limit(10) - .forEach(e -> langs.append(e.getKey()).append(" (") - .append(nf.format(e.getValue())).append("), ")); - if (langs.length() > 2) langs.setLength(langs.length() - 2); - CliOutput.info(langs.toString()); - } + CliOutput.printBreakdowns(result, nf); System.out.println(); CliOutput.info(" Next step: code-iq enrich " + root); diff --git a/src/main/java/io/github/randomcodespace/iq/detector/AbstractStructuredDetector.java b/src/main/java/io/github/randomcodespace/iq/detector/AbstractStructuredDetector.java index 09f589ea..58e8592d 100644 --- a/src/main/java/io/github/randomcodespace/iq/detector/AbstractStructuredDetector.java +++ b/src/main/java/io/github/randomcodespace/iq/detector/AbstractStructuredDetector.java @@ -1,6 +1,12 @@ package io.github.randomcodespace.iq.detector; +import io.github.randomcodespace.iq.model.CodeEdge; +import io.github.randomcodespace.iq.model.CodeNode; +import io.github.randomcodespace.iq.model.EdgeKind; +import io.github.randomcodespace.iq.model.NodeKind; + import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -87,4 +93,40 @@ protected int getInt(Object container, String key, int defaultValue) { } return defaultValue; } + + /** + * Build a CONFIG_FILE node for the given context and format. + * The returned node is NOT added to any list — callers must do that themselves. + */ + protected CodeNode buildFileNode(DetectorContext ctx, String format) { + String fp = ctx.filePath(); + String fileId = format + ":" + fp; + CodeNode fileNode = new CodeNode(fileId, NodeKind.CONFIG_FILE, fp); + fileNode.setFqn(fp); + fileNode.setModule(ctx.moduleName()); + fileNode.setFilePath(fp); + fileNode.setLineStart(1); + fileNode.setProperties(new HashMap<>(Map.of("format", format))); + return fileNode; + } + + /** + * Build a CONFIG_KEY node and a CONTAINS edge from {@code fileId} to it, + * appending both to the supplied lists. + */ + protected void addKeyNode(String fileId, String fp, String key, String format, + DetectorContext ctx, List nodes, List edges) { + String keyId = format + ":" + fp + ":" + key; + CodeNode keyNode = new CodeNode(keyId, NodeKind.CONFIG_KEY, key); + keyNode.setFqn(fp + ":" + key); + keyNode.setModule(ctx.moduleName()); + keyNode.setFilePath(fp); + nodes.add(keyNode); + CodeEdge edge = new CodeEdge(); + edge.setId(fileId + "->" + keyId); + edge.setKind(EdgeKind.CONTAINS); + edge.setSourceId(fileId); + edge.setTarget(new CodeNode(keyId, null, null)); + edges.add(edge); + } } diff --git a/src/main/java/io/github/randomcodespace/iq/detector/DetectorDbHelper.java b/src/main/java/io/github/randomcodespace/iq/detector/DetectorDbHelper.java new file mode 100644 index 00000000..f4f625ae --- /dev/null +++ b/src/main/java/io/github/randomcodespace/iq/detector/DetectorDbHelper.java @@ -0,0 +1,72 @@ +package io.github.randomcodespace.iq.detector; + +import io.github.randomcodespace.iq.analyzer.InfraEndpoint; +import io.github.randomcodespace.iq.analyzer.InfrastructureRegistry; +import io.github.randomcodespace.iq.model.CodeEdge; +import io.github.randomcodespace.iq.model.CodeNode; +import io.github.randomcodespace.iq.model.EdgeKind; +import io.github.randomcodespace.iq.model.NodeKind; + +import java.util.List; + +/** + * Shared helper for detectors that emit DATABASE_CONNECTION nodes and CONNECTS_TO edges. + * Used by Python, TypeScript, and Java ORM/database detectors. + */ +public final class DetectorDbHelper { + + private DetectorDbHelper() {} + + /** + * Ensure a DATABASE_CONNECTION node exists in the result, creating it if needed. + * Uses the first database from the InfrastructureRegistry if available, + * otherwise creates a generic "database:unknown" node. + * + * @param registry infrastructure registry (may be null) + * @param nodes the nodes list to add the DB node to if missing + * @return the database node ID + */ + public static String ensureDbNode(InfrastructureRegistry registry, List nodes) { + String dbNodeId; + if (registry != null && !registry.getDatabases().isEmpty()) { + InfraEndpoint db = registry.getDatabases().values().iterator().next(); + dbNodeId = "infra:" + db.id(); + if (nodes.stream().noneMatch(n -> dbNodeId.equals(n.getId()))) { + CodeNode dbNode = new CodeNode(dbNodeId, NodeKind.DATABASE_CONNECTION, + db.name() + " (" + db.type() + ")"); + dbNode.getProperties().put("type", db.type()); + if (db.connectionUrl() != null) dbNode.getProperties().put("url", db.connectionUrl()); + nodes.add(dbNode); + } + } else { + dbNodeId = "database:unknown"; + if (nodes.stream().noneMatch(n -> dbNodeId.equals(n.getId()))) { + nodes.add(new CodeNode(dbNodeId, NodeKind.DATABASE_CONNECTION, "Database")); + } + } + return dbNodeId; + } + + /** + * Add a CONNECTS_TO edge from the given source node to the database node. + * + * @param sourceId the source node ID + * @param registry infrastructure registry (may be null) + * @param nodes the nodes list (used to find/create the DB node) + * @param edges the edges list to add the edge to + */ + public static void addDbEdge(String sourceId, InfrastructureRegistry registry, + List nodes, List edges) { + String dbNodeId = ensureDbNode(registry, nodes); + CodeNode targetRef = nodes.stream() + .filter(n -> dbNodeId.equals(n.getId())) + .findFirst() + .orElseGet(() -> new CodeNode(dbNodeId, NodeKind.DATABASE_CONNECTION, "Database")); + CodeEdge edge = new CodeEdge(); + edge.setId(sourceId + "->connects_to->" + dbNodeId); + edge.setKind(EdgeKind.CONNECTS_TO); + edge.setSourceId(sourceId); + edge.setTarget(targetRef); + edges.add(edge); + } +} diff --git a/src/main/java/io/github/randomcodespace/iq/detector/config/IniStructureDetector.java b/src/main/java/io/github/randomcodespace/iq/detector/config/IniStructureDetector.java index 98016b83..82212c40 100644 --- a/src/main/java/io/github/randomcodespace/iq/detector/config/IniStructureDetector.java +++ b/src/main/java/io/github/randomcodespace/iq/detector/config/IniStructureDetector.java @@ -52,13 +52,7 @@ public DetectorResult detect(DetectorContext ctx) { List edges = new ArrayList<>(); // CONFIG_FILE node for the file itself - CodeNode fileNode = new CodeNode(fileId, NodeKind.CONFIG_FILE, fp); - fileNode.setFqn(fp); - fileNode.setModule(ctx.moduleName()); - fileNode.setFilePath(fp); - fileNode.setLineStart(1); - fileNode.setProperties(Map.of("format", "ini")); - nodes.add(fileNode); + nodes.add(buildFileNode(ctx, "ini")); Object parsedData = ctx.parsedData(); if (parsedData == null) { diff --git a/src/main/java/io/github/randomcodespace/iq/detector/config/JsonStructureDetector.java b/src/main/java/io/github/randomcodespace/iq/detector/config/JsonStructureDetector.java index 6838bdfa..60d9f979 100644 --- a/src/main/java/io/github/randomcodespace/iq/detector/config/JsonStructureDetector.java +++ b/src/main/java/io/github/randomcodespace/iq/detector/config/JsonStructureDetector.java @@ -49,13 +49,7 @@ public DetectorResult detect(DetectorContext ctx) { List edges = new ArrayList<>(); // CONFIG_FILE node for the file itself - CodeNode fileNode = new CodeNode(fileId, NodeKind.CONFIG_FILE, fp); - fileNode.setFqn(fp); - fileNode.setModule(ctx.moduleName()); - fileNode.setFilePath(fp); - fileNode.setLineStart(1); - fileNode.setProperties(Map.of("format", "json")); - nodes.add(fileNode); + nodes.add(buildFileNode(ctx, "json")); // Extract data from parsed_data Object parsedData = ctx.parsedData(); @@ -71,20 +65,7 @@ public DetectorResult detect(DetectorContext ctx) { } for (String key : data.keySet()) { - String keyId = "json:" + fp + ":" + key; - - CodeNode keyNode = new CodeNode(keyId, NodeKind.CONFIG_KEY, key); - keyNode.setFqn(fp + ":" + key); - keyNode.setModule(ctx.moduleName()); - keyNode.setFilePath(fp); - nodes.add(keyNode); - - CodeEdge edge = new CodeEdge(); - edge.setId(fileId + "->" + keyId); - edge.setKind(EdgeKind.CONTAINS); - edge.setSourceId(fileId); - edge.setTarget(new CodeNode(keyId, null, null)); - edges.add(edge); + addKeyNode(fileId, fp, key, "json", ctx, nodes, edges); } return DetectorResult.of(nodes, edges); diff --git a/src/main/java/io/github/randomcodespace/iq/detector/config/TomlStructureDetector.java b/src/main/java/io/github/randomcodespace/iq/detector/config/TomlStructureDetector.java index 0a95cbf9..fed52902 100644 --- a/src/main/java/io/github/randomcodespace/iq/detector/config/TomlStructureDetector.java +++ b/src/main/java/io/github/randomcodespace/iq/detector/config/TomlStructureDetector.java @@ -14,6 +14,7 @@ import java.util.List; import java.util.Map; import java.util.Set; + import io.github.randomcodespace.iq.detector.DetectorInfo; import io.github.randomcodespace.iq.detector.ParserType; @@ -52,13 +53,7 @@ public DetectorResult detect(DetectorContext ctx) { List edges = new ArrayList<>(); // CONFIG_FILE node for the file itself - CodeNode fileNode = new CodeNode(fileId, NodeKind.CONFIG_FILE, fp); - fileNode.setFqn(fp); - fileNode.setModule(ctx.moduleName()); - fileNode.setFilePath(fp); - fileNode.setLineStart(1); - fileNode.setProperties(Map.of("format", "toml")); - nodes.add(fileNode); + nodes.add(buildFileNode(ctx, "toml")); Object parsedData = ctx.parsedData(); if (parsedData == null) { diff --git a/src/main/java/io/github/randomcodespace/iq/detector/config/YamlStructureDetector.java b/src/main/java/io/github/randomcodespace/iq/detector/config/YamlStructureDetector.java index 3deec1a8..4b529872 100644 --- a/src/main/java/io/github/randomcodespace/iq/detector/config/YamlStructureDetector.java +++ b/src/main/java/io/github/randomcodespace/iq/detector/config/YamlStructureDetector.java @@ -50,13 +50,7 @@ public DetectorResult detect(DetectorContext ctx) { List edges = new ArrayList<>(); // CONFIG_FILE node for the file itself - CodeNode fileNode = new CodeNode(fileId, NodeKind.CONFIG_FILE, fp); - fileNode.setFqn(fp); - fileNode.setModule(ctx.moduleName()); - fileNode.setFilePath(fp); - fileNode.setLineStart(1); - fileNode.setProperties(Map.of("format", "yaml")); - nodes.add(fileNode); + nodes.add(buildFileNode(ctx, "yaml")); Object parsedData = ctx.parsedData(); if (parsedData == null) { @@ -84,20 +78,7 @@ public DetectorResult detect(DetectorContext ctx) { } for (String keyStr : topLevelKeys) { - String keyId = "yaml:" + fp + ":" + keyStr; - - CodeNode keyNode = new CodeNode(keyId, NodeKind.CONFIG_KEY, keyStr); - keyNode.setFqn(fp + ":" + keyStr); - keyNode.setModule(ctx.moduleName()); - keyNode.setFilePath(fp); - nodes.add(keyNode); - - CodeEdge edge = new CodeEdge(); - edge.setId(fileId + "->" + keyId); - edge.setKind(EdgeKind.CONTAINS); - edge.setSourceId(fileId); - edge.setTarget(new CodeNode(keyId, null, null)); - edges.add(edge); + addKeyNode(fileId, fp, keyStr, "yaml", ctx, nodes, edges); } return DetectorResult.of(nodes, edges); diff --git a/src/main/java/io/github/randomcodespace/iq/detector/java/JpaEntityDetector.java b/src/main/java/io/github/randomcodespace/iq/detector/java/JpaEntityDetector.java index 00643190..7734c802 100644 --- a/src/main/java/io/github/randomcodespace/iq/detector/java/JpaEntityDetector.java +++ b/src/main/java/io/github/randomcodespace/iq/detector/java/JpaEntityDetector.java @@ -9,6 +9,7 @@ import com.github.javaparser.ast.type.ClassOrInterfaceType; import com.github.javaparser.ast.type.Type; import io.github.randomcodespace.iq.detector.DetectorContext; +import io.github.randomcodespace.iq.detector.DetectorDbHelper; import io.github.randomcodespace.iq.detector.DetectorResult; import io.github.randomcodespace.iq.model.CodeEdge; import io.github.randomcodespace.iq.model.CodeNode; @@ -145,7 +146,7 @@ private DetectorResult detectWithAst(CompilationUnit cu, DetectorContext ctx) { node.setAnnotations(new ArrayList<>(List.of("@Entity"))); node.setProperties(properties); nodes.add(node); - addDbEdge(entityId, ctx.registry(), nodes, edges); + DetectorDbHelper.addDbEdge(entityId, ctx.registry(), nodes, edges); // Extract relationship edges from fields for (FieldDeclaration field : classDecl.getFields()) { @@ -278,7 +279,7 @@ private DetectorResult detectWithRegex(DetectorContext ctx) { node.setAnnotations(new ArrayList<>(List.of("@Entity"))); node.setProperties(properties); nodes.add(node); - addDbEdge(entityId, ctx.registry(), nodes, edges); + DetectorDbHelper.addDbEdge(entityId, ctx.registry(), nodes, edges); for (int i = 0; i < lines.length; i++) { Matcher relMatch = RELATIONSHIP_REGEX.matcher(lines[i]); @@ -325,45 +326,4 @@ private DetectorResult detectWithRegex(DetectorContext ctx) { return DetectorResult.of(nodes, edges); } - // ==================== InfrastructureRegistry helpers ==================== - - private static String ensureDbNode( - io.github.randomcodespace.iq.analyzer.InfrastructureRegistry registry, - List nodes) { - String dbNodeId; - if (registry != null && !registry.getDatabases().isEmpty()) { - io.github.randomcodespace.iq.analyzer.InfraEndpoint db = - registry.getDatabases().values().iterator().next(); - dbNodeId = "infra:" + db.id(); - if (nodes.stream().noneMatch(n -> dbNodeId.equals(n.getId()))) { - CodeNode dbNode = new CodeNode(dbNodeId, NodeKind.DATABASE_CONNECTION, - db.name() + " (" + db.type() + ")"); - dbNode.getProperties().put("type", db.type()); - if (db.connectionUrl() != null) dbNode.getProperties().put("url", db.connectionUrl()); - nodes.add(dbNode); - } - } else { - dbNodeId = "database:unknown"; - if (nodes.stream().noneMatch(n -> dbNodeId.equals(n.getId()))) { - nodes.add(new CodeNode(dbNodeId, NodeKind.DATABASE_CONNECTION, "Database")); - } - } - return dbNodeId; - } - - private static void addDbEdge(String sourceId, - io.github.randomcodespace.iq.analyzer.InfrastructureRegistry registry, - List nodes, List edges) { - String dbNodeId = ensureDbNode(registry, nodes); - CodeNode targetRef = nodes.stream() - .filter(n -> dbNodeId.equals(n.getId())) - .findFirst() - .orElseGet(() -> new CodeNode(dbNodeId, NodeKind.DATABASE_CONNECTION, "Database")); - CodeEdge edge = new CodeEdge(); - edge.setId(sourceId + "->connects_to->" + dbNodeId); - edge.setKind(EdgeKind.CONNECTS_TO); - edge.setSourceId(sourceId); - edge.setTarget(targetRef); - edges.add(edge); - } } diff --git a/src/main/java/io/github/randomcodespace/iq/detector/java/RepositoryDetector.java b/src/main/java/io/github/randomcodespace/iq/detector/java/RepositoryDetector.java index 0577e3a1..dd2728dd 100644 --- a/src/main/java/io/github/randomcodespace/iq/detector/java/RepositoryDetector.java +++ b/src/main/java/io/github/randomcodespace/iq/detector/java/RepositoryDetector.java @@ -2,6 +2,7 @@ import io.github.randomcodespace.iq.detector.AbstractRegexDetector; import io.github.randomcodespace.iq.detector.DetectorContext; +import io.github.randomcodespace.iq.detector.DetectorDbHelper; import io.github.randomcodespace.iq.detector.DetectorResult; import io.github.randomcodespace.iq.model.CodeEdge; import io.github.randomcodespace.iq.model.CodeNode; @@ -156,50 +157,9 @@ public DetectorResult detect(DetectorContext ctx) { edge.setTarget(targetRef); edges.add(edge); } - addDbEdge(repoId, ctx.registry(), nodes, edges); + DetectorDbHelper.addDbEdge(repoId, ctx.registry(), nodes, edges); return DetectorResult.of(nodes, edges); } - // ==================== InfrastructureRegistry helpers ==================== - - private static String ensureDbNode( - io.github.randomcodespace.iq.analyzer.InfrastructureRegistry registry, - List nodes) { - String dbNodeId; - if (registry != null && !registry.getDatabases().isEmpty()) { - io.github.randomcodespace.iq.analyzer.InfraEndpoint db = - registry.getDatabases().values().iterator().next(); - dbNodeId = "infra:" + db.id(); - if (nodes.stream().noneMatch(n -> dbNodeId.equals(n.getId()))) { - CodeNode dbNode = new CodeNode(dbNodeId, NodeKind.DATABASE_CONNECTION, - db.name() + " (" + db.type() + ")"); - dbNode.getProperties().put("type", db.type()); - if (db.connectionUrl() != null) dbNode.getProperties().put("url", db.connectionUrl()); - nodes.add(dbNode); - } - } else { - dbNodeId = "database:unknown"; - if (nodes.stream().noneMatch(n -> dbNodeId.equals(n.getId()))) { - nodes.add(new CodeNode(dbNodeId, NodeKind.DATABASE_CONNECTION, "Database")); - } - } - return dbNodeId; - } - - private static void addDbEdge(String sourceId, - io.github.randomcodespace.iq.analyzer.InfrastructureRegistry registry, - List nodes, List edges) { - String dbNodeId = ensureDbNode(registry, nodes); - CodeNode targetRef = nodes.stream() - .filter(n -> dbNodeId.equals(n.getId())) - .findFirst() - .orElseGet(() -> new CodeNode(dbNodeId, NodeKind.DATABASE_CONNECTION, "Database")); - CodeEdge edge = new CodeEdge(); - edge.setId(sourceId + "->connects_to->" + dbNodeId); - edge.setKind(EdgeKind.CONNECTS_TO); - edge.setSourceId(sourceId); - edge.setTarget(targetRef); - edges.add(edge); - } } diff --git a/src/main/java/io/github/randomcodespace/iq/detector/python/AbstractPythonDbDetector.java b/src/main/java/io/github/randomcodespace/iq/detector/python/AbstractPythonDbDetector.java index e99e6555..857c530c 100644 --- a/src/main/java/io/github/randomcodespace/iq/detector/python/AbstractPythonDbDetector.java +++ b/src/main/java/io/github/randomcodespace/iq/detector/python/AbstractPythonDbDetector.java @@ -1,11 +1,9 @@ package io.github.randomcodespace.iq.detector.python; -import io.github.randomcodespace.iq.analyzer.InfraEndpoint; import io.github.randomcodespace.iq.analyzer.InfrastructureRegistry; +import io.github.randomcodespace.iq.detector.DetectorDbHelper; import io.github.randomcodespace.iq.model.CodeEdge; import io.github.randomcodespace.iq.model.CodeNode; -import io.github.randomcodespace.iq.model.EdgeKind; -import io.github.randomcodespace.iq.model.NodeKind; import java.util.List; @@ -26,24 +24,7 @@ public abstract class AbstractPythonDbDetector extends AbstractPythonAntlrDetect * @return the database node ID */ protected static String ensureDbNode(InfrastructureRegistry registry, List nodes) { - String dbNodeId; - if (registry != null && !registry.getDatabases().isEmpty()) { - InfraEndpoint db = registry.getDatabases().values().iterator().next(); - dbNodeId = "infra:" + db.id(); - if (nodes.stream().noneMatch(n -> dbNodeId.equals(n.getId()))) { - CodeNode dbNode = new CodeNode(dbNodeId, NodeKind.DATABASE_CONNECTION, - db.name() + " (" + db.type() + ")"); - dbNode.getProperties().put("type", db.type()); - if (db.connectionUrl() != null) dbNode.getProperties().put("url", db.connectionUrl()); - nodes.add(dbNode); - } - } else { - dbNodeId = "database:unknown"; - if (nodes.stream().noneMatch(n -> dbNodeId.equals(n.getId()))) { - nodes.add(new CodeNode(dbNodeId, NodeKind.DATABASE_CONNECTION, "Database")); - } - } - return dbNodeId; + return DetectorDbHelper.ensureDbNode(registry, nodes); } /** @@ -56,16 +37,6 @@ protected static String ensureDbNode(InfrastructureRegistry registry, List nodes, List edges) { - String dbNodeId = ensureDbNode(registry, nodes); - CodeNode targetRef = nodes.stream() - .filter(n -> dbNodeId.equals(n.getId())) - .findFirst() - .orElseGet(() -> new CodeNode(dbNodeId, NodeKind.DATABASE_CONNECTION, "Database")); - CodeEdge edge = new CodeEdge(); - edge.setId(sourceId + "->connects_to->" + dbNodeId); - edge.setKind(EdgeKind.CONNECTS_TO); - edge.setSourceId(sourceId); - edge.setTarget(targetRef); - edges.add(edge); + DetectorDbHelper.addDbEdge(sourceId, registry, nodes, edges); } } diff --git a/src/main/java/io/github/randomcodespace/iq/detector/typescript/PrismaORMDetector.java b/src/main/java/io/github/randomcodespace/iq/detector/typescript/PrismaORMDetector.java index 82480404..fc99c579 100644 --- a/src/main/java/io/github/randomcodespace/iq/detector/typescript/PrismaORMDetector.java +++ b/src/main/java/io/github/randomcodespace/iq/detector/typescript/PrismaORMDetector.java @@ -1,6 +1,7 @@ package io.github.randomcodespace.iq.detector.typescript; import io.github.randomcodespace.iq.detector.AbstractAntlrDetector; +import io.github.randomcodespace.iq.detector.DetectorDbHelper; import io.github.randomcodespace.iq.grammar.AntlrParserFactory; import io.github.randomcodespace.iq.detector.DetectorContext; import io.github.randomcodespace.iq.detector.DetectorResult; @@ -90,7 +91,7 @@ protected DetectorResult detectWithRegex(DetectorContext ctx) { node.getProperties().put("transaction", true); } nodes.add(node); - addDbEdge(nodeId, ctx.registry(), nodes, edges); + DetectorDbHelper.addDbEdge(nodeId, ctx.registry(), nodes, edges); } // @prisma/client imports -> IMPORTS edge @@ -140,45 +141,4 @@ protected DetectorResult detectWithRegex(DetectorContext ctx) { return DetectorResult.of(nodes, edges); } - // ==================== InfrastructureRegistry helpers ==================== - - private static String ensureDbNode( - io.github.randomcodespace.iq.analyzer.InfrastructureRegistry registry, - List nodes) { - String dbNodeId; - if (registry != null && !registry.getDatabases().isEmpty()) { - io.github.randomcodespace.iq.analyzer.InfraEndpoint db = - registry.getDatabases().values().iterator().next(); - dbNodeId = "infra:" + db.id(); - if (nodes.stream().noneMatch(n -> dbNodeId.equals(n.getId()))) { - CodeNode dbNode = new CodeNode(dbNodeId, NodeKind.DATABASE_CONNECTION, - db.name() + " (" + db.type() + ")"); - dbNode.getProperties().put("type", db.type()); - if (db.connectionUrl() != null) dbNode.getProperties().put("url", db.connectionUrl()); - nodes.add(dbNode); - } - } else { - dbNodeId = "database:unknown"; - if (nodes.stream().noneMatch(n -> dbNodeId.equals(n.getId()))) { - nodes.add(new CodeNode(dbNodeId, NodeKind.DATABASE_CONNECTION, "Database")); - } - } - return dbNodeId; - } - - private static void addDbEdge(String sourceId, - io.github.randomcodespace.iq.analyzer.InfrastructureRegistry registry, - List nodes, List edges) { - String dbNodeId = ensureDbNode(registry, nodes); - CodeNode targetRef = nodes.stream() - .filter(n -> dbNodeId.equals(n.getId())) - .findFirst() - .orElseGet(() -> new CodeNode(dbNodeId, NodeKind.DATABASE_CONNECTION, "Database")); - CodeEdge edge = new CodeEdge(); - edge.setId(sourceId + "->connects_to->" + dbNodeId); - edge.setKind(EdgeKind.CONNECTS_TO); - edge.setSourceId(sourceId); - edge.setTarget(targetRef); - edges.add(edge); - } } diff --git a/src/main/java/io/github/randomcodespace/iq/detector/typescript/TypeORMEntityDetector.java b/src/main/java/io/github/randomcodespace/iq/detector/typescript/TypeORMEntityDetector.java index f42ac909..28addb79 100644 --- a/src/main/java/io/github/randomcodespace/iq/detector/typescript/TypeORMEntityDetector.java +++ b/src/main/java/io/github/randomcodespace/iq/detector/typescript/TypeORMEntityDetector.java @@ -1,6 +1,7 @@ package io.github.randomcodespace.iq.detector.typescript; import io.github.randomcodespace.iq.detector.AbstractAntlrDetector; +import io.github.randomcodespace.iq.detector.DetectorDbHelper; import io.github.randomcodespace.iq.grammar.AntlrParserFactory; import io.github.randomcodespace.iq.detector.DetectorContext; import io.github.randomcodespace.iq.detector.DetectorResult; @@ -128,51 +129,10 @@ else if (ch == '}') { edge.setSourceId(nodeId); edges.add(edge); } - addDbEdge(nodeId, ctx.registry(), nodes, edges); + DetectorDbHelper.addDbEdge(nodeId, ctx.registry(), nodes, edges); } return DetectorResult.of(nodes, edges); } - // ==================== InfrastructureRegistry helpers ==================== - - private static String ensureDbNode( - io.github.randomcodespace.iq.analyzer.InfrastructureRegistry registry, - List nodes) { - String dbNodeId; - if (registry != null && !registry.getDatabases().isEmpty()) { - io.github.randomcodespace.iq.analyzer.InfraEndpoint db = - registry.getDatabases().values().iterator().next(); - dbNodeId = "infra:" + db.id(); - if (nodes.stream().noneMatch(n -> dbNodeId.equals(n.getId()))) { - CodeNode dbNode = new CodeNode(dbNodeId, NodeKind.DATABASE_CONNECTION, - db.name() + " (" + db.type() + ")"); - dbNode.getProperties().put("type", db.type()); - if (db.connectionUrl() != null) dbNode.getProperties().put("url", db.connectionUrl()); - nodes.add(dbNode); - } - } else { - dbNodeId = "database:unknown"; - if (nodes.stream().noneMatch(n -> dbNodeId.equals(n.getId()))) { - nodes.add(new CodeNode(dbNodeId, NodeKind.DATABASE_CONNECTION, "Database")); - } - } - return dbNodeId; - } - - private static void addDbEdge(String sourceId, - io.github.randomcodespace.iq.analyzer.InfrastructureRegistry registry, - List nodes, List edges) { - String dbNodeId = ensureDbNode(registry, nodes); - CodeNode targetRef = nodes.stream() - .filter(n -> dbNodeId.equals(n.getId())) - .findFirst() - .orElseGet(() -> new CodeNode(dbNodeId, NodeKind.DATABASE_CONNECTION, "Database")); - CodeEdge edge = new CodeEdge(); - edge.setId(sourceId + "->connects_to->" + dbNodeId); - edge.setKind(EdgeKind.CONNECTS_TO); - edge.setSourceId(sourceId); - edge.setTarget(targetRef); - edges.add(edge); - } }