From 49b0effe2087c2e9de6008d2bbd5e2f20ae7cb24 Mon Sep 17 00:00:00 2001 From: Theros Date: Sun, 1 Mar 2026 14:43:27 +0000 Subject: [PATCH 1/2] Refine !rename directive token detection --- logic.ts | 2 +- logic_test.ts | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) 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..f2b6dba 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 still rejects malformed true !rename directives", async () => { + const root = await Deno.makeTempDir(); + try { + await Deno.writeTextFile( + join(root, KEEPLIST_FILE), + ["mods/**", "!rename from.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 { From c2e112a3799ae546e1694ffbf5d8cb333946d676 Mon Sep 17 00:00:00 2001 From: Theros Date: Sun, 1 Mar 2026 15:02:29 +0000 Subject: [PATCH 2/2] Refine rename boundary regression test --- logic_test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/logic_test.ts b/logic_test.ts index f2b6dba..96cdca4 100644 --- a/logic_test.ts +++ b/logic_test.ts @@ -1162,12 +1162,12 @@ Deno.test("scanForRemoval treats !rename-prefixed literals as keep rules", async } }); -Deno.test("scanForRemoval still rejects malformed true !rename directives", async () => { +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 from.txt to.txt"].join("\n"), + ["mods/**", "!rename\tfrom.txt to.txt"].join("\n"), ); await assertRejects(