(projectRoot: string)
| 143 | * resolver does it via {@link aliasCache}). |
| 144 | */ |
| 145 | export function loadProjectAliases(projectRoot: string): AliasMap | null { |
| 146 | const candidates = ['tsconfig.json', 'jsconfig.json']; |
| 147 | let raw: RawTsconfig | null = null; |
| 148 | let usedFile: string | null = null; |
| 149 | for (const name of candidates) { |
| 150 | const p = path.join(projectRoot, name); |
| 151 | if (fs.existsSync(p)) { |
| 152 | raw = readTsconfigLike(p); |
| 153 | if (raw) { |
| 154 | usedFile = name; |
| 155 | break; |
| 156 | } |
| 157 | } |
| 158 | } |
| 159 | if (!raw) return null; |
| 160 | |
| 161 | const co = raw.compilerOptions ?? {}; |
| 162 | const baseUrlRel = co.baseUrl ?? '.'; |
| 163 | const baseUrl = path.resolve(projectRoot, baseUrlRel); |
| 164 | |
| 165 | const paths = co.paths; |
| 166 | if (!paths || typeof paths !== 'object') { |
| 167 | // baseUrl alone isn't an "alias" per se; with no paths we'd just |
| 168 | // be redirecting the whole tree. Skip — the existing resolver |
| 169 | // already handles relative imports. |
| 170 | return null; |
| 171 | } |
| 172 | |
| 173 | const patterns: AliasPattern[] = []; |
| 174 | for (const [pattern, targets] of Object.entries(paths)) { |
| 175 | if (!Array.isArray(targets) || targets.length === 0) continue; |
| 176 | const filtered = targets.filter((t): t is string => typeof t === 'string'); |
| 177 | if (filtered.length === 0) continue; |
| 178 | const { prefix, suffix, hasWildcard } = splitWildcard(pattern); |
| 179 | patterns.push({ prefix, suffix, hasWildcard, replacements: filtered }); |
| 180 | } |
| 181 | |
| 182 | if (patterns.length === 0) return null; |
| 183 | |
| 184 | // Specificity sort: longer prefix first; literal patterns before |
| 185 | // wildcard patterns of the same prefix length. TypeScript itself |
| 186 | // uses a similar "most specific match wins" rule. |
| 187 | patterns.sort((a, b) => { |
| 188 | if (a.prefix.length !== b.prefix.length) return b.prefix.length - a.prefix.length; |
| 189 | if (a.hasWildcard !== b.hasWildcard) return a.hasWildcard ? 1 : -1; |
| 190 | return 0; |
| 191 | }); |
| 192 | |
| 193 | logDebug('path-aliases loaded', { |
| 194 | file: usedFile, |
| 195 | baseUrl, |
| 196 | patternCount: patterns.length, |
| 197 | }); |
| 198 | |
| 199 | return { baseUrl, patterns }; |
| 200 | } |
| 201 | |
| 202 | /** |
no test coverage detected