* Second-chance typing for a PHP `$this->prop` receiver whose property * declaration carries no static type (classic pre-7.4 style): find the * `$this->prop = $var` assignment, then recover `$var`'s type from its own * declaration WITHIN the assignment's function — the constructor's (possibly *
( escapedProp: string, lines: string[], callIdx: number, )
| 1560 | * can never type the property. |
| 1561 | */ |
| 1562 | function inferPhpAssignedPropertyType( |
| 1563 | escapedProp: string, |
| 1564 | lines: string[], |
| 1565 | callIdx: number, |
| 1566 | ): string | null { |
| 1567 | const assignRe = new RegExp(`\\$this->${escapedProp}\\b\\s*=\\s*\\$(\\w+)\\b`); |
| 1568 | const assignAt = (i: number): RegExpMatchArray | null => { |
| 1569 | const line = lines[i]; |
| 1570 | if (!line || line.length > 10_000) return null; |
| 1571 | return line.match(assignRe); |
| 1572 | }; |
| 1573 | // The assignment is position-independent relative to the call — nearest- |
| 1574 | // backward first, then sweep forward, same order as the componentScoped scan. |
| 1575 | let assignIdx = -1; |
| 1576 | let varName: string | null = null; |
| 1577 | for (let i = callIdx; i >= 0; i--) { |
| 1578 | const m = assignAt(i); |
| 1579 | if (m) { assignIdx = i; varName = m[1]!; break; } |
| 1580 | } |
| 1581 | if (varName === null) { |
| 1582 | for (let i = callIdx + 1; i < lines.length; i++) { |
| 1583 | const m = assignAt(i); |
| 1584 | if (m) { assignIdx = i; varName = m[1]!; break; } |
| 1585 | } |
| 1586 | } |
| 1587 | if (varName === null) return null; |
| 1588 | |
| 1589 | const varPatterns = localReceiverTypePatterns( |
| 1590 | 'php', |
| 1591 | varName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), |
| 1592 | ); |
| 1593 | for (let i = assignIdx; i >= 0; i--) { |
| 1594 | const line = lines[i]; |
| 1595 | if (line && line.length <= 10_000) { |
| 1596 | for (const re of varPatterns) { |
| 1597 | const m = line.match(re); |
| 1598 | if (m && m[1]) { |
| 1599 | const type = normalizeInferredTypeName(m[1]); |
| 1600 | if (type) return type; |
| 1601 | } |
| 1602 | } |
| 1603 | } |
| 1604 | if (line && /\bfunction\b/.test(line)) break; |
| 1605 | } |
| 1606 | return null; |
| 1607 | } |
| 1608 | |
| 1609 | /** |
| 1610 | * Try to resolve by method name on a class/object |
no test coverage detected