@@ -770,6 +770,16 @@ def parse_bytes(self, path: Path, source: bytes) -> tuple[list[NodeInfo], list[E
770770 tree .root_node , language , file_path_str , edges , import_map ,
771771 )
772772
773+ # Enrich: detect function/class references passed as call arguments
774+ self ._enrich_func_ref_args (
775+ tree .root_node , language , file_path_str , edges , defined_names ,
776+ )
777+
778+ # Enrich: detect function/class references returned or assigned
779+ self ._enrich_func_ref_returns (
780+ tree .root_node , language , file_path_str , edges , defined_names ,
781+ )
782+
773783 # Resolve bare call targets to qualified names using same-file definitions
774784 edges = self ._resolve_call_targets (nodes , edges , file_path_str , import_map )
775785
@@ -3692,6 +3702,236 @@ def _extract_dunder_all(root) -> Optional[set[str]]:
36923702 return set ()
36933703 return None
36943704
3705+ # ------------------------------------------------------------------
3706+ # Function-reference-as-argument enrichment
3707+ # ------------------------------------------------------------------
3708+
3709+ def _enrich_func_ref_args (
3710+ self ,
3711+ root ,
3712+ language : str ,
3713+ file_path : str ,
3714+ edges : list [EdgeInfo ],
3715+ defined_names : set [str ],
3716+ ) -> None :
3717+ """Detect function/class names passed as arguments to calls."""
3718+ if not defined_names :
3719+ return
3720+ arg_list_types = {
3721+ "argument_list" , "arguments" , "value_arguments" ,
3722+ "actual_parameters" , "template_argument_list" ,
3723+ }
3724+ ident_types = {"identifier" , "simple_identifier" }
3725+ kw_types = {"keyword_argument" , "value_argument" , "named_argument" }
3726+
3727+ self ._walk_func_ref_args (
3728+ root , language , file_path , edges , defined_names ,
3729+ arg_list_types , ident_types , kw_types ,
3730+ enclosing_func = None , enclosing_class = None ,
3731+ )
3732+
3733+ def _walk_func_ref_args (
3734+ self ,
3735+ node ,
3736+ language : str ,
3737+ file_path : str ,
3738+ edges : list [EdgeInfo ],
3739+ defined_names : set [str ],
3740+ arg_list_types : set [str ],
3741+ ident_types : set [str ],
3742+ kw_types : set [str ],
3743+ enclosing_func : Optional [str ],
3744+ enclosing_class : Optional [str ],
3745+ _depth : int = 0 ,
3746+ ) -> None :
3747+ if _depth > 50 :
3748+ return
3749+ func_types = set (_FUNCTION_TYPES .get (language , []))
3750+
3751+ for child in node .children :
3752+ if child .type in func_types :
3753+ fname = self ._get_name (child , language , "function" )
3754+ if fname :
3755+ defined_names .add (fname )
3756+ self ._walk_func_ref_args (
3757+ child , language , file_path , edges , defined_names ,
3758+ arg_list_types , ident_types , kw_types ,
3759+ enclosing_func = fname , enclosing_class = enclosing_class ,
3760+ _depth = _depth + 1 ,
3761+ )
3762+ continue
3763+
3764+ if child .type in arg_list_types :
3765+ for arg in child .children :
3766+ ref_name = None
3767+ line = arg .start_point [0 ] + 1
3768+ if arg .type in ident_types :
3769+ ref_name = arg .text .decode ("utf-8" , errors = "replace" )
3770+ elif arg .type in kw_types :
3771+ for sub in arg .children :
3772+ if sub .type in ident_types :
3773+ ref_name = sub .text .decode ("utf-8" , errors = "replace" )
3774+ elif arg .type == "callable_reference" :
3775+ for sub in arg .children :
3776+ if sub .type in ident_types :
3777+ ref_name = sub .text .decode ("utf-8" , errors = "replace" )
3778+
3779+ if ref_name and ref_name in defined_names :
3780+ source = (
3781+ self ._qualify (enclosing_func , file_path , enclosing_class )
3782+ if enclosing_func else file_path
3783+ )
3784+ edges .append (EdgeInfo (
3785+ kind = "CALLS" ,
3786+ source = source ,
3787+ target = ref_name ,
3788+ file_path = file_path ,
3789+ line = line ,
3790+ ))
3791+ continue
3792+
3793+ if child .type == "jsx_expression" :
3794+ for sub in child .children :
3795+ if sub .type in ident_types :
3796+ ref_name = sub .text .decode ("utf-8" , errors = "replace" )
3797+ if ref_name in defined_names :
3798+ source = (
3799+ self ._qualify (
3800+ enclosing_func , file_path , enclosing_class ,
3801+ )
3802+ if enclosing_func else file_path
3803+ )
3804+ edges .append (EdgeInfo (
3805+ kind = "CALLS" ,
3806+ source = source ,
3807+ target = ref_name ,
3808+ file_path = file_path ,
3809+ line = sub .start_point [0 ] + 1 ,
3810+ ))
3811+ continue
3812+
3813+ self ._walk_func_ref_args (
3814+ child , language , file_path , edges , defined_names ,
3815+ arg_list_types , ident_types , kw_types ,
3816+ enclosing_func = enclosing_func ,
3817+ enclosing_class = enclosing_class ,
3818+ _depth = _depth + 1 ,
3819+ )
3820+
3821+ # ------------------------------------------------------------------
3822+ # Function-reference in return/assignment enrichment
3823+ # ------------------------------------------------------------------
3824+
3825+ def _enrich_func_ref_returns (
3826+ self ,
3827+ root ,
3828+ language : str ,
3829+ file_path : str ,
3830+ edges : list [EdgeInfo ],
3831+ defined_names : set [str ],
3832+ ) -> None :
3833+ """Detect function/class names used as values in return and assignment."""
3834+ if not defined_names :
3835+ return
3836+ ident_types = {"identifier" , "simple_identifier" }
3837+ return_types = {"return_statement" }
3838+ assign_types = {
3839+ "assignment" , "variable_declarator" ,
3840+ "assignment_expression" , "augmented_assignment" ,
3841+ }
3842+ self ._walk_func_ref_returns (
3843+ root , language , file_path , edges , defined_names ,
3844+ ident_types , return_types , assign_types ,
3845+ enclosing_func = None , enclosing_class = None ,
3846+ )
3847+
3848+ def _walk_func_ref_returns (
3849+ self ,
3850+ node ,
3851+ language : str ,
3852+ file_path : str ,
3853+ edges : list [EdgeInfo ],
3854+ defined_names : set [str ],
3855+ ident_types : set [str ],
3856+ return_types : set [str ],
3857+ assign_types : set [str ],
3858+ enclosing_func : Optional [str ],
3859+ enclosing_class : Optional [str ],
3860+ _depth : int = 0 ,
3861+ ) -> None :
3862+ if _depth > 50 :
3863+ return
3864+ func_types = set (_FUNCTION_TYPES .get (language , []))
3865+
3866+ for child in node .children :
3867+ if child .type in func_types :
3868+ fname = self ._get_name (child , language , "function" )
3869+ if fname :
3870+ self ._walk_func_ref_returns (
3871+ child , language , file_path , edges , defined_names ,
3872+ ident_types , return_types , assign_types ,
3873+ enclosing_func = fname , enclosing_class = enclosing_class ,
3874+ _depth = _depth + 1 ,
3875+ )
3876+ continue
3877+
3878+ if child .type in return_types :
3879+ for sub in child .children :
3880+ if sub .type in ident_types :
3881+ ref_name = sub .text .decode ("utf-8" , errors = "replace" )
3882+ if ref_name in defined_names :
3883+ source = (
3884+ self ._qualify (
3885+ enclosing_func , file_path , enclosing_class ,
3886+ )
3887+ if enclosing_func else file_path
3888+ )
3889+ edges .append (EdgeInfo (
3890+ kind = "CALLS" ,
3891+ source = source ,
3892+ target = ref_name ,
3893+ file_path = file_path ,
3894+ line = sub .start_point [0 ] + 1 ,
3895+ ))
3896+ continue
3897+
3898+ if child .type in assign_types :
3899+ last_ident = None
3900+ for sub in child .children :
3901+ if sub .type in ident_types :
3902+ last_ident = sub
3903+ if last_ident :
3904+ ref_name = last_ident .text .decode ("utf-8" , errors = "replace" )
3905+ if ref_name in defined_names :
3906+ first_ident = None
3907+ for sub in child .children :
3908+ if sub .type in ident_types :
3909+ first_ident = sub
3910+ break
3911+ if first_ident and first_ident != last_ident :
3912+ source = (
3913+ self ._qualify (
3914+ enclosing_func , file_path , enclosing_class ,
3915+ )
3916+ if enclosing_func else file_path
3917+ )
3918+ edges .append (EdgeInfo (
3919+ kind = "CALLS" ,
3920+ source = source ,
3921+ target = ref_name ,
3922+ file_path = file_path ,
3923+ line = last_ident .start_point [0 ] + 1 ,
3924+ ))
3925+ continue
3926+
3927+ self ._walk_func_ref_returns (
3928+ child , language , file_path , edges , defined_names ,
3929+ ident_types , return_types , assign_types ,
3930+ enclosing_func = enclosing_func ,
3931+ enclosing_class = enclosing_class ,
3932+ _depth = _depth + 1 ,
3933+ )
3934+
36953935 # ------------------------------------------------------------------
36963936 # Typed-variable call enrichment
36973937 # ------------------------------------------------------------------
0 commit comments