( ref: UnresolvedRef, context: ResolutionContext )
| 1198 | * Try to resolve by method name on a class/object |
| 1199 | */ |
| 1200 | export function matchMethodCall( |
| 1201 | ref: UnresolvedRef, |
| 1202 | context: ResolutionContext |
| 1203 | ): ResolvedRef | null { |
| 1204 | // Parse method call patterns like "obj.method" or "Class::method". The method |
| 1205 | // part allows trailing `:` keywords so Objective-C selectors resolve |
| 1206 | // (`SDImageCache.storeImage:`, `obj.setX:y:`); colons never appear in other |
| 1207 | // languages' method refs, so this is a no-op for them. |
| 1208 | // The receiver allows dots (`builder.Services.AddCoreServices`) so a CHAINED |
| 1209 | // call resolves by its last segment — Strategy 3 below name-matches the method |
| 1210 | // (with its existing single-candidate / receiver-overlap guards). Without this |
| 1211 | // a multi-dot extension-method call (C# DI `builder.Services.AddCoreServices()`, |
| 1212 | // `Guard.Against.X()`) matched no pattern and never resolved. |
| 1213 | const dotMatch = ref.referenceName.match(/^([\w.]+)\.(\w+:?(?:\w+:)*)$/); |
| 1214 | const colonMatch = ref.referenceName.match(/^(\w+)::(\w+)$/); |
| 1215 | // Lua/Luau method calls use a single colon (`lg:log`); R uses `$` (`lg$log`). |
| 1216 | // Recognize these receiver/method separators so local-variable receiver-type |
| 1217 | // inference (#1108) applies to them too — extraction already emits the ref in |
| 1218 | // this shape, but the resolver otherwise only understood `.` and `::`. |
| 1219 | const luaColonMatch = (ref.language === 'lua' || ref.language === 'luau') |
| 1220 | ? ref.referenceName.match(/^([\w.]+):(\w+)$/) |
| 1221 | : null; |
| 1222 | const rDollarMatch = ref.language === 'r' |
| 1223 | ? ref.referenceName.match(/^([\w.]+)\$(\w+)$/) |
| 1224 | : null; |
| 1225 | |
| 1226 | const match = dotMatch || colonMatch || luaColonMatch || rDollarMatch; |
| 1227 | if (!match) { |
| 1228 | return null; |
| 1229 | } |
| 1230 | |
| 1231 | const [, objectOrClass, methodName] = match; |
| 1232 | // A simple `receiver.method` / `receiver:method` / `receiver$method` shape whose |
| 1233 | // receiver type we can try to infer from its local declaration. |
| 1234 | const inferableReceiver = dotMatch || luaColonMatch || rDollarMatch; |
| 1235 | |
| 1236 | // Infer the receiver's type from its local declaration/initializer in the |
| 1237 | // enclosing scope, then resolve the method on that type (#1108). C++ keeps its |
| 1238 | // dedicated inferrer (header scan + `auto`); every other language uses the |
| 1239 | // shared source-based inferrer. resolveMethodOnType validates the method |
| 1240 | // exists on the inferred type, so a mis-inference produces no edge. |
| 1241 | if (inferableReceiver) { |
| 1242 | const inferredType = |
| 1243 | ref.language === 'cpp' |
| 1244 | ? inferCppReceiverType(objectOrClass!, ref, context) |
| 1245 | : inferLocalReceiverType(objectOrClass!, ref, context); |
| 1246 | if (inferredType) { |
| 1247 | // Java/Kotlin: when two classes share the simple name, the file's import |
| 1248 | // pins WHICH one (#314). Other languages disambiguate by call-site file. |
| 1249 | const importedFqn = |
| 1250 | ref.language === 'java' || ref.language === 'kotlin' |
| 1251 | ? context |
| 1252 | .getImportMappings(ref.filePath, ref.language) |
| 1253 | .find((i) => i.localName === inferredType)?.source |
| 1254 | : undefined; |
| 1255 | const typedMatch = resolveMethodOnType( |
| 1256 | inferredType, |
| 1257 | methodName!, |
no test coverage detected