@optave/codegraph 3.0.2 → 3.0.4
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 +5 -5
- package/package.json +8 -5
- package/src/ast.js +32 -34
- package/src/builder.js +45 -10
- package/src/cfg.js +28 -9
- package/src/dataflow.js +33 -29
- package/src/native.js +23 -3
- package/src/parser.js +58 -2
package/README.md
CHANGED
|
@@ -555,14 +555,14 @@ Self-measured on every release via CI ([build benchmarks](generated/benchmarks/B
|
|
|
555
555
|
|
|
556
556
|
| Metric | Latest |
|
|
557
557
|
|---|---|
|
|
558
|
-
| Build speed (native) | **
|
|
559
|
-
| Build speed (WASM) | **
|
|
558
|
+
| Build speed (native) | **12.3 ms/file** |
|
|
559
|
+
| Build speed (WASM) | **16.3 ms/file** |
|
|
560
560
|
| Query time | **3ms** |
|
|
561
561
|
| No-op rebuild (native) | **5ms** |
|
|
562
|
-
| 1-file rebuild (native) | **
|
|
563
|
-
| Query: fn-deps | **0.
|
|
562
|
+
| 1-file rebuild (native) | **375ms** |
|
|
563
|
+
| Query: fn-deps | **0.8ms** |
|
|
564
564
|
| Query: path | **0.8ms** |
|
|
565
|
-
| ~50,000 files (est.) | **~
|
|
565
|
+
| ~50,000 files (est.) | **~615.0s build** |
|
|
566
566
|
|
|
567
567
|
Metrics are normalized per file for cross-version comparability. Times above are for a full initial build — incremental rebuilds only re-parse changed files.
|
|
568
568
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@optave/codegraph",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.4",
|
|
4
4
|
"description": "Local code graph CLI — parse codebases with tree-sitter, build dependency graphs, query them",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -71,10 +71,13 @@
|
|
|
71
71
|
},
|
|
72
72
|
"optionalDependencies": {
|
|
73
73
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
74
|
-
"@optave/codegraph-darwin-arm64": "3.0.
|
|
75
|
-
"@optave/codegraph-darwin-x64": "3.0.
|
|
76
|
-
"@optave/codegraph-linux-
|
|
77
|
-
"@optave/codegraph-
|
|
74
|
+
"@optave/codegraph-darwin-arm64": "3.0.4",
|
|
75
|
+
"@optave/codegraph-darwin-x64": "3.0.4",
|
|
76
|
+
"@optave/codegraph-linux-arm64-gnu": "3.0.4",
|
|
77
|
+
"@optave/codegraph-linux-arm64-musl": "3.0.4",
|
|
78
|
+
"@optave/codegraph-linux-x64-gnu": "3.0.4",
|
|
79
|
+
"@optave/codegraph-linux-x64-musl": "3.0.4",
|
|
80
|
+
"@optave/codegraph-win32-x64-msvc": "3.0.4"
|
|
78
81
|
},
|
|
79
82
|
"devDependencies": {
|
|
80
83
|
"@biomejs/biome": "^2.4.4",
|
package/src/ast.js
CHANGED
|
@@ -165,13 +165,12 @@ export async function buildAstNodes(db, fileSymbols, _rootDir, _engineOpts) {
|
|
|
165
165
|
}
|
|
166
166
|
});
|
|
167
167
|
|
|
168
|
-
|
|
168
|
+
const allRows = [];
|
|
169
169
|
|
|
170
170
|
for (const [relPath, symbols] of fileSymbols) {
|
|
171
|
-
const rows = [];
|
|
172
171
|
const defs = symbols.definitions || [];
|
|
173
172
|
|
|
174
|
-
// Pre-load all node IDs for this file into a map
|
|
173
|
+
// Pre-load all node IDs for this file into a map (read-only, fast)
|
|
175
174
|
const nodeIdMap = new Map();
|
|
176
175
|
for (const row of bulkGetNodeIds.all(relPath)) {
|
|
177
176
|
nodeIdMap.set(`${row.name}|${row.kind}|${row.line}`, row.id);
|
|
@@ -186,7 +185,7 @@ export async function buildAstNodes(db, fileSymbols, _rootDir, _engineOpts) {
|
|
|
186
185
|
parentNodeId =
|
|
187
186
|
nodeIdMap.get(`${parentDef.name}|${parentDef.kind}|${parentDef.line}`) || null;
|
|
188
187
|
}
|
|
189
|
-
|
|
188
|
+
allRows.push({
|
|
190
189
|
file: relPath,
|
|
191
190
|
line: call.line,
|
|
192
191
|
kind: 'call',
|
|
@@ -198,43 +197,42 @@ export async function buildAstNodes(db, fileSymbols, _rootDir, _engineOpts) {
|
|
|
198
197
|
}
|
|
199
198
|
}
|
|
200
199
|
|
|
201
|
-
// 2. AST
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
200
|
+
// 2. Non-call AST nodes (new, throw, await, string, regex)
|
|
201
|
+
if (symbols.astNodes?.length) {
|
|
202
|
+
// Native path: use pre-extracted AST nodes from Rust (all languages)
|
|
203
|
+
for (const n of symbols.astNodes) {
|
|
204
|
+
const parentDef = findParentDef(defs, n.line);
|
|
205
|
+
let parentNodeId = null;
|
|
206
|
+
if (parentDef) {
|
|
207
|
+
parentNodeId =
|
|
208
|
+
nodeIdMap.get(`${parentDef.name}|${parentDef.kind}|${parentDef.line}`) || null;
|
|
209
|
+
}
|
|
210
|
+
allRows.push({
|
|
211
|
+
file: relPath,
|
|
212
|
+
line: n.line,
|
|
213
|
+
kind: n.kind,
|
|
214
|
+
name: n.name,
|
|
215
|
+
text: n.text || null,
|
|
216
|
+
receiver: n.receiver || null,
|
|
217
|
+
parentNodeId,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
} else {
|
|
221
|
+
// WASM fallback: walk the tree-sitter AST (JS/TS/TSX only)
|
|
222
|
+
const ext = path.extname(relPath).toLowerCase();
|
|
223
|
+
if (WALK_EXTENSIONS.has(ext) && symbols._tree) {
|
|
206
224
|
const astRows = [];
|
|
207
225
|
walkAst(symbols._tree.rootNode, defs, relPath, astRows, nodeIdMap);
|
|
208
|
-
|
|
209
|
-
} else if (symbols.astNodes?.length) {
|
|
210
|
-
// Native path: use pre-extracted AST nodes from Rust
|
|
211
|
-
for (const n of symbols.astNodes) {
|
|
212
|
-
const parentDef = findParentDef(defs, n.line);
|
|
213
|
-
let parentNodeId = null;
|
|
214
|
-
if (parentDef) {
|
|
215
|
-
parentNodeId =
|
|
216
|
-
nodeIdMap.get(`${parentDef.name}|${parentDef.kind}|${parentDef.line}`) || null;
|
|
217
|
-
}
|
|
218
|
-
rows.push({
|
|
219
|
-
file: relPath,
|
|
220
|
-
line: n.line,
|
|
221
|
-
kind: n.kind,
|
|
222
|
-
name: n.name,
|
|
223
|
-
text: n.text || null,
|
|
224
|
-
receiver: n.receiver || null,
|
|
225
|
-
parentNodeId,
|
|
226
|
-
});
|
|
227
|
-
}
|
|
226
|
+
allRows.push(...astRows);
|
|
228
227
|
}
|
|
229
228
|
}
|
|
229
|
+
}
|
|
230
230
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
totalInserted += rows.length;
|
|
234
|
-
}
|
|
231
|
+
if (allRows.length > 0) {
|
|
232
|
+
tx(allRows);
|
|
235
233
|
}
|
|
236
234
|
|
|
237
|
-
debug(`AST extraction: ${
|
|
235
|
+
debug(`AST extraction: ${allRows.length} nodes stored`);
|
|
238
236
|
}
|
|
239
237
|
|
|
240
238
|
/**
|
package/src/builder.js
CHANGED
|
@@ -444,7 +444,7 @@ export async function buildGraph(rootDir, opts = {}) {
|
|
|
444
444
|
opts.incremental !== false && config.build && config.build.incremental !== false;
|
|
445
445
|
|
|
446
446
|
// Engine selection: 'native', 'wasm', or 'auto' (default)
|
|
447
|
-
const engineOpts = { engine: opts.engine || 'auto' };
|
|
447
|
+
const engineOpts = { engine: opts.engine || 'auto', dataflow: opts.dataflow !== false };
|
|
448
448
|
const { name: engineName, version: engineVersion } = getActiveEngine(engineOpts);
|
|
449
449
|
info(`Using ${engineName} engine${engineVersion ? ` (v${engineVersion})` : ''}`);
|
|
450
450
|
|
|
@@ -548,7 +548,11 @@ export async function buildGraph(rootDir, opts = {}) {
|
|
|
548
548
|
|
|
549
549
|
if (needsCfg || needsDataflow) {
|
|
550
550
|
info('No file changes. Running pending analysis pass...');
|
|
551
|
-
const
|
|
551
|
+
const analysisOpts = {
|
|
552
|
+
...engineOpts,
|
|
553
|
+
dataflow: needsDataflow && opts.dataflow !== false,
|
|
554
|
+
};
|
|
555
|
+
const analysisSymbols = await parseFilesAuto(files, rootDir, analysisOpts);
|
|
552
556
|
if (needsCfg) {
|
|
553
557
|
const { buildCFGData } = await import('./cfg.js');
|
|
554
558
|
await buildCFGData(db, analysisSymbols, rootDir, engineOpts);
|
|
@@ -1317,16 +1321,47 @@ export async function buildGraph(rootDir, opts = {}) {
|
|
|
1317
1321
|
_t.complexityMs = performance.now() - _t.complexity0;
|
|
1318
1322
|
|
|
1319
1323
|
// Pre-parse files missing WASM trees (native builds) so CFG + dataflow
|
|
1320
|
-
// share a single parse pass instead of each creating parsers independently
|
|
1324
|
+
// share a single parse pass instead of each creating parsers independently.
|
|
1325
|
+
// Skip entirely when native engine already provides CFG + dataflow data.
|
|
1321
1326
|
if (opts.cfg !== false || opts.dataflow !== false) {
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1327
|
+
const needsCfg = opts.cfg !== false;
|
|
1328
|
+
const needsDataflow = opts.dataflow !== false;
|
|
1329
|
+
|
|
1330
|
+
let needsWasmTrees = false;
|
|
1331
|
+
for (const [, symbols] of astComplexitySymbols) {
|
|
1332
|
+
if (symbols._tree) continue; // already has a tree
|
|
1333
|
+
// CFG: need tree if any function/method def lacks native CFG
|
|
1334
|
+
if (needsCfg) {
|
|
1335
|
+
const fnDefs = (symbols.definitions || []).filter(
|
|
1336
|
+
(d) => (d.kind === 'function' || d.kind === 'method') && d.line,
|
|
1337
|
+
);
|
|
1338
|
+
if (
|
|
1339
|
+
fnDefs.length > 0 &&
|
|
1340
|
+
!fnDefs.every((d) => d.cfg === null || Array.isArray(d.cfg?.blocks))
|
|
1341
|
+
) {
|
|
1342
|
+
needsWasmTrees = true;
|
|
1343
|
+
break;
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
// Dataflow: need tree if file lacks native dataflow
|
|
1347
|
+
if (needsDataflow && !symbols.dataflow) {
|
|
1348
|
+
needsWasmTrees = true;
|
|
1349
|
+
break;
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
if (needsWasmTrees) {
|
|
1354
|
+
_t.wasmPre0 = performance.now();
|
|
1355
|
+
try {
|
|
1356
|
+
const { ensureWasmTrees } = await import('./parser.js');
|
|
1357
|
+
await ensureWasmTrees(astComplexitySymbols, rootDir);
|
|
1358
|
+
} catch (err) {
|
|
1359
|
+
debug(`WASM pre-parse failed: ${err.message}`);
|
|
1360
|
+
}
|
|
1361
|
+
_t.wasmPreMs = performance.now() - _t.wasmPre0;
|
|
1362
|
+
} else {
|
|
1363
|
+
_t.wasmPreMs = 0;
|
|
1328
1364
|
}
|
|
1329
|
-
_t.wasmPreMs = performance.now() - _t.wasmPre0;
|
|
1330
1365
|
}
|
|
1331
1366
|
|
|
1332
1367
|
// CFG analysis (skip with --no-cfg)
|
package/src/cfg.js
CHANGED
|
@@ -1053,8 +1053,14 @@ export async function buildCFGData(db, fileSymbols, rootDir, _engineOpts) {
|
|
|
1053
1053
|
if (!symbols._tree) {
|
|
1054
1054
|
const ext = path.extname(relPath).toLowerCase();
|
|
1055
1055
|
if (CFG_EXTENSIONS.has(ext)) {
|
|
1056
|
-
|
|
1057
|
-
|
|
1056
|
+
// Check if all function/method defs already have native CFG data
|
|
1057
|
+
const hasNativeCfg = symbols.definitions
|
|
1058
|
+
.filter((d) => (d.kind === 'function' || d.kind === 'method') && d.line)
|
|
1059
|
+
.every((d) => d.cfg === null || d.cfg?.blocks?.length);
|
|
1060
|
+
if (!hasNativeCfg) {
|
|
1061
|
+
needsFallback = true;
|
|
1062
|
+
break;
|
|
1063
|
+
}
|
|
1058
1064
|
}
|
|
1059
1065
|
}
|
|
1060
1066
|
}
|
|
@@ -1102,8 +1108,13 @@ export async function buildCFGData(db, fileSymbols, rootDir, _engineOpts) {
|
|
|
1102
1108
|
let tree = symbols._tree;
|
|
1103
1109
|
let langId = symbols._langId;
|
|
1104
1110
|
|
|
1105
|
-
// WASM
|
|
1106
|
-
|
|
1111
|
+
// Check if all defs already have native CFG — skip WASM parse if so
|
|
1112
|
+
const allNative = symbols.definitions
|
|
1113
|
+
.filter((d) => (d.kind === 'function' || d.kind === 'method') && d.line)
|
|
1114
|
+
.every((d) => d.cfg === null || d.cfg?.blocks?.length);
|
|
1115
|
+
|
|
1116
|
+
// WASM fallback if no cached tree and not all native
|
|
1117
|
+
if (!tree && !allNative) {
|
|
1107
1118
|
if (!extToLang || !getParserFn) continue;
|
|
1108
1119
|
langId = extToLang.get(ext);
|
|
1109
1120
|
if (!langId || !CFG_LANG_IDS.has(langId)) continue;
|
|
@@ -1135,7 +1146,7 @@ export async function buildCFGData(db, fileSymbols, rootDir, _engineOpts) {
|
|
|
1135
1146
|
if (!cfgRules) continue;
|
|
1136
1147
|
|
|
1137
1148
|
const complexityRules = COMPLEXITY_RULES.get(langId);
|
|
1138
|
-
|
|
1149
|
+
// complexityRules only needed for WASM fallback path
|
|
1139
1150
|
|
|
1140
1151
|
for (const def of symbols.definitions) {
|
|
1141
1152
|
if (def.kind !== 'function' && def.kind !== 'method') continue;
|
|
@@ -1144,11 +1155,19 @@ export async function buildCFGData(db, fileSymbols, rootDir, _engineOpts) {
|
|
|
1144
1155
|
const row = getNodeId.get(def.name, relPath, def.line);
|
|
1145
1156
|
if (!row) continue;
|
|
1146
1157
|
|
|
1147
|
-
|
|
1148
|
-
|
|
1158
|
+
// Native path: use pre-computed CFG from Rust engine
|
|
1159
|
+
let cfg = null;
|
|
1160
|
+
if (def.cfg?.blocks?.length) {
|
|
1161
|
+
cfg = def.cfg;
|
|
1162
|
+
} else {
|
|
1163
|
+
// WASM fallback: compute CFG from tree-sitter AST
|
|
1164
|
+
if (!tree || !complexityRules) continue;
|
|
1165
|
+
const funcNode = findFunctionNode(tree.rootNode, def.line, def.endLine, complexityRules);
|
|
1166
|
+
if (!funcNode) continue;
|
|
1167
|
+
cfg = buildFunctionCFG(funcNode, langId);
|
|
1168
|
+
}
|
|
1149
1169
|
|
|
1150
|
-
|
|
1151
|
-
if (cfg.blocks.length === 0) continue;
|
|
1170
|
+
if (!cfg || cfg.blocks.length === 0) continue;
|
|
1152
1171
|
|
|
1153
1172
|
// Clear old CFG data for this function
|
|
1154
1173
|
deleteEdges.run(row.id);
|
package/src/dataflow.js
CHANGED
|
@@ -1009,7 +1009,7 @@ export async function buildDataflowEdges(db, fileSymbols, rootDir, _engineOpts)
|
|
|
1009
1009
|
let needsFallback = false;
|
|
1010
1010
|
|
|
1011
1011
|
for (const [relPath, symbols] of fileSymbols) {
|
|
1012
|
-
if (!symbols._tree) {
|
|
1012
|
+
if (!symbols._tree && !symbols.dataflow) {
|
|
1013
1013
|
const ext = path.extname(relPath).toLowerCase();
|
|
1014
1014
|
if (DATAFLOW_EXTENSIONS.has(ext)) {
|
|
1015
1015
|
needsFallback = true;
|
|
@@ -1061,41 +1061,45 @@ export async function buildDataflowEdges(db, fileSymbols, rootDir, _engineOpts)
|
|
|
1061
1061
|
const ext = path.extname(relPath).toLowerCase();
|
|
1062
1062
|
if (!DATAFLOW_EXTENSIONS.has(ext)) continue;
|
|
1063
1063
|
|
|
1064
|
-
|
|
1065
|
-
let
|
|
1064
|
+
// Use native dataflow data if available — skip WASM extraction
|
|
1065
|
+
let data = symbols.dataflow;
|
|
1066
|
+
if (!data) {
|
|
1067
|
+
let tree = symbols._tree;
|
|
1068
|
+
let langId = symbols._langId;
|
|
1069
|
+
|
|
1070
|
+
// WASM fallback if no cached tree
|
|
1071
|
+
if (!tree) {
|
|
1072
|
+
if (!extToLang || !getParserFn) continue;
|
|
1073
|
+
langId = extToLang.get(ext);
|
|
1074
|
+
if (!langId || !DATAFLOW_LANG_IDS.has(langId)) continue;
|
|
1075
|
+
|
|
1076
|
+
const absPath = path.join(rootDir, relPath);
|
|
1077
|
+
let code;
|
|
1078
|
+
try {
|
|
1079
|
+
code = fs.readFileSync(absPath, 'utf-8');
|
|
1080
|
+
} catch {
|
|
1081
|
+
continue;
|
|
1082
|
+
}
|
|
1066
1083
|
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
if (!extToLang || !getParserFn) continue;
|
|
1070
|
-
langId = extToLang.get(ext);
|
|
1071
|
-
if (!langId || !DATAFLOW_LANG_IDS.has(langId)) continue;
|
|
1084
|
+
const parser = getParserFn(parsers, absPath);
|
|
1085
|
+
if (!parser) continue;
|
|
1072
1086
|
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
continue;
|
|
1087
|
+
try {
|
|
1088
|
+
tree = parser.parse(code);
|
|
1089
|
+
} catch {
|
|
1090
|
+
continue;
|
|
1091
|
+
}
|
|
1079
1092
|
}
|
|
1080
1093
|
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
try {
|
|
1085
|
-
tree = parser.parse(code);
|
|
1086
|
-
} catch {
|
|
1087
|
-
continue;
|
|
1094
|
+
if (!langId) {
|
|
1095
|
+
langId = extToLang ? extToLang.get(ext) : null;
|
|
1096
|
+
if (!langId) continue;
|
|
1088
1097
|
}
|
|
1089
|
-
}
|
|
1090
|
-
|
|
1091
|
-
if (!langId) {
|
|
1092
|
-
langId = extToLang ? extToLang.get(ext) : null;
|
|
1093
|
-
if (!langId) continue;
|
|
1094
|
-
}
|
|
1095
1098
|
|
|
1096
|
-
|
|
1099
|
+
if (!DATAFLOW_RULES.has(langId)) continue;
|
|
1097
1100
|
|
|
1098
|
-
|
|
1101
|
+
data = extractDataflow(tree, relPath, symbols.definitions, langId);
|
|
1102
|
+
}
|
|
1099
1103
|
|
|
1100
1104
|
// Resolve function names to node IDs in this file first, then globally
|
|
1101
1105
|
function resolveNode(funcName) {
|
package/src/native.js
CHANGED
|
@@ -12,9 +12,27 @@ import os from 'node:os';
|
|
|
12
12
|
let _cached; // undefined = not yet tried, null = failed, object = module
|
|
13
13
|
let _loadError = null;
|
|
14
14
|
|
|
15
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* Detect whether the current Linux environment uses glibc or musl.
|
|
17
|
+
* Returns 'gnu' for glibc, 'musl' for musl, 'gnu' as fallback.
|
|
18
|
+
*/
|
|
19
|
+
function detectLibc() {
|
|
20
|
+
try {
|
|
21
|
+
const { readdirSync } = require('node:fs');
|
|
22
|
+
const files = readdirSync('/lib');
|
|
23
|
+
if (files.some((f) => f.startsWith('ld-musl-') && f.endsWith('.so.1'))) {
|
|
24
|
+
return 'musl';
|
|
25
|
+
}
|
|
26
|
+
} catch {}
|
|
27
|
+
return 'gnu';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Map of (platform-arch[-libc]) → npm package name. */
|
|
16
31
|
const PLATFORM_PACKAGES = {
|
|
17
|
-
'linux-x64': '@optave/codegraph-linux-x64-gnu',
|
|
32
|
+
'linux-x64-gnu': '@optave/codegraph-linux-x64-gnu',
|
|
33
|
+
'linux-x64-musl': '@optave/codegraph-linux-x64-musl',
|
|
34
|
+
'linux-arm64-gnu': '@optave/codegraph-linux-arm64-gnu',
|
|
35
|
+
'linux-arm64-musl': '@optave/codegraph-linux-arm64-musl', // not yet published — placeholder for future CI target
|
|
18
36
|
'darwin-arm64': '@optave/codegraph-darwin-arm64',
|
|
19
37
|
'darwin-x64': '@optave/codegraph-darwin-x64',
|
|
20
38
|
'win32-x64': '@optave/codegraph-win32-x64-msvc',
|
|
@@ -29,7 +47,9 @@ export function loadNative() {
|
|
|
29
47
|
|
|
30
48
|
const require = createRequire(import.meta.url);
|
|
31
49
|
|
|
32
|
-
const
|
|
50
|
+
const platform = os.platform();
|
|
51
|
+
const arch = os.arch();
|
|
52
|
+
const key = platform === 'linux' ? `${platform}-${arch}-${detectLibc()}` : `${platform}-${arch}`;
|
|
33
53
|
const pkg = PLATFORM_PACKAGES[key];
|
|
34
54
|
if (pkg) {
|
|
35
55
|
try {
|
package/src/parser.js
CHANGED
|
@@ -205,6 +205,22 @@ function normalizeNativeSymbols(result) {
|
|
|
205
205
|
maintainabilityIndex: d.complexity.maintainabilityIndex ?? null,
|
|
206
206
|
}
|
|
207
207
|
: null,
|
|
208
|
+
cfg: d.cfg?.blocks?.length
|
|
209
|
+
? {
|
|
210
|
+
blocks: d.cfg.blocks.map((b) => ({
|
|
211
|
+
index: b.index,
|
|
212
|
+
type: b.type,
|
|
213
|
+
startLine: b.startLine,
|
|
214
|
+
endLine: b.endLine,
|
|
215
|
+
label: b.label ?? null,
|
|
216
|
+
})),
|
|
217
|
+
edges: d.cfg.edges.map((e) => ({
|
|
218
|
+
sourceIndex: e.sourceIndex,
|
|
219
|
+
targetIndex: e.targetIndex,
|
|
220
|
+
kind: e.kind,
|
|
221
|
+
})),
|
|
222
|
+
}
|
|
223
|
+
: null,
|
|
208
224
|
children: d.children?.length
|
|
209
225
|
? d.children.map((c) => ({
|
|
210
226
|
name: c.name,
|
|
@@ -253,6 +269,46 @@ function normalizeNativeSymbols(result) {
|
|
|
253
269
|
text: n.text ?? null,
|
|
254
270
|
receiver: n.receiver ?? null,
|
|
255
271
|
})),
|
|
272
|
+
dataflow: result.dataflow
|
|
273
|
+
? {
|
|
274
|
+
parameters: (result.dataflow.parameters || []).map((p) => ({
|
|
275
|
+
funcName: p.funcName,
|
|
276
|
+
paramName: p.paramName,
|
|
277
|
+
paramIndex: p.paramIndex,
|
|
278
|
+
line: p.line,
|
|
279
|
+
})),
|
|
280
|
+
returns: (result.dataflow.returns || []).map((r) => ({
|
|
281
|
+
funcName: r.funcName,
|
|
282
|
+
expression: r.expression ?? '',
|
|
283
|
+
referencedNames: r.referencedNames ?? [],
|
|
284
|
+
line: r.line,
|
|
285
|
+
})),
|
|
286
|
+
assignments: (result.dataflow.assignments || []).map((a) => ({
|
|
287
|
+
varName: a.varName,
|
|
288
|
+
callerFunc: a.callerFunc ?? null,
|
|
289
|
+
sourceCallName: a.sourceCallName,
|
|
290
|
+
expression: a.expression ?? '',
|
|
291
|
+
line: a.line,
|
|
292
|
+
})),
|
|
293
|
+
argFlows: (result.dataflow.argFlows ?? []).map((f) => ({
|
|
294
|
+
callerFunc: f.callerFunc ?? null,
|
|
295
|
+
calleeName: f.calleeName,
|
|
296
|
+
argIndex: f.argIndex,
|
|
297
|
+
argName: f.argName ?? null,
|
|
298
|
+
binding: f.bindingType ? { type: f.bindingType } : null,
|
|
299
|
+
confidence: f.confidence,
|
|
300
|
+
expression: f.expression ?? '',
|
|
301
|
+
line: f.line,
|
|
302
|
+
})),
|
|
303
|
+
mutations: (result.dataflow.mutations || []).map((m) => ({
|
|
304
|
+
funcName: m.funcName ?? null,
|
|
305
|
+
receiverName: m.receiverName,
|
|
306
|
+
binding: m.bindingType ? { type: m.bindingType } : null,
|
|
307
|
+
mutatingExpr: m.mutatingExpr,
|
|
308
|
+
line: m.line,
|
|
309
|
+
})),
|
|
310
|
+
}
|
|
311
|
+
: null,
|
|
256
312
|
};
|
|
257
313
|
}
|
|
258
314
|
|
|
@@ -384,7 +440,7 @@ export async function parseFileAuto(filePath, source, opts = {}) {
|
|
|
384
440
|
const { native } = resolveEngine(opts);
|
|
385
441
|
|
|
386
442
|
if (native) {
|
|
387
|
-
const result = native.parseFile(filePath, source);
|
|
443
|
+
const result = native.parseFile(filePath, source, !!opts.dataflow);
|
|
388
444
|
return result ? normalizeNativeSymbols(result) : null;
|
|
389
445
|
}
|
|
390
446
|
|
|
@@ -407,7 +463,7 @@ export async function parseFilesAuto(filePaths, rootDir, opts = {}) {
|
|
|
407
463
|
const result = new Map();
|
|
408
464
|
|
|
409
465
|
if (native) {
|
|
410
|
-
const nativeResults = native.parseFiles(filePaths, rootDir);
|
|
466
|
+
const nativeResults = native.parseFiles(filePaths, rootDir, !!opts.dataflow);
|
|
411
467
|
for (const r of nativeResults) {
|
|
412
468
|
if (!r) continue;
|
|
413
469
|
const relPath = path.relative(rootDir, r.file).split(path.sep).join('/');
|