ucn 3.7.28 → 3.7.30
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/callers.js +91 -5
- package/core/execute.js +8 -1
- package/core/expand-cache.js +51 -1
- package/core/imports.js +26 -0
- package/core/output.js +5 -1
- package/core/project.js +31 -3
- package/core/verify.js +23 -0
- package/languages/python.js +28 -1
- package/package.json +1 -1
package/core/callers.js
CHANGED
|
@@ -386,6 +386,13 @@ function findCallees(index, def, options = {}) {
|
|
|
386
386
|
let selfAttrCalls = null; // collected for Python self.attr.method() resolution
|
|
387
387
|
let selfMethodCalls = null; // collected for Python self.method() resolution
|
|
388
388
|
|
|
389
|
+
// Build local variable type map for receiver resolution
|
|
390
|
+
// Scans for patterns like: bt = Backtester(...) → bt maps to Backtester
|
|
391
|
+
let localTypes = null;
|
|
392
|
+
if (language === 'python' || language === 'javascript') {
|
|
393
|
+
localTypes = _buildLocalTypeMap(index, def, calls);
|
|
394
|
+
}
|
|
395
|
+
|
|
389
396
|
for (const call of calls) {
|
|
390
397
|
// Filter to calls within this function's scope
|
|
391
398
|
// Method 1: Direct match via enclosingFunction (fast path for direct calls)
|
|
@@ -412,6 +419,22 @@ function findCallees(index, def, options = {}) {
|
|
|
412
419
|
// self.method() / cls.method() / this.method() — resolve to same-class method below
|
|
413
420
|
} else if (call.receiver === 'super') {
|
|
414
421
|
// super().method() — resolve to parent class method below
|
|
422
|
+
} else if (localTypes && localTypes.has(call.receiver)) {
|
|
423
|
+
// Resolve method calls on locally-constructed objects:
|
|
424
|
+
// bt = Backtester(...); bt.run_backtest() → Backtester.run_backtest
|
|
425
|
+
const className = localTypes.get(call.receiver);
|
|
426
|
+
const symbols = index.symbols.get(call.name);
|
|
427
|
+
const match = symbols?.find(s => s.className === className);
|
|
428
|
+
if (match) {
|
|
429
|
+
const key = match.bindingId || `${className}.${call.name}`;
|
|
430
|
+
const existing = callees.get(key);
|
|
431
|
+
if (existing) {
|
|
432
|
+
existing.count += 1;
|
|
433
|
+
} else {
|
|
434
|
+
callees.set(key, { name: call.name, bindingId: match.bindingId, count: 1 });
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
continue;
|
|
415
438
|
} else if (language !== 'go' && language !== 'java' && language !== 'rust' && !options.includeMethods) {
|
|
416
439
|
continue;
|
|
417
440
|
}
|
|
@@ -696,11 +719,20 @@ function findCallees(index, def, options = {}) {
|
|
|
696
719
|
const sameDir = symbols.find(s => path.dirname(s.file) === defDir);
|
|
697
720
|
if (sameDir) {
|
|
698
721
|
callee = sameDir;
|
|
699
|
-
} else
|
|
700
|
-
// Priority
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
722
|
+
} else {
|
|
723
|
+
// Priority 2.5: Imported file — check if the caller's file imports
|
|
724
|
+
// from any of the candidate callee files
|
|
725
|
+
const callerImports = fileEntry?.imports || [];
|
|
726
|
+
const importedFiles = new Set(callerImports.map(imp => imp.resolvedPath).filter(Boolean));
|
|
727
|
+
const importedCallee = symbols.find(s => importedFiles.has(s.file));
|
|
728
|
+
if (importedCallee) {
|
|
729
|
+
callee = importedCallee;
|
|
730
|
+
} else if (defReceiver) {
|
|
731
|
+
// Priority 3: Same receiver type (for methods)
|
|
732
|
+
const sameReceiver = symbols.find(s => s.receiver === defReceiver);
|
|
733
|
+
if (sameReceiver) {
|
|
734
|
+
callee = sameReceiver;
|
|
735
|
+
}
|
|
704
736
|
}
|
|
705
737
|
}
|
|
706
738
|
}
|
|
@@ -789,6 +821,60 @@ function getInstanceAttributeTypes(index, filePath, className) {
|
|
|
789
821
|
return fileCache.get(className) || null;
|
|
790
822
|
}
|
|
791
823
|
|
|
824
|
+
/**
|
|
825
|
+
* Build a local variable type map for a function body.
|
|
826
|
+
* Scans for constructor-call assignments: var = ClassName(...)
|
|
827
|
+
* Returns Map<varName, className> or null if none found.
|
|
828
|
+
* @param {object} index - ProjectIndex instance
|
|
829
|
+
* @param {object} def - Function definition with file, startLine, endLine
|
|
830
|
+
* @param {Array} calls - Cached call sites for the file
|
|
831
|
+
*/
|
|
832
|
+
function _buildLocalTypeMap(index, def, calls) {
|
|
833
|
+
let content;
|
|
834
|
+
try {
|
|
835
|
+
content = index._readFile(def.file);
|
|
836
|
+
} catch {
|
|
837
|
+
return null;
|
|
838
|
+
}
|
|
839
|
+
const lines = content.split('\n');
|
|
840
|
+
const localTypes = new Map();
|
|
841
|
+
|
|
842
|
+
for (const call of calls) {
|
|
843
|
+
// Only look at calls within this function's scope
|
|
844
|
+
if (call.line < def.startLine || call.line > def.endLine) continue;
|
|
845
|
+
// Only direct calls (not method calls) — these are potential constructors
|
|
846
|
+
if (call.isMethod || call.isPotentialCallback) continue;
|
|
847
|
+
|
|
848
|
+
// Check if this call's name corresponds to a class in the symbol table
|
|
849
|
+
const symbols = index.symbols.get(call.name);
|
|
850
|
+
if (!symbols) continue;
|
|
851
|
+
const isClass = symbols.some(s => NON_CALLABLE_TYPES.has(s.type));
|
|
852
|
+
if (!isClass) continue;
|
|
853
|
+
|
|
854
|
+
// Check the source line for assignment pattern: var = ClassName(...)
|
|
855
|
+
const sourceLine = lines[call.line - 1];
|
|
856
|
+
if (!sourceLine) continue;
|
|
857
|
+
|
|
858
|
+
// Match: identifier = ClassName(...) or identifier: Type = ClassName(...)
|
|
859
|
+
const escapedName = call.name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
860
|
+
const assignMatch = sourceLine.match(
|
|
861
|
+
new RegExp(`(\\w+)\\s*(?::\\s*\\w+)?\\s*=\\s*${escapedName}\\s*\\(`)
|
|
862
|
+
);
|
|
863
|
+
if (assignMatch) {
|
|
864
|
+
localTypes.set(assignMatch[1], call.name);
|
|
865
|
+
}
|
|
866
|
+
// Match: with ClassName(...) as identifier:
|
|
867
|
+
const withMatch = sourceLine.match(
|
|
868
|
+
new RegExp(`with\\s+${escapedName}\\s*\\([^)]*\\)\\s+as\\s+(\\w+)`)
|
|
869
|
+
);
|
|
870
|
+
if (withMatch) {
|
|
871
|
+
localTypes.set(withMatch[1], call.name);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
return localTypes.size > 0 ? localTypes : null;
|
|
876
|
+
}
|
|
877
|
+
|
|
792
878
|
/**
|
|
793
879
|
* Check if a function is used as a callback anywhere in the codebase
|
|
794
880
|
* @param {object} index - ProjectIndex instance
|
package/core/execute.js
CHANGED
|
@@ -126,6 +126,7 @@ const HANDLERS = {
|
|
|
126
126
|
const result = index.impact(p.name, {
|
|
127
127
|
file: p.file,
|
|
128
128
|
exclude: toExcludeArray(p.exclude),
|
|
129
|
+
top: num(p.top, undefined),
|
|
129
130
|
});
|
|
130
131
|
if (!result) return { ok: false, error: `Function "${p.name}" not found.` };
|
|
131
132
|
return { ok: true, result };
|
|
@@ -184,7 +185,13 @@ const HANDLERS = {
|
|
|
184
185
|
find: (index, p) => {
|
|
185
186
|
const err = requireName(p.name);
|
|
186
187
|
if (err) return { ok: false, error: err };
|
|
187
|
-
|
|
188
|
+
// Auto-include tests when pattern clearly targets test functions
|
|
189
|
+
// But only if the user didn't explicitly set include_tests=false
|
|
190
|
+
let includeTests = p.includeTests;
|
|
191
|
+
if (includeTests === undefined && p.name && /^test[_*?]/i.test(p.name)) {
|
|
192
|
+
includeTests = true;
|
|
193
|
+
}
|
|
194
|
+
const exclude = applyTestExclusions(p.exclude, includeTests);
|
|
188
195
|
const result = index.find(p.name, {
|
|
189
196
|
file: p.file,
|
|
190
197
|
exact: p.exact || false,
|
package/core/expand-cache.js
CHANGED
|
@@ -125,6 +125,50 @@ class ExpandCache {
|
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
+
/**
|
|
129
|
+
* Detect the end of a function/method body starting from startLine.
|
|
130
|
+
* Uses brace/indent counting to find the closing boundary.
|
|
131
|
+
* Falls back to startLine + 30 if detection fails.
|
|
132
|
+
*/
|
|
133
|
+
function _detectFunctionEnd(fileLines, startLine) {
|
|
134
|
+
const maxScan = 500; // Avoid scanning huge files
|
|
135
|
+
const idx = startLine - 1;
|
|
136
|
+
if (idx >= fileLines.length) return startLine;
|
|
137
|
+
|
|
138
|
+
const firstLine = fileLines[idx];
|
|
139
|
+
|
|
140
|
+
// Python: indentation-based — find the first non-empty line at same or lesser indent
|
|
141
|
+
if (/^\s*def\s|^\s*class\s|^\s*async\s+def\s/.test(firstLine)) {
|
|
142
|
+
const baseIndent = firstLine.match(/^(\s*)/)[1].length;
|
|
143
|
+
let end = startLine;
|
|
144
|
+
for (let i = idx + 1; i < Math.min(idx + maxScan, fileLines.length); i++) {
|
|
145
|
+
const line = fileLines[i];
|
|
146
|
+
if (line.trim() === '') { end = i + 1; continue; } // blank lines are part of body
|
|
147
|
+
const indent = line.match(/^(\s*)/)[1].length;
|
|
148
|
+
if (indent <= baseIndent) break;
|
|
149
|
+
end = i + 1;
|
|
150
|
+
}
|
|
151
|
+
return end;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Brace-based languages (JS/TS/Go/Java/Rust): count braces
|
|
155
|
+
let braceCount = 0;
|
|
156
|
+
let foundBrace = false;
|
|
157
|
+
for (let i = idx; i < Math.min(idx + maxScan, fileLines.length); i++) {
|
|
158
|
+
const line = fileLines[i];
|
|
159
|
+
for (const ch of line) {
|
|
160
|
+
if (ch === '{') { braceCount++; foundBrace = true; }
|
|
161
|
+
else if (ch === '}') { braceCount--; }
|
|
162
|
+
}
|
|
163
|
+
if (foundBrace && braceCount <= 0) {
|
|
164
|
+
return i + 1;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Fallback: show 30 lines from start
|
|
169
|
+
return Math.min(startLine + 30, fileLines.length);
|
|
170
|
+
}
|
|
171
|
+
|
|
128
172
|
/**
|
|
129
173
|
* Render an expand match to text lines.
|
|
130
174
|
* Shared by MCP and interactive mode to avoid duplicated rendering logic.
|
|
@@ -156,7 +200,13 @@ function renderExpandItem(match, root, { validateRoot = false } = {}) {
|
|
|
156
200
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
157
201
|
const fileLines = content.split('\n');
|
|
158
202
|
const startLine = match.startLine || match.line || 1;
|
|
159
|
-
|
|
203
|
+
let endLine = match.endLine;
|
|
204
|
+
|
|
205
|
+
// When endLine is missing or equals startLine, the expand would show only 1 line.
|
|
206
|
+
// Scan forward from startLine to find the actual function/method body end.
|
|
207
|
+
if (!endLine || endLine <= startLine) {
|
|
208
|
+
endLine = _detectFunctionEnd(fileLines, startLine);
|
|
209
|
+
}
|
|
160
210
|
|
|
161
211
|
const lines = [];
|
|
162
212
|
lines.push(`[${match.num}] ${match.name} (${match.type})`);
|
package/core/imports.js
CHANGED
|
@@ -507,6 +507,32 @@ function loadTsConfig(tsconfigPath, visited) {
|
|
|
507
507
|
}
|
|
508
508
|
}
|
|
509
509
|
|
|
510
|
+
// Follow project references to collect paths from referenced configs
|
|
511
|
+
// (tsconfig.json with "references" pointing to tsconfig.app.json, tsconfig.node.json, etc.)
|
|
512
|
+
if (config.references && Array.isArray(config.references)) {
|
|
513
|
+
for (const ref of config.references) {
|
|
514
|
+
if (!ref.path) continue;
|
|
515
|
+
let refPath = path.resolve(configDir, ref.path);
|
|
516
|
+
// If reference points to a directory, look for tsconfig.json inside it
|
|
517
|
+
if (fs.existsSync(refPath) && fs.statSync(refPath).isDirectory()) {
|
|
518
|
+
refPath = path.join(refPath, 'tsconfig.json');
|
|
519
|
+
}
|
|
520
|
+
// Add .json extension if not present
|
|
521
|
+
if (!refPath.endsWith('.json')) refPath += '.json';
|
|
522
|
+
if (fs.existsSync(refPath)) {
|
|
523
|
+
try {
|
|
524
|
+
const refResult = loadTsConfig(refPath, visited);
|
|
525
|
+
if (refResult) {
|
|
526
|
+
basePaths = { ...basePaths, ...refResult.paths };
|
|
527
|
+
if (refResult.baseUrl && !baseUrl) baseUrl = refResult.baseUrl;
|
|
528
|
+
}
|
|
529
|
+
} catch {
|
|
530
|
+
// Skip malformed reference config
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
510
536
|
// Child config values override base config
|
|
511
537
|
const mergedPaths = { ...basePaths, ...(config.compilerOptions?.paths || {}) };
|
|
512
538
|
const compiledPaths = Object.entries(mergedPaths).map(([pattern, targets]) => ({
|
package/core/output.js
CHANGED
|
@@ -938,7 +938,11 @@ function formatImpact(impact, options = {}) {
|
|
|
938
938
|
lines.push('');
|
|
939
939
|
|
|
940
940
|
// Summary
|
|
941
|
-
|
|
941
|
+
if (impact.shownCallSites !== undefined && impact.shownCallSites < impact.totalCallSites) {
|
|
942
|
+
lines.push(`CALL SITES: ${impact.shownCallSites} shown of ${impact.totalCallSites} total`);
|
|
943
|
+
} else {
|
|
944
|
+
lines.push(`CALL SITES: ${impact.totalCallSites}`);
|
|
945
|
+
}
|
|
942
946
|
lines.push(` Files affected: ${impact.byFile.length}`);
|
|
943
947
|
|
|
944
948
|
// Patterns
|
package/core/project.js
CHANGED
|
@@ -735,6 +735,20 @@ class ProjectIndex {
|
|
|
735
735
|
// Sort by score descending, then by index order for stability
|
|
736
736
|
scored.sort((a, b) => b.score - a.score);
|
|
737
737
|
|
|
738
|
+
// Tiebreaker: when top candidates have equal score, prefer by usage count
|
|
739
|
+
if (scored.length > 1 && scored[0].score === scored[1].score) {
|
|
740
|
+
const tiedScore = scored[0].score;
|
|
741
|
+
const tiedCandidates = scored.filter(s => s.score === tiedScore);
|
|
742
|
+
for (const candidate of tiedCandidates) {
|
|
743
|
+
candidate.usageCount = this.countSymbolUsages(candidate.def).total;
|
|
744
|
+
}
|
|
745
|
+
tiedCandidates.sort((a, b) => b.usageCount - a.usageCount);
|
|
746
|
+
// Rebuild scored array: sorted tied candidates first, then rest
|
|
747
|
+
const rest = scored.filter(s => s.score !== tiedScore);
|
|
748
|
+
scored.length = 0;
|
|
749
|
+
scored.push(...tiedCandidates, ...rest);
|
|
750
|
+
}
|
|
751
|
+
|
|
738
752
|
const def = scored[0].def;
|
|
739
753
|
|
|
740
754
|
// Build warnings
|
|
@@ -759,10 +773,17 @@ class ProjectIndex {
|
|
|
759
773
|
// Glob pattern matching (e.g., _update*, handle*Request, get?ata)
|
|
760
774
|
const isGlob = name.includes('*') || name.includes('?');
|
|
761
775
|
if (isGlob && !options.exact) {
|
|
762
|
-
//
|
|
776
|
+
// Bare wildcard: return all symbols
|
|
763
777
|
const stripped = name.replace(/[*?]/g, '');
|
|
764
778
|
if (stripped.length === 0) {
|
|
765
|
-
|
|
779
|
+
const all = [];
|
|
780
|
+
for (const [, symbols] of this.symbols) {
|
|
781
|
+
for (const sym of symbols) {
|
|
782
|
+
all.push({ ...sym, _fuzzyScore: 800 });
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
all.sort((a, b) => (a.name || '').localeCompare(b.name || ''));
|
|
786
|
+
return this._applyFindFilters(all, options);
|
|
766
787
|
}
|
|
767
788
|
const globRegex = new RegExp('^' + name.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*').replace(/\?/g, '.') + '$', 'i');
|
|
768
789
|
const matches = [];
|
|
@@ -2661,6 +2682,12 @@ class ProjectIndex {
|
|
|
2661
2682
|
filteredSites = callSites.filter(s => this.matchesFilters(s.file, { exclude: options.exclude }));
|
|
2662
2683
|
}
|
|
2663
2684
|
|
|
2685
|
+
// Apply top limit if specified (limits total call sites shown)
|
|
2686
|
+
const totalBeforeLimit = filteredSites.length;
|
|
2687
|
+
if (options.top && options.top > 0 && filteredSites.length > options.top) {
|
|
2688
|
+
filteredSites = filteredSites.slice(0, options.top);
|
|
2689
|
+
}
|
|
2690
|
+
|
|
2664
2691
|
// Group by file
|
|
2665
2692
|
const byFile = new Map();
|
|
2666
2693
|
for (const site of filteredSites) {
|
|
@@ -2680,7 +2707,8 @@ class ProjectIndex {
|
|
|
2680
2707
|
signature: this.formatSignature(def),
|
|
2681
2708
|
params: def.params,
|
|
2682
2709
|
paramsStructured: def.paramsStructured,
|
|
2683
|
-
totalCallSites:
|
|
2710
|
+
totalCallSites: totalBeforeLimit,
|
|
2711
|
+
shownCallSites: filteredSites.length,
|
|
2684
2712
|
byFile: Array.from(byFile.entries()).map(([file, sites]) => ({
|
|
2685
2713
|
file,
|
|
2686
2714
|
count: sites.length,
|
package/core/verify.js
CHANGED
|
@@ -433,6 +433,29 @@ function plan(index, name, options = {}) {
|
|
|
433
433
|
});
|
|
434
434
|
}
|
|
435
435
|
}
|
|
436
|
+
|
|
437
|
+
// Also include import statements that reference the renamed function
|
|
438
|
+
const usages = index.usages(name, { codeOnly: true });
|
|
439
|
+
const importUsages = usages.filter(u => u.usageType === 'import' && !u.isDefinition);
|
|
440
|
+
for (const imp of importUsages) {
|
|
441
|
+
// Skip if already covered by a call site change in the same file:line
|
|
442
|
+
const alreadyCovered = changes.some(c =>
|
|
443
|
+
c.file === (imp.relativePath || imp.file) && c.line === imp.line
|
|
444
|
+
);
|
|
445
|
+
if (alreadyCovered) continue;
|
|
446
|
+
const newImport = imp.content.trim().replace(
|
|
447
|
+
new RegExp('\\b' + escapeRegExp(name) + '\\b'),
|
|
448
|
+
options.renameTo
|
|
449
|
+
);
|
|
450
|
+
changes.push({
|
|
451
|
+
file: imp.relativePath || imp.file,
|
|
452
|
+
line: imp.line,
|
|
453
|
+
expression: imp.content.trim(),
|
|
454
|
+
suggestion: `Update import: ${newImport}`,
|
|
455
|
+
newExpression: newImport,
|
|
456
|
+
isImport: true
|
|
457
|
+
});
|
|
458
|
+
}
|
|
436
459
|
}
|
|
437
460
|
|
|
438
461
|
return {
|
package/languages/python.js
CHANGED
|
@@ -330,6 +330,14 @@ function findStateObjects(code, parser) {
|
|
|
330
330
|
const objects = [];
|
|
331
331
|
|
|
332
332
|
const statePattern = /^(CONFIG|SETTINGS|[A-Z][A-Z0-9_]+|[A-Z][a-zA-Z]*(?:Config|Settings|Options|State|Store|Context))$/;
|
|
333
|
+
// Pattern for UPPER_CASE constants that may have scalar values (string, number, bool, etc.)
|
|
334
|
+
const constantPattern = /^[A-Z][A-Z0-9_]{1,}$/;
|
|
335
|
+
// RHS types that are scalar/simple values (not dict/list which are handled separately)
|
|
336
|
+
const scalarTypes = new Set([
|
|
337
|
+
'string', 'concatenated_string', 'integer', 'float', 'true', 'false', 'none',
|
|
338
|
+
'unary_operator', 'binary_operator', 'tuple', 'set', 'parenthesized_expression',
|
|
339
|
+
'call', 'attribute', 'identifier', 'subscript',
|
|
340
|
+
]);
|
|
333
341
|
|
|
334
342
|
traverseTree(tree.rootNode, (node) => {
|
|
335
343
|
if (node.type === 'expression_statement' && node.parent === tree.rootNode) {
|
|
@@ -346,6 +354,10 @@ function findStateObjects(code, parser) {
|
|
|
346
354
|
if ((isObject || isArray) && statePattern.test(name)) {
|
|
347
355
|
const { startLine, endLine } = nodeToLocation(node, code);
|
|
348
356
|
objects.push({ name, startLine, endLine });
|
|
357
|
+
} else if (constantPattern.test(name) && scalarTypes.has(rightNode.type)) {
|
|
358
|
+
// Module-level UPPER_CASE constants with scalar values
|
|
359
|
+
const { startLine, endLine } = nodeToLocation(node, code);
|
|
360
|
+
objects.push({ name, startLine, endLine, isConstant: true });
|
|
349
361
|
}
|
|
350
362
|
}
|
|
351
363
|
}
|
|
@@ -487,10 +499,25 @@ function findCallsInCode(code, parser) {
|
|
|
487
499
|
}
|
|
488
500
|
}
|
|
489
501
|
}
|
|
490
|
-
// Track non-callable assignments
|
|
502
|
+
// Track non-callable assignments.
|
|
503
|
+
// First: explicit literal check (handles dicts-with-lambdas correctly)
|
|
491
504
|
if (right && isNonCallableInit(right)) {
|
|
492
505
|
nonCallableNames.add(left.text);
|
|
493
506
|
}
|
|
507
|
+
// Second: function call results are generally non-callable data
|
|
508
|
+
// (e.g., close = series.dropna(), result = db.query(...))
|
|
509
|
+
// Exception: partial() already handled above via alias tracking.
|
|
510
|
+
else if (right?.type === 'call' && !aliases.has(left.text)) {
|
|
511
|
+
nonCallableNames.add(left.text);
|
|
512
|
+
}
|
|
513
|
+
// Third: subscript/attribute access results are non-callable data
|
|
514
|
+
// (e.g., close = candles["close"].values, item = data[0])
|
|
515
|
+
else if (right && !aliases.has(left.text) &&
|
|
516
|
+
['subscript', 'attribute', 'binary_operator', 'comparison_operator',
|
|
517
|
+
'unary_operator', 'conditional_expression', 'await',
|
|
518
|
+
'parenthesized_expression', 'not_operator', 'boolean_operator'].includes(right.type)) {
|
|
519
|
+
nonCallableNames.add(left.text);
|
|
520
|
+
}
|
|
494
521
|
}
|
|
495
522
|
}
|
|
496
523
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ucn",
|
|
3
|
-
"version": "3.7.
|
|
3
|
+
"version": "3.7.30",
|
|
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",
|