| 3844 | } |
| 3845 | |
| 3846 | static void process_class_for_fields(PHPLSPContext *ctx, CBMTypeRegistry *reg, TSNode class_node, |
| 3847 | php_class_field_table_t *tab) { |
| 3848 | const char *cqn = class_qn_for_node(ctx, class_node); |
| 3849 | if (!cqn) |
| 3850 | return; |
| 3851 | php_class_fields_t *f = fields_for(tab, cqn, ctx->arena); |
| 3852 | if (!f) |
| 3853 | return; |
| 3854 | |
| 3855 | /* PHPDoc on the class itself: @property + @method tags. */ |
| 3856 | char *class_doc = fetch_leading_phpdoc(ctx, class_node); |
| 3857 | if (class_doc) { |
| 3858 | apply_phpdoc_class_tags(ctx, reg, cqn, class_doc, f); |
| 3859 | } |
| 3860 | |
| 3861 | /* Belt-and-suspenders: walk the class declaration's `base_clause` and |
| 3862 | * `class_interface_clause` to gather extends/implements QNs and append |
| 3863 | * them to the registered type's embedded_types. The unified extractor |
| 3864 | * is supposed to populate base_classes for class_declaration but |
| 3865 | * may miss interface_declaration's `extends Base` chain or use a |
| 3866 | * raw form (with backslashes) that doesn't round-trip cleanly. |
| 3867 | * Doing this AST extraction here keeps the LSP self-sufficient. */ |
| 3868 | { |
| 3869 | const char *parent_qns[16]; |
| 3870 | int parent_count = 0; |
| 3871 | uint32_t cnc = ts_node_child_count(class_node); |
| 3872 | for (uint32_t i = 0; i < cnc && parent_count < 16; i++) { |
| 3873 | TSNode c = ts_node_child(class_node, i); |
| 3874 | if (ts_node_is_null(c) || !ts_node_is_named(c)) |
| 3875 | continue; |
| 3876 | const char *k = ts_node_type(c); |
| 3877 | if (strcmp(k, "base_clause") != 0 && strcmp(k, "class_interface_clause") != 0) { |
| 3878 | continue; |
| 3879 | } |
| 3880 | uint32_t bnc = ts_node_child_count(c); |
| 3881 | for (uint32_t j = 0; j < bnc && parent_count < 16; j++) { |
| 3882 | TSNode bc = ts_node_child(c, j); |
| 3883 | if (ts_node_is_null(bc) || !ts_node_is_named(bc)) |
| 3884 | continue; |
| 3885 | const char *bk = ts_node_type(bc); |
| 3886 | if (strcmp(bk, "name") != 0 && strcmp(bk, "qualified_name") != 0) |
| 3887 | continue; |
| 3888 | char *t = php_node_text(ctx, bc); |
| 3889 | if (!t) |
| 3890 | continue; |
| 3891 | const char *resolved = php_resolve_class_name(ctx, t); |
| 3892 | if (!resolved) |
| 3893 | continue; |
| 3894 | parent_qns[parent_count++] = resolved; |
| 3895 | } |
| 3896 | } |
| 3897 | if (parent_count > 0) { |
| 3898 | for (int t = 0; t < reg->type_count; t++) { |
| 3899 | if (strcmp(reg->types[t].qualified_name, cqn) != 0) |
| 3900 | continue; |
| 3901 | /* Merge with existing embedded_types — dedup. */ |
| 3902 | int existing = 0; |
| 3903 | if (reg->types[t].embedded_types) { |
no test coverage detected