@shvmgyl15/tsgraph 0.1.0
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/AGENTS.md +64 -0
- package/README.md +128 -0
- package/TODOS.md +61 -0
- package/dist/analysis/analysis.test.d.ts +2 -0
- package/dist/analysis/analysis.test.d.ts.map +1 -0
- package/dist/analysis/analysis.test.js +359 -0
- package/dist/analysis/analysis.test.js.map +1 -0
- package/dist/analysis/complexity.d.ts +8 -0
- package/dist/analysis/complexity.d.ts.map +1 -0
- package/dist/analysis/complexity.js +88 -0
- package/dist/analysis/complexity.js.map +1 -0
- package/dist/analysis/coupling.d.ts +17 -0
- package/dist/analysis/coupling.d.ts.map +1 -0
- package/dist/analysis/coupling.js +71 -0
- package/dist/analysis/coupling.js.map +1 -0
- package/dist/analysis/hotspot.d.ts +10 -0
- package/dist/analysis/hotspot.d.ts.map +1 -0
- package/dist/analysis/hotspot.js +33 -0
- package/dist/analysis/hotspot.js.map +1 -0
- package/dist/analysis/index.d.ts +9 -0
- package/dist/analysis/index.d.ts.map +1 -0
- package/dist/analysis/index.js +5 -0
- package/dist/analysis/index.js.map +1 -0
- package/dist/boundaries/index.d.ts +25 -0
- package/dist/boundaries/index.d.ts.map +1 -0
- package/dist/boundaries/index.js +103 -0
- package/dist/boundaries/index.js.map +1 -0
- package/dist/boundaries/index.test.d.ts +2 -0
- package/dist/boundaries/index.test.d.ts.map +1 -0
- package/dist/boundaries/index.test.js +293 -0
- package/dist/boundaries/index.test.js.map +1 -0
- package/dist/changes/index.d.ts +28 -0
- package/dist/changes/index.d.ts.map +1 -0
- package/dist/changes/index.js +48 -0
- package/dist/changes/index.js.map +1 -0
- package/dist/changes/index.test.d.ts +2 -0
- package/dist/changes/index.test.d.ts.map +1 -0
- package/dist/changes/index.test.js +104 -0
- package/dist/changes/index.test.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +659 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/git/index.d.ts +16 -0
- package/dist/git/index.d.ts.map +1 -0
- package/dist/git/index.js +73 -0
- package/dist/git/index.js.map +1 -0
- package/dist/git/index.test.d.ts +2 -0
- package/dist/git/index.test.d.ts.map +1 -0
- package/dist/git/index.test.js +78 -0
- package/dist/git/index.test.js.map +1 -0
- package/dist/graph/types.d.ts +156 -0
- package/dist/graph/types.d.ts.map +1 -0
- package/dist/graph/types.js +166 -0
- package/dist/graph/types.js.map +1 -0
- package/dist/graph/types.test.d.ts +2 -0
- package/dist/graph/types.test.d.ts.map +1 -0
- package/dist/graph/types.test.js +326 -0
- package/dist/graph/types.test.js.map +1 -0
- package/dist/mcp/mcp.test.d.ts +2 -0
- package/dist/mcp/mcp.test.d.ts.map +1 -0
- package/dist/mcp/mcp.test.js +151 -0
- package/dist/mcp/mcp.test.js.map +1 -0
- package/dist/mcp/server.d.ts +2 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +209 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/nextjs/index.d.ts +8 -0
- package/dist/nextjs/index.d.ts.map +1 -0
- package/dist/nextjs/index.js +16 -0
- package/dist/nextjs/index.js.map +1 -0
- package/dist/nextjs/nextjs.test.d.ts +2 -0
- package/dist/nextjs/nextjs.test.d.ts.map +1 -0
- package/dist/nextjs/nextjs.test.js +190 -0
- package/dist/nextjs/nextjs.test.js.map +1 -0
- package/dist/nextjs/pages.d.ts +4 -0
- package/dist/nextjs/pages.d.ts.map +1 -0
- package/dist/nextjs/pages.js +36 -0
- package/dist/nextjs/pages.js.map +1 -0
- package/dist/nextjs/react.d.ts +3 -0
- package/dist/nextjs/react.d.ts.map +1 -0
- package/dist/nextjs/react.js +86 -0
- package/dist/nextjs/react.js.map +1 -0
- package/dist/nextjs/router.d.ts +4 -0
- package/dist/nextjs/router.d.ts.map +1 -0
- package/dist/nextjs/router.js +86 -0
- package/dist/nextjs/router.js.map +1 -0
- package/dist/nextjs/routes.d.ts +4 -0
- package/dist/nextjs/routes.d.ts.map +1 -0
- package/dist/nextjs/routes.js +58 -0
- package/dist/nextjs/routes.js.map +1 -0
- package/dist/opencode/index.d.ts +7 -0
- package/dist/opencode/index.d.ts.map +1 -0
- package/dist/opencode/index.js +71 -0
- package/dist/opencode/index.js.map +1 -0
- package/dist/opencode/index.test.d.ts +2 -0
- package/dist/opencode/index.test.d.ts.map +1 -0
- package/dist/opencode/index.test.js +71 -0
- package/dist/opencode/index.test.js.map +1 -0
- package/dist/parser/index.d.ts +4 -0
- package/dist/parser/index.d.ts.map +1 -0
- package/dist/parser/index.js +282 -0
- package/dist/parser/index.js.map +1 -0
- package/dist/parser/parser.test.d.ts +2 -0
- package/dist/parser/parser.test.d.ts.map +1 -0
- package/dist/parser/parser.test.js +225 -0
- package/dist/parser/parser.test.js.map +1 -0
- package/dist/plan/index.d.ts +32 -0
- package/dist/plan/index.d.ts.map +1 -0
- package/dist/plan/index.js +107 -0
- package/dist/plan/index.js.map +1 -0
- package/dist/plan/index.test.d.ts +2 -0
- package/dist/plan/index.test.d.ts.map +1 -0
- package/dist/plan/index.test.js +143 -0
- package/dist/plan/index.test.js.map +1 -0
- package/dist/report/index.d.ts +9 -0
- package/dist/report/index.d.ts.map +1 -0
- package/dist/report/index.js +108 -0
- package/dist/report/index.js.map +1 -0
- package/dist/scanner/index.d.ts +13 -0
- package/dist/scanner/index.d.ts.map +1 -0
- package/dist/scanner/index.js +78 -0
- package/dist/scanner/index.js.map +1 -0
- package/dist/scanner/scanner.test.d.ts +2 -0
- package/dist/scanner/scanner.test.d.ts.map +1 -0
- package/dist/scanner/scanner.test.js +113 -0
- package/dist/scanner/scanner.test.js.map +1 -0
- package/dist/search/index.d.ts +32 -0
- package/dist/search/index.d.ts.map +1 -0
- package/dist/search/index.js +97 -0
- package/dist/search/index.js.map +1 -0
- package/dist/search/search.test.d.ts +2 -0
- package/dist/search/search.test.d.ts.map +1 -0
- package/dist/search/search.test.js +446 -0
- package/dist/search/search.test.js.map +1 -0
- package/dist/traversal/index.d.ts +5 -0
- package/dist/traversal/index.d.ts.map +1 -0
- package/dist/traversal/index.js +3 -0
- package/dist/traversal/index.js.map +1 -0
- package/dist/traversal/traversal.d.ts +31 -0
- package/dist/traversal/traversal.d.ts.map +1 -0
- package/dist/traversal/traversal.js +130 -0
- package/dist/traversal/traversal.js.map +1 -0
- package/dist/traversal/traversal.test.d.ts +2 -0
- package/dist/traversal/traversal.test.d.ts.map +1 -0
- package/dist/traversal/traversal.test.js +224 -0
- package/dist/traversal/traversal.test.js.map +1 -0
- package/opencode.json +24 -0
- package/package.json +29 -0
- package/src/analysis/analysis.test.ts +405 -0
- package/src/analysis/complexity.ts +107 -0
- package/src/analysis/coupling.ts +106 -0
- package/src/analysis/hotspot.ts +52 -0
- package/src/analysis/index.ts +17 -0
- package/src/boundaries/index.test.ts +335 -0
- package/src/boundaries/index.ts +137 -0
- package/src/changes/index.test.ts +114 -0
- package/src/changes/index.ts +95 -0
- package/src/cli/index.ts +736 -0
- package/src/git/index.test.ts +92 -0
- package/src/git/index.ts +86 -0
- package/src/graph/types.test.ts +383 -0
- package/src/graph/types.ts +353 -0
- package/src/mcp/mcp.test.ts +176 -0
- package/src/mcp/server.ts +217 -0
- package/src/nextjs/index.ts +23 -0
- package/src/nextjs/nextjs.test.ts +233 -0
- package/src/nextjs/pages.ts +43 -0
- package/src/nextjs/react.ts +100 -0
- package/src/nextjs/router.ts +102 -0
- package/src/nextjs/routes.ts +69 -0
- package/src/opencode/index.test.ts +90 -0
- package/src/opencode/index.ts +83 -0
- package/src/parser/index.ts +339 -0
- package/src/parser/parser.test.ts +282 -0
- package/src/plan/index.test.ts +162 -0
- package/src/plan/index.ts +161 -0
- package/src/report/index.ts +128 -0
- package/src/scanner/index.ts +97 -0
- package/src/scanner/scanner.test.ts +135 -0
- package/src/search/index.ts +163 -0
- package/src/search/search.test.ts +512 -0
- package/src/traversal/index.ts +5 -0
- package/src/traversal/traversal.test.ts +266 -0
- package/src/traversal/traversal.ts +185 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +7 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
const DECISION_PATTERNS = [
|
|
4
|
+
{ regex: /\bif\s*\(/g, label: "if" },
|
|
5
|
+
{ regex: /\belse\s+if\s*\(/g, label: "elseif" },
|
|
6
|
+
{ regex: /\bfor\s*\(/g, label: "for" },
|
|
7
|
+
{ regex: /\bwhile\s*\(/g, label: "while" },
|
|
8
|
+
{ regex: /\bdo\s*\{/g, label: "do" },
|
|
9
|
+
{ regex: /\bcatch\s*\(/g, label: "catch" },
|
|
10
|
+
{ regex: /\bcase\s+/g, label: "case" },
|
|
11
|
+
{ regex: /\|\|/g, label: "or" },
|
|
12
|
+
{ regex: /&&/g, label: "and" },
|
|
13
|
+
];
|
|
14
|
+
function stripComments(code) {
|
|
15
|
+
return code
|
|
16
|
+
.replace(/\/\/.*$/gm, "")
|
|
17
|
+
.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
18
|
+
}
|
|
19
|
+
function stripStrings(code) {
|
|
20
|
+
const inString = (s) => {
|
|
21
|
+
let result = "";
|
|
22
|
+
let i = 0;
|
|
23
|
+
while (i < s.length) {
|
|
24
|
+
if (s[i] === '"' || s[i] === "'" || s[i] === "`") {
|
|
25
|
+
const quote = s[i];
|
|
26
|
+
i++;
|
|
27
|
+
while (i < s.length && s[i] !== quote) {
|
|
28
|
+
if (s[i] === "\\")
|
|
29
|
+
i++;
|
|
30
|
+
i++;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
result += s[i];
|
|
35
|
+
}
|
|
36
|
+
i++;
|
|
37
|
+
}
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
return inString(code);
|
|
41
|
+
}
|
|
42
|
+
export function cyclomaticComplexity(sourceCode) {
|
|
43
|
+
const cleaned = stripStrings(stripComments(sourceCode));
|
|
44
|
+
let decisions = 0;
|
|
45
|
+
for (const dp of DECISION_PATTERNS) {
|
|
46
|
+
const matches = cleaned.match(dp.regex);
|
|
47
|
+
if (matches)
|
|
48
|
+
decisions += matches.length;
|
|
49
|
+
}
|
|
50
|
+
const elseIfCount = (cleaned.match(/\belse\s+if\s*\(/g) || []).length;
|
|
51
|
+
decisions -= elseIfCount;
|
|
52
|
+
const ternaryMatches = cleaned.match(/\?/g);
|
|
53
|
+
if (ternaryMatches) {
|
|
54
|
+
for (const m of ternaryMatches) {
|
|
55
|
+
const idx = cleaned.indexOf(m);
|
|
56
|
+
const before = cleaned[idx - 1];
|
|
57
|
+
if (before !== "?" && before !== "!" && before !== "=") {
|
|
58
|
+
decisions++;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return Math.max(1, 1 + decisions);
|
|
63
|
+
}
|
|
64
|
+
export function analyzeComplexity(graph, fileFilter) {
|
|
65
|
+
const results = [];
|
|
66
|
+
const root = graph.root;
|
|
67
|
+
for (const sym of graph.symbols) {
|
|
68
|
+
if (fileFilter && !sym.file.includes(fileFilter))
|
|
69
|
+
continue;
|
|
70
|
+
if (sym.kind !== "function" && sym.kind !== "method")
|
|
71
|
+
continue;
|
|
72
|
+
const filePath = path.join(root, sym.file);
|
|
73
|
+
try {
|
|
74
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
75
|
+
const lines = content.split("\n");
|
|
76
|
+
const start = Math.max(0, sym.line - 1);
|
|
77
|
+
const end = Math.min(lines.length, sym.endLine);
|
|
78
|
+
const snippet = lines.slice(start, end).join("\n");
|
|
79
|
+
const complexity = cyclomaticComplexity(snippet);
|
|
80
|
+
results.push({ symbol: sym, complexity });
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return results;
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=complexity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"complexity.js","sourceRoot":"","sources":["../../src/analysis/complexity.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Graph } from "../graph/types.js";
|
|
2
|
+
export interface CouplingResult {
|
|
3
|
+
packageName: string;
|
|
4
|
+
coupledTo: string;
|
|
5
|
+
importCount: number;
|
|
6
|
+
fileCount: number;
|
|
7
|
+
}
|
|
8
|
+
export interface DepsNode {
|
|
9
|
+
name: string;
|
|
10
|
+
kind: string;
|
|
11
|
+
file: string;
|
|
12
|
+
line: number;
|
|
13
|
+
children: DepsNode[];
|
|
14
|
+
}
|
|
15
|
+
export declare function analyzeCoupling(graph: Graph): CouplingResult[];
|
|
16
|
+
export declare function dependencyTree(graph: Graph, symbolName: string, maxDepth?: number): DepsNode | undefined;
|
|
17
|
+
//# sourceMappingURL=coupling.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coupling.d.ts","sourceRoot":"","sources":["../../src/analysis/coupling.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAA0B,MAAM,mBAAmB,CAAC;AAEvE,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,QAAQ,EAAE,CAAC;CACtB;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,KAAK,GAAG,cAAc,EAAE,CAkC9D;AAED,wBAAgB,cAAc,CAC5B,KAAK,EAAE,KAAK,EACZ,UAAU,EAAE,MAAM,EAClB,QAAQ,GAAE,MAAU,GACnB,QAAQ,GAAG,SAAS,CAgDtB"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
export function analyzeCoupling(graph) {
|
|
2
|
+
const pkgFiles = new Map();
|
|
3
|
+
for (const f of graph.files) {
|
|
4
|
+
const files = pkgFiles.get(f.packageName) ?? new Set();
|
|
5
|
+
files.add(f.path);
|
|
6
|
+
pkgFiles.set(f.packageName, files);
|
|
7
|
+
}
|
|
8
|
+
const pkgImportTargets = new Map();
|
|
9
|
+
for (const imp of graph.imports) {
|
|
10
|
+
const target = imp.importPath.split("/")[0];
|
|
11
|
+
const byPkg = pkgImportTargets.get(imp.fromPackage) ?? new Map();
|
|
12
|
+
const entry = byPkg.get(target) ?? { files: new Set(), count: 0 };
|
|
13
|
+
entry.files.add(imp.fromFile);
|
|
14
|
+
entry.count++;
|
|
15
|
+
byPkg.set(target, entry);
|
|
16
|
+
pkgImportTargets.set(imp.fromPackage, byPkg);
|
|
17
|
+
}
|
|
18
|
+
const results = [];
|
|
19
|
+
for (const [pkg, targets] of pkgImportTargets) {
|
|
20
|
+
for (const [target, entry] of targets) {
|
|
21
|
+
if (pkg === target)
|
|
22
|
+
continue;
|
|
23
|
+
results.push({
|
|
24
|
+
packageName: pkg,
|
|
25
|
+
coupledTo: target,
|
|
26
|
+
importCount: entry.count,
|
|
27
|
+
fileCount: entry.files.size,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
results.sort((a, b) => b.importCount - a.importCount);
|
|
32
|
+
return results;
|
|
33
|
+
}
|
|
34
|
+
export function dependencyTree(graph, symbolName, maxDepth = 3) {
|
|
35
|
+
const sym = graph.symbols.find((s) => s.name === symbolName);
|
|
36
|
+
if (!sym)
|
|
37
|
+
return undefined;
|
|
38
|
+
function buildTree(current, depth, visited) {
|
|
39
|
+
const node = {
|
|
40
|
+
name: current.name,
|
|
41
|
+
kind: current.kind,
|
|
42
|
+
file: current.file,
|
|
43
|
+
line: current.line,
|
|
44
|
+
children: [],
|
|
45
|
+
};
|
|
46
|
+
if (depth >= maxDepth)
|
|
47
|
+
return node;
|
|
48
|
+
const calleeEdges = graph.calls.filter((c) => c.callerSymbolId === current.id);
|
|
49
|
+
for (const edge of calleeEdges) {
|
|
50
|
+
if (visited.has(edge.calleeRaw))
|
|
51
|
+
continue;
|
|
52
|
+
visited.add(edge.calleeRaw);
|
|
53
|
+
const calleeSym = graph.symbols.find((s) => s.name === edge.calleeRaw && s.file === edge.file);
|
|
54
|
+
if (calleeSym) {
|
|
55
|
+
node.children.push(buildTree(calleeSym, depth + 1, visited));
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
node.children.push({
|
|
59
|
+
name: edge.calleeRaw,
|
|
60
|
+
kind: "unknown",
|
|
61
|
+
file: edge.file,
|
|
62
|
+
line: edge.line,
|
|
63
|
+
children: [],
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return node;
|
|
68
|
+
}
|
|
69
|
+
return buildTree(sym, 0, new Set([symbolName]));
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=coupling.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coupling.js","sourceRoot":"","sources":["../../src/analysis/coupling.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Graph } from "../graph/types.js";
|
|
2
|
+
export interface HotspotResult {
|
|
3
|
+
file: string;
|
|
4
|
+
score: number;
|
|
5
|
+
symbolCount: number;
|
|
6
|
+
totalComplexity: number;
|
|
7
|
+
lines: number;
|
|
8
|
+
}
|
|
9
|
+
export declare function findHotspots(graph: Graph, topN?: number): HotspotResult[];
|
|
10
|
+
//# sourceMappingURL=hotspot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hotspot.d.ts","sourceRoot":"","sources":["../../src/analysis/hotspot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAG/C,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,YAAY,CAC1B,KAAK,EAAE,KAAK,EACZ,IAAI,GAAE,MAAW,GAChB,aAAa,EAAE,CAqCjB"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { analyzeComplexity } from "./complexity.js";
|
|
2
|
+
export function findHotspots(graph, topN = 10) {
|
|
3
|
+
const fileNodes = new Map(graph.files.map((f) => [f.path, f]));
|
|
4
|
+
const complexityResults = analyzeComplexity(graph);
|
|
5
|
+
const fileScores = new Map();
|
|
6
|
+
for (const r of complexityResults) {
|
|
7
|
+
const entry = fileScores.get(r.symbol.file) ?? {
|
|
8
|
+
totalComplexity: 0,
|
|
9
|
+
symbolCount: 0,
|
|
10
|
+
lines: 1,
|
|
11
|
+
};
|
|
12
|
+
entry.totalComplexity += r.complexity;
|
|
13
|
+
entry.symbolCount++;
|
|
14
|
+
fileScores.set(r.symbol.file, entry);
|
|
15
|
+
}
|
|
16
|
+
for (const [filePath, entry] of fileScores) {
|
|
17
|
+
const fn = fileNodes.get(filePath);
|
|
18
|
+
entry.lines = fn?.lines ?? 1;
|
|
19
|
+
}
|
|
20
|
+
const results = [];
|
|
21
|
+
for (const [file, data] of fileScores) {
|
|
22
|
+
results.push({
|
|
23
|
+
file,
|
|
24
|
+
score: data.totalComplexity * data.lines,
|
|
25
|
+
symbolCount: data.symbolCount,
|
|
26
|
+
totalComplexity: data.totalComplexity,
|
|
27
|
+
lines: data.lines,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
results.sort((a, b) => b.score - a.score);
|
|
31
|
+
return results.slice(0, topN);
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=hotspot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hotspot.js","sourceRoot":"","sources":["../../src/analysis/hotspot.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAUpD,MAAM,UAAU,YAAY,CAC1B,KAAY,EACZ,OAAe,EAAE;IAEjB,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/D,MAAM,iBAAiB,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC;IACnD,MAAM,UAAU,GAAG,IAAI,GAAG,EAGvB,CAAC;IAEJ,KAAK,MAAM,CAAC,IAAI,iBAAiB,EAAE,CAAC;QAClC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI;YAC7C,eAAe,EAAE,CAAC;YAClB,WAAW,EAAE,CAAC;YACd,KAAK,EAAE,CAAC;SACT,CAAC;QACF,KAAK,CAAC,eAAe,IAAI,CAAC,CAAC,UAAU,CAAC;QACtC,KAAK,CAAC,WAAW,EAAE,CAAC;QACpB,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACvC,CAAC;IAED,KAAK,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,UAAU,EAAE,CAAC;QAC3C,MAAM,EAAE,GAAG,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACnC,KAAK,CAAC,KAAK,GAAG,EAAE,EAAE,KAAK,IAAI,CAAC,CAAC;IAC/B,CAAC;IAED,MAAM,OAAO,GAAoB,EAAE,CAAC;IACpC,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,UAAU,EAAE,CAAC;QACtC,OAAO,CAAC,IAAI,CAAC;YACX,IAAI;YACJ,KAAK,EAAE,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,KAAK;YACxC,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,eAAe,EAAE,IAAI,CAAC,eAAe;YACrC,KAAK,EAAE,IAAI,CAAC,KAAK;SAClB,CAAC,CAAC;IACL,CAAC;IAED,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;IAC1C,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;AAChC,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { analyzeComplexity, cyclomaticComplexity } from "./complexity.js";
|
|
2
|
+
import type { ComplexityResult } from "./complexity.js";
|
|
3
|
+
import { findHotspots } from "./hotspot.js";
|
|
4
|
+
import type { HotspotResult } from "./hotspot.js";
|
|
5
|
+
import { analyzeCoupling, dependencyTree } from "./coupling.js";
|
|
6
|
+
import type { CouplingResult, DepsNode } from "./coupling.js";
|
|
7
|
+
export { analyzeComplexity, cyclomaticComplexity, findHotspots, analyzeCoupling, dependencyTree, };
|
|
8
|
+
export type { ComplexityResult, HotspotResult, CouplingResult, DepsNode };
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/analysis/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAC1E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAChE,OAAO,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9D,OAAO,EACL,iBAAiB,EACjB,oBAAoB,EACpB,YAAY,EACZ,eAAe,EACf,cAAc,GACf,CAAC;AAEF,YAAY,EAAE,gBAAgB,EAAE,aAAa,EAAE,cAAc,EAAE,QAAQ,EAAE,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { analyzeComplexity, cyclomaticComplexity } from "./complexity.js";
|
|
2
|
+
import { findHotspots } from "./hotspot.js";
|
|
3
|
+
import { analyzeCoupling, dependencyTree } from "./coupling.js";
|
|
4
|
+
export { analyzeComplexity, cyclomaticComplexity, findHotspots, analyzeCoupling, dependencyTree, };
|
|
5
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/analysis/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAE1E,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAGhE,OAAO,EACL,iBAAiB,EACjB,oBAAoB,EACpB,YAAY,EACZ,eAAe,EACf,cAAc,GACf,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Graph } from "../graph/types.js";
|
|
2
|
+
export interface LayerConfig {
|
|
3
|
+
name: string;
|
|
4
|
+
path: string;
|
|
5
|
+
dependsOn: string[];
|
|
6
|
+
}
|
|
7
|
+
export interface BoundariesConfig {
|
|
8
|
+
layers: LayerConfig[];
|
|
9
|
+
}
|
|
10
|
+
export interface BoundaryViolation {
|
|
11
|
+
fromFile: string;
|
|
12
|
+
fromLayer: string;
|
|
13
|
+
toFile: string;
|
|
14
|
+
toLayer: string;
|
|
15
|
+
toPackage: string;
|
|
16
|
+
rule: string;
|
|
17
|
+
}
|
|
18
|
+
export interface BoundaryResult {
|
|
19
|
+
violations: BoundaryViolation[];
|
|
20
|
+
allowed: number;
|
|
21
|
+
config: BoundariesConfig;
|
|
22
|
+
}
|
|
23
|
+
export declare function loadBoundariesConfig(rootDir: string): BoundariesConfig | null;
|
|
24
|
+
export declare function checkBoundaries(graph: Graph, config: BoundariesConfig): BoundaryResult;
|
|
25
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/boundaries/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAK/C,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,WAAW,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,iBAAiB,EAAE,CAAC;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,gBAAgB,CAAC;CAC1B;AAYD,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAW7E;AAmCD,wBAAgB,eAAe,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,gBAAgB,GAAG,cAAc,CAkDtF"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import { z } from "zod/v4";
|
|
4
|
+
const layerConfigSchema = z.object({
|
|
5
|
+
name: z.string(),
|
|
6
|
+
path: z.string(),
|
|
7
|
+
dependsOn: z.array(z.string()),
|
|
8
|
+
});
|
|
9
|
+
const boundariesConfigSchema = z.object({
|
|
10
|
+
layers: z.array(layerConfigSchema),
|
|
11
|
+
});
|
|
12
|
+
export function loadBoundariesConfig(rootDir) {
|
|
13
|
+
const configPath = path.join(rootDir, ".tsgraph", "boundaries.json");
|
|
14
|
+
try {
|
|
15
|
+
const raw = fs.readFileSync(configPath, "utf-8");
|
|
16
|
+
const parsed = JSON.parse(raw);
|
|
17
|
+
const result = boundariesConfigSchema.safeParse(parsed);
|
|
18
|
+
if (!result.success)
|
|
19
|
+
return null;
|
|
20
|
+
return result.data;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const EXTENSIONS = [".ts", ".tsx", ".js", ".jsx"];
|
|
27
|
+
const INDEX_PATTERNS = ["/index", ""];
|
|
28
|
+
function findLayerByPath(filePath, config) {
|
|
29
|
+
return config.layers.find((layer) => filePath.startsWith(layer.path));
|
|
30
|
+
}
|
|
31
|
+
function resolveTargetPath(fromFile, importPath) {
|
|
32
|
+
if (importPath.startsWith(".")) {
|
|
33
|
+
const dir = path.posix.dirname(fromFile);
|
|
34
|
+
return path.posix.normalize(path.posix.join(dir, importPath));
|
|
35
|
+
}
|
|
36
|
+
if (importPath.startsWith("/")) {
|
|
37
|
+
return path.posix.normalize(importPath);
|
|
38
|
+
}
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
function matchFile(resolved, files) {
|
|
42
|
+
for (const file of files) {
|
|
43
|
+
if (file.path === resolved)
|
|
44
|
+
return file.path;
|
|
45
|
+
for (const ext of EXTENSIONS) {
|
|
46
|
+
if (file.path === resolved + ext)
|
|
47
|
+
return file.path;
|
|
48
|
+
}
|
|
49
|
+
for (const ext of EXTENSIONS) {
|
|
50
|
+
for (const idx of INDEX_PATTERNS) {
|
|
51
|
+
if (file.path === resolved + idx + ext)
|
|
52
|
+
return file.path;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
export function checkBoundaries(graph, config) {
|
|
59
|
+
const violations = [];
|
|
60
|
+
let allowed = 0;
|
|
61
|
+
for (const imp of graph.imports) {
|
|
62
|
+
const fromLayer = findLayerByPath(imp.fromFile, config);
|
|
63
|
+
if (!fromLayer) {
|
|
64
|
+
allowed++;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const resolved = resolveTargetPath(imp.fromFile, imp.importPath);
|
|
68
|
+
if (!resolved) {
|
|
69
|
+
allowed++;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
const matchedFile = matchFile(resolved, graph.files);
|
|
73
|
+
if (!matchedFile) {
|
|
74
|
+
allowed++;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const toLayer = findLayerByPath(matchedFile, config);
|
|
78
|
+
if (!toLayer) {
|
|
79
|
+
allowed++;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (fromLayer.name === toLayer.name) {
|
|
83
|
+
allowed++;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (!fromLayer.dependsOn.includes(toLayer.name)) {
|
|
87
|
+
const targetFile = graph.files.find((f) => f.path === matchedFile);
|
|
88
|
+
violations.push({
|
|
89
|
+
fromFile: imp.fromFile,
|
|
90
|
+
fromLayer: fromLayer.name,
|
|
91
|
+
toFile: matchedFile,
|
|
92
|
+
toLayer: toLayer.name,
|
|
93
|
+
toPackage: targetFile?.packageName ?? toLayer.name,
|
|
94
|
+
rule: `${fromLayer.name} → ${toLayer.name} not allowed`,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
allowed++;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return { violations, allowed, config };
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/boundaries/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../../src/boundaries/index.test.ts"],"names":[],"mappings":""}
|