@optave/codegraph 3.0.4 → 3.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/README.md +60 -53
- package/package.json +9 -9
- package/src/builder.js +274 -154
- package/src/cfg.js +11 -9
- package/src/cli.js +35 -0
- package/src/dataflow.js +11 -9
- package/src/db.js +7 -0
- package/src/flow.js +3 -70
- package/src/index.js +2 -1
- package/src/mcp.js +60 -0
- package/src/parser.js +58 -131
- package/src/queries.js +60 -21
- package/src/resolve.js +11 -2
- package/src/sequence.js +369 -0
package/src/cli.js
CHANGED
|
@@ -277,6 +277,7 @@ program
|
|
|
277
277
|
.option('--limit <number>', 'Max results to return')
|
|
278
278
|
.option('--offset <number>', 'Skip N results (default: 0)')
|
|
279
279
|
.option('--ndjson', 'Newline-delimited JSON output')
|
|
280
|
+
.option('--unused', 'Show only exports with zero consumers')
|
|
280
281
|
.action((file, opts) => {
|
|
281
282
|
fileExports(file, opts.db, {
|
|
282
283
|
noTests: resolveNoTests(opts),
|
|
@@ -284,6 +285,7 @@ program
|
|
|
284
285
|
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
|
|
285
286
|
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
|
|
286
287
|
ndjson: opts.ndjson,
|
|
288
|
+
unused: opts.unused,
|
|
287
289
|
});
|
|
288
290
|
});
|
|
289
291
|
|
|
@@ -1137,6 +1139,39 @@ program
|
|
|
1137
1139
|
});
|
|
1138
1140
|
});
|
|
1139
1141
|
|
|
1142
|
+
program
|
|
1143
|
+
.command('sequence <name>')
|
|
1144
|
+
.description('Generate a Mermaid sequence diagram from call graph edges (participants = files)')
|
|
1145
|
+
.option('--depth <n>', 'Max forward traversal depth', '10')
|
|
1146
|
+
.option('--dataflow', 'Annotate with parameter names and return arrows from dataflow table')
|
|
1147
|
+
.option('-d, --db <path>', 'Path to graph.db')
|
|
1148
|
+
.option('-f, --file <path>', 'Scope to a specific file (partial match)')
|
|
1149
|
+
.option('-k, --kind <kind>', 'Filter by symbol kind')
|
|
1150
|
+
.option('-T, --no-tests', 'Exclude test/spec files from results')
|
|
1151
|
+
.option('--include-tests', 'Include test/spec files (overrides excludeTests config)')
|
|
1152
|
+
.option('-j, --json', 'Output as JSON')
|
|
1153
|
+
.option('--limit <number>', 'Max results to return')
|
|
1154
|
+
.option('--offset <number>', 'Skip N results (default: 0)')
|
|
1155
|
+
.option('--ndjson', 'Newline-delimited JSON output')
|
|
1156
|
+
.action(async (name, opts) => {
|
|
1157
|
+
if (opts.kind && !EVERY_SYMBOL_KIND.includes(opts.kind)) {
|
|
1158
|
+
console.error(`Invalid kind "${opts.kind}". Valid: ${EVERY_SYMBOL_KIND.join(', ')}`);
|
|
1159
|
+
process.exit(1);
|
|
1160
|
+
}
|
|
1161
|
+
const { sequence } = await import('./sequence.js');
|
|
1162
|
+
sequence(name, opts.db, {
|
|
1163
|
+
depth: parseInt(opts.depth, 10),
|
|
1164
|
+
file: opts.file,
|
|
1165
|
+
kind: opts.kind,
|
|
1166
|
+
noTests: resolveNoTests(opts),
|
|
1167
|
+
json: opts.json,
|
|
1168
|
+
dataflow: opts.dataflow,
|
|
1169
|
+
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
|
|
1170
|
+
offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
|
|
1171
|
+
ndjson: opts.ndjson,
|
|
1172
|
+
});
|
|
1173
|
+
});
|
|
1174
|
+
|
|
1140
1175
|
program
|
|
1141
1176
|
.command('dataflow <name>')
|
|
1142
1177
|
.description('Show data flow for a function: parameters, return consumers, mutations')
|
package/src/dataflow.js
CHANGED
|
@@ -1005,9 +1005,17 @@ function collectIdentifiers(node, out, rules) {
|
|
|
1005
1005
|
export async function buildDataflowEdges(db, fileSymbols, rootDir, _engineOpts) {
|
|
1006
1006
|
// Lazily init WASM parsers if needed
|
|
1007
1007
|
let parsers = null;
|
|
1008
|
-
let extToLang = null;
|
|
1009
1008
|
let needsFallback = false;
|
|
1010
1009
|
|
|
1010
|
+
// Always build ext→langId map so native-only builds (where _langId is unset)
|
|
1011
|
+
// can still derive the language from the file extension.
|
|
1012
|
+
const extToLang = new Map();
|
|
1013
|
+
for (const entry of LANGUAGE_REGISTRY) {
|
|
1014
|
+
for (const ext of entry.extensions) {
|
|
1015
|
+
extToLang.set(ext, entry.id);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1011
1019
|
for (const [relPath, symbols] of fileSymbols) {
|
|
1012
1020
|
if (!symbols._tree && !symbols.dataflow) {
|
|
1013
1021
|
const ext = path.extname(relPath).toLowerCase();
|
|
@@ -1021,12 +1029,6 @@ export async function buildDataflowEdges(db, fileSymbols, rootDir, _engineOpts)
|
|
|
1021
1029
|
if (needsFallback) {
|
|
1022
1030
|
const { createParsers } = await import('./parser.js');
|
|
1023
1031
|
parsers = await createParsers();
|
|
1024
|
-
extToLang = new Map();
|
|
1025
|
-
for (const entry of LANGUAGE_REGISTRY) {
|
|
1026
|
-
for (const ext of entry.extensions) {
|
|
1027
|
-
extToLang.set(ext, entry.id);
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
1030
1032
|
}
|
|
1031
1033
|
|
|
1032
1034
|
let getParserFn = null;
|
|
@@ -1069,7 +1071,7 @@ export async function buildDataflowEdges(db, fileSymbols, rootDir, _engineOpts)
|
|
|
1069
1071
|
|
|
1070
1072
|
// WASM fallback if no cached tree
|
|
1071
1073
|
if (!tree) {
|
|
1072
|
-
if (!
|
|
1074
|
+
if (!getParserFn) continue;
|
|
1073
1075
|
langId = extToLang.get(ext);
|
|
1074
1076
|
if (!langId || !DATAFLOW_LANG_IDS.has(langId)) continue;
|
|
1075
1077
|
|
|
@@ -1092,7 +1094,7 @@ export async function buildDataflowEdges(db, fileSymbols, rootDir, _engineOpts)
|
|
|
1092
1094
|
}
|
|
1093
1095
|
|
|
1094
1096
|
if (!langId) {
|
|
1095
|
-
langId = extToLang
|
|
1097
|
+
langId = extToLang.get(ext);
|
|
1096
1098
|
if (!langId) continue;
|
|
1097
1099
|
}
|
|
1098
1100
|
|
package/src/db.js
CHANGED
|
@@ -225,6 +225,13 @@ export const MIGRATIONS = [
|
|
|
225
225
|
CREATE INDEX IF NOT EXISTS idx_ast_kind_name ON ast_nodes(kind, name);
|
|
226
226
|
`,
|
|
227
227
|
},
|
|
228
|
+
{
|
|
229
|
+
version: 14,
|
|
230
|
+
up: `
|
|
231
|
+
ALTER TABLE nodes ADD COLUMN exported INTEGER DEFAULT 0;
|
|
232
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_exported ON nodes(exported);
|
|
233
|
+
`,
|
|
234
|
+
},
|
|
228
235
|
];
|
|
229
236
|
|
|
230
237
|
export function getBuildMeta(db, key) {
|
package/src/flow.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { openReadonlyOrFail } from './db.js';
|
|
9
9
|
import { paginateResult, printNdjson } from './paginate.js';
|
|
10
|
-
import { isTestFile, kindIcon } from './queries.js';
|
|
10
|
+
import { findMatchingNodes, isTestFile, kindIcon } from './queries.js';
|
|
11
11
|
import { FRAMEWORK_ENTRY_PREFIXES } from './structure.js';
|
|
12
12
|
|
|
13
13
|
/**
|
|
@@ -95,12 +95,12 @@ export function flowData(name, dbPath, opts = {}) {
|
|
|
95
95
|
const noTests = opts.noTests || false;
|
|
96
96
|
|
|
97
97
|
// Phase 1: Direct LIKE match on full name
|
|
98
|
-
let matchNode =
|
|
98
|
+
let matchNode = findMatchingNodes(db, name, opts)[0] ?? null;
|
|
99
99
|
|
|
100
100
|
// Phase 2: Prefix-stripped matching — try adding framework prefixes
|
|
101
101
|
if (!matchNode) {
|
|
102
102
|
for (const prefix of FRAMEWORK_ENTRY_PREFIXES) {
|
|
103
|
-
matchNode =
|
|
103
|
+
matchNode = findMatchingNodes(db, `${prefix}${name}`, opts)[0] ?? null;
|
|
104
104
|
if (matchNode) break;
|
|
105
105
|
}
|
|
106
106
|
}
|
|
@@ -219,73 +219,6 @@ export function flowData(name, dbPath, opts = {}) {
|
|
|
219
219
|
return paginateResult(base, 'steps', { limit: opts.limit, offset: opts.offset });
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
-
/**
|
|
223
|
-
* Find the best matching node using the same relevance scoring as queries.js findMatchingNodes.
|
|
224
|
-
*/
|
|
225
|
-
function findBestMatch(db, name, opts = {}) {
|
|
226
|
-
const kinds = opts.kind
|
|
227
|
-
? [opts.kind]
|
|
228
|
-
: [
|
|
229
|
-
'function',
|
|
230
|
-
'method',
|
|
231
|
-
'class',
|
|
232
|
-
'interface',
|
|
233
|
-
'type',
|
|
234
|
-
'struct',
|
|
235
|
-
'enum',
|
|
236
|
-
'trait',
|
|
237
|
-
'record',
|
|
238
|
-
'module',
|
|
239
|
-
];
|
|
240
|
-
const placeholders = kinds.map(() => '?').join(', ');
|
|
241
|
-
const params = [`%${name}%`, ...kinds];
|
|
242
|
-
|
|
243
|
-
let fileCondition = '';
|
|
244
|
-
if (opts.file) {
|
|
245
|
-
fileCondition = ' AND n.file LIKE ?';
|
|
246
|
-
params.push(`%${opts.file}%`);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
const rows = db
|
|
250
|
-
.prepare(
|
|
251
|
-
`SELECT n.*, COALESCE(fi.cnt, 0) AS fan_in
|
|
252
|
-
FROM nodes n
|
|
253
|
-
LEFT JOIN (
|
|
254
|
-
SELECT target_id, COUNT(*) AS cnt FROM edges WHERE kind = 'calls' GROUP BY target_id
|
|
255
|
-
) fi ON fi.target_id = n.id
|
|
256
|
-
WHERE n.name LIKE ? AND n.kind IN (${placeholders})${fileCondition}`,
|
|
257
|
-
)
|
|
258
|
-
.all(...params);
|
|
259
|
-
|
|
260
|
-
const noTests = opts.noTests || false;
|
|
261
|
-
const nodes = noTests ? rows.filter((n) => !isTestFile(n.file)) : rows;
|
|
262
|
-
|
|
263
|
-
if (nodes.length === 0) return null;
|
|
264
|
-
|
|
265
|
-
const lowerQuery = name.toLowerCase();
|
|
266
|
-
for (const node of nodes) {
|
|
267
|
-
const lowerName = node.name.toLowerCase();
|
|
268
|
-
const bareName = lowerName.includes('.') ? lowerName.split('.').pop() : lowerName;
|
|
269
|
-
|
|
270
|
-
let matchScore;
|
|
271
|
-
if (lowerName === lowerQuery || bareName === lowerQuery) {
|
|
272
|
-
matchScore = 100;
|
|
273
|
-
} else if (lowerName.startsWith(lowerQuery) || bareName.startsWith(lowerQuery)) {
|
|
274
|
-
matchScore = 60;
|
|
275
|
-
} else if (lowerName.includes(`.${lowerQuery}`) || lowerName.includes(`${lowerQuery}.`)) {
|
|
276
|
-
matchScore = 40;
|
|
277
|
-
} else {
|
|
278
|
-
matchScore = 10;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
const fanInBonus = Math.min(Math.log2(node.fan_in + 1) * 5, 25);
|
|
282
|
-
node._relevance = matchScore + fanInBonus;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
nodes.sort((a, b) => b._relevance - a._relevance);
|
|
286
|
-
return nodes[0];
|
|
287
|
-
}
|
|
288
|
-
|
|
289
222
|
/**
|
|
290
223
|
* CLI formatter — text or JSON output.
|
|
291
224
|
*/
|
package/src/index.js
CHANGED
|
@@ -121,7 +121,6 @@ export { isNativeAvailable } from './native.js';
|
|
|
121
121
|
export { matchOwners, owners, ownersData, ownersForFiles, parseCodeowners } from './owners.js';
|
|
122
122
|
// Pagination utilities
|
|
123
123
|
export { MCP_DEFAULTS, MCP_MAX_LIMIT, paginate, paginateResult, printNdjson } from './paginate.js';
|
|
124
|
-
|
|
125
124
|
// Unified parser API
|
|
126
125
|
export { getActiveEngine, isWasmAvailable, parseFileAuto, parseFilesAuto } from './parser.js';
|
|
127
126
|
// Query functions (data-returning)
|
|
@@ -170,6 +169,8 @@ export {
|
|
|
170
169
|
saveRegistry,
|
|
171
170
|
unregisterRepo,
|
|
172
171
|
} from './registry.js';
|
|
172
|
+
// Sequence diagram generation
|
|
173
|
+
export { sequence, sequenceData, sequenceToMermaid } from './sequence.js';
|
|
173
174
|
// Snapshot management
|
|
174
175
|
export {
|
|
175
176
|
snapshotDelete,
|
package/src/mcp.js
CHANGED
|
@@ -113,6 +113,11 @@ const BASE_TOOLS = [
|
|
|
113
113
|
properties: {
|
|
114
114
|
file: { type: 'string', description: 'File path (partial match supported)' },
|
|
115
115
|
no_tests: { type: 'boolean', description: 'Exclude test files', default: false },
|
|
116
|
+
unused: {
|
|
117
|
+
type: 'boolean',
|
|
118
|
+
description: 'Show only exports with zero consumers',
|
|
119
|
+
default: false,
|
|
120
|
+
},
|
|
116
121
|
...PAGINATION_PROPS,
|
|
117
122
|
},
|
|
118
123
|
required: ['file'],
|
|
@@ -418,6 +423,43 @@ const BASE_TOOLS = [
|
|
|
418
423
|
},
|
|
419
424
|
},
|
|
420
425
|
},
|
|
426
|
+
{
|
|
427
|
+
name: 'sequence',
|
|
428
|
+
description:
|
|
429
|
+
'Generate a Mermaid sequence diagram from call graph edges. Participants are files, messages are function calls between them.',
|
|
430
|
+
inputSchema: {
|
|
431
|
+
type: 'object',
|
|
432
|
+
properties: {
|
|
433
|
+
name: {
|
|
434
|
+
type: 'string',
|
|
435
|
+
description: 'Entry point or function name to trace from (partial match)',
|
|
436
|
+
},
|
|
437
|
+
depth: { type: 'number', description: 'Max forward traversal depth', default: 10 },
|
|
438
|
+
format: {
|
|
439
|
+
type: 'string',
|
|
440
|
+
enum: ['mermaid', 'json'],
|
|
441
|
+
description: 'Output format (default: mermaid)',
|
|
442
|
+
},
|
|
443
|
+
dataflow: {
|
|
444
|
+
type: 'boolean',
|
|
445
|
+
description: 'Annotate with parameter names and return arrows',
|
|
446
|
+
default: false,
|
|
447
|
+
},
|
|
448
|
+
file: {
|
|
449
|
+
type: 'string',
|
|
450
|
+
description: 'Scope search to functions in this file (partial match)',
|
|
451
|
+
},
|
|
452
|
+
kind: {
|
|
453
|
+
type: 'string',
|
|
454
|
+
enum: EVERY_SYMBOL_KIND,
|
|
455
|
+
description: 'Filter to a specific symbol kind',
|
|
456
|
+
},
|
|
457
|
+
no_tests: { type: 'boolean', description: 'Exclude test files', default: false },
|
|
458
|
+
...PAGINATION_PROPS,
|
|
459
|
+
},
|
|
460
|
+
required: ['name'],
|
|
461
|
+
},
|
|
462
|
+
},
|
|
421
463
|
{
|
|
422
464
|
name: 'complexity',
|
|
423
465
|
description:
|
|
@@ -902,6 +944,7 @@ export async function startMCPServer(customDbPath, options = {}) {
|
|
|
902
944
|
case 'file_exports':
|
|
903
945
|
result = exportsData(args.file, dbPath, {
|
|
904
946
|
noTests: args.no_tests,
|
|
947
|
+
unused: args.unused,
|
|
905
948
|
limit: Math.min(args.limit ?? MCP_DEFAULTS.file_exports, MCP_MAX_LIMIT),
|
|
906
949
|
offset: args.offset ?? 0,
|
|
907
950
|
});
|
|
@@ -1165,6 +1208,23 @@ export async function startMCPServer(customDbPath, options = {}) {
|
|
|
1165
1208
|
}
|
|
1166
1209
|
break;
|
|
1167
1210
|
}
|
|
1211
|
+
case 'sequence': {
|
|
1212
|
+
const { sequenceData, sequenceToMermaid } = await import('./sequence.js');
|
|
1213
|
+
const seqResult = sequenceData(args.name, dbPath, {
|
|
1214
|
+
depth: args.depth,
|
|
1215
|
+
file: args.file,
|
|
1216
|
+
kind: args.kind,
|
|
1217
|
+
dataflow: args.dataflow,
|
|
1218
|
+
noTests: args.no_tests,
|
|
1219
|
+
limit: Math.min(args.limit ?? MCP_DEFAULTS.execution_flow, MCP_MAX_LIMIT),
|
|
1220
|
+
offset: args.offset ?? 0,
|
|
1221
|
+
});
|
|
1222
|
+
result =
|
|
1223
|
+
args.format === 'json'
|
|
1224
|
+
? seqResult
|
|
1225
|
+
: { text: sequenceToMermaid(seqResult), ...seqResult };
|
|
1226
|
+
break;
|
|
1227
|
+
}
|
|
1168
1228
|
case 'complexity': {
|
|
1169
1229
|
const { complexityData } = await import('./complexity.js');
|
|
1170
1230
|
result = complexityData(dbPath, {
|
package/src/parser.js
CHANGED
|
@@ -183,133 +183,55 @@ function resolveEngine(opts = {}) {
|
|
|
183
183
|
}
|
|
184
184
|
|
|
185
185
|
/**
|
|
186
|
-
*
|
|
187
|
-
*
|
|
186
|
+
* Patch native engine output in-place for the few remaining semantic transforms.
|
|
187
|
+
* With #[napi(js_name)] on Rust types, most fields already arrive as camelCase.
|
|
188
|
+
* This only handles:
|
|
189
|
+
* - _lineCount compat for builder.js
|
|
190
|
+
* - Backward compat for older native binaries missing js_name annotations
|
|
191
|
+
* - dataflow argFlows/mutations bindingType → binding wrapper
|
|
188
192
|
*/
|
|
189
|
-
function
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
?
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
: undefined,
|
|
232
|
-
})),
|
|
233
|
-
calls: (result.calls || []).map((c) => ({
|
|
234
|
-
name: c.name,
|
|
235
|
-
line: c.line,
|
|
236
|
-
dynamic: c.dynamic,
|
|
237
|
-
receiver: c.receiver,
|
|
238
|
-
})),
|
|
239
|
-
imports: (result.imports || []).map((i) => ({
|
|
240
|
-
source: i.source,
|
|
241
|
-
names: i.names || [],
|
|
242
|
-
line: i.line,
|
|
243
|
-
typeOnly: i.typeOnly ?? i.type_only,
|
|
244
|
-
reexport: i.reexport,
|
|
245
|
-
wildcardReexport: i.wildcardReexport ?? i.wildcard_reexport,
|
|
246
|
-
pythonImport: i.pythonImport ?? i.python_import,
|
|
247
|
-
goImport: i.goImport ?? i.go_import,
|
|
248
|
-
rustUse: i.rustUse ?? i.rust_use,
|
|
249
|
-
javaImport: i.javaImport ?? i.java_import,
|
|
250
|
-
csharpUsing: i.csharpUsing ?? i.csharp_using,
|
|
251
|
-
rubyRequire: i.rubyRequire ?? i.ruby_require,
|
|
252
|
-
phpUse: i.phpUse ?? i.php_use,
|
|
253
|
-
})),
|
|
254
|
-
classes: (result.classes || []).map((c) => ({
|
|
255
|
-
name: c.name,
|
|
256
|
-
extends: c.extends,
|
|
257
|
-
implements: c.implements,
|
|
258
|
-
line: c.line,
|
|
259
|
-
})),
|
|
260
|
-
exports: (result.exports || []).map((e) => ({
|
|
261
|
-
name: e.name,
|
|
262
|
-
kind: e.kind,
|
|
263
|
-
line: e.line,
|
|
264
|
-
})),
|
|
265
|
-
astNodes: (result.astNodes ?? result.ast_nodes ?? []).map((n) => ({
|
|
266
|
-
kind: n.kind,
|
|
267
|
-
name: n.name,
|
|
268
|
-
line: n.line,
|
|
269
|
-
text: n.text ?? null,
|
|
270
|
-
receiver: n.receiver ?? null,
|
|
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,
|
|
312
|
-
};
|
|
193
|
+
function patchNativeResult(r) {
|
|
194
|
+
// lineCount: napi(js_name) emits "lineCount"; older binaries may emit "line_count"
|
|
195
|
+
r.lineCount = r.lineCount ?? r.line_count ?? null;
|
|
196
|
+
r._lineCount = r.lineCount;
|
|
197
|
+
|
|
198
|
+
// Backward compat for older binaries missing js_name annotations
|
|
199
|
+
if (r.definitions) {
|
|
200
|
+
for (const d of r.definitions) {
|
|
201
|
+
if (d.endLine === undefined && d.end_line !== undefined) {
|
|
202
|
+
d.endLine = d.end_line;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (r.imports) {
|
|
207
|
+
for (const i of r.imports) {
|
|
208
|
+
if (i.typeOnly === undefined) i.typeOnly = i.type_only;
|
|
209
|
+
if (i.wildcardReexport === undefined) i.wildcardReexport = i.wildcard_reexport;
|
|
210
|
+
if (i.pythonImport === undefined) i.pythonImport = i.python_import;
|
|
211
|
+
if (i.goImport === undefined) i.goImport = i.go_import;
|
|
212
|
+
if (i.rustUse === undefined) i.rustUse = i.rust_use;
|
|
213
|
+
if (i.javaImport === undefined) i.javaImport = i.java_import;
|
|
214
|
+
if (i.csharpUsing === undefined) i.csharpUsing = i.csharp_using;
|
|
215
|
+
if (i.rubyRequire === undefined) i.rubyRequire = i.ruby_require;
|
|
216
|
+
if (i.phpUse === undefined) i.phpUse = i.php_use;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// dataflow: wrap bindingType into binding object for argFlows and mutations
|
|
221
|
+
if (r.dataflow) {
|
|
222
|
+
if (r.dataflow.argFlows) {
|
|
223
|
+
for (const f of r.dataflow.argFlows) {
|
|
224
|
+
f.binding = f.bindingType ? { type: f.bindingType } : null;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (r.dataflow.mutations) {
|
|
228
|
+
for (const m of r.dataflow.mutations) {
|
|
229
|
+
m.binding = m.bindingType ? { type: m.bindingType } : null;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return r;
|
|
313
235
|
}
|
|
314
236
|
|
|
315
237
|
/**
|
|
@@ -440,8 +362,8 @@ export async function parseFileAuto(filePath, source, opts = {}) {
|
|
|
440
362
|
const { native } = resolveEngine(opts);
|
|
441
363
|
|
|
442
364
|
if (native) {
|
|
443
|
-
const result = native.parseFile(filePath, source, !!opts.dataflow);
|
|
444
|
-
return result ?
|
|
365
|
+
const result = native.parseFile(filePath, source, !!opts.dataflow, opts.ast !== false);
|
|
366
|
+
return result ? patchNativeResult(result) : null;
|
|
445
367
|
}
|
|
446
368
|
|
|
447
369
|
// WASM path
|
|
@@ -463,11 +385,16 @@ export async function parseFilesAuto(filePaths, rootDir, opts = {}) {
|
|
|
463
385
|
const result = new Map();
|
|
464
386
|
|
|
465
387
|
if (native) {
|
|
466
|
-
const nativeResults = native.parseFiles(
|
|
388
|
+
const nativeResults = native.parseFiles(
|
|
389
|
+
filePaths,
|
|
390
|
+
rootDir,
|
|
391
|
+
!!opts.dataflow,
|
|
392
|
+
opts.ast !== false,
|
|
393
|
+
);
|
|
467
394
|
for (const r of nativeResults) {
|
|
468
395
|
if (!r) continue;
|
|
469
396
|
const relPath = path.relative(rootDir, r.file).split(path.sep).join('/');
|
|
470
|
-
result.set(relPath,
|
|
397
|
+
result.set(relPath, patchNativeResult(r));
|
|
471
398
|
}
|
|
472
399
|
return result;
|
|
473
400
|
}
|
|
@@ -532,7 +459,7 @@ export function createParseTreeCache() {
|
|
|
532
459
|
export async function parseFileIncremental(cache, filePath, source, opts = {}) {
|
|
533
460
|
if (cache) {
|
|
534
461
|
const result = cache.parseFile(filePath, source);
|
|
535
|
-
return result ?
|
|
462
|
+
return result ? patchNativeResult(result) : null;
|
|
536
463
|
}
|
|
537
464
|
return parseFileAuto(filePath, source, opts);
|
|
538
465
|
}
|