projscan 0.10.0 → 0.11.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/README.md +25 -7
- package/dist/cli/index.js +294 -10
- package/dist/cli/index.js.map +1 -1
- package/dist/core/ast.d.ts +2 -0
- package/dist/core/ast.js +35 -2
- package/dist/core/ast.js.map +1 -1
- package/dist/core/codeGraph.d.ts +2 -0
- package/dist/core/codeGraph.js +2 -0
- package/dist/core/codeGraph.js.map +1 -1
- package/dist/core/couplingAnalyzer.d.ts +18 -0
- package/dist/core/couplingAnalyzer.js +174 -0
- package/dist/core/couplingAnalyzer.js.map +1 -0
- package/dist/core/fileInspector.d.ts +1 -1
- package/dist/core/fileInspector.js +31 -1
- package/dist/core/fileInspector.js.map +1 -1
- package/dist/core/hotspotAnalyzer.d.ts +13 -0
- package/dist/core/hotspotAnalyzer.js +29 -6
- package/dist/core/hotspotAnalyzer.js.map +1 -1
- package/dist/core/indexCache.js +6 -3
- package/dist/core/indexCache.js.map +1 -1
- package/dist/core/languages/LanguageAdapter.d.ts +1 -1
- package/dist/core/languages/goAdapter.d.ts +2 -0
- package/dist/core/languages/goAdapter.js +136 -0
- package/dist/core/languages/goAdapter.js.map +1 -0
- package/dist/core/languages/goCyclomatic.d.ts +21 -0
- package/dist/core/languages/goCyclomatic.js +55 -0
- package/dist/core/languages/goCyclomatic.js.map +1 -0
- package/dist/core/languages/goExports.d.ts +26 -0
- package/dist/core/languages/goExports.js +89 -0
- package/dist/core/languages/goExports.js.map +1 -0
- package/dist/core/languages/goImports.d.ts +26 -0
- package/dist/core/languages/goImports.js +64 -0
- package/dist/core/languages/goImports.js.map +1 -0
- package/dist/core/languages/goManifests.d.ts +19 -0
- package/dist/core/languages/goManifests.js +56 -0
- package/dist/core/languages/goManifests.js.map +1 -0
- package/dist/core/languages/pythonAdapter.js +5 -0
- package/dist/core/languages/pythonAdapter.js.map +1 -1
- package/dist/core/languages/pythonCyclomatic.d.ts +18 -0
- package/dist/core/languages/pythonCyclomatic.js +45 -0
- package/dist/core/languages/pythonCyclomatic.js.map +1 -0
- package/dist/core/languages/registry.js +2 -1
- package/dist/core/languages/registry.js.map +1 -1
- package/dist/core/languages/treeSitterLoader.js +2 -1
- package/dist/core/languages/treeSitterLoader.js.map +1 -1
- package/dist/core/monorepo.d.ts +20 -0
- package/dist/core/monorepo.js +270 -0
- package/dist/core/monorepo.js.map +1 -0
- package/dist/core/prDiff.d.ts +43 -0
- package/dist/core/prDiff.js +298 -0
- package/dist/core/prDiff.js.map +1 -0
- package/dist/core/telemetry.d.ts +90 -0
- package/dist/core/telemetry.js +199 -0
- package/dist/core/telemetry.js.map +1 -0
- package/dist/grammars/tree-sitter-go.wasm +0 -0
- package/dist/mcp/server.js +22 -0
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools.js +269 -20
- package/dist/mcp/tools.js.map +1 -1
- package/dist/reporters/consoleReporter.d.ts +4 -1
- package/dist/reporters/consoleReporter.js +113 -0
- package/dist/reporters/consoleReporter.js.map +1 -1
- package/dist/reporters/jsonReporter.d.ts +4 -1
- package/dist/reporters/jsonReporter.js +9 -0
- package/dist/reporters/jsonReporter.js.map +1 -1
- package/dist/reporters/markdownReporter.d.ts +4 -1
- package/dist/reporters/markdownReporter.js +103 -3
- package/dist/reporters/markdownReporter.js.map +1 -1
- package/dist/types.d.ts +113 -0
- package/dist/utils/cache.d.ts +3 -0
- package/dist/utils/cache.js +51 -0
- package/dist/utils/cache.js.map +1 -0
- package/dist/utils/config.js +10 -0
- package/dist/utils/config.js.map +1 -1
- package/package.json +3 -2
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract package-level "exports" from a Go AST.
|
|
3
|
+
*
|
|
4
|
+
* Go's export rule is mechanical: an identifier is exported iff it starts
|
|
5
|
+
* with an uppercase letter. We scan top-level declarations only — local
|
|
6
|
+
* symbols inside functions are not part of the package surface.
|
|
7
|
+
*
|
|
8
|
+
* Captured forms:
|
|
9
|
+
* func Foo() {} → function
|
|
10
|
+
* func (r *T) Foo() {} → function (method)
|
|
11
|
+
* type Foo struct {} | interface {} → type / interface
|
|
12
|
+
* var Foo = ... / var Foo Type → variable
|
|
13
|
+
* const Foo = ... → variable
|
|
14
|
+
*/
|
|
15
|
+
export function extractGoExports(root) {
|
|
16
|
+
const out = [];
|
|
17
|
+
for (const child of root.namedChildren) {
|
|
18
|
+
visitTopLevel(child, out);
|
|
19
|
+
}
|
|
20
|
+
return out;
|
|
21
|
+
}
|
|
22
|
+
function visitTopLevel(node, out) {
|
|
23
|
+
switch (node.type) {
|
|
24
|
+
case 'function_declaration':
|
|
25
|
+
addIfExported(name(node), 'function', node, out);
|
|
26
|
+
return;
|
|
27
|
+
case 'method_declaration':
|
|
28
|
+
addIfExported(name(node), 'function', node, out);
|
|
29
|
+
return;
|
|
30
|
+
case 'type_declaration':
|
|
31
|
+
for (const spec of node.namedChildren) {
|
|
32
|
+
if (spec.type === 'type_spec' || spec.type === 'type_alias') {
|
|
33
|
+
const id = spec.childForFieldName?.('name') ?? firstIdent(spec);
|
|
34
|
+
const kind = detectTypeKind(spec);
|
|
35
|
+
addIfExported(id?.text, kind, spec, out);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return;
|
|
39
|
+
case 'var_declaration':
|
|
40
|
+
case 'const_declaration':
|
|
41
|
+
for (const spec of node.namedChildren) {
|
|
42
|
+
if (spec.type === 'var_spec' || spec.type === 'const_spec') {
|
|
43
|
+
// A spec can declare multiple names: `var X, Y int`.
|
|
44
|
+
for (const c of spec.namedChildren) {
|
|
45
|
+
if (c.type === 'identifier')
|
|
46
|
+
addIfExported(c.text, 'variable', spec, out);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return;
|
|
51
|
+
default:
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function name(node) {
|
|
56
|
+
return node.childForFieldName?.('name')?.text ?? firstIdent(node)?.text;
|
|
57
|
+
}
|
|
58
|
+
function firstIdent(node) {
|
|
59
|
+
for (const c of node.namedChildren) {
|
|
60
|
+
if (c.type === 'identifier' || c.type === 'field_identifier' || c.type === 'type_identifier')
|
|
61
|
+
return c;
|
|
62
|
+
}
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
function detectTypeKind(spec) {
|
|
66
|
+
// type_spec children: identifier + (struct_type | interface_type | ...).
|
|
67
|
+
for (const c of spec.namedChildren) {
|
|
68
|
+
if (c.type === 'interface_type')
|
|
69
|
+
return 'interface';
|
|
70
|
+
if (c.type === 'struct_type')
|
|
71
|
+
return 'class';
|
|
72
|
+
}
|
|
73
|
+
return 'type';
|
|
74
|
+
}
|
|
75
|
+
function addIfExported(name, kind, node, out) {
|
|
76
|
+
if (!name)
|
|
77
|
+
return;
|
|
78
|
+
// Go's export rule: leading character is an uppercase Unicode letter.
|
|
79
|
+
const first = name.charAt(0);
|
|
80
|
+
if (first !== first.toUpperCase() || first === first.toLowerCase())
|
|
81
|
+
return;
|
|
82
|
+
out.push({
|
|
83
|
+
name,
|
|
84
|
+
kind,
|
|
85
|
+
typeOnly: false,
|
|
86
|
+
line: node.startPosition.row + 1,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=goExports.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"goExports.js","sourceRoot":"","sources":["../../../src/core/languages/goExports.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { AstImport } from '../ast.js';
|
|
2
|
+
interface TsNode {
|
|
3
|
+
type: string;
|
|
4
|
+
text: string;
|
|
5
|
+
startPosition: {
|
|
6
|
+
row: number;
|
|
7
|
+
};
|
|
8
|
+
namedChildren: TsNode[];
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Extract import paths from a tree-sitter-go AST.
|
|
12
|
+
*
|
|
13
|
+
* Go imports come in two shapes:
|
|
14
|
+
* import "fmt"
|
|
15
|
+
* import (
|
|
16
|
+
* "fmt"
|
|
17
|
+
* "github.com/foo/bar"
|
|
18
|
+
* util "github.com/foo/util" // alias
|
|
19
|
+
* )
|
|
20
|
+
*
|
|
21
|
+
* The grammar emits `import_declaration` containing one or more
|
|
22
|
+
* `import_spec` nodes. Each spec has an `interpreted_string_literal` child
|
|
23
|
+
* holding the import path (with surrounding quotes that we strip).
|
|
24
|
+
*/
|
|
25
|
+
export declare function extractGoImports(root: TsNode): AstImport[];
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract import paths from a tree-sitter-go AST.
|
|
3
|
+
*
|
|
4
|
+
* Go imports come in two shapes:
|
|
5
|
+
* import "fmt"
|
|
6
|
+
* import (
|
|
7
|
+
* "fmt"
|
|
8
|
+
* "github.com/foo/bar"
|
|
9
|
+
* util "github.com/foo/util" // alias
|
|
10
|
+
* )
|
|
11
|
+
*
|
|
12
|
+
* The grammar emits `import_declaration` containing one or more
|
|
13
|
+
* `import_spec` nodes. Each spec has an `interpreted_string_literal` child
|
|
14
|
+
* holding the import path (with surrounding quotes that we strip).
|
|
15
|
+
*/
|
|
16
|
+
export function extractGoImports(root) {
|
|
17
|
+
const out = [];
|
|
18
|
+
visit(root, out);
|
|
19
|
+
return out;
|
|
20
|
+
}
|
|
21
|
+
function visit(node, out) {
|
|
22
|
+
for (const child of node.namedChildren) {
|
|
23
|
+
if (child.type === 'import_declaration') {
|
|
24
|
+
handleImportDecl(child, out);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
// Imports must be top-level in Go, but we still descend through any
|
|
28
|
+
// top-level wrappers (e.g. source_file).
|
|
29
|
+
visit(child, out);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function handleImportDecl(node, out) {
|
|
34
|
+
const line = node.startPosition.row + 1;
|
|
35
|
+
for (const c of node.namedChildren) {
|
|
36
|
+
if (c.type === 'import_spec') {
|
|
37
|
+
addSpec(c, line, out);
|
|
38
|
+
}
|
|
39
|
+
else if (c.type === 'import_spec_list') {
|
|
40
|
+
for (const s of c.namedChildren) {
|
|
41
|
+
if (s.type === 'import_spec')
|
|
42
|
+
addSpec(s, line, out);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function addSpec(spec, line, out) {
|
|
48
|
+
// Path is the interpreted_string_literal; we strip the surrounding quotes.
|
|
49
|
+
const pathNode = spec.namedChildren.find((c) => c.type === 'interpreted_string_literal' || c.type === 'raw_string_literal');
|
|
50
|
+
if (!pathNode)
|
|
51
|
+
return;
|
|
52
|
+
const raw = pathNode.text;
|
|
53
|
+
const source = raw.replace(/^["`]|["`]$/g, '');
|
|
54
|
+
if (!source)
|
|
55
|
+
return;
|
|
56
|
+
out.push({
|
|
57
|
+
source,
|
|
58
|
+
kind: 'static',
|
|
59
|
+
specifiers: [],
|
|
60
|
+
typeOnly: false,
|
|
61
|
+
line,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=goImports.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"goImports.js","sourceRoot":"","sources":["../../../src/core/languages/goImports.ts"],"names":[],"mappings":"AASA;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,MAAM,GAAG,GAAgB,EAAE,CAAC;IAC5B,KAAK,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IACjB,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,KAAK,CAAC,IAAY,EAAE,GAAgB;IAC3C,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;QACvC,IAAI,KAAK,CAAC,IAAI,KAAK,oBAAoB,EAAE,CAAC;YACxC,gBAAgB,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QAC/B,CAAC;aAAM,CAAC;YACN,oEAAoE;YACpE,yCAAyC;YACzC,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QACpB,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAY,EAAE,GAAgB;IACtD,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,GAAG,CAAC,CAAC;IACxC,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;QACnC,IAAI,CAAC,CAAC,IAAI,KAAK,aAAa,EAAE,CAAC;YAC7B,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;QACxB,CAAC;aAAM,IAAI,CAAC,CAAC,IAAI,KAAK,kBAAkB,EAAE,CAAC;YACzC,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,aAAa,EAAE,CAAC;gBAChC,IAAI,CAAC,CAAC,IAAI,KAAK,aAAa;oBAAE,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;YACtD,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAS,OAAO,CAAC,IAAY,EAAE,IAAY,EAAE,GAAgB;IAC3D,2EAA2E;IAC3E,MAAM,QAAQ,GAAG,IAAI,CAAC,aAAa,CAAC,IAAI,CACtC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,4BAA4B,IAAI,CAAC,CAAC,IAAI,KAAK,oBAAoB,CAClF,CAAC;IACF,IAAI,CAAC,QAAQ;QAAE,OAAO;IACtB,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC;IAC1B,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC;IAC/C,IAAI,CAAC,MAAM;QAAE,OAAO;IACpB,GAAG,CAAC,IAAI,CAAC;QACP,MAAM;QACN,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE,EAAE;QACd,QAAQ,EAAE,KAAK;QACf,IAAI;KACL,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { FileEntry } from '../../types.js';
|
|
2
|
+
export interface GoProjectInfo {
|
|
3
|
+
/**
|
|
4
|
+
* Module path declared in go.mod (e.g. "github.com/acme/widget"). Local
|
|
5
|
+
* imports starting with this prefix resolve into the repo.
|
|
6
|
+
*/
|
|
7
|
+
modulePath: string;
|
|
8
|
+
/** Absolute directory containing go.mod. Imports resolve relative to this. */
|
|
9
|
+
moduleRoot: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Find the closest go.mod and read its module declaration. Searches the
|
|
13
|
+
* repository root first, then any directory that contains at least one .go
|
|
14
|
+
* file (handles nested modules / examples directories).
|
|
15
|
+
*
|
|
16
|
+
* Returns null if no go.mod exists — Go files outside any module are valid
|
|
17
|
+
* (e.g. snippets) but their imports can't be resolved to local files.
|
|
18
|
+
*/
|
|
19
|
+
export declare function detectGoProject(rootPath: string, files: FileEntry[]): Promise<GoProjectInfo | null>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* Find the closest go.mod and read its module declaration. Searches the
|
|
5
|
+
* repository root first, then any directory that contains at least one .go
|
|
6
|
+
* file (handles nested modules / examples directories).
|
|
7
|
+
*
|
|
8
|
+
* Returns null if no go.mod exists — Go files outside any module are valid
|
|
9
|
+
* (e.g. snippets) but their imports can't be resolved to local files.
|
|
10
|
+
*/
|
|
11
|
+
export async function detectGoProject(rootPath, files) {
|
|
12
|
+
// 1) Try repo root first — by far the most common case.
|
|
13
|
+
const rootGoMod = await readGoMod(path.join(rootPath, 'go.mod'));
|
|
14
|
+
if (rootGoMod)
|
|
15
|
+
return { modulePath: rootGoMod, moduleRoot: rootPath };
|
|
16
|
+
// 2) Look for go.mod in any directory containing a .go file. Take the
|
|
17
|
+
// shortest path (closest-to-root) when multiple match.
|
|
18
|
+
const candidates = new Set();
|
|
19
|
+
for (const f of files) {
|
|
20
|
+
if (!f.relativePath.endsWith('.go'))
|
|
21
|
+
continue;
|
|
22
|
+
let dir = path.posix.dirname(f.relativePath);
|
|
23
|
+
while (dir && dir !== '.' && dir !== '/') {
|
|
24
|
+
candidates.add(dir);
|
|
25
|
+
dir = path.posix.dirname(dir);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const sorted = [...candidates].sort((a, b) => a.length - b.length);
|
|
29
|
+
for (const dir of sorted) {
|
|
30
|
+
const p = path.join(rootPath, dir, 'go.mod');
|
|
31
|
+
const mod = await readGoMod(p);
|
|
32
|
+
if (mod)
|
|
33
|
+
return { modulePath: mod, moduleRoot: path.join(rootPath, dir) };
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
async function readGoMod(absPath) {
|
|
38
|
+
let content;
|
|
39
|
+
try {
|
|
40
|
+
content = await fs.readFile(absPath, 'utf-8');
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
// First non-comment `module <path>` line wins.
|
|
46
|
+
for (const raw of content.split('\n')) {
|
|
47
|
+
const line = raw.trim();
|
|
48
|
+
if (!line || line.startsWith('//'))
|
|
49
|
+
continue;
|
|
50
|
+
const m = /^module\s+(\S+)/.exec(line);
|
|
51
|
+
if (m)
|
|
52
|
+
return m[1];
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=goManifests.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"goManifests.js","sourceRoot":"","sources":["../../../src/core/languages/goManifests.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAa7B;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,QAAgB,EAChB,KAAkB;IAElB,wDAAwD;IACxD,MAAM,SAAS,GAAG,MAAM,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC;IACjE,IAAI,SAAS;QAAE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC;IAEtE,sEAAsE;IACtE,uDAAuD;IACvD,MAAM,UAAU,GAAG,IAAI,GAAG,EAAU,CAAC;IACrC,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,QAAQ,CAAC,KAAK,CAAC;YAAE,SAAS;QAC9C,IAAI,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;QAC7C,OAAO,GAAG,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,GAAG,EAAE,CAAC;YACzC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACpB,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAChC,CAAC;IACH,CAAC;IACD,MAAM,MAAM,GAAG,CAAC,GAAG,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC;IACnE,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;QACzB,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC;QAC7C,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,CAAC,CAAC,CAAC;QAC/B,IAAI,GAAG;YAAE,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,EAAE,CAAC;IAC5E,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,KAAK,UAAU,SAAS,CAAC,OAAe;IACtC,IAAI,OAAe,CAAC;IACpB,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAChD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,+CAA+C;IAC/C,KAAK,MAAM,GAAG,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;QACxB,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,SAAS;QAC7C,MAAM,CAAC,GAAG,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACvC,IAAI,CAAC;YAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;IACrB,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -2,6 +2,7 @@ import path from 'node:path';
|
|
|
2
2
|
import { createParserFor } from './treeSitterLoader.js';
|
|
3
3
|
import { extractPythonImports } from './pythonImports.js';
|
|
4
4
|
import { extractPythonExports } from './pythonExports.js';
|
|
5
|
+
import { extractPythonCyclomatic } from './pythonCyclomatic.js';
|
|
5
6
|
import { detectPythonProject } from './pythonManifests.js';
|
|
6
7
|
// Pinned grammar: tree-sitter-python 0.25.0. See treeSitterLoader.ts.
|
|
7
8
|
const PY_EXTENSIONS = new Set(['.py', '.pyw', '.pyi']);
|
|
@@ -34,16 +35,19 @@ export const pythonAdapter = {
|
|
|
34
35
|
exports: [],
|
|
35
36
|
callSites: [],
|
|
36
37
|
lineCount: content ? content.split('\n').length : 0,
|
|
38
|
+
cyclomaticComplexity: 0,
|
|
37
39
|
};
|
|
38
40
|
}
|
|
39
41
|
const imports = extractPythonImports(tree.rootNode);
|
|
40
42
|
const exports = extractPythonExports(tree.rootNode);
|
|
43
|
+
const cyclomaticComplexity = extractPythonCyclomatic(tree.rootNode);
|
|
41
44
|
return {
|
|
42
45
|
ok: true,
|
|
43
46
|
imports,
|
|
44
47
|
exports,
|
|
45
48
|
callSites: [],
|
|
46
49
|
lineCount: content ? content.split('\n').length : 0,
|
|
50
|
+
cyclomaticComplexity,
|
|
47
51
|
};
|
|
48
52
|
}
|
|
49
53
|
catch (err) {
|
|
@@ -55,6 +59,7 @@ export const pythonAdapter = {
|
|
|
55
59
|
exports: [],
|
|
56
60
|
callSites: [],
|
|
57
61
|
lineCount: content ? content.split('\n').length : 0,
|
|
62
|
+
cyclomaticComplexity: 0,
|
|
58
63
|
};
|
|
59
64
|
}
|
|
60
65
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pythonAdapter.js","sourceRoot":"","sources":["../../../src/core/languages/pythonAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAG7B,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAO3D,sEAAsE;AAEtE,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AACvD,MAAM,WAAW,GAAG,IAAI,GAAG,IAAI,CAAC;AAEhC,IAAI,aAAa,GAAqD,IAAI,CAAC;AAC3E,KAAK,UAAU,SAAS;IACtB,IAAI,CAAC,aAAa;QAAE,aAAa,GAAG,eAAe,CAAC,yBAAyB,CAAC,CAAC;IAC/E,OAAO,aAAa,CAAC;AACvB,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,aAAa,GAAoB;IAC5C,EAAE,EAAE,QAAQ;IACZ,UAAU,EAAE,aAAa;IACzB,gBAAgB,EAAE,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAC1C,eAAe,EAAE,IAAI,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC;IACtC,WAAW,EAAE,WAAW;IAExB,KAAK,CAAC,KAAK,CAAC,SAAiB,EAAE,OAAe;QAC5C,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,SAAS,EAAE,CAAC;YACjC,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YACnC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;gBAC5B,OAAO;oBACL,EAAE,EAAE,KAAK;oBACT,MAAM,EAAE,gCAAgC;oBACxC,OAAO,EAAE,EAAE;oBACX,OAAO,EAAE,EAAE;oBACX,SAAS,EAAE,EAAE;oBACb,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"pythonAdapter.js","sourceRoot":"","sources":["../../../src/core/languages/pythonAdapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Minimal tree-sitter node surface we depend on. Mirrors pythonImports.ts. */
|
|
2
|
+
interface TsNode {
|
|
3
|
+
type: string;
|
|
4
|
+
text: string;
|
|
5
|
+
namedChildren: TsNode[];
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Compute file-level McCabe cyclomatic complexity from a tree-sitter-python
|
|
9
|
+
* AST. Counts decision points across the entire file (module + nested
|
|
10
|
+
* functions/classes) and returns count + 1.
|
|
11
|
+
*
|
|
12
|
+
* Decision points: if/elif, for, while, except, conditional expression,
|
|
13
|
+
* boolean operators (and/or each add one), and `if` clauses inside
|
|
14
|
+
* comprehensions. The plain `else` branch is the fall-through path and does
|
|
15
|
+
* not count.
|
|
16
|
+
*/
|
|
17
|
+
export declare function extractPythonCyclomatic(root: TsNode): number;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compute file-level McCabe cyclomatic complexity from a tree-sitter-python
|
|
3
|
+
* AST. Counts decision points across the entire file (module + nested
|
|
4
|
+
* functions/classes) and returns count + 1.
|
|
5
|
+
*
|
|
6
|
+
* Decision points: if/elif, for, while, except, conditional expression,
|
|
7
|
+
* boolean operators (and/or each add one), and `if` clauses inside
|
|
8
|
+
* comprehensions. The plain `else` branch is the fall-through path and does
|
|
9
|
+
* not count.
|
|
10
|
+
*/
|
|
11
|
+
export function extractPythonCyclomatic(root) {
|
|
12
|
+
let decisions = 0;
|
|
13
|
+
walk(root, (n) => {
|
|
14
|
+
if (isDecisionPoint(n))
|
|
15
|
+
decisions++;
|
|
16
|
+
});
|
|
17
|
+
return decisions + 1;
|
|
18
|
+
}
|
|
19
|
+
function isDecisionPoint(n) {
|
|
20
|
+
switch (n.type) {
|
|
21
|
+
case 'if_statement':
|
|
22
|
+
case 'elif_clause':
|
|
23
|
+
case 'for_statement':
|
|
24
|
+
case 'while_statement':
|
|
25
|
+
case 'except_clause':
|
|
26
|
+
case 'conditional_expression':
|
|
27
|
+
case 'for_in_clause': // comprehension `for`
|
|
28
|
+
case 'if_clause': // comprehension `if`
|
|
29
|
+
case 'case_clause': // match/case (Python 3.10+)
|
|
30
|
+
return true;
|
|
31
|
+
case 'boolean_operator':
|
|
32
|
+
// Each `and` / `or` introduces a branch. tree-sitter-python emits one
|
|
33
|
+
// boolean_operator node per logical operator, so each occurrence = +1.
|
|
34
|
+
return true;
|
|
35
|
+
default:
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function walk(node, visit) {
|
|
40
|
+
visit(node);
|
|
41
|
+
for (const child of node.namedChildren) {
|
|
42
|
+
walk(child, visit);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=pythonCyclomatic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pythonCyclomatic.js","sourceRoot":"","sources":["../../../src/core/languages/pythonCyclomatic.ts"],"names":[],"mappings":"AAOA;;;;;;;;;GASG;AACH,MAAM,UAAU,uBAAuB,CAAC,IAAY;IAClD,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE;QACf,IAAI,eAAe,CAAC,CAAC,CAAC;YAAE,SAAS,EAAE,CAAC;IACtC,CAAC,CAAC,CAAC;IACH,OAAO,SAAS,GAAG,CAAC,CAAC;AACvB,CAAC;AAED,SAAS,eAAe,CAAC,CAAS;IAChC,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC;QACf,KAAK,cAAc,CAAC;QACpB,KAAK,aAAa,CAAC;QACnB,KAAK,eAAe,CAAC;QACrB,KAAK,iBAAiB,CAAC;QACvB,KAAK,eAAe,CAAC;QACrB,KAAK,wBAAwB,CAAC;QAC9B,KAAK,eAAe,CAAC,CAAC,sBAAsB;QAC5C,KAAK,WAAW,CAAC,CAAC,qBAAqB;QACvC,KAAK,aAAa,EAAE,4BAA4B;YAC9C,OAAO,IAAI,CAAC;QACd,KAAK,kBAAkB;YACrB,sEAAsE;YACtE,uEAAuE;YACvE,OAAO,IAAI,CAAC;QACd;YACE,OAAO,KAAK,CAAC;IACjB,CAAC;AACH,CAAC;AAED,SAAS,IAAI,CAAC,IAAY,EAAE,KAA0B;IACpD,KAAK,CAAC,IAAI,CAAC,CAAC;IACZ,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;QACvC,IAAI,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IACrB,CAAC;AACH,CAAC"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { javascriptAdapter } from './javascriptAdapter.js';
|
|
3
3
|
import { pythonAdapter } from './pythonAdapter.js';
|
|
4
|
-
|
|
4
|
+
import { goAdapter } from './goAdapter.js';
|
|
5
|
+
const adapters = [javascriptAdapter, pythonAdapter, goAdapter];
|
|
5
6
|
const byExtension = new Map();
|
|
6
7
|
function rebuildIndex() {
|
|
7
8
|
byExtension.clear();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../src/core/languages/registry.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../src/core/languages/registry.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAG3C,MAAM,QAAQ,GAAsB,CAAC,iBAAiB,EAAE,aAAa,EAAE,SAAS,CAAC,CAAC;AAClF,MAAM,WAAW,GAAG,IAAI,GAAG,EAA2B,CAAC;AAEvD,SAAS,YAAY;IACnB,WAAW,CAAC,KAAK,EAAE,CAAC;IACpB,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,KAAK,MAAM,GAAG,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;YACrC,WAAW,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QAChC,CAAC;IACH,CAAC;AACH,CAAC;AACD,YAAY,EAAE,CAAC;AAEf,MAAM,UAAU,eAAe,CAAC,OAAwB;IACtD,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QAChC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACvB,YAAY,EAAE,CAAC;IACjB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,QAAgB;IAC5C,OAAO,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;AAC/D,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,QAAgB;IACjD,OAAO,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;AAC/D,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|
|
@@ -22,7 +22,8 @@ function grammarDirs() {
|
|
|
22
22
|
const repoRoot = path.resolve(here, '..', '..', '..');
|
|
23
23
|
const nodeModulesWebTs = path.join(repoRoot, 'node_modules', 'web-tree-sitter');
|
|
24
24
|
const nodeModulesPy = path.join(repoRoot, 'node_modules', 'tree-sitter-python');
|
|
25
|
-
|
|
25
|
+
const nodeModulesGo = path.join(repoRoot, 'node_modules', 'tree-sitter-go');
|
|
26
|
+
return [distDir, nodeModulesWebTs, nodeModulesPy, nodeModulesGo];
|
|
26
27
|
}
|
|
27
28
|
function findWasm(filename) {
|
|
28
29
|
for (const dir of grammarDirs()) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"treeSitterLoader.js","sourceRoot":"","sources":["../../../src/core/languages/treeSitterLoader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAEnD;;;GAGG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,QAAQ,CAAC;AAEnD,IAAI,iBAAiB,GAAyB,IAAI,CAAC;AACnD,MAAM,aAAa,GAAG,IAAI,GAAG,EAA6B,CAAC;AAE3D;;;;;GAKG;AACH,SAAS,WAAW;IAClB,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1D,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC;IAC3D,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IACtD,MAAM,gBAAgB,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,cAAc,EAAE,iBAAiB,CAAC,CAAC;IAChF,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,cAAc,EAAE,oBAAoB,CAAC,CAAC;IAChF,OAAO,CAAC,OAAO,EAAE,gBAAgB,EAAE,aAAa,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"treeSitterLoader.js","sourceRoot":"","sources":["../../../src/core/languages/treeSitterLoader.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { WorkspaceInfo, WorkspacePackage } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Detect monorepo workspaces. Covers the three setups that account for the
|
|
4
|
+
* vast majority of JS/TS monorepos: npm/yarn workspaces (package.json
|
|
5
|
+
* `workspaces`), pnpm (pnpm-workspace.yaml), and the Nx/Turbo/Lerna fallback
|
|
6
|
+
* (their files exist but they typically piggy-back on package.json
|
|
7
|
+
* workspaces or rely on a `packages/` convention).
|
|
8
|
+
*
|
|
9
|
+
* Returns kind 'none' for non-monorepos. Always includes the workspace root
|
|
10
|
+
* package itself if it has its own package.json with a name.
|
|
11
|
+
*/
|
|
12
|
+
export declare function detectWorkspaces(rootPath: string): Promise<WorkspaceInfo>;
|
|
13
|
+
/**
|
|
14
|
+
* Given a file's project-relative path, return the workspace package whose
|
|
15
|
+
* `relativePath` is its longest matching prefix. Used to filter hotspots /
|
|
16
|
+
* coupling rows by --package.
|
|
17
|
+
*/
|
|
18
|
+
export declare function findPackageForFile(workspaces: WorkspaceInfo, filePath: string): WorkspacePackage | null;
|
|
19
|
+
/** Filter helper for callers passing --package <name>. */
|
|
20
|
+
export declare function filterFilesByPackage(workspaces: WorkspaceInfo, packageName: string, files: string[]): string[];
|