diff --git a/logic.ts b/logic.ts index c38260f..4ce9323 100644 --- a/logic.ts +++ b/logic.ts @@ -236,7 +236,7 @@ function parseKeeplist(text: string): KeeplistConfig { continue; } - if (line.startsWith("!rename")) { + if (/^!rename(\s|$)/.test(line)) { const directive = parseRenameDirective(line, lineNumber); if (seenFrom.has(directive.from)) { diff --git a/logic_test.ts b/logic_test.ts index be980f7..96cdca4 100644 --- a/logic_test.ts +++ b/logic_test.ts @@ -1139,6 +1139,47 @@ Deno.test("scanForRemoval rejects malformed !rename without arrow", async () => } }); +Deno.test("scanForRemoval treats !rename-prefixed literals as keep rules", async () => { + const root = await Deno.makeTempDir(); + try { + await Deno.writeTextFile( + join(root, KEEPLIST_FILE), + ["mods/**", "!rename_backup.dll"].join("\n"), + ); + + await Deno.mkdir(join(root, "mods"), { recursive: true }); + await Deno.writeTextFile(join(root, "mods", "keep.txt"), "keep"); + await Deno.mkdir(join(root, "trash"), { recursive: true }); + await Deno.writeTextFile(join(root, "trash", "remove.txt"), "remove"); + + const removable = await scanForRemoval(root); + assertEquals(removable, ["trash/remove.txt"]); + + const keepRules = await readKeeplist(root); + assertEquals(keepRules, ["mods/**", "!rename_backup.dll"]); + } finally { + await Deno.remove(root, { recursive: true }); + } +}); + +Deno.test("scanForRemoval rejects malformed !rename directives with tab after token", async () => { + const root = await Deno.makeTempDir(); + try { + await Deno.writeTextFile( + join(root, KEEPLIST_FILE), + ["mods/**", "!rename\tfrom.txt to.txt"].join("\n"), + ); + + await assertRejects( + () => scanForRemoval(root), + Error, + "expected '!rename -> '", + ); + } finally { + await Deno.remove(root, { recursive: true }); + } +}); + Deno.test("scanForRemoval rejects !rename with missing from path", async () => { const root = await Deno.makeTempDir(); try {