@@ -2753,6 +2753,69 @@ 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 child .type in ("variable_declaration" , "lexical_declaration" ):
2773+ for decl in child .children :
2774+ if decl .type != "variable_declarator" :
2775+ continue
2776+ name_node = decl .child_by_field_name ("name" )
2777+ val_node = decl .child_by_field_name ("value" )
2778+ if name_node and name_node .type == "identifier" and val_node :
2779+ name_str = _read_text (name_node , source )
2780+ curr = val_node
2781+ while curr and curr .type == "assignment_expression" :
2782+ left = curr .child_by_field_name ("left" )
2783+ if left :
2784+ left_str = _read_text (left , source )
2785+ if left_str in ("module.exports" , "exports" ) or left_str .startswith (("module.exports." , "exports." )):
2786+ exported .add (name_str )
2787+ break
2788+ curr = curr .child_by_field_name ("right" )
2789+ if curr :
2790+ val_str = _read_text (curr , source )
2791+ if val_str in ("module.exports" , "exports" ):
2792+ exported .add (name_str )
2793+ elif child .type == "expression_statement" :
2794+ assign = next ((c for c in child .children if c .type == "assignment_expression" ), None )
2795+ if assign :
2796+ left = assign .child_by_field_name ("left" )
2797+ right = assign .child_by_field_name ("right" )
2798+ if left :
2799+ left_str = _read_text (left , source )
2800+ if left_str in ("module.exports" , "exports" ) or left_str .startswith (("module.exports." , "exports." )):
2801+ curr = right
2802+ while curr and curr .type == "assignment_expression" :
2803+ l = curr .child_by_field_name ("left" )
2804+ if l and l .type == "identifier" :
2805+ exported .add (_read_text (l , source ))
2806+ curr = curr .child_by_field_name ("right" )
2807+ if curr and curr .type == "identifier" :
2808+ exported .add (_read_text (curr , source ))
2809+ elif curr and curr .type == "object" :
2810+ for prop in curr .children :
2811+ if prop .type == "shorthand_property_identifier" :
2812+ exported .add (_read_text (prop , source ))
2813+ elif prop .type == "pair" :
2814+ v = prop .child_by_field_name ("value" )
2815+ if v and v .type == "identifier" :
2816+ exported .add (_read_text (v , source ))
2817+ return exported
2818+
27562819def _js_extra_walk (node , source : bytes , file_nid : str , stem : str , str_path : str ,
27572820 nodes : list , edges : list , seen_ids : set , function_bodies : list ,
27582821 parent_class_nid : str | None , add_node_fn , add_edge_fn ,
@@ -2828,6 +2891,20 @@ def _js_extra_walk(node, source: bytes, file_nid: str, stem: str, str_path: str,
28282891 add_node_fn (nid , f".{ member_name } ()" , line )
28292892 add_edge_fn (owner_nid , nid , "method" , line )
28302893 handled = True
2894+ elif kind == "object" :
2895+ is_exported = False
2896+ if node .parent is not None and node .parent .type == "program" :
2897+ exported_names = _js_find_exported_objects (node .parent , source )
2898+ is_exported = owner_name in exported_names
2899+ if is_exported :
2900+ owner_nid = _make_id (stem , owner_name )
2901+ nid = _make_id (owner_nid , member_name )
2902+ if owner_nid not in seen_ids :
2903+ add_node_fn (owner_nid , owner_name , line )
2904+ add_edge_fn (file_nid , owner_nid , "contains" , line )
2905+ add_node_fn (nid , f".{ member_name } ()" , line )
2906+ add_edge_fn (owner_nid , nid , "method" , line )
2907+ handled = True
28312908 if handled :
28322909 if callable_def_nids is not None :
28332910 callable_def_nids .add (nid ) # CJS/prototype fn is callable
0 commit comments