Skip to content

TypeScript: unresolved calls bound to same-named exports in unimported packages → phantom cross-package calls edges (conf 0.8) #1659

Description

@leonaburime-ucla

Summary

graphify's call resolver attributes an unresolved call to a same-named export in an unrelated package that was never imported, producing a phantom cross-package calls edge at confidence 0.8. On a monorepo this fabricates dependency relationships that don't exist.

Minimal repro

Two packages, no import between them:

pkg-a/src/index.ts (imports nothing, defines validate nowhere):

declare function validate(x: number): boolean; // ambient/unresolved locally
export function run(x: number): boolean {
  return validate(x); // pkg-b is never imported
}

pkg-b/src/index.ts:

export function validate(name: string): boolean { return name.length > 0; }

Run:

graphify update <dir> --no-cluster

Observed: a cross-package edge pkg-a.run() --calls--> pkg-b.validate() (confidence 0.8).
Expected: no cross-package edge — pkg-a has no import path to pkg-b, so the call cannot resolve there. It should stay unresolved (or ambient) rather than being bound to an unrelated package's export by name alone.

Real-world impact

On a ~14-package TypeScript monorepo (Apache-2.0, open-design), this produced edges like platform → registry-protocol and sidecar → registry-protocol even though platform and sidecar import nothing (verified against package.json and every import statement). registry-protocol exports many generically-named symbols (*Schema, etc.), so ambiguous/unresolved calls across the repo collapsed onto it — it appeared "depended on" by 14 packages spuriously. A reader trusting the graph would conclude a dependency direction that does not exist; only cross-checking against source caught it.

Likely root cause

When a call has no local definition and no matching import, the resolver appears to fall back to any same-named exported symbol repo-wide, and emits the edge at 0.8 rather than leaving it unresolved. The fallback isn't gated on an actual import/module-resolution path existing between the two files.

Suggestion

Gate cross-file/cross-package calls attribution on there being a real import (or module-resolution) path from caller to callee; when the only candidate lives in a package with no such path, leave the call unresolved (or emit it as ambiguous/inferred at markedly lower confidence) instead of a 0.8 calls edge. A diagnose-style warning for "cross-package edge with no supporting import" would also help users spot these.

Related

Environment

  • graphify 0.8.50
  • TypeScript sources

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions