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.
- package/.github/workflows/ci.yml +33 -0
- package/.github/workflows/publish.yml +79 -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/javascript.js
CHANGED
|
@@ -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
|
-
*
|
|
112
|
-
*
|
|
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
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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
|
-
|
|
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
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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
|
-
|
|
328
|
-
const name = getAssignmentName(leftNode);
|
|
329
|
-
if (name) {
|
|
205
|
+
if (isArrow || isFnExpr) {
|
|
330
206
|
processedRanges.add(rangeKey);
|
|
331
|
-
const paramsNode =
|
|
332
|
-
const { startLine, endLine, indent } = nodeToLocation(node,
|
|
333
|
-
const returnType = extractReturnType(
|
|
334
|
-
const generics = extractGenerics(
|
|
335
|
-
const docstring = extractJSDocstring(
|
|
336
|
-
const isGen = isGenerator(
|
|
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
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
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
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
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
|
-
|
|
401
|
-
return functions;
|
|
393
|
+
return false;
|
|
402
394
|
}
|
|
403
395
|
|
|
404
396
|
/**
|
|
405
|
-
* Find all
|
|
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
|
|
402
|
+
function findFunctions(code, parser) {
|
|
411
403
|
const tree = parseTree(parser, code);
|
|
412
|
-
const
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
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
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
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
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
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
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
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
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
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
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
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
|
-
|
|
509
|
-
|
|
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
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
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,
|
|
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
|
-
*
|
|
908
|
+
* Process a node for state object extraction (single-pass helper)
|
|
909
|
+
* Returns true if node was matched, false otherwise
|
|
871
910
|
*/
|
|
872
|
-
function
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
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:
|
|
929
|
-
functions
|
|
930
|
-
classes
|
|
931
|
-
stateObjects
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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';
|