ucn 3.7.21 → 3.7.22
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/core/output.js +4 -1
- package/core/project.js +40 -6
- package/languages/utils.js +3 -2
- package/package.json +1 -1
package/core/output.js
CHANGED
|
@@ -754,10 +754,13 @@ function formatTrace(trace, options = {}) {
|
|
|
754
754
|
if (node.callCount) {
|
|
755
755
|
label += ` ${node.callCount}x`;
|
|
756
756
|
}
|
|
757
|
+
if (node.alreadyShown) {
|
|
758
|
+
label += ' (see above)';
|
|
759
|
+
}
|
|
757
760
|
|
|
758
761
|
lines.push(prefix + connector + label);
|
|
759
762
|
|
|
760
|
-
if (node.children) {
|
|
763
|
+
if (node.children && !node.alreadyShown) {
|
|
761
764
|
const hasMore = node.truncatedChildren > 0;
|
|
762
765
|
for (let i = 0; i < node.children.length; i++) {
|
|
763
766
|
const isChildLast = !hasMore && i === node.children.length - 1;
|
package/core/project.js
CHANGED
|
@@ -3007,10 +3007,30 @@ class ProjectIndex {
|
|
|
3007
3007
|
// Filter out usages that are at the definition location
|
|
3008
3008
|
// nameLine: when decorators/annotations are present, startLine is the decorator line
|
|
3009
3009
|
// but the name identifier is on a different line (nameLine). Check both.
|
|
3010
|
-
|
|
3010
|
+
let nonDefUsages = allUsages.filter(u =>
|
|
3011
3011
|
!(u.file === symbol.file && (u.line === symbol.startLine || u.line === symbol.nameLine))
|
|
3012
3012
|
);
|
|
3013
3013
|
|
|
3014
|
+
// For exported symbols in --include-exported mode, also filter out export-site
|
|
3015
|
+
// references (e.g., `module.exports = { helperC }` or `export { helperC }`).
|
|
3016
|
+
// These are just re-statements of the export, not actual consumption.
|
|
3017
|
+
if (isExported && options.includeExported) {
|
|
3018
|
+
nonDefUsages = nonDefUsages.filter(u => {
|
|
3019
|
+
if (u.file !== symbol.file) return true; // cross-file usage always counts
|
|
3020
|
+
// Check if same-file usage is on an export line
|
|
3021
|
+
const content = this._readFile(u.file);
|
|
3022
|
+
if (!content) return true;
|
|
3023
|
+
const lines = content.split('\n');
|
|
3024
|
+
const line = lines[u.line - 1] || '';
|
|
3025
|
+
const trimmed = line.trim();
|
|
3026
|
+
// CJS: module.exports = { ... } or exports.name = ...
|
|
3027
|
+
if (trimmed.startsWith('module.exports') || /^exports\.\w+\s*=/.test(trimmed)) return false;
|
|
3028
|
+
// ESM: export { ... } or export default
|
|
3029
|
+
if (/^export\s*\{/.test(trimmed) || /^export\s+default\s/.test(trimmed)) return false;
|
|
3030
|
+
return true;
|
|
3031
|
+
});
|
|
3032
|
+
}
|
|
3033
|
+
|
|
3014
3034
|
// Total includes all usage types (calls, references, callbacks, re-exports)
|
|
3015
3035
|
const totalUsages = nonDefUsages.length;
|
|
3016
3036
|
|
|
@@ -3376,10 +3396,22 @@ class ProjectIndex {
|
|
|
3376
3396
|
|
|
3377
3397
|
const buildTree = (funcDef, currentDepth, dir) => {
|
|
3378
3398
|
const funcName = funcDef.name;
|
|
3379
|
-
|
|
3399
|
+
const key = `${funcDef.file}:${funcDef.startLine}`;
|
|
3400
|
+
if (currentDepth > maxDepth) {
|
|
3380
3401
|
return null;
|
|
3381
3402
|
}
|
|
3382
|
-
visited.
|
|
3403
|
+
if (visited.has(key)) {
|
|
3404
|
+
// Already explored — show as leaf node without recursing (prevents infinite loops)
|
|
3405
|
+
return {
|
|
3406
|
+
name: funcName,
|
|
3407
|
+
file: funcDef.relativePath,
|
|
3408
|
+
line: funcDef.startLine,
|
|
3409
|
+
type: funcDef.type,
|
|
3410
|
+
children: [],
|
|
3411
|
+
alreadyShown: true
|
|
3412
|
+
};
|
|
3413
|
+
}
|
|
3414
|
+
visited.add(key);
|
|
3383
3415
|
|
|
3384
3416
|
const node = {
|
|
3385
3417
|
name: funcName,
|
|
@@ -4011,10 +4043,12 @@ class ProjectIndex {
|
|
|
4011
4043
|
params = params.slice(1);
|
|
4012
4044
|
}
|
|
4013
4045
|
}
|
|
4014
|
-
const expectedParamCount = params.length;
|
|
4015
|
-
const optionalCount = params.filter(p => p.optional || p.default !== undefined).length;
|
|
4016
|
-
const minArgs = expectedParamCount - optionalCount;
|
|
4017
4046
|
const hasRest = params.some(p => p.rest);
|
|
4047
|
+
// Rest params don't count toward expected/min — they accept 0+ extra args
|
|
4048
|
+
const nonRestParams = params.filter(p => !p.rest);
|
|
4049
|
+
const expectedParamCount = nonRestParams.length;
|
|
4050
|
+
const optionalCount = nonRestParams.filter(p => p.optional || p.default !== undefined).length;
|
|
4051
|
+
const minArgs = expectedParamCount - optionalCount;
|
|
4018
4052
|
|
|
4019
4053
|
// Get all call sites
|
|
4020
4054
|
const usages = this.usages(name, { codeOnly: true });
|
package/languages/utils.js
CHANGED
|
@@ -96,8 +96,9 @@ function parseJSParam(param, info) {
|
|
|
96
96
|
if (patternNode) info.name = patternNode.text;
|
|
97
97
|
if (typeNode) info.type = typeNode.text.replace(/^:\s*/, '');
|
|
98
98
|
if (param.type === 'optional_parameter') info.optional = true;
|
|
99
|
-
} else if (param.type === 'rest_parameter') {
|
|
100
|
-
|
|
99
|
+
} else if (param.type === 'rest_parameter' || param.type === 'rest_pattern') {
|
|
100
|
+
// rest_parameter = TypeScript, rest_pattern = JavaScript
|
|
101
|
+
const patternNode = param.childForFieldName('pattern') || param.namedChild(0);
|
|
101
102
|
if (patternNode) info.name = patternNode.text;
|
|
102
103
|
info.rest = true;
|
|
103
104
|
} else if (param.type === 'assignment_pattern') {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ucn",
|
|
3
|
-
"version": "3.7.
|
|
3
|
+
"version": "3.7.22",
|
|
4
4
|
"mcpName": "io.github.mleoca/ucn",
|
|
5
5
|
"description": "Universal Code Navigator — AST-based call graph analysis for AI agents. Find callers, trace impact, detect dead code across JS/TS, Python, Go, Rust, Java, and HTML. CLI, MCP server, and agent skill.",
|
|
6
6
|
"main": "index.js",
|