Skip to content

fix(svelte): stamp dynamic-import stub source_file to target, not importer (#712)#715

Open
jippi wants to merge 1 commit intosafishamsi:v7from
jippi:fix/svelte-stub-node-source-file
Open

fix(svelte): stamp dynamic-import stub source_file to target, not importer (#712)#715
jippi wants to merge 1 commit intosafishamsi:v7from
jippi:fix/svelte-stub-node-source-file

Conversation

@jippi
Copy link
Copy Markdown

@jippi jippi commented May 4, 2026

Code written by Claude Code, but reviewed by me (I'm not super familiar with Python, but SWE with +20 years experience).

Fixes #712. Independent of #714 — branched off v7, applies cleanly with or without #714 merged first.

Problem

extract_svelte()'s regex pass for import('...') resolved a target path (relative or via tsconfig alias) and created a stub node for it — but stamped the stub's source_file to str(path), which is the importer's path, not the target's.

build_from_json does last-write-wins on node attributes (G.add_node overwrites). When the target file is later extracted by _extract_generic on its own, both nodes share the same id (_make_id(str(target))) and merge. Whether the stub's wrong source_file or the real correct one wins depends purely on file-iteration order — non-deterministic.

Effect: downstream tools that read source_file off file nodes (display, "where is X defined", blast-radius queries, community summaries) see the file as living inside whichever component first imported it. On a real 1,873-file SvelteKit codebase, 16 .svelte file nodes were corrupted this way — every dynamically-imported component sampled.

Fix

Thread the resolved target path through to source_file:

  • relative importsstr(resolved)
  • alias imports ($lib/...) → str(resolved_alias)
  • bare/scoped externals → leave source_file empty (we genuinely don't know where node_modules live, and stamping the importer would lie)

The stub edge's source_file is unchanged — that legitimately is the importer (where the import statement lives). Edge vs node distinction is preserved.

Tests

7 new tests in tests/test_svelte_dynamic_import_stub.py:

Test What it guards
test_relative_dynamic_import_stub_points_to_target Stub source_file == target's path
test_relative_dynamic_import_stub_does_not_carry_importer_path The literal #712 bug
test_alias_dynamic_import_stub_points_to_resolved_path $lib/... aliases resolve correctly
test_two_importers_agree_on_target_source_file Two stubs of same target are consistent → merge-order independent
test_target_extraction_does_not_clobber_correct_source_file End-to-end: stub's source_file matches real extraction's source_file
test_bare_module_dynamic_import_does_not_corrupt_source_file External modules don't get importer-path stamped
test_dynamic_import_edge_source_file_is_importer Edge source_file unchanged (over-correction guard)

6 of 7 fail against unpatched v7 — confirming they're real regression guards. The 7th is the over-correction check that intentionally documents what should NOT change (edge source_file remains the importer).

Test plan

  • pytest tests/test_svelte_dynamic_import_stub.py — 7/7 pass
  • Confirmed 6/7 fail against unpatched v7
  • pytest tests/ — 556 pass, 7 pre-existing failures unrelated to this change

…orter (safishamsi#712)

When extract_svelte()'s regex pass for `import('...')` resolved a target
path (relative or via tsconfig alias) and created a stub node for it,
the stub's source_file was stamped to str(path) — the IMPORTER's path,
not the target's path.

build_from_json does last-write-wins on node attributes (G.add_node
overwrites). When the target file is later extracted by _extract_generic
on its own, both nodes share the same id (_make_id(str(target))) and
merge. Whether the stub's wrong source_file or the real correct one wins
depends purely on file-iteration order — non-deterministic.

Effect: downstream tools that read source_file off file nodes (display,
"where is X defined", blast-radius queries, community summaries) see the
file as living inside whichever component first imported it. On a real
1,873-file SvelteKit codebase, 16 .svelte file nodes were corrupted this
way — every dynamically-imported component sampled.

The fix threads the resolved target path through to source_file:
  - relative imports use str(resolved)
  - alias imports use str(resolved_alias)
  - bare/scoped externals leave source_file empty (we genuinely don't
    know where node_modules live, and stamping the importer would lie)

The stub edge's source_file is unchanged — that legitimately is the
importer (where the import statement lives).

7 new tests in tests/test_svelte_dynamic_import_stub.py:
  - relative import stub points to target
  - relative import stub does NOT carry importer's path
  - alias import resolves and points to target
  - two importers of same target produce consistent source_file
  - stub doesn't clobber target's own extraction (merge-order safety)
  - bare-module stub doesn't claim to be importer
  - dynamic_import edge source_file remains the importer (no over-correction)

6 of 7 fail against unpatched v7 (proving they're real regression
guards); the 7th is the over-correction check which intentionally
documents what should NOT change.
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.

extract_svelte() regex fallback stamps stub-node source_file to importer's path, corrupting target file metadata after #701

1 participant