@optave/codegraph 3.6.0 → 3.8.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.
Files changed (186) hide show
  1. package/README.md +32 -16
  2. package/dist/ast-analysis/engine.d.ts.map +1 -1
  3. package/dist/ast-analysis/engine.js +158 -1
  4. package/dist/ast-analysis/engine.js.map +1 -1
  5. package/dist/ast-analysis/rules/javascript.d.ts.map +1 -1
  6. package/dist/ast-analysis/rules/javascript.js +0 -1
  7. package/dist/ast-analysis/rules/javascript.js.map +1 -1
  8. package/dist/ast-analysis/visitors/ast-store-visitor.d.ts.map +1 -1
  9. package/dist/ast-analysis/visitors/ast-store-visitor.js +2 -75
  10. package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
  11. package/dist/cli/commands/ast.js +2 -2
  12. package/dist/cli/commands/ast.js.map +1 -1
  13. package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
  14. package/dist/domain/graph/builder/pipeline.js +128 -6
  15. package/dist/domain/graph/builder/pipeline.js.map +1 -1
  16. package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
  17. package/dist/domain/graph/builder/stages/build-edges.js +101 -1
  18. package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
  19. package/dist/domain/graph/builder/stages/collect-files.d.ts.map +1 -1
  20. package/dist/domain/graph/builder/stages/collect-files.js +17 -5
  21. package/dist/domain/graph/builder/stages/collect-files.js.map +1 -1
  22. package/dist/domain/graph/builder/stages/detect-changes.d.ts.map +1 -1
  23. package/dist/domain/graph/builder/stages/detect-changes.js +98 -50
  24. package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
  25. package/dist/domain/graph/builder/stages/finalize.d.ts.map +1 -1
  26. package/dist/domain/graph/builder/stages/finalize.js +32 -5
  27. package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
  28. package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
  29. package/dist/domain/graph/builder/stages/insert-nodes.js +20 -7
  30. package/dist/domain/graph/builder/stages/insert-nodes.js.map +1 -1
  31. package/dist/domain/parser.d.ts +1 -1
  32. package/dist/domain/parser.d.ts.map +1 -1
  33. package/dist/domain/parser.js +129 -3
  34. package/dist/domain/parser.js.map +1 -1
  35. package/dist/extractors/clojure.d.ts +12 -0
  36. package/dist/extractors/clojure.d.ts.map +1 -0
  37. package/dist/extractors/clojure.js +245 -0
  38. package/dist/extractors/clojure.js.map +1 -0
  39. package/dist/extractors/cuda.d.ts +11 -0
  40. package/dist/extractors/cuda.d.ts.map +1 -0
  41. package/dist/extractors/cuda.js +302 -0
  42. package/dist/extractors/cuda.js.map +1 -0
  43. package/dist/extractors/dart.d.ts +6 -0
  44. package/dist/extractors/dart.d.ts.map +1 -0
  45. package/dist/extractors/dart.js +277 -0
  46. package/dist/extractors/dart.js.map +1 -0
  47. package/dist/extractors/elixir.d.ts +9 -0
  48. package/dist/extractors/elixir.d.ts.map +1 -0
  49. package/dist/extractors/elixir.js +223 -0
  50. package/dist/extractors/elixir.js.map +1 -0
  51. package/dist/extractors/erlang.d.ts +14 -0
  52. package/dist/extractors/erlang.d.ts.map +1 -0
  53. package/dist/extractors/erlang.js +239 -0
  54. package/dist/extractors/erlang.js.map +1 -0
  55. package/dist/extractors/fsharp.d.ts +13 -0
  56. package/dist/extractors/fsharp.d.ts.map +1 -0
  57. package/dist/extractors/fsharp.js +218 -0
  58. package/dist/extractors/fsharp.js.map +1 -0
  59. package/dist/extractors/gleam.d.ts +14 -0
  60. package/dist/extractors/gleam.d.ts.map +1 -0
  61. package/dist/extractors/gleam.js +229 -0
  62. package/dist/extractors/gleam.js.map +1 -0
  63. package/dist/extractors/groovy.d.ts +10 -0
  64. package/dist/extractors/groovy.d.ts.map +1 -0
  65. package/dist/extractors/groovy.js +304 -0
  66. package/dist/extractors/groovy.js.map +1 -0
  67. package/dist/extractors/haskell.d.ts +8 -0
  68. package/dist/extractors/haskell.d.ts.map +1 -0
  69. package/dist/extractors/haskell.js +217 -0
  70. package/dist/extractors/haskell.js.map +1 -0
  71. package/dist/extractors/index.d.ts +17 -0
  72. package/dist/extractors/index.d.ts.map +1 -1
  73. package/dist/extractors/index.js +17 -0
  74. package/dist/extractors/index.js.map +1 -1
  75. package/dist/extractors/julia.d.ts +16 -0
  76. package/dist/extractors/julia.d.ts.map +1 -0
  77. package/dist/extractors/julia.js +287 -0
  78. package/dist/extractors/julia.js.map +1 -0
  79. package/dist/extractors/lua.d.ts +6 -0
  80. package/dist/extractors/lua.d.ts.map +1 -0
  81. package/dist/extractors/lua.js +162 -0
  82. package/dist/extractors/lua.js.map +1 -0
  83. package/dist/extractors/objc.d.ts +9 -0
  84. package/dist/extractors/objc.d.ts.map +1 -0
  85. package/dist/extractors/objc.js +406 -0
  86. package/dist/extractors/objc.js.map +1 -0
  87. package/dist/extractors/ocaml.d.ts +6 -0
  88. package/dist/extractors/ocaml.d.ts.map +1 -0
  89. package/dist/extractors/ocaml.js +310 -0
  90. package/dist/extractors/ocaml.js.map +1 -0
  91. package/dist/extractors/r.d.ts +13 -0
  92. package/dist/extractors/r.d.ts.map +1 -0
  93. package/dist/extractors/r.js +251 -0
  94. package/dist/extractors/r.js.map +1 -0
  95. package/dist/extractors/solidity.d.ts +9 -0
  96. package/dist/extractors/solidity.d.ts.map +1 -0
  97. package/dist/extractors/solidity.js +374 -0
  98. package/dist/extractors/solidity.js.map +1 -0
  99. package/dist/extractors/verilog.d.ts +9 -0
  100. package/dist/extractors/verilog.d.ts.map +1 -0
  101. package/dist/extractors/verilog.js +286 -0
  102. package/dist/extractors/verilog.js.map +1 -0
  103. package/dist/extractors/zig.d.ts +9 -0
  104. package/dist/extractors/zig.d.ts.map +1 -0
  105. package/dist/extractors/zig.js +276 -0
  106. package/dist/extractors/zig.js.map +1 -0
  107. package/dist/features/ast.d.ts.map +1 -1
  108. package/dist/features/ast.js +1 -2
  109. package/dist/features/ast.js.map +1 -1
  110. package/dist/features/cfg.d.ts +1 -1
  111. package/dist/features/cfg.d.ts.map +1 -1
  112. package/dist/features/cfg.js +6 -51
  113. package/dist/features/cfg.js.map +1 -1
  114. package/dist/graph/algorithms/bfs.d.ts +2 -0
  115. package/dist/graph/algorithms/bfs.d.ts.map +1 -1
  116. package/dist/graph/algorithms/bfs.js +27 -0
  117. package/dist/graph/algorithms/bfs.js.map +1 -1
  118. package/dist/graph/algorithms/centrality.d.ts +2 -0
  119. package/dist/graph/algorithms/centrality.d.ts.map +1 -1
  120. package/dist/graph/algorithms/centrality.js +28 -0
  121. package/dist/graph/algorithms/centrality.js.map +1 -1
  122. package/dist/graph/algorithms/louvain.d.ts +3 -4
  123. package/dist/graph/algorithms/louvain.d.ts.map +1 -1
  124. package/dist/graph/algorithms/louvain.js +29 -0
  125. package/dist/graph/algorithms/louvain.js.map +1 -1
  126. package/dist/graph/algorithms/shortest-path.d.ts +2 -0
  127. package/dist/graph/algorithms/shortest-path.d.ts.map +1 -1
  128. package/dist/graph/algorithms/shortest-path.js +18 -1
  129. package/dist/graph/algorithms/shortest-path.js.map +1 -1
  130. package/dist/types.d.ts +122 -2
  131. package/dist/types.d.ts.map +1 -1
  132. package/grammars/tree-sitter-clojure.wasm +0 -0
  133. package/grammars/tree-sitter-cuda.wasm +0 -0
  134. package/grammars/tree-sitter-dart.wasm +0 -0
  135. package/grammars/tree-sitter-elixir.wasm +0 -0
  136. package/grammars/tree-sitter-erlang.wasm +0 -0
  137. package/grammars/tree-sitter-fsharp.wasm +0 -0
  138. package/grammars/tree-sitter-gleam.wasm +0 -0
  139. package/grammars/tree-sitter-groovy.wasm +0 -0
  140. package/grammars/tree-sitter-haskell.wasm +0 -0
  141. package/grammars/tree-sitter-julia.wasm +0 -0
  142. package/grammars/tree-sitter-lua.wasm +0 -0
  143. package/grammars/tree-sitter-objc.wasm +0 -0
  144. package/grammars/tree-sitter-ocaml.wasm +0 -0
  145. package/grammars/tree-sitter-ocaml_interface.wasm +0 -0
  146. package/grammars/tree-sitter-r.wasm +0 -0
  147. package/grammars/tree-sitter-solidity.wasm +0 -0
  148. package/grammars/tree-sitter-verilog.wasm +0 -0
  149. package/grammars/tree-sitter-zig.wasm +0 -0
  150. package/package.json +24 -7
  151. package/src/ast-analysis/engine.ts +183 -1
  152. package/src/ast-analysis/rules/javascript.ts +0 -1
  153. package/src/ast-analysis/visitors/ast-store-visitor.ts +2 -75
  154. package/src/cli/commands/ast.ts +2 -2
  155. package/src/domain/graph/builder/pipeline.ts +142 -6
  156. package/src/domain/graph/builder/stages/build-edges.ts +158 -1
  157. package/src/domain/graph/builder/stages/collect-files.ts +18 -7
  158. package/src/domain/graph/builder/stages/detect-changes.ts +109 -55
  159. package/src/domain/graph/builder/stages/finalize.ts +39 -9
  160. package/src/domain/graph/builder/stages/insert-nodes.ts +18 -7
  161. package/src/domain/parser.ts +161 -1
  162. package/src/extractors/clojure.ts +273 -0
  163. package/src/extractors/cuda.ts +316 -0
  164. package/src/extractors/dart.ts +304 -0
  165. package/src/extractors/elixir.ts +251 -0
  166. package/src/extractors/erlang.ts +252 -0
  167. package/src/extractors/fsharp.ts +253 -0
  168. package/src/extractors/gleam.ts +246 -0
  169. package/src/extractors/groovy.ts +332 -0
  170. package/src/extractors/haskell.ts +235 -0
  171. package/src/extractors/index.ts +17 -0
  172. package/src/extractors/julia.ts +318 -0
  173. package/src/extractors/lua.ts +169 -0
  174. package/src/extractors/objc.ts +431 -0
  175. package/src/extractors/ocaml.ts +337 -0
  176. package/src/extractors/r.ts +253 -0
  177. package/src/extractors/solidity.ts +398 -0
  178. package/src/extractors/verilog.ts +315 -0
  179. package/src/extractors/zig.ts +294 -0
  180. package/src/features/ast.ts +1 -2
  181. package/src/features/cfg.ts +6 -51
  182. package/src/graph/algorithms/bfs.ts +34 -0
  183. package/src/graph/algorithms/centrality.ts +30 -0
  184. package/src/graph/algorithms/louvain.ts +31 -4
  185. package/src/graph/algorithms/shortest-path.ts +20 -1
  186. package/src/types.ts +123 -2
@@ -0,0 +1,431 @@
1
+ import type {
2
+ Call,
3
+ ExtractorOutput,
4
+ SubDeclaration,
5
+ TreeSitterNode,
6
+ TreeSitterTree,
7
+ } from '../types.js';
8
+ import { findChild, nodeEndLine } from './helpers.js';
9
+
10
+ /**
11
+ * Extract symbols from Objective-C files.
12
+ *
13
+ * The tree-sitter-objc grammar extends C with @interface, @implementation,
14
+ * @protocol, method declarations, #import, and message expressions.
15
+ */
16
+ export function extractObjCSymbols(tree: TreeSitterTree, _filePath: string): ExtractorOutput {
17
+ const ctx: ExtractorOutput = {
18
+ definitions: [],
19
+ calls: [],
20
+ imports: [],
21
+ classes: [],
22
+ exports: [],
23
+ typeMap: new Map(),
24
+ };
25
+
26
+ walkObjCNode(tree.rootNode, ctx);
27
+ return ctx;
28
+ }
29
+
30
+ function walkObjCNode(node: TreeSitterNode, ctx: ExtractorOutput): void {
31
+ switch (node.type) {
32
+ case 'class_interface':
33
+ handleClassInterface(node, ctx);
34
+ break;
35
+ case 'class_implementation':
36
+ handleClassImplementation(node, ctx);
37
+ break;
38
+ case 'protocol_declaration':
39
+ handleProtocolDecl(node, ctx);
40
+ break;
41
+ case 'category_interface':
42
+ handleCategoryInterface(node, ctx);
43
+ break;
44
+ case 'category_implementation':
45
+ handleCategoryImplementation(node, ctx);
46
+ break;
47
+ case 'method_declaration':
48
+ case 'method_definition':
49
+ handleMethodDecl(node, ctx);
50
+ break;
51
+ case 'function_definition':
52
+ handleFunctionDef(node, ctx);
53
+ break;
54
+ case 'preproc_include':
55
+ case 'preproc_import':
56
+ handleImport(node, ctx);
57
+ break;
58
+ case 'import_declaration':
59
+ handleAtImport(node, ctx);
60
+ break;
61
+ case 'struct_specifier':
62
+ handleStructSpecifier(node, ctx);
63
+ break;
64
+ case 'enum_specifier':
65
+ handleEnumSpecifier(node, ctx);
66
+ break;
67
+ case 'type_definition':
68
+ handleTypedef(node, ctx);
69
+ break;
70
+ case 'call_expression':
71
+ handleCCallExpr(node, ctx);
72
+ break;
73
+ case 'message_expression':
74
+ handleMessageExpr(node, ctx);
75
+ break;
76
+ }
77
+
78
+ for (let i = 0; i < node.childCount; i++) {
79
+ const child = node.child(i);
80
+ if (child) walkObjCNode(child, ctx);
81
+ }
82
+ }
83
+
84
+ // ── ObjC class/protocol handlers ──────────────────────────────────────────
85
+
86
+ function handleClassInterface(node: TreeSitterNode, ctx: ExtractorOutput): void {
87
+ const nameNode = node.childForFieldName('name') || findObjCDeclName(node);
88
+ if (!nameNode) return;
89
+ const name = nameNode.text;
90
+
91
+ const members = collectClassMembers(node);
92
+ ctx.definitions.push({
93
+ name,
94
+ kind: 'class',
95
+ line: node.startPosition.row + 1,
96
+ endLine: nodeEndLine(node),
97
+ children: members.length > 0 ? members : undefined,
98
+ });
99
+
100
+ // Superclass
101
+ const superclass = node.childForFieldName('superclass');
102
+ if (superclass) {
103
+ ctx.classes.push({ name, extends: superclass.text, line: node.startPosition.row + 1 });
104
+ }
105
+
106
+ // Protocols
107
+ const protocols = findChild(node, 'protocol_qualifiers');
108
+ if (protocols) {
109
+ for (let i = 0; i < protocols.childCount; i++) {
110
+ const proto = protocols.child(i);
111
+ if (proto && proto.type === 'identifier') {
112
+ ctx.classes.push({ name, implements: proto.text, line: node.startPosition.row + 1 });
113
+ }
114
+ }
115
+ }
116
+ }
117
+
118
+ function handleClassImplementation(node: TreeSitterNode, ctx: ExtractorOutput): void {
119
+ const nameNode = node.childForFieldName('name') || findObjCDeclName(node);
120
+ if (!nameNode) return;
121
+
122
+ ctx.definitions.push({
123
+ name: nameNode.text,
124
+ kind: 'class',
125
+ line: node.startPosition.row + 1,
126
+ endLine: nodeEndLine(node),
127
+ });
128
+ }
129
+
130
+ function handleProtocolDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
131
+ const nameNode = node.childForFieldName('name') || findObjCDeclName(node);
132
+ if (!nameNode) return;
133
+
134
+ ctx.definitions.push({
135
+ name: nameNode.text,
136
+ kind: 'interface',
137
+ line: node.startPosition.row + 1,
138
+ endLine: nodeEndLine(node),
139
+ });
140
+ }
141
+
142
+ function handleCategoryInterface(node: TreeSitterNode, ctx: ExtractorOutput): void {
143
+ const nameNode = node.childForFieldName('name') || findObjCDeclName(node);
144
+ if (!nameNode) return;
145
+ const category = node.childForFieldName('category');
146
+ const catName = category ? `${nameNode.text}(${category.text})` : nameNode.text;
147
+
148
+ ctx.definitions.push({
149
+ name: catName,
150
+ kind: 'class',
151
+ line: node.startPosition.row + 1,
152
+ endLine: nodeEndLine(node),
153
+ });
154
+ }
155
+
156
+ function handleCategoryImplementation(node: TreeSitterNode, ctx: ExtractorOutput): void {
157
+ const nameNode = node.childForFieldName('name') || findObjCDeclName(node);
158
+ if (!nameNode) return;
159
+ const category = node.childForFieldName('category');
160
+ const catName = category ? `${nameNode.text}(${category.text})` : nameNode.text;
161
+
162
+ ctx.definitions.push({
163
+ name: catName,
164
+ kind: 'class',
165
+ line: node.startPosition.row + 1,
166
+ endLine: nodeEndLine(node),
167
+ });
168
+ }
169
+
170
+ // ── Method / function handlers ────────────────────────────────────────────
171
+
172
+ function handleMethodDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
173
+ const selector = buildSelector(node);
174
+ if (!selector) return;
175
+
176
+ const parentClass = findObjCParentClass(node);
177
+ const fullName = parentClass ? `${parentClass}.${selector}` : selector;
178
+
179
+ const params = extractMethodParams(node);
180
+ ctx.definitions.push({
181
+ name: fullName,
182
+ kind: 'method',
183
+ line: node.startPosition.row + 1,
184
+ endLine: nodeEndLine(node),
185
+ children: params.length > 0 ? params : undefined,
186
+ });
187
+ }
188
+
189
+ function handleFunctionDef(node: TreeSitterNode, ctx: ExtractorOutput): void {
190
+ const declarator = node.childForFieldName('declarator');
191
+ if (!declarator) return;
192
+ const funcDeclarator =
193
+ declarator.type === 'function_declarator'
194
+ ? declarator
195
+ : findChild(declarator, 'function_declarator');
196
+ if (!funcDeclarator) return;
197
+ const nameNode = funcDeclarator.childForFieldName('declarator');
198
+ if (!nameNode) return;
199
+
200
+ const params = extractCParams(funcDeclarator.childForFieldName('parameters'));
201
+ ctx.definitions.push({
202
+ name: nameNode.text,
203
+ kind: 'function',
204
+ line: node.startPosition.row + 1,
205
+ endLine: nodeEndLine(node),
206
+ children: params.length > 0 ? params : undefined,
207
+ });
208
+ }
209
+
210
+ // ── Import handlers ───────────────────────────────────────────────────────
211
+
212
+ function handleImport(node: TreeSitterNode, ctx: ExtractorOutput): void {
213
+ const pathNode = node.childForFieldName('path');
214
+ if (!pathNode) return;
215
+ const raw = pathNode.text;
216
+ const source = raw.replace(/^["<]|[">]$/g, '');
217
+ const lastName = source.split('/').pop() ?? source;
218
+ ctx.imports.push({
219
+ source,
220
+ names: [lastName],
221
+ line: node.startPosition.row + 1,
222
+ cInclude: true,
223
+ });
224
+ }
225
+
226
+ function handleAtImport(node: TreeSitterNode, ctx: ExtractorOutput): void {
227
+ // @import Foundation;
228
+ const moduleNode = node.childForFieldName('module') || findChild(node, 'identifier');
229
+ if (moduleNode) {
230
+ ctx.imports.push({
231
+ source: moduleNode.text,
232
+ names: [moduleNode.text],
233
+ line: node.startPosition.row + 1,
234
+ });
235
+ }
236
+ }
237
+
238
+ // ── C-compatible type handlers ────────────────────────────────────────────
239
+
240
+ function handleStructSpecifier(node: TreeSitterNode, ctx: ExtractorOutput): void {
241
+ const nameNode = node.childForFieldName('name');
242
+ if (!nameNode) return;
243
+ ctx.definitions.push({
244
+ name: nameNode.text,
245
+ kind: 'struct',
246
+ line: node.startPosition.row + 1,
247
+ endLine: nodeEndLine(node),
248
+ });
249
+ }
250
+
251
+ function handleEnumSpecifier(node: TreeSitterNode, ctx: ExtractorOutput): void {
252
+ const nameNode = node.childForFieldName('name');
253
+ if (!nameNode) return;
254
+ ctx.definitions.push({
255
+ name: nameNode.text,
256
+ kind: 'enum',
257
+ line: node.startPosition.row + 1,
258
+ endLine: nodeEndLine(node),
259
+ });
260
+ }
261
+
262
+ function handleTypedef(node: TreeSitterNode, ctx: ExtractorOutput): void {
263
+ let name: string | undefined;
264
+ for (let i = node.childCount - 1; i >= 0; i--) {
265
+ const child = node.child(i);
266
+ if (
267
+ child &&
268
+ (child.type === 'type_identifier' ||
269
+ child.type === 'identifier' ||
270
+ child.type === 'primitive_type')
271
+ ) {
272
+ name = child.text;
273
+ break;
274
+ }
275
+ }
276
+ if (!name) return;
277
+ ctx.definitions.push({
278
+ name,
279
+ kind: 'type',
280
+ line: node.startPosition.row + 1,
281
+ endLine: nodeEndLine(node),
282
+ });
283
+ }
284
+
285
+ // ── Call handlers ─────────────────────────────────────────────────────────
286
+
287
+ function handleCCallExpr(node: TreeSitterNode, ctx: ExtractorOutput): void {
288
+ const funcNode = node.childForFieldName('function');
289
+ if (!funcNode) return;
290
+ const call: Call = { name: '', line: node.startPosition.row + 1 };
291
+ if (funcNode.type === 'field_expression') {
292
+ const field = funcNode.childForFieldName('field');
293
+ const argument = funcNode.childForFieldName('argument');
294
+ if (field) call.name = field.text;
295
+ if (argument) call.receiver = argument.text;
296
+ } else {
297
+ call.name = funcNode.text;
298
+ }
299
+ if (call.name) ctx.calls.push(call);
300
+ }
301
+
302
+ function handleMessageExpr(node: TreeSitterNode, ctx: ExtractorOutput): void {
303
+ // [receiver selector:arg ...]
304
+ const receiver = node.childForFieldName('receiver');
305
+ const selector = node.childForFieldName('selector');
306
+ if (!selector) return;
307
+
308
+ const call: Call = { name: selector.text, line: node.startPosition.row + 1 };
309
+ if (receiver) call.receiver = receiver.text;
310
+ ctx.calls.push(call);
311
+ }
312
+
313
+ // ── Helpers ───────────────────────────────────────────────────────────────
314
+
315
+ function buildSelector(methodNode: TreeSitterNode): string | null {
316
+ const selector = methodNode.childForFieldName('selector');
317
+ if (selector) return selector.text;
318
+
319
+ // Build selector from keyword children: initWith:name:
320
+ const parts: string[] = [];
321
+ for (let i = 0; i < methodNode.childCount; i++) {
322
+ const child = methodNode.child(i);
323
+ if (!child) continue;
324
+ if (child.type === 'keyword_selector') {
325
+ for (let j = 0; j < child.childCount; j++) {
326
+ const kw = child.child(j);
327
+ if (kw && kw.type === 'keyword_declarator') {
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;
336
+ }
337
+ }
338
+ return parts.length > 0 ? `${parts.join(':')}:` : null;
339
+ }
340
+
341
+ function findObjCParentClass(node: TreeSitterNode): string | null {
342
+ let current = node.parent;
343
+ while (current) {
344
+ if (
345
+ current.type === 'class_interface' ||
346
+ current.type === 'class_implementation' ||
347
+ current.type === 'protocol_declaration' ||
348
+ current.type === 'category_interface' ||
349
+ current.type === 'category_implementation'
350
+ ) {
351
+ const nameNode = current.childForFieldName('name') || findObjCDeclName(current);
352
+ return nameNode ? nameNode.text : null;
353
+ }
354
+ current = current.parent;
355
+ }
356
+ return null;
357
+ }
358
+
359
+ /**
360
+ * Find the declaration name for ObjC constructs where the grammar does not
361
+ * expose the class/protocol name as a named field. The identifier appears
362
+ * right after the `@interface` / `@implementation` / `@protocol` keyword.
363
+ */
364
+ function findObjCDeclName(node: TreeSitterNode): TreeSitterNode | null {
365
+ for (let i = 0; i < node.childCount; i++) {
366
+ const child = node.child(i);
367
+ if (child && child.type === 'identifier') return child;
368
+ }
369
+ return null;
370
+ }
371
+
372
+ function collectClassMembers(classNode: TreeSitterNode): SubDeclaration[] {
373
+ const members: SubDeclaration[] = [];
374
+ for (let i = 0; i < classNode.childCount; i++) {
375
+ const child = classNode.child(i);
376
+ if (!child) continue;
377
+ if (child.type === 'method_declaration' || child.type === 'method_definition') {
378
+ const sel = buildSelector(child);
379
+ if (sel) {
380
+ members.push({ name: sel, kind: 'method', line: child.startPosition.row + 1 });
381
+ }
382
+ }
383
+ if (child.type === 'property_declaration') {
384
+ const propName = child.childForFieldName('name');
385
+ if (propName) {
386
+ members.push({ name: propName.text, kind: 'property', line: child.startPosition.row + 1 });
387
+ }
388
+ }
389
+ }
390
+ return members;
391
+ }
392
+
393
+ function extractMethodParams(methodNode: TreeSitterNode): SubDeclaration[] {
394
+ const params: SubDeclaration[] = [];
395
+ for (let i = 0; i < methodNode.childCount; i++) {
396
+ const child = methodNode.child(i);
397
+ if (!child || child.type !== 'keyword_selector') continue;
398
+ for (let j = 0; j < child.childCount; j++) {
399
+ const kw = child.child(j);
400
+ if (kw && kw.type === 'keyword_declarator') {
401
+ const nameNode = kw.childForFieldName('name');
402
+ if (nameNode) {
403
+ params.push({
404
+ name: nameNode.text,
405
+ kind: 'parameter',
406
+ line: nameNode.startPosition.row + 1,
407
+ });
408
+ }
409
+ }
410
+ }
411
+ }
412
+ return params;
413
+ }
414
+
415
+ function extractCParams(paramListNode: TreeSitterNode | null): SubDeclaration[] {
416
+ const params: SubDeclaration[] = [];
417
+ if (!paramListNode) return params;
418
+ for (let i = 0; i < paramListNode.childCount; i++) {
419
+ const param = paramListNode.child(i);
420
+ if (!param || param.type !== 'parameter_declaration') continue;
421
+ const nameNode = param.childForFieldName('declarator');
422
+ if (nameNode) {
423
+ const name =
424
+ nameNode.type === 'identifier'
425
+ ? nameNode.text
426
+ : (findChild(nameNode, 'identifier')?.text ?? nameNode.text);
427
+ params.push({ name, kind: 'parameter', line: param.startPosition.row + 1 });
428
+ }
429
+ }
430
+ return params;
431
+ }