fix(extract): apply resolver fixups to JS/TS dynamic_import handler
Third call site that re-implemented the same .js→.ts rewrite in
isolation. Previously only handled the explicit .js→.ts case; bare
paths, multi-dot helper files, and alias-resolved dynamic imports
all dropped silently.
Now uses _resolve_js_module_path on both branches (relative and
alias) — same shape as the static-import and Svelte regex paths.
Real-world impact: TS files using `await import('./foo')` patterns
for code splitting (e.g. lazy-loading a profanity check) now produce
edges to the resolved target.
This commit is contained in:
+6
-4
@@ -453,10 +453,11 @@ def _dynamic_import_js(node, source: bytes, caller_nid: str, str_path: str, edge
|
|||||||
# Resolve path using the same logic as static imports
|
# Resolve path using the same logic as static imports
|
||||||
if raw.startswith("."):
|
if raw.startswith("."):
|
||||||
resolved = Path(os.path.normpath(Path(str_path).parent / raw))
|
resolved = Path(os.path.normpath(Path(str_path).parent / raw))
|
||||||
if resolved.suffix == ".js":
|
# Same TS/SvelteKit resolver fixups static imports use, so
|
||||||
resolved = resolved.with_suffix(".ts")
|
# `await import('./foo')` (bare path), `import('./bar.shared')`
|
||||||
elif resolved.suffix == ".jsx":
|
# (multi-dot helper), and Svelte 5 rune-file dynamic imports
|
||||||
resolved = resolved.with_suffix(".tsx")
|
# all land on real file nodes.
|
||||||
|
resolved = _resolve_js_module_path(resolved)
|
||||||
tgt_nid = _make_id(str(resolved))
|
tgt_nid = _make_id(str(resolved))
|
||||||
else:
|
else:
|
||||||
aliases = _load_tsconfig_aliases(Path(str_path).parent)
|
aliases = _load_tsconfig_aliases(Path(str_path).parent)
|
||||||
@@ -467,6 +468,7 @@ def _dynamic_import_js(node, source: bytes, caller_nid: str, str_path: str, edge
|
|||||||
resolved_alias = Path(os.path.normpath(Path(alias_base) / rest))
|
resolved_alias = Path(os.path.normpath(Path(alias_base) / rest))
|
||||||
break
|
break
|
||||||
if resolved_alias is not None:
|
if resolved_alias is not None:
|
||||||
|
resolved_alias = _resolve_js_module_path(resolved_alias)
|
||||||
tgt_nid = _make_id(str(resolved_alias))
|
tgt_nid = _make_id(str(resolved_alias))
|
||||||
else:
|
else:
|
||||||
module_name = raw.split("/")[-1]
|
module_name = raw.split("/")[-1]
|
||||||
|
|||||||
@@ -458,6 +458,55 @@ def test_resolve_chain_alias_and_extension_compose(tmp_path):
|
|||||||
# ── End-to-end: dynamic_import in .svelte regex pass uses resolver ──────────
|
# ── End-to-end: dynamic_import in .svelte regex pass uses resolver ──────────
|
||||||
|
|
||||||
|
|
||||||
|
def test_ts_dynamic_import_bare_path_resolves(tmp_path):
|
||||||
|
"""Real-world repro: a TS file uses `await import('./foo')` (no extension)
|
||||||
|
to lazy-load a sibling module. The dynamic-import handler in JS/TS files
|
||||||
|
has its own copy of the resolution logic — distinct from the static-import
|
||||||
|
handler and from the Svelte regex pass — and was missing the bare-path
|
||||||
|
extension append, silently dropping every such edge."""
|
||||||
|
target = _write(tmp_path / "profanity.ts",
|
||||||
|
"export const hasProfanity = (s: string) => false")
|
||||||
|
importer = _write(tmp_path / "auth-validators.ts", """\
|
||||||
|
export async function validate(name: string) {
|
||||||
|
const { hasProfanity } = await import('./profanity')
|
||||||
|
return hasProfanity(name)
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
result = extract_js(importer)
|
||||||
|
expected = _make_id(str(target))
|
||||||
|
targets = {str(e.get("target") or "") for e in result["edges"]
|
||||||
|
if e.get("relation") in ("imports", "imports_from")}
|
||||||
|
assert expected in targets, (
|
||||||
|
f"Bare-path TS dynamic import failed to resolve; "
|
||||||
|
f"expected {expected}; got {targets}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_ts_dynamic_import_alias_with_bare_path_resolves(tmp_path):
|
||||||
|
"""The other branch of the dynamic-import handler — alias resolution —
|
||||||
|
also needs the same fixups. `import('$lib/foo')` should resolve to
|
||||||
|
`$lib/foo.ts` after both alias substitution and extension append."""
|
||||||
|
src = tmp_path / "src"
|
||||||
|
target = _write(src / "lib" / "lazy-module.ts", "export const x = 1")
|
||||||
|
_write(tmp_path / "tsconfig.json",
|
||||||
|
'{"compilerOptions":{"paths":{"$lib":["./src/lib"],'
|
||||||
|
'"$lib/*":["./src/lib/*"]}}}')
|
||||||
|
importer = _write(src / "routes" / "page.ts", """\
|
||||||
|
export async function load() {
|
||||||
|
const m = await import('$lib/lazy-module')
|
||||||
|
return m.x
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
result = extract_js(importer)
|
||||||
|
expected = _make_id(str(target))
|
||||||
|
targets = {str(e.get("target") or "") for e in result["edges"]
|
||||||
|
if e.get("relation") in ("imports", "imports_from")}
|
||||||
|
assert expected in targets, (
|
||||||
|
f"Alias + bare-path dynamic import failed to resolve; "
|
||||||
|
f"expected {expected}; got {targets}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_dynamic_import_bare_path_resolves(tmp_path):
|
def test_dynamic_import_bare_path_resolves(tmp_path):
|
||||||
"""The regex pass for `import('...')` in .svelte files must also use
|
"""The regex pass for `import('...')` in .svelte files must also use
|
||||||
the new resolver — otherwise dynamic imports of bare paths still
|
the new resolver — otherwise dynamic imports of bare paths still
|
||||||
|
|||||||
Reference in New Issue
Block a user