eslint-plugin-th-rules 3.2.3 → 3.3.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/dist/plugin.d.ts +6 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +2 -0
- package/dist/rules/no-boolean-coercion.d.ts.map +1 -1
- package/dist/rules/no-boolean-coercion.js +5 -5
- package/dist/rules/no-destructuring.d.ts.map +1 -1
- package/dist/rules/no-destructuring.js +5 -4
- package/dist/rules/no-explicit-nil-compare.js +3 -3
- package/dist/rules/prefer-explicit-nil-check.d.ts +6 -0
- package/dist/rules/prefer-explicit-nil-check.d.ts.map +1 -0
- package/dist/rules/prefer-explicit-nil-check.js +309 -0
- package/dist/rules/prefer-is-empty.js +5 -5
- package/dist/rules/schemas-in-schemas-file.d.ts.map +1 -1
- package/dist/rules/schemas-in-schemas-file.js +2 -1
- package/dist/rules/top-level-functions.d.ts.map +1 -1
- package/dist/rules/top-level-functions.js +7 -6
- package/dist/rules/types-in-dts.d.ts.map +1 -1
- package/dist/rules/types-in-dts.js +2 -1
- package/package.json +1 -1
package/dist/plugin.d.ts
CHANGED
|
@@ -20,6 +20,9 @@ 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
|
+
'no-explicit-nil-check': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useIsNil", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
24
|
+
name: string;
|
|
25
|
+
};
|
|
23
26
|
'prefer-is-empty': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useIsEmpty" | "useIsEmptyUnary" | "useIsEmptyBoolean", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
24
27
|
name: string;
|
|
25
28
|
};
|
|
@@ -63,6 +66,9 @@ declare const plugin: {
|
|
|
63
66
|
'no-explicit-nil-compare': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useIsNull" | "useIsUndefined", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
64
67
|
name: string;
|
|
65
68
|
};
|
|
69
|
+
'no-explicit-nil-check': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useIsNil", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
70
|
+
name: string;
|
|
71
|
+
};
|
|
66
72
|
'prefer-is-empty': import("@typescript-eslint/utils/ts-eslint").RuleModule<"useIsEmpty" | "useIsEmptyUnary" | "useIsEmptyBoolean", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
67
73
|
name: string;
|
|
68
74
|
};
|
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":"AAYA,eAAO,MAAM,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAWjB,CAAC;AAEF,QAAA,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAAY,CAAC;AACzB,eAAe,MAAM,CAAC"}
|
package/dist/plugin.js
CHANGED
|
@@ -3,6 +3,7 @@ import noComments from './rules/no-comments.js';
|
|
|
3
3
|
import noDefaultExport from './rules/no-default-export.js';
|
|
4
4
|
import noDestructuring from './rules/no-destructuring.js';
|
|
5
5
|
import noExplicitNilCompare from './rules/no-explicit-nil-compare.js';
|
|
6
|
+
import preferExplicitNilCheck from './rules/prefer-explicit-nil-check.js';
|
|
6
7
|
import preferIsEmpty from './rules/prefer-is-empty.js';
|
|
7
8
|
import schemasInSchemasFile from './rules/schemas-in-schemas-file.js';
|
|
8
9
|
import topLevelFunctions from './rules/top-level-functions.js';
|
|
@@ -13,6 +14,7 @@ export const rules = {
|
|
|
13
14
|
'no-default-export': noDefaultExport,
|
|
14
15
|
'no-destructuring': noDestructuring,
|
|
15
16
|
'no-explicit-nil-compare': noExplicitNilCompare,
|
|
17
|
+
'no-explicit-nil-check': preferExplicitNilCheck,
|
|
16
18
|
'prefer-is-empty': preferIsEmpty,
|
|
17
19
|
'schemas-in-schemas-file': schemasInSchemasFile,
|
|
18
20
|
'top-level-functions': topLevelFunctions,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-boolean-coercion.d.ts","sourceRoot":"","sources":["../../src/rules/no-boolean-coercion.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"no-boolean-coercion.d.ts","sourceRoot":"","sources":["../../src/rules/no-boolean-coercion.ts"],"names":[],"mappings":"AAGA,OAAO,EAAkB,WAAW,EAAiB,MAAM,0BAA0B,CAAC;AAStF,QAAA,MAAM,iBAAiB;;CAyHrB,CAAC;AAEH,eAAe,iBAAiB,CAAC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* eslint-disable new-cap */
|
|
2
|
-
|
|
2
|
+
import _ from 'lodash';
|
|
3
3
|
import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils';
|
|
4
4
|
import * as ts from 'typescript';
|
|
5
5
|
const createRule = ESLintUtils.RuleCreator(() => 'https://github.com/tomerh2001/eslint-plugin-th-rules/blob/main/docs/rules/no-boolean-coercion.md');
|
|
@@ -32,7 +32,7 @@ const noBooleanCoercion = createRule({
|
|
|
32
32
|
}
|
|
33
33
|
const firstNode = context.sourceCode.ast.body[0];
|
|
34
34
|
const importText = `import ${LODASH_IDENT} from '${LODASH_MODULE}';\n`;
|
|
35
|
-
if (
|
|
35
|
+
if (_.isNil(firstNode)) {
|
|
36
36
|
return fixer.insertTextAfterRange([0, 0], importText);
|
|
37
37
|
}
|
|
38
38
|
return fixer.insertTextBefore(firstNode, importText);
|
|
@@ -45,7 +45,7 @@ const noBooleanCoercion = createRule({
|
|
|
45
45
|
}
|
|
46
46
|
function isBooleanByTS(node) {
|
|
47
47
|
const tsNode = services.esTreeNodeToTSNodeMap.get(node);
|
|
48
|
-
if (
|
|
48
|
+
if (_.isNil(tsNode)) {
|
|
49
49
|
return false;
|
|
50
50
|
}
|
|
51
51
|
const type = checker.getTypeAtLocation(tsNode);
|
|
@@ -53,7 +53,7 @@ const noBooleanCoercion = createRule({
|
|
|
53
53
|
}
|
|
54
54
|
function isCollectionLikeByTS(node) {
|
|
55
55
|
const tsNode = services.esTreeNodeToTSNodeMap.get(node);
|
|
56
|
-
if (
|
|
56
|
+
if (_.isNil(tsNode)) {
|
|
57
57
|
return false;
|
|
58
58
|
}
|
|
59
59
|
const type = checker.getTypeAtLocation(tsNode);
|
|
@@ -83,7 +83,7 @@ const noBooleanCoercion = createRule({
|
|
|
83
83
|
fix(fixer) {
|
|
84
84
|
const fixes = [fixer.replaceText(node, replacement)];
|
|
85
85
|
const importFix = getLodashImportFixer(fixer);
|
|
86
|
-
if (importFix) {
|
|
86
|
+
if (!_.isNil(importFix)) {
|
|
87
87
|
fixes.push(importFix);
|
|
88
88
|
}
|
|
89
89
|
return fixes;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-destructuring.d.ts","sourceRoot":"","sources":["../../src/rules/no-destructuring.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"no-destructuring.d.ts","sourceRoot":"","sources":["../../src/rules/no-destructuring.ts"],"names":[],"mappings":"AAGA,OAAO,EAAkB,WAAW,EAAiB,MAAM,0BAA0B,CAAC;AAItF,QAAA,MAAM,eAAe;;;;;CAyInB,CAAC;AACH,eAAe,eAAe,CAAC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/* eslint-disable new-cap */
|
|
2
2
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
3
|
+
import _ from 'lodash';
|
|
3
4
|
import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils';
|
|
4
5
|
const MAX_TAB_COUNT = 3;
|
|
5
6
|
const noDestructuring = ESLintUtils.RuleCreator(() => 'https://github.com/tomerh2001/eslint-plugin-th-rules/blob/main/docs/rules/no-destructuring.md')({
|
|
@@ -35,7 +36,7 @@ const noDestructuring = ESLintUtils.RuleCreator(() => 'https://github.com/tomerh
|
|
|
35
36
|
const MAX_VARIABLES = options.maximumDestructuredVariables ?? 2;
|
|
36
37
|
const MAX_LINE_LENGTH = options.maximumLineLength ?? 100;
|
|
37
38
|
function reportIfNeeded(patternNode, reportNode = patternNode) {
|
|
38
|
-
if (patternNode?.type !== AST_NODE_TYPES.ObjectPattern ||
|
|
39
|
+
if (patternNode?.type !== AST_NODE_TYPES.ObjectPattern || _.isNil(patternNode.loc)) {
|
|
39
40
|
return;
|
|
40
41
|
}
|
|
41
42
|
const startLine = patternNode.loc.start.line;
|
|
@@ -81,7 +82,7 @@ const noDestructuring = ESLintUtils.RuleCreator(() => 'https://github.com/tomerh
|
|
|
81
82
|
}
|
|
82
83
|
function checkParameters(parameters) {
|
|
83
84
|
for (const p of parameters || []) {
|
|
84
|
-
if (
|
|
85
|
+
if (_.isNil(p)) {
|
|
85
86
|
continue;
|
|
86
87
|
}
|
|
87
88
|
if (p.type === AST_NODE_TYPES.AssignmentPattern) {
|
|
@@ -105,12 +106,12 @@ const noDestructuring = ESLintUtils.RuleCreator(() => 'https://github.com/tomerh
|
|
|
105
106
|
checkParameters(node.params);
|
|
106
107
|
},
|
|
107
108
|
MethodDefinition(node) {
|
|
108
|
-
if (node.value?.params) {
|
|
109
|
+
if (!_.isNil(node.value?.params)) {
|
|
109
110
|
checkParameters(node.value.params);
|
|
110
111
|
}
|
|
111
112
|
},
|
|
112
113
|
TSDeclareFunction(node) {
|
|
113
|
-
if (node.params) {
|
|
114
|
+
if (!_.isNil(node.params)) {
|
|
114
115
|
checkParameters(node.params);
|
|
115
116
|
}
|
|
116
117
|
},
|
|
@@ -23,7 +23,7 @@ const noExplicitNilCompare = createRule({
|
|
|
23
23
|
/** Ensure lodash default import exists */
|
|
24
24
|
function ensureLodashImport(fixer) {
|
|
25
25
|
const existingImport = context.sourceCode.ast.body.find((node) => node.type === AST_NODE_TYPES.ImportDeclaration && node.source.value === 'lodash');
|
|
26
|
-
if (existingImport)
|
|
26
|
+
if (!_.isNil(existingImport))
|
|
27
27
|
return null;
|
|
28
28
|
return fixer.insertTextBeforeRange([0, 0], `import _ from 'lodash';\n`);
|
|
29
29
|
}
|
|
@@ -51,7 +51,7 @@ const noExplicitNilCompare = createRule({
|
|
|
51
51
|
else if (isUndefinedIdentifier(left)) {
|
|
52
52
|
targetNode = right;
|
|
53
53
|
}
|
|
54
|
-
if (
|
|
54
|
+
if (_.isNil(targetNode))
|
|
55
55
|
return;
|
|
56
56
|
const text = context.sourceCode.getText(targetNode);
|
|
57
57
|
const positive = isNull ? `_.isNull(${text})` : `_.isUndefined(${text})`;
|
|
@@ -63,7 +63,7 @@ const noExplicitNilCompare = createRule({
|
|
|
63
63
|
fix(fixer) {
|
|
64
64
|
const fixes = [];
|
|
65
65
|
const importFix = ensureLodashImport(fixer);
|
|
66
|
-
if (importFix)
|
|
66
|
+
if (!_.isNil(importFix))
|
|
67
67
|
fixes.push(importFix);
|
|
68
68
|
fixes.push(fixer.replaceText(node, replacement));
|
|
69
69
|
return fixes;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
2
|
+
declare const preferExplicitNilCheck: ESLintUtils.RuleModule<"useIsNil", [], unknown, ESLintUtils.RuleListener> & {
|
|
3
|
+
name: string;
|
|
4
|
+
};
|
|
5
|
+
export default preferExplicitNilCheck;
|
|
6
|
+
//# sourceMappingURL=prefer-explicit-nil-check.d.ts.map
|
|
@@ -0,0 +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;;CAoW1B,CAAC;AAEH,eAAe,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,309 @@
|
|
|
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/prefer-explicit-nil-check.md');
|
|
7
|
+
const LODASH_MODULE = 'lodash';
|
|
8
|
+
const LODASH_IDENT = '_';
|
|
9
|
+
const preferExplicitNilCheck = createRule({
|
|
10
|
+
name: 'prefer-explicit-nil-check',
|
|
11
|
+
meta: {
|
|
12
|
+
type: 'problem',
|
|
13
|
+
fixable: 'code',
|
|
14
|
+
docs: {
|
|
15
|
+
description: 'Disallow implicit truthy/falsy checks in boolean-test positions. Prefer explicit _.isNil(value) or _.isEmpty(value) (depending on the value type).',
|
|
16
|
+
},
|
|
17
|
+
schema: [],
|
|
18
|
+
messages: {
|
|
19
|
+
useIsNil: 'Implicit truthy/falsy checks are not allowed. Use _.isNil(value) / !_.isNil(value) or _.isEmpty(value) / !_.isEmpty(value).',
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
defaultOptions: [],
|
|
23
|
+
create(context) {
|
|
24
|
+
const services = ESLintUtils.getParserServices(context);
|
|
25
|
+
const checker = services.program.getTypeChecker();
|
|
26
|
+
const reported = new WeakSet();
|
|
27
|
+
let lodashImportFixAdded = false;
|
|
28
|
+
function hasLodashImport() {
|
|
29
|
+
return context.sourceCode.ast.body.some((node) => node.type === AST_NODE_TYPES.ImportDeclaration && node.source.value === LODASH_MODULE);
|
|
30
|
+
}
|
|
31
|
+
function getLodashImportFixer(fixer) {
|
|
32
|
+
if (hasLodashImport())
|
|
33
|
+
return null;
|
|
34
|
+
if (lodashImportFixAdded)
|
|
35
|
+
return null;
|
|
36
|
+
lodashImportFixAdded = true;
|
|
37
|
+
const firstNode = context.sourceCode.ast.body[0];
|
|
38
|
+
const importText = `import ${LODASH_IDENT} from '${LODASH_MODULE}';\n`;
|
|
39
|
+
if (_.isNil(firstNode)) {
|
|
40
|
+
return fixer.insertTextAfterRange([0, 0], importText);
|
|
41
|
+
}
|
|
42
|
+
return fixer.insertTextBefore(firstNode, importText);
|
|
43
|
+
}
|
|
44
|
+
function unwrapChainExpression(node) {
|
|
45
|
+
if (node.type === AST_NODE_TYPES.ChainExpression)
|
|
46
|
+
return node.expression;
|
|
47
|
+
return node;
|
|
48
|
+
}
|
|
49
|
+
function getTsType(node) {
|
|
50
|
+
const unwrapped = unwrapChainExpression(node);
|
|
51
|
+
const tsNode = services.esTreeNodeToTSNodeMap.get(unwrapped);
|
|
52
|
+
if (_.isNil(tsNode))
|
|
53
|
+
return null;
|
|
54
|
+
return checker.getTypeAtLocation(tsNode);
|
|
55
|
+
}
|
|
56
|
+
function isNullableFlag(flags) {
|
|
57
|
+
return (flags & ts.TypeFlags.Null) !== 0 || (flags & ts.TypeFlags.Undefined) !== 0;
|
|
58
|
+
}
|
|
59
|
+
function isStringLikeFlag(flags) {
|
|
60
|
+
return (flags & ts.TypeFlags.String) !== 0 || (flags & ts.TypeFlags.StringLiteral) !== 0;
|
|
61
|
+
}
|
|
62
|
+
function isNumberLikeFlag(flags) {
|
|
63
|
+
return (flags & ts.TypeFlags.Number) !== 0 || (flags & ts.TypeFlags.NumberLiteral) !== 0;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Returns true iff the expression type is effectively:
|
|
67
|
+
* string | null | undefined
|
|
68
|
+
* (i.e., all non-nullish constituents are string/string-literal).
|
|
69
|
+
*/
|
|
70
|
+
function isStringByTS(node) {
|
|
71
|
+
const type = getTsType(node);
|
|
72
|
+
if (_.isNil(type))
|
|
73
|
+
return false;
|
|
74
|
+
if (!type.isUnion()) {
|
|
75
|
+
const flags = type.getFlags();
|
|
76
|
+
return isStringLikeFlag(flags);
|
|
77
|
+
}
|
|
78
|
+
let sawNonNullish = false;
|
|
79
|
+
for (const t of type.types) {
|
|
80
|
+
const flags = t.getFlags();
|
|
81
|
+
if (isNullableFlag(flags))
|
|
82
|
+
continue;
|
|
83
|
+
sawNonNullish = true;
|
|
84
|
+
if (!isStringLikeFlag(flags))
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
return sawNonNullish;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Returns true iff the expression type is effectively:
|
|
91
|
+
* number | null | undefined
|
|
92
|
+
*
|
|
93
|
+
* We intentionally DO NOT auto-fix numeric truthy/falsy checks because
|
|
94
|
+
* converting `if (n)` into `!_.isNil(n)` changes semantics for `0`.
|
|
95
|
+
*/
|
|
96
|
+
function isNumberByTS(node) {
|
|
97
|
+
const type = getTsType(node);
|
|
98
|
+
if (_.isNil(type))
|
|
99
|
+
return false;
|
|
100
|
+
if (!type.isUnion()) {
|
|
101
|
+
const flags = type.getFlags();
|
|
102
|
+
return isNumberLikeFlag(flags);
|
|
103
|
+
}
|
|
104
|
+
let sawNonNullish = false;
|
|
105
|
+
for (const t of type.types) {
|
|
106
|
+
const flags = t.getFlags();
|
|
107
|
+
if (isNullableFlag(flags))
|
|
108
|
+
continue;
|
|
109
|
+
sawNonNullish = true;
|
|
110
|
+
if (!isNumberLikeFlag(flags))
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
return sawNonNullish;
|
|
114
|
+
}
|
|
115
|
+
function isBooleanByTS(node) {
|
|
116
|
+
const type = getTsType(node);
|
|
117
|
+
if (_.isNil(type))
|
|
118
|
+
return false;
|
|
119
|
+
const flags = type.getFlags();
|
|
120
|
+
return (flags & ts.TypeFlags.Boolean) !== 0 || (flags & ts.TypeFlags.BooleanLiteral) !== 0;
|
|
121
|
+
}
|
|
122
|
+
function isAlreadyExplicitCheck(node) {
|
|
123
|
+
return (node.type === AST_NODE_TYPES.CallExpression &&
|
|
124
|
+
node.callee.type === AST_NODE_TYPES.MemberExpression &&
|
|
125
|
+
node.callee.object.type === AST_NODE_TYPES.Identifier &&
|
|
126
|
+
node.callee.object.name === LODASH_IDENT &&
|
|
127
|
+
node.callee.property.type === AST_NODE_TYPES.Identifier &&
|
|
128
|
+
(node.callee.property.name === 'isNil' || node.callee.property.name === 'isEmpty'));
|
|
129
|
+
}
|
|
130
|
+
function isImplicitOperand(node) {
|
|
131
|
+
if (node.type === AST_NODE_TYPES.Identifier)
|
|
132
|
+
return true;
|
|
133
|
+
if (node.type === AST_NODE_TYPES.MemberExpression)
|
|
134
|
+
return true;
|
|
135
|
+
if (node.type === AST_NODE_TYPES.ChainExpression) {
|
|
136
|
+
return node.expression.type === AST_NODE_TYPES.MemberExpression;
|
|
137
|
+
}
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
function reportFull(node, replacement) {
|
|
141
|
+
if (reported.has(node))
|
|
142
|
+
return;
|
|
143
|
+
reported.add(node);
|
|
144
|
+
context.report({
|
|
145
|
+
node,
|
|
146
|
+
messageId: 'useIsNil',
|
|
147
|
+
fix(fixer) {
|
|
148
|
+
const fixes = [fixer.replaceText(node, replacement)];
|
|
149
|
+
const importFix = getLodashImportFixer(fixer);
|
|
150
|
+
if (!_.isNil(importFix))
|
|
151
|
+
fixes.push(importFix);
|
|
152
|
+
return fixes;
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
function transformTruthy(node) {
|
|
157
|
+
if (isNumberByTS(node))
|
|
158
|
+
return;
|
|
159
|
+
const text = context.sourceCode.getText(node);
|
|
160
|
+
if (isStringByTS(node)) {
|
|
161
|
+
reportFull(node, `!${LODASH_IDENT}.isEmpty(${text})`);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
reportFull(node, `!${LODASH_IDENT}.isNil(${text})`);
|
|
165
|
+
}
|
|
166
|
+
function transformFalsyUnary(node) {
|
|
167
|
+
const arg = node.argument;
|
|
168
|
+
if (isNumberByTS(arg))
|
|
169
|
+
return;
|
|
170
|
+
const text = context.sourceCode.getText(arg);
|
|
171
|
+
if (isStringByTS(arg)) {
|
|
172
|
+
reportFull(node, `${LODASH_IDENT}.isEmpty(${text})`);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
reportFull(node, `${LODASH_IDENT}.isNil(${text})`);
|
|
176
|
+
}
|
|
177
|
+
function expressionHasSideEffects(node) {
|
|
178
|
+
switch (node.type) {
|
|
179
|
+
case AST_NODE_TYPES.CallExpression:
|
|
180
|
+
case AST_NODE_TYPES.NewExpression:
|
|
181
|
+
case AST_NODE_TYPES.AssignmentExpression:
|
|
182
|
+
case AST_NODE_TYPES.UpdateExpression:
|
|
183
|
+
case AST_NODE_TYPES.AwaitExpression:
|
|
184
|
+
case AST_NODE_TYPES.YieldExpression:
|
|
185
|
+
case AST_NODE_TYPES.ImportExpression:
|
|
186
|
+
case AST_NODE_TYPES.TaggedTemplateExpression: {
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
case AST_NODE_TYPES.SequenceExpression: {
|
|
190
|
+
return node.expressions.some(expressionHasSideEffects);
|
|
191
|
+
}
|
|
192
|
+
case AST_NODE_TYPES.UnaryExpression: {
|
|
193
|
+
return node.operator === 'delete';
|
|
194
|
+
}
|
|
195
|
+
case AST_NODE_TYPES.ConditionalExpression: {
|
|
196
|
+
return expressionHasSideEffects(node.test) || expressionHasSideEffects(node.consequent) || expressionHasSideEffects(node.alternate);
|
|
197
|
+
}
|
|
198
|
+
case AST_NODE_TYPES.LogicalExpression: {
|
|
199
|
+
return expressionHasSideEffects(node.left) || expressionHasSideEffects(node.right);
|
|
200
|
+
}
|
|
201
|
+
case AST_NODE_TYPES.ChainExpression: {
|
|
202
|
+
return expressionHasSideEffects(node.expression);
|
|
203
|
+
}
|
|
204
|
+
default: {
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Inspect an expression for truthy/falsy coercions.
|
|
211
|
+
*
|
|
212
|
+
* mode:
|
|
213
|
+
* - 'test': expression is in a boolean-test position (must enforce)
|
|
214
|
+
* - 'shortCircuit': expression-statement short-circuit control flow (must enforce gating)
|
|
215
|
+
* - 'value': value context (must NOT rewrite operands)
|
|
216
|
+
*/
|
|
217
|
+
function inspectExpression(node, mode) {
|
|
218
|
+
if (isAlreadyExplicitCheck(node))
|
|
219
|
+
return;
|
|
220
|
+
switch (node.type) {
|
|
221
|
+
case AST_NODE_TYPES.ChainExpression: {
|
|
222
|
+
inspectExpression(node.expression, mode);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
case AST_NODE_TYPES.LogicalExpression: {
|
|
226
|
+
if (node.operator === '??') {
|
|
227
|
+
inspectExpression(node.left, 'value');
|
|
228
|
+
inspectExpression(node.right, 'value');
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
if (node.operator === '&&' || node.operator === '||') {
|
|
232
|
+
if (mode === 'test') {
|
|
233
|
+
inspectExpression(node.left, 'test');
|
|
234
|
+
inspectExpression(node.right, 'test');
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
if (mode === 'shortCircuit') {
|
|
238
|
+
inspectExpression(node.left, 'test');
|
|
239
|
+
inspectExpression(node.right, 'shortCircuit');
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
inspectExpression(node.left, 'value');
|
|
243
|
+
inspectExpression(node.right, 'value');
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
inspectExpression(node.left, 'value');
|
|
247
|
+
inspectExpression(node.right, 'value');
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
case AST_NODE_TYPES.UnaryExpression: {
|
|
251
|
+
if (node.operator !== '!')
|
|
252
|
+
return;
|
|
253
|
+
const arg = node.argument;
|
|
254
|
+
if (arg.type === AST_NODE_TYPES.UnaryExpression && arg.operator === '!') {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
if (isImplicitOperand(arg)) {
|
|
258
|
+
if (isBooleanByTS(arg))
|
|
259
|
+
return;
|
|
260
|
+
if (isNumberByTS(arg))
|
|
261
|
+
return;
|
|
262
|
+
transformFalsyUnary(node);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
inspectExpression(arg, 'test');
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
case AST_NODE_TYPES.ConditionalExpression: {
|
|
269
|
+
inspectExpression(node.test, 'test');
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
default: {
|
|
273
|
+
if (mode === 'test' && isImplicitOperand(node)) {
|
|
274
|
+
if (isBooleanByTS(node))
|
|
275
|
+
return;
|
|
276
|
+
if (isNumberByTS(node))
|
|
277
|
+
return;
|
|
278
|
+
transformTruthy(node);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
IfStatement(node) {
|
|
285
|
+
inspectExpression(node.test, 'test');
|
|
286
|
+
},
|
|
287
|
+
WhileStatement(node) {
|
|
288
|
+
inspectExpression(node.test, 'test');
|
|
289
|
+
},
|
|
290
|
+
DoWhileStatement(node) {
|
|
291
|
+
inspectExpression(node.test, 'test');
|
|
292
|
+
},
|
|
293
|
+
ForStatement(node) {
|
|
294
|
+
if (!_.isNil(node.test))
|
|
295
|
+
inspectExpression(node.test, 'test');
|
|
296
|
+
},
|
|
297
|
+
ConditionalExpression(node) {
|
|
298
|
+
inspectExpression(node.test, 'test');
|
|
299
|
+
},
|
|
300
|
+
ExpressionStatement(node) {
|
|
301
|
+
const expr = node.expression;
|
|
302
|
+
if (expr.type === AST_NODE_TYPES.LogicalExpression && (expr.operator === '&&' || expr.operator === '||') && expressionHasSideEffects(expr.right)) {
|
|
303
|
+
inspectExpression(expr, 'shortCircuit');
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
};
|
|
307
|
+
},
|
|
308
|
+
});
|
|
309
|
+
export default preferExplicitNilCheck;
|
|
@@ -27,7 +27,7 @@ const preferIsEmpty = ESLintUtils.RuleCreator(() => 'https://github.com/tomerh20
|
|
|
27
27
|
if (hasLodash)
|
|
28
28
|
return null;
|
|
29
29
|
const firstImport = imports[0];
|
|
30
|
-
return firstImport ? fixer.
|
|
30
|
+
return _.isNil(firstImport) ? fixer.insertTextBeforeRange([0, 0], `import _ from 'lodash';\n`) : fixer.insertTextBefore(firstImport, `import _ from 'lodash';\n`);
|
|
31
31
|
}
|
|
32
32
|
function unwrapChain(node) {
|
|
33
33
|
return node?.type === AST_NODE_TYPES.ChainExpression ? node.expression : node;
|
|
@@ -51,7 +51,7 @@ const preferIsEmpty = ESLintUtils.RuleCreator(() => 'https://github.com/tomerh20
|
|
|
51
51
|
}
|
|
52
52
|
function isBooleanContext(node) {
|
|
53
53
|
const { parent } = node;
|
|
54
|
-
if (
|
|
54
|
+
if (_.isNil(parent))
|
|
55
55
|
return false;
|
|
56
56
|
switch (parent.type) {
|
|
57
57
|
case AST_NODE_TYPES.IfStatement:
|
|
@@ -87,7 +87,7 @@ const preferIsEmpty = ESLintUtils.RuleCreator(() => 'https://github.com/tomerh20
|
|
|
87
87
|
fix(fixer) {
|
|
88
88
|
const fixes = [fixer.replaceText(node, replacement)];
|
|
89
89
|
const importFix = ensureLodashImport(fixer);
|
|
90
|
-
if (importFix)
|
|
90
|
+
if (!_.isNil(importFix))
|
|
91
91
|
fixes.push(importFix);
|
|
92
92
|
return fixes;
|
|
93
93
|
},
|
|
@@ -102,7 +102,7 @@ const preferIsEmpty = ESLintUtils.RuleCreator(() => 'https://github.com/tomerh20
|
|
|
102
102
|
fix(fixer) {
|
|
103
103
|
const fixes = [fixer.replaceText(node, `_.isEmpty(${collection})`)];
|
|
104
104
|
const importFix = ensureLodashImport(fixer);
|
|
105
|
-
if (importFix)
|
|
105
|
+
if (!_.isNil(importFix))
|
|
106
106
|
fixes.push(importFix);
|
|
107
107
|
return fixes;
|
|
108
108
|
},
|
|
@@ -117,7 +117,7 @@ const preferIsEmpty = ESLintUtils.RuleCreator(() => 'https://github.com/tomerh20
|
|
|
117
117
|
fix(fixer) {
|
|
118
118
|
const fixes = [fixer.replaceText(node, `!_.isEmpty(${collection})`)];
|
|
119
119
|
const importFix = ensureLodashImport(fixer);
|
|
120
|
-
if (importFix)
|
|
120
|
+
if (!_.isNil(importFix))
|
|
121
121
|
fixes.push(importFix);
|
|
122
122
|
return fixes;
|
|
123
123
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schemas-in-schemas-file.d.ts","sourceRoot":"","sources":["../../src/rules/schemas-in-schemas-file.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"schemas-in-schemas-file.d.ts","sourceRoot":"","sources":["../../src/rules/schemas-in-schemas-file.ts"],"names":[],"mappings":"AAGA,OAAO,EAAkB,WAAW,EAAiB,MAAM,0BAA0B,CAAC;AAEtF,QAAA,MAAM,oBAAoB;;;;;;CAmIxB,CAAC;AAEH,eAAe,oBAAoB,CAAC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
|
2
2
|
/* eslint-disable new-cap */
|
|
3
|
+
import _ from 'lodash';
|
|
3
4
|
import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils';
|
|
4
5
|
const schemasInSchemasFile = ESLintUtils.RuleCreator(() => 'https://github.com/tomerh2001/eslint-plugin-th-rules/blob/main/docs/rules/schemas-in-schemas-file.md')({
|
|
5
6
|
name: 'schemas-in-schemas-file',
|
|
@@ -41,7 +42,7 @@ const schemasInSchemasFile = ESLintUtils.RuleCreator(() => 'https://github.com/t
|
|
|
41
42
|
};
|
|
42
43
|
const zodIdentifiers = new Set();
|
|
43
44
|
function filenameAllowed(filename) {
|
|
44
|
-
if (
|
|
45
|
+
if (_.isNil(filename) || filename === '<input>') {
|
|
45
46
|
return false;
|
|
46
47
|
}
|
|
47
48
|
if (options.allowInTests && /\.(test|spec)\.[jt]sx?$/.test(filename)) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"top-level-functions.d.ts","sourceRoot":"","sources":["../../src/rules/top-level-functions.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"top-level-functions.d.ts","sourceRoot":"","sources":["../../src/rules/top-level-functions.ts"],"names":[],"mappings":"AACA,OAAO,EAAkB,WAAW,EAAiB,MAAM,0BAA0B,CAAC;AAEtF,QAAA,MAAM,iBAAiB;;CA4JrB,CAAC;AACH,eAAe,iBAAiB,CAAC"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils';
|
|
2
3
|
const topLevelFunctions = ESLintUtils.RuleCreator(() => 'https://github.com/tomerh2001/eslint-plugin-th-rules/blob/main/docs/rules/top-level-functions.md')({
|
|
3
4
|
name: 'top-level-functions',
|
|
4
5
|
meta: {
|
|
@@ -62,7 +63,7 @@ const topLevelFunctions = ESLintUtils.RuleCreator(() => 'https://github.com/tome
|
|
|
62
63
|
VariableDeclarator(node) {
|
|
63
64
|
const declParent = node.parent;
|
|
64
65
|
const grandParent = declParent?.parent;
|
|
65
|
-
if (
|
|
66
|
+
if (_.isNil(grandParent)) {
|
|
66
67
|
return;
|
|
67
68
|
}
|
|
68
69
|
const topLevel = grandParent.type === 'Program' || grandParent.type === 'ExportNamedDeclaration' || grandParent.type === 'ExportDefaultDeclaration';
|
|
@@ -70,11 +71,11 @@ const topLevelFunctions = ESLintUtils.RuleCreator(() => 'https://github.com/tome
|
|
|
70
71
|
return;
|
|
71
72
|
}
|
|
72
73
|
const isExport = grandParent.type === 'ExportNamedDeclaration' || grandParent.type === 'ExportDefaultDeclaration';
|
|
73
|
-
if (
|
|
74
|
+
if (_.isNil(node.init)) {
|
|
74
75
|
return;
|
|
75
76
|
}
|
|
76
77
|
const functionName = node.id.type === 'Identifier' ? node.id.name : null;
|
|
77
|
-
if (
|
|
78
|
+
if (_.isNil(functionName)) {
|
|
78
79
|
return;
|
|
79
80
|
}
|
|
80
81
|
if (node.init.type === 'ArrowFunctionExpression') {
|
|
@@ -88,7 +89,7 @@ const topLevelFunctions = ESLintUtils.RuleCreator(() => 'https://github.com/tome
|
|
|
88
89
|
},
|
|
89
90
|
});
|
|
90
91
|
}
|
|
91
|
-
if (node.init.type ===
|
|
92
|
+
if (node.init.type === AST_NODE_TYPES.FunctionExpression) {
|
|
92
93
|
const funcExpr = node.init;
|
|
93
94
|
context.report({
|
|
94
95
|
node: funcExpr,
|
|
@@ -101,7 +102,7 @@ const topLevelFunctions = ESLintUtils.RuleCreator(() => 'https://github.com/tome
|
|
|
101
102
|
}
|
|
102
103
|
},
|
|
103
104
|
FunctionDeclaration(node) {
|
|
104
|
-
if (node.id) {
|
|
105
|
+
if (!_.isNil(node.id)) {
|
|
105
106
|
return;
|
|
106
107
|
}
|
|
107
108
|
if (!isTopLevel(node)) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types-in-dts.d.ts","sourceRoot":"","sources":["../../src/rules/types-in-dts.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"types-in-dts.d.ts","sourceRoot":"","sources":["../../src/rules/types-in-dts.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAiB,MAAM,0BAA0B,CAAC;AAEtE,QAAA,MAAM,UAAU;;;;;CAkFd,CAAC;AACH,eAAe,UAAU,CAAC"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/* eslint-disable new-cap */
|
|
2
|
+
import _ from 'lodash';
|
|
2
3
|
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
3
4
|
const typesInDts = ESLintUtils.RuleCreator(() => 'https://github.com/tomerh2001/eslint-plugin-th-rules/blob/main/docs/rules/types-in-dts.md')({
|
|
4
5
|
name: 'types-in-dts',
|
|
@@ -29,7 +30,7 @@ const typesInDts = ESLintUtils.RuleCreator(() => 'https://github.com/tomerh2001/
|
|
|
29
30
|
],
|
|
30
31
|
create(context, [options]) {
|
|
31
32
|
function isDtsFile(filename) {
|
|
32
|
-
if (
|
|
33
|
+
if (_.isNil(filename) || filename === '<input>') {
|
|
33
34
|
return false;
|
|
34
35
|
}
|
|
35
36
|
return filename.endsWith('.d.ts');
|