diff --git a/eslint-factory/src/rules/no-duplicate-constant-values.test.ts b/eslint-factory/src/rules/no-duplicate-constant-values.test.ts index ff19cfb6cb2..4c3f8bd6b28 100644 --- a/eslint-factory/src/rules/no-duplicate-constant-values.test.ts +++ b/eslint-factory/src/rules/no-duplicate-constant-values.test.ts @@ -23,6 +23,10 @@ describe("no-duplicate-constant-values", () => { `let first = "same"; let second = "same";`, `const { first, second } = value;`, `function first() { const VALUE = "same"; } function second() { const VALUE = "same"; }`, + `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 A_PREFIX_LENGTH = 2; const B_PREFIX_LENGTH = 2;`, + `const DEFAULT_HTTP_TIMEOUT_MS = 15000; const TOOL_CALL_TIMEOUT_BUFFER_MS = 15000; const NOTIFY_TIMEOUT_MS = 10000; const KEEPALIVE_PING_INTERVAL_MS = 10000;`, ], invalid: [], }); @@ -41,8 +45,11 @@ describe("no-duplicate-constant-values", () => { errors: [{ messageId: "duplicateConstantValue", data: { name: "SECOND", originalName: "FIRST", value: `"same"` } }], }, { - code: `const FIRST = -42; const SECOND = -42; const THIRD = 42;`, - errors: [{ messageId: "duplicateConstantValue", data: { name: "SECOND", originalName: "FIRST", value: "-42" } }], + code: `const FIRST = -42; const SECOND = -42; const THIRD = -42;`, + errors: [ + { messageId: "duplicateConstantValue", data: { name: "SECOND", originalName: "FIRST", value: "-42" } }, + { messageId: "duplicateConstantValue", data: { name: "THIRD", originalName: "FIRST", value: "-42" } }, + ], }, { code: `const FIRST = /value/gi; const SECOND = /value/gi;`, @@ -56,6 +63,13 @@ describe("no-duplicate-constant-values", () => { }); }); + it("requires at least three matching numeric constants before reporting duplicates", () => { + ruleTester.run("no-duplicate-constant-values", noDuplicateConstantValuesRule, { + valid: [`const FIRST = -42; const SECOND = -42;`], + invalid: [], + }); + }); + it("reports every duplicate after the first declaration", () => { ruleTester.run("no-duplicate-constant-values", noDuplicateConstantValuesRule, { valid: [], diff --git a/eslint-factory/src/rules/no-duplicate-constant-values.ts b/eslint-factory/src/rules/no-duplicate-constant-values.ts index 0eeef5c5722..3fa4907054c 100644 --- a/eslint-factory/src/rules/no-duplicate-constant-values.ts +++ b/eslint-factory/src/rules/no-duplicate-constant-values.ts @@ -4,8 +4,11 @@ const createRule = ESLintUtils.RuleCreator(name => `https://github.com/github/gh interface ConstantDeclaration { name: string; + declaration: TSESTree.VariableDeclarator; } +const MIN_NUMERIC_DUPLICATE_GROUP_SIZE = 3; + function getStaticValueKey(node: TSESTree.Expression): string | null { if (node.type === AST_NODE_TYPES.Literal) { if ("regex" in node && node.regex) { @@ -39,7 +42,7 @@ export const noDuplicateConstantValuesRule = createRule({ }, defaultOptions: [], create(context) { - const constantsByValue = new Map(); + const constantsByValue = new Map(); return { VariableDeclaration(node) { @@ -57,25 +60,42 @@ export const noDuplicateConstantValuesRule = createRule({ continue; } - const original = constantsByValue.get(valueKey); - if (!original) { - constantsByValue.set(valueKey, { - name: declaration.id.name, - }); + const declarationsForValue = constantsByValue.get(valueKey); + if (!declarationsForValue) { + constantsByValue.set(valueKey, [ + { + name: declaration.id.name, + declaration, + }, + ]); continue; } - context.report({ - node: declaration, - messageId: "duplicateConstantValue", - data: { - name: declaration.id.name, - originalName: original.name, - value: context.sourceCode.getText(declaration.init), - }, + declarationsForValue.push({ + name: declaration.id.name, + declaration, }); } }, + "Program:exit"() { + for (const [valueKey, declarations] of constantsByValue) { + const shouldReportDuplicates = declarations.length > 1 && (!valueKey.startsWith("number:") || declarations.length >= MIN_NUMERIC_DUPLICATE_GROUP_SIZE); + if (!shouldReportDuplicates) continue; + + const original = declarations[0]; + for (const duplicate of declarations.slice(1)) { + context.report({ + node: duplicate.declaration, + messageId: "duplicateConstantValue", + data: { + name: duplicate.name, + originalName: original.name, + value: context.sourceCode.getText(duplicate.declaration.init!), + }, + }); + } + } + }, }; }, });