From 3969960bfed55fb3b3499c105e72aad90fb617f0 Mon Sep 17 00:00:00 2001 From: Luke Policinski Date: Tue, 29 Sep 2026 20:46:19 -0400 Subject: [PATCH] bug: map_rotation file tokens must be whole values --- scripts/build.test.mjs | 6 ++++++ scripts/validate.mjs | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/scripts/build.test.mjs b/scripts/build.test.mjs index f036c7b..888bf07 100644 --- a/scripts/build.test.mjs +++ b/scripts/build.test.mjs @@ -126,6 +126,12 @@ test("rejects map rotation paths that escape game/csgo", () => { assert.equal(validateMapRotation(rotation({ "../maps.jsonc": { Maps: "{{maps}}" } }), "x.json").length, 1); }); +test("rejects a file token embedded in longer text", () => { + const problems = validateMapRotation(rotation({ "a/maps.jsonc": { Maps: "x{{maps}}" } }), "x.json"); + assert.equal(problems.length, 1); + assert.match(problems[0], /as a whole value/); +}); + test("rejects map rotation tokens used in the wrong place", () => { assert.equal(validateMapRotation(rotation({ "a/maps.jsonc": { Maps: "{{maps}}", Id: "{{id}}" } }), "x.json").length, 1); assert.equal( diff --git a/scripts/validate.mjs b/scripts/validate.mjs index 4e4d523..ca3e118 100644 --- a/scripts/validate.mjs +++ b/scripts/validate.mjs @@ -170,6 +170,18 @@ function tokensIn(value) { return []; } +function stringsIn(value) { + if (typeof value === "string") { + return [value]; + } + + if (value && typeof value === "object") { + return Object.values(value).flatMap(stringsIn); + } + + return []; +} + export function validateMapRotation(entry, filePath) { const problems = []; const fail = (message) => problems.push(`${filePath}: map_rotation ${message}`); @@ -204,6 +216,16 @@ export function validateMapRotation(entry, filePath) { fail(`"${target}" uses ${token}; files may only use ${[...MAP_ROTATION_DOCUMENT_TOKENS].join(", ")}`); } } + + // {{maps}} is a list and {{shuffle}} a boolean; inside longer text either + // would render as "[object Object]" or "true". + for (const value of stringsIn(document)) { + const embedded = tokensIn(value).filter((token) => MAP_ROTATION_DOCUMENT_TOKENS.has(token)); + + if (embedded.length > 0 && value !== embedded[0]) { + fail(`"${target}" must use ${embedded.join(", ")} as a whole value, not inside "${value}"`); + } + } } if (!Object.values(files).some((document) => tokensIn(document).includes("{{maps}}"))) {