eslint-plugin-class-validator-type-match 0.1.1

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/LICENCE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Robert Linde
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,14 @@
1
+ declare const _default: {
2
+ rules: {
3
+ 'decorator-type-match': import("@typescript-eslint/utils/dist/ts-eslint").RuleModule<"mismatch", [], unknown, import("@typescript-eslint/utils/dist/ts-eslint").RuleListener>;
4
+ };
5
+ configs: {
6
+ recommended: {
7
+ plugins: string[];
8
+ rules: {
9
+ 'class-validator-type-match/decorator-type-match': string;
10
+ };
11
+ };
12
+ };
13
+ };
14
+ export = _default;
package/dist/index.js ADDED
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ const decorator_type_match_1 = __importDefault(require("./rules/decorator-type-match"));
6
+ module.exports = {
7
+ rules: {
8
+ 'decorator-type-match': decorator_type_match_1.default,
9
+ },
10
+ configs: {
11
+ recommended: {
12
+ plugins: ['class-validator-type-match'],
13
+ rules: {
14
+ 'class-validator-type-match/decorator-type-match': 'error',
15
+ },
16
+ },
17
+ },
18
+ };
@@ -0,0 +1,23 @@
1
+ import { ESLintUtils } from '@typescript-eslint/utils';
2
+ /**
3
+ * ESLint rule to ensure class-validator decorators match TypeScript type annotations.
4
+ *
5
+ * This rule prevents common mistakes where the decorator type (e.g., @IsString)
6
+ * doesn't match the actual TypeScript type annotation (e.g., number).
7
+ *
8
+ * @example
9
+ * // ❌ Bad - will trigger error
10
+ * class User {
11
+ * @IsString()
12
+ * name!: number;
13
+ * }
14
+ *
15
+ * @example
16
+ * // ✅ Good - types match
17
+ * class User {
18
+ * @IsString()
19
+ * name!: string;
20
+ * }
21
+ */
22
+ declare const _default: ESLintUtils.RuleModule<"mismatch", [], unknown, ESLintUtils.RuleListener>;
23
+ export default _default;
@@ -0,0 +1,186 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const utils_1 = require("@typescript-eslint/utils");
4
+ /**
5
+ * Creates an ESLint rule with proper documentation URL
6
+ */
7
+ const createRule = utils_1.ESLintUtils.RuleCreator((name) => `https://github.com/robertlinde/eslint-plugin-class-validator-type-match#${name}`);
8
+ /**
9
+ * Mapping of class-validator decorators to their expected TypeScript types.
10
+ * Empty arrays indicate decorators that can accept any type.
11
+ *
12
+ * @example
13
+ * IsString: ["string"] - expects string type
14
+ * IsOptional: [] - accepts any type
15
+ */
16
+ const decoratorTypeMap = {
17
+ // String validators
18
+ IsString: ['string'],
19
+ IsNotEmpty: [], // Can be any type
20
+ // Number validators
21
+ IsNumber: ['number'],
22
+ IsInt: ['number'],
23
+ IsPositive: ['number'],
24
+ IsNegative: ['number'],
25
+ Min: ['number'],
26
+ Max: ['number'],
27
+ // Boolean validators
28
+ IsBoolean: ['boolean'],
29
+ // Date validators
30
+ IsDate: ['Date'],
31
+ MinDate: ['Date'],
32
+ MaxDate: ['Date'],
33
+ // Array validators
34
+ IsArray: ['array', 'Array'],
35
+ ArrayMinSize: ['array', 'Array'],
36
+ ArrayMaxSize: ['array', 'Array'],
37
+ // Object validators
38
+ IsObject: ['object'],
39
+ // Enum validators
40
+ IsEnum: ['enum'],
41
+ // Type-agnostic validators (no type checking)
42
+ IsOptional: [],
43
+ ValidateNested: [],
44
+ IsDefined: [],
45
+ IsEmpty: [],
46
+ Equals: [],
47
+ NotEquals: [],
48
+ IsIn: [],
49
+ IsNotIn: [],
50
+ };
51
+ /**
52
+ * ESLint rule to ensure class-validator decorators match TypeScript type annotations.
53
+ *
54
+ * This rule prevents common mistakes where the decorator type (e.g., @IsString)
55
+ * doesn't match the actual TypeScript type annotation (e.g., number).
56
+ *
57
+ * @example
58
+ * // ❌ Bad - will trigger error
59
+ * class User {
60
+ * @IsString()
61
+ * name!: number;
62
+ * }
63
+ *
64
+ * @example
65
+ * // ✅ Good - types match
66
+ * class User {
67
+ * @IsString()
68
+ * name!: string;
69
+ * }
70
+ */
71
+ exports.default = createRule({
72
+ name: 'decorator-type-match',
73
+ meta: {
74
+ type: 'problem',
75
+ docs: {
76
+ description: 'Ensure class-validator decorators match TypeScript type annotations',
77
+ },
78
+ messages: {
79
+ mismatch: 'Decorator @{{decorator}} does not match type annotation {{actualType}}. Expected: {{expectedTypes}}',
80
+ },
81
+ schema: [],
82
+ },
83
+ defaultOptions: [],
84
+ create(context) {
85
+ return {
86
+ /**
87
+ * Analyzes class property definitions to check if decorators match type annotations
88
+ * @param node - The PropertyDefinition AST node to analyze
89
+ */
90
+ PropertyDefinition(node) {
91
+ // Skip if no decorators present
92
+ if (!node.decorators || node.decorators.length === 0)
93
+ return;
94
+ // Skip if no type annotation
95
+ if (!node.typeAnnotation)
96
+ return;
97
+ /**
98
+ * Extract decorator names from the property.
99
+ * Handles both @Decorator and @Decorator() syntax for maximum compatibility.
100
+ */
101
+ const decorators = node.decorators
102
+ .filter((d) => d.expression.type === 'CallExpression' || d.expression.type === 'Identifier')
103
+ .map((d) => {
104
+ if (d.expression.type === 'CallExpression' && d.expression.callee.type === 'Identifier') {
105
+ return d.expression.callee.name;
106
+ }
107
+ if (d.expression.type === 'Identifier') {
108
+ return d.expression.name;
109
+ }
110
+ return null;
111
+ })
112
+ .filter((name) => name !== null);
113
+ const { typeAnnotation } = node.typeAnnotation;
114
+ let actualType = null;
115
+ /**
116
+ * Determine the actual TypeScript type from the annotation.
117
+ * Supports primitive types, arrays, and type references.
118
+ */
119
+ switch (typeAnnotation.type) {
120
+ case 'TSStringKeyword': {
121
+ actualType = 'string';
122
+ break;
123
+ }
124
+ case 'TSNumberKeyword': {
125
+ actualType = 'number';
126
+ break;
127
+ }
128
+ case 'TSBooleanKeyword': {
129
+ actualType = 'boolean';
130
+ break;
131
+ }
132
+ case 'TSArrayType': {
133
+ actualType = 'array';
134
+ break;
135
+ }
136
+ case 'TSTypeReference': {
137
+ if (typeAnnotation.typeName.type === 'Identifier') {
138
+ actualType = typeAnnotation.typeName.name;
139
+ }
140
+ break;
141
+ }
142
+ // No default
143
+ }
144
+ // Skip if we couldn't determine the type
145
+ if (!actualType)
146
+ return;
147
+ /**
148
+ * Check each decorator against the actual type.
149
+ * Report an error if there's a mismatch.
150
+ */
151
+ for (const decorator of decorators) {
152
+ const expectedTypes = decoratorTypeMap[decorator];
153
+ // Skip decorators not in our map
154
+ if (!expectedTypes)
155
+ continue;
156
+ // Skip type-agnostic decorators (empty array)
157
+ if (expectedTypes.length === 0)
158
+ continue;
159
+ /**
160
+ * Check if the actual type matches any of the expected types.
161
+ * Handles both 'array' and 'Array' as equivalent.
162
+ */
163
+ const matches = expectedTypes.some((expected) => {
164
+ if (expected === 'array' && actualType === 'Array')
165
+ return true;
166
+ if (expected === 'Array' && actualType === 'array')
167
+ return true;
168
+ return expected === actualType;
169
+ });
170
+ // Report mismatch
171
+ if (!matches) {
172
+ context.report({
173
+ node,
174
+ messageId: 'mismatch',
175
+ data: {
176
+ decorator,
177
+ actualType,
178
+ expectedTypes: expectedTypes.join(' or '),
179
+ },
180
+ });
181
+ }
182
+ }
183
+ },
184
+ };
185
+ },
186
+ });
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "eslint-plugin-class-validator-type-match",
3
+ "version": "0.1.1",
4
+ "description": "ESLint plugin to ensure class-validator decorators match TypeScript type annotations",
5
+ "keywords": [
6
+ "eslint",
7
+ "eslintplugin",
8
+ "class-validator",
9
+ "typescript",
10
+ "decorators"
11
+ ],
12
+ "author": "Robert Linde",
13
+ "license": "MIT",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "https://github.com/robertlinde/eslint-plugin-class-validator-type-match"
17
+ },
18
+ "main": "dist/index.js",
19
+ "types": "dist/index.d.ts",
20
+ "files": [
21
+ "dist"
22
+ ],
23
+ "scripts": {
24
+ "build": "tsc",
25
+ "prepublishOnly": "npm run build",
26
+ "lint": "eslint \"src/**/*.ts\"",
27
+ "lint:fix": "eslint \"src/**/*.ts\" --fix",
28
+ "format": "prettier --write \"**/*.{ts,md}\"",
29
+ "prepare": "husky"
30
+ },
31
+ "peerDependencies": {
32
+ "@typescript-eslint/parser": "^6.0.0 || ^7.0.0 || ^8.0.0",
33
+ "eslint": "^8.0.0 || ^9.0.0"
34
+ },
35
+ "dependencies": {
36
+ "@typescript-eslint/utils": "^8.0.0"
37
+ },
38
+ "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",
45
+ "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"
50
+ },
51
+ "engines": {
52
+ "node": ">=16.0.0"
53
+ }
54
+ }
package/readme.md ADDED
@@ -0,0 +1,75 @@
1
+ # eslint-plugin-class-validator-type-match
2
+
3
+ ESLint plugin to ensure class-validator decorators match TypeScript type annotations.
4
+
5
+ ## Installation
6
+
7
+ **npm**
8
+
9
+ ```bash
10
+ npm install --save-dev eslint-plugin-class-validator-type-match
11
+ ```
12
+
13
+ **yarn**
14
+
15
+ ```bash
16
+ yarn add -D eslint-plugin-class-validator-type-match
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ // .eslintrc.js
22
+
23
+ ```javascript
24
+ module.exports = {
25
+ parser: '@typescript-eslint/parser',
26
+ plugins: ['class-validator-type-match'],
27
+ rules: {
28
+ 'class-validator-type-match/decorator-type-match': 'error',
29
+ },
30
+ };
31
+ ```
32
+
33
+ Or use the recommended config:
34
+
35
+ ```javascript
36
+ module.exports = {
37
+ parser: '@typescript-eslint/parser',
38
+ extends: ['plugin:class-validator-type-match/recommended'],
39
+ };
40
+ ```
41
+
42
+ ## Example
43
+
44
+ ```javascript
45
+ module.exports = {
46
+ parser: '@typescript-eslint/parser',
47
+ extends: ['plugin:class-validator-type-match/recommended'],
48
+ };
49
+ ```
50
+
51
+ ## Example
52
+
53
+ ```typescript
54
+ import {IsString, IsNumber} from 'class-validator';
55
+
56
+ class User {
57
+ @IsString()
58
+ name!: number; // ❌ Error: Decorator @IsString does not match type annotation number
59
+
60
+ @IsNumber()
61
+ age!: string; // ❌ Error: Decorator @IsNumber does not match type annotation string
62
+
63
+ @IsString()
64
+ email!: string; // ✅ Correct
65
+ }
66
+ ```
67
+
68
+ ## Supported Decorators
69
+
70
+ - `@IsString` → string
71
+ - `@IsNumber` / `@IsInt` → number
72
+ - `@IsBoolean` → boolean
73
+ - `@IsArray` → array or Array<T>
74
+ - `@IsDate` → Date
75
+ - `@IsObject` → object