import * as path from "node:path"; import { resolvePath, type Analysis, type Invocation, type ShellWord, type WorkingDirectory, } from "bash-effect-analyzer"; export function resolvesToFilesystemRoot(cwd: string, target: string): boolean { const resolved = path.resolve(cwd, target); return resolved === path.parse(resolved).root; } function wordTargetsRoot(word: ShellWord, cwd: WorkingDirectory, initialCwd: string): boolean { return word.kind === "literal" && resolvePath(word, cwd, initialCwd).paths.some((target) => target === "/"); } function findTargetsRoot(invocation: Invocation, initialCwd: string): boolean { let hasStartingPoint = false; for (let i = 1; i < invocation.argv.length; i++) { const word = invocation.argv[i]; const value = word.kind === "literal" ? word.value : undefined; if (value === "--") continue; if (value === "-H" || value === "-L" || value === "-P" || value?.startsWith("-O")) { continue; } if (value === "-D") { i++; continue; } if (value === undefined) return false; if (value.startsWith("-") || ["!", "(", ")", ","].includes(value)) break; hasStartingPoint = true; if (wordTargetsRoot(word, invocation.cwd, initialCwd)) return true; } return !hasStartingPoint && wordTargetsRoot( { kind: "literal", value: ".", text: "." }, invocation.cwd, initialCwd, ); } export function rootTargetCommand(analysis: Analysis, cwd: string): "find" | "rm" | undefined { for (const invocation of analysis.invocations) { if (invocation.effects.some((effect) => effect.kind === "delete" && effect.via === "rm" && wordTargetsRoot(effect.path, effect.cwd, cwd) )) return "rm"; if (invocation.name === "find" && findTargetsRoot(invocation, cwd)) return "find"; } }