From 738261290a3d87c5e65e2e1798f27a59f168d2ff Mon Sep 17 00:00:00 2001 From: Max Hsu Date: Fri, 16 Oct 2026 08:51:25 +0800 Subject: [PATCH 1/2] fix(extraction): parse Vue +`; + +// TypeScript types and JSX together: only the TSX grammar reads this cleanly, +// and the extra declarations keep the "no symbols" warning from firing either way. +const TYPES_AND_JSX = ` + +`; + +/** The script body of a single-script SFC, as the viewer locates it. */ +function scriptRegion(source: string) { + const regions = syntaxRegionsFor(source, 'vue'); + expect(regions).toHaveLength(1); + const region = regions![0]!; + return { language: region.language, body: source.slice(region.start, region.end) }; +} + +describe('Vue \n`; + const js = `\n`; + expect(scriptRegion(ts).language).toBe('typescript'); + expect(scriptRegion(js).language).toBe('javascript'); + expect(extractFromSource('A.vue', ts).nodes.some((n) => n.name === 'n')).toBe(true); + expect(extractFromSource('B.vue', js).nodes.some((n) => n.name === 'n')).toBe(true); + }); +}); diff --git a/src/extraction/syntax-tokens.ts b/src/extraction/syntax-tokens.ts index 67c0d1b710..bfee678f74 100644 --- a/src/extraction/syntax-tokens.ts +++ b/src/extraction/syntax-tokens.ts @@ -362,6 +362,8 @@ export interface SyntaxRegion { const SCRIPT_BLOCK = /]*)?>([\s\S]*?)<\/script>/gi; const TS_LANG_ATTR = /lang\s*=\s*["'](ts|typescript)["']/i; +/** Vue's extractor reads `lang="tsx"` with the TSX grammar; Svelte's has no TSX. */ +const TSX_LANG_ATTR = /lang\s*=\s*["']tsx["']/i; /** Astro's frontmatter: a `---` fence at the very top of the file. */ const ASTRO_FRONTMATTER = /^(---\r?\n)([\s\S]*?)\r?\n---/; @@ -392,12 +394,18 @@ export function syntaxRegionsFor(source: string, language: Language): SyntaxRegi regions.push({ start, end: start + body.length, - language: TS_LANG_ATTR.test(match[1] ?? '') ? 'typescript' : 'javascript', + language: scriptLanguage(match[1] ?? '', language), }); } return regions; } +/** The grammar a `\n`; + expect(scriptRegion(ts).language).toBe('typescript'); + expect(extractFromSource('C.vue', ts).nodes.some((n) => n.kind === 'interface' && n.name === 'P')).toBe(true); + }); + it('still reads lang="ts" as TypeScript and no lang as JavaScript', () => { const ts = `\n`; const js = `\n`; diff --git a/src/extraction/vue-extractor.ts b/src/extraction/vue-extractor.ts index 347eb9a281..9f2ed04217 100644 --- a/src/extraction/vue-extractor.ts +++ b/src/extraction/vue-extractor.ts @@ -208,8 +208,9 @@ export class VueExtractor { const attrs = match[1] || ''; const content = match.groups?.content || match[2] || ''; - // The script's language from its lang attribute: tsx, TypeScript or JavaScript - const lang = /lang\s*=\s*["'](tsx|ts|typescript)["']/.exec(attrs)?.[1]; + // The script's language from its lang attribute: tsx, TypeScript or JavaScript. + // Matched case-insensitively, as the viewer's syntax regions do. + const lang = /lang\s*=\s*["'](tsx|ts|typescript)["']/i.exec(attrs)?.[1]?.toLowerCase(); const language: Language = lang === 'tsx' ? 'tsx' : lang ? 'typescript' : 'javascript'; // Detect