@optave/codegraph 3.6.0 → 3.8.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 +32 -16
- package/dist/ast-analysis/engine.d.ts.map +1 -1
- package/dist/ast-analysis/engine.js +158 -1
- package/dist/ast-analysis/engine.js.map +1 -1
- package/dist/ast-analysis/rules/javascript.d.ts.map +1 -1
- package/dist/ast-analysis/rules/javascript.js +0 -1
- package/dist/ast-analysis/rules/javascript.js.map +1 -1
- package/dist/ast-analysis/visitors/ast-store-visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitors/ast-store-visitor.js +2 -75
- package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
- package/dist/cli/commands/ast.js +2 -2
- package/dist/cli/commands/ast.js.map +1 -1
- package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
- package/dist/domain/graph/builder/pipeline.js +128 -6
- package/dist/domain/graph/builder/pipeline.js.map +1 -1
- package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/build-edges.js +101 -1
- package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
- package/dist/domain/graph/builder/stages/collect-files.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/collect-files.js +17 -5
- package/dist/domain/graph/builder/stages/collect-files.js.map +1 -1
- package/dist/domain/graph/builder/stages/detect-changes.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/detect-changes.js +98 -50
- package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
- package/dist/domain/graph/builder/stages/finalize.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/finalize.js +32 -5
- package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
- package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/insert-nodes.js +20 -7
- package/dist/domain/graph/builder/stages/insert-nodes.js.map +1 -1
- package/dist/domain/parser.d.ts +1 -1
- package/dist/domain/parser.d.ts.map +1 -1
- package/dist/domain/parser.js +129 -3
- package/dist/domain/parser.js.map +1 -1
- package/dist/extractors/clojure.d.ts +12 -0
- package/dist/extractors/clojure.d.ts.map +1 -0
- package/dist/extractors/clojure.js +245 -0
- package/dist/extractors/clojure.js.map +1 -0
- package/dist/extractors/cuda.d.ts +11 -0
- package/dist/extractors/cuda.d.ts.map +1 -0
- package/dist/extractors/cuda.js +302 -0
- package/dist/extractors/cuda.js.map +1 -0
- package/dist/extractors/dart.d.ts +6 -0
- package/dist/extractors/dart.d.ts.map +1 -0
- package/dist/extractors/dart.js +277 -0
- package/dist/extractors/dart.js.map +1 -0
- package/dist/extractors/elixir.d.ts +9 -0
- package/dist/extractors/elixir.d.ts.map +1 -0
- package/dist/extractors/elixir.js +223 -0
- package/dist/extractors/elixir.js.map +1 -0
- package/dist/extractors/erlang.d.ts +14 -0
- package/dist/extractors/erlang.d.ts.map +1 -0
- package/dist/extractors/erlang.js +239 -0
- package/dist/extractors/erlang.js.map +1 -0
- package/dist/extractors/fsharp.d.ts +13 -0
- package/dist/extractors/fsharp.d.ts.map +1 -0
- package/dist/extractors/fsharp.js +218 -0
- package/dist/extractors/fsharp.js.map +1 -0
- package/dist/extractors/gleam.d.ts +14 -0
- package/dist/extractors/gleam.d.ts.map +1 -0
- package/dist/extractors/gleam.js +229 -0
- package/dist/extractors/gleam.js.map +1 -0
- package/dist/extractors/groovy.d.ts +10 -0
- package/dist/extractors/groovy.d.ts.map +1 -0
- package/dist/extractors/groovy.js +304 -0
- package/dist/extractors/groovy.js.map +1 -0
- package/dist/extractors/haskell.d.ts +8 -0
- package/dist/extractors/haskell.d.ts.map +1 -0
- package/dist/extractors/haskell.js +217 -0
- package/dist/extractors/haskell.js.map +1 -0
- package/dist/extractors/index.d.ts +17 -0
- package/dist/extractors/index.d.ts.map +1 -1
- package/dist/extractors/index.js +17 -0
- package/dist/extractors/index.js.map +1 -1
- package/dist/extractors/julia.d.ts +16 -0
- package/dist/extractors/julia.d.ts.map +1 -0
- package/dist/extractors/julia.js +287 -0
- package/dist/extractors/julia.js.map +1 -0
- package/dist/extractors/lua.d.ts +6 -0
- package/dist/extractors/lua.d.ts.map +1 -0
- package/dist/extractors/lua.js +162 -0
- package/dist/extractors/lua.js.map +1 -0
- package/dist/extractors/objc.d.ts +9 -0
- package/dist/extractors/objc.d.ts.map +1 -0
- package/dist/extractors/objc.js +406 -0
- package/dist/extractors/objc.js.map +1 -0
- package/dist/extractors/ocaml.d.ts +6 -0
- package/dist/extractors/ocaml.d.ts.map +1 -0
- package/dist/extractors/ocaml.js +310 -0
- package/dist/extractors/ocaml.js.map +1 -0
- package/dist/extractors/r.d.ts +13 -0
- package/dist/extractors/r.d.ts.map +1 -0
- package/dist/extractors/r.js +251 -0
- package/dist/extractors/r.js.map +1 -0
- package/dist/extractors/solidity.d.ts +9 -0
- package/dist/extractors/solidity.d.ts.map +1 -0
- package/dist/extractors/solidity.js +374 -0
- package/dist/extractors/solidity.js.map +1 -0
- package/dist/extractors/verilog.d.ts +9 -0
- package/dist/extractors/verilog.d.ts.map +1 -0
- package/dist/extractors/verilog.js +286 -0
- package/dist/extractors/verilog.js.map +1 -0
- package/dist/extractors/zig.d.ts +9 -0
- package/dist/extractors/zig.d.ts.map +1 -0
- package/dist/extractors/zig.js +276 -0
- package/dist/extractors/zig.js.map +1 -0
- package/dist/features/ast.d.ts.map +1 -1
- package/dist/features/ast.js +1 -2
- package/dist/features/ast.js.map +1 -1
- package/dist/features/cfg.d.ts +1 -1
- package/dist/features/cfg.d.ts.map +1 -1
- package/dist/features/cfg.js +6 -51
- package/dist/features/cfg.js.map +1 -1
- package/dist/graph/algorithms/bfs.d.ts +2 -0
- package/dist/graph/algorithms/bfs.d.ts.map +1 -1
- package/dist/graph/algorithms/bfs.js +27 -0
- package/dist/graph/algorithms/bfs.js.map +1 -1
- package/dist/graph/algorithms/centrality.d.ts +2 -0
- package/dist/graph/algorithms/centrality.d.ts.map +1 -1
- package/dist/graph/algorithms/centrality.js +28 -0
- package/dist/graph/algorithms/centrality.js.map +1 -1
- package/dist/graph/algorithms/louvain.d.ts +3 -4
- package/dist/graph/algorithms/louvain.d.ts.map +1 -1
- package/dist/graph/algorithms/louvain.js +29 -0
- package/dist/graph/algorithms/louvain.js.map +1 -1
- package/dist/graph/algorithms/shortest-path.d.ts +2 -0
- package/dist/graph/algorithms/shortest-path.d.ts.map +1 -1
- package/dist/graph/algorithms/shortest-path.js +18 -1
- package/dist/graph/algorithms/shortest-path.js.map +1 -1
- package/dist/types.d.ts +122 -2
- package/dist/types.d.ts.map +1 -1
- package/grammars/tree-sitter-clojure.wasm +0 -0
- package/grammars/tree-sitter-cuda.wasm +0 -0
- package/grammars/tree-sitter-dart.wasm +0 -0
- package/grammars/tree-sitter-elixir.wasm +0 -0
- package/grammars/tree-sitter-erlang.wasm +0 -0
- package/grammars/tree-sitter-fsharp.wasm +0 -0
- package/grammars/tree-sitter-gleam.wasm +0 -0
- package/grammars/tree-sitter-groovy.wasm +0 -0
- package/grammars/tree-sitter-haskell.wasm +0 -0
- package/grammars/tree-sitter-julia.wasm +0 -0
- package/grammars/tree-sitter-lua.wasm +0 -0
- package/grammars/tree-sitter-objc.wasm +0 -0
- package/grammars/tree-sitter-ocaml.wasm +0 -0
- package/grammars/tree-sitter-ocaml_interface.wasm +0 -0
- package/grammars/tree-sitter-r.wasm +0 -0
- package/grammars/tree-sitter-solidity.wasm +0 -0
- package/grammars/tree-sitter-verilog.wasm +0 -0
- package/grammars/tree-sitter-zig.wasm +0 -0
- package/package.json +24 -7
- package/src/ast-analysis/engine.ts +183 -1
- package/src/ast-analysis/rules/javascript.ts +0 -1
- package/src/ast-analysis/visitors/ast-store-visitor.ts +2 -75
- package/src/cli/commands/ast.ts +2 -2
- package/src/domain/graph/builder/pipeline.ts +142 -6
- package/src/domain/graph/builder/stages/build-edges.ts +158 -1
- package/src/domain/graph/builder/stages/collect-files.ts +18 -7
- package/src/domain/graph/builder/stages/detect-changes.ts +109 -55
- package/src/domain/graph/builder/stages/finalize.ts +39 -9
- package/src/domain/graph/builder/stages/insert-nodes.ts +18 -7
- package/src/domain/parser.ts +161 -1
- package/src/extractors/clojure.ts +273 -0
- package/src/extractors/cuda.ts +316 -0
- package/src/extractors/dart.ts +304 -0
- package/src/extractors/elixir.ts +251 -0
- package/src/extractors/erlang.ts +252 -0
- package/src/extractors/fsharp.ts +253 -0
- package/src/extractors/gleam.ts +246 -0
- package/src/extractors/groovy.ts +332 -0
- package/src/extractors/haskell.ts +235 -0
- package/src/extractors/index.ts +17 -0
- package/src/extractors/julia.ts +318 -0
- package/src/extractors/lua.ts +169 -0
- package/src/extractors/objc.ts +431 -0
- package/src/extractors/ocaml.ts +337 -0
- package/src/extractors/r.ts +253 -0
- package/src/extractors/solidity.ts +398 -0
- package/src/extractors/verilog.ts +315 -0
- package/src/extractors/zig.ts +294 -0
- package/src/features/ast.ts +1 -2
- package/src/features/cfg.ts +6 -51
- package/src/graph/algorithms/bfs.ts +34 -0
- package/src/graph/algorithms/centrality.ts +30 -0
- package/src/graph/algorithms/louvain.ts +31 -4
- package/src/graph/algorithms/shortest-path.ts +20 -1
- package/src/types.ts +123 -2
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Call,
|
|
3
|
+
ExtractorOutput,
|
|
4
|
+
SubDeclaration,
|
|
5
|
+
TreeSitterNode,
|
|
6
|
+
TreeSitterTree,
|
|
7
|
+
} from '../types.js';
|
|
8
|
+
import { findChild, nodeEndLine } from './helpers.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Extract symbols from Zig files.
|
|
12
|
+
*
|
|
13
|
+
* Zig's structs/enums/unions are anonymous — their names come from the
|
|
14
|
+
* enclosing `variable_declaration` (e.g. `const Foo = struct { ... };`).
|
|
15
|
+
*/
|
|
16
|
+
export function extractZigSymbols(tree: TreeSitterTree, _filePath: string): ExtractorOutput {
|
|
17
|
+
const ctx: ExtractorOutput = {
|
|
18
|
+
definitions: [],
|
|
19
|
+
calls: [],
|
|
20
|
+
imports: [],
|
|
21
|
+
classes: [],
|
|
22
|
+
exports: [],
|
|
23
|
+
typeMap: new Map(),
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
walkZigNode(tree.rootNode, ctx);
|
|
27
|
+
return ctx;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function walkZigNode(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
31
|
+
switch (node.type) {
|
|
32
|
+
case 'function_declaration':
|
|
33
|
+
handleZigFunction(node, ctx);
|
|
34
|
+
break;
|
|
35
|
+
case 'variable_declaration':
|
|
36
|
+
handleZigVariable(node, ctx);
|
|
37
|
+
break;
|
|
38
|
+
case 'call_expression':
|
|
39
|
+
handleZigCallExpression(node, ctx);
|
|
40
|
+
break;
|
|
41
|
+
case 'builtin_function':
|
|
42
|
+
handleZigBuiltin(node, ctx);
|
|
43
|
+
break;
|
|
44
|
+
case 'test_declaration':
|
|
45
|
+
handleZigTest(node, ctx);
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
50
|
+
const child = node.child(i);
|
|
51
|
+
if (child) walkZigNode(child, ctx);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function isInsideZigContainer(node: TreeSitterNode): boolean {
|
|
56
|
+
let current = node.parent;
|
|
57
|
+
while (current) {
|
|
58
|
+
if (current.type === 'struct_declaration' || current.type === 'union_declaration') return true;
|
|
59
|
+
current = current.parent;
|
|
60
|
+
}
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function handleZigFunction(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
65
|
+
if (isInsideZigContainer(node)) return; // already emitted by extractZigContainerMethods
|
|
66
|
+
|
|
67
|
+
const nameNode = node.childForFieldName('name');
|
|
68
|
+
if (!nameNode) return;
|
|
69
|
+
|
|
70
|
+
const params = extractZigParams(node);
|
|
71
|
+
|
|
72
|
+
ctx.definitions.push({
|
|
73
|
+
name: nameNode.text,
|
|
74
|
+
kind: 'function',
|
|
75
|
+
line: node.startPosition.row + 1,
|
|
76
|
+
endLine: nodeEndLine(node),
|
|
77
|
+
children: params.length > 0 ? params : undefined,
|
|
78
|
+
visibility: isZigPub(node) ? 'public' : 'private',
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function extractZigParams(funcNode: TreeSitterNode): SubDeclaration[] {
|
|
83
|
+
const params: SubDeclaration[] = [];
|
|
84
|
+
const paramList = funcNode.childForFieldName('parameters');
|
|
85
|
+
if (!paramList) return params;
|
|
86
|
+
|
|
87
|
+
for (let i = 0; i < paramList.childCount; i++) {
|
|
88
|
+
const param = paramList.child(i);
|
|
89
|
+
if (!param || param.type !== 'parameter') continue;
|
|
90
|
+
const nameNode = findChild(param, 'identifier');
|
|
91
|
+
if (nameNode) {
|
|
92
|
+
params.push({ name: nameNode.text, kind: 'parameter', line: param.startPosition.row + 1 });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return params;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function handleZigVariable(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
99
|
+
const nameNode = findChild(node, 'identifier');
|
|
100
|
+
if (!nameNode) return;
|
|
101
|
+
const name = nameNode.text;
|
|
102
|
+
|
|
103
|
+
// Check if this is a struct/enum/union definition
|
|
104
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
105
|
+
const child = node.child(i);
|
|
106
|
+
if (!child) continue;
|
|
107
|
+
|
|
108
|
+
if (child.type === 'struct_declaration') {
|
|
109
|
+
const members = extractZigContainerFields(child);
|
|
110
|
+
ctx.definitions.push({
|
|
111
|
+
name,
|
|
112
|
+
kind: 'struct',
|
|
113
|
+
line: node.startPosition.row + 1,
|
|
114
|
+
endLine: nodeEndLine(node),
|
|
115
|
+
children: members.length > 0 ? members : undefined,
|
|
116
|
+
visibility: isZigPub(node) ? 'public' : undefined,
|
|
117
|
+
});
|
|
118
|
+
extractZigContainerMethods(child, name, ctx);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (child.type === 'enum_declaration') {
|
|
122
|
+
ctx.definitions.push({
|
|
123
|
+
name,
|
|
124
|
+
kind: 'enum',
|
|
125
|
+
line: node.startPosition.row + 1,
|
|
126
|
+
endLine: nodeEndLine(node),
|
|
127
|
+
visibility: isZigPub(node) ? 'public' : undefined,
|
|
128
|
+
});
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (child.type === 'union_declaration') {
|
|
132
|
+
ctx.definitions.push({
|
|
133
|
+
name,
|
|
134
|
+
kind: 'struct',
|
|
135
|
+
line: node.startPosition.row + 1,
|
|
136
|
+
endLine: nodeEndLine(node),
|
|
137
|
+
visibility: isZigPub(node) ? 'public' : undefined,
|
|
138
|
+
});
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Check for @import
|
|
144
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
145
|
+
const child = node.child(i);
|
|
146
|
+
if (!child) continue;
|
|
147
|
+
if (child.type === 'builtin_function') {
|
|
148
|
+
const builtinId = findChild(child, 'builtin_identifier');
|
|
149
|
+
if (builtinId?.text === '@import') {
|
|
150
|
+
const args = findChild(child, 'arguments');
|
|
151
|
+
if (args) {
|
|
152
|
+
const strArg = findChild(args, 'string_literal') || findChild(args, 'string');
|
|
153
|
+
if (strArg) {
|
|
154
|
+
const source = strArg.text.replace(/^"|"$/g, '');
|
|
155
|
+
ctx.imports.push({
|
|
156
|
+
source,
|
|
157
|
+
names: [name],
|
|
158
|
+
line: node.startPosition.row + 1,
|
|
159
|
+
});
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Regular constant/variable
|
|
168
|
+
const isConst = hasChildText(node, 'const');
|
|
169
|
+
ctx.definitions.push({
|
|
170
|
+
name,
|
|
171
|
+
kind: isConst ? 'constant' : 'variable',
|
|
172
|
+
line: node.startPosition.row + 1,
|
|
173
|
+
endLine: nodeEndLine(node),
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function extractZigContainerFields(container: TreeSitterNode): SubDeclaration[] {
|
|
178
|
+
const fields: SubDeclaration[] = [];
|
|
179
|
+
for (let i = 0; i < container.childCount; i++) {
|
|
180
|
+
const child = container.child(i);
|
|
181
|
+
if (!child || child.type !== 'container_field') continue;
|
|
182
|
+
const nameNode = child.childForFieldName('name') || findChild(child, 'identifier');
|
|
183
|
+
if (nameNode) {
|
|
184
|
+
fields.push({ name: nameNode.text, kind: 'property', line: child.startPosition.row + 1 });
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return fields;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function extractZigContainerMethods(
|
|
191
|
+
container: TreeSitterNode,
|
|
192
|
+
parentName: string,
|
|
193
|
+
ctx: ExtractorOutput,
|
|
194
|
+
): void {
|
|
195
|
+
for (let i = 0; i < container.childCount; i++) {
|
|
196
|
+
const child = container.child(i);
|
|
197
|
+
if (!child || child.type !== 'function_declaration') continue;
|
|
198
|
+
const nameNode = child.childForFieldName('name');
|
|
199
|
+
if (nameNode) {
|
|
200
|
+
ctx.definitions.push({
|
|
201
|
+
name: `${parentName}.${nameNode.text}`,
|
|
202
|
+
kind: 'method',
|
|
203
|
+
line: child.startPosition.row + 1,
|
|
204
|
+
endLine: nodeEndLine(child),
|
|
205
|
+
visibility: isZigPub(child) ? 'public' : 'private',
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function handleZigCallExpression(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
212
|
+
const funcNode = node.childForFieldName('function');
|
|
213
|
+
if (!funcNode) return;
|
|
214
|
+
|
|
215
|
+
const call: Call = { name: '', line: node.startPosition.row + 1 };
|
|
216
|
+
|
|
217
|
+
if (funcNode.type === 'field_expression' || funcNode.type === 'field_access') {
|
|
218
|
+
const field = funcNode.childForFieldName('field') || funcNode.childForFieldName('member');
|
|
219
|
+
const value = funcNode.childForFieldName('value') || funcNode.child(0);
|
|
220
|
+
if (field) call.name = field.text;
|
|
221
|
+
if (value) call.receiver = value.text;
|
|
222
|
+
} else {
|
|
223
|
+
call.name = funcNode.text;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (call.name) ctx.calls.push(call);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function handleZigBuiltin(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
230
|
+
const builtinId = findChild(node, 'builtin_identifier');
|
|
231
|
+
if (!builtinId) return;
|
|
232
|
+
|
|
233
|
+
// Treat @import as import (when standalone, not in variable_declaration)
|
|
234
|
+
if (builtinId.text === '@import' && node.parent?.type !== 'variable_declaration') {
|
|
235
|
+
const args = findChild(node, 'arguments');
|
|
236
|
+
if (args) {
|
|
237
|
+
const strArg = findChild(args, 'string_literal') || findChild(args, 'string');
|
|
238
|
+
if (strArg) {
|
|
239
|
+
const source = strArg.text.replace(/^"|"$/g, '');
|
|
240
|
+
ctx.imports.push({
|
|
241
|
+
source,
|
|
242
|
+
names: ['@import'],
|
|
243
|
+
line: node.startPosition.row + 1,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Other builtins are calls
|
|
251
|
+
ctx.calls.push({ name: builtinId.text, line: node.startPosition.row + 1 });
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function handleZigTest(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
255
|
+
let name = 'test';
|
|
256
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
257
|
+
const child = node.child(i);
|
|
258
|
+
if (!child) continue;
|
|
259
|
+
if (child.type === 'string_literal' || child.type === 'string') {
|
|
260
|
+
// Extract the string content child if available, otherwise strip quotes
|
|
261
|
+
const content = findChild(child, 'string_content');
|
|
262
|
+
name = content ? content.text : child.text.replace(/^"|"$/g, '');
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
if (child.type === 'identifier') {
|
|
266
|
+
name = child.text;
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
ctx.definitions.push({
|
|
272
|
+
name,
|
|
273
|
+
kind: 'function',
|
|
274
|
+
line: node.startPosition.row + 1,
|
|
275
|
+
endLine: nodeEndLine(node),
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function isZigPub(node: TreeSitterNode): boolean {
|
|
280
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
281
|
+
const child = node.child(i);
|
|
282
|
+
if (child && child.type === 'pub') return true;
|
|
283
|
+
if (child && child.text === 'pub') return true;
|
|
284
|
+
}
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function hasChildText(node: TreeSitterNode, text: string): boolean {
|
|
289
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
290
|
+
const child = node.child(i);
|
|
291
|
+
if (child && child.text === text) return true;
|
|
292
|
+
}
|
|
293
|
+
return false;
|
|
294
|
+
}
|
package/src/features/ast.ts
CHANGED
|
@@ -12,10 +12,9 @@ import type { ASTNodeKind, BetterSqlite3Database, Definition, TreeSitterNode } f
|
|
|
12
12
|
|
|
13
13
|
// ─── Constants ────────────────────────────────────────────────────────
|
|
14
14
|
|
|
15
|
-
export const AST_NODE_KINDS: ASTNodeKind[] = ['
|
|
15
|
+
export const AST_NODE_KINDS: ASTNodeKind[] = ['new', 'string', 'regex', 'throw', 'await'];
|
|
16
16
|
|
|
17
17
|
const KIND_ICONS: Record<string, string> = {
|
|
18
|
-
call: '\u0192', // ƒ
|
|
19
18
|
new: '\u2295', // ⊕
|
|
20
19
|
string: '"',
|
|
21
20
|
regex: '/',
|
package/src/features/cfg.ts
CHANGED
|
@@ -369,7 +369,7 @@ export async function buildCFGData(
|
|
|
369
369
|
db: BetterSqlite3Database,
|
|
370
370
|
fileSymbols: Map<string, FileSymbols>,
|
|
371
371
|
rootDir: string,
|
|
372
|
-
|
|
372
|
+
_engineOpts?: {
|
|
373
373
|
nativeDb?: { bulkInsertCfg?(entries: Array<Record<string, unknown>>): number };
|
|
374
374
|
suspendJsDb?: () => void;
|
|
375
375
|
resumeJsDb?: () => void;
|
|
@@ -379,56 +379,11 @@ export async function buildCFGData(
|
|
|
379
379
|
// skip WASM parser init, tree parsing, and JS visitor entirely — just persist.
|
|
380
380
|
const allNative = allCfgNative(fileSymbols);
|
|
381
381
|
|
|
382
|
-
//
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
for (const [relPath, symbols] of fileSymbols) {
|
|
388
|
-
const ext = path.extname(relPath).toLowerCase();
|
|
389
|
-
if (!CFG_EXTENSIONS.has(ext)) continue;
|
|
390
|
-
|
|
391
|
-
for (const def of symbols.definitions) {
|
|
392
|
-
if (def.kind !== 'function' && def.kind !== 'method') continue;
|
|
393
|
-
if (!def.line) continue;
|
|
394
|
-
|
|
395
|
-
const nodeId = getFunctionNodeId(db, def.name, relPath, def.line);
|
|
396
|
-
if (!nodeId) continue;
|
|
397
|
-
|
|
398
|
-
deleteCfgForNode(db, nodeId);
|
|
399
|
-
if (!def.cfg?.blocks?.length) continue;
|
|
400
|
-
|
|
401
|
-
const cfg = def.cfg as unknown as { blocks: CfgBuildBlock[]; edges: CfgBuildEdge[] };
|
|
402
|
-
entries.push({
|
|
403
|
-
nodeId,
|
|
404
|
-
blocks: cfg.blocks.map((b) => ({
|
|
405
|
-
index: b.index,
|
|
406
|
-
blockType: b.type,
|
|
407
|
-
startLine: b.startLine ?? null,
|
|
408
|
-
endLine: b.endLine ?? null,
|
|
409
|
-
label: b.label ?? null,
|
|
410
|
-
})),
|
|
411
|
-
edges: cfg.edges.map((e) => ({
|
|
412
|
-
sourceIndex: e.sourceIndex,
|
|
413
|
-
targetIndex: e.targetIndex,
|
|
414
|
-
kind: e.kind,
|
|
415
|
-
})),
|
|
416
|
-
});
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
if (entries.length > 0) {
|
|
421
|
-
let inserted: number;
|
|
422
|
-
try {
|
|
423
|
-
engineOpts?.suspendJsDb?.();
|
|
424
|
-
inserted = nativeDb.bulkInsertCfg(entries);
|
|
425
|
-
} finally {
|
|
426
|
-
engineOpts?.resumeJsDb?.();
|
|
427
|
-
}
|
|
428
|
-
info(`CFG (native bulk): ${inserted} blocks across ${entries.length} functions`);
|
|
429
|
-
}
|
|
430
|
-
return;
|
|
431
|
-
}
|
|
382
|
+
// NOTE: nativeDb.bulkInsertCfg is intentionally NOT used here.
|
|
383
|
+
// The CFG path requires delete-before-insert (deleteCfgForNode) which creates
|
|
384
|
+
// a dual-connection WAL conflict when deletes go through JS (better-sqlite3)
|
|
385
|
+
// and inserts go through native (rusqlite). The JS-only persistNativeFileCfg
|
|
386
|
+
// path below handles both on a single connection safely.
|
|
432
387
|
|
|
433
388
|
const extToLang = buildExtToLangMap();
|
|
434
389
|
let parsers: unknown = null;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { loadNative } from '../../infrastructure/native.js';
|
|
1
2
|
import type { CodeGraph } from '../model.js';
|
|
2
3
|
|
|
3
4
|
export interface BfsOpts {
|
|
@@ -8,6 +9,8 @@ export interface BfsOpts {
|
|
|
8
9
|
/**
|
|
9
10
|
* Breadth-first traversal on a CodeGraph.
|
|
10
11
|
*
|
|
12
|
+
* Tries the native Rust implementation first, falls back to JS.
|
|
13
|
+
*
|
|
11
14
|
* @returns nodeId → depth from nearest start node
|
|
12
15
|
*/
|
|
13
16
|
export function bfs(
|
|
@@ -19,6 +22,37 @@ export function bfs(
|
|
|
19
22
|
const direction = opts.direction ?? 'forward';
|
|
20
23
|
const starts = Array.isArray(startIds) ? startIds : [startIds];
|
|
21
24
|
|
|
25
|
+
const native = loadNative();
|
|
26
|
+
if (native?.bfsTraversal) {
|
|
27
|
+
const edges = graph.toEdgeArray();
|
|
28
|
+
const nativeMaxDepth = maxDepth === Infinity ? null : maxDepth;
|
|
29
|
+
// Undirected graphs deduplicate edges to one canonical direction in toEdgeArray(),
|
|
30
|
+
// so the Rust side must traverse both directions to preserve symmetry.
|
|
31
|
+
const nativeDirection = !graph.directed ? 'both' : direction;
|
|
32
|
+
const result = native.bfsTraversal(edges, starts, nativeMaxDepth, nativeDirection);
|
|
33
|
+
const depths = new Map<string, number>();
|
|
34
|
+
for (const entry of result) {
|
|
35
|
+
depths.set(entry.node, entry.depth);
|
|
36
|
+
}
|
|
37
|
+
// The Rust side only knows nodes referenced by edges; restore any isolated start nodes.
|
|
38
|
+
for (const startId of starts) {
|
|
39
|
+
if (graph.hasNode(startId) && !depths.has(startId)) {
|
|
40
|
+
depths.set(startId, 0);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return depths;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return bfsJS(graph, starts, maxDepth, direction);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Pure JS fallback for BFS (used when native addon is unavailable). */
|
|
50
|
+
function bfsJS(
|
|
51
|
+
graph: CodeGraph,
|
|
52
|
+
starts: string[],
|
|
53
|
+
maxDepth: number,
|
|
54
|
+
direction: string,
|
|
55
|
+
): Map<string, number> {
|
|
22
56
|
const depths = new Map<string, number>();
|
|
23
57
|
const queue: string[] = [];
|
|
24
58
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { loadNative } from '../../infrastructure/native.js';
|
|
1
2
|
import type { CodeGraph } from '../model.js';
|
|
2
3
|
|
|
3
4
|
export interface FanInOut {
|
|
@@ -7,8 +8,37 @@ export interface FanInOut {
|
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Fan-in / fan-out centrality for all nodes in a CodeGraph.
|
|
11
|
+
*
|
|
12
|
+
* Tries the native Rust implementation first, falls back to JS.
|
|
10
13
|
*/
|
|
11
14
|
export function fanInOut(graph: CodeGraph): Map<string, FanInOut> {
|
|
15
|
+
const native = loadNative();
|
|
16
|
+
if (native?.fanInOut) {
|
|
17
|
+
let edges = graph.toEdgeArray();
|
|
18
|
+
if (!graph.directed) {
|
|
19
|
+
// Undirected: toEdgeArray() deduplicates to one canonical direction;
|
|
20
|
+
// mirror each edge so the Rust side counts symmetric in/out degrees.
|
|
21
|
+
edges = [...edges, ...edges.map((e) => ({ source: e.target, target: e.source }))];
|
|
22
|
+
}
|
|
23
|
+
const nativeResult = native.fanInOut(edges);
|
|
24
|
+
const result = new Map<string, FanInOut>();
|
|
25
|
+
for (const entry of nativeResult) {
|
|
26
|
+
result.set(entry.node, { fanIn: entry.fanIn, fanOut: entry.fanOut });
|
|
27
|
+
}
|
|
28
|
+
// Ensure isolated nodes (no edges) are included
|
|
29
|
+
for (const id of graph.nodeIds()) {
|
|
30
|
+
if (!result.has(id)) {
|
|
31
|
+
result.set(id, { fanIn: 0, fanOut: 0 });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return fanInOutJS(graph);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Pure JS fallback for fan-in/out. */
|
|
41
|
+
function fanInOutJS(graph: CodeGraph): Map<string, FanInOut> {
|
|
12
42
|
const result = new Map<string, FanInOut>();
|
|
13
43
|
for (const id of graph.nodeIds()) {
|
|
14
44
|
result.set(id, {
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Community detection via vendored Leiden algorithm.
|
|
2
|
+
* Community detection via native Rust Louvain or vendored Leiden algorithm.
|
|
3
3
|
* Maintains backward-compatible API: { assignments: Map<string, number>, modularity: number }
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* use `detectClusters` from `./leiden/index.js` directly.
|
|
5
|
+
* Native path: classic Louvain (Rust, undirected modularity optimization).
|
|
6
|
+
* JS fallback: Leiden algorithm via `detectClusters` (always undirected, `directed: false`).
|
|
8
7
|
*/
|
|
8
|
+
|
|
9
|
+
import { warn } from '../../infrastructure/logger.js';
|
|
10
|
+
import { loadNative } from '../../infrastructure/native.js';
|
|
9
11
|
import type { CodeGraph } from '../model.js';
|
|
10
12
|
import type { DetectClustersResult } from './leiden/index.js';
|
|
11
13
|
import { detectClusters } from './leiden/index.js';
|
|
@@ -28,6 +30,31 @@ export function louvainCommunities(graph: CodeGraph, opts: LouvainOptions = {}):
|
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
const resolution: number = opts.resolution ?? 1.0;
|
|
33
|
+
|
|
34
|
+
const native = loadNative();
|
|
35
|
+
if (native?.louvainCommunities) {
|
|
36
|
+
// maxLevels, maxLocalPasses, and refinementTheta are Leiden-specific tuning knobs
|
|
37
|
+
// not supported by the Rust Louvain implementation. Warn callers who set them.
|
|
38
|
+
if (opts.maxLevels != null || opts.maxLocalPasses != null || opts.refinementTheta != null) {
|
|
39
|
+
warn(
|
|
40
|
+
'louvainCommunities: maxLevels/maxLocalPasses/refinementTheta are ignored by the native Rust path',
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
const edges = graph.toEdgeArray();
|
|
44
|
+
const nodeIds = graph.nodeIds();
|
|
45
|
+
const result = native.louvainCommunities(edges, nodeIds, resolution, 42);
|
|
46
|
+
const assignments = new Map<string, number>();
|
|
47
|
+
for (const entry of result.assignments) {
|
|
48
|
+
assignments.set(entry.node, entry.community);
|
|
49
|
+
}
|
|
50
|
+
return { assignments, modularity: result.modularity };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return louvainJS(graph, opts, resolution);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** JS fallback using the vendored Leiden algorithm. */
|
|
57
|
+
function louvainJS(graph: CodeGraph, opts: LouvainOptions, resolution: number): LouvainResult {
|
|
31
58
|
const result: DetectClustersResult = detectClusters(graph, {
|
|
32
59
|
resolution,
|
|
33
60
|
randomSeed: 42,
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { loadNative } from '../../infrastructure/native.js';
|
|
1
2
|
import type { CodeGraph } from '../model.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* BFS-based shortest path on a CodeGraph.
|
|
5
6
|
*
|
|
7
|
+
* Tries the native Rust implementation first, falls back to JS.
|
|
8
|
+
*
|
|
6
9
|
* @returns Path from fromId to toId (inclusive), or null if unreachable
|
|
7
10
|
*/
|
|
8
11
|
export function shortestPath(graph: CodeGraph, fromId: string, toId: string): string[] | null {
|
|
@@ -12,6 +15,23 @@ export function shortestPath(graph: CodeGraph, fromId: string, toId: string): st
|
|
|
12
15
|
if (!graph.hasNode(from) || !graph.hasNode(to)) return null;
|
|
13
16
|
if (from === to) return [from];
|
|
14
17
|
|
|
18
|
+
const native = loadNative();
|
|
19
|
+
if (native?.shortestPath) {
|
|
20
|
+
let edges = graph.toEdgeArray();
|
|
21
|
+
if (!graph.directed) {
|
|
22
|
+
// Undirected: toEdgeArray() deduplicates to one canonical direction;
|
|
23
|
+
// mirror each edge so the Rust BFS can traverse in both directions.
|
|
24
|
+
edges = [...edges, ...edges.map((e) => ({ source: e.target, target: e.source }))];
|
|
25
|
+
}
|
|
26
|
+
const result = native.shortestPath(edges, from, to);
|
|
27
|
+
return result.length > 0 ? result : null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return shortestPathJS(graph, from, to);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Pure JS fallback for shortest path. */
|
|
34
|
+
function shortestPathJS(graph: CodeGraph, from: string, to: string): string[] | null {
|
|
15
35
|
const parent = new Map<string, string | null>();
|
|
16
36
|
parent.set(from, null);
|
|
17
37
|
const queue = [from];
|
|
@@ -23,7 +43,6 @@ export function shortestPath(graph: CodeGraph, fromId: string, toId: string): st
|
|
|
23
43
|
if (parent.has(neighbor)) continue;
|
|
24
44
|
parent.set(neighbor, current);
|
|
25
45
|
if (neighbor === to) {
|
|
26
|
-
// Reconstruct path
|
|
27
46
|
const path: string[] = [];
|
|
28
47
|
let node: string | null = to;
|
|
29
48
|
while (node !== null) {
|