@optave/codegraph 3.10.0 → 3.11.0
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 +13 -13
- package/dist/ast-analysis/rules/index.d.ts.map +1 -1
- package/dist/ast-analysis/rules/index.js +77 -0
- package/dist/ast-analysis/rules/index.js.map +1 -1
- package/dist/cli/commands/audit.js +1 -1
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/build.js +2 -0
- package/dist/cli/commands/build.js.map +1 -1
- package/dist/cli/commands/check.js +1 -1
- package/dist/cli/commands/check.js.map +1 -1
- package/dist/cli/commands/children.js +1 -1
- package/dist/cli/commands/children.js.map +1 -1
- package/dist/cli/commands/diff-impact.js +1 -1
- package/dist/cli/commands/diff-impact.js.map +1 -1
- package/dist/cli/commands/roles.js +1 -1
- package/dist/cli/commands/roles.js.map +1 -1
- package/dist/cli/commands/structure.js +1 -1
- package/dist/cli/commands/structure.js.map +1 -1
- package/dist/cli/shared/options.js +1 -1
- package/dist/cli/shared/options.js.map +1 -1
- package/dist/db/connection.d.ts.map +1 -1
- package/dist/db/connection.js +8 -0
- package/dist/db/connection.js.map +1 -1
- package/dist/domain/graph/builder/incremental.d.ts +0 -6
- package/dist/domain/graph/builder/incremental.d.ts.map +1 -1
- package/dist/domain/graph/builder/incremental.js +6 -23
- package/dist/domain/graph/builder/incremental.js.map +1 -1
- package/dist/domain/graph/builder/pipeline.d.ts +44 -0
- package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
- package/dist/domain/graph/builder/pipeline.js +181 -39
- 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 +8 -2
- package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
- package/dist/domain/graph/builder/stages/resolve-imports.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/resolve-imports.js +73 -22
- package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
- package/dist/domain/graph/watcher.d.ts.map +1 -1
- package/dist/domain/graph/watcher.js +23 -18
- package/dist/domain/graph/watcher.js.map +1 -1
- package/dist/domain/parser.d.ts.map +1 -1
- package/dist/domain/parser.js +27 -1
- package/dist/domain/parser.js.map +1 -1
- package/dist/domain/search/models.d.ts +16 -0
- package/dist/domain/search/models.d.ts.map +1 -1
- package/dist/domain/search/models.js +35 -1
- package/dist/domain/search/models.js.map +1 -1
- package/dist/domain/wasm-worker-entry.js +8 -1
- package/dist/domain/wasm-worker-entry.js.map +1 -1
- package/dist/extractors/c.js +25 -6
- package/dist/extractors/c.js.map +1 -1
- package/dist/extractors/cpp.js +47 -6
- package/dist/extractors/cpp.js.map +1 -1
- package/dist/extractors/cuda.js +90 -14
- package/dist/extractors/cuda.js.map +1 -1
- package/dist/extractors/elixir.js +83 -3
- package/dist/extractors/elixir.js.map +1 -1
- package/dist/extractors/erlang.js +56 -20
- package/dist/extractors/erlang.js.map +1 -1
- package/dist/extractors/fsharp.d.ts +7 -0
- package/dist/extractors/fsharp.d.ts.map +1 -1
- package/dist/extractors/fsharp.js +94 -0
- package/dist/extractors/fsharp.js.map +1 -1
- package/dist/extractors/gleam.js +6 -2
- package/dist/extractors/gleam.js.map +1 -1
- package/dist/extractors/groovy.js +41 -1
- package/dist/extractors/groovy.js.map +1 -1
- package/dist/extractors/haskell.js +48 -4
- package/dist/extractors/haskell.js.map +1 -1
- package/dist/extractors/julia.js +172 -41
- package/dist/extractors/julia.js.map +1 -1
- package/dist/extractors/kotlin.js +4 -0
- package/dist/extractors/kotlin.js.map +1 -1
- package/dist/extractors/objc.js +184 -47
- package/dist/extractors/objc.js.map +1 -1
- package/dist/extractors/python.js +7 -4
- package/dist/extractors/python.js.map +1 -1
- package/dist/extractors/r.js +93 -52
- package/dist/extractors/r.js.map +1 -1
- package/dist/extractors/scala.d.ts.map +1 -1
- package/dist/extractors/scala.js +18 -32
- package/dist/extractors/scala.js.map +1 -1
- package/dist/extractors/solidity.js +18 -9
- package/dist/extractors/solidity.js.map +1 -1
- package/dist/extractors/verilog.js +80 -15
- package/dist/extractors/verilog.js.map +1 -1
- package/dist/mcp/tool-registry.d.ts.map +1 -1
- package/dist/mcp/tool-registry.js +4 -0
- package/dist/mcp/tool-registry.js.map +1 -1
- package/dist/mcp/tools/semantic-search.d.ts +1 -0
- package/dist/mcp/tools/semantic-search.d.ts.map +1 -1
- package/dist/mcp/tools/semantic-search.js +1 -0
- package/dist/mcp/tools/semantic-search.js.map +1 -1
- package/dist/types.d.ts +15 -1
- package/dist/types.d.ts.map +1 -1
- package/grammars/tree-sitter-erlang.wasm +0 -0
- package/grammars/tree-sitter-fsharp.wasm +0 -0
- package/grammars/tree-sitter-fsharp_signature.wasm +0 -0
- package/grammars/tree-sitter-gleam.wasm +0 -0
- package/package.json +10 -10
- package/src/ast-analysis/rules/index.ts +87 -0
- package/src/cli/commands/audit.ts +1 -1
- package/src/cli/commands/build.ts +2 -0
- package/src/cli/commands/check.ts +1 -1
- package/src/cli/commands/children.ts +1 -1
- package/src/cli/commands/diff-impact.ts +1 -1
- package/src/cli/commands/roles.ts +1 -1
- package/src/cli/commands/structure.ts +1 -1
- package/src/cli/shared/options.ts +1 -1
- package/src/db/connection.ts +8 -0
- package/src/domain/graph/builder/incremental.ts +6 -41
- package/src/domain/graph/builder/pipeline.ts +222 -37
- package/src/domain/graph/builder/stages/build-edges.ts +9 -2
- package/src/domain/graph/builder/stages/resolve-imports.ts +79 -25
- package/src/domain/graph/watcher.ts +21 -23
- package/src/domain/parser.ts +27 -1
- package/src/domain/search/models.ts +36 -1
- package/src/domain/wasm-worker-entry.ts +8 -1
- package/src/extractors/c.ts +27 -8
- package/src/extractors/cpp.ts +50 -8
- package/src/extractors/cuda.ts +90 -16
- package/src/extractors/elixir.ts +75 -3
- package/src/extractors/erlang.ts +63 -20
- package/src/extractors/fsharp.ts +104 -0
- package/src/extractors/gleam.ts +7 -2
- package/src/extractors/groovy.ts +45 -1
- package/src/extractors/haskell.ts +45 -4
- package/src/extractors/julia.ts +164 -43
- package/src/extractors/kotlin.ts +4 -0
- package/src/extractors/objc.ts +171 -47
- package/src/extractors/python.ts +5 -3
- package/src/extractors/r.ts +88 -48
- package/src/extractors/scala.ts +24 -36
- package/src/extractors/solidity.ts +17 -8
- package/src/extractors/verilog.ts +83 -15
- package/src/mcp/tool-registry.ts +5 -0
- package/src/mcp/tools/semantic-search.ts +2 -0
- package/src/types.ts +15 -0
package/src/extractors/erlang.ts
CHANGED
|
@@ -70,7 +70,10 @@ function walkErlangNode(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
70
70
|
|
|
71
71
|
function handleModuleAttr(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
72
72
|
// module_attribute: - module ( atom ) .
|
|
73
|
-
|
|
73
|
+
// Prefer the named `name` field exposed by tree-sitter-erlang so we don't
|
|
74
|
+
// accidentally pick up the `module` keyword if a future grammar exposes it
|
|
75
|
+
// as a named `atom` child.
|
|
76
|
+
const nameNode = node.childForFieldName('name') ?? findChild(node, 'atom');
|
|
74
77
|
if (!nameNode) return;
|
|
75
78
|
|
|
76
79
|
ctx.definitions.push({
|
|
@@ -83,7 +86,10 @@ function handleModuleAttr(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
83
86
|
|
|
84
87
|
function handleRecordDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
85
88
|
// record_decl: - record ( atom , { record_field, ... } ) .
|
|
86
|
-
|
|
89
|
+
// Prefer the named `name` field exposed by tree-sitter-erlang; fall back to
|
|
90
|
+
// the first atom child for grammar versions that don't expose it. Mirrors
|
|
91
|
+
// the Rust `handle_record_decl` defensive pattern.
|
|
92
|
+
const nameNode = node.childForFieldName('name') ?? findChild(node, 'atom');
|
|
87
93
|
if (!nameNode) return;
|
|
88
94
|
|
|
89
95
|
const children: SubDeclaration[] = [];
|
|
@@ -112,7 +118,14 @@ function handleRecordDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
112
118
|
}
|
|
113
119
|
|
|
114
120
|
function handleTypeAlias(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
115
|
-
|
|
121
|
+
// type_alias: -type name(...) :: ty.
|
|
122
|
+
// Name is typically wrapped in a `type_name` node containing an `atom`.
|
|
123
|
+
// Mirrors the Rust `handle_type_alias` fallback so the two engines agree
|
|
124
|
+
// even when the grammar nests the name inside `type_name`.
|
|
125
|
+
const directAtom = findChild(node, 'atom');
|
|
126
|
+
const typeNameNode = !directAtom ? findChild(node, 'type_name') : null;
|
|
127
|
+
const wrappedAtom = typeNameNode ? findChild(typeNameNode, 'atom') : null;
|
|
128
|
+
const nameNode = directAtom ?? wrappedAtom;
|
|
116
129
|
if (!nameNode) return;
|
|
117
130
|
|
|
118
131
|
ctx.definitions.push({
|
|
@@ -134,13 +147,22 @@ function handleFunDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
134
147
|
|
|
135
148
|
function handleFunctionClause(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
136
149
|
// function_clause: atom expr_args clause_body
|
|
137
|
-
const nameNode = findChild(node, 'atom');
|
|
150
|
+
const nameNode = node.childForFieldName('name') ?? findChild(node, 'atom');
|
|
138
151
|
if (!nameNode) return;
|
|
139
152
|
|
|
140
|
-
// Don't duplicate if we already have this function
|
|
141
|
-
if (ctx.definitions.some((d) => d.name === nameNode.text && d.kind === 'function')) return;
|
|
142
|
-
|
|
143
153
|
const params = extractErlangParams(node);
|
|
154
|
+
const arity = params.length;
|
|
155
|
+
|
|
156
|
+
// Don't duplicate if we already have this function at the same arity.
|
|
157
|
+
// Erlang overloads by arity, so `foo/1` and `foo/2` are distinct definitions.
|
|
158
|
+
if (
|
|
159
|
+
ctx.definitions.some(
|
|
160
|
+
(d) =>
|
|
161
|
+
d.name === nameNode.text && d.kind === 'function' && (d.children?.length ?? 0) === arity,
|
|
162
|
+
)
|
|
163
|
+
) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
144
166
|
|
|
145
167
|
ctx.definitions.push({
|
|
146
168
|
name: nameNode.text,
|
|
@@ -154,31 +176,42 @@ function handleFunctionClause(node: TreeSitterNode, ctx: ExtractorOutput): void
|
|
|
154
176
|
|
|
155
177
|
function extractErlangParams(clauseNode: TreeSitterNode): SubDeclaration[] {
|
|
156
178
|
const params: SubDeclaration[] = [];
|
|
157
|
-
const argsNode = findChild(clauseNode, 'expr_args');
|
|
179
|
+
const argsNode = clauseNode.childForFieldName('args') ?? findChild(clauseNode, 'expr_args');
|
|
158
180
|
if (!argsNode) return params;
|
|
159
181
|
|
|
160
|
-
|
|
161
|
-
|
|
182
|
+
// Iterate named children so every argument pattern counts as one parameter,
|
|
183
|
+
// independent of whether it is a bare `var`/`atom` or a complex destructuring
|
|
184
|
+
// pattern (tuple, list, binary, etc.). Punctuation tokens are anonymous and
|
|
185
|
+
// therefore excluded automatically.
|
|
186
|
+
for (let i = 0; i < argsNode.namedChildCount; i++) {
|
|
187
|
+
const child = argsNode.namedChild(i);
|
|
162
188
|
if (!child) continue;
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
189
|
+
const label =
|
|
190
|
+
child.type === 'var' || child.type === 'atom'
|
|
191
|
+
? child.text
|
|
192
|
+
: // Placeholder for complex patterns so arity is preserved.
|
|
193
|
+
`_${i}`;
|
|
194
|
+
params.push({ name: label, kind: 'parameter', line: child.startPosition.row + 1 });
|
|
169
195
|
}
|
|
170
196
|
return params;
|
|
171
197
|
}
|
|
172
198
|
|
|
173
199
|
function handleDefine(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
174
200
|
// pp_define: -define(NAME, value).
|
|
201
|
+
// For parametric macros, the grammar wraps the name in a `macro_lhs(name, args)`
|
|
202
|
+
// node. Inside `macro_lhs` the name comes first, followed by `(`, the argument
|
|
203
|
+
// `var` children, and `)`. We must therefore try `atom` (lowercase macros,
|
|
204
|
+
// e.g. `-define(foo(X), X+1)`) before `var` (uppercase macros, e.g.
|
|
205
|
+
// `-define(FOO(X), X+1)`) — otherwise `findChild(.., 'var')` skips the leading
|
|
206
|
+
// atom and lands on the first argument variable, mislabeling the definition.
|
|
207
|
+
// Mirrors the Rust `handle_define` so both engines agree.
|
|
175
208
|
const nameNode =
|
|
176
209
|
findChild(node, 'var') || findChild(node, 'atom') || findChild(node, 'macro_lhs');
|
|
177
210
|
if (!nameNode) return;
|
|
178
211
|
|
|
179
212
|
const name =
|
|
180
213
|
nameNode.type === 'macro_lhs'
|
|
181
|
-
? (findChild(nameNode, 'var')?.text ?? nameNode.text)
|
|
214
|
+
? (findChild(nameNode, 'atom')?.text ?? findChild(nameNode, 'var')?.text ?? nameNode.text)
|
|
182
215
|
: nameNode.text;
|
|
183
216
|
|
|
184
217
|
ctx.definitions.push({
|
|
@@ -194,9 +227,15 @@ function handleInclude(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
194
227
|
if (!strNode) return;
|
|
195
228
|
|
|
196
229
|
const source = strNode.text.replace(/^"|"$/g, '');
|
|
230
|
+
// Preserve the distinction between local includes (`-include("foo.hrl")`)
|
|
231
|
+
// and OTP library includes (`-include_lib("kernel/include/file.hrl")`) so
|
|
232
|
+
// downstream consumers can apply the correct path-resolution strategy
|
|
233
|
+
// (local: relative to the source file; lib: relative to an OTP app root).
|
|
234
|
+
// Mirrors the Rust `handle_include` so both engines agree.
|
|
235
|
+
const kind = node.type === 'pp_include_lib' ? 'include_lib' : 'include';
|
|
197
236
|
ctx.imports.push({
|
|
198
237
|
source,
|
|
199
|
-
names: [
|
|
238
|
+
names: [kind],
|
|
200
239
|
line: node.startPosition.row + 1,
|
|
201
240
|
});
|
|
202
241
|
}
|
|
@@ -224,8 +263,12 @@ function handleImportAttr(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
224
263
|
}
|
|
225
264
|
|
|
226
265
|
function handleCall(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
227
|
-
// call: first child is function ref (atom or remote), then expr_args
|
|
228
|
-
|
|
266
|
+
// call: first named child is function ref (atom or remote), then expr_args.
|
|
267
|
+
// Using `namedChild(0)` rather than `child(0)` skips anonymous tokens
|
|
268
|
+
// (punctuation, keywords) so a future grammar revision that inserts a
|
|
269
|
+
// leading anonymous node won't silently drop the call. Mirrors the Rust
|
|
270
|
+
// `handle_call` so both engines emit the same set of calls.
|
|
271
|
+
const funcNode = node.namedChild(0);
|
|
229
272
|
if (!funcNode) return;
|
|
230
273
|
|
|
231
274
|
if (funcNode.type === 'atom' || funcNode.type === 'identifier') {
|
package/src/extractors/fsharp.ts
CHANGED
|
@@ -10,6 +10,13 @@ import { findChild, nodeEndLine } from './helpers.js';
|
|
|
10
10
|
/**
|
|
11
11
|
* Extract symbols from F# files.
|
|
12
12
|
*
|
|
13
|
+
* Grammar source: `tree-sitter-fsharp` v0.3.0 installed via a pinned GitHub
|
|
14
|
+
* tarball in `package.json` because the ionide/tree-sitter-fsharp project has
|
|
15
|
+
* no v0.3.0 release published to the npm registry. The cargo crate the native
|
|
16
|
+
* engine uses is also v0.3.0; both engines must stay aligned. Upgrading
|
|
17
|
+
* requires a manual edit of the tarball URL in `package.json` and
|
|
18
|
+
* `package-lock.json` — `npm update` will not bump this entry.
|
|
19
|
+
*
|
|
13
20
|
* tree-sitter-fsharp grammar notes:
|
|
14
21
|
* - named_module: top-level module declaration
|
|
15
22
|
* - function_declaration_left: LHS of `let name params = ...`
|
|
@@ -42,6 +49,14 @@ function walkFSharpNode(
|
|
|
42
49
|
case 'named_module':
|
|
43
50
|
nextModule = handleNamedModule(node, ctx);
|
|
44
51
|
break;
|
|
52
|
+
case 'module_defn':
|
|
53
|
+
// Nested signature module (`module Foo = ...`) in `.fsi` files,
|
|
54
|
+
// emitted by both the WASM (npm ionide tarball v0.3.0) and cargo
|
|
55
|
+
// v0.3.0 tree-sitter-fsharp signature grammars. Accumulate the
|
|
56
|
+
// dotted module path so nested `val` declarations are qualified
|
|
57
|
+
// as `Outer.Inner.foo` in parity with the native engine.
|
|
58
|
+
nextModule = handleModuleDefn(node, ctx, currentModule);
|
|
59
|
+
break;
|
|
45
60
|
case 'function_declaration_left':
|
|
46
61
|
handleFunctionDecl(node, ctx, currentModule);
|
|
47
62
|
break;
|
|
@@ -57,6 +72,9 @@ function walkFSharpNode(
|
|
|
57
72
|
case 'dot_expression':
|
|
58
73
|
handleDotExpression(node, ctx);
|
|
59
74
|
break;
|
|
75
|
+
case 'value_definition':
|
|
76
|
+
handleValueDefinition(node, ctx, currentModule);
|
|
77
|
+
break;
|
|
60
78
|
}
|
|
61
79
|
|
|
62
80
|
for (let i = 0; i < node.childCount; i++) {
|
|
@@ -79,6 +97,27 @@ function handleNamedModule(node: TreeSitterNode, ctx: ExtractorOutput): string |
|
|
|
79
97
|
return nameNode.text;
|
|
80
98
|
}
|
|
81
99
|
|
|
100
|
+
function handleModuleDefn(
|
|
101
|
+
node: TreeSitterNode,
|
|
102
|
+
ctx: ExtractorOutput,
|
|
103
|
+
currentModule: string | null,
|
|
104
|
+
): string | null {
|
|
105
|
+
// `module_defn` (cargo 0.3.0 signature grammar) wraps `module Foo = ...`
|
|
106
|
+
// sections inside an outer `namespace` or another module. The name is a
|
|
107
|
+
// direct `identifier` child.
|
|
108
|
+
const nameNode = findChild(node, 'identifier');
|
|
109
|
+
if (!nameNode) return currentModule;
|
|
110
|
+
|
|
111
|
+
const qualified = currentModule ? `${currentModule}.${nameNode.text}` : nameNode.text;
|
|
112
|
+
ctx.definitions.push({
|
|
113
|
+
name: qualified,
|
|
114
|
+
kind: 'module',
|
|
115
|
+
line: node.startPosition.row + 1,
|
|
116
|
+
endLine: nodeEndLine(node),
|
|
117
|
+
});
|
|
118
|
+
return qualified;
|
|
119
|
+
}
|
|
120
|
+
|
|
82
121
|
function handleFunctionDecl(
|
|
83
122
|
node: TreeSitterNode,
|
|
84
123
|
ctx: ExtractorOutput,
|
|
@@ -251,3 +290,68 @@ function handleDotExpression(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
251
290
|
ctx.calls.push(call);
|
|
252
291
|
}
|
|
253
292
|
}
|
|
293
|
+
|
|
294
|
+
// Handle `val name : type` declarations in `.fsi` signature files.
|
|
295
|
+
// The signature grammar reuses `value_definition` for `val` bindings,
|
|
296
|
+
// distinguished from the source grammar's `let` bindings by the first
|
|
297
|
+
// child being the literal `val` keyword. Source-file `value_definition`
|
|
298
|
+
// nodes (which start with `let`) are intentionally ignored to preserve
|
|
299
|
+
// `.fs` extractor parity.
|
|
300
|
+
function handleValueDefinition(
|
|
301
|
+
node: TreeSitterNode,
|
|
302
|
+
ctx: ExtractorOutput,
|
|
303
|
+
currentModule: string | null,
|
|
304
|
+
): void {
|
|
305
|
+
const first = node.child(0);
|
|
306
|
+
if (!first || first.type !== 'val') return;
|
|
307
|
+
|
|
308
|
+
const declLeft = findChild(node, 'value_declaration_left');
|
|
309
|
+
if (!declLeft) return;
|
|
310
|
+
|
|
311
|
+
const pattern = findChild(declLeft, 'identifier_pattern');
|
|
312
|
+
if (!pattern) return;
|
|
313
|
+
|
|
314
|
+
const ident =
|
|
315
|
+
findChild(findChild(pattern, 'long_identifier_or_op') ?? pattern, 'identifier') ??
|
|
316
|
+
findChild(pattern, 'identifier');
|
|
317
|
+
if (!ident) return;
|
|
318
|
+
|
|
319
|
+
// The npm and cargo tree-sitter-fsharp 0.3.0 grammars — though sharing a
|
|
320
|
+
// version tag — emit type signatures with different node shapes:
|
|
321
|
+
// • WASM (npm 0.3.0 ionide tarball): `function_type` is the explicit
|
|
322
|
+
// function-type kind, present as a direct child of `value_definition`
|
|
323
|
+
// for `a -> b` types; plain values (e.g. `val pi : float`) appear as
|
|
324
|
+
// `simple_type`.
|
|
325
|
+
// • Native (cargo 0.3.0): every type signature is wrapped in
|
|
326
|
+
// `curried_spec`. A function type contains one or more `arguments_spec`
|
|
327
|
+
// children; a plain value wraps a single `simple_type`.
|
|
328
|
+
// Classify as a function whenever `function_type` appears OR a
|
|
329
|
+
// `curried_spec` contains an `arguments_spec` child, so both engines stay
|
|
330
|
+
// in parity until the grammars converge.
|
|
331
|
+
let hasFunctionType = false;
|
|
332
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
333
|
+
const c = node.child(i);
|
|
334
|
+
if (!c) continue;
|
|
335
|
+
if (c.type === 'function_type') {
|
|
336
|
+
hasFunctionType = true;
|
|
337
|
+
break;
|
|
338
|
+
}
|
|
339
|
+
if (c.type === 'curried_spec') {
|
|
340
|
+
for (let j = 0; j < c.childCount; j++) {
|
|
341
|
+
if (c.child(j)?.type === 'arguments_spec') {
|
|
342
|
+
hasFunctionType = true;
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
if (hasFunctionType) break;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const name = currentModule ? `${currentModule}.${ident.text}` : ident.text;
|
|
351
|
+
ctx.definitions.push({
|
|
352
|
+
name,
|
|
353
|
+
kind: hasFunctionType ? 'function' : 'variable',
|
|
354
|
+
line: node.startPosition.row + 1,
|
|
355
|
+
endLine: nodeEndLine(node),
|
|
356
|
+
});
|
|
357
|
+
}
|
package/src/extractors/gleam.ts
CHANGED
|
@@ -85,12 +85,15 @@ function handleExternalFunction(node: TreeSitterNode, ctx: ExtractorOutput): voi
|
|
|
85
85
|
const nameNode = node.childForFieldName('name') || findChild(node, 'identifier');
|
|
86
86
|
if (!nameNode) return;
|
|
87
87
|
|
|
88
|
+
const params = extractParams(node);
|
|
89
|
+
|
|
88
90
|
ctx.definitions.push({
|
|
89
91
|
name: nameNode.text,
|
|
90
92
|
kind: 'function',
|
|
91
93
|
line: node.startPosition.row + 1,
|
|
92
94
|
endLine: nodeEndLine(node),
|
|
93
95
|
visibility: isPublic(node) ? 'public' : 'private',
|
|
96
|
+
children: params.length > 0 ? params : undefined,
|
|
94
97
|
});
|
|
95
98
|
}
|
|
96
99
|
|
|
@@ -198,14 +201,16 @@ function handleImport(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
198
201
|
}
|
|
199
202
|
|
|
200
203
|
function handleCall(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
201
|
-
const funcNode = node.childForFieldName('function') || node.
|
|
204
|
+
const funcNode = node.childForFieldName('function') || node.namedChild(0);
|
|
202
205
|
if (!funcNode) return;
|
|
203
206
|
|
|
204
207
|
if (funcNode.type === 'identifier' || funcNode.type === 'variable') {
|
|
205
208
|
ctx.calls.push({ name: funcNode.text, line: node.startPosition.row + 1 });
|
|
206
209
|
} else if (funcNode.type === 'field_access' || funcNode.type === 'module_select') {
|
|
207
210
|
const field = funcNode.childForFieldName('field') || funcNode.childForFieldName('label');
|
|
208
|
-
|
|
211
|
+
// Prefer the `record` field; fall back to first named child to skip
|
|
212
|
+
// anonymous punctuation tokens (the `.` between record and field).
|
|
213
|
+
const record = funcNode.childForFieldName('record') || funcNode.namedChild(0);
|
|
209
214
|
if (field) {
|
|
210
215
|
const call: Call = { name: field.text, line: node.startPosition.row + 1 };
|
|
211
216
|
if (record && record !== field) call.receiver = record.text;
|
package/src/extractors/groovy.ts
CHANGED
|
@@ -68,6 +68,7 @@ function walkGroovyNode(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
68
68
|
case 'method_invocation':
|
|
69
69
|
case 'call_expression':
|
|
70
70
|
case 'function_call':
|
|
71
|
+
case 'juxt_function_call':
|
|
71
72
|
handleGroovyCallExpr(node, ctx);
|
|
72
73
|
break;
|
|
73
74
|
case 'object_creation_expression':
|
|
@@ -140,14 +141,57 @@ function handleGroovyClassDecl(node: TreeSitterNode, ctx: ExtractorOutput): void
|
|
|
140
141
|
function handleGroovyInterfaceDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
141
142
|
const nameNode = node.childForFieldName('name');
|
|
142
143
|
if (!nameNode) return;
|
|
144
|
+
const ifaceName = nameNode.text;
|
|
143
145
|
|
|
144
146
|
ctx.definitions.push({
|
|
145
|
-
name:
|
|
147
|
+
name: ifaceName,
|
|
146
148
|
kind: 'interface',
|
|
147
149
|
line: node.startPosition.row + 1,
|
|
148
150
|
endLine: nodeEndLine(node),
|
|
149
151
|
visibility: extractModifierVisibility(node),
|
|
150
152
|
});
|
|
153
|
+
|
|
154
|
+
// `interface X extends Y, Z` — tree-sitter-groovy 0.1.x exposes parent
|
|
155
|
+
// interfaces via an unnamed `extends_interfaces` child (not a field), which
|
|
156
|
+
// wraps a `type_list` of `_type` nodes. Mirrors the Rust extractor.
|
|
157
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
158
|
+
const child = node.child(i);
|
|
159
|
+
if (child && child.type === 'extends_interfaces') {
|
|
160
|
+
collectGroovyParentInterfaces(child, ifaceName, ctx);
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function collectGroovyParentInterfaces(
|
|
167
|
+
parent: TreeSitterNode,
|
|
168
|
+
name: string,
|
|
169
|
+
ctx: ExtractorOutput,
|
|
170
|
+
): void {
|
|
171
|
+
// Use the current node's start line at each recursion level — matches the
|
|
172
|
+
// Rust `collect_interfaces` helper, which re-evaluates `start_line(interfaces)`
|
|
173
|
+
// for whatever node (`extends_interfaces` → `type_list`) is being processed.
|
|
174
|
+
const line = parent.startPosition.row + 1;
|
|
175
|
+
for (let i = 0; i < parent.childCount; i++) {
|
|
176
|
+
const child = parent.child(i);
|
|
177
|
+
if (!child) continue;
|
|
178
|
+
switch (child.type) {
|
|
179
|
+
case 'type_identifier':
|
|
180
|
+
case 'identifier':
|
|
181
|
+
case 'scoped_type_identifier': {
|
|
182
|
+
ctx.classes.push({ name, implements: child.text, line });
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
case 'generic_type': {
|
|
186
|
+
const inner = child.child(0)?.text;
|
|
187
|
+
if (inner) ctx.classes.push({ name, implements: inner, line });
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
case 'type_list':
|
|
191
|
+
collectGroovyParentInterfaces(child, name, ctx);
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
151
195
|
}
|
|
152
196
|
|
|
153
197
|
function handleGroovyEnumDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
@@ -81,19 +81,60 @@ function extractHaskellParams(funcNode: TreeSitterNode): SubDeclaration[] {
|
|
|
81
81
|
if (child.type === 'patterns' || child.type === 'parameter') {
|
|
82
82
|
for (let j = 0; j < child.childCount; j++) {
|
|
83
83
|
const pat = child.child(j);
|
|
84
|
-
if (pat
|
|
85
|
-
params.push({ name: pat.text, kind: 'parameter', line: pat.startPosition.row + 1 });
|
|
86
|
-
}
|
|
84
|
+
if (pat) collectHaskellPatternBindings(pat, params);
|
|
87
85
|
}
|
|
88
86
|
}
|
|
89
87
|
if (child.type === 'variable' && i > 0) {
|
|
90
|
-
// Pattern parameters after the function name
|
|
88
|
+
// Pattern parameters after the function name (no enclosing `patterns` node)
|
|
91
89
|
params.push({ name: child.text, kind: 'parameter', line: child.startPosition.row + 1 });
|
|
92
90
|
}
|
|
93
91
|
}
|
|
94
92
|
return params;
|
|
95
93
|
}
|
|
96
94
|
|
|
95
|
+
// Walk a pattern node and emit each bound variable (and `_` for wildcards) as a parameter.
|
|
96
|
+
// Container patterns — parens, constructor application, infix (cons), tuple, list, as, strict,
|
|
97
|
+
// irrefutable, qualified — are transparent: descend into their children. `record` is special:
|
|
98
|
+
// only the right-hand-side of each `field_pattern` is bound (the field name is not).
|
|
99
|
+
// Literals, bare constructors, and operators do not bind.
|
|
100
|
+
function collectHaskellPatternBindings(node: TreeSitterNode, out: SubDeclaration[]): void {
|
|
101
|
+
switch (node.type) {
|
|
102
|
+
case 'variable':
|
|
103
|
+
case 'identifier':
|
|
104
|
+
out.push({ name: node.text, kind: 'parameter', line: node.startPosition.row + 1 });
|
|
105
|
+
return;
|
|
106
|
+
case 'wildcard':
|
|
107
|
+
out.push({ name: '_', kind: 'parameter', line: node.startPosition.row + 1 });
|
|
108
|
+
return;
|
|
109
|
+
case 'parens':
|
|
110
|
+
case 'apply':
|
|
111
|
+
case 'infix':
|
|
112
|
+
case 'tuple':
|
|
113
|
+
case 'list':
|
|
114
|
+
case 'strict':
|
|
115
|
+
case 'irrefutable':
|
|
116
|
+
case 'as':
|
|
117
|
+
case 'qualified':
|
|
118
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
119
|
+
const c = node.child(i);
|
|
120
|
+
if (c) collectHaskellPatternBindings(c, out);
|
|
121
|
+
}
|
|
122
|
+
return;
|
|
123
|
+
case 'record':
|
|
124
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
125
|
+
const fp = node.child(i);
|
|
126
|
+
if (!fp || fp.type !== 'field_pattern') continue;
|
|
127
|
+
for (let j = 0; j < fp.childCount; j++) {
|
|
128
|
+
const g = fp.child(j);
|
|
129
|
+
if (g && g.type !== 'field_name') collectHaskellPatternBindings(g, out);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return;
|
|
133
|
+
default:
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
97
138
|
function handleHaskellBind(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
98
139
|
const nameNode = node.childForFieldName('name');
|
|
99
140
|
if (!nameNode) return;
|