@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.
Files changed (49) hide show
  1. package/README.md +59 -52
  2. package/grammars/tree-sitter-go.wasm +0 -0
  3. package/package.json +9 -10
  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 +274 -159
  19. package/src/cfg.js +111 -341
  20. package/src/check.js +3 -3
  21. package/src/cli.js +122 -167
  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 +274 -697
  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 -392
  32. package/src/embedder.js +145 -141
  33. package/src/export.js +1 -1
  34. package/src/flow.js +160 -228
  35. package/src/index.js +36 -2
  36. package/src/kinds.js +49 -0
  37. package/src/manifesto.js +3 -8
  38. package/src/mcp.js +97 -20
  39. package/src/owners.js +132 -132
  40. package/src/parser.js +58 -131
  41. package/src/queries-cli.js +866 -0
  42. package/src/queries.js +1356 -2261
  43. package/src/resolve.js +11 -2
  44. package/src/result-formatter.js +21 -0
  45. package/src/sequence.js +364 -0
  46. package/src/structure.js +200 -199
  47. package/src/test-filter.js +7 -0
  48. package/src/triage.js +120 -162
  49. 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
  }
@@ -1046,9 +825,12 @@ export function buildFunctionCFG(functionNode, langId) {
1046
825
  export async function buildCFGData(db, fileSymbols, rootDir, _engineOpts) {
1047
826
  // Lazily init WASM parsers if needed
1048
827
  let parsers = null;
1049
- let extToLang = null;
1050
828
  let needsFallback = false;
1051
829
 
830
+ // Always build ext→langId map so native-only builds (where _langId is unset)
831
+ // can still derive the language from the file extension.
832
+ const extToLang = buildExtToLangMap();
833
+
1052
834
  for (const [relPath, symbols] of fileSymbols) {
1053
835
  if (!symbols._tree) {
1054
836
  const ext = path.extname(relPath).toLowerCase();
@@ -1068,12 +850,6 @@ export async function buildCFGData(db, fileSymbols, rootDir, _engineOpts) {
1068
850
  if (needsFallback) {
1069
851
  const { createParsers } = await import('./parser.js');
1070
852
  parsers = await createParsers();
1071
- extToLang = new Map();
1072
- for (const entry of LANGUAGE_REGISTRY) {
1073
- for (const ext of entry.extensions) {
1074
- extToLang.set(ext, entry.id);
1075
- }
1076
- }
1077
853
  }
1078
854
 
1079
855
  let getParserFn = null;
@@ -1082,7 +858,7 @@ export async function buildCFGData(db, fileSymbols, rootDir, _engineOpts) {
1082
858
  getParserFn = mod.getParser;
1083
859
  }
1084
860
 
1085
- const { findFunctionNode } = await import('./complexity.js');
861
+ // findFunctionNode imported from ./ast-analysis/shared.js at module level
1086
862
 
1087
863
  const insertBlock = db.prepare(
1088
864
  `INSERT INTO cfg_blocks (function_node_id, block_index, block_type, start_line, end_line, label)
@@ -1115,9 +891,9 @@ export async function buildCFGData(db, fileSymbols, rootDir, _engineOpts) {
1115
891
 
1116
892
  // WASM fallback if no cached tree and not all native
1117
893
  if (!tree && !allNative) {
1118
- if (!extToLang || !getParserFn) continue;
894
+ if (!getParserFn) continue;
1119
895
  langId = extToLang.get(ext);
1120
- if (!langId || !CFG_LANG_IDS.has(langId)) continue;
896
+ if (!langId || !CFG_RULES.has(langId)) continue;
1121
897
 
1122
898
  const absPath = path.join(rootDir, relPath);
1123
899
  let code;
@@ -1138,7 +914,7 @@ export async function buildCFGData(db, fileSymbols, rootDir, _engineOpts) {
1138
914
  }
1139
915
 
1140
916
  if (!langId) {
1141
- langId = extToLang ? extToLang.get(ext) : null;
917
+ langId = extToLang.get(ext);
1142
918
  if (!langId) continue;
1143
919
  }
1144
920
 
@@ -1253,72 +1029,73 @@ function findNodes(db, name, opts = {}) {
1253
1029
  */
1254
1030
  export function cfgData(name, customDbPath, opts = {}) {
1255
1031
  const db = openReadonlyOrFail(customDbPath);
1256
- const noTests = opts.noTests || false;
1257
-
1258
- if (!hasCfgTables(db)) {
1259
- db.close();
1260
- return {
1261
- name,
1262
- results: [],
1263
- warning:
1264
- 'No CFG data found. Rebuild with `codegraph build` (CFG is now included by default).',
1265
- };
1266
- }
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
+ }
1267
1043
 
1268
- const nodes = findNodes(db, name, { noTests, file: opts.file, kind: opts.kind });
1269
- if (nodes.length === 0) {
1270
- db.close();
1271
- return { name, results: [] };
1272
- }
1044
+ const nodes = findNodes(db, name, { noTests, file: opts.file, kind: opts.kind });
1045
+ if (nodes.length === 0) {
1046
+ return { name, results: [] };
1047
+ }
1273
1048
 
1274
- const blockStmt = db.prepare(
1275
- `SELECT id, block_index, block_type, start_line, end_line, label
1276
- FROM cfg_blocks WHERE function_node_id = ?
1277
- ORDER BY block_index`,
1278
- );
1279
- const edgeStmt = db.prepare(
1280
- `SELECT e.kind,
1281
- sb.block_index AS source_index, sb.block_type AS source_type,
1282
- tb.block_index AS target_index, tb.block_type AS target_type
1283
- FROM cfg_edges e
1284
- JOIN cfg_blocks sb ON e.source_block_id = sb.id
1285
- JOIN cfg_blocks tb ON e.target_block_id = tb.id
1286
- WHERE e.function_node_id = ?
1287
- ORDER BY sb.block_index, tb.block_index`,
1288
- );
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
+ );
1289
1064
 
1290
- const results = nodes.map((node) => {
1291
- const cfgBlocks = blockStmt.all(node.id);
1292
- const cfgEdges = edgeStmt.all(node.id);
1293
-
1294
- return {
1295
- name: node.name,
1296
- kind: node.kind,
1297
- file: node.file,
1298
- line: node.line,
1299
- blocks: cfgBlocks.map((b) => ({
1300
- index: b.block_index,
1301
- type: b.block_type,
1302
- startLine: b.start_line,
1303
- endLine: b.end_line,
1304
- label: b.label,
1305
- })),
1306
- edges: cfgEdges.map((e) => ({
1307
- source: e.source_index,
1308
- sourceType: e.source_type,
1309
- target: e.target_index,
1310
- targetType: e.target_type,
1311
- kind: e.kind,
1312
- })),
1313
- summary: {
1314
- blockCount: cfgBlocks.length,
1315
- edgeCount: cfgEdges.length,
1316
- },
1317
- };
1318
- });
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
+ });
1319
1094
 
1320
- db.close();
1321
- return paginateResult({ name, results }, 'results', opts);
1095
+ return paginateResult({ name, results }, 'results', opts);
1096
+ } finally {
1097
+ db.close();
1098
+ }
1322
1099
  }
1323
1100
 
1324
1101
  // ─── Export Formats ─────────────────────────────────────────────────────
@@ -1416,14 +1193,7 @@ function edgeStyle(kind) {
1416
1193
  export function cfg(name, customDbPath, opts = {}) {
1417
1194
  const data = cfgData(name, customDbPath, opts);
1418
1195
 
1419
- if (opts.json) {
1420
- console.log(JSON.stringify(data, null, 2));
1421
- return;
1422
- }
1423
- if (opts.ndjson) {
1424
- printNdjson(data.results);
1425
- return;
1426
- }
1196
+ if (outputResult(data, 'results', opts)) return;
1427
1197
 
1428
1198
  if (data.warning) {
1429
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
  }