@colbymchenry/codegraph 0.3.1 → 0.4.8
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.
Potentially problematic release.
This version of @colbymchenry/codegraph might be problematic. Click here for more details.
- package/LICENSE +21 -21
- package/README.md +647 -641
- package/dist/bin/codegraph.d.ts +7 -2
- package/dist/bin/codegraph.d.ts.map +1 -1
- package/dist/bin/codegraph.js +360 -140
- package/dist/bin/codegraph.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +8 -1
- package/dist/config.js.map +1 -1
- package/dist/context/index.d.ts +17 -4
- package/dist/context/index.d.ts.map +1 -1
- package/dist/context/index.js +182 -15
- package/dist/context/index.js.map +1 -1
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +16 -0
- package/dist/db/index.js.map +1 -1
- package/dist/db/migrations.d.ts +1 -1
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/migrations.js +11 -12
- package/dist/db/migrations.js.map +1 -1
- package/dist/db/queries.d.ts +19 -0
- package/dist/db/queries.d.ts.map +1 -1
- package/dist/db/queries.js +221 -107
- package/dist/db/queries.js.map +1 -1
- package/dist/db/schema.sql +154 -149
- package/dist/directory.d.ts +13 -1
- package/dist/directory.d.ts.map +1 -1
- package/dist/directory.js +62 -19
- package/dist/directory.js.map +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +7 -1
- package/dist/errors.js.map +1 -1
- package/dist/extraction/grammars.d.ts +9 -4
- package/dist/extraction/grammars.d.ts.map +1 -1
- package/dist/extraction/grammars.js +133 -65
- package/dist/extraction/grammars.js.map +1 -1
- package/dist/extraction/index.d.ts.map +1 -1
- package/dist/extraction/index.js +119 -7
- package/dist/extraction/index.js.map +1 -1
- package/dist/extraction/tree-sitter.d.ts +62 -0
- package/dist/extraction/tree-sitter.d.ts.map +1 -1
- package/dist/extraction/tree-sitter.js +937 -34
- package/dist/extraction/tree-sitter.js.map +1 -1
- package/dist/graph/traversal.d.ts.map +1 -1
- package/dist/graph/traversal.js +6 -2
- package/dist/graph/traversal.js.map +1 -1
- package/dist/index.d.ts +6 -38
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +85 -73
- package/dist/index.js.map +1 -1
- package/dist/installer/banner.js +7 -7
- package/dist/installer/claude-md-template.js +32 -32
- package/dist/installer/config-writer.d.ts +9 -0
- package/dist/installer/config-writer.d.ts.map +1 -1
- package/dist/installer/config-writer.js +78 -0
- package/dist/installer/config-writer.js.map +1 -1
- package/dist/installer/index.d.ts.map +1 -1
- package/dist/installer/index.js +11 -9
- package/dist/installer/index.js.map +1 -1
- package/dist/mcp/index.d.ts +14 -3
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +109 -29
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/tools.d.ts +62 -1
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +414 -43
- package/dist/mcp/tools.js.map +1 -1
- package/dist/mcp/transport.d.ts.map +1 -1
- package/dist/mcp/transport.js +2 -0
- package/dist/mcp/transport.js.map +1 -1
- package/dist/resolution/frameworks/index.d.ts +1 -0
- package/dist/resolution/frameworks/index.d.ts.map +1 -1
- package/dist/resolution/frameworks/index.js +5 -1
- package/dist/resolution/frameworks/index.js.map +1 -1
- package/dist/resolution/frameworks/svelte.d.ts +9 -0
- package/dist/resolution/frameworks/svelte.d.ts.map +1 -0
- package/dist/resolution/frameworks/svelte.js +268 -0
- package/dist/resolution/frameworks/svelte.js.map +1 -0
- package/dist/resolution/index.d.ts +11 -2
- package/dist/resolution/index.d.ts.map +1 -1
- package/dist/resolution/index.js +94 -13
- package/dist/resolution/index.js.map +1 -1
- package/dist/sentry.d.ts +22 -0
- package/dist/sentry.d.ts.map +1 -0
- package/dist/sentry.js +159 -0
- package/dist/sentry.js.map +1 -0
- package/dist/sync/index.d.ts +4 -2
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/index.js +3 -5
- package/dist/sync/index.js.map +1 -1
- package/dist/types.d.ts +5 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +11 -0
- package/dist/types.js.map +1 -1
- package/dist/utils.d.ts +45 -2
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +114 -3
- package/dist/utils.js.map +1 -1
- package/dist/vectors/embedder.d.ts +1 -1
- package/dist/vectors/embedder.d.ts.map +1 -1
- package/dist/vectors/embedder.js +2 -2
- package/dist/vectors/embedder.js.map +1 -1
- package/dist/vectors/search.d.ts.map +1 -1
- package/dist/vectors/search.js +33 -32
- package/dist/vectors/search.js.map +1 -1
- package/package.json +72 -67
- package/scripts/patch-tree-sitter-dart.js +112 -0
- package/scripts/postinstall.js +71 -68
- package/dist/sync/git-hooks.d.ts +0 -66
- package/dist/sync/git-hooks.d.ts.map +0 -1
- package/dist/sync/git-hooks.js +0 -281
- package/dist/sync/git-hooks.js.map +0 -1
|
@@ -38,11 +38,12 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
38
38
|
};
|
|
39
39
|
})();
|
|
40
40
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
-
exports.LiquidExtractor = exports.TreeSitterExtractor = void 0;
|
|
41
|
+
exports.SvelteExtractor = exports.LiquidExtractor = exports.TreeSitterExtractor = void 0;
|
|
42
42
|
exports.generateNodeId = generateNodeId;
|
|
43
43
|
exports.extractFromSource = extractFromSource;
|
|
44
44
|
const crypto = __importStar(require("crypto"));
|
|
45
45
|
const grammars_1 = require("./grammars");
|
|
46
|
+
const sentry_1 = require("../sentry");
|
|
46
47
|
/**
|
|
47
48
|
* Generate a unique node ID
|
|
48
49
|
*
|
|
@@ -110,8 +111,10 @@ const EXTRACTORS = {
|
|
|
110
111
|
interfaceTypes: ['interface_declaration'],
|
|
111
112
|
structTypes: [],
|
|
112
113
|
enumTypes: ['enum_declaration'],
|
|
114
|
+
typeAliasTypes: ['type_alias_declaration'],
|
|
113
115
|
importTypes: ['import_statement'],
|
|
114
116
|
callTypes: ['call_expression'],
|
|
117
|
+
variableTypes: ['lexical_declaration', 'variable_declaration'],
|
|
115
118
|
nameField: 'name',
|
|
116
119
|
bodyField: 'body',
|
|
117
120
|
paramsField: 'parameters',
|
|
@@ -142,13 +145,18 @@ const EXTRACTORS = {
|
|
|
142
145
|
}
|
|
143
146
|
return undefined;
|
|
144
147
|
},
|
|
145
|
-
isExported: (node,
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
//
|
|
150
|
-
|
|
151
|
-
|
|
148
|
+
isExported: (node, _source) => {
|
|
149
|
+
// Walk the parent chain to find an export_statement ancestor.
|
|
150
|
+
// This correctly handles deeply nested nodes like arrow functions
|
|
151
|
+
// inside variable declarations: `export const X = () => { ... }`
|
|
152
|
+
// where the arrow_function is 3 levels deep under export_statement.
|
|
153
|
+
let current = node.parent;
|
|
154
|
+
while (current) {
|
|
155
|
+
if (current.type === 'export_statement')
|
|
156
|
+
return true;
|
|
157
|
+
current = current.parent;
|
|
158
|
+
}
|
|
159
|
+
return false;
|
|
152
160
|
},
|
|
153
161
|
isAsync: (node) => {
|
|
154
162
|
for (let i = 0; i < node.childCount; i++) {
|
|
@@ -166,6 +174,18 @@ const EXTRACTORS = {
|
|
|
166
174
|
}
|
|
167
175
|
return false;
|
|
168
176
|
},
|
|
177
|
+
isConst: (node) => {
|
|
178
|
+
// For lexical_declaration, check if it's 'const' or 'let'
|
|
179
|
+
// For variable_declaration, it's always 'var'
|
|
180
|
+
if (node.type === 'lexical_declaration') {
|
|
181
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
182
|
+
const child = node.child(i);
|
|
183
|
+
if (child?.type === 'const')
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return false;
|
|
188
|
+
},
|
|
169
189
|
},
|
|
170
190
|
javascript: {
|
|
171
191
|
functionTypes: ['function_declaration', 'arrow_function', 'function_expression'],
|
|
@@ -174,8 +194,10 @@ const EXTRACTORS = {
|
|
|
174
194
|
interfaceTypes: [],
|
|
175
195
|
structTypes: [],
|
|
176
196
|
enumTypes: [],
|
|
197
|
+
typeAliasTypes: [],
|
|
177
198
|
importTypes: ['import_statement'],
|
|
178
199
|
callTypes: ['call_expression'],
|
|
200
|
+
variableTypes: ['lexical_declaration', 'variable_declaration'],
|
|
179
201
|
nameField: 'name',
|
|
180
202
|
bodyField: 'body',
|
|
181
203
|
paramsField: 'parameters',
|
|
@@ -183,12 +205,14 @@ const EXTRACTORS = {
|
|
|
183
205
|
const params = getChildByField(node, 'parameters');
|
|
184
206
|
return params ? getNodeText(params, source) : undefined;
|
|
185
207
|
},
|
|
186
|
-
isExported: (node,
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
208
|
+
isExported: (node, _source) => {
|
|
209
|
+
let current = node.parent;
|
|
210
|
+
while (current) {
|
|
211
|
+
if (current.type === 'export_statement')
|
|
212
|
+
return true;
|
|
213
|
+
current = current.parent;
|
|
214
|
+
}
|
|
215
|
+
return false;
|
|
192
216
|
},
|
|
193
217
|
isAsync: (node) => {
|
|
194
218
|
for (let i = 0; i < node.childCount; i++) {
|
|
@@ -198,6 +222,16 @@ const EXTRACTORS = {
|
|
|
198
222
|
}
|
|
199
223
|
return false;
|
|
200
224
|
},
|
|
225
|
+
isConst: (node) => {
|
|
226
|
+
if (node.type === 'lexical_declaration') {
|
|
227
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
228
|
+
const child = node.child(i);
|
|
229
|
+
if (child?.type === 'const')
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return false;
|
|
234
|
+
},
|
|
201
235
|
},
|
|
202
236
|
python: {
|
|
203
237
|
functionTypes: ['function_definition'],
|
|
@@ -206,8 +240,10 @@ const EXTRACTORS = {
|
|
|
206
240
|
interfaceTypes: [],
|
|
207
241
|
structTypes: [],
|
|
208
242
|
enumTypes: [],
|
|
243
|
+
typeAliasTypes: [],
|
|
209
244
|
importTypes: ['import_statement', 'import_from_statement'],
|
|
210
245
|
callTypes: ['call'],
|
|
246
|
+
variableTypes: ['assignment'], // Python uses assignment for variable declarations
|
|
211
247
|
nameField: 'name',
|
|
212
248
|
bodyField: 'body',
|
|
213
249
|
paramsField: 'parameters',
|
|
@@ -244,8 +280,10 @@ const EXTRACTORS = {
|
|
|
244
280
|
interfaceTypes: ['interface_type'],
|
|
245
281
|
structTypes: ['struct_type'],
|
|
246
282
|
enumTypes: [],
|
|
283
|
+
typeAliasTypes: ['type_spec'], // Go type declarations
|
|
247
284
|
importTypes: ['import_declaration'],
|
|
248
285
|
callTypes: ['call_expression'],
|
|
286
|
+
variableTypes: ['var_declaration', 'short_var_declaration', 'const_declaration'],
|
|
249
287
|
nameField: 'name',
|
|
250
288
|
bodyField: 'body',
|
|
251
289
|
paramsField: 'parameters',
|
|
@@ -269,8 +307,10 @@ const EXTRACTORS = {
|
|
|
269
307
|
interfaceTypes: ['trait_item'],
|
|
270
308
|
structTypes: ['struct_item'],
|
|
271
309
|
enumTypes: ['enum_item'],
|
|
310
|
+
typeAliasTypes: ['type_item'], // Rust type aliases
|
|
272
311
|
importTypes: ['use_declaration'],
|
|
273
312
|
callTypes: ['call_expression'],
|
|
313
|
+
variableTypes: ['let_declaration', 'const_item', 'static_item'],
|
|
274
314
|
nameField: 'name',
|
|
275
315
|
bodyField: 'body',
|
|
276
316
|
paramsField: 'parameters',
|
|
@@ -311,8 +351,10 @@ const EXTRACTORS = {
|
|
|
311
351
|
interfaceTypes: ['interface_declaration'],
|
|
312
352
|
structTypes: [],
|
|
313
353
|
enumTypes: ['enum_declaration'],
|
|
354
|
+
typeAliasTypes: [],
|
|
314
355
|
importTypes: ['import_declaration'],
|
|
315
356
|
callTypes: ['method_invocation'],
|
|
357
|
+
variableTypes: ['local_variable_declaration', 'field_declaration'],
|
|
316
358
|
nameField: 'name',
|
|
317
359
|
bodyField: 'body',
|
|
318
360
|
paramsField: 'parameters',
|
|
@@ -357,8 +399,10 @@ const EXTRACTORS = {
|
|
|
357
399
|
interfaceTypes: [],
|
|
358
400
|
structTypes: ['struct_specifier'],
|
|
359
401
|
enumTypes: ['enum_specifier'],
|
|
402
|
+
typeAliasTypes: ['type_definition'], // typedef
|
|
360
403
|
importTypes: ['preproc_include'],
|
|
361
404
|
callTypes: ['call_expression'],
|
|
405
|
+
variableTypes: ['declaration'],
|
|
362
406
|
nameField: 'declarator',
|
|
363
407
|
bodyField: 'body',
|
|
364
408
|
paramsField: 'parameters',
|
|
@@ -370,8 +414,10 @@ const EXTRACTORS = {
|
|
|
370
414
|
interfaceTypes: [],
|
|
371
415
|
structTypes: ['struct_specifier'],
|
|
372
416
|
enumTypes: ['enum_specifier'],
|
|
417
|
+
typeAliasTypes: ['type_definition', 'alias_declaration'], // typedef and using
|
|
373
418
|
importTypes: ['preproc_include'],
|
|
374
419
|
callTypes: ['call_expression'],
|
|
420
|
+
variableTypes: ['declaration'],
|
|
375
421
|
nameField: 'declarator',
|
|
376
422
|
bodyField: 'body',
|
|
377
423
|
paramsField: 'parameters',
|
|
@@ -402,8 +448,10 @@ const EXTRACTORS = {
|
|
|
402
448
|
interfaceTypes: ['interface_declaration'],
|
|
403
449
|
structTypes: ['struct_declaration'],
|
|
404
450
|
enumTypes: ['enum_declaration'],
|
|
451
|
+
typeAliasTypes: [],
|
|
405
452
|
importTypes: ['using_directive'],
|
|
406
453
|
callTypes: ['invocation_expression'],
|
|
454
|
+
variableTypes: ['local_declaration_statement', 'field_declaration'],
|
|
407
455
|
nameField: 'name',
|
|
408
456
|
bodyField: 'body',
|
|
409
457
|
paramsField: 'parameter_list',
|
|
@@ -450,8 +498,10 @@ const EXTRACTORS = {
|
|
|
450
498
|
interfaceTypes: ['interface_declaration'],
|
|
451
499
|
structTypes: [],
|
|
452
500
|
enumTypes: ['enum_declaration'],
|
|
501
|
+
typeAliasTypes: [],
|
|
453
502
|
importTypes: ['namespace_use_declaration'],
|
|
454
503
|
callTypes: ['function_call_expression', 'member_call_expression', 'scoped_call_expression'],
|
|
504
|
+
variableTypes: ['property_declaration', 'const_declaration'],
|
|
455
505
|
nameField: 'name',
|
|
456
506
|
bodyField: 'body',
|
|
457
507
|
paramsField: 'parameters',
|
|
@@ -487,8 +537,10 @@ const EXTRACTORS = {
|
|
|
487
537
|
interfaceTypes: [], // Ruby uses modules
|
|
488
538
|
structTypes: [],
|
|
489
539
|
enumTypes: [],
|
|
540
|
+
typeAliasTypes: [],
|
|
490
541
|
importTypes: ['call'], // require/require_relative
|
|
491
542
|
callTypes: ['call', 'method_call'],
|
|
543
|
+
variableTypes: ['assignment'], // Ruby uses assignment like Python
|
|
492
544
|
nameField: 'name',
|
|
493
545
|
bodyField: 'body',
|
|
494
546
|
paramsField: 'parameters',
|
|
@@ -520,8 +572,10 @@ const EXTRACTORS = {
|
|
|
520
572
|
interfaceTypes: ['protocol_declaration'],
|
|
521
573
|
structTypes: ['struct_declaration'],
|
|
522
574
|
enumTypes: ['enum_declaration'],
|
|
575
|
+
typeAliasTypes: ['typealias_declaration'],
|
|
523
576
|
importTypes: ['import_declaration'],
|
|
524
577
|
callTypes: ['call_expression'],
|
|
578
|
+
variableTypes: ['property_declaration', 'constant_declaration'],
|
|
525
579
|
nameField: 'name',
|
|
526
580
|
bodyField: 'body',
|
|
527
581
|
paramsField: 'parameter',
|
|
@@ -584,8 +638,10 @@ const EXTRACTORS = {
|
|
|
584
638
|
interfaceTypes: ['class_declaration'], // Interfaces use class_declaration with 'interface' modifier
|
|
585
639
|
structTypes: [], // Kotlin uses data classes
|
|
586
640
|
enumTypes: ['class_declaration'], // Enums use class_declaration with 'enum' modifier
|
|
641
|
+
typeAliasTypes: ['type_alias'],
|
|
587
642
|
importTypes: ['import_header'],
|
|
588
643
|
callTypes: ['call_expression'],
|
|
644
|
+
variableTypes: ['property_declaration'],
|
|
589
645
|
nameField: 'simple_identifier',
|
|
590
646
|
bodyField: 'function_body',
|
|
591
647
|
paramsField: 'function_value_parameters',
|
|
@@ -636,6 +692,80 @@ const EXTRACTORS = {
|
|
|
636
692
|
return false;
|
|
637
693
|
},
|
|
638
694
|
},
|
|
695
|
+
dart: {
|
|
696
|
+
functionTypes: ['function_signature'],
|
|
697
|
+
classTypes: ['class_definition'],
|
|
698
|
+
methodTypes: ['method_signature'],
|
|
699
|
+
interfaceTypes: [],
|
|
700
|
+
structTypes: [],
|
|
701
|
+
enumTypes: ['enum_declaration'],
|
|
702
|
+
typeAliasTypes: ['type_alias'],
|
|
703
|
+
importTypes: ['import_or_export'],
|
|
704
|
+
callTypes: [], // Dart calls use identifier+selector, handled via function body traversal
|
|
705
|
+
variableTypes: [],
|
|
706
|
+
nameField: 'name',
|
|
707
|
+
bodyField: 'body', // class_definition uses 'body' field
|
|
708
|
+
paramsField: 'formal_parameter_list',
|
|
709
|
+
returnField: 'type',
|
|
710
|
+
getSignature: (node, source) => {
|
|
711
|
+
// For function_signature: extract params + return type
|
|
712
|
+
// For method_signature: delegate to inner function_signature
|
|
713
|
+
let sig = node;
|
|
714
|
+
if (node.type === 'method_signature') {
|
|
715
|
+
const inner = node.namedChildren.find((c) => c.type === 'function_signature' || c.type === 'getter_signature' || c.type === 'setter_signature');
|
|
716
|
+
if (inner)
|
|
717
|
+
sig = inner;
|
|
718
|
+
}
|
|
719
|
+
const params = sig.namedChildren.find((c) => c.type === 'formal_parameter_list');
|
|
720
|
+
const retType = sig.namedChildren.find((c) => c.type === 'type_identifier' || c.type === 'void_type');
|
|
721
|
+
if (!params && !retType)
|
|
722
|
+
return undefined;
|
|
723
|
+
let result = '';
|
|
724
|
+
if (retType)
|
|
725
|
+
result += getNodeText(retType, source) + ' ';
|
|
726
|
+
if (params)
|
|
727
|
+
result += getNodeText(params, source);
|
|
728
|
+
return result.trim() || undefined;
|
|
729
|
+
},
|
|
730
|
+
getVisibility: (node) => {
|
|
731
|
+
// Dart convention: _ prefix means private, otherwise public
|
|
732
|
+
let nameNode = null;
|
|
733
|
+
if (node.type === 'method_signature') {
|
|
734
|
+
const inner = node.namedChildren.find((c) => c.type === 'function_signature' || c.type === 'getter_signature' || c.type === 'setter_signature');
|
|
735
|
+
if (inner)
|
|
736
|
+
nameNode = inner.namedChildren.find((c) => c.type === 'identifier') || null;
|
|
737
|
+
}
|
|
738
|
+
else {
|
|
739
|
+
nameNode = node.childForFieldName('name');
|
|
740
|
+
}
|
|
741
|
+
if (nameNode && nameNode.text.startsWith('_'))
|
|
742
|
+
return 'private';
|
|
743
|
+
return 'public';
|
|
744
|
+
},
|
|
745
|
+
isAsync: (node) => {
|
|
746
|
+
// In Dart, 'async' is on the function_body (next sibling), not the signature
|
|
747
|
+
const nextSibling = node.nextNamedSibling;
|
|
748
|
+
if (nextSibling?.type === 'function_body') {
|
|
749
|
+
for (let i = 0; i < nextSibling.childCount; i++) {
|
|
750
|
+
const child = nextSibling.child(i);
|
|
751
|
+
if (child?.type === 'async')
|
|
752
|
+
return true;
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
return false;
|
|
756
|
+
},
|
|
757
|
+
isStatic: (node) => {
|
|
758
|
+
// For method_signature, check for 'static' child
|
|
759
|
+
if (node.type === 'method_signature') {
|
|
760
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
761
|
+
const child = node.child(i);
|
|
762
|
+
if (child?.type === 'static')
|
|
763
|
+
return true;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
return false;
|
|
767
|
+
},
|
|
768
|
+
},
|
|
639
769
|
};
|
|
640
770
|
// TSX and JSX use the same extractors as their base languages
|
|
641
771
|
EXTRACTORS.tsx = EXTRACTORS.typescript;
|
|
@@ -654,6 +784,25 @@ function extractName(node, source, extractor) {
|
|
|
654
784
|
}
|
|
655
785
|
return getNodeText(nameNode, source);
|
|
656
786
|
}
|
|
787
|
+
// For Dart method_signature, look inside inner signature types
|
|
788
|
+
if (node.type === 'method_signature') {
|
|
789
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
790
|
+
const child = node.namedChild(i);
|
|
791
|
+
if (child && (child.type === 'function_signature' ||
|
|
792
|
+
child.type === 'getter_signature' ||
|
|
793
|
+
child.type === 'setter_signature' ||
|
|
794
|
+
child.type === 'constructor_signature' ||
|
|
795
|
+
child.type === 'factory_constructor_signature')) {
|
|
796
|
+
// Find identifier inside the inner signature
|
|
797
|
+
for (let j = 0; j < child.namedChildCount; j++) {
|
|
798
|
+
const inner = child.namedChild(j);
|
|
799
|
+
if (inner?.type === 'identifier') {
|
|
800
|
+
return getNodeText(inner, source);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
}
|
|
657
806
|
// Fall back to first identifier child
|
|
658
807
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
659
808
|
const child = node.namedChild(i);
|
|
@@ -726,6 +875,7 @@ class TreeSitterExtractor {
|
|
|
726
875
|
this.visitNode(this.tree.rootNode);
|
|
727
876
|
}
|
|
728
877
|
catch (error) {
|
|
878
|
+
(0, sentry_1.captureException)(error, { operation: 'tree-sitter-parse', filePath: this.filePath, language: this.language });
|
|
729
879
|
this.errors.push({
|
|
730
880
|
message: `Parse error: ${error instanceof Error ? error.message : String(error)}`,
|
|
731
881
|
severity: 'error',
|
|
@@ -775,6 +925,11 @@ class TreeSitterExtractor {
|
|
|
775
925
|
}
|
|
776
926
|
skipChildren = true; // extractClass visits body children
|
|
777
927
|
}
|
|
928
|
+
// Dart-specific: mixin and extension declarations treated as classes
|
|
929
|
+
else if (this.language === 'dart' && (nodeType === 'mixin_declaration' || nodeType === 'extension_declaration')) {
|
|
930
|
+
this.extractClass(node);
|
|
931
|
+
skipChildren = true;
|
|
932
|
+
}
|
|
778
933
|
// Check for method declarations (only if not already handled by functionTypes)
|
|
779
934
|
else if (this.extractor.methodTypes.includes(nodeType)) {
|
|
780
935
|
this.extractMethod(node);
|
|
@@ -795,6 +950,22 @@ class TreeSitterExtractor {
|
|
|
795
950
|
this.extractEnum(node);
|
|
796
951
|
skipChildren = true; // extractEnum visits body children
|
|
797
952
|
}
|
|
953
|
+
// Check for type alias declarations (e.g. `type X = ...` in TypeScript)
|
|
954
|
+
else if (this.extractor.typeAliasTypes.includes(nodeType)) {
|
|
955
|
+
this.extractTypeAlias(node);
|
|
956
|
+
}
|
|
957
|
+
// Check for variable declarations (const, let, var, etc.)
|
|
958
|
+
// Only extract top-level variables (not inside functions/methods)
|
|
959
|
+
else if (this.extractor.variableTypes.includes(nodeType) && this.nodeStack.length === 0) {
|
|
960
|
+
this.extractVariable(node);
|
|
961
|
+
skipChildren = true; // extractVariable handles children
|
|
962
|
+
}
|
|
963
|
+
// Check for export statements containing non-function variable declarations
|
|
964
|
+
// e.g. `export const X = create(...)`, `export const X = { ... }`
|
|
965
|
+
else if (nodeType === 'export_statement') {
|
|
966
|
+
this.extractExportedVariables(node);
|
|
967
|
+
// Don't skip children — still need to visit inner nodes (functions, calls, etc.)
|
|
968
|
+
}
|
|
798
969
|
// Check for imports
|
|
799
970
|
else if (this.extractor.importTypes.includes(nodeType)) {
|
|
800
971
|
this.extractImport(node);
|
|
@@ -879,7 +1050,21 @@ class TreeSitterExtractor {
|
|
|
879
1050
|
extractFunction(node) {
|
|
880
1051
|
if (!this.extractor)
|
|
881
1052
|
return;
|
|
882
|
-
|
|
1053
|
+
let name = extractName(node, this.source, this.extractor);
|
|
1054
|
+
// For arrow functions and function expressions assigned to variables,
|
|
1055
|
+
// resolve the name from the parent variable_declarator.
|
|
1056
|
+
// e.g. `export const useAuth = () => { ... }` — the arrow_function node
|
|
1057
|
+
// has no `name` field; the name lives on the variable_declarator.
|
|
1058
|
+
if (name === '<anonymous>' &&
|
|
1059
|
+
(node.type === 'arrow_function' || node.type === 'function_expression')) {
|
|
1060
|
+
const parent = node.parent;
|
|
1061
|
+
if (parent?.type === 'variable_declarator') {
|
|
1062
|
+
const varName = getChildByField(parent, 'name');
|
|
1063
|
+
if (varName) {
|
|
1064
|
+
name = getNodeText(varName, this.source);
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
883
1068
|
if (name === '<anonymous>')
|
|
884
1069
|
return; // Skip anonymous functions
|
|
885
1070
|
const docstring = getPrecedingDocstring(node, this.source);
|
|
@@ -898,7 +1083,10 @@ class TreeSitterExtractor {
|
|
|
898
1083
|
});
|
|
899
1084
|
// Push to stack and visit body
|
|
900
1085
|
this.nodeStack.push(funcNode.id);
|
|
901
|
-
|
|
1086
|
+
// Dart: function_body is a next sibling of function_signature, not a child
|
|
1087
|
+
const body = this.language === 'dart'
|
|
1088
|
+
? node.nextNamedSibling?.type === 'function_body' ? node.nextNamedSibling : null
|
|
1089
|
+
: getChildByField(node, this.extractor.bodyField);
|
|
902
1090
|
if (body) {
|
|
903
1091
|
this.visitFunctionBody(body, funcNode.id);
|
|
904
1092
|
}
|
|
@@ -923,7 +1111,13 @@ class TreeSitterExtractor {
|
|
|
923
1111
|
this.extractInheritance(node, classNode.id);
|
|
924
1112
|
// Push to stack and visit body
|
|
925
1113
|
this.nodeStack.push(classNode.id);
|
|
926
|
-
|
|
1114
|
+
let body = getChildByField(node, this.extractor.bodyField);
|
|
1115
|
+
// Dart: mixin_declaration uses class_body, extension uses extension_body
|
|
1116
|
+
if (!body && this.language === 'dart') {
|
|
1117
|
+
body = node.namedChildren.find((c) => c.type === 'class_body' || c.type === 'extension_body') || null;
|
|
1118
|
+
}
|
|
1119
|
+
if (!body)
|
|
1120
|
+
body = node;
|
|
927
1121
|
// Visit all children for methods and properties
|
|
928
1122
|
for (let i = 0; i < body.namedChildCount; i++) {
|
|
929
1123
|
const child = body.namedChild(i);
|
|
@@ -961,7 +1155,10 @@ class TreeSitterExtractor {
|
|
|
961
1155
|
});
|
|
962
1156
|
// Push to stack and visit body
|
|
963
1157
|
this.nodeStack.push(methodNode.id);
|
|
964
|
-
|
|
1158
|
+
// Dart: function_body is a next sibling of method_signature, not a child
|
|
1159
|
+
const body = this.language === 'dart'
|
|
1160
|
+
? node.nextNamedSibling?.type === 'function_body' ? node.nextNamedSibling : null
|
|
1161
|
+
: getChildByField(node, this.extractor.bodyField);
|
|
965
1162
|
if (body) {
|
|
966
1163
|
this.visitFunctionBody(body, methodNode.id);
|
|
967
1164
|
}
|
|
@@ -1027,37 +1224,532 @@ class TreeSitterExtractor {
|
|
|
1027
1224
|
isExported,
|
|
1028
1225
|
});
|
|
1029
1226
|
}
|
|
1227
|
+
/**
|
|
1228
|
+
* Extract a variable declaration (const, let, var, etc.)
|
|
1229
|
+
*
|
|
1230
|
+
* Extracts top-level and module-level variable declarations.
|
|
1231
|
+
* Captures the variable name and first 100 chars of initializer in signature for searchability.
|
|
1232
|
+
*/
|
|
1233
|
+
extractVariable(node) {
|
|
1234
|
+
if (!this.extractor)
|
|
1235
|
+
return;
|
|
1236
|
+
// Different languages have different variable declaration structures
|
|
1237
|
+
// TypeScript/JavaScript: lexical_declaration contains variable_declarator children
|
|
1238
|
+
// Python: assignment has left (identifier) and right (value)
|
|
1239
|
+
// Go: var_declaration, short_var_declaration, const_declaration
|
|
1240
|
+
const isConst = this.extractor.isConst?.(node) ?? false;
|
|
1241
|
+
const kind = isConst ? 'constant' : 'variable';
|
|
1242
|
+
const docstring = getPrecedingDocstring(node, this.source);
|
|
1243
|
+
const isExported = this.extractor.isExported?.(node, this.source) ?? false;
|
|
1244
|
+
// Extract variable declarators based on language
|
|
1245
|
+
if (this.language === 'typescript' || this.language === 'javascript' ||
|
|
1246
|
+
this.language === 'tsx' || this.language === 'jsx') {
|
|
1247
|
+
// Handle lexical_declaration and variable_declaration
|
|
1248
|
+
// These contain one or more variable_declarator children
|
|
1249
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
1250
|
+
const child = node.namedChild(i);
|
|
1251
|
+
if (child?.type === 'variable_declarator') {
|
|
1252
|
+
const nameNode = getChildByField(child, 'name');
|
|
1253
|
+
const valueNode = getChildByField(child, 'value');
|
|
1254
|
+
if (nameNode) {
|
|
1255
|
+
const name = getNodeText(nameNode, this.source);
|
|
1256
|
+
// Arrow functions / function expressions: extract as function instead of variable
|
|
1257
|
+
if (valueNode && (valueNode.type === 'arrow_function' || valueNode.type === 'function_expression')) {
|
|
1258
|
+
this.extractFunction(valueNode);
|
|
1259
|
+
continue;
|
|
1260
|
+
}
|
|
1261
|
+
// Capture first 100 chars of initializer for context (stored in signature for searchability)
|
|
1262
|
+
const initValue = valueNode ? getNodeText(valueNode, this.source).slice(0, 100) : undefined;
|
|
1263
|
+
const initSignature = initValue ? `= ${initValue}${initValue.length >= 100 ? '...' : ''}` : undefined;
|
|
1264
|
+
this.createNode(kind, name, child, {
|
|
1265
|
+
docstring,
|
|
1266
|
+
signature: initSignature,
|
|
1267
|
+
isExported,
|
|
1268
|
+
});
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
else if (this.language === 'python' || this.language === 'ruby') {
|
|
1274
|
+
// Python/Ruby assignment: left = right
|
|
1275
|
+
const left = getChildByField(node, 'left') || node.namedChild(0);
|
|
1276
|
+
const right = getChildByField(node, 'right') || node.namedChild(1);
|
|
1277
|
+
if (left && left.type === 'identifier') {
|
|
1278
|
+
const name = getNodeText(left, this.source);
|
|
1279
|
+
// Skip if name starts with lowercase and looks like a function call result
|
|
1280
|
+
// Python constants are usually UPPER_CASE
|
|
1281
|
+
const initValue = right ? getNodeText(right, this.source).slice(0, 100) : undefined;
|
|
1282
|
+
const initSignature = initValue ? `= ${initValue}${initValue.length >= 100 ? '...' : ''}` : undefined;
|
|
1283
|
+
this.createNode(kind, name, node, {
|
|
1284
|
+
docstring,
|
|
1285
|
+
signature: initSignature,
|
|
1286
|
+
});
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
else if (this.language === 'go') {
|
|
1290
|
+
// Go: var_declaration, short_var_declaration, const_declaration
|
|
1291
|
+
// These can have multiple identifiers on the left
|
|
1292
|
+
const specs = node.namedChildren.filter(c => c.type === 'var_spec' || c.type === 'const_spec');
|
|
1293
|
+
for (const spec of specs) {
|
|
1294
|
+
const nameNode = spec.namedChild(0);
|
|
1295
|
+
if (nameNode && nameNode.type === 'identifier') {
|
|
1296
|
+
const name = getNodeText(nameNode, this.source);
|
|
1297
|
+
const valueNode = spec.namedChildCount > 1 ? spec.namedChild(spec.namedChildCount - 1) : null;
|
|
1298
|
+
const initValue = valueNode ? getNodeText(valueNode, this.source).slice(0, 100) : undefined;
|
|
1299
|
+
const initSignature = initValue ? `= ${initValue}${initValue.length >= 100 ? '...' : ''}` : undefined;
|
|
1300
|
+
this.createNode(node.type === 'const_declaration' ? 'constant' : 'variable', name, spec, {
|
|
1301
|
+
docstring,
|
|
1302
|
+
signature: initSignature,
|
|
1303
|
+
});
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
// Handle short_var_declaration (:=)
|
|
1307
|
+
if (node.type === 'short_var_declaration') {
|
|
1308
|
+
const left = getChildByField(node, 'left');
|
|
1309
|
+
const right = getChildByField(node, 'right');
|
|
1310
|
+
if (left) {
|
|
1311
|
+
// Can be expression_list with multiple identifiers
|
|
1312
|
+
const identifiers = left.type === 'expression_list'
|
|
1313
|
+
? left.namedChildren.filter(c => c.type === 'identifier')
|
|
1314
|
+
: [left];
|
|
1315
|
+
for (const id of identifiers) {
|
|
1316
|
+
const name = getNodeText(id, this.source);
|
|
1317
|
+
const initValue = right ? getNodeText(right, this.source).slice(0, 100) : undefined;
|
|
1318
|
+
const initSignature = initValue ? `= ${initValue}${initValue.length >= 100 ? '...' : ''}` : undefined;
|
|
1319
|
+
this.createNode('variable', name, node, {
|
|
1320
|
+
docstring,
|
|
1321
|
+
signature: initSignature,
|
|
1322
|
+
});
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
else {
|
|
1328
|
+
// Generic fallback for other languages
|
|
1329
|
+
// Try to find identifier children
|
|
1330
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
1331
|
+
const child = node.namedChild(i);
|
|
1332
|
+
if (child?.type === 'identifier' || child?.type === 'variable_declarator') {
|
|
1333
|
+
const name = child.type === 'identifier'
|
|
1334
|
+
? getNodeText(child, this.source)
|
|
1335
|
+
: extractName(child, this.source, this.extractor);
|
|
1336
|
+
if (name && name !== '<anonymous>') {
|
|
1337
|
+
this.createNode(kind, name, child, {
|
|
1338
|
+
docstring,
|
|
1339
|
+
isExported,
|
|
1340
|
+
});
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
/**
|
|
1347
|
+
* Extract a type alias (e.g. `export type X = ...` in TypeScript)
|
|
1348
|
+
*/
|
|
1349
|
+
extractTypeAlias(node) {
|
|
1350
|
+
if (!this.extractor)
|
|
1351
|
+
return;
|
|
1352
|
+
const name = extractName(node, this.source, this.extractor);
|
|
1353
|
+
if (name === '<anonymous>')
|
|
1354
|
+
return;
|
|
1355
|
+
const docstring = getPrecedingDocstring(node, this.source);
|
|
1356
|
+
const isExported = this.extractor.isExported?.(node, this.source);
|
|
1357
|
+
this.createNode('type_alias', name, node, {
|
|
1358
|
+
docstring,
|
|
1359
|
+
isExported,
|
|
1360
|
+
});
|
|
1361
|
+
}
|
|
1362
|
+
/**
|
|
1363
|
+
* Extract an exported variable declaration that isn't a function.
|
|
1364
|
+
* Handles patterns like:
|
|
1365
|
+
* export const X = create(...)
|
|
1366
|
+
* export const X = { ... }
|
|
1367
|
+
* export const X = [...]
|
|
1368
|
+
* export const X = "value"
|
|
1369
|
+
*
|
|
1370
|
+
* This is called for `export_statement` nodes that contain a
|
|
1371
|
+
* `lexical_declaration` with `variable_declarator` children whose
|
|
1372
|
+
* values are NOT already handled by functionTypes (arrow_function,
|
|
1373
|
+
* function_expression).
|
|
1374
|
+
*/
|
|
1375
|
+
extractExportedVariables(exportNode) {
|
|
1376
|
+
if (!this.extractor)
|
|
1377
|
+
return;
|
|
1378
|
+
// Find the lexical_declaration or variable_declaration child
|
|
1379
|
+
for (let i = 0; i < exportNode.namedChildCount; i++) {
|
|
1380
|
+
const decl = exportNode.namedChild(i);
|
|
1381
|
+
if (!decl || (decl.type !== 'lexical_declaration' && decl.type !== 'variable_declaration')) {
|
|
1382
|
+
continue;
|
|
1383
|
+
}
|
|
1384
|
+
// Iterate over each variable_declarator in the declaration
|
|
1385
|
+
for (let j = 0; j < decl.namedChildCount; j++) {
|
|
1386
|
+
const declarator = decl.namedChild(j);
|
|
1387
|
+
if (!declarator || declarator.type !== 'variable_declarator')
|
|
1388
|
+
continue;
|
|
1389
|
+
const nameNode = getChildByField(declarator, 'name');
|
|
1390
|
+
if (!nameNode)
|
|
1391
|
+
continue;
|
|
1392
|
+
const name = getNodeText(nameNode, this.source);
|
|
1393
|
+
// Skip if the value is a function type — those are already handled
|
|
1394
|
+
// by extractFunction via the functionTypes dispatch
|
|
1395
|
+
const value = getChildByField(declarator, 'value');
|
|
1396
|
+
if (value) {
|
|
1397
|
+
const valueType = value.type;
|
|
1398
|
+
if (this.extractor.functionTypes.includes(valueType)) {
|
|
1399
|
+
continue; // Already handled by extractFunction
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
const docstring = getPrecedingDocstring(exportNode, this.source);
|
|
1403
|
+
this.createNode('variable', name, declarator, {
|
|
1404
|
+
docstring,
|
|
1405
|
+
isExported: true,
|
|
1406
|
+
});
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1030
1410
|
/**
|
|
1031
1411
|
* Extract an import
|
|
1412
|
+
*
|
|
1413
|
+
* Creates an import node with the full import statement stored in signature for searchability.
|
|
1414
|
+
* Also creates unresolved references for resolution purposes.
|
|
1032
1415
|
*/
|
|
1033
1416
|
extractImport(node) {
|
|
1034
|
-
|
|
1035
|
-
// For now, we'll create unresolved references
|
|
1036
|
-
const importText = getNodeText(node, this.source);
|
|
1417
|
+
const importText = getNodeText(node, this.source).trim();
|
|
1037
1418
|
// Extract module/package name based on language
|
|
1038
1419
|
let moduleName = '';
|
|
1039
|
-
if (this.language === 'typescript' || this.language === 'javascript'
|
|
1420
|
+
if (this.language === 'typescript' || this.language === 'javascript' ||
|
|
1421
|
+
this.language === 'tsx' || this.language === 'jsx') {
|
|
1040
1422
|
const source = getChildByField(node, 'source');
|
|
1041
1423
|
if (source) {
|
|
1042
1424
|
moduleName = getNodeText(source, this.source).replace(/['"]/g, '');
|
|
1043
1425
|
}
|
|
1426
|
+
// Create import node with full statement as signature for searchability
|
|
1427
|
+
if (moduleName) {
|
|
1428
|
+
this.createNode('import', moduleName, node, {
|
|
1429
|
+
signature: importText,
|
|
1430
|
+
});
|
|
1431
|
+
}
|
|
1044
1432
|
}
|
|
1045
1433
|
else if (this.language === 'python') {
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1434
|
+
// Python has two import forms:
|
|
1435
|
+
// 1. import_statement: import os, sys
|
|
1436
|
+
// 2. import_from_statement: from os import path
|
|
1437
|
+
if (node.type === 'import_from_statement') {
|
|
1438
|
+
const moduleNode = getChildByField(node, 'module_name');
|
|
1439
|
+
if (moduleNode) {
|
|
1440
|
+
moduleName = getNodeText(moduleNode, this.source);
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
else {
|
|
1444
|
+
// import_statement - may have multiple modules
|
|
1445
|
+
// Can be dotted_name (import os) or aliased_import (import numpy as np)
|
|
1446
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
1447
|
+
const child = node.namedChild(i);
|
|
1448
|
+
if (child?.type === 'dotted_name') {
|
|
1449
|
+
const name = getNodeText(child, this.source);
|
|
1450
|
+
this.createNode('import', name, node, {
|
|
1451
|
+
signature: importText,
|
|
1452
|
+
});
|
|
1453
|
+
}
|
|
1454
|
+
else if (child?.type === 'aliased_import') {
|
|
1455
|
+
// Extract the module name from inside aliased_import
|
|
1456
|
+
const dottedName = child.namedChildren.find(c => c.type === 'dotted_name');
|
|
1457
|
+
if (dottedName) {
|
|
1458
|
+
const name = getNodeText(dottedName, this.source);
|
|
1459
|
+
this.createNode('import', name, node, {
|
|
1460
|
+
signature: importText,
|
|
1461
|
+
});
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
// Skip creating another node below if we handled import_statement
|
|
1466
|
+
if (node.type === 'import_statement') {
|
|
1467
|
+
return;
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
if (moduleName) {
|
|
1471
|
+
this.createNode('import', moduleName, node, {
|
|
1472
|
+
signature: importText,
|
|
1473
|
+
});
|
|
1049
1474
|
}
|
|
1050
1475
|
}
|
|
1051
1476
|
else if (this.language === 'go') {
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1477
|
+
// Go imports can be single or grouped
|
|
1478
|
+
// Single: import "fmt" - uses import_spec directly as child
|
|
1479
|
+
// Grouped: import ( "fmt" \n "os" ) - uses import_spec_list containing import_spec children
|
|
1480
|
+
// Helper function to extract path from import_spec
|
|
1481
|
+
const extractFromSpec = (spec) => {
|
|
1482
|
+
const stringLiteral = spec.namedChildren.find(c => c.type === 'interpreted_string_literal');
|
|
1483
|
+
if (stringLiteral) {
|
|
1484
|
+
const path = getNodeText(stringLiteral, this.source).replace(/['"]/g, '');
|
|
1485
|
+
if (path) {
|
|
1486
|
+
this.createNode('import', path, spec, {
|
|
1487
|
+
signature: getNodeText(spec, this.source).trim(),
|
|
1488
|
+
});
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
};
|
|
1492
|
+
// Find import_spec_list for grouped imports
|
|
1493
|
+
const importSpecList = node.namedChildren.find(c => c.type === 'import_spec_list');
|
|
1494
|
+
if (importSpecList) {
|
|
1495
|
+
// Grouped imports - iterate through import_spec children
|
|
1496
|
+
const importSpecs = importSpecList.namedChildren.filter(c => c.type === 'import_spec');
|
|
1497
|
+
for (const spec of importSpecs) {
|
|
1498
|
+
extractFromSpec(spec);
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
else {
|
|
1502
|
+
// Single import: import "fmt" - import_spec is direct child
|
|
1503
|
+
const importSpec = node.namedChildren.find(c => c.type === 'import_spec');
|
|
1504
|
+
if (importSpec) {
|
|
1505
|
+
extractFromSpec(importSpec);
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
return; // Go handled completely above
|
|
1509
|
+
}
|
|
1510
|
+
else if (this.language === 'rust') {
|
|
1511
|
+
// Rust use declarations
|
|
1512
|
+
// use std::{ffi::OsStr, io}; -> scoped_use_list with identifier "std"
|
|
1513
|
+
// use crate::error::Error; -> scoped_identifier starting with "crate"
|
|
1514
|
+
// use super::utils; -> scoped_identifier starting with "super"
|
|
1515
|
+
// Helper to get the root crate/module from a scoped path
|
|
1516
|
+
const getRootModule = (scopedNode) => {
|
|
1517
|
+
// Recursively find the leftmost identifier/crate/super/self
|
|
1518
|
+
const firstChild = scopedNode.namedChild(0);
|
|
1519
|
+
if (!firstChild)
|
|
1520
|
+
return getNodeText(scopedNode, this.source);
|
|
1521
|
+
if (firstChild.type === 'identifier' ||
|
|
1522
|
+
firstChild.type === 'crate' ||
|
|
1523
|
+
firstChild.type === 'super' ||
|
|
1524
|
+
firstChild.type === 'self') {
|
|
1525
|
+
return getNodeText(firstChild, this.source);
|
|
1526
|
+
}
|
|
1527
|
+
else if (firstChild.type === 'scoped_identifier') {
|
|
1528
|
+
return getRootModule(firstChild);
|
|
1529
|
+
}
|
|
1530
|
+
return getNodeText(firstChild, this.source);
|
|
1531
|
+
};
|
|
1532
|
+
// Find the use argument (scoped_use_list or scoped_identifier)
|
|
1533
|
+
const useArg = node.namedChildren.find(c => c.type === 'scoped_use_list' ||
|
|
1534
|
+
c.type === 'scoped_identifier' ||
|
|
1535
|
+
c.type === 'use_list' ||
|
|
1536
|
+
c.type === 'identifier');
|
|
1537
|
+
if (useArg) {
|
|
1538
|
+
moduleName = getRootModule(useArg);
|
|
1539
|
+
this.createNode('import', moduleName, node, {
|
|
1540
|
+
signature: importText,
|
|
1541
|
+
});
|
|
1055
1542
|
}
|
|
1543
|
+
return; // Rust handled completely above
|
|
1544
|
+
}
|
|
1545
|
+
else if (this.language === 'swift') {
|
|
1546
|
+
// Swift imports: import Foundation, @testable import Alamofire
|
|
1547
|
+
// AST structure: import_declaration -> identifier -> simple_identifier
|
|
1548
|
+
const identifier = node.namedChildren.find(c => c.type === 'identifier');
|
|
1549
|
+
if (identifier) {
|
|
1550
|
+
moduleName = getNodeText(identifier, this.source);
|
|
1551
|
+
this.createNode('import', moduleName, node, {
|
|
1552
|
+
signature: importText,
|
|
1553
|
+
});
|
|
1554
|
+
}
|
|
1555
|
+
return; // Swift handled completely above
|
|
1556
|
+
}
|
|
1557
|
+
else if (this.language === 'kotlin') {
|
|
1558
|
+
// Kotlin imports: import java.io.IOException, import x.y.Z as Alias, import x.y.*
|
|
1559
|
+
// AST structure: import_header -> identifier (dotted path)
|
|
1560
|
+
const identifier = node.namedChildren.find(c => c.type === 'identifier');
|
|
1561
|
+
if (identifier) {
|
|
1562
|
+
moduleName = getNodeText(identifier, this.source);
|
|
1563
|
+
this.createNode('import', moduleName, node, {
|
|
1564
|
+
signature: importText,
|
|
1565
|
+
});
|
|
1566
|
+
}
|
|
1567
|
+
return; // Kotlin handled completely above
|
|
1568
|
+
}
|
|
1569
|
+
else if (this.language === 'java') {
|
|
1570
|
+
// Java imports: import java.util.List, import static x.Y.method, import x.y.*
|
|
1571
|
+
// AST structure: import_declaration -> scoped_identifier (dotted path)
|
|
1572
|
+
const scopedId = node.namedChildren.find(c => c.type === 'scoped_identifier');
|
|
1573
|
+
if (scopedId) {
|
|
1574
|
+
moduleName = getNodeText(scopedId, this.source);
|
|
1575
|
+
this.createNode('import', moduleName, node, {
|
|
1576
|
+
signature: importText,
|
|
1577
|
+
});
|
|
1578
|
+
}
|
|
1579
|
+
return; // Java handled completely above
|
|
1580
|
+
}
|
|
1581
|
+
else if (this.language === 'csharp') {
|
|
1582
|
+
// C# using directives: using System, using System.Collections.Generic, using static X, using Alias = X
|
|
1583
|
+
// AST structure: using_directive -> qualified_name (dotted) or identifier (simple)
|
|
1584
|
+
// For alias imports: identifier = qualified_name - we want the qualified_name
|
|
1585
|
+
const qualifiedName = node.namedChildren.find(c => c.type === 'qualified_name');
|
|
1586
|
+
if (qualifiedName) {
|
|
1587
|
+
moduleName = getNodeText(qualifiedName, this.source);
|
|
1588
|
+
}
|
|
1589
|
+
else {
|
|
1590
|
+
// Simple namespace like "using System;" - get the first identifier
|
|
1591
|
+
const identifier = node.namedChildren.find(c => c.type === 'identifier');
|
|
1592
|
+
if (identifier) {
|
|
1593
|
+
moduleName = getNodeText(identifier, this.source);
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
if (moduleName) {
|
|
1597
|
+
this.createNode('import', moduleName, node, {
|
|
1598
|
+
signature: importText,
|
|
1599
|
+
});
|
|
1600
|
+
}
|
|
1601
|
+
return; // C# handled completely above
|
|
1602
|
+
}
|
|
1603
|
+
else if (this.language === 'php') {
|
|
1604
|
+
// PHP use declarations: use X\Y\Z, use X as Y, use function X\func, use X\{A, B}
|
|
1605
|
+
// AST structure: namespace_use_declaration -> namespace_use_clause -> qualified_name or name
|
|
1606
|
+
// Check for grouped imports first: use X\{A, B}
|
|
1607
|
+
const namespacePrefix = node.namedChildren.find(c => c.type === 'namespace_name');
|
|
1608
|
+
const useGroup = node.namedChildren.find(c => c.type === 'namespace_use_group');
|
|
1609
|
+
if (namespacePrefix && useGroup) {
|
|
1610
|
+
// Grouped import - create one import per item
|
|
1611
|
+
const prefix = getNodeText(namespacePrefix, this.source);
|
|
1612
|
+
const useClauses = useGroup.namedChildren.filter((c) => c.type === 'namespace_use_clause');
|
|
1613
|
+
for (const clause of useClauses) {
|
|
1614
|
+
const name = clause.namedChildren.find((c) => c.type === 'name');
|
|
1615
|
+
if (name) {
|
|
1616
|
+
const fullPath = `${prefix}\\${getNodeText(name, this.source)}`;
|
|
1617
|
+
this.createNode('import', fullPath, node, {
|
|
1618
|
+
signature: importText,
|
|
1619
|
+
});
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
return;
|
|
1623
|
+
}
|
|
1624
|
+
// Single import - find namespace_use_clause
|
|
1625
|
+
const useClause = node.namedChildren.find(c => c.type === 'namespace_use_clause');
|
|
1626
|
+
if (useClause) {
|
|
1627
|
+
// Look for qualified_name (full path) or name (simple)
|
|
1628
|
+
const qualifiedName = useClause.namedChildren.find((c) => c.type === 'qualified_name');
|
|
1629
|
+
if (qualifiedName) {
|
|
1630
|
+
moduleName = getNodeText(qualifiedName, this.source);
|
|
1631
|
+
}
|
|
1632
|
+
else {
|
|
1633
|
+
const name = useClause.namedChildren.find((c) => c.type === 'name');
|
|
1634
|
+
if (name) {
|
|
1635
|
+
moduleName = getNodeText(name, this.source);
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
if (moduleName) {
|
|
1640
|
+
this.createNode('import', moduleName, node, {
|
|
1641
|
+
signature: importText,
|
|
1642
|
+
});
|
|
1643
|
+
}
|
|
1644
|
+
return; // PHP handled completely above
|
|
1645
|
+
}
|
|
1646
|
+
else if (this.language === 'ruby') {
|
|
1647
|
+
// Ruby imports: require 'json', require_relative '../helper'
|
|
1648
|
+
// AST structure: call -> identifier (require/require_relative) + argument_list -> string -> string_content
|
|
1649
|
+
// Check if this is a require/require_relative call
|
|
1650
|
+
const identifier = node.namedChildren.find(c => c.type === 'identifier');
|
|
1651
|
+
if (!identifier)
|
|
1652
|
+
return;
|
|
1653
|
+
const methodName = getNodeText(identifier, this.source);
|
|
1654
|
+
if (methodName !== 'require' && methodName !== 'require_relative') {
|
|
1655
|
+
return; // Not an import, skip
|
|
1656
|
+
}
|
|
1657
|
+
// Find the argument (string)
|
|
1658
|
+
const argList = node.namedChildren.find(c => c.type === 'argument_list');
|
|
1659
|
+
if (argList) {
|
|
1660
|
+
const stringNode = argList.namedChildren.find((c) => c.type === 'string');
|
|
1661
|
+
if (stringNode) {
|
|
1662
|
+
// Get string_content (without quotes)
|
|
1663
|
+
const stringContent = stringNode.namedChildren.find((c) => c.type === 'string_content');
|
|
1664
|
+
if (stringContent) {
|
|
1665
|
+
moduleName = getNodeText(stringContent, this.source);
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
if (moduleName) {
|
|
1670
|
+
this.createNode('import', moduleName, node, {
|
|
1671
|
+
signature: importText,
|
|
1672
|
+
});
|
|
1673
|
+
}
|
|
1674
|
+
return; // Ruby handled completely above
|
|
1675
|
+
}
|
|
1676
|
+
else if (this.language === 'dart') {
|
|
1677
|
+
// Dart imports: import 'dart:async'; import 'package:foo/bar.dart' as bar;
|
|
1678
|
+
// AST: import_or_export -> library_import -> import_specification -> configurable_uri -> uri -> string_literal
|
|
1679
|
+
const libraryImport = node.namedChildren.find(c => c.type === 'library_import');
|
|
1680
|
+
if (libraryImport) {
|
|
1681
|
+
const importSpec = libraryImport.namedChildren.find((c) => c.type === 'import_specification');
|
|
1682
|
+
if (importSpec) {
|
|
1683
|
+
const configurableUri = importSpec.namedChildren.find((c) => c.type === 'configurable_uri');
|
|
1684
|
+
if (configurableUri) {
|
|
1685
|
+
const uri = configurableUri.namedChildren.find((c) => c.type === 'uri');
|
|
1686
|
+
if (uri) {
|
|
1687
|
+
const stringLiteral = uri.namedChildren.find((c) => c.type === 'string_literal');
|
|
1688
|
+
if (stringLiteral) {
|
|
1689
|
+
moduleName = getNodeText(stringLiteral, this.source).replace(/['"]/g, '');
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
// Also handle exports: export 'src/foo.dart';
|
|
1696
|
+
const libraryExport = node.namedChildren.find(c => c.type === 'library_export');
|
|
1697
|
+
if (libraryExport) {
|
|
1698
|
+
const configurableUri = libraryExport.namedChildren.find((c) => c.type === 'configurable_uri');
|
|
1699
|
+
if (configurableUri) {
|
|
1700
|
+
const uri = configurableUri.namedChildren.find((c) => c.type === 'uri');
|
|
1701
|
+
if (uri) {
|
|
1702
|
+
const stringLiteral = uri.namedChildren.find((c) => c.type === 'string_literal');
|
|
1703
|
+
if (stringLiteral) {
|
|
1704
|
+
moduleName = getNodeText(stringLiteral, this.source).replace(/['"]/g, '');
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
if (moduleName) {
|
|
1710
|
+
this.createNode('import', moduleName, node, {
|
|
1711
|
+
signature: importText,
|
|
1712
|
+
});
|
|
1713
|
+
}
|
|
1714
|
+
return; // Dart handled completely above
|
|
1715
|
+
}
|
|
1716
|
+
else if (this.language === 'c' || this.language === 'cpp') {
|
|
1717
|
+
// C/C++ includes: #include <iostream>, #include "myheader.h"
|
|
1718
|
+
// AST: preproc_include -> system_lib_string (<...>) or string_literal ("...")
|
|
1719
|
+
// Check for system include: <path>
|
|
1720
|
+
const systemLib = node.namedChildren.find(c => c.type === 'system_lib_string');
|
|
1721
|
+
if (systemLib) {
|
|
1722
|
+
// Remove angle brackets: <iostream> -> iostream
|
|
1723
|
+
moduleName = getNodeText(systemLib, this.source).replace(/^<|>$/g, '');
|
|
1724
|
+
}
|
|
1725
|
+
else {
|
|
1726
|
+
// Check for local include: "path"
|
|
1727
|
+
const stringLiteral = node.namedChildren.find(c => c.type === 'string_literal');
|
|
1728
|
+
if (stringLiteral) {
|
|
1729
|
+
const stringContent = stringLiteral.namedChildren.find((c) => c.type === 'string_content');
|
|
1730
|
+
if (stringContent) {
|
|
1731
|
+
moduleName = getNodeText(stringContent, this.source);
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
if (moduleName) {
|
|
1736
|
+
this.createNode('import', moduleName, node, {
|
|
1737
|
+
signature: importText,
|
|
1738
|
+
});
|
|
1739
|
+
}
|
|
1740
|
+
return; // C/C++ handled completely above
|
|
1056
1741
|
}
|
|
1057
1742
|
else {
|
|
1058
|
-
// Generic extraction
|
|
1743
|
+
// Generic extraction for other languages
|
|
1059
1744
|
moduleName = importText;
|
|
1745
|
+
if (moduleName) {
|
|
1746
|
+
this.createNode('import', moduleName, node, {
|
|
1747
|
+
signature: importText,
|
|
1748
|
+
});
|
|
1749
|
+
}
|
|
1060
1750
|
}
|
|
1751
|
+
// Keep unresolved reference creation for resolution purposes
|
|
1752
|
+
// This is used to resolve imports to their target files/modules
|
|
1061
1753
|
if (moduleName && this.nodeStack.length > 0) {
|
|
1062
1754
|
const parentId = this.nodeStack[this.nodeStack.length - 1];
|
|
1063
1755
|
if (parentId) {
|
|
@@ -1155,7 +1847,9 @@ class TreeSitterExtractor {
|
|
|
1155
1847
|
}
|
|
1156
1848
|
}
|
|
1157
1849
|
if (child.type === 'implements_clause' ||
|
|
1158
|
-
child.type === 'class_interface_clause'
|
|
1850
|
+
child.type === 'class_interface_clause' ||
|
|
1851
|
+
child.type === 'interfaces' // Dart
|
|
1852
|
+
) {
|
|
1159
1853
|
// Extract implemented interfaces
|
|
1160
1854
|
for (let j = 0; j < child.namedChildCount; j++) {
|
|
1161
1855
|
const iface = child.namedChild(j);
|
|
@@ -1213,6 +1907,7 @@ class LiquidExtractor {
|
|
|
1213
1907
|
this.extractAssignments(fileNode.id);
|
|
1214
1908
|
}
|
|
1215
1909
|
catch (error) {
|
|
1910
|
+
(0, sentry_1.captureException)(error, { operation: 'liquid-extraction', filePath: this.filePath });
|
|
1216
1911
|
this.errors.push({
|
|
1217
1912
|
message: `Liquid extraction error: ${error instanceof Error ? error.message : String(error)}`,
|
|
1218
1913
|
severity: 'error',
|
|
@@ -1256,8 +1951,31 @@ class LiquidExtractor {
|
|
|
1256
1951
|
const renderRegex = /\{%[-]?\s*(render|include)\s+['"]([^'"]+)['"]/g;
|
|
1257
1952
|
let match;
|
|
1258
1953
|
while ((match = renderRegex.exec(this.source)) !== null) {
|
|
1259
|
-
const [, tagType, snippetName] = match;
|
|
1954
|
+
const [fullMatch, tagType, snippetName] = match;
|
|
1260
1955
|
const line = this.getLineNumber(match.index);
|
|
1956
|
+
// Create an import node for searchability
|
|
1957
|
+
const importNodeId = generateNodeId(this.filePath, 'import', snippetName, line);
|
|
1958
|
+
const importNode = {
|
|
1959
|
+
id: importNodeId,
|
|
1960
|
+
kind: 'import',
|
|
1961
|
+
name: snippetName,
|
|
1962
|
+
qualifiedName: `${this.filePath}::import:${snippetName}`,
|
|
1963
|
+
filePath: this.filePath,
|
|
1964
|
+
language: 'liquid',
|
|
1965
|
+
signature: fullMatch,
|
|
1966
|
+
startLine: line,
|
|
1967
|
+
endLine: line,
|
|
1968
|
+
startColumn: match.index - this.getLineStart(line),
|
|
1969
|
+
endColumn: match.index - this.getLineStart(line) + fullMatch.length,
|
|
1970
|
+
updatedAt: Date.now(),
|
|
1971
|
+
};
|
|
1972
|
+
this.nodes.push(importNode);
|
|
1973
|
+
// Add containment edge from file to import
|
|
1974
|
+
this.edges.push({
|
|
1975
|
+
source: fileNodeId,
|
|
1976
|
+
target: importNodeId,
|
|
1977
|
+
kind: 'contains',
|
|
1978
|
+
});
|
|
1261
1979
|
// Create a component node for the snippet reference
|
|
1262
1980
|
const nodeId = generateNodeId(this.filePath, 'component', `${tagType}:${snippetName}`, line);
|
|
1263
1981
|
const node = {
|
|
@@ -1270,7 +1988,7 @@ class LiquidExtractor {
|
|
|
1270
1988
|
startLine: line,
|
|
1271
1989
|
endLine: line,
|
|
1272
1990
|
startColumn: match.index - this.getLineStart(line),
|
|
1273
|
-
endColumn: match.index - this.getLineStart(line) +
|
|
1991
|
+
endColumn: match.index - this.getLineStart(line) + fullMatch.length,
|
|
1274
1992
|
updatedAt: Date.now(),
|
|
1275
1993
|
};
|
|
1276
1994
|
this.nodes.push(node);
|
|
@@ -1298,8 +2016,31 @@ class LiquidExtractor {
|
|
|
1298
2016
|
const sectionRegex = /\{%[-]?\s*section\s+['"]([^'"]+)['"]/g;
|
|
1299
2017
|
let match;
|
|
1300
2018
|
while ((match = sectionRegex.exec(this.source)) !== null) {
|
|
1301
|
-
const [, sectionName] = match;
|
|
2019
|
+
const [fullMatch, sectionName] = match;
|
|
1302
2020
|
const line = this.getLineNumber(match.index);
|
|
2021
|
+
// Create an import node for searchability
|
|
2022
|
+
const importNodeId = generateNodeId(this.filePath, 'import', sectionName, line);
|
|
2023
|
+
const importNode = {
|
|
2024
|
+
id: importNodeId,
|
|
2025
|
+
kind: 'import',
|
|
2026
|
+
name: sectionName,
|
|
2027
|
+
qualifiedName: `${this.filePath}::import:${sectionName}`,
|
|
2028
|
+
filePath: this.filePath,
|
|
2029
|
+
language: 'liquid',
|
|
2030
|
+
signature: fullMatch,
|
|
2031
|
+
startLine: line,
|
|
2032
|
+
endLine: line,
|
|
2033
|
+
startColumn: match.index - this.getLineStart(line),
|
|
2034
|
+
endColumn: match.index - this.getLineStart(line) + fullMatch.length,
|
|
2035
|
+
updatedAt: Date.now(),
|
|
2036
|
+
};
|
|
2037
|
+
this.nodes.push(importNode);
|
|
2038
|
+
// Add containment edge from file to import
|
|
2039
|
+
this.edges.push({
|
|
2040
|
+
source: fileNodeId,
|
|
2041
|
+
target: importNodeId,
|
|
2042
|
+
kind: 'contains',
|
|
2043
|
+
});
|
|
1303
2044
|
// Create a component node for the section reference
|
|
1304
2045
|
const nodeId = generateNodeId(this.filePath, 'component', `section:${sectionName}`, line);
|
|
1305
2046
|
const node = {
|
|
@@ -1312,7 +2053,7 @@ class LiquidExtractor {
|
|
|
1312
2053
|
startLine: line,
|
|
1313
2054
|
endLine: line,
|
|
1314
2055
|
startColumn: match.index - this.getLineStart(line),
|
|
1315
|
-
endColumn: match.index - this.getLineStart(line) +
|
|
2056
|
+
endColumn: match.index - this.getLineStart(line) + fullMatch.length,
|
|
1316
2057
|
updatedAt: Date.now(),
|
|
1317
2058
|
};
|
|
1318
2059
|
this.nodes.push(node);
|
|
@@ -1433,11 +2174,173 @@ class LiquidExtractor {
|
|
|
1433
2174
|
}
|
|
1434
2175
|
}
|
|
1435
2176
|
exports.LiquidExtractor = LiquidExtractor;
|
|
2177
|
+
/**
|
|
2178
|
+
* SvelteExtractor - Extracts code relationships from Svelte component files
|
|
2179
|
+
*
|
|
2180
|
+
* Svelte files are multi-language (script + template + style). Rather than
|
|
2181
|
+
* parsing the full Svelte grammar, we extract the <script> block content
|
|
2182
|
+
* and delegate it to the TypeScript/JavaScript TreeSitterExtractor.
|
|
2183
|
+
*
|
|
2184
|
+
* Every .svelte file produces a component node (Svelte components are always importable).
|
|
2185
|
+
*/
|
|
2186
|
+
class SvelteExtractor {
|
|
2187
|
+
filePath;
|
|
2188
|
+
source;
|
|
2189
|
+
nodes = [];
|
|
2190
|
+
edges = [];
|
|
2191
|
+
unresolvedReferences = [];
|
|
2192
|
+
errors = [];
|
|
2193
|
+
constructor(filePath, source) {
|
|
2194
|
+
this.filePath = filePath;
|
|
2195
|
+
this.source = source;
|
|
2196
|
+
}
|
|
2197
|
+
/**
|
|
2198
|
+
* Extract from Svelte source
|
|
2199
|
+
*/
|
|
2200
|
+
extract() {
|
|
2201
|
+
const startTime = Date.now();
|
|
2202
|
+
try {
|
|
2203
|
+
// Create component node for the .svelte file itself
|
|
2204
|
+
const componentNode = this.createComponentNode();
|
|
2205
|
+
// Extract and process script blocks
|
|
2206
|
+
const scriptBlocks = this.extractScriptBlocks();
|
|
2207
|
+
for (const block of scriptBlocks) {
|
|
2208
|
+
this.processScriptBlock(block, componentNode.id);
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
catch (error) {
|
|
2212
|
+
(0, sentry_1.captureException)(error, { operation: 'svelte-extraction', filePath: this.filePath });
|
|
2213
|
+
this.errors.push({
|
|
2214
|
+
message: `Svelte extraction error: ${error instanceof Error ? error.message : String(error)}`,
|
|
2215
|
+
severity: 'error',
|
|
2216
|
+
});
|
|
2217
|
+
}
|
|
2218
|
+
return {
|
|
2219
|
+
nodes: this.nodes,
|
|
2220
|
+
edges: this.edges,
|
|
2221
|
+
unresolvedReferences: this.unresolvedReferences,
|
|
2222
|
+
errors: this.errors,
|
|
2223
|
+
durationMs: Date.now() - startTime,
|
|
2224
|
+
};
|
|
2225
|
+
}
|
|
2226
|
+
/**
|
|
2227
|
+
* Create a component node for the .svelte file
|
|
2228
|
+
*/
|
|
2229
|
+
createComponentNode() {
|
|
2230
|
+
const lines = this.source.split('\n');
|
|
2231
|
+
const fileName = this.filePath.split(/[/\\]/).pop() || this.filePath;
|
|
2232
|
+
const componentName = fileName.replace(/\.svelte$/, '');
|
|
2233
|
+
const id = generateNodeId(this.filePath, 'component', componentName, 1);
|
|
2234
|
+
const node = {
|
|
2235
|
+
id,
|
|
2236
|
+
kind: 'component',
|
|
2237
|
+
name: componentName,
|
|
2238
|
+
qualifiedName: `${this.filePath}::${componentName}`,
|
|
2239
|
+
filePath: this.filePath,
|
|
2240
|
+
language: 'svelte',
|
|
2241
|
+
startLine: 1,
|
|
2242
|
+
endLine: lines.length,
|
|
2243
|
+
startColumn: 0,
|
|
2244
|
+
endColumn: lines[lines.length - 1]?.length || 0,
|
|
2245
|
+
isExported: true, // Svelte components are always importable
|
|
2246
|
+
updatedAt: Date.now(),
|
|
2247
|
+
};
|
|
2248
|
+
this.nodes.push(node);
|
|
2249
|
+
return node;
|
|
2250
|
+
}
|
|
2251
|
+
/**
|
|
2252
|
+
* Extract <script> blocks from the Svelte source
|
|
2253
|
+
*/
|
|
2254
|
+
extractScriptBlocks() {
|
|
2255
|
+
const blocks = [];
|
|
2256
|
+
const scriptRegex = /<script(\s[^>]*)?>(?<content>[\s\S]*?)<\/script>/g;
|
|
2257
|
+
let match;
|
|
2258
|
+
while ((match = scriptRegex.exec(this.source)) !== null) {
|
|
2259
|
+
const attrs = match[1] || '';
|
|
2260
|
+
const content = match.groups?.content || match[2] || '';
|
|
2261
|
+
// Detect TypeScript from lang attribute
|
|
2262
|
+
const isTypeScript = /lang\s*=\s*["'](ts|typescript)["']/.test(attrs);
|
|
2263
|
+
// Detect module script
|
|
2264
|
+
const isModule = /context\s*=\s*["']module["']/.test(attrs);
|
|
2265
|
+
// Calculate start line of the script content (line after <script>)
|
|
2266
|
+
const beforeScript = this.source.substring(0, match.index);
|
|
2267
|
+
const scriptTagLine = (beforeScript.match(/\n/g) || []).length;
|
|
2268
|
+
// The content starts on the line after the opening <script> tag
|
|
2269
|
+
const openingTag = match[0].substring(0, match[0].indexOf('>') + 1);
|
|
2270
|
+
const openingTagLines = (openingTag.match(/\n/g) || []).length;
|
|
2271
|
+
const contentStartLine = scriptTagLine + openingTagLines + 1; // 0-indexed line
|
|
2272
|
+
blocks.push({
|
|
2273
|
+
content,
|
|
2274
|
+
startLine: contentStartLine,
|
|
2275
|
+
isModule,
|
|
2276
|
+
isTypeScript,
|
|
2277
|
+
});
|
|
2278
|
+
}
|
|
2279
|
+
return blocks;
|
|
2280
|
+
}
|
|
2281
|
+
/**
|
|
2282
|
+
* Process a script block by delegating to TreeSitterExtractor
|
|
2283
|
+
*/
|
|
2284
|
+
processScriptBlock(block, componentNodeId) {
|
|
2285
|
+
const scriptLanguage = block.isTypeScript ? 'typescript' : 'javascript';
|
|
2286
|
+
// Check if the script language parser is available
|
|
2287
|
+
if (!(0, grammars_1.isLanguageSupported)(scriptLanguage)) {
|
|
2288
|
+
this.errors.push({
|
|
2289
|
+
message: `Parser for ${scriptLanguage} not available, cannot parse Svelte script block`,
|
|
2290
|
+
severity: 'warning',
|
|
2291
|
+
});
|
|
2292
|
+
return;
|
|
2293
|
+
}
|
|
2294
|
+
// Delegate to TreeSitterExtractor
|
|
2295
|
+
const extractor = new TreeSitterExtractor(this.filePath, block.content, scriptLanguage);
|
|
2296
|
+
const result = extractor.extract();
|
|
2297
|
+
// Offset line numbers from script block back to .svelte file positions
|
|
2298
|
+
for (const node of result.nodes) {
|
|
2299
|
+
node.startLine += block.startLine;
|
|
2300
|
+
node.endLine += block.startLine;
|
|
2301
|
+
node.language = 'svelte'; // Mark as svelte, not TS/JS
|
|
2302
|
+
this.nodes.push(node);
|
|
2303
|
+
// Add containment edge from component to this node
|
|
2304
|
+
this.edges.push({
|
|
2305
|
+
source: componentNodeId,
|
|
2306
|
+
target: node.id,
|
|
2307
|
+
kind: 'contains',
|
|
2308
|
+
});
|
|
2309
|
+
}
|
|
2310
|
+
// Offset edges (they reference line numbers)
|
|
2311
|
+
for (const edge of result.edges) {
|
|
2312
|
+
if (edge.line) {
|
|
2313
|
+
edge.line += block.startLine;
|
|
2314
|
+
}
|
|
2315
|
+
this.edges.push(edge);
|
|
2316
|
+
}
|
|
2317
|
+
// Offset unresolved references
|
|
2318
|
+
for (const ref of result.unresolvedReferences) {
|
|
2319
|
+
ref.line += block.startLine;
|
|
2320
|
+
ref.filePath = this.filePath;
|
|
2321
|
+
ref.language = 'svelte';
|
|
2322
|
+
this.unresolvedReferences.push(ref);
|
|
2323
|
+
}
|
|
2324
|
+
// Carry over errors
|
|
2325
|
+
for (const error of result.errors) {
|
|
2326
|
+
if (error.line) {
|
|
2327
|
+
error.line += block.startLine;
|
|
2328
|
+
}
|
|
2329
|
+
this.errors.push(error);
|
|
2330
|
+
}
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
exports.SvelteExtractor = SvelteExtractor;
|
|
1436
2334
|
/**
|
|
1437
2335
|
* Extract nodes and edges from source code
|
|
1438
2336
|
*/
|
|
1439
2337
|
function extractFromSource(filePath, source, language) {
|
|
1440
2338
|
const detectedLanguage = language || (0, grammars_1.detectLanguage)(filePath);
|
|
2339
|
+
// Use custom extractor for Svelte
|
|
2340
|
+
if (detectedLanguage === 'svelte') {
|
|
2341
|
+
const extractor = new SvelteExtractor(filePath, source);
|
|
2342
|
+
return extractor.extract();
|
|
2343
|
+
}
|
|
1441
2344
|
// Use custom extractor for Liquid
|
|
1442
2345
|
if (detectedLanguage === 'liquid') {
|
|
1443
2346
|
const extractor = new LiquidExtractor(filePath, source);
|