( ref: UnresolvedRef, context: ResolutionContext )
| 206 | * edge is worse than none. |
| 207 | */ |
| 208 | export function matchFunctionRef( |
| 209 | ref: UnresolvedRef, |
| 210 | context: ResolutionContext |
| 211 | ): ResolvedRef | null { |
| 212 | // `this.<member>` refs are resolved ONLY by the class-scoped resolver in |
| 213 | // resolveOne (resolveThisMemberFnRef) — never by name matching here. |
| 214 | if (ref.referenceName.startsWith('this.')) return null; |
| 215 | |
| 216 | // In JS/TS/Python a bare identifier can never be a method value (methods |
| 217 | // are only reachable through a receiver — `this.m` / `self.m` / |
| 218 | // `Cls.m`), so bare fn-refs match FUNCTIONS only. This also sidesteps the |
| 219 | // pre-existing TS quirk of class fields extracting as method-kind nodes, |
| 220 | // which otherwise soaked up local names passed as arguments (excalidraw |
| 221 | // A/B finding; same pattern in vendored docopt.py). Python's `self.m` |
| 222 | // form keeps method targets via its own capture shape. C++ likewise: a |
| 223 | // bare identifier can only be a FREE function (member values need |
| 224 | // `&Cls::method`). PHP string callables name global FUNCTIONS (methods |
| 225 | // need the `[$obj, 'm']` array form, which carries its own shape). Other |
| 226 | // languages keep method targets: C# method groups, Swift/Dart |
| 227 | // implicit-self, Java/Kotlin method references. |
| 228 | const bareFnOnly = |
| 229 | ref.language === 'typescript' || ref.language === 'tsx' || |
| 230 | ref.language === 'javascript' || ref.language === 'jsx' || |
| 231 | ref.language === 'arkts' || |
| 232 | ref.language === 'cpp' || ref.language === 'python' || |
| 233 | ref.language === 'php'; |
| 234 | |
| 235 | // Python additionally accepts CLASS targets for bare identifiers (#1478): |
| 236 | // class-as-value is a core Python idiom (`return SomeSerializer`, |
| 237 | // `Meta.model = Org`, registry dicts, `admin.site.register(Model, Admin)`) |
| 238 | // and, unlike TS, Python has no type-annotation recovery path. The |
| 239 | // false-positive mechanism behind the function-only rule was lowercase |
| 240 | // locals colliding with same-named METHODS (docopt.py) — a candidate must |
| 241 | // be an exact-name CLASS node here, and the extraction gate (same-file |
| 242 | // class ∪ imports) plus unique-or-drop still apply. Methods stay excluded. |
| 243 | const bareClassOk = ref.language === 'python'; |
| 244 | |
| 245 | // Qualified member-pointer (`&Widget::on_click` → "Widget::on_click"): |
| 246 | // resolve the member ON THAT SCOPE — exempt from bareFnOnly (the `&Cls::m` |
| 247 | // shape is an explicit member reference). Unique-or-drop like everything else. |
| 248 | if (ref.referenceName.includes('::')) { |
| 249 | const memberName = ref.referenceName.slice(ref.referenceName.lastIndexOf('::') + 2); |
| 250 | const scoped = context |
| 251 | .getNodesByName(memberName) |
| 252 | .filter( |
| 253 | (n) => |
| 254 | (n.kind === 'function' || n.kind === 'method') && |
| 255 | sameLanguageFamily(n.language, ref.language) && |
| 256 | n.id !== ref.fromNodeId && |
| 257 | (n.qualifiedName === ref.referenceName || |
| 258 | n.qualifiedName.endsWith(`::${ref.referenceName}`)) |
| 259 | ); |
| 260 | if (scoped.length === 0) return null; |
| 261 | const sameFileScoped = scoped.filter((n) => n.filePath === ref.filePath); |
| 262 | const pool = sameFileScoped.length > 0 ? sameFileScoped : scoped; |
| 263 | if (sameFileScoped.length === 0 && scoped.length > 1) return null; |
| 264 | const target = pool.reduce((a, b) => (a.startLine <= b.startLine ? a : b)); |
| 265 | return { |
no test coverage detected