@@ -42,6 +42,7 @@ function walkSwiftNode(node: TreeSitterNode, ctx: ExtractorOutput): void {
4242 handleSwiftCallExpression ( node , ctx ) ;
4343 break ;
4444 case 'property_declaration' :
45+ seedSwiftPropertyTypeMap ( node , ctx ) ;
4546 handleSwiftPropertyDecl ( node , ctx ) ;
4647 break ;
4748 }
@@ -250,11 +251,26 @@ function handleSwiftCallExpression(node: TreeSitterNode, ctx: ExtractorOutput):
250251 if ( ! funcNode ) return ;
251252 const call : Call = { name : '' , line : node . startPosition . row + 1 } ;
252253 if ( funcNode . type === 'navigation_expression' ) {
253- // obj.method(...)
254+ // obj.method(...) — Swift's tree-sitter grammar wraps the suffix in a
255+ // `navigation_suffix` node: navigation_expression > [simple_identifier, navigation_suffix].
256+ // We must descend into navigation_suffix to get the bare method name (e.g. "save" not ".save").
257+ // Mirrors Rust match_swift_node which also descends into navigation_suffix via find_child
258+ // to extract the inner simple_identifier, with a trim_start_matches('.') fallback.
254259 const lastChild = funcNode . child ( funcNode . childCount - 1 ) ;
255260 const firstChild = funcNode . child ( 0 ) ;
256- if ( lastChild && lastChild . type === 'simple_identifier' && firstChild ) {
257- call . name = lastChild . text ;
261+ if ( lastChild && firstChild ) {
262+ // Resolve the method name: descend into navigation_suffix to find the
263+ // simple_identifier, or fall back to stripping the leading dot from the text.
264+ let methodName : string ;
265+ if ( lastChild . type === 'simple_identifier' ) {
266+ methodName = lastChild . text ;
267+ } else if ( lastChild . type === 'navigation_suffix' ) {
268+ const inner = findChild ( lastChild , 'simple_identifier' ) ;
269+ methodName = inner ? inner . text : lastChild . text . replace ( / ^ \. / , '' ) ;
270+ } else {
271+ methodName = lastChild . text ;
272+ }
273+ call . name = methodName ;
258274 call . receiver = firstChild . text ;
259275 }
260276 } else if ( funcNode . type === 'simple_identifier' ) {
@@ -265,6 +281,33 @@ function handleSwiftCallExpression(node: TreeSitterNode, ctx: ExtractorOutput):
265281 if ( call . name ) ctx . calls . push ( call ) ;
266282}
267283
284+ /**
285+ * Seed the typeMap for a property_declaration with a type annotation.
286+ * This runs for ALL property_declaration nodes (including class-body ones)
287+ * so that `repo.method()` calls can be resolved to the correct class.
288+ * Mirrors Rust match_swift_type_map which walks all nodes unconditionally.
289+ */
290+ function seedSwiftPropertyTypeMap ( node : TreeSitterNode , ctx : ExtractorOutput ) : void {
291+ const typeAnn = findChild ( node , 'type_annotation' ) ;
292+ if ( ! typeAnn ) return ;
293+ // type_annotation: ":" <user_type | simple_identifier | ...>
294+ // The last child is the actual type node.
295+ const lastChild = typeAnn . child ( typeAnn . childCount - 1 ) ;
296+ if ( ! lastChild ) return ;
297+ // For "user_type > type_identifier", grab the inner identifier text;
298+ // for a plain simple_identifier, use it directly.
299+ const typeNode =
300+ lastChild . type === 'user_type' ? findChild ( lastChild , 'type_identifier' ) : lastChild ;
301+ if ( ! typeNode ) return ;
302+ const typeName = typeNode . text ;
303+ if ( ! typeName ) return ;
304+ const pattern = findChild ( node , 'pattern' ) ;
305+ if ( ! pattern ) return ;
306+ const varName = findChild ( pattern , 'simple_identifier' ) ?. text ?? pattern . text ;
307+ if ( ! varName ) return ;
308+ ctx . typeMap . set ( varName , { type : typeName , confidence : 0.9 } ) ;
309+ }
310+
268311function handleSwiftPropertyDecl ( node : TreeSitterNode , ctx : ExtractorOutput ) : void {
269312 // Only handle top-level properties (class properties are handled inline)
270313 if (
0 commit comments