@optave/codegraph 3.11.0 → 3.11.2
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 +38 -31
- package/dist/ast-analysis/engine.d.ts.map +1 -1
- package/dist/ast-analysis/engine.js +91 -60
- package/dist/ast-analysis/engine.js.map +1 -1
- package/dist/ast-analysis/visitor-utils.d.ts +3 -0
- package/dist/ast-analysis/visitor-utils.d.ts.map +1 -1
- package/dist/ast-analysis/visitor-utils.js +83 -49
- package/dist/ast-analysis/visitor-utils.js.map +1 -1
- package/dist/ast-analysis/visitors/ast-store-visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitors/ast-store-visitor.js +78 -62
- package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
- package/dist/ast-analysis/visitors/dataflow-visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitors/dataflow-visitor.js +61 -42
- package/dist/ast-analysis/visitors/dataflow-visitor.js.map +1 -1
- package/dist/cli/commands/embed.d.ts.map +1 -1
- package/dist/cli/commands/embed.js +49 -4
- package/dist/cli/commands/embed.js.map +1 -1
- package/dist/domain/analysis/dependencies.d.ts.map +1 -1
- package/dist/domain/analysis/dependencies.js +106 -80
- package/dist/domain/analysis/dependencies.js.map +1 -1
- package/dist/domain/analysis/fn-impact.d.ts.map +1 -1
- package/dist/domain/analysis/fn-impact.js +77 -52
- package/dist/domain/analysis/fn-impact.js.map +1 -1
- package/dist/domain/analysis/module-map.d.ts.map +1 -1
- package/dist/domain/analysis/module-map.js +132 -121
- package/dist/domain/analysis/module-map.js.map +1 -1
- package/dist/domain/graph/builder/call-resolver.d.ts +71 -0
- package/dist/domain/graph/builder/call-resolver.d.ts.map +1 -0
- package/dist/domain/graph/builder/call-resolver.js +130 -0
- package/dist/domain/graph/builder/call-resolver.js.map +1 -0
- package/dist/domain/graph/builder/helpers.d.ts +4 -4
- package/dist/domain/graph/builder/helpers.d.ts.map +1 -1
- package/dist/domain/graph/builder/helpers.js +47 -33
- package/dist/domain/graph/builder/helpers.js.map +1 -1
- package/dist/domain/graph/builder/incremental.d.ts +6 -0
- package/dist/domain/graph/builder/incremental.d.ts.map +1 -1
- package/dist/domain/graph/builder/incremental.js +214 -127
- package/dist/domain/graph/builder/incremental.js.map +1 -1
- package/dist/domain/graph/builder/pipeline.d.ts +1 -44
- package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
- package/dist/domain/graph/builder/pipeline.js +10 -766
- package/dist/domain/graph/builder/pipeline.js.map +1 -1
- package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/build-edges.js +151 -192
- package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
- package/dist/domain/graph/builder/stages/build-structure.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/build-structure.js +82 -65
- package/dist/domain/graph/builder/stages/build-structure.js.map +1 -1
- package/dist/domain/graph/builder/stages/detect-changes.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/detect-changes.js +84 -56
- package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
- package/dist/domain/graph/builder/stages/finalize.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/finalize.js +60 -51
- package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
- package/dist/domain/graph/builder/stages/insert-nodes.d.ts +8 -6
- package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/insert-nodes.js +107 -122
- package/dist/domain/graph/builder/stages/insert-nodes.js.map +1 -1
- package/dist/domain/graph/builder/stages/native-db-lifecycle.d.ts +14 -0
- package/dist/domain/graph/builder/stages/native-db-lifecycle.d.ts.map +1 -0
- package/dist/domain/graph/builder/stages/native-db-lifecycle.js +77 -0
- package/dist/domain/graph/builder/stages/native-db-lifecycle.js.map +1 -0
- package/dist/domain/graph/builder/stages/native-orchestrator.d.ts +62 -0
- package/dist/domain/graph/builder/stages/native-orchestrator.d.ts.map +1 -0
- package/dist/domain/graph/builder/stages/native-orchestrator.js +747 -0
- package/dist/domain/graph/builder/stages/native-orchestrator.js.map +1 -0
- package/dist/domain/graph/cycles.d.ts +6 -4
- package/dist/domain/graph/cycles.d.ts.map +1 -1
- package/dist/domain/graph/cycles.js +50 -55
- package/dist/domain/graph/cycles.js.map +1 -1
- package/dist/domain/graph/journal.d.ts.map +1 -1
- package/dist/domain/graph/journal.js +89 -70
- package/dist/domain/graph/journal.js.map +1 -1
- package/dist/domain/graph/watcher.d.ts.map +1 -1
- package/dist/domain/graph/watcher.js +10 -4
- package/dist/domain/graph/watcher.js.map +1 -1
- package/dist/domain/parser.d.ts +12 -23
- package/dist/domain/parser.d.ts.map +1 -1
- package/dist/domain/parser.js +126 -79
- package/dist/domain/parser.js.map +1 -1
- package/dist/domain/search/generator.d.ts +3 -1
- package/dist/domain/search/generator.d.ts.map +1 -1
- package/dist/domain/search/generator.js +68 -45
- package/dist/domain/search/generator.js.map +1 -1
- package/dist/domain/search/models.d.ts +2 -0
- package/dist/domain/search/models.d.ts.map +1 -1
- package/dist/domain/search/models.js +37 -3
- package/dist/domain/search/models.js.map +1 -1
- package/dist/domain/search/search/hybrid.d.ts.map +1 -1
- package/dist/domain/search/search/hybrid.js +49 -40
- package/dist/domain/search/search/hybrid.js.map +1 -1
- package/dist/domain/search/search/semantic.d.ts.map +1 -1
- package/dist/domain/search/search/semantic.js +69 -49
- package/dist/domain/search/search/semantic.js.map +1 -1
- package/dist/domain/wasm-worker-entry.js +201 -136
- package/dist/domain/wasm-worker-entry.js.map +1 -1
- package/dist/extractors/elixir.js +95 -71
- package/dist/extractors/elixir.js.map +1 -1
- package/dist/extractors/gleam.d.ts.map +1 -1
- package/dist/extractors/gleam.js +23 -31
- package/dist/extractors/gleam.js.map +1 -1
- package/dist/extractors/helpers.d.ts +79 -1
- package/dist/extractors/helpers.d.ts.map +1 -1
- package/dist/extractors/helpers.js +137 -0
- package/dist/extractors/helpers.js.map +1 -1
- package/dist/extractors/java.d.ts.map +1 -1
- package/dist/extractors/java.js +37 -49
- package/dist/extractors/java.js.map +1 -1
- package/dist/extractors/javascript.d.ts.map +1 -1
- package/dist/extractors/javascript.js +44 -44
- package/dist/extractors/javascript.js.map +1 -1
- package/dist/extractors/julia.js +27 -34
- package/dist/extractors/julia.js.map +1 -1
- package/dist/extractors/r.d.ts.map +1 -1
- package/dist/extractors/r.js +33 -58
- package/dist/extractors/r.js.map +1 -1
- package/dist/extractors/solidity.d.ts.map +1 -1
- package/dist/extractors/solidity.js +38 -61
- package/dist/extractors/solidity.js.map +1 -1
- package/dist/features/boundaries.d.ts.map +1 -1
- package/dist/features/boundaries.js +49 -39
- package/dist/features/boundaries.js.map +1 -1
- package/dist/features/cfg.d.ts.map +1 -1
- package/dist/features/cfg.js +90 -63
- package/dist/features/cfg.js.map +1 -1
- package/dist/features/check.d.ts.map +1 -1
- package/dist/features/check.js +43 -34
- package/dist/features/check.js.map +1 -1
- package/dist/features/cochange.d.ts.map +1 -1
- package/dist/features/cochange.js +68 -56
- package/dist/features/cochange.js.map +1 -1
- package/dist/features/complexity.d.ts.map +1 -1
- package/dist/features/complexity.js +105 -75
- package/dist/features/complexity.js.map +1 -1
- package/dist/features/dataflow.d.ts.map +1 -1
- package/dist/features/dataflow.js +37 -29
- package/dist/features/dataflow.js.map +1 -1
- package/dist/features/flow.d.ts.map +1 -1
- package/dist/features/flow.js +31 -22
- package/dist/features/flow.js.map +1 -1
- package/dist/features/graph-enrichment.d.ts.map +1 -1
- package/dist/features/graph-enrichment.js +77 -70
- package/dist/features/graph-enrichment.js.map +1 -1
- package/dist/features/owners.d.ts +17 -26
- package/dist/features/owners.d.ts.map +1 -1
- package/dist/features/owners.js +120 -109
- package/dist/features/owners.js.map +1 -1
- package/dist/features/sequence.d.ts.map +1 -1
- package/dist/features/sequence.js +59 -54
- package/dist/features/sequence.js.map +1 -1
- package/dist/features/structure-query.d.ts.map +1 -1
- package/dist/features/structure-query.js +60 -60
- package/dist/features/structure-query.js.map +1 -1
- package/dist/features/structure.d.ts.map +1 -1
- package/dist/features/structure.js +149 -52
- package/dist/features/structure.js.map +1 -1
- package/dist/graph/algorithms/leiden/optimiser.d.ts.map +1 -1
- package/dist/graph/algorithms/leiden/optimiser.js +100 -69
- package/dist/graph/algorithms/leiden/optimiser.js.map +1 -1
- package/dist/graph/classifiers/roles.d.ts.map +1 -1
- package/dist/graph/classifiers/roles.js +63 -59
- package/dist/graph/classifiers/roles.js.map +1 -1
- package/dist/infrastructure/config.d.ts +1 -1
- package/dist/infrastructure/config.d.ts.map +1 -1
- package/dist/infrastructure/config.js +1 -1
- package/dist/infrastructure/config.js.map +1 -1
- package/dist/presentation/cfg.d.ts.map +1 -1
- package/dist/presentation/cfg.js +44 -29
- package/dist/presentation/cfg.js.map +1 -1
- package/dist/presentation/flow.d.ts.map +1 -1
- package/dist/presentation/flow.js +58 -38
- package/dist/presentation/flow.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/grammars/tree-sitter-erlang.wasm +0 -0
- package/package.json +9 -9
- package/src/ast-analysis/engine.ts +145 -61
- package/src/ast-analysis/visitor-utils.ts +86 -46
- package/src/ast-analysis/visitors/ast-store-visitor.ts +104 -69
- package/src/ast-analysis/visitors/dataflow-visitor.ts +86 -47
- package/src/cli/commands/embed.ts +54 -4
- package/src/domain/analysis/dependencies.ts +166 -85
- package/src/domain/analysis/fn-impact.ts +120 -50
- package/src/domain/analysis/module-map.ts +175 -140
- package/src/domain/graph/builder/call-resolver.ts +181 -0
- package/src/domain/graph/builder/helpers.ts +85 -76
- package/src/domain/graph/builder/incremental.ts +321 -152
- package/src/domain/graph/builder/pipeline.ts +19 -957
- package/src/domain/graph/builder/stages/build-edges.ts +229 -275
- package/src/domain/graph/builder/stages/build-structure.ts +115 -82
- package/src/domain/graph/builder/stages/detect-changes.ts +107 -64
- package/src/domain/graph/builder/stages/finalize.ts +72 -70
- package/src/domain/graph/builder/stages/insert-nodes.ts +154 -120
- package/src/domain/graph/builder/stages/native-db-lifecycle.ts +74 -0
- package/src/domain/graph/builder/stages/native-orchestrator.ts +942 -0
- package/src/domain/graph/cycles.ts +51 -49
- package/src/domain/graph/journal.ts +84 -69
- package/src/domain/graph/watcher.ts +12 -4
- package/src/domain/parser.ts +143 -66
- package/src/domain/search/generator.ts +132 -74
- package/src/domain/search/models.ts +39 -3
- package/src/domain/search/search/hybrid.ts +53 -42
- package/src/domain/search/search/semantic.ts +105 -65
- package/src/domain/wasm-worker-entry.ts +235 -152
- package/src/extractors/elixir.ts +91 -64
- package/src/extractors/gleam.ts +33 -37
- package/src/extractors/helpers.ts +205 -1
- package/src/extractors/java.ts +42 -45
- package/src/extractors/javascript.ts +44 -43
- package/src/extractors/julia.ts +28 -35
- package/src/extractors/r.ts +38 -56
- package/src/extractors/solidity.ts +43 -71
- package/src/features/boundaries.ts +64 -46
- package/src/features/cfg.ts +145 -74
- package/src/features/check.ts +60 -43
- package/src/features/cochange.ts +95 -72
- package/src/features/complexity.ts +134 -79
- package/src/features/dataflow.ts +57 -34
- package/src/features/flow.ts +48 -24
- package/src/features/graph-enrichment.ts +105 -70
- package/src/features/owners.ts +186 -146
- package/src/features/sequence.ts +99 -69
- package/src/features/structure-query.ts +94 -79
- package/src/features/structure.ts +199 -79
- package/src/graph/algorithms/leiden/optimiser.ts +142 -87
- package/src/graph/classifiers/roles.ts +64 -54
- package/src/infrastructure/config.ts +1 -1
- package/src/presentation/cfg.ts +48 -32
- package/src/presentation/flow.ts +100 -52
- package/src/types.ts +1 -1
package/src/extractors/r.ts
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import type { ExtractorOutput, SubDeclaration, TreeSitterNode, TreeSitterTree } from '../types.js';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
findChild,
|
|
4
|
+
findFirstChildOfTypes,
|
|
5
|
+
nodeEndLine,
|
|
6
|
+
nodeStartLine,
|
|
7
|
+
pushCall,
|
|
8
|
+
pushImport,
|
|
9
|
+
stripQuotes,
|
|
10
|
+
} from './helpers.js';
|
|
3
11
|
|
|
4
12
|
/**
|
|
5
13
|
* Extract symbols from R files.
|
|
@@ -58,7 +66,7 @@ function handleBinaryOp(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
58
66
|
ctx.definitions.push({
|
|
59
67
|
name: lhs.text,
|
|
60
68
|
kind: 'function',
|
|
61
|
-
line: node
|
|
69
|
+
line: nodeStartLine(node),
|
|
62
70
|
endLine: nodeEndLine(node),
|
|
63
71
|
children: params.length > 0 ? params : undefined,
|
|
64
72
|
});
|
|
@@ -68,7 +76,7 @@ function handleBinaryOp(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
68
76
|
ctx.definitions.push({
|
|
69
77
|
name: lhs.text,
|
|
70
78
|
kind: 'variable',
|
|
71
|
-
line: node
|
|
79
|
+
line: nodeStartLine(node),
|
|
72
80
|
endLine: nodeEndLine(node),
|
|
73
81
|
});
|
|
74
82
|
}
|
|
@@ -87,14 +95,14 @@ function extractRParams(funcDef: TreeSitterNode): SubDeclaration[] {
|
|
|
87
95
|
// parameter node has name and possibly default value
|
|
88
96
|
const nameNode = child.childForFieldName('name') || findChild(child, 'identifier');
|
|
89
97
|
if (nameNode) {
|
|
90
|
-
params.push({ name: nameNode.text, kind: 'parameter', line: child
|
|
98
|
+
params.push({ name: nameNode.text, kind: 'parameter', line: nodeStartLine(child) });
|
|
91
99
|
} else if (child.text && child.text !== ',' && child.text !== '(' && child.text !== ')') {
|
|
92
100
|
// Some grammars have the param as plain text
|
|
93
|
-
params.push({ name: child.text, kind: 'parameter', line: child
|
|
101
|
+
params.push({ name: child.text, kind: 'parameter', line: nodeStartLine(child) });
|
|
94
102
|
}
|
|
95
103
|
}
|
|
96
104
|
if (child.type === 'identifier') {
|
|
97
|
-
params.push({ name: child.text, kind: 'parameter', line: child
|
|
105
|
+
params.push({ name: child.text, kind: 'parameter', line: nodeStartLine(child) });
|
|
98
106
|
}
|
|
99
107
|
}
|
|
100
108
|
return params;
|
|
@@ -137,15 +145,13 @@ function handleCall(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
137
145
|
|
|
138
146
|
// Regular call
|
|
139
147
|
if (funcNode.type === 'identifier') {
|
|
140
|
-
ctx
|
|
148
|
+
pushCall(ctx, node, funcName);
|
|
141
149
|
} else if (funcNode.type === 'namespace_operator') {
|
|
142
150
|
// pkg::func
|
|
143
151
|
const parts = funcName.split('::');
|
|
144
152
|
if (parts.length >= 2) {
|
|
145
|
-
ctx.
|
|
146
|
-
name: parts[parts.length - 1]!,
|
|
153
|
+
pushCall(ctx, node, parts[parts.length - 1]!, {
|
|
147
154
|
receiver: parts.slice(0, -1).join('::'),
|
|
148
|
-
line: node.startPosition.row + 1,
|
|
149
155
|
});
|
|
150
156
|
}
|
|
151
157
|
}
|
|
@@ -164,20 +170,12 @@ function handleLibraryCall(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
164
170
|
const arg = child.child(j);
|
|
165
171
|
if (!arg) continue;
|
|
166
172
|
if (arg.type === 'identifier') {
|
|
167
|
-
ctx.
|
|
168
|
-
source: arg.text,
|
|
169
|
-
names: [arg.text],
|
|
170
|
-
line: node.startPosition.row + 1,
|
|
171
|
-
});
|
|
173
|
+
pushImport(ctx, node, arg.text, [arg.text]);
|
|
172
174
|
return;
|
|
173
175
|
}
|
|
174
176
|
if (arg.type === 'string' || arg.type === 'string_content') {
|
|
175
|
-
const text = arg.text
|
|
176
|
-
ctx
|
|
177
|
-
source: text,
|
|
178
|
-
names: [text],
|
|
179
|
-
line: node.startPosition.row + 1,
|
|
180
|
-
});
|
|
177
|
+
const text = stripQuotes(arg.text);
|
|
178
|
+
pushImport(ctx, node, text, [text]);
|
|
181
179
|
return;
|
|
182
180
|
}
|
|
183
181
|
// Argument might be wrapped
|
|
@@ -202,12 +200,8 @@ function handleLibraryCall(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
202
200
|
}
|
|
203
201
|
}
|
|
204
202
|
if (pick) {
|
|
205
|
-
const text = pick.text
|
|
206
|
-
ctx
|
|
207
|
-
source: text,
|
|
208
|
-
names: [text],
|
|
209
|
-
line: node.startPosition.row + 1,
|
|
210
|
-
});
|
|
203
|
+
const text = stripQuotes(pick.text);
|
|
204
|
+
pushImport(ctx, node, text, [text]);
|
|
211
205
|
return;
|
|
212
206
|
}
|
|
213
207
|
}
|
|
@@ -220,11 +214,7 @@ function handleSourceCall(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
220
214
|
// source() only accepts string literals — `source(varname)` is not an import.
|
|
221
215
|
const path = firstStringArgument(node);
|
|
222
216
|
if (path === null) return;
|
|
223
|
-
ctx
|
|
224
|
-
source: path,
|
|
225
|
-
names: ['source'],
|
|
226
|
-
line: node.startPosition.row + 1,
|
|
227
|
-
});
|
|
217
|
+
pushImport(ctx, node, path, ['source']);
|
|
228
218
|
}
|
|
229
219
|
|
|
230
220
|
function handleSetClass(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
@@ -233,7 +223,7 @@ function handleSetClass(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
233
223
|
ctx.definitions.push({
|
|
234
224
|
name,
|
|
235
225
|
kind: 'class',
|
|
236
|
-
line: node
|
|
226
|
+
line: nodeStartLine(node),
|
|
237
227
|
endLine: nodeEndLine(node),
|
|
238
228
|
});
|
|
239
229
|
}
|
|
@@ -244,7 +234,7 @@ function handleSetGeneric(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
244
234
|
ctx.definitions.push({
|
|
245
235
|
name,
|
|
246
236
|
kind: 'function',
|
|
247
|
-
line: node
|
|
237
|
+
line: nodeStartLine(node),
|
|
248
238
|
endLine: nodeEndLine(node),
|
|
249
239
|
});
|
|
250
240
|
}
|
|
@@ -258,7 +248,7 @@ function handleSetGeneric(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
258
248
|
function handleSetMethod(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
259
249
|
const name = firstStringArgument(node);
|
|
260
250
|
if (name === null) return;
|
|
261
|
-
ctx
|
|
251
|
+
pushCall(ctx, node, name);
|
|
262
252
|
}
|
|
263
253
|
|
|
264
254
|
// tree-sitter-r wraps each positional argument in an `argument` node that
|
|
@@ -266,28 +256,20 @@ function handleSetMethod(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
266
256
|
// must be unwrapped — checking `child.type === 'string'` directly misses it.
|
|
267
257
|
// Mirrors `first_argument_value` in the Rust extractor for parity.
|
|
268
258
|
function firstStringArgument(node: TreeSitterNode): string | null {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
const inner = arg.child(k);
|
|
283
|
-
if (inner && inner.type === 'string') return stripQuotes(inner.text);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
259
|
+
const args = findFirstChildOfTypes(node, ['arguments']);
|
|
260
|
+
if (!args) return null;
|
|
261
|
+
for (let j = 0; j < args.childCount; j++) {
|
|
262
|
+
const arg = args.child(j);
|
|
263
|
+
if (!arg) continue;
|
|
264
|
+
if (arg.type === 'string') {
|
|
265
|
+
return stripQuotes(arg.text);
|
|
266
|
+
}
|
|
267
|
+
if (arg.type === 'argument') {
|
|
268
|
+
const valueNode = arg.childForFieldName('value');
|
|
269
|
+
if (valueNode && valueNode.type === 'string') return stripQuotes(valueNode.text);
|
|
270
|
+
const innerStr = findFirstChildOfTypes(arg, ['string']);
|
|
271
|
+
if (innerStr) return stripQuotes(innerStr.text);
|
|
286
272
|
}
|
|
287
273
|
}
|
|
288
274
|
return null;
|
|
289
275
|
}
|
|
290
|
-
|
|
291
|
-
function stripQuotes(text: string): string {
|
|
292
|
-
return text.replace(/^["']|["']$/g, '');
|
|
293
|
-
}
|
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
Call,
|
|
3
|
-
ExtractorOutput,
|
|
4
|
-
SubDeclaration,
|
|
5
|
-
TreeSitterNode,
|
|
6
|
-
TreeSitterTree,
|
|
7
|
-
} from '../types.js';
|
|
1
|
+
import type { ExtractorOutput, SubDeclaration, TreeSitterNode, TreeSitterTree } from '../types.js';
|
|
8
2
|
import {
|
|
9
3
|
extractModifierVisibility,
|
|
4
|
+
extractSimpleParameters,
|
|
10
5
|
findChild,
|
|
6
|
+
findFirstChildOfTypes,
|
|
11
7
|
findParentNode,
|
|
12
8
|
nodeEndLine,
|
|
9
|
+
nodeStartLine,
|
|
10
|
+
pushCall,
|
|
11
|
+
pushImport,
|
|
13
12
|
stripQuotes,
|
|
14
13
|
} from './helpers.js';
|
|
15
14
|
|
|
@@ -103,7 +102,7 @@ function handleContractDecl(
|
|
|
103
102
|
ctx.definitions.push({
|
|
104
103
|
name,
|
|
105
104
|
kind,
|
|
106
|
-
line: node
|
|
105
|
+
line: nodeStartLine(node),
|
|
107
106
|
endLine: nodeEndLine(node),
|
|
108
107
|
children: members.length > 0 ? members : undefined,
|
|
109
108
|
});
|
|
@@ -125,7 +124,7 @@ function extractContractMembers(body: TreeSitterNode): SubDeclaration[] {
|
|
|
125
124
|
|
|
126
125
|
/** Map a single contract body child to a SubDeclaration, or null if not a recognized member. */
|
|
127
126
|
function extractContractMember(child: TreeSitterNode): SubDeclaration | null {
|
|
128
|
-
const line = child
|
|
127
|
+
const line = nodeStartLine(child);
|
|
129
128
|
switch (child.type) {
|
|
130
129
|
case 'function_definition': {
|
|
131
130
|
const fnName = child.childForFieldName('name');
|
|
@@ -172,7 +171,7 @@ function extractInheritance(node: TreeSitterNode, name: string, ctx: ExtractorOu
|
|
|
172
171
|
const child = inheritance.child(j);
|
|
173
172
|
if (!child) continue;
|
|
174
173
|
if (child.type === 'user_defined_type' || child.type === 'identifier') {
|
|
175
|
-
ctx.classes.push({ name, extends: child.text, line: node
|
|
174
|
+
ctx.classes.push({ name, extends: child.text, line: nodeStartLine(node) });
|
|
176
175
|
}
|
|
177
176
|
}
|
|
178
177
|
}
|
|
@@ -191,19 +190,16 @@ function handleStructDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
191
190
|
members.push({
|
|
192
191
|
name: memberName.text,
|
|
193
192
|
kind: 'property',
|
|
194
|
-
line: child
|
|
193
|
+
line: nodeStartLine(child),
|
|
195
194
|
});
|
|
196
195
|
}
|
|
197
196
|
}
|
|
198
197
|
}
|
|
199
198
|
|
|
200
|
-
const parent = findParentNode(node, SOL_PARENT_TYPES);
|
|
201
|
-
const fullName = parent ? `${parent}.${nameNode.text}` : nameNode.text;
|
|
202
|
-
|
|
203
199
|
ctx.definitions.push({
|
|
204
|
-
name:
|
|
200
|
+
name: qualifyWithParent(node, nameNode.text),
|
|
205
201
|
kind: 'struct',
|
|
206
|
-
line: node
|
|
202
|
+
line: nodeStartLine(node),
|
|
207
203
|
endLine: nodeEndLine(node),
|
|
208
204
|
children: members.length > 0 ? members : undefined,
|
|
209
205
|
});
|
|
@@ -217,17 +213,14 @@ function handleEnumDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
217
213
|
for (let i = 0; i < node.childCount; i++) {
|
|
218
214
|
const child = node.child(i);
|
|
219
215
|
if (child && child.type === 'enum_value') {
|
|
220
|
-
members.push({ name: child.text, kind: 'constant', line: child
|
|
216
|
+
members.push({ name: child.text, kind: 'constant', line: nodeStartLine(child) });
|
|
221
217
|
}
|
|
222
218
|
}
|
|
223
219
|
|
|
224
|
-
const parent = findParentNode(node, SOL_PARENT_TYPES);
|
|
225
|
-
const fullName = parent ? `${parent}.${nameNode.text}` : nameNode.text;
|
|
226
|
-
|
|
227
220
|
ctx.definitions.push({
|
|
228
|
-
name:
|
|
221
|
+
name: qualifyWithParent(node, nameNode.text),
|
|
229
222
|
kind: 'enum',
|
|
230
|
-
line: node
|
|
223
|
+
line: nodeStartLine(node),
|
|
231
224
|
endLine: nodeEndLine(node),
|
|
232
225
|
children: members.length > 0 ? members : undefined,
|
|
233
226
|
});
|
|
@@ -244,7 +237,7 @@ function handleFunctionDef(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
244
237
|
ctx.definitions.push({
|
|
245
238
|
name: fullName,
|
|
246
239
|
kind,
|
|
247
|
-
line: node
|
|
240
|
+
line: nodeStartLine(node),
|
|
248
241
|
endLine: nodeEndLine(node),
|
|
249
242
|
children: params.length > 0 ? params : undefined,
|
|
250
243
|
visibility: extractSolVisibility(node),
|
|
@@ -254,13 +247,10 @@ function handleFunctionDef(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
254
247
|
function handleModifierDef(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
255
248
|
const nameNode = node.childForFieldName('name');
|
|
256
249
|
if (!nameNode) return;
|
|
257
|
-
const parent = findParentNode(node, SOL_PARENT_TYPES);
|
|
258
|
-
const fullName = parent ? `${parent}.${nameNode.text}` : nameNode.text;
|
|
259
|
-
|
|
260
250
|
ctx.definitions.push({
|
|
261
|
-
name:
|
|
251
|
+
name: qualifyWithParent(node, nameNode.text),
|
|
262
252
|
kind: 'function',
|
|
263
|
-
line: node
|
|
253
|
+
line: nodeStartLine(node),
|
|
264
254
|
endLine: nodeEndLine(node),
|
|
265
255
|
decorators: ['modifier'],
|
|
266
256
|
});
|
|
@@ -269,13 +259,10 @@ function handleModifierDef(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
269
259
|
function handleEventDef(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
270
260
|
const nameNode = node.childForFieldName('name');
|
|
271
261
|
if (!nameNode) return;
|
|
272
|
-
const parent = findParentNode(node, SOL_PARENT_TYPES);
|
|
273
|
-
const fullName = parent ? `${parent}.${nameNode.text}` : nameNode.text;
|
|
274
|
-
|
|
275
262
|
ctx.definitions.push({
|
|
276
|
-
name:
|
|
263
|
+
name: qualifyWithParent(node, nameNode.text),
|
|
277
264
|
kind: 'type',
|
|
278
|
-
line: node
|
|
265
|
+
line: nodeStartLine(node),
|
|
279
266
|
endLine: nodeEndLine(node),
|
|
280
267
|
decorators: ['event'],
|
|
281
268
|
});
|
|
@@ -284,13 +271,10 @@ function handleEventDef(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
284
271
|
function handleErrorDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
285
272
|
const nameNode = node.childForFieldName('name');
|
|
286
273
|
if (!nameNode) return;
|
|
287
|
-
const parent = findParentNode(node, SOL_PARENT_TYPES);
|
|
288
|
-
const fullName = parent ? `${parent}.${nameNode.text}` : nameNode.text;
|
|
289
|
-
|
|
290
274
|
ctx.definitions.push({
|
|
291
|
-
name:
|
|
275
|
+
name: qualifyWithParent(node, nameNode.text),
|
|
292
276
|
kind: 'type',
|
|
293
|
-
line: node
|
|
277
|
+
line: nodeStartLine(node),
|
|
294
278
|
endLine: nodeEndLine(node),
|
|
295
279
|
decorators: ['error'],
|
|
296
280
|
});
|
|
@@ -299,18 +283,21 @@ function handleErrorDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
299
283
|
function handleStateVarDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
300
284
|
const nameNode = node.childForFieldName('name');
|
|
301
285
|
if (!nameNode) return;
|
|
302
|
-
const parent = findParentNode(node, SOL_PARENT_TYPES);
|
|
303
|
-
const fullName = parent ? `${parent}.${nameNode.text}` : nameNode.text;
|
|
304
|
-
|
|
305
286
|
ctx.definitions.push({
|
|
306
|
-
name:
|
|
287
|
+
name: qualifyWithParent(node, nameNode.text),
|
|
307
288
|
kind: 'variable',
|
|
308
|
-
line: node
|
|
289
|
+
line: nodeStartLine(node),
|
|
309
290
|
endLine: nodeEndLine(node),
|
|
310
291
|
visibility: extractSolVisibility(node),
|
|
311
292
|
});
|
|
312
293
|
}
|
|
313
294
|
|
|
295
|
+
/** Qualify `name` with the nearest contract/interface/library, if any. */
|
|
296
|
+
function qualifyWithParent(node: TreeSitterNode, name: string): string {
|
|
297
|
+
const parent = findParentNode(node, SOL_PARENT_TYPES);
|
|
298
|
+
return parent ? `${parent}.${name}` : name;
|
|
299
|
+
}
|
|
300
|
+
|
|
314
301
|
function handleImportDirective(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
315
302
|
// import "path"; or import { X } from "path"; or import "path" as Alias;
|
|
316
303
|
for (let i = 0; i < node.childCount; i++) {
|
|
@@ -328,22 +315,17 @@ function handleImportDirective(node: TreeSitterNode, ctx: ExtractorOutput): void
|
|
|
328
315
|
if (id) names.push(id.text);
|
|
329
316
|
}
|
|
330
317
|
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
});
|
|
318
|
+
// Preserve the explicit `['*']` fallback — pushImport's default uses the
|
|
319
|
+
// source basename, but Solidity's convention here is to mark unqualified
|
|
320
|
+
// imports as `*`.
|
|
321
|
+
pushImport(ctx, node, source, names.length > 0 ? names : ['*']);
|
|
336
322
|
return;
|
|
337
323
|
}
|
|
338
324
|
// source_import: handles `import * as X from "path"`
|
|
339
325
|
if (child.type === 'source_import' || child.type === 'import_clause') {
|
|
340
|
-
const strNode =
|
|
326
|
+
const strNode = findFirstChildOfTypes(child, ['string', 'string_literal']);
|
|
341
327
|
if (strNode) {
|
|
342
|
-
ctx.
|
|
343
|
-
source: stripQuotes(strNode.text),
|
|
344
|
-
names: ['*'],
|
|
345
|
-
line: node.startPosition.row + 1,
|
|
346
|
-
});
|
|
328
|
+
pushImport(ctx, node, stripQuotes(strNode.text), ['*']);
|
|
347
329
|
return;
|
|
348
330
|
}
|
|
349
331
|
}
|
|
@@ -354,35 +336,25 @@ function handleCallExpression(node: TreeSitterNode, ctx: ExtractorOutput): void
|
|
|
354
336
|
const funcNode = node.childForFieldName('function') || node.childForFieldName('callee');
|
|
355
337
|
if (!funcNode) return;
|
|
356
338
|
|
|
357
|
-
|
|
339
|
+
let name = '';
|
|
340
|
+
let receiver: string | undefined;
|
|
358
341
|
if (funcNode.type === 'member_expression' || funcNode.type === 'member_access') {
|
|
359
342
|
const prop = funcNode.childForFieldName('property') || funcNode.childForFieldName('member');
|
|
360
343
|
const obj = funcNode.childForFieldName('object') || funcNode.childForFieldName('expression');
|
|
361
|
-
if (prop)
|
|
362
|
-
if (obj)
|
|
344
|
+
if (prop) name = prop.text;
|
|
345
|
+
if (obj) receiver = obj.text;
|
|
363
346
|
} else {
|
|
364
|
-
|
|
347
|
+
name = funcNode.text;
|
|
365
348
|
}
|
|
366
|
-
if (
|
|
349
|
+
if (name) pushCall(ctx, node, name, receiver !== undefined ? { receiver } : {});
|
|
367
350
|
}
|
|
368
351
|
|
|
369
352
|
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
370
353
|
|
|
371
354
|
function extractSolParams(funcNode: TreeSitterNode): SubDeclaration[] {
|
|
372
|
-
const params: SubDeclaration[] = [];
|
|
373
355
|
const paramList =
|
|
374
356
|
funcNode.childForFieldName('parameters') || findChild(funcNode, 'parameter_list');
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
for (let i = 0; i < paramList.childCount; i++) {
|
|
378
|
-
const param = paramList.child(i);
|
|
379
|
-
if (!param || param.type !== 'parameter') continue;
|
|
380
|
-
const nameNode = param.childForFieldName('name');
|
|
381
|
-
if (nameNode) {
|
|
382
|
-
params.push({ name: nameNode.text, kind: 'parameter', line: param.startPosition.row + 1 });
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
return params;
|
|
357
|
+
return extractSimpleParameters(paramList, { paramTypes: ['parameter'] });
|
|
386
358
|
}
|
|
387
359
|
|
|
388
360
|
function extractSolVisibility(
|
|
@@ -235,30 +235,23 @@ interface EvaluateBoundariesOpts {
|
|
|
235
235
|
noTests?: boolean;
|
|
236
236
|
}
|
|
237
237
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
const { valid, errors } = validateBoundaryConfig(boundaryConfig);
|
|
246
|
-
if (!valid) {
|
|
247
|
-
throw new BoundaryError(`Invalid boundary configuration: ${errors.join('; ')}`);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
const modules = resolveModules(boundaryConfig);
|
|
251
|
-
if (modules.size === 0) return { violations: [], violationCount: 0 };
|
|
252
|
-
|
|
253
|
-
let allRules: BoundaryRule[] = [];
|
|
254
|
-
if (boundaryConfig.preset) {
|
|
255
|
-
allRules = generatePresetRules(modules, boundaryConfig.preset);
|
|
256
|
-
}
|
|
238
|
+
function collectAllRules(
|
|
239
|
+
boundaryConfig: BoundaryConfig,
|
|
240
|
+
modules: Map<string, ResolvedModule>,
|
|
241
|
+
): BoundaryRule[] {
|
|
242
|
+
const rules: BoundaryRule[] = boundaryConfig.preset
|
|
243
|
+
? generatePresetRules(modules, boundaryConfig.preset)
|
|
244
|
+
: [];
|
|
257
245
|
if (boundaryConfig.rules && Array.isArray(boundaryConfig.rules)) {
|
|
258
|
-
|
|
246
|
+
return rules.concat(boundaryConfig.rules);
|
|
259
247
|
}
|
|
260
|
-
|
|
248
|
+
return rules;
|
|
249
|
+
}
|
|
261
250
|
|
|
251
|
+
function loadImportEdges(
|
|
252
|
+
db: BetterSqlite3Database,
|
|
253
|
+
opts: EvaluateBoundariesOpts,
|
|
254
|
+
): Array<{ source: string; target: string }> {
|
|
262
255
|
let edges: Array<{ source: string; target: string }>;
|
|
263
256
|
try {
|
|
264
257
|
edges = db
|
|
@@ -281,38 +274,63 @@ export function evaluateBoundaries(
|
|
|
281
274
|
const scope = new Set(opts.scopeFiles);
|
|
282
275
|
edges = edges.filter((e) => scope.has(e.source));
|
|
283
276
|
}
|
|
277
|
+
return edges;
|
|
278
|
+
}
|
|
284
279
|
|
|
285
|
-
|
|
280
|
+
function ruleViolated(rule: BoundaryRule, toModule: string): boolean {
|
|
281
|
+
if (rule.notTo?.includes(toModule)) return true;
|
|
282
|
+
if (rule.onlyTo && !rule.onlyTo.includes(toModule)) return true;
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
286
285
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
286
|
+
function emitEdgeViolations(
|
|
287
|
+
edge: { source: string; target: string },
|
|
288
|
+
fromModule: string,
|
|
289
|
+
toModule: string,
|
|
290
|
+
allRules: BoundaryRule[],
|
|
291
|
+
violations: BoundaryViolation[],
|
|
292
|
+
): void {
|
|
293
|
+
for (const rule of allRules) {
|
|
294
|
+
if (rule.from !== fromModule) continue;
|
|
295
|
+
if (!ruleViolated(rule, toModule)) continue;
|
|
296
|
+
violations.push({
|
|
297
|
+
rule: 'boundaries',
|
|
298
|
+
name: `${fromModule} -> ${toModule}`,
|
|
299
|
+
file: edge.source,
|
|
300
|
+
targetFile: edge.target,
|
|
301
|
+
message: rule.message || `${fromModule} must not depend on ${toModule}`,
|
|
302
|
+
value: 1,
|
|
303
|
+
threshold: 0,
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
}
|
|
290
307
|
|
|
291
|
-
|
|
308
|
+
export function evaluateBoundaries(
|
|
309
|
+
db: BetterSqlite3Database,
|
|
310
|
+
boundaryConfig: BoundaryConfig | undefined,
|
|
311
|
+
opts: EvaluateBoundariesOpts = {},
|
|
312
|
+
): { violations: BoundaryViolation[]; violationCount: number } {
|
|
313
|
+
if (!boundaryConfig) return { violations: [], violationCount: 0 };
|
|
292
314
|
|
|
293
|
-
|
|
294
|
-
|
|
315
|
+
const { valid, errors } = validateBoundaryConfig(boundaryConfig);
|
|
316
|
+
if (!valid) {
|
|
317
|
+
throw new BoundaryError(`Invalid boundary configuration: ${errors.join('; ')}`);
|
|
318
|
+
}
|
|
295
319
|
|
|
296
|
-
|
|
320
|
+
const modules = resolveModules(boundaryConfig);
|
|
321
|
+
if (modules.size === 0) return { violations: [], violationCount: 0 };
|
|
297
322
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
} else if (rule.onlyTo && !rule.onlyTo.includes(toModule)) {
|
|
301
|
-
isViolation = true;
|
|
302
|
-
}
|
|
323
|
+
const allRules = collectAllRules(boundaryConfig, modules);
|
|
324
|
+
if (allRules.length === 0) return { violations: [], violationCount: 0 };
|
|
303
325
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
threshold: 0,
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
}
|
|
326
|
+
const edges = loadImportEdges(db, opts);
|
|
327
|
+
const violations: BoundaryViolation[] = [];
|
|
328
|
+
|
|
329
|
+
for (const edge of edges) {
|
|
330
|
+
const fromModule = classifyFile(edge.source, modules);
|
|
331
|
+
const toModule = classifyFile(edge.target, modules);
|
|
332
|
+
if (!fromModule || !toModule) continue;
|
|
333
|
+
emitEdgeViolations(edge, fromModule, toModule, allRules, violations);
|
|
316
334
|
}
|
|
317
335
|
|
|
318
336
|
return { violations, violationCount: violations.length };
|