filesystem.ts 2.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. import { realpathSync } from "fs"
  2. import { exists } from "fs/promises"
  3. import { dirname, join, relative } from "path"
  4. export namespace Filesystem {
  5. /**
  6. * On Windows, normalize a path to its canonical casing using the filesystem.
  7. * This is needed because Windows paths are case-insensitive but LSP servers
  8. * may return paths with different casing than what we send them.
  9. */
  10. export function normalizePath(p: string): string {
  11. if (process.platform !== "win32") return p
  12. try {
  13. return realpathSync.native(p)
  14. } catch {
  15. return p
  16. }
  17. }
  18. export function overlaps(a: string, b: string) {
  19. const relA = relative(a, b)
  20. const relB = relative(b, a)
  21. return !relA || !relA.startsWith("..") || !relB || !relB.startsWith("..")
  22. }
  23. export function contains(parent: string, child: string) {
  24. return !relative(parent, child).startsWith("..")
  25. }
  26. export async function findUp(target: string, start: string, stop?: string) {
  27. let current = start
  28. const result = []
  29. while (true) {
  30. const search = join(current, target)
  31. if (await exists(search).catch(() => false)) result.push(search)
  32. if (stop === current) break
  33. const parent = dirname(current)
  34. if (parent === current) break
  35. current = parent
  36. }
  37. return result
  38. }
  39. export async function* up(options: { targets: string[]; start: string; stop?: string }) {
  40. const { targets, start, stop } = options
  41. let current = start
  42. while (true) {
  43. for (const target of targets) {
  44. const search = join(current, target)
  45. if (await exists(search).catch(() => false)) yield search
  46. }
  47. if (stop === current) break
  48. const parent = dirname(current)
  49. if (parent === current) break
  50. current = parent
  51. }
  52. }
  53. export async function globUp(pattern: string, start: string, stop?: string) {
  54. let current = start
  55. const result = []
  56. while (true) {
  57. try {
  58. const glob = new Bun.Glob(pattern)
  59. for await (const match of glob.scan({
  60. cwd: current,
  61. absolute: true,
  62. onlyFiles: true,
  63. followSymlinks: true,
  64. dot: true,
  65. })) {
  66. result.push(match)
  67. }
  68. } catch {
  69. // Skip invalid glob patterns
  70. }
  71. if (stop === current) break
  72. const parent = dirname(current)
  73. if (parent === current) break
  74. current = parent
  75. }
  76. return result
  77. }
  78. }