@@ -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' ) ;
@@ -1531,7 +1536,7 @@ function handleVarDeclaratorTypeMap(
15311536function handleParamTypeMap ( node : TreeSitterNode , typeMap : Map < string , TypeMapEntry > ) : void {
15321537 const nameNode =
15331538 node . childForFieldName ( 'pattern' ) || node . childForFieldName ( 'left' ) || node . child ( 0 ) ;
1534- if ( ! nameNode || nameNode . type !== 'identifier' ) return ;
1539+ if ( nameNode ? .type !== 'identifier' ) return ;
15351540 const typeAnno = findChild ( node , 'type_annotation' ) ;
15361541 if ( typeAnno ) {
15371542 const typeName = extractSimpleTypeName ( typeAnno ) ;
@@ -1924,7 +1929,7 @@ function extractCallbackDefinition(
19241929 fn ?: TreeSitterNode | null ,
19251930) : Definition | null {
19261931 if ( ! fn ) fn = callNode . childForFieldName ( 'function' ) ;
1927- if ( ! fn || fn . type !== 'member_expression' ) return null ;
1932+ if ( fn ? .type !== 'member_expression' ) return null ;
19281933
19291934 const prop = fn . childForFieldName ( 'property' ) ;
19301935 if ( ! prop ) return null ;
@@ -2035,7 +2040,7 @@ function extractDynamicImportNames(callNode: TreeSitterNode): string[] {
20352040 // Skip await_expression wrapper if present
20362041 if ( current && current . type === 'await_expression' ) current = current . parent ;
20372042 // We should now be at a variable_declarator (or not, if standalone import())
2038- if ( ! current || current . type !== 'variable_declarator' ) return [ ] ;
2043+ if ( current ? .type !== 'variable_declarator' ) return [ ] ;
20392044
20402045 const nameNode = current . childForFieldName ( 'name' ) ;
20412046 if ( ! nameNode ) return [ ] ;
@@ -2078,3 +2083,152 @@ function extractDynamicImportNames(callNode: TreeSitterNode): string[] {
20782083
20792084 return [ ] ;
20802085}
2086+
2087+ // ── Phase 8.X: Prototype-based method extraction ────────────────────────────
2088+
2089+ /**
2090+ * Walk the AST and extract prototype-based method definitions and aliases.
2091+ *
2092+ * Handles three patterns:
2093+ * 1. `Foo.prototype.bar = function(){...}` — emits Foo.bar as method definition
2094+ * 2. `Foo.prototype.bar = identifier` — sets typeMap['Foo.bar'] = { type: identifier }
2095+ * 3. `Foo.prototype = { bar: fn, ... }` — emits defs and typeMap entries per property
2096+ *
2097+ * Emitting definitions under the canonical `ClassName.methodName` name lets the
2098+ * existing typeMap-based call resolver find them when a typed receiver dispatches
2099+ * `instance.method()` (lookup.byName('C.foo') in resolveByMethodOrGlobal).
2100+ *
2101+ * typeMap entries for identifier aliases (`Foo.bar → { type: 'someId' }`) are
2102+ * consumed by the prototype-alias fallback added to resolveByMethodOrGlobal.
2103+ */
2104+ function extractPrototypeMethodsWalk (
2105+ rootNode : TreeSitterNode ,
2106+ definitions : Definition [ ] ,
2107+ typeMap : Map < string , TypeMapEntry > ,
2108+ ) : void {
2109+ function walk ( node : TreeSitterNode , depth : number ) : void {
2110+ if ( depth >= MAX_WALK_DEPTH ) return ;
2111+ if ( node . type === 'expression_statement' ) {
2112+ const expr = node . child ( 0 ) ;
2113+ if ( expr ?. type === 'assignment_expression' ) {
2114+ const lhs = expr . childForFieldName ( 'left' ) ;
2115+ const rhs = expr . childForFieldName ( 'right' ) ;
2116+ if ( lhs && rhs ) handlePrototypeAssignment ( lhs , rhs , definitions , typeMap ) ;
2117+ }
2118+ }
2119+ for ( let i = 0 ; i < node . childCount ; i ++ ) {
2120+ walk ( node . child ( i ) ! , depth + 1 ) ;
2121+ }
2122+ }
2123+ walk ( rootNode , 0 ) ;
2124+ }
2125+
2126+ /**
2127+ * Handle an assignment_expression that may be a prototype assignment.
2128+ *
2129+ * Matches:
2130+ * - `Foo.prototype.bar = rhs` (lhs ends in .prototype.bar)
2131+ * - `Foo.prototype = { ... }` (lhs ends in .prototype, rhs is object literal)
2132+ */
2133+ function handlePrototypeAssignment (
2134+ lhs : TreeSitterNode ,
2135+ rhs : TreeSitterNode ,
2136+ definitions : Definition [ ] ,
2137+ typeMap : Map < string , TypeMapEntry > ,
2138+ ) : void {
2139+ if ( lhs . type !== 'member_expression' ) return ;
2140+
2141+ const lhsObj = lhs . childForFieldName ( 'object' ) ;
2142+ const lhsProp = lhs . childForFieldName ( 'property' ) ;
2143+ if ( ! lhsObj || ! lhsProp ) return ;
2144+
2145+ // Pattern 1: `Foo.prototype.bar = rhs`
2146+ // lhs.object is `Foo.prototype` (member_expression), lhs.property is `bar`
2147+ if (
2148+ lhsObj . type === 'member_expression' &&
2149+ ( lhsProp . type === 'property_identifier' || lhsProp . type === 'identifier' )
2150+ ) {
2151+ const protoObj = lhsObj . childForFieldName ( 'object' ) ;
2152+ const protoProp = lhsObj . childForFieldName ( 'property' ) ;
2153+ if (
2154+ protoObj ?. type === 'identifier' &&
2155+ protoProp ?. text === 'prototype' &&
2156+ ! BUILTIN_GLOBALS . has ( protoObj . text )
2157+ ) {
2158+ emitPrototypeMethod ( protoObj . text , lhsProp . text , rhs , definitions , typeMap ) ;
2159+ }
2160+ return ;
2161+ }
2162+
2163+ // Pattern 2: `Foo.prototype = { bar: fn, ... }`
2164+ // lhs.object is `Foo` (identifier), lhs.property is `prototype`
2165+ if (
2166+ lhsObj . type === 'identifier' &&
2167+ lhsProp . text === 'prototype' &&
2168+ ! BUILTIN_GLOBALS . has ( lhsObj . text ) &&
2169+ rhs . type === 'object'
2170+ ) {
2171+ extractPrototypeObjectLiteral ( lhsObj . text , rhs , definitions , typeMap ) ;
2172+ }
2173+ }
2174+
2175+ /** Emit one prototype method definition or typeMap alias for `ClassName.methodName = rhs`. */
2176+ function emitPrototypeMethod (
2177+ className : string ,
2178+ methodName : string ,
2179+ rhs : TreeSitterNode ,
2180+ definitions : Definition [ ] ,
2181+ typeMap : Map < string , TypeMapEntry > ,
2182+ ) : void {
2183+ const fullName = `${ className } .${ methodName } ` ;
2184+ if ( rhs . type === 'function_expression' || rhs . type === 'arrow_function' ) {
2185+ definitions . push ( {
2186+ name : fullName ,
2187+ kind : 'method' ,
2188+ line : nodeStartLine ( rhs ) ,
2189+ endLine : nodeEndLine ( rhs ) ,
2190+ } ) ;
2191+ } else if ( rhs . type === 'identifier' && ! BUILTIN_GLOBALS . has ( rhs . text ) ) {
2192+ // Prototype alias: `A.prototype.t = f` → typeMap['A.t'] = { type: 'f' }
2193+ // Consumed by the prototype-alias fallback in resolveByMethodOrGlobal.
2194+ setTypeMapEntry ( typeMap , fullName , rhs . text , 0.9 ) ;
2195+ }
2196+ }
2197+
2198+ /** Iterate over an object literal assigned to `Foo.prototype` and emit defs/aliases. */
2199+ function extractPrototypeObjectLiteral (
2200+ className : string ,
2201+ objNode : TreeSitterNode ,
2202+ definitions : Definition [ ] ,
2203+ typeMap : Map < string , TypeMapEntry > ,
2204+ ) : void {
2205+ for ( let i = 0 ; i < objNode . childCount ; i ++ ) {
2206+ const child = objNode . child ( i ) ;
2207+ if ( ! child ) continue ;
2208+
2209+ if ( child . type === 'method_definition' ) {
2210+ // Shorthand method: `Foo.prototype = { bar() {} }`
2211+ const nameNode = child . childForFieldName ( 'name' ) ;
2212+ if ( nameNode ) {
2213+ definitions . push ( {
2214+ name : `${ className } .${ nameNode . text } ` ,
2215+ kind : 'method' ,
2216+ line : nodeStartLine ( child ) ,
2217+ endLine : nodeEndLine ( child ) ,
2218+ } ) ;
2219+ }
2220+ continue ;
2221+ }
2222+
2223+ if ( child . type !== 'pair' ) continue ;
2224+
2225+ const keyNode = child . childForFieldName ( 'key' ) ;
2226+ const valueNode = child . childForFieldName ( 'value' ) ;
2227+ if ( ! keyNode || ! valueNode ) continue ;
2228+
2229+ const methodName = keyNode . type === 'string' ? keyNode . text . replace ( / [ ' " ] / g, '' ) : keyNode . text ;
2230+ if ( ! methodName ) continue ;
2231+
2232+ emitPrototypeMethod ( className , methodName , valueNode , definitions , typeMap ) ;
2233+ }
2234+ }
0 commit comments