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/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, code) {
65
+ function extractAttributes(node, codeOrLines) {
65
66
  const attributes = [];
66
- const lines = code.split('\n');
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
- * Find all functions in Rust code using tree-sitter
102
+ * Process a node for function extraction (single-pass helper)
103
+ * Returns true if node was matched, false otherwise
97
104
  */
98
- function findFunctions(code, parser) {
99
- const tree = parseTree(parser, code);
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
- if (node.type === 'function_item') {
107
- if (processedRanges.has(rangeKey)) return true;
108
- processedRanges.add(rangeKey);
109
-
110
- // Skip functions inside impl/trait blocks (they're extracted as members)
111
- let parent = node.parent;
112
- if (parent && (parent.type === 'impl_item' || parent.type === 'trait_item' || parent.type === 'declaration_list')) {
113
- // declaration_list is the body of an impl/trait block
114
- const grandparent = parent.parent;
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
- const nameNode = node.childForFieldName('name');
124
- const paramsNode = node.childForFieldName('parameters');
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
- functions.push({
153
- name: nameNode.text,
154
- params: extractRustParams(paramsNode),
155
- paramsStructured: parseStructuredParams(paramsNode, 'rust'),
156
- startLine,
157
- endLine,
158
- indent,
159
- modifiers,
160
- ...(returnType && { returnType }),
161
- ...(docstring && { docstring }),
162
- ...(generics && { generics })
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
- return true;
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
- // Extern block declarations: extern "C" { fn foreign_func(); }
169
- if (node.type === 'foreign_mod_item') {
170
- if (processedRanges.has(rangeKey)) return true;
171
- processedRanges.add(rangeKey);
172
-
173
- const declList = node.childForFieldName('body');
174
- if (declList) {
175
- for (let i = 0; i < declList.namedChildCount; i++) {
176
- const child = declList.namedChild(i);
177
- if (child.type === 'function_signature_item') {
178
- const fName = child.childForFieldName('name');
179
- const fParams = child.childForFieldName('parameters');
180
- if (fName) {
181
- const { startLine, endLine, indent } = nodeToLocation(child, code);
182
- const visibility = extractVisibility(child.text);
183
- const returnType = extractReturnType(child);
184
- const docstring = extractRustDocstring(code, startLine);
185
- const modifiers = ['extern'];
186
- if (visibility) modifiers.push(visibility);
187
-
188
- functions.push({
189
- name: fName.text,
190
- params: extractRustParams(fParams),
191
- paramsStructured: parseStructuredParams(fParams, 'rust'),
192
- startLine,
193
- endLine,
194
- indent,
195
- modifiers,
196
- ...(returnType && { returnType }),
197
- ...(docstring && { docstring })
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
- return null;
207
+
208
+ return false;
222
209
  }
223
210
 
224
211
  /**
225
- * Find all types (structs, enums, traits, impls) in Rust code
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 findClasses(code, parser) {
228
- const tree = parseTree(parser, code);
229
- const types = [];
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
- // Struct items
236
- if (node.type === 'struct_item') {
237
- if (processedRanges.has(rangeKey)) return true;
238
- processedRanges.add(rangeKey);
239
-
240
- const nameNode = node.childForFieldName('name');
241
- if (nameNode) {
242
- const { startLine, endLine } = nodeToLocation(node, code);
243
- const docstring = extractRustDocstring(code, startLine);
244
- const visibility = extractVisibility(node.text);
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
- // Enum items
266
- if (node.type === 'enum_item') {
267
- if (processedRanges.has(rangeKey)) return true;
268
- processedRanges.add(rangeKey);
269
-
270
- const nameNode = node.childForFieldName('name');
271
- if (nameNode) {
272
- const { startLine, endLine } = nodeToLocation(node, code);
273
- const docstring = extractRustDocstring(code, startLine);
274
- const visibility = extractVisibility(node.text);
275
- const generics = extractGenerics(node);
276
- const attributes = extractAttributes(node, code);
277
- const modifiers = visibility ? [visibility] : [];
278
- for (const attr of attributes) modifiers.push(attr);
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
- types.push({
281
- name: nameNode.text,
282
- startLine,
283
- endLine,
284
- type: 'enum',
285
- members: extractEnumVariants(node, code),
286
- modifiers,
287
- ...(docstring && { docstring }),
288
- ...(generics && { generics })
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
- // Trait items
295
- if (node.type === 'trait_item') {
296
- if (processedRanges.has(rangeKey)) return true;
297
- processedRanges.add(rangeKey);
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
- const nameNode = node.childForFieldName('name');
300
- if (nameNode) {
301
- const { startLine, endLine } = nodeToLocation(node, code);
302
- const docstring = extractRustDocstring(code, startLine);
303
- const visibility = extractVisibility(node.text);
304
- const generics = extractGenerics(node);
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
- types.push({
307
- name: nameNode.text,
308
- startLine,
309
- endLine,
310
- type: 'trait',
311
- members: extractTraitMembers(node, code),
312
- modifiers: visibility ? [visibility] : [],
313
- ...(docstring && { docstring }),
314
- ...(generics && { generics })
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
- // Impl items
321
- if (node.type === 'impl_item') {
322
- if (processedRanges.has(rangeKey)) return true;
323
- processedRanges.add(rangeKey);
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
- const { startLine, endLine } = nodeToLocation(node, code);
326
- const implInfo = extractImplInfo(node);
327
- const docstring = extractRustDocstring(code, startLine);
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: implInfo.name,
345
+ name: nameNode.text,
331
346
  startLine,
332
347
  endLine,
333
- type: 'impl',
334
- traitName: implInfo.traitName,
335
- typeName: implInfo.typeName,
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
- // Module items
344
- if (node.type === 'mod_item') {
345
- if (processedRanges.has(rangeKey)) return true;
346
- processedRanges.add(rangeKey);
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
- const nameNode = node.childForFieldName('name');
349
- if (nameNode) {
350
- const { startLine, endLine } = nodeToLocation(node, code);
351
- const docstring = extractRustDocstring(code, startLine);
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
- types.push({
358
- name: nameNode.text,
359
- startLine,
360
- endLine,
361
- type: 'module',
362
- members: [],
363
- modifiers,
364
- ...(docstring && { docstring })
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
- // Macro definitions
371
- if (node.type === 'macro_definition') {
372
- if (processedRanges.has(rangeKey)) return true;
373
- processedRanges.add(rangeKey);
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
- types.push({
381
- name: nameNode.text,
382
- startLine,
383
- endLine,
384
- type: 'macro',
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
- return true;
392
+ parent = parent.parent;
391
393
  }
392
394
 
393
- // Type aliases (only top-level, not inside traits/impls)
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
- processedRanges.add(rangeKey);
407
-
408
- const nameNode = node.childForFieldName('name');
409
- if (nameNode) {
410
- const { startLine, endLine } = nodeToLocation(node, code);
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
- types.push({
415
- name: nameNode.text,
416
- startLine,
417
- endLine,
418
- type: 'type',
419
- members: [],
420
- modifiers: visibility ? [visibility] : [],
421
- ...(docstring && { docstring })
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
- // Post-process: surface trait impls as 'implements' on the corresponding struct/enum
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, code) {
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, code) {
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, code) {
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, code, typeName) {
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, code);
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
- const statePattern = /^([A-Z][A-Z0-9_]+|DEFAULT_[A-Z_]+)$/;
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
- return {
696
- language: 'rust',
697
- totalLines: code.split('\n').length,
698
- functions: findFunctions(code, parser),
699
- classes: findClasses(code, parser),
700
- stateObjects: findStateObjects(code, parser),
701
- imports: [],
702
- exports: []
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
- traverseTree(tree.rootNode, (node) => {
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
- traverseTree(tree.rootNode, (node) => {
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
- traverseTree(tree.rootNode, (node) => {
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
- traverseTree(tree.rootNode, (node) => {
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';