Skip to content

fix: resolve unmapped @/ imports in JS projects - #3362

Closed
hopstreax wants to merge 1 commit into
Graphify-Labs:v8from
hopstreax:investigate/3357-js-alias-calls-regression
Closed

fix: resolve unmapped @/ imports in JS projects#3362
hopstreax wants to merge 1 commit into
Graphify-Labs:v8from
hopstreax:investigate/3357-js-alias-calls-regression

Conversation

@hopstreax

Copy link
Copy Markdown
Contributor

Summary

Fixes #3357 by resolving conventional @/ imports in JavaScript/TypeScript projects that do not have a tsconfig.json or jsconfig.json.

Previously, an import such as:

import { enableBackgroundBle } from "@/adapter.js";

could remain unresolved, preventing Graphify from establishing import evidence and therefore preventing the corresponding cross-file calls edge under the #1659 safety gate.

This change resolves @/ imports to concrete files on disk without restoring the historical name-based inference fallback.

What changed

  • Added unmapped @/ convention resolution to the central JS/TS module resolver.

  • Discover the project anchor using:

    • nearest package.json or pnpm-workspace.yaml
    • otherwise the existing VCS root
    • otherwise the importing file's directory
  • Probe conventional locations in this order:

    1. <project>/src/<path>
    2. <project>/<path>
  • Reuse _resolve_js_import_path() so normal JS/TS extensions and index resolution remain consistent.

  • Applied the same resolution behavior to regex-rescued imports.

  • Preserve existing tsconfig.json / jsconfig.json alias and baseUrl behavior.

Safety / compatibility

This does not restore the old repository-wide name-based INFERRED call fallback.

Instead, a calls edge is produced only after the @/ import resolves to a concrete file and provides legitimate import evidence, preserving the #1659 safety boundary.

Also preserves the #3125 behavior: when a tsconfig.json exists without a paths mapping, the unmapped @/ convention is not synthesized.

Scoped packages such as @scope/pkg remain unaffected.

Tests

Added regression coverage for:

  • Minimal unmapped @/ reproduction.
  • Nested importers with src/ layouts.
  • Nested importers with flat layouts.
  • tsconfig.json without paths (#3125).
  • Explicit tsconfig alias precedence.
  • Missing @/ targets.
  • Scoped package imports.
  • Regex-rescued imports.
  • Prevention of phantom calls to unrelated definitions (#1659).

Verification:

123 passed
git diff --check: clean

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 2 advisory finding(s) below merit a look before merge.

Formal verification. No changes could be formally verified in this run.


Graphify review — findings

Adds an unmapped @/ path-alias fallback for JS/TS so that, when no tsconfig.json/jsconfig.json is present, _resolve_js_module_path treats @/... as a project-root convention alias — resolving against <anchor>/src/<subpath> when a src/ dir exists, else <anchor>/<subpath>, with _find_js_project_anchor walking upward to the nearest package.json/pnpm-workspace.yaml, then a VCS root, then start_dir. Wires the same fallback into _resolve_rescued_specifier so rescued specifiers resolve to real file/symbol/call edges. Preserves the existing #3125 behaviour: any tsconfig.json/jsconfig.json (with or without paths) disables the convention, and explicit paths mappings still take precedence.

Worth a look

  • _resolve_js_module_path @/ fallback in extract.py bypasses tsconfig guard used in resolution.pygraphify/extract.py:1620 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Unresolved @/ fallback can dereference Nonegraphify/extractors/resolution.py:568 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 1996 functions depend on the 373 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract() — 546 callers, 43 callees
  • new: _rebuild_code() — 113 callers, 50 callees
  • new: _extract_generic() — 18 callers, 25 callees
  • new: extract_js() — 85 callers, 4 callees
  • new: extract_xaml() — 19 callers, 17 callees
  • new: dispatch_command() — 2 callers, 124 callees
  • new: extract_objc() — 27 callers, 9 callees
  • new: _resolve_js_module_path() — 28 callers, 8 callees
  • …and 44 more — each is listed as a finding

Verification — 1996 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 1831 function(s) in the blast radius were not formally verified this run

Formal verification

Could not verify: Could not verify \_resolve\_rescued\_specifier.

The verifier did not have enough to check \_resolve\_rescued\_specifier, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify \_resolve\_js\_module\_path.

The verifier did not have enough to check \_resolve\_js\_module\_path, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `start_dir` is annotated `Path | None` — outside the synthesizable primitive/collection set

· 2 grounded finding(s) anchored inline below; 50 more finding(s) on lines outside this diff (see the check run).




def _resolve_js_module_path(raw: str | Path, start_dir: Path | None = None) -> Path | None:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regression_resolve_js_module_path()

fans out to 8 callees (efferent coupling); 28 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.


return None

def _resolve_js_import_target(raw: str, str_path: str) -> "tuple[str, Path | None] | None":

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regression_resolve_js_import_target()

9 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

@safishamsi

Copy link
Copy Markdown
Collaborator

Shipped in v0.9.56 — landed on v8 via cherry-pick with your authorship preserved. Thanks @hopstreax — JS/TS @/ project-root alias imports now resolve when no tsconfig paths mapping exists. Release: https://github.com/Graphify-Labs/graphify/releases/tag/v0.9.56

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

JS: calls edge dropped for a @/ alias import when no tsconfig/jsconfig exists (0.8.11 emitted it, 0.9.53 does not)

2 participants