eslint-plugin-th-rules 3.3.3 → 3.4.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/README.md +12 -12
- package/dist/plugin.d.ts +6 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +2 -0
- 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 +22 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -166,18 +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-
|
|
176
|
-
| [
|
|
177
|
-
| [prefer-is-empty](docs/rules/prefer-is-empty.md)
|
|
178
|
-
| [schemas-in-schemas-file](docs/rules/schemas-in-schemas-file.md)
|
|
179
|
-
| [top-level-functions](docs/rules/top-level-functions.md)
|
|
180
|
-
| [types-in-dts](docs/rules/types-in-dts.md)
|
|
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. | ✅ ⚛️ 🟦 🎲 | |
|
|
181
181
|
|
|
182
182
|
<!-- end auto-generated rules list -->
|
|
183
183
|
|
package/dist/plugin.d.ts
CHANGED
|
@@ -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: {
|
|
@@ -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,6 +8,7 @@ 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,
|
|
@@ -19,6 +20,7 @@ export const rules = {
|
|
|
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;;CAmb1B,CAAC;AAEH,eAAe,sBAAsB,CAAC"}
|
|
@@ -46,12 +46,33 @@ const preferExplicitNilCheck = createRule({
|
|
|
46
46
|
return node.expression;
|
|
47
47
|
return node;
|
|
48
48
|
}
|
|
49
|
+
function getTypeFromSymbolAnnotation(symbol) {
|
|
50
|
+
const decl = symbol.valueDeclaration ?? symbol.declarations?.[0];
|
|
51
|
+
if (_.isNil(decl))
|
|
52
|
+
return null;
|
|
53
|
+
if ('type' in decl) {
|
|
54
|
+
const typeNode = decl.type;
|
|
55
|
+
if (!_.isNil(typeNode)) {
|
|
56
|
+
return checker.getTypeFromTypeNode(typeNode);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
49
61
|
function getTsType(node) {
|
|
50
62
|
const unwrapped = unwrapChainExpression(node);
|
|
51
63
|
const tsNode = services.esTreeNodeToTSNodeMap.get(unwrapped);
|
|
52
64
|
if (_.isNil(tsNode))
|
|
53
65
|
return null;
|
|
54
|
-
|
|
66
|
+
let type;
|
|
67
|
+
if (unwrapped.type === AST_NODE_TYPES.Identifier) {
|
|
68
|
+
const symbol = checker.getSymbolAtLocation(tsNode);
|
|
69
|
+
const annotated = _.isNil(symbol) ? null : getTypeFromSymbolAnnotation(symbol);
|
|
70
|
+
type = annotated ?? checker.getTypeAtLocation(tsNode);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
type = checker.getTypeAtLocation(tsNode);
|
|
74
|
+
}
|
|
75
|
+
return checker.getWidenedType(type);
|
|
55
76
|
}
|
|
56
77
|
function isNullableFlag(flags) {
|
|
57
78
|
return (flags & ts.TypeFlags.Null) !== 0 || (flags & ts.TypeFlags.Undefined) !== 0;
|