@var Type $name — bind a variable in current scope. * Generic types like `array ` may contain whitespace-free `<>` — we * tolerate those by including '<' and '>' in the type token. */
| 746 | * Generic types like `array<User>` may contain whitespace-free `<>` — we |
| 747 | * tolerate those by including '<' and '>' in the type token. */ |
| 748 | static void bind_phpdoc_var(PHPLSPContext *ctx, const char *docstring) { |
| 749 | if (!docstring || !ctx->current_scope) |
| 750 | return; |
| 751 | const char *p = docstring; |
| 752 | while ((p = strstr(p, "@var")) != NULL) { |
| 753 | p += 4; |
| 754 | while (*p == ' ' || *p == '\t') |
| 755 | p++; |
| 756 | const char *type_start = p; |
| 757 | /* Take a type token: stop at whitespace or '$' (start of var name). |
| 758 | * Allow '<', '>', ',', '|', '?' inside the type. */ |
| 759 | int depth = 0; |
| 760 | while (*p && *p != '\n') { |
| 761 | if (*p == '<') |
| 762 | depth++; |
| 763 | else if (*p == '>') |
| 764 | depth--; |
| 765 | else if (depth == 0 && (*p == ' ' || *p == '\t' || *p == '$')) |
| 766 | break; |
| 767 | p++; |
| 768 | } |
| 769 | if (p == type_start) |
| 770 | continue; |
| 771 | char *type_text = cbm_arena_strndup(ctx->arena, type_start, (size_t)(p - type_start)); |
| 772 | while (*p == ' ' || *p == '\t') |
| 773 | p++; |
| 774 | if (*p != '$') |
| 775 | continue; |
| 776 | p++; |
| 777 | const char *name_start = p; |
| 778 | while (*p && (isalnum((unsigned char)*p) || *p == '_')) |
| 779 | p++; |
| 780 | if (p == name_start) |
| 781 | continue; |
| 782 | char *vname = cbm_arena_strndup(ctx->arena, name_start, (size_t)(p - name_start)); |
| 783 | const CBMType *t = resolve_phpdoc_type(ctx, type_text); |
| 784 | if (vname && t && t->kind != CBM_TYPE_UNKNOWN) { |
| 785 | cbm_scope_bind(ctx->current_scope, vname, t); |
| 786 | } |
| 787 | } |
| 788 | } |
| 789 | |
| 790 | /* Walk siblings backwards from `node` to find a leading PHPDoc comment |
| 791 | * ("/**...*/"). Returns cleaned doc text or NULL. */ |
no test coverage detected