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 +21 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +18 -0
- package/dist/rules/decorator-type-match.d.ts +23 -0
- package/dist/rules/decorator-type-match.js +186 -0
- package/package.json +54 -0
- package/readme.md +75 -0
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.
|
package/dist/index.d.ts
ADDED
|
@@ -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
|