ucn 3.8.11 → 3.8.13

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.
@@ -7,6 +7,7 @@
7
7
 
8
8
  const {
9
9
  traverseTree,
10
+ traverseTreeCached,
10
11
  nodeToLocation,
11
12
  extractParams,
12
13
  parseStructuredParams,
@@ -107,236 +108,115 @@ function extractDecorators(node) {
107
108
  return decorators;
108
109
  }
109
110
 
111
+ // --- Single-pass helpers: extracted from find* callbacks ---
112
+
110
113
  /**
111
- * Find all functions in JS/TS code using tree-sitter
112
- * @param {string} code - Source code
113
- * @param {object} parser - Tree-sitter parser instance
114
- * @returns {Array}
114
+ * Process a node for function extraction (single-pass helper)
115
+ * Returns true if node was matched, false otherwise
115
116
  */
116
- function findFunctions(code, parser) {
117
- const tree = parseTree(parser, code);
118
- const functions = [];
119
- const processedRanges = new Set();
120
-
121
- traverseTree(tree.rootNode, (node) => {
122
- const rangeKey = `${node.startIndex}-${node.endIndex}`;
123
-
124
- // Function declarations
125
- if (node.type === 'function_declaration' || node.type === 'generator_function_declaration') {
126
- if (processedRanges.has(rangeKey)) return true;
127
- processedRanges.add(rangeKey);
128
-
129
- const nameNode = node.childForFieldName('name');
130
- const paramsNode = node.childForFieldName('parameters');
131
-
132
- if (nameNode) {
133
- const { startLine, endLine, indent } = nodeToLocation(node, code);
134
- const returnType = extractReturnType(node);
135
- const generics = extractGenerics(node);
136
- const docstring = extractJSDocstring(code, startLine);
137
- const isGen = isGenerator(node);
138
- // Check parent for export status (function_declaration inside export_statement)
139
- const modifiers = node.parent && node.parent.type === 'export_statement'
140
- ? extractModifiers(node.parent.text)
141
- : extractModifiers(node.text);
142
-
143
- functions.push({
144
- name: nameNode.text,
145
- params: extractParams(paramsNode),
146
- paramsStructured: parseStructuredParams(paramsNode, 'javascript'),
147
- startLine,
148
- endLine,
149
- indent,
150
- isArrow: false,
151
- isGenerator: isGen,
152
- modifiers,
153
- ...(returnType && { returnType }),
154
- ...(generics && { generics }),
155
- ...(docstring && { docstring })
156
- });
157
- }
158
- return true;
159
- }
160
-
161
- // TypeScript function signatures (e.g., in .d.ts files)
162
- if (node.type === 'function_signature') {
163
- if (processedRanges.has(rangeKey)) return true;
164
- processedRanges.add(rangeKey);
165
-
166
- const nameNode = node.childForFieldName('name');
167
- const paramsNode = node.childForFieldName('parameters');
168
-
169
- if (nameNode) {
170
- const { startLine, endLine, indent } = nodeToLocation(node, code);
171
- const returnType = extractReturnType(node);
172
- const generics = extractGenerics(node);
173
- const docstring = extractJSDocstring(code, startLine);
174
-
175
- functions.push({
176
- name: nameNode.text,
177
- params: extractParams(paramsNode),
178
- paramsStructured: parseStructuredParams(paramsNode, 'typescript'),
179
- startLine,
180
- endLine,
181
- indent,
182
- isArrow: false,
183
- isGenerator: false,
184
- isSignature: true,
185
- modifiers: [],
186
- ...(returnType && { returnType }),
187
- ...(generics && { generics }),
188
- ...(docstring && { docstring })
189
- });
190
- }
191
- return true;
117
+ function _processFunction(node, functions, processedRanges, lines) {
118
+ const rangeKey = `${node.startIndex}-${node.endIndex}`;
119
+
120
+ // Function declarations
121
+ if (node.type === 'function_declaration' || node.type === 'generator_function_declaration') {
122
+ if (processedRanges.has(rangeKey)) return false;
123
+ processedRanges.add(rangeKey);
124
+
125
+ const nameNode = node.childForFieldName('name');
126
+ const paramsNode = node.childForFieldName('parameters');
127
+
128
+ if (nameNode) {
129
+ const { startLine, endLine, indent } = nodeToLocation(node, lines);
130
+ const returnType = extractReturnType(node);
131
+ const generics = extractGenerics(node);
132
+ const docstring = extractJSDocstring(lines, startLine);
133
+ const isGen = isGenerator(node);
134
+ // Check parent for export status (function_declaration inside export_statement)
135
+ const modifiers = node.parent && node.parent.type === 'export_statement'
136
+ ? extractModifiers(node.parent.text)
137
+ : extractModifiers(node.text);
138
+
139
+ functions.push({
140
+ name: nameNode.text,
141
+ params: extractParams(paramsNode),
142
+ paramsStructured: parseStructuredParams(paramsNode, 'javascript'),
143
+ startLine,
144
+ endLine,
145
+ indent,
146
+ isArrow: false,
147
+ isGenerator: isGen,
148
+ modifiers,
149
+ ...(returnType && { returnType }),
150
+ ...(generics && { generics }),
151
+ ...(docstring && { docstring })
152
+ });
192
153
  }
154
+ return true;
155
+ }
193
156
 
194
- // Variable declarations with arrow functions or function expressions
195
- if (node.type === 'lexical_declaration' || node.type === 'variable_declaration') {
196
- if (processedRanges.has(rangeKey)) return true;
197
-
198
- for (let i = 0; i < node.namedChildCount; i++) {
199
- const declarator = node.namedChild(i);
200
- if (declarator.type === 'variable_declarator') {
201
- const nameNode = declarator.childForFieldName('name');
202
- const valueNode = declarator.childForFieldName('value');
203
-
204
- if (nameNode && valueNode) {
205
- const isArrow = valueNode.type === 'arrow_function';
206
- const isFnExpr = valueNode.type === 'function_expression' ||
207
- valueNode.type === 'generator_function';
208
-
209
- if (isArrow || isFnExpr) {
210
- processedRanges.add(rangeKey);
211
- const paramsNode = valueNode.childForFieldName('parameters');
212
- const { startLine, endLine, indent } = nodeToLocation(node, code);
213
- const returnType = extractReturnType(valueNode);
214
- const generics = extractGenerics(valueNode);
215
- const docstring = extractJSDocstring(code, startLine);
216
- const isGen = isGenerator(valueNode);
217
- // Check parent for export status (lexical_declaration inside export_statement)
218
- const modifiers = node.parent && node.parent.type === 'export_statement'
219
- ? extractModifiers(node.parent.text)
220
- : extractModifiers(node.text);
221
-
222
- functions.push({
223
- name: nameNode.text,
224
- params: extractParams(paramsNode),
225
- paramsStructured: parseStructuredParams(paramsNode, 'javascript'),
226
- startLine,
227
- endLine,
228
- indent,
229
- isArrow,
230
- isGenerator: isGen,
231
- modifiers,
232
- ...(returnType && { returnType }),
233
- ...(generics && { generics }),
234
- ...(docstring && { docstring })
235
- });
236
- }
237
-
238
- // React wrapper patterns: React.forwardRef(...), React.memo(...), forwardRef(...), memo(...)
239
- // const Button = React.forwardRef<Props, Ref>((props, ref) => ...)
240
- // const Memoized = memo((props) => ...)
241
- if (!isArrow && !isFnExpr && valueNode.type === 'call_expression') {
242
- const funcNode = valueNode.childForFieldName('function');
243
- if (funcNode) {
244
- let wrapperName = null;
245
- if (funcNode.type === 'member_expression') {
246
- const prop = funcNode.childForFieldName('property');
247
- wrapperName = prop?.text;
248
- } else if (funcNode.type === 'identifier') {
249
- wrapperName = funcNode.text;
250
- }
251
- if (wrapperName === 'forwardRef' || wrapperName === 'memo') {
252
- const argsNode = valueNode.childForFieldName('arguments');
253
- if (argsNode && argsNode.namedChildCount > 0) {
254
- const innerFn = argsNode.namedChild(0);
255
- if (innerFn && (innerFn.type === 'arrow_function' || innerFn.type === 'function_expression')) {
256
- processedRanges.add(rangeKey);
257
- const paramsNode = innerFn.childForFieldName('parameters');
258
- const { startLine, endLine, indent } = nodeToLocation(node, code);
259
- const returnType = extractReturnType(innerFn);
260
- const generics = extractGenerics(innerFn);
261
- const docstring = extractJSDocstring(code, startLine);
262
- const modifiers = node.parent && node.parent.type === 'export_statement'
263
- ? extractModifiers(node.parent.text)
264
- : extractModifiers(node.text);
265
-
266
- functions.push({
267
- name: nameNode.text,
268
- params: extractParams(paramsNode),
269
- paramsStructured: parseStructuredParams(paramsNode, 'javascript'),
270
- startLine,
271
- endLine,
272
- indent,
273
- isArrow: innerFn.type === 'arrow_function',
274
- isGenerator: false,
275
- modifiers,
276
- ...(returnType && { returnType }),
277
- ...(generics && { generics }),
278
- ...(docstring && { docstring })
279
- });
280
- }
281
- }
282
- }
283
- }
284
- }
285
- }
286
- }
287
- }
288
- return true;
157
+ // TypeScript function signatures (e.g., in .d.ts files)
158
+ if (node.type === 'function_signature') {
159
+ if (processedRanges.has(rangeKey)) return false;
160
+ processedRanges.add(rangeKey);
161
+
162
+ const nameNode = node.childForFieldName('name');
163
+ const paramsNode = node.childForFieldName('parameters');
164
+
165
+ if (nameNode) {
166
+ const { startLine, endLine, indent } = nodeToLocation(node, lines);
167
+ const returnType = extractReturnType(node);
168
+ const generics = extractGenerics(node);
169
+ const docstring = extractJSDocstring(lines, startLine);
170
+
171
+ functions.push({
172
+ name: nameNode.text,
173
+ params: extractParams(paramsNode),
174
+ paramsStructured: parseStructuredParams(paramsNode, 'typescript'),
175
+ startLine,
176
+ endLine,
177
+ indent,
178
+ isArrow: false,
179
+ isGenerator: false,
180
+ isSignature: true,
181
+ modifiers: [],
182
+ ...(returnType && { returnType }),
183
+ ...(generics && { generics }),
184
+ ...(docstring && { docstring })
185
+ });
289
186
  }
187
+ return true;
188
+ }
290
189
 
291
- // Assignment expressions: obj.method = function() {} or prototype assignments
292
- if (node.type === 'assignment_expression') {
293
- if (processedRanges.has(rangeKey)) return true;
294
-
295
- const leftNode = node.childForFieldName('left');
296
- const isPrototypeAssignment = leftNode && leftNode.type === 'member_expression' &&
297
- leftNode.text.includes('.prototype.');
298
-
299
- // For non-prototype assignments, check if nested
300
- if (!isPrototypeAssignment) {
301
- let parent = node.parent;
302
- let isTopLevel = true;
303
- while (parent) {
304
- const ptype = parent.type;
305
- if (ptype === 'function_declaration' || ptype === 'arrow_function' ||
306
- ptype === 'function_expression' || ptype === 'method_definition' ||
307
- ptype === 'generator_function_declaration' || ptype === 'generator_function' ||
308
- ptype === 'class_body') {
309
- isTopLevel = false;
310
- break;
311
- }
312
- if (ptype === 'program' || ptype === 'module') {
313
- break;
314
- }
315
- parent = parent.parent;
316
- }
317
- if (!isTopLevel) return true;
318
- }
190
+ // Variable declarations with arrow functions or function expressions
191
+ if (node.type === 'lexical_declaration' || node.type === 'variable_declaration') {
192
+ if (processedRanges.has(rangeKey)) return false;
319
193
 
320
- const rightNode = node.childForFieldName('right');
194
+ for (let i = 0; i < node.namedChildCount; i++) {
195
+ const declarator = node.namedChild(i);
196
+ if (declarator.type === 'variable_declarator') {
197
+ const nameNode = declarator.childForFieldName('name');
198
+ const valueNode = declarator.childForFieldName('value');
321
199
 
322
- if (leftNode && rightNode) {
323
- const isArrow = rightNode.type === 'arrow_function';
324
- const isFnExpr = rightNode.type === 'function_expression' ||
325
- rightNode.type === 'generator_function';
200
+ if (nameNode && valueNode) {
201
+ const isArrow = valueNode.type === 'arrow_function';
202
+ const isFnExpr = valueNode.type === 'function_expression' ||
203
+ valueNode.type === 'generator_function';
326
204
 
327
- if (isArrow || isFnExpr) {
328
- const name = getAssignmentName(leftNode);
329
- if (name) {
205
+ if (isArrow || isFnExpr) {
330
206
  processedRanges.add(rangeKey);
331
- const paramsNode = rightNode.childForFieldName('parameters');
332
- const { startLine, endLine, indent } = nodeToLocation(node, code);
333
- const returnType = extractReturnType(rightNode);
334
- const generics = extractGenerics(rightNode);
335
- const docstring = extractJSDocstring(code, startLine);
336
- const isGen = isGenerator(rightNode);
207
+ const paramsNode = valueNode.childForFieldName('parameters');
208
+ const { startLine, endLine, indent } = nodeToLocation(node, lines);
209
+ const returnType = extractReturnType(valueNode);
210
+ const generics = extractGenerics(valueNode);
211
+ const docstring = extractJSDocstring(lines, startLine);
212
+ const isGen = isGenerator(valueNode);
213
+ // Check parent for export status (lexical_declaration inside export_statement)
214
+ const modifiers = node.parent && node.parent.type === 'export_statement'
215
+ ? extractModifiers(node.parent.text)
216
+ : extractModifiers(node.text);
337
217
 
338
218
  functions.push({
339
- name,
219
+ name: nameNode.text,
340
220
  params: extractParams(paramsNode),
341
221
  paramsStructured: parseStructuredParams(paramsNode, 'javascript'),
342
222
  startLine,
@@ -344,190 +224,334 @@ function findFunctions(code, parser) {
344
224
  indent,
345
225
  isArrow,
346
226
  isGenerator: isGen,
347
- modifiers: [],
227
+ modifiers,
348
228
  ...(returnType && { returnType }),
349
229
  ...(generics && { generics }),
350
230
  ...(docstring && { docstring })
351
231
  });
352
232
  }
233
+
234
+ // React wrapper patterns: React.forwardRef(...), React.memo(...), forwardRef(...), memo(...)
235
+ // const Button = React.forwardRef<Props, Ref>((props, ref) => ...)
236
+ // const Memoized = memo((props) => ...)
237
+ if (!isArrow && !isFnExpr && valueNode.type === 'call_expression') {
238
+ const funcNode = valueNode.childForFieldName('function');
239
+ if (funcNode) {
240
+ let wrapperName = null;
241
+ if (funcNode.type === 'member_expression') {
242
+ const prop = funcNode.childForFieldName('property');
243
+ wrapperName = prop?.text;
244
+ } else if (funcNode.type === 'identifier') {
245
+ wrapperName = funcNode.text;
246
+ }
247
+ if (wrapperName === 'forwardRef' || wrapperName === 'memo') {
248
+ const argsNode = valueNode.childForFieldName('arguments');
249
+ if (argsNode && argsNode.namedChildCount > 0) {
250
+ const innerFn = argsNode.namedChild(0);
251
+ if (innerFn && (innerFn.type === 'arrow_function' || innerFn.type === 'function_expression')) {
252
+ processedRanges.add(rangeKey);
253
+ const paramsNode = innerFn.childForFieldName('parameters');
254
+ const { startLine, endLine, indent } = nodeToLocation(node, lines);
255
+ const returnType = extractReturnType(innerFn);
256
+ const generics = extractGenerics(innerFn);
257
+ const docstring = extractJSDocstring(lines, startLine);
258
+ const modifiers = node.parent && node.parent.type === 'export_statement'
259
+ ? extractModifiers(node.parent.text)
260
+ : extractModifiers(node.text);
261
+
262
+ functions.push({
263
+ name: nameNode.text,
264
+ params: extractParams(paramsNode),
265
+ paramsStructured: parseStructuredParams(paramsNode, 'javascript'),
266
+ startLine,
267
+ endLine,
268
+ indent,
269
+ isArrow: innerFn.type === 'arrow_function',
270
+ isGenerator: false,
271
+ modifiers,
272
+ ...(returnType && { returnType }),
273
+ ...(generics && { generics }),
274
+ ...(docstring && { docstring })
275
+ });
276
+ }
277
+ }
278
+ }
279
+ }
280
+ }
353
281
  }
354
282
  }
355
- return true;
356
283
  }
284
+ return true;
285
+ }
357
286
 
358
- // Export statements with anonymous functions
359
- if (node.type === 'export_statement') {
360
- const declaration = node.childForFieldName('declaration');
361
- if (!declaration) {
362
- for (let i = 0; i < node.namedChildCount; i++) {
363
- const child = node.namedChild(i);
364
- if (child.type === 'arrow_function' || child.type === 'function_expression' ||
365
- child.type === 'generator_function') {
366
- if (processedRanges.has(rangeKey)) return true;
367
- processedRanges.add(rangeKey);
368
-
369
- const paramsNode = child.childForFieldName('parameters');
370
- const { startLine, endLine, indent } = nodeToLocation(node, code);
371
- const returnType = extractReturnType(child);
372
- const generics = extractGenerics(child);
373
- const docstring = extractJSDocstring(code, startLine);
374
- const isGen = isGenerator(child);
287
+ // Assignment expressions: obj.method = function() {} or prototype assignments
288
+ if (node.type === 'assignment_expression') {
289
+ if (processedRanges.has(rangeKey)) return false;
290
+
291
+ const leftNode = node.childForFieldName('left');
292
+ const isPrototypeAssignment = leftNode && leftNode.type === 'member_expression' &&
293
+ leftNode.text.includes('.prototype.');
294
+
295
+ // For non-prototype assignments, check if nested
296
+ if (!isPrototypeAssignment) {
297
+ let parent = node.parent;
298
+ let isTopLevel = true;
299
+ while (parent) {
300
+ const ptype = parent.type;
301
+ if (ptype === 'function_declaration' || ptype === 'arrow_function' ||
302
+ ptype === 'function_expression' || ptype === 'method_definition' ||
303
+ ptype === 'generator_function_declaration' || ptype === 'generator_function' ||
304
+ ptype === 'class_body') {
305
+ isTopLevel = false;
306
+ break;
307
+ }
308
+ if (ptype === 'program' || ptype === 'module') {
309
+ break;
310
+ }
311
+ parent = parent.parent;
312
+ }
313
+ if (!isTopLevel) return true;
314
+ }
375
315
 
376
- functions.push({
377
- name: 'default',
378
- params: extractParams(paramsNode),
379
- paramsStructured: parseStructuredParams(paramsNode, 'javascript'),
380
- startLine,
381
- endLine,
382
- indent,
383
- isArrow: child.type === 'arrow_function',
384
- isGenerator: isGen,
385
- modifiers: ['export', 'default'],
386
- ...(returnType && { returnType }),
387
- ...(generics && { generics }),
388
- ...(docstring && { docstring })
389
- });
390
- return true;
391
- }
316
+ const rightNode = node.childForFieldName('right');
317
+
318
+ if (leftNode && rightNode) {
319
+ const isArrow = rightNode.type === 'arrow_function';
320
+ const isFnExpr = rightNode.type === 'function_expression' ||
321
+ rightNode.type === 'generator_function';
322
+
323
+ if (isArrow || isFnExpr) {
324
+ const name = getAssignmentName(leftNode);
325
+ if (name) {
326
+ processedRanges.add(rangeKey);
327
+ const paramsNode = rightNode.childForFieldName('parameters');
328
+ const { startLine, endLine, indent } = nodeToLocation(node, lines);
329
+ const returnType = extractReturnType(rightNode);
330
+ const generics = extractGenerics(rightNode);
331
+ const docstring = extractJSDocstring(lines, startLine);
332
+ const isGen = isGenerator(rightNode);
333
+
334
+ functions.push({
335
+ name,
336
+ params: extractParams(paramsNode),
337
+ paramsStructured: parseStructuredParams(paramsNode, 'javascript'),
338
+ startLine,
339
+ endLine,
340
+ indent,
341
+ isArrow,
342
+ isGenerator: isGen,
343
+ modifiers: [],
344
+ ...(returnType && { returnType }),
345
+ ...(generics && { generics }),
346
+ ...(docstring && { docstring })
347
+ });
392
348
  }
393
349
  }
394
- return true;
395
350
  }
351
+ return true;
352
+ }
396
353
 
354
+ // Export statements with anonymous functions
355
+ if (node.type === 'export_statement') {
356
+ const declaration = node.childForFieldName('declaration');
357
+ if (!declaration) {
358
+ for (let i = 0; i < node.namedChildCount; i++) {
359
+ const child = node.namedChild(i);
360
+ if (child.type === 'arrow_function' || child.type === 'function_expression' ||
361
+ child.type === 'generator_function') {
362
+ if (processedRanges.has(rangeKey)) return true;
363
+ processedRanges.add(rangeKey);
364
+
365
+ const paramsNode = child.childForFieldName('parameters');
366
+ const { startLine, endLine, indent } = nodeToLocation(node, lines);
367
+ const returnType = extractReturnType(child);
368
+ const generics = extractGenerics(child);
369
+ const docstring = extractJSDocstring(lines, startLine);
370
+ const isGen = isGenerator(child);
371
+
372
+ functions.push({
373
+ name: 'default',
374
+ params: extractParams(paramsNode),
375
+ paramsStructured: parseStructuredParams(paramsNode, 'javascript'),
376
+ startLine,
377
+ endLine,
378
+ indent,
379
+ isArrow: child.type === 'arrow_function',
380
+ isGenerator: isGen,
381
+ modifiers: ['export', 'default'],
382
+ ...(returnType && { returnType }),
383
+ ...(generics && { generics }),
384
+ ...(docstring && { docstring })
385
+ });
386
+ return true;
387
+ }
388
+ }
389
+ }
397
390
  return true;
398
- });
391
+ }
399
392
 
400
- functions.sort((a, b) => a.startLine - b.startLine);
401
- return functions;
393
+ return false;
402
394
  }
403
395
 
404
396
  /**
405
- * Find all classes, interfaces, types, and enums
397
+ * Find all functions in JS/TS code using tree-sitter
406
398
  * @param {string} code - Source code
407
399
  * @param {object} parser - Tree-sitter parser instance
408
400
  * @returns {Array}
409
401
  */
410
- function findClasses(code, parser) {
402
+ function findFunctions(code, parser) {
411
403
  const tree = parseTree(parser, code);
412
- const classes = [];
413
-
414
- traverseTree(tree.rootNode, (node) => {
415
- // Class declarations (including abstract classes)
416
- if (node.type === 'class_declaration' || node.type === 'class' || node.type === 'abstract_class_declaration') {
417
- const nameNode = node.childForFieldName('name');
418
- if (nameNode) {
419
- const { startLine, endLine } = nodeToLocation(node, code);
420
- const members = extractClassMembers(node, code);
421
- const docstring = extractJSDocstring(code, startLine);
422
- const generics = extractGenerics(node);
423
- const extendsInfo = extractExtends(node);
424
- const implementsInfo = extractImplements(node);
425
- const decorators = extractDecorators(node);
404
+ const lines = code.split('\n');
405
+ const functions = [];
406
+ const processedRanges = new Set();
407
+ traverseTreeCached(tree.rootNode, (node) => {
408
+ _processFunction(node, functions, processedRanges, lines);
409
+ return true;
410
+ });
411
+ functions.sort((a, b) => a.startLine - b.startLine);
412
+ return functions;
413
+ }
426
414
 
427
- const isAbstract = node.type === 'abstract_class_declaration';
428
- classes.push({
429
- name: nameNode.text,
430
- startLine,
431
- endLine,
432
- type: 'class',
433
- members,
434
- ...(isAbstract && { modifiers: ['abstract'] }),
435
- ...(docstring && { docstring }),
436
- ...(generics && { generics }),
437
- ...(extendsInfo && { extends: extendsInfo }),
438
- ...(implementsInfo.length > 0 && { implements: implementsInfo }),
439
- ...(decorators.length > 0 && { decorators })
440
- });
441
- }
442
- return false;
415
+ /**
416
+ * Process a node for class/interface/type/enum extraction (single-pass helper)
417
+ * Returns true if node was matched, false otherwise
418
+ */
419
+ function _processClass(node, classes, processedRanges, lines) {
420
+ // Class declarations (including abstract classes)
421
+ if (node.type === 'class_declaration' || node.type === 'class' || node.type === 'abstract_class_declaration') {
422
+ const nameNode = node.childForFieldName('name');
423
+ if (nameNode) {
424
+ const { startLine, endLine } = nodeToLocation(node, lines);
425
+ const members = extractClassMembers(node, lines);
426
+ const docstring = extractJSDocstring(lines, startLine);
427
+ const generics = extractGenerics(node);
428
+ const extendsInfo = extractExtends(node);
429
+ const implementsInfo = extractImplements(node);
430
+ const decorators = extractDecorators(node);
431
+
432
+ const isAbstract = node.type === 'abstract_class_declaration';
433
+ classes.push({
434
+ name: nameNode.text,
435
+ startLine,
436
+ endLine,
437
+ type: 'class',
438
+ members,
439
+ ...(isAbstract && { modifiers: ['abstract'] }),
440
+ ...(docstring && { docstring }),
441
+ ...(generics && { generics }),
442
+ ...(extendsInfo && { extends: extendsInfo }),
443
+ ...(implementsInfo.length > 0 && { implements: implementsInfo }),
444
+ ...(decorators.length > 0 && { decorators })
445
+ });
443
446
  }
447
+ return true;
448
+ }
444
449
 
445
- // TypeScript interface declarations
446
- if (node.type === 'interface_declaration') {
447
- const nameNode = node.childForFieldName('name');
448
- if (nameNode) {
449
- const { startLine, endLine } = nodeToLocation(node, code);
450
- const docstring = extractJSDocstring(code, startLine);
451
- const generics = extractGenerics(node);
452
- const extendsInfo = extractInterfaceExtends(node);
453
- const members = extractInterfaceMembers(node, code);
454
-
455
- classes.push({
456
- name: nameNode.text,
457
- startLine,
458
- endLine,
459
- type: 'interface',
460
- members,
461
- ...(docstring && { docstring }),
462
- ...(generics && { generics }),
463
- ...(extendsInfo.length > 0 && { extends: extendsInfo.join(', ') })
464
- });
465
- }
466
- return false;
450
+ // TypeScript interface declarations
451
+ if (node.type === 'interface_declaration') {
452
+ const nameNode = node.childForFieldName('name');
453
+ if (nameNode) {
454
+ const { startLine, endLine } = nodeToLocation(node, lines);
455
+ const docstring = extractJSDocstring(lines, startLine);
456
+ const generics = extractGenerics(node);
457
+ const extendsInfo = extractInterfaceExtends(node);
458
+ const members = extractInterfaceMembers(node, lines);
459
+
460
+ classes.push({
461
+ name: nameNode.text,
462
+ startLine,
463
+ endLine,
464
+ type: 'interface',
465
+ members,
466
+ ...(docstring && { docstring }),
467
+ ...(generics && { generics }),
468
+ ...(extendsInfo.length > 0 && { extends: extendsInfo.join(', ') })
469
+ });
467
470
  }
471
+ return true;
472
+ }
468
473
 
469
- // TypeScript type alias declarations
470
- if (node.type === 'type_alias_declaration') {
471
- const nameNode = node.childForFieldName('name');
472
- if (nameNode) {
473
- const { startLine, endLine } = nodeToLocation(node, code);
474
- const docstring = extractJSDocstring(code, startLine);
475
-
476
- classes.push({
477
- name: nameNode.text,
478
- startLine,
479
- endLine,
480
- type: 'type',
481
- members: [],
482
- ...(docstring && { docstring })
483
- });
484
- }
485
- return false;
474
+ // TypeScript type alias declarations
475
+ if (node.type === 'type_alias_declaration') {
476
+ const nameNode = node.childForFieldName('name');
477
+ if (nameNode) {
478
+ const { startLine, endLine } = nodeToLocation(node, lines);
479
+ const docstring = extractJSDocstring(lines, startLine);
480
+
481
+ classes.push({
482
+ name: nameNode.text,
483
+ startLine,
484
+ endLine,
485
+ type: 'type',
486
+ members: [],
487
+ ...(docstring && { docstring })
488
+ });
486
489
  }
490
+ return true;
491
+ }
487
492
 
488
- // TypeScript enum declarations
489
- if (node.type === 'enum_declaration') {
490
- const nameNode = node.childForFieldName('name');
491
- if (nameNode) {
492
- const { startLine, endLine } = nodeToLocation(node, code);
493
- const docstring = extractJSDocstring(code, startLine);
494
- const members = extractEnumMembers(node, code);
493
+ // TypeScript enum declarations
494
+ if (node.type === 'enum_declaration') {
495
+ const nameNode = node.childForFieldName('name');
496
+ if (nameNode) {
497
+ const { startLine, endLine } = nodeToLocation(node, lines);
498
+ const docstring = extractJSDocstring(lines, startLine);
499
+ const members = extractEnumMembers(node, lines);
500
+
501
+ classes.push({
502
+ name: nameNode.text,
503
+ startLine,
504
+ endLine,
505
+ type: 'enum',
506
+ members,
507
+ ...(docstring && { docstring })
508
+ });
509
+ }
510
+ return true;
511
+ }
495
512
 
496
- classes.push({
497
- name: nameNode.text,
498
- startLine,
499
- endLine,
500
- type: 'enum',
501
- members,
502
- ...(docstring && { docstring })
503
- });
504
- }
505
- return false;
513
+ // TypeScript namespace/module declarations
514
+ if (node.type === 'internal_module' || node.type === 'module') {
515
+ const nameNode = node.childForFieldName('name');
516
+ if (nameNode) {
517
+ const { startLine, endLine } = nodeToLocation(node, lines);
518
+ const docstring = extractJSDocstring(lines, startLine);
519
+
520
+ classes.push({
521
+ name: nameNode.text,
522
+ startLine,
523
+ endLine,
524
+ type: 'namespace',
525
+ members: [],
526
+ ...(docstring && { docstring })
527
+ });
506
528
  }
529
+ // Matched but continue traversal to find inner functions/classes
530
+ return true;
531
+ }
507
532
 
508
- // TypeScript namespace/module declarations
509
- if (node.type === 'internal_module' || node.type === 'module') {
510
- const nameNode = node.childForFieldName('name');
511
- if (nameNode) {
512
- const { startLine, endLine } = nodeToLocation(node, code);
513
- const docstring = extractJSDocstring(code, startLine);
533
+ return false;
534
+ }
514
535
 
515
- classes.push({
516
- name: nameNode.text,
517
- startLine,
518
- endLine,
519
- type: 'namespace',
520
- members: [],
521
- ...(docstring && { docstring })
522
- });
523
- }
524
- // Continue traversal to find inner functions/classes
525
- return true;
536
+ /**
537
+ * Find all classes, interfaces, types, and enums
538
+ * @param {string} code - Source code
539
+ * @param {object} parser - Tree-sitter parser instance
540
+ * @returns {Array}
541
+ */
542
+ function findClasses(code, parser) {
543
+ const tree = parseTree(parser, code);
544
+ const lines = code.split('\n');
545
+ const classes = [];
546
+ const processedRanges = new Set();
547
+ traverseTreeCached(tree.rootNode, (node) => {
548
+ const matched = _processClass(node, classes, processedRanges, lines);
549
+ // Skip subtrees for class/interface/type/enum (but not namespace)
550
+ if (matched && node.type !== 'internal_module' && node.type !== 'module') {
551
+ return false;
526
552
  }
527
-
528
553
  return true;
529
554
  });
530
-
531
555
  classes.sort((a, b) => a.startLine - b.startLine);
532
556
  return classes;
533
557
  }
@@ -686,7 +710,8 @@ function extractEnumMembers(enumNode, code) {
686
710
  /**
687
711
  * Extract class members
688
712
  */
689
- function extractClassMembers(classNode, code) {
713
+ function extractClassMembers(classNode, codeOrLines) {
714
+ const code = codeOrLines; // Accept either string or lines array (nodeToLocation handles both)
690
715
  const members = [];
691
716
  const bodyNode = classNode.childForFieldName('body');
692
717
  if (!bodyNode) return members;
@@ -866,52 +891,62 @@ function extractClassMembers(classNode, code) {
866
891
  return members;
867
892
  }
868
893
 
894
+ // Module-level state detection helpers
895
+ const _STATE_PATTERN = /^(CONFIG|[A-Z][a-zA-Z]*(?:State|Store|Context|Options|Settings)|[A-Z][A-Z_]+|Entities|Input)$/;
896
+ const _ACTION_PATTERN = /^(action\w*|[a-z]+Action|[a-z]+State)$/;
897
+ const _FACTORY_FUNCTIONS = ['register', 'createAction', 'defineAction', 'makeAction'];
898
+
899
+ function _isFactoryCall(node) {
900
+ if (node.type !== 'call_expression') return false;
901
+ const funcNode = node.childForFieldName('function');
902
+ if (!funcNode) return false;
903
+ const funcName = funcNode.type === 'identifier' ? funcNode.text : null;
904
+ return funcName && _FACTORY_FUNCTIONS.includes(funcName);
905
+ }
906
+
869
907
  /**
870
- * Find state objects (CONFIG, constants, etc.)
908
+ * Process a node for state object extraction (single-pass helper)
909
+ * Returns true if node was matched, false otherwise
871
910
  */
872
- function findStateObjects(code, parser) {
873
- const tree = parseTree(parser, code);
874
- const objects = [];
875
-
876
- const statePattern = /^(CONFIG|[A-Z][a-zA-Z]*(?:State|Store|Context|Options|Settings)|[A-Z][A-Z_]+|Entities|Input)$/;
877
- const actionPattern = /^(action\w*|[a-z]+Action|[a-z]+State)$/;
878
- const factoryFunctions = ['register', 'createAction', 'defineAction', 'makeAction'];
879
-
880
- const isFactoryCall = (node) => {
881
- if (node.type !== 'call_expression') return false;
882
- const funcNode = node.childForFieldName('function');
883
- if (!funcNode) return false;
884
- const funcName = funcNode.type === 'identifier' ? funcNode.text : null;
885
- return funcName && factoryFunctions.includes(funcName);
886
- };
887
-
888
- traverseTree(tree.rootNode, (node) => {
889
- if (node.type === 'lexical_declaration' || node.type === 'variable_declaration') {
890
- for (let i = 0; i < node.namedChildCount; i++) {
891
- const declarator = node.namedChild(i);
892
- if (declarator.type === 'variable_declarator') {
893
- const nameNode = declarator.childForFieldName('name');
894
- const valueNode = declarator.childForFieldName('value');
895
-
896
- if (nameNode && valueNode) {
897
- const name = nameNode.text;
898
- const isObject = valueNode.type === 'object';
899
- const isArray = valueNode.type === 'array';
900
-
901
- if ((isObject || isArray) && statePattern.test(name)) {
902
- const { startLine, endLine } = nodeToLocation(node, code);
903
- objects.push({ name, startLine, endLine });
904
- } else if (isFactoryCall(valueNode) && (actionPattern.test(name) || statePattern.test(name))) {
905
- const { startLine, endLine } = nodeToLocation(node, code);
906
- objects.push({ name, startLine, endLine });
907
- }
911
+ function _processState(node, objects, lines) {
912
+ if (node.type === 'lexical_declaration' || node.type === 'variable_declaration') {
913
+ for (let i = 0; i < node.namedChildCount; i++) {
914
+ const declarator = node.namedChild(i);
915
+ if (declarator.type === 'variable_declarator') {
916
+ const nameNode = declarator.childForFieldName('name');
917
+ const valueNode = declarator.childForFieldName('value');
918
+
919
+ if (nameNode && valueNode) {
920
+ const name = nameNode.text;
921
+ const isObject = valueNode.type === 'object';
922
+ const isArray = valueNode.type === 'array';
923
+
924
+ if ((isObject || isArray) && _STATE_PATTERN.test(name)) {
925
+ const { startLine, endLine } = nodeToLocation(node, lines);
926
+ objects.push({ name, startLine, endLine });
927
+ } else if (_isFactoryCall(valueNode) && (_ACTION_PATTERN.test(name) || _STATE_PATTERN.test(name))) {
928
+ const { startLine, endLine } = nodeToLocation(node, lines);
929
+ objects.push({ name, startLine, endLine });
908
930
  }
909
931
  }
910
932
  }
911
933
  }
912
934
  return true;
913
- });
935
+ }
936
+ return false;
937
+ }
914
938
 
939
+ /**
940
+ * Find state objects (CONFIG, constants, etc.)
941
+ */
942
+ function findStateObjects(code, parser) {
943
+ const tree = parseTree(parser, code);
944
+ const lines = code.split('\n');
945
+ const objects = [];
946
+ traverseTreeCached(tree.rootNode, (node) => {
947
+ _processState(node, objects, lines);
948
+ return true;
949
+ });
915
950
  objects.sort((a, b) => a.startLine - b.startLine);
916
951
  return objects;
917
952
  }
@@ -923,12 +958,28 @@ function findStateObjects(code, parser) {
923
958
  * @returns {ParseResult}
924
959
  */
925
960
  function parse(code, parser) {
961
+ const tree = parseTree(parser, code);
962
+ const lines = code.split('\n');
963
+ const functions = [], classes = [], stateObjects = [];
964
+ const processedFn = new Set(), processedCls = new Set();
965
+
966
+ traverseTreeCached(tree.rootNode, (node) => {
967
+ _processFunction(node, functions, processedFn, lines);
968
+ _processClass(node, classes, processedCls, lines);
969
+ _processState(node, stateObjects, lines);
970
+ return true; // always continue, never skip subtrees
971
+ });
972
+
973
+ functions.sort((a, b) => a.startLine - b.startLine);
974
+ classes.sort((a, b) => a.startLine - b.startLine);
975
+ stateObjects.sort((a, b) => a.startLine - b.startLine);
976
+
926
977
  return {
927
978
  language: 'javascript',
928
- totalLines: code.split('\n').length,
929
- functions: findFunctions(code, parser),
930
- classes: findClasses(code, parser),
931
- stateObjects: findStateObjects(code, parser),
979
+ totalLines: lines.length,
980
+ functions,
981
+ classes,
982
+ stateObjects,
932
983
  imports: [], // Handled by core/imports.js
933
984
  exports: [] // Handled by core/imports.js
934
985
  };
@@ -1473,7 +1524,7 @@ function findCallbackUsages(code, name, parser) {
1473
1524
  const tree = parseTree(parser, code);
1474
1525
  const usages = [];
1475
1526
 
1476
- traverseTree(tree.rootNode, (node) => {
1527
+ traverseTreeCached(tree.rootNode, (node) => {
1477
1528
  // Look for call expressions where our name is passed as an argument
1478
1529
  if (node.type === 'call_expression') {
1479
1530
  const argsNode = node.childForFieldName('arguments');
@@ -1570,7 +1621,7 @@ function findReExports(code, parser) {
1570
1621
  const tree = parseTree(parser, code);
1571
1622
  const reExports = [];
1572
1623
 
1573
- traverseTree(tree.rootNode, (node) => {
1624
+ traverseTreeCached(tree.rootNode, (node) => {
1574
1625
  // export { name } from './module'
1575
1626
  if (node.type === 'export_statement') {
1576
1627
  let hasFrom = false;
@@ -1623,7 +1674,7 @@ function findImportsInCode(code, parser) {
1623
1674
  const imports = [];
1624
1675
  let importAliases = null; // {original, local}[] — tracks renamed imports
1625
1676
 
1626
- traverseTree(tree.rootNode, (node) => {
1677
+ traverseTreeCached(tree.rootNode, (node) => {
1627
1678
  // ES6 import statements
1628
1679
  if (node.type === 'import_statement') {
1629
1680
  const line = node.startPosition.row + 1;
@@ -1804,7 +1855,7 @@ function findExportsInCode(code, parser) {
1804
1855
  const tree = parseTree(parser, code);
1805
1856
  const exports = [];
1806
1857
 
1807
- traverseTree(tree.rootNode, (node) => {
1858
+ traverseTreeCached(tree.rootNode, (node) => {
1808
1859
  // ES6 export statements
1809
1860
  if (node.type === 'export_statement') {
1810
1861
  const line = node.startPosition.row + 1;
@@ -1980,7 +2031,7 @@ function findUsagesInCode(code, name, parser) {
1980
2031
  const tree = parseTree(parser, code);
1981
2032
  const usages = [];
1982
2033
 
1983
- traverseTree(tree.rootNode, (node) => {
2034
+ traverseTreeCached(tree.rootNode, (node) => {
1984
2035
  // Look for identifier, property_identifier (method names in obj.method() calls),
1985
2036
  // and type_identifier (TypeScript type annotations like `params: MyType`)
1986
2037
  const isIdentifier = node.type === 'identifier' || node.type === 'property_identifier' || node.type === 'type_identifier';