From 5220cea223ef76cb2ad7d83b6e7191c3ebc10dd8 Mon Sep 17 00:00:00 2001 From: Matthieu Riegler Date: Fri, 29 Aug 2025 18:03:43 +0200 Subject: [PATCH] build: add a noDuplicateEnumValue rule (#63483) It caught several legitimate issues. In the cases I wasn't sure, I just disabled the rule. fixes #45843 PR Close #63483 --- goldens/public-api/common/errors.api.md | 4 +- goldens/public-api/router/errors.api.md | 2 +- packages/common/src/errors.ts | 4 +- packages/core/src/render3/interfaces/i18n.ts | 7 +++ .../core/src/render3/interfaces/injector.ts | 2 + .../core/src/render3/interfaces/styling.ts | 1 + packages/core/src/util/char_code.ts | 6 +-- packages/router/src/errors.ts | 2 +- tools/tslint/noDuplicateEnumValuesRule.ts | 54 +++++++++++++++++++ tslint.json | 1 + 10 files changed, 73 insertions(+), 10 deletions(-) create mode 100644 tools/tslint/noDuplicateEnumValuesRule.ts diff --git a/goldens/public-api/common/errors.api.md b/goldens/public-api/common/errors.api.md index 549af624e47..a63bc2014d6 100644 --- a/goldens/public-api/common/errors.api.md +++ b/goldens/public-api/common/errors.api.md @@ -21,7 +21,7 @@ export const enum RuntimeErrorCode { // (undocumented) INVALID_PIPE_ARGUMENT = 2100, // (undocumented) - INVALID_TO_DATE_CONVERSION = 2302, + INVALID_TO_DATE_CONVERSION = 2311, // (undocumented) LCP_IMG_MISSING_PRIORITY = 2955, // (undocumented) @@ -73,7 +73,7 @@ export const enum RuntimeErrorCode { // (undocumented) UNKNOWN_DATE_TYPE_VALUE = 2301, // (undocumented) - UNKNOWN_ZONE_WIDTH = 2302, + UNKNOWN_ZONE_WIDTH = 2310, // (undocumented) VALUE_NOT_A_NUMBER = 2309 } diff --git a/goldens/public-api/router/errors.api.md b/goldens/public-api/router/errors.api.md index 2a5f536ae3d..81f7d42e67c 100644 --- a/goldens/public-api/router/errors.api.md +++ b/goldens/public-api/router/errors.api.md @@ -19,7 +19,7 @@ export const enum RuntimeErrorCode { // (undocumented) INVALID_ROUTE_CONFIG = 4014, // (undocumented) - INVALID_ROUTER_LINK_INPUTS = 4016, + INVALID_ROUTER_LINK_INPUTS = 4017, // (undocumented) MISPLACED_OUTLETS_COMMAND = 4004, // (undocumented) diff --git a/packages/common/src/errors.ts b/packages/common/src/errors.ts index 575bfc7c5c9..a8da95d662f 100644 --- a/packages/common/src/errors.ts +++ b/packages/common/src/errors.ts @@ -28,8 +28,6 @@ export const enum RuntimeErrorCode { SUSPICIOUS_DATE_FORMAT = 2300, UNKNOWN_DATE_TYPE_VALUE = 2301, UNEXPECTED_TRANSLATION_TYPE = 2302, - UNKNOWN_ZONE_WIDTH = 2302, - INVALID_TO_DATE_CONVERSION = 2302, MISSING_EXTRA_LOCALE_DATA_FOR_LOCALE = 2303, LOCALE_DATA_UNDEFINED = 2304, INVALID_INTEGER_LITERAL = 2305, @@ -37,6 +35,8 @@ export const enum RuntimeErrorCode { INVALID_NUMBER_OF_DIGITS_AFTER_FRACTION = 2307, NO_PLURAL_MESSAGE_FOUND = 2308, VALUE_NOT_A_NUMBER = 2309, + UNKNOWN_ZONE_WIDTH = 2310, + INVALID_TO_DATE_CONVERSION = 2311, // Miscellaneous errors SCROLL_RESTORATION_UNSUPPORTED = 2400, diff --git a/packages/core/src/render3/interfaces/i18n.ts b/packages/core/src/render3/interfaces/i18n.ts index ee29eaf4c74..7588c968cde 100644 --- a/packages/core/src/render3/interfaces/i18n.ts +++ b/packages/core/src/render3/interfaces/i18n.ts @@ -43,6 +43,8 @@ export interface I18nRemoveOpCodes extends Array { * See: `I18nCreateOpCodes` for example of usage. */ export const enum IcuCreateOpCode { + /* tslint:disable:no-duplicate-enum-values */ + /** * Stores shift amount for bits 17-3 that contain reference index. */ @@ -72,6 +74,7 @@ export const enum IcuCreateOpCode { * Instruction to set the attribute of a node. */ Attr = 0b1, + /* tslint:enable:no-duplicate-enum-values */ } /** @@ -126,6 +129,7 @@ export interface IcuCreateOpCodes } export const enum I18nUpdateOpCode { + /* tslint:disable:no-duplicate-enum-values */ /** * Stores shift amount for bits 17-2 that contain reference index. */ @@ -151,6 +155,7 @@ export const enum I18nUpdateOpCode { * Instruction to update the current ICU case. */ IcuUpdate = 0b11, + /* tslint:enable:no-duplicate-enum-values */ } /** @@ -230,6 +235,7 @@ export interface I18nCreateOpCodes extends Array, I18nDebug { * See `I18nCreateOpCodes` */ export enum I18nCreateOpCode { + /* tslint:disable:no-duplicate-enum-values */ /** * Number of bits to shift index so that it can be combined with the `APPEND_EAGERLY` and * `COMMENT`. @@ -245,6 +251,7 @@ export enum I18nCreateOpCode { * If set the node should be comment (rather than a text) node. */ COMMENT = 0b10, + /* tslint:enable:no-duplicate-enum-values */ } /** diff --git a/packages/core/src/render3/interfaces/injector.ts b/packages/core/src/render3/interfaces/injector.ts index e7e2b2ec68b..ff10ad19a0e 100644 --- a/packages/core/src/render3/interfaces/injector.ts +++ b/packages/core/src/render3/interfaces/injector.ts @@ -52,9 +52,11 @@ import {LView, TData} from './view'; * ``` */ export const enum NodeInjectorOffset { + /* tslint:disable:no-duplicate-enum-values */ TNODE = 8, PARENT = 8, BLOOM_SIZE = 8, + /* tslint:enable: no-duplicate-enum-values */ SIZE = 9, } diff --git a/packages/core/src/render3/interfaces/styling.ts b/packages/core/src/render3/interfaces/styling.ts index b0b2f93c865..c4ee91ec12d 100644 --- a/packages/core/src/render3/interfaces/styling.ts +++ b/packages/core/src/render3/interfaces/styling.ts @@ -145,6 +145,7 @@ export const enum StylingRange { * have previous duplicate set. The implication is that if `width` binding becomes `null`, it is * necessary to defer the value to `map.width`. (Because `width` overwrites `map.width`.) */ + /* tslint:disable-next-line:no-duplicate-enum-values */ PREV_DUPLICATE = 0x02, /** diff --git a/packages/core/src/util/char_code.ts b/packages/core/src/util/char_code.ts index c3731c5b587..03e6b24aecf 100644 --- a/packages/core/src/util/char_code.ts +++ b/packages/core/src/util/char_code.ts @@ -19,7 +19,7 @@ export const enum CharCode { CLOSE_PAREN = 41, // ")" STAR = 42, // "*" SLASH = 47, // "/" - _0 = 48, // "0" + ZERO = 48, // "0" _1 = 49, // "1" _2 = 50, // "2" _3 = 51, // "3" @@ -28,15 +28,13 @@ export const enum CharCode { _6 = 54, // "6" _7 = 55, // "7" _8 = 56, // "8" - _9 = 57, // "9" + NINE = 57, // "9" COLON = 58, // ":" DASH = 45, // "-" UNDERSCORE = 95, // "_" SEMI_COLON = 59, // ";" BACK_SLASH = 92, // "\\" AT_SIGN = 64, // "@" - ZERO = 48, // "0" - NINE = 57, // "9" A = 65, // "A" U = 85, // "U" R = 82, // "R" diff --git a/packages/router/src/errors.ts b/packages/router/src/errors.ts index b1ac4615b57..37d08f5bf65 100644 --- a/packages/router/src/errors.ts +++ b/packages/router/src/errors.ts @@ -27,5 +27,5 @@ export const enum RuntimeErrorCode { INVALID_ROUTE_CONFIG = 4014, INVALID_ROOT_URL_SEGMENT = 4015, INFINITE_REDIRECT = 4016, - INVALID_ROUTER_LINK_INPUTS = 4016, + INVALID_ROUTER_LINK_INPUTS = 4017, } diff --git a/tools/tslint/noDuplicateEnumValuesRule.ts b/tools/tslint/noDuplicateEnumValuesRule.ts new file mode 100644 index 00000000000..f958038ac5a --- /dev/null +++ b/tools/tslint/noDuplicateEnumValuesRule.ts @@ -0,0 +1,54 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {RuleFailure} from 'tslint'; +import {TypedRule} from 'tslint/lib/rules'; +import ts from 'typescript'; + +/** + * Rule that detects duplicate enum values. + * + * This is useful to prevent collisons on ErrorCodes. + * + * ```ts + * enum E { + * A = 1, + * B = 1, // Wrong! + * } + * ``` + */ +export class Rule extends TypedRule { + override applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): RuleFailure[] { + const checker = program.getTypeChecker(); + + return this.applyWithFunction(sourceFile, (ctx) => { + ts.forEachChild(sourceFile, function walk(node) { + if (ts.isEnumDeclaration(node)) { + const seenValues = new Map(); + for (const member of node.members) { + const value = checker.getConstantValue(member); + if (value !== undefined) { + if (seenValues.has(value)) { + ctx.addFailureAtNode( + member, + `Enum member has a duplicate value '${value}'. First occurrence is on line ${ + sourceFile.getLineAndCharacterOfPosition(seenValues.get(value)!.getStart()) + .line + 1 + }.`, + ); + } else { + seenValues.set(value, member); + } + } + } + } + ts.forEachChild(node, walk); + }); + }); + } +} diff --git a/tslint.json b/tslint.json index 8e296e2771e..3aa6b1bbaad 100644 --- a/tslint.json +++ b/tslint.json @@ -11,6 +11,7 @@ "ts-node-loader": true, // Custom rules written in TypeScript. "no-exported-inferred-call-type": true, + "no-duplicate-enum-values": true, "require-internal-with-underscore": true, "no-implicit-override-abstract": true, "validate-import-for-esm-cjs-interop": [