* Resolve a single reference
(ref: UnresolvedRef)
| 853 | * Resolve a single reference |
| 854 | */ |
| 855 | resolveOne(ref: UnresolvedRef): ResolvedRef | null { |
| 856 | // Skip built-in/external references |
| 857 | if (this.isBuiltInOrExternal(ref)) { |
| 858 | return null; |
| 859 | } |
| 860 | |
| 861 | // CFML component paths in inheritance (#1152): `extends="coldbox.system.web. |
| 862 | // Controller"` names the supertype by its dot-separated path (or `extends= |
| 863 | // "../base"` by relative file path) — the graph indexes the class under its |
| 864 | // final segment only, so these die at the fast pre-filter below and never |
| 865 | // resolved. Handled by a dedicated path-corroborated matcher, gated to |
| 866 | // inheritance refs only (a dotted `calls` ref is a member-access chain, not |
| 867 | // a component path). No fallthrough on miss: the full path string can only |
| 868 | // ever mis-match downstream, and an unresolvable supertype usually lives in |
| 869 | // an out-of-repo library (mxunit, testbox) — silent beats wrong. |
| 870 | if ( |
| 871 | (ref.language === 'cfml' || ref.language === 'cfscript') && |
| 872 | (ref.referenceKind === 'extends' || ref.referenceKind === 'implements') && |
| 873 | (ref.referenceName.includes('.') || ref.referenceName.includes('/')) |
| 874 | ) { |
| 875 | return this.resolveCfmlComponentPath(ref); |
| 876 | } |
| 877 | |
| 878 | // Fast pre-filter: skip if no symbol with this name exists anywhere |
| 879 | // AND the name doesn't match a local import. The import escape is |
| 880 | // necessary because re-export rename chains (`import { login } |
| 881 | // from './barrel'` where the barrel has `export { signIn as login } |
| 882 | // from './auth'`) intentionally call a name that has no |
| 883 | // declaration anywhere — only the renamed upstream symbol does. |
| 884 | // ArkTS chained-attribute refs carry a leading dot (`.titleStyle`) that |
| 885 | // routes them to the decorator-gated matcher; the symbol itself is |
| 886 | // indexed under the bare name, so the existence check strips the dot. |
| 887 | // Nix static path imports (`import ./x.nix`) name a FILE, not a symbol — |
| 888 | // they bypass the symbol-existence check and resolve via resolveViaImport. |
| 889 | const existenceName = |
| 890 | ref.language === 'arkts' && ref.referenceName.startsWith('.') |
| 891 | ? ref.referenceName.slice(1) |
| 892 | : ref.referenceName; |
| 893 | const tPre = this.profileStages ? process.hrtime.bigint() : 0n; |
| 894 | const preFilterPass = |
| 895 | isNixPathImportRef(ref) || |
| 896 | this.hasAnyPossibleMatch(existenceName) || |
| 897 | this.matchesAnyImport(ref) || |
| 898 | this.frameworks.some((f) => f.claimsReference?.(ref.referenceName)); |
| 899 | if (this.profileStages) this.stageAdd('preFilter', ref, preFilterPass, tPre); |
| 900 | if (!preFilterPass) { |
| 901 | return null; |
| 902 | } |
| 903 | |
| 904 | // Function-as-value refs (#756) get a dedicated, strictly-gated path: |
| 905 | // import-based resolution first (an imported callback resolves through its |
| 906 | // import, the most precise cross-file signal), then matchFunctionRef |
| 907 | // (same-file first, unique-only cross-file, function/method targets only). |
| 908 | // They never reach the framework or fuzzy strategies below. |
| 909 | if (ref.referenceKind === 'function_ref') { |
| 910 | // `this.<member>` values (TS/JS) resolve ONLY against the enclosing |
| 911 | // class's own members — never a same-named symbol elsewhere. |
| 912 | if (ref.referenceName.startsWith('this.')) { |
no test coverage detected