Skip to content

Commit 4ab7b83

Browse files
aksOpsclaude
andcommitted
refactor: extract AbstractTypeScriptDetector + consolidate CLI output helpers
- Create AbstractTypeScriptDetector base class providing getSupportedLanguages() and detect() → detectWithRegex() shared by all 13 TypeScript detectors - Remove ~160 lines of duplicated boilerplate across TS detectors - Extract CliOutput.configureFromOptions() and printResultSummary() shared by AnalyzeCommand and IndexCommand — removes ~40 lines of duplication Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 275548b commit 4ab7b83

19 files changed

Lines changed: 86 additions & 231 deletions

src/main/java/io/github/randomcodespace/iq/cli/AnalyzeCommand.java

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import io.github.randomcodespace.iq.analyzer.AnalysisResult;
44
import io.github.randomcodespace.iq.analyzer.Analyzer;
55
import io.github.randomcodespace.iq.config.CodeIqConfig;
6-
import io.github.randomcodespace.iq.config.ProjectConfigLoader;
76
import org.springframework.stereotype.Component;
87
import picocli.CommandLine.Command;
98
import picocli.CommandLine.Option;
@@ -59,21 +58,7 @@ public AnalyzeCommand(Analyzer analyzer, CodeIqConfig config) {
5958
public Integer call() {
6059
Path root = path.toAbsolutePath().normalize();
6160

62-
// If --graph is set, override the cache directory to the shared location
63-
if (graphDir != null) {
64-
Path sharedDir = graphDir.toAbsolutePath().normalize();
65-
config.setCacheDir(sharedDir.toString());
66-
CliOutput.info(" Graph dir: " + sharedDir + " (shared multi-repo)");
67-
}
68-
69-
// If --service-name is set, tag all nodes with this service identifier
70-
if (serviceName != null && !serviceName.isBlank()) {
71-
config.setServiceName(serviceName);
72-
CliOutput.info(" Service name: " + serviceName);
73-
}
74-
75-
// Load project-level config overrides from .osscodeiq.yml if present
76-
ProjectConfigLoader.loadIfPresent(root, config);
61+
CliOutput.configureFromOptions(config, graphDir, serviceName, root);
7762

7863
NumberFormat nf = NumberFormat.getIntegerInstance(Locale.US);
7964
int cores = parallelism != null ? parallelism : Runtime.getRuntime().availableProcessors();
@@ -110,16 +95,7 @@ public Integer call() {
11095
}
11196
});
11297

113-
long secs = result.elapsed().toSeconds();
114-
String timeStr = secs > 0 ? secs + "s" : result.elapsed().toMillis() + "ms";
115-
116-
System.out.println();
117-
CliOutput.success("\u2705 Analysis complete \u2014 "
118-
+ nf.format(result.nodeCount()) + " nodes, "
119-
+ nf.format(result.edgeCount()) + " edges in " + timeStr);
120-
System.out.println();
121-
CliOutput.printAnalysisStats(result, nf);
122-
CliOutput.printBreakdowns(result, nf);
98+
CliOutput.printResultSummary(result, nf);
12399

124100
if (result.frameworkBreakdown() != null && !result.frameworkBreakdown().isEmpty()) {
125101
StringBuilder fws = new StringBuilder(" Frameworks: ");

src/main/java/io/github/randomcodespace/iq/cli/CliOutput.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,42 @@ static String format(String ansiFormatted) {
7979
return ANSI.string(ansiFormatted);
8080
}
8181

82+
/**
83+
* Configure shared CLI options: graph directory, service name, and project-level overrides.
84+
* Used by both {@code analyze} and {@code index} commands.
85+
*/
86+
static void configureFromOptions(io.github.randomcodespace.iq.config.CodeIqConfig config,
87+
java.nio.file.Path graphDir, String serviceName,
88+
java.nio.file.Path root) {
89+
if (graphDir != null) {
90+
java.nio.file.Path sharedDir = graphDir.toAbsolutePath().normalize();
91+
config.setCacheDir(sharedDir.toString());
92+
info(" Graph dir: " + sharedDir + " (shared multi-repo)");
93+
}
94+
if (serviceName != null && !serviceName.isBlank()) {
95+
config.setServiceName(serviceName);
96+
info(" Service name: " + serviceName);
97+
}
98+
io.github.randomcodespace.iq.config.ProjectConfigLoader.loadIfPresent(root, config);
99+
}
100+
101+
/**
102+
* Print the result summary shared by analyze and index commands:
103+
* blank line, success banner, blank line, files/nodes/edges/time, and breakdowns.
104+
*/
105+
static void printResultSummary(AnalysisResult result, NumberFormat nf) {
106+
long secs = result.elapsed().toSeconds();
107+
String timeStr = secs > 0 ? secs + "s" : result.elapsed().toMillis() + "ms";
108+
109+
System.out.println();
110+
success("\u2705 Complete \u2014 "
111+
+ nf.format(result.nodeCount()) + " nodes, "
112+
+ nf.format(result.edgeCount()) + " edges in " + timeStr);
113+
System.out.println();
114+
printAnalysisStats(result, nf);
115+
printBreakdowns(result, nf);
116+
}
117+
82118
/**
83119
* Print files/nodes/edges/time summary lines shared by analyze and index commands.
84120
* Callers are responsible for printing the preceding success banner and any

src/main/java/io/github/randomcodespace/iq/cli/IndexCommand.java

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import io.github.randomcodespace.iq.analyzer.AnalysisResult;
44
import io.github.randomcodespace.iq.analyzer.Analyzer;
55
import io.github.randomcodespace.iq.config.CodeIqConfig;
6-
import io.github.randomcodespace.iq.config.ProjectConfigLoader;
76
import org.springframework.stereotype.Component;
87
import picocli.CommandLine.Command;
98
import picocli.CommandLine.Option;
@@ -63,21 +62,7 @@ public IndexCommand(Analyzer analyzer, CodeIqConfig config) {
6362
public Integer call() {
6463
Path root = path.toAbsolutePath().normalize();
6564

66-
// If --graph is set, override the cache directory to the shared location
67-
if (graphDir != null) {
68-
Path sharedDir = graphDir.toAbsolutePath().normalize();
69-
config.setCacheDir(sharedDir.toString());
70-
CliOutput.info(" Graph dir: " + sharedDir + " (shared multi-repo)");
71-
}
72-
73-
// If --service-name is set, tag all nodes with this service identifier
74-
if (serviceName != null && !serviceName.isBlank()) {
75-
config.setServiceName(serviceName);
76-
CliOutput.info(" Service name: " + serviceName);
77-
}
78-
79-
// Load project-level config overrides from .osscodeiq.yml if present
80-
ProjectConfigLoader.loadIfPresent(root, config);
65+
CliOutput.configureFromOptions(config, graphDir, serviceName, root);
8166

8267
// Use configured batch size if not overridden on command line
8368
int effectiveBatchSize = batchSize > 0 ? batchSize : config.getBatchSize();
@@ -118,17 +103,8 @@ public Integer call() {
118103
}
119104
});
120105

121-
long secs = result.elapsed().toSeconds();
122-
String timeStr = secs > 0 ? secs + "s" : result.elapsed().toMillis() + "ms";
123-
124-
System.out.println();
125-
CliOutput.success("\u2705 Index complete -- "
126-
+ nf.format(result.nodeCount()) + " nodes, "
127-
+ nf.format(result.edgeCount()) + " edges in " + timeStr);
128-
System.out.println();
129-
CliOutput.printAnalysisStats(result, nf);
106+
CliOutput.printResultSummary(result, nf);
130107
CliOutput.info(" Store: H2 (.code-intelligence/analysis-cache)");
131-
CliOutput.printBreakdowns(result, nf);
132108

133109
System.out.println();
134110
CliOutput.info(" Next step: code-iq enrich " + root);
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package io.github.randomcodespace.iq.detector.typescript;
2+
3+
import io.github.randomcodespace.iq.detector.AbstractAntlrDetector;
4+
import io.github.randomcodespace.iq.detector.DetectorContext;
5+
import io.github.randomcodespace.iq.detector.DetectorResult;
6+
import java.util.Set;
7+
8+
public abstract class AbstractTypeScriptDetector extends AbstractAntlrDetector {
9+
@Override
10+
public Set<String> getSupportedLanguages() {
11+
return Set.of("typescript", "javascript");
12+
}
13+
14+
@Override
15+
public DetectorResult detect(DetectorContext ctx) {
16+
return detectWithRegex(ctx);
17+
}
18+
}

src/main/java/io/github/randomcodespace/iq/detector/typescript/ExpressRouteDetector.java

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package io.github.randomcodespace.iq.detector.typescript;
22

3-
import io.github.randomcodespace.iq.detector.AbstractAntlrDetector;
43
import io.github.randomcodespace.iq.detector.DetectorContext;
54
import io.github.randomcodespace.iq.detector.DetectorResult;
65
import io.github.randomcodespace.iq.grammar.AntlrParserFactory;
@@ -30,7 +29,7 @@
3029
properties = {"framework", "http_method", "protocol"}
3130
)
3231
@Component
33-
public class ExpressRouteDetector extends AbstractAntlrDetector {
32+
public class ExpressRouteDetector extends AbstractTypeScriptDetector {
3433

3534
private static final Set<String> HTTP_METHODS = Set.of(
3635
"get", "post", "put", "delete", "patch", "options", "head", "all"
@@ -45,19 +44,6 @@ public String getName() {
4544
return "typescript.express_routes";
4645
}
4746

48-
@Override
49-
public Set<String> getSupportedLanguages() {
50-
return Set.of("typescript", "javascript");
51-
}
52-
53-
@Override
54-
public DetectorResult detect(DetectorContext ctx) {
55-
// Skip ANTLR parsing — regex is the primary detection method for this detector.
56-
// The JavaScript ANTLR grammar is too slow for production use (1-20s per file).
57-
// Regex produces identical results for Express route detection.
58-
return detectWithRegex(ctx);
59-
}
60-
6147
@Override
6248
protected ParseTree parse(DetectorContext ctx) {
6349
// Not called when detect() is overridden, kept for potential future use

src/main/java/io/github/randomcodespace/iq/detector/typescript/FastifyRouteDetector.java

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package io.github.randomcodespace.iq.detector.typescript;
22

3-
import io.github.randomcodespace.iq.detector.AbstractAntlrDetector;
43
import io.github.randomcodespace.iq.detector.DetectorContext;
54
import io.github.randomcodespace.iq.detector.DetectorResult;
65
import io.github.randomcodespace.iq.model.CodeEdge;
@@ -29,7 +28,7 @@
2928
properties = {"framework", "http_method", "protocol"}
3029
)
3130
@Component
32-
public class FastifyRouteDetector extends AbstractAntlrDetector {
31+
public class FastifyRouteDetector extends AbstractTypeScriptDetector {
3332
private static final String PROP_FASTIFY = "fastify";
3433
private static final String PROP_FRAMEWORK = "framework";
3534

@@ -70,18 +69,6 @@ public String getName() {
7069
return "fastify_routes";
7170
}
7271

73-
@Override
74-
public Set<String> getSupportedLanguages() {
75-
return Set.of("typescript", "javascript");
76-
}
77-
78-
@Override
79-
public DetectorResult detect(DetectorContext ctx) {
80-
// Skip ANTLR parsing — regex is the primary detection method for this detector
81-
// ANTLR infrastructure is in place for future enhancement
82-
return detectWithRegex(ctx);
83-
}
84-
8572
@Override
8673
protected DetectorResult detectWithRegex(DetectorContext ctx) {
8774
String text = ctx.content();

src/main/java/io/github/randomcodespace/iq/detector/typescript/GraphQLResolverDetector.java

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package io.github.randomcodespace.iq.detector.typescript;
22

3-
import io.github.randomcodespace.iq.detector.AbstractAntlrDetector;
4-
import io.github.randomcodespace.iq.grammar.AntlrParserFactory;
53
import io.github.randomcodespace.iq.detector.DetectorContext;
64
import io.github.randomcodespace.iq.detector.DetectorResult;
75
import io.github.randomcodespace.iq.model.CodeNode;
@@ -10,7 +8,6 @@
108

119
import java.util.ArrayList;
1210
import java.util.List;
13-
import java.util.Set;
1411
import java.util.regex.Matcher;
1512
import java.util.regex.Pattern;
1613
import io.github.randomcodespace.iq.detector.DetectorInfo;
@@ -26,7 +23,7 @@
2623
properties = {"framework", "protocol"}
2724
)
2825
@Component
29-
public class GraphQLResolverDetector extends AbstractAntlrDetector {
26+
public class GraphQLResolverDetector extends AbstractTypeScriptDetector {
3027

3128
private static final Pattern NESTJS_RESOLVER = Pattern.compile(
3229
"@Resolver\\(\\s*(?:of\\s*=>\\s*)?(\\w+)?\\s*\\)\\s*\\n\\s*(?:export\\s+)?class\\s+(\\w+)"
@@ -49,18 +46,6 @@ public String getName() {
4946
return "typescript.graphql_resolvers";
5047
}
5148

52-
@Override
53-
public Set<String> getSupportedLanguages() {
54-
return Set.of("typescript", "javascript");
55-
}
56-
57-
@Override
58-
public DetectorResult detect(DetectorContext ctx) {
59-
// Skip ANTLR parsing — regex is the primary detection method for this detector
60-
// ANTLR infrastructure is in place for future enhancement
61-
return detectWithRegex(ctx);
62-
}
63-
6449
@Override
6550
protected DetectorResult detectWithRegex(DetectorContext ctx) {
6651
List<CodeNode> nodes = new ArrayList<>();

src/main/java/io/github/randomcodespace/iq/detector/typescript/KafkaJSDetector.java

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package io.github.randomcodespace.iq.detector.typescript;
22

3-
import io.github.randomcodespace.iq.detector.AbstractAntlrDetector;
43
import io.github.randomcodespace.iq.detector.DetectorContext;
54
import io.github.randomcodespace.iq.detector.DetectorResult;
65
import io.github.randomcodespace.iq.model.CodeEdge;
@@ -26,7 +25,7 @@
2625
properties = {"broker", "group_id", "topic"}
2726
)
2827
@Component
29-
public class KafkaJSDetector extends AbstractAntlrDetector {
28+
public class KafkaJSDetector extends AbstractTypeScriptDetector {
3029
private static final String PROP_KAFKA = "kafka";
3130
private static final String PROP_TOPIC = "topic";
3231

@@ -51,17 +50,6 @@ public String getName() {
5150
return "kafka_js";
5251
}
5352

54-
@Override
55-
public Set<String> getSupportedLanguages() {
56-
return Set.of("typescript", "javascript");
57-
}
58-
@Override
59-
public DetectorResult detect(DetectorContext ctx) {
60-
// Skip ANTLR parsing — regex is the primary detection method for this detector
61-
// ANTLR infrastructure is in place for future enhancement
62-
return detectWithRegex(ctx);
63-
}
64-
6553
@Override
6654
protected DetectorResult detectWithRegex(DetectorContext ctx) {
6755
List<CodeNode> nodes = new ArrayList<>();

src/main/java/io/github/randomcodespace/iq/detector/typescript/MongooseORMDetector.java

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package io.github.randomcodespace.iq.detector.typescript;
22

3-
import io.github.randomcodespace.iq.detector.AbstractAntlrDetector;
43
import io.github.randomcodespace.iq.detector.DetectorContext;
54
import io.github.randomcodespace.iq.detector.DetectorResult;
65
import io.github.randomcodespace.iq.model.CodeEdge;
@@ -26,7 +25,7 @@
2625
properties = {"framework", "operation"}
2726
)
2827
@Component
29-
public class MongooseORMDetector extends AbstractAntlrDetector {
28+
public class MongooseORMDetector extends AbstractTypeScriptDetector {
3029
private static final String PROP_DEFINITION = "definition";
3130
private static final String PROP_FRAMEWORK = "framework";
3231
private static final String PROP_MONGOOSE = "mongoose";
@@ -58,18 +57,6 @@ public String getName() {
5857
return "mongoose_orm";
5958
}
6059

61-
@Override
62-
public Set<String> getSupportedLanguages() {
63-
return Set.of("typescript", "javascript");
64-
}
65-
66-
@Override
67-
public DetectorResult detect(DetectorContext ctx) {
68-
// Skip ANTLR parsing — regex is the primary detection method for this detector
69-
// ANTLR infrastructure is in place for future enhancement
70-
return detectWithRegex(ctx);
71-
}
72-
7360
@Override
7461
protected DetectorResult detectWithRegex(DetectorContext ctx) {
7562
List<CodeNode> nodes = new ArrayList<>();

src/main/java/io/github/randomcodespace/iq/detector/typescript/NestJSControllerDetector.java

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package io.github.randomcodespace.iq.detector.typescript;
22

3-
import io.github.randomcodespace.iq.detector.AbstractAntlrDetector;
43
import io.github.randomcodespace.iq.grammar.AntlrParserFactory;
54
import io.github.randomcodespace.iq.detector.DetectorContext;
65
import io.github.randomcodespace.iq.detector.DetectorResult;
@@ -13,7 +12,6 @@
1312

1413
import java.util.ArrayList;
1514
import java.util.List;
16-
import java.util.Set;
1715
import java.util.regex.Matcher;
1816
import java.util.regex.Pattern;
1917
import io.github.randomcodespace.iq.detector.DetectorInfo;
@@ -24,13 +22,13 @@
2422
category = "endpoints",
2523
description = "Detects NestJS controllers and their route definitions",
2624
parser = ParserType.REGEX,
27-
languages = {"typescript"},
25+
languages = {"typescript", "javascript"},
2826
nodeKinds = {NodeKind.CLASS, NodeKind.ENDPOINT},
2927
edgeKinds = {EdgeKind.EXPOSES, EdgeKind.CALLS},
3028
properties = {"framework", "http_method", "protocol"}
3129
)
3230
@Component
33-
public class NestJSControllerDetector extends AbstractAntlrDetector {
31+
public class NestJSControllerDetector extends AbstractTypeScriptDetector {
3432

3533
// ---- HTTP client patterns (for CALLS edge emission) ----
3634
private static final Pattern HTTP_CLIENT_RE = Pattern.compile(
@@ -53,11 +51,6 @@ public String getName() {
5351
return "typescript.nestjs_controllers";
5452
}
5553

56-
@Override
57-
public Set<String> getSupportedLanguages() {
58-
return Set.of("typescript");
59-
}
60-
6154
@Override
6255
protected ParseTree parse(DetectorContext ctx) {
6356
// Use the dedicated TypeScript ANTLR grammar for parsing;

0 commit comments

Comments
 (0)