| 948 | // --- Module-level constant collection --- |
| 949 | |
| 950 | static void handle_string_constants(CBMExtractCtx *ctx, TSNode node, const WalkState *state) { |
| 951 | /* Only collect at module level (not inside functions/classes) */ |
| 952 | if (state->enclosing_func_qn != NULL && state->enclosing_func_qn != ctx->module_qn) { |
| 953 | return; |
| 954 | } |
| 955 | |
| 956 | const char *kind = ts_node_type(node); |
| 957 | |
| 958 | /* Python: expression_statement → assignment → identifier = string */ |
| 959 | /* Go: short_var_declaration, const_spec */ |
| 960 | /* JS/TS: variable_declarator, lexical_declaration */ |
| 961 | if (strcmp(kind, "assignment") != 0 && strcmp(kind, "expression_statement") != 0 && |
| 962 | strcmp(kind, "short_var_declaration") != 0 && strcmp(kind, "const_spec") != 0 && |
| 963 | strcmp(kind, "variable_declarator") != 0) { |
| 964 | return; |
| 965 | } |
| 966 | |
| 967 | /* Find name (left side) and value (right side) */ |
| 968 | TSNode name_node = ts_node_child_by_field_name(node, TS_FIELD("left")); |
| 969 | TSNode value_node = ts_node_child_by_field_name(node, TS_FIELD("right")); |
| 970 | |
| 971 | /* Some grammars use "name" + "value" fields */ |
| 972 | if (ts_node_is_null(name_node)) { |
| 973 | name_node = ts_node_child_by_field_name(node, TS_FIELD("name")); |
| 974 | } |
| 975 | if (ts_node_is_null(value_node)) { |
| 976 | value_node = ts_node_child_by_field_name(node, TS_FIELD("value")); |
| 977 | } |
| 978 | |
| 979 | if (ts_node_is_null(name_node) || ts_node_is_null(value_node)) { |
| 980 | return; |
| 981 | } |
| 982 | |
| 983 | /* Name must be an identifier */ |
| 984 | const char *name_kind = ts_node_type(name_node); |
| 985 | if (strcmp(name_kind, "identifier") != 0 && strcmp(name_kind, "constant") != 0) { |
| 986 | return; |
| 987 | } |
| 988 | |
| 989 | /* Value must be a string literal (template literals flatten to "{}" form) */ |
| 990 | const char *value_kind = ts_node_type(value_node); |
| 991 | const char *flat_value = NULL; |
| 992 | if (strcmp(value_kind, "template_string") == 0) { |
| 993 | flat_value = cbm_template_string_text(ctx->arena, value_node, ctx->source); |
| 994 | if (!flat_value) { |
| 995 | return; |
| 996 | } |
| 997 | } else if (!is_string_node(value_kind)) { |
| 998 | return; |
| 999 | } |
| 1000 | |
| 1001 | char *name = cbm_node_text(ctx->arena, name_node, ctx->source); |
| 1002 | char *value = |
| 1003 | flat_value ? (char *)flat_value : cbm_node_text(ctx->arena, value_node, ctx->source); |
| 1004 | if (!name || !name[0] || !value || !value[0]) { |
| 1005 | return; |
| 1006 | } |
| 1007 |
no test coverage detected