@optave/codegraph 3.1.0 → 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 +5 -5
- package/grammars/tree-sitter-go.wasm +0 -0
- package/package.json +8 -9
- 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 +0 -5
- package/src/cfg.js +106 -338
- package/src/check.js +3 -3
- package/src/cli.js +99 -179
- 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 +269 -694
- 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 -399
- package/src/embedder.js +145 -141
- package/src/export.js +1 -1
- package/src/flow.js +161 -162
- package/src/index.js +34 -1
- package/src/kinds.js +49 -0
- package/src/manifesto.js +3 -8
- package/src/mcp.js +37 -20
- package/src/owners.js +132 -132
- package/src/queries-cli.js +866 -0
- package/src/queries.js +1323 -2267
- package/src/result-formatter.js +21 -0
- package/src/sequence.js +177 -182
- 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
|
|
|
@@ -1009,12 +607,7 @@ export async function buildDataflowEdges(db, fileSymbols, rootDir, _engineOpts)
|
|
|
1009
607
|
|
|
1010
608
|
// Always build ext→langId map so native-only builds (where _langId is unset)
|
|
1011
609
|
// can still derive the language from the file extension.
|
|
1012
|
-
const extToLang =
|
|
1013
|
-
for (const entry of LANGUAGE_REGISTRY) {
|
|
1014
|
-
for (const ext of entry.extensions) {
|
|
1015
|
-
extToLang.set(ext, entry.id);
|
|
1016
|
-
}
|
|
1017
|
-
}
|
|
610
|
+
const extToLang = buildExtToLangMap();
|
|
1018
611
|
|
|
1019
612
|
for (const [relPath, symbols] of fileSymbols) {
|
|
1020
613
|
if (!symbols._tree && !symbols.dataflow) {
|
|
@@ -1073,7 +666,7 @@ export async function buildDataflowEdges(db, fileSymbols, rootDir, _engineOpts)
|
|
|
1073
666
|
if (!tree) {
|
|
1074
667
|
if (!getParserFn) continue;
|
|
1075
668
|
langId = extToLang.get(ext);
|
|
1076
|
-
if (!langId || !
|
|
669
|
+
if (!langId || !DATAFLOW_RULES.has(langId)) continue;
|
|
1077
670
|
|
|
1078
671
|
const absPath = path.join(rootDir, relPath);
|
|
1079
672
|
let code;
|
|
@@ -1221,134 +814,135 @@ function hasDataflowTable(db) {
|
|
|
1221
814
|
*/
|
|
1222
815
|
export function dataflowData(name, customDbPath, opts = {}) {
|
|
1223
816
|
const db = openReadonlyOrFail(customDbPath);
|
|
1224
|
-
|
|
817
|
+
try {
|
|
818
|
+
const noTests = opts.noTests || false;
|
|
1225
819
|
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
}
|
|
1234
|
-
}
|
|
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
|
+
}
|
|
1235
828
|
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
}
|
|
829
|
+
const nodes = findNodes(db, name, { noTests, file: opts.file, kind: opts.kind });
|
|
830
|
+
if (nodes.length === 0) {
|
|
831
|
+
return { name, results: [] };
|
|
832
|
+
}
|
|
1241
833
|
|
|
1242
|
-
|
|
1243
|
-
|
|
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
|
|
1244
836
|
FROM dataflow d JOIN nodes n ON d.target_id = n.id
|
|
1245
837
|
WHERE d.source_id = ? AND d.kind = 'flows_to'`,
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
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
|
|
1249
841
|
FROM dataflow d JOIN nodes n ON d.source_id = n.id
|
|
1250
842
|
WHERE d.target_id = ? AND d.kind = 'flows_to'`,
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
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
|
|
1254
846
|
FROM dataflow d JOIN nodes n ON d.target_id = n.id
|
|
1255
847
|
WHERE d.source_id = ? AND d.kind = 'returns'`,
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
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
|
|
1259
851
|
FROM dataflow d JOIN nodes n ON d.source_id = n.id
|
|
1260
852
|
WHERE d.target_id = ? AND d.kind = 'returns'`,
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
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
|
|
1264
856
|
FROM dataflow d JOIN nodes n ON d.target_id = n.id
|
|
1265
857
|
WHERE d.source_id = ? AND d.kind = 'mutates'`,
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
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
|
|
1269
861
|
FROM dataflow d JOIN nodes n ON d.source_id = n.id
|
|
1270
862
|
WHERE d.target_id = ? AND d.kind = 'mutates'`,
|
|
1271
|
-
|
|
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
|
+
}
|
|
1272
929
|
|
|
1273
|
-
const hc = new Map();
|
|
1274
|
-
const results = nodes.map((node) => {
|
|
1275
|
-
const sym = normalizeSymbol(node, db, hc);
|
|
1276
|
-
|
|
1277
|
-
const flowsTo = flowsToOut.all(node.id).map((r) => ({
|
|
1278
|
-
target: r.target_name,
|
|
1279
|
-
kind: r.target_kind,
|
|
1280
|
-
file: r.target_file,
|
|
1281
|
-
line: r.line,
|
|
1282
|
-
paramIndex: r.param_index,
|
|
1283
|
-
expression: r.expression,
|
|
1284
|
-
confidence: r.confidence,
|
|
1285
|
-
}));
|
|
1286
|
-
|
|
1287
|
-
const flowsFrom = flowsToIn.all(node.id).map((r) => ({
|
|
1288
|
-
source: r.source_name,
|
|
1289
|
-
kind: r.source_kind,
|
|
1290
|
-
file: r.source_file,
|
|
1291
|
-
line: r.line,
|
|
1292
|
-
paramIndex: r.param_index,
|
|
1293
|
-
expression: r.expression,
|
|
1294
|
-
confidence: r.confidence,
|
|
1295
|
-
}));
|
|
1296
|
-
|
|
1297
|
-
const returnConsumers = returnsOut.all(node.id).map((r) => ({
|
|
1298
|
-
consumer: r.target_name,
|
|
1299
|
-
kind: r.target_kind,
|
|
1300
|
-
file: r.target_file,
|
|
1301
|
-
line: r.line,
|
|
1302
|
-
expression: r.expression,
|
|
1303
|
-
}));
|
|
1304
|
-
|
|
1305
|
-
const returnedBy = returnsIn.all(node.id).map((r) => ({
|
|
1306
|
-
producer: r.source_name,
|
|
1307
|
-
kind: r.source_kind,
|
|
1308
|
-
file: r.source_file,
|
|
1309
|
-
line: r.line,
|
|
1310
|
-
expression: r.expression,
|
|
1311
|
-
}));
|
|
1312
|
-
|
|
1313
|
-
const mutatesTargets = mutatesOut.all(node.id).map((r) => ({
|
|
1314
|
-
target: r.target_name,
|
|
1315
|
-
expression: r.expression,
|
|
1316
|
-
line: r.line,
|
|
1317
|
-
}));
|
|
1318
|
-
|
|
1319
|
-
const mutatedBy = mutatesIn.all(node.id).map((r) => ({
|
|
1320
|
-
source: r.source_name,
|
|
1321
|
-
expression: r.expression,
|
|
1322
|
-
line: r.line,
|
|
1323
|
-
}));
|
|
1324
|
-
|
|
1325
|
-
if (noTests) {
|
|
1326
|
-
const filter = (arr) => arr.filter((r) => !isTestFile(r.file));
|
|
1327
930
|
return {
|
|
1328
931
|
...sym,
|
|
1329
|
-
flowsTo
|
|
1330
|
-
flowsFrom
|
|
1331
|
-
returns: returnConsumers
|
|
1332
|
-
returnedBy
|
|
932
|
+
flowsTo,
|
|
933
|
+
flowsFrom,
|
|
934
|
+
returns: returnConsumers,
|
|
935
|
+
returnedBy,
|
|
1333
936
|
mutates: mutatesTargets,
|
|
1334
937
|
mutatedBy,
|
|
1335
938
|
};
|
|
1336
|
-
}
|
|
1337
|
-
|
|
1338
|
-
return {
|
|
1339
|
-
...sym,
|
|
1340
|
-
flowsTo,
|
|
1341
|
-
flowsFrom,
|
|
1342
|
-
returns: returnConsumers,
|
|
1343
|
-
returnedBy,
|
|
1344
|
-
mutates: mutatesTargets,
|
|
1345
|
-
mutatedBy,
|
|
1346
|
-
};
|
|
1347
|
-
});
|
|
939
|
+
});
|
|
1348
940
|
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
941
|
+
const base = { name, results };
|
|
942
|
+
return paginateResult(base, 'results', { limit: opts.limit, offset: opts.offset });
|
|
943
|
+
} finally {
|
|
944
|
+
db.close();
|
|
945
|
+
}
|
|
1352
946
|
}
|
|
1353
947
|
|
|
1354
948
|
/**
|
|
@@ -1362,125 +956,123 @@ export function dataflowData(name, customDbPath, opts = {}) {
|
|
|
1362
956
|
*/
|
|
1363
957
|
export function dataflowPathData(from, to, customDbPath, opts = {}) {
|
|
1364
958
|
const db = openReadonlyOrFail(customDbPath);
|
|
1365
|
-
|
|
1366
|
-
|
|
959
|
+
try {
|
|
960
|
+
const noTests = opts.noTests || false;
|
|
961
|
+
const maxDepth = opts.maxDepth || 10;
|
|
1367
962
|
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
}
|
|
1377
|
-
}
|
|
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
|
+
}
|
|
1378
972
|
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
}
|
|
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
|
+
}
|
|
1384
977
|
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
}
|
|
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
|
+
}
|
|
1390
982
|
|
|
1391
|
-
|
|
1392
|
-
|
|
983
|
+
const sourceNode = fromNodes[0];
|
|
984
|
+
const targetNode = toNodes[0];
|
|
1393
985
|
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
}
|
|
1405
|
-
}
|
|
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
|
+
}
|
|
1406
997
|
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
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
|
|
1410
1001
|
FROM dataflow d JOIN nodes n ON d.target_id = n.id
|
|
1411
1002
|
WHERE d.source_id = ? AND d.kind IN ('flows_to', 'returns')`,
|
|
1412
|
-
|
|
1003
|
+
);
|
|
1413
1004
|
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
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);
|
|
1428
1029
|
parent.set(n.id, {
|
|
1429
1030
|
parentId: currentId,
|
|
1430
1031
|
edgeKind: n.edge_kind,
|
|
1431
1032
|
expression: n.expression,
|
|
1432
1033
|
});
|
|
1034
|
+
nextQueue.push(n.id);
|
|
1433
1035
|
}
|
|
1434
|
-
continue;
|
|
1435
|
-
}
|
|
1436
|
-
if (!visited.has(n.id)) {
|
|
1437
|
-
visited.add(n.id);
|
|
1438
|
-
parent.set(n.id, {
|
|
1439
|
-
parentId: currentId,
|
|
1440
|
-
edgeKind: n.edge_kind,
|
|
1441
|
-
expression: n.expression,
|
|
1442
|
-
});
|
|
1443
|
-
nextQueue.push(n.id);
|
|
1444
1036
|
}
|
|
1445
1037
|
}
|
|
1038
|
+
if (found) break;
|
|
1039
|
+
queue = nextQueue;
|
|
1040
|
+
if (queue.length === 0) break;
|
|
1446
1041
|
}
|
|
1447
|
-
if (found) break;
|
|
1448
|
-
queue = nextQueue;
|
|
1449
|
-
if (queue.length === 0) break;
|
|
1450
|
-
}
|
|
1451
1042
|
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
}
|
|
1043
|
+
if (!found) {
|
|
1044
|
+
return { from, to, found: false };
|
|
1045
|
+
}
|
|
1456
1046
|
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
pathItems.unshift({
|
|
1466
|
-
...normalizeSymbol(nodeRow, db, hc),
|
|
1467
|
-
edgeKind: parentInfo?.edgeKind ?? null,
|
|
1468
|
-
expression: parentInfo?.expression ?? null,
|
|
1469
|
-
});
|
|
1470
|
-
cur = parentInfo?.parentId;
|
|
1471
|
-
if (cur === sourceNode.id) {
|
|
1472
|
-
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);
|
|
1473
1055
|
pathItems.unshift({
|
|
1474
|
-
...normalizeSymbol(
|
|
1475
|
-
edgeKind: null,
|
|
1476
|
-
expression: null,
|
|
1056
|
+
...normalizeSymbol(nodeRow, db, hc),
|
|
1057
|
+
edgeKind: parentInfo?.edgeKind ?? null,
|
|
1058
|
+
expression: parentInfo?.expression ?? null,
|
|
1477
1059
|
});
|
|
1478
|
-
|
|
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
|
+
}
|
|
1479
1070
|
}
|
|
1480
|
-
}
|
|
1481
1071
|
|
|
1482
|
-
|
|
1483
|
-
|
|
1072
|
+
return { from, to, found: true, hops: pathItems.length - 1, path: pathItems };
|
|
1073
|
+
} finally {
|
|
1074
|
+
db.close();
|
|
1075
|
+
}
|
|
1484
1076
|
}
|
|
1485
1077
|
|
|
1486
1078
|
/**
|
|
@@ -1493,66 +1085,67 @@ export function dataflowPathData(from, to, customDbPath, opts = {}) {
|
|
|
1493
1085
|
*/
|
|
1494
1086
|
export function dataflowImpactData(name, customDbPath, opts = {}) {
|
|
1495
1087
|
const db = openReadonlyOrFail(customDbPath);
|
|
1496
|
-
|
|
1497
|
-
|
|
1088
|
+
try {
|
|
1089
|
+
const maxDepth = opts.depth || 5;
|
|
1090
|
+
const noTests = opts.noTests || false;
|
|
1498
1091
|
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
}
|
|
1507
|
-
}
|
|
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
|
+
}
|
|
1508
1100
|
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
}
|
|
1101
|
+
const nodes = findNodes(db, name, { noTests, file: opts.file, kind: opts.kind });
|
|
1102
|
+
if (nodes.length === 0) {
|
|
1103
|
+
return { name, results: [] };
|
|
1104
|
+
}
|
|
1514
1105
|
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1106
|
+
// Forward BFS: who consumes this function's return value (directly or transitively)?
|
|
1107
|
+
const consumersStmt = db.prepare(
|
|
1108
|
+
`SELECT DISTINCT n.*
|
|
1518
1109
|
FROM dataflow d JOIN nodes n ON d.target_id = n.id
|
|
1519
1110
|
WHERE d.source_id = ? AND d.kind = 'returns'`,
|
|
1520
|
-
|
|
1111
|
+
);
|
|
1521
1112
|
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
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
|
+
}
|
|
1539
1131
|
}
|
|
1540
1132
|
}
|
|
1133
|
+
frontier = nextFrontier;
|
|
1134
|
+
if (frontier.length === 0) break;
|
|
1541
1135
|
}
|
|
1542
|
-
frontier = nextFrontier;
|
|
1543
|
-
if (frontier.length === 0) break;
|
|
1544
|
-
}
|
|
1545
1136
|
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1137
|
+
return {
|
|
1138
|
+
...sym,
|
|
1139
|
+
levels,
|
|
1140
|
+
totalAffected: visited.size - 1,
|
|
1141
|
+
};
|
|
1142
|
+
});
|
|
1552
1143
|
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1144
|
+
const base = { name, results };
|
|
1145
|
+
return paginateResult(base, 'results', { limit: opts.limit, offset: opts.offset });
|
|
1146
|
+
} finally {
|
|
1147
|
+
db.close();
|
|
1148
|
+
}
|
|
1556
1149
|
}
|
|
1557
1150
|
|
|
1558
1151
|
// ── Display formatters ──────────────────────────────────────────────────────
|
|
@@ -1567,16 +1160,7 @@ export function dataflow(name, customDbPath, opts = {}) {
|
|
|
1567
1160
|
|
|
1568
1161
|
const data = dataflowData(name, customDbPath, opts);
|
|
1569
1162
|
|
|
1570
|
-
if (opts
|
|
1571
|
-
console.log(JSON.stringify(data, null, 2));
|
|
1572
|
-
return;
|
|
1573
|
-
}
|
|
1574
|
-
if (opts.ndjson) {
|
|
1575
|
-
for (const r of data.results) {
|
|
1576
|
-
console.log(JSON.stringify(r));
|
|
1577
|
-
}
|
|
1578
|
-
return;
|
|
1579
|
-
}
|
|
1163
|
+
if (outputResult(data, 'results', opts)) return;
|
|
1580
1164
|
|
|
1581
1165
|
if (data.warning) {
|
|
1582
1166
|
console.log(`⚠ ${data.warning}`);
|
|
@@ -1650,16 +1234,7 @@ function dataflowImpact(name, customDbPath, opts = {}) {
|
|
|
1650
1234
|
offset: opts.offset,
|
|
1651
1235
|
});
|
|
1652
1236
|
|
|
1653
|
-
if (opts
|
|
1654
|
-
console.log(JSON.stringify(data, null, 2));
|
|
1655
|
-
return;
|
|
1656
|
-
}
|
|
1657
|
-
if (opts.ndjson) {
|
|
1658
|
-
for (const r of data.results) {
|
|
1659
|
-
console.log(JSON.stringify(r));
|
|
1660
|
-
}
|
|
1661
|
-
return;
|
|
1662
|
-
}
|
|
1237
|
+
if (outputResult(data, 'results', opts)) return;
|
|
1663
1238
|
|
|
1664
1239
|
if (data.warning) {
|
|
1665
1240
|
console.log(`⚠ ${data.warning}`);
|