* Extract a variable declaration (const, let, var, etc.) * * Extracts top-level and module-level variable declarations. * Captures the variable name and first 100 chars of initializer in signature for searchability.
(node: SyntaxNode)
| 2409 | * Captures the variable name and first 100 chars of initializer in signature for searchability. |
| 2410 | */ |
| 2411 | private extractVariable(node: SyntaxNode): void { |
| 2412 | if (!this.extractor) return; |
| 2413 | |
| 2414 | // Different languages have different variable declaration structures |
| 2415 | // TypeScript/JavaScript: lexical_declaration contains variable_declarator children |
| 2416 | // Python: assignment has left (identifier) and right (value) |
| 2417 | // Go: var_declaration, short_var_declaration, const_declaration |
| 2418 | |
| 2419 | const isConst = this.extractor.isConst?.(node) ?? false; |
| 2420 | const kind: NodeKind = isConst ? 'constant' : 'variable'; |
| 2421 | const docstring = getPrecedingDocstring(node, this.source); |
| 2422 | const isExported = this.extractor.isExported?.(node, this.source) ?? false; |
| 2423 | |
| 2424 | // Extract variable declarators based on language |
| 2425 | if (this.language === 'typescript' || this.language === 'javascript' || |
| 2426 | this.language === 'tsx' || this.language === 'jsx') { |
| 2427 | // Handle lexical_declaration and variable_declaration |
| 2428 | // These contain one or more variable_declarator children |
| 2429 | for (let i = 0; i < node.namedChildCount; i++) { |
| 2430 | const child = node.namedChild(i); |
| 2431 | if (child?.type === 'variable_declarator') { |
| 2432 | const nameNode = getChildByField(child, 'name'); |
| 2433 | const valueNode = getChildByField(child, 'value'); |
| 2434 | |
| 2435 | if (nameNode) { |
| 2436 | // Skip destructured patterns (e.g., `let { x, y } = $props()` in Svelte) |
| 2437 | // These produce ugly multi-line names like "{ class: className }". |
| 2438 | // EXCEPT `export const { useGetXQuery } = someApi` — the RTK Query |
| 2439 | // generated hooks: real exported symbols destructured off a createApi |
| 2440 | // result. Mint a node per binding matching the hook convention (gated |
| 2441 | // on a bare-identifier RHS so ordinary destructures stay skipped). |
| 2442 | if (nameNode.type === 'object_pattern' || nameNode.type === 'array_pattern') { |
| 2443 | if (nameNode.type === 'object_pattern' && valueNode?.type === 'identifier') { |
| 2444 | this.extractRtkHookBindings(nameNode, isExported); |
| 2445 | } |
| 2446 | continue; |
| 2447 | } |
| 2448 | const name = getNodeText(nameNode, this.source); |
| 2449 | // Arrow functions / function expressions: extract as function instead of variable |
| 2450 | if (valueNode && (valueNode.type === 'arrow_function' || valueNode.type === 'function_expression')) { |
| 2451 | this.extractFunction(valueNode); |
| 2452 | continue; |
| 2453 | } |
| 2454 | |
| 2455 | // Capture first 100 chars of initializer for context (stored in signature for searchability) |
| 2456 | const initValue = valueNode ? getNodeText(valueNode, this.source).slice(0, 100) : undefined; |
| 2457 | const initSignature = initValue ? `= ${initValue}${initValue.length >= 100 ? '...' : ''}` : undefined; |
| 2458 | |
| 2459 | // React HOC-wrapped components (`forwardRef`/`memo`/`styled`) — see |
| 2460 | // reactComponentHoc. The initializer is a call / tagged-template (not |
| 2461 | // a bare arrow), so without this the const is a plain `constant`, |
| 2462 | // which the JSX-render synthesizer and component resolution both skip |
| 2463 | // → `<Button/>` usages get no edge and callers/impact return empty |
| 2464 | // (the whole shadcn/ui design-system pattern, #841). PascalCase-gated |
| 2465 | // to the component naming convention so a memoization util |
| 2466 | // (`const cache = memo(fn)`) stays a constant. |
| 2467 | if (valueNode && /^[A-Z]/.test(name)) { |
| 2468 | const hoc = this.reactComponentHoc(valueNode); |
no test coverage detected