candor-ts 0.5.17 → 0.5.18
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 +40 -1
package/package.json
CHANGED
package/scan.mjs
CHANGED
|
@@ -614,6 +614,31 @@ function realDecl(sym) {
|
|
|
614
614
|
return sym.valueDeclaration ?? sym.declarations?.[0];
|
|
615
615
|
}
|
|
616
616
|
|
|
617
|
+
// Resolve a value-reference NODE (`expr` in `expr.call(…)`/`expr.apply(…)`) to the qualified name of the
|
|
618
|
+
// FUNCTION UNIT it ultimately denotes — following ONE OR MORE local-variable aliases. The `.call`/`.apply`
|
|
619
|
+
// arm lands on the es-lib member so `getResolvedSignature` never sees the real fn; for a direct identifier
|
|
620
|
+
// (`effectful.call`) `realDecl` → the fn decl → a minted unit. But `const m = effectful; m.call(…)` resolves
|
|
621
|
+
// `m` to its VARIABLE declaration, whose initializer is the bare identifier `effectful` — the variable node
|
|
622
|
+
// itself is NOT a minted unit (only var-decls whose initializer is an arrow/fn-expr are), so the edge was
|
|
623
|
+
// dropped → silent-pure (the cardinal sin). Here we chase the variable's initializer identifier/member to
|
|
624
|
+
// the function it aliases. Returns the unit name, or null if the chain can't be pinned to a function unit.
|
|
625
|
+
// Bounded depth guards a pathological `const a=b, b=a` cycle. NO fabrication: a non-fn binding (or any link
|
|
626
|
+
// that doesn't resolve to a minted fn unit) returns null and the caller adds nothing / discloses Unknown.
|
|
627
|
+
function resolveFnRefUnit(refNode, depth = 0) {
|
|
628
|
+
if (!refNode || depth > 8) return null;
|
|
629
|
+
if (!ts.isIdentifier(refNode) && !ts.isPropertyAccessExpression(refNode)) return null;
|
|
630
|
+
const d = realDecl(checker.getSymbolAtLocation(refNode));
|
|
631
|
+
if (!d) return null;
|
|
632
|
+
// Already a minted unit (the function itself, an arrow/fn-expr const, a class method/property)?
|
|
633
|
+
const direct = nodeName.get(d);
|
|
634
|
+
if (direct) return direct;
|
|
635
|
+
// A local variable / parameter bound to a function reference — follow the initializer alias.
|
|
636
|
+
if ((ts.isVariableDeclaration(d) || ts.isBindingElement(d) || ts.isParameter(d)) && d.initializer
|
|
637
|
+
&& (ts.isIdentifier(d.initializer) || ts.isPropertyAccessExpression(d.initializer)))
|
|
638
|
+
return resolveFnRefUnit(d.initializer, depth + 1);
|
|
639
|
+
return null;
|
|
640
|
+
}
|
|
641
|
+
|
|
617
642
|
// Accessor resolution (the silent-pure-accessor fix): a property READ (`x.raw`) or property
|
|
618
643
|
// ASSIGNMENT target (`x.path = v`) may resolve to a getter/setter whose body performs effects. We
|
|
619
644
|
// resolve the property-name symbol to its declarations and look for an accessor of the matching
|
|
@@ -879,8 +904,22 @@ function visitCalls(node) {
|
|
|
879
904
|
invokedRef = (node.arguments ?? [])[0] ?? null;
|
|
880
905
|
if (invokedRef && (ts.isIdentifier(invokedRef) || ts.isPropertyAccessExpression(invokedRef))) {
|
|
881
906
|
const d2 = realDecl(checker.getSymbolAtLocation(invokedRef));
|
|
882
|
-
|
|
907
|
+
// Resolve the receiver/arg0 to its function unit, FOLLOWING local-variable aliases
|
|
908
|
+
// (`const m = effectful; m.call(…)`) — the direct-identifier form already landed on a minted
|
|
909
|
+
// unit, but an aliased local var resolves to its VARIABLE decl (not a unit), which dropped the
|
|
910
|
+
// edge silent-pure. `resolveFnRefUnit` chases the initializer alias to the real fn.
|
|
911
|
+
const t = (d2 && nodeName.get(d2)) || resolveFnRefUnit(invokedRef);
|
|
883
912
|
if (t) rec.edges.add(t);
|
|
913
|
+
// HONESTY: the receiver IS a local variable/parameter (it resolved to a value declaration)
|
|
914
|
+
// but we could NOT pin it to a function unit — e.g. bound to a param, a reassigned/branched
|
|
915
|
+
// value, an `any`-typed holder. The `.call`/`.apply` still INVOKES whatever it holds, so a
|
|
916
|
+
// silent-pure verdict would be the cardinal sin. Disclose Unknown instead. (A direct fn
|
|
917
|
+
// identifier / known fn always resolves above, so this never fires for the precise forms; a
|
|
918
|
+
// non-value receiver — a type, a literal — resolves to no decl and stays out, no fabrication.)
|
|
919
|
+
else if (d2 && (ts.isVariableDeclaration(d2) || ts.isBindingElement(d2) || ts.isParameter(d2))) {
|
|
920
|
+
rec.direct.add("Unknown");
|
|
921
|
+
rec.why.add(`call:${recvText.slice(0, 40)}.${m}`);
|
|
922
|
+
}
|
|
884
923
|
}
|
|
885
924
|
}
|
|
886
925
|
if (mod === "<local>") {
|