@@ -345,6 +345,9 @@ function extractSymbolsQuery(tree: TreeSitterTree, query: TreeSitterQuery): Extr
345345 // Extract typeMap with intra-file return-type propagation
346346 extractTypeMapWalk ( tree . rootNode , typeMap , returnTypeMap , callAssignments , fnRefBindings ) ;
347347
348+ // Prototype-based method definitions: `Foo.prototype.bar = fn` and `Foo.prototype = { bar: fn }`
349+ extractPrototypeMethodsWalk ( tree . rootNode , definitions , typeMap ) ;
350+
348351 // Phase 8.3c: Extract call-site argument bindings for parameter-flow pts analysis
349352 extractParamBindingsWalk ( tree . rootNode , paramBindings ) ;
350353
@@ -476,7 +479,7 @@ function extractConstDeclarators(declNode: TreeSitterNode, definitions: Definiti
476479 if ( declarator ?. type !== 'variable_declarator' ) continue ;
477480 const nameN = declarator . childForFieldName ( 'name' ) ;
478481 const valueN = declarator . childForFieldName ( 'value' ) ;
479- if ( ! nameN || nameN . type !== 'identifier' || ! valueN ) continue ;
482+ if ( nameN ? .type !== 'identifier' || ! valueN ) continue ;
480483 // Skip functions — already captured by query patterns
481484 const valType = valueN . type ;
482485 if ( valType === 'arrow_function' || valType === 'function_expression' || valType === 'function' )
@@ -612,6 +615,8 @@ function extractSymbolsWalk(tree: TreeSitterTree): ExtractorOutput {
612615 ctx . callAssignments ,
613616 ctx . fnRefBindings ,
614617 ) ;
618+ // Prototype-based method definitions: `Foo.prototype.bar = fn` and `Foo.prototype = { bar: fn }`
619+ extractPrototypeMethodsWalk ( tree . rootNode , ctx . definitions , ctx . typeMap ! ) ;
615620 // Phase 8.3c: Extract call-site argument bindings for parameter-flow pts analysis
616621 extractParamBindingsWalk ( tree . rootNode , ctx . paramBindings ! ) ;
617622 // Phase 8.5: collect all `new X()` constructor names for RTA instantiation tracking
@@ -1162,7 +1167,7 @@ function extractSimpleTypeName(typeAnnotationNode: TreeSitterNode): string | nul
11621167}
11631168
11641169function extractNewExprTypeName ( newExprNode : TreeSitterNode ) : string | null {
1165- if ( ! newExprNode || newExprNode . type !== 'new_expression' ) return null ;
1170+ if ( newExprNode ? .type !== 'new_expression' ) return null ;
11661171 const ctor = newExprNode . childForFieldName ( 'constructor' ) || newExprNode . child ( 1 ) ;
11671172 if ( ! ctor ) return null ;
11681173 if ( ctor . type === 'identifier' ) return ctor . text ;
@@ -1275,7 +1280,7 @@ function storeReturnType(
12751280function findReturnNewExprType ( bodyNode : TreeSitterNode ) : string | null {
12761281 for ( let i = 0 ; i < bodyNode . childCount ; i ++ ) {
12771282 const child = bodyNode . child ( i ) ;
1278- if ( ! child || child . type !== 'return_statement' ) continue ;
1283+ if ( child ? .type !== 'return_statement' ) continue ;
12791284 for ( let j = 0 ; j < child . childCount ; j ++ ) {
12801285 const expr = child . child ( j ) ;
12811286 if ( expr ?. type === 'new_expression' ) return extractNewExprTypeName ( expr ) ;
@@ -1442,7 +1447,7 @@ function handleVarDeclaratorTypeMap(
14421447 fnRefBindings ?: FnRefBinding [ ] ,
14431448) : void {
14441449 const nameN = node . childForFieldName ( 'name' ) ;
1445- if ( ! nameN || nameN . type !== 'identifier' ) return ;
1450+ if ( nameN ? .type !== 'identifier' ) return ;
14461451
14471452 const typeAnno = findChild ( node , 'type_annotation' ) ;
14481453 const valueN = node . childForFieldName ( 'value' ) ;
@@ -1545,7 +1550,7 @@ function handleVarDeclaratorTypeMap(
15451550function handleParamTypeMap ( node : TreeSitterNode , typeMap : Map < string , TypeMapEntry > ) : void {
15461551 const nameNode =
15471552 node . childForFieldName ( 'pattern' ) || node . childForFieldName ( 'left' ) || node . child ( 0 ) ;
1548- if ( ! nameNode || nameNode . type !== 'identifier' ) return ;
1553+ if ( nameNode ? .type !== 'identifier' ) return ;
15491554 const typeAnno = findChild ( node , 'type_annotation' ) ;
15501555 if ( typeAnno ) {
15511556 const typeName = extractSimpleTypeName ( typeAnno ) ;
@@ -1938,7 +1943,7 @@ function extractCallbackDefinition(
19381943 fn ?: TreeSitterNode | null ,
19391944) : Definition | null {
19401945 if ( ! fn ) fn = callNode . childForFieldName ( 'function' ) ;
1941- if ( ! fn || fn . type !== 'member_expression' ) return null ;
1946+ if ( fn ? .type !== 'member_expression' ) return null ;
19421947
19431948 const prop = fn . childForFieldName ( 'property' ) ;
19441949 if ( ! prop ) return null ;
@@ -2049,7 +2054,7 @@ function extractDynamicImportNames(callNode: TreeSitterNode): string[] {
20492054 // Skip await_expression wrapper if present
20502055 if ( current && current . type === 'await_expression' ) current = current . parent ;
20512056 // We should now be at a variable_declarator (or not, if standalone import())
2052- if ( ! current || current . type !== 'variable_declarator' ) return [ ] ;
2057+ if ( current ? .type !== 'variable_declarator' ) return [ ] ;
20532058
20542059 const nameNode = current . childForFieldName ( 'name' ) ;
20552060 if ( ! nameNode ) return [ ] ;
@@ -2092,3 +2097,152 @@ function extractDynamicImportNames(callNode: TreeSitterNode): string[] {
20922097
20932098 return [ ] ;
20942099}
2100+
2101+ // ── Phase 8.X: Prototype-based method extraction ────────────────────────────
2102+
2103+ /**
2104+ * Walk the AST and extract prototype-based method definitions and aliases.
2105+ *
2106+ * Handles three patterns:
2107+ * 1. `Foo.prototype.bar = function(){...}` — emits Foo.bar as method definition
2108+ * 2. `Foo.prototype.bar = identifier` — sets typeMap['Foo.bar'] = { type: identifier }
2109+ * 3. `Foo.prototype = { bar: fn, ... }` — emits defs and typeMap entries per property
2110+ *
2111+ * Emitting definitions under the canonical `ClassName.methodName` name lets the
2112+ * existing typeMap-based call resolver find them when a typed receiver dispatches
2113+ * `instance.method()` (lookup.byName('C.foo') in resolveByMethodOrGlobal).
2114+ *
2115+ * typeMap entries for identifier aliases (`Foo.bar → { type: 'someId' }`) are
2116+ * consumed by the prototype-alias fallback added to resolveByMethodOrGlobal.
2117+ */
2118+ function extractPrototypeMethodsWalk (
2119+ rootNode : TreeSitterNode ,
2120+ definitions : Definition [ ] ,
2121+ typeMap : Map < string , TypeMapEntry > ,
2122+ ) : void {
2123+ function walk ( node : TreeSitterNode , depth : number ) : void {
2124+ if ( depth >= MAX_WALK_DEPTH ) return ;
2125+ if ( node . type === 'expression_statement' ) {
2126+ const expr = node . child ( 0 ) ;
2127+ if ( expr ?. type === 'assignment_expression' ) {
2128+ const lhs = expr . childForFieldName ( 'left' ) ;
2129+ const rhs = expr . childForFieldName ( 'right' ) ;
2130+ if ( lhs && rhs ) handlePrototypeAssignment ( lhs , rhs , definitions , typeMap ) ;
2131+ }
2132+ }
2133+ for ( let i = 0 ; i < node . childCount ; i ++ ) {
2134+ walk ( node . child ( i ) ! , depth + 1 ) ;
2135+ }
2136+ }
2137+ walk ( rootNode , 0 ) ;
2138+ }
2139+
2140+ /**
2141+ * Handle an assignment_expression that may be a prototype assignment.
2142+ *
2143+ * Matches:
2144+ * - `Foo.prototype.bar = rhs` (lhs ends in .prototype.bar)
2145+ * - `Foo.prototype = { ... }` (lhs ends in .prototype, rhs is object literal)
2146+ */
2147+ function handlePrototypeAssignment (
2148+ lhs : TreeSitterNode ,
2149+ rhs : TreeSitterNode ,
2150+ definitions : Definition [ ] ,
2151+ typeMap : Map < string , TypeMapEntry > ,
2152+ ) : void {
2153+ if ( lhs . type !== 'member_expression' ) return ;
2154+
2155+ const lhsObj = lhs . childForFieldName ( 'object' ) ;
2156+ const lhsProp = lhs . childForFieldName ( 'property' ) ;
2157+ if ( ! lhsObj || ! lhsProp ) return ;
2158+
2159+ // Pattern 1: `Foo.prototype.bar = rhs`
2160+ // lhs.object is `Foo.prototype` (member_expression), lhs.property is `bar`
2161+ if (
2162+ lhsObj . type === 'member_expression' &&
2163+ ( lhsProp . type === 'property_identifier' || lhsProp . type === 'identifier' )
2164+ ) {
2165+ const protoObj = lhsObj . childForFieldName ( 'object' ) ;
2166+ const protoProp = lhsObj . childForFieldName ( 'property' ) ;
2167+ if (
2168+ protoObj ?. type === 'identifier' &&
2169+ protoProp ?. text === 'prototype' &&
2170+ ! BUILTIN_GLOBALS . has ( protoObj . text )
2171+ ) {
2172+ emitPrototypeMethod ( protoObj . text , lhsProp . text , rhs , definitions , typeMap ) ;
2173+ }
2174+ return ;
2175+ }
2176+
2177+ // Pattern 2: `Foo.prototype = { bar: fn, ... }`
2178+ // lhs.object is `Foo` (identifier), lhs.property is `prototype`
2179+ if (
2180+ lhsObj . type === 'identifier' &&
2181+ lhsProp . text === 'prototype' &&
2182+ ! BUILTIN_GLOBALS . has ( lhsObj . text ) &&
2183+ rhs . type === 'object'
2184+ ) {
2185+ extractPrototypeObjectLiteral ( lhsObj . text , rhs , definitions , typeMap ) ;
2186+ }
2187+ }
2188+
2189+ /** Emit one prototype method definition or typeMap alias for `ClassName.methodName = rhs`. */
2190+ function emitPrototypeMethod (
2191+ className : string ,
2192+ methodName : string ,
2193+ rhs : TreeSitterNode ,
2194+ definitions : Definition [ ] ,
2195+ typeMap : Map < string , TypeMapEntry > ,
2196+ ) : void {
2197+ const fullName = `${ className } .${ methodName } ` ;
2198+ if ( rhs . type === 'function_expression' || rhs . type === 'arrow_function' ) {
2199+ definitions . push ( {
2200+ name : fullName ,
2201+ kind : 'method' ,
2202+ line : nodeStartLine ( rhs ) ,
2203+ endLine : nodeEndLine ( rhs ) ,
2204+ } ) ;
2205+ } else if ( rhs . type === 'identifier' && ! BUILTIN_GLOBALS . has ( rhs . text ) ) {
2206+ // Prototype alias: `A.prototype.t = f` → typeMap['A.t'] = { type: 'f' }
2207+ // Consumed by the prototype-alias fallback in resolveByMethodOrGlobal.
2208+ setTypeMapEntry ( typeMap , fullName , rhs . text , 0.9 ) ;
2209+ }
2210+ }
2211+
2212+ /** Iterate over an object literal assigned to `Foo.prototype` and emit defs/aliases. */
2213+ function extractPrototypeObjectLiteral (
2214+ className : string ,
2215+ objNode : TreeSitterNode ,
2216+ definitions : Definition [ ] ,
2217+ typeMap : Map < string , TypeMapEntry > ,
2218+ ) : void {
2219+ for ( let i = 0 ; i < objNode . childCount ; i ++ ) {
2220+ const child = objNode . child ( i ) ;
2221+ if ( ! child ) continue ;
2222+
2223+ if ( child . type === 'method_definition' ) {
2224+ // Shorthand method: `Foo.prototype = { bar() {} }`
2225+ const nameNode = child . childForFieldName ( 'name' ) ;
2226+ if ( nameNode ) {
2227+ definitions . push ( {
2228+ name : `${ className } .${ nameNode . text } ` ,
2229+ kind : 'method' ,
2230+ line : nodeStartLine ( child ) ,
2231+ endLine : nodeEndLine ( child ) ,
2232+ } ) ;
2233+ }
2234+ continue ;
2235+ }
2236+
2237+ if ( child . type !== 'pair' ) continue ;
2238+
2239+ const keyNode = child . childForFieldName ( 'key' ) ;
2240+ const valueNode = child . childForFieldName ( 'value' ) ;
2241+ if ( ! keyNode || ! valueNode ) continue ;
2242+
2243+ const methodName = keyNode . type === 'string' ? keyNode . text . replace ( / [ ' " ] / g, '' ) : keyNode . text ;
2244+ if ( ! methodName ) continue ;
2245+
2246+ emitPrototypeMethod ( className , methodName , valueNode , definitions , typeMap ) ;
2247+ }
2248+ }
0 commit comments