@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.
Files changed (230) hide show
  1. package/README.md +38 -31
  2. package/dist/ast-analysis/engine.d.ts.map +1 -1
  3. package/dist/ast-analysis/engine.js +91 -60
  4. package/dist/ast-analysis/engine.js.map +1 -1
  5. package/dist/ast-analysis/visitor-utils.d.ts +3 -0
  6. package/dist/ast-analysis/visitor-utils.d.ts.map +1 -1
  7. package/dist/ast-analysis/visitor-utils.js +83 -49
  8. package/dist/ast-analysis/visitor-utils.js.map +1 -1
  9. package/dist/ast-analysis/visitors/ast-store-visitor.d.ts.map +1 -1
  10. package/dist/ast-analysis/visitors/ast-store-visitor.js +78 -62
  11. package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
  12. package/dist/ast-analysis/visitors/dataflow-visitor.d.ts.map +1 -1
  13. package/dist/ast-analysis/visitors/dataflow-visitor.js +61 -42
  14. package/dist/ast-analysis/visitors/dataflow-visitor.js.map +1 -1
  15. package/dist/cli/commands/embed.d.ts.map +1 -1
  16. package/dist/cli/commands/embed.js +49 -4
  17. package/dist/cli/commands/embed.js.map +1 -1
  18. package/dist/domain/analysis/dependencies.d.ts.map +1 -1
  19. package/dist/domain/analysis/dependencies.js +106 -80
  20. package/dist/domain/analysis/dependencies.js.map +1 -1
  21. package/dist/domain/analysis/fn-impact.d.ts.map +1 -1
  22. package/dist/domain/analysis/fn-impact.js +77 -52
  23. package/dist/domain/analysis/fn-impact.js.map +1 -1
  24. package/dist/domain/analysis/module-map.d.ts.map +1 -1
  25. package/dist/domain/analysis/module-map.js +132 -121
  26. package/dist/domain/analysis/module-map.js.map +1 -1
  27. package/dist/domain/graph/builder/call-resolver.d.ts +71 -0
  28. package/dist/domain/graph/builder/call-resolver.d.ts.map +1 -0
  29. package/dist/domain/graph/builder/call-resolver.js +130 -0
  30. package/dist/domain/graph/builder/call-resolver.js.map +1 -0
  31. package/dist/domain/graph/builder/helpers.d.ts +4 -4
  32. package/dist/domain/graph/builder/helpers.d.ts.map +1 -1
  33. package/dist/domain/graph/builder/helpers.js +47 -33
  34. package/dist/domain/graph/builder/helpers.js.map +1 -1
  35. package/dist/domain/graph/builder/incremental.d.ts +6 -0
  36. package/dist/domain/graph/builder/incremental.d.ts.map +1 -1
  37. package/dist/domain/graph/builder/incremental.js +214 -127
  38. package/dist/domain/graph/builder/incremental.js.map +1 -1
  39. package/dist/domain/graph/builder/pipeline.d.ts +1 -44
  40. package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
  41. package/dist/domain/graph/builder/pipeline.js +10 -766
  42. package/dist/domain/graph/builder/pipeline.js.map +1 -1
  43. package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
  44. package/dist/domain/graph/builder/stages/build-edges.js +151 -192
  45. package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
  46. package/dist/domain/graph/builder/stages/build-structure.d.ts.map +1 -1
  47. package/dist/domain/graph/builder/stages/build-structure.js +82 -65
  48. package/dist/domain/graph/builder/stages/build-structure.js.map +1 -1
  49. package/dist/domain/graph/builder/stages/detect-changes.d.ts.map +1 -1
  50. package/dist/domain/graph/builder/stages/detect-changes.js +84 -56
  51. package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
  52. package/dist/domain/graph/builder/stages/finalize.d.ts.map +1 -1
  53. package/dist/domain/graph/builder/stages/finalize.js +60 -51
  54. package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
  55. package/dist/domain/graph/builder/stages/insert-nodes.d.ts +8 -6
  56. package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
  57. package/dist/domain/graph/builder/stages/insert-nodes.js +107 -122
  58. package/dist/domain/graph/builder/stages/insert-nodes.js.map +1 -1
  59. package/dist/domain/graph/builder/stages/native-db-lifecycle.d.ts +14 -0
  60. package/dist/domain/graph/builder/stages/native-db-lifecycle.d.ts.map +1 -0
  61. package/dist/domain/graph/builder/stages/native-db-lifecycle.js +77 -0
  62. package/dist/domain/graph/builder/stages/native-db-lifecycle.js.map +1 -0
  63. package/dist/domain/graph/builder/stages/native-orchestrator.d.ts +62 -0
  64. package/dist/domain/graph/builder/stages/native-orchestrator.d.ts.map +1 -0
  65. package/dist/domain/graph/builder/stages/native-orchestrator.js +747 -0
  66. package/dist/domain/graph/builder/stages/native-orchestrator.js.map +1 -0
  67. package/dist/domain/graph/cycles.d.ts +6 -4
  68. package/dist/domain/graph/cycles.d.ts.map +1 -1
  69. package/dist/domain/graph/cycles.js +50 -55
  70. package/dist/domain/graph/cycles.js.map +1 -1
  71. package/dist/domain/graph/journal.d.ts.map +1 -1
  72. package/dist/domain/graph/journal.js +89 -70
  73. package/dist/domain/graph/journal.js.map +1 -1
  74. package/dist/domain/graph/watcher.d.ts.map +1 -1
  75. package/dist/domain/graph/watcher.js +10 -4
  76. package/dist/domain/graph/watcher.js.map +1 -1
  77. package/dist/domain/parser.d.ts +12 -23
  78. package/dist/domain/parser.d.ts.map +1 -1
  79. package/dist/domain/parser.js +126 -79
  80. package/dist/domain/parser.js.map +1 -1
  81. package/dist/domain/search/generator.d.ts +3 -1
  82. package/dist/domain/search/generator.d.ts.map +1 -1
  83. package/dist/domain/search/generator.js +68 -45
  84. package/dist/domain/search/generator.js.map +1 -1
  85. package/dist/domain/search/models.d.ts +2 -0
  86. package/dist/domain/search/models.d.ts.map +1 -1
  87. package/dist/domain/search/models.js +37 -3
  88. package/dist/domain/search/models.js.map +1 -1
  89. package/dist/domain/search/search/hybrid.d.ts.map +1 -1
  90. package/dist/domain/search/search/hybrid.js +49 -40
  91. package/dist/domain/search/search/hybrid.js.map +1 -1
  92. package/dist/domain/search/search/semantic.d.ts.map +1 -1
  93. package/dist/domain/search/search/semantic.js +69 -49
  94. package/dist/domain/search/search/semantic.js.map +1 -1
  95. package/dist/domain/wasm-worker-entry.js +201 -136
  96. package/dist/domain/wasm-worker-entry.js.map +1 -1
  97. package/dist/extractors/elixir.js +95 -71
  98. package/dist/extractors/elixir.js.map +1 -1
  99. package/dist/extractors/gleam.d.ts.map +1 -1
  100. package/dist/extractors/gleam.js +23 -31
  101. package/dist/extractors/gleam.js.map +1 -1
  102. package/dist/extractors/helpers.d.ts +79 -1
  103. package/dist/extractors/helpers.d.ts.map +1 -1
  104. package/dist/extractors/helpers.js +137 -0
  105. package/dist/extractors/helpers.js.map +1 -1
  106. package/dist/extractors/java.d.ts.map +1 -1
  107. package/dist/extractors/java.js +37 -49
  108. package/dist/extractors/java.js.map +1 -1
  109. package/dist/extractors/javascript.d.ts.map +1 -1
  110. package/dist/extractors/javascript.js +44 -44
  111. package/dist/extractors/javascript.js.map +1 -1
  112. package/dist/extractors/julia.js +27 -34
  113. package/dist/extractors/julia.js.map +1 -1
  114. package/dist/extractors/r.d.ts.map +1 -1
  115. package/dist/extractors/r.js +33 -58
  116. package/dist/extractors/r.js.map +1 -1
  117. package/dist/extractors/solidity.d.ts.map +1 -1
  118. package/dist/extractors/solidity.js +38 -61
  119. package/dist/extractors/solidity.js.map +1 -1
  120. package/dist/features/boundaries.d.ts.map +1 -1
  121. package/dist/features/boundaries.js +49 -39
  122. package/dist/features/boundaries.js.map +1 -1
  123. package/dist/features/cfg.d.ts.map +1 -1
  124. package/dist/features/cfg.js +90 -63
  125. package/dist/features/cfg.js.map +1 -1
  126. package/dist/features/check.d.ts.map +1 -1
  127. package/dist/features/check.js +43 -34
  128. package/dist/features/check.js.map +1 -1
  129. package/dist/features/cochange.d.ts.map +1 -1
  130. package/dist/features/cochange.js +68 -56
  131. package/dist/features/cochange.js.map +1 -1
  132. package/dist/features/complexity.d.ts.map +1 -1
  133. package/dist/features/complexity.js +105 -75
  134. package/dist/features/complexity.js.map +1 -1
  135. package/dist/features/dataflow.d.ts.map +1 -1
  136. package/dist/features/dataflow.js +37 -29
  137. package/dist/features/dataflow.js.map +1 -1
  138. package/dist/features/flow.d.ts.map +1 -1
  139. package/dist/features/flow.js +31 -22
  140. package/dist/features/flow.js.map +1 -1
  141. package/dist/features/graph-enrichment.d.ts.map +1 -1
  142. package/dist/features/graph-enrichment.js +77 -70
  143. package/dist/features/graph-enrichment.js.map +1 -1
  144. package/dist/features/owners.d.ts +17 -26
  145. package/dist/features/owners.d.ts.map +1 -1
  146. package/dist/features/owners.js +120 -109
  147. package/dist/features/owners.js.map +1 -1
  148. package/dist/features/sequence.d.ts.map +1 -1
  149. package/dist/features/sequence.js +59 -54
  150. package/dist/features/sequence.js.map +1 -1
  151. package/dist/features/structure-query.d.ts.map +1 -1
  152. package/dist/features/structure-query.js +60 -60
  153. package/dist/features/structure-query.js.map +1 -1
  154. package/dist/features/structure.d.ts.map +1 -1
  155. package/dist/features/structure.js +149 -52
  156. package/dist/features/structure.js.map +1 -1
  157. package/dist/graph/algorithms/leiden/optimiser.d.ts.map +1 -1
  158. package/dist/graph/algorithms/leiden/optimiser.js +100 -69
  159. package/dist/graph/algorithms/leiden/optimiser.js.map +1 -1
  160. package/dist/graph/classifiers/roles.d.ts.map +1 -1
  161. package/dist/graph/classifiers/roles.js +63 -59
  162. package/dist/graph/classifiers/roles.js.map +1 -1
  163. package/dist/infrastructure/config.d.ts +1 -1
  164. package/dist/infrastructure/config.d.ts.map +1 -1
  165. package/dist/infrastructure/config.js +1 -1
  166. package/dist/infrastructure/config.js.map +1 -1
  167. package/dist/presentation/cfg.d.ts.map +1 -1
  168. package/dist/presentation/cfg.js +44 -29
  169. package/dist/presentation/cfg.js.map +1 -1
  170. package/dist/presentation/flow.d.ts.map +1 -1
  171. package/dist/presentation/flow.js +58 -38
  172. package/dist/presentation/flow.js.map +1 -1
  173. package/dist/types.d.ts +1 -1
  174. package/dist/types.d.ts.map +1 -1
  175. package/grammars/tree-sitter-erlang.wasm +0 -0
  176. package/package.json +9 -9
  177. package/src/ast-analysis/engine.ts +145 -61
  178. package/src/ast-analysis/visitor-utils.ts +86 -46
  179. package/src/ast-analysis/visitors/ast-store-visitor.ts +104 -69
  180. package/src/ast-analysis/visitors/dataflow-visitor.ts +86 -47
  181. package/src/cli/commands/embed.ts +54 -4
  182. package/src/domain/analysis/dependencies.ts +166 -85
  183. package/src/domain/analysis/fn-impact.ts +120 -50
  184. package/src/domain/analysis/module-map.ts +175 -140
  185. package/src/domain/graph/builder/call-resolver.ts +181 -0
  186. package/src/domain/graph/builder/helpers.ts +85 -76
  187. package/src/domain/graph/builder/incremental.ts +321 -152
  188. package/src/domain/graph/builder/pipeline.ts +19 -957
  189. package/src/domain/graph/builder/stages/build-edges.ts +229 -275
  190. package/src/domain/graph/builder/stages/build-structure.ts +115 -82
  191. package/src/domain/graph/builder/stages/detect-changes.ts +107 -64
  192. package/src/domain/graph/builder/stages/finalize.ts +72 -70
  193. package/src/domain/graph/builder/stages/insert-nodes.ts +154 -120
  194. package/src/domain/graph/builder/stages/native-db-lifecycle.ts +74 -0
  195. package/src/domain/graph/builder/stages/native-orchestrator.ts +942 -0
  196. package/src/domain/graph/cycles.ts +51 -49
  197. package/src/domain/graph/journal.ts +84 -69
  198. package/src/domain/graph/watcher.ts +12 -4
  199. package/src/domain/parser.ts +143 -66
  200. package/src/domain/search/generator.ts +132 -74
  201. package/src/domain/search/models.ts +39 -3
  202. package/src/domain/search/search/hybrid.ts +53 -42
  203. package/src/domain/search/search/semantic.ts +105 -65
  204. package/src/domain/wasm-worker-entry.ts +235 -152
  205. package/src/extractors/elixir.ts +91 -64
  206. package/src/extractors/gleam.ts +33 -37
  207. package/src/extractors/helpers.ts +205 -1
  208. package/src/extractors/java.ts +42 -45
  209. package/src/extractors/javascript.ts +44 -43
  210. package/src/extractors/julia.ts +28 -35
  211. package/src/extractors/r.ts +38 -56
  212. package/src/extractors/solidity.ts +43 -71
  213. package/src/features/boundaries.ts +64 -46
  214. package/src/features/cfg.ts +145 -74
  215. package/src/features/check.ts +60 -43
  216. package/src/features/cochange.ts +95 -72
  217. package/src/features/complexity.ts +134 -79
  218. package/src/features/dataflow.ts +57 -34
  219. package/src/features/flow.ts +48 -24
  220. package/src/features/graph-enrichment.ts +105 -70
  221. package/src/features/owners.ts +186 -146
  222. package/src/features/sequence.ts +99 -69
  223. package/src/features/structure-query.ts +94 -79
  224. package/src/features/structure.ts +199 -79
  225. package/src/graph/algorithms/leiden/optimiser.ts +142 -87
  226. package/src/graph/classifiers/roles.ts +64 -54
  227. package/src/infrastructure/config.ts +1 -1
  228. package/src/presentation/cfg.ts +48 -32
  229. package/src/presentation/flow.ts +100 -52
  230. package/src/types.ts +1 -1
@@ -1,5 +1,13 @@
1
1
  import type { ExtractorOutput, SubDeclaration, TreeSitterNode, TreeSitterTree } from '../types.js';
2
- import { findChild, nodeEndLine } from './helpers.js';
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.startPosition.row + 1,
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.startPosition.row + 1,
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.startPosition.row + 1 });
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.startPosition.row + 1 });
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.startPosition.row + 1 });
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.calls.push({ name: funcName, line: node.startPosition.row + 1 });
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.calls.push({
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.imports.push({
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.replace(/^["']|["']$/g, '');
176
- ctx.imports.push({
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.replace(/^["']|["']$/g, '');
206
- ctx.imports.push({
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.imports.push({
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.startPosition.row + 1,
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.startPosition.row + 1,
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.calls.push({ name, line: node.startPosition.row + 1 });
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
- for (let i = 0; i < node.childCount; i++) {
270
- const child = node.child(i);
271
- if (!child || child.type !== 'arguments') continue;
272
- for (let j = 0; j < child.childCount; j++) {
273
- const arg = child.child(j);
274
- if (!arg) continue;
275
- if (arg.type === 'string') {
276
- return stripQuotes(arg.text);
277
- }
278
- if (arg.type === 'argument') {
279
- const valueNode = arg.childForFieldName('value');
280
- if (valueNode && valueNode.type === 'string') return stripQuotes(valueNode.text);
281
- for (let k = 0; k < arg.childCount; k++) {
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.startPosition.row + 1,
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.startPosition.row + 1;
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.startPosition.row + 1 });
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.startPosition.row + 1,
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: fullName,
200
+ name: qualifyWithParent(node, nameNode.text),
205
201
  kind: 'struct',
206
- line: node.startPosition.row + 1,
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.startPosition.row + 1 });
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: fullName,
221
+ name: qualifyWithParent(node, nameNode.text),
229
222
  kind: 'enum',
230
- line: node.startPosition.row + 1,
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.startPosition.row + 1,
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: fullName,
251
+ name: qualifyWithParent(node, nameNode.text),
262
252
  kind: 'function',
263
- line: node.startPosition.row + 1,
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: fullName,
263
+ name: qualifyWithParent(node, nameNode.text),
277
264
  kind: 'type',
278
- line: node.startPosition.row + 1,
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: fullName,
275
+ name: qualifyWithParent(node, nameNode.text),
292
276
  kind: 'type',
293
- line: node.startPosition.row + 1,
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: fullName,
287
+ name: qualifyWithParent(node, nameNode.text),
307
288
  kind: 'variable',
308
- line: node.startPosition.row + 1,
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
- ctx.imports.push({
332
- source,
333
- names: names.length > 0 ? names : ['*'],
334
- line: node.startPosition.row + 1,
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 = findChild(child, 'string') || findChild(child, 'string_literal');
326
+ const strNode = findFirstChildOfTypes(child, ['string', 'string_literal']);
341
327
  if (strNode) {
342
- ctx.imports.push({
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
- const call: Call = { name: '', line: node.startPosition.row + 1 };
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) call.name = prop.text;
362
- if (obj) call.receiver = obj.text;
344
+ if (prop) name = prop.text;
345
+ if (obj) receiver = obj.text;
363
346
  } else {
364
- call.name = funcNode.text;
347
+ name = funcNode.text;
365
348
  }
366
- if (call.name) ctx.calls.push(call);
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
- if (!paramList) return params;
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
- export function evaluateBoundaries(
239
- db: BetterSqlite3Database,
240
- boundaryConfig: BoundaryConfig | undefined,
241
- opts: EvaluateBoundariesOpts = {},
242
- ): { violations: BoundaryViolation[]; violationCount: number } {
243
- if (!boundaryConfig) return { violations: [], violationCount: 0 };
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
- allRules = allRules.concat(boundaryConfig.rules);
246
+ return rules.concat(boundaryConfig.rules);
259
247
  }
260
- if (allRules.length === 0) return { violations: [], violationCount: 0 };
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
- const violations: BoundaryViolation[] = [];
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
- for (const edge of edges) {
288
- const fromModule = classifyFile(edge.source, modules);
289
- const toModule = classifyFile(edge.target, modules);
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
- if (!fromModule || !toModule) continue;
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
- for (const rule of allRules) {
294
- if (rule.from !== fromModule) continue;
315
+ const { valid, errors } = validateBoundaryConfig(boundaryConfig);
316
+ if (!valid) {
317
+ throw new BoundaryError(`Invalid boundary configuration: ${errors.join('; ')}`);
318
+ }
295
319
 
296
- let isViolation = false;
320
+ const modules = resolveModules(boundaryConfig);
321
+ if (modules.size === 0) return { violations: [], violationCount: 0 };
297
322
 
298
- if (rule.notTo?.includes(toModule)) {
299
- isViolation = true;
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
- if (isViolation) {
305
- violations.push({
306
- rule: 'boundaries',
307
- name: `${fromModule} -> ${toModule}`,
308
- file: edge.source,
309
- targetFile: edge.target,
310
- message: rule.message || `${fromModule} must not depend on ${toModule}`,
311
- value: 1,
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 };