diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e11c331f..7b4c6a2a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ Full release notes with details on each version: [GitHub Releases](https://githu ## 0.9.42 (unreleased) +- Fix: `.svelte` and `.astro` files now get a real AST pass. Both extractors fed the raw file to the JS grammar, which errors on the first tag (Svelte) or the opening `---` (Astro), abandoning the AST and leaving only the regex import rescue — so every function, const, interface and type in those files was invisible to the graph. Both now mask the non-code regions and parse the script with the TypeScript grammar, mirroring `extract_vue` (#850 family). Measured on a Svelte 5 + Astro monorepo: `.svelte` 931 → 4,307 nodes and 1,047 → 6,225 edges across 260 files; `.astro` 2,946 → 6,607 nodes and 9,963 → 15,337 edges across 1,107 files. - Fix: a JS/TS `for...of` / `for...in` loop binding is now shadowed, so passing it as a call argument no longer fabricates an `indirect_call` edge to an unrelated same-named callable (#2685, thanks @ousamabenyounes); completes the loop/closure/catch shadow family (#2568/#2569/#2517). - Fix: graph provenance (`built_at_commit`) is stamped from the analysed repository rather than the shell's working directory, so `graphify extract` run from elsewhere records the target's commit, not the caller's (#2534 family; #2699, thanks @C0KERNEL). - Fix: `affected` resolves a seed passed as a `./`-relative path (or an absolute path when run from the repo root) instead of silently returning nothing (#2707, thanks @phudayyy). Note: an absolute-path seed still requires the working directory to be the analysed repo root. diff --git a/graphify/extract.py b/graphify/extract.py index 6e1fca854..9dc36988a 100644 --- a/graphify/extract.py +++ b/graphify/extract.py @@ -134,6 +134,7 @@ _ts_heritage_clause_entries, _ts_walk_class_members, _vue_mask_non_script, + _astro_best_mask, _walk_js_tree, _walk_python_tree, _workspace_globs, @@ -1577,7 +1578,23 @@ def extract_svelte(path: Path) -> dict: {#await import('./X.svelte')} lives in the markup layer and is invisible to the JS parser, so a regex pass covers those dynamic imports. """ - result = _extract_generic(path, _JS_CONFIG) + # Mask the markup so the + +
+ + {#if greeting}{greeting}{/if} +
+ + +""" + +ASTRO_SRC = """\ +--- +import Layout from '../layouts/Layout.astro'; +import { getItems } from '../lib/items'; + +interface PageProps { slug: string } + +const items = await getItems(); + +function renderCount(n: number): string { + return `${n} items`; +} +--- + + +

{renderCount(items.length)}

+
+ + +""" + + +def test_extensions_registered(): + assert ".svelte" in CODE_EXTENSIONS + assert ".astro" in CODE_EXTENSIONS + + +def test_svelte_mask_preserves_line_numbers_and_blanks_markup(): + masked, lang = _vue_mask_non_script(SVELTE_SRC) + assert lang == "ts" + assert len(masked.splitlines()) == len(SVELTE_SRC.splitlines()) + # Script body survives; markup and style do not. + assert "formatTitle" in masked + assert "color: red" not in masked + assert "on:click" not in masked + + +def test_astro_mask_preserves_line_numbers_and_blanks_template(): + masked = _astro_mask(ASTRO_SRC) + assert len(masked.splitlines()) == len(ASTRO_SRC.splitlines()) + assert "renderCount" in masked + assert "clientOnly" in masked # client