ucn 5.1.1 → 5.2.0
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/.claude/skills/ucn/SKILL.md +11 -4
- package/.claude/skills/ucn/references/commands.md +2 -2
- package/README.md +28 -13
- package/core/cache.js +28 -1
- package/core/callers.js +1242 -57
- package/core/graph-build.js +6 -0
- package/core/index-ir.js +3 -2
- package/core/ir.js +3 -2
- package/core/output/refactoring.js +1 -1
- package/core/project.js +32 -0
- package/core/search.js +9 -0
- package/core/verify.js +1084 -36
- package/languages/c-family.js +231 -9
- package/languages/csharp.js +14 -3
- package/languages/go.js +473 -71
- package/languages/javascript.js +85 -3
- package/languages/python.js +364 -95
- package/languages/rust.js +87 -4
- package/languages/utils.js +11 -0
- package/mcp/server.js +99 -103
- package/mcp/stdio-server.js +296 -0
- package/package.json +10 -8
package/languages/javascript.js
CHANGED
|
@@ -1854,6 +1854,34 @@ function jsAssignmentTargetOf(callNode) {
|
|
|
1854
1854
|
return undefined;
|
|
1855
1855
|
}
|
|
1856
1856
|
|
|
1857
|
+
/**
|
|
1858
|
+
* For-of iteration target of a call used as the iterable:
|
|
1859
|
+
* `for (const x of getItems())`. The loop variable holds an ELEMENT of the
|
|
1860
|
+
* producer's result, never the return value — provenance only (fix #294).
|
|
1861
|
+
*/
|
|
1862
|
+
function jsIterTargetOf(callNode) {
|
|
1863
|
+
let n = callNode;
|
|
1864
|
+
let p = n.parent;
|
|
1865
|
+
if (p && p.type === 'await_expression') { n = p; p = n.parent; }
|
|
1866
|
+
if (!p || p.type !== 'for_in_statement') return undefined;
|
|
1867
|
+
if (p.childForFieldName('operator')?.text !== 'of') return undefined;
|
|
1868
|
+
const right = p.childForFieldName('right');
|
|
1869
|
+
if (!right || right.id !== n.id) return undefined;
|
|
1870
|
+
const left = p.childForFieldName('left');
|
|
1871
|
+
if (!left) return undefined;
|
|
1872
|
+
const names = [];
|
|
1873
|
+
if (left.type === 'identifier') {
|
|
1874
|
+
names.push(left.text);
|
|
1875
|
+
} else if (left.type === 'array_pattern') {
|
|
1876
|
+
for (let i = 0; i < left.namedChildCount; i++) {
|
|
1877
|
+
const c = left.namedChild(i);
|
|
1878
|
+
if (c.type === 'identifier') names.push(c.text);
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
if (names.length === 0) return undefined;
|
|
1882
|
+
return { first: names[0], rest: names.slice(1) };
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1857
1885
|
/**
|
|
1858
1886
|
* Type name from a new-expression constructor node: new Foo() or new pkg.Foo().
|
|
1859
1887
|
*/
|
|
@@ -2463,7 +2491,18 @@ function findCallsInCode(code, parser) {
|
|
|
2463
2491
|
const resolvedName = typeof alias === 'string' ? alias : undefined;
|
|
2464
2492
|
const resolvedNames = Array.isArray(alias) ? alias : undefined;
|
|
2465
2493
|
const firstArg = getFirstStringArg(node);
|
|
2466
|
-
|
|
2494
|
+
let assignedTo = jsAssignmentTargetOf(node);
|
|
2495
|
+
let assignedIterFields = {};
|
|
2496
|
+
if (!assignedTo) {
|
|
2497
|
+
const iterTarget = jsIterTargetOf(node);
|
|
2498
|
+
if (iterTarget) {
|
|
2499
|
+
assignedTo = iterTarget.first;
|
|
2500
|
+
assignedIterFields = {
|
|
2501
|
+
assignedIter: true,
|
|
2502
|
+
...(iterTarget.rest.length > 0 && { assignedTupleRest: iterTarget.rest }),
|
|
2503
|
+
};
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2467
2506
|
// MEDIUM-5: capture explicit method for fetch(url, { method }).
|
|
2468
2507
|
const optionsMethod = funcNode.text === 'fetch'
|
|
2469
2508
|
? getOptionsMethod(node, 1)
|
|
@@ -2477,6 +2516,7 @@ function findCallsInCode(code, parser) {
|
|
|
2477
2516
|
callEnd: node.endIndex,
|
|
2478
2517
|
isMethod: false,
|
|
2479
2518
|
...(assignedTo && { assignedTo }),
|
|
2519
|
+
...assignedIterFields,
|
|
2480
2520
|
enclosingFunction,
|
|
2481
2521
|
uncertain,
|
|
2482
2522
|
...(firstArg && { firstStringArg: firstArg.value, firstStringArgInterp: firstArg.interp }),
|
|
@@ -2658,7 +2698,19 @@ function findCallsInCode(code, parser) {
|
|
|
2658
2698
|
!localVarTypes.has(receiver));
|
|
2659
2699
|
const firstArg = getFirstStringArg(node);
|
|
2660
2700
|
const argCount = getArgCount(node);
|
|
2661
|
-
|
|
2701
|
+
let assignedTo = jsAssignmentTargetOf(node);
|
|
2702
|
+
let assignedIterFields = {};
|
|
2703
|
+
if (!assignedTo) {
|
|
2704
|
+
const iterTarget = jsIterTargetOf(node);
|
|
2705
|
+
if (iterTarget) {
|
|
2706
|
+
assignedTo = iterTarget.first;
|
|
2707
|
+
assignedIterFields = {
|
|
2708
|
+
assignedIter: true,
|
|
2709
|
+
...(iterTarget.rest.length > 0 &&
|
|
2710
|
+
{ assignedTupleRest: iterTarget.rest }),
|
|
2711
|
+
};
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2662
2714
|
calls.push({
|
|
2663
2715
|
name: propName,
|
|
2664
2716
|
// Multi-line chains (builder.x()\n.y()) must report
|
|
@@ -2694,6 +2746,7 @@ function findCallsInCode(code, parser) {
|
|
|
2694
2746
|
...(receiverCallStart != null && { receiverCallStart }),
|
|
2695
2747
|
...(receiverCallEnd != null && { receiverCallEnd }),
|
|
2696
2748
|
...(assignedTo && { assignedTo }),
|
|
2749
|
+
...assignedIterFields,
|
|
2697
2750
|
enclosingFunction,
|
|
2698
2751
|
uncertain,
|
|
2699
2752
|
...(firstArg && { firstStringArg: firstArg.value, firstStringArgInterp: firstArg.interp }),
|
|
@@ -2742,11 +2795,17 @@ function findCallsInCode(code, parser) {
|
|
|
2742
2795
|
const propNode = arg.childForFieldName('property');
|
|
2743
2796
|
const objNode = arg.childForFieldName('object');
|
|
2744
2797
|
if (propNode && !SKIP_IDENTS.has(propNode.text)) {
|
|
2798
|
+
const hofRecv = objNode?.type === 'identifier' ? objNode.text : undefined;
|
|
2799
|
+
// Same typing evidence as the general-arg
|
|
2800
|
+
// member-value shape (fix #295) — tier must
|
|
2801
|
+
// not depend on argument position (#234).
|
|
2802
|
+
const hofRecvType = hofRecv ? localVarTypes.get(hofRecv) : undefined;
|
|
2745
2803
|
calls.push({
|
|
2746
2804
|
name: propNode.text,
|
|
2747
2805
|
line: arg.startPosition.row + 1,
|
|
2748
2806
|
isMethod: true,
|
|
2749
|
-
receiver:
|
|
2807
|
+
receiver: hofRecv,
|
|
2808
|
+
...(hofRecvType && { receiverType: hofRecvType }),
|
|
2750
2809
|
isFunctionReference: true,
|
|
2751
2810
|
enclosingFunction
|
|
2752
2811
|
});
|
|
@@ -2777,6 +2836,29 @@ function findCallsInCode(code, parser) {
|
|
|
2777
2836
|
enclosingFunction
|
|
2778
2837
|
});
|
|
2779
2838
|
}
|
|
2839
|
+
// Method-value references (fix #295): `expectRaises(t.check, null)`
|
|
2840
|
+
// passes the method value t.check — one-hop identifier
|
|
2841
|
+
// receivers, typed from the same localVarTypes evidence
|
|
2842
|
+
// method calls use. No isPotentialCallback: the record
|
|
2843
|
+
// rides the MAIN path's full receiver physics (the HOF
|
|
2844
|
+
// member-value convention above).
|
|
2845
|
+
if (arg.type === 'member_expression') {
|
|
2846
|
+
const mvProp = arg.childForFieldName('property');
|
|
2847
|
+
const mvObj = arg.childForFieldName('object');
|
|
2848
|
+
if (mvProp && mvObj?.type === 'identifier' &&
|
|
2849
|
+
!SKIP_IDENTS.has(mvProp.text) && !SKIP_IDENTS.has(mvObj.text)) {
|
|
2850
|
+
const mvType = localVarTypes.get(mvObj.text);
|
|
2851
|
+
calls.push({
|
|
2852
|
+
name: mvProp.text,
|
|
2853
|
+
line: arg.startPosition.row + 1,
|
|
2854
|
+
isMethod: true,
|
|
2855
|
+
receiver: mvObj.text,
|
|
2856
|
+
...(mvType && { receiverType: mvType }),
|
|
2857
|
+
isFunctionReference: true,
|
|
2858
|
+
enclosingFunction
|
|
2859
|
+
});
|
|
2860
|
+
}
|
|
2861
|
+
}
|
|
2780
2862
|
// Scan object literal args for function refs in property values
|
|
2781
2863
|
// e.g., doRequest({onSuccess: handleSuccess, onError: handleError})
|
|
2782
2864
|
if (arg.type === 'object') {
|