@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/julia.ts
CHANGED
|
@@ -83,17 +83,49 @@ function handleModuleDef(node: TreeSitterNode, ctx: ExtractorOutput): string | n
|
|
|
83
83
|
return nameNode.text;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
function qualifyName(base: string, currentModule: string | null): string {
|
|
87
|
+
// For qualified names (`function Base.show ... end` inside `module Foo`,
|
|
88
|
+
// or short-form `Foo.bar(x, y) = x + y` inside `module Outer`), the LHS
|
|
89
|
+
// is a `scoped_identifier` already containing the qualifier — skip the
|
|
90
|
+
// module prefix to avoid producing `Foo.Base.show` / `Outer.Foo.bar`.
|
|
91
|
+
if (currentModule && !base.includes('.')) return `${currentModule}.${base}`;
|
|
92
|
+
return base;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Extract the call_expression from a function/macro definition's signature.
|
|
97
|
+
*
|
|
98
|
+
* tree-sitter-julia wraps the signature in a `signature` node whose direct
|
|
99
|
+
* children include the `call_expression` for the function name and parameters.
|
|
100
|
+
* `findChild` only inspects direct children, so we unwrap one level explicitly.
|
|
101
|
+
* Without this step, `findChild(node, 'call_expression')` on a
|
|
102
|
+
* `function_definition` would match the *body's* first call_expression
|
|
103
|
+
* (e.g. `println(...)` inside the body) instead of the signature.
|
|
104
|
+
*
|
|
105
|
+
* Grammar assumption: every `function_definition` / `macro_definition` emits a
|
|
106
|
+
* `signature` child in the current tree-sitter-julia grammar. The fallback to
|
|
107
|
+
* `findChild(node, 'call_expression')` exists only as a defensive measure for
|
|
108
|
+
* grammar drift — if it ever fires on a real definition, that fallback would
|
|
109
|
+
* silently match the first body call_expression and mis-record the function
|
|
110
|
+
* name. Callers must therefore treat a missing `signature` as a parser/grammar
|
|
111
|
+
* mismatch worth investigating, not as a routine code path.
|
|
112
|
+
*/
|
|
113
|
+
function signatureCall(node: TreeSitterNode): TreeSitterNode | null {
|
|
114
|
+
const sig = findChild(node, 'signature');
|
|
115
|
+
if (sig) return findChild(sig, 'call_expression');
|
|
116
|
+
return findChild(node, 'call_expression');
|
|
117
|
+
}
|
|
118
|
+
|
|
86
119
|
function handleFunctionDef(
|
|
87
120
|
node: TreeSitterNode,
|
|
88
121
|
ctx: ExtractorOutput,
|
|
89
122
|
currentModule: string | null,
|
|
90
123
|
): void {
|
|
91
|
-
|
|
92
|
-
const callSig = findChild(node, 'call_expression');
|
|
124
|
+
const callSig = signatureCall(node);
|
|
93
125
|
if (callSig) {
|
|
94
126
|
const funcNameNode = callSig.child(0);
|
|
95
127
|
if (funcNameNode) {
|
|
96
|
-
const name =
|
|
128
|
+
const name = qualifyName(funcNameNode.text, currentModule);
|
|
97
129
|
const params = extractJuliaParams(callSig);
|
|
98
130
|
ctx.definitions.push({
|
|
99
131
|
name,
|
|
@@ -110,9 +142,8 @@ function handleFunctionDef(
|
|
|
110
142
|
const nameNode = node.childForFieldName('name') || findChild(node, 'identifier');
|
|
111
143
|
if (!nameNode) return;
|
|
112
144
|
|
|
113
|
-
const name = currentModule ? `${currentModule}.${nameNode.text}` : nameNode.text;
|
|
114
145
|
ctx.definitions.push({
|
|
115
|
-
name,
|
|
146
|
+
name: qualifyName(nameNode.text, currentModule),
|
|
116
147
|
kind: 'function',
|
|
117
148
|
line: node.startPosition.row + 1,
|
|
118
149
|
endLine: nodeEndLine(node),
|
|
@@ -133,11 +164,10 @@ function handleAssignment(
|
|
|
133
164
|
const funcNameNode = lhs.child(0);
|
|
134
165
|
if (!funcNameNode) return;
|
|
135
166
|
|
|
136
|
-
const name = currentModule ? `${currentModule}.${funcNameNode.text}` : funcNameNode.text;
|
|
137
167
|
const params = extractJuliaParams(lhs);
|
|
138
168
|
|
|
139
169
|
ctx.definitions.push({
|
|
140
|
-
name,
|
|
170
|
+
name: qualifyName(funcNameNode.text, currentModule),
|
|
141
171
|
kind: 'function',
|
|
142
172
|
line: node.startPosition.row + 1,
|
|
143
173
|
endLine: nodeEndLine(node),
|
|
@@ -146,16 +176,74 @@ function handleAssignment(
|
|
|
146
176
|
}
|
|
147
177
|
}
|
|
148
178
|
|
|
179
|
+
/**
|
|
180
|
+
* Locate the base-name identifier within a `type_head` node.
|
|
181
|
+
*
|
|
182
|
+
* Handles plain identifiers, `Name <: Super` binary expressions, and
|
|
183
|
+
* parameterized forms like `Name{T}` / `Name{T} <: Super{T,1}` by recursing
|
|
184
|
+
* into wrapper kinds the Julia grammar actually emits for type heads
|
|
185
|
+
* (binary expressions, parametrized type expressions, parameterized
|
|
186
|
+
* identifiers). Returns `null` when no identifier can be located — callers
|
|
187
|
+
* should skip emitting a definition in that case.
|
|
188
|
+
*
|
|
189
|
+
* Note: `type_parameter_list` / `type_argument_list` are intentionally
|
|
190
|
+
* excluded — Julia's grammar uses `curly_expression` for `{T}` constructs,
|
|
191
|
+
* not those node kinds. Including them would risk recursing into a
|
|
192
|
+
* type-parameter list and returning a type variable (e.g. `T`) instead of
|
|
193
|
+
* the struct name if `findBaseName` were ever called on a node lacking a
|
|
194
|
+
* direct `identifier` child.
|
|
195
|
+
*/
|
|
196
|
+
const TYPE_HEAD_WRAPPERS: ReadonlySet<string> = new Set([
|
|
197
|
+
'binary_expression',
|
|
198
|
+
'parametrized_type_expression',
|
|
199
|
+
'parameterized_identifier',
|
|
200
|
+
]);
|
|
201
|
+
|
|
202
|
+
function findBaseName(node: TreeSitterNode): TreeSitterNode | null {
|
|
203
|
+
if (node.type === 'identifier') return node;
|
|
204
|
+
const direct = findChild(node, 'identifier');
|
|
205
|
+
if (direct) return direct;
|
|
206
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
207
|
+
const child = node.child(i);
|
|
208
|
+
if (!child) continue;
|
|
209
|
+
if (TYPE_HEAD_WRAPPERS.has(child.type)) {
|
|
210
|
+
const found = findBaseName(child);
|
|
211
|
+
if (found) return found;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
|
|
149
217
|
function handleStructDef(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
150
218
|
// struct_definition: struct type_head fields... end
|
|
219
|
+
// type_head wraps the name and optional supertype. The name may be a
|
|
220
|
+
// bare `identifier`, a parameterized form (e.g. `Vec{T}`), or either
|
|
221
|
+
// of those nested inside a `binary_expression` (`Name <: Super`).
|
|
151
222
|
const typeHead = findChild(node, 'type_head');
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
223
|
+
if (!typeHead) return;
|
|
224
|
+
|
|
225
|
+
let nameNode: TreeSitterNode | null;
|
|
226
|
+
let supertypeNode: TreeSitterNode | null = null;
|
|
227
|
+
|
|
228
|
+
const binary = findChild(typeHead, 'binary_expression');
|
|
229
|
+
if (binary) {
|
|
230
|
+
// Walk into each side of the binary expression to find the base-name
|
|
231
|
+
// identifier — handles parameterized forms like `Vec{T} <: AbstractArray{T,1}`.
|
|
232
|
+
const sides: TreeSitterNode[] = [];
|
|
233
|
+
for (let i = 0; i < binary.childCount; i++) {
|
|
234
|
+
const c = binary.child(i);
|
|
235
|
+
if (c && c.type !== 'operator') sides.push(c);
|
|
236
|
+
}
|
|
237
|
+
nameNode = sides[0] ? findBaseName(sides[0]) : null;
|
|
238
|
+
supertypeNode = sides[1] ? findBaseName(sides[1]) : null;
|
|
239
|
+
} else {
|
|
240
|
+
nameNode = findBaseName(typeHead);
|
|
241
|
+
}
|
|
242
|
+
|
|
155
243
|
if (!nameNode) return;
|
|
244
|
+
const structName = nameNode.text;
|
|
156
245
|
|
|
157
246
|
const children: SubDeclaration[] = [];
|
|
158
|
-
// Fields are typed_expression children of struct_definition
|
|
159
247
|
for (let i = 0; i < node.childCount; i++) {
|
|
160
248
|
const child = node.child(i);
|
|
161
249
|
if (!child) continue;
|
|
@@ -168,33 +256,24 @@ function handleStructDef(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
168
256
|
line: child.startPosition.row + 1,
|
|
169
257
|
});
|
|
170
258
|
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
|
|
259
|
+
} else if (child.type === 'identifier') {
|
|
260
|
+
// Plain identifier fields (no type annotation) appear as direct
|
|
261
|
+
// identifier children of struct_definition. The type_head is a
|
|
262
|
+
// separate node so there is nothing to filter out here.
|
|
174
263
|
children.push({ name: child.text, kind: 'property', line: child.startPosition.row + 1 });
|
|
175
264
|
}
|
|
176
265
|
}
|
|
177
266
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const child = subtypeExpr.child(i);
|
|
185
|
-
if (child?.type === 'identifier' && i > 0) {
|
|
186
|
-
ctx.classes.push({
|
|
187
|
-
name: nameNode.text,
|
|
188
|
-
extends: child.text,
|
|
189
|
-
line: node.startPosition.row + 1,
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
267
|
+
if (supertypeNode) {
|
|
268
|
+
ctx.classes.push({
|
|
269
|
+
name: structName,
|
|
270
|
+
extends: supertypeNode.text,
|
|
271
|
+
line: node.startPosition.row + 1,
|
|
272
|
+
});
|
|
194
273
|
}
|
|
195
274
|
|
|
196
275
|
ctx.definitions.push({
|
|
197
|
-
name:
|
|
276
|
+
name: structName,
|
|
198
277
|
kind: 'struct',
|
|
199
278
|
line: node.startPosition.row + 1,
|
|
200
279
|
endLine: nodeEndLine(node),
|
|
@@ -203,7 +282,14 @@ function handleStructDef(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
203
282
|
}
|
|
204
283
|
|
|
205
284
|
function handleAbstractDef(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
206
|
-
|
|
285
|
+
// abstract_definition: `abstract type` type_head `end`
|
|
286
|
+
// The identifier is nested inside `type_head` — possibly wrapped in a
|
|
287
|
+
// `Name <: Super` binary_expression or a `Name{T,...}` parameterized form.
|
|
288
|
+
// Mirror handleStructDef and skip rather than emit a garbled name when no
|
|
289
|
+
// base identifier can be located.
|
|
290
|
+
const typeHead = findChild(node, 'type_head');
|
|
291
|
+
if (!typeHead) return;
|
|
292
|
+
const nameNode = findBaseName(typeHead);
|
|
207
293
|
if (!nameNode) return;
|
|
208
294
|
|
|
209
295
|
ctx.definitions.push({
|
|
@@ -219,10 +305,17 @@ function handleMacroDef(
|
|
|
219
305
|
ctx: ExtractorOutput,
|
|
220
306
|
currentModule: string | null,
|
|
221
307
|
): void {
|
|
222
|
-
|
|
308
|
+
// macro_definition: `macro` signature/call_expression body `end`.
|
|
309
|
+
// The name lives in the same shape as a function signature — unwrap via
|
|
310
|
+
// signatureCall so we don't pick up an identifier from the body (e.g.
|
|
311
|
+
// `macro mymac(x) x end` would otherwise resolve to `@x`).
|
|
312
|
+
const callSig = signatureCall(node);
|
|
313
|
+
const nameNode =
|
|
314
|
+
callSig?.child(0) ?? node.childForFieldName('name') ?? findChild(node, 'identifier');
|
|
223
315
|
if (!nameNode) return;
|
|
224
316
|
|
|
225
|
-
const
|
|
317
|
+
const base = nameNode.text;
|
|
318
|
+
const name = currentModule ? `${currentModule}.@${base}` : `@${base}`;
|
|
226
319
|
ctx.definitions.push({
|
|
227
320
|
name,
|
|
228
321
|
kind: 'function',
|
|
@@ -232,19 +325,40 @@ function handleMacroDef(
|
|
|
232
325
|
}
|
|
233
326
|
|
|
234
327
|
function handleImport(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
328
|
+
// tree-sitter-julia shapes:
|
|
329
|
+
// `using LinearAlgebra` → using_statement [ using, identifier ]
|
|
330
|
+
// `import Foo.Bar` → import_statement [ import, scoped_identifier ]
|
|
331
|
+
// `import Base: show` → import_statement [ import, selected_import[Base, show] ]
|
|
332
|
+
// `import Foo.Bar: baz` → import_statement [ import, selected_import[scoped_identifier, baz] ]
|
|
235
333
|
const names: string[] = [];
|
|
236
334
|
let source = '';
|
|
237
335
|
|
|
238
336
|
for (let i = 0; i < node.childCount; i++) {
|
|
239
337
|
const child = node.child(i);
|
|
240
338
|
if (!child) continue;
|
|
241
|
-
if (
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
) {
|
|
246
|
-
|
|
247
|
-
names.
|
|
339
|
+
if (child.type === 'identifier' || child.type === 'scoped_identifier') {
|
|
340
|
+
const txt = child.text;
|
|
341
|
+
if (!source) source = txt;
|
|
342
|
+
names.push(txt.split('.').pop() || txt);
|
|
343
|
+
} else if (child.type === 'selected_import') {
|
|
344
|
+
// First identifier-bearing node is the source module; the rest are
|
|
345
|
+
// imported names. The module may itself be a `scoped_identifier`
|
|
346
|
+
// (e.g. `import Foo.Bar: baz`) — handle it alongside bare
|
|
347
|
+
// `identifier` and use the trailing segment as the display name,
|
|
348
|
+
// mirroring the outer loop.
|
|
349
|
+
let first = true;
|
|
350
|
+
for (let j = 0; j < child.childCount; j++) {
|
|
351
|
+
const part = child.child(j);
|
|
352
|
+
if (!part) continue;
|
|
353
|
+
if (part.type !== 'identifier' && part.type !== 'scoped_identifier') continue;
|
|
354
|
+
const txt = part.text;
|
|
355
|
+
if (first) {
|
|
356
|
+
if (!source) source = txt;
|
|
357
|
+
first = false;
|
|
358
|
+
} else {
|
|
359
|
+
names.push(txt.split('.').pop() || txt);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
248
362
|
}
|
|
249
363
|
}
|
|
250
364
|
|
|
@@ -260,8 +374,15 @@ function handleImport(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
260
374
|
function handleCall(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
261
375
|
// Don't record if parent is assignment LHS (that's a function definition)
|
|
262
376
|
if (node.parent?.type === 'assignment' && node === node.parent.child(0)) return;
|
|
263
|
-
//
|
|
264
|
-
|
|
377
|
+
// Skip when this call is the signature of a function/macro definition.
|
|
378
|
+
// tree-sitter-julia wraps the signature in a `signature` node whose parent
|
|
379
|
+
// is `function_definition` or `macro_definition`. Body calls (e.g.
|
|
380
|
+
// `println(name)` inside `function greet ... end`) appear as descendants of
|
|
381
|
+
// the body, not as direct children of `signature`, so they are unaffected.
|
|
382
|
+
if (node.parent?.type === 'signature') {
|
|
383
|
+
const grand = node.parent.parent;
|
|
384
|
+
if (grand?.type === 'function_definition' || grand?.type === 'macro_definition') return;
|
|
385
|
+
}
|
|
265
386
|
|
|
266
387
|
const funcNode = node.child(0);
|
|
267
388
|
if (!funcNode) return;
|
package/src/extractors/kotlin.ts
CHANGED
|
@@ -147,11 +147,13 @@ function collectKotlinMethods(node: TreeSitterNode, className: string, ctx: Extr
|
|
|
147
147
|
if (!child || child.type !== 'function_declaration') continue;
|
|
148
148
|
const methName = findChild(child, 'simple_identifier');
|
|
149
149
|
if (methName) {
|
|
150
|
+
const params = extractKotlinParameters(child);
|
|
150
151
|
ctx.definitions.push({
|
|
151
152
|
name: `${className}.${methName.text}`,
|
|
152
153
|
kind: 'method',
|
|
153
154
|
line: child.startPosition.row + 1,
|
|
154
155
|
endLine: child.endPosition.row + 1,
|
|
156
|
+
children: params.length > 0 ? params : undefined,
|
|
155
157
|
visibility: extractModifierVisibility(child),
|
|
156
158
|
});
|
|
157
159
|
}
|
|
@@ -214,11 +216,13 @@ function handleKotlinObjectDecl(node: TreeSitterNode, ctx: ExtractorOutput): voi
|
|
|
214
216
|
if (child && child.type === 'function_declaration') {
|
|
215
217
|
const methName = findChild(child, 'simple_identifier');
|
|
216
218
|
if (methName) {
|
|
219
|
+
const params = extractKotlinParameters(child);
|
|
217
220
|
ctx.definitions.push({
|
|
218
221
|
name: `${nameNode.text}.${methName.text}`,
|
|
219
222
|
kind: 'method',
|
|
220
223
|
line: child.startPosition.row + 1,
|
|
221
224
|
endLine: child.endPosition.row + 1,
|
|
225
|
+
children: params.length > 0 ? params : undefined,
|
|
222
226
|
visibility: extractModifierVisibility(child),
|
|
223
227
|
});
|
|
224
228
|
}
|
package/src/extractors/objc.ts
CHANGED
|
@@ -55,6 +55,11 @@ function walkObjCNode(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
55
55
|
case 'preproc_import':
|
|
56
56
|
handleImport(node, ctx);
|
|
57
57
|
break;
|
|
58
|
+
// tree-sitter-objc v3 emits `module_import` for `@import Foundation;`
|
|
59
|
+
// statements. Older grammar revisions used `import_declaration`, so we
|
|
60
|
+
// accept both for forward/backward compatibility and keep behaviour
|
|
61
|
+
// aligned with `handle_at_import` on the Rust side.
|
|
62
|
+
case 'module_import':
|
|
58
63
|
case 'import_declaration':
|
|
59
64
|
handleAtImport(node, ctx);
|
|
60
65
|
break;
|
|
@@ -87,29 +92,46 @@ function handleClassInterface(node: TreeSitterNode, ctx: ExtractorOutput): void
|
|
|
87
92
|
const nameNode = node.childForFieldName('name') || findObjCDeclName(node);
|
|
88
93
|
if (!nameNode) return;
|
|
89
94
|
const name = nameNode.text;
|
|
95
|
+
// Categories declared as `@interface Foo (Cat)` arrive as `class_interface`
|
|
96
|
+
// with a `category` field (rather than the `category_interface` node type).
|
|
97
|
+
// Qualify the display name with `(Cat)` so symbols stay grouped per category
|
|
98
|
+
// and match the Rust extractor.
|
|
99
|
+
const category = node.childForFieldName('category');
|
|
100
|
+
const displayName = category ? `${name}(${category.text})` : name;
|
|
90
101
|
|
|
91
102
|
const members = collectClassMembers(node);
|
|
92
103
|
ctx.definitions.push({
|
|
93
|
-
name,
|
|
104
|
+
name: displayName,
|
|
94
105
|
kind: 'class',
|
|
95
106
|
line: node.startPosition.row + 1,
|
|
96
107
|
endLine: nodeEndLine(node),
|
|
97
108
|
children: members.length > 0 ? members : undefined,
|
|
98
109
|
});
|
|
99
110
|
|
|
100
|
-
// Superclass
|
|
111
|
+
// Superclass — keyed on the bare class name (categories don't have a superclass).
|
|
101
112
|
const superclass = node.childForFieldName('superclass');
|
|
102
113
|
if (superclass) {
|
|
103
114
|
ctx.classes.push({ name, extends: superclass.text, line: node.startPosition.row + 1 });
|
|
104
115
|
}
|
|
105
116
|
|
|
106
|
-
//
|
|
107
|
-
|
|
117
|
+
// Adopted protocols. tree-sitter-objc v3 wraps the adopted-protocol list in
|
|
118
|
+
// `parameterized_arguments` (not `protocol_qualifiers`, which was the v2
|
|
119
|
+
// grammar shape). Each child is wrapped in `type_name > type_identifier`;
|
|
120
|
+
// fall back to a bare `identifier`/`type_identifier` for older grammars.
|
|
121
|
+
const protocols = findChild(node, 'parameterized_arguments');
|
|
108
122
|
if (protocols) {
|
|
109
123
|
for (let i = 0; i < protocols.childCount; i++) {
|
|
110
124
|
const proto = protocols.child(i);
|
|
111
|
-
if (proto
|
|
112
|
-
|
|
125
|
+
if (!proto) continue;
|
|
126
|
+
let protoName: string | null = null;
|
|
127
|
+
if (proto.type === 'type_name') {
|
|
128
|
+
const inner = findChild(proto, 'type_identifier') || findChild(proto, 'identifier');
|
|
129
|
+
if (inner) protoName = inner.text;
|
|
130
|
+
} else if (proto.type === 'identifier' || proto.type === 'type_identifier') {
|
|
131
|
+
protoName = proto.text;
|
|
132
|
+
}
|
|
133
|
+
if (protoName) {
|
|
134
|
+
ctx.classes.push({ name, implements: protoName, line: node.startPosition.row + 1 });
|
|
113
135
|
}
|
|
114
136
|
}
|
|
115
137
|
}
|
|
@@ -118,9 +140,14 @@ function handleClassInterface(node: TreeSitterNode, ctx: ExtractorOutput): void
|
|
|
118
140
|
function handleClassImplementation(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
119
141
|
const nameNode = node.childForFieldName('name') || findObjCDeclName(node);
|
|
120
142
|
if (!nameNode) return;
|
|
143
|
+
// Categories declared as `@implementation Foo (Cat)` arrive as
|
|
144
|
+
// `class_implementation` with a `category` field. Mirror the Rust extractor
|
|
145
|
+
// and qualify the display name with `(Cat)`.
|
|
146
|
+
const category = node.childForFieldName('category');
|
|
147
|
+
const displayName = category ? `${nameNode.text}(${category.text})` : nameNode.text;
|
|
121
148
|
|
|
122
149
|
ctx.definitions.push({
|
|
123
|
-
name:
|
|
150
|
+
name: displayName,
|
|
124
151
|
kind: 'class',
|
|
125
152
|
line: node.startPosition.row + 1,
|
|
126
153
|
endLine: nodeEndLine(node),
|
|
@@ -285,7 +312,20 @@ function handleTypedef(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
285
312
|
// ── Call handlers ─────────────────────────────────────────────────────────
|
|
286
313
|
|
|
287
314
|
function handleCCallExpr(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
288
|
-
|
|
315
|
+
// tree-sitter-objc does not expose a `function` field on `call_expression`,
|
|
316
|
+
// so the named-field lookup almost always misses. Fall back to the first
|
|
317
|
+
// `identifier` / `field_expression` child to mirror `handle_c_call_expr` in
|
|
318
|
+
// `crates/codegraph-core/src/extractors/objc.rs` and keep engine parity.
|
|
319
|
+
let funcNode = node.childForFieldName('function');
|
|
320
|
+
if (!funcNode) {
|
|
321
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
322
|
+
const child = node.child(i);
|
|
323
|
+
if (child && (child.type === 'identifier' || child.type === 'field_expression')) {
|
|
324
|
+
funcNode = child;
|
|
325
|
+
break;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
289
329
|
if (!funcNode) return;
|
|
290
330
|
const call: Call = { name: '', line: node.startPosition.row + 1 };
|
|
291
331
|
if (funcNode.type === 'field_expression') {
|
|
@@ -302,10 +342,33 @@ function handleCCallExpr(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
302
342
|
function handleMessageExpr(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
303
343
|
// [receiver selector:arg ...]
|
|
304
344
|
const receiver = node.childForFieldName('receiver');
|
|
305
|
-
const selector = node.childForFieldName('selector');
|
|
306
|
-
if (!selector) return;
|
|
307
345
|
|
|
308
|
-
|
|
346
|
+
// tree-sitter-objc v3 does not expose a `selector` field on
|
|
347
|
+
// `message_expression`; instead every keyword identifier has the `method`
|
|
348
|
+
// field. Assemble the selector by joining `method` children with `:`,
|
|
349
|
+
// appending a trailing `:` when the message has at least one colon
|
|
350
|
+
// (keyword form). Mirrors `build_message_selector` in
|
|
351
|
+
// `crates/codegraph-core/src/extractors/objc.rs`.
|
|
352
|
+
const parts: string[] = [];
|
|
353
|
+
let hasColon = false;
|
|
354
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
355
|
+
const child = node.child(i);
|
|
356
|
+
if (!child) continue;
|
|
357
|
+
const fieldName = node.fieldNameForChild(i);
|
|
358
|
+
if (fieldName === 'method') parts.push(child.text);
|
|
359
|
+
if (child.type === ':') hasColon = true;
|
|
360
|
+
}
|
|
361
|
+
let name: string;
|
|
362
|
+
if (parts.length > 0) {
|
|
363
|
+
name = hasColon ? `${parts.join(':')}:` : parts.join(':');
|
|
364
|
+
} else {
|
|
365
|
+
// Fallback: some grammar revisions expose a `selector` field.
|
|
366
|
+
const selector = node.childForFieldName('selector');
|
|
367
|
+
if (!selector) return;
|
|
368
|
+
name = selector.text;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const call: Call = { name, line: node.startPosition.row + 1 };
|
|
309
372
|
if (receiver) call.receiver = receiver.text;
|
|
310
373
|
ctx.calls.push(call);
|
|
311
374
|
}
|
|
@@ -313,29 +376,25 @@ function handleMessageExpr(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
313
376
|
// ── Helpers ───────────────────────────────────────────────────────────────
|
|
314
377
|
|
|
315
378
|
function buildSelector(methodNode: TreeSitterNode): string | null {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
//
|
|
379
|
+
// tree-sitter-objc v3 does not expose a `selector` field; the selector is
|
|
380
|
+
// assembled from the leading `identifier` keywords. Multi-keyword forms
|
|
381
|
+
// look like `setName:(...)x age:(...)y` and appear as flat
|
|
382
|
+
// `identifier` + `method_parameter` children directly under the method
|
|
383
|
+
// node (not wrapped in `keyword_selector`). Mirrors `build_selector` in
|
|
384
|
+
// `crates/codegraph-core/src/extractors/objc.rs`.
|
|
320
385
|
const parts: string[] = [];
|
|
386
|
+
let hasParams = false;
|
|
321
387
|
for (let i = 0; i < methodNode.childCount; i++) {
|
|
322
388
|
const child = methodNode.child(i);
|
|
323
389
|
if (!child) continue;
|
|
324
|
-
if (child.type === '
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
const kwName = kw.childForFieldName('keyword');
|
|
329
|
-
if (kwName) parts.push(kwName.text);
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
if (child.type === 'identifier' && i === 1) {
|
|
334
|
-
// Simple unary selector
|
|
335
|
-
return child.text;
|
|
390
|
+
if (child.type === 'identifier') {
|
|
391
|
+
parts.push(child.text);
|
|
392
|
+
} else if (child.type === 'method_parameter') {
|
|
393
|
+
hasParams = true;
|
|
336
394
|
}
|
|
337
395
|
}
|
|
338
|
-
|
|
396
|
+
if (parts.length === 0) return null;
|
|
397
|
+
return hasParams ? `${parts.join(':')}:` : parts.join(':');
|
|
339
398
|
}
|
|
340
399
|
|
|
341
400
|
function findObjCParentClass(node: TreeSitterNode): string | null {
|
|
@@ -349,7 +408,14 @@ function findObjCParentClass(node: TreeSitterNode): string | null {
|
|
|
349
408
|
current.type === 'category_implementation'
|
|
350
409
|
) {
|
|
351
410
|
const nameNode = current.childForFieldName('name') || findObjCDeclName(current);
|
|
352
|
-
|
|
411
|
+
if (!nameNode) return null;
|
|
412
|
+
// Categories: include `(Cat)` so methods are grouped per category.
|
|
413
|
+
// Two categories on the same class can declare same-named methods, so
|
|
414
|
+
// qualifying the parent name keeps the symbols disambiguated. Mirrors
|
|
415
|
+
// `find_objc_parent_class` in `crates/codegraph-core/src/extractors/objc.rs`.
|
|
416
|
+
const category = current.childForFieldName('category');
|
|
417
|
+
if (category) return `${nameNode.text}(${category.text})`;
|
|
418
|
+
return nameNode.text;
|
|
353
419
|
}
|
|
354
420
|
current = current.parent;
|
|
355
421
|
}
|
|
@@ -381,32 +447,65 @@ function collectClassMembers(classNode: TreeSitterNode): SubDeclaration[] {
|
|
|
381
447
|
}
|
|
382
448
|
}
|
|
383
449
|
if (child.type === 'property_declaration') {
|
|
384
|
-
const propName = child
|
|
450
|
+
const propName = extractPropertyName(child);
|
|
385
451
|
if (propName) {
|
|
386
|
-
members.push({ name: propName
|
|
452
|
+
members.push({ name: propName, kind: 'property', line: child.startPosition.row + 1 });
|
|
387
453
|
}
|
|
388
454
|
}
|
|
389
455
|
}
|
|
390
456
|
return members;
|
|
391
457
|
}
|
|
392
458
|
|
|
459
|
+
/**
|
|
460
|
+
* Extract the property name from `@property (...) Type *foo;`. The v3 grammar
|
|
461
|
+
* does not expose `name` as a named field on `property_declaration`; instead
|
|
462
|
+
* the identifier nests under `struct_declaration > struct_declarator >
|
|
463
|
+
* [pointer_declarator >] identifier`. Mirrors `extract_property_name` in
|
|
464
|
+
* `crates/codegraph-core/src/extractors/objc.rs`.
|
|
465
|
+
*/
|
|
466
|
+
function extractPropertyName(propNode: TreeSitterNode): string | null {
|
|
467
|
+
const structDecl = findChild(propNode, 'struct_declaration');
|
|
468
|
+
if (!structDecl) return null;
|
|
469
|
+
for (let i = 0; i < structDecl.childCount; i++) {
|
|
470
|
+
const child = structDecl.child(i);
|
|
471
|
+
if (!child || child.type !== 'struct_declarator') continue;
|
|
472
|
+
const id = findIdentifierDeep(child);
|
|
473
|
+
if (id) return id.text;
|
|
474
|
+
}
|
|
475
|
+
return null;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function findIdentifierDeep(node: TreeSitterNode): TreeSitterNode | null {
|
|
479
|
+
if (node.type === 'identifier') return node;
|
|
480
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
481
|
+
const child = node.child(i);
|
|
482
|
+
if (!child) continue;
|
|
483
|
+
const found = findIdentifierDeep(child);
|
|
484
|
+
if (found) return found;
|
|
485
|
+
}
|
|
486
|
+
return null;
|
|
487
|
+
}
|
|
488
|
+
|
|
393
489
|
function extractMethodParams(methodNode: TreeSitterNode): SubDeclaration[] {
|
|
490
|
+
// The v3 grammar emits flat `method_parameter` children under the method
|
|
491
|
+
// node; the parameter name is the last `identifier` inside each
|
|
492
|
+
// `method_parameter`. Mirrors `extract_method_params` in
|
|
493
|
+
// `crates/codegraph-core/src/extractors/objc.rs`.
|
|
394
494
|
const params: SubDeclaration[] = [];
|
|
395
495
|
for (let i = 0; i < methodNode.childCount; i++) {
|
|
396
496
|
const child = methodNode.child(i);
|
|
397
|
-
if (!child || child.type !== '
|
|
497
|
+
if (!child || child.type !== 'method_parameter') continue;
|
|
498
|
+
let nameNode: TreeSitterNode | null = null;
|
|
398
499
|
for (let j = 0; j < child.childCount; j++) {
|
|
399
|
-
const
|
|
400
|
-
if (
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
}
|
|
409
|
-
}
|
|
500
|
+
const inner = child.child(j);
|
|
501
|
+
if (inner && inner.type === 'identifier') nameNode = inner;
|
|
502
|
+
}
|
|
503
|
+
if (nameNode) {
|
|
504
|
+
params.push({
|
|
505
|
+
name: nameNode.text,
|
|
506
|
+
kind: 'parameter',
|
|
507
|
+
line: nameNode.startPosition.row + 1,
|
|
508
|
+
});
|
|
410
509
|
}
|
|
411
510
|
}
|
|
412
511
|
return params;
|
|
@@ -420,12 +519,37 @@ function extractCParams(paramListNode: TreeSitterNode | null): SubDeclaration[]
|
|
|
420
519
|
if (!param || param.type !== 'parameter_declaration') continue;
|
|
421
520
|
const nameNode = param.childForFieldName('declarator');
|
|
422
521
|
if (nameNode) {
|
|
423
|
-
const name =
|
|
424
|
-
nameNode.type === 'identifier'
|
|
425
|
-
? nameNode.text
|
|
426
|
-
: (findChild(nameNode, 'identifier')?.text ?? nameNode.text);
|
|
522
|
+
const name = unwrapObjCDeclaratorName(nameNode);
|
|
427
523
|
params.push({ name, kind: 'parameter', line: param.startPosition.row + 1 });
|
|
428
524
|
}
|
|
429
525
|
}
|
|
430
526
|
return params;
|
|
431
527
|
}
|
|
528
|
+
|
|
529
|
+
const OBJC_DECLARATOR_WRAPPERS = new Set([
|
|
530
|
+
'pointer_declarator',
|
|
531
|
+
'reference_declarator',
|
|
532
|
+
'array_declarator',
|
|
533
|
+
'parenthesized_declarator',
|
|
534
|
+
'function_declarator',
|
|
535
|
+
]);
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Drill through pointer/array/reference/parenthesized/function declarator
|
|
539
|
+
* wrappers to recover the bare parameter identifier. Without this the WASM
|
|
540
|
+
* extractor emitted raw declarator text (e.g. `*argv[]` or `callback(int x)`)
|
|
541
|
+
* while native unwrapped to `argv` / `callback`, producing cross-engine
|
|
542
|
+
* `contains` divergence on C-style parameters such as
|
|
543
|
+
* `int main(int argc, const char *argv[])` and function-type parameters such
|
|
544
|
+
* as `void process(int callback(int))`.
|
|
545
|
+
*/
|
|
546
|
+
function unwrapObjCDeclaratorName(node: TreeSitterNode): string {
|
|
547
|
+
let current: TreeSitterNode | null = node;
|
|
548
|
+
while (current && OBJC_DECLARATOR_WRAPPERS.has(current.type)) {
|
|
549
|
+
current = current.childForFieldName('declarator');
|
|
550
|
+
}
|
|
551
|
+
if (current?.type === 'identifier' || current?.type === 'field_identifier') {
|
|
552
|
+
return current.text;
|
|
553
|
+
}
|
|
554
|
+
return current?.text ?? node.text;
|
|
555
|
+
}
|