eslint-plugin-class-validator-type-match 3.1.1 → 3.1.3
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
|
@@ -8,7 +8,7 @@ declare const _default: {
|
|
|
8
8
|
}], unknown, import("@typescript-eslint/utils/dist/ts-eslint").RuleListener>;
|
|
9
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
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<"
|
|
11
|
+
'definite-assignment-match': import("@typescript-eslint/utils/dist/ts-eslint").RuleModule<"incorrectDefiniteAssignment", [], unknown, import("@typescript-eslint/utils/dist/ts-eslint").RuleListener>;
|
|
12
12
|
'dto-filename-match': import("@typescript-eslint/utils/dist/ts-eslint").RuleModule<"incorrectDtoClassName", [], unknown, import("@typescript-eslint/utils/dist/ts-eslint").RuleListener>;
|
|
13
13
|
};
|
|
14
14
|
configs: {
|
|
@@ -1,24 +1,29 @@
|
|
|
1
1
|
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
2
2
|
/**
|
|
3
|
-
* ESLint rule to ensure
|
|
3
|
+
* ESLint rule to ensure definite assignment assertion (!) is used correctly with decorators.
|
|
4
4
|
*
|
|
5
5
|
* This rule validates that:
|
|
6
|
-
* - Properties with
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* Note: Properties with | null still require ! unless they also have ?
|
|
6
|
+
* - Properties with ! should NOT have:
|
|
7
|
+
* - Optional marker (?)
|
|
8
|
+
* - An initializer (= value)
|
|
9
|
+
* - undefined in their type
|
|
12
10
|
*
|
|
13
11
|
* @example
|
|
14
|
-
* // ✅ Good -
|
|
12
|
+
* // ✅ Good - ! without optional or initializer
|
|
15
13
|
* class User {
|
|
16
14
|
* @IsString()
|
|
17
15
|
* name!: string;
|
|
18
16
|
* }
|
|
19
17
|
*
|
|
20
18
|
* @example
|
|
21
|
-
* // ✅ Good -
|
|
19
|
+
* // ✅ Good - no ! is also fine for non-optional
|
|
20
|
+
* class User {
|
|
21
|
+
* @IsString()
|
|
22
|
+
* name: string;
|
|
23
|
+
* }
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* // ✅ Good - optional property without !
|
|
22
27
|
* class User {
|
|
23
28
|
* @IsOptional()
|
|
24
29
|
* @IsString()
|
|
@@ -26,32 +31,40 @@ import { ESLintUtils } from '@typescript-eslint/utils';
|
|
|
26
31
|
* }
|
|
27
32
|
*
|
|
28
33
|
* @example
|
|
29
|
-
* // ✅ Good - has initializer
|
|
34
|
+
* // ✅ Good - has initializer without !
|
|
30
35
|
* class User {
|
|
31
36
|
* @IsString()
|
|
32
37
|
* name: string = 'default';
|
|
33
38
|
* }
|
|
34
39
|
*
|
|
35
40
|
* @example
|
|
36
|
-
* // ✅ Good - has undefined in type
|
|
41
|
+
* // ✅ Good - has undefined in type without !
|
|
37
42
|
* class User {
|
|
38
43
|
* @IsString()
|
|
39
44
|
* name: string | undefined;
|
|
40
45
|
* }
|
|
41
46
|
*
|
|
42
47
|
* @example
|
|
43
|
-
* // ❌ Bad -
|
|
48
|
+
* // ❌ Bad - ! with optional property
|
|
44
49
|
* class User {
|
|
50
|
+
* @IsOptional()
|
|
45
51
|
* @IsString()
|
|
46
|
-
* name
|
|
52
|
+
* name?!: string;
|
|
53
|
+
* }
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* // ❌ Bad - ! with initializer
|
|
57
|
+
* class User {
|
|
58
|
+
* @IsString()
|
|
59
|
+
* name!: string = 'default';
|
|
47
60
|
* }
|
|
48
61
|
*
|
|
49
62
|
* @example
|
|
50
|
-
* // ❌ Bad -
|
|
63
|
+
* // ❌ Bad - ! with undefined in type
|
|
51
64
|
* class User {
|
|
52
65
|
* @IsString()
|
|
53
|
-
* name
|
|
66
|
+
* name!: string | undefined;
|
|
54
67
|
* }
|
|
55
68
|
*/
|
|
56
|
-
declare const _default: ESLintUtils.RuleModule<"
|
|
69
|
+
declare const _default: ESLintUtils.RuleModule<"incorrectDefiniteAssignment", [], unknown, ESLintUtils.RuleListener>;
|
|
57
70
|
export default _default;
|
|
@@ -7,25 +7,30 @@ const type_helpers_util_1 = require("../utils/type-helpers.util");
|
|
|
7
7
|
*/
|
|
8
8
|
const createRule = utils_1.ESLintUtils.RuleCreator((name) => `https://github.com/robertlinde/eslint-plugin-class-validator-type-match#${name}`);
|
|
9
9
|
/**
|
|
10
|
-
* ESLint rule to ensure
|
|
10
|
+
* ESLint rule to ensure definite assignment assertion (!) is used correctly with decorators.
|
|
11
11
|
*
|
|
12
12
|
* This rule validates that:
|
|
13
|
-
* - Properties with
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* Note: Properties with | null still require ! unless they also have ?
|
|
13
|
+
* - Properties with ! should NOT have:
|
|
14
|
+
* - Optional marker (?)
|
|
15
|
+
* - An initializer (= value)
|
|
16
|
+
* - undefined in their type
|
|
19
17
|
*
|
|
20
18
|
* @example
|
|
21
|
-
* // ✅ Good -
|
|
19
|
+
* // ✅ Good - ! without optional or initializer
|
|
22
20
|
* class User {
|
|
23
21
|
* @IsString()
|
|
24
22
|
* name!: string;
|
|
25
23
|
* }
|
|
26
24
|
*
|
|
27
25
|
* @example
|
|
28
|
-
* // ✅ Good -
|
|
26
|
+
* // ✅ Good - no ! is also fine for non-optional
|
|
27
|
+
* class User {
|
|
28
|
+
* @IsString()
|
|
29
|
+
* name: string;
|
|
30
|
+
* }
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* // ✅ Good - optional property without !
|
|
29
34
|
* class User {
|
|
30
35
|
* @IsOptional()
|
|
31
36
|
* @IsString()
|
|
@@ -33,31 +38,39 @@ const createRule = utils_1.ESLintUtils.RuleCreator((name) => `https://github.com
|
|
|
33
38
|
* }
|
|
34
39
|
*
|
|
35
40
|
* @example
|
|
36
|
-
* // ✅ Good - has initializer
|
|
41
|
+
* // ✅ Good - has initializer without !
|
|
37
42
|
* class User {
|
|
38
43
|
* @IsString()
|
|
39
44
|
* name: string = 'default';
|
|
40
45
|
* }
|
|
41
46
|
*
|
|
42
47
|
* @example
|
|
43
|
-
* // ✅ Good - has undefined in type
|
|
48
|
+
* // ✅ Good - has undefined in type without !
|
|
44
49
|
* class User {
|
|
45
50
|
* @IsString()
|
|
46
51
|
* name: string | undefined;
|
|
47
52
|
* }
|
|
48
53
|
*
|
|
49
54
|
* @example
|
|
50
|
-
* // ❌ Bad -
|
|
55
|
+
* // ❌ Bad - ! with optional property
|
|
51
56
|
* class User {
|
|
57
|
+
* @IsOptional()
|
|
52
58
|
* @IsString()
|
|
53
|
-
* name
|
|
59
|
+
* name?!: string;
|
|
60
|
+
* }
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* // ❌ Bad - ! with initializer
|
|
64
|
+
* class User {
|
|
65
|
+
* @IsString()
|
|
66
|
+
* name!: string = 'default';
|
|
54
67
|
* }
|
|
55
68
|
*
|
|
56
69
|
* @example
|
|
57
|
-
* // ❌ Bad -
|
|
70
|
+
* // ❌ Bad - ! with undefined in type
|
|
58
71
|
* class User {
|
|
59
72
|
* @IsString()
|
|
60
|
-
* name
|
|
73
|
+
* name!: string | undefined;
|
|
61
74
|
* }
|
|
62
75
|
*/
|
|
63
76
|
exports.default = createRule({
|
|
@@ -65,10 +78,10 @@ exports.default = createRule({
|
|
|
65
78
|
meta: {
|
|
66
79
|
type: 'problem',
|
|
67
80
|
docs: {
|
|
68
|
-
description: 'Ensure
|
|
81
|
+
description: 'Ensure definite assignment assertion (!) is not used with optional properties, initializers, or undefined types',
|
|
69
82
|
},
|
|
70
83
|
messages: {
|
|
71
|
-
|
|
84
|
+
incorrectDefiniteAssignment: 'Property {{propertyName}} should not use definite assignment assertion (!) because it {{reason}}. Remove the ! from: {{propertyName}}!',
|
|
72
85
|
},
|
|
73
86
|
schema: [],
|
|
74
87
|
fixable: 'code',
|
|
@@ -105,37 +118,46 @@ exports.default = createRule({
|
|
|
105
118
|
if (!actualType)
|
|
106
119
|
return;
|
|
107
120
|
/**
|
|
108
|
-
* Check for
|
|
109
|
-
* Properties
|
|
110
|
-
* -
|
|
111
|
-
* -
|
|
112
|
-
* -
|
|
113
|
-
* Should use the definite assignment assertion (!)
|
|
114
|
-
* Note: Properties with | null still require ! unless they also have ?
|
|
121
|
+
* Check for incorrect usage of definite assignment assertion (!)
|
|
122
|
+
* Properties should NOT use ! if they:
|
|
123
|
+
* - Are optional (have ?)
|
|
124
|
+
* - Have an initializer (= value)
|
|
125
|
+
* - Have undefined in their type
|
|
115
126
|
*/
|
|
116
127
|
const isOptionalProperty = node.optional === true;
|
|
117
128
|
const hasInitializer = node.value !== undefined && node.value !== null;
|
|
118
129
|
const hasDefiniteAssignment = node.definite === true;
|
|
119
130
|
const hasUndefinedInType = typeAnnotation.type === 'TSUndefinedKeyword' ||
|
|
120
131
|
(typeAnnotation.type === 'TSUnionType' && typeAnnotation.types.some((t) => t.type === 'TSUndefinedKeyword'));
|
|
121
|
-
// If property
|
|
122
|
-
|
|
123
|
-
if (!isOptionalProperty && !hasInitializer && !hasUndefinedInType && !hasDefiniteAssignment) {
|
|
132
|
+
// If property has definite assignment (!), check if it's used incorrectly
|
|
133
|
+
if (hasDefiniteAssignment) {
|
|
124
134
|
const propertyName = node.key.type === 'Identifier' ? node.key.name : 'property';
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
135
|
+
let reason = null;
|
|
136
|
+
if (isOptionalProperty) {
|
|
137
|
+
reason = 'is optional (has ?)';
|
|
138
|
+
}
|
|
139
|
+
else if (hasInitializer) {
|
|
140
|
+
reason = 'has an initializer';
|
|
141
|
+
}
|
|
142
|
+
else if (hasUndefinedInType) {
|
|
143
|
+
reason = 'has undefined in its type';
|
|
144
|
+
}
|
|
145
|
+
if (reason) {
|
|
146
|
+
context.report({
|
|
147
|
+
node,
|
|
148
|
+
messageId: 'incorrectDefiniteAssignment',
|
|
149
|
+
data: {
|
|
150
|
+
propertyName,
|
|
151
|
+
reason,
|
|
152
|
+
},
|
|
153
|
+
fix(fixer) {
|
|
154
|
+
// Remove the ! after the property key
|
|
155
|
+
const keyEnd = node.key.range[1];
|
|
156
|
+
// The ! is between the key and the optional marker or colon
|
|
157
|
+
return fixer.removeRange([keyEnd, keyEnd + 1]);
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
}
|
|
139
161
|
}
|
|
140
162
|
},
|
|
141
163
|
};
|
|
@@ -6,6 +6,15 @@ const type_helpers_util_1 = require("../utils/type-helpers.util");
|
|
|
6
6
|
* Creates an ESLint rule with proper documentation URL
|
|
7
7
|
*/
|
|
8
8
|
const createRule = utils_1.ESLintUtils.RuleCreator((name) => `https://github.com/robertlinde/eslint-plugin-class-validator-type-match#${name}`);
|
|
9
|
+
/**
|
|
10
|
+
* Maps JavaScript wrapper class names to their corresponding primitive type names.
|
|
11
|
+
* Used to validate @Type(() => Number) with `number` type, etc.
|
|
12
|
+
*/
|
|
13
|
+
const PRIMITIVE_WRAPPER_MAP = {
|
|
14
|
+
Number: 'number',
|
|
15
|
+
String: 'string',
|
|
16
|
+
Boolean: 'boolean',
|
|
17
|
+
};
|
|
9
18
|
/**
|
|
10
19
|
* ESLint rule to ensure @Type(() => ClassName) decorator matches TypeScript type annotations.
|
|
11
20
|
*
|
|
@@ -129,15 +138,18 @@ exports.default = createRule({
|
|
|
129
138
|
}
|
|
130
139
|
}
|
|
131
140
|
else {
|
|
132
|
-
// @Type is used with a primitive type
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
+
// @Type is used with a primitive type - check if it's a valid wrapper class match
|
|
142
|
+
const expectedPrimitive = PRIMITIVE_WRAPPER_MAP[typeClassName];
|
|
143
|
+
if (!expectedPrimitive || expectedPrimitive !== actualType) {
|
|
144
|
+
context.report({
|
|
145
|
+
node,
|
|
146
|
+
messageId: 'typeMismatch',
|
|
147
|
+
data: {
|
|
148
|
+
typeDecoratorClass: typeClassName,
|
|
149
|
+
actualType,
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
}
|
|
141
153
|
}
|
|
142
154
|
}
|
|
143
155
|
// For array types, check if @Type matches the array element type
|
|
@@ -158,17 +170,20 @@ exports.default = createRule({
|
|
|
158
170
|
}
|
|
159
171
|
}
|
|
160
172
|
else {
|
|
161
|
-
// @Type is used with an array of primitives
|
|
173
|
+
// @Type is used with an array of primitives - check if it's a valid wrapper class match
|
|
162
174
|
const elementType = (0, type_helpers_util_1.getTypeString)(elementTypeNode, checker, esTreeNodeMap);
|
|
163
175
|
if (elementType) {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
176
|
+
const expectedPrimitive = PRIMITIVE_WRAPPER_MAP[typeClassName];
|
|
177
|
+
if (!expectedPrimitive || expectedPrimitive !== elementType) {
|
|
178
|
+
context.report({
|
|
179
|
+
node,
|
|
180
|
+
messageId: 'typeMismatch',
|
|
181
|
+
data: {
|
|
182
|
+
typeDecoratorClass: typeClassName,
|
|
183
|
+
actualType: `${elementType}[]`,
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
}
|
|
172
187
|
}
|
|
173
188
|
}
|
|
174
189
|
}
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -148,7 +148,7 @@ class User {
|
|
|
148
148
|
|
|
149
149
|
### `definite-assignment-match`
|
|
150
150
|
|
|
151
|
-
Ensures
|
|
151
|
+
Ensures definite assignment assertion (`!`) is used correctly with decorators.
|
|
152
152
|
|
|
153
153
|
**Examples:**
|
|
154
154
|
|
|
@@ -157,20 +157,24 @@ import {IsString, IsOptional} from 'class-validator';
|
|
|
157
157
|
|
|
158
158
|
class User {
|
|
159
159
|
@IsString()
|
|
160
|
-
name!: string; // ✅ Correct -
|
|
160
|
+
name!: string; // ✅ Correct - ! can be used for non-optional
|
|
161
161
|
|
|
162
162
|
@IsString()
|
|
163
|
-
email: string; //
|
|
163
|
+
email: string; // ✅ Correct - ! is optional for non-optional properties
|
|
164
164
|
|
|
165
165
|
@IsOptional()
|
|
166
166
|
@IsString()
|
|
167
|
-
bio?: string; // ✅ Correct - optional property
|
|
167
|
+
bio?: string; // ✅ Correct - optional property without !
|
|
168
|
+
|
|
169
|
+
@IsOptional()
|
|
170
|
+
@IsString()
|
|
171
|
+
username?!: string; // ❌ Error: ! should not be used with optional (?)
|
|
168
172
|
|
|
169
173
|
@IsString()
|
|
170
|
-
nickname
|
|
174
|
+
nickname!: string = 'default'; // ❌ Error: ! should not be used with initializer
|
|
171
175
|
|
|
172
176
|
@IsString()
|
|
173
|
-
description
|
|
177
|
+
description!: string | undefined; // ❌ Error: ! should not be used with undefined type
|
|
174
178
|
}
|
|
175
179
|
```
|
|
176
180
|
|