Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion eslint-factory/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ This project hosts custom ESLint linters for `/actions/setup/js`.

Inventory module-level `const` declarations with static primitive initializers and report each declaration after the first one that uses the same value in a file. The diagnostic names both constants and shows the duplicated value.

The rule compares string, number, boolean, `null`, bigint, regular-expression, static template-literal, and signed numeric initializers. To avoid collisions in their small value spaces, it reports duplicate numeric and boolean values only when at least three module-level constants share them. Dynamic expressions, object and array literals, destructuring declarations, function-local declarations, and `let` or `var` declarations are ignored.
The rule compares string, number, boolean, `null`, bigint, regular-expression, static template-literal, and signed numeric initializers. To avoid collisions in their small value spaces, it reports duplicate numeric, boolean, and `null` values only when at least three module-level constants share them. Dynamic expressions, object and array literals, destructuring declarations, function-local declarations, and `let` or `var` declarations are ignored.

### `no-github-request-interpolated-route`

Expand Down
16 changes: 16 additions & 0 deletions eslint-factory/src/rules/no-duplicate-constant-values.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ describe("no-duplicate-constant-values", () => {
`const DEFAULT_HTTP_TIMEOUT_MS = 15000; const TOOL_CALL_TIMEOUT_BUFFER_MS = 15000; const NOTIFY_TIMEOUT_MS = 10000; const KEEPALIVE_PING_INTERVAL_MS = 10000;`,
`const FIRST_ENABLED = true; const SECOND_ENABLED = true;`,
`const FIRST_ENABLED = false; const SECOND_ENABLED = false;`,
`const FIRST_VALUE = null; const SECOND_VALUE = null;`,
],
invalid: [],
});
Expand Down Expand Up @@ -87,6 +88,21 @@ describe("no-duplicate-constant-values", () => {
});
});

it("requires at least three matching null constants before reporting duplicates", () => {
ruleTester.run("no-duplicate-constant-values", noDuplicateConstantValuesRule, {
valid: [`const FIRST_VALUE = null; const SECOND_VALUE = null;`],
invalid: [
{
code: `const FIRST_VALUE = null; const SECOND_VALUE = null; const THIRD_VALUE = null;`,
errors: [
{ messageId: "duplicateConstantValue", data: { name: "SECOND_VALUE", originalName: "FIRST_VALUE", value: "null" } },
{ messageId: "duplicateConstantValue", data: { name: "THIRD_VALUE", originalName: "FIRST_VALUE", value: "null" } },
],
},
],
});
});

it("reports every duplicate after the first declaration", () => {
ruleTester.run("no-duplicate-constant-values", noDuplicateConstantValuesRule, {
valid: [],
Expand Down
14 changes: 13 additions & 1 deletion eslint-factory/src/rules/no-duplicate-constant-values.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ const MIN_NUMERIC_DUPLICATE_GROUP_SIZE = 3;
// 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;
// `null` has exactly 1 possible value, an even smaller space than booleans, so unrelated
// placeholder or not-yet-initialized constants coincidentally sharing `null` are at least as
// likely as the boolean case above. Apply the same minimum-group-size guard.
const MIN_NULL_DUPLICATE_GROUP_SIZE = 3;
const NULL_VALUE_KEY = "object:null";

function getStaticValueKey(node: TSESTree.Expression): string | null {
if (node.type === AST_NODE_TYPES.Literal) {
Expand All @@ -32,6 +37,13 @@ function getStaticValueKey(node: TSESTree.Expression): string | null {
return null;
}

function getMinDuplicateGroupSize(valueKey: string): number {
if (valueKey.startsWith("number:")) return MIN_NUMERIC_DUPLICATE_GROUP_SIZE;
if (valueKey.startsWith("boolean:")) return MIN_BOOLEAN_DUPLICATE_GROUP_SIZE;
if (valueKey === NULL_VALUE_KEY) return MIN_NULL_DUPLICATE_GROUP_SIZE;
return 2;
}

export const noDuplicateConstantValuesRule = createRule({
name: "no-duplicate-constant-values",
meta: {
Expand Down Expand Up @@ -83,7 +95,7 @@ export const noDuplicateConstantValuesRule = createRule({
},
"Program:exit"() {
for (const [valueKey, declarations] of constantsByValue) {
const minGroupSize = valueKey.startsWith("number:") ? MIN_NUMERIC_DUPLICATE_GROUP_SIZE : valueKey.startsWith("boolean:") ? MIN_BOOLEAN_DUPLICATE_GROUP_SIZE : 2;
const minGroupSize = getMinDuplicateGroupSize(valueKey);
const shouldReportDuplicates = declarations.length >= minGroupSize;
if (!shouldReportDuplicates) continue;

Expand Down
Loading