| 1732 | } |
| 1733 | |
| 1734 | static void resolve_static_call(PHPLSPContext *ctx, TSNode call, CBMResolvedKind kind) { |
| 1735 | TSNode scope = ts_node_child_by_field_name(call, "scope", 5); |
| 1736 | TSNode name_node = ts_node_child_by_field_name(call, "name", 4); |
| 1737 | if (ts_node_is_null(name_node)) |
| 1738 | return; |
| 1739 | if (kind == CBM_RESOLVED_CALL_REFERENCE && |
| 1740 | php_callable_reference_has_dynamic_member_name(ctx, call)) { |
| 1741 | return; |
| 1742 | } |
| 1743 | char *method_name = php_node_text(ctx, name_node); |
| 1744 | if (!method_name) |
| 1745 | return; |
| 1746 | |
| 1747 | const char *class_qn = NULL; |
| 1748 | const char *strategy = "php_static_resolved"; |
| 1749 | if (!ts_node_is_null(scope)) { |
| 1750 | char *cls = php_node_text(ctx, scope); |
| 1751 | if (cls) { |
| 1752 | if (strcmp(cls, "self") == 0 || strcmp(cls, "static") == 0) { |
| 1753 | class_qn = ctx->enclosing_class_qn; |
| 1754 | strategy = "php_self_static"; |
| 1755 | } else if (strcmp(cls, "parent") == 0) { |
| 1756 | class_qn = ctx->enclosing_parent_qn; |
| 1757 | strategy = "php_self_static"; |
| 1758 | } else { |
| 1759 | class_qn = php_resolve_class_name(ctx, cls); |
| 1760 | } |
| 1761 | } |
| 1762 | } |
| 1763 | if (!class_qn) |
| 1764 | return; |
| 1765 | |
| 1766 | const CBMRegisteredFunc *f = php_lookup_method(ctx, class_qn, method_name); |
| 1767 | if (f) { |
| 1768 | emit_resolved_kind(ctx, f->qualified_name, strategy, 0.95f, kind); |
| 1769 | return; |
| 1770 | } |
| 1771 | /* A first-class reference to an absent static method has an exact target |
| 1772 | * only when a materialized `__callStatic` exists. Preserve the source name |
| 1773 | * as the occurrence-join reason, but point at the real method node. Normal |
| 1774 | * facade invocations retain their existing low-confidence behavior. */ |
| 1775 | const CBMRegisteredFunc *magic_static = php_lookup_method(ctx, class_qn, "__callStatic"); |
| 1776 | if (magic_static && kind == CBM_RESOLVED_CALL_REFERENCE) { |
| 1777 | emit_resolved_reason_kind(ctx, magic_static->qualified_name, "php_static_magic_reference", |
| 1778 | 0.95f, method_name, kind); |
| 1779 | return; |
| 1780 | } |
| 1781 | /* Facade detection: class has __callStatic in its chain. */ |
| 1782 | if (magic_static) { |
| 1783 | emit_resolved_kind(ctx, cbm_arena_sprintf(ctx->arena, "%s.%s", class_qn, method_name), |
| 1784 | "php_dynamic_unresolved", 0.10f, kind); |
| 1785 | return; |
| 1786 | } |
| 1787 | /* Class resolved (e.g., from a `use` clause), method unknown — emit |
| 1788 | * synthetic resolved call so the pipeline bridge can suppress the |
| 1789 | * unified extractor's name-fallback misroute. */ |
| 1790 | emit_resolved_kind(ctx, cbm_arena_sprintf(ctx->arena, "%s.%s", class_qn, method_name), |
| 1791 | "php_static_unindexed", 0.55f, kind); |
no test coverage detected