@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.
- package/LICENSE +21 -0
- package/README.md +641 -0
- package/dist/bin/codegraph.d.ts +20 -0
- package/dist/bin/codegraph.d.ts.map +1 -0
- package/dist/bin/codegraph.js +704 -0
- package/dist/bin/codegraph.js.map +1 -0
- package/dist/config.d.ts +51 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +291 -0
- package/dist/config.js.map +1 -0
- package/dist/context/formatter.d.ts +30 -0
- package/dist/context/formatter.d.ts.map +1 -0
- package/dist/context/formatter.js +244 -0
- package/dist/context/formatter.js.map +1 -0
- package/dist/context/index.d.ts +86 -0
- package/dist/context/index.d.ts.map +1 -0
- package/dist/context/index.js +402 -0
- package/dist/context/index.js.map +1 -0
- package/dist/db/index.d.ts +64 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +170 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/migrations.d.ts +44 -0
- package/dist/db/migrations.d.ts.map +1 -0
- package/dist/db/migrations.js +105 -0
- package/dist/db/migrations.js.map +1 -0
- package/dist/db/queries.d.ts +148 -0
- package/dist/db/queries.d.ts.map +1 -0
- package/dist/db/queries.js +669 -0
- package/dist/db/queries.js.map +1 -0
- package/dist/directory.d.ts +45 -0
- package/dist/directory.d.ts.map +1 -0
- package/dist/directory.js +191 -0
- package/dist/directory.js.map +1 -0
- package/dist/errors.d.ts +136 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +219 -0
- package/dist/errors.js.map +1 -0
- package/dist/extraction/grammars.d.ts +36 -0
- package/dist/extraction/grammars.d.ts.map +1 -0
- package/dist/extraction/grammars.js +181 -0
- package/dist/extraction/grammars.js.map +1 -0
- package/dist/extraction/index.d.ts +91 -0
- package/dist/extraction/index.d.ts.map +1 -0
- package/dist/extraction/index.js +493 -0
- package/dist/extraction/index.js.map +1 -0
- package/dist/extraction/tree-sitter.d.ts +176 -0
- package/dist/extraction/tree-sitter.d.ts.map +1 -0
- package/dist/extraction/tree-sitter.js +1798 -0
- package/dist/extraction/tree-sitter.js.map +1 -0
- package/dist/graph/index.d.ts +8 -0
- package/dist/graph/index.d.ts.map +1 -0
- package/dist/graph/index.js +13 -0
- package/dist/graph/index.js.map +1 -0
- package/dist/graph/queries.d.ts +106 -0
- package/dist/graph/queries.d.ts.map +1 -0
- package/dist/graph/queries.js +355 -0
- package/dist/graph/queries.js.map +1 -0
- package/dist/graph/traversal.d.ts +127 -0
- package/dist/graph/traversal.d.ts.map +1 -0
- package/dist/graph/traversal.js +465 -0
- package/dist/graph/traversal.js.map +1 -0
- package/dist/index.d.ts +496 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +818 -0
- package/dist/index.js.map +1 -0
- package/dist/installer/banner.d.ts +40 -0
- package/dist/installer/banner.d.ts.map +1 -0
- package/dist/installer/banner.js +162 -0
- package/dist/installer/banner.js.map +1 -0
- package/dist/installer/claude-md-template.d.ts +10 -0
- package/dist/installer/claude-md-template.d.ts.map +1 -0
- package/dist/installer/claude-md-template.js +46 -0
- package/dist/installer/claude-md-template.js.map +1 -0
- package/dist/installer/config-writer.d.ts +36 -0
- package/dist/installer/config-writer.d.ts.map +1 -0
- package/dist/installer/config-writer.js +282 -0
- package/dist/installer/config-writer.js.map +1 -0
- package/dist/installer/index.d.ts +13 -0
- package/dist/installer/index.d.ts.map +1 -0
- package/dist/installer/index.js +155 -0
- package/dist/installer/index.js.map +1 -0
- package/dist/installer/prompts.d.ts +18 -0
- package/dist/installer/prompts.d.ts.map +1 -0
- package/dist/installer/prompts.js +113 -0
- package/dist/installer/prompts.js.map +1 -0
- package/dist/mcp/index.d.ts +64 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +207 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/tools.d.ts +93 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +442 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/mcp/transport.d.ts +89 -0
- package/dist/mcp/transport.d.ts.map +1 -0
- package/dist/mcp/transport.js +170 -0
- package/dist/mcp/transport.js.map +1 -0
- package/dist/resolution/frameworks/csharp.d.ts +8 -0
- package/dist/resolution/frameworks/csharp.d.ts.map +1 -0
- package/dist/resolution/frameworks/csharp.js +274 -0
- package/dist/resolution/frameworks/csharp.js.map +1 -0
- package/dist/resolution/frameworks/express.d.ts +8 -0
- package/dist/resolution/frameworks/express.d.ts.map +1 -0
- package/dist/resolution/frameworks/express.js +208 -0
- package/dist/resolution/frameworks/express.js.map +1 -0
- package/dist/resolution/frameworks/go.d.ts +8 -0
- package/dist/resolution/frameworks/go.d.ts.map +1 -0
- package/dist/resolution/frameworks/go.js +225 -0
- package/dist/resolution/frameworks/go.js.map +1 -0
- package/dist/resolution/frameworks/index.d.ts +33 -0
- package/dist/resolution/frameworks/index.d.ts.map +1 -0
- package/dist/resolution/frameworks/index.js +113 -0
- package/dist/resolution/frameworks/index.js.map +1 -0
- package/dist/resolution/frameworks/java.d.ts +8 -0
- package/dist/resolution/frameworks/java.d.ts.map +1 -0
- package/dist/resolution/frameworks/java.js +239 -0
- package/dist/resolution/frameworks/java.js.map +1 -0
- package/dist/resolution/frameworks/laravel.d.ts +13 -0
- package/dist/resolution/frameworks/laravel.d.ts.map +1 -0
- package/dist/resolution/frameworks/laravel.js +198 -0
- package/dist/resolution/frameworks/laravel.js.map +1 -0
- package/dist/resolution/frameworks/python.d.ts +10 -0
- package/dist/resolution/frameworks/python.d.ts.map +1 -0
- package/dist/resolution/frameworks/python.js +331 -0
- package/dist/resolution/frameworks/python.js.map +1 -0
- package/dist/resolution/frameworks/react.d.ts +8 -0
- package/dist/resolution/frameworks/react.d.ts.map +1 -0
- package/dist/resolution/frameworks/react.js +294 -0
- package/dist/resolution/frameworks/react.js.map +1 -0
- package/dist/resolution/frameworks/ruby.d.ts +8 -0
- package/dist/resolution/frameworks/ruby.d.ts.map +1 -0
- package/dist/resolution/frameworks/ruby.js +262 -0
- package/dist/resolution/frameworks/ruby.js.map +1 -0
- package/dist/resolution/frameworks/rust.d.ts +8 -0
- package/dist/resolution/frameworks/rust.d.ts.map +1 -0
- package/dist/resolution/frameworks/rust.js +222 -0
- package/dist/resolution/frameworks/rust.js.map +1 -0
- package/dist/resolution/frameworks/swift.d.ts +10 -0
- package/dist/resolution/frameworks/swift.d.ts.map +1 -0
- package/dist/resolution/frameworks/swift.js +486 -0
- package/dist/resolution/frameworks/swift.js.map +1 -0
- package/dist/resolution/import-resolver.d.ts +20 -0
- package/dist/resolution/import-resolver.d.ts.map +1 -0
- package/dist/resolution/import-resolver.js +445 -0
- package/dist/resolution/import-resolver.js.map +1 -0
- package/dist/resolution/index.d.ts +72 -0
- package/dist/resolution/index.d.ts.map +1 -0
- package/dist/resolution/index.js +301 -0
- package/dist/resolution/index.js.map +1 -0
- package/dist/resolution/name-matcher.d.ts +27 -0
- package/dist/resolution/name-matcher.d.ts.map +1 -0
- package/dist/resolution/name-matcher.js +210 -0
- package/dist/resolution/name-matcher.js.map +1 -0
- package/dist/resolution/types.d.ts +108 -0
- package/dist/resolution/types.d.ts.map +1 -0
- package/dist/resolution/types.js +8 -0
- package/dist/resolution/types.js.map +1 -0
- package/dist/sync/git-hooks.d.ts +66 -0
- package/dist/sync/git-hooks.d.ts.map +1 -0
- package/dist/sync/git-hooks.js +281 -0
- package/dist/sync/git-hooks.js.map +1 -0
- package/dist/sync/index.d.ts +13 -0
- package/dist/sync/index.d.ts.map +1 -0
- package/dist/sync/index.js +18 -0
- package/dist/sync/index.js.map +1 -0
- package/dist/types.d.ts +410 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +165 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +116 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +295 -0
- package/dist/utils.js.map +1 -0
- package/dist/vectors/embedder.d.ts +140 -0
- package/dist/vectors/embedder.d.ts.map +1 -0
- package/dist/vectors/embedder.js +336 -0
- package/dist/vectors/embedder.js.map +1 -0
- package/dist/vectors/index.d.ts +9 -0
- package/dist/vectors/index.d.ts.map +1 -0
- package/dist/vectors/index.js +20 -0
- package/dist/vectors/index.js.map +1 -0
- package/dist/vectors/manager.d.ts +119 -0
- package/dist/vectors/manager.d.ts.map +1 -0
- package/dist/vectors/manager.js +274 -0
- package/dist/vectors/manager.js.map +1 -0
- package/dist/vectors/search.d.ts +134 -0
- package/dist/vectors/search.d.ts.map +1 -0
- package/dist/vectors/search.js +409 -0
- package/dist/vectors/search.js.map +1 -0
- package/package.json +67 -0
- 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
|