( input: string, projectRoot: string, baseDir?: string, )
| 295 | * not_found rather than fabricating a match against `foo.ts` somewhere in cwd. |
| 296 | */ |
| 297 | export async function resolveCodeFile( |
| 298 | input: string, |
| 299 | projectRoot: string, |
| 300 | baseDir?: string, |
| 301 | ): Promise<ResolveResult> { |
| 302 | const originalInput = input.trim(); |
| 303 | const unquotedInput = stripWrappingQuotes(originalInput); |
| 304 | const normalizedInput = normalizeUserPathInput(unquotedInput); |
| 305 | const searchInput = normalizeSeparators(normalizedInput); |
| 306 | |
| 307 | if (!searchInput) { |
| 308 | return { kind: "not_found", input: originalInput }; |
| 309 | } |
| 310 | |
| 311 | if (isAbsoluteNormalizedUserPath(normalizedInput)) { |
| 312 | const absolutePath = resolveAbsolutePath(normalizedInput); |
| 313 | if (fileExists(absolutePath)) { |
| 314 | return { kind: "found", path: absolutePath }; |
| 315 | } |
| 316 | return { kind: "not_found", input: originalInput }; |
| 317 | } |
| 318 | |
| 319 | const fromRoot = resolve(projectRoot, searchInput); |
| 320 | if (isWithinProjectRoot(fromRoot, projectRoot) && fileExists(fromRoot)) { |
| 321 | return { kind: "found", path: fromRoot }; |
| 322 | } |
| 323 | |
| 324 | if (baseDir) { |
| 325 | const fromBase = resolve(baseDir, searchInput); |
| 326 | if (fileExists(fromBase)) { |
| 327 | return { kind: "found", path: fromBase }; |
| 328 | } |
| 329 | } |
| 330 | |
| 331 | const fileList = await warmFileListCache(projectRoot, "code"); |
| 332 | if (fileList === null) { |
| 333 | return { kind: "unavailable", input: originalInput }; |
| 334 | } |
| 335 | |
| 336 | // Strip leading `./` so suffix matching works on inputs like |
| 337 | // `./editor/App.tsx` — file list entries never carry that segment. |
| 338 | // `../` is intentionally NOT stripped: `..` is meaningful (escape parent), |
| 339 | // not noise. If we can't honor it via baseDir, the input has no |
| 340 | // suffix-match equivalent in the in-tree file list. |
| 341 | const cleanedInput = searchInput.replace(/^(?:\.\/)+/, ""); |
| 342 | if (!cleanedInput || cleanedInput.startsWith("../")) { |
| 343 | return { kind: "not_found", input: originalInput }; |
| 344 | } |
| 345 | const target = cleanedInput.toLowerCase(); |
| 346 | const isBareFilename = !cleanedInput.includes("/"); |
| 347 | const matches: string[] = []; |
| 348 | |
| 349 | for (const f of fileList) { |
| 350 | const fl = f.toLowerCase(); |
| 351 | if (isBareFilename) { |
| 352 | const base = fl.split("/").pop(); |
| 353 | if (base === target) matches.push(resolve(projectRoot, f)); |
| 354 | } else { |
no test coverage detected