@optave/codegraph 3.0.4 → 3.1.1
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 +59 -52
- package/grammars/tree-sitter-go.wasm +0 -0
- package/package.json +9 -10
- package/src/ast-analysis/rules/csharp.js +201 -0
- package/src/ast-analysis/rules/go.js +182 -0
- package/src/ast-analysis/rules/index.js +82 -0
- package/src/ast-analysis/rules/java.js +175 -0
- package/src/ast-analysis/rules/javascript.js +246 -0
- package/src/ast-analysis/rules/php.js +219 -0
- package/src/ast-analysis/rules/python.js +196 -0
- package/src/ast-analysis/rules/ruby.js +204 -0
- package/src/ast-analysis/rules/rust.js +173 -0
- package/src/ast-analysis/shared.js +223 -0
- package/src/ast.js +15 -28
- package/src/audit.js +4 -5
- package/src/boundaries.js +1 -1
- package/src/branch-compare.js +84 -79
- package/src/builder.js +274 -159
- package/src/cfg.js +111 -341
- package/src/check.js +3 -3
- package/src/cli.js +122 -167
- package/src/cochange.js +1 -1
- package/src/communities.js +13 -16
- package/src/complexity.js +196 -1239
- package/src/cycles.js +1 -1
- package/src/dataflow.js +274 -697
- package/src/db/connection.js +88 -0
- package/src/db/migrations.js +312 -0
- package/src/db/query-builder.js +280 -0
- package/src/db/repository.js +134 -0
- package/src/db.js +19 -392
- package/src/embedder.js +145 -141
- package/src/export.js +1 -1
- package/src/flow.js +160 -228
- package/src/index.js +36 -2
- package/src/kinds.js +49 -0
- package/src/manifesto.js +3 -8
- package/src/mcp.js +97 -20
- package/src/owners.js +132 -132
- package/src/parser.js +58 -131
- package/src/queries-cli.js +866 -0
- package/src/queries.js +1356 -2261
- package/src/resolve.js +11 -2
- package/src/result-formatter.js +21 -0
- package/src/sequence.js +364 -0
- package/src/structure.js +200 -199
- package/src/test-filter.js +7 -0
- package/src/triage.js +120 -162
- package/src/viewer.js +1 -1
package/src/dataflow.js
CHANGED
|
@@ -11,427 +11,25 @@
|
|
|
11
11
|
|
|
12
12
|
import fs from 'node:fs';
|
|
13
13
|
import path from 'node:path';
|
|
14
|
+
import { DATAFLOW_RULES } from './ast-analysis/rules/index.js';
|
|
15
|
+
import {
|
|
16
|
+
makeDataflowRules as _makeDataflowRules,
|
|
17
|
+
buildExtensionSet,
|
|
18
|
+
buildExtToLangMap,
|
|
19
|
+
} from './ast-analysis/shared.js';
|
|
14
20
|
import { openReadonlyOrFail } from './db.js';
|
|
15
21
|
import { info } from './logger.js';
|
|
16
22
|
import { paginateResult } from './paginate.js';
|
|
17
|
-
import { LANGUAGE_REGISTRY } from './parser.js';
|
|
18
|
-
import { ALL_SYMBOL_KINDS, isTestFile, normalizeSymbol } from './queries.js';
|
|
19
|
-
|
|
20
|
-
// ─── Language-Specific Dataflow Rules ────────────────────────────────────
|
|
21
|
-
|
|
22
|
-
const DATAFLOW_DEFAULTS = {
|
|
23
|
-
// Scope entry
|
|
24
|
-
functionNodes: new Set(), // REQUIRED: non-empty
|
|
25
|
-
|
|
26
|
-
// Function name extraction
|
|
27
|
-
nameField: 'name',
|
|
28
|
-
varAssignedFnParent: null, // parent type for `const fn = ...` (JS only)
|
|
29
|
-
assignmentFnParent: null, // parent type for `x = function...` (JS only)
|
|
30
|
-
pairFnParent: null, // parent type for `{ key: function }` (JS only)
|
|
31
|
-
|
|
32
|
-
// Parameters
|
|
33
|
-
paramListField: 'parameters',
|
|
34
|
-
paramIdentifier: 'identifier',
|
|
35
|
-
paramWrapperTypes: new Set(),
|
|
36
|
-
defaultParamType: null,
|
|
37
|
-
restParamType: null,
|
|
38
|
-
objectDestructType: null,
|
|
39
|
-
arrayDestructType: null,
|
|
40
|
-
shorthandPropPattern: null,
|
|
41
|
-
pairPatternType: null,
|
|
42
|
-
extractParamName: null, // override: (node) => string[]
|
|
43
|
-
|
|
44
|
-
// Return
|
|
45
|
-
returnNode: null,
|
|
46
|
-
|
|
47
|
-
// Variable declarations
|
|
48
|
-
varDeclaratorNode: null,
|
|
49
|
-
varDeclaratorNodes: null,
|
|
50
|
-
varNameField: 'name',
|
|
51
|
-
varValueField: 'value',
|
|
52
|
-
assignmentNode: null,
|
|
53
|
-
assignLeftField: 'left',
|
|
54
|
-
assignRightField: 'right',
|
|
55
|
-
|
|
56
|
-
// Calls
|
|
57
|
-
callNode: null,
|
|
58
|
-
callNodes: null,
|
|
59
|
-
callFunctionField: 'function',
|
|
60
|
-
callArgsField: 'arguments',
|
|
61
|
-
spreadType: null,
|
|
62
|
-
|
|
63
|
-
// Member access
|
|
64
|
-
memberNode: null,
|
|
65
|
-
memberObjectField: 'object',
|
|
66
|
-
memberPropertyField: 'property',
|
|
67
|
-
optionalChainNode: null,
|
|
68
|
-
|
|
69
|
-
// Await
|
|
70
|
-
awaitNode: null,
|
|
71
|
-
|
|
72
|
-
// Mutation
|
|
73
|
-
mutatingMethods: new Set(),
|
|
74
|
-
expressionStmtNode: 'expression_statement',
|
|
75
|
-
callObjectField: null, // Java: combined call+member has [object] field on call node
|
|
76
|
-
|
|
77
|
-
// Structural wrappers
|
|
78
|
-
expressionListType: null, // Go: expression_list wraps LHS/RHS of short_var_declaration
|
|
79
|
-
equalsClauseType: null, // C#: equals_value_clause wraps variable initializer
|
|
80
|
-
argumentWrapperType: null, // PHP: individual args wrapped in 'argument' nodes
|
|
81
|
-
extraIdentifierTypes: null, // Set of additional identifier-like types (PHP: variable_name, name)
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
const DATAFLOW_RULE_KEYS = new Set(Object.keys(DATAFLOW_DEFAULTS));
|
|
85
|
-
|
|
86
|
-
export function makeDataflowRules(overrides) {
|
|
87
|
-
for (const key of Object.keys(overrides)) {
|
|
88
|
-
if (!DATAFLOW_RULE_KEYS.has(key)) {
|
|
89
|
-
throw new Error(`Dataflow rules: unknown key "${key}"`);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
const rules = { ...DATAFLOW_DEFAULTS, ...overrides };
|
|
93
|
-
if (!(rules.functionNodes instanceof Set) || rules.functionNodes.size === 0) {
|
|
94
|
-
throw new Error('Dataflow rules: functionNodes must be a non-empty Set');
|
|
95
|
-
}
|
|
96
|
-
return rules;
|
|
97
|
-
}
|
|
98
23
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
'reverse',
|
|
109
|
-
'fill',
|
|
110
|
-
'set',
|
|
111
|
-
'delete',
|
|
112
|
-
'add',
|
|
113
|
-
'clear',
|
|
114
|
-
]);
|
|
115
|
-
|
|
116
|
-
const JS_TS_DATAFLOW = makeDataflowRules({
|
|
117
|
-
functionNodes: new Set([
|
|
118
|
-
'function_declaration',
|
|
119
|
-
'method_definition',
|
|
120
|
-
'arrow_function',
|
|
121
|
-
'function_expression',
|
|
122
|
-
'function',
|
|
123
|
-
]),
|
|
124
|
-
varAssignedFnParent: 'variable_declarator',
|
|
125
|
-
assignmentFnParent: 'assignment_expression',
|
|
126
|
-
pairFnParent: 'pair',
|
|
127
|
-
paramWrapperTypes: new Set(['required_parameter', 'optional_parameter']),
|
|
128
|
-
defaultParamType: 'assignment_pattern',
|
|
129
|
-
restParamType: 'rest_pattern',
|
|
130
|
-
objectDestructType: 'object_pattern',
|
|
131
|
-
arrayDestructType: 'array_pattern',
|
|
132
|
-
shorthandPropPattern: 'shorthand_property_identifier_pattern',
|
|
133
|
-
pairPatternType: 'pair_pattern',
|
|
134
|
-
returnNode: 'return_statement',
|
|
135
|
-
varDeclaratorNode: 'variable_declarator',
|
|
136
|
-
assignmentNode: 'assignment_expression',
|
|
137
|
-
callNode: 'call_expression',
|
|
138
|
-
spreadType: 'spread_element',
|
|
139
|
-
memberNode: 'member_expression',
|
|
140
|
-
optionalChainNode: 'optional_chain_expression',
|
|
141
|
-
awaitNode: 'await_expression',
|
|
142
|
-
mutatingMethods: JS_TS_MUTATING,
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
// ── Python ───────────────────────────────────────────────────────────────
|
|
146
|
-
|
|
147
|
-
const PYTHON_DATAFLOW = makeDataflowRules({
|
|
148
|
-
functionNodes: new Set(['function_definition', 'lambda']),
|
|
149
|
-
defaultParamType: 'default_parameter',
|
|
150
|
-
restParamType: 'list_splat_pattern',
|
|
151
|
-
returnNode: 'return_statement',
|
|
152
|
-
varDeclaratorNode: null,
|
|
153
|
-
assignmentNode: 'assignment',
|
|
154
|
-
assignLeftField: 'left',
|
|
155
|
-
assignRightField: 'right',
|
|
156
|
-
callNode: 'call',
|
|
157
|
-
callFunctionField: 'function',
|
|
158
|
-
callArgsField: 'arguments',
|
|
159
|
-
spreadType: 'list_splat',
|
|
160
|
-
memberNode: 'attribute',
|
|
161
|
-
memberObjectField: 'object',
|
|
162
|
-
memberPropertyField: 'attribute',
|
|
163
|
-
awaitNode: 'await',
|
|
164
|
-
mutatingMethods: new Set([
|
|
165
|
-
'append',
|
|
166
|
-
'extend',
|
|
167
|
-
'insert',
|
|
168
|
-
'pop',
|
|
169
|
-
'remove',
|
|
170
|
-
'clear',
|
|
171
|
-
'sort',
|
|
172
|
-
'reverse',
|
|
173
|
-
'add',
|
|
174
|
-
'discard',
|
|
175
|
-
'update',
|
|
176
|
-
]),
|
|
177
|
-
extractParamName(node) {
|
|
178
|
-
// typed_parameter / typed_default_parameter: first identifier child is the name
|
|
179
|
-
if (node.type === 'typed_parameter' || node.type === 'typed_default_parameter') {
|
|
180
|
-
for (const c of node.namedChildren) {
|
|
181
|
-
if (c.type === 'identifier') return [c.text];
|
|
182
|
-
}
|
|
183
|
-
return null;
|
|
184
|
-
}
|
|
185
|
-
if (node.type === 'default_parameter') {
|
|
186
|
-
const nameNode = node.childForFieldName('name');
|
|
187
|
-
return nameNode ? [nameNode.text] : null;
|
|
188
|
-
}
|
|
189
|
-
if (node.type === 'list_splat_pattern' || node.type === 'dictionary_splat_pattern') {
|
|
190
|
-
for (const c of node.namedChildren) {
|
|
191
|
-
if (c.type === 'identifier') return [c.text];
|
|
192
|
-
}
|
|
193
|
-
return null;
|
|
194
|
-
}
|
|
195
|
-
return null;
|
|
196
|
-
},
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
// ── Go ───────────────────────────────────────────────────────────────────
|
|
200
|
-
|
|
201
|
-
const GO_DATAFLOW = makeDataflowRules({
|
|
202
|
-
functionNodes: new Set(['function_declaration', 'method_declaration', 'func_literal']),
|
|
203
|
-
returnNode: 'return_statement',
|
|
204
|
-
varDeclaratorNodes: new Set(['short_var_declaration', 'var_declaration']),
|
|
205
|
-
varNameField: 'left',
|
|
206
|
-
varValueField: 'right',
|
|
207
|
-
assignmentNode: 'assignment_statement',
|
|
208
|
-
assignLeftField: 'left',
|
|
209
|
-
assignRightField: 'right',
|
|
210
|
-
callNode: 'call_expression',
|
|
211
|
-
callFunctionField: 'function',
|
|
212
|
-
callArgsField: 'arguments',
|
|
213
|
-
memberNode: 'selector_expression',
|
|
214
|
-
memberObjectField: 'operand',
|
|
215
|
-
memberPropertyField: 'field',
|
|
216
|
-
mutatingMethods: new Set(),
|
|
217
|
-
expressionListType: 'expression_list',
|
|
218
|
-
extractParamName(node) {
|
|
219
|
-
// Go: parameter_declaration has name(s) + type; e.g. `a, b int`
|
|
220
|
-
if (node.type === 'parameter_declaration') {
|
|
221
|
-
const names = [];
|
|
222
|
-
for (const c of node.namedChildren) {
|
|
223
|
-
if (c.type === 'identifier') names.push(c.text);
|
|
224
|
-
}
|
|
225
|
-
return names.length > 0 ? names : null;
|
|
226
|
-
}
|
|
227
|
-
if (node.type === 'variadic_parameter_declaration') {
|
|
228
|
-
const nameNode = node.childForFieldName('name');
|
|
229
|
-
return nameNode ? [nameNode.text] : null;
|
|
230
|
-
}
|
|
231
|
-
return null;
|
|
232
|
-
},
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
// ── Rust ─────────────────────────────────────────────────────────────────
|
|
236
|
-
|
|
237
|
-
const RUST_DATAFLOW = makeDataflowRules({
|
|
238
|
-
functionNodes: new Set(['function_item', 'closure_expression']),
|
|
239
|
-
returnNode: 'return_expression',
|
|
240
|
-
varDeclaratorNode: 'let_declaration',
|
|
241
|
-
varNameField: 'pattern',
|
|
242
|
-
varValueField: 'value',
|
|
243
|
-
assignmentNode: 'assignment_expression',
|
|
244
|
-
callNode: 'call_expression',
|
|
245
|
-
callFunctionField: 'function',
|
|
246
|
-
callArgsField: 'arguments',
|
|
247
|
-
memberNode: 'field_expression',
|
|
248
|
-
memberObjectField: 'value',
|
|
249
|
-
memberPropertyField: 'field',
|
|
250
|
-
awaitNode: 'await_expression',
|
|
251
|
-
mutatingMethods: new Set(['push', 'pop', 'insert', 'remove', 'clear', 'sort', 'reverse']),
|
|
252
|
-
extractParamName(node) {
|
|
253
|
-
if (node.type === 'parameter') {
|
|
254
|
-
const pat = node.childForFieldName('pattern');
|
|
255
|
-
if (pat?.type === 'identifier') return [pat.text];
|
|
256
|
-
return null;
|
|
257
|
-
}
|
|
258
|
-
if (node.type === 'identifier') return [node.text];
|
|
259
|
-
return null;
|
|
260
|
-
},
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
// ── Java ─────────────────────────────────────────────────────────────────
|
|
264
|
-
|
|
265
|
-
const JAVA_DATAFLOW = makeDataflowRules({
|
|
266
|
-
functionNodes: new Set(['method_declaration', 'constructor_declaration', 'lambda_expression']),
|
|
267
|
-
returnNode: 'return_statement',
|
|
268
|
-
varDeclaratorNode: 'variable_declarator',
|
|
269
|
-
assignmentNode: 'assignment_expression',
|
|
270
|
-
callNodes: new Set(['method_invocation', 'object_creation_expression']),
|
|
271
|
-
callFunctionField: 'name',
|
|
272
|
-
callArgsField: 'arguments',
|
|
273
|
-
memberNode: 'field_access',
|
|
274
|
-
memberObjectField: 'object',
|
|
275
|
-
memberPropertyField: 'field',
|
|
276
|
-
callObjectField: 'object',
|
|
277
|
-
argumentWrapperType: 'argument',
|
|
278
|
-
mutatingMethods: new Set(['add', 'remove', 'clear', 'put', 'set', 'push', 'pop', 'sort']),
|
|
279
|
-
extractParamName(node) {
|
|
280
|
-
if (node.type === 'formal_parameter' || node.type === 'spread_parameter') {
|
|
281
|
-
const nameNode = node.childForFieldName('name');
|
|
282
|
-
return nameNode ? [nameNode.text] : null;
|
|
283
|
-
}
|
|
284
|
-
if (node.type === 'identifier') return [node.text];
|
|
285
|
-
return null;
|
|
286
|
-
},
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
// ── C# ───────────────────────────────────────────────────────────────────
|
|
290
|
-
|
|
291
|
-
const CSHARP_DATAFLOW = makeDataflowRules({
|
|
292
|
-
functionNodes: new Set([
|
|
293
|
-
'method_declaration',
|
|
294
|
-
'constructor_declaration',
|
|
295
|
-
'lambda_expression',
|
|
296
|
-
'local_function_statement',
|
|
297
|
-
]),
|
|
298
|
-
returnNode: 'return_statement',
|
|
299
|
-
varDeclaratorNode: 'variable_declarator',
|
|
300
|
-
varNameField: 'name',
|
|
301
|
-
assignmentNode: 'assignment_expression',
|
|
302
|
-
callNode: 'invocation_expression',
|
|
303
|
-
callFunctionField: 'function',
|
|
304
|
-
callArgsField: 'arguments',
|
|
305
|
-
memberNode: 'member_access_expression',
|
|
306
|
-
memberObjectField: 'expression',
|
|
307
|
-
memberPropertyField: 'name',
|
|
308
|
-
awaitNode: 'await_expression',
|
|
309
|
-
argumentWrapperType: 'argument',
|
|
310
|
-
mutatingMethods: new Set(['Add', 'Remove', 'Clear', 'Insert', 'Sort', 'Reverse', 'Push', 'Pop']),
|
|
311
|
-
extractParamName(node) {
|
|
312
|
-
if (node.type === 'parameter') {
|
|
313
|
-
const nameNode = node.childForFieldName('name');
|
|
314
|
-
return nameNode ? [nameNode.text] : null;
|
|
315
|
-
}
|
|
316
|
-
if (node.type === 'identifier') return [node.text];
|
|
317
|
-
return null;
|
|
318
|
-
},
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
// ── PHP ──────────────────────────────────────────────────────────────────
|
|
322
|
-
|
|
323
|
-
const PHP_DATAFLOW = makeDataflowRules({
|
|
324
|
-
functionNodes: new Set([
|
|
325
|
-
'function_definition',
|
|
326
|
-
'method_declaration',
|
|
327
|
-
'anonymous_function_creation_expression',
|
|
328
|
-
'arrow_function',
|
|
329
|
-
]),
|
|
330
|
-
paramListField: 'parameters',
|
|
331
|
-
paramIdentifier: 'variable_name',
|
|
332
|
-
returnNode: 'return_statement',
|
|
333
|
-
varDeclaratorNode: null,
|
|
334
|
-
assignmentNode: 'assignment_expression',
|
|
335
|
-
assignLeftField: 'left',
|
|
336
|
-
assignRightField: 'right',
|
|
337
|
-
callNodes: new Set([
|
|
338
|
-
'function_call_expression',
|
|
339
|
-
'member_call_expression',
|
|
340
|
-
'scoped_call_expression',
|
|
341
|
-
]),
|
|
342
|
-
callFunctionField: 'function',
|
|
343
|
-
callArgsField: 'arguments',
|
|
344
|
-
spreadType: 'spread_expression',
|
|
345
|
-
memberNode: 'member_access_expression',
|
|
346
|
-
memberObjectField: 'object',
|
|
347
|
-
memberPropertyField: 'name',
|
|
348
|
-
argumentWrapperType: 'argument',
|
|
349
|
-
extraIdentifierTypes: new Set(['variable_name', 'name']),
|
|
350
|
-
mutatingMethods: new Set(['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']),
|
|
351
|
-
extractParamName(node) {
|
|
352
|
-
// PHP: simple_parameter → $name or &$name
|
|
353
|
-
if (node.type === 'simple_parameter' || node.type === 'variadic_parameter') {
|
|
354
|
-
const nameNode = node.childForFieldName('name');
|
|
355
|
-
return nameNode ? [nameNode.text] : null;
|
|
356
|
-
}
|
|
357
|
-
if (node.type === 'variable_name') return [node.text];
|
|
358
|
-
return null;
|
|
359
|
-
},
|
|
360
|
-
});
|
|
361
|
-
|
|
362
|
-
// ── Ruby ─────────────────────────────────────────────────────────────────
|
|
363
|
-
|
|
364
|
-
const RUBY_DATAFLOW = makeDataflowRules({
|
|
365
|
-
functionNodes: new Set(['method', 'singleton_method', 'lambda']),
|
|
366
|
-
paramListField: 'parameters',
|
|
367
|
-
returnNode: 'return',
|
|
368
|
-
varDeclaratorNode: null,
|
|
369
|
-
assignmentNode: 'assignment',
|
|
370
|
-
assignLeftField: 'left',
|
|
371
|
-
assignRightField: 'right',
|
|
372
|
-
callNode: 'call',
|
|
373
|
-
callFunctionField: 'method',
|
|
374
|
-
callArgsField: 'arguments',
|
|
375
|
-
spreadType: 'splat_parameter',
|
|
376
|
-
memberNode: 'call',
|
|
377
|
-
memberObjectField: 'receiver',
|
|
378
|
-
memberPropertyField: 'method',
|
|
379
|
-
mutatingMethods: new Set([
|
|
380
|
-
'push',
|
|
381
|
-
'pop',
|
|
382
|
-
'shift',
|
|
383
|
-
'unshift',
|
|
384
|
-
'delete',
|
|
385
|
-
'clear',
|
|
386
|
-
'sort!',
|
|
387
|
-
'reverse!',
|
|
388
|
-
'map!',
|
|
389
|
-
'select!',
|
|
390
|
-
'reject!',
|
|
391
|
-
'compact!',
|
|
392
|
-
'flatten!',
|
|
393
|
-
'concat',
|
|
394
|
-
'replace',
|
|
395
|
-
'insert',
|
|
396
|
-
]),
|
|
397
|
-
extractParamName(node) {
|
|
398
|
-
if (node.type === 'identifier') return [node.text];
|
|
399
|
-
if (
|
|
400
|
-
node.type === 'optional_parameter' ||
|
|
401
|
-
node.type === 'keyword_parameter' ||
|
|
402
|
-
node.type === 'splat_parameter' ||
|
|
403
|
-
node.type === 'hash_splat_parameter'
|
|
404
|
-
) {
|
|
405
|
-
const nameNode = node.childForFieldName('name');
|
|
406
|
-
return nameNode ? [nameNode.text] : null;
|
|
407
|
-
}
|
|
408
|
-
return null;
|
|
409
|
-
},
|
|
410
|
-
});
|
|
411
|
-
|
|
412
|
-
// ── Rules Map + Extensions Set ───────────────────────────────────────────
|
|
413
|
-
|
|
414
|
-
export const DATAFLOW_RULES = new Map([
|
|
415
|
-
['javascript', JS_TS_DATAFLOW],
|
|
416
|
-
['typescript', JS_TS_DATAFLOW],
|
|
417
|
-
['tsx', JS_TS_DATAFLOW],
|
|
418
|
-
['python', PYTHON_DATAFLOW],
|
|
419
|
-
['go', GO_DATAFLOW],
|
|
420
|
-
['rust', RUST_DATAFLOW],
|
|
421
|
-
['java', JAVA_DATAFLOW],
|
|
422
|
-
['csharp', CSHARP_DATAFLOW],
|
|
423
|
-
['php', PHP_DATAFLOW],
|
|
424
|
-
['ruby', RUBY_DATAFLOW],
|
|
425
|
-
]);
|
|
426
|
-
|
|
427
|
-
const DATAFLOW_LANG_IDS = new Set(DATAFLOW_RULES.keys());
|
|
428
|
-
|
|
429
|
-
export const DATAFLOW_EXTENSIONS = new Set();
|
|
430
|
-
for (const entry of LANGUAGE_REGISTRY) {
|
|
431
|
-
if (DATAFLOW_RULES.has(entry.id)) {
|
|
432
|
-
for (const ext of entry.extensions) DATAFLOW_EXTENSIONS.add(ext);
|
|
433
|
-
}
|
|
434
|
-
}
|
|
24
|
+
import { ALL_SYMBOL_KINDS, normalizeSymbol } from './queries.js';
|
|
25
|
+
import { outputResult } from './result-formatter.js';
|
|
26
|
+
import { isTestFile } from './test-filter.js';
|
|
27
|
+
|
|
28
|
+
// Re-export for backward compatibility
|
|
29
|
+
export { DATAFLOW_RULES };
|
|
30
|
+
export { _makeDataflowRules as makeDataflowRules };
|
|
31
|
+
|
|
32
|
+
export const DATAFLOW_EXTENSIONS = buildExtensionSet(DATAFLOW_RULES);
|
|
435
33
|
|
|
436
34
|
// ── AST helpers ──────────────────────────────────────────────────────────────
|
|
437
35
|
|
|
@@ -1005,9 +603,12 @@ function collectIdentifiers(node, out, rules) {
|
|
|
1005
603
|
export async function buildDataflowEdges(db, fileSymbols, rootDir, _engineOpts) {
|
|
1006
604
|
// Lazily init WASM parsers if needed
|
|
1007
605
|
let parsers = null;
|
|
1008
|
-
let extToLang = null;
|
|
1009
606
|
let needsFallback = false;
|
|
1010
607
|
|
|
608
|
+
// Always build ext→langId map so native-only builds (where _langId is unset)
|
|
609
|
+
// can still derive the language from the file extension.
|
|
610
|
+
const extToLang = buildExtToLangMap();
|
|
611
|
+
|
|
1011
612
|
for (const [relPath, symbols] of fileSymbols) {
|
|
1012
613
|
if (!symbols._tree && !symbols.dataflow) {
|
|
1013
614
|
const ext = path.extname(relPath).toLowerCase();
|
|
@@ -1021,12 +622,6 @@ export async function buildDataflowEdges(db, fileSymbols, rootDir, _engineOpts)
|
|
|
1021
622
|
if (needsFallback) {
|
|
1022
623
|
const { createParsers } = await import('./parser.js');
|
|
1023
624
|
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
625
|
}
|
|
1031
626
|
|
|
1032
627
|
let getParserFn = null;
|
|
@@ -1069,9 +664,9 @@ export async function buildDataflowEdges(db, fileSymbols, rootDir, _engineOpts)
|
|
|
1069
664
|
|
|
1070
665
|
// WASM fallback if no cached tree
|
|
1071
666
|
if (!tree) {
|
|
1072
|
-
if (!
|
|
667
|
+
if (!getParserFn) continue;
|
|
1073
668
|
langId = extToLang.get(ext);
|
|
1074
|
-
if (!langId || !
|
|
669
|
+
if (!langId || !DATAFLOW_RULES.has(langId)) continue;
|
|
1075
670
|
|
|
1076
671
|
const absPath = path.join(rootDir, relPath);
|
|
1077
672
|
let code;
|
|
@@ -1092,7 +687,7 @@ export async function buildDataflowEdges(db, fileSymbols, rootDir, _engineOpts)
|
|
|
1092
687
|
}
|
|
1093
688
|
|
|
1094
689
|
if (!langId) {
|
|
1095
|
-
langId = extToLang
|
|
690
|
+
langId = extToLang.get(ext);
|
|
1096
691
|
if (!langId) continue;
|
|
1097
692
|
}
|
|
1098
693
|
|
|
@@ -1219,134 +814,135 @@ function hasDataflowTable(db) {
|
|
|
1219
814
|
*/
|
|
1220
815
|
export function dataflowData(name, customDbPath, opts = {}) {
|
|
1221
816
|
const db = openReadonlyOrFail(customDbPath);
|
|
1222
|
-
|
|
817
|
+
try {
|
|
818
|
+
const noTests = opts.noTests || false;
|
|
1223
819
|
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
}
|
|
1232
|
-
}
|
|
820
|
+
if (!hasDataflowTable(db)) {
|
|
821
|
+
return {
|
|
822
|
+
name,
|
|
823
|
+
results: [],
|
|
824
|
+
warning:
|
|
825
|
+
'No dataflow data found. Rebuild with `codegraph build` (dataflow is now included by default).',
|
|
826
|
+
};
|
|
827
|
+
}
|
|
1233
828
|
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
}
|
|
829
|
+
const nodes = findNodes(db, name, { noTests, file: opts.file, kind: opts.kind });
|
|
830
|
+
if (nodes.length === 0) {
|
|
831
|
+
return { name, results: [] };
|
|
832
|
+
}
|
|
1239
833
|
|
|
1240
|
-
|
|
1241
|
-
|
|
834
|
+
const flowsToOut = db.prepare(
|
|
835
|
+
`SELECT d.*, n.name AS target_name, n.kind AS target_kind, n.file AS target_file, n.line AS target_line
|
|
1242
836
|
FROM dataflow d JOIN nodes n ON d.target_id = n.id
|
|
1243
837
|
WHERE d.source_id = ? AND d.kind = 'flows_to'`,
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
838
|
+
);
|
|
839
|
+
const flowsToIn = db.prepare(
|
|
840
|
+
`SELECT d.*, n.name AS source_name, n.kind AS source_kind, n.file AS source_file, n.line AS source_line
|
|
1247
841
|
FROM dataflow d JOIN nodes n ON d.source_id = n.id
|
|
1248
842
|
WHERE d.target_id = ? AND d.kind = 'flows_to'`,
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
843
|
+
);
|
|
844
|
+
const returnsOut = db.prepare(
|
|
845
|
+
`SELECT d.*, n.name AS target_name, n.kind AS target_kind, n.file AS target_file, n.line AS target_line
|
|
1252
846
|
FROM dataflow d JOIN nodes n ON d.target_id = n.id
|
|
1253
847
|
WHERE d.source_id = ? AND d.kind = 'returns'`,
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
848
|
+
);
|
|
849
|
+
const returnsIn = db.prepare(
|
|
850
|
+
`SELECT d.*, n.name AS source_name, n.kind AS source_kind, n.file AS source_file, n.line AS source_line
|
|
1257
851
|
FROM dataflow d JOIN nodes n ON d.source_id = n.id
|
|
1258
852
|
WHERE d.target_id = ? AND d.kind = 'returns'`,
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
853
|
+
);
|
|
854
|
+
const mutatesOut = db.prepare(
|
|
855
|
+
`SELECT d.*, n.name AS target_name, n.kind AS target_kind, n.file AS target_file, n.line AS target_line
|
|
1262
856
|
FROM dataflow d JOIN nodes n ON d.target_id = n.id
|
|
1263
857
|
WHERE d.source_id = ? AND d.kind = 'mutates'`,
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
858
|
+
);
|
|
859
|
+
const mutatesIn = db.prepare(
|
|
860
|
+
`SELECT d.*, n.name AS source_name, n.kind AS source_kind, n.file AS source_file, n.line AS source_line
|
|
1267
861
|
FROM dataflow d JOIN nodes n ON d.source_id = n.id
|
|
1268
862
|
WHERE d.target_id = ? AND d.kind = 'mutates'`,
|
|
1269
|
-
|
|
863
|
+
);
|
|
864
|
+
|
|
865
|
+
const hc = new Map();
|
|
866
|
+
const results = nodes.map((node) => {
|
|
867
|
+
const sym = normalizeSymbol(node, db, hc);
|
|
868
|
+
|
|
869
|
+
const flowsTo = flowsToOut.all(node.id).map((r) => ({
|
|
870
|
+
target: r.target_name,
|
|
871
|
+
kind: r.target_kind,
|
|
872
|
+
file: r.target_file,
|
|
873
|
+
line: r.line,
|
|
874
|
+
paramIndex: r.param_index,
|
|
875
|
+
expression: r.expression,
|
|
876
|
+
confidence: r.confidence,
|
|
877
|
+
}));
|
|
878
|
+
|
|
879
|
+
const flowsFrom = flowsToIn.all(node.id).map((r) => ({
|
|
880
|
+
source: r.source_name,
|
|
881
|
+
kind: r.source_kind,
|
|
882
|
+
file: r.source_file,
|
|
883
|
+
line: r.line,
|
|
884
|
+
paramIndex: r.param_index,
|
|
885
|
+
expression: r.expression,
|
|
886
|
+
confidence: r.confidence,
|
|
887
|
+
}));
|
|
888
|
+
|
|
889
|
+
const returnConsumers = returnsOut.all(node.id).map((r) => ({
|
|
890
|
+
consumer: r.target_name,
|
|
891
|
+
kind: r.target_kind,
|
|
892
|
+
file: r.target_file,
|
|
893
|
+
line: r.line,
|
|
894
|
+
expression: r.expression,
|
|
895
|
+
}));
|
|
896
|
+
|
|
897
|
+
const returnedBy = returnsIn.all(node.id).map((r) => ({
|
|
898
|
+
producer: r.source_name,
|
|
899
|
+
kind: r.source_kind,
|
|
900
|
+
file: r.source_file,
|
|
901
|
+
line: r.line,
|
|
902
|
+
expression: r.expression,
|
|
903
|
+
}));
|
|
904
|
+
|
|
905
|
+
const mutatesTargets = mutatesOut.all(node.id).map((r) => ({
|
|
906
|
+
target: r.target_name,
|
|
907
|
+
expression: r.expression,
|
|
908
|
+
line: r.line,
|
|
909
|
+
}));
|
|
910
|
+
|
|
911
|
+
const mutatedBy = mutatesIn.all(node.id).map((r) => ({
|
|
912
|
+
source: r.source_name,
|
|
913
|
+
expression: r.expression,
|
|
914
|
+
line: r.line,
|
|
915
|
+
}));
|
|
916
|
+
|
|
917
|
+
if (noTests) {
|
|
918
|
+
const filter = (arr) => arr.filter((r) => !isTestFile(r.file));
|
|
919
|
+
return {
|
|
920
|
+
...sym,
|
|
921
|
+
flowsTo: filter(flowsTo),
|
|
922
|
+
flowsFrom: filter(flowsFrom),
|
|
923
|
+
returns: returnConsumers.filter((r) => !isTestFile(r.file)),
|
|
924
|
+
returnedBy: returnedBy.filter((r) => !isTestFile(r.file)),
|
|
925
|
+
mutates: mutatesTargets,
|
|
926
|
+
mutatedBy,
|
|
927
|
+
};
|
|
928
|
+
}
|
|
1270
929
|
|
|
1271
|
-
const hc = new Map();
|
|
1272
|
-
const results = nodes.map((node) => {
|
|
1273
|
-
const sym = normalizeSymbol(node, db, hc);
|
|
1274
|
-
|
|
1275
|
-
const flowsTo = flowsToOut.all(node.id).map((r) => ({
|
|
1276
|
-
target: r.target_name,
|
|
1277
|
-
kind: r.target_kind,
|
|
1278
|
-
file: r.target_file,
|
|
1279
|
-
line: r.line,
|
|
1280
|
-
paramIndex: r.param_index,
|
|
1281
|
-
expression: r.expression,
|
|
1282
|
-
confidence: r.confidence,
|
|
1283
|
-
}));
|
|
1284
|
-
|
|
1285
|
-
const flowsFrom = flowsToIn.all(node.id).map((r) => ({
|
|
1286
|
-
source: r.source_name,
|
|
1287
|
-
kind: r.source_kind,
|
|
1288
|
-
file: r.source_file,
|
|
1289
|
-
line: r.line,
|
|
1290
|
-
paramIndex: r.param_index,
|
|
1291
|
-
expression: r.expression,
|
|
1292
|
-
confidence: r.confidence,
|
|
1293
|
-
}));
|
|
1294
|
-
|
|
1295
|
-
const returnConsumers = returnsOut.all(node.id).map((r) => ({
|
|
1296
|
-
consumer: r.target_name,
|
|
1297
|
-
kind: r.target_kind,
|
|
1298
|
-
file: r.target_file,
|
|
1299
|
-
line: r.line,
|
|
1300
|
-
expression: r.expression,
|
|
1301
|
-
}));
|
|
1302
|
-
|
|
1303
|
-
const returnedBy = returnsIn.all(node.id).map((r) => ({
|
|
1304
|
-
producer: r.source_name,
|
|
1305
|
-
kind: r.source_kind,
|
|
1306
|
-
file: r.source_file,
|
|
1307
|
-
line: r.line,
|
|
1308
|
-
expression: r.expression,
|
|
1309
|
-
}));
|
|
1310
|
-
|
|
1311
|
-
const mutatesTargets = mutatesOut.all(node.id).map((r) => ({
|
|
1312
|
-
target: r.target_name,
|
|
1313
|
-
expression: r.expression,
|
|
1314
|
-
line: r.line,
|
|
1315
|
-
}));
|
|
1316
|
-
|
|
1317
|
-
const mutatedBy = mutatesIn.all(node.id).map((r) => ({
|
|
1318
|
-
source: r.source_name,
|
|
1319
|
-
expression: r.expression,
|
|
1320
|
-
line: r.line,
|
|
1321
|
-
}));
|
|
1322
|
-
|
|
1323
|
-
if (noTests) {
|
|
1324
|
-
const filter = (arr) => arr.filter((r) => !isTestFile(r.file));
|
|
1325
930
|
return {
|
|
1326
931
|
...sym,
|
|
1327
|
-
flowsTo
|
|
1328
|
-
flowsFrom
|
|
1329
|
-
returns: returnConsumers
|
|
1330
|
-
returnedBy
|
|
932
|
+
flowsTo,
|
|
933
|
+
flowsFrom,
|
|
934
|
+
returns: returnConsumers,
|
|
935
|
+
returnedBy,
|
|
1331
936
|
mutates: mutatesTargets,
|
|
1332
937
|
mutatedBy,
|
|
1333
938
|
};
|
|
1334
|
-
}
|
|
1335
|
-
|
|
1336
|
-
return {
|
|
1337
|
-
...sym,
|
|
1338
|
-
flowsTo,
|
|
1339
|
-
flowsFrom,
|
|
1340
|
-
returns: returnConsumers,
|
|
1341
|
-
returnedBy,
|
|
1342
|
-
mutates: mutatesTargets,
|
|
1343
|
-
mutatedBy,
|
|
1344
|
-
};
|
|
1345
|
-
});
|
|
939
|
+
});
|
|
1346
940
|
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
941
|
+
const base = { name, results };
|
|
942
|
+
return paginateResult(base, 'results', { limit: opts.limit, offset: opts.offset });
|
|
943
|
+
} finally {
|
|
944
|
+
db.close();
|
|
945
|
+
}
|
|
1350
946
|
}
|
|
1351
947
|
|
|
1352
948
|
/**
|
|
@@ -1360,125 +956,123 @@ export function dataflowData(name, customDbPath, opts = {}) {
|
|
|
1360
956
|
*/
|
|
1361
957
|
export function dataflowPathData(from, to, customDbPath, opts = {}) {
|
|
1362
958
|
const db = openReadonlyOrFail(customDbPath);
|
|
1363
|
-
|
|
1364
|
-
|
|
959
|
+
try {
|
|
960
|
+
const noTests = opts.noTests || false;
|
|
961
|
+
const maxDepth = opts.maxDepth || 10;
|
|
1365
962
|
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
}
|
|
1375
|
-
}
|
|
963
|
+
if (!hasDataflowTable(db)) {
|
|
964
|
+
return {
|
|
965
|
+
from,
|
|
966
|
+
to,
|
|
967
|
+
found: false,
|
|
968
|
+
warning:
|
|
969
|
+
'No dataflow data found. Rebuild with `codegraph build` (dataflow is now included by default).',
|
|
970
|
+
};
|
|
971
|
+
}
|
|
1376
972
|
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
}
|
|
973
|
+
const fromNodes = findNodes(db, from, { noTests, file: opts.fromFile, kind: opts.kind });
|
|
974
|
+
if (fromNodes.length === 0) {
|
|
975
|
+
return { from, to, found: false, error: `No symbol matching "${from}"` };
|
|
976
|
+
}
|
|
1382
977
|
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
}
|
|
978
|
+
const toNodes = findNodes(db, to, { noTests, file: opts.toFile, kind: opts.kind });
|
|
979
|
+
if (toNodes.length === 0) {
|
|
980
|
+
return { from, to, found: false, error: `No symbol matching "${to}"` };
|
|
981
|
+
}
|
|
1388
982
|
|
|
1389
|
-
|
|
1390
|
-
|
|
983
|
+
const sourceNode = fromNodes[0];
|
|
984
|
+
const targetNode = toNodes[0];
|
|
1391
985
|
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
}
|
|
1403
|
-
}
|
|
986
|
+
if (sourceNode.id === targetNode.id) {
|
|
987
|
+
const hc = new Map();
|
|
988
|
+
const sym = normalizeSymbol(sourceNode, db, hc);
|
|
989
|
+
return {
|
|
990
|
+
from,
|
|
991
|
+
to,
|
|
992
|
+
found: true,
|
|
993
|
+
hops: 0,
|
|
994
|
+
path: [{ ...sym, edgeKind: null }],
|
|
995
|
+
};
|
|
996
|
+
}
|
|
1404
997
|
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
998
|
+
// BFS through flows_to and returns edges
|
|
999
|
+
const neighborStmt = db.prepare(
|
|
1000
|
+
`SELECT n.id, n.name, n.kind, n.file, n.line, d.kind AS edge_kind, d.expression
|
|
1408
1001
|
FROM dataflow d JOIN nodes n ON d.target_id = n.id
|
|
1409
1002
|
WHERE d.source_id = ? AND d.kind IN ('flows_to', 'returns')`,
|
|
1410
|
-
|
|
1003
|
+
);
|
|
1411
1004
|
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1005
|
+
const visited = new Set([sourceNode.id]);
|
|
1006
|
+
const parent = new Map();
|
|
1007
|
+
let queue = [sourceNode.id];
|
|
1008
|
+
let found = false;
|
|
1009
|
+
|
|
1010
|
+
for (let depth = 1; depth <= maxDepth; depth++) {
|
|
1011
|
+
const nextQueue = [];
|
|
1012
|
+
for (const currentId of queue) {
|
|
1013
|
+
const neighbors = neighborStmt.all(currentId);
|
|
1014
|
+
for (const n of neighbors) {
|
|
1015
|
+
if (noTests && isTestFile(n.file)) continue;
|
|
1016
|
+
if (n.id === targetNode.id) {
|
|
1017
|
+
if (!found) {
|
|
1018
|
+
found = true;
|
|
1019
|
+
parent.set(n.id, {
|
|
1020
|
+
parentId: currentId,
|
|
1021
|
+
edgeKind: n.edge_kind,
|
|
1022
|
+
expression: n.expression,
|
|
1023
|
+
});
|
|
1024
|
+
}
|
|
1025
|
+
continue;
|
|
1026
|
+
}
|
|
1027
|
+
if (!visited.has(n.id)) {
|
|
1028
|
+
visited.add(n.id);
|
|
1426
1029
|
parent.set(n.id, {
|
|
1427
1030
|
parentId: currentId,
|
|
1428
1031
|
edgeKind: n.edge_kind,
|
|
1429
1032
|
expression: n.expression,
|
|
1430
1033
|
});
|
|
1034
|
+
nextQueue.push(n.id);
|
|
1431
1035
|
}
|
|
1432
|
-
continue;
|
|
1433
|
-
}
|
|
1434
|
-
if (!visited.has(n.id)) {
|
|
1435
|
-
visited.add(n.id);
|
|
1436
|
-
parent.set(n.id, {
|
|
1437
|
-
parentId: currentId,
|
|
1438
|
-
edgeKind: n.edge_kind,
|
|
1439
|
-
expression: n.expression,
|
|
1440
|
-
});
|
|
1441
|
-
nextQueue.push(n.id);
|
|
1442
1036
|
}
|
|
1443
1037
|
}
|
|
1038
|
+
if (found) break;
|
|
1039
|
+
queue = nextQueue;
|
|
1040
|
+
if (queue.length === 0) break;
|
|
1444
1041
|
}
|
|
1445
|
-
if (found) break;
|
|
1446
|
-
queue = nextQueue;
|
|
1447
|
-
if (queue.length === 0) break;
|
|
1448
|
-
}
|
|
1449
1042
|
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
}
|
|
1043
|
+
if (!found) {
|
|
1044
|
+
return { from, to, found: false };
|
|
1045
|
+
}
|
|
1454
1046
|
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
pathItems.unshift({
|
|
1464
|
-
...normalizeSymbol(nodeRow, db, hc),
|
|
1465
|
-
edgeKind: parentInfo?.edgeKind ?? null,
|
|
1466
|
-
expression: parentInfo?.expression ?? null,
|
|
1467
|
-
});
|
|
1468
|
-
cur = parentInfo?.parentId;
|
|
1469
|
-
if (cur === sourceNode.id) {
|
|
1470
|
-
const srcRow = nodeById.get(cur);
|
|
1047
|
+
// Reconstruct path
|
|
1048
|
+
const nodeById = db.prepare('SELECT * FROM nodes WHERE id = ?');
|
|
1049
|
+
const hc = new Map();
|
|
1050
|
+
const pathItems = [];
|
|
1051
|
+
let cur = targetNode.id;
|
|
1052
|
+
while (cur !== undefined) {
|
|
1053
|
+
const nodeRow = nodeById.get(cur);
|
|
1054
|
+
const parentInfo = parent.get(cur);
|
|
1471
1055
|
pathItems.unshift({
|
|
1472
|
-
...normalizeSymbol(
|
|
1473
|
-
edgeKind: null,
|
|
1474
|
-
expression: null,
|
|
1056
|
+
...normalizeSymbol(nodeRow, db, hc),
|
|
1057
|
+
edgeKind: parentInfo?.edgeKind ?? null,
|
|
1058
|
+
expression: parentInfo?.expression ?? null,
|
|
1475
1059
|
});
|
|
1476
|
-
|
|
1060
|
+
cur = parentInfo?.parentId;
|
|
1061
|
+
if (cur === sourceNode.id) {
|
|
1062
|
+
const srcRow = nodeById.get(cur);
|
|
1063
|
+
pathItems.unshift({
|
|
1064
|
+
...normalizeSymbol(srcRow, db, hc),
|
|
1065
|
+
edgeKind: null,
|
|
1066
|
+
expression: null,
|
|
1067
|
+
});
|
|
1068
|
+
break;
|
|
1069
|
+
}
|
|
1477
1070
|
}
|
|
1478
|
-
}
|
|
1479
1071
|
|
|
1480
|
-
|
|
1481
|
-
|
|
1072
|
+
return { from, to, found: true, hops: pathItems.length - 1, path: pathItems };
|
|
1073
|
+
} finally {
|
|
1074
|
+
db.close();
|
|
1075
|
+
}
|
|
1482
1076
|
}
|
|
1483
1077
|
|
|
1484
1078
|
/**
|
|
@@ -1491,66 +1085,67 @@ export function dataflowPathData(from, to, customDbPath, opts = {}) {
|
|
|
1491
1085
|
*/
|
|
1492
1086
|
export function dataflowImpactData(name, customDbPath, opts = {}) {
|
|
1493
1087
|
const db = openReadonlyOrFail(customDbPath);
|
|
1494
|
-
|
|
1495
|
-
|
|
1088
|
+
try {
|
|
1089
|
+
const maxDepth = opts.depth || 5;
|
|
1090
|
+
const noTests = opts.noTests || false;
|
|
1496
1091
|
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
}
|
|
1505
|
-
}
|
|
1092
|
+
if (!hasDataflowTable(db)) {
|
|
1093
|
+
return {
|
|
1094
|
+
name,
|
|
1095
|
+
results: [],
|
|
1096
|
+
warning:
|
|
1097
|
+
'No dataflow data found. Rebuild with `codegraph build` (dataflow is now included by default).',
|
|
1098
|
+
};
|
|
1099
|
+
}
|
|
1506
1100
|
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
}
|
|
1101
|
+
const nodes = findNodes(db, name, { noTests, file: opts.file, kind: opts.kind });
|
|
1102
|
+
if (nodes.length === 0) {
|
|
1103
|
+
return { name, results: [] };
|
|
1104
|
+
}
|
|
1512
1105
|
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1106
|
+
// Forward BFS: who consumes this function's return value (directly or transitively)?
|
|
1107
|
+
const consumersStmt = db.prepare(
|
|
1108
|
+
`SELECT DISTINCT n.*
|
|
1516
1109
|
FROM dataflow d JOIN nodes n ON d.target_id = n.id
|
|
1517
1110
|
WHERE d.source_id = ? AND d.kind = 'returns'`,
|
|
1518
|
-
|
|
1111
|
+
);
|
|
1519
1112
|
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1113
|
+
const hc = new Map();
|
|
1114
|
+
const results = nodes.map((node) => {
|
|
1115
|
+
const sym = normalizeSymbol(node, db, hc);
|
|
1116
|
+
const visited = new Set([node.id]);
|
|
1117
|
+
const levels = {};
|
|
1118
|
+
let frontier = [node.id];
|
|
1119
|
+
|
|
1120
|
+
for (let d = 1; d <= maxDepth; d++) {
|
|
1121
|
+
const nextFrontier = [];
|
|
1122
|
+
for (const fid of frontier) {
|
|
1123
|
+
const consumers = consumersStmt.all(fid);
|
|
1124
|
+
for (const c of consumers) {
|
|
1125
|
+
if (!visited.has(c.id) && (!noTests || !isTestFile(c.file))) {
|
|
1126
|
+
visited.add(c.id);
|
|
1127
|
+
nextFrontier.push(c.id);
|
|
1128
|
+
if (!levels[d]) levels[d] = [];
|
|
1129
|
+
levels[d].push(normalizeSymbol(c, db, hc));
|
|
1130
|
+
}
|
|
1537
1131
|
}
|
|
1538
1132
|
}
|
|
1133
|
+
frontier = nextFrontier;
|
|
1134
|
+
if (frontier.length === 0) break;
|
|
1539
1135
|
}
|
|
1540
|
-
frontier = nextFrontier;
|
|
1541
|
-
if (frontier.length === 0) break;
|
|
1542
|
-
}
|
|
1543
1136
|
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1137
|
+
return {
|
|
1138
|
+
...sym,
|
|
1139
|
+
levels,
|
|
1140
|
+
totalAffected: visited.size - 1,
|
|
1141
|
+
};
|
|
1142
|
+
});
|
|
1550
1143
|
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1144
|
+
const base = { name, results };
|
|
1145
|
+
return paginateResult(base, 'results', { limit: opts.limit, offset: opts.offset });
|
|
1146
|
+
} finally {
|
|
1147
|
+
db.close();
|
|
1148
|
+
}
|
|
1554
1149
|
}
|
|
1555
1150
|
|
|
1556
1151
|
// ── Display formatters ──────────────────────────────────────────────────────
|
|
@@ -1565,16 +1160,7 @@ export function dataflow(name, customDbPath, opts = {}) {
|
|
|
1565
1160
|
|
|
1566
1161
|
const data = dataflowData(name, customDbPath, opts);
|
|
1567
1162
|
|
|
1568
|
-
if (opts
|
|
1569
|
-
console.log(JSON.stringify(data, null, 2));
|
|
1570
|
-
return;
|
|
1571
|
-
}
|
|
1572
|
-
if (opts.ndjson) {
|
|
1573
|
-
for (const r of data.results) {
|
|
1574
|
-
console.log(JSON.stringify(r));
|
|
1575
|
-
}
|
|
1576
|
-
return;
|
|
1577
|
-
}
|
|
1163
|
+
if (outputResult(data, 'results', opts)) return;
|
|
1578
1164
|
|
|
1579
1165
|
if (data.warning) {
|
|
1580
1166
|
console.log(`⚠ ${data.warning}`);
|
|
@@ -1648,16 +1234,7 @@ function dataflowImpact(name, customDbPath, opts = {}) {
|
|
|
1648
1234
|
offset: opts.offset,
|
|
1649
1235
|
});
|
|
1650
1236
|
|
|
1651
|
-
if (opts
|
|
1652
|
-
console.log(JSON.stringify(data, null, 2));
|
|
1653
|
-
return;
|
|
1654
|
-
}
|
|
1655
|
-
if (opts.ndjson) {
|
|
1656
|
-
for (const r of data.results) {
|
|
1657
|
-
console.log(JSON.stringify(r));
|
|
1658
|
-
}
|
|
1659
|
-
return;
|
|
1660
|
-
}
|
|
1237
|
+
if (outputResult(data, 'results', opts)) return;
|
|
1661
1238
|
|
|
1662
1239
|
if (data.warning) {
|
|
1663
1240
|
console.log(`⚠ ${data.warning}`);
|