@@ -1023,6 +1023,16 @@ def parse_bytes(self, path: Path, source: bytes) -> tuple[list[NodeInfo], list[E
10231023 tree .root_node , language , file_path_str , edges , import_map ,
10241024 )
10251025
1026+ # Enrich: detect function/class references passed as call arguments
1027+ self ._enrich_func_ref_args (
1028+ tree .root_node , language , file_path_str , edges , defined_names ,
1029+ )
1030+
1031+ # Enrich: detect function/class references returned or assigned
1032+ self ._enrich_func_ref_returns (
1033+ tree .root_node , language , file_path_str , edges , defined_names ,
1034+ )
1035+
10261036 # Resolve bare call targets to qualified names using same-file definitions
10271037 edges = self ._resolve_call_targets (nodes , edges , file_path_str , import_map )
10281038
@@ -5354,6 +5364,236 @@ def _extract_dunder_all(root) -> Optional[set[str]]:
53545364 return set ()
53555365 return None
53565366
5367+ # ------------------------------------------------------------------
5368+ # Function-reference-as-argument enrichment
5369+ # ------------------------------------------------------------------
5370+
5371+ def _enrich_func_ref_args (
5372+ self ,
5373+ root ,
5374+ language : str ,
5375+ file_path : str ,
5376+ edges : list [EdgeInfo ],
5377+ defined_names : set [str ],
5378+ ) -> None :
5379+ """Detect function/class names passed as arguments to calls."""
5380+ if not defined_names :
5381+ return
5382+ arg_list_types = {
5383+ "argument_list" , "arguments" , "value_arguments" ,
5384+ "actual_parameters" , "template_argument_list" ,
5385+ }
5386+ ident_types = {"identifier" , "simple_identifier" }
5387+ kw_types = {"keyword_argument" , "value_argument" , "named_argument" }
5388+
5389+ self ._walk_func_ref_args (
5390+ root , language , file_path , edges , defined_names ,
5391+ arg_list_types , ident_types , kw_types ,
5392+ enclosing_func = None , enclosing_class = None ,
5393+ )
5394+
5395+ def _walk_func_ref_args (
5396+ self ,
5397+ node ,
5398+ language : str ,
5399+ file_path : str ,
5400+ edges : list [EdgeInfo ],
5401+ defined_names : set [str ],
5402+ arg_list_types : set [str ],
5403+ ident_types : set [str ],
5404+ kw_types : set [str ],
5405+ enclosing_func : Optional [str ],
5406+ enclosing_class : Optional [str ],
5407+ _depth : int = 0 ,
5408+ ) -> None :
5409+ if _depth > 50 :
5410+ return
5411+ func_types = set (_FUNCTION_TYPES .get (language , []))
5412+
5413+ for child in node .children :
5414+ if child .type in func_types :
5415+ fname = self ._get_name (child , language , "function" )
5416+ if fname :
5417+ defined_names .add (fname )
5418+ self ._walk_func_ref_args (
5419+ child , language , file_path , edges , defined_names ,
5420+ arg_list_types , ident_types , kw_types ,
5421+ enclosing_func = fname , enclosing_class = enclosing_class ,
5422+ _depth = _depth + 1 ,
5423+ )
5424+ continue
5425+
5426+ if child .type in arg_list_types :
5427+ for arg in child .children :
5428+ ref_name = None
5429+ line = arg .start_point [0 ] + 1
5430+ if arg .type in ident_types :
5431+ ref_name = arg .text .decode ("utf-8" , errors = "replace" )
5432+ elif arg .type in kw_types :
5433+ for sub in arg .children :
5434+ if sub .type in ident_types :
5435+ ref_name = sub .text .decode ("utf-8" , errors = "replace" )
5436+ elif arg .type == "callable_reference" :
5437+ for sub in arg .children :
5438+ if sub .type in ident_types :
5439+ ref_name = sub .text .decode ("utf-8" , errors = "replace" )
5440+
5441+ if ref_name and ref_name in defined_names :
5442+ source = (
5443+ self ._qualify (enclosing_func , file_path , enclosing_class )
5444+ if enclosing_func else file_path
5445+ )
5446+ edges .append (EdgeInfo (
5447+ kind = "CALLS" ,
5448+ source = source ,
5449+ target = ref_name ,
5450+ file_path = file_path ,
5451+ line = line ,
5452+ ))
5453+ continue
5454+
5455+ if child .type == "jsx_expression" :
5456+ for sub in child .children :
5457+ if sub .type in ident_types :
5458+ ref_name = sub .text .decode ("utf-8" , errors = "replace" )
5459+ if ref_name in defined_names :
5460+ source = (
5461+ self ._qualify (
5462+ enclosing_func , file_path , enclosing_class ,
5463+ )
5464+ if enclosing_func else file_path
5465+ )
5466+ edges .append (EdgeInfo (
5467+ kind = "CALLS" ,
5468+ source = source ,
5469+ target = ref_name ,
5470+ file_path = file_path ,
5471+ line = sub .start_point [0 ] + 1 ,
5472+ ))
5473+ continue
5474+
5475+ self ._walk_func_ref_args (
5476+ child , language , file_path , edges , defined_names ,
5477+ arg_list_types , ident_types , kw_types ,
5478+ enclosing_func = enclosing_func ,
5479+ enclosing_class = enclosing_class ,
5480+ _depth = _depth + 1 ,
5481+ )
5482+
5483+ # ------------------------------------------------------------------
5484+ # Function-reference in return/assignment enrichment
5485+ # ------------------------------------------------------------------
5486+
5487+ def _enrich_func_ref_returns (
5488+ self ,
5489+ root ,
5490+ language : str ,
5491+ file_path : str ,
5492+ edges : list [EdgeInfo ],
5493+ defined_names : set [str ],
5494+ ) -> None :
5495+ """Detect function/class names used as values in return and assignment."""
5496+ if not defined_names :
5497+ return
5498+ ident_types = {"identifier" , "simple_identifier" }
5499+ return_types = {"return_statement" }
5500+ assign_types = {
5501+ "assignment" , "variable_declarator" ,
5502+ "assignment_expression" , "augmented_assignment" ,
5503+ }
5504+ self ._walk_func_ref_returns (
5505+ root , language , file_path , edges , defined_names ,
5506+ ident_types , return_types , assign_types ,
5507+ enclosing_func = None , enclosing_class = None ,
5508+ )
5509+
5510+ def _walk_func_ref_returns (
5511+ self ,
5512+ node ,
5513+ language : str ,
5514+ file_path : str ,
5515+ edges : list [EdgeInfo ],
5516+ defined_names : set [str ],
5517+ ident_types : set [str ],
5518+ return_types : set [str ],
5519+ assign_types : set [str ],
5520+ enclosing_func : Optional [str ],
5521+ enclosing_class : Optional [str ],
5522+ _depth : int = 0 ,
5523+ ) -> None :
5524+ if _depth > 50 :
5525+ return
5526+ func_types = set (_FUNCTION_TYPES .get (language , []))
5527+
5528+ for child in node .children :
5529+ if child .type in func_types :
5530+ fname = self ._get_name (child , language , "function" )
5531+ if fname :
5532+ self ._walk_func_ref_returns (
5533+ child , language , file_path , edges , defined_names ,
5534+ ident_types , return_types , assign_types ,
5535+ enclosing_func = fname , enclosing_class = enclosing_class ,
5536+ _depth = _depth + 1 ,
5537+ )
5538+ continue
5539+
5540+ if child .type in return_types :
5541+ for sub in child .children :
5542+ if sub .type in ident_types :
5543+ ref_name = sub .text .decode ("utf-8" , errors = "replace" )
5544+ if ref_name in defined_names :
5545+ source = (
5546+ self ._qualify (
5547+ enclosing_func , file_path , enclosing_class ,
5548+ )
5549+ if enclosing_func else file_path
5550+ )
5551+ edges .append (EdgeInfo (
5552+ kind = "CALLS" ,
5553+ source = source ,
5554+ target = ref_name ,
5555+ file_path = file_path ,
5556+ line = sub .start_point [0 ] + 1 ,
5557+ ))
5558+ continue
5559+
5560+ if child .type in assign_types :
5561+ last_ident = None
5562+ for sub in child .children :
5563+ if sub .type in ident_types :
5564+ last_ident = sub
5565+ if last_ident :
5566+ ref_name = last_ident .text .decode ("utf-8" , errors = "replace" )
5567+ if ref_name in defined_names :
5568+ first_ident = None
5569+ for sub in child .children :
5570+ if sub .type in ident_types :
5571+ first_ident = sub
5572+ break
5573+ if first_ident and first_ident != last_ident :
5574+ source = (
5575+ self ._qualify (
5576+ enclosing_func , file_path , enclosing_class ,
5577+ )
5578+ if enclosing_func else file_path
5579+ )
5580+ edges .append (EdgeInfo (
5581+ kind = "CALLS" ,
5582+ source = source ,
5583+ target = ref_name ,
5584+ file_path = file_path ,
5585+ line = last_ident .start_point [0 ] + 1 ,
5586+ ))
5587+ continue
5588+
5589+ self ._walk_func_ref_returns (
5590+ child , language , file_path , edges , defined_names ,
5591+ ident_types , return_types , assign_types ,
5592+ enclosing_func = enclosing_func ,
5593+ enclosing_class = enclosing_class ,
5594+ _depth = _depth + 1 ,
5595+ )
5596+
53575597 # ------------------------------------------------------------------
53585598 # Typed-variable call enrichment
53595599 # ------------------------------------------------------------------
0 commit comments