@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.
Files changed (47) hide show
  1. package/README.md +5 -5
  2. package/grammars/tree-sitter-go.wasm +0 -0
  3. package/package.json +8 -9
  4. package/src/ast-analysis/rules/csharp.js +201 -0
  5. package/src/ast-analysis/rules/go.js +182 -0
  6. package/src/ast-analysis/rules/index.js +82 -0
  7. package/src/ast-analysis/rules/java.js +175 -0
  8. package/src/ast-analysis/rules/javascript.js +246 -0
  9. package/src/ast-analysis/rules/php.js +219 -0
  10. package/src/ast-analysis/rules/python.js +196 -0
  11. package/src/ast-analysis/rules/ruby.js +204 -0
  12. package/src/ast-analysis/rules/rust.js +173 -0
  13. package/src/ast-analysis/shared.js +223 -0
  14. package/src/ast.js +15 -28
  15. package/src/audit.js +4 -5
  16. package/src/boundaries.js +1 -1
  17. package/src/branch-compare.js +84 -79
  18. package/src/builder.js +0 -5
  19. package/src/cfg.js +106 -338
  20. package/src/check.js +3 -3
  21. package/src/cli.js +99 -179
  22. package/src/cochange.js +1 -1
  23. package/src/communities.js +13 -16
  24. package/src/complexity.js +196 -1239
  25. package/src/cycles.js +1 -1
  26. package/src/dataflow.js +269 -694
  27. package/src/db/connection.js +88 -0
  28. package/src/db/migrations.js +312 -0
  29. package/src/db/query-builder.js +280 -0
  30. package/src/db/repository.js +134 -0
  31. package/src/db.js +19 -399
  32. package/src/embedder.js +145 -141
  33. package/src/export.js +1 -1
  34. package/src/flow.js +161 -162
  35. package/src/index.js +34 -1
  36. package/src/kinds.js +49 -0
  37. package/src/manifesto.js +3 -8
  38. package/src/mcp.js +37 -20
  39. package/src/owners.js +132 -132
  40. package/src/queries-cli.js +866 -0
  41. package/src/queries.js +1323 -2267
  42. package/src/result-formatter.js +21 -0
  43. package/src/sequence.js +177 -182
  44. package/src/structure.js +200 -199
  45. package/src/test-filter.js +7 -0
  46. package/src/triage.js +120 -162
  47. package/src/viewer.js +1 -1
package/src/cfg.js CHANGED
@@ -7,265 +7,25 @@
7
7
 
8
8
  import fs from 'node:fs';
9
9
  import path from 'node:path';
10
- import { COMPLEXITY_RULES } from './complexity.js';
10
+ import { CFG_RULES, COMPLEXITY_RULES } from './ast-analysis/rules/index.js';
11
+ import {
12
+ makeCfgRules as _makeCfgRules,
13
+ buildExtensionSet,
14
+ buildExtToLangMap,
15
+ findFunctionNode,
16
+ } from './ast-analysis/shared.js';
11
17
  import { openReadonlyOrFail } from './db.js';
12
18
  import { info } from './logger.js';
13
- import { paginateResult, printNdjson } from './paginate.js';
14
- import { LANGUAGE_REGISTRY } from './parser.js';
15
- import { isTestFile } from './queries.js';
16
-
17
- // ─── CFG Node Type Rules (extends COMPLEXITY_RULES) ──────────────────────
18
-
19
- const CFG_DEFAULTS = {
20
- ifNode: null,
21
- ifNodes: null,
22
- elifNode: null,
23
- elseClause: null,
24
- elseViaAlternative: false,
25
- ifConsequentField: null,
26
- forNodes: new Set(),
27
- whileNode: null,
28
- whileNodes: null,
29
- doNode: null,
30
- infiniteLoopNode: null,
31
- unlessNode: null,
32
- untilNode: null,
33
- switchNode: null,
34
- switchNodes: null,
35
- caseNode: null,
36
- caseNodes: null,
37
- defaultNode: null,
38
- tryNode: null,
39
- catchNode: null,
40
- finallyNode: null,
41
- returnNode: null,
42
- throwNode: null,
43
- breakNode: null,
44
- continueNode: null,
45
- blockNode: null,
46
- blockNodes: null,
47
- labeledNode: null,
48
- functionNodes: new Set(),
49
- };
50
-
51
- const CFG_RULE_KEYS = new Set(Object.keys(CFG_DEFAULTS));
52
-
53
- export function makeCfgRules(overrides) {
54
- for (const key of Object.keys(overrides)) {
55
- if (!CFG_RULE_KEYS.has(key)) {
56
- throw new Error(`CFG rules: unknown key "${key}"`);
57
- }
58
- }
59
- const rules = { ...CFG_DEFAULTS, ...overrides };
60
- if (!(rules.functionNodes instanceof Set) || rules.functionNodes.size === 0) {
61
- throw new Error('CFG rules: functionNodes must be a non-empty Set');
62
- }
63
- if (!(rules.forNodes instanceof Set)) {
64
- throw new Error('CFG rules: forNodes must be a Set');
65
- }
66
- return rules;
67
- }
19
+ import { paginateResult } from './paginate.js';
68
20
 
69
- const JS_TS_CFG = makeCfgRules({
70
- ifNode: 'if_statement',
71
- elseClause: 'else_clause',
72
- forNodes: new Set(['for_statement', 'for_in_statement']),
73
- whileNode: 'while_statement',
74
- doNode: 'do_statement',
75
- switchNode: 'switch_statement',
76
- caseNode: 'switch_case',
77
- defaultNode: 'switch_default',
78
- tryNode: 'try_statement',
79
- catchNode: 'catch_clause',
80
- finallyNode: 'finally_clause',
81
- returnNode: 'return_statement',
82
- throwNode: 'throw_statement',
83
- breakNode: 'break_statement',
84
- continueNode: 'continue_statement',
85
- blockNode: 'statement_block',
86
- labeledNode: 'labeled_statement',
87
- functionNodes: new Set([
88
- 'function_declaration',
89
- 'function_expression',
90
- 'arrow_function',
91
- 'method_definition',
92
- 'generator_function',
93
- 'generator_function_declaration',
94
- ]),
95
- });
96
-
97
- const PYTHON_CFG = makeCfgRules({
98
- ifNode: 'if_statement',
99
- elifNode: 'elif_clause',
100
- elseClause: 'else_clause',
101
- forNodes: new Set(['for_statement']),
102
- whileNode: 'while_statement',
103
- switchNode: 'match_statement',
104
- caseNode: 'case_clause',
105
- tryNode: 'try_statement',
106
- catchNode: 'except_clause',
107
- finallyNode: 'finally_clause',
108
- returnNode: 'return_statement',
109
- throwNode: 'raise_statement',
110
- breakNode: 'break_statement',
111
- continueNode: 'continue_statement',
112
- blockNode: 'block',
113
- functionNodes: new Set(['function_definition']),
114
- });
115
-
116
- const GO_CFG = makeCfgRules({
117
- ifNode: 'if_statement',
118
- elseViaAlternative: true,
119
- forNodes: new Set(['for_statement']),
120
- switchNodes: new Set([
121
- 'expression_switch_statement',
122
- 'type_switch_statement',
123
- 'select_statement',
124
- ]),
125
- caseNode: 'expression_case',
126
- caseNodes: new Set(['type_case', 'communication_case']),
127
- defaultNode: 'default_case',
128
- returnNode: 'return_statement',
129
- breakNode: 'break_statement',
130
- continueNode: 'continue_statement',
131
- blockNode: 'block',
132
- labeledNode: 'labeled_statement',
133
- functionNodes: new Set(['function_declaration', 'method_declaration', 'func_literal']),
134
- });
135
-
136
- const RUST_CFG = makeCfgRules({
137
- ifNode: 'if_expression',
138
- ifNodes: new Set(['if_let_expression']),
139
- elseClause: 'else_clause',
140
- forNodes: new Set(['for_expression']),
141
- whileNode: 'while_expression',
142
- whileNodes: new Set(['while_let_expression']),
143
- infiniteLoopNode: 'loop_expression',
144
- switchNode: 'match_expression',
145
- caseNode: 'match_arm',
146
- returnNode: 'return_expression',
147
- breakNode: 'break_expression',
148
- continueNode: 'continue_expression',
149
- blockNode: 'block',
150
- functionNodes: new Set(['function_item', 'closure_expression']),
151
- });
152
-
153
- const JAVA_CFG = makeCfgRules({
154
- ifNode: 'if_statement',
155
- elseViaAlternative: true,
156
- forNodes: new Set(['for_statement', 'enhanced_for_statement']),
157
- whileNode: 'while_statement',
158
- doNode: 'do_statement',
159
- switchNode: 'switch_expression',
160
- caseNode: 'switch_block_statement_group',
161
- caseNodes: new Set(['switch_rule']),
162
- tryNode: 'try_statement',
163
- catchNode: 'catch_clause',
164
- finallyNode: 'finally_clause',
165
- returnNode: 'return_statement',
166
- throwNode: 'throw_statement',
167
- breakNode: 'break_statement',
168
- continueNode: 'continue_statement',
169
- blockNode: 'block',
170
- labeledNode: 'labeled_statement',
171
- functionNodes: new Set(['method_declaration', 'constructor_declaration', 'lambda_expression']),
172
- });
173
-
174
- const CSHARP_CFG = makeCfgRules({
175
- ifNode: 'if_statement',
176
- elseViaAlternative: true,
177
- forNodes: new Set(['for_statement', 'foreach_statement']),
178
- whileNode: 'while_statement',
179
- doNode: 'do_statement',
180
- switchNode: 'switch_statement',
181
- caseNode: 'switch_section',
182
- tryNode: 'try_statement',
183
- catchNode: 'catch_clause',
184
- finallyNode: 'finally_clause',
185
- returnNode: 'return_statement',
186
- throwNode: 'throw_statement',
187
- breakNode: 'break_statement',
188
- continueNode: 'continue_statement',
189
- blockNode: 'block',
190
- labeledNode: 'labeled_statement',
191
- functionNodes: new Set([
192
- 'method_declaration',
193
- 'constructor_declaration',
194
- 'lambda_expression',
195
- 'local_function_statement',
196
- ]),
197
- });
198
-
199
- const RUBY_CFG = makeCfgRules({
200
- ifNode: 'if',
201
- elifNode: 'elsif',
202
- elseClause: 'else',
203
- forNodes: new Set(['for']),
204
- whileNode: 'while',
205
- unlessNode: 'unless',
206
- untilNode: 'until',
207
- switchNode: 'case',
208
- caseNode: 'when',
209
- defaultNode: 'else',
210
- tryNode: 'begin',
211
- catchNode: 'rescue',
212
- finallyNode: 'ensure',
213
- returnNode: 'return',
214
- breakNode: 'break',
215
- continueNode: 'next',
216
- blockNodes: new Set(['then', 'do', 'body_statement']),
217
- functionNodes: new Set(['method', 'singleton_method']),
218
- });
219
-
220
- const PHP_CFG = makeCfgRules({
221
- ifNode: 'if_statement',
222
- elifNode: 'else_if_clause',
223
- elseClause: 'else_clause',
224
- ifConsequentField: 'body',
225
- forNodes: new Set(['for_statement', 'foreach_statement']),
226
- whileNode: 'while_statement',
227
- doNode: 'do_statement',
228
- switchNode: 'switch_statement',
229
- caseNode: 'case_statement',
230
- defaultNode: 'default_statement',
231
- tryNode: 'try_statement',
232
- catchNode: 'catch_clause',
233
- finallyNode: 'finally_clause',
234
- returnNode: 'return_statement',
235
- throwNode: 'throw_expression',
236
- breakNode: 'break_statement',
237
- continueNode: 'continue_statement',
238
- blockNode: 'compound_statement',
239
- functionNodes: new Set([
240
- 'function_definition',
241
- 'method_declaration',
242
- 'anonymous_function_creation_expression',
243
- 'arrow_function',
244
- ]),
245
- });
246
-
247
- export const CFG_RULES = new Map([
248
- ['javascript', JS_TS_CFG],
249
- ['typescript', JS_TS_CFG],
250
- ['tsx', JS_TS_CFG],
251
- ['python', PYTHON_CFG],
252
- ['go', GO_CFG],
253
- ['rust', RUST_CFG],
254
- ['java', JAVA_CFG],
255
- ['csharp', CSHARP_CFG],
256
- ['ruby', RUBY_CFG],
257
- ['php', PHP_CFG],
258
- ]);
259
-
260
- const CFG_LANG_IDS = new Set(CFG_RULES.keys());
261
-
262
- // JS/TS extensions
263
- const CFG_EXTENSIONS = new Set();
264
- for (const entry of LANGUAGE_REGISTRY) {
265
- if (CFG_LANG_IDS.has(entry.id)) {
266
- for (const ext of entry.extensions) CFG_EXTENSIONS.add(ext);
267
- }
268
- }
21
+ import { outputResult } from './result-formatter.js';
22
+ import { isTestFile } from './test-filter.js';
23
+
24
+ // Re-export for backward compatibility
25
+ export { CFG_RULES };
26
+ export { _makeCfgRules as makeCfgRules };
27
+
28
+ const CFG_EXTENSIONS = buildExtensionSet(CFG_RULES);
269
29
 
270
30
  // ─── Core Algorithm: AST → CFG ──────────────────────────────────────────
271
31
 
@@ -327,11 +87,23 @@ export function buildFunctionCFG(functionNode, langId) {
327
87
  */
328
88
  function getStatements(node) {
329
89
  if (!node) return [];
330
- // Block-like nodes: extract named children
331
- if (node.type === rules.blockNode || rules.blockNodes?.has(node.type)) {
90
+ // Block-like nodes (including statement_list wrappers from tree-sitter-go 0.25+)
91
+ if (
92
+ node.type === 'statement_list' ||
93
+ node.type === rules.blockNode ||
94
+ rules.blockNodes?.has(node.type)
95
+ ) {
332
96
  const stmts = [];
333
97
  for (let i = 0; i < node.namedChildCount; i++) {
334
- stmts.push(node.namedChild(i));
98
+ const child = node.namedChild(i);
99
+ if (child.type === 'statement_list') {
100
+ // Unwrap nested statement_list (block → statement_list → stmts)
101
+ for (let j = 0; j < child.namedChildCount; j++) {
102
+ stmts.push(child.namedChild(j));
103
+ }
104
+ } else {
105
+ stmts.push(child);
106
+ }
335
107
  }
336
108
  return stmts;
337
109
  }
@@ -888,7 +660,14 @@ export function buildFunctionCFG(functionNode, langId) {
888
660
  for (let j = 0; j < caseClause.namedChildCount; j++) {
889
661
  const child = caseClause.namedChild(j);
890
662
  if (child !== valueNode && child !== patternNode && child.type !== 'switch_label') {
891
- caseStmts.push(child);
663
+ if (child.type === 'statement_list') {
664
+ // Unwrap statement_list (tree-sitter-go 0.25+)
665
+ for (let k = 0; k < child.namedChildCount; k++) {
666
+ caseStmts.push(child.namedChild(k));
667
+ }
668
+ } else {
669
+ caseStmts.push(child);
670
+ }
892
671
  }
893
672
  }
894
673
  }
@@ -1050,12 +829,7 @@ export async function buildCFGData(db, fileSymbols, rootDir, _engineOpts) {
1050
829
 
1051
830
  // Always build ext→langId map so native-only builds (where _langId is unset)
1052
831
  // can still derive the language from the file extension.
1053
- const extToLang = new Map();
1054
- for (const entry of LANGUAGE_REGISTRY) {
1055
- for (const ext of entry.extensions) {
1056
- extToLang.set(ext, entry.id);
1057
- }
1058
- }
832
+ const extToLang = buildExtToLangMap();
1059
833
 
1060
834
  for (const [relPath, symbols] of fileSymbols) {
1061
835
  if (!symbols._tree) {
@@ -1084,7 +858,7 @@ export async function buildCFGData(db, fileSymbols, rootDir, _engineOpts) {
1084
858
  getParserFn = mod.getParser;
1085
859
  }
1086
860
 
1087
- const { findFunctionNode } = await import('./complexity.js');
861
+ // findFunctionNode imported from ./ast-analysis/shared.js at module level
1088
862
 
1089
863
  const insertBlock = db.prepare(
1090
864
  `INSERT INTO cfg_blocks (function_node_id, block_index, block_type, start_line, end_line, label)
@@ -1119,7 +893,7 @@ export async function buildCFGData(db, fileSymbols, rootDir, _engineOpts) {
1119
893
  if (!tree && !allNative) {
1120
894
  if (!getParserFn) continue;
1121
895
  langId = extToLang.get(ext);
1122
- if (!langId || !CFG_LANG_IDS.has(langId)) continue;
896
+ if (!langId || !CFG_RULES.has(langId)) continue;
1123
897
 
1124
898
  const absPath = path.join(rootDir, relPath);
1125
899
  let code;
@@ -1255,72 +1029,73 @@ function findNodes(db, name, opts = {}) {
1255
1029
  */
1256
1030
  export function cfgData(name, customDbPath, opts = {}) {
1257
1031
  const db = openReadonlyOrFail(customDbPath);
1258
- const noTests = opts.noTests || false;
1259
-
1260
- if (!hasCfgTables(db)) {
1261
- db.close();
1262
- return {
1263
- name,
1264
- results: [],
1265
- warning:
1266
- 'No CFG data found. Rebuild with `codegraph build` (CFG is now included by default).',
1267
- };
1268
- }
1032
+ try {
1033
+ const noTests = opts.noTests || false;
1034
+
1035
+ if (!hasCfgTables(db)) {
1036
+ return {
1037
+ name,
1038
+ results: [],
1039
+ warning:
1040
+ 'No CFG data found. Rebuild with `codegraph build` (CFG is now included by default).',
1041
+ };
1042
+ }
1269
1043
 
1270
- const nodes = findNodes(db, name, { noTests, file: opts.file, kind: opts.kind });
1271
- if (nodes.length === 0) {
1272
- db.close();
1273
- return { name, results: [] };
1274
- }
1044
+ const nodes = findNodes(db, name, { noTests, file: opts.file, kind: opts.kind });
1045
+ if (nodes.length === 0) {
1046
+ return { name, results: [] };
1047
+ }
1275
1048
 
1276
- const blockStmt = db.prepare(
1277
- `SELECT id, block_index, block_type, start_line, end_line, label
1278
- FROM cfg_blocks WHERE function_node_id = ?
1279
- ORDER BY block_index`,
1280
- );
1281
- const edgeStmt = db.prepare(
1282
- `SELECT e.kind,
1283
- sb.block_index AS source_index, sb.block_type AS source_type,
1284
- tb.block_index AS target_index, tb.block_type AS target_type
1285
- FROM cfg_edges e
1286
- JOIN cfg_blocks sb ON e.source_block_id = sb.id
1287
- JOIN cfg_blocks tb ON e.target_block_id = tb.id
1288
- WHERE e.function_node_id = ?
1289
- ORDER BY sb.block_index, tb.block_index`,
1290
- );
1049
+ const blockStmt = db.prepare(
1050
+ `SELECT id, block_index, block_type, start_line, end_line, label
1051
+ FROM cfg_blocks WHERE function_node_id = ?
1052
+ ORDER BY block_index`,
1053
+ );
1054
+ const edgeStmt = db.prepare(
1055
+ `SELECT e.kind,
1056
+ sb.block_index AS source_index, sb.block_type AS source_type,
1057
+ tb.block_index AS target_index, tb.block_type AS target_type
1058
+ FROM cfg_edges e
1059
+ JOIN cfg_blocks sb ON e.source_block_id = sb.id
1060
+ JOIN cfg_blocks tb ON e.target_block_id = tb.id
1061
+ WHERE e.function_node_id = ?
1062
+ ORDER BY sb.block_index, tb.block_index`,
1063
+ );
1291
1064
 
1292
- const results = nodes.map((node) => {
1293
- const cfgBlocks = blockStmt.all(node.id);
1294
- const cfgEdges = edgeStmt.all(node.id);
1295
-
1296
- return {
1297
- name: node.name,
1298
- kind: node.kind,
1299
- file: node.file,
1300
- line: node.line,
1301
- blocks: cfgBlocks.map((b) => ({
1302
- index: b.block_index,
1303
- type: b.block_type,
1304
- startLine: b.start_line,
1305
- endLine: b.end_line,
1306
- label: b.label,
1307
- })),
1308
- edges: cfgEdges.map((e) => ({
1309
- source: e.source_index,
1310
- sourceType: e.source_type,
1311
- target: e.target_index,
1312
- targetType: e.target_type,
1313
- kind: e.kind,
1314
- })),
1315
- summary: {
1316
- blockCount: cfgBlocks.length,
1317
- edgeCount: cfgEdges.length,
1318
- },
1319
- };
1320
- });
1065
+ const results = nodes.map((node) => {
1066
+ const cfgBlocks = blockStmt.all(node.id);
1067
+ const cfgEdges = edgeStmt.all(node.id);
1068
+
1069
+ return {
1070
+ name: node.name,
1071
+ kind: node.kind,
1072
+ file: node.file,
1073
+ line: node.line,
1074
+ blocks: cfgBlocks.map((b) => ({
1075
+ index: b.block_index,
1076
+ type: b.block_type,
1077
+ startLine: b.start_line,
1078
+ endLine: b.end_line,
1079
+ label: b.label,
1080
+ })),
1081
+ edges: cfgEdges.map((e) => ({
1082
+ source: e.source_index,
1083
+ sourceType: e.source_type,
1084
+ target: e.target_index,
1085
+ targetType: e.target_type,
1086
+ kind: e.kind,
1087
+ })),
1088
+ summary: {
1089
+ blockCount: cfgBlocks.length,
1090
+ edgeCount: cfgEdges.length,
1091
+ },
1092
+ };
1093
+ });
1321
1094
 
1322
- db.close();
1323
- return paginateResult({ name, results }, 'results', opts);
1095
+ return paginateResult({ name, results }, 'results', opts);
1096
+ } finally {
1097
+ db.close();
1098
+ }
1324
1099
  }
1325
1100
 
1326
1101
  // ─── Export Formats ─────────────────────────────────────────────────────
@@ -1418,14 +1193,7 @@ function edgeStyle(kind) {
1418
1193
  export function cfg(name, customDbPath, opts = {}) {
1419
1194
  const data = cfgData(name, customDbPath, opts);
1420
1195
 
1421
- if (opts.json) {
1422
- console.log(JSON.stringify(data, null, 2));
1423
- return;
1424
- }
1425
- if (opts.ndjson) {
1426
- printNdjson(data.results);
1427
- return;
1428
- }
1196
+ if (outputResult(data, 'results', opts)) return;
1429
1197
 
1430
1198
  if (data.warning) {
1431
1199
  console.log(`\u26A0 ${data.warning}`);
package/src/check.js CHANGED
@@ -5,7 +5,8 @@ import { loadConfig } from './config.js';
5
5
  import { findCycles } from './cycles.js';
6
6
  import { findDbPath, openReadonlyOrFail } from './db.js';
7
7
  import { matchOwners, parseCodeowners } from './owners.js';
8
- import { isTestFile } from './queries.js';
8
+ import { outputResult } from './result-formatter.js';
9
+ import { isTestFile } from './test-filter.js';
9
10
 
10
11
  // ─── Diff Parser ──────────────────────────────────────────────────────
11
12
 
@@ -361,8 +362,7 @@ export function check(customDbPath, opts = {}) {
361
362
  process.exit(1);
362
363
  }
363
364
 
364
- if (opts.json) {
365
- console.log(JSON.stringify(data, null, 2));
365
+ if (outputResult(data, null, opts)) {
366
366
  if (!data.passed) process.exit(1);
367
367
  return;
368
368
  }