eslint-plugin-class-validator-type-match 3.1.3 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,48 +1,22 @@
1
+ import type { TSESLint } from '@typescript-eslint/utils';
1
2
  declare const _default: {
3
+ meta: {
4
+ name: string;
5
+ version: string;
6
+ };
2
7
  rules: {
3
- 'decorator-type-match': import("@typescript-eslint/utils/dist/ts-eslint").RuleModule<"mismatch" | "enumMismatch" | "invalidEachOption", [], unknown, import("@typescript-eslint/utils/dist/ts-eslint").RuleListener>;
4
- 'optional-decorator-match': import("@typescript-eslint/utils/dist/ts-eslint").RuleModule<"missingOptionalDecorator" | "missingOptionalSyntax" | "conflictingDefiniteAssignment" | "undefinedUnionWithoutDecorator" | "undefinedUnionWithoutOptional" | "nullUnionIncorrect" | "redundantUndefinedInType", [{
8
+ 'decorator-type-match': TSESLint.RuleModule<"mismatch" | "enumMismatch" | "invalidEachOption", [], unknown, TSESLint.RuleListener>;
9
+ 'optional-decorator-match': TSESLint.RuleModule<"missingOptionalDecorator" | "missingOptionalSyntax" | "conflictingDefiniteAssignment" | "undefinedUnionWithoutDecorator" | "undefinedUnionWithoutOptional" | "nullUnionIncorrect" | "redundantUndefinedInType", [{
5
10
  strictNullChecks?: boolean;
6
11
  checkDefaultValues?: boolean;
7
12
  customOptionalDecorators?: string[];
8
- }], unknown, import("@typescript-eslint/utils/dist/ts-eslint").RuleListener>;
9
- 'validate-nested-match': import("@typescript-eslint/utils/dist/ts-eslint").RuleModule<"nestedArrayMismatch" | "missingValidateNested" | "missingEachOption" | "unnecessaryValidateNested" | "tupleValidationWarning" | "multiTypeUnionWarning" | "mixedComplexityUnionWarning" | "pickOmitWarning", [], unknown, import("@typescript-eslint/utils/dist/ts-eslint").RuleListener>;
10
- 'type-decorator-match': import("@typescript-eslint/utils/dist/ts-eslint").RuleModule<"typeMismatch" | "missingTypeDecorator", [], unknown, import("@typescript-eslint/utils/dist/ts-eslint").RuleListener>;
11
- 'definite-assignment-match': import("@typescript-eslint/utils/dist/ts-eslint").RuleModule<"incorrectDefiniteAssignment", [], unknown, import("@typescript-eslint/utils/dist/ts-eslint").RuleListener>;
12
- 'dto-filename-match': import("@typescript-eslint/utils/dist/ts-eslint").RuleModule<"incorrectDtoClassName", [], unknown, import("@typescript-eslint/utils/dist/ts-eslint").RuleListener>;
13
- };
14
- configs: {
15
- recommended: {
16
- plugins: string[];
17
- rules: {
18
- 'class-validator-type-match/decorator-type-match': string;
19
- 'class-validator-type-match/optional-decorator-match': string;
20
- 'class-validator-type-match/validate-nested-match': string;
21
- 'class-validator-type-match/type-decorator-match': string;
22
- 'class-validator-type-match/definite-assignment-match': string;
23
- 'class-validator-type-match/dto-filename-match': string;
24
- };
25
- };
26
- strict: {
27
- plugins: string[];
28
- rules: {
29
- 'class-validator-type-match/decorator-type-match': string;
30
- 'class-validator-type-match/optional-decorator-match': string;
31
- 'class-validator-type-match/validate-nested-match': string;
32
- 'class-validator-type-match/type-decorator-match': string;
33
- 'class-validator-type-match/definite-assignment-match': string;
34
- 'class-validator-type-match/dto-filename-match': string;
35
- };
36
- };
37
- basic: {
38
- plugins: string[];
39
- rules: {
40
- 'class-validator-type-match/decorator-type-match': string;
41
- 'class-validator-type-match/optional-decorator-match': string;
42
- 'class-validator-type-match/definite-assignment-match': string;
43
- 'class-validator-type-match/dto-filename-match': string;
44
- };
45
- };
13
+ }], unknown, TSESLint.RuleListener>;
14
+ 'validate-nested-match': TSESLint.RuleModule<"nestedArrayMismatch" | "missingValidateNested" | "missingEachOption" | "unnecessaryValidateNested" | "tupleValidationWarning" | "multiTypeUnionWarning" | "mixedComplexityUnionWarning" | "pickOmitWarning", [], unknown, TSESLint.RuleListener>;
15
+ 'type-decorator-match': TSESLint.RuleModule<"typeMismatch" | "missingTypeDecorator", [], unknown, TSESLint.RuleListener>;
16
+ 'definite-assignment-match': TSESLint.RuleModule<"incorrectDefiniteAssignment", [], unknown, TSESLint.RuleListener>;
17
+ 'dto-filename-match': TSESLint.RuleModule<"incorrectDtoClassName", [], unknown, TSESLint.RuleListener>;
46
18
  };
19
+ configs: Record<string, TSESLint.ClassicConfig.Config>;
20
+ flatConfigs: TSESLint.FlatConfig.SharedConfigs;
47
21
  };
48
22
  export = _default;
package/dist/index.js CHANGED
@@ -8,46 +8,102 @@ const validate_nested_match_1 = __importDefault(require("./rules/validate-nested
8
8
  const type_decorator_match_1 = __importDefault(require("./rules/type-decorator-match"));
9
9
  const definite_assignment_match_1 = __importDefault(require("./rules/definite-assignment-match"));
10
10
  const dto_filename_match_1 = __importDefault(require("./rules/dto-filename-match"));
11
- module.exports = {
12
- rules: {
13
- 'decorator-type-match': decorator_type_match_1.default,
14
- 'optional-decorator-match': optional_decorator_match_1.default,
15
- 'validate-nested-match': validate_nested_match_1.default,
16
- 'type-decorator-match': type_decorator_match_1.default,
17
- 'definite-assignment-match': definite_assignment_match_1.default,
18
- 'dto-filename-match': dto_filename_match_1.default,
11
+ const package_json_1 = require("../package.json");
12
+ const rules = {
13
+ 'decorator-type-match': decorator_type_match_1.default,
14
+ 'optional-decorator-match': optional_decorator_match_1.default,
15
+ 'validate-nested-match': validate_nested_match_1.default,
16
+ 'type-decorator-match': type_decorator_match_1.default,
17
+ 'definite-assignment-match': definite_assignment_match_1.default,
18
+ 'dto-filename-match': dto_filename_match_1.default,
19
+ };
20
+ const plugin = {
21
+ meta: {
22
+ name: package_json_1.name,
23
+ version: package_json_1.version,
24
+ },
25
+ rules,
26
+ };
27
+ // Legacy configs for .eslintrc (extends: ['plugin:class-validator-type-match/recommended'])
28
+ const legacyConfigs = {
29
+ recommended: {
30
+ plugins: ['class-validator-type-match'],
31
+ rules: {
32
+ 'class-validator-type-match/decorator-type-match': 'error',
33
+ 'class-validator-type-match/optional-decorator-match': 'error',
34
+ 'class-validator-type-match/validate-nested-match': 'error',
35
+ 'class-validator-type-match/type-decorator-match': 'error',
36
+ 'class-validator-type-match/definite-assignment-match': 'error',
37
+ 'class-validator-type-match/dto-filename-match': 'error',
38
+ },
19
39
  },
20
- configs: {
21
- recommended: {
22
- plugins: ['class-validator-type-match'],
23
- rules: {
24
- 'class-validator-type-match/decorator-type-match': 'error',
25
- 'class-validator-type-match/optional-decorator-match': 'error',
26
- 'class-validator-type-match/validate-nested-match': 'error',
27
- 'class-validator-type-match/type-decorator-match': 'error',
28
- 'class-validator-type-match/definite-assignment-match': 'error',
29
- 'class-validator-type-match/dto-filename-match': 'error',
30
- },
31
- },
32
- strict: {
33
- plugins: ['class-validator-type-match'],
34
- rules: {
35
- 'class-validator-type-match/decorator-type-match': 'error',
36
- 'class-validator-type-match/optional-decorator-match': 'error',
37
- 'class-validator-type-match/validate-nested-match': 'error',
38
- 'class-validator-type-match/type-decorator-match': 'error',
39
- 'class-validator-type-match/definite-assignment-match': 'error',
40
- 'class-validator-type-match/dto-filename-match': 'error',
41
- },
42
- },
43
- basic: {
44
- plugins: ['class-validator-type-match'],
45
- rules: {
46
- 'class-validator-type-match/decorator-type-match': 'error',
47
- 'class-validator-type-match/optional-decorator-match': 'error',
48
- 'class-validator-type-match/definite-assignment-match': 'error',
49
- 'class-validator-type-match/dto-filename-match': 'error',
50
- },
40
+ strict: {
41
+ plugins: ['class-validator-type-match'],
42
+ rules: {
43
+ 'class-validator-type-match/decorator-type-match': 'error',
44
+ 'class-validator-type-match/optional-decorator-match': 'error',
45
+ 'class-validator-type-match/validate-nested-match': 'error',
46
+ 'class-validator-type-match/type-decorator-match': 'error',
47
+ 'class-validator-type-match/definite-assignment-match': 'error',
48
+ 'class-validator-type-match/dto-filename-match': 'error',
51
49
  },
52
50
  },
51
+ basic: {
52
+ plugins: ['class-validator-type-match'],
53
+ rules: {
54
+ 'class-validator-type-match/decorator-type-match': 'error',
55
+ 'class-validator-type-match/optional-decorator-match': 'error',
56
+ 'class-validator-type-match/definite-assignment-match': 'error',
57
+ 'class-validator-type-match/dto-filename-match': 'error',
58
+ },
59
+ },
60
+ };
61
+ // Flat configs for eslint.config.ts (classValidatorTypeMatch.configs.recommended)
62
+ const flatConfigs = {
63
+ recommended: {
64
+ name: 'class-validator-type-match/recommended',
65
+ plugins: {
66
+ 'class-validator-type-match': plugin,
67
+ },
68
+ rules: {
69
+ 'class-validator-type-match/decorator-type-match': 'error',
70
+ 'class-validator-type-match/optional-decorator-match': 'error',
71
+ 'class-validator-type-match/validate-nested-match': 'error',
72
+ 'class-validator-type-match/type-decorator-match': 'error',
73
+ 'class-validator-type-match/definite-assignment-match': 'error',
74
+ 'class-validator-type-match/dto-filename-match': 'error',
75
+ },
76
+ },
77
+ strict: {
78
+ name: 'class-validator-type-match/strict',
79
+ plugins: {
80
+ 'class-validator-type-match': plugin,
81
+ },
82
+ rules: {
83
+ 'class-validator-type-match/decorator-type-match': 'error',
84
+ 'class-validator-type-match/optional-decorator-match': 'error',
85
+ 'class-validator-type-match/validate-nested-match': 'error',
86
+ 'class-validator-type-match/type-decorator-match': 'error',
87
+ 'class-validator-type-match/definite-assignment-match': 'error',
88
+ 'class-validator-type-match/dto-filename-match': 'error',
89
+ },
90
+ },
91
+ basic: {
92
+ name: 'class-validator-type-match/basic',
93
+ plugins: {
94
+ 'class-validator-type-match': plugin,
95
+ },
96
+ rules: {
97
+ 'class-validator-type-match/decorator-type-match': 'error',
98
+ 'class-validator-type-match/optional-decorator-match': 'error',
99
+ 'class-validator-type-match/definite-assignment-match': 'error',
100
+ 'class-validator-type-match/dto-filename-match': 'error',
101
+ },
102
+ },
103
+ };
104
+ module.exports = {
105
+ meta: plugin.meta,
106
+ rules,
107
+ configs: legacyConfigs,
108
+ flatConfigs,
53
109
  };
@@ -73,7 +73,7 @@ exports.default = createRule({
73
73
  let checker = null;
74
74
  let esTreeNodeMap = null;
75
75
  try {
76
- const parserServices = context.parserServices;
76
+ const parserServices = context.sourceCode.parserServices;
77
77
  if (parserServices?.program && parserServices?.esTreeNodeToTSNodeMap) {
78
78
  checker = parserServices.program.getTypeChecker();
79
79
  esTreeNodeMap = parserServices.esTreeNodeToTSNodeMap;
@@ -131,7 +131,9 @@ exports.default = createRule({
131
131
  const enumArg = (0, type_helpers_util_1.getIsEnumArgument)(isEnumDecorator);
132
132
  const typeName = (0, type_helpers_util_1.getTypeReferenceName)(typeAnnotation.typeName);
133
133
  // For TypeScript enum references, the argument should match the type
134
- if (enumArg && enumArg !== typeName) {
134
+ // Skip this check if the argument is an array of enum values (subset pattern)
135
+ const isArraySubset = (0, type_helpers_util_1.isEnumArgumentArraySubset)(isEnumDecorator, checker, esTreeNodeMap);
136
+ if (enumArg && enumArg !== typeName && !isArraySubset) {
135
137
  context.report({
136
138
  node,
137
139
  messageId: 'enumMismatch',
@@ -128,6 +128,20 @@ export declare function isUnionEnumType(typeNode: TSESTree.TypeNode): boolean;
128
128
  * Handles both simple identifiers (MyEnum) and member expressions (MyNamespace.MyEnum).
129
129
  */
130
130
  export declare function getIsEnumArgument(decorator: TSESTree.Decorator): string | null;
131
+ /**
132
+ * Checks if the @IsEnum decorator argument is an array of enum values.
133
+ * This is a valid class-validator pattern for restricting validation to a subset of enum values.
134
+ *
135
+ * Example:
136
+ * const WEIGHT_UNITS = [Unit.G, Unit.KG] as const;
137
+ * @IsEnum(WEIGHT_UNITS)
138
+ * unit?: Unit;
139
+ *
140
+ * Uses TypeScript type checker to determine if the argument resolves to an array/tuple type.
141
+ */
142
+ export declare function isEnumArgumentArraySubset(decorator: TSESTree.Decorator, checker: ts.TypeChecker | null, esTreeNodeMap: {
143
+ get(key: TSESTree.Node): ts.Node | undefined;
144
+ } | null): boolean;
131
145
  /**
132
146
  * Extracts the class name from @Type(() => ClassName) decorator.
133
147
  * Supports both arrow functions and function expressions.
@@ -18,6 +18,7 @@ exports.isArrayType = isArrayType;
18
18
  exports.isTupleType = isTupleType;
19
19
  exports.isUnionEnumType = isUnionEnumType;
20
20
  exports.getIsEnumArgument = getIsEnumArgument;
21
+ exports.isEnumArgumentArraySubset = isEnumArgumentArraySubset;
21
22
  exports.getTypeDecoratorClassName = getTypeDecoratorClassName;
22
23
  exports.hasEachOption = hasEachOption;
23
24
  exports.hasValidateNestedEachOption = hasValidateNestedEachOption;
@@ -670,6 +671,46 @@ function getIsEnumArgument(decorator) {
670
671
  }
671
672
  return null;
672
673
  }
674
+ /**
675
+ * Checks if the @IsEnum decorator argument is an array of enum values.
676
+ * This is a valid class-validator pattern for restricting validation to a subset of enum values.
677
+ *
678
+ * Example:
679
+ * const WEIGHT_UNITS = [Unit.G, Unit.KG] as const;
680
+ * @IsEnum(WEIGHT_UNITS)
681
+ * unit?: Unit;
682
+ *
683
+ * Uses TypeScript type checker to determine if the argument resolves to an array/tuple type.
684
+ */
685
+ function isEnumArgumentArraySubset(decorator, checker, esTreeNodeMap) {
686
+ if (!checker || !esTreeNodeMap) {
687
+ return false;
688
+ }
689
+ if (decorator.expression.type !== 'CallExpression' || decorator.expression.arguments.length === 0) {
690
+ return false;
691
+ }
692
+ const firstArg = decorator.expression.arguments[0];
693
+ // Get the TypeScript node for the argument
694
+ const tsNode = esTreeNodeMap.get(firstArg);
695
+ if (!tsNode) {
696
+ return false;
697
+ }
698
+ try {
699
+ const type = checker.getTypeAtLocation(tsNode);
700
+ // Check if it's an array type
701
+ if (checker.isArrayType(type)) {
702
+ return true;
703
+ }
704
+ // Check if it's a tuple type (for `as const` arrays)
705
+ if (checker.isTupleType(type)) {
706
+ return true;
707
+ }
708
+ return false;
709
+ }
710
+ catch {
711
+ return false;
712
+ }
713
+ }
673
714
  /**
674
715
  * Extracts the class name from @Type(() => ClassName) decorator.
675
716
  * Supports both arrow functions and function expressions.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-class-validator-type-match",
3
- "version": "3.1.3",
3
+ "version": "4.1.0",
4
4
  "description": "ESLint plugin to ensure class-validator decorators match TypeScript type annotations",
5
5
  "keywords": [
6
6
  "eslint",
@@ -26,29 +26,35 @@
26
26
  "lint": "eslint \"src/**/*.ts\"",
27
27
  "lint:fix": "eslint \"src/**/*.ts\" --fix",
28
28
  "format": "prettier --write \"**/*.{ts,md}\"",
29
+ "test": "vitest run",
30
+ "test:watch": "vitest",
31
+ "test:coverage": "vitest run --coverage",
29
32
  "prepare": "husky"
30
33
  },
31
34
  "peerDependencies": {
32
- "@typescript-eslint/parser": "^6.0.0 || ^7.0.0 || ^8.0.0",
33
- "eslint": "^8.0.0 || ^9.0.0"
35
+ "@typescript-eslint/parser": "8.46.0",
36
+ "eslint": "9.37.0"
34
37
  },
35
38
  "dependencies": {
36
- "@typescript-eslint/utils": "^8.0.0"
39
+ "@typescript-eslint/utils": "8.46.0"
37
40
  },
38
41
  "devDependencies": {
39
- "@commitlint/cli": "20.1.0",
40
- "@commitlint/config-conventional": "20.0.0",
41
- "@eslint/js": "9.37.0",
42
- "@typescript-eslint/parser": "8.46.0",
43
- "eslint": "9.37.0",
44
- "globals": "16.4.0",
42
+ "@commitlint/cli": "20.3.1",
43
+ "@commitlint/config-conventional": "20.3.1",
44
+ "@eslint/js": "9.39.2",
45
+ "@typescript-eslint/parser": "8.53.0",
46
+ "@typescript-eslint/rule-tester": "8.46.0",
47
+ "@vitest/coverage-v8": "4.0.17",
48
+ "eslint": "9.39.2",
49
+ "globals": "16.5.0",
45
50
  "husky": "9.1.7",
46
- "lint-staged": "16.2.4",
47
- "prettier": "3.6.2",
48
- "typescript": "^5.6.0",
49
- "typescript-eslint": "8.46.0"
51
+ "lint-staged": "16.2.7",
52
+ "prettier": "3.8.0",
53
+ "typescript": "5.9.3",
54
+ "typescript-eslint": "8.53.0",
55
+ "vitest": "4.0.17"
50
56
  },
51
57
  "engines": {
52
- "node": ">=16.0.0"
58
+ "node": "v25.3.0"
53
59
  }
54
60
  }
package/readme.md CHANGED
@@ -18,12 +18,50 @@ yarn add -D eslint-plugin-class-validator-type-match
18
18
 
19
19
  ## Usage
20
20
 
21
- ### Manual Configuration
21
+ ### Flat Config (eslint.config.ts)
22
+
23
+ ```typescript
24
+ import classValidatorTypeMatch from 'eslint-plugin-class-validator-type-match';
25
+
26
+ export default [classValidatorTypeMatch.flatConfigs.recommended];
27
+ ```
28
+
29
+ Or with manual rule configuration:
30
+
31
+ ```typescript
32
+ import classValidatorTypeMatch from 'eslint-plugin-class-validator-type-match';
33
+
34
+ export default [
35
+ {
36
+ plugins: {
37
+ 'class-validator-type-match': classValidatorTypeMatch,
38
+ },
39
+ rules: {
40
+ 'class-validator-type-match/decorator-type-match': 'error',
41
+ 'class-validator-type-match/optional-decorator-match': 'error',
42
+ 'class-validator-type-match/validate-nested-match': 'error',
43
+ 'class-validator-type-match/type-decorator-match': 'error',
44
+ 'class-validator-type-match/definite-assignment-match': 'error',
45
+ 'class-validator-type-match/dto-filename-match': 'error',
46
+ },
47
+ },
48
+ ];
49
+ ```
50
+
51
+ ### Legacy Config (.eslintrc)
52
+
53
+ ```javascript
54
+ // .eslintrc.js
55
+ module.exports = {
56
+ extends: ['plugin:class-validator-type-match/recommended'],
57
+ };
58
+ ```
59
+
60
+ Or with manual rule configuration:
22
61
 
23
62
  ```javascript
24
63
  // .eslintrc.js
25
64
  module.exports = {
26
- parser: '@typescript-eslint/parser',
27
65
  plugins: ['class-validator-type-match'],
28
66
  rules: {
29
67
  'class-validator-type-match/decorator-type-match': 'error',
@@ -36,30 +74,12 @@ module.exports = {
36
74
  };
37
75
  ```
38
76
 
39
- ### Recommended Configuration
40
-
41
- ```javascript
42
- // .eslintrc.js
43
- module.exports = {
44
- parser: '@typescript-eslint/parser',
45
- extends: ['plugin:class-validator-type-match/recommended'],
46
- };
47
- ```
48
-
49
77
  ### Configuration Presets
50
78
 
51
79
  - **`recommended`** - All rules enabled (best for most projects)
52
80
  - **`strict`** - All rules enabled with strict settings
53
81
  - **`basic`** - Only core type matching rules (decorator-type-match, optional-decorator-match, definite-assignment-match, dto-filename-match)
54
82
 
55
- ```javascript
56
- // Use basic preset for less strict validation
57
- module.exports = {
58
- parser: '@typescript-eslint/parser',
59
- extends: ['plugin:class-validator-type-match/basic'],
60
- };
61
- ```
62
-
63
83
  ## Rules
64
84
 
65
85
  ### `decorator-type-match`