Walk siblings backwards from `node` to find a leading PHPDoc comment * ("/**...*/"). Returns cleaned doc text or NULL. */
| 790 | /* Walk siblings backwards from `node` to find a leading PHPDoc comment |
| 791 | * ("/**...*/"). Returns cleaned doc text or NULL. */ |
| 792 | static char *fetch_leading_phpdoc(PHPLSPContext *ctx, TSNode node) { |
| 793 | TSNode parent = ts_node_parent(node); |
| 794 | if (ts_node_is_null(parent)) |
| 795 | return NULL; |
| 796 | uint32_t nc = ts_node_child_count(parent); |
| 797 | /* Find index of node within parent. */ |
| 798 | int idx = -1; |
| 799 | for (uint32_t i = 0; i < nc; i++) { |
| 800 | TSNode c = ts_node_child(parent, i); |
| 801 | if (ts_node_eq(c, node)) { |
| 802 | idx = (int)i; |
| 803 | break; |
| 804 | } |
| 805 | } |
| 806 | if (idx <= 0) |
| 807 | return NULL; |
| 808 | for (int i = idx - 1; i >= 0; i--) { |
| 809 | TSNode c = ts_node_child(parent, (uint32_t)i); |
| 810 | if (ts_node_is_null(c)) |
| 811 | continue; |
| 812 | const char *k = ts_node_type(c); |
| 813 | if (strcmp(k, "comment") == 0) { |
| 814 | char *raw = php_node_text(ctx, c); |
| 815 | if (raw && raw[0] == '/' && raw[1] == '*' && raw[2] == '*') { |
| 816 | return phpdoc_clean(ctx->arena, raw); |
| 817 | } |
| 818 | return NULL; |
| 819 | } |
| 820 | /* Skip whitespace-only or attributes; stop at any other node. */ |
| 821 | if (strcmp(k, "attribute_list") == 0 || strcmp(k, "attribute_group") == 0) |
| 822 | continue; |
| 823 | return NULL; |
| 824 | } |
| 825 | return NULL; |
| 826 | } |
| 827 | |
| 828 | /* ── expression evaluation ──────────────────────────────────────── */ |
| 829 |
no test coverage detected