@@ -58,10 +58,25 @@ static const char *strip_quotes(CBMArena *a, const char *text) {
5858 return text ;
5959}
6060
61+ // Callee suffixes for IRIS Python interop string-dispatch. Kept at file scope
62+ // (not inside the function) to satisfy cppcheck variableScope.
63+ static const char * s_py_dispatch_suffixes [] = {".classMethodValue" , ".classMethodVoid" ,
64+ ".classMethodBoolean" , ".classMethodObject" , NULL };
65+
66+ // Per-language callee-suffix dispatch table — returns a NULL-terminated list of
67+ // method-name suffixes whose calls should be resolved by extracting class+method
68+ // from the first two string arguments (e.g. IRIS Python interop). Kept here
69+ // rather than in CBMLangSpec to avoid -Wmissing-field-initializers across 155
70+ // language rows.
71+ const char * * cbm_string_dispatch_suffixes (CBMLanguage lang ) {
72+ if (lang == CBM_LANG_PYTHON ) {
73+ return s_py_dispatch_suffixes ;
74+ }
75+ return NULL ;
76+ }
77+
6178// Forward declarations
62- static void walk_calls (CBMExtractCtx * ctx , TSNode root , const CBMLangSpec * spec );
6379static char * extract_callee_name (CBMArena * a , TSNode node , const char * source , CBMLanguage lang );
64- static void extract_jsx_refs (CBMExtractCtx * ctx , TSNode node );
6580static char * gotemplate_callee (CBMArena * a , TSNode node , const char * source );
6681
6782// Lean 4: check if an apply node is inside a type annotation.
@@ -735,20 +750,6 @@ static const char *strip_and_validate_string_arg(CBMArena *a, char *text) {
735750 return text ;
736751}
737752
738- // Extract first string argument from a call's arguments node.
739- static const char * extract_first_string_arg (CBMExtractCtx * ctx , TSNode args ) {
740- uint32_t nc = ts_node_named_child_count (args );
741- for (uint32_t ai = 0 ; ai < nc && ai < MAX_POSITIONAL_SCAN ; ai ++ ) {
742- TSNode arg = ts_node_named_child (args , ai );
743- const char * ak = ts_node_type (arg );
744- if (is_string_like (ak )) {
745- char * text = cbm_node_text (ctx -> arena , arg , ctx -> source );
746- return strip_and_validate_string_arg (ctx -> arena , text );
747- }
748- }
749- return NULL ;
750- }
751-
752753// Return the (dequoted) first string-literal child of a node, or NULL.
753754static char * gotemplate_string_child (CBMArena * a , TSNode parent , const char * source ) {
754755 TSNode s = cbm_find_child_by_kind (parent , "interpreted_string_literal" );
@@ -791,72 +792,21 @@ static char *gotemplate_callee(CBMArena *a, TSNode node, const char *source) {
791792 return NULL ;
792793}
793794
794- // Walk AST for call nodes (iterative)
795- static void walk_calls (CBMExtractCtx * ctx , TSNode root , const CBMLangSpec * spec ) {
796- TSNodeStack stack ;
797- ts_nstack_init (& stack , ctx -> arena , CBM_SZ_512 );
798- ts_nstack_push (& stack , ctx -> arena , root );
799-
800- while (stack .count > 0 ) {
801- TSNode node = ts_nstack_pop (& stack );
802- const char * kind = ts_node_type (node );
803-
804- if (cbm_kind_in_set (node , spec -> call_node_types )) {
805- char * callee = extract_callee_name (ctx -> arena , node , ctx -> source , ctx -> language );
806- if (callee && callee [0 ] && !cbm_is_keyword (callee , ctx -> language )) {
807- CBMCall call = {0 };
808- call .callee_name = callee ;
809- call .enclosing_func_qn = cbm_enclosing_func_qn_cached (ctx , node );
810-
811- TSNode args = ts_node_child_by_field_name (node , TS_FIELD ("arguments" ));
812- if (!ts_node_is_null (args )) {
813- call .first_string_arg = extract_first_string_arg (ctx , args );
814- }
815- cbm_calls_push (& ctx -> result -> calls , ctx -> arena , call );
816- }
817- }
818-
819- if (ctx -> language == CBM_LANG_TSX || ctx -> language == CBM_LANG_JAVASCRIPT ) {
820- if (strcmp (kind , "jsx_self_closing_element" ) == 0 ||
821- strcmp (kind , "jsx_opening_element" ) == 0 ) {
822- extract_jsx_refs (ctx , node );
795+ static const char * extract_nth_string_arg (CBMExtractCtx * ctx , TSNode args , uint32_t n ) {
796+ uint32_t nc = ts_node_named_child_count (args );
797+ uint32_t found = 0 ;
798+ for (uint32_t ai = 0 ; ai < nc && ai < MAX_POSITIONAL_SCAN + n ; ai ++ ) {
799+ TSNode arg = ts_node_named_child (args , ai );
800+ const char * ak = ts_node_type (arg );
801+ if (is_string_like (ak )) {
802+ if (found == n ) {
803+ char * text = cbm_node_text (ctx -> arena , arg , ctx -> source );
804+ return strip_and_validate_string_arg (ctx -> arena , text );
823805 }
806+ found ++ ;
824807 }
825-
826- ts_nstack_push_children (& stack , ctx -> arena , node );
827808 }
828- }
829-
830- // Extract JSX component references (uppercase = component, lowercase = HTML)
831- static void extract_jsx_refs (CBMExtractCtx * ctx , TSNode node ) {
832- TSNode name_node = ts_node_child_by_field_name (node , TS_FIELD ("name" ));
833- if (ts_node_is_null (name_node )) {
834- return ;
835- }
836-
837- char * name = cbm_node_text (ctx -> arena , name_node , ctx -> source );
838- if (!name || !name [0 ]) {
839- return ;
840- }
841-
842- // Only uppercase names are components
843- if (name [0 ] < 'A' || name [0 ] > 'Z' ) {
844- return ;
845- }
846-
847- CBMCall call = {0 };
848- call .callee_name = name ;
849- call .enclosing_func_qn = cbm_enclosing_func_qn_cached (ctx , node );
850- cbm_calls_push (& ctx -> result -> calls , ctx -> arena , call );
851- }
852-
853- void cbm_extract_calls (CBMExtractCtx * ctx ) {
854- const CBMLangSpec * spec = cbm_lang_spec (ctx -> language );
855- if (!spec || !spec -> call_node_types || !spec -> call_node_types [0 ]) {
856- return ;
857- }
858-
859- walk_calls (ctx , ctx -> root , spec );
809+ return NULL ;
860810}
861811
862812// --- Unified handler: called once per node by the cursor walk ---
@@ -1204,6 +1154,26 @@ void handle_calls(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec *spec, Walk
12041154 }
12051155
12061156 cbm_calls_push (& ctx -> result -> calls , ctx -> arena , call );
1157+
1158+ const char * * dispatch_suffixes = cbm_string_dispatch_suffixes (ctx -> language );
1159+ if (dispatch_suffixes && !ts_node_is_null (args )) {
1160+ const char * cn = call .callee_name ;
1161+ size_t len = cn ? strlen (cn ) : 0 ;
1162+ for (const char * * nm = dispatch_suffixes ; * nm ; nm ++ ) {
1163+ size_t nlen = strlen (* nm );
1164+ if (len >= nlen && strcmp (cn + len - nlen , * nm ) == 0 ) {
1165+ const char * cls = extract_nth_string_arg (ctx , args , 0 );
1166+ const char * mth = extract_nth_string_arg (ctx , args , 1 );
1167+ if (cls && mth ) {
1168+ CBMCall xcall = {0 };
1169+ xcall .callee_name = cbm_arena_sprintf (ctx -> arena , "%s.%s" , cls , mth );
1170+ xcall .enclosing_func_qn = call .enclosing_func_qn ;
1171+ cbm_calls_push (& ctx -> result -> calls , ctx -> arena , xcall );
1172+ }
1173+ break ;
1174+ }
1175+ }
1176+ }
12071177 }
12081178 }
12091179
0 commit comments