Skip to content

fix(extract): don't report deferred import() as a file cycle#1679

Open
Synvoya wants to merge 1 commit into
Graphify-Labs:v8from
Synvoya:fix/ts-dynamic-import-phantom-cycle
Open

fix(extract): don't report deferred import() as a file cycle#1679
Synvoya wants to merge 1 commit into
Graphify-Labs:v8from
Synvoya:fix/ts-dynamic-import-phantom-cycle

Conversation

@Synvoya

@Synvoya Synvoya commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

What it fixes

A deferred import('./x') was emitted by _dynamic_import_js (graphify/extract.py) as a plain imports_from edge — indistinguishable from a static import. find_import_cycles (graphify/analyze.py) counts every imports_from / re_exports edge as a hard file-cycle edge, so two files that reference each other via one static import + one dynamic import() were reported as a circular dependency (#1241).

Fix

Keep the deferred edge as imports_from — the dependency stays visible in the graph — but tag it "deferred": True, and skip deferred edges in find_import_cycles.

This is deliberately not a retag to dynamic_import: the deferred edge's source is a symbol node (the enclosing function) and its target isn't a materialized file node, so the valid-id prune passes (extract.py:5380 and the sibling passes) only spare it while its relation is imports_from / re_exports. Retagging would drop the edge entirely and lose the dependency; the deferred marker keeps it.

// actions.ts
export async function lazy() { const m = await import("./modal"); return m.openModal(); }
// modal.ts
import { doThing } from "./actions";
  • Before: find_import_cycles[{'cycle': ['actions.ts', 'modal.ts'], 'length': 2, ...}]
  • After: [], and the deferred dependency edge is still present (imports_from, deferred=True).

Test

tests/test_js_import_resolution.py::test_ts_dynamic_import_does_not_create_phantom_cycle asserts the deferred edge stays (imports_from + deferred), the real static import is unaffected, and find_import_cycles returns no cycle. Full test_js_import_resolution.py + test_analyze.py pass (99 passed); reverting the fix fails the new test (negative control).

Closes #1241

…y-Labs#1241)

`_dynamic_import_js` emitted a deferred `import('./x')` as a plain
`imports_from` edge, so `find_import_cycles` counted it as a static import.
A file that statically imports another which dynamically imports it back was
reported as a phantom circular dependency.

Keep the edge as `imports_from` (the dependency stays visible in the graph)
but mark it `deferred`, and skip deferred edges in `find_import_cycles`.

Closes Graphify-Labs#1241
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.

Typescript, deferred imports incorrectly detected as cycles

1 participant