eslint-plugin-th-rules 2.5.0 → 2.5.1
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/CHANGELOG.md +7 -0
- package/package.json +1 -1
- package/src/index.js +1 -0
- package/src/rules/prefer-is-empty.js +61 -23
- package/tests/prefer-is-empty.test.ts +84 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
## [2.5.1](https://github.com/tomerh2001/eslint-plugin-th-rules/compare/v2.5.0...v2.5.1) (2026-01-14)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* add prefer-is-empty rule to enforce _.isEmpty over length comparisons and update documentation ([06ee673](https://github.com/tomerh2001/eslint-plugin-th-rules/commit/06ee673a0fde79b7cc2af1f72807d9716576310c))
|
|
7
|
+
|
|
1
8
|
# [2.5.0](https://github.com/tomerh2001/eslint-plugin-th-rules/compare/v2.4.0...v2.5.0) (2026-01-14)
|
|
2
9
|
|
|
3
10
|
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -66,6 +66,7 @@ const baseRecommended = {
|
|
|
66
66
|
'th-rules/schemas-in-schemas-file': 'error',
|
|
67
67
|
'th-rules/types-in-dts': 'error',
|
|
68
68
|
'th-rules/no-boolean-coercion': 'error',
|
|
69
|
+
'th-rules/prefer-is-empty': 'error',
|
|
69
70
|
'unicorn/filename-case': 'off',
|
|
70
71
|
'unicorn/no-array-callback-reference': 'off',
|
|
71
72
|
'import/extensions': 'off',
|
|
@@ -6,10 +6,13 @@ const meta = {
|
|
|
6
6
|
recommended: true,
|
|
7
7
|
url: 'https://github.com/tomerh2001/eslint-plugin-th-rules/blob/main/docs/rules/prefer-is-empty.md',
|
|
8
8
|
},
|
|
9
|
+
hasSuggestions: true,
|
|
9
10
|
schema: [],
|
|
10
11
|
};
|
|
11
12
|
|
|
12
13
|
function create(context) {
|
|
14
|
+
const sourceCode = context.getSourceCode();
|
|
15
|
+
|
|
13
16
|
function isLengthAccess(node) {
|
|
14
17
|
return (
|
|
15
18
|
node
|
|
@@ -20,21 +23,27 @@ function create(context) {
|
|
|
20
23
|
);
|
|
21
24
|
}
|
|
22
25
|
|
|
23
|
-
function
|
|
24
|
-
return
|
|
25
|
-
node
|
|
26
|
-
&& node.type === 'Literal'
|
|
27
|
-
&& (node.value === 0 || node.value === 1)
|
|
28
|
-
);
|
|
26
|
+
function isNumericLiteral(node) {
|
|
27
|
+
return node && node.type === 'Literal' && typeof node.value === 'number';
|
|
29
28
|
}
|
|
30
29
|
|
|
31
|
-
function report(node, collectionNode, operator,
|
|
32
|
-
const sourceCode = context.getSourceCode();
|
|
30
|
+
function report(node, collectionNode, operator, value, isEmptyCheck) {
|
|
33
31
|
const collectionText = sourceCode.getText(collectionNode.object);
|
|
32
|
+
const replacement = isEmptyCheck
|
|
33
|
+
? `_.isEmpty(${collectionText})`
|
|
34
|
+
: `!_.isEmpty(${collectionText})`;
|
|
34
35
|
|
|
35
36
|
context.report({
|
|
36
37
|
node,
|
|
37
|
-
message: `Use _.isEmpty(${collectionText}) instead of checking ${collectionText}.length ${operator} ${
|
|
38
|
+
message: `Use _.isEmpty(${collectionText}) instead of checking ${collectionText}.length ${operator} ${value}`,
|
|
39
|
+
suggest: [
|
|
40
|
+
{
|
|
41
|
+
desc: `Replace with ${replacement}`,
|
|
42
|
+
fix(fixer) {
|
|
43
|
+
return fixer.replaceText(node, replacement);
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
],
|
|
38
47
|
});
|
|
39
48
|
}
|
|
40
49
|
|
|
@@ -42,22 +51,51 @@ function create(context) {
|
|
|
42
51
|
BinaryExpression(node) {
|
|
43
52
|
const {left, right, operator} = node;
|
|
44
53
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
54
|
+
// Values.length <op> N
|
|
55
|
+
if (isLengthAccess(left) && isNumericLiteral(right)) {
|
|
56
|
+
const {value} = right;
|
|
57
|
+
|
|
58
|
+
// EMPTY checks
|
|
59
|
+
if (
|
|
60
|
+
(operator === '===' && value === 0)
|
|
61
|
+
|| (operator === '<=' && value === 0)
|
|
62
|
+
|| (operator === '<' && value === 1)
|
|
63
|
+
) {
|
|
64
|
+
report(node, left, operator, value, true);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// NOT EMPTY checks
|
|
69
|
+
if (
|
|
70
|
+
(operator === '>' && value === 0)
|
|
71
|
+
|| (operator === '>=' && value === 1)
|
|
72
|
+
|| ((operator === '!=' || operator === '!==') && value === 0)
|
|
73
|
+
) {
|
|
74
|
+
report(node, left, operator, value, false);
|
|
75
|
+
}
|
|
52
76
|
}
|
|
53
77
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
78
|
+
// N <op> values.length (reversed)
|
|
79
|
+
if (isNumericLiteral(left) && isLengthAccess(right)) {
|
|
80
|
+
const {value} = left;
|
|
81
|
+
|
|
82
|
+
// EMPTY checks
|
|
83
|
+
if (
|
|
84
|
+
(operator === '===' && value === 0)
|
|
85
|
+
|| (operator === '>=' && value === 0)
|
|
86
|
+
|| (operator === '>' && value === 0)
|
|
87
|
+
) {
|
|
88
|
+
report(node, right, operator, value, true);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// NOT EMPTY checks
|
|
93
|
+
if (
|
|
94
|
+
(operator === '<' && value === 1)
|
|
95
|
+
|| (operator === '<=' && value === 0)
|
|
96
|
+
) {
|
|
97
|
+
report(node, right, operator, value, false);
|
|
98
|
+
}
|
|
61
99
|
}
|
|
62
100
|
},
|
|
63
101
|
};
|
|
@@ -7,40 +7,87 @@ ruleTester.run('prefer-is-empty', rule, {
|
|
|
7
7
|
valid: [
|
|
8
8
|
'_.isEmpty(values);',
|
|
9
9
|
'!_.isEmpty(values);',
|
|
10
|
-
'values.length === 0;',
|
|
11
|
-
'values.length <= 0;',
|
|
12
|
-
'values.length < 1;',
|
|
13
10
|
'Array.isArray(values);',
|
|
14
11
|
'values.size > 0;', // Non-length property
|
|
15
12
|
],
|
|
16
13
|
|
|
17
14
|
invalid: [
|
|
18
15
|
{
|
|
19
|
-
code: 'values.length
|
|
16
|
+
code: 'values.length === 0;',
|
|
20
17
|
errors: [
|
|
21
18
|
{
|
|
22
19
|
message:
|
|
23
|
-
'Use _.isEmpty(values) instead of checking values.length
|
|
20
|
+
'Use _.isEmpty(values) instead of checking values.length === 0',
|
|
21
|
+
suggestions: [
|
|
22
|
+
{
|
|
23
|
+
desc: 'Replace with _.isEmpty(values)',
|
|
24
|
+
output: '_.isEmpty(values);',
|
|
25
|
+
},
|
|
26
|
+
],
|
|
24
27
|
},
|
|
25
28
|
],
|
|
26
29
|
},
|
|
27
30
|
|
|
28
31
|
{
|
|
29
|
-
code: 'values.length
|
|
32
|
+
code: 'values.length < 1;',
|
|
30
33
|
errors: [
|
|
31
34
|
{
|
|
32
35
|
message:
|
|
33
|
-
'Use _.isEmpty(values) instead of checking values.length
|
|
36
|
+
'Use _.isEmpty(values) instead of checking values.length < 1',
|
|
37
|
+
suggestions: [
|
|
38
|
+
{
|
|
39
|
+
desc: 'Replace with _.isEmpty(values)',
|
|
40
|
+
output: '_.isEmpty(values);',
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
{
|
|
48
|
+
code: 'values.length <= 0;',
|
|
49
|
+
errors: [
|
|
50
|
+
{
|
|
51
|
+
message:
|
|
52
|
+
'Use _.isEmpty(values) instead of checking values.length <= 0',
|
|
53
|
+
suggestions: [
|
|
54
|
+
{
|
|
55
|
+
desc: 'Replace with _.isEmpty(values)',
|
|
56
|
+
output: '_.isEmpty(values);',
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
{
|
|
64
|
+
code: 'values.length > 0;',
|
|
65
|
+
errors: [
|
|
66
|
+
{
|
|
67
|
+
message:
|
|
68
|
+
'Use _.isEmpty(values) instead of checking values.length > 0',
|
|
69
|
+
suggestions: [
|
|
70
|
+
{
|
|
71
|
+
desc: 'Replace with !_.isEmpty(values)',
|
|
72
|
+
output: '!_.isEmpty(values);',
|
|
73
|
+
},
|
|
74
|
+
],
|
|
34
75
|
},
|
|
35
76
|
],
|
|
36
77
|
},
|
|
37
78
|
|
|
38
79
|
{
|
|
39
|
-
code: 'values.length
|
|
80
|
+
code: 'values.length >= 1;',
|
|
40
81
|
errors: [
|
|
41
82
|
{
|
|
42
83
|
message:
|
|
43
|
-
'Use _.isEmpty(values) instead of checking values.length
|
|
84
|
+
'Use _.isEmpty(values) instead of checking values.length >= 1',
|
|
85
|
+
suggestions: [
|
|
86
|
+
{
|
|
87
|
+
desc: 'Replace with !_.isEmpty(values)',
|
|
88
|
+
output: '!_.isEmpty(values);',
|
|
89
|
+
},
|
|
90
|
+
],
|
|
44
91
|
},
|
|
45
92
|
],
|
|
46
93
|
},
|
|
@@ -51,6 +98,12 @@ ruleTester.run('prefer-is-empty', rule, {
|
|
|
51
98
|
{
|
|
52
99
|
message:
|
|
53
100
|
'Use _.isEmpty(values) instead of checking values.length !== 0',
|
|
101
|
+
suggestions: [
|
|
102
|
+
{
|
|
103
|
+
desc: 'Replace with !_.isEmpty(values)',
|
|
104
|
+
output: '!_.isEmpty(values);',
|
|
105
|
+
},
|
|
106
|
+
],
|
|
54
107
|
},
|
|
55
108
|
],
|
|
56
109
|
},
|
|
@@ -61,6 +114,28 @@ ruleTester.run('prefer-is-empty', rule, {
|
|
|
61
114
|
{
|
|
62
115
|
message:
|
|
63
116
|
'Use _.isEmpty(items) instead of checking items.length > 0',
|
|
117
|
+
suggestions: [
|
|
118
|
+
{
|
|
119
|
+
desc: 'Replace with !_.isEmpty(items)',
|
|
120
|
+
output: 'if (!_.isEmpty(items)) {}',
|
|
121
|
+
},
|
|
122
|
+
],
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
{
|
|
128
|
+
code: '0 === items.length;',
|
|
129
|
+
errors: [
|
|
130
|
+
{
|
|
131
|
+
message:
|
|
132
|
+
'Use _.isEmpty(items) instead of checking items.length === 0',
|
|
133
|
+
suggestions: [
|
|
134
|
+
{
|
|
135
|
+
desc: 'Replace with _.isEmpty(items)',
|
|
136
|
+
output: '_.isEmpty(items);',
|
|
137
|
+
},
|
|
138
|
+
],
|
|
64
139
|
},
|
|
65
140
|
],
|
|
66
141
|
},
|