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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-th-rules",
3
- "version": "2.5.0",
3
+ "version": "2.5.1",
4
4
  "description": "A List of custom ESLint rules created by Tomer Horowitz",
5
5
  "keywords": [
6
6
  "eslint",
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 isZeroOrOneLiteral(node) {
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, literalValue) {
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} ${literalValue}`,
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
- if (
46
- (operator === '>' || operator === '>=')
47
- && isLengthAccess(left)
48
- && isZeroOrOneLiteral(right)
49
- ) {
50
- report(node, left, operator, right.value);
51
- return;
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
- if (
55
- (operator === '!=' || operator === '!==')
56
- && isLengthAccess(left)
57
- && right?.type === 'Literal'
58
- && right.value === 0
59
- ) {
60
- report(node, left, operator, 0);
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 > 0;',
16
+ code: 'values.length === 0;',
20
17
  errors: [
21
18
  {
22
19
  message:
23
- 'Use _.isEmpty(values) instead of checking values.length > 0',
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 >= 1;',
32
+ code: 'values.length < 1;',
30
33
  errors: [
31
34
  {
32
35
  message:
33
- 'Use _.isEmpty(values) instead of checking values.length >= 1',
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 != 0;',
80
+ code: 'values.length >= 1;',
40
81
  errors: [
41
82
  {
42
83
  message:
43
- 'Use _.isEmpty(values) instead of checking values.length != 0',
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
  },