summaryrefslogtreecommitdiffhomepage
path: root/packages/lsp/src/root.ts
blob: b0801fd4bf774d26becfba66f990174e13d61dcb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
 * Root finder — nearest ancestor containing a marker file, bounded at cwd.
 */

export async function findRoot(
  startDir: string,
  cwd: string,
  markers: readonly string[],
  exists: (path: string) => Promise<boolean>,
): Promise<string> {
  const normalizedStart = normalizePath(startDir);
  const normalizedCwd = normalizePath(cwd);

  let current = normalizedStart;
  while (true) {
    for (const marker of markers) {
      const markerPath = current === "/" ? `/${marker}` : `${current}/${marker}`;
      if (await exists(markerPath)) {
        return current;
      }
    }
    if (current === normalizedCwd || current === "/") {
      return normalizedCwd;
    }
    const parent = getParent(current);
    if (parent === current) return normalizedCwd;
    current = parent;
  }
}

function normalizePath(p: string): string {
  let normalized = p.replace(/\\/g, "/");
  if (normalized.length > 1 && normalized.endsWith("/")) {
    normalized = normalized.slice(0, -1);
  }
  return normalized || "/";
}

function getParent(p: string): string {
  if (p === "/") return "/";
  const lastSlash = p.lastIndexOf("/");
  if (lastSlash <= 0) return "/";
  return p.slice(0, lastSlash) || "/";
}