WIP: fix export/override prefix and ?= first-wins in make variable parser
All checks were successful
check / check (push) Successful in 14s

Two defects in the lint-once resolver's make variable handling:

- The assignment pattern anchored at the start of the name, so
  `export FMT := script/fmt-check` was never collected and `@$(FMT)`
  went unresolved. An optional `export `/`override ` prefix is now
  allowed.
- `?=` assigns only when the name is unset, so the first assignment
  wins. The parser called .set() unconditionally, letting a later
  `FMT ?= script/build` overwrite an earlier `FMT := script/fmt-check`
  and resolve to a command make never runs.

Tests pinning both to follow.
This commit is contained in:
user
2026-09-04 11:17:32 +00:00
parent 075b1bb921
commit 18226d345b

View File

@@ -159,7 +159,7 @@ const MAKE_CONDITIONAL = /^\s*(?:ifeq|ifneq|ifdef|ifndef|else|endif)\b/;
// not happen. An unresolved reference is left standing verbatim instead, which // not happen. An unresolved reference is left standing verbatim instead, which
// is the same dead end as any other unfollowed edge rather than a wrong answer. // is the same dead end as any other unfollowed edge rather than a wrong answer.
const MAKE_ASSIGNMENT = new RegExp( const MAKE_ASSIGNMENT = new RegExp(
`^([A-Za-z_][A-Za-z0-9_]*)\\s*(?::=|\\?=|=)\\s*(.*)$`, `^(?:(?:export|override)\\s+)*([A-Za-z_][A-Za-z0-9_]*)\\s*(:=|\\?=|=)\\s*(.*)$`,
); );
const MAKE_VARIABLE_REFERENCE = /\$[({]([A-Za-z_][A-Za-z0-9_]*)[)}]/g; const MAKE_VARIABLE_REFERENCE = /\$[({]([A-Za-z_][A-Za-z0-9_]*)[)}]/g;
@@ -181,8 +181,13 @@ const makeVariables = (text: string): Map<string, string> => {
if (inDefine || raw.startsWith("\t")) continue; if (inDefine || raw.startsWith("\t")) continue;
const assignment = MAKE_ASSIGNMENT.exec(raw.replace(/#.*$/, "").trim()); const assignment = MAKE_ASSIGNMENT.exec(raw.replace(/#.*$/, "").trim());
if (assignment === null) continue; if (assignment === null) continue;
const [, name, value] = assignment; const [, name, operator, value] = assignment;
if (name === undefined || value === undefined) continue; if (name === undefined || operator === undefined) continue;
if (value === undefined) continue;
// `?=` assigns only when the name has no value yet, so the first one
// wins where `:=` and `=` let the last one win. Overwriting here would
// resolve the reference to a string make never uses.
if (operator === "?=" && variables.has(name)) continue;
// A value that is itself a reference is the recursive case, and is not // A value that is itself a reference is the recursive case, and is not
// resolved. Recording it would hand the substitution below a string it // resolved. Recording it would hand the substitution below a string it
// cannot finish expanding. // cannot finish expanding.