( ref: UnresolvedRef, context: ResolutionContext )
| 1610 | * Try to resolve by method name on a class/object |
| 1611 | */ |
| 1612 | export function matchMethodCall( |
| 1613 | ref: UnresolvedRef, |
| 1614 | context: ResolutionContext |
| 1615 | ): ResolvedRef | null { |
| 1616 | // Parse method call patterns like "obj.method" or "Class::method". The method |
| 1617 | // part allows trailing `:` keywords so Objective-C selectors resolve |
| 1618 | // (`SDImageCache.storeImage:`, `obj.setX:y:`); colons never appear in other |
| 1619 | // languages' method refs, so this is a no-op for them. |
| 1620 | // The receiver allows dots (`builder.Services.AddCoreServices`) so a CHAINED |
| 1621 | // call resolves by its last segment — Strategy 3 below name-matches the method |
| 1622 | // (with its existing single-candidate / receiver-overlap guards). Without this |
| 1623 | // a multi-dot extension-method call (C# DI `builder.Services.AddCoreServices()`, |
| 1624 | // `Guard.Against.X()`) matched no pattern and never resolved. |
| 1625 | // C++ explicit operator call `a.operator+(b)` reaches the resolver as |
| 1626 | // `a.operator+` (#1247) — the operator's symbol chars (`+`, `==`, `[]`, `()`) |
| 1627 | // fail the \w method part of the plain pattern, so admit them explicitly. |
| 1628 | // Names like `operatorTable` stay on the plain pattern (tried first); the |
| 1629 | // operator form requires at least one non-word char after `operator`, and |
| 1630 | // every downstream strategy compares the method part by exact string |
| 1631 | // equality, so a stray match can't invent an edge. |
| 1632 | const dotMatch = |
| 1633 | ref.referenceName.match(/^([\w.]+)\.(\w+:?(?:\w+:)*)$/) ?? |
| 1634 | (ref.language === 'cpp' |
| 1635 | ? ref.referenceName.match(/^([\w.]+)\.(operator[^\w\s.]+)$/) |
| 1636 | : null); |
| 1637 | const colonMatch = ref.referenceName.match(/^(\w+)::(\w+)$/); |
| 1638 | // Lua/Luau method calls use a single colon (`lg:log`); R uses `$` (`lg$log`). |
| 1639 | // Recognize these receiver/method separators so local-variable receiver-type |
| 1640 | // inference (#1108) applies to them too — extraction already emits the ref in |
| 1641 | // this shape, but the resolver otherwise only understood `.` and `::`. |
| 1642 | const luaColonMatch = (ref.language === 'lua' || ref.language === 'luau') |
| 1643 | ? ref.referenceName.match(/^([\w.]+):(\w+)$/) |
| 1644 | : null; |
| 1645 | const rDollarMatch = ref.language === 'r' |
| 1646 | ? ref.referenceName.match(/^([\w.]+)\$(\w+)$/) |
| 1647 | : null; |
| 1648 | |
| 1649 | // PHP property receiver: `$this->prop->method()` reaches the resolver as |
| 1650 | // `this->prop.method` (the extractor records the receiver's raw text with the |
| 1651 | // leading `$` stripped). Resolve it EXCLUSIVELY through declared-type |
| 1652 | // inference + resolveMethodOnType validation — the name-similarity strategies |
| 1653 | // below must never see this shape, so a property whose type can't be |
| 1654 | // recovered stays unlinked rather than guessed (a wrong inference produces no |
| 1655 | // edge rather than a wrong one). Deeper chains (`this->a->b.method`) don't |
| 1656 | // match the single-property pattern and stay unlinked, same as before. |
| 1657 | const phpThisPropMatch = ref.language === 'php' |
| 1658 | ? ref.referenceName.match(/^(this->\w+)\.(\w+)$/) |
| 1659 | : null; |
| 1660 | if (phpThisPropMatch) { |
| 1661 | const [, receiver, phpMethodName] = phpThisPropMatch; |
| 1662 | const inferredType = inferLocalReceiverType(receiver!, ref, context); |
| 1663 | if (!inferredType) return null; |
| 1664 | return resolveMethodOnType( |
| 1665 | inferredType, |
| 1666 | phpMethodName!, |
| 1667 | ref, |
| 1668 | context, |
| 1669 | 0.9, |
no test coverage detected