diff --git a/docs/SEARCH_QUALITY_LOOP.md b/docs/SEARCH_QUALITY_LOOP.md
index 06fcdd1..784b765 100644
--- a/docs/SEARCH_QUALITY_LOOP.md
+++ b/docs/SEARCH_QUALITY_LOOP.md
@@ -455,6 +455,10 @@ test().catch(console.error);
| Kotlin `navigation_expression` calls not resolved cleanly | `navigation_expression` fell through to `getNodeText` producing messy names with parentheses | `src/extraction/tree-sitter.ts: extractCall` — handle `navigation_expression` by extracting method name from `navigation_suffix > simple_identifier` |
| Kotlin `fun interface` declarations invisible | Tree-sitter-kotlin doesn't support `fun interface` syntax (Kotlin 1.4+), producing ERROR or misparse as `function_declaration` | `src/extraction/languages/kotlin.ts: visitNode` detects three misparse patterns: (1) ERROR node + lambda body, (2) function_declaration with `user_type("interface")` direct child + name in ERROR child, (3) function_declaration with ERROR child containing `user_type("interface")` + name. `isFunInterfaceNode` checks both direct and ERROR-nested `user_type` children |
| Kotlin class/interface methods missing when nested `fun interface` present | Tree-sitter misparsed parent body as ERROR (starting with `{`) + class_body (nested interface body); `resolveBody` found wrong body | `src/extraction/languages/kotlin.ts: resolveBody` prefers ERROR bodies starting with `{`; `visitNode` excludes body-like ERROR from `fun interface` detection |
+| Svelte `$props()` destructuring produces ugly variable names | `let { x, y } = $props()` has `object_pattern` as variable name node; `getNodeText` returns full pattern | `src/extraction/tree-sitter.ts: extractVariable` skips `object_pattern`/`array_pattern` named declarators |
+| Svelte template function calls invisible (e.g. `class={cn(...)}`) | SvelteExtractor only parsed ` and ranges
+ const tagRegex = /<(script|style)(\s[^>]*)?>[\s\S]*?<\/\1>/g;
+ let tagMatch;
+ while ((tagMatch = tagRegex.exec(this.source)) !== null) {
+ const startLine = (this.source.substring(0, tagMatch.index).match(/\n/g) || []).length;
+ const endLine = startLine + (tagMatch[0].match(/\n/g) || []).length;
+ coveredRanges.push([startLine, endLine]);
+ }
+
+ // Find template expressions: {...} outside of script/style blocks
+ // Matches curly-brace expressions, excluding Svelte block syntax ({#if}, {:else}, {/if}, {@html}, {@render})
+ const lines = this.source.split('\n');
+ const exprRegex = /\{([^}#/:@][^}]*)\}/g;
+
+ for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {
+ // Skip lines inside script/style blocks
+ if (coveredRanges.some(([start, end]) => lineIdx >= start && lineIdx <= end)) continue;
+
+ const line = lines[lineIdx]!;
+ let exprMatch;
+ while ((exprMatch = exprRegex.exec(line)) !== null) {
+ const expr = exprMatch[1]!;
+ // Extract function calls: identifiers followed by (
+ // Matches: cn(...), buttonVariants(...), obj.method(...)
+ const callRegex = /\b([a-zA-Z_$][\w$.]*)\s*\(/g;
+ let callMatch;
+ while ((callMatch = callRegex.exec(expr)) !== null) {
+ const calleeName = callMatch[1]!;
+ // Skip Svelte runes, control flow keywords, and common non-function patterns
+ if (SVELTE_RUNES.has(calleeName)) continue;
+ if (calleeName === 'if' || calleeName === 'else' || calleeName === 'each' || calleeName === 'await') continue;
+
+ this.unresolvedReferences.push({
+ fromNodeId: componentNodeId,
+ referenceName: calleeName,
+ referenceKind: 'calls',
+ line: lineIdx + 1, // 1-indexed
+ column: exprMatch.index + callMatch.index,
+ filePath: this.filePath,
+ language: 'svelte',
+ });
+ }
+ }
+ }
+ }
}
diff --git a/src/extraction/tree-sitter.ts b/src/extraction/tree-sitter.ts
index 640c78f..5bce14e 100644
--- a/src/extraction/tree-sitter.ts
+++ b/src/extraction/tree-sitter.ts
@@ -590,6 +590,12 @@ export class TreeSitterExtractor {
// Languages with methodsAreTopLevel (e.g. Go) always treat them as methods
// Languages with getReceiverType (e.g. Rust) extract as method when receiver is found
if (!this.isInsideClassLikeNode() && !this.extractor.methodsAreTopLevel && !receiverType) {
+ // Skip method_definition nodes inside object literals (getters/setters/methods
+ // in inline objects). These are ephemeral and create noise (e.g., Svelte context
+ // objects: `ctx.set({ get view() { ... } })`).
+ if (node.parent?.type === 'object' || node.parent?.type === 'object_expression') {
+ return;
+ }
// Not inside a class-like node and no receiver type, treat as function
this.extractFunction(node);
return;
@@ -929,6 +935,11 @@ export class TreeSitterExtractor {
const valueNode = getChildByField(child, 'value');
if (nameNode) {
+ // Skip destructured patterns (e.g., `let { x, y } = $props()` in Svelte)
+ // These produce ugly multi-line names like "{ class: className }"
+ if (nameNode.type === 'object_pattern' || nameNode.type === 'array_pattern') {
+ continue;
+ }
const name = getNodeText(nameNode, this.source);
// Arrow functions / function expressions: extract as function instead of variable
if (valueNode && (valueNode.type === 'arrow_function' || valueNode.type === 'function_expression')) {