-
-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathno-indexof-equality.ts
More file actions
119 lines (106 loc) · 3.4 KB
/
no-indexof-equality.ts
File metadata and controls
119 lines (106 loc) · 3.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
import type {TSESLint, TSESTree} from '@typescript-eslint/utils';
import {getTypedParserServices} from '../utils/typescript.js';
type MessageIds = 'preferDirectAccess' | 'preferStartsWith';
export const noIndexOfEquality: TSESLint.RuleModule<MessageIds, []> = {
meta: {
type: 'suggestion',
docs: {
description:
'Prefer optimized alternatives to `indexOf()` equality checks'
},
fixable: 'code',
schema: [],
messages: {
preferDirectAccess:
'Use direct array access `{{array}}[{{index}}] === {{item}}` instead of `indexOf() === {{index}}`',
preferStartsWith:
'Use `.startsWith()` instead of `indexOf() === 0` for strings'
}
},
defaultOptions: [],
create(context) {
const sourceCode = context.sourceCode;
const services = getTypedParserServices(context);
const checker = services.program.getTypeChecker();
return {
BinaryExpression(node: TSESTree.BinaryExpression) {
if (node.operator !== '===' && node.operator !== '==') {
return;
}
let indexOfCall: TSESTree.CallExpression | undefined;
let compareIndex: number | undefined;
if (
node.left.type === 'CallExpression' &&
node.right.type === 'Literal' &&
typeof node.right.value === 'number' &&
node.right.value >= 0
) {
indexOfCall = node.left;
compareIndex = node.right.value;
} else if (
node.right.type === 'CallExpression' &&
node.left.type === 'Literal' &&
typeof node.left.value === 'number' &&
node.left.value >= 0
) {
indexOfCall = node.right;
compareIndex = node.left.value;
}
if (!indexOfCall || compareIndex === undefined) {
return;
}
if (
indexOfCall.callee.type !== 'MemberExpression' ||
indexOfCall.callee.property.type !== 'Identifier' ||
indexOfCall.callee.property.name !== 'indexOf'
) {
return;
}
if (indexOfCall.arguments.length !== 1) {
return;
}
const objectNode = indexOfCall.callee.object;
const searchArg = indexOfCall.arguments[0];
const type = services.getTypeAtLocation(objectNode as TSESTree.Node);
if (!type) {
return;
}
const objectText = sourceCode.getText(objectNode);
const searchText = sourceCode.getText(searchArg);
const stringType = checker.getStringType();
if (checker.isTypeAssignableTo(type, stringType)) {
if (compareIndex === 0) {
context.report({
node,
messageId: 'preferStartsWith',
fix(fixer) {
return fixer.replaceText(
node,
`${objectText}.startsWith(${searchText})`
);
}
});
}
return;
}
if (checker.isArrayType(type)) {
context.report({
node,
messageId: 'preferDirectAccess',
data: {
array: objectText,
item: searchText,
index: String(compareIndex)
},
fix(fixer) {
return fixer.replaceText(
node,
`${objectText}[${compareIndex}] === ${searchText}`
);
}
});
}
}
};
}
};