( ref: UnresolvedRef, context: ResolutionContext )
| 204 | * edge is worse than none. |
| 205 | */ |
| 206 | export function matchFunctionRef( |
| 207 | ref: UnresolvedRef, |
| 208 | context: ResolutionContext |
| 209 | ): ResolvedRef | null { |
| 210 | // `this.<member>` refs are resolved ONLY by the class-scoped resolver in |
| 211 | // resolveOne (resolveThisMemberFnRef) — never by name matching here. |
| 212 | if (ref.referenceName.startsWith('this.')) return null; |
| 213 | |
| 214 | // In JS/TS/Python a bare identifier can never be a method value (methods |
| 215 | // are only reachable through a receiver — `this.m` / `self.m` / |
| 216 | // `Cls.m`), so bare fn-refs match FUNCTIONS only. This also sidesteps the |
| 217 | // pre-existing TS quirk of class fields extracting as method-kind nodes, |
| 218 | // which otherwise soaked up local names passed as arguments (excalidraw |
| 219 | // A/B finding; same pattern in vendored docopt.py). Python's `self.m` |
| 220 | // form keeps method targets via its own capture shape. C++ likewise: a |
| 221 | // bare identifier can only be a FREE function (member values need |
| 222 | // `&Cls::method`). PHP string callables name global FUNCTIONS (methods |
| 223 | // need the `[$obj, 'm']` array form, which carries its own shape). Other |
| 224 | // languages keep method targets: C# method groups, Swift/Dart |
| 225 | // implicit-self, Java/Kotlin method references. |
| 226 | const bareFnOnly = |
| 227 | ref.language === 'typescript' || ref.language === 'tsx' || |
| 228 | ref.language === 'javascript' || ref.language === 'jsx' || |
| 229 | ref.language === 'cpp' || ref.language === 'python' || |
| 230 | ref.language === 'php'; |
| 231 | |
| 232 | // Qualified member-pointer (`&Widget::on_click` → "Widget::on_click"): |
| 233 | // resolve the member ON THAT SCOPE — exempt from bareFnOnly (the `&Cls::m` |
| 234 | // shape is an explicit member reference). Unique-or-drop like everything else. |
| 235 | if (ref.referenceName.includes('::')) { |
| 236 | const memberName = ref.referenceName.slice(ref.referenceName.lastIndexOf('::') + 2); |
| 237 | const scoped = context |
| 238 | .getNodesByName(memberName) |
| 239 | .filter( |
| 240 | (n) => |
| 241 | (n.kind === 'function' || n.kind === 'method') && |
| 242 | sameLanguageFamily(n.language, ref.language) && |
| 243 | n.id !== ref.fromNodeId && |
| 244 | (n.qualifiedName === ref.referenceName || |
| 245 | n.qualifiedName.endsWith(`::${ref.referenceName}`)) |
| 246 | ); |
| 247 | if (scoped.length === 0) return null; |
| 248 | const sameFileScoped = scoped.filter((n) => n.filePath === ref.filePath); |
| 249 | const pool = sameFileScoped.length > 0 ? sameFileScoped : scoped; |
| 250 | if (sameFileScoped.length === 0 && scoped.length > 1) return null; |
| 251 | const target = pool.reduce((a, b) => (a.startLine <= b.startLine ? a : b)); |
| 252 | return { |
| 253 | original: ref, |
| 254 | targetNodeId: target.id, |
| 255 | confidence: 0.9, |
| 256 | resolvedBy: 'function-ref', |
| 257 | }; |
| 258 | } |
| 259 | |
| 260 | let candidates = context |
| 261 | .getNodesByName(ref.referenceName) |
| 262 | .filter( |
| 263 | (n) => |
no test coverage detected