Rule
no-duplicate-constant-values (eslint-factory/src/rules/no-duplicate-constant-values.ts) — first review since it shipped; no prior issues filed.
What the rule does
Groups module-level const NAME = <static primitive> declarations by value and reports later duplicates once a value's group reaches a minimum size. The thresholds are deliberately asymmetric, and the code comment explains why:
const MIN_NUMERIC_DUPLICATE_GROUP_SIZE = 3;
// Booleans only have 2 possible values module-wide, an even smaller space than "small numbers",
// so unrelated constants coincidentally sharing `true`/`false` are at least as likely as the
// numeric case above. Apply the same minimum-group-size guard to avoid false positives.
const MIN_BOOLEAN_DUPLICATE_GROUP_SIZE = 3;
Everything else (strings, template literals, regexps — and, by omission, null) falls through to the generic minGroupSize = 2 in Program:exit:
const minGroupSize = valueKey.startsWith("number:") ? MIN_NUMERIC_DUPLICATE_GROUP_SIZE : valueKey.startsWith("boolean:") ? MIN_BOOLEAN_DUPLICATE_GROUP_SIZE : 2;
The gap
getStaticValueKey maps a null literal to `${typeof node.value}:${String(node.value)}` i.e. "object:null", which isn't number:/boolean:-prefixed, so it gets the generic threshold of 2. But null has exactly one possible value — a strictly smaller value space than booleans (2 values, which the rule already guards at 3) or "small numbers" (the numeric guard's stated justification). By the rule's own reasoning for the boolean guard, null is more prone to coincidental, semantically-unrelated matches than booleans are, yet it's the one primitive type left without a raised threshold. Two unrelated placeholder/not-yet-initialized constants such as:
const CACHED_CLIENT = null;
const PENDING_HANDLE = null;
would be flagged as "duplicating" each other at group size 2, which is exactly the kind of coincidental-match false positive the boolean guard was added to prevent. No live occurrence was found in actions/setup/js/**/*.cjs today (grep found no module-level const X = null; declarations), so this is a soundness/consistency gap rather than a currently-firing false positive — same category as the previously-filed, ungrounded-but-accepted require-fs-sync-try-catch catch-less-try/finally finding.
Suggested fix
Either add a MIN_NULL_DUPLICATE_GROUP_SIZE (align it with the boolean guard, i.e. 3, or higher since the value space is smaller still), or exclude null/undefined-valued constants from this rule entirely on the grounds that reusing null as a sentinel initializer across unrelated constants is idiomatic and carries no useful signal.
Acceptance criteria
Generated by 🤖 ESLint Refiner · agent · 224.5 AIC · ⌖ 40.7 AIC · ⊞ 4.9K · ◷
Rule
no-duplicate-constant-values(eslint-factory/src/rules/no-duplicate-constant-values.ts) — first review since it shipped; no prior issues filed.What the rule does
Groups module-level
const NAME = <static primitive>declarations by value and reports later duplicates once a value's group reaches a minimum size. The thresholds are deliberately asymmetric, and the code comment explains why:Everything else (strings, template literals, regexps — and, by omission,
null) falls through to the genericminGroupSize = 2inProgram:exit:The gap
getStaticValueKeymaps anullliteral to`${typeof node.value}:${String(node.value)}`i.e."object:null", which isn'tnumber:/boolean:-prefixed, so it gets the generic threshold of 2. Butnullhas exactly one possible value — a strictly smaller value space than booleans (2 values, which the rule already guards at 3) or "small numbers" (the numeric guard's stated justification). By the rule's own reasoning for the boolean guard,nullis more prone to coincidental, semantically-unrelated matches than booleans are, yet it's the one primitive type left without a raised threshold. Two unrelated placeholder/not-yet-initialized constants such as:would be flagged as "duplicating" each other at group size 2, which is exactly the kind of coincidental-match false positive the boolean guard was added to prevent. No live occurrence was found in
actions/setup/js/**/*.cjstoday (grep found no module-levelconst X = null;declarations), so this is a soundness/consistency gap rather than a currently-firing false positive — same category as the previously-filed, ungrounded-but-acceptedrequire-fs-sync-try-catchcatch-less-try/finally finding.Suggested fix
Either add a
MIN_NULL_DUPLICATE_GROUP_SIZE(align it with the boolean guard, i.e. 3, or higher since the value space is smaller still), or excludenull/undefined-valued constants from this rule entirely on the grounds that reusingnullas a sentinel initializer across unrelated constants is idiomatic and carries no useful signal.Acceptance criteria
constdeclarations both initialized tonullat group size 2 are NOT reported.nullin scope with a raised threshold, add an invalid-case test at the new minimum group size (mirroring the existing boolean test structure).