@@ -2753,6 +2753,75 @@ def _js_scan_member_assignments(
27532753 function_bodies .append ((m_nid , m_body ))
27542754
27552755
2756+ def _js_find_exported_objects (program_node , source : bytes ) -> set [str ]:
2757+ """Find names of identifiers exported from a JS module (CJS or ESM)."""
2758+ if program_node is None or program_node .type != "program" :
2759+ return set ()
2760+ exported = set ()
2761+ for child in program_node .children :
2762+ if child .type == "export_statement" :
2763+ for c in child .children :
2764+ if c .type == "identifier" :
2765+ exported .add (_read_text (c , source ))
2766+ elif c .type == "export_clause" :
2767+ for sc in c .children :
2768+ if sc .type == "export_specifier" :
2769+ name_node = sc .child_by_field_name ("name" )
2770+ if name_node :
2771+ exported .add (_read_text (name_node , source ))
2772+ elif c .type in ("variable_declaration" , "lexical_declaration" ):
2773+ for decl in c .children :
2774+ if decl .type == "variable_declarator" :
2775+ name_node = decl .child_by_field_name ("name" )
2776+ if name_node and name_node .type == "identifier" :
2777+ exported .add (_read_text (name_node , source ))
2778+ elif child .type in ("variable_declaration" , "lexical_declaration" ):
2779+ for decl in child .children :
2780+ if decl .type != "variable_declarator" :
2781+ continue
2782+ name_node = decl .child_by_field_name ("name" )
2783+ val_node = decl .child_by_field_name ("value" )
2784+ if name_node and name_node .type == "identifier" and val_node :
2785+ name_str = _read_text (name_node , source )
2786+ curr = val_node
2787+ while curr and curr .type == "assignment_expression" :
2788+ left = curr .child_by_field_name ("left" )
2789+ if left :
2790+ left_str = _read_text (left , source )
2791+ if left_str in ("module.exports" , "exports" ) or left_str .startswith (("module.exports." , "exports." )):
2792+ exported .add (name_str )
2793+ break
2794+ curr = curr .child_by_field_name ("right" )
2795+ if curr :
2796+ val_str = _read_text (curr , source )
2797+ if val_str in ("module.exports" , "exports" ):
2798+ exported .add (name_str )
2799+ elif child .type == "expression_statement" :
2800+ assign = next ((c for c in child .children if c .type == "assignment_expression" ), None )
2801+ if assign :
2802+ left = assign .child_by_field_name ("left" )
2803+ right = assign .child_by_field_name ("right" )
2804+ if left :
2805+ left_str = _read_text (left , source )
2806+ if left_str in ("module.exports" , "exports" ) or left_str .startswith (("module.exports." , "exports." )):
2807+ curr = right
2808+ while curr and curr .type == "assignment_expression" :
2809+ l = curr .child_by_field_name ("left" )
2810+ if l and l .type == "identifier" :
2811+ exported .add (_read_text (l , source ))
2812+ curr = curr .child_by_field_name ("right" )
2813+ if curr and curr .type == "identifier" :
2814+ exported .add (_read_text (curr , source ))
2815+ elif curr and curr .type == "object" :
2816+ for prop in curr .children :
2817+ if prop .type == "shorthand_property_identifier" :
2818+ exported .add (_read_text (prop , source ))
2819+ elif prop .type == "pair" :
2820+ v = prop .child_by_field_name ("value" )
2821+ if v and v .type == "identifier" :
2822+ exported .add (_read_text (v , source ))
2823+ return exported
2824+
27562825def _js_extra_walk (node , source : bytes , file_nid : str , stem : str , str_path : str ,
27572826 nodes : list , edges : list , seen_ids : set , function_bodies : list ,
27582827 parent_class_nid : str | None , add_node_fn , add_edge_fn ,
@@ -2828,6 +2897,20 @@ def _js_extra_walk(node, source: bytes, file_nid: str, stem: str, str_path: str,
28282897 add_node_fn (nid , f".{ member_name } ()" , line )
28292898 add_edge_fn (owner_nid , nid , "method" , line )
28302899 handled = True
2900+ elif kind == "object" :
2901+ is_exported = False
2902+ if node .parent is not None and node .parent .type == "program" :
2903+ exported_names = _js_find_exported_objects (node .parent , source )
2904+ is_exported = owner_name in exported_names
2905+ if is_exported :
2906+ owner_nid = _make_id (stem , owner_name )
2907+ nid = _make_id (owner_nid , member_name )
2908+ if owner_nid not in seen_ids :
2909+ add_node_fn (owner_nid , owner_name , line )
2910+ add_edge_fn (file_nid , owner_nid , "contains" , line )
2911+ add_node_fn (nid , f".{ member_name } ()" , line )
2912+ add_edge_fn (owner_nid , nid , "method" , line )
2913+ handled = True
28312914 if handled :
28322915 if callable_def_nids is not None :
28332916 callable_def_nids .add (nid ) # CJS/prototype fn is callable
0 commit comments