candor-ts 0.5.20 → 0.5.21
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/package.json +1 -1
- package/scan.mjs +45 -0
package/package.json
CHANGED
package/scan.mjs
CHANGED
|
@@ -759,6 +759,34 @@ function resolveFnRefUnit(refNode, depth = 0) {
|
|
|
759
759
|
return null;
|
|
760
760
|
}
|
|
761
761
|
|
|
762
|
+
// Unwrap a `<ref>.bind(…)` partial-application chain to the underlying function-reference RECEIVER.
|
|
763
|
+
// `setTimeout(this.flush.bind(this), 0)` / `effFs.bind(null)` / `cb.bind(null,a).bind(null,b)` schedule the
|
|
764
|
+
// BOUND function, but the argument node is a CallExpression (callee = PropertyAccessExpression `.bind`), so
|
|
765
|
+
// the HOF-ref arm — which only edges identifier / property-access args — dropped it (silent-pure: the
|
|
766
|
+
// cardinal sin). `.bind` is the third reflective-invoke member alongside `.call`/`.apply`. Given an arg
|
|
767
|
+
// node, returns:
|
|
768
|
+
// { ref } — it IS a `.bind` chain and the root receiver is a resolvable id/property-access ref
|
|
769
|
+
// (recursing through chained `.bind().bind()`); the caller resolves it to its fn unit.
|
|
770
|
+
// { ref:null }— it IS a `.bind` chain but the root receiver is NOT a plain ref (`getCallback().bind(null)`,
|
|
771
|
+
// a parenthesized/`any` holder) — still INVOKED by the HOF, so the caller discloses Unknown.
|
|
772
|
+
// null — not a `.bind` call at all; the caller's id/property-access path handles it.
|
|
773
|
+
// A `.bind` on a PURE fn resolves to a pure unit (no fabrication); the bind-unresolvable case never goes
|
|
774
|
+
// silent-pure.
|
|
775
|
+
function unwrapBind(node, depth = 0) {
|
|
776
|
+
if (!node || depth > 8) return null;
|
|
777
|
+
if (!ts.isCallExpression(node) || !ts.isPropertyAccessExpression(node.expression)) return null;
|
|
778
|
+
if (node.expression.name.text !== "bind") return null;
|
|
779
|
+
let recv = node.expression.expression;
|
|
780
|
+
while (ts.isParenthesizedExpression(recv)) recv = recv.expression;
|
|
781
|
+
// chained `.bind().bind()` — recurse only when the receiver is ITSELF a `.bind` call, else it's an
|
|
782
|
+
// arbitrary call (`getCallback().bind`) whose result we can't pin → unresolvable bind.
|
|
783
|
+
if (ts.isCallExpression(recv)) {
|
|
784
|
+
const inner = unwrapBind(recv, depth + 1);
|
|
785
|
+
return inner ?? { ref: null };
|
|
786
|
+
}
|
|
787
|
+
return { ref: (ts.isIdentifier(recv) || ts.isPropertyAccessExpression(recv)) ? recv : null };
|
|
788
|
+
}
|
|
789
|
+
|
|
762
790
|
// Accessor resolution (the silent-pure-accessor fix): a property READ (`x.raw`) or property
|
|
763
791
|
// ASSIGNMENT target (`x.path = v`) may resolve to a getter/setter whose body performs effects. We
|
|
764
792
|
// resolve the property-name symbol to its declarations and look for an accessor of the matching
|
|
@@ -1085,6 +1113,23 @@ function visitCalls(node) {
|
|
|
1085
1113
|
: ts.isIdentifier(node.expression) ? node.expression.text : null;
|
|
1086
1114
|
if (mod !== "<local>" && calleeName && HOF_INVOKERS.has(calleeName)) {
|
|
1087
1115
|
for (const a of node.arguments ?? []) {
|
|
1116
|
+
// A `<ref>.bind(…)` partial-application is a CallExpression (skipped by the id/property-access
|
|
1117
|
+
// gate below) but the INVOKING HOF calls the bound fn → its effects are reachable. Unwrap the
|
|
1118
|
+
// `.bind` chain to the root receiver and resolve it like a bare ref (`resolveFnRefUnit` follows
|
|
1119
|
+
// local aliases too). A `.bind` whose receiver can't be pinned to a fn unit (`getCallback().bind`,
|
|
1120
|
+
// a param/`any` holder) still INVOKES whatever it wraps — disclose Unknown, never silent-pure.
|
|
1121
|
+
const bound = unwrapBind(a);
|
|
1122
|
+
if (bound) {
|
|
1123
|
+
const bref = bound.ref;
|
|
1124
|
+
const d3 = bref && realDecl(checker.getSymbolAtLocation(bref));
|
|
1125
|
+
const tb = (d3 && nodeName.get(d3)) || (bref && resolveFnRefUnit(bref));
|
|
1126
|
+
if (tb) rec.edges.add(tb);
|
|
1127
|
+
else {
|
|
1128
|
+
rec.direct.add("Unknown");
|
|
1129
|
+
rec.why.add(`bind:${(bref ?? a).getText().replace(/\s+/g, "").slice(0, 40)}`);
|
|
1130
|
+
}
|
|
1131
|
+
continue;
|
|
1132
|
+
}
|
|
1088
1133
|
if (!ts.isIdentifier(a) && !ts.isPropertyAccessExpression(a)) continue;
|
|
1089
1134
|
const d2 = realDecl(checker.getSymbolAtLocation(a));
|
|
1090
1135
|
const t = d2 && nodeName.get(d2);
|