mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-11-15 05:59:11 +08:00
A self-closing `<script ... />`, such as Astro's
`<script is:inline set:html={code} />` or a third-party
`<script src="/analytics.js" />` in Svelte's `<svelte:head>`, matched the
extractors' script regex as an opening tag, so the match ran on to the next
`</script>`. The real script that followed was parsed with the markup in
between, failed with "parse produced no symbols", and contributed nothing.
The covered-range regexes had the same shape, so the template scans also
skipped the markup between the two tags, and a self-closing `<style />` hid
the markup after it.
The opening-tag pattern now matches a `/>` ending on its own as an empty
tag: in the Astro extractor's script blocks and covered ranges, the Svelte
extractor's script blocks and both of its covered-range scans, and the
viewer's highlight regions.
With a fix of ours: a self-closing tag has no body, so the covered-range
scans skip it, and a call in its attributes, like
`set:html={serializeJsonLd(schema)}`, is read as on main. Matched as a
range, such a tag hid those calls even where no script followed it.
Validation on main bc9291af (Windows 11):
- Red/green, 6 tests: 4 from the PRs, plus 2 of ours for an attribute call
on a self-closing script in Astro and in Svelte. With main's
astro-extractor.ts, svelte-extractor.ts and syntax-tokens.ts all 6 fail;
with the PRs' code as written the 2 of ours fail; with this commit all 6
pass.
- Svelte 5.56.10's own compiler reads the Svelte fixture as an empty head
script plus an instance script, as the extractor now does.
- A/B against main on 7 Astro repos, with flask and express as controls.
starlight, diploi.com and both controls are byte-identical, and
devfest-website keeps every edge. Gains: the genkit docsite 2 import
links, nodejsdesignpatterns.com 2 nodes and 2 import links, a portfolio
25 component references, and wm-project-astro-components 3
`serializeJsonLd` calls from JSON-LD scripts plus 1 call in a script main
swallowed. That repo also drops 31 false calls, CSS like
`border-radius: var(...)` that main read as calls after parsing a
swallowed `<style>` block as TypeScript. No other edge changes.
- Index time unchanged: median of 3 interleaved runs on
wm-project-astro-components, 4.67 s on main and 4.52 s with the fix.
Vue's extractor has the same pattern and the same bug; a follow-up shares
one pattern across the three extractors.
Original PRs #2995 and #2997 by @maxmilian.
Refs #2989
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
61 lines
2.2 KiB
TypeScript
61 lines
2.2 KiB
TypeScript
/**
|
|
* A self-closing `<script ... />` in a Svelte component, such as a
|
|
* third-party script in `<svelte:head>`, has no body. It used to be read as
|
|
* an opening tag running on to the next `</script>`: the component's real
|
|
* script was parsed with the markup in between, failed, and contributed no
|
|
* symbols, and the template scan skipped that markup. The same fix as #2989
|
|
* for Astro.
|
|
*/
|
|
import { describe, it, expect, beforeAll } from 'vitest';
|
|
import { extractFromSource } from '../src/extraction';
|
|
import { initGrammars, loadAllGrammars } from '../src/extraction/grammars';
|
|
|
|
beforeAll(async () => {
|
|
await initGrammars();
|
|
await loadAllGrammars();
|
|
});
|
|
|
|
describe('Svelte self-closing <script />', () => {
|
|
it('does not swallow the component script that follows it', () => {
|
|
const code = `<svelte:head>
|
|
<script src="/analytics.js" />
|
|
</svelte:head>
|
|
<p>{formatDate(date)}</p>
|
|
<Header />
|
|
<script>
|
|
const ready = true;
|
|
</script>
|
|
`;
|
|
const result = extractFromSource('src/routes/+page.svelte', code);
|
|
|
|
expect(result.errors).toEqual([]);
|
|
const ready = result.nodes.find((n) => n.name === 'ready');
|
|
expect(ready).toBeDefined();
|
|
expect(ready?.startLine).toBe(7);
|
|
// The markup between the two tags is still scanned.
|
|
const call = result.unresolvedReferences.find(
|
|
(ref) => ref.referenceKind === 'calls' && ref.referenceName === 'formatDate' && ref.line === 4
|
|
);
|
|
expect(call).toBeDefined();
|
|
expect(result.unresolvedReferences.some((ref) => ref.referenceName === 'Header' && ref.line === 5)).toBe(true);
|
|
});
|
|
|
|
it("still reads a call in a self-closing <script />'s attributes", () => {
|
|
// A self-closing tag has no body for the template scan to skip.
|
|
const code = `<svelte:head>
|
|
<script src={assetUrl('/analytics.js')} />
|
|
</svelte:head>
|
|
<script>
|
|
const ready = true;
|
|
</script>
|
|
`;
|
|
const result = extractFromSource('src/routes/+layout.svelte', code);
|
|
|
|
expect(result.errors).toEqual([]);
|
|
expect(result.nodes.find((n) => n.name === 'ready')?.startLine).toBe(5);
|
|
expect(result.unresolvedReferences.some(
|
|
(ref) => ref.referenceKind === 'calls' && ref.referenceName === 'assetUrl' && ref.line === 2
|
|
)).toBe(true);
|
|
});
|
|
});
|