candor-ts 0.5.15 → 0.5.17
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 -4
package/package.json
CHANGED
package/scan.mjs
CHANGED
|
@@ -442,6 +442,19 @@ function localName(node) {
|
|
|
442
442
|
}
|
|
443
443
|
return null;
|
|
444
444
|
}
|
|
445
|
+
// True when `node` is lexically inside a FUNCTION body — so its bare local name can collide with a
|
|
446
|
+
// module-level or sibling-scope unit of the same name (see the qual disambiguation below). A namespace/
|
|
447
|
+
// module block and the source file are NOT function scopes (their members are top-level units).
|
|
448
|
+
function isFunctionScoped(node) {
|
|
449
|
+
for (let p = node.parent; p; p = p.parent) {
|
|
450
|
+
if (ts.isSourceFile(p) || ts.isModuleBlock(p)) return false;
|
|
451
|
+
if (ts.isFunctionDeclaration(p) || ts.isFunctionExpression(p) || ts.isArrowFunction(p)
|
|
452
|
+
|| ts.isMethodDeclaration(p) || ts.isConstructorDeclaration(p)
|
|
453
|
+
|| ts.isGetAccessorDeclaration(p) || ts.isSetAccessorDeclaration(p))
|
|
454
|
+
return true;
|
|
455
|
+
}
|
|
456
|
+
return false;
|
|
457
|
+
}
|
|
445
458
|
for (const sf of sources) {
|
|
446
459
|
const mod = moduleOf(sf);
|
|
447
460
|
(function collect(node) {
|
|
@@ -491,8 +504,15 @@ for (const sf of sources) {
|
|
|
491
504
|
const n = localName(node);
|
|
492
505
|
const isCjsExport = _lastCjs; // captured immediately: localName set it for THIS node only
|
|
493
506
|
if (n) {
|
|
494
|
-
const qual = `${mod}.${n}`;
|
|
495
507
|
const { line, character } = sf.getLineAndCharacterOfPosition(node.getStart());
|
|
508
|
+
// Disambiguate FUNCTION-SCOPED local function units by position. A `const persist = () => …` (or a
|
|
509
|
+
// nested `function persist(){}`) inside a fn body shares the bare `mod.name` key with a module-level
|
|
510
|
+
// OR sibling-scope unit of the same name, so the second `fns.set` CLOBBERS the first and a call that
|
|
511
|
+
// resolves (correctly, via the checker) to the LOCAL decl reads the OTHER unit's effects off the
|
|
512
|
+
// shared entry — FABRICATING them onto a pure caller. `nodeName` is keyed by NODE identity, so a
|
|
513
|
+
// per-node-unique key keeps resolution exact; only TOP-LEVEL units need the stable bare name a
|
|
514
|
+
// consumer's hash-join targets (a function-scoped local is never an export, so nothing joins to it).
|
|
515
|
+
const qual = isFunctionScoped(node) ? `${mod}.${n}#${line + 1}:${character + 1}` : `${mod}.${n}`;
|
|
496
516
|
fns.set(qual, { local: n, direct: new Set(), edges: new Set(), hosts: new Set(), tables: new Set(),
|
|
497
517
|
cmds: new Set(), paths: new Set(), blind: new Set(), incomplete: new Set(), why: new Set(), entry: false, isCjsExport,
|
|
498
518
|
loc: `${path.relative(rootDir, sf.fileName)}:${line + 1}:${character + 1}` });
|
|
@@ -791,9 +811,30 @@ function visitCalls(node) {
|
|
|
791
811
|
}
|
|
792
812
|
}
|
|
793
813
|
if (!edged && !externalClass) {
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
814
|
+
// Blind PACKAGE member call: the receiver is a NAMESPACE import from a bare specifier
|
|
815
|
+
// (`import * as winstonm from "winston"; winstonm.info()`) that didn't resolve — typically the
|
|
816
|
+
// package isn't installed in this tree. The κ table may still MODEL it, so classify by the import
|
|
817
|
+
// SPECIFIER (the syntactic path, mirroring how the Rust scanner classifies a crate path without
|
|
818
|
+
// building). Only fires for κ-modeled packages (winston/pino/pg/…); everything else still → Unknown.
|
|
819
|
+
let kEff = null;
|
|
820
|
+
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)
|
|
821
|
+
&& ts.isIdentifier(node.expression.expression)) {
|
|
822
|
+
const sym = checker.getSymbolAtLocation(node.expression.expression);
|
|
823
|
+
for (const d of sym?.declarations ?? []) {
|
|
824
|
+
if (ts.isNamespaceImport(d)) {
|
|
825
|
+
const spec = d.parent?.parent?.moduleSpecifier;
|
|
826
|
+
if (spec && ts.isStringLiteralLike(spec)) kEff = kappa(spec.text, node.expression.name.text);
|
|
827
|
+
break;
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
if (kEff) {
|
|
832
|
+
rec.direct.add(kEff); // κ-modeled package reached via an uninstalled namespace import
|
|
833
|
+
} else {
|
|
834
|
+
rec.direct.add("Unknown"); // unresolvable call → Unknown, never silent-pure (SPEC §4)
|
|
835
|
+
const callee = (node.expression?.getText?.() ?? "?").replace(/\s+/g, "").slice(0, 60);
|
|
836
|
+
rec.why.add(`call:${callee}`); // an `any`-typed/indeterminate callee — named, so triage starts here
|
|
837
|
+
}
|
|
797
838
|
}
|
|
798
839
|
} else {
|
|
799
840
|
const mod = declModule(decl);
|