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')) {