eslint-plugin-class-validator-type-match 3.1.0 → 3.1.2
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 +1 -1
- package/dist/rules/decorator-type-match.js +2 -1
- package/dist/rules/definite-assignment-match.d.ts +29 -16
- package/dist/rules/definite-assignment-match.js +63 -41
- package/dist/rules/type-decorator-match.js +53 -20
- package/dist/rules/validate-nested-match.js +32 -0
- package/dist/utils/type-helpers.util.js +20 -0
- package/package.json +1 -1
- package/readme.md +10 -6
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: {
|
|
@@ -122,7 +122,8 @@ exports.default = createRule({
|
|
|
122
122
|
return;
|
|
123
123
|
const hasIsEnum = decorators.includes('IsEnum');
|
|
124
124
|
// Validate @IsEnum argument matches the type annotation for enum type references
|
|
125
|
-
|
|
125
|
+
// Skip this check for array types with { each: true }, as those validate array elements
|
|
126
|
+
if (hasIsEnum && typeAnnotation.type === 'TSTypeReference' && !(0, type_helpers_util_1.isArrayType)(typeAnnotation)) {
|
|
126
127
|
const isEnumDecorator = node.decorators?.find((d) => d.expression.type === 'CallExpression' &&
|
|
127
128
|
d.expression.callee.type === 'Identifier' &&
|
|
128
129
|
d.expression.callee.name === 'IsEnum');
|
|
@@ -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
|
};
|
|
@@ -179,25 +179,30 @@ exports.default = createRule({
|
|
|
179
179
|
(0, type_helpers_util_1.isComplexType)(typeAnnotation, checker, esTreeNodeMap) &&
|
|
180
180
|
hasValidateNested &&
|
|
181
181
|
!hasTypeDecorator) {
|
|
182
|
-
|
|
183
|
-
//
|
|
184
|
-
const
|
|
185
|
-
if (
|
|
186
|
-
typeToCheck =
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
182
|
+
// Check if there's an @IsEnum decorator (single enum doesn't need each: true)
|
|
183
|
+
// This indicates it's an enum and doesn't need @Type
|
|
184
|
+
const hasIsEnum = decorators.includes('IsEnum');
|
|
185
|
+
if (!hasIsEnum) {
|
|
186
|
+
let typeToCheck = typeAnnotation;
|
|
187
|
+
// Handle nullable complex types: Address | null | undefined
|
|
188
|
+
const nullableCheck = (0, type_helpers_util_1.isNullableUnion)(typeAnnotation);
|
|
189
|
+
if (nullableCheck.isNullable && nullableCheck.baseType) {
|
|
190
|
+
typeToCheck = nullableCheck.baseType;
|
|
191
|
+
}
|
|
192
|
+
// Unwrap utility types to get the base class name
|
|
193
|
+
const unwrappedType = (0, type_helpers_util_1.unwrapUtilityTypeForClassName)(typeToCheck);
|
|
194
|
+
if (unwrappedType.type === 'TSTypeReference') {
|
|
195
|
+
const className = (0, type_helpers_util_1.getTypeReferenceName)(unwrappedType.typeName);
|
|
196
|
+
const displayType = (0, type_helpers_util_1.getTypeString)(typeToCheck, checker, esTreeNodeMap);
|
|
197
|
+
context.report({
|
|
198
|
+
node,
|
|
199
|
+
messageId: 'missingTypeDecorator',
|
|
200
|
+
data: {
|
|
201
|
+
actualType: displayType || className,
|
|
202
|
+
className,
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
}
|
|
201
206
|
}
|
|
202
207
|
}
|
|
203
208
|
// Check if array of complex types with @ValidateNested also have @Type
|
|
@@ -205,7 +210,35 @@ exports.default = createRule({
|
|
|
205
210
|
const elementTypeNode = (0, type_helpers_util_1.getArrayElementTypeNode)(typeAnnotation);
|
|
206
211
|
if (elementTypeNode) {
|
|
207
212
|
const isElementComplex = (0, type_helpers_util_1.isComplexType)(elementTypeNode, checker, esTreeNodeMap);
|
|
208
|
-
if
|
|
213
|
+
// Check if there's an @IsEnum decorator with { each: true }
|
|
214
|
+
// This indicates the array elements are enums and don't need @Type
|
|
215
|
+
const hasIsEnumWithEach = decorators.includes('IsEnum') &&
|
|
216
|
+
node.decorators?.some((d) => {
|
|
217
|
+
if (d.expression.type === 'CallExpression' &&
|
|
218
|
+
d.expression.callee.type === 'Identifier' &&
|
|
219
|
+
d.expression.callee.name === 'IsEnum') {
|
|
220
|
+
// Check if it has { each: true }
|
|
221
|
+
const args = d.expression.arguments;
|
|
222
|
+
for (let i = 0; i < Math.min(args.length, 2); i++) {
|
|
223
|
+
const arg = args[i];
|
|
224
|
+
if (arg.type === 'ObjectExpression') {
|
|
225
|
+
const hasEach = arg.properties.some((prop) => {
|
|
226
|
+
if (prop.type === 'Property' &&
|
|
227
|
+
prop.key.type === 'Identifier' &&
|
|
228
|
+
prop.key.name === 'each' &&
|
|
229
|
+
prop.value.type === 'Literal') {
|
|
230
|
+
return prop.value.value === true;
|
|
231
|
+
}
|
|
232
|
+
return false;
|
|
233
|
+
});
|
|
234
|
+
if (hasEach)
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return false;
|
|
240
|
+
});
|
|
241
|
+
if (isElementComplex && !hasIsEnumWithEach) {
|
|
209
242
|
let elementTypeToCheck = elementTypeNode;
|
|
210
243
|
// Handle nullable element types: (Address | null)[]
|
|
211
244
|
const nullableCheck = (0, type_helpers_util_1.isNullableUnion)(elementTypeNode);
|
|
@@ -170,7 +170,39 @@ exports.default = createRule({
|
|
|
170
170
|
if (elementTypeNode) {
|
|
171
171
|
const elementTypeName = (0, type_helpers_util_1.getTypeString)(elementTypeNode, checker, esTreeNodeMap);
|
|
172
172
|
const isElementComplex = (0, type_helpers_util_1.isComplexType)(elementTypeNode, checker, esTreeNodeMap);
|
|
173
|
+
// Check if there's an @IsEnum decorator with { each: true }
|
|
174
|
+
// This indicates the array elements are enums and don't need @ValidateNested
|
|
175
|
+
const hasIsEnumWithEach = decorators.includes('IsEnum') &&
|
|
176
|
+
node.decorators?.some((d) => {
|
|
177
|
+
if (d.expression.type === 'CallExpression' &&
|
|
178
|
+
d.expression.callee.type === 'Identifier' &&
|
|
179
|
+
d.expression.callee.name === 'IsEnum') {
|
|
180
|
+
// Check if it has { each: true }
|
|
181
|
+
const args = d.expression.arguments;
|
|
182
|
+
for (let i = 0; i < Math.min(args.length, 2); i++) {
|
|
183
|
+
const arg = args[i];
|
|
184
|
+
if (arg.type === 'ObjectExpression') {
|
|
185
|
+
const hasEach = arg.properties.some((prop) => {
|
|
186
|
+
if (prop.type === 'Property' &&
|
|
187
|
+
prop.key.type === 'Identifier' &&
|
|
188
|
+
prop.key.name === 'each' &&
|
|
189
|
+
prop.value.type === 'Literal') {
|
|
190
|
+
return prop.value.value === true;
|
|
191
|
+
}
|
|
192
|
+
return false;
|
|
193
|
+
});
|
|
194
|
+
if (hasEach)
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return false;
|
|
200
|
+
});
|
|
173
201
|
if (isElementComplex && elementTypeName) {
|
|
202
|
+
// Skip validation if @IsEnum({ each: true }) is present - enums are handled by @IsEnum
|
|
203
|
+
if (hasIsEnumWithEach) {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
174
206
|
// Complex element type - requires @ValidateNested({ each: true })
|
|
175
207
|
if (!hasValidateNested) {
|
|
176
208
|
context.report({
|
|
@@ -537,6 +537,26 @@ function isComplexType(typeNode, checker = null, esTreeNodeMap = null) {
|
|
|
537
537
|
if (nonValidatableTypes.includes(typeName)) {
|
|
538
538
|
return false;
|
|
539
539
|
}
|
|
540
|
+
// Check if it's an enum type by using the type checker
|
|
541
|
+
// Enums are type references but should not be treated as complex types
|
|
542
|
+
// They can be validated with @IsEnum instead of @ValidateNested
|
|
543
|
+
if (checker && esTreeNodeMap) {
|
|
544
|
+
const tsNode = esTreeNodeMap.get(unwrapped);
|
|
545
|
+
if (tsNode) {
|
|
546
|
+
try {
|
|
547
|
+
const type = checker.getTypeAtLocation(tsNode);
|
|
548
|
+
// Check if it's an enum type using TypeScript's type flags
|
|
549
|
+
// ts.TypeFlags.Enum = 1 << 10 = 1024
|
|
550
|
+
// ts.TypeFlags.EnumLiteral = 1 << 11 = 2048
|
|
551
|
+
if (type.flags & 1024 || type.flags & 2048) {
|
|
552
|
+
return false;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
catch {
|
|
556
|
+
// If we can't determine, continue with other checks
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
540
560
|
// Check for utility types - delegate to the underlying type
|
|
541
561
|
const utilityTypeInfo = getUtilityTypeArgument(unwrapped);
|
|
542
562
|
if (utilityTypeInfo) {
|
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
|
|