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 `