eslint-plugin-class-validator-type-match 3.1.2 → 3.1.4
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.
|
@@ -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
|
|
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',
|
|
@@ -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
|
}
|
|
@@ -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.
|