@typescript-eslint/eslint-plugin 8.67.1-alpha.20 → 8.67.1-alpha.22
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/rules/enum-utils/shared.d.ts +5 -0
- package/dist/rules/enum-utils/shared.js +67 -0
- package/dist/rules/no-empty-object-type.js +8 -4
- package/dist/rules/no-unnecessary-template-expression.js +2 -14
- package/dist/rules/no-unsafe-argument.js +1 -91
- package/dist/rules/no-unsafe-enum-comparison.js +2 -113
- package/dist/util/FunctionSignature.d.ts +15 -0
- package/dist/util/FunctionSignature.js +123 -0
- package/dist/util/baseTypeUtils.d.ts +2 -0
- package/dist/util/baseTypeUtils.js +16 -0
- package/dist/util/index.d.ts +1 -0
- package/dist/util/index.js +1 -0
- package/package.json +8 -8
|
@@ -19,6 +19,11 @@ export declare function getEnumLiterals(type: ts.Type): ts.LiteralType[];
|
|
|
19
19
|
* - T extends Fruit --> [Fruit]
|
|
20
20
|
*/
|
|
21
21
|
export declare function getEnumTypes(typeChecker: ts.TypeChecker, type: ts.Type): ts.Type[];
|
|
22
|
+
/**
|
|
23
|
+
* @returns Whether two types compare unsafely because an enum value is being
|
|
24
|
+
* compared against a non-enum value of the same primitive kind.
|
|
25
|
+
*/
|
|
26
|
+
export declare function isMismatchedEnumComparisonTypes(typeChecker: ts.TypeChecker, leftType: ts.Type, rightType: ts.Type): boolean;
|
|
22
27
|
/**
|
|
23
28
|
* Returns the enum key that matches the given literal node, or null if none
|
|
24
29
|
* match. For example:
|
|
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.getEnumLiterals = getEnumLiterals;
|
|
37
37
|
exports.getEnumTypes = getEnumTypes;
|
|
38
|
+
exports.isMismatchedEnumComparisonTypes = isMismatchedEnumComparisonTypes;
|
|
38
39
|
exports.getEnumKeyForLiteral = getEnumKeyForLiteral;
|
|
39
40
|
const tsutils = __importStar(require("ts-api-utils"));
|
|
40
41
|
const ts = __importStar(require("typescript"));
|
|
@@ -81,6 +82,72 @@ function getEnumLiterals(type) {
|
|
|
81
82
|
function getEnumTypes(typeChecker, type) {
|
|
82
83
|
return getEnumLiterals(type).map(type => getBaseEnumType(typeChecker, type));
|
|
83
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* @returns Whether two types compare unsafely because an enum value is being
|
|
87
|
+
* compared against a non-enum value of the same primitive kind.
|
|
88
|
+
*/
|
|
89
|
+
function isMismatchedEnumComparisonTypes(typeChecker, leftType, rightType) {
|
|
90
|
+
// Allow comparisons that don't have anything to do with enums:
|
|
91
|
+
//
|
|
92
|
+
// ```ts
|
|
93
|
+
// 1 === 2;
|
|
94
|
+
// ```
|
|
95
|
+
const leftEnumTypes = getEnumTypes(typeChecker, leftType);
|
|
96
|
+
const rightEnumTypes = new Set(getEnumTypes(typeChecker, rightType));
|
|
97
|
+
if (leftEnumTypes.length === 0 && rightEnumTypes.size === 0) {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
// Allow comparisons that share an enum type:
|
|
101
|
+
//
|
|
102
|
+
// ```ts
|
|
103
|
+
// Fruit.Apple === Fruit.Banana;
|
|
104
|
+
// ```
|
|
105
|
+
for (const leftEnumType of leftEnumTypes) {
|
|
106
|
+
if (rightEnumTypes.has(leftEnumType)) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// We need to split the type into the union type parts in order to find
|
|
111
|
+
// valid enum comparisons like:
|
|
112
|
+
//
|
|
113
|
+
// ```ts
|
|
114
|
+
// declare const something: Fruit | Vegetable;
|
|
115
|
+
// something === Fruit.Apple;
|
|
116
|
+
// ```
|
|
117
|
+
const leftTypeParts = tsutils.unionConstituents(leftType);
|
|
118
|
+
const rightTypeParts = tsutils.unionConstituents(rightType);
|
|
119
|
+
// If a type exists in both sides, we consider this comparison safe:
|
|
120
|
+
//
|
|
121
|
+
// ```ts
|
|
122
|
+
// declare const fruit: Fruit.Apple | 0;
|
|
123
|
+
// fruit === 0;
|
|
124
|
+
// ```
|
|
125
|
+
for (const leftTypePart of leftTypeParts) {
|
|
126
|
+
if (rightTypeParts.includes(leftTypePart)) {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return (typeViolates(leftTypeParts, rightType) ||
|
|
131
|
+
typeViolates(rightTypeParts, leftType));
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* @returns Whether the right type is an unsafe comparison against any left type.
|
|
135
|
+
*/
|
|
136
|
+
function typeViolates(leftTypeParts, rightType) {
|
|
137
|
+
const leftEnumValueTypes = new Set(leftTypeParts.map(getEnumValueType));
|
|
138
|
+
return ((leftEnumValueTypes.has(ts.TypeFlags.Number) && (0, util_1.isNumberLike)(rightType)) ||
|
|
139
|
+
(leftEnumValueTypes.has(ts.TypeFlags.String) && (0, util_1.isStringLike)(rightType)));
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* @returns What type a type's enum value is (number or string), if either.
|
|
143
|
+
*/
|
|
144
|
+
function getEnumValueType(type) {
|
|
145
|
+
return tsutils.isTypeFlagSet(type, ts.TypeFlags.EnumLike)
|
|
146
|
+
? tsutils.isTypeFlagSet(type, ts.TypeFlags.NumberLiteral)
|
|
147
|
+
? ts.TypeFlags.Number
|
|
148
|
+
: ts.TypeFlags.String
|
|
149
|
+
: undefined;
|
|
150
|
+
}
|
|
84
151
|
/**
|
|
85
152
|
* Returns the enum key that matches the given literal node, or null if none
|
|
86
153
|
* match. For example:
|
|
@@ -72,15 +72,19 @@ exports.default = (0, util_1.createRule)({
|
|
|
72
72
|
return;
|
|
73
73
|
}
|
|
74
74
|
const scope = context.sourceCode.getScope(node);
|
|
75
|
-
const
|
|
75
|
+
const mergedWithOtherDeclaration = scope.set
|
|
76
76
|
.get(node.id.name)
|
|
77
|
-
?.defs.some(def => def.node
|
|
77
|
+
?.defs.some(def => def.node !== node &&
|
|
78
|
+
(def.node.type === utils_1.AST_NODE_TYPES.ClassDeclaration ||
|
|
79
|
+
def.node.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration));
|
|
80
|
+
const isDefaultExport = node.parent.type === utils_1.AST_NODE_TYPES.ExportDefaultDeclaration;
|
|
81
|
+
const shouldSuggest = !mergedWithOtherDeclaration && !isDefaultExport;
|
|
78
82
|
if (extend.length === 0) {
|
|
79
83
|
context.report({
|
|
80
84
|
node: node.id,
|
|
81
85
|
messageId: 'noEmptyInterface',
|
|
82
86
|
data: { option: 'allowInterfaces' },
|
|
83
|
-
...(
|
|
87
|
+
...(shouldSuggest && {
|
|
84
88
|
suggest: ['object', 'unknown'].map(replacement => ({
|
|
85
89
|
messageId: 'replaceEmptyInterface',
|
|
86
90
|
data: { replacement },
|
|
@@ -99,7 +103,7 @@ exports.default = (0, util_1.createRule)({
|
|
|
99
103
|
context.report({
|
|
100
104
|
node: node.id,
|
|
101
105
|
messageId: 'noEmptyInterfaceWithSuper',
|
|
102
|
-
...(
|
|
106
|
+
...(shouldSuggest && {
|
|
103
107
|
suggest: [
|
|
104
108
|
{
|
|
105
109
|
messageId: 'replaceEmptyInterfaceWithSuper',
|
|
@@ -64,18 +64,6 @@ exports.default = (0, util_1.createRule)({
|
|
|
64
64
|
create(context) {
|
|
65
65
|
const services = (0, util_1.getParserServices)(context);
|
|
66
66
|
const checker = services.program.getTypeChecker();
|
|
67
|
-
function isStringLike(type) {
|
|
68
|
-
return (0, util_1.isTypeFlagSet)(type, ts.TypeFlags.StringLike);
|
|
69
|
-
}
|
|
70
|
-
function isUnderlyingTypeString(type) {
|
|
71
|
-
if (type.isUnion()) {
|
|
72
|
-
return type.types.every(isStringLike);
|
|
73
|
-
}
|
|
74
|
-
if (type.isIntersection()) {
|
|
75
|
-
return type.types.some(isStringLike);
|
|
76
|
-
}
|
|
77
|
-
return isStringLike(type);
|
|
78
|
-
}
|
|
79
67
|
function isEnumMemberType(type) {
|
|
80
68
|
return tsutils.typeConstituents(type).some(t => {
|
|
81
69
|
const symbol = t.getSymbol();
|
|
@@ -315,7 +303,7 @@ exports.default = (0, util_1.createRule)({
|
|
|
315
303
|
if (isTrivialInterpolation(node) &&
|
|
316
304
|
!hasCommentsBetweenQuasi(node.quasis[0], node.quasis[1])) {
|
|
317
305
|
const { constraintType } = (0, util_1.getConstraintInfo)(checker, services.getTypeAtLocation(node.expressions[0]));
|
|
318
|
-
if (constraintType &&
|
|
306
|
+
if (constraintType && (0, util_1.isStringLike)(constraintType)) {
|
|
319
307
|
reportSingleInterpolation(node);
|
|
320
308
|
return;
|
|
321
309
|
}
|
|
@@ -331,7 +319,7 @@ exports.default = (0, util_1.createRule)({
|
|
|
331
319
|
const { constraintType, isTypeParameter } = (0, util_1.getConstraintInfo)(checker, services.getTypeAtLocation(node.types[0]));
|
|
332
320
|
if (constraintType &&
|
|
333
321
|
!isTypeParameter &&
|
|
334
|
-
|
|
322
|
+
(0, util_1.isStringLike)(constraintType) &&
|
|
335
323
|
!isEnumMemberType(constraintType)) {
|
|
336
324
|
reportSingleInterpolation(node);
|
|
337
325
|
return;
|
|
@@ -37,96 +37,6 @@ const utils_1 = require("@typescript-eslint/utils");
|
|
|
37
37
|
const tsutils = __importStar(require("ts-api-utils"));
|
|
38
38
|
const ts = __importStar(require("typescript"));
|
|
39
39
|
const util_1 = require("../util");
|
|
40
|
-
var RestTypeKind;
|
|
41
|
-
(function (RestTypeKind) {
|
|
42
|
-
RestTypeKind[RestTypeKind["Array"] = 0] = "Array";
|
|
43
|
-
RestTypeKind[RestTypeKind["Tuple"] = 1] = "Tuple";
|
|
44
|
-
RestTypeKind[RestTypeKind["Other"] = 2] = "Other";
|
|
45
|
-
})(RestTypeKind || (RestTypeKind = {}));
|
|
46
|
-
class FunctionSignature {
|
|
47
|
-
paramTypes;
|
|
48
|
-
restType;
|
|
49
|
-
hasConsumedArguments = false;
|
|
50
|
-
parameterTypeIndex = 0;
|
|
51
|
-
constructor(paramTypes, restType) {
|
|
52
|
-
this.paramTypes = paramTypes;
|
|
53
|
-
this.restType = restType;
|
|
54
|
-
}
|
|
55
|
-
static create(checker, tsNode) {
|
|
56
|
-
const signature = checker.getResolvedSignature(tsNode);
|
|
57
|
-
if (!signature) {
|
|
58
|
-
return null;
|
|
59
|
-
}
|
|
60
|
-
const paramTypes = [];
|
|
61
|
-
let restType = null;
|
|
62
|
-
const parameters = signature.getParameters();
|
|
63
|
-
for (let i = 0; i < parameters.length; i += 1) {
|
|
64
|
-
const param = parameters[i];
|
|
65
|
-
const type = checker.getTypeOfSymbolAtLocation(param, tsNode);
|
|
66
|
-
const decl = param.getDeclarations()?.[0];
|
|
67
|
-
if (decl && (0, util_1.isRestParameterDeclaration)(decl)) {
|
|
68
|
-
// is a rest param
|
|
69
|
-
if (checker.isArrayType(type)) {
|
|
70
|
-
restType = {
|
|
71
|
-
type: checker.getTypeArguments(type)[0],
|
|
72
|
-
index: i,
|
|
73
|
-
kind: RestTypeKind.Array,
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
else if (checker.isTupleType(type)) {
|
|
77
|
-
restType = {
|
|
78
|
-
index: i,
|
|
79
|
-
kind: RestTypeKind.Tuple,
|
|
80
|
-
typeArguments: checker.getTypeArguments(type),
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
else {
|
|
84
|
-
restType = {
|
|
85
|
-
type,
|
|
86
|
-
index: i,
|
|
87
|
-
kind: RestTypeKind.Other,
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
break;
|
|
91
|
-
}
|
|
92
|
-
paramTypes.push(type);
|
|
93
|
-
}
|
|
94
|
-
return new this(paramTypes, restType);
|
|
95
|
-
}
|
|
96
|
-
consumeRemainingArguments() {
|
|
97
|
-
this.hasConsumedArguments = true;
|
|
98
|
-
}
|
|
99
|
-
getNextParameterType() {
|
|
100
|
-
const index = this.parameterTypeIndex;
|
|
101
|
-
this.parameterTypeIndex += 1;
|
|
102
|
-
if (index >= this.paramTypes.length || this.hasConsumedArguments) {
|
|
103
|
-
if (this.restType == null) {
|
|
104
|
-
return null;
|
|
105
|
-
}
|
|
106
|
-
switch (this.restType.kind) {
|
|
107
|
-
case RestTypeKind.Tuple: {
|
|
108
|
-
const typeArguments = this.restType.typeArguments;
|
|
109
|
-
if (this.hasConsumedArguments) {
|
|
110
|
-
// all types consumed by a rest - just assume it's the last type
|
|
111
|
-
// there is one edge case where this is wrong, but we ignore it because
|
|
112
|
-
// it's rare and really complicated to handle
|
|
113
|
-
// eg: function foo(...a: [number, ...string[], number])
|
|
114
|
-
return typeArguments[typeArguments.length - 1];
|
|
115
|
-
}
|
|
116
|
-
const typeIndex = index - this.restType.index;
|
|
117
|
-
if (typeIndex >= typeArguments.length) {
|
|
118
|
-
return typeArguments[typeArguments.length - 1];
|
|
119
|
-
}
|
|
120
|
-
return typeArguments[typeIndex];
|
|
121
|
-
}
|
|
122
|
-
case RestTypeKind.Array:
|
|
123
|
-
case RestTypeKind.Other:
|
|
124
|
-
return this.restType.type;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
return this.paramTypes[index];
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
40
|
exports.default = (0, util_1.createRule)({
|
|
131
41
|
name: 'no-unsafe-argument',
|
|
132
42
|
meta: {
|
|
@@ -176,7 +86,7 @@ exports.default = (0, util_1.createRule)({
|
|
|
176
86
|
return;
|
|
177
87
|
}
|
|
178
88
|
const tsNode = services.esTreeNodeToTSNodeMap.get(node);
|
|
179
|
-
const signature =
|
|
89
|
+
const signature = util_1.FunctionSignature.create(checker, tsNode);
|
|
180
90
|
if (node.type === utils_1.AST_NODE_TYPES.TaggedTemplateExpression) {
|
|
181
91
|
// Consumes the first parameter (TemplateStringsArray) of the function called with TaggedTemplateExpression.
|
|
182
92
|
signature.getNextParameterType();
|
|
@@ -1,74 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
const tsutils = __importStar(require("ts-api-utils"));
|
|
37
|
-
const ts = __importStar(require("typescript"));
|
|
38
3
|
const util_1 = require("../util");
|
|
39
4
|
const shared_1 = require("./enum-utils/shared");
|
|
40
|
-
/**
|
|
41
|
-
* @returns Whether the right type is an unsafe comparison against any left type.
|
|
42
|
-
*/
|
|
43
|
-
function typeViolates(leftTypeParts, rightType) {
|
|
44
|
-
const leftEnumValueTypes = new Set(leftTypeParts.map(getEnumValueType));
|
|
45
|
-
return ((leftEnumValueTypes.has(ts.TypeFlags.Number) && isNumberLike(rightType)) ||
|
|
46
|
-
(leftEnumValueTypes.has(ts.TypeFlags.String) && isStringLike(rightType)));
|
|
47
|
-
}
|
|
48
|
-
function isNumberLike(type) {
|
|
49
|
-
return tsutils
|
|
50
|
-
.unionConstituents(type)
|
|
51
|
-
.every(unionPart => tsutils
|
|
52
|
-
.intersectionConstituents(unionPart)
|
|
53
|
-
.some(intersectionPart => tsutils.isTypeFlagSet(intersectionPart, ts.TypeFlags.Number | ts.TypeFlags.NumberLike)));
|
|
54
|
-
}
|
|
55
|
-
function isStringLike(type) {
|
|
56
|
-
return tsutils
|
|
57
|
-
.unionConstituents(type)
|
|
58
|
-
.every(unionPart => tsutils
|
|
59
|
-
.intersectionConstituents(unionPart)
|
|
60
|
-
.some(intersectionPart => tsutils.isTypeFlagSet(intersectionPart, ts.TypeFlags.String | ts.TypeFlags.StringLike)));
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* @returns What type a type's enum value is (number or string), if either.
|
|
64
|
-
*/
|
|
65
|
-
function getEnumValueType(type) {
|
|
66
|
-
return tsutils.isTypeFlagSet(type, ts.TypeFlags.EnumLike)
|
|
67
|
-
? tsutils.isTypeFlagSet(type, ts.TypeFlags.NumberLiteral)
|
|
68
|
-
? ts.TypeFlags.Number
|
|
69
|
-
: ts.TypeFlags.String
|
|
70
|
-
: undefined;
|
|
71
|
-
}
|
|
72
5
|
exports.default = (0, util_1.createRule)({
|
|
73
6
|
name: 'no-unsafe-enum-comparison',
|
|
74
7
|
meta: {
|
|
@@ -90,55 +23,11 @@ exports.default = (0, util_1.createRule)({
|
|
|
90
23
|
create(context) {
|
|
91
24
|
const parserServices = (0, util_1.getParserServices)(context);
|
|
92
25
|
const typeChecker = parserServices.program.getTypeChecker();
|
|
93
|
-
function isMismatchedComparison(leftType, rightType) {
|
|
94
|
-
// Allow comparisons that don't have anything to do with enums:
|
|
95
|
-
//
|
|
96
|
-
// ```ts
|
|
97
|
-
// 1 === 2;
|
|
98
|
-
// ```
|
|
99
|
-
const leftEnumTypes = (0, shared_1.getEnumTypes)(typeChecker, leftType);
|
|
100
|
-
const rightEnumTypes = new Set((0, shared_1.getEnumTypes)(typeChecker, rightType));
|
|
101
|
-
if (leftEnumTypes.length === 0 && rightEnumTypes.size === 0) {
|
|
102
|
-
return false;
|
|
103
|
-
}
|
|
104
|
-
// Allow comparisons that share an enum type:
|
|
105
|
-
//
|
|
106
|
-
// ```ts
|
|
107
|
-
// Fruit.Apple === Fruit.Banana;
|
|
108
|
-
// ```
|
|
109
|
-
for (const leftEnumType of leftEnumTypes) {
|
|
110
|
-
if (rightEnumTypes.has(leftEnumType)) {
|
|
111
|
-
return false;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
// We need to split the type into the union type parts in order to find
|
|
115
|
-
// valid enum comparisons like:
|
|
116
|
-
//
|
|
117
|
-
// ```ts
|
|
118
|
-
// declare const something: Fruit | Vegetable;
|
|
119
|
-
// something === Fruit.Apple;
|
|
120
|
-
// ```
|
|
121
|
-
const leftTypeParts = tsutils.unionConstituents(leftType);
|
|
122
|
-
const rightTypeParts = tsutils.unionConstituents(rightType);
|
|
123
|
-
// If a type exists in both sides, we consider this comparison safe:
|
|
124
|
-
//
|
|
125
|
-
// ```ts
|
|
126
|
-
// declare const fruit: Fruit.Apple | 0;
|
|
127
|
-
// fruit === 0;
|
|
128
|
-
// ```
|
|
129
|
-
for (const leftTypePart of leftTypeParts) {
|
|
130
|
-
if (rightTypeParts.includes(leftTypePart)) {
|
|
131
|
-
return false;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
return (typeViolates(leftTypeParts, rightType) ||
|
|
135
|
-
typeViolates(rightTypeParts, leftType));
|
|
136
|
-
}
|
|
137
26
|
return {
|
|
138
27
|
'BinaryExpression[operator=/^[<>!=]?={0,2}$/]'(node) {
|
|
139
28
|
const leftType = parserServices.getTypeAtLocation(node.left);
|
|
140
29
|
const rightType = parserServices.getTypeAtLocation(node.right);
|
|
141
|
-
if (
|
|
30
|
+
if ((0, shared_1.isMismatchedEnumComparisonTypes)(typeChecker, leftType, rightType)) {
|
|
142
31
|
context.report({
|
|
143
32
|
node,
|
|
144
33
|
messageId: 'mismatchedCondition',
|
|
@@ -180,7 +69,7 @@ exports.default = (0, util_1.createRule)({
|
|
|
180
69
|
const { parent } = node;
|
|
181
70
|
const leftType = parserServices.getTypeAtLocation(parent.discriminant);
|
|
182
71
|
const rightType = parserServices.getTypeAtLocation(node.test);
|
|
183
|
-
if (
|
|
72
|
+
if ((0, shared_1.isMismatchedEnumComparisonTypes)(typeChecker, leftType, rightType)) {
|
|
184
73
|
context.report({
|
|
185
74
|
node,
|
|
186
75
|
messageId: 'mismatchedCase',
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as ts from 'typescript';
|
|
2
|
+
/**
|
|
3
|
+
* Tracks checking the parameters of a single function signature.
|
|
4
|
+
* This allows rules to "consume" parameters and check for unsafe comparisons.
|
|
5
|
+
*/
|
|
6
|
+
export declare class FunctionSignature {
|
|
7
|
+
private readonly paramTypes;
|
|
8
|
+
private readonly restType;
|
|
9
|
+
private hasConsumedArguments;
|
|
10
|
+
private parameterTypeIndex;
|
|
11
|
+
private constructor();
|
|
12
|
+
static create(checker: ts.TypeChecker, tsNode: ts.CallLikeExpression): FunctionSignature;
|
|
13
|
+
consumeRemainingArguments(): void;
|
|
14
|
+
getNextParameterType(): ts.Type | null;
|
|
15
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.FunctionSignature = void 0;
|
|
37
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
38
|
+
const ts = __importStar(require("typescript"));
|
|
39
|
+
const misc_1 = require("./misc");
|
|
40
|
+
const { nullThrows } = utils_1.ESLintUtils;
|
|
41
|
+
var RestTypeKind;
|
|
42
|
+
(function (RestTypeKind) {
|
|
43
|
+
RestTypeKind[RestTypeKind["Array"] = 0] = "Array";
|
|
44
|
+
RestTypeKind[RestTypeKind["Other"] = 1] = "Other";
|
|
45
|
+
RestTypeKind[RestTypeKind["Tuple"] = 2] = "Tuple";
|
|
46
|
+
})(RestTypeKind || (RestTypeKind = {}));
|
|
47
|
+
/**
|
|
48
|
+
* Tracks checking the parameters of a single function signature.
|
|
49
|
+
* This allows rules to "consume" parameters and check for unsafe comparisons.
|
|
50
|
+
*/
|
|
51
|
+
class FunctionSignature {
|
|
52
|
+
paramTypes;
|
|
53
|
+
restType;
|
|
54
|
+
hasConsumedArguments = false;
|
|
55
|
+
parameterTypeIndex = 0;
|
|
56
|
+
constructor(paramTypes, restType) {
|
|
57
|
+
this.paramTypes = paramTypes;
|
|
58
|
+
this.restType = restType;
|
|
59
|
+
}
|
|
60
|
+
static create(checker, tsNode) {
|
|
61
|
+
// getResolvedSignature only returns undefined for nodes outside the parse
|
|
62
|
+
// tree, and tsNode always comes from the AST node map.
|
|
63
|
+
const signature = nullThrows(checker.getResolvedSignature(tsNode), 'Expected the call-like node to resolve to a signature.');
|
|
64
|
+
const paramTypes = [];
|
|
65
|
+
let restType = null;
|
|
66
|
+
const parameters = signature.getParameters();
|
|
67
|
+
for (let index = 0; index < parameters.length; index += 1) {
|
|
68
|
+
const param = parameters[index];
|
|
69
|
+
const declaration = param.getDeclarations()?.[0];
|
|
70
|
+
const type = checker.getTypeOfSymbolAtLocation(param, tsNode);
|
|
71
|
+
const constrainedType = checker.getBaseConstraintOfType(type) ?? type;
|
|
72
|
+
if (declaration && (0, misc_1.isRestParameterDeclaration)(declaration)) {
|
|
73
|
+
if (checker.isTupleType(constrainedType)) {
|
|
74
|
+
restType = {
|
|
75
|
+
index,
|
|
76
|
+
kind: RestTypeKind.Tuple,
|
|
77
|
+
typeArguments: checker.getTypeArguments(constrainedType),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
const elementType = checker.getIndexTypeOfType(constrainedType, ts.IndexKind.Number);
|
|
82
|
+
restType = elementType
|
|
83
|
+
? { index, kind: RestTypeKind.Array, type: elementType }
|
|
84
|
+
: { index, kind: RestTypeKind.Other, type: constrainedType };
|
|
85
|
+
}
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
paramTypes.push(type);
|
|
89
|
+
}
|
|
90
|
+
return new FunctionSignature(paramTypes, restType);
|
|
91
|
+
}
|
|
92
|
+
consumeRemainingArguments() {
|
|
93
|
+
this.hasConsumedArguments = true;
|
|
94
|
+
}
|
|
95
|
+
getNextParameterType() {
|
|
96
|
+
const index = this.parameterTypeIndex;
|
|
97
|
+
this.parameterTypeIndex += 1;
|
|
98
|
+
if (index >= this.paramTypes.length || this.hasConsumedArguments) {
|
|
99
|
+
if (this.restType == null) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
switch (this.restType.kind) {
|
|
103
|
+
case RestTypeKind.Tuple: {
|
|
104
|
+
const { typeArguments } = this.restType;
|
|
105
|
+
if (this.hasConsumedArguments) {
|
|
106
|
+
return typeArguments[typeArguments.length - 1];
|
|
107
|
+
}
|
|
108
|
+
const typeIndex = index - this.restType.index;
|
|
109
|
+
if (typeIndex >= typeArguments.length) {
|
|
110
|
+
return typeArguments[typeArguments.length - 1];
|
|
111
|
+
}
|
|
112
|
+
return typeArguments[typeIndex];
|
|
113
|
+
}
|
|
114
|
+
case RestTypeKind.Array:
|
|
115
|
+
case RestTypeKind.Other:
|
|
116
|
+
return this.restType.type;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return this.paramTypes[index];
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
exports.FunctionSignature = FunctionSignature;
|
|
123
|
+
//# sourceMappingURL=FunctionSignature.js.map
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { ParserServicesWithTypeInformation } from '@typescript-eslint/utils';
|
|
2
2
|
import type { InterfaceType, Type } from 'typescript';
|
|
3
3
|
export declare function hasBaseTypes(type: Type): type is InterfaceType;
|
|
4
|
+
export declare function isNumberLike(type: Type): boolean;
|
|
5
|
+
export declare function isStringLike(type: Type): boolean;
|
|
4
6
|
/**
|
|
5
7
|
* Recursively checks if a type or any of its base types matches the provided
|
|
6
8
|
* matcher function.
|
|
@@ -34,6 +34,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.hasBaseTypes = hasBaseTypes;
|
|
37
|
+
exports.isNumberLike = isNumberLike;
|
|
38
|
+
exports.isStringLike = isStringLike;
|
|
37
39
|
exports.matchesTypeOrBaseType = matchesTypeOrBaseType;
|
|
38
40
|
const ts_api_utils_1 = require("ts-api-utils");
|
|
39
41
|
const tsutils = __importStar(require("ts-api-utils"));
|
|
@@ -53,6 +55,20 @@ function hasBaseTypes(type) {
|
|
|
53
55
|
return ((0, ts_api_utils_1.isObjectType)(type) &&
|
|
54
56
|
(0, ts_api_utils_1.isObjectFlagSet)(type, ts.ObjectFlags.Interface | ts.ObjectFlags.Class));
|
|
55
57
|
}
|
|
58
|
+
function isNumberLike(type) {
|
|
59
|
+
return tsutils
|
|
60
|
+
.unionConstituents(type)
|
|
61
|
+
.every(unionPart => tsutils
|
|
62
|
+
.intersectionConstituents(unionPart)
|
|
63
|
+
.some(intersectionPart => tsutils.isTypeFlagSet(intersectionPart, ts.TypeFlags.NumberLike)));
|
|
64
|
+
}
|
|
65
|
+
function isStringLike(type) {
|
|
66
|
+
return tsutils
|
|
67
|
+
.unionConstituents(type)
|
|
68
|
+
.every(unionPart => tsutils
|
|
69
|
+
.intersectionConstituents(unionPart)
|
|
70
|
+
.some(intersectionPart => tsutils.isTypeFlagSet(intersectionPart, ts.TypeFlags.StringLike)));
|
|
71
|
+
}
|
|
56
72
|
/**
|
|
57
73
|
* Recursively checks if a type or any of its base types matches the provided
|
|
58
74
|
* matcher function.
|
package/dist/util/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export * from './astUtils';
|
|
|
3
3
|
export * from './baseTypeUtils';
|
|
4
4
|
export * from './collectUnusedVariables';
|
|
5
5
|
export * from './createRule';
|
|
6
|
+
export * from './FunctionSignature';
|
|
6
7
|
export * from './getBaseTypesOfClassMember';
|
|
7
8
|
export * from './getFixOrSuggest';
|
|
8
9
|
export * from './getFunctionHeadLoc';
|
package/dist/util/index.js
CHANGED
|
@@ -20,6 +20,7 @@ __exportStar(require("./astUtils"), exports);
|
|
|
20
20
|
__exportStar(require("./baseTypeUtils"), exports);
|
|
21
21
|
__exportStar(require("./collectUnusedVariables"), exports);
|
|
22
22
|
__exportStar(require("./createRule"), exports);
|
|
23
|
+
__exportStar(require("./FunctionSignature"), exports);
|
|
23
24
|
__exportStar(require("./getBaseTypesOfClassMember"), exports);
|
|
24
25
|
__exportStar(require("./getFixOrSuggest"), exports);
|
|
25
26
|
__exportStar(require("./getFunctionHeadLoc"), exports);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@typescript-eslint/eslint-plugin",
|
|
3
|
-
"version": "8.67.1-alpha.
|
|
3
|
+
"version": "8.67.1-alpha.22",
|
|
4
4
|
"description": "TypeScript plugin for ESLint",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -47,10 +47,10 @@
|
|
|
47
47
|
],
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@eslint-community/regexpp": "^4.12.2",
|
|
50
|
-
"@typescript-eslint/scope-manager": "8.67.1-alpha.
|
|
51
|
-
"@typescript-eslint/type-utils": "8.67.1-alpha.
|
|
52
|
-
"@typescript-eslint/utils": "8.67.1-alpha.
|
|
53
|
-
"@typescript-eslint/visitor-keys": "8.67.1-alpha.
|
|
50
|
+
"@typescript-eslint/scope-manager": "8.67.1-alpha.22",
|
|
51
|
+
"@typescript-eslint/type-utils": "8.67.1-alpha.22",
|
|
52
|
+
"@typescript-eslint/utils": "8.67.1-alpha.22",
|
|
53
|
+
"@typescript-eslint/visitor-keys": "8.67.1-alpha.22",
|
|
54
54
|
"ignore": "^7.0.5",
|
|
55
55
|
"natural-compare": "^1.4.0",
|
|
56
56
|
"ts-api-utils": "^2.5.0"
|
|
@@ -60,8 +60,8 @@
|
|
|
60
60
|
"@types/mdast": "^4.0.4",
|
|
61
61
|
"@types/natural-compare": "^1.4.3",
|
|
62
62
|
"@types/react": "^18.3.21",
|
|
63
|
-
"@typescript-eslint/rule-schema-to-typescript-types": "8.67.1-alpha.
|
|
64
|
-
"@typescript-eslint/rule-tester": "8.67.1-alpha.
|
|
63
|
+
"@typescript-eslint/rule-schema-to-typescript-types": "8.67.1-alpha.22",
|
|
64
|
+
"@typescript-eslint/rule-tester": "8.67.1-alpha.22",
|
|
65
65
|
"@typescript/native": "npm:typescript@^7.0.2",
|
|
66
66
|
"@vitest/coverage-v8": "^4.0.18",
|
|
67
67
|
"ajv": "^6.12.6",
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"vitest": "^4.0.18"
|
|
82
82
|
},
|
|
83
83
|
"peerDependencies": {
|
|
84
|
-
"@typescript-eslint/parser": "^8.67.1-alpha.
|
|
84
|
+
"@typescript-eslint/parser": "^8.67.1-alpha.22",
|
|
85
85
|
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
|
86
86
|
"typescript": ">=4.8.4 <6.1.0"
|
|
87
87
|
},
|