| 473 | // --- Module-level constant collection --- |
| 474 | |
| 475 | static void handle_string_constants(CBMExtractCtx *ctx, TSNode node, const WalkState *state) { |
| 476 | /* Only collect at module level (not inside functions/classes) */ |
| 477 | if (state->enclosing_func_qn != NULL && state->enclosing_func_qn != ctx->module_qn) { |
| 478 | return; |
| 479 | } |
| 480 | |
| 481 | const char *kind = ts_node_type(node); |
| 482 | |
| 483 | /* Python: expression_statement → assignment → identifier = string */ |
| 484 | /* Go: short_var_declaration, const_spec */ |
| 485 | /* JS/TS: variable_declarator, lexical_declaration */ |
| 486 | if (strcmp(kind, "assignment") != 0 && strcmp(kind, "expression_statement") != 0 && |
| 487 | strcmp(kind, "short_var_declaration") != 0 && strcmp(kind, "const_spec") != 0 && |
| 488 | strcmp(kind, "variable_declarator") != 0) { |
| 489 | return; |
| 490 | } |
| 491 | |
| 492 | /* Find name (left side) and value (right side) */ |
| 493 | TSNode name_node = ts_node_child_by_field_name(node, TS_FIELD("left")); |
| 494 | TSNode value_node = ts_node_child_by_field_name(node, TS_FIELD("right")); |
| 495 | |
| 496 | /* Some grammars use "name" + "value" fields */ |
| 497 | if (ts_node_is_null(name_node)) { |
| 498 | name_node = ts_node_child_by_field_name(node, TS_FIELD("name")); |
| 499 | } |
| 500 | if (ts_node_is_null(value_node)) { |
| 501 | value_node = ts_node_child_by_field_name(node, TS_FIELD("value")); |
| 502 | } |
| 503 | |
| 504 | if (ts_node_is_null(name_node) || ts_node_is_null(value_node)) { |
| 505 | return; |
| 506 | } |
| 507 | |
| 508 | /* Name must be an identifier */ |
| 509 | const char *name_kind = ts_node_type(name_node); |
| 510 | if (strcmp(name_kind, "identifier") != 0 && strcmp(name_kind, "constant") != 0) { |
| 511 | return; |
| 512 | } |
| 513 | |
| 514 | /* Value must be a string literal */ |
| 515 | if (!is_string_node(ts_node_type(value_node))) { |
| 516 | return; |
| 517 | } |
| 518 | |
| 519 | char *name = cbm_node_text(ctx->arena, name_node, ctx->source); |
| 520 | char *value = cbm_node_text(ctx->arena, value_node, ctx->source); |
| 521 | if (!name || !name[0] || !value || !value[0]) { |
| 522 | return; |
| 523 | } |
| 524 | |
| 525 | /* Strip quotes from value */ |
| 526 | int vlen = (int)strlen(value); |
| 527 | if (vlen >= CBM_QUOTE_PAIR && (value[0] == '"' || value[0] == '\'')) { |
| 528 | value = cbm_arena_strndup(ctx->arena, value + SKIP_ONE, (size_t)(vlen - PAIR_LEN)); |
| 529 | if (!value) { |
| 530 | return; |
| 531 | } |
| 532 | } |
no test coverage detected