import { Node, Edge, ExtractionResult, ExtractionError, UnresolvedReference, Language } from '../types'; import { generateNodeId } from './tree-sitter-helpers'; import { TreeSitterExtractor } from './tree-sitter'; import { isLanguageSupported } from './grammars'; import { foldScriptResult, sfcFileNode, sfcScriptLanguage, SFC_SCRIPT_ELEMENT, sfcBlockLineRanges } from './sfc-script'; /** Svelte 5 rune names — compiler builtins, not real functions */ const SVELTE_RUNES = new Set([ '$props', '$state', '$derived', '$effect', '$bindable', '$inspect', '$host', '$snippet', ]); /** * SvelteExtractor - Extracts code relationships from Svelte component files * * Svelte files are multi-language (script + template + style). Rather than * parsing the full Svelte grammar, we extract the `. const scriptRegex = new RegExp(SFC_SCRIPT_ELEMENT); let match; while ((match = scriptRegex.exec(this.source)) !== null) { if (match.groups?.content === undefined) continue; const attrs = match[1] || ''; const content = match.groups.content; // Detect TypeScript from lang attribute const isTypeScript = sfcScriptLanguage(attrs, 'svelte') === 'typescript'; // Detect module script: Svelte 4's `context="module"`, Svelte 5's `module` const isModule = /context\s*=\s*["']module["']|(?:^|\s)module(?=[\s=]|$)/.test(attrs); // Calculate the 0-indexed line where the content begins. The content // starts right after the opening tag's `>` — its leading `\n` is part // of the content, so relative line 1 sits ON the tag's closing line // (adding 1 here double-counted the embedded newline and shifted every // script-block symbol down a line). const beforeScript = this.source.substring(0, match.index); const scriptTagLine = (beforeScript.match(/\n/g) || []).length; const openingTag = match[0].substring(0, match[0].indexOf('>') + 1); const openingTagLines = (openingTag.match(/\n/g) || []).length; const contentStartLine = scriptTagLine + openingTagLines; // 0-indexed line blocks.push({ content, startLine: contentStartLine, isModule, isTypeScript, }); } return blocks; } /** * Process a script block by delegating to TreeSitterExtractor */ private processScriptBlock( block: { content: string; startLine: number; isModule: boolean; isTypeScript: boolean }, componentNodeId: string ): void { const scriptLanguage: Language = block.isTypeScript ? 'typescript' : 'javascript'; // Check if the script language parser is available if (!isLanguageSupported(scriptLanguage)) { this.errors.push({ message: `Parser for ${scriptLanguage} not available, cannot parse Svelte script block`, severity: 'warning', }); return; } // Delegate to TreeSitterExtractor const extractor = new TreeSitterExtractor(this.filePath, block.content, scriptLanguage); const result = extractor.extract(); foldScriptResult( result, { filePath: this.filePath, componentNodeId, lineOffset: block.startLine, language: 'svelte', perInstance: !block.isModule }, { nodes: this.nodes, edges: this.edges, unresolvedReferences: this.unresolvedReferences, errors: this.errors } ); } /** * Extract function calls from Svelte template expressions. * * In Svelte, many function calls happen in markup (e.g., `class={cn(...)}`), * not inside `