diff --git a/gitnexus/src/core/ingestion/field-extractor.ts b/gitnexus/src/core/ingestion/field-extractor.ts index e19000826..9a60e48f0 100644 --- a/gitnexus/src/core/ingestion/field-extractor.ts +++ b/gitnexus/src/core/ingestion/field-extractor.ts @@ -2,9 +2,10 @@ import type { SyntaxNode } from './utils/ast-helpers.js'; import { SupportedLanguages } from '../../config/supported-languages.js'; -import type { - FieldExtractorContext, - ExtractedFields +import type { + FieldExtractorContext, + ExtractedFields, + FieldVisibility, } from './field-types.js'; /** @@ -56,5 +57,5 @@ export abstract class BaseFieldExtractor implements FieldExtractor { return typeName; } - protected abstract extractVisibility(node: SyntaxNode): string; + protected abstract extractVisibility(node: SyntaxNode): FieldVisibility; } diff --git a/gitnexus/src/core/ingestion/field-extractors/configs/c-cpp.ts b/gitnexus/src/core/ingestion/field-extractors/configs/c-cpp.ts index 34d6862f2..7ecf64828 100644 --- a/gitnexus/src/core/ingestion/field-extractors/configs/c-cpp.ts +++ b/gitnexus/src/core/ingestion/field-extractors/configs/c-cpp.ts @@ -5,12 +5,13 @@ import type { FieldExtractionConfig } from '../generic.js'; import { hasKeyword } from './helpers.js'; import { extractSimpleTypeName } from '../../type-extractors/shared.js'; import type { SyntaxNode } from '../../utils/ast-helpers.js'; +import type { FieldVisibility } from '../../field-types.js'; /** * Detect C++ access specifier (public:/private:/protected:) by walking * backwards from the field node through siblings. */ -function cppAccessSpecifier(node: SyntaxNode): string | undefined { +function cppAccessSpecifier(node: SyntaxNode): FieldVisibility | undefined { let sibling = node.previousNamedSibling; while (sibling) { if (sibling.type === 'access_specifier') { diff --git a/gitnexus/src/core/ingestion/field-extractors/configs/csharp.ts b/gitnexus/src/core/ingestion/field-extractors/configs/csharp.ts index 9238a51ec..90ccd861d 100644 --- a/gitnexus/src/core/ingestion/field-extractors/configs/csharp.ts +++ b/gitnexus/src/core/ingestion/field-extractors/configs/csharp.ts @@ -4,8 +4,9 @@ import { SupportedLanguages } from '../../../../config/supported-languages.js'; import type { FieldExtractionConfig } from '../generic.js'; import { findVisibility, hasKeyword, hasModifier } from './helpers.js'; import { extractSimpleTypeName } from '../../type-extractors/shared.js'; +import type { FieldVisibility } from '../../field-types.js'; -const CSHARP_VIS = new Set(['public', 'private', 'protected', 'internal']); +const CSHARP_VIS = new Set(['public', 'private', 'protected', 'internal']); /** * C# field extraction config. diff --git a/gitnexus/src/core/ingestion/field-extractors/configs/helpers.ts b/gitnexus/src/core/ingestion/field-extractors/configs/helpers.ts index 27490b4bd..63fe9b905 100644 --- a/gitnexus/src/core/ingestion/field-extractors/configs/helpers.ts +++ b/gitnexus/src/core/ingestion/field-extractors/configs/helpers.ts @@ -7,6 +7,7 @@ import type { SyntaxNode } from '../../utils/ast-helpers.js'; import { extractSimpleTypeName } from '../../type-extractors/shared.js'; +import type { FieldVisibility } from '../../field-types.js'; // --------------------------------------------------------------------------- // Modifier scanning @@ -48,14 +49,15 @@ export function hasModifier(node: SyntaxNode, modifierType: string, keyword: str */ export function findVisibility( node: SyntaxNode, - keywords: ReadonlySet, - defaultVis: string, + keywords: ReadonlySet, + defaultVis: FieldVisibility, modifierNodeType?: string, -): string { +): FieldVisibility { // Direct keyword children for (let i = 0; i < node.childCount; i++) { const child = node.child(i); - if (child && keywords.has(child.text.trim())) return child.text.trim(); + const text = child?.text.trim() as FieldVisibility | undefined; + if (text && (keywords as ReadonlySet).has(text)) return text; } // Modifier wrapper if (modifierNodeType) { @@ -64,7 +66,8 @@ export function findVisibility( if (child && child.type === modifierNodeType) { for (let j = 0; j < child.childCount; j++) { const mod = child.child(j); - if (mod && keywords.has(mod.text.trim())) return mod.text.trim(); + const modText = mod?.text.trim() as FieldVisibility | undefined; + if (modText && (keywords as ReadonlySet).has(modText)) return modText; } } } diff --git a/gitnexus/src/core/ingestion/field-extractors/configs/jvm.ts b/gitnexus/src/core/ingestion/field-extractors/configs/jvm.ts index 7b05ff7a0..e72bd245c 100644 --- a/gitnexus/src/core/ingestion/field-extractors/configs/jvm.ts +++ b/gitnexus/src/core/ingestion/field-extractors/configs/jvm.ts @@ -4,12 +4,13 @@ import { SupportedLanguages } from '../../../../config/supported-languages.js'; import type { FieldExtractionConfig } from '../generic.js'; import { findVisibility, hasKeyword, hasModifier, typeFromField } from './helpers.js'; import { extractSimpleTypeName } from '../../type-extractors/shared.js'; +import type { FieldVisibility } from '../../field-types.js'; // --------------------------------------------------------------------------- // Java // --------------------------------------------------------------------------- -const JAVA_VIS = new Set(['public', 'private', 'protected']); +const JAVA_VIS = new Set(['public', 'private', 'protected']); export const javaConfig: FieldExtractionConfig = { language: SupportedLanguages.Java, @@ -70,7 +71,7 @@ export const javaConfig: FieldExtractionConfig = { // Kotlin // --------------------------------------------------------------------------- -const KOTLIN_VIS = new Set(['public', 'private', 'protected', 'internal']); +const KOTLIN_VIS = new Set(['public', 'private', 'protected', 'internal']); export const kotlinConfig: FieldExtractionConfig = { language: SupportedLanguages.Kotlin, diff --git a/gitnexus/src/core/ingestion/field-extractors/configs/php.ts b/gitnexus/src/core/ingestion/field-extractors/configs/php.ts index 49437ec51..d6054209f 100644 --- a/gitnexus/src/core/ingestion/field-extractors/configs/php.ts +++ b/gitnexus/src/core/ingestion/field-extractors/configs/php.ts @@ -4,8 +4,9 @@ import { SupportedLanguages } from '../../../../config/supported-languages.js'; import type { FieldExtractionConfig } from '../generic.js'; import { findVisibility, hasKeyword } from './helpers.js'; import { extractSimpleTypeName } from '../../type-extractors/shared.js'; +import type { FieldVisibility } from '../../field-types.js'; -const PHP_VIS = new Set(['public', 'private', 'protected']); +const PHP_VIS = new Set(['public', 'private', 'protected']); /** * PHP field extraction config. diff --git a/gitnexus/src/core/ingestion/field-extractors/configs/python.ts b/gitnexus/src/core/ingestion/field-extractors/configs/python.ts index 4eae9559c..aea0f94eb 100644 --- a/gitnexus/src/core/ingestion/field-extractors/configs/python.ts +++ b/gitnexus/src/core/ingestion/field-extractors/configs/python.ts @@ -85,8 +85,8 @@ export const pythonConfig: FieldExtractionConfig = { }, isStatic(_node) { - // Class-level variables in Python are effectively static; - // instance variables (self.x) live in __init__ and are not extracted here. + // Reports syntactic static keyword — Python class variables don't use explicit static keyword. + // Instance variables (self.x) live in __init__ and are not extracted here. return false; }, diff --git a/gitnexus/src/core/ingestion/field-extractors/configs/ruby.ts b/gitnexus/src/core/ingestion/field-extractors/configs/ruby.ts index b28781e92..17cfac62f 100644 --- a/gitnexus/src/core/ingestion/field-extractors/configs/ruby.ts +++ b/gitnexus/src/core/ingestion/field-extractors/configs/ruby.ts @@ -2,6 +2,33 @@ import { SupportedLanguages } from '../../../../config/supported-languages.js'; import type { FieldExtractionConfig } from '../generic.js'; +import type { SyntaxNode } from '../../utils/ast-helpers.js'; + +/** + * Collect all field names declared by an `attr_accessor`, `attr_reader`, or + * `attr_writer` call node. A single call may list multiple symbols: + * attr_accessor :foo, :bar, :baz + */ +function extractAttrNames(node: SyntaxNode): string[] { + const method = node.childForFieldName('method'); + if (!method) return []; + const methodName = method.text; + if (methodName !== 'attr_accessor' && methodName !== 'attr_reader' + && methodName !== 'attr_writer') { + return []; + } + const args = node.childForFieldName('arguments'); + if (!args) return []; + const names: string[] = []; + for (let i = 0; i < args.namedChildCount; i++) { + const arg = args.namedChild(i); + if (!arg) continue; + // simple_symbol text is :name — strip the leading colon + const text = arg.text; + names.push(text.startsWith(':') ? text.slice(1) : text); + } + return names; +} /** * Ruby field extraction config. @@ -25,22 +52,13 @@ export const rubyConfig: FieldExtractionConfig = { defaultVisibility: 'public', extractName(node) { - // call node: method = attr_accessor / attr_reader / attr_writer - const method = node.childForFieldName('method'); - if (!method) return undefined; - const methodName = method.text; - if (methodName !== 'attr_accessor' && methodName !== 'attr_reader' - && methodName !== 'attr_writer') { - return undefined; - } - // arguments: argument_list > simple_symbol (:name) - const args = node.childForFieldName('arguments'); - if (!args) return undefined; - const firstArg = args.firstNamedChild; - if (!firstArg) return undefined; - // simple_symbol text is :name — strip the colon - const text = firstArg.text; - return text.startsWith(':') ? text.slice(1) : text; + // Returns the first symbol name for interface compatibility. + // Use extractNames to obtain all names from a single attr_* call. + return extractAttrNames(node)[0]; + }, + + extractNames(node) { + return extractAttrNames(node); }, extractType(_node) { @@ -48,7 +66,7 @@ export const rubyConfig: FieldExtractionConfig = { return undefined; }, - extractVisibility(node) { + extractVisibility(_node) { // attr_accessor/attr_writer fields are effectively public // attr_reader fields are read-only from outside but still public return 'public'; diff --git a/gitnexus/src/core/ingestion/field-extractors/configs/swift.ts b/gitnexus/src/core/ingestion/field-extractors/configs/swift.ts index 05af283ab..e353f0406 100644 --- a/gitnexus/src/core/ingestion/field-extractors/configs/swift.ts +++ b/gitnexus/src/core/ingestion/field-extractors/configs/swift.ts @@ -4,8 +4,9 @@ import { SupportedLanguages } from '../../../../config/supported-languages.js'; import type { FieldExtractionConfig } from '../generic.js'; import { hasKeyword, findVisibility } from './helpers.js'; import { extractSimpleTypeName } from '../../type-extractors/shared.js'; +import type { FieldVisibility } from '../../field-types.js'; -const SWIFT_VIS = new Set(['public', 'private', 'fileprivate', 'internal', 'open']); +const SWIFT_VIS = new Set(['public', 'private', 'fileprivate', 'internal', 'open']); /** * Swift field extraction config. diff --git a/gitnexus/src/core/ingestion/field-extractors/configs/typescript-javascript.ts b/gitnexus/src/core/ingestion/field-extractors/configs/typescript-javascript.ts index af012c1c1..67ad53f6a 100644 --- a/gitnexus/src/core/ingestion/field-extractors/configs/typescript-javascript.ts +++ b/gitnexus/src/core/ingestion/field-extractors/configs/typescript-javascript.ts @@ -3,8 +3,9 @@ import { SupportedLanguages } from '../../../../config/supported-languages.js'; import type { FieldExtractionConfig } from '../generic.js'; import { hasKeyword, findVisibility, typeFromAnnotation } from './helpers.js'; +import type { FieldVisibility } from '../../field-types.js'; -const VISIBILITY_KEYWORDS = new Set(['public', 'private', 'protected']); +const VISIBILITY_KEYWORDS = new Set(['public', 'private', 'protected']); const shared: Omit = { typeDeclarationNodes: [ @@ -43,7 +44,7 @@ const shared: Omit = { for (let i = 0; i < node.namedChildCount; i++) { const child = node.namedChild(i); if (child && child.type === 'accessibility_modifier') { - const t = child.text.trim(); + const t = child.text.trim() as FieldVisibility; if (VISIBILITY_KEYWORDS.has(t)) return t; } } diff --git a/gitnexus/src/core/ingestion/field-extractors/generic.ts b/gitnexus/src/core/ingestion/field-extractors/generic.ts index 9ae32dce4..baf850b33 100644 --- a/gitnexus/src/core/ingestion/field-extractors/generic.ts +++ b/gitnexus/src/core/ingestion/field-extractors/generic.ts @@ -12,7 +12,7 @@ import type { SyntaxNode } from '../utils/ast-helpers.js'; import { SupportedLanguages } from '../../../config/supported-languages.js'; import { BaseFieldExtractor } from '../field-extractor.js'; import type { FieldExtractor } from '../field-extractor.js'; -import type { FieldExtractorContext, ExtractedFields, FieldInfo } from '../field-types.js'; +import type { FieldExtractorContext, ExtractedFields, FieldInfo, FieldVisibility } from '../field-types.js'; // --------------------------------------------------------------------------- // Config interface @@ -27,13 +27,23 @@ export interface FieldExtractionConfig { /** AST node type(s) for the class body container (e.g., 'class_body', 'declaration_list') */ bodyNodeTypes: string[]; /** Default visibility when no modifier is present */ - defaultVisibility: string; - /** Extract field name from a field declaration node */ + defaultVisibility: FieldVisibility; + /** + * Extract field name from a field declaration node. + * Use this for nodes that declare exactly one field. + */ extractName: (node: SyntaxNode) => string | undefined; + /** + * Extract multiple field names from a single declaration node. + * Optional override for languages where one AST node can declare + * several fields (e.g. Ruby `attr_accessor :foo, :bar`). + * When present, the factory uses this instead of `extractName`. + */ + extractNames?: (node: SyntaxNode) => string[]; /** Extract type annotation from a field declaration node */ extractType: (node: SyntaxNode) => string | undefined; /** Extract visibility from a field declaration node */ - extractVisibility: (node: SyntaxNode) => string; + extractVisibility: (node: SyntaxNode) => FieldVisibility; /** Check if a field is static */ isStatic: (node: SyntaxNode) => boolean; /** Check if a field is readonly/final/const */ @@ -59,7 +69,7 @@ export function createFieldExtractor(config: FieldExtractionConfig): FieldExtrac return typeDeclarationSet.has(node.type); } - protected extractVisibility(node: SyntaxNode): string { + protected extractVisibility(node: SyntaxNode): FieldVisibility { return config.extractVisibility(node); } @@ -117,8 +127,17 @@ export function createFieldExtractor(config: FieldExtractionConfig): FieldExtrac if (!child) continue; if (fieldNodeSet.has(child.type)) { - const field = this.extractSingleField(child, context); - if (field) out.push(field); + if (config.extractNames) { + // Multi-name path: one node may declare several fields (e.g. Ruby attr_accessor) + const names = config.extractNames(child); + for (const name of names) { + const field = this.buildField(child, name, context); + if (field) out.push(field); + } + } else { + const field = this.extractSingleField(child, context); + if (field) out.push(field); + } } } } @@ -129,6 +148,15 @@ export function createFieldExtractor(config: FieldExtractionConfig): FieldExtrac ): FieldInfo | null { const name = config.extractName(node); if (!name) return null; + return this.buildField(node, name, context); + } + + private buildField( + node: SyntaxNode, + name: string, + context: FieldExtractorContext, + ): FieldInfo | null { + if (!name) return null; let type: string | null = config.extractType(node) ?? null; if (type) { diff --git a/gitnexus/src/core/ingestion/field-extractors/typescript.ts b/gitnexus/src/core/ingestion/field-extractors/typescript.ts index 6282fecf7..4ab518284 100644 --- a/gitnexus/src/core/ingestion/field-extractors/typescript.ts +++ b/gitnexus/src/core/ingestion/field-extractors/typescript.ts @@ -3,17 +3,19 @@ import type { SyntaxNode } from '../utils/ast-helpers.js'; import { SupportedLanguages } from '../../../config/supported-languages.js'; import { BaseFieldExtractor } from '../field-extractor.js'; -import type { FieldExtractorContext, ExtractedFields, FieldInfo } from '../field-types.js'; +import type { FieldExtractorContext, ExtractedFields, FieldInfo, FieldVisibility } from '../field-types.js'; /** - * TypeScript field extractor for class and interface declarations. - * - * Handles: - * - Class fields with visibility modifiers (public/private/protected) - * - Interface properties with optional markers (?:) - * - Static and readonly modifiers - * - Complex generic types - * - Property signatures in interface bodies + * Hand-written TypeScript field extractor. + * + * This exists alongside the config-based extractor in configs/typescript-javascript.ts + * (used for JavaScript) because TypeScript has unique requirements: + * 1. type_alias_declaration with object type literals (e.g., type Config = { key: string }) + * 2. Optional property detection appending '| undefined' to types + * 3. Nested type discovery within class/interface bodies + * + * The config-based extractor cannot express these TS-specific capabilities. + * JavaScript uses the config-based version since it lacks type syntax. */ export class TypeScriptFieldExtractor extends BaseFieldExtractor { language = SupportedLanguages.TypeScript; @@ -40,7 +42,7 @@ export class TypeScriptFieldExtractor extends BaseFieldExtractor { /** * Visibility modifiers in TypeScript */ - private static readonly VISIBILITY_MODIFIERS = new Set([ + private static readonly VISIBILITY_MODIFIERS = new Set([ 'public', 'private', 'protected', @@ -56,12 +58,12 @@ export class TypeScriptFieldExtractor extends BaseFieldExtractor { /** * Extract visibility modifier from a field node */ - protected extractVisibility(node: SyntaxNode): string { + protected extractVisibility(node: SyntaxNode): FieldVisibility { // Check for accessibility_modifier named child (tree-sitter typescript) for (let i = 0; i < node.namedChildCount; i++) { const child = node.namedChild(i); if (child && child.type === 'accessibility_modifier') { - const text = child.text.trim(); + const text = child.text.trim() as FieldVisibility; if (TypeScriptFieldExtractor.VISIBILITY_MODIFIERS.has(text)) { return text; } @@ -72,7 +74,7 @@ export class TypeScriptFieldExtractor extends BaseFieldExtractor { for (let i = 0; i < node.childCount; i++) { const child = node.child(i); if (child && !child.isNamed) { - const text = child.text.trim(); + const text = child.text.trim() as FieldVisibility; if (TypeScriptFieldExtractor.VISIBILITY_MODIFIERS.has(text)) { return text; } @@ -84,8 +86,9 @@ export class TypeScriptFieldExtractor extends BaseFieldExtractor { if (modifiers) { for (let i = 0; i < modifiers.childCount; i++) { const modifier = modifiers.child(i); - if (modifier && TypeScriptFieldExtractor.VISIBILITY_MODIFIERS.has(modifier.text)) { - return modifier.text; + const modText = modifier?.text.trim() as FieldVisibility | undefined; + if (modText && TypeScriptFieldExtractor.VISIBILITY_MODIFIERS.has(modText)) { + return modText; } } } @@ -164,65 +167,19 @@ export class TypeScriptFieldExtractor extends BaseFieldExtractor { } /** - * Extract the full type text, handling complex generic types + * Extract the full type text, handling complex generic types. + * + * type_annotation nodes wrap the literal ': SomeType' — only that branch + * needs special handling to unwrap the inner child and skip the colon. + * All other node kinds are already the type text itself, so normalizeType + * is applied directly. */ private extractFullType(typeNode: SyntaxNode | null): string | null { if (!typeNode) return null; - - // For type_annotation, get the inner type (skip the ':') if (typeNode.type === 'type_annotation') { const innerType = typeNode.firstNamedChild; - if (innerType) { - return this.normalizeType(innerType.text); - } + return innerType ? this.normalizeType(innerType.text) : null; } - - // Handle predefined_type (string, number, boolean, etc.) - if (typeNode.type === 'predefined_type') { - return typeNode.text; - } - - // Handle type_identifier (custom types) - if (typeNode.type === 'type_identifier') { - return typeNode.text; - } - - // Handle generic_type (Array, Map, etc.) - if (typeNode.type === 'generic_type') { - return this.normalizeType(typeNode.text); - } - - // Handle array_type (User[]) - if (typeNode.type === 'array_type') { - return this.normalizeType(typeNode.text); - } - - // Handle union_type (User | null) - if (typeNode.type === 'union_type') { - return this.normalizeType(typeNode.text); - } - - // Handle intersection_type (A & B) - if (typeNode.type === 'intersection_type') { - return this.normalizeType(typeNode.text); - } - - // Handle object_type ({ name: string; age: number }) - if (typeNode.type === 'object_type') { - return this.normalizeType(typeNode.text); - } - - // Handle literal types - if (typeNode.type === 'literal_type') { - return this.normalizeType(typeNode.text); - } - - // Handle nullable_type (string | null shorthand) - if (typeNode.type === 'nullable_type') { - return this.normalizeType(typeNode.text); - } - - // Fallback: use the full text and normalize return this.normalizeType(typeNode.text); } diff --git a/gitnexus/src/core/ingestion/field-types.ts b/gitnexus/src/core/ingestion/field-types.ts index 6c5fe84e4..29e7adda5 100644 --- a/gitnexus/src/core/ingestion/field-types.ts +++ b/gitnexus/src/core/ingestion/field-types.ts @@ -4,6 +4,23 @@ import type { TypeEnvironment } from './type-env.js'; import type { SymbolTable } from './symbol-table.js'; import { SupportedLanguages } from '../../config/supported-languages.js'; +/** + * Visibility levels used across all supported languages. + * - public / private / protected: universal modifiers + * - internal: C#, Kotlin (assembly/module scope) + * - package: Java (package-private, no keyword) + * - fileprivate: Swift (file scope) + * - open: Swift (subclassable across modules) + */ +export type FieldVisibility = + | 'public' + | 'private' + | 'protected' + | 'internal' + | 'package' + | 'fileprivate' + | 'open'; + /** * Represents a field or property within a class/struct/interface */ @@ -12,8 +29,8 @@ export interface FieldInfo { name: string; /** Resolved type (may be primitive, FQN, or generic) */ type: string | null; - /** Visibility: public, private, protected, internal */ - visibility: string; + /** Visibility modifier */ + visibility: FieldVisibility; /** Is this a static member? */ isStatic: boolean; /** Is this readonly/const? */ diff --git a/gitnexus/src/core/ingestion/parsing-processor.ts b/gitnexus/src/core/ingestion/parsing-processor.ts index f4664ff76..c9f5eb12d 100644 --- a/gitnexus/src/core/ingestion/parsing-processor.ts +++ b/gitnexus/src/core/ingestion/parsing-processor.ts @@ -7,7 +7,7 @@ import { SymbolTable } from './symbol-table.js'; import { ASTCache } from './ast-cache.js'; import { getLanguageFromFilename } from './utils/language-detection.js'; import { yieldToEventLoop } from './utils/event-loop.js'; -import { getDefinitionNodeFromCaptures, findEnclosingClassId, extractMethodSignature, getLabelFromCaptures, CLASS_CONTAINER_TYPES } from './utils/ast-helpers.js'; +import { getDefinitionNodeFromCaptures, findEnclosingClassId, extractMethodSignature, getLabelFromCaptures, CLASS_CONTAINER_TYPES, type SyntaxNode } from './utils/ast-helpers.js'; import { detectFrameworkFromAST } from './framework-detection.js'; import { buildTypeEnv } from './type-env.js'; import type { FieldInfo, FieldExtractorContext } from './field-types.js'; @@ -177,12 +177,12 @@ const NOOP_SYMBOL_TABLE_SEQ: any = { }; function seqGetFieldInfo( - classNode: any, + classNode: SyntaxNode, provider: LanguageProvider, context: FieldExtractorContext, ): Map | undefined { if (!provider.fieldExtractor) return undefined; - const cacheKey = classNode.startIndex as number; + const cacheKey = classNode.startIndex; let cached = seqFieldInfoCache.get(cacheKey); if (cached) return cached; const extracted = provider.fieldExtractor.extract(classNode, context); diff --git a/gitnexus/src/core/ingestion/workers/parse-worker.ts b/gitnexus/src/core/ingestion/workers/parse-worker.ts index 002247ebf..252713652 100644 --- a/gitnexus/src/core/ingestion/workers/parse-worker.ts +++ b/gitnexus/src/core/ingestion/workers/parse-worker.ts @@ -38,6 +38,7 @@ import { extractMethodSignature, findDescendant, extractStringContent, + type SyntaxNode, } from '../utils/ast-helpers.js'; import { countCallArguments, @@ -309,7 +310,7 @@ const fieldInfoCache = new Map>(); * Walk up from a definition node to find the nearest enclosing class/struct/interface * AST node. Returns the SyntaxNode itself (not an ID) for passing to FieldExtractor. */ -function findEnclosingClassNode(node: any): any | null { +function findEnclosingClassNode(node: SyntaxNode): SyntaxNode | null { let current = node.parent; while (current) { if (CLASS_CONTAINER_TYPES.has(current.type)) { @@ -337,13 +338,13 @@ const NOOP_SYMBOL_TABLE: any = { * or the class yielded no fields. */ function getFieldInfo( - classNode: any, + classNode: SyntaxNode, provider: LanguageProvider, context: FieldExtractorContext, ): Map | undefined { if (!provider.fieldExtractor) return undefined; - const cacheKey = classNode.startIndex as number; + const cacheKey = classNode.startIndex; let cached = fieldInfoCache.get(cacheKey); if (cached) return cached;