@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
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ruby — AST analysis rules.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { makeCfgRules, makeDataflowRules } from '../shared.js';
|
|
6
|
+
|
|
7
|
+
// ─── Complexity ───────────────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
export const complexity = {
|
|
10
|
+
branchNodes: new Set([
|
|
11
|
+
'if',
|
|
12
|
+
'elsif',
|
|
13
|
+
'else',
|
|
14
|
+
'unless',
|
|
15
|
+
'case',
|
|
16
|
+
'for',
|
|
17
|
+
'while',
|
|
18
|
+
'until',
|
|
19
|
+
'rescue',
|
|
20
|
+
'conditional',
|
|
21
|
+
]),
|
|
22
|
+
caseNodes: new Set(['when']),
|
|
23
|
+
logicalOperators: new Set(['and', 'or', '&&', '||']),
|
|
24
|
+
logicalNodeType: 'binary',
|
|
25
|
+
optionalChainType: null,
|
|
26
|
+
nestingNodes: new Set(['if', 'unless', 'case', 'for', 'while', 'until', 'rescue', 'conditional']),
|
|
27
|
+
functionNodes: new Set(['method', 'singleton_method', 'lambda', 'do_block']),
|
|
28
|
+
ifNodeType: 'if',
|
|
29
|
+
elseNodeType: 'else',
|
|
30
|
+
elifNodeType: 'elsif',
|
|
31
|
+
elseViaAlternative: false,
|
|
32
|
+
switchLikeNodes: new Set(['case']),
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// ─── Halstead ─────────────────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
export const halstead = {
|
|
38
|
+
operatorLeafTypes: new Set([
|
|
39
|
+
'+',
|
|
40
|
+
'-',
|
|
41
|
+
'*',
|
|
42
|
+
'/',
|
|
43
|
+
'%',
|
|
44
|
+
'**',
|
|
45
|
+
'=',
|
|
46
|
+
'+=',
|
|
47
|
+
'-=',
|
|
48
|
+
'*=',
|
|
49
|
+
'/=',
|
|
50
|
+
'%=',
|
|
51
|
+
'**=',
|
|
52
|
+
'&=',
|
|
53
|
+
'|=',
|
|
54
|
+
'^=',
|
|
55
|
+
'<<=',
|
|
56
|
+
'>>=',
|
|
57
|
+
'==',
|
|
58
|
+
'!=',
|
|
59
|
+
'<',
|
|
60
|
+
'>',
|
|
61
|
+
'<=',
|
|
62
|
+
'>=',
|
|
63
|
+
'<=>',
|
|
64
|
+
'===',
|
|
65
|
+
'=~',
|
|
66
|
+
'!~',
|
|
67
|
+
'&&',
|
|
68
|
+
'||',
|
|
69
|
+
'!',
|
|
70
|
+
'and',
|
|
71
|
+
'or',
|
|
72
|
+
'not',
|
|
73
|
+
'&',
|
|
74
|
+
'|',
|
|
75
|
+
'^',
|
|
76
|
+
'~',
|
|
77
|
+
'<<',
|
|
78
|
+
'>>',
|
|
79
|
+
'if',
|
|
80
|
+
'else',
|
|
81
|
+
'elsif',
|
|
82
|
+
'unless',
|
|
83
|
+
'case',
|
|
84
|
+
'when',
|
|
85
|
+
'for',
|
|
86
|
+
'while',
|
|
87
|
+
'until',
|
|
88
|
+
'do',
|
|
89
|
+
'begin',
|
|
90
|
+
'end',
|
|
91
|
+
'return',
|
|
92
|
+
'raise',
|
|
93
|
+
'break',
|
|
94
|
+
'next',
|
|
95
|
+
'redo',
|
|
96
|
+
'retry',
|
|
97
|
+
'rescue',
|
|
98
|
+
'ensure',
|
|
99
|
+
'yield',
|
|
100
|
+
'def',
|
|
101
|
+
'class',
|
|
102
|
+
'module',
|
|
103
|
+
'.',
|
|
104
|
+
',',
|
|
105
|
+
':',
|
|
106
|
+
'::',
|
|
107
|
+
'=>',
|
|
108
|
+
'->',
|
|
109
|
+
]),
|
|
110
|
+
operandLeafTypes: new Set([
|
|
111
|
+
'identifier',
|
|
112
|
+
'constant',
|
|
113
|
+
'instance_variable',
|
|
114
|
+
'class_variable',
|
|
115
|
+
'global_variable',
|
|
116
|
+
'integer',
|
|
117
|
+
'float',
|
|
118
|
+
'string_content',
|
|
119
|
+
'symbol',
|
|
120
|
+
'true',
|
|
121
|
+
'false',
|
|
122
|
+
'nil',
|
|
123
|
+
'self',
|
|
124
|
+
]),
|
|
125
|
+
compoundOperators: new Set(['call', 'element_reference']),
|
|
126
|
+
skipTypes: new Set([]),
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// ─── CFG ──────────────────────────────────────────────────────────────────
|
|
130
|
+
|
|
131
|
+
export const cfg = makeCfgRules({
|
|
132
|
+
ifNode: 'if',
|
|
133
|
+
elifNode: 'elsif',
|
|
134
|
+
elseClause: 'else',
|
|
135
|
+
forNodes: new Set(['for']),
|
|
136
|
+
whileNode: 'while',
|
|
137
|
+
unlessNode: 'unless',
|
|
138
|
+
untilNode: 'until',
|
|
139
|
+
switchNode: 'case',
|
|
140
|
+
caseNode: 'when',
|
|
141
|
+
defaultNode: 'else',
|
|
142
|
+
tryNode: 'begin',
|
|
143
|
+
catchNode: 'rescue',
|
|
144
|
+
finallyNode: 'ensure',
|
|
145
|
+
returnNode: 'return',
|
|
146
|
+
breakNode: 'break',
|
|
147
|
+
continueNode: 'next',
|
|
148
|
+
blockNodes: new Set(['then', 'do', 'body_statement']),
|
|
149
|
+
functionNodes: new Set(['method', 'singleton_method']),
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// ─── Dataflow ─────────────────────────────────────────────────────────────
|
|
153
|
+
|
|
154
|
+
export const dataflow = makeDataflowRules({
|
|
155
|
+
functionNodes: new Set(['method', 'singleton_method', 'lambda']),
|
|
156
|
+
paramListField: 'parameters',
|
|
157
|
+
returnNode: 'return',
|
|
158
|
+
varDeclaratorNode: null,
|
|
159
|
+
assignmentNode: 'assignment',
|
|
160
|
+
assignLeftField: 'left',
|
|
161
|
+
assignRightField: 'right',
|
|
162
|
+
callNode: 'call',
|
|
163
|
+
callFunctionField: 'method',
|
|
164
|
+
callArgsField: 'arguments',
|
|
165
|
+
spreadType: 'splat_parameter',
|
|
166
|
+
memberNode: 'call',
|
|
167
|
+
memberObjectField: 'receiver',
|
|
168
|
+
memberPropertyField: 'method',
|
|
169
|
+
mutatingMethods: new Set([
|
|
170
|
+
'push',
|
|
171
|
+
'pop',
|
|
172
|
+
'shift',
|
|
173
|
+
'unshift',
|
|
174
|
+
'delete',
|
|
175
|
+
'clear',
|
|
176
|
+
'sort!',
|
|
177
|
+
'reverse!',
|
|
178
|
+
'map!',
|
|
179
|
+
'select!',
|
|
180
|
+
'reject!',
|
|
181
|
+
'compact!',
|
|
182
|
+
'flatten!',
|
|
183
|
+
'concat',
|
|
184
|
+
'replace',
|
|
185
|
+
'insert',
|
|
186
|
+
]),
|
|
187
|
+
extractParamName(node) {
|
|
188
|
+
if (node.type === 'identifier') return [node.text];
|
|
189
|
+
if (
|
|
190
|
+
node.type === 'optional_parameter' ||
|
|
191
|
+
node.type === 'keyword_parameter' ||
|
|
192
|
+
node.type === 'splat_parameter' ||
|
|
193
|
+
node.type === 'hash_splat_parameter'
|
|
194
|
+
) {
|
|
195
|
+
const nameNode = node.childForFieldName('name');
|
|
196
|
+
return nameNode ? [nameNode.text] : null;
|
|
197
|
+
}
|
|
198
|
+
return null;
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// ─── AST Node Types ───────────────────────────────────────────────────────
|
|
203
|
+
|
|
204
|
+
export const astTypes = null;
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rust — AST analysis rules.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { makeCfgRules, makeDataflowRules } from '../shared.js';
|
|
6
|
+
|
|
7
|
+
// ─── Complexity ───────────────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
export const complexity = {
|
|
10
|
+
branchNodes: new Set([
|
|
11
|
+
'if_expression',
|
|
12
|
+
'else_clause',
|
|
13
|
+
'for_expression',
|
|
14
|
+
'while_expression',
|
|
15
|
+
'loop_expression',
|
|
16
|
+
'if_let_expression',
|
|
17
|
+
'while_let_expression',
|
|
18
|
+
'match_expression',
|
|
19
|
+
]),
|
|
20
|
+
caseNodes: new Set(['match_arm']),
|
|
21
|
+
logicalOperators: new Set(['&&', '||']),
|
|
22
|
+
logicalNodeType: 'binary_expression',
|
|
23
|
+
optionalChainType: null,
|
|
24
|
+
nestingNodes: new Set([
|
|
25
|
+
'if_expression',
|
|
26
|
+
'for_expression',
|
|
27
|
+
'while_expression',
|
|
28
|
+
'loop_expression',
|
|
29
|
+
'if_let_expression',
|
|
30
|
+
'while_let_expression',
|
|
31
|
+
'match_expression',
|
|
32
|
+
]),
|
|
33
|
+
functionNodes: new Set(['function_item', 'closure_expression']),
|
|
34
|
+
ifNodeType: 'if_expression',
|
|
35
|
+
elseNodeType: 'else_clause',
|
|
36
|
+
elifNodeType: null,
|
|
37
|
+
elseViaAlternative: false,
|
|
38
|
+
switchLikeNodes: new Set(['match_expression']),
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// ─── Halstead ─────────────────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
export const halstead = {
|
|
44
|
+
operatorLeafTypes: new Set([
|
|
45
|
+
'+',
|
|
46
|
+
'-',
|
|
47
|
+
'*',
|
|
48
|
+
'/',
|
|
49
|
+
'%',
|
|
50
|
+
'=',
|
|
51
|
+
'+=',
|
|
52
|
+
'-=',
|
|
53
|
+
'*=',
|
|
54
|
+
'/=',
|
|
55
|
+
'%=',
|
|
56
|
+
'&=',
|
|
57
|
+
'|=',
|
|
58
|
+
'^=',
|
|
59
|
+
'<<=',
|
|
60
|
+
'>>=',
|
|
61
|
+
'==',
|
|
62
|
+
'!=',
|
|
63
|
+
'<',
|
|
64
|
+
'>',
|
|
65
|
+
'<=',
|
|
66
|
+
'>=',
|
|
67
|
+
'&&',
|
|
68
|
+
'||',
|
|
69
|
+
'!',
|
|
70
|
+
'&',
|
|
71
|
+
'|',
|
|
72
|
+
'^',
|
|
73
|
+
'<<',
|
|
74
|
+
'>>',
|
|
75
|
+
'if',
|
|
76
|
+
'else',
|
|
77
|
+
'for',
|
|
78
|
+
'while',
|
|
79
|
+
'loop',
|
|
80
|
+
'match',
|
|
81
|
+
'return',
|
|
82
|
+
'break',
|
|
83
|
+
'continue',
|
|
84
|
+
'let',
|
|
85
|
+
'mut',
|
|
86
|
+
'ref',
|
|
87
|
+
'as',
|
|
88
|
+
'in',
|
|
89
|
+
'move',
|
|
90
|
+
'fn',
|
|
91
|
+
'struct',
|
|
92
|
+
'enum',
|
|
93
|
+
'trait',
|
|
94
|
+
'impl',
|
|
95
|
+
'pub',
|
|
96
|
+
'mod',
|
|
97
|
+
'use',
|
|
98
|
+
'.',
|
|
99
|
+
',',
|
|
100
|
+
';',
|
|
101
|
+
':',
|
|
102
|
+
'::',
|
|
103
|
+
'=>',
|
|
104
|
+
'->',
|
|
105
|
+
'?',
|
|
106
|
+
]),
|
|
107
|
+
operandLeafTypes: new Set([
|
|
108
|
+
'identifier',
|
|
109
|
+
'field_identifier',
|
|
110
|
+
'type_identifier',
|
|
111
|
+
'integer_literal',
|
|
112
|
+
'float_literal',
|
|
113
|
+
'string_content',
|
|
114
|
+
'char_literal',
|
|
115
|
+
'true',
|
|
116
|
+
'false',
|
|
117
|
+
'self',
|
|
118
|
+
'Self',
|
|
119
|
+
]),
|
|
120
|
+
compoundOperators: new Set(['call_expression', 'index_expression', 'field_expression']),
|
|
121
|
+
skipTypes: new Set([]),
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// ─── CFG ──────────────────────────────────────────────────────────────────
|
|
125
|
+
|
|
126
|
+
export const cfg = makeCfgRules({
|
|
127
|
+
ifNode: 'if_expression',
|
|
128
|
+
ifNodes: new Set(['if_let_expression']),
|
|
129
|
+
elseClause: 'else_clause',
|
|
130
|
+
forNodes: new Set(['for_expression']),
|
|
131
|
+
whileNode: 'while_expression',
|
|
132
|
+
whileNodes: new Set(['while_let_expression']),
|
|
133
|
+
infiniteLoopNode: 'loop_expression',
|
|
134
|
+
switchNode: 'match_expression',
|
|
135
|
+
caseNode: 'match_arm',
|
|
136
|
+
returnNode: 'return_expression',
|
|
137
|
+
breakNode: 'break_expression',
|
|
138
|
+
continueNode: 'continue_expression',
|
|
139
|
+
blockNode: 'block',
|
|
140
|
+
functionNodes: new Set(['function_item', 'closure_expression']),
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// ─── Dataflow ─────────────────────────────────────────────────────────────
|
|
144
|
+
|
|
145
|
+
export const dataflow = makeDataflowRules({
|
|
146
|
+
functionNodes: new Set(['function_item', 'closure_expression']),
|
|
147
|
+
returnNode: 'return_expression',
|
|
148
|
+
varDeclaratorNode: 'let_declaration',
|
|
149
|
+
varNameField: 'pattern',
|
|
150
|
+
varValueField: 'value',
|
|
151
|
+
assignmentNode: 'assignment_expression',
|
|
152
|
+
callNode: 'call_expression',
|
|
153
|
+
callFunctionField: 'function',
|
|
154
|
+
callArgsField: 'arguments',
|
|
155
|
+
memberNode: 'field_expression',
|
|
156
|
+
memberObjectField: 'value',
|
|
157
|
+
memberPropertyField: 'field',
|
|
158
|
+
awaitNode: 'await_expression',
|
|
159
|
+
mutatingMethods: new Set(['push', 'pop', 'insert', 'remove', 'clear', 'sort', 'reverse']),
|
|
160
|
+
extractParamName(node) {
|
|
161
|
+
if (node.type === 'parameter') {
|
|
162
|
+
const pat = node.childForFieldName('pattern');
|
|
163
|
+
if (pat?.type === 'identifier') return [pat.text];
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
if (node.type === 'identifier') return [node.text];
|
|
167
|
+
return null;
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// ─── AST Node Types ───────────────────────────────────────────────────────
|
|
172
|
+
|
|
173
|
+
export const astTypes = null;
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utilities for AST analysis modules (complexity, CFG, dataflow, AST nodes).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { LANGUAGE_REGISTRY } from '../parser.js';
|
|
6
|
+
|
|
7
|
+
// ─── Generic Rule Factory ─────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Merge defaults with overrides, validating that all keys are known.
|
|
11
|
+
*
|
|
12
|
+
* @param {object} defaults - Default rule values (defines the valid key set)
|
|
13
|
+
* @param {object} overrides - Language-specific overrides
|
|
14
|
+
* @param {string} label - Label for error messages (e.g. "CFG", "Dataflow")
|
|
15
|
+
* @returns {object} Merged rules
|
|
16
|
+
*/
|
|
17
|
+
export function makeRules(defaults, overrides, label) {
|
|
18
|
+
const validKeys = new Set(Object.keys(defaults));
|
|
19
|
+
for (const key of Object.keys(overrides)) {
|
|
20
|
+
if (!validKeys.has(key)) {
|
|
21
|
+
throw new Error(`${label} rules: unknown key "${key}"`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return { ...defaults, ...overrides };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ─── CFG Defaults + Factory ───────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
export const CFG_DEFAULTS = {
|
|
30
|
+
ifNode: null,
|
|
31
|
+
ifNodes: null,
|
|
32
|
+
elifNode: null,
|
|
33
|
+
elseClause: null,
|
|
34
|
+
elseViaAlternative: false,
|
|
35
|
+
ifConsequentField: null,
|
|
36
|
+
forNodes: new Set(),
|
|
37
|
+
whileNode: null,
|
|
38
|
+
whileNodes: null,
|
|
39
|
+
doNode: null,
|
|
40
|
+
infiniteLoopNode: null,
|
|
41
|
+
unlessNode: null,
|
|
42
|
+
untilNode: null,
|
|
43
|
+
switchNode: null,
|
|
44
|
+
switchNodes: null,
|
|
45
|
+
caseNode: null,
|
|
46
|
+
caseNodes: null,
|
|
47
|
+
defaultNode: null,
|
|
48
|
+
tryNode: null,
|
|
49
|
+
catchNode: null,
|
|
50
|
+
finallyNode: null,
|
|
51
|
+
returnNode: null,
|
|
52
|
+
throwNode: null,
|
|
53
|
+
breakNode: null,
|
|
54
|
+
continueNode: null,
|
|
55
|
+
blockNode: null,
|
|
56
|
+
blockNodes: null,
|
|
57
|
+
labeledNode: null,
|
|
58
|
+
functionNodes: new Set(),
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export function makeCfgRules(overrides) {
|
|
62
|
+
const rules = makeRules(CFG_DEFAULTS, overrides, 'CFG');
|
|
63
|
+
if (!(rules.functionNodes instanceof Set) || rules.functionNodes.size === 0) {
|
|
64
|
+
throw new Error('CFG rules: functionNodes must be a non-empty Set');
|
|
65
|
+
}
|
|
66
|
+
if (!(rules.forNodes instanceof Set)) {
|
|
67
|
+
throw new Error('CFG rules: forNodes must be a Set');
|
|
68
|
+
}
|
|
69
|
+
return rules;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ─── Dataflow Defaults + Factory ──────────────────────────────────────────
|
|
73
|
+
|
|
74
|
+
export const DATAFLOW_DEFAULTS = {
|
|
75
|
+
// Scope entry
|
|
76
|
+
functionNodes: new Set(), // REQUIRED: non-empty
|
|
77
|
+
|
|
78
|
+
// Function name extraction
|
|
79
|
+
nameField: 'name',
|
|
80
|
+
varAssignedFnParent: null, // parent type for `const fn = ...` (JS only)
|
|
81
|
+
assignmentFnParent: null, // parent type for `x = function...` (JS only)
|
|
82
|
+
pairFnParent: null, // parent type for `{ key: function }` (JS only)
|
|
83
|
+
|
|
84
|
+
// Parameters
|
|
85
|
+
paramListField: 'parameters',
|
|
86
|
+
paramIdentifier: 'identifier',
|
|
87
|
+
paramWrapperTypes: new Set(),
|
|
88
|
+
defaultParamType: null,
|
|
89
|
+
restParamType: null,
|
|
90
|
+
objectDestructType: null,
|
|
91
|
+
arrayDestructType: null,
|
|
92
|
+
shorthandPropPattern: null,
|
|
93
|
+
pairPatternType: null,
|
|
94
|
+
extractParamName: null, // override: (node) => string[]
|
|
95
|
+
|
|
96
|
+
// Return
|
|
97
|
+
returnNode: null,
|
|
98
|
+
|
|
99
|
+
// Variable declarations
|
|
100
|
+
varDeclaratorNode: null,
|
|
101
|
+
varDeclaratorNodes: null,
|
|
102
|
+
varNameField: 'name',
|
|
103
|
+
varValueField: 'value',
|
|
104
|
+
assignmentNode: null,
|
|
105
|
+
assignLeftField: 'left',
|
|
106
|
+
assignRightField: 'right',
|
|
107
|
+
|
|
108
|
+
// Calls
|
|
109
|
+
callNode: null,
|
|
110
|
+
callNodes: null,
|
|
111
|
+
callFunctionField: 'function',
|
|
112
|
+
callArgsField: 'arguments',
|
|
113
|
+
spreadType: null,
|
|
114
|
+
|
|
115
|
+
// Member access
|
|
116
|
+
memberNode: null,
|
|
117
|
+
memberObjectField: 'object',
|
|
118
|
+
memberPropertyField: 'property',
|
|
119
|
+
optionalChainNode: null,
|
|
120
|
+
|
|
121
|
+
// Await
|
|
122
|
+
awaitNode: null,
|
|
123
|
+
|
|
124
|
+
// Mutation
|
|
125
|
+
mutatingMethods: new Set(),
|
|
126
|
+
expressionStmtNode: 'expression_statement',
|
|
127
|
+
callObjectField: null, // Java: combined call+member has [object] field on call node
|
|
128
|
+
|
|
129
|
+
// Structural wrappers
|
|
130
|
+
expressionListType: null, // Go: expression_list wraps LHS/RHS of short_var_declaration
|
|
131
|
+
equalsClauseType: null, // C#: equals_value_clause wraps variable initializer
|
|
132
|
+
argumentWrapperType: null, // PHP: individual args wrapped in 'argument' nodes
|
|
133
|
+
extraIdentifierTypes: null, // Set of additional identifier-like types (PHP: variable_name, name)
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
export function makeDataflowRules(overrides) {
|
|
137
|
+
const rules = makeRules(DATAFLOW_DEFAULTS, overrides, 'Dataflow');
|
|
138
|
+
if (!(rules.functionNodes instanceof Set) || rules.functionNodes.size === 0) {
|
|
139
|
+
throw new Error('Dataflow rules: functionNodes must be a non-empty Set');
|
|
140
|
+
}
|
|
141
|
+
return rules;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ─── AST Helpers ──────────────────────────────────────────────────────────
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Find the function body node in a parse tree that matches a given line range.
|
|
148
|
+
*/
|
|
149
|
+
export function findFunctionNode(rootNode, startLine, _endLine, rules) {
|
|
150
|
+
// tree-sitter lines are 0-indexed
|
|
151
|
+
const targetStart = startLine - 1;
|
|
152
|
+
|
|
153
|
+
let best = null;
|
|
154
|
+
|
|
155
|
+
function search(node) {
|
|
156
|
+
const nodeStart = node.startPosition.row;
|
|
157
|
+
const nodeEnd = node.endPosition.row;
|
|
158
|
+
|
|
159
|
+
// Prune branches outside range
|
|
160
|
+
if (nodeEnd < targetStart || nodeStart > targetStart + 1) return;
|
|
161
|
+
|
|
162
|
+
if (rules.functionNodes.has(node.type) && nodeStart === targetStart) {
|
|
163
|
+
// Found a function node at the right position — pick it
|
|
164
|
+
if (!best || nodeEnd - nodeStart < best.endPosition.row - best.startPosition.row) {
|
|
165
|
+
best = node;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
170
|
+
search(node.child(i));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
search(rootNode);
|
|
175
|
+
return best;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Truncate a string to a maximum length, appending an ellipsis if truncated.
|
|
180
|
+
*
|
|
181
|
+
* @param {string} str - Input string
|
|
182
|
+
* @param {number} [max=200] - Maximum length
|
|
183
|
+
* @returns {string}
|
|
184
|
+
*/
|
|
185
|
+
export function truncate(str, max = 200) {
|
|
186
|
+
if (!str) return '';
|
|
187
|
+
return str.length > max ? `${str.slice(0, max)}…` : str;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ─── Extension / Language Mapping ─────────────────────────────────────────
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Build a Map from file extension → language ID using the parser registry.
|
|
194
|
+
*
|
|
195
|
+
* @param {Iterable} [registry=LANGUAGE_REGISTRY] - Language registry entries
|
|
196
|
+
* @returns {Map<string, string>}
|
|
197
|
+
*/
|
|
198
|
+
export function buildExtToLangMap(registry = LANGUAGE_REGISTRY) {
|
|
199
|
+
const map = new Map();
|
|
200
|
+
for (const entry of registry) {
|
|
201
|
+
for (const ext of entry.extensions) {
|
|
202
|
+
map.set(ext, entry.id);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return map;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Build a Set of file extensions for languages that have entries in the given rules Map.
|
|
210
|
+
*
|
|
211
|
+
* @param {Map<string, any>} rulesMap - e.g. COMPLEXITY_RULES, CFG_RULES
|
|
212
|
+
* @param {Iterable} [registry=LANGUAGE_REGISTRY] - Language registry entries
|
|
213
|
+
* @returns {Set<string>}
|
|
214
|
+
*/
|
|
215
|
+
export function buildExtensionSet(rulesMap, registry = LANGUAGE_REGISTRY) {
|
|
216
|
+
const extensions = new Set();
|
|
217
|
+
for (const entry of registry) {
|
|
218
|
+
if (rulesMap.has(entry.id)) {
|
|
219
|
+
for (const ext of entry.extensions) extensions.add(ext);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return extensions;
|
|
223
|
+
}
|
package/src/ast.js
CHANGED
|
@@ -7,10 +7,13 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import path from 'node:path';
|
|
10
|
+
import { AST_TYPE_MAPS } from './ast-analysis/rules/index.js';
|
|
11
|
+
import { buildExtensionSet } from './ast-analysis/shared.js';
|
|
10
12
|
import { openReadonlyOrFail } from './db.js';
|
|
11
13
|
import { debug } from './logger.js';
|
|
12
|
-
import { paginateResult
|
|
13
|
-
|
|
14
|
+
import { paginateResult } from './paginate.js';
|
|
15
|
+
|
|
16
|
+
import { outputResult } from './result-formatter.js';
|
|
14
17
|
|
|
15
18
|
// ─── Constants ────────────────────────────────────────────────────────
|
|
16
19
|
|
|
@@ -28,23 +31,11 @@ const KIND_ICONS = {
|
|
|
28
31
|
/** Max length for the `text` column. */
|
|
29
32
|
const TEXT_MAX = 200;
|
|
30
33
|
|
|
31
|
-
/** tree-sitter node types that map to our AST node kinds
|
|
32
|
-
const JS_TS_AST_TYPES =
|
|
33
|
-
new_expression: 'new',
|
|
34
|
-
throw_statement: 'throw',
|
|
35
|
-
await_expression: 'await',
|
|
36
|
-
string: 'string',
|
|
37
|
-
template_string: 'string',
|
|
38
|
-
regex: 'regex',
|
|
39
|
-
};
|
|
34
|
+
/** tree-sitter node types that map to our AST node kinds — imported from rules. */
|
|
35
|
+
const JS_TS_AST_TYPES = AST_TYPE_MAPS.get('javascript');
|
|
40
36
|
|
|
41
37
|
/** Extensions that support full AST walk (new/throw/await/string/regex). */
|
|
42
|
-
const WALK_EXTENSIONS =
|
|
43
|
-
for (const lang of Object.values(LANGUAGE_REGISTRY)) {
|
|
44
|
-
if (['javascript', 'typescript', 'tsx'].includes(lang.id)) {
|
|
45
|
-
for (const ext of lang.extensions) WALK_EXTENSIONS.add(ext);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
38
|
+
const WALK_EXTENSIONS = buildExtensionSet(AST_TYPE_MAPS);
|
|
48
39
|
|
|
49
40
|
// ─── Helpers ──────────────────────────────────────────────────────────
|
|
50
41
|
|
|
@@ -351,8 +342,12 @@ export function astQueryData(pattern, customDbPath, opts = {}) {
|
|
|
351
342
|
ORDER BY a.file, a.line
|
|
352
343
|
`;
|
|
353
344
|
|
|
354
|
-
|
|
355
|
-
|
|
345
|
+
let rows;
|
|
346
|
+
try {
|
|
347
|
+
rows = db.prepare(sql).all(...params);
|
|
348
|
+
} finally {
|
|
349
|
+
db.close();
|
|
350
|
+
}
|
|
356
351
|
|
|
357
352
|
const results = rows.map((r) => ({
|
|
358
353
|
kind: r.kind,
|
|
@@ -382,15 +377,7 @@ export function astQueryData(pattern, customDbPath, opts = {}) {
|
|
|
382
377
|
export function astQuery(pattern, customDbPath, opts = {}) {
|
|
383
378
|
const data = astQueryData(pattern, customDbPath, opts);
|
|
384
379
|
|
|
385
|
-
if (opts
|
|
386
|
-
printNdjson(data, 'results');
|
|
387
|
-
return;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
if (opts.json) {
|
|
391
|
-
console.log(JSON.stringify(data, null, 2));
|
|
392
|
-
return;
|
|
393
|
-
}
|
|
380
|
+
if (outputResult(data, 'results', opts)) return;
|
|
394
381
|
|
|
395
382
|
// Human-readable output
|
|
396
383
|
if (data.results.length === 0) {
|
package/src/audit.js
CHANGED
|
@@ -10,7 +10,9 @@ import path from 'node:path';
|
|
|
10
10
|
import { loadConfig } from './config.js';
|
|
11
11
|
import { openReadonlyOrFail } from './db.js';
|
|
12
12
|
import { RULE_DEFS } from './manifesto.js';
|
|
13
|
-
import { explainData,
|
|
13
|
+
import { explainData, kindIcon } from './queries.js';
|
|
14
|
+
import { outputResult } from './result-formatter.js';
|
|
15
|
+
import { isTestFile } from './test-filter.js';
|
|
14
16
|
|
|
15
17
|
// ─── Threshold resolution ───────────────────────────────────────────
|
|
16
18
|
|
|
@@ -340,10 +342,7 @@ function defaultHealth() {
|
|
|
340
342
|
export function audit(target, customDbPath, opts = {}) {
|
|
341
343
|
const data = auditData(target, customDbPath, opts);
|
|
342
344
|
|
|
343
|
-
if (opts
|
|
344
|
-
console.log(JSON.stringify(data, null, 2));
|
|
345
|
-
return;
|
|
346
|
-
}
|
|
345
|
+
if (outputResult(data, null, opts)) return;
|
|
347
346
|
|
|
348
347
|
if (data.functions.length === 0) {
|
|
349
348
|
console.log(`No ${data.kind === 'file' ? 'file' : 'function/symbol'} matching "${target}"`);
|
package/src/boundaries.js
CHANGED