ucn 3.8.11 → 3.8.12
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/.github/workflows/ci.yml +33 -0
- package/.github/workflows/publish.yml +67 -0
- package/README.md +5 -2
- package/core/project.js +30 -9
- package/languages/go.js +249 -216
- package/languages/java.js +303 -250
- package/languages/javascript.js +463 -412
- package/languages/python.js +189 -148
- package/languages/rust.js +394 -337
- package/languages/utils.js +89 -10
- package/mcp/server.js +43 -33
- package/package.json +1 -1
- package/.claude/scheduled_tasks.lock +0 -1
package/languages/rust.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
const {
|
|
9
9
|
traverseTree,
|
|
10
|
+
traverseTreeCached,
|
|
10
11
|
nodeToLocation,
|
|
11
12
|
parseStructuredParams,
|
|
12
13
|
extractRustDocstring
|
|
@@ -61,9 +62,9 @@ function extractVisibility(text) {
|
|
|
61
62
|
* @param {string} code - Source code
|
|
62
63
|
* @returns {string[]} Array of attribute names
|
|
63
64
|
*/
|
|
64
|
-
function extractAttributes(node,
|
|
65
|
+
function extractAttributes(node, codeOrLines) {
|
|
65
66
|
const attributes = [];
|
|
66
|
-
const lines =
|
|
67
|
+
const lines = Array.isArray(codeOrLines) ? codeOrLines : codeOrLines.split('\n');
|
|
67
68
|
|
|
68
69
|
// Look at lines before the function for attributes
|
|
69
70
|
const startLine = node.startPosition.row;
|
|
@@ -92,342 +93,333 @@ function extractAttributes(node, code) {
|
|
|
92
93
|
return attributes;
|
|
93
94
|
}
|
|
94
95
|
|
|
96
|
+
// --- Module-scope constants for state object detection ---
|
|
97
|
+
const _STATE_PATTERN = /^([A-Z][A-Z0-9_]+|DEFAULT_[A-Z_]+)$/;
|
|
98
|
+
|
|
99
|
+
// --- Single-pass helpers: extracted from find* callbacks ---
|
|
100
|
+
|
|
95
101
|
/**
|
|
96
|
-
*
|
|
102
|
+
* Process a node for function extraction (single-pass helper)
|
|
103
|
+
* Returns true if node was matched, false otherwise
|
|
97
104
|
*/
|
|
98
|
-
function
|
|
99
|
-
|
|
100
|
-
const functions = [];
|
|
101
|
-
const processedRanges = new Set();
|
|
102
|
-
|
|
103
|
-
traverseTree(tree.rootNode, (node) => {
|
|
105
|
+
function _processFunction(node, functions, processedRanges, lines, code) {
|
|
106
|
+
if (node.type === 'function_item') {
|
|
104
107
|
const rangeKey = `${node.startIndex}-${node.endIndex}`;
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if (grandparent && (grandparent.type === 'impl_item' || grandparent.type === 'trait_item')) {
|
|
116
|
-
return true; // Skip - this is an impl/trait method
|
|
117
|
-
}
|
|
118
|
-
if (parent.type === 'impl_item' || parent.type === 'trait_item') {
|
|
119
|
-
return true; // Skip - this is an impl/trait method
|
|
120
|
-
}
|
|
108
|
+
if (processedRanges.has(rangeKey)) return true;
|
|
109
|
+
processedRanges.add(rangeKey);
|
|
110
|
+
|
|
111
|
+
// Skip functions inside impl/trait blocks (they're extracted as members)
|
|
112
|
+
let parent = node.parent;
|
|
113
|
+
if (parent && (parent.type === 'impl_item' || parent.type === 'trait_item' || parent.type === 'declaration_list')) {
|
|
114
|
+
// declaration_list is the body of an impl/trait block
|
|
115
|
+
const grandparent = parent.parent;
|
|
116
|
+
if (grandparent && (grandparent.type === 'impl_item' || grandparent.type === 'trait_item')) {
|
|
117
|
+
return true; // Skip - this is an impl/trait method
|
|
121
118
|
}
|
|
119
|
+
if (parent.type === 'impl_item' || parent.type === 'trait_item') {
|
|
120
|
+
return true; // Skip - this is an impl/trait method
|
|
121
|
+
}
|
|
122
|
+
}
|
|
122
123
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if (nameNode) {
|
|
127
|
-
const { startLine, endLine, indent } = nodeToLocation(node, code);
|
|
128
|
-
const text = node.text;
|
|
129
|
-
const firstLine = text.split('\n')[0];
|
|
130
|
-
|
|
131
|
-
const isAsync = firstLine.includes('async ');
|
|
132
|
-
const isUnsafe = firstLine.includes('unsafe ');
|
|
133
|
-
const isConst = firstLine.includes('const fn');
|
|
134
|
-
const isExtern = firstLine.includes('extern ');
|
|
135
|
-
const visibility = extractVisibility(text);
|
|
136
|
-
const returnType = extractReturnType(node);
|
|
137
|
-
const docstring = extractRustDocstring(code, startLine);
|
|
138
|
-
const generics = extractGenerics(node);
|
|
139
|
-
const attributes = extractAttributes(node, code);
|
|
140
|
-
|
|
141
|
-
const modifiers = [];
|
|
142
|
-
if (visibility) modifiers.push(visibility);
|
|
143
|
-
if (isAsync) modifiers.push('async');
|
|
144
|
-
if (isUnsafe) modifiers.push('unsafe');
|
|
145
|
-
if (isConst) modifiers.push('const');
|
|
146
|
-
if (isExtern) modifiers.push('extern');
|
|
147
|
-
// Add attributes like #[test] to modifiers
|
|
148
|
-
for (const attr of attributes) {
|
|
149
|
-
modifiers.push(attr);
|
|
150
|
-
}
|
|
124
|
+
const nameNode = node.childForFieldName('name');
|
|
125
|
+
const paramsNode = node.childForFieldName('parameters');
|
|
151
126
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
127
|
+
if (nameNode) {
|
|
128
|
+
const { startLine, endLine, indent } = nodeToLocation(node, lines);
|
|
129
|
+
const text = node.text;
|
|
130
|
+
const firstLine = text.split('\n')[0];
|
|
131
|
+
|
|
132
|
+
const isAsync = firstLine.includes('async ');
|
|
133
|
+
const isUnsafe = firstLine.includes('unsafe ');
|
|
134
|
+
const isConst = firstLine.includes('const fn');
|
|
135
|
+
const isExtern = firstLine.includes('extern ');
|
|
136
|
+
const visibility = extractVisibility(text);
|
|
137
|
+
const returnType = extractReturnType(node);
|
|
138
|
+
const docstring = extractRustDocstring(lines, startLine);
|
|
139
|
+
const generics = extractGenerics(node);
|
|
140
|
+
const attributes = extractAttributes(node, lines);
|
|
141
|
+
|
|
142
|
+
const modifiers = [];
|
|
143
|
+
if (visibility) modifiers.push(visibility);
|
|
144
|
+
if (isAsync) modifiers.push('async');
|
|
145
|
+
if (isUnsafe) modifiers.push('unsafe');
|
|
146
|
+
if (isConst) modifiers.push('const');
|
|
147
|
+
if (isExtern) modifiers.push('extern');
|
|
148
|
+
// Add attributes like #[test] to modifiers
|
|
149
|
+
for (const attr of attributes) {
|
|
150
|
+
modifiers.push(attr);
|
|
164
151
|
}
|
|
165
|
-
|
|
152
|
+
|
|
153
|
+
functions.push({
|
|
154
|
+
name: nameNode.text,
|
|
155
|
+
params: extractRustParams(paramsNode),
|
|
156
|
+
paramsStructured: parseStructuredParams(paramsNode, 'rust'),
|
|
157
|
+
startLine,
|
|
158
|
+
endLine,
|
|
159
|
+
indent,
|
|
160
|
+
modifiers,
|
|
161
|
+
...(returnType && { returnType }),
|
|
162
|
+
...(docstring && { docstring }),
|
|
163
|
+
...(generics && { generics })
|
|
164
|
+
});
|
|
166
165
|
}
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
167
168
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
})
|
|
199
|
-
}
|
|
169
|
+
// Extern block declarations: extern "C" { fn foreign_func(); }
|
|
170
|
+
if (node.type === 'foreign_mod_item') {
|
|
171
|
+
const rangeKey = `${node.startIndex}-${node.endIndex}`;
|
|
172
|
+
if (processedRanges.has(rangeKey)) return true;
|
|
173
|
+
processedRanges.add(rangeKey);
|
|
174
|
+
|
|
175
|
+
const declList = node.childForFieldName('body');
|
|
176
|
+
if (declList) {
|
|
177
|
+
for (let i = 0; i < declList.namedChildCount; i++) {
|
|
178
|
+
const child = declList.namedChild(i);
|
|
179
|
+
if (child.type === 'function_signature_item') {
|
|
180
|
+
const fName = child.childForFieldName('name');
|
|
181
|
+
const fParams = child.childForFieldName('parameters');
|
|
182
|
+
if (fName) {
|
|
183
|
+
const { startLine, endLine, indent } = nodeToLocation(child, lines);
|
|
184
|
+
const visibility = extractVisibility(child.text);
|
|
185
|
+
const returnType = extractReturnType(child);
|
|
186
|
+
const docstring = extractRustDocstring(lines, startLine);
|
|
187
|
+
const modifiers = ['extern'];
|
|
188
|
+
if (visibility) modifiers.push(visibility);
|
|
189
|
+
|
|
190
|
+
functions.push({
|
|
191
|
+
name: fName.text,
|
|
192
|
+
params: extractRustParams(fParams),
|
|
193
|
+
paramsStructured: parseStructuredParams(fParams, 'rust'),
|
|
194
|
+
startLine,
|
|
195
|
+
endLine,
|
|
196
|
+
indent,
|
|
197
|
+
modifiers,
|
|
198
|
+
...(returnType && { returnType }),
|
|
199
|
+
...(docstring && { docstring })
|
|
200
|
+
});
|
|
200
201
|
}
|
|
201
202
|
}
|
|
202
203
|
}
|
|
203
|
-
return true;
|
|
204
204
|
}
|
|
205
|
-
|
|
206
205
|
return true;
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
functions.sort((a, b) => a.startLine - b.startLine);
|
|
210
|
-
return functions;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* Extract generics from a node
|
|
215
|
-
*/
|
|
216
|
-
function extractGenerics(node) {
|
|
217
|
-
const typeParamsNode = node.childForFieldName('type_parameters');
|
|
218
|
-
if (typeParamsNode) {
|
|
219
|
-
return typeParamsNode.text;
|
|
220
206
|
}
|
|
221
|
-
|
|
207
|
+
|
|
208
|
+
return false;
|
|
222
209
|
}
|
|
223
210
|
|
|
224
211
|
/**
|
|
225
|
-
*
|
|
212
|
+
* Process a node for type/class extraction (single-pass helper)
|
|
213
|
+
* Returns true if node was matched, false otherwise
|
|
214
|
+
* Note: for impl_item, caller should NOT skip subtrees (parse() always returns true)
|
|
226
215
|
*/
|
|
227
|
-
function
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
const processedRanges = new Set();
|
|
231
|
-
|
|
232
|
-
traverseTree(tree.rootNode, (node) => {
|
|
216
|
+
function _processClass(node, types, processedRanges, lines, code) {
|
|
217
|
+
// Struct items
|
|
218
|
+
if (node.type === 'struct_item') {
|
|
233
219
|
const rangeKey = `${node.startIndex}-${node.endIndex}`;
|
|
220
|
+
if (processedRanges.has(rangeKey)) return true;
|
|
221
|
+
processedRanges.add(rangeKey);
|
|
222
|
+
|
|
223
|
+
const nameNode = node.childForFieldName('name');
|
|
224
|
+
if (nameNode) {
|
|
225
|
+
const { startLine, endLine } = nodeToLocation(node, lines);
|
|
226
|
+
const docstring = extractRustDocstring(lines, startLine);
|
|
227
|
+
const visibility = extractVisibility(node.text);
|
|
228
|
+
const generics = extractGenerics(node);
|
|
229
|
+
const members = extractStructFields(node, lines);
|
|
230
|
+
const attributes = extractAttributes(node, lines);
|
|
231
|
+
const modifiers = visibility ? [visibility] : [];
|
|
232
|
+
for (const attr of attributes) modifiers.push(attr);
|
|
234
233
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
const generics = extractGenerics(node);
|
|
246
|
-
const members = extractStructFields(node, code);
|
|
247
|
-
const attributes = extractAttributes(node, code);
|
|
248
|
-
const modifiers = visibility ? [visibility] : [];
|
|
249
|
-
for (const attr of attributes) modifiers.push(attr);
|
|
250
|
-
|
|
251
|
-
types.push({
|
|
252
|
-
name: nameNode.text,
|
|
253
|
-
startLine,
|
|
254
|
-
endLine,
|
|
255
|
-
type: 'struct',
|
|
256
|
-
members,
|
|
257
|
-
modifiers,
|
|
258
|
-
...(docstring && { docstring }),
|
|
259
|
-
...(generics && { generics })
|
|
260
|
-
});
|
|
261
|
-
}
|
|
262
|
-
return true;
|
|
234
|
+
types.push({
|
|
235
|
+
name: nameNode.text,
|
|
236
|
+
startLine,
|
|
237
|
+
endLine,
|
|
238
|
+
type: 'struct',
|
|
239
|
+
members,
|
|
240
|
+
modifiers,
|
|
241
|
+
...(docstring && { docstring }),
|
|
242
|
+
...(generics && { generics })
|
|
243
|
+
});
|
|
263
244
|
}
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
264
247
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
248
|
+
// Enum items
|
|
249
|
+
if (node.type === 'enum_item') {
|
|
250
|
+
const rangeKey = `${node.startIndex}-${node.endIndex}`;
|
|
251
|
+
if (processedRanges.has(rangeKey)) return true;
|
|
252
|
+
processedRanges.add(rangeKey);
|
|
253
|
+
|
|
254
|
+
const nameNode = node.childForFieldName('name');
|
|
255
|
+
if (nameNode) {
|
|
256
|
+
const { startLine, endLine } = nodeToLocation(node, lines);
|
|
257
|
+
const docstring = extractRustDocstring(lines, startLine);
|
|
258
|
+
const visibility = extractVisibility(node.text);
|
|
259
|
+
const generics = extractGenerics(node);
|
|
260
|
+
const attributes = extractAttributes(node, lines);
|
|
261
|
+
const modifiers = visibility ? [visibility] : [];
|
|
262
|
+
for (const attr of attributes) modifiers.push(attr);
|
|
279
263
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
}
|
|
291
|
-
return true;
|
|
264
|
+
types.push({
|
|
265
|
+
name: nameNode.text,
|
|
266
|
+
startLine,
|
|
267
|
+
endLine,
|
|
268
|
+
type: 'enum',
|
|
269
|
+
members: extractEnumVariants(node, lines),
|
|
270
|
+
modifiers,
|
|
271
|
+
...(docstring && { docstring }),
|
|
272
|
+
...(generics && { generics })
|
|
273
|
+
});
|
|
292
274
|
}
|
|
275
|
+
return true;
|
|
276
|
+
}
|
|
293
277
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
278
|
+
// Trait items
|
|
279
|
+
if (node.type === 'trait_item') {
|
|
280
|
+
const rangeKey = `${node.startIndex}-${node.endIndex}`;
|
|
281
|
+
if (processedRanges.has(rangeKey)) return true;
|
|
282
|
+
processedRanges.add(rangeKey);
|
|
298
283
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
284
|
+
const nameNode = node.childForFieldName('name');
|
|
285
|
+
if (nameNode) {
|
|
286
|
+
const { startLine, endLine } = nodeToLocation(node, lines);
|
|
287
|
+
const docstring = extractRustDocstring(lines, startLine);
|
|
288
|
+
const visibility = extractVisibility(node.text);
|
|
289
|
+
const generics = extractGenerics(node);
|
|
305
290
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
}
|
|
317
|
-
return true;
|
|
291
|
+
types.push({
|
|
292
|
+
name: nameNode.text,
|
|
293
|
+
startLine,
|
|
294
|
+
endLine,
|
|
295
|
+
type: 'trait',
|
|
296
|
+
members: extractTraitMembers(node, lines),
|
|
297
|
+
modifiers: visibility ? [visibility] : [],
|
|
298
|
+
...(docstring && { docstring }),
|
|
299
|
+
...(generics && { generics })
|
|
300
|
+
});
|
|
318
301
|
}
|
|
302
|
+
return true;
|
|
303
|
+
}
|
|
319
304
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
305
|
+
// Impl items
|
|
306
|
+
if (node.type === 'impl_item') {
|
|
307
|
+
const rangeKey = `${node.startIndex}-${node.endIndex}`;
|
|
308
|
+
if (processedRanges.has(rangeKey)) return true;
|
|
309
|
+
processedRanges.add(rangeKey);
|
|
310
|
+
|
|
311
|
+
const { startLine, endLine } = nodeToLocation(node, lines);
|
|
312
|
+
const implInfo = extractImplInfo(node);
|
|
313
|
+
const docstring = extractRustDocstring(lines, startLine);
|
|
314
|
+
|
|
315
|
+
types.push({
|
|
316
|
+
name: implInfo.name,
|
|
317
|
+
startLine,
|
|
318
|
+
endLine,
|
|
319
|
+
type: 'impl',
|
|
320
|
+
traitName: implInfo.traitName,
|
|
321
|
+
typeName: implInfo.typeName,
|
|
322
|
+
members: extractImplMembers(node, lines, implInfo.typeName),
|
|
323
|
+
modifiers: [],
|
|
324
|
+
...(docstring && { docstring })
|
|
325
|
+
});
|
|
326
|
+
return true; // matched
|
|
327
|
+
}
|
|
324
328
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
329
|
+
// Module items
|
|
330
|
+
if (node.type === 'mod_item') {
|
|
331
|
+
const rangeKey = `${node.startIndex}-${node.endIndex}`;
|
|
332
|
+
if (processedRanges.has(rangeKey)) return true;
|
|
333
|
+
processedRanges.add(rangeKey);
|
|
334
|
+
|
|
335
|
+
const nameNode = node.childForFieldName('name');
|
|
336
|
+
if (nameNode) {
|
|
337
|
+
const { startLine, endLine } = nodeToLocation(node, lines);
|
|
338
|
+
const docstring = extractRustDocstring(lines, startLine);
|
|
339
|
+
const visibility = extractVisibility(node.text);
|
|
340
|
+
const attributes = extractAttributes(node, lines);
|
|
341
|
+
const modifiers = visibility ? [visibility] : [];
|
|
342
|
+
for (const attr of attributes) modifiers.push(attr);
|
|
328
343
|
|
|
329
344
|
types.push({
|
|
330
|
-
name:
|
|
345
|
+
name: nameNode.text,
|
|
331
346
|
startLine,
|
|
332
347
|
endLine,
|
|
333
|
-
type: '
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
members: extractImplMembers(node, code, implInfo.typeName),
|
|
337
|
-
modifiers: [],
|
|
348
|
+
type: 'module',
|
|
349
|
+
members: [],
|
|
350
|
+
modifiers,
|
|
338
351
|
...(docstring && { docstring })
|
|
339
352
|
});
|
|
340
|
-
return false; // Don't traverse into impl body
|
|
341
353
|
}
|
|
354
|
+
return true;
|
|
355
|
+
}
|
|
342
356
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
357
|
+
// Macro definitions
|
|
358
|
+
if (node.type === 'macro_definition') {
|
|
359
|
+
const rangeKey = `${node.startIndex}-${node.endIndex}`;
|
|
360
|
+
if (processedRanges.has(rangeKey)) return true;
|
|
361
|
+
processedRanges.add(rangeKey);
|
|
347
362
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
const visibility = extractVisibility(node.text);
|
|
353
|
-
const attributes = extractAttributes(node, code);
|
|
354
|
-
const modifiers = visibility ? [visibility] : [];
|
|
355
|
-
for (const attr of attributes) modifiers.push(attr);
|
|
363
|
+
const nameNode = node.childForFieldName('name');
|
|
364
|
+
if (nameNode) {
|
|
365
|
+
const { startLine, endLine } = nodeToLocation(node, lines);
|
|
366
|
+
const docstring = extractRustDocstring(lines, startLine);
|
|
356
367
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
}
|
|
367
|
-
return true;
|
|
368
|
+
types.push({
|
|
369
|
+
name: nameNode.text,
|
|
370
|
+
startLine,
|
|
371
|
+
endLine,
|
|
372
|
+
type: 'macro',
|
|
373
|
+
members: [],
|
|
374
|
+
modifiers: [],
|
|
375
|
+
...(docstring && { docstring })
|
|
376
|
+
});
|
|
368
377
|
}
|
|
378
|
+
return true;
|
|
379
|
+
}
|
|
369
380
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
const nameNode = node.childForFieldName('name');
|
|
376
|
-
if (nameNode) {
|
|
377
|
-
const { startLine, endLine } = nodeToLocation(node, code);
|
|
378
|
-
const docstring = extractRustDocstring(code, startLine);
|
|
381
|
+
// Type aliases (only top-level, not inside traits/impls)
|
|
382
|
+
if (node.type === 'type_item') {
|
|
383
|
+
const rangeKey = `${node.startIndex}-${node.endIndex}`;
|
|
384
|
+
if (processedRanges.has(rangeKey)) return true;
|
|
379
385
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
members: [],
|
|
386
|
-
modifiers: [],
|
|
387
|
-
...(docstring && { docstring })
|
|
388
|
-
});
|
|
386
|
+
// Skip if inside trait or impl
|
|
387
|
+
let parent = node.parent;
|
|
388
|
+
while (parent) {
|
|
389
|
+
if (parent.type === 'trait_item' || parent.type === 'impl_item') {
|
|
390
|
+
return true; // Skip this one
|
|
389
391
|
}
|
|
390
|
-
|
|
392
|
+
parent = parent.parent;
|
|
391
393
|
}
|
|
392
394
|
|
|
393
|
-
|
|
394
|
-
if (node.type === 'type_item') {
|
|
395
|
-
if (processedRanges.has(rangeKey)) return true;
|
|
396
|
-
|
|
397
|
-
// Skip if inside trait or impl
|
|
398
|
-
let parent = node.parent;
|
|
399
|
-
while (parent) {
|
|
400
|
-
if (parent.type === 'trait_item' || parent.type === 'impl_item') {
|
|
401
|
-
return true; // Skip this one
|
|
402
|
-
}
|
|
403
|
-
parent = parent.parent;
|
|
404
|
-
}
|
|
395
|
+
processedRanges.add(rangeKey);
|
|
405
396
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
const
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
const docstring = extractRustDocstring(code, startLine);
|
|
412
|
-
const visibility = extractVisibility(node.text);
|
|
397
|
+
const nameNode = node.childForFieldName('name');
|
|
398
|
+
if (nameNode) {
|
|
399
|
+
const { startLine, endLine } = nodeToLocation(node, lines);
|
|
400
|
+
const docstring = extractRustDocstring(lines, startLine);
|
|
401
|
+
const visibility = extractVisibility(node.text);
|
|
413
402
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
}
|
|
424
|
-
return true;
|
|
403
|
+
types.push({
|
|
404
|
+
name: nameNode.text,
|
|
405
|
+
startLine,
|
|
406
|
+
endLine,
|
|
407
|
+
type: 'type',
|
|
408
|
+
members: [],
|
|
409
|
+
modifiers: visibility ? [visibility] : [],
|
|
410
|
+
...(docstring && { docstring })
|
|
411
|
+
});
|
|
425
412
|
}
|
|
426
|
-
|
|
427
413
|
return true;
|
|
428
|
-
}
|
|
414
|
+
}
|
|
429
415
|
|
|
430
|
-
|
|
416
|
+
return false;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Post-process types: surface trait impls as 'implements' on the corresponding struct/enum
|
|
421
|
+
*/
|
|
422
|
+
function _postProcessTraitImpls(types) {
|
|
431
423
|
const implTraits = new Map(); // typeName → [traitName, ...]
|
|
432
424
|
for (const t of types) {
|
|
433
425
|
if (t.type === 'impl' && t.traitName && t.typeName) {
|
|
@@ -440,7 +432,88 @@ function findClasses(code, parser) {
|
|
|
440
432
|
t.implements = implTraits.get(t.name);
|
|
441
433
|
}
|
|
442
434
|
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Process a node for state object extraction (single-pass helper)
|
|
439
|
+
* Returns true if node was matched, false otherwise
|
|
440
|
+
*/
|
|
441
|
+
function _processState(node, objects, lines) {
|
|
442
|
+
// Handle const items (only top-level)
|
|
443
|
+
if (node.type === 'const_item') {
|
|
444
|
+
if (!node.parent || node.parent.type !== 'source_file') return false;
|
|
445
|
+
const nameNode = node.childForFieldName('name');
|
|
446
|
+
if (nameNode) {
|
|
447
|
+
const name = nameNode.text;
|
|
448
|
+
if (_STATE_PATTERN.test(name)) {
|
|
449
|
+
const { startLine, endLine } = nodeToLocation(node, lines);
|
|
450
|
+
objects.push({ name, startLine, endLine });
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
return true;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Handle static items (only top-level)
|
|
457
|
+
if (node.type === 'static_item') {
|
|
458
|
+
if (!node.parent || node.parent.type !== 'source_file') return false;
|
|
459
|
+
const nameNode = node.childForFieldName('name');
|
|
460
|
+
if (nameNode) {
|
|
461
|
+
const name = nameNode.text;
|
|
462
|
+
if (_STATE_PATTERN.test(name)) {
|
|
463
|
+
const { startLine, endLine } = nodeToLocation(node, lines);
|
|
464
|
+
objects.push({ name, startLine, endLine });
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
return true;
|
|
468
|
+
}
|
|
443
469
|
|
|
470
|
+
return false;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// --- End single-pass helpers ---
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Find all functions in Rust code using tree-sitter
|
|
477
|
+
*/
|
|
478
|
+
function findFunctions(code, parser) {
|
|
479
|
+
const tree = parseTree(parser, code);
|
|
480
|
+
const lines = code.split('\n');
|
|
481
|
+
const functions = [];
|
|
482
|
+
const processedRanges = new Set();
|
|
483
|
+
traverseTreeCached(tree.rootNode, (node) => {
|
|
484
|
+
_processFunction(node, functions, processedRanges, lines, code);
|
|
485
|
+
return true;
|
|
486
|
+
});
|
|
487
|
+
functions.sort((a, b) => a.startLine - b.startLine);
|
|
488
|
+
return functions;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Extract generics from a node
|
|
493
|
+
*/
|
|
494
|
+
function extractGenerics(node) {
|
|
495
|
+
const typeParamsNode = node.childForFieldName('type_parameters');
|
|
496
|
+
if (typeParamsNode) {
|
|
497
|
+
return typeParamsNode.text;
|
|
498
|
+
}
|
|
499
|
+
return null;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Find all types (structs, enums, traits, impls) in Rust code
|
|
504
|
+
*/
|
|
505
|
+
function findClasses(code, parser) {
|
|
506
|
+
const tree = parseTree(parser, code);
|
|
507
|
+
const lines = code.split('\n');
|
|
508
|
+
const types = [];
|
|
509
|
+
const processedRanges = new Set();
|
|
510
|
+
traverseTreeCached(tree.rootNode, (node) => {
|
|
511
|
+
const matched = _processClass(node, types, processedRanges, lines, code);
|
|
512
|
+
// For impl_item, don't traverse into impl body (original behavior)
|
|
513
|
+
if (matched && node.type === 'impl_item') return false;
|
|
514
|
+
return true;
|
|
515
|
+
});
|
|
516
|
+
_postProcessTraitImpls(types);
|
|
444
517
|
types.sort((a, b) => a.startLine - b.startLine);
|
|
445
518
|
return types;
|
|
446
519
|
}
|
|
@@ -448,7 +521,8 @@ function findClasses(code, parser) {
|
|
|
448
521
|
/**
|
|
449
522
|
* Extract struct fields
|
|
450
523
|
*/
|
|
451
|
-
function extractStructFields(structNode,
|
|
524
|
+
function extractStructFields(structNode, codeOrLines) {
|
|
525
|
+
const code = codeOrLines;
|
|
452
526
|
const fields = [];
|
|
453
527
|
const bodyNode = structNode.childForFieldName('body');
|
|
454
528
|
if (!bodyNode) return fields;
|
|
@@ -519,7 +593,8 @@ function extractImplInfo(implNode) {
|
|
|
519
593
|
/**
|
|
520
594
|
* Extract enum variants
|
|
521
595
|
*/
|
|
522
|
-
function extractEnumVariants(enumNode,
|
|
596
|
+
function extractEnumVariants(enumNode, codeOrLines) {
|
|
597
|
+
const code = codeOrLines;
|
|
523
598
|
const variants = [];
|
|
524
599
|
const bodyNode = enumNode.childForFieldName('body');
|
|
525
600
|
if (!bodyNode) return variants;
|
|
@@ -554,7 +629,8 @@ function extractEnumVariants(enumNode, code) {
|
|
|
554
629
|
/**
|
|
555
630
|
* Extract trait method signatures
|
|
556
631
|
*/
|
|
557
|
-
function extractTraitMembers(traitNode,
|
|
632
|
+
function extractTraitMembers(traitNode, codeOrLines) {
|
|
633
|
+
const code = codeOrLines;
|
|
558
634
|
const members = [];
|
|
559
635
|
const bodyNode = traitNode.childForFieldName('body');
|
|
560
636
|
if (!bodyNode) return members;
|
|
@@ -593,7 +669,8 @@ function extractTraitMembers(traitNode, code) {
|
|
|
593
669
|
* @param {string} code - Source code
|
|
594
670
|
* @param {string} [typeName] - The type this impl is for (e.g., "MyStruct")
|
|
595
671
|
*/
|
|
596
|
-
function extractImplMembers(implNode,
|
|
672
|
+
function extractImplMembers(implNode, codeOrLines, typeName) {
|
|
673
|
+
const code = codeOrLines;
|
|
597
674
|
const members = [];
|
|
598
675
|
const bodyNode = implNode.childForFieldName('body');
|
|
599
676
|
if (!bodyNode) return members;
|
|
@@ -617,7 +694,7 @@ function extractImplMembers(implNode, code, typeName) {
|
|
|
617
694
|
const hasSelf = paramsNode && paramsNode.text.includes('self');
|
|
618
695
|
|
|
619
696
|
// Extract attributes (#[test], #[inline], etc.) for impl members
|
|
620
|
-
const attributes = extractAttributes(child,
|
|
697
|
+
const attributes = extractAttributes(child, codeOrLines);
|
|
621
698
|
const modifiers = [];
|
|
622
699
|
if (visibility) modifiers.push(visibility);
|
|
623
700
|
for (const attr of attributes) modifiers.push(attr);
|
|
@@ -648,42 +725,12 @@ function extractImplMembers(implNode, code, typeName) {
|
|
|
648
725
|
*/
|
|
649
726
|
function findStateObjects(code, parser) {
|
|
650
727
|
const tree = parseTree(parser, code);
|
|
728
|
+
const lines = code.split('\n');
|
|
651
729
|
const objects = [];
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
traverseTree(tree.rootNode, (node) => {
|
|
656
|
-
// Handle const items (only top-level)
|
|
657
|
-
if (node.type === 'const_item') {
|
|
658
|
-
if (!node.parent || node.parent.type !== 'source_file') return true;
|
|
659
|
-
const nameNode = node.childForFieldName('name');
|
|
660
|
-
if (nameNode) {
|
|
661
|
-
const name = nameNode.text;
|
|
662
|
-
if (statePattern.test(name)) {
|
|
663
|
-
const { startLine, endLine } = nodeToLocation(node, code);
|
|
664
|
-
objects.push({ name, startLine, endLine });
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
return true;
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
// Handle static items (only top-level)
|
|
671
|
-
if (node.type === 'static_item') {
|
|
672
|
-
if (!node.parent || node.parent.type !== 'source_file') return true;
|
|
673
|
-
const nameNode = node.childForFieldName('name');
|
|
674
|
-
if (nameNode) {
|
|
675
|
-
const name = nameNode.text;
|
|
676
|
-
if (statePattern.test(name)) {
|
|
677
|
-
const { startLine, endLine } = nodeToLocation(node, code);
|
|
678
|
-
objects.push({ name, startLine, endLine });
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
return true;
|
|
682
|
-
}
|
|
683
|
-
|
|
730
|
+
traverseTreeCached(tree.rootNode, (node) => {
|
|
731
|
+
_processState(node, objects, lines);
|
|
684
732
|
return true;
|
|
685
733
|
});
|
|
686
|
-
|
|
687
734
|
objects.sort((a, b) => a.startLine - b.startLine);
|
|
688
735
|
return objects;
|
|
689
736
|
}
|
|
@@ -692,15 +739,25 @@ function findStateObjects(code, parser) {
|
|
|
692
739
|
* Parse a Rust file completely
|
|
693
740
|
*/
|
|
694
741
|
function parse(code, parser) {
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
742
|
+
const tree = parseTree(parser, code);
|
|
743
|
+
const lines = code.split('\n');
|
|
744
|
+
const functions = [], classes = [], stateObjects = [];
|
|
745
|
+
const processedFn = new Set(), processedCls = new Set();
|
|
746
|
+
|
|
747
|
+
traverseTreeCached(tree.rootNode, (node) => {
|
|
748
|
+
_processFunction(node, functions, processedFn, lines, code);
|
|
749
|
+
_processClass(node, classes, processedCls, lines, code);
|
|
750
|
+
_processState(node, stateObjects, lines);
|
|
751
|
+
return true; // always continue, never skip subtrees
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
_postProcessTraitImpls(classes);
|
|
755
|
+
|
|
756
|
+
functions.sort((a, b) => a.startLine - b.startLine);
|
|
757
|
+
classes.sort((a, b) => a.startLine - b.startLine);
|
|
758
|
+
stateObjects.sort((a, b) => a.startLine - b.startLine);
|
|
759
|
+
|
|
760
|
+
return { language: 'rust', totalLines: lines.length, functions, classes, stateObjects, imports: [], exports: [] };
|
|
704
761
|
}
|
|
705
762
|
|
|
706
763
|
/**
|
|
@@ -994,7 +1051,7 @@ function findImportsInCode(code, parser) {
|
|
|
994
1051
|
const tree = parseTree(parser, code);
|
|
995
1052
|
const imports = [];
|
|
996
1053
|
|
|
997
|
-
|
|
1054
|
+
traverseTreeCached(tree.rootNode, (node) => {
|
|
998
1055
|
// use declarations
|
|
999
1056
|
if (node.type === 'use_declaration') {
|
|
1000
1057
|
const line = node.startPosition.row + 1;
|
|
@@ -1098,7 +1155,7 @@ function findImportsInCode(code, parser) {
|
|
|
1098
1155
|
});
|
|
1099
1156
|
|
|
1100
1157
|
// include! macros with non-literal paths
|
|
1101
|
-
|
|
1158
|
+
traverseTreeCached(tree.rootNode, (node) => {
|
|
1102
1159
|
if (node.type === 'macro_invocation') {
|
|
1103
1160
|
const nameNode = node.childForFieldName('macro');
|
|
1104
1161
|
if (nameNode && /^include(_str|_bytes)?$/.test(nameNode.text)) {
|
|
@@ -1144,7 +1201,7 @@ function findExportsInCode(code, parser) {
|
|
|
1144
1201
|
return false;
|
|
1145
1202
|
}
|
|
1146
1203
|
|
|
1147
|
-
|
|
1204
|
+
traverseTreeCached(tree.rootNode, (node) => {
|
|
1148
1205
|
// Public functions
|
|
1149
1206
|
if (node.type === 'function_item' && hasVisibility(node)) {
|
|
1150
1207
|
const nameNode = node.childForFieldName('name');
|
|
@@ -1266,7 +1323,7 @@ function findUsagesInCode(code, name, parser) {
|
|
|
1266
1323
|
const tree = parseTree(parser, code);
|
|
1267
1324
|
const usages = [];
|
|
1268
1325
|
|
|
1269
|
-
|
|
1326
|
+
traverseTreeCached(tree.rootNode, (node) => {
|
|
1270
1327
|
// Look for identifier, field_identifier (method names in obj.method() calls),
|
|
1271
1328
|
// and type_identifier (type references in params, return types, struct expressions, etc.)
|
|
1272
1329
|
const isIdentifier = node.type === 'identifier' || node.type === 'field_identifier' || node.type === 'type_identifier';
|