@@ -165,8 +165,6 @@ def get_section_names(node: cst.CSTNode) -> list[str]:
165165class DependencyCollector (cst .CSTVisitor ):
166166 """Collects dependencies between definitions using the visitor pattern with depth tracking."""
167167
168- METADATA_DEPENDENCIES = (cst .metadata .ParentNodeProvider ,)
169-
170168 def __init__ (self , definitions : dict [str , UsageInfo ]) -> None :
171169 super ().__init__ ()
172170 self .definitions = definitions
@@ -179,6 +177,8 @@ def __init__(self, definitions: dict[str, UsageInfo]) -> None:
179177 # Track if we're processing a top-level variable
180178 self .processing_variable = False
181179 self .current_variable_names = set ()
180+ # Track Name nodes that are the .attr part of Attribute nodes (by id)
181+ self .attr_name_ids : set [int ] = set ()
182182
183183 def visit_FunctionDef (self , node : cst .FunctionDef ) -> None :
184184 function_name = node .name .value
@@ -281,6 +281,12 @@ def visit_AnnAssign(self, node: cst.AnnAssign) -> None:
281281 self .processing_variable = False
282282 self .current_variable_names .clear ()
283283
284+ def visit_Attribute (self , node : cst .Attribute ) -> None :
285+ self .attr_name_ids .add (id (node .attr ))
286+
287+ def leave_Attribute (self , original_node : cst .Attribute ) -> None :
288+ self .attr_name_ids .discard (id (original_node .attr ))
289+
284290 def visit_Name (self , node : cst .Name ) -> None :
285291 name = node .value
286292
@@ -296,15 +302,11 @@ def visit_Name(self, node: cst.Name) -> None:
296302 # Skip if this Name is the .attr part of an Attribute (e.g., 'x' in 'self.x')
297303 # We only want to track the base/value of attribute access, not the attribute name itself
298304 if self .class_depth > 0 :
299- parent = self .get_metadata (cst .metadata .ParentNodeProvider , node )
300- if parent is not None and isinstance (parent , cst .Attribute ):
301- # Check if this Name is the .attr (property name), not the .value (base)
302- # If it's the .attr, skip it - attribute names aren't references to definitions
303- if parent .attr is node :
304- return
305- # If it's the .value (base), only skip if it's self/cls
306- if name in ("self" , "cls" ):
307- return
305+ if id (node ) in self .attr_name_ids :
306+ return
307+ # If it's the .value (base), only skip if it's self/cls
308+ if name in ("self" , "cls" ):
309+ return
308310 self .definitions [self .current_top_level_name ].dependencies .add (name )
309311
310312
@@ -409,17 +411,16 @@ def remove_unused_definitions_recursively(
409411
410412
411413def collect_top_level_defs_with_dependencies (code : Union [str , cst .Module ]) -> dict [str , UsageInfo ]:
412- """Collect all top level definitions and their inter-definition dependencies (expensive CST traversal) .
414+ """Collect all top level definitions and their inter-definition dependencies via CST traversal.
413415
414416 Returns a definitions dict with dependencies populated but no usage marks set.
415417 This result can be reused across multiple mark_defs_for_functions calls to avoid
416- repeating the expensive MetadataWrapper + DependencyCollector traversal.
418+ repeating the DependencyCollector traversal.
417419 """
418420 module = code if isinstance (code , cst .Module ) else cst .parse_module (code )
419421 definitions = collect_top_level_definitions (module )
420- wrapper = cst .MetadataWrapper (module )
421422 dependency_collector = DependencyCollector (definitions )
422- wrapper .visit (dependency_collector )
423+ module .visit (dependency_collector )
423424 return definitions
424425
425426
0 commit comments