@@ -1741,23 +1741,41 @@ function extractTypeMapWalk(
17411741 callAssignments ?: CallAssignment [ ] ,
17421742 fnRefBindings ?: FnRefBinding [ ] ,
17431743) : void {
1744- function walk ( node : TreeSitterNode , depth : number ) : void {
1744+ function walk ( node : TreeSitterNode , depth : number , currentClass : string | null ) : void {
17451745 if ( depth >= MAX_WALK_DEPTH ) return ;
17461746 const t = node . type ;
1747+ if ( t === 'class_declaration' || t === 'abstract_class_declaration' ) {
1748+ const nameNode = node . childForFieldName ( 'name' ) ;
1749+ const className = nameNode ?. text ?? null ;
1750+ for ( let i = 0 ; i < node . childCount ; i ++ ) {
1751+ walk ( node . child ( i ) ! , depth + 1 , className ) ;
1752+ }
1753+ return ;
1754+ }
1755+ // Class expressions (e.g. `const Foo = class Bar { ... }`): the expression-internal
1756+ // name (`Bar`) is never visible to the resolver, which derives callerClass from the
1757+ // binding name (`Foo`). Walking with null preserves the pre-fix `this.prop` fallback
1758+ // so the second lookup in resolveByMethodOrGlobal still finds the entry.
1759+ if ( t === 'class' ) {
1760+ for ( let i = 0 ; i < node . childCount ; i ++ ) {
1761+ walk ( node . child ( i ) ! , depth + 1 , null ) ;
1762+ }
1763+ return ;
1764+ }
17471765 if ( t === 'variable_declarator' ) {
17481766 handleVarDeclaratorTypeMap ( node , typeMap , returnTypeMap , callAssignments , fnRefBindings ) ;
17491767 } else if ( t === 'required_parameter' || t === 'optional_parameter' ) {
17501768 handleParamTypeMap ( node , typeMap ) ;
17511769 } else if ( t === 'assignment_expression' ) {
1752- handlePropWriteTypeMap ( node , typeMap ) ;
1770+ handlePropWriteTypeMap ( node , typeMap , currentClass ) ;
17531771 } else if ( t === 'call_expression' ) {
17541772 handleDefinePropertyTypeMap ( node , typeMap ) ;
17551773 }
17561774 for ( let i = 0 ; i < node . childCount ; i ++ ) {
1757- walk ( node . child ( i ) ! , depth + 1 ) ;
1775+ walk ( node . child ( i ) ! , depth + 1 , currentClass ) ;
17581776 }
17591777 }
1760- walk ( rootNode , 0 ) ;
1778+ walk ( rootNode , 0 , null ) ;
17611779}
17621780
17631781/** Extract type info from a variable_declarator: type annotation, constructor, or factory. */
@@ -1957,12 +1975,17 @@ function handleParamTypeMap(node: TreeSitterNode, typeMap: Map<string, TypeMapEn
19571975 * Phase 8.3d: seed the pts map from object property writes.
19581976 *
19591977 * `handlers.auth = authMiddleware` → typeMap.set('handlers.auth', { type: 'authMiddleware', confidence: 0.85 })
1960- * `this.logger = new Logger(...)` → typeMap.set('this.logger', { type: 'Logger', confidence: 1.0 })
1978+ * `this.logger = new Logger(...)` → typeMap.set('UserService.logger', { type: 'Logger', confidence: 1.0 })
1979+ * (keyed as ClassName.prop when currentClass is known, to avoid collisions across classes)
19611980 *
19621981 * Only simple `obj.prop = identifier` and `this.prop = new Ctor()` writes are tracked
19631982 * (not chained `a.b.c = x`). BUILTIN_GLOBALS are skipped (e.g. `console.log = fn`).
19641983 */
1965- function handlePropWriteTypeMap ( node : TreeSitterNode , typeMap : Map < string , TypeMapEntry > ) : void {
1984+ function handlePropWriteTypeMap (
1985+ node : TreeSitterNode ,
1986+ typeMap : Map < string , TypeMapEntry > ,
1987+ currentClass : string | null ,
1988+ ) : void {
19661989 const lhsN = node . childForFieldName ( 'left' ) ;
19671990 const rhsN = node . childForFieldName ( 'right' ) ;
19681991 if ( ! lhsN || ! rhsN ) return ;
@@ -1975,10 +1998,15 @@ function handlePropWriteTypeMap(node: TreeSitterNode, typeMap: Map<string, TypeM
19751998 // computed subscript expressions — consistent with the adjacent fnRefBindings block.
19761999 if ( prop . type !== 'property_identifier' && prop . type !== 'identifier' ) return ;
19772000
1978- // this.prop = new ClassName(...) — constructor-assigned property type
2001+ // this.prop = new ClassName(...) — constructor-assigned property type.
2002+ // Key as ClassName.prop (class-scoped) so two classes with identically-named
2003+ // properties don't overwrite each other's typeMap entry.
19792004 if ( obj . type === 'this' && rhsN . type === 'new_expression' ) {
19802005 const ctorType = extractNewExprTypeName ( rhsN ) ;
1981- if ( ctorType ) setTypeMapEntry ( typeMap , `this.${ prop . text } ` , ctorType , 1.0 ) ;
2006+ if ( ctorType ) {
2007+ const key = currentClass ? `${ currentClass } .${ prop . text } ` : `this.${ prop . text } ` ;
2008+ setTypeMapEntry ( typeMap , key , ctorType , 1.0 ) ;
2009+ }
19822010 return ;
19832011 }
19842012
@@ -2255,6 +2283,29 @@ function extractSpreadForOfWalk(
22552283 funcStack . push ( nameNode . text ) ;
22562284 pushedFunc = true ;
22572285 }
2286+ } else if ( node . type === 'assignment_expression' ) {
2287+ // `obj.method = function() { ... }` — func-prop assignment.
2288+ // Mirror handleFuncPropAssignment's logic so for-of loops inside the
2289+ // body get the correct enclosingFunc (e.g. 'obj.method') instead of
2290+ // '<module>' or the wrong outer function name.
2291+ const lhs = node . childForFieldName ( 'left' ) ;
2292+ const rhs = node . childForFieldName ( 'right' ) ;
2293+ if (
2294+ lhs ?. type === 'member_expression' &&
2295+ ( rhs ?. type === 'function_expression' || rhs ?. type === 'arrow_function' )
2296+ ) {
2297+ const obj = lhs . childForFieldName ( 'object' ) ;
2298+ const prop = lhs . childForFieldName ( 'property' ) ;
2299+ if (
2300+ obj ?. type === 'identifier' &&
2301+ ( prop ?. type === 'property_identifier' || prop ?. type === 'identifier' ) &&
2302+ ! BUILTIN_GLOBALS . has ( obj . text ) &&
2303+ prop . text !== 'prototype'
2304+ ) {
2305+ funcStack . push ( `${ obj . text } .${ prop . text } ` ) ;
2306+ pushedFunc = true ;
2307+ }
2308+ }
22582309 }
22592310
22602311 if ( node . type === 'call_expression' ) {
0 commit comments