eslint-plugin-th-rules 3.3.2 → 3.4.0
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/README.md +12 -11
- package/dist/plugin.d.ts +8 -2
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +3 -1
- package/dist/rules/no-isnil-isempty-on-boolean.d.ts +6 -0
- package/dist/rules/no-isnil-isempty-on-boolean.d.ts.map +1 -0
- package/dist/rules/no-isnil-isempty-on-boolean.js +122 -0
- package/dist/rules/prefer-explicit-nil-check.d.ts.map +1 -1
- package/dist/rules/prefer-explicit-nil-check.js +29 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -166,17 +166,18 @@ Do not edit below this line.
|
|
|
166
166
|
🎲 Set in the `recommendedTypescriptReact` configuration.\
|
|
167
167
|
🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).
|
|
168
168
|
|
|
169
|
-
| Name
|
|
170
|
-
|
|
|
171
|
-
| [no-boolean-coercion](docs/rules/no-boolean-coercion.md)
|
|
172
|
-
| [no-comments](docs/rules/no-comments.md)
|
|
173
|
-
| [no-default-export](docs/rules/no-default-export.md)
|
|
174
|
-
| [no-destructuring](docs/rules/no-destructuring.md)
|
|
175
|
-
| [no-explicit-nil-compare](docs/rules/no-explicit-nil-compare.md)
|
|
176
|
-
| [prefer-
|
|
177
|
-
| [
|
|
178
|
-
| [
|
|
179
|
-
| [
|
|
169
|
+
| Name | Description | 💼 | 🔧 |
|
|
170
|
+
| :------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------- | :- |
|
|
171
|
+
| [no-boolean-coercion](docs/rules/no-boolean-coercion.md) | Disallow Boolean(value) or !!value. Enforce explicit checks: !_.isNil(value) for scalars and !_.isEmpty(value) for strings, arrays, and objects. If the value is already boolean, remove coercion. | ✅ ⚛️ 🟦 🎲 | 🔧 |
|
|
172
|
+
| [no-comments](docs/rules/no-comments.md) | Disallow comments except for specified allowed patterns. | ✅ ⚛️ 🟦 🎲 | 🔧 |
|
|
173
|
+
| [no-default-export](docs/rules/no-default-export.md) | Convert unnamed default exports to named default exports based on the file name. | ✅ ⚛️ 🟦 🎲 | 🔧 |
|
|
174
|
+
| [no-destructuring](docs/rules/no-destructuring.md) | Disallow destructuring that does not meet certain conditions. | ✅ ⚛️ 🟦 🎲 | |
|
|
175
|
+
| [no-explicit-nil-compare](docs/rules/no-explicit-nil-compare.md) | Disallow direct comparisons to null or undefined. Use _.isNull(x) / _.isUndefined(x) instead. | ✅ ⚛️ 🟦 🎲 | 🔧 |
|
|
176
|
+
| [prefer-explicit-nil-check](docs/rules/prefer-explicit-nil-check.md) | Disallow implicit truthy/falsy checks in boolean-test positions. Prefer explicit _.isNil(value) or _.isEmpty(value) (depending on the value type). | ✅ ⚛️ 🟦 🎲 | 🔧 |
|
|
177
|
+
| [prefer-is-empty](docs/rules/prefer-is-empty.md) | Require _.isEmpty instead of length comparisons or boolean checks on .length. | ✅ ⚛️ 🟦 🎲 | 🔧 |
|
|
178
|
+
| [schemas-in-schemas-file](docs/rules/schemas-in-schemas-file.md) | Require Zod schema declarations to be placed in a .schemas.ts file. | ✅ ⚛️ 🟦 🎲 | |
|
|
179
|
+
| [top-level-functions](docs/rules/top-level-functions.md) | Require all top-level functions to be named regular functions. | ✅ ⚛️ 🟦 🎲 | 🔧 |
|
|
180
|
+
| [types-in-dts](docs/rules/types-in-dts.md) | Require TypeScript type declarations (type/interface/enum) to be placed in .d.ts files. | ✅ ⚛️ 🟦 🎲 | |
|
|
180
181
|
|
|
181
182
|
<!-- end auto-generated rules list -->
|
|
182
183
|
|
package/dist/plugin.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ export declare const rules: {
|
|
|
20
20
|
'no-explicit-nil-compare': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useIsNull" | "useIsUndefined", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
21
21
|
name: string;
|
|
22
22
|
};
|
|
23
|
-
'
|
|
23
|
+
'prefer-explicit-nil-check': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useIsNil", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
24
24
|
name: string;
|
|
25
25
|
};
|
|
26
26
|
'prefer-is-empty': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useIsEmpty" | "useIsEmptyUnary" | "useIsEmptyBoolean", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
@@ -42,6 +42,9 @@ export declare const rules: {
|
|
|
42
42
|
}], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
43
43
|
name: string;
|
|
44
44
|
};
|
|
45
|
+
'no-isnil-isempty-on-boolean': import("@typescript-eslint/utils/ts-eslint").RuleModule<"noBoolean", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
46
|
+
name: string;
|
|
47
|
+
};
|
|
45
48
|
};
|
|
46
49
|
declare const plugin: {
|
|
47
50
|
rules: {
|
|
@@ -66,7 +69,7 @@ declare const plugin: {
|
|
|
66
69
|
'no-explicit-nil-compare': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useIsNull" | "useIsUndefined", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
67
70
|
name: string;
|
|
68
71
|
};
|
|
69
|
-
'
|
|
72
|
+
'prefer-explicit-nil-check': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useIsNil", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
70
73
|
name: string;
|
|
71
74
|
};
|
|
72
75
|
'prefer-is-empty': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useIsEmpty" | "useIsEmptyUnary" | "useIsEmptyBoolean", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
@@ -88,6 +91,9 @@ declare const plugin: {
|
|
|
88
91
|
}], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
89
92
|
name: string;
|
|
90
93
|
};
|
|
94
|
+
'no-isnil-isempty-on-boolean': import("@typescript-eslint/utils/ts-eslint").RuleModule<"noBoolean", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
95
|
+
name: string;
|
|
96
|
+
};
|
|
91
97
|
};
|
|
92
98
|
};
|
|
93
99
|
export default plugin;
|
package/dist/plugin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAaA,eAAO,MAAM,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAYjB,CAAC;AAEF,QAAA,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAAY,CAAC;AACzB,eAAe,MAAM,CAAC"}
|
package/dist/plugin.js
CHANGED
|
@@ -8,17 +8,19 @@ import preferIsEmpty from './rules/prefer-is-empty.js';
|
|
|
8
8
|
import schemasInSchemasFile from './rules/schemas-in-schemas-file.js';
|
|
9
9
|
import topLevelFunctions from './rules/top-level-functions.js';
|
|
10
10
|
import typesInDts from './rules/types-in-dts.js';
|
|
11
|
+
import noIsNilOrIsEmptyOnBoolean from './rules/no-isnil-isempty-on-boolean.js';
|
|
11
12
|
export const rules = {
|
|
12
13
|
'no-boolean-coercion': noBooleanCoercion,
|
|
13
14
|
'no-comments': noComments,
|
|
14
15
|
'no-default-export': noDefaultExport,
|
|
15
16
|
'no-destructuring': noDestructuring,
|
|
16
17
|
'no-explicit-nil-compare': noExplicitNilCompare,
|
|
17
|
-
'
|
|
18
|
+
'prefer-explicit-nil-check': preferExplicitNilCheck,
|
|
18
19
|
'prefer-is-empty': preferIsEmpty,
|
|
19
20
|
'schemas-in-schemas-file': schemasInSchemasFile,
|
|
20
21
|
'top-level-functions': topLevelFunctions,
|
|
21
22
|
'types-in-dts': typesInDts,
|
|
23
|
+
'no-isnil-isempty-on-boolean': noIsNilOrIsEmptyOnBoolean,
|
|
22
24
|
};
|
|
23
25
|
const plugin = { rules };
|
|
24
26
|
export default plugin;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
2
|
+
declare const noIsNilOrIsEmptyOnBoolean: ESLintUtils.RuleModule<"noBoolean", [], unknown, ESLintUtils.RuleListener> & {
|
|
3
|
+
name: string;
|
|
4
|
+
};
|
|
5
|
+
export default noIsNilOrIsEmptyOnBoolean;
|
|
6
|
+
//# sourceMappingURL=no-isnil-isempty-on-boolean.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-isnil-isempty-on-boolean.d.ts","sourceRoot":"","sources":["../../src/rules/no-isnil-isempty-on-boolean.ts"],"names":[],"mappings":"AAIA,OAAO,EAAkB,WAAW,EAAiB,MAAM,0BAA0B,CAAC;AAQtF,QAAA,MAAM,yBAAyB;;CA+H7B,CAAC;AAEH,eAAe,yBAAyB,CAAC"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/* eslint-disable no-bitwise */
|
|
2
|
+
/* eslint-disable new-cap */
|
|
3
|
+
import _ from 'lodash';
|
|
4
|
+
import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils';
|
|
5
|
+
import * as ts from 'typescript';
|
|
6
|
+
const createRule = ESLintUtils.RuleCreator(() => 'https://github.com/tomerh2001/eslint-plugin-th-rules/blob/main/docs/rules/no-isnil-isempty-on-boolean.md');
|
|
7
|
+
const LODASH_IDENT = '_';
|
|
8
|
+
const DISALLOWED_METHODS = new Set(['isNil', 'isEmpty']);
|
|
9
|
+
const noIsNilOrIsEmptyOnBoolean = createRule({
|
|
10
|
+
name: 'no-isnil-isempty-on-boolean',
|
|
11
|
+
meta: {
|
|
12
|
+
type: 'problem',
|
|
13
|
+
docs: {
|
|
14
|
+
description: 'Disallow _.isNil(...) / _.isEmpty(...) when the argument type is boolean or a union containing boolean (e.g., boolean | undefined, boolean | X).',
|
|
15
|
+
},
|
|
16
|
+
schema: [],
|
|
17
|
+
messages: {
|
|
18
|
+
noBoolean: 'Do not use _.{{method}}(...) on boolean or unions containing boolean. Use explicit boolean checks/comparisons instead.',
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
defaultOptions: [],
|
|
22
|
+
create(context) {
|
|
23
|
+
const services = ESLintUtils.getParserServices(context);
|
|
24
|
+
const checker = services.program.getTypeChecker();
|
|
25
|
+
function unwrapChainExpression(node) {
|
|
26
|
+
return node.type === AST_NODE_TYPES.ChainExpression ? node.expression : node;
|
|
27
|
+
}
|
|
28
|
+
function getTypeFromSymbolAnnotation(symbol) {
|
|
29
|
+
const decl = symbol.valueDeclaration ?? symbol.declarations?.[0];
|
|
30
|
+
if (_.isNil(decl))
|
|
31
|
+
return null;
|
|
32
|
+
if ('type' in decl) {
|
|
33
|
+
const typeNode = decl.type;
|
|
34
|
+
if (!_.isNil(typeNode)) {
|
|
35
|
+
return checker.getTypeFromTypeNode(typeNode);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
function getTsTypeForCheck(node) {
|
|
41
|
+
const unwrapped = unwrapChainExpression(node);
|
|
42
|
+
const tsNode = services.esTreeNodeToTSNodeMap.get(unwrapped);
|
|
43
|
+
if (_.isNil(tsNode))
|
|
44
|
+
return null;
|
|
45
|
+
let type;
|
|
46
|
+
if (unwrapped.type === AST_NODE_TYPES.Identifier) {
|
|
47
|
+
const symbol = checker.getSymbolAtLocation(tsNode);
|
|
48
|
+
const annotated = _.isNil(symbol) ? null : getTypeFromSymbolAnnotation(symbol);
|
|
49
|
+
type = annotated ?? checker.getTypeAtLocation(tsNode);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
type = checker.getTypeAtLocation(tsNode);
|
|
53
|
+
}
|
|
54
|
+
return checker.getWidenedType(type);
|
|
55
|
+
}
|
|
56
|
+
function isNullableFlag(flags) {
|
|
57
|
+
return (flags & ts.TypeFlags.Null) !== 0 || (flags & ts.TypeFlags.Undefined) !== 0;
|
|
58
|
+
}
|
|
59
|
+
function isBooleanLikeFlag(flags) {
|
|
60
|
+
return (flags & ts.TypeFlags.BooleanLike) !== 0;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Returns true if the type is boolean-like, or a union containing any boolean-like
|
|
64
|
+
* non-nullish constituent.
|
|
65
|
+
*/
|
|
66
|
+
function hasBooleanInType(node) {
|
|
67
|
+
const type = getTsTypeForCheck(node);
|
|
68
|
+
if (_.isNil(type))
|
|
69
|
+
return false;
|
|
70
|
+
if (!type.isUnion()) {
|
|
71
|
+
const flags = type.getFlags();
|
|
72
|
+
if (isNullableFlag(flags))
|
|
73
|
+
return false;
|
|
74
|
+
return isBooleanLikeFlag(flags);
|
|
75
|
+
}
|
|
76
|
+
for (const t of type.types) {
|
|
77
|
+
const flags = t.getFlags();
|
|
78
|
+
if (isNullableFlag(flags))
|
|
79
|
+
continue;
|
|
80
|
+
if (isBooleanLikeFlag(flags))
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
function isTargetLodashCall(node) {
|
|
86
|
+
if (node.callee.type !== AST_NODE_TYPES.MemberExpression)
|
|
87
|
+
return null;
|
|
88
|
+
const { callee } = node;
|
|
89
|
+
if (callee.object.type !== AST_NODE_TYPES.Identifier)
|
|
90
|
+
return null;
|
|
91
|
+
if (callee.object.name !== LODASH_IDENT)
|
|
92
|
+
return null;
|
|
93
|
+
if (callee.property.type !== AST_NODE_TYPES.Identifier)
|
|
94
|
+
return null;
|
|
95
|
+
const method = callee.property.name;
|
|
96
|
+
if (!DISALLOWED_METHODS.has(method))
|
|
97
|
+
return null;
|
|
98
|
+
return { method: method };
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
CallExpression(node) {
|
|
102
|
+
const target = isTargetLodashCall(node);
|
|
103
|
+
if (_.isNil(target))
|
|
104
|
+
return;
|
|
105
|
+
const [arg] = node.arguments;
|
|
106
|
+
if (_.isNil(arg))
|
|
107
|
+
return;
|
|
108
|
+
if (arg.type === AST_NODE_TYPES.SpreadElement)
|
|
109
|
+
return;
|
|
110
|
+
const expr = arg;
|
|
111
|
+
if (!hasBooleanInType(expr))
|
|
112
|
+
return;
|
|
113
|
+
context.report({
|
|
114
|
+
node,
|
|
115
|
+
messageId: 'noBoolean',
|
|
116
|
+
data: { method: target.method },
|
|
117
|
+
});
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
export default noIsNilOrIsEmptyOnBoolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prefer-explicit-nil-check.d.ts","sourceRoot":"","sources":["../../src/rules/prefer-explicit-nil-check.ts"],"names":[],"mappings":"AAIA,OAAO,EAAkB,WAAW,EAAiB,MAAM,0BAA0B,CAAC;AAWtF,QAAA,MAAM,sBAAsB;;
|
|
1
|
+
{"version":3,"file":"prefer-explicit-nil-check.d.ts","sourceRoot":"","sources":["../../src/rules/prefer-explicit-nil-check.ts"],"names":[],"mappings":"AAIA,OAAO,EAAkB,WAAW,EAAiB,MAAM,0BAA0B,CAAC;AAWtF,QAAA,MAAM,sBAAsB;;CA0Z1B,CAAC;AAEH,eAAe,sBAAsB,CAAC"}
|
|
@@ -65,6 +65,27 @@ const preferExplicitNilCheck = createRule({
|
|
|
65
65
|
function isBooleanLikeFlag(flags) {
|
|
66
66
|
return (flags & ts.TypeFlags.BooleanLike) !== 0;
|
|
67
67
|
}
|
|
68
|
+
function isAnyOrUnknownFlag(flags) {
|
|
69
|
+
return (flags & ts.TypeFlags.Any) !== 0 || (flags & ts.TypeFlags.Unknown) !== 0;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Skip when the type is `any` or `unknown` (including union constituents),
|
|
73
|
+
* because we can't safely decide whether to prefer isNil/isEmpty without
|
|
74
|
+
* risking semantic changes.
|
|
75
|
+
*/
|
|
76
|
+
function isAnyOrUnknownByTS(node) {
|
|
77
|
+
const type = getTsType(node);
|
|
78
|
+
if (_.isNil(type))
|
|
79
|
+
return false;
|
|
80
|
+
if (!type.isUnion()) {
|
|
81
|
+
return isAnyOrUnknownFlag(type.getFlags());
|
|
82
|
+
}
|
|
83
|
+
for (const t of type.types) {
|
|
84
|
+
if (isAnyOrUnknownFlag(t.getFlags()))
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
68
89
|
/**
|
|
69
90
|
* Returns true iff the expression type is effectively:
|
|
70
91
|
* string | null | undefined
|
|
@@ -176,6 +197,8 @@ const preferExplicitNilCheck = createRule({
|
|
|
176
197
|
});
|
|
177
198
|
}
|
|
178
199
|
function transformTruthy(node) {
|
|
200
|
+
if (isAnyOrUnknownByTS(node))
|
|
201
|
+
return;
|
|
179
202
|
if (isNumberByTS(node))
|
|
180
203
|
return;
|
|
181
204
|
const text = context.sourceCode.getText(node);
|
|
@@ -187,6 +210,8 @@ const preferExplicitNilCheck = createRule({
|
|
|
187
210
|
}
|
|
188
211
|
function transformFalsyUnary(node) {
|
|
189
212
|
const arg = node.argument;
|
|
213
|
+
if (isAnyOrUnknownByTS(arg))
|
|
214
|
+
return;
|
|
190
215
|
if (isNumberByTS(arg))
|
|
191
216
|
return;
|
|
192
217
|
const text = context.sourceCode.getText(arg);
|
|
@@ -277,6 +302,8 @@ const preferExplicitNilCheck = createRule({
|
|
|
277
302
|
return;
|
|
278
303
|
}
|
|
279
304
|
if (isImplicitOperand(arg)) {
|
|
305
|
+
if (isAnyOrUnknownByTS(arg))
|
|
306
|
+
return;
|
|
280
307
|
if (isBooleanByTS(arg))
|
|
281
308
|
return;
|
|
282
309
|
if (isNumberByTS(arg))
|
|
@@ -293,6 +320,8 @@ const preferExplicitNilCheck = createRule({
|
|
|
293
320
|
}
|
|
294
321
|
default: {
|
|
295
322
|
if (mode === 'test' && isImplicitOperand(node)) {
|
|
323
|
+
if (isAnyOrUnknownByTS(node))
|
|
324
|
+
return;
|
|
296
325
|
if (isBooleanByTS(node))
|
|
297
326
|
return;
|
|
298
327
|
if (isNumberByTS(node))
|