From 18b0f039b5b5b70cebc76de63c9bc39b82a8daa5 Mon Sep 17 00:00:00 2001 From: user Date: Fri, 4 Sep 2026 11:18:19 +0000 Subject: [PATCH] Pin the export/override prefix and `?=` first-wins with tests --- test/packaging/lint-once.test.ts | 75 ++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/test/packaging/lint-once.test.ts b/test/packaging/lint-once.test.ts index 8137ccc..4bf4c33 100644 --- a/test/packaging/lint-once.test.ts +++ b/test/packaging/lint-once.test.ts @@ -826,6 +826,81 @@ describe("the resolver reads what the shell would run", () => { expect(recipes.get("check")?.recipe).toEqual(["script/fmt-check"]); }); + // `export FMT := script/fmt-check` is an assignment make honours and the + // pattern anchored at the name, so the whole line read as neither an + // assignment nor a target and `@$(FMT)` stood unresolved: `make -n check` + // printed `script/check` and `script/fmt-check` while this file scored + // one. `override` reaches the same place by the same route. + it("expands a variable assigned through an export or override prefix", () => { + expect( + parseMakefile( + [ + "export FMT := script/fmt-check", + "check:", + "\t@$(FMT)", + "\t@script/check", + ].join("\n"), + ).get("check")?.recipe, + ).toEqual(["script/fmt-check", "script/check"]); + expect( + parseMakefile( + ["override FMT = script/fmt-check", "check:", "\t@$(FMT)"].join( + "\n", + ), + ).get("check")?.recipe, + ).toEqual(["script/fmt-check"]); + // `export` on its own line names a variable without assigning one, and + // is not read as an assignment of the empty string. + expect( + parseMakefile( + ["FMT := script/fmt-check", "export FMT", "check:", "\t@$(FMT)"].join( + "\n", + ), + ).get("check")?.recipe, + ).toEqual(["script/fmt-check"]); + }); + + // `?=` assigns only when the name has no value yet, so among assignments + // to one name the first `?=` wins where `:=` and `=` let the last one win. + // Reading every operator as last-wins resolved `@$(FMT)` to the value make + // discards: the file counted an invocation that never happens and missed + // the one that does, with the suite green either way. + it("keeps the first value when a later assignment is conditional", () => { + expect( + parseMakefile( + [ + "FMT := script/fmt-check", + "FMT ?= script/build", + "check:", + "\t@$(FMT)", + ].join("\n"), + ).get("check")?.recipe, + ).toEqual(["script/fmt-check"]); + // A `?=` that is itself the first assignment does assign, and a `?=` + // after it does not. + expect( + parseMakefile( + [ + "FMT ?= script/fmt-check", + "FMT ?= script/build", + "check:", + "\t@$(FMT)", + ].join("\n"), + ).get("check")?.recipe, + ).toEqual(["script/fmt-check"]); + // `:=` and `=` stay last-wins, which is what make does. + expect( + parseMakefile( + [ + "FMT := script/build", + "FMT := script/fmt-check", + "check:", + "\t@$(FMT)", + ].join("\n"), + ).get("check")?.recipe, + ).toEqual(["script/fmt-check"]); + }); + // The edges of the expansion, asserted so the header's not-followed list // is the code's behaviour rather than a claim about it. Each of these // leaves the reference standing verbatim, which reaches nothing — the same