@lbroth/rothunter 1.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +141 -0
- package/dist/adapters/llm.d.ts +68 -0
- package/dist/adapters/llm.d.ts.map +1 -0
- package/dist/adapters/llm.js +189 -0
- package/dist/adapters/llm.js.map +1 -0
- package/dist/config.d.ts +37 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +81 -0
- package/dist/config.js.map +1 -0
- package/dist/detector-registry.d.ts +32 -0
- package/dist/detector-registry.d.ts.map +1 -0
- package/dist/detector-registry.js +74 -0
- package/dist/detector-registry.js.map +1 -0
- package/dist/detectors/api-race.d.ts +6 -0
- package/dist/detectors/api-race.d.ts.map +1 -0
- package/dist/detectors/api-race.js +222 -0
- package/dist/detectors/api-race.js.map +1 -0
- package/dist/detectors/bad-config.d.ts +6 -0
- package/dist/detectors/bad-config.d.ts.map +1 -0
- package/dist/detectors/bad-config.js +529 -0
- package/dist/detectors/bad-config.js.map +1 -0
- package/dist/detectors/console-log-prod.d.ts +6 -0
- package/dist/detectors/console-log-prod.d.ts.map +1 -0
- package/dist/detectors/console-log-prod.js +72 -0
- package/dist/detectors/console-log-prod.js.map +1 -0
- package/dist/detectors/dead-api.d.ts +10 -0
- package/dist/detectors/dead-api.d.ts.map +1 -0
- package/dist/detectors/dead-api.js +115 -0
- package/dist/detectors/dead-api.js.map +1 -0
- package/dist/detectors/dead-export.d.ts +12 -0
- package/dist/detectors/dead-export.d.ts.map +1 -0
- package/dist/detectors/dead-export.js +140 -0
- package/dist/detectors/dead-export.js.map +1 -0
- package/dist/detectors/dead-handler.d.ts +12 -0
- package/dist/detectors/dead-handler.d.ts.map +1 -0
- package/dist/detectors/dead-handler.js +40 -0
- package/dist/detectors/dead-handler.js.map +1 -0
- package/dist/detectors/dead-module.d.ts +14 -0
- package/dist/detectors/dead-module.d.ts.map +1 -0
- package/dist/detectors/dead-module.js +50 -0
- package/dist/detectors/dead-module.js.map +1 -0
- package/dist/detectors/deep-nesting.d.ts +12 -0
- package/dist/detectors/deep-nesting.d.ts.map +1 -0
- package/dist/detectors/deep-nesting.js +133 -0
- package/dist/detectors/deep-nesting.js.map +1 -0
- package/dist/detectors/duplicate-function.d.ts +9 -0
- package/dist/detectors/duplicate-function.d.ts.map +1 -0
- package/dist/detectors/duplicate-function.js +199 -0
- package/dist/detectors/duplicate-function.js.map +1 -0
- package/dist/detectors/duplicate-type.d.ts +9 -0
- package/dist/detectors/duplicate-type.d.ts.map +1 -0
- package/dist/detectors/duplicate-type.js +166 -0
- package/dist/detectors/duplicate-type.js.map +1 -0
- package/dist/detectors/hot-hub-file.d.ts +11 -0
- package/dist/detectors/hot-hub-file.d.ts.map +1 -0
- package/dist/detectors/hot-hub-file.js +42 -0
- package/dist/detectors/hot-hub-file.js.map +1 -0
- package/dist/detectors/long-file.d.ts +12 -0
- package/dist/detectors/long-file.d.ts.map +1 -0
- package/dist/detectors/long-file.js +82 -0
- package/dist/detectors/long-file.js.map +1 -0
- package/dist/detectors/long-function.d.ts +12 -0
- package/dist/detectors/long-function.d.ts.map +1 -0
- package/dist/detectors/long-function.js +45 -0
- package/dist/detectors/long-function.js.map +1 -0
- package/dist/detectors/magic-numbers.d.ts +10 -0
- package/dist/detectors/magic-numbers.d.ts.map +1 -0
- package/dist/detectors/magic-numbers.js +332 -0
- package/dist/detectors/magic-numbers.js.map +1 -0
- package/dist/detectors/mutable-globals.d.ts +6 -0
- package/dist/detectors/mutable-globals.d.ts.map +1 -0
- package/dist/detectors/mutable-globals.js +95 -0
- package/dist/detectors/mutable-globals.js.map +1 -0
- package/dist/detectors/mutation.d.ts +11 -0
- package/dist/detectors/mutation.d.ts.map +1 -0
- package/dist/detectors/mutation.js +397 -0
- package/dist/detectors/mutation.js.map +1 -0
- package/dist/detectors/public-any.d.ts +6 -0
- package/dist/detectors/public-any.d.ts.map +1 -0
- package/dist/detectors/public-any.js +52 -0
- package/dist/detectors/public-any.js.map +1 -0
- package/dist/detectors/race-condition.d.ts +6 -0
- package/dist/detectors/race-condition.d.ts.map +1 -0
- package/dist/detectors/race-condition.js +608 -0
- package/dist/detectors/race-condition.js.map +1 -0
- package/dist/detectors/shared-db-write.d.ts +6 -0
- package/dist/detectors/shared-db-write.d.ts.map +1 -0
- package/dist/detectors/shared-db-write.js +656 -0
- package/dist/detectors/shared-db-write.js.map +1 -0
- package/dist/detectors/silent-catch.d.ts +6 -0
- package/dist/detectors/silent-catch.d.ts.map +1 -0
- package/dist/detectors/silent-catch.js +167 -0
- package/dist/detectors/silent-catch.js.map +1 -0
- package/dist/detectors/similar-functions.d.ts +15 -0
- package/dist/detectors/similar-functions.d.ts.map +1 -0
- package/dist/detectors/similar-functions.js +334 -0
- package/dist/detectors/similar-functions.js.map +1 -0
- package/dist/detectors/skip-tests.d.ts +6 -0
- package/dist/detectors/skip-tests.d.ts.map +1 -0
- package/dist/detectors/skip-tests.js +69 -0
- package/dist/detectors/skip-tests.js.map +1 -0
- package/dist/detectors/todo-comments.d.ts +29 -0
- package/dist/detectors/todo-comments.d.ts.map +1 -0
- package/dist/detectors/todo-comments.js +154 -0
- package/dist/detectors/todo-comments.js.map +1 -0
- package/dist/detectors/unused-deps.d.ts +8 -0
- package/dist/detectors/unused-deps.d.ts.map +1 -0
- package/dist/detectors/unused-deps.js +115 -0
- package/dist/detectors/unused-deps.js.map +1 -0
- package/dist/extraction/api-race-confirmer.d.ts +31 -0
- package/dist/extraction/api-race-confirmer.d.ts.map +1 -0
- package/dist/extraction/api-race-confirmer.js +110 -0
- package/dist/extraction/api-race-confirmer.js.map +1 -0
- package/dist/extraction/llm-confirmer.d.ts +25 -0
- package/dist/extraction/llm-confirmer.d.ts.map +1 -0
- package/dist/extraction/llm-confirmer.js +118 -0
- package/dist/extraction/llm-confirmer.js.map +1 -0
- package/dist/extraction/mutation-confirmer.d.ts +30 -0
- package/dist/extraction/mutation-confirmer.d.ts.map +1 -0
- package/dist/extraction/mutation-confirmer.js +73 -0
- package/dist/extraction/mutation-confirmer.js.map +1 -0
- package/dist/extraction/prompt-chunking.d.ts +37 -0
- package/dist/extraction/prompt-chunking.d.ts.map +1 -0
- package/dist/extraction/prompt-chunking.js +61 -0
- package/dist/extraction/prompt-chunking.js.map +1 -0
- package/dist/extraction/race-confirmer.d.ts +28 -0
- package/dist/extraction/race-confirmer.d.ts.map +1 -0
- package/dist/extraction/race-confirmer.js +68 -0
- package/dist/extraction/race-confirmer.js.map +1 -0
- package/dist/extraction/shared-db-write-confirmer.d.ts +31 -0
- package/dist/extraction/shared-db-write-confirmer.d.ts.map +1 -0
- package/dist/extraction/shared-db-write-confirmer.js +141 -0
- package/dist/extraction/shared-db-write-confirmer.js.map +1 -0
- package/dist/extraction/triage-confirmer.d.ts +59 -0
- package/dist/extraction/triage-confirmer.d.ts.map +1 -0
- package/dist/extraction/triage-confirmer.js +104 -0
- package/dist/extraction/triage-confirmer.js.map +1 -0
- package/dist/graph/cfg.d.ts +45 -0
- package/dist/graph/cfg.d.ts.map +1 -0
- package/dist/graph/cfg.js +198 -0
- package/dist/graph/cfg.js.map +1 -0
- package/dist/graph/decorator-entries.d.ts +2 -0
- package/dist/graph/decorator-entries.d.ts.map +1 -0
- package/dist/graph/decorator-entries.js +89 -0
- package/dist/graph/decorator-entries.js.map +1 -0
- package/dist/graph/entry-points.d.ts +12 -0
- package/dist/graph/entry-points.d.ts.map +1 -0
- package/dist/graph/entry-points.js +282 -0
- package/dist/graph/entry-points.js.map +1 -0
- package/dist/graph/handler-conventions.d.ts +2 -0
- package/dist/graph/handler-conventions.d.ts.map +1 -0
- package/dist/graph/handler-conventions.js +26 -0
- package/dist/graph/handler-conventions.js.map +1 -0
- package/dist/graph/iac-entries.d.ts +2 -0
- package/dist/graph/iac-entries.d.ts.map +1 -0
- package/dist/graph/iac-entries.js +123 -0
- package/dist/graph/iac-entries.js.map +1 -0
- package/dist/graph/import-graph.d.ts +48 -0
- package/dist/graph/import-graph.d.ts.map +1 -0
- package/dist/graph/import-graph.js +86 -0
- package/dist/graph/import-graph.js.map +1 -0
- package/dist/graph/monorepo-detect.d.ts +3 -0
- package/dist/graph/monorepo-detect.d.ts.map +1 -0
- package/dist/graph/monorepo-detect.js +166 -0
- package/dist/graph/monorepo-detect.js.map +1 -0
- package/dist/graph/tsconfig-paths.d.ts +23 -0
- package/dist/graph/tsconfig-paths.d.ts.map +1 -0
- package/dist/graph/tsconfig-paths.js +217 -0
- package/dist/graph/tsconfig-paths.js.map +1 -0
- package/dist/multi-workspace-scanner.d.ts +13 -0
- package/dist/multi-workspace-scanner.d.ts.map +1 -0
- package/dist/multi-workspace-scanner.js +130 -0
- package/dist/multi-workspace-scanner.js.map +1 -0
- package/dist/normalizers/type-normalizer.d.ts +16 -0
- package/dist/normalizers/type-normalizer.d.ts.map +1 -0
- package/dist/normalizers/type-normalizer.js +189 -0
- package/dist/normalizers/type-normalizer.js.map +1 -0
- package/dist/parsers/typescript-parser.d.ts +57 -0
- package/dist/parsers/typescript-parser.d.ts.map +1 -0
- package/dist/parsers/typescript-parser.js +502 -0
- package/dist/parsers/typescript-parser.js.map +1 -0
- package/dist/reporter/json-reporter.d.ts +12 -0
- package/dist/reporter/json-reporter.d.ts.map +1 -0
- package/dist/reporter/json-reporter.js +28 -0
- package/dist/reporter/json-reporter.js.map +1 -0
- package/dist/reporter/markdown-reporter.d.ts +11 -0
- package/dist/reporter/markdown-reporter.d.ts.map +1 -0
- package/dist/reporter/markdown-reporter.js +77 -0
- package/dist/reporter/markdown-reporter.js.map +1 -0
- package/dist/rothunter.d.ts +125 -0
- package/dist/rothunter.d.ts.map +1 -0
- package/dist/rothunter.js +1038 -0
- package/dist/rothunter.js.map +1 -0
- package/dist/server/false-positives.d.ts +34 -0
- package/dist/server/false-positives.d.ts.map +1 -0
- package/dist/server/false-positives.js +85 -0
- package/dist/server/false-positives.js.map +1 -0
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +1529 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/marked-to-fix.d.ts +16 -0
- package/dist/server/marked-to-fix.d.ts.map +1 -0
- package/dist/server/marked-to-fix.js +36 -0
- package/dist/server/marked-to-fix.js.map +1 -0
- package/dist/server/scan-store.d.ts +147 -0
- package/dist/server/scan-store.d.ts.map +1 -0
- package/dist/server/scan-store.js +291 -0
- package/dist/server/scan-store.js.map +1 -0
- package/dist/server/settings-store.d.ts +28 -0
- package/dist/server/settings-store.d.ts.map +1 -0
- package/dist/server/settings-store.js +46 -0
- package/dist/server/settings-store.js.map +1 -0
- package/dist/server/workspace-store.d.ts +39 -0
- package/dist/server/workspace-store.d.ts.map +1 -0
- package/dist/server/workspace-store.js +108 -0
- package/dist/server/workspace-store.js.map +1 -0
- package/dist/types/detector-input.d.ts +37 -0
- package/dist/types/detector-input.d.ts.map +1 -0
- package/dist/types/detector-input.js +2 -0
- package/dist/types/detector-input.js.map +1 -0
- package/dist/types.d.ts +110 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/clustering.d.ts +14 -0
- package/dist/utils/clustering.d.ts.map +1 -0
- package/dist/utils/clustering.js +56 -0
- package/dist/utils/clustering.js.map +1 -0
- package/dist/utils/gitignore.d.ts +32 -0
- package/dist/utils/gitignore.d.ts.map +1 -0
- package/dist/utils/gitignore.js +122 -0
- package/dist/utils/gitignore.js.map +1 -0
- package/dist/utils/hash.d.ts +11 -0
- package/dist/utils/hash.d.ts.map +1 -0
- package/dist/utils/hash.js +14 -0
- package/dist/utils/hash.js.map +1 -0
- package/dist/utils/ignore-annotation.d.ts +28 -0
- package/dist/utils/ignore-annotation.d.ts.map +1 -0
- package/dist/utils/ignore-annotation.js +46 -0
- package/dist/utils/ignore-annotation.js.map +1 -0
- package/dist/utils/llm-json.d.ts +2 -0
- package/dist/utils/llm-json.d.ts.map +1 -0
- package/dist/utils/llm-json.js +53 -0
- package/dist/utils/llm-json.js.map +1 -0
- package/dist/utils/logger.d.ts +3 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +4 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/project-conventions.d.ts +2 -0
- package/dist/utils/project-conventions.d.ts.map +1 -0
- package/dist/utils/project-conventions.js +108 -0
- package/dist/utils/project-conventions.js.map +1 -0
- package/dist/utils/regex.d.ts +9 -0
- package/dist/utils/regex.d.ts.map +1 -0
- package/dist/utils/regex.js +11 -0
- package/dist/utils/regex.js.map +1 -0
- package/dist/utils/snippet.d.ts +20 -0
- package/dist/utils/snippet.d.ts.map +1 -0
- package/dist/utils/snippet.js +28 -0
- package/dist/utils/snippet.js.map +1 -0
- package/dist/utils/source-reader.d.ts +19 -0
- package/dist/utils/source-reader.d.ts.map +1 -0
- package/dist/utils/source-reader.js +32 -0
- package/dist/utils/source-reader.js.map +1 -0
- package/logo.png +0 -0
- package/package.json +92 -0
- package/scripts/start-llm.mjs +161 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dead-api.d.ts","sourceRoot":"","sources":["../../src/detectors/dead-api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAG7D,MAAM,WAAW,oBAAoB;IACnC,4EAA4E;IAC5E,OAAO,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC;IACrC,gGAAgG;IAChG,OAAO,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC;CACtC;AAKD,wBAAgB,cAAc,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,EAAE,CAmGrE"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { stableHash } from '../utils/hash.js';
|
|
2
|
+
// Cross-workspace dead-API: exported symbol with no consumer in any
|
|
3
|
+
// OTHER workspace. Intra-workspace use intentionally ignored (dead-export
|
|
4
|
+
// covers that). Severity low — external dependents are invisible.
|
|
5
|
+
export function detectDeadApis(input) {
|
|
6
|
+
// Per-target consumed name sets across workspace boundaries.
|
|
7
|
+
const crossConsumed = new Map();
|
|
8
|
+
const crossNamespace = new Set(); // target file: a namespace consumer exists from another workspace
|
|
9
|
+
const crossDefault = new Set();
|
|
10
|
+
const addCross = (target, name) => {
|
|
11
|
+
const s = crossConsumed.get(target) ?? new Set();
|
|
12
|
+
s.add(name);
|
|
13
|
+
crossConsumed.set(target, s);
|
|
14
|
+
};
|
|
15
|
+
for (const imp of input.imports) {
|
|
16
|
+
if (!imp.target)
|
|
17
|
+
continue;
|
|
18
|
+
if (imp.targetWorkspace && imp.sourceWorkspace && imp.targetWorkspace !== imp.sourceWorkspace) {
|
|
19
|
+
if (imp.namespaceAlias)
|
|
20
|
+
crossNamespace.add(imp.target);
|
|
21
|
+
if (imp.isStarReExport)
|
|
22
|
+
crossNamespace.add(imp.target);
|
|
23
|
+
if (imp.defaultImport)
|
|
24
|
+
crossDefault.add(imp.target);
|
|
25
|
+
for (const name of imp.namedImports)
|
|
26
|
+
addCross(imp.target, name);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// Re-export propagation. If `pkg/src/index.ts` does
|
|
30
|
+
// `export { getUser } from './api/users'` and a cross-workspace consumer
|
|
31
|
+
// imports `getUser` from `pkg`, the consumption was recorded against
|
|
32
|
+
// `pkg/src/index.ts` (the resolved target). We need to forward that
|
|
33
|
+
// consumption to `pkg/src/api/users.ts`, where the actual symbol lives.
|
|
34
|
+
// Fixpoint iteration handles re-export chains (A re-exports from B
|
|
35
|
+
// which re-exports from C).
|
|
36
|
+
let changed = true;
|
|
37
|
+
while (changed) {
|
|
38
|
+
changed = false;
|
|
39
|
+
for (const imp of input.imports) {
|
|
40
|
+
if (!imp.isReExport)
|
|
41
|
+
continue;
|
|
42
|
+
if (!imp.target)
|
|
43
|
+
continue;
|
|
44
|
+
const sourceConsumed = crossConsumed.get(imp.source);
|
|
45
|
+
const sourceIsNamespaceConsumed = crossNamespace.has(imp.source);
|
|
46
|
+
if (imp.isStarReExport) {
|
|
47
|
+
// `export * from './x'` — every cross-consumed name on the re-exporting
|
|
48
|
+
// file must propagate to the target.
|
|
49
|
+
if (sourceConsumed) {
|
|
50
|
+
for (const name of sourceConsumed) {
|
|
51
|
+
const targetSet = crossConsumed.get(imp.target) ?? new Set();
|
|
52
|
+
if (!targetSet.has(name)) {
|
|
53
|
+
targetSet.add(name);
|
|
54
|
+
crossConsumed.set(imp.target, targetSet);
|
|
55
|
+
changed = true;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (sourceIsNamespaceConsumed && !crossNamespace.has(imp.target)) {
|
|
60
|
+
crossNamespace.add(imp.target);
|
|
61
|
+
changed = true;
|
|
62
|
+
}
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
for (const name of imp.reExportNames ?? imp.namedImports) {
|
|
66
|
+
const consumedHere = sourceConsumed?.has(name) || sourceIsNamespaceConsumed;
|
|
67
|
+
if (!consumedHere)
|
|
68
|
+
continue;
|
|
69
|
+
const targetSet = crossConsumed.get(imp.target) ?? new Set();
|
|
70
|
+
if (!targetSet.has(name)) {
|
|
71
|
+
targetSet.add(name);
|
|
72
|
+
crossConsumed.set(imp.target, targetSet);
|
|
73
|
+
changed = true;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const findings = [];
|
|
79
|
+
const exportedSymbols = input.symbols.filter((s) => s.exported);
|
|
80
|
+
for (const sym of exportedSymbols) {
|
|
81
|
+
if (!sym.workspace)
|
|
82
|
+
continue; // single-workspace scan — skip
|
|
83
|
+
// `sym.file` is already workspace-prefixed by the multi-workspace scanner.
|
|
84
|
+
if (crossNamespace.has(sym.file))
|
|
85
|
+
continue;
|
|
86
|
+
if (crossDefault.has(sym.file))
|
|
87
|
+
continue;
|
|
88
|
+
const consumed = crossConsumed.get(sym.file);
|
|
89
|
+
if (consumed && consumed.has(sym.name))
|
|
90
|
+
continue;
|
|
91
|
+
findings.push({
|
|
92
|
+
detectorId: 'dead-api',
|
|
93
|
+
severity: 'low',
|
|
94
|
+
confidence: 0.7,
|
|
95
|
+
layer: 1,
|
|
96
|
+
title: `Unused public API: ${sym.name} (${sym.workspace}/${stripWorkspace(sym.file, sym.workspace)})`,
|
|
97
|
+
description: `\`${sym.name}\` is exported from \`${sym.file}\` but no file in any sibling workspace imports it.\nLocations:\n- ${sym.file}:${sym.range.startLine} (${sym.kind} ${sym.name})`,
|
|
98
|
+
evidence: [
|
|
99
|
+
{
|
|
100
|
+
file: sym.file,
|
|
101
|
+
range: sym.range,
|
|
102
|
+
snippet: sym.source,
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
suggestion: 'If this is an internal helper accidentally exported, remove the `export` keyword. If it is meant for external consumers outside the linked group, mark this finding as a false positive.',
|
|
106
|
+
fingerprint: `dead-api:${stableHash(`${sym.workspace}::${sym.file}::${sym.name}`)}`,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
return findings;
|
|
110
|
+
}
|
|
111
|
+
function stripWorkspace(file, workspace) {
|
|
112
|
+
const prefix = `${workspace}/`;
|
|
113
|
+
return file.startsWith(prefix) ? file.slice(prefix.length) : file;
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=dead-api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dead-api.js","sourceRoot":"","sources":["../../src/detectors/dead-api.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Finding, SymbolRecord } from '../types.js';
|
|
2
|
+
import type { ImportRecord } from '../graph/import-graph.js';
|
|
3
|
+
export interface DeadExportDetectorInput {
|
|
4
|
+
/** Symbols parsed from the workspace, with `exported: boolean` set by the parser. */
|
|
5
|
+
symbols: ReadonlyArray<SymbolRecord>;
|
|
6
|
+
/** Import + re-export records produced by the parser. */
|
|
7
|
+
imports: ReadonlyArray<ImportRecord>;
|
|
8
|
+
/** Entry-point files (workspace-relative) — their exports are externally consumed. */
|
|
9
|
+
entryPoints: ReadonlySet<string>;
|
|
10
|
+
}
|
|
11
|
+
export declare function detectDeadExports(input: DeadExportDetectorInput): Finding[];
|
|
12
|
+
//# sourceMappingURL=dead-export.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dead-export.d.ts","sourceRoot":"","sources":["../../src/detectors/dead-export.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAG7D,MAAM,WAAW,uBAAuB;IACtC,qFAAqF;IACrF,OAAO,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC;IACrC,yDAAyD;IACzD,OAAO,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC;IACrC,sFAAsF;IACtF,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CAClC;AAMD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,uBAAuB,GAAG,OAAO,EAAE,CA0E3E"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import * as path from 'node:path';
|
|
2
|
+
import { stableHash } from '../utils/hash.js';
|
|
3
|
+
// Flag exports nothing imports. Consumed = named import, `* as ns`, `export *`,
|
|
4
|
+
// re-export, OR default import IFF the symbol is the file's `isDefault`.
|
|
5
|
+
// Dynamic / string-eval'd imports invisible. Severity low, confidence 0.65 —
|
|
6
|
+
// mark as FP from the dashboard for framework conventions.
|
|
7
|
+
export function detectDeadExports(input) {
|
|
8
|
+
const consumedNames = new Map();
|
|
9
|
+
const namespaceConsumedFiles = new Set();
|
|
10
|
+
const defaultConsumedFiles = new Set();
|
|
11
|
+
const addConsumed = (file, name) => {
|
|
12
|
+
const s = consumedNames.get(file) ?? new Set();
|
|
13
|
+
s.add(name);
|
|
14
|
+
consumedNames.set(file, s);
|
|
15
|
+
};
|
|
16
|
+
for (const imp of input.imports) {
|
|
17
|
+
if (!imp.target)
|
|
18
|
+
continue;
|
|
19
|
+
if (imp.namespaceAlias)
|
|
20
|
+
namespaceConsumedFiles.add(imp.target);
|
|
21
|
+
if (imp.isStarReExport)
|
|
22
|
+
namespaceConsumedFiles.add(imp.target);
|
|
23
|
+
if (imp.defaultImport)
|
|
24
|
+
defaultConsumedFiles.add(imp.target);
|
|
25
|
+
for (const name of imp.namedImports)
|
|
26
|
+
addConsumed(imp.target, name);
|
|
27
|
+
}
|
|
28
|
+
// Per-file map of EVERY symbol (exported AND internal) → its source.
|
|
29
|
+
// Used for the same-file reachability pass: an exported helper /
|
|
30
|
+
// type / class referenced from any sibling — exported or not —
|
|
31
|
+
// counts as used. Restricting to siblings-also-exported missed the
|
|
32
|
+
// common case where an exported utility (`sha256File`) is called
|
|
33
|
+
// only from a non-exported helper (`checksumMatchesSidecar`) within
|
|
34
|
+
// the same file.
|
|
35
|
+
const symbolsByFile = new Map();
|
|
36
|
+
for (const sym of input.symbols) {
|
|
37
|
+
const arr = symbolsByFile.get(sym.file) ?? [];
|
|
38
|
+
arr.push(sym);
|
|
39
|
+
symbolsByFile.set(sym.file, arr);
|
|
40
|
+
}
|
|
41
|
+
const findings = [];
|
|
42
|
+
for (const sym of input.symbols) {
|
|
43
|
+
if (!sym.exported)
|
|
44
|
+
continue;
|
|
45
|
+
if (input.entryPoints.has(sym.file))
|
|
46
|
+
continue;
|
|
47
|
+
if (shouldSkipFile(sym.file))
|
|
48
|
+
continue;
|
|
49
|
+
if (namespaceConsumedFiles.has(sym.file))
|
|
50
|
+
continue;
|
|
51
|
+
const consumed = consumedNames.get(sym.file);
|
|
52
|
+
if (consumed && consumed.has(sym.name))
|
|
53
|
+
continue;
|
|
54
|
+
// Default-import gate: only protect the symbol that IS the file's
|
|
55
|
+
// default. Other named exports in the same file remain subject to
|
|
56
|
+
// dead-export. The parser tags `isDefault` on the symbol; absent
|
|
57
|
+
// that tag the symbol is NOT the default, so leave the rest of the
|
|
58
|
+
// file's named exports open to the check.
|
|
59
|
+
if (defaultConsumedFiles.has(sym.file) && sym.isDefault)
|
|
60
|
+
continue;
|
|
61
|
+
// Type-surface reachability: an exported type / interface used in
|
|
62
|
+
// another exported symbol's source is reachable through that
|
|
63
|
+
// symbol's signature, even if no module imports it by name. Only
|
|
64
|
+
// applies to types — runtime symbols (function / class / enum)
|
|
65
|
+
// would show up via named import anyway.
|
|
66
|
+
if (isTypeSurface(sym, symbolsByFile))
|
|
67
|
+
continue;
|
|
68
|
+
findings.push({
|
|
69
|
+
detectorId: 'dead-export',
|
|
70
|
+
severity: 'low',
|
|
71
|
+
confidence: 0.65,
|
|
72
|
+
layer: 1,
|
|
73
|
+
title: `Unused export: ${sym.name} in ${sym.file}`,
|
|
74
|
+
description: `\`${sym.name}\` is exported from \`${sym.file}\` but no other workspace file imports it.\nLocations:\n- ${sym.file}:${sym.range.startLine} (${sym.kind} ${sym.name})`,
|
|
75
|
+
evidence: [
|
|
76
|
+
{
|
|
77
|
+
file: sym.file,
|
|
78
|
+
range: sym.range,
|
|
79
|
+
snippet: sym.source,
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
suggestion: 'If this export is consumed via dynamic import, framework convention, or an external package, mark this finding as a false positive. Otherwise drop the `export` keyword (or delete the symbol).',
|
|
83
|
+
fingerprint: `dead-export:${stableHash(`${sym.file}::${sym.name}`)}`,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return findings;
|
|
87
|
+
}
|
|
88
|
+
const SKIP_FILE_PATTERNS = [
|
|
89
|
+
/\.d\.ts$/,
|
|
90
|
+
/(^|\/)__fixtures__\//,
|
|
91
|
+
/(^|\/)__mocks__\//,
|
|
92
|
+
/\.story\.tsx?$/,
|
|
93
|
+
/\.stories\.tsx?$/,
|
|
94
|
+
];
|
|
95
|
+
function shouldSkipFile(file) {
|
|
96
|
+
const posix = file.split(path.sep).join('/');
|
|
97
|
+
return SKIP_FILE_PATTERNS.some((re) => re.test(posix));
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Decide whether `sym` is exposed transitively through the signature
|
|
101
|
+
* of any OTHER exported symbol in the same file. A `\bName\b` hit in a
|
|
102
|
+
* sibling's source is treated as a structural reference — return type,
|
|
103
|
+
* parameter, generic constraint, extends clause.
|
|
104
|
+
*
|
|
105
|
+
* Restricted to `interface` / `type-alias` kinds: those are the ones
|
|
106
|
+
* regularly exported purely to name a shape passed across the module
|
|
107
|
+
* boundary. A runtime symbol (function / class / enum) imported only
|
|
108
|
+
* through a sibling is rare; relying on the named-import path keeps
|
|
109
|
+
* the FP risk low.
|
|
110
|
+
*/
|
|
111
|
+
function isTypeSurface(sym, symbolsByFile) {
|
|
112
|
+
// Restricted to symbols whose surface is naturally referenced from a
|
|
113
|
+
// sibling — types from signatures, helpers from another function's
|
|
114
|
+
// body. Enum / class also count: extending or referencing the type
|
|
115
|
+
// inside another symbol of the same module is enough proof the
|
|
116
|
+
// symbol is consumed transitively. Variables / constants are
|
|
117
|
+
// intentionally excluded because their name regularly collides with
|
|
118
|
+
// local variables and would over-mask.
|
|
119
|
+
if (sym.kind !== 'interface' &&
|
|
120
|
+
sym.kind !== 'type-alias' &&
|
|
121
|
+
sym.kind !== 'function' &&
|
|
122
|
+
sym.kind !== 'class' &&
|
|
123
|
+
sym.kind !== 'enum')
|
|
124
|
+
return false;
|
|
125
|
+
const siblings = symbolsByFile.get(sym.file);
|
|
126
|
+
if (!siblings)
|
|
127
|
+
return false;
|
|
128
|
+
const nameRe = new RegExp(`\\b${escapeRegex(sym.name)}\\b`);
|
|
129
|
+
for (const other of siblings) {
|
|
130
|
+
if (other === sym)
|
|
131
|
+
continue;
|
|
132
|
+
if (nameRe.test(other.source))
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
function escapeRegex(s) {
|
|
138
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=dead-export.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dead-export.js","sourceRoot":"","sources":["../../src/detectors/dead-export.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Finding } from '../types.js';
|
|
2
|
+
import type { ImportRecord } from '../graph/import-graph.js';
|
|
3
|
+
export interface DeadHandlerDetectorInput {
|
|
4
|
+
/** All workspace-relative files parsed in this run. */
|
|
5
|
+
files: ReadonlyArray<string>;
|
|
6
|
+
/** Files referenced by an IaC construct's `entry`/`handler`/routes config. */
|
|
7
|
+
iacEntries: ReadonlySet<string>;
|
|
8
|
+
/** Import records — used to know if a file is statically/dynamically imported. */
|
|
9
|
+
imports: ReadonlyArray<ImportRecord>;
|
|
10
|
+
}
|
|
11
|
+
export declare function detectDeadHandlers(input: DeadHandlerDetectorInput): Finding[];
|
|
12
|
+
//# sourceMappingURL=dead-handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dead-handler.d.ts","sourceRoot":"","sources":["../../src/detectors/dead-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAI7D,MAAM,WAAW,wBAAwB;IACvC,uDAAuD;IACvD,KAAK,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC7B,8EAA8E;IAC9E,UAAU,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAChC,kFAAkF;IAClF,OAAO,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC;CACtC;AAKD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,wBAAwB,GAAG,OAAO,EAAE,CA+B7E"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { isHandlerConventionFile } from '../graph/handler-conventions.js';
|
|
2
|
+
import { stableHash } from '../utils/hash.js';
|
|
3
|
+
// Files in handler-convention dirs (src/handlers, src/lambdas, …) that are
|
|
4
|
+
// neither IaC-referenced nor imported anywhere. Confidence 0.65 (runtime
|
|
5
|
+
// config-file wiring is invisible to ts-morph).
|
|
6
|
+
export function detectDeadHandlers(input) {
|
|
7
|
+
const importedTargets = new Set();
|
|
8
|
+
for (const imp of input.imports) {
|
|
9
|
+
if (imp.target)
|
|
10
|
+
importedTargets.add(imp.target);
|
|
11
|
+
}
|
|
12
|
+
const findings = [];
|
|
13
|
+
for (const file of input.files) {
|
|
14
|
+
if (!isHandlerConventionFile(file))
|
|
15
|
+
continue;
|
|
16
|
+
if (input.iacEntries.has(file))
|
|
17
|
+
continue;
|
|
18
|
+
if (importedTargets.has(file))
|
|
19
|
+
continue;
|
|
20
|
+
findings.push({
|
|
21
|
+
detectorId: 'dead-handler',
|
|
22
|
+
severity: 'low',
|
|
23
|
+
confidence: 0.65,
|
|
24
|
+
layer: 1,
|
|
25
|
+
title: `Handler with no IaC wiring: ${file}`,
|
|
26
|
+
description: `\`${file}\` lives in a handler-convention directory but no CDK / SST / Serverless-framework construct references its path, and no other file imports it.\nLocations:\n- ${file} (entire file)`,
|
|
27
|
+
evidence: [
|
|
28
|
+
{
|
|
29
|
+
file,
|
|
30
|
+
range: { startLine: 1, endLine: 1 },
|
|
31
|
+
snippet: `// (no IaC reference and no inbound import for ${file})`,
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
suggestion: 'If this handler is wired by a non-TypeScript config (`serverless.yml`, `sam.template.yaml`, runtime path string), mark this finding as a false positive. Otherwise it is dead infrastructure — wire it or remove the file.',
|
|
35
|
+
fingerprint: `dead-handler:${stableHash(file)}`,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return findings;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=dead-handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dead-handler.js","sourceRoot":"","sources":["../../src/detectors/dead-handler.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;AAC1E,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAW9C,2EAA2E;AAC3E,yEAAyE;AACzE,gDAAgD;AAChD,MAAM,UAAU,kBAAkB,CAAC,KAA+B;IAChE,MAAM,eAAe,GAAG,IAAI,GAAG,EAAU,CAAC;IAC1C,KAAK,MAAM,GAAG,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QAChC,IAAI,GAAG,CAAC,MAAM;YAAE,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAClD,CAAC;IAED,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;QAC/B,IAAI,CAAC,uBAAuB,CAAC,IAAI,CAAC;YAAE,SAAS;QAC7C,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,SAAS;QACzC,IAAI,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,SAAS;QACxC,QAAQ,CAAC,IAAI,CAAC;YACZ,UAAU,EAAE,cAAc;YAC1B,QAAQ,EAAE,KAAK;YACf,UAAU,EAAE,IAAI;YAChB,KAAK,EAAE,CAAC;YACR,KAAK,EAAE,+BAA+B,IAAI,EAAE;YAC5C,WAAW,EAAE,KAAK,IAAI,kKAAkK,IAAI,gBAAgB;YAC5M,QAAQ,EAAE;gBACR;oBACE,IAAI;oBACJ,KAAK,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE;oBACnC,OAAO,EAAE,kDAAkD,IAAI,GAAG;iBACnE;aACF;YACD,UAAU,EACR,4NAA4N;YAC9N,WAAW,EAAE,gBAAgB,UAAU,CAAC,IAAI,CAAC,EAAE;SAChD,CAAC,CAAC;IACL,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Finding } from '../types.js';
|
|
2
|
+
import type { ImportGraph } from '../graph/import-graph.js';
|
|
3
|
+
export interface DeadModuleDetectorInput {
|
|
4
|
+
/** All workspace-relative files parsed in this run. */
|
|
5
|
+
files: ReadonlyArray<string>;
|
|
6
|
+
/** Import graph built from those files. */
|
|
7
|
+
graph: ImportGraph;
|
|
8
|
+
/** Set of entry-point files (workspace-relative). */
|
|
9
|
+
entryPoints: ReadonlySet<string>;
|
|
10
|
+
/** Files reachable from entry points via BFS. */
|
|
11
|
+
reachable: ReadonlySet<string>;
|
|
12
|
+
}
|
|
13
|
+
export declare function detectDeadModules(input: DeadModuleDetectorInput): Finding[];
|
|
14
|
+
//# sourceMappingURL=dead-module.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dead-module.d.ts","sourceRoot":"","sources":["../../src/detectors/dead-module.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAG5D,MAAM,WAAW,uBAAuB;IACtC,uDAAuD;IACvD,KAAK,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC7B,2CAA2C;IAC3C,KAAK,EAAE,WAAW,CAAC;IACnB,qDAAqD;IACrD,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACjC,iDAAiD;IACjD,SAAS,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CAChC;AAID,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,uBAAuB,GAAG,OAAO,EAAE,CA+B3E"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import * as path from 'node:path';
|
|
2
|
+
import { stableHash } from '../utils/hash.js';
|
|
3
|
+
// Files not reachable from any entry point + not themselves entry points
|
|
4
|
+
// + not .d.ts/ambient. LOW — framework conventions create FPs; mark as FP.
|
|
5
|
+
export function detectDeadModules(input) {
|
|
6
|
+
const findings = [];
|
|
7
|
+
for (const file of input.files) {
|
|
8
|
+
if (input.entryPoints.has(file))
|
|
9
|
+
continue;
|
|
10
|
+
if (input.reachable.has(file))
|
|
11
|
+
continue;
|
|
12
|
+
if (shouldExcludeFromDeadCheck(file))
|
|
13
|
+
continue;
|
|
14
|
+
findings.push({
|
|
15
|
+
detectorId: 'dead-module',
|
|
16
|
+
severity: 'low',
|
|
17
|
+
// 0.7 is the post-LLM-confirmation default; deterministic-only this would
|
|
18
|
+
// be higher (1.0 — the file is genuinely unimported) but the LLM is
|
|
19
|
+
// expected to drop borderline framework-handler files below the report
|
|
20
|
+
// threshold. We start in the middle so a no-LLM run still reports.
|
|
21
|
+
confidence: 0.7,
|
|
22
|
+
layer: 1,
|
|
23
|
+
title: `Unused module: ${file}`,
|
|
24
|
+
description: `\`${file}\` is not imported by any other workspace file and is not a known entry point.\nLocations:\n- ${file} (entire file)`,
|
|
25
|
+
evidence: [
|
|
26
|
+
{
|
|
27
|
+
file,
|
|
28
|
+
range: { startLine: 1, endLine: 1 },
|
|
29
|
+
snippet: `// (entire file ${file} appears to have no inbound imports)`,
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
suggestion: 'If this file is intentionally loaded by convention (framework route, dynamic import, build step), add an entry-point hint or mark this finding as a false positive. Otherwise delete it.',
|
|
33
|
+
fingerprint: `dead-module:${stableHash(file)}`,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return findings;
|
|
37
|
+
}
|
|
38
|
+
const ALWAYS_SKIP_PATTERNS = [
|
|
39
|
+
/\.d\.ts$/, // ambient declarations — consumed via tsconfig, no import edge
|
|
40
|
+
/(^|\/)global\.d\.ts$/,
|
|
41
|
+
/(^|\/)vite-env\.d\.ts$/,
|
|
42
|
+
/(^|\/)next-env\.d\.ts$/,
|
|
43
|
+
/\.story\.tsx?$/,
|
|
44
|
+
/\.stories\.tsx?$/,
|
|
45
|
+
];
|
|
46
|
+
function shouldExcludeFromDeadCheck(file) {
|
|
47
|
+
const posix = file.split(path.sep).join('/');
|
|
48
|
+
return ALWAYS_SKIP_PATTERNS.some((re) => re.test(posix));
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=dead-module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dead-module.js","sourceRoot":"","sources":["../../src/detectors/dead-module.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAGlC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAa9C,yEAAyE;AACzE,2EAA2E;AAC3E,MAAM,UAAU,iBAAiB,CAAC,KAA8B;IAC9D,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;QAC/B,IAAI,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,SAAS;QAC1C,IAAI,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,SAAS;QACxC,IAAI,0BAA0B,CAAC,IAAI,CAAC;YAAE,SAAS;QAE/C,QAAQ,CAAC,IAAI,CAAC;YACZ,UAAU,EAAE,aAAa;YACzB,QAAQ,EAAE,KAAK;YACf,0EAA0E;YAC1E,oEAAoE;YACpE,uEAAuE;YACvE,mEAAmE;YACnE,UAAU,EAAE,GAAG;YACf,KAAK,EAAE,CAAC;YACR,KAAK,EAAE,kBAAkB,IAAI,EAAE;YAC/B,WAAW,EAAE,KAAK,IAAI,iGAAiG,IAAI,gBAAgB;YAC3I,QAAQ,EAAE;gBACR;oBACE,IAAI;oBACJ,KAAK,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE;oBACnC,OAAO,EAAE,mBAAmB,IAAI,sCAAsC;iBACvE;aACF;YACD,UAAU,EACR,0LAA0L;YAC5L,WAAW,EAAE,eAAe,UAAU,CAAC,IAAI,CAAC,EAAE;SAC/C,CAAC,CAAC;IACL,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,oBAAoB,GAAa;IACrC,UAAU,EAAE,+DAA+D;IAC3E,sBAAsB;IACtB,wBAAwB;IACxB,wBAAwB;IACxB,gBAAgB;IAChB,kBAAkB;CACnB,CAAC;AAEF,SAAS,0BAA0B,CAAC,IAAY;IAC9C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC7C,OAAO,oBAAoB,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;AAC3D,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Finding, SymbolRecord } from '../types.js';
|
|
2
|
+
export interface DeepNestingDetectorInput {
|
|
3
|
+
symbols: ReadonlyArray<SymbolRecord>;
|
|
4
|
+
/** Nesting depth that triggers LOW. Default 4. */
|
|
5
|
+
lowThreshold?: number;
|
|
6
|
+
/** Nesting depth that triggers MED. Default 5. */
|
|
7
|
+
medThreshold?: number;
|
|
8
|
+
/** Nesting depth that triggers HIGH. Default 6. */
|
|
9
|
+
highThreshold?: number;
|
|
10
|
+
}
|
|
11
|
+
export declare function detectDeepNesting(input: DeepNestingDetectorInput): Finding[];
|
|
12
|
+
//# sourceMappingURL=deep-nesting.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deep-nesting.d.ts","sourceRoot":"","sources":["../../src/detectors/deep-nesting.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGzD,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC;IACrC,kDAAkD;IAClD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kDAAkD;IAClD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mDAAmD;IACnD,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAID,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,wBAAwB,GAAG,OAAO,EAAE,CA+B5E"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { stableHash } from '../utils/hash.js';
|
|
2
|
+
// Max if/for/while/try/switch depth per function via regex single-pass.
|
|
3
|
+
// LOW ≥4, MED ≥5, HIGH ≥6.
|
|
4
|
+
export function detectDeepNesting(input) {
|
|
5
|
+
const low = input.lowThreshold ?? 4;
|
|
6
|
+
const med = input.medThreshold ?? 5;
|
|
7
|
+
const high = input.highThreshold ?? 6;
|
|
8
|
+
const findings = [];
|
|
9
|
+
for (const sym of input.symbols) {
|
|
10
|
+
if (sym.kind !== 'function')
|
|
11
|
+
continue;
|
|
12
|
+
const depth = maxNestingDepth(sym.source);
|
|
13
|
+
if (depth < low)
|
|
14
|
+
continue;
|
|
15
|
+
const severity = depth >= high ? 'high' : depth >= med ? 'medium' : 'low';
|
|
16
|
+
findings.push({
|
|
17
|
+
detectorId: 'deep-nesting',
|
|
18
|
+
severity,
|
|
19
|
+
confidence: 0.85,
|
|
20
|
+
layer: 1,
|
|
21
|
+
title: `Deeply nested function: \`${sym.name}\` (depth ${depth}) in ${sym.file}`,
|
|
22
|
+
description: `\`${sym.name}\` reaches ${depth} levels of nested control flow. Past depth 4 the reader has to track too many active conditions at once.`,
|
|
23
|
+
evidence: [
|
|
24
|
+
{
|
|
25
|
+
file: sym.file,
|
|
26
|
+
range: { startLine: sym.range.startLine, endLine: sym.range.endLine },
|
|
27
|
+
snippet: sym.source.split('\n').slice(0, 4).join('\n'),
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
suggestion: 'Invert the deepest condition with an early return ("guard clause"). Replace nested if/else trees with a discriminated dispatch table. Extract inner loops into named helpers.',
|
|
31
|
+
fingerprint: `deep-nesting:${stableHash(`${sym.file}:${sym.name}:${sym.range.startLine}`)}`,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return findings;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Counts the maximum depth of control-flow nesting in a function body.
|
|
38
|
+
* Walks the source once, ignoring strings/comments, and tracks the
|
|
39
|
+
* brace stack alongside the most recent keyword introducing each block.
|
|
40
|
+
*
|
|
41
|
+
* Only `if`/`for`/`while`/`switch`/`try`/`catch` keyword-introduced
|
|
42
|
+
* braces count toward depth — plain object literals and function-bodies
|
|
43
|
+
* don't inflate the metric.
|
|
44
|
+
*/
|
|
45
|
+
function maxNestingDepth(source) {
|
|
46
|
+
const masked = maskStringsAndComments(source);
|
|
47
|
+
let depth = 0;
|
|
48
|
+
let max = 0;
|
|
49
|
+
const stack = [];
|
|
50
|
+
// Use a sliding 16-char lookbehind for the most recent keyword.
|
|
51
|
+
for (let i = 0; i < masked.length; i++) {
|
|
52
|
+
const c = masked[i];
|
|
53
|
+
if (c === '{') {
|
|
54
|
+
const before = masked.slice(Math.max(0, i - 20), i);
|
|
55
|
+
const isControl = /\b(?:if|else|for|while|switch|try|catch|finally|do)\b\s*(?:\([^)]*\)\s*)?$/.test(before);
|
|
56
|
+
stack.push(isControl ? 'control' : 'other');
|
|
57
|
+
if (isControl) {
|
|
58
|
+
depth++;
|
|
59
|
+
if (depth > max)
|
|
60
|
+
max = depth;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
else if (c === '}') {
|
|
64
|
+
const popped = stack.pop();
|
|
65
|
+
if (popped === 'control' && depth > 0)
|
|
66
|
+
depth--;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return max;
|
|
70
|
+
}
|
|
71
|
+
function maskStringsAndComments(raw) {
|
|
72
|
+
let out = '';
|
|
73
|
+
let inString = null;
|
|
74
|
+
let inLineComment = false;
|
|
75
|
+
let inBlockComment = false;
|
|
76
|
+
for (let i = 0; i < raw.length; i++) {
|
|
77
|
+
const c = raw[i];
|
|
78
|
+
const next = raw[i + 1];
|
|
79
|
+
if (inLineComment) {
|
|
80
|
+
if (c === '\n') {
|
|
81
|
+
inLineComment = false;
|
|
82
|
+
out += '\n';
|
|
83
|
+
}
|
|
84
|
+
else
|
|
85
|
+
out += ' ';
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (inBlockComment) {
|
|
89
|
+
if (c === '*' && next === '/') {
|
|
90
|
+
inBlockComment = false;
|
|
91
|
+
out += ' ';
|
|
92
|
+
i++;
|
|
93
|
+
}
|
|
94
|
+
else
|
|
95
|
+
out += c === '\n' ? '\n' : ' ';
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (inString) {
|
|
99
|
+
if (c === '\\') {
|
|
100
|
+
out += ' ';
|
|
101
|
+
i++;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (c === inString) {
|
|
105
|
+
inString = null;
|
|
106
|
+
out += c;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
out += c === '\n' ? '\n' : ' ';
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (c === '/' && next === '/') {
|
|
113
|
+
inLineComment = true;
|
|
114
|
+
out += ' ';
|
|
115
|
+
i++;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (c === '/' && next === '*') {
|
|
119
|
+
inBlockComment = true;
|
|
120
|
+
out += ' ';
|
|
121
|
+
i++;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (c === '"' || c === "'" || c === '`') {
|
|
125
|
+
inString = c;
|
|
126
|
+
out += c;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
out += c;
|
|
130
|
+
}
|
|
131
|
+
return out;
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=deep-nesting.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deep-nesting.js","sourceRoot":"","sources":["../../src/detectors/deep-nesting.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Detector, Finding, SymbolRecord } from '../types.js';
|
|
2
|
+
export declare class DuplicateFunctionDetector implements Detector {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
run(symbols: SymbolRecord[]): Promise<Finding[]>;
|
|
6
|
+
private toFinding;
|
|
7
|
+
private buildDescription;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=duplicate-function.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"duplicate-function.d.ts","sourceRoot":"","sources":["../../src/detectors/duplicate-function.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAqB,YAAY,EAAE,MAAM,aAAa,CAAC;AAwBtF,qBAAa,yBAA0B,YAAW,QAAQ;IACxD,EAAE,SAAwB;IAC1B,IAAI,SAAiC;IAE/B,GAAG,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IAoHtD,OAAO,CAAC,SAAS;IAmCjB,OAAO,CAAC,gBAAgB;CAgBzB"}
|