@mduenas/codegraph 0.4.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 (192) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +641 -0
  3. package/dist/bin/codegraph.d.ts +20 -0
  4. package/dist/bin/codegraph.d.ts.map +1 -0
  5. package/dist/bin/codegraph.js +704 -0
  6. package/dist/bin/codegraph.js.map +1 -0
  7. package/dist/config.d.ts +51 -0
  8. package/dist/config.d.ts.map +1 -0
  9. package/dist/config.js +291 -0
  10. package/dist/config.js.map +1 -0
  11. package/dist/context/formatter.d.ts +30 -0
  12. package/dist/context/formatter.d.ts.map +1 -0
  13. package/dist/context/formatter.js +244 -0
  14. package/dist/context/formatter.js.map +1 -0
  15. package/dist/context/index.d.ts +86 -0
  16. package/dist/context/index.d.ts.map +1 -0
  17. package/dist/context/index.js +402 -0
  18. package/dist/context/index.js.map +1 -0
  19. package/dist/db/index.d.ts +64 -0
  20. package/dist/db/index.d.ts.map +1 -0
  21. package/dist/db/index.js +170 -0
  22. package/dist/db/index.js.map +1 -0
  23. package/dist/db/migrations.d.ts +44 -0
  24. package/dist/db/migrations.d.ts.map +1 -0
  25. package/dist/db/migrations.js +105 -0
  26. package/dist/db/migrations.js.map +1 -0
  27. package/dist/db/queries.d.ts +148 -0
  28. package/dist/db/queries.d.ts.map +1 -0
  29. package/dist/db/queries.js +669 -0
  30. package/dist/db/queries.js.map +1 -0
  31. package/dist/directory.d.ts +45 -0
  32. package/dist/directory.d.ts.map +1 -0
  33. package/dist/directory.js +191 -0
  34. package/dist/directory.js.map +1 -0
  35. package/dist/errors.d.ts +136 -0
  36. package/dist/errors.d.ts.map +1 -0
  37. package/dist/errors.js +219 -0
  38. package/dist/errors.js.map +1 -0
  39. package/dist/extraction/grammars.d.ts +36 -0
  40. package/dist/extraction/grammars.d.ts.map +1 -0
  41. package/dist/extraction/grammars.js +181 -0
  42. package/dist/extraction/grammars.js.map +1 -0
  43. package/dist/extraction/index.d.ts +91 -0
  44. package/dist/extraction/index.d.ts.map +1 -0
  45. package/dist/extraction/index.js +493 -0
  46. package/dist/extraction/index.js.map +1 -0
  47. package/dist/extraction/tree-sitter.d.ts +176 -0
  48. package/dist/extraction/tree-sitter.d.ts.map +1 -0
  49. package/dist/extraction/tree-sitter.js +1798 -0
  50. package/dist/extraction/tree-sitter.js.map +1 -0
  51. package/dist/graph/index.d.ts +8 -0
  52. package/dist/graph/index.d.ts.map +1 -0
  53. package/dist/graph/index.js +13 -0
  54. package/dist/graph/index.js.map +1 -0
  55. package/dist/graph/queries.d.ts +106 -0
  56. package/dist/graph/queries.d.ts.map +1 -0
  57. package/dist/graph/queries.js +355 -0
  58. package/dist/graph/queries.js.map +1 -0
  59. package/dist/graph/traversal.d.ts +127 -0
  60. package/dist/graph/traversal.d.ts.map +1 -0
  61. package/dist/graph/traversal.js +465 -0
  62. package/dist/graph/traversal.js.map +1 -0
  63. package/dist/index.d.ts +496 -0
  64. package/dist/index.d.ts.map +1 -0
  65. package/dist/index.js +818 -0
  66. package/dist/index.js.map +1 -0
  67. package/dist/installer/banner.d.ts +40 -0
  68. package/dist/installer/banner.d.ts.map +1 -0
  69. package/dist/installer/banner.js +162 -0
  70. package/dist/installer/banner.js.map +1 -0
  71. package/dist/installer/claude-md-template.d.ts +10 -0
  72. package/dist/installer/claude-md-template.d.ts.map +1 -0
  73. package/dist/installer/claude-md-template.js +46 -0
  74. package/dist/installer/claude-md-template.js.map +1 -0
  75. package/dist/installer/config-writer.d.ts +36 -0
  76. package/dist/installer/config-writer.d.ts.map +1 -0
  77. package/dist/installer/config-writer.js +282 -0
  78. package/dist/installer/config-writer.js.map +1 -0
  79. package/dist/installer/index.d.ts +13 -0
  80. package/dist/installer/index.d.ts.map +1 -0
  81. package/dist/installer/index.js +155 -0
  82. package/dist/installer/index.js.map +1 -0
  83. package/dist/installer/prompts.d.ts +18 -0
  84. package/dist/installer/prompts.d.ts.map +1 -0
  85. package/dist/installer/prompts.js +113 -0
  86. package/dist/installer/prompts.js.map +1 -0
  87. package/dist/mcp/index.d.ts +64 -0
  88. package/dist/mcp/index.d.ts.map +1 -0
  89. package/dist/mcp/index.js +207 -0
  90. package/dist/mcp/index.js.map +1 -0
  91. package/dist/mcp/tools.d.ts +93 -0
  92. package/dist/mcp/tools.d.ts.map +1 -0
  93. package/dist/mcp/tools.js +442 -0
  94. package/dist/mcp/tools.js.map +1 -0
  95. package/dist/mcp/transport.d.ts +89 -0
  96. package/dist/mcp/transport.d.ts.map +1 -0
  97. package/dist/mcp/transport.js +170 -0
  98. package/dist/mcp/transport.js.map +1 -0
  99. package/dist/resolution/frameworks/csharp.d.ts +8 -0
  100. package/dist/resolution/frameworks/csharp.d.ts.map +1 -0
  101. package/dist/resolution/frameworks/csharp.js +274 -0
  102. package/dist/resolution/frameworks/csharp.js.map +1 -0
  103. package/dist/resolution/frameworks/express.d.ts +8 -0
  104. package/dist/resolution/frameworks/express.d.ts.map +1 -0
  105. package/dist/resolution/frameworks/express.js +208 -0
  106. package/dist/resolution/frameworks/express.js.map +1 -0
  107. package/dist/resolution/frameworks/go.d.ts +8 -0
  108. package/dist/resolution/frameworks/go.d.ts.map +1 -0
  109. package/dist/resolution/frameworks/go.js +225 -0
  110. package/dist/resolution/frameworks/go.js.map +1 -0
  111. package/dist/resolution/frameworks/index.d.ts +33 -0
  112. package/dist/resolution/frameworks/index.d.ts.map +1 -0
  113. package/dist/resolution/frameworks/index.js +113 -0
  114. package/dist/resolution/frameworks/index.js.map +1 -0
  115. package/dist/resolution/frameworks/java.d.ts +8 -0
  116. package/dist/resolution/frameworks/java.d.ts.map +1 -0
  117. package/dist/resolution/frameworks/java.js +239 -0
  118. package/dist/resolution/frameworks/java.js.map +1 -0
  119. package/dist/resolution/frameworks/laravel.d.ts +13 -0
  120. package/dist/resolution/frameworks/laravel.d.ts.map +1 -0
  121. package/dist/resolution/frameworks/laravel.js +198 -0
  122. package/dist/resolution/frameworks/laravel.js.map +1 -0
  123. package/dist/resolution/frameworks/python.d.ts +10 -0
  124. package/dist/resolution/frameworks/python.d.ts.map +1 -0
  125. package/dist/resolution/frameworks/python.js +331 -0
  126. package/dist/resolution/frameworks/python.js.map +1 -0
  127. package/dist/resolution/frameworks/react.d.ts +8 -0
  128. package/dist/resolution/frameworks/react.d.ts.map +1 -0
  129. package/dist/resolution/frameworks/react.js +294 -0
  130. package/dist/resolution/frameworks/react.js.map +1 -0
  131. package/dist/resolution/frameworks/ruby.d.ts +8 -0
  132. package/dist/resolution/frameworks/ruby.d.ts.map +1 -0
  133. package/dist/resolution/frameworks/ruby.js +262 -0
  134. package/dist/resolution/frameworks/ruby.js.map +1 -0
  135. package/dist/resolution/frameworks/rust.d.ts +8 -0
  136. package/dist/resolution/frameworks/rust.d.ts.map +1 -0
  137. package/dist/resolution/frameworks/rust.js +222 -0
  138. package/dist/resolution/frameworks/rust.js.map +1 -0
  139. package/dist/resolution/frameworks/swift.d.ts +10 -0
  140. package/dist/resolution/frameworks/swift.d.ts.map +1 -0
  141. package/dist/resolution/frameworks/swift.js +486 -0
  142. package/dist/resolution/frameworks/swift.js.map +1 -0
  143. package/dist/resolution/import-resolver.d.ts +20 -0
  144. package/dist/resolution/import-resolver.d.ts.map +1 -0
  145. package/dist/resolution/import-resolver.js +445 -0
  146. package/dist/resolution/import-resolver.js.map +1 -0
  147. package/dist/resolution/index.d.ts +72 -0
  148. package/dist/resolution/index.d.ts.map +1 -0
  149. package/dist/resolution/index.js +301 -0
  150. package/dist/resolution/index.js.map +1 -0
  151. package/dist/resolution/name-matcher.d.ts +27 -0
  152. package/dist/resolution/name-matcher.d.ts.map +1 -0
  153. package/dist/resolution/name-matcher.js +210 -0
  154. package/dist/resolution/name-matcher.js.map +1 -0
  155. package/dist/resolution/types.d.ts +108 -0
  156. package/dist/resolution/types.d.ts.map +1 -0
  157. package/dist/resolution/types.js +8 -0
  158. package/dist/resolution/types.js.map +1 -0
  159. package/dist/sync/git-hooks.d.ts +66 -0
  160. package/dist/sync/git-hooks.d.ts.map +1 -0
  161. package/dist/sync/git-hooks.js +281 -0
  162. package/dist/sync/git-hooks.js.map +1 -0
  163. package/dist/sync/index.d.ts +13 -0
  164. package/dist/sync/index.d.ts.map +1 -0
  165. package/dist/sync/index.js +18 -0
  166. package/dist/sync/index.js.map +1 -0
  167. package/dist/types.d.ts +410 -0
  168. package/dist/types.d.ts.map +1 -0
  169. package/dist/types.js +165 -0
  170. package/dist/types.js.map +1 -0
  171. package/dist/utils.d.ts +116 -0
  172. package/dist/utils.d.ts.map +1 -0
  173. package/dist/utils.js +295 -0
  174. package/dist/utils.js.map +1 -0
  175. package/dist/vectors/embedder.d.ts +140 -0
  176. package/dist/vectors/embedder.d.ts.map +1 -0
  177. package/dist/vectors/embedder.js +336 -0
  178. package/dist/vectors/embedder.js.map +1 -0
  179. package/dist/vectors/index.d.ts +9 -0
  180. package/dist/vectors/index.d.ts.map +1 -0
  181. package/dist/vectors/index.js +20 -0
  182. package/dist/vectors/index.js.map +1 -0
  183. package/dist/vectors/manager.d.ts +119 -0
  184. package/dist/vectors/manager.d.ts.map +1 -0
  185. package/dist/vectors/manager.js +274 -0
  186. package/dist/vectors/manager.js.map +1 -0
  187. package/dist/vectors/search.d.ts +134 -0
  188. package/dist/vectors/search.d.ts.map +1 -0
  189. package/dist/vectors/search.js +409 -0
  190. package/dist/vectors/search.js.map +1 -0
  191. package/package.json +67 -0
  192. package/scripts/postinstall.js +68 -0
@@ -0,0 +1,1798 @@
1
+ "use strict";
2
+ /**
3
+ * Tree-sitter Parser Wrapper
4
+ *
5
+ * Handles parsing source code and extracting structural information.
6
+ */
7
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
+ if (k2 === undefined) k2 = k;
9
+ var desc = Object.getOwnPropertyDescriptor(m, k);
10
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
11
+ desc = { enumerable: true, get: function() { return m[k]; } };
12
+ }
13
+ Object.defineProperty(o, k2, desc);
14
+ }) : (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ o[k2] = m[k];
17
+ }));
18
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
19
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
20
+ }) : function(o, v) {
21
+ o["default"] = v;
22
+ });
23
+ var __importStar = (this && this.__importStar) || (function () {
24
+ var ownKeys = function(o) {
25
+ ownKeys = Object.getOwnPropertyNames || function (o) {
26
+ var ar = [];
27
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
28
+ return ar;
29
+ };
30
+ return ownKeys(o);
31
+ };
32
+ return function (mod) {
33
+ if (mod && mod.__esModule) return mod;
34
+ var result = {};
35
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
36
+ __setModuleDefault(result, mod);
37
+ return result;
38
+ };
39
+ })();
40
+ Object.defineProperty(exports, "__esModule", { value: true });
41
+ exports.LiquidExtractor = exports.TreeSitterExtractor = void 0;
42
+ exports.generateNodeId = generateNodeId;
43
+ exports.extractFromSource = extractFromSource;
44
+ const crypto = __importStar(require("crypto"));
45
+ const grammars_1 = require("./grammars");
46
+ /**
47
+ * Generate a unique node ID
48
+ *
49
+ * Uses a 32-character (128-bit) hash to avoid collisions when indexing
50
+ * large codebases with many files containing similar symbols.
51
+ */
52
+ function generateNodeId(filePath, kind, name, line) {
53
+ const hash = crypto
54
+ .createHash('sha256')
55
+ .update(`${filePath}:${kind}:${name}:${line}`)
56
+ .digest('hex')
57
+ .substring(0, 32);
58
+ return `${kind}:${hash}`;
59
+ }
60
+ /**
61
+ * Extract text from a syntax node
62
+ */
63
+ function getNodeText(node, source) {
64
+ return source.substring(node.startIndex, node.endIndex);
65
+ }
66
+ /**
67
+ * Find a child node by field name, with fallback to finding by type
68
+ */
69
+ function getChildByField(node, fieldName) {
70
+ // First try the field name
71
+ const byField = node.childForFieldName(fieldName);
72
+ if (byField)
73
+ return byField;
74
+ // Fall back to finding a named child with matching type (for languages like Kotlin
75
+ // where tree-sitter doesn't define fields for some node types)
76
+ for (let i = 0; i < node.namedChildCount; i++) {
77
+ const child = node.namedChild(i);
78
+ if (child?.type === fieldName) {
79
+ return child;
80
+ }
81
+ }
82
+ return null;
83
+ }
84
+ /**
85
+ * Get the docstring/comment preceding a node
86
+ */
87
+ function getPrecedingDocstring(node, source) {
88
+ let sibling = node.previousNamedSibling;
89
+ const comments = [];
90
+ while (sibling) {
91
+ if (sibling.type === 'comment' ||
92
+ sibling.type === 'line_comment' ||
93
+ sibling.type === 'block_comment' ||
94
+ sibling.type === 'documentation_comment') {
95
+ comments.unshift(getNodeText(sibling, source));
96
+ sibling = sibling.previousNamedSibling;
97
+ }
98
+ else {
99
+ break;
100
+ }
101
+ }
102
+ if (comments.length === 0)
103
+ return undefined;
104
+ // Clean up comment markers
105
+ return comments
106
+ .map((c) => c
107
+ .replace(/^\/\*\*?|\*\/$/g, '')
108
+ .replace(/^\/\/\s?/gm, '')
109
+ .replace(/^\s*\*\s?/gm, '')
110
+ .trim())
111
+ .join('\n')
112
+ .trim();
113
+ }
114
+ // =============================================================================
115
+ // Kotlin-specific Helper Functions
116
+ // =============================================================================
117
+ /**
118
+ * Check if a Kotlin node has a specific modifier
119
+ */
120
+ function kotlinHasModifier(node, modifier) {
121
+ for (let i = 0; i < node.childCount; i++) {
122
+ const child = node.child(i);
123
+ if (child?.type === 'modifiers' && child.text.includes(modifier)) {
124
+ return true;
125
+ }
126
+ }
127
+ return false;
128
+ }
129
+ /**
130
+ * Check if a Kotlin class_declaration is actually an interface
131
+ */
132
+ function isKotlinInterface(node) {
133
+ // Look for 'interface' keyword (not 'class')
134
+ for (let i = 0; i < node.childCount; i++) {
135
+ const child = node.child(i);
136
+ if (child?.type === 'interface') {
137
+ return true;
138
+ }
139
+ }
140
+ return false;
141
+ }
142
+ /**
143
+ * Check if a Kotlin class_declaration is an enum
144
+ */
145
+ function isKotlinEnum(node) {
146
+ // Look for 'enum' child node (it's a direct child, not inside modifiers)
147
+ for (let i = 0; i < node.childCount; i++) {
148
+ const child = node.child(i);
149
+ if (child?.type === 'enum') {
150
+ return true;
151
+ }
152
+ }
153
+ return false;
154
+ }
155
+ /**
156
+ * Check if a Kotlin class is abstract
157
+ */
158
+ function isKotlinAbstractClass(node) {
159
+ return kotlinHasModifier(node, 'abstract');
160
+ }
161
+ /**
162
+ * Language-specific extractors
163
+ */
164
+ const EXTRACTORS = {
165
+ typescript: {
166
+ functionTypes: ['function_declaration', 'arrow_function', 'function_expression'],
167
+ classTypes: ['class_declaration'],
168
+ methodTypes: ['method_definition', 'public_field_definition'],
169
+ interfaceTypes: ['interface_declaration'],
170
+ structTypes: [],
171
+ enumTypes: ['enum_declaration'],
172
+ importTypes: ['import_statement'],
173
+ callTypes: ['call_expression'],
174
+ nameField: 'name',
175
+ bodyField: 'body',
176
+ paramsField: 'parameters',
177
+ returnField: 'return_type',
178
+ getSignature: (node, source) => {
179
+ const params = getChildByField(node, 'parameters');
180
+ const returnType = getChildByField(node, 'return_type');
181
+ if (!params)
182
+ return undefined;
183
+ let sig = getNodeText(params, source);
184
+ if (returnType) {
185
+ sig += ': ' + getNodeText(returnType, source).replace(/^:\s*/, '');
186
+ }
187
+ return sig;
188
+ },
189
+ getVisibility: (node) => {
190
+ for (let i = 0; i < node.childCount; i++) {
191
+ const child = node.child(i);
192
+ if (child?.type === 'accessibility_modifier') {
193
+ const text = child.text;
194
+ if (text === 'public')
195
+ return 'public';
196
+ if (text === 'private')
197
+ return 'private';
198
+ if (text === 'protected')
199
+ return 'protected';
200
+ }
201
+ }
202
+ return undefined;
203
+ },
204
+ isExported: (node, source) => {
205
+ const parent = node.parent;
206
+ if (parent?.type === 'export_statement')
207
+ return true;
208
+ // Check for 'export' keyword before declaration
209
+ const text = source.substring(Math.max(0, node.startIndex - 10), node.startIndex);
210
+ return text.includes('export');
211
+ },
212
+ isAsync: (node) => {
213
+ for (let i = 0; i < node.childCount; i++) {
214
+ const child = node.child(i);
215
+ if (child?.type === 'async')
216
+ return true;
217
+ }
218
+ return false;
219
+ },
220
+ isStatic: (node) => {
221
+ for (let i = 0; i < node.childCount; i++) {
222
+ const child = node.child(i);
223
+ if (child?.type === 'static')
224
+ return true;
225
+ }
226
+ return false;
227
+ },
228
+ },
229
+ javascript: {
230
+ functionTypes: ['function_declaration', 'arrow_function', 'function_expression'],
231
+ classTypes: ['class_declaration'],
232
+ methodTypes: ['method_definition', 'field_definition'],
233
+ interfaceTypes: [],
234
+ structTypes: [],
235
+ enumTypes: [],
236
+ importTypes: ['import_statement'],
237
+ callTypes: ['call_expression'],
238
+ nameField: 'name',
239
+ bodyField: 'body',
240
+ paramsField: 'parameters',
241
+ getSignature: (node, source) => {
242
+ const params = getChildByField(node, 'parameters');
243
+ return params ? getNodeText(params, source) : undefined;
244
+ },
245
+ isExported: (node, source) => {
246
+ const parent = node.parent;
247
+ if (parent?.type === 'export_statement')
248
+ return true;
249
+ const text = source.substring(Math.max(0, node.startIndex - 10), node.startIndex);
250
+ return text.includes('export');
251
+ },
252
+ isAsync: (node) => {
253
+ for (let i = 0; i < node.childCount; i++) {
254
+ const child = node.child(i);
255
+ if (child?.type === 'async')
256
+ return true;
257
+ }
258
+ return false;
259
+ },
260
+ },
261
+ python: {
262
+ functionTypes: ['function_definition'],
263
+ classTypes: ['class_definition'],
264
+ methodTypes: ['function_definition'], // Methods are functions inside classes
265
+ interfaceTypes: [],
266
+ structTypes: [],
267
+ enumTypes: [],
268
+ importTypes: ['import_statement', 'import_from_statement'],
269
+ callTypes: ['call'],
270
+ nameField: 'name',
271
+ bodyField: 'body',
272
+ paramsField: 'parameters',
273
+ returnField: 'return_type',
274
+ getSignature: (node, source) => {
275
+ const params = getChildByField(node, 'parameters');
276
+ const returnType = getChildByField(node, 'return_type');
277
+ if (!params)
278
+ return undefined;
279
+ let sig = getNodeText(params, source);
280
+ if (returnType) {
281
+ sig += ' -> ' + getNodeText(returnType, source);
282
+ }
283
+ return sig;
284
+ },
285
+ isAsync: (node) => {
286
+ const prev = node.previousSibling;
287
+ return prev?.type === 'async';
288
+ },
289
+ isStatic: (node) => {
290
+ // Check for @staticmethod decorator
291
+ const prev = node.previousNamedSibling;
292
+ if (prev?.type === 'decorator') {
293
+ const text = prev.text;
294
+ return text.includes('staticmethod');
295
+ }
296
+ return false;
297
+ },
298
+ },
299
+ go: {
300
+ functionTypes: ['function_declaration'],
301
+ classTypes: [], // Go doesn't have classes
302
+ methodTypes: ['method_declaration'],
303
+ interfaceTypes: ['interface_type'],
304
+ structTypes: ['struct_type'],
305
+ enumTypes: [],
306
+ importTypes: ['import_declaration'],
307
+ callTypes: ['call_expression'],
308
+ nameField: 'name',
309
+ bodyField: 'body',
310
+ paramsField: 'parameters',
311
+ returnField: 'result',
312
+ getSignature: (node, source) => {
313
+ const params = getChildByField(node, 'parameters');
314
+ const result = getChildByField(node, 'result');
315
+ if (!params)
316
+ return undefined;
317
+ let sig = getNodeText(params, source);
318
+ if (result) {
319
+ sig += ' ' + getNodeText(result, source);
320
+ }
321
+ return sig;
322
+ },
323
+ },
324
+ rust: {
325
+ functionTypes: ['function_item'],
326
+ classTypes: [], // Rust has impl blocks
327
+ methodTypes: ['function_item'], // Methods are functions in impl blocks
328
+ interfaceTypes: ['trait_item'],
329
+ structTypes: ['struct_item'],
330
+ enumTypes: ['enum_item'],
331
+ importTypes: ['use_declaration'],
332
+ callTypes: ['call_expression'],
333
+ nameField: 'name',
334
+ bodyField: 'body',
335
+ paramsField: 'parameters',
336
+ returnField: 'return_type',
337
+ getSignature: (node, source) => {
338
+ const params = getChildByField(node, 'parameters');
339
+ const returnType = getChildByField(node, 'return_type');
340
+ if (!params)
341
+ return undefined;
342
+ let sig = getNodeText(params, source);
343
+ if (returnType) {
344
+ sig += ' -> ' + getNodeText(returnType, source);
345
+ }
346
+ return sig;
347
+ },
348
+ isAsync: (node) => {
349
+ for (let i = 0; i < node.childCount; i++) {
350
+ const child = node.child(i);
351
+ if (child?.type === 'async')
352
+ return true;
353
+ }
354
+ return false;
355
+ },
356
+ getVisibility: (node) => {
357
+ for (let i = 0; i < node.childCount; i++) {
358
+ const child = node.child(i);
359
+ if (child?.type === 'visibility_modifier') {
360
+ return child.text.includes('pub') ? 'public' : 'private';
361
+ }
362
+ }
363
+ return 'private'; // Rust defaults to private
364
+ },
365
+ },
366
+ java: {
367
+ functionTypes: [],
368
+ classTypes: ['class_declaration'],
369
+ methodTypes: ['method_declaration', 'constructor_declaration'],
370
+ interfaceTypes: ['interface_declaration'],
371
+ structTypes: [],
372
+ enumTypes: ['enum_declaration'],
373
+ importTypes: ['import_declaration'],
374
+ callTypes: ['method_invocation'],
375
+ nameField: 'name',
376
+ bodyField: 'body',
377
+ paramsField: 'parameters',
378
+ returnField: 'type',
379
+ getSignature: (node, source) => {
380
+ const params = getChildByField(node, 'parameters');
381
+ const returnType = getChildByField(node, 'type');
382
+ if (!params)
383
+ return undefined;
384
+ const paramsText = getNodeText(params, source);
385
+ return returnType ? getNodeText(returnType, source) + ' ' + paramsText : paramsText;
386
+ },
387
+ getVisibility: (node) => {
388
+ for (let i = 0; i < node.childCount; i++) {
389
+ const child = node.child(i);
390
+ if (child?.type === 'modifiers') {
391
+ const text = child.text;
392
+ if (text.includes('public'))
393
+ return 'public';
394
+ if (text.includes('private'))
395
+ return 'private';
396
+ if (text.includes('protected'))
397
+ return 'protected';
398
+ }
399
+ }
400
+ return undefined;
401
+ },
402
+ isStatic: (node) => {
403
+ for (let i = 0; i < node.childCount; i++) {
404
+ const child = node.child(i);
405
+ if (child?.type === 'modifiers' && child.text.includes('static')) {
406
+ return true;
407
+ }
408
+ }
409
+ return false;
410
+ },
411
+ },
412
+ c: {
413
+ functionTypes: ['function_definition'],
414
+ classTypes: [],
415
+ methodTypes: [],
416
+ interfaceTypes: [],
417
+ structTypes: ['struct_specifier'],
418
+ enumTypes: ['enum_specifier'],
419
+ importTypes: ['preproc_include'],
420
+ callTypes: ['call_expression'],
421
+ nameField: 'declarator',
422
+ bodyField: 'body',
423
+ paramsField: 'parameters',
424
+ },
425
+ cpp: {
426
+ functionTypes: ['function_definition'],
427
+ classTypes: ['class_specifier'],
428
+ methodTypes: ['function_definition'],
429
+ interfaceTypes: [],
430
+ structTypes: ['struct_specifier'],
431
+ enumTypes: ['enum_specifier'],
432
+ importTypes: ['preproc_include'],
433
+ callTypes: ['call_expression'],
434
+ nameField: 'declarator',
435
+ bodyField: 'body',
436
+ paramsField: 'parameters',
437
+ getVisibility: (node) => {
438
+ // Check for access specifier in parent
439
+ const parent = node.parent;
440
+ if (parent) {
441
+ for (let i = 0; i < parent.childCount; i++) {
442
+ const child = parent.child(i);
443
+ if (child?.type === 'access_specifier') {
444
+ const text = child.text;
445
+ if (text.includes('public'))
446
+ return 'public';
447
+ if (text.includes('private'))
448
+ return 'private';
449
+ if (text.includes('protected'))
450
+ return 'protected';
451
+ }
452
+ }
453
+ }
454
+ return undefined;
455
+ },
456
+ },
457
+ csharp: {
458
+ functionTypes: [],
459
+ classTypes: ['class_declaration'],
460
+ methodTypes: ['method_declaration', 'constructor_declaration'],
461
+ interfaceTypes: ['interface_declaration'],
462
+ structTypes: ['struct_declaration'],
463
+ enumTypes: ['enum_declaration'],
464
+ importTypes: ['using_directive'],
465
+ callTypes: ['invocation_expression'],
466
+ nameField: 'name',
467
+ bodyField: 'body',
468
+ paramsField: 'parameter_list',
469
+ getVisibility: (node) => {
470
+ for (let i = 0; i < node.childCount; i++) {
471
+ const child = node.child(i);
472
+ if (child?.type === 'modifier') {
473
+ const text = child.text;
474
+ if (text === 'public')
475
+ return 'public';
476
+ if (text === 'private')
477
+ return 'private';
478
+ if (text === 'protected')
479
+ return 'protected';
480
+ if (text === 'internal')
481
+ return 'internal';
482
+ }
483
+ }
484
+ return 'private'; // C# defaults to private
485
+ },
486
+ isStatic: (node) => {
487
+ for (let i = 0; i < node.childCount; i++) {
488
+ const child = node.child(i);
489
+ if (child?.type === 'modifier' && child.text === 'static') {
490
+ return true;
491
+ }
492
+ }
493
+ return false;
494
+ },
495
+ isAsync: (node) => {
496
+ for (let i = 0; i < node.childCount; i++) {
497
+ const child = node.child(i);
498
+ if (child?.type === 'modifier' && child.text === 'async') {
499
+ return true;
500
+ }
501
+ }
502
+ return false;
503
+ },
504
+ },
505
+ php: {
506
+ functionTypes: ['function_definition'],
507
+ classTypes: ['class_declaration'],
508
+ methodTypes: ['method_declaration'],
509
+ interfaceTypes: ['interface_declaration'],
510
+ structTypes: [],
511
+ enumTypes: ['enum_declaration'],
512
+ importTypes: ['namespace_use_declaration'],
513
+ callTypes: ['function_call_expression', 'member_call_expression', 'scoped_call_expression'],
514
+ nameField: 'name',
515
+ bodyField: 'body',
516
+ paramsField: 'parameters',
517
+ returnField: 'return_type',
518
+ getVisibility: (node) => {
519
+ for (let i = 0; i < node.childCount; i++) {
520
+ const child = node.child(i);
521
+ if (child?.type === 'visibility_modifier') {
522
+ const text = child.text;
523
+ if (text === 'public')
524
+ return 'public';
525
+ if (text === 'private')
526
+ return 'private';
527
+ if (text === 'protected')
528
+ return 'protected';
529
+ }
530
+ }
531
+ return 'public'; // PHP defaults to public
532
+ },
533
+ isStatic: (node) => {
534
+ for (let i = 0; i < node.childCount; i++) {
535
+ const child = node.child(i);
536
+ if (child?.type === 'static_modifier')
537
+ return true;
538
+ }
539
+ return false;
540
+ },
541
+ },
542
+ ruby: {
543
+ functionTypes: ['method'],
544
+ classTypes: ['class'],
545
+ methodTypes: ['method', 'singleton_method'],
546
+ interfaceTypes: [], // Ruby uses modules
547
+ structTypes: [],
548
+ enumTypes: [],
549
+ importTypes: ['call'], // require/require_relative
550
+ callTypes: ['call', 'method_call'],
551
+ nameField: 'name',
552
+ bodyField: 'body',
553
+ paramsField: 'parameters',
554
+ getVisibility: (node) => {
555
+ // Ruby visibility is based on preceding visibility modifiers
556
+ let sibling = node.previousNamedSibling;
557
+ while (sibling) {
558
+ if (sibling.type === 'call') {
559
+ const methodName = getChildByField(sibling, 'method');
560
+ if (methodName) {
561
+ const text = methodName.text;
562
+ if (text === 'private')
563
+ return 'private';
564
+ if (text === 'protected')
565
+ return 'protected';
566
+ if (text === 'public')
567
+ return 'public';
568
+ }
569
+ }
570
+ sibling = sibling.previousNamedSibling;
571
+ }
572
+ return 'public';
573
+ },
574
+ },
575
+ swift: {
576
+ functionTypes: ['function_declaration'],
577
+ classTypes: ['class_declaration'],
578
+ methodTypes: ['function_declaration'], // Methods are functions inside classes
579
+ interfaceTypes: ['protocol_declaration'],
580
+ structTypes: ['struct_declaration'],
581
+ enumTypes: ['enum_declaration'],
582
+ importTypes: ['import_declaration'],
583
+ callTypes: ['call_expression'],
584
+ nameField: 'name',
585
+ bodyField: 'body',
586
+ paramsField: 'parameter',
587
+ returnField: 'return_type',
588
+ getSignature: (node, source) => {
589
+ // Swift function signature: func name(params) -> ReturnType
590
+ const params = getChildByField(node, 'parameter');
591
+ const returnType = getChildByField(node, 'return_type');
592
+ if (!params)
593
+ return undefined;
594
+ let sig = getNodeText(params, source);
595
+ if (returnType) {
596
+ sig += ' -> ' + getNodeText(returnType, source);
597
+ }
598
+ return sig;
599
+ },
600
+ getVisibility: (node) => {
601
+ // Check for visibility modifiers in Swift
602
+ for (let i = 0; i < node.childCount; i++) {
603
+ const child = node.child(i);
604
+ if (child?.type === 'modifiers') {
605
+ const text = child.text;
606
+ if (text.includes('public'))
607
+ return 'public';
608
+ if (text.includes('private'))
609
+ return 'private';
610
+ if (text.includes('internal'))
611
+ return 'internal';
612
+ if (text.includes('fileprivate'))
613
+ return 'private';
614
+ }
615
+ }
616
+ return 'internal'; // Swift defaults to internal
617
+ },
618
+ isStatic: (node) => {
619
+ for (let i = 0; i < node.childCount; i++) {
620
+ const child = node.child(i);
621
+ if (child?.type === 'modifiers') {
622
+ if (child.text.includes('static') || child.text.includes('class')) {
623
+ return true;
624
+ }
625
+ }
626
+ }
627
+ return false;
628
+ },
629
+ isAsync: (node) => {
630
+ for (let i = 0; i < node.childCount; i++) {
631
+ const child = node.child(i);
632
+ if (child?.type === 'modifiers' && child.text.includes('async')) {
633
+ return true;
634
+ }
635
+ }
636
+ return false;
637
+ },
638
+ },
639
+ kotlin: {
640
+ functionTypes: ['function_declaration'],
641
+ classTypes: ['class_declaration', 'object_declaration'],
642
+ methodTypes: ['function_declaration'], // Methods are functions inside classes
643
+ interfaceTypes: ['class_declaration'], // Interfaces use class_declaration with 'interface' modifier
644
+ structTypes: [], // Kotlin uses data classes
645
+ enumTypes: ['class_declaration'], // Enums use class_declaration with 'enum' modifier
646
+ importTypes: ['import_header'],
647
+ callTypes: ['call_expression'],
648
+ nameField: 'simple_identifier',
649
+ bodyField: 'function_body',
650
+ paramsField: 'function_value_parameters',
651
+ returnField: 'type',
652
+ getSignature: (node, source) => {
653
+ // Kotlin function signature: fun name(params): ReturnType
654
+ const params = getChildByField(node, 'function_value_parameters');
655
+ const returnType = getChildByField(node, 'type');
656
+ if (!params)
657
+ return undefined;
658
+ let sig = getNodeText(params, source);
659
+ if (returnType) {
660
+ sig += ': ' + getNodeText(returnType, source);
661
+ }
662
+ return sig;
663
+ },
664
+ getVisibility: (node) => {
665
+ // Check for visibility modifiers in Kotlin
666
+ for (let i = 0; i < node.childCount; i++) {
667
+ const child = node.child(i);
668
+ if (child?.type === 'modifiers') {
669
+ const text = child.text;
670
+ if (text.includes('public'))
671
+ return 'public';
672
+ if (text.includes('private'))
673
+ return 'private';
674
+ if (text.includes('protected'))
675
+ return 'protected';
676
+ if (text.includes('internal'))
677
+ return 'internal';
678
+ }
679
+ }
680
+ return 'public'; // Kotlin defaults to public
681
+ },
682
+ isStatic: (_node) => {
683
+ // Kotlin doesn't have static, uses companion objects
684
+ // Check if inside companion object would require more context
685
+ return false;
686
+ },
687
+ isAsync: (node) => {
688
+ // Kotlin uses suspend keyword for coroutines
689
+ for (let i = 0; i < node.childCount; i++) {
690
+ const child = node.child(i);
691
+ if (child?.type === 'modifiers' && child.text.includes('suspend')) {
692
+ return true;
693
+ }
694
+ }
695
+ return false;
696
+ },
697
+ },
698
+ };
699
+ // TSX and JSX use the same extractors as their base languages
700
+ EXTRACTORS.tsx = EXTRACTORS.typescript;
701
+ EXTRACTORS.jsx = EXTRACTORS.javascript;
702
+ /**
703
+ * Extract the name from a node based on language
704
+ */
705
+ function extractName(node, source, extractor) {
706
+ // Try field name first
707
+ const nameNode = getChildByField(node, extractor.nameField);
708
+ if (nameNode) {
709
+ // Handle complex declarators (C/C++)
710
+ if (nameNode.type === 'function_declarator' || nameNode.type === 'declarator') {
711
+ const innerName = getChildByField(nameNode, 'declarator') || nameNode.namedChild(0);
712
+ return innerName ? getNodeText(innerName, source) : getNodeText(nameNode, source);
713
+ }
714
+ return getNodeText(nameNode, source);
715
+ }
716
+ // Fall back to first identifier child
717
+ for (let i = 0; i < node.namedChildCount; i++) {
718
+ const child = node.namedChild(i);
719
+ if (child &&
720
+ (child.type === 'identifier' ||
721
+ child.type === 'type_identifier' ||
722
+ child.type === 'simple_identifier' ||
723
+ child.type === 'constant')) {
724
+ return getNodeText(child, source);
725
+ }
726
+ }
727
+ return '<anonymous>';
728
+ }
729
+ /**
730
+ * TreeSitterExtractor - Main extraction class
731
+ */
732
+ class TreeSitterExtractor {
733
+ filePath;
734
+ language;
735
+ source;
736
+ tree = null;
737
+ nodes = [];
738
+ edges = [];
739
+ unresolvedReferences = [];
740
+ errors = [];
741
+ extractor = null;
742
+ nodeStack = []; // Stack of parent node IDs
743
+ constructor(filePath, source, language) {
744
+ this.filePath = filePath;
745
+ this.source = source;
746
+ this.language = language || (0, grammars_1.detectLanguage)(filePath);
747
+ this.extractor = EXTRACTORS[this.language] || null;
748
+ }
749
+ /**
750
+ * Parse and extract from the source code
751
+ */
752
+ extract() {
753
+ const startTime = Date.now();
754
+ if (!(0, grammars_1.isLanguageSupported)(this.language)) {
755
+ return {
756
+ nodes: [],
757
+ edges: [],
758
+ unresolvedReferences: [],
759
+ errors: [
760
+ {
761
+ message: `Unsupported language: ${this.language}`,
762
+ severity: 'error',
763
+ },
764
+ ],
765
+ durationMs: Date.now() - startTime,
766
+ };
767
+ }
768
+ const parser = (0, grammars_1.getParser)(this.language);
769
+ if (!parser) {
770
+ return {
771
+ nodes: [],
772
+ edges: [],
773
+ unresolvedReferences: [],
774
+ errors: [
775
+ {
776
+ message: `Failed to get parser for language: ${this.language}`,
777
+ severity: 'error',
778
+ },
779
+ ],
780
+ durationMs: Date.now() - startTime,
781
+ };
782
+ }
783
+ try {
784
+ this.tree = parser.parse(this.source);
785
+ this.visitNode(this.tree.rootNode);
786
+ }
787
+ catch (error) {
788
+ this.errors.push({
789
+ message: `Parse error: ${error instanceof Error ? error.message : String(error)}`,
790
+ severity: 'error',
791
+ });
792
+ }
793
+ return {
794
+ nodes: this.nodes,
795
+ edges: this.edges,
796
+ unresolvedReferences: this.unresolvedReferences,
797
+ errors: this.errors,
798
+ durationMs: Date.now() - startTime,
799
+ };
800
+ }
801
+ /**
802
+ * Visit a node and extract information
803
+ */
804
+ visitNode(node) {
805
+ if (!this.extractor)
806
+ return;
807
+ const nodeType = node.type;
808
+ let skipChildren = false;
809
+ // Check for function declarations
810
+ // For Python/Ruby, function_definition inside a class should be treated as method
811
+ if (this.extractor.functionTypes.includes(nodeType)) {
812
+ if (this.nodeStack.length > 0 && this.extractor.methodTypes.includes(nodeType)) {
813
+ // Inside a class - treat as method
814
+ this.extractMethod(node);
815
+ skipChildren = true; // extractMethod visits children via visitFunctionBody
816
+ }
817
+ else {
818
+ this.extractFunction(node);
819
+ skipChildren = true; // extractFunction visits children via visitFunctionBody
820
+ }
821
+ }
822
+ // Check for class declarations
823
+ else if (this.extractor.classTypes.includes(nodeType)) {
824
+ // Swift uses class_declaration for both classes and structs
825
+ // Check for 'struct' child to differentiate
826
+ if (this.language === 'swift' && this.hasChildOfType(node, 'struct')) {
827
+ this.extractStruct(node);
828
+ }
829
+ else if (this.language === 'swift' && this.hasChildOfType(node, 'enum')) {
830
+ this.extractEnum(node);
831
+ }
832
+ // Kotlin uses class_declaration for classes, interfaces, and enums
833
+ else if (this.language === 'kotlin' && nodeType === 'class_declaration') {
834
+ if (isKotlinInterface(node)) {
835
+ this.extractInterface(node);
836
+ }
837
+ else if (isKotlinEnum(node)) {
838
+ this.extractKotlinEnum(node);
839
+ }
840
+ else {
841
+ this.extractKotlinClass(node);
842
+ }
843
+ }
844
+ // Kotlin object_declaration (singleton objects)
845
+ else if (this.language === 'kotlin' && nodeType === 'object_declaration') {
846
+ this.extractKotlinObject(node);
847
+ }
848
+ else {
849
+ this.extractClass(node);
850
+ }
851
+ skipChildren = true; // extractClass visits body children
852
+ }
853
+ // Kotlin companion_object
854
+ else if (this.language === 'kotlin' && nodeType === 'companion_object') {
855
+ this.extractKotlinCompanionObject(node);
856
+ skipChildren = true;
857
+ }
858
+ // Kotlin property_declaration
859
+ else if (this.language === 'kotlin' && nodeType === 'property_declaration') {
860
+ this.extractKotlinProperty(node);
861
+ }
862
+ // Kotlin type_alias
863
+ else if (this.language === 'kotlin' && nodeType === 'type_alias') {
864
+ this.extractKotlinTypeAlias(node);
865
+ }
866
+ // Check for method declarations (only if not already handled by functionTypes)
867
+ else if (this.extractor.methodTypes.includes(nodeType)) {
868
+ this.extractMethod(node);
869
+ skipChildren = true; // extractMethod visits children via visitFunctionBody
870
+ }
871
+ // Check for interface/protocol/trait declarations
872
+ else if (this.extractor.interfaceTypes.includes(nodeType)) {
873
+ this.extractInterface(node);
874
+ skipChildren = true; // extractInterface visits body children
875
+ }
876
+ // Check for struct declarations
877
+ else if (this.extractor.structTypes.includes(nodeType)) {
878
+ this.extractStruct(node);
879
+ skipChildren = true; // extractStruct visits body children
880
+ }
881
+ // Check for enum declarations
882
+ else if (this.extractor.enumTypes.includes(nodeType)) {
883
+ this.extractEnum(node);
884
+ skipChildren = true; // extractEnum visits body children
885
+ }
886
+ // Check for imports
887
+ else if (this.extractor.importTypes.includes(nodeType)) {
888
+ this.extractImport(node);
889
+ }
890
+ // Check for function calls
891
+ else if (this.extractor.callTypes.includes(nodeType)) {
892
+ this.extractCall(node);
893
+ }
894
+ // Visit children (unless the extract method already visited them)
895
+ if (!skipChildren) {
896
+ for (let i = 0; i < node.namedChildCount; i++) {
897
+ const child = node.namedChild(i);
898
+ if (child) {
899
+ this.visitNode(child);
900
+ }
901
+ }
902
+ }
903
+ }
904
+ /**
905
+ * Create a Node object
906
+ */
907
+ createNode(kind, name, node, extra) {
908
+ const id = generateNodeId(this.filePath, kind, name, node.startPosition.row + 1);
909
+ const newNode = {
910
+ id,
911
+ kind,
912
+ name,
913
+ qualifiedName: this.buildQualifiedName(name),
914
+ filePath: this.filePath,
915
+ language: this.language,
916
+ startLine: node.startPosition.row + 1,
917
+ endLine: node.endPosition.row + 1,
918
+ startColumn: node.startPosition.column,
919
+ endColumn: node.endPosition.column,
920
+ updatedAt: Date.now(),
921
+ ...extra,
922
+ };
923
+ this.nodes.push(newNode);
924
+ // Add containment edge from parent
925
+ if (this.nodeStack.length > 0) {
926
+ const parentId = this.nodeStack[this.nodeStack.length - 1];
927
+ if (parentId) {
928
+ this.edges.push({
929
+ source: parentId,
930
+ target: id,
931
+ kind: 'contains',
932
+ });
933
+ }
934
+ }
935
+ return newNode;
936
+ }
937
+ /**
938
+ * Build qualified name from node stack
939
+ */
940
+ buildQualifiedName(name) {
941
+ // Get names from the node stack
942
+ const parts = [this.filePath];
943
+ for (const nodeId of this.nodeStack) {
944
+ const node = this.nodes.find((n) => n.id === nodeId);
945
+ if (node) {
946
+ parts.push(node.name);
947
+ }
948
+ }
949
+ parts.push(name);
950
+ return parts.join('::');
951
+ }
952
+ /**
953
+ * Check if a node has a child of a specific type
954
+ */
955
+ hasChildOfType(node, type) {
956
+ for (let i = 0; i < node.childCount; i++) {
957
+ const child = node.child(i);
958
+ if (child?.type === type) {
959
+ return true;
960
+ }
961
+ }
962
+ return false;
963
+ }
964
+ /**
965
+ * Extract a function
966
+ */
967
+ extractFunction(node) {
968
+ if (!this.extractor)
969
+ return;
970
+ const name = extractName(node, this.source, this.extractor);
971
+ if (name === '<anonymous>')
972
+ return; // Skip anonymous functions
973
+ const docstring = getPrecedingDocstring(node, this.source);
974
+ const signature = this.extractor.getSignature?.(node, this.source);
975
+ const visibility = this.extractor.getVisibility?.(node);
976
+ const isExported = this.extractor.isExported?.(node, this.source);
977
+ const isAsync = this.extractor.isAsync?.(node);
978
+ const isStatic = this.extractor.isStatic?.(node);
979
+ const funcNode = this.createNode('function', name, node, {
980
+ docstring,
981
+ signature,
982
+ visibility,
983
+ isExported,
984
+ isAsync,
985
+ isStatic,
986
+ });
987
+ // Push to stack and visit body
988
+ this.nodeStack.push(funcNode.id);
989
+ const body = getChildByField(node, this.extractor.bodyField);
990
+ if (body) {
991
+ this.visitFunctionBody(body, funcNode.id);
992
+ }
993
+ this.nodeStack.pop();
994
+ }
995
+ /**
996
+ * Extract a class
997
+ */
998
+ extractClass(node) {
999
+ if (!this.extractor)
1000
+ return;
1001
+ const name = extractName(node, this.source, this.extractor);
1002
+ const docstring = getPrecedingDocstring(node, this.source);
1003
+ const visibility = this.extractor.getVisibility?.(node);
1004
+ const isExported = this.extractor.isExported?.(node, this.source);
1005
+ const classNode = this.createNode('class', name, node, {
1006
+ docstring,
1007
+ visibility,
1008
+ isExported,
1009
+ });
1010
+ // Extract extends/implements
1011
+ this.extractInheritance(node, classNode.id);
1012
+ // Push to stack and visit body
1013
+ this.nodeStack.push(classNode.id);
1014
+ const body = getChildByField(node, this.extractor.bodyField) || node;
1015
+ // Visit all children for methods and properties
1016
+ for (let i = 0; i < body.namedChildCount; i++) {
1017
+ const child = body.namedChild(i);
1018
+ if (child) {
1019
+ this.visitNode(child);
1020
+ }
1021
+ }
1022
+ this.nodeStack.pop();
1023
+ }
1024
+ /**
1025
+ * Extract a method
1026
+ */
1027
+ extractMethod(node) {
1028
+ if (!this.extractor)
1029
+ return;
1030
+ // For most languages, only extract as method if inside a class
1031
+ // But Go methods are top-level with a receiver, so always treat them as methods
1032
+ if (this.nodeStack.length === 0 && this.language !== 'go') {
1033
+ // Top-level and not Go, treat as function
1034
+ this.extractFunction(node);
1035
+ return;
1036
+ }
1037
+ const name = extractName(node, this.source, this.extractor);
1038
+ const docstring = getPrecedingDocstring(node, this.source);
1039
+ const signature = this.extractor.getSignature?.(node, this.source);
1040
+ const visibility = this.extractor.getVisibility?.(node);
1041
+ const isAsync = this.extractor.isAsync?.(node);
1042
+ const isStatic = this.extractor.isStatic?.(node);
1043
+ const methodNode = this.createNode('method', name, node, {
1044
+ docstring,
1045
+ signature,
1046
+ visibility,
1047
+ isAsync,
1048
+ isStatic,
1049
+ });
1050
+ // Push to stack and visit body
1051
+ this.nodeStack.push(methodNode.id);
1052
+ const body = getChildByField(node, this.extractor.bodyField);
1053
+ if (body) {
1054
+ this.visitFunctionBody(body, methodNode.id);
1055
+ }
1056
+ this.nodeStack.pop();
1057
+ }
1058
+ /**
1059
+ * Extract an interface/protocol/trait
1060
+ */
1061
+ extractInterface(node) {
1062
+ if (!this.extractor)
1063
+ return;
1064
+ const name = extractName(node, this.source, this.extractor);
1065
+ const docstring = getPrecedingDocstring(node, this.source);
1066
+ const isExported = this.extractor.isExported?.(node, this.source);
1067
+ // Determine kind based on language
1068
+ let kind = 'interface';
1069
+ if (this.language === 'rust')
1070
+ kind = 'trait';
1071
+ this.createNode(kind, name, node, {
1072
+ docstring,
1073
+ isExported,
1074
+ });
1075
+ }
1076
+ /**
1077
+ * Extract a struct
1078
+ */
1079
+ extractStruct(node) {
1080
+ if (!this.extractor)
1081
+ return;
1082
+ const name = extractName(node, this.source, this.extractor);
1083
+ const docstring = getPrecedingDocstring(node, this.source);
1084
+ const visibility = this.extractor.getVisibility?.(node);
1085
+ const isExported = this.extractor.isExported?.(node, this.source);
1086
+ const structNode = this.createNode('struct', name, node, {
1087
+ docstring,
1088
+ visibility,
1089
+ isExported,
1090
+ });
1091
+ // Push to stack for field extraction
1092
+ this.nodeStack.push(structNode.id);
1093
+ const body = getChildByField(node, this.extractor.bodyField) || node;
1094
+ for (let i = 0; i < body.namedChildCount; i++) {
1095
+ const child = body.namedChild(i);
1096
+ if (child) {
1097
+ this.visitNode(child);
1098
+ }
1099
+ }
1100
+ this.nodeStack.pop();
1101
+ }
1102
+ /**
1103
+ * Extract an enum
1104
+ */
1105
+ extractEnum(node) {
1106
+ if (!this.extractor)
1107
+ return;
1108
+ const name = extractName(node, this.source, this.extractor);
1109
+ const docstring = getPrecedingDocstring(node, this.source);
1110
+ const visibility = this.extractor.getVisibility?.(node);
1111
+ const isExported = this.extractor.isExported?.(node, this.source);
1112
+ this.createNode('enum', name, node, {
1113
+ docstring,
1114
+ visibility,
1115
+ isExported,
1116
+ });
1117
+ }
1118
+ /**
1119
+ * Extract an import
1120
+ */
1121
+ extractImport(node) {
1122
+ // Create an edge to track the import
1123
+ // For now, we'll create unresolved references
1124
+ const importText = getNodeText(node, this.source);
1125
+ // Extract module/package name based on language
1126
+ let moduleName = '';
1127
+ if (this.language === 'typescript' || this.language === 'javascript') {
1128
+ const source = getChildByField(node, 'source');
1129
+ if (source) {
1130
+ moduleName = getNodeText(source, this.source).replace(/['"]/g, '');
1131
+ }
1132
+ }
1133
+ else if (this.language === 'python') {
1134
+ const module = getChildByField(node, 'module_name') || node.namedChild(0);
1135
+ if (module) {
1136
+ moduleName = getNodeText(module, this.source);
1137
+ }
1138
+ }
1139
+ else if (this.language === 'go') {
1140
+ const path = node.namedChild(0);
1141
+ if (path) {
1142
+ moduleName = getNodeText(path, this.source).replace(/['"]/g, '');
1143
+ }
1144
+ }
1145
+ else {
1146
+ // Generic extraction
1147
+ moduleName = importText;
1148
+ }
1149
+ if (moduleName && this.nodeStack.length > 0) {
1150
+ const parentId = this.nodeStack[this.nodeStack.length - 1];
1151
+ if (parentId) {
1152
+ this.unresolvedReferences.push({
1153
+ fromNodeId: parentId,
1154
+ referenceName: moduleName,
1155
+ referenceKind: 'imports',
1156
+ line: node.startPosition.row + 1,
1157
+ column: node.startPosition.column,
1158
+ });
1159
+ }
1160
+ }
1161
+ }
1162
+ /**
1163
+ * Extract a function call
1164
+ */
1165
+ extractCall(node) {
1166
+ if (this.nodeStack.length === 0)
1167
+ return;
1168
+ const callerId = this.nodeStack[this.nodeStack.length - 1];
1169
+ if (!callerId)
1170
+ return;
1171
+ // Get the function/method being called
1172
+ let calleeName = '';
1173
+ const func = getChildByField(node, 'function') || node.namedChild(0);
1174
+ if (func) {
1175
+ if (func.type === 'member_expression' || func.type === 'attribute') {
1176
+ // Method call: obj.method()
1177
+ const property = getChildByField(func, 'property') || func.namedChild(1);
1178
+ if (property) {
1179
+ calleeName = getNodeText(property, this.source);
1180
+ }
1181
+ }
1182
+ else if (func.type === 'scoped_identifier' || func.type === 'scoped_call_expression') {
1183
+ // Scoped call: Module::function()
1184
+ calleeName = getNodeText(func, this.source);
1185
+ }
1186
+ else {
1187
+ calleeName = getNodeText(func, this.source);
1188
+ }
1189
+ }
1190
+ if (calleeName) {
1191
+ this.unresolvedReferences.push({
1192
+ fromNodeId: callerId,
1193
+ referenceName: calleeName,
1194
+ referenceKind: 'calls',
1195
+ line: node.startPosition.row + 1,
1196
+ column: node.startPosition.column,
1197
+ });
1198
+ }
1199
+ }
1200
+ /**
1201
+ * Visit function body and extract calls
1202
+ */
1203
+ visitFunctionBody(body, _functionId) {
1204
+ if (!this.extractor)
1205
+ return;
1206
+ // Recursively find all call expressions
1207
+ const visitForCalls = (node) => {
1208
+ if (this.extractor.callTypes.includes(node.type)) {
1209
+ this.extractCall(node);
1210
+ }
1211
+ for (let i = 0; i < node.namedChildCount; i++) {
1212
+ const child = node.namedChild(i);
1213
+ if (child) {
1214
+ visitForCalls(child);
1215
+ }
1216
+ }
1217
+ };
1218
+ visitForCalls(body);
1219
+ }
1220
+ /**
1221
+ * Extract inheritance relationships
1222
+ */
1223
+ extractInheritance(node, classId) {
1224
+ // Look for extends/implements clauses
1225
+ for (let i = 0; i < node.namedChildCount; i++) {
1226
+ const child = node.namedChild(i);
1227
+ if (!child)
1228
+ continue;
1229
+ if (child.type === 'extends_clause' ||
1230
+ child.type === 'class_heritage' ||
1231
+ child.type === 'superclass') {
1232
+ // Extract parent class name
1233
+ const superclass = child.namedChild(0);
1234
+ if (superclass) {
1235
+ const name = getNodeText(superclass, this.source);
1236
+ this.unresolvedReferences.push({
1237
+ fromNodeId: classId,
1238
+ referenceName: name,
1239
+ referenceKind: 'extends',
1240
+ line: child.startPosition.row + 1,
1241
+ column: child.startPosition.column,
1242
+ });
1243
+ }
1244
+ }
1245
+ if (child.type === 'implements_clause' ||
1246
+ child.type === 'class_interface_clause') {
1247
+ // Extract implemented interfaces
1248
+ for (let j = 0; j < child.namedChildCount; j++) {
1249
+ const iface = child.namedChild(j);
1250
+ if (iface) {
1251
+ const name = getNodeText(iface, this.source);
1252
+ this.unresolvedReferences.push({
1253
+ fromNodeId: classId,
1254
+ referenceName: name,
1255
+ referenceKind: 'implements',
1256
+ line: iface.startPosition.row + 1,
1257
+ column: iface.startPosition.column,
1258
+ });
1259
+ }
1260
+ }
1261
+ }
1262
+ }
1263
+ }
1264
+ // =============================================================================
1265
+ // Kotlin-specific Extraction Methods
1266
+ // =============================================================================
1267
+ /**
1268
+ * Extract a Kotlin class with enhanced metadata (data, sealed, abstract)
1269
+ */
1270
+ extractKotlinClass(node) {
1271
+ if (!this.extractor)
1272
+ return;
1273
+ const name = extractName(node, this.source, this.extractor);
1274
+ const docstring = getPrecedingDocstring(node, this.source);
1275
+ const visibility = this.extractor.getVisibility?.(node);
1276
+ // Determine class modifiers
1277
+ const isAbstractClass = isKotlinAbstractClass(node);
1278
+ const classNode = this.createNode('class', name, node, {
1279
+ docstring,
1280
+ visibility,
1281
+ isAbstract: isAbstractClass,
1282
+ });
1283
+ // Extract inheritance (delegation specifiers in Kotlin)
1284
+ this.extractKotlinInheritance(node, classNode.id);
1285
+ // Push to stack and visit body
1286
+ this.nodeStack.push(classNode.id);
1287
+ // Find class_body child
1288
+ for (let i = 0; i < node.namedChildCount; i++) {
1289
+ const child = node.namedChild(i);
1290
+ if (child?.type === 'class_body' || child?.type === 'enum_class_body') {
1291
+ for (let j = 0; j < child.namedChildCount; j++) {
1292
+ const bodyChild = child.namedChild(j);
1293
+ if (bodyChild) {
1294
+ this.visitNode(bodyChild);
1295
+ }
1296
+ }
1297
+ }
1298
+ }
1299
+ this.nodeStack.pop();
1300
+ }
1301
+ /**
1302
+ * Extract a Kotlin enum class
1303
+ */
1304
+ extractKotlinEnum(node) {
1305
+ if (!this.extractor)
1306
+ return;
1307
+ const name = extractName(node, this.source, this.extractor);
1308
+ const docstring = getPrecedingDocstring(node, this.source);
1309
+ const visibility = this.extractor.getVisibility?.(node);
1310
+ const enumNode = this.createNode('enum', name, node, {
1311
+ docstring,
1312
+ visibility,
1313
+ });
1314
+ // Extract inheritance
1315
+ this.extractKotlinInheritance(node, enumNode.id);
1316
+ // Push to stack and visit body for enum entries and methods
1317
+ this.nodeStack.push(enumNode.id);
1318
+ // Find enum_class_body child
1319
+ for (let i = 0; i < node.namedChildCount; i++) {
1320
+ const child = node.namedChild(i);
1321
+ if (child?.type === 'enum_class_body') {
1322
+ for (let j = 0; j < child.namedChildCount; j++) {
1323
+ const bodyChild = child.namedChild(j);
1324
+ if (bodyChild) {
1325
+ // Extract enum entries
1326
+ if (bodyChild.type === 'enum_entry') {
1327
+ this.extractKotlinEnumEntry(bodyChild);
1328
+ }
1329
+ else {
1330
+ this.visitNode(bodyChild);
1331
+ }
1332
+ }
1333
+ }
1334
+ }
1335
+ }
1336
+ this.nodeStack.pop();
1337
+ }
1338
+ /**
1339
+ * Extract a Kotlin enum entry
1340
+ */
1341
+ extractKotlinEnumEntry(node) {
1342
+ // Find the simple_identifier for the enum entry name
1343
+ let name = '<unknown>';
1344
+ for (let i = 0; i < node.namedChildCount; i++) {
1345
+ const child = node.namedChild(i);
1346
+ if (child?.type === 'simple_identifier') {
1347
+ name = getNodeText(child, this.source);
1348
+ break;
1349
+ }
1350
+ }
1351
+ this.createNode('enum_member', name, node, {});
1352
+ }
1353
+ /**
1354
+ * Extract a Kotlin object declaration (singleton)
1355
+ */
1356
+ extractKotlinObject(node) {
1357
+ if (!this.extractor)
1358
+ return;
1359
+ const name = extractName(node, this.source, this.extractor);
1360
+ const docstring = getPrecedingDocstring(node, this.source);
1361
+ const visibility = this.extractor.getVisibility?.(node);
1362
+ const objectNode = this.createNode('class', name, node, {
1363
+ docstring,
1364
+ visibility,
1365
+ });
1366
+ // Extract inheritance
1367
+ this.extractKotlinInheritance(node, objectNode.id);
1368
+ // Push to stack and visit body
1369
+ this.nodeStack.push(objectNode.id);
1370
+ // Find class_body child
1371
+ for (let i = 0; i < node.namedChildCount; i++) {
1372
+ const child = node.namedChild(i);
1373
+ if (child?.type === 'class_body') {
1374
+ for (let j = 0; j < child.namedChildCount; j++) {
1375
+ const bodyChild = child.namedChild(j);
1376
+ if (bodyChild) {
1377
+ this.visitNode(bodyChild);
1378
+ }
1379
+ }
1380
+ }
1381
+ }
1382
+ this.nodeStack.pop();
1383
+ }
1384
+ /**
1385
+ * Extract a Kotlin companion object
1386
+ */
1387
+ extractKotlinCompanionObject(node) {
1388
+ if (!this.extractor)
1389
+ return;
1390
+ // Companion objects may or may not have a name
1391
+ let name = 'Companion';
1392
+ for (let i = 0; i < node.namedChildCount; i++) {
1393
+ const child = node.namedChild(i);
1394
+ if (child?.type === 'simple_identifier') {
1395
+ name = getNodeText(child, this.source);
1396
+ break;
1397
+ }
1398
+ }
1399
+ const docstring = getPrecedingDocstring(node, this.source);
1400
+ const visibility = this.extractor.getVisibility?.(node);
1401
+ const companionNode = this.createNode('class', name, node, {
1402
+ docstring,
1403
+ visibility,
1404
+ isStatic: true, // Mark companion object members as static
1405
+ });
1406
+ // Push to stack and visit body
1407
+ this.nodeStack.push(companionNode.id);
1408
+ // Find class_body child
1409
+ for (let i = 0; i < node.namedChildCount; i++) {
1410
+ const child = node.namedChild(i);
1411
+ if (child?.type === 'class_body') {
1412
+ for (let j = 0; j < child.namedChildCount; j++) {
1413
+ const bodyChild = child.namedChild(j);
1414
+ if (bodyChild) {
1415
+ this.visitNode(bodyChild);
1416
+ }
1417
+ }
1418
+ }
1419
+ }
1420
+ this.nodeStack.pop();
1421
+ }
1422
+ /**
1423
+ * Extract a Kotlin property (val/var)
1424
+ */
1425
+ extractKotlinProperty(node) {
1426
+ if (!this.extractor)
1427
+ return;
1428
+ // Find the property name (variable_declaration child contains it)
1429
+ let name = '<unknown>';
1430
+ for (let i = 0; i < node.namedChildCount; i++) {
1431
+ const child = node.namedChild(i);
1432
+ if (child?.type === 'variable_declaration') {
1433
+ // The simple_identifier is inside variable_declaration
1434
+ for (let j = 0; j < child.namedChildCount; j++) {
1435
+ const varChild = child.namedChild(j);
1436
+ if (varChild?.type === 'simple_identifier') {
1437
+ name = getNodeText(varChild, this.source);
1438
+ break;
1439
+ }
1440
+ }
1441
+ break;
1442
+ }
1443
+ }
1444
+ // Check if it's a const (compile-time constant)
1445
+ const isConst = kotlinHasModifier(node, 'const');
1446
+ const docstring = getPrecedingDocstring(node, this.source);
1447
+ const visibility = this.extractor.getVisibility?.(node);
1448
+ // Use 'property' for class properties, 'constant' for const val
1449
+ const kind = isConst ? 'constant' : 'property';
1450
+ this.createNode(kind, name, node, {
1451
+ docstring,
1452
+ visibility,
1453
+ });
1454
+ }
1455
+ /**
1456
+ * Extract a Kotlin type alias
1457
+ */
1458
+ extractKotlinTypeAlias(node) {
1459
+ if (!this.extractor)
1460
+ return;
1461
+ // Find the type_identifier for the alias name
1462
+ let name = '<unknown>';
1463
+ for (let i = 0; i < node.namedChildCount; i++) {
1464
+ const child = node.namedChild(i);
1465
+ if (child?.type === 'type_identifier') {
1466
+ name = getNodeText(child, this.source);
1467
+ break;
1468
+ }
1469
+ }
1470
+ const docstring = getPrecedingDocstring(node, this.source);
1471
+ const visibility = this.extractor.getVisibility?.(node);
1472
+ this.createNode('type_alias', name, node, {
1473
+ docstring,
1474
+ visibility,
1475
+ });
1476
+ }
1477
+ /**
1478
+ * Extract Kotlin inheritance (delegation specifiers after ":")
1479
+ */
1480
+ extractKotlinInheritance(node, classId) {
1481
+ // Delegation specifiers are direct children of class_declaration
1482
+ let isFirst = true;
1483
+ for (let i = 0; i < node.namedChildCount; i++) {
1484
+ const child = node.namedChild(i);
1485
+ if (child?.type === 'delegation_specifier') {
1486
+ // Extract the type name from the specifier
1487
+ // It could be a constructor_invocation (e.g., ParentClass()) or user_type (e.g., Interface)
1488
+ for (let k = 0; k < child.namedChildCount; k++) {
1489
+ const specChild = child.namedChild(k);
1490
+ if (specChild?.type === 'constructor_invocation') {
1491
+ // Get the user_type inside constructor_invocation
1492
+ for (let m = 0; m < specChild.namedChildCount; m++) {
1493
+ const ciChild = specChild.namedChild(m);
1494
+ if (ciChild?.type === 'user_type') {
1495
+ const typeName = getNodeText(ciChild, this.source);
1496
+ // First delegation specifier with constructor call is usually extends
1497
+ this.unresolvedReferences.push({
1498
+ fromNodeId: classId,
1499
+ referenceName: typeName,
1500
+ referenceKind: isFirst ? 'extends' : 'implements',
1501
+ line: specChild.startPosition.row + 1,
1502
+ column: specChild.startPosition.column,
1503
+ });
1504
+ isFirst = false;
1505
+ break;
1506
+ }
1507
+ }
1508
+ }
1509
+ else if (specChild?.type === 'user_type') {
1510
+ // Simple type reference (likely an interface)
1511
+ const typeName = getNodeText(specChild, this.source);
1512
+ this.unresolvedReferences.push({
1513
+ fromNodeId: classId,
1514
+ referenceName: typeName,
1515
+ referenceKind: 'implements',
1516
+ line: specChild.startPosition.row + 1,
1517
+ column: specChild.startPosition.column,
1518
+ });
1519
+ isFirst = false;
1520
+ }
1521
+ }
1522
+ }
1523
+ }
1524
+ }
1525
+ }
1526
+ exports.TreeSitterExtractor = TreeSitterExtractor;
1527
+ /**
1528
+ * LiquidExtractor - Extracts relationships from Liquid template files
1529
+ *
1530
+ * Liquid is a templating language (used by Shopify, Jekyll, etc.) that doesn't
1531
+ * have traditional functions or classes. Instead, we extract:
1532
+ * - Section references ({% section 'name' %})
1533
+ * - Snippet references ({% render 'name' %} and {% include 'name' %})
1534
+ * - Schema blocks ({% schema %}...{% endschema %})
1535
+ */
1536
+ class LiquidExtractor {
1537
+ filePath;
1538
+ source;
1539
+ nodes = [];
1540
+ edges = [];
1541
+ unresolvedReferences = [];
1542
+ errors = [];
1543
+ constructor(filePath, source) {
1544
+ this.filePath = filePath;
1545
+ this.source = source;
1546
+ }
1547
+ /**
1548
+ * Extract from Liquid source
1549
+ */
1550
+ extract() {
1551
+ const startTime = Date.now();
1552
+ try {
1553
+ // Create file node
1554
+ const fileNode = this.createFileNode();
1555
+ // Extract render/include statements (snippet references)
1556
+ this.extractSnippetReferences(fileNode.id);
1557
+ // Extract section references
1558
+ this.extractSectionReferences(fileNode.id);
1559
+ // Extract schema block
1560
+ this.extractSchema(fileNode.id);
1561
+ // Extract assign statements as variables
1562
+ this.extractAssignments(fileNode.id);
1563
+ }
1564
+ catch (error) {
1565
+ this.errors.push({
1566
+ message: `Liquid extraction error: ${error instanceof Error ? error.message : String(error)}`,
1567
+ severity: 'error',
1568
+ });
1569
+ }
1570
+ return {
1571
+ nodes: this.nodes,
1572
+ edges: this.edges,
1573
+ unresolvedReferences: this.unresolvedReferences,
1574
+ errors: this.errors,
1575
+ durationMs: Date.now() - startTime,
1576
+ };
1577
+ }
1578
+ /**
1579
+ * Create a file node for the Liquid template
1580
+ */
1581
+ createFileNode() {
1582
+ const lines = this.source.split('\n');
1583
+ const id = generateNodeId(this.filePath, 'file', this.filePath, 1);
1584
+ const fileNode = {
1585
+ id,
1586
+ kind: 'file',
1587
+ name: this.filePath.split('/').pop() || this.filePath,
1588
+ qualifiedName: this.filePath,
1589
+ filePath: this.filePath,
1590
+ language: 'liquid',
1591
+ startLine: 1,
1592
+ endLine: lines.length,
1593
+ startColumn: 0,
1594
+ endColumn: lines[lines.length - 1]?.length || 0,
1595
+ updatedAt: Date.now(),
1596
+ };
1597
+ this.nodes.push(fileNode);
1598
+ return fileNode;
1599
+ }
1600
+ /**
1601
+ * Extract {% render 'snippet' %} and {% include 'snippet' %} references
1602
+ */
1603
+ extractSnippetReferences(fileNodeId) {
1604
+ // Match {% render 'name' %} or {% include 'name' %} with optional parameters
1605
+ const renderRegex = /\{%[-]?\s*(render|include)\s+['"]([^'"]+)['"]/g;
1606
+ let match;
1607
+ while ((match = renderRegex.exec(this.source)) !== null) {
1608
+ const [, tagType, snippetName] = match;
1609
+ const line = this.getLineNumber(match.index);
1610
+ // Create a component node for the snippet reference
1611
+ const nodeId = generateNodeId(this.filePath, 'component', `${tagType}:${snippetName}`, line);
1612
+ const node = {
1613
+ id: nodeId,
1614
+ kind: 'component',
1615
+ name: snippetName,
1616
+ qualifiedName: `${this.filePath}::${tagType}:${snippetName}`,
1617
+ filePath: this.filePath,
1618
+ language: 'liquid',
1619
+ startLine: line,
1620
+ endLine: line,
1621
+ startColumn: match.index - this.getLineStart(line),
1622
+ endColumn: match.index - this.getLineStart(line) + match[0].length,
1623
+ updatedAt: Date.now(),
1624
+ };
1625
+ this.nodes.push(node);
1626
+ // Add containment edge from file
1627
+ this.edges.push({
1628
+ source: fileNodeId,
1629
+ target: nodeId,
1630
+ kind: 'contains',
1631
+ });
1632
+ // Add unresolved reference to the snippet file
1633
+ this.unresolvedReferences.push({
1634
+ fromNodeId: fileNodeId,
1635
+ referenceName: `snippets/${snippetName}.liquid`,
1636
+ referenceKind: 'references',
1637
+ line,
1638
+ column: match.index - this.getLineStart(line),
1639
+ });
1640
+ }
1641
+ }
1642
+ /**
1643
+ * Extract {% section 'name' %} references
1644
+ */
1645
+ extractSectionReferences(fileNodeId) {
1646
+ // Match {% section 'name' %}
1647
+ const sectionRegex = /\{%[-]?\s*section\s+['"]([^'"]+)['"]/g;
1648
+ let match;
1649
+ while ((match = sectionRegex.exec(this.source)) !== null) {
1650
+ const [, sectionName] = match;
1651
+ const line = this.getLineNumber(match.index);
1652
+ // Create a component node for the section reference
1653
+ const nodeId = generateNodeId(this.filePath, 'component', `section:${sectionName}`, line);
1654
+ const node = {
1655
+ id: nodeId,
1656
+ kind: 'component',
1657
+ name: sectionName,
1658
+ qualifiedName: `${this.filePath}::section:${sectionName}`,
1659
+ filePath: this.filePath,
1660
+ language: 'liquid',
1661
+ startLine: line,
1662
+ endLine: line,
1663
+ startColumn: match.index - this.getLineStart(line),
1664
+ endColumn: match.index - this.getLineStart(line) + match[0].length,
1665
+ updatedAt: Date.now(),
1666
+ };
1667
+ this.nodes.push(node);
1668
+ // Add containment edge from file
1669
+ this.edges.push({
1670
+ source: fileNodeId,
1671
+ target: nodeId,
1672
+ kind: 'contains',
1673
+ });
1674
+ // Add unresolved reference to the section file
1675
+ this.unresolvedReferences.push({
1676
+ fromNodeId: fileNodeId,
1677
+ referenceName: `sections/${sectionName}.liquid`,
1678
+ referenceKind: 'references',
1679
+ line,
1680
+ column: match.index - this.getLineStart(line),
1681
+ });
1682
+ }
1683
+ }
1684
+ /**
1685
+ * Extract {% schema %}...{% endschema %} blocks
1686
+ */
1687
+ extractSchema(fileNodeId) {
1688
+ // Match {% schema %}...{% endschema %}
1689
+ const schemaRegex = /\{%[-]?\s*schema\s*[-]?%\}([\s\S]*?)\{%[-]?\s*endschema\s*[-]?%\}/g;
1690
+ let match;
1691
+ while ((match = schemaRegex.exec(this.source)) !== null) {
1692
+ const [fullMatch, schemaContent] = match;
1693
+ const startLine = this.getLineNumber(match.index);
1694
+ const endLine = this.getLineNumber(match.index + fullMatch.length);
1695
+ // Try to parse the schema JSON to get the name
1696
+ let schemaName = 'schema';
1697
+ try {
1698
+ const schemaJson = JSON.parse(schemaContent);
1699
+ if (schemaJson.name) {
1700
+ schemaName = schemaJson.name;
1701
+ }
1702
+ }
1703
+ catch {
1704
+ // Schema isn't valid JSON, use default name
1705
+ }
1706
+ // Create a node for the schema
1707
+ const nodeId = generateNodeId(this.filePath, 'constant', `schema:${schemaName}`, startLine);
1708
+ const node = {
1709
+ id: nodeId,
1710
+ kind: 'constant',
1711
+ name: schemaName,
1712
+ qualifiedName: `${this.filePath}::schema:${schemaName}`,
1713
+ filePath: this.filePath,
1714
+ language: 'liquid',
1715
+ startLine,
1716
+ endLine,
1717
+ startColumn: match.index - this.getLineStart(startLine),
1718
+ endColumn: 0,
1719
+ docstring: schemaContent?.trim().substring(0, 200), // Store first 200 chars as docstring
1720
+ updatedAt: Date.now(),
1721
+ };
1722
+ this.nodes.push(node);
1723
+ // Add containment edge from file
1724
+ this.edges.push({
1725
+ source: fileNodeId,
1726
+ target: nodeId,
1727
+ kind: 'contains',
1728
+ });
1729
+ }
1730
+ }
1731
+ /**
1732
+ * Extract {% assign var = value %} statements
1733
+ */
1734
+ extractAssignments(fileNodeId) {
1735
+ // Match {% assign variable_name = ... %}
1736
+ const assignRegex = /\{%[-]?\s*assign\s+(\w+)\s*=/g;
1737
+ let match;
1738
+ while ((match = assignRegex.exec(this.source)) !== null) {
1739
+ const [, variableName] = match;
1740
+ const line = this.getLineNumber(match.index);
1741
+ // Create a variable node
1742
+ const nodeId = generateNodeId(this.filePath, 'variable', variableName, line);
1743
+ const node = {
1744
+ id: nodeId,
1745
+ kind: 'variable',
1746
+ name: variableName,
1747
+ qualifiedName: `${this.filePath}::${variableName}`,
1748
+ filePath: this.filePath,
1749
+ language: 'liquid',
1750
+ startLine: line,
1751
+ endLine: line,
1752
+ startColumn: match.index - this.getLineStart(line),
1753
+ endColumn: match.index - this.getLineStart(line) + match[0].length,
1754
+ updatedAt: Date.now(),
1755
+ };
1756
+ this.nodes.push(node);
1757
+ // Add containment edge from file
1758
+ this.edges.push({
1759
+ source: fileNodeId,
1760
+ target: nodeId,
1761
+ kind: 'contains',
1762
+ });
1763
+ }
1764
+ }
1765
+ /**
1766
+ * Get the line number for a character index
1767
+ */
1768
+ getLineNumber(index) {
1769
+ const substring = this.source.substring(0, index);
1770
+ return (substring.match(/\n/g) || []).length + 1;
1771
+ }
1772
+ /**
1773
+ * Get the character index of the start of a line
1774
+ */
1775
+ getLineStart(lineNumber) {
1776
+ const lines = this.source.split('\n');
1777
+ let index = 0;
1778
+ for (let i = 0; i < lineNumber - 1 && i < lines.length; i++) {
1779
+ index += lines[i].length + 1; // +1 for newline
1780
+ }
1781
+ return index;
1782
+ }
1783
+ }
1784
+ exports.LiquidExtractor = LiquidExtractor;
1785
+ /**
1786
+ * Extract nodes and edges from source code
1787
+ */
1788
+ function extractFromSource(filePath, source, language) {
1789
+ const detectedLanguage = language || (0, grammars_1.detectLanguage)(filePath);
1790
+ // Use custom extractor for Liquid
1791
+ if (detectedLanguage === 'liquid') {
1792
+ const extractor = new LiquidExtractor(filePath, source);
1793
+ return extractor.extract();
1794
+ }
1795
+ const extractor = new TreeSitterExtractor(filePath, source, detectedLanguage);
1796
+ return extractor.extract();
1797
+ }
1798
+ //# sourceMappingURL=tree-sitter.js.map