| 3604 | } |
| 3605 | |
| 3606 | static void process_class_for_fields(PHPLSPContext *ctx, CBMTypeRegistry *reg, TSNode class_node, |
| 3607 | php_class_field_table_t *tab) { |
| 3608 | const char *cqn = class_qn_for_node(ctx, class_node); |
| 3609 | if (!cqn) |
| 3610 | return; |
| 3611 | php_class_fields_t *f = fields_for(tab, cqn, ctx->arena); |
| 3612 | if (!f) |
| 3613 | return; |
| 3614 | |
| 3615 | /* PHPDoc on the class itself: @property + @method tags. */ |
| 3616 | char *class_doc = fetch_leading_phpdoc(ctx, class_node); |
| 3617 | if (class_doc) { |
| 3618 | apply_phpdoc_class_tags(ctx, reg, cqn, class_doc, f); |
| 3619 | } |
| 3620 | |
| 3621 | /* Belt-and-suspenders: walk the class declaration's `base_clause` and |
| 3622 | * `class_interface_clause` to gather extends/implements QNs and append |
| 3623 | * them to the registered type's embedded_types. The unified extractor |
| 3624 | * is supposed to populate base_classes for class_declaration but |
| 3625 | * may miss interface_declaration's `extends Base` chain or use a |
| 3626 | * raw form (with backslashes) that doesn't round-trip cleanly. |
| 3627 | * Doing this AST extraction here keeps the LSP self-sufficient. */ |
| 3628 | { |
| 3629 | const char *parent_qns[16]; |
| 3630 | int parent_count = 0; |
| 3631 | uint32_t cnc = ts_node_child_count(class_node); |
| 3632 | for (uint32_t i = 0; i < cnc && parent_count < 16; i++) { |
| 3633 | TSNode c = ts_node_child(class_node, i); |
| 3634 | if (ts_node_is_null(c) || !ts_node_is_named(c)) |
| 3635 | continue; |
| 3636 | const char *k = ts_node_type(c); |
| 3637 | if (strcmp(k, "base_clause") != 0 && strcmp(k, "class_interface_clause") != 0) { |
| 3638 | continue; |
| 3639 | } |
| 3640 | uint32_t bnc = ts_node_child_count(c); |
| 3641 | for (uint32_t j = 0; j < bnc && parent_count < 16; j++) { |
| 3642 | TSNode bc = ts_node_child(c, j); |
| 3643 | if (ts_node_is_null(bc) || !ts_node_is_named(bc)) |
| 3644 | continue; |
| 3645 | const char *bk = ts_node_type(bc); |
| 3646 | if (strcmp(bk, "name") != 0 && strcmp(bk, "qualified_name") != 0) |
| 3647 | continue; |
| 3648 | char *t = php_node_text(ctx, bc); |
| 3649 | if (!t) |
| 3650 | continue; |
| 3651 | const char *resolved = php_resolve_class_name(ctx, t); |
| 3652 | if (!resolved) |
| 3653 | continue; |
| 3654 | parent_qns[parent_count++] = resolved; |
| 3655 | } |
| 3656 | } |
| 3657 | if (parent_count > 0) { |
| 3658 | for (int t = 0; t < reg->type_count; t++) { |
| 3659 | if (strcmp(reg->types[t].qualified_name, cqn) != 0) |
| 3660 | continue; |
| 3661 | /* Merge with existing embedded_types — dedup. */ |
| 3662 | int existing = 0; |
| 3663 | if (reg->types[t].embedded_types) { |
no test coverage detected