| 2492 | } |
| 2493 | |
| 2494 | static const char **extract_base_classes(CBMArena *a, TSNode node, const char *source, |
| 2495 | CBMLanguage lang) { |
| 2496 | // ObjectScript: `Class X Extends (A, B)` — bases are class_name children of |
| 2497 | // the class_extends node. |
| 2498 | if (lang == CBM_LANG_OBJECTSCRIPT_UDL) { |
| 2499 | TSNode ext = cbm_find_child_by_kind(node, "class_extends"); |
| 2500 | if (!ts_node_is_null(ext)) { |
| 2501 | const char *bases[MAX_BASES]; |
| 2502 | int base_count = 0; |
| 2503 | uint32_t nc = ts_node_named_child_count(ext); |
| 2504 | for (uint32_t i = 0; i < nc && base_count < MAX_BASES_MINUS_1; i++) { |
| 2505 | TSNode ch = ts_node_named_child(ext, i); |
| 2506 | if (strcmp(ts_node_type(ch), "class_name") == 0) { |
| 2507 | char *base = cbm_node_text(a, ch, source); |
| 2508 | if (base && base[0]) { |
| 2509 | bases[base_count++] = base; |
| 2510 | } |
| 2511 | } |
| 2512 | } |
| 2513 | if (base_count > 0) { |
| 2514 | const char **result = |
| 2515 | (const char **)cbm_arena_alloc(a, (base_count + 1) * sizeof(const char *)); |
| 2516 | if (result) { |
| 2517 | for (int i = 0; i < base_count; i++) { |
| 2518 | result[i] = bases[i]; |
| 2519 | } |
| 2520 | result[base_count] = NULL; |
| 2521 | return result; |
| 2522 | } |
| 2523 | } |
| 2524 | } |
| 2525 | return NULL; |
| 2526 | } |
| 2527 | // Languages whose heritage is not exposed via a tree-sitter field need |
| 2528 | // dedicated walkers; the generic field/keyword path mis-captures them. |
| 2529 | if (lang == CBM_LANG_TYPESCRIPT || lang == CBM_LANG_TSX) { |
| 2530 | const char **ts_result = extract_ts_bases(a, node, source); |
| 2531 | if (ts_result) { |
| 2532 | return ts_result; |
| 2533 | } |
| 2534 | } |
| 2535 | if (lang == CBM_LANG_PHP) { |
| 2536 | const char **php_result = extract_php_bases(a, node, source); |
| 2537 | if (php_result) { |
| 2538 | return php_result; |
| 2539 | } |
| 2540 | } |
| 2541 | if (lang == CBM_LANG_KOTLIN) { |
| 2542 | const char **kt_result = extract_kotlin_bases(a, node, source); |
| 2543 | if (kt_result) { |
| 2544 | return kt_result; |
| 2545 | } |
| 2546 | } |
| 2547 | // Squirrel: `class Dog extends Animal` — heritage is an `identifier` child |
| 2548 | // directly following the `extends` keyword (no field). Grab it. |
| 2549 | if (lang == CBM_LANG_SQUIRREL) { |
| 2550 | uint32_t sc = ts_node_child_count(node); |
| 2551 | bool seen_extends = false; |
no test coverage detected