@dfinity/eslint-config-oisy-wallet 0.2.4 → 0.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
CHANGED
|
@@ -142,6 +142,64 @@ export default [
|
|
|
142
142
|
|
|
143
143
|
Note: To override local rules, make sure you have the `eslint-local-rules.cjs` file at the root as described above.
|
|
144
144
|
|
|
145
|
+
## 📐 Custom Rules
|
|
146
|
+
|
|
147
|
+
This configuration ships with several custom local rules:
|
|
148
|
+
|
|
149
|
+
| Rule | Description |
|
|
150
|
+
| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
|
|
151
|
+
| `local-rules/use-nullish-checks` | Enforce the use of `isNullish()` / `nonNullish()` instead of direct truthiness checks for nullish assertions. |
|
|
152
|
+
| `local-rules/use-nullish-type-wrapper` | Enforce proper usage of nullish type wrappers. |
|
|
153
|
+
| `local-rules/prefer-object-params` | Prefer a single object parameter over multiple positional parameters. |
|
|
154
|
+
| `local-rules/no-svelte-store-in-api` | Disallow importing Svelte stores in API modules. |
|
|
155
|
+
| `local-rules/no-relative-imports` | Disallow relative imports; prefer alias paths. |
|
|
156
|
+
| `local-rules/explicit-non-void-return-type` | Require explicit return types for functions that return a value. |
|
|
157
|
+
|
|
158
|
+
### `use-nullish-checks` Options
|
|
159
|
+
|
|
160
|
+
The rule accepts an optional configuration object:
|
|
161
|
+
|
|
162
|
+
| Option | Type | Default | Description |
|
|
163
|
+
| ------------------------- | --------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
164
|
+
| `includeBooleans` | `boolean` | `false` | When `true`, also enforce nullish checks on variables typed as `boolean` (or `boolean \| null \| undefined`). Boolean expressions — comparisons, known boolean methods, literals, and negations — are always allowed regardless of this setting. |
|
|
165
|
+
| `allowFindUndefinedCheck` | `boolean` | `false` | When `true`, allow direct nullish comparisons on the result of `.find()` calls (e.g. `array.find(x => x) === undefined`). Useful because `.find()` legitimately returns `undefined` when no match is found. |
|
|
166
|
+
|
|
167
|
+
Example:
|
|
168
|
+
|
|
169
|
+
```javascript
|
|
170
|
+
// Enable the rule with boolean variable checking
|
|
171
|
+
"local-rules/use-nullish-checks": ["error", { includeBooleans: true }]
|
|
172
|
+
|
|
173
|
+
// Allow .find() results to be compared with undefined/null directly
|
|
174
|
+
"local-rules/use-nullish-checks": ["error", { allowFindUndefinedCheck: true }]
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
With `includeBooleans: true`:
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
const b: boolean = true;
|
|
181
|
+
|
|
182
|
+
// ❌ Error — boolean variable should use nonNullish()
|
|
183
|
+
if (b) {
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ✅ OK — boolean expression, always allowed
|
|
187
|
+
if (a === b) {
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
With `allowFindUndefinedCheck: true`:
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
// ✅ OK — .find() result compared with undefined is allowed
|
|
195
|
+
if (array.find((item) => item.id === id) === undefined) {
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ✅ OK — .find() result compared with null is allowed
|
|
199
|
+
if (array.find((item) => item.id === id) !== null) {
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
145
203
|
## 🛠️ TypeScript Support
|
|
146
204
|
|
|
147
205
|
If your project uses TypeScript, make sure you have a `tsconfig.json` file in your project root.
|
package/configs/eslint.rules.mjs
CHANGED
|
@@ -83,7 +83,6 @@ export const eslintRules = [
|
|
|
83
83
|
"newlines-between": "never",
|
|
84
84
|
},
|
|
85
85
|
],
|
|
86
|
-
"import/no-duplicates": ["error", { "prefer-inline": true }],
|
|
87
86
|
"import/no-relative-parent-imports": "error",
|
|
88
87
|
|
|
89
88
|
"object-shorthand": "error",
|
|
@@ -91,6 +90,7 @@ export const eslintRules = [
|
|
|
91
90
|
"no-console": ["error", { allow: ["error", "warn"] }],
|
|
92
91
|
"no-continue": "warn",
|
|
93
92
|
"no-delete-var": "error",
|
|
93
|
+
"no-duplicate-imports": "error",
|
|
94
94
|
"no-else-return": ["warn", { allowElseIf: false }],
|
|
95
95
|
"no-restricted-imports": [
|
|
96
96
|
"error",
|
package/eslint-local-rules.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
module.exports = {
|
|
2
2
|
"no-svelte-store-in-api": require("./rules/no-svelte-store-in-api.cjs"),
|
|
3
|
-
"use-option-type-wrapper": require("./rules/use-
|
|
3
|
+
"use-option-type-wrapper": require("./rules/use-nullish-type-wrapper.cjs"),
|
|
4
4
|
"prefer-object-params": require("./rules/prefer-object-params.cjs"),
|
|
5
5
|
"no-relative-imports": require("./rules/no-relative-imports.cjs"),
|
|
6
6
|
"explicit-non-void-return-type": require("./rules/explicit-non-void-return-type.cjs"),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dfinity/eslint-config-oisy-wallet",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "Shared ESLint configurations from the Oisy Wallet team",
|
|
6
6
|
"repository": {
|
|
@@ -44,7 +44,8 @@
|
|
|
44
44
|
"format:check": "prettier --check .",
|
|
45
45
|
"lint": "eslint .",
|
|
46
46
|
"lint:fix": "eslint --fix .",
|
|
47
|
-
"test": "vitest --run"
|
|
47
|
+
"test": "vitest --run",
|
|
48
|
+
"update:peer": "npm update $(node -e \"const p=require('./package.json');console.log(Object.keys(p.peerDependencies).filter(d=>/eslint|prettier/.test(d)).join(' '))\")"
|
|
48
49
|
},
|
|
49
50
|
"keywords": [
|
|
50
51
|
"eslint",
|
|
@@ -70,7 +71,6 @@
|
|
|
70
71
|
"@typescript-eslint/eslint-plugin": "^8.56.0",
|
|
71
72
|
"@vitest/eslint-plugin": "^1.6.9",
|
|
72
73
|
"eslint": "^9.39.2",
|
|
73
|
-
"eslint-config-love": "^151.0.0",
|
|
74
74
|
"eslint-config-prettier": "^10.1.8",
|
|
75
75
|
"eslint-plugin-import": "^2.32.0",
|
|
76
76
|
"eslint-plugin-local-rules": "^3.0.2",
|
|
@@ -82,7 +82,8 @@
|
|
|
82
82
|
"globals": "^17.3.0",
|
|
83
83
|
"prettier": "^3.8.1",
|
|
84
84
|
"prettier-plugin-organize-imports": "^4.3.0",
|
|
85
|
-
"typescript": "^5"
|
|
85
|
+
"typescript": "^5",
|
|
86
|
+
"typescript-eslint": "^8.56.0"
|
|
86
87
|
},
|
|
87
88
|
"devDependencies": {
|
|
88
89
|
"@types/node": "^25.3.0",
|
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
const ts = require("typescript");
|
|
2
|
+
|
|
3
|
+
const NULLISH_UTILS = new Set(["isNullish", "nonNullish"]);
|
|
4
|
+
|
|
5
|
+
// TODO: shall we expand it with `==` and `!=` ?
|
|
6
|
+
const EQ_OPS = new Set(["==="]);
|
|
7
|
+
const NOT_EQ_OPS = new Set(["!=="]);
|
|
8
|
+
const COMPARISON_OPS = new Set([...EQ_OPS, ...NOT_EQ_OPS]);
|
|
9
|
+
const BOOLEAN_BINARY_OPS = new Set([...COMPARISON_OPS, ">", "<", ">=", "<="]);
|
|
10
|
+
const KNOWN_BOOLEAN_METHODS = new Set([
|
|
11
|
+
"includes",
|
|
12
|
+
"startsWith",
|
|
13
|
+
"endsWith",
|
|
14
|
+
"test",
|
|
15
|
+
"some",
|
|
16
|
+
"every",
|
|
17
|
+
]);
|
|
18
|
+
|
|
1
19
|
module.exports = {
|
|
2
20
|
meta: {
|
|
3
21
|
type: "suggestion",
|
|
@@ -13,39 +31,340 @@ module.exports = {
|
|
|
13
31
|
"Use nonNullish() instead of direct variable check for nullish checks.",
|
|
14
32
|
},
|
|
15
33
|
fixable: "code",
|
|
16
|
-
schema: [
|
|
34
|
+
schema: [
|
|
35
|
+
{
|
|
36
|
+
type: "object",
|
|
37
|
+
properties: {
|
|
38
|
+
includeBooleans: {
|
|
39
|
+
type: "boolean",
|
|
40
|
+
description:
|
|
41
|
+
"When true, also enforce nullish checks on variables typed as boolean or nullish boolean. " +
|
|
42
|
+
"Boolean expressions (comparisons, known boolean methods, literals, negations) are always allowed. " +
|
|
43
|
+
"Defaults to false.",
|
|
44
|
+
},
|
|
45
|
+
allowFindUndefinedCheck: {
|
|
46
|
+
type: "boolean",
|
|
47
|
+
description:
|
|
48
|
+
"When true, allow direct nullish comparisons on the result of .find() calls " +
|
|
49
|
+
"(e.g. array.find(x => x) === undefined). " +
|
|
50
|
+
"Defaults to false.",
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
additionalProperties: false,
|
|
54
|
+
},
|
|
55
|
+
],
|
|
17
56
|
},
|
|
18
57
|
|
|
19
58
|
create: (context) => {
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
59
|
+
const [options] = context.options;
|
|
60
|
+
|
|
61
|
+
const shouldLintBooleans = options?.includeBooleans ?? false;
|
|
62
|
+
|
|
63
|
+
const allowFindUndefinedCheck = options?.allowFindUndefinedCheck ?? false;
|
|
64
|
+
|
|
65
|
+
const { sourceCode } = context;
|
|
66
|
+
|
|
67
|
+
const parserServices = sourceCode?.parserServices ?? context.parserServices;
|
|
68
|
+
|
|
69
|
+
const checker = parserServices?.program?.getTypeChecker();
|
|
70
|
+
|
|
71
|
+
const isNullishUtilityCall = (node) =>
|
|
72
|
+
node.type === "CallExpression" &&
|
|
73
|
+
node.callee.type === "Identifier" &&
|
|
74
|
+
NULLISH_UTILS.has(node.callee.name);
|
|
75
|
+
|
|
76
|
+
const hasKnownBooleanMethodCall = (node) =>
|
|
77
|
+
node.type === "CallExpression" &&
|
|
78
|
+
node.callee.type === "MemberExpression" &&
|
|
79
|
+
node.callee.property.type === "Identifier" &&
|
|
80
|
+
KNOWN_BOOLEAN_METHODS.has(node.callee.property.name);
|
|
81
|
+
|
|
82
|
+
const isBooleanBinaryExpression = (node) =>
|
|
83
|
+
node.type === "BinaryExpression" && BOOLEAN_BINARY_OPS.has(node.operator);
|
|
84
|
+
|
|
85
|
+
const isBooleanTypeAnnotation = (typeAnn) => {
|
|
86
|
+
if (!typeAnn) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (typeAnn.type === "TSBooleanKeyword") {
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (typeAnn.type !== "TSUnionType") {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return typeAnn.types.every(
|
|
99
|
+
(typeNode) =>
|
|
100
|
+
typeNode.type === "TSBooleanKeyword" ||
|
|
101
|
+
typeNode.type === "TSNullKeyword" ||
|
|
102
|
+
typeNode.type === "TSUndefinedKeyword" ||
|
|
103
|
+
(typeNode.type === "TSLiteralType" &&
|
|
104
|
+
typeof typeNode.literal?.value === "boolean"),
|
|
105
|
+
);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const findVariableTypeAnnotation = ({ scope, variableName }) => {
|
|
109
|
+
if (!scope) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const variable = scope.set.get(variableName);
|
|
114
|
+
const [def] = variable?.defs ?? [];
|
|
115
|
+
|
|
116
|
+
const typeAnn =
|
|
117
|
+
def?.node?.id?.typeAnnotation?.typeAnnotation ??
|
|
118
|
+
def?.node?.typeAnnotation?.typeAnnotation;
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
typeAnn ??
|
|
122
|
+
findVariableTypeAnnotation({ scope: scope.upper, variableName })
|
|
123
|
+
);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const isBooleanTypeFromScope = (node) => {
|
|
127
|
+
if (node.type === "Literal" && typeof node.value === "boolean") {
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (isBooleanBinaryExpression(node)) {
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (node.type === "UnaryExpression" && node.operator === "!") {
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (isNullishUtilityCall(node)) {
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (hasKnownBooleanMethodCall(node)) {
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (node.type !== "Identifier") {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const initialScope = sourceCode?.getScope
|
|
152
|
+
? sourceCode.getScope(node)
|
|
153
|
+
: context.getScope();
|
|
154
|
+
|
|
155
|
+
const typeAnn = findVariableTypeAnnotation({
|
|
156
|
+
scope: initialScope,
|
|
157
|
+
variableName: node.name,
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
return isBooleanTypeAnnotation(typeAnn);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const isBooleanTypeFromTs = (node) => {
|
|
164
|
+
if (!checker || !parserServices) {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
|
|
169
|
+
const type = checker.getTypeAtLocation(tsNode);
|
|
170
|
+
|
|
171
|
+
const isStrictBoolean = (typeToCheck) =>
|
|
172
|
+
(typeToCheck.getFlags() &
|
|
173
|
+
(ts.TypeFlags.Boolean | ts.TypeFlags.BooleanLiteral)) !==
|
|
174
|
+
0;
|
|
175
|
+
|
|
176
|
+
if (isStrictBoolean(type)) {
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return (
|
|
181
|
+
type.isUnion() &&
|
|
182
|
+
type.types.every(
|
|
183
|
+
(typeToCheck) =>
|
|
184
|
+
(typeToCheck.getFlags() &
|
|
185
|
+
(ts.TypeFlags.Boolean |
|
|
186
|
+
ts.TypeFlags.BooleanLiteral |
|
|
187
|
+
ts.TypeFlags.Null |
|
|
188
|
+
ts.TypeFlags.Undefined)) !==
|
|
189
|
+
0,
|
|
190
|
+
)
|
|
191
|
+
);
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const isBooleanExpression = (node) =>
|
|
195
|
+
isBooleanBinaryExpression(node) ||
|
|
196
|
+
hasKnownBooleanMethodCall(node) ||
|
|
197
|
+
isNullishUtilityCall(node) ||
|
|
198
|
+
(node.type === "Literal" && typeof node.value === "boolean") ||
|
|
199
|
+
(node.type === "UnaryExpression" && node.operator === "!");
|
|
200
|
+
|
|
201
|
+
const shouldTreatAsBooleanCondition = (node) => {
|
|
202
|
+
if (shouldLintBooleans) {
|
|
203
|
+
return isBooleanExpression(node);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
|
208
|
+
return isBooleanTypeFromTs(node) || isBooleanTypeFromScope(node);
|
|
209
|
+
} catch {
|
|
210
|
+
return isBooleanTypeFromScope(node);
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const isNullishLiteral = (expression) =>
|
|
215
|
+
(expression.type === "Identifier" && expression.name === "undefined") ||
|
|
216
|
+
(expression.type === "Literal" && expression.value === null);
|
|
217
|
+
|
|
218
|
+
const isFindCall = (node) =>
|
|
219
|
+
node.type === "CallExpression" &&
|
|
220
|
+
node.callee.type === "MemberExpression" &&
|
|
221
|
+
node.callee.property.type === "Identifier" &&
|
|
222
|
+
node.callee.property.name === "find";
|
|
223
|
+
|
|
224
|
+
const getNullishComparisonTarget = (node) => {
|
|
225
|
+
if (node.type !== "BinaryExpression") {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (!COMPARISON_OPS.has(node.operator)) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (isNullishLiteral(node.right)) {
|
|
234
|
+
return node.left;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (isNullishLiteral(node.left)) {
|
|
238
|
+
return node.right;
|
|
28
239
|
}
|
|
29
240
|
};
|
|
30
241
|
|
|
31
|
-
const
|
|
242
|
+
const getNullishReplacement = (operator) =>
|
|
243
|
+
EQ_OPS.has(operator) ? "isNullish" : "nonNullish";
|
|
244
|
+
|
|
245
|
+
const report = ({ node, messageId, replacementFn, fixNode }) => {
|
|
32
246
|
context.report({
|
|
33
247
|
node,
|
|
34
|
-
messageId
|
|
248
|
+
messageId,
|
|
35
249
|
fix: (fixer) =>
|
|
36
250
|
fixer.replaceText(
|
|
37
251
|
node,
|
|
38
|
-
`${
|
|
252
|
+
`${replacementFn}(${sourceCode ? sourceCode.getText(fixNode) : context.getSourceCode().getText(fixNode)})`,
|
|
39
253
|
),
|
|
40
254
|
});
|
|
41
255
|
};
|
|
42
256
|
|
|
257
|
+
const checkCondition = (node) => {
|
|
258
|
+
if (!node) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (isNullishUtilityCall(node)) {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (getNullishComparisonTarget(node)) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (node.type === "UnaryExpression" && node.operator === "!") {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (node.type === "LogicalExpression") {
|
|
275
|
+
checkCondition(node.left);
|
|
276
|
+
|
|
277
|
+
checkCondition(node.right);
|
|
278
|
+
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (shouldTreatAsBooleanCondition(node)) {
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
report({
|
|
287
|
+
node,
|
|
288
|
+
messageId: "nonNullish",
|
|
289
|
+
replacementFn: "nonNullish",
|
|
290
|
+
fixNode: node,
|
|
291
|
+
});
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
// ESLint AST puts the condition of if/while/for/ternary in `node.test`, so we analyse that specifically
|
|
295
|
+
const checkTestCondition = (node) => {
|
|
296
|
+
if (node.test) {
|
|
297
|
+
checkCondition(node.test);
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
|
|
43
301
|
return {
|
|
44
302
|
BinaryExpression: (node) => {
|
|
45
|
-
|
|
46
|
-
|
|
303
|
+
const target = getNullishComparisonTarget(node);
|
|
304
|
+
|
|
305
|
+
if (!target) {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (allowFindUndefinedCheck && isFindCall(target)) {
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const replacementFn = getNullishReplacement(node.operator);
|
|
314
|
+
|
|
315
|
+
report({
|
|
316
|
+
node,
|
|
317
|
+
messageId: replacementFn,
|
|
318
|
+
replacementFn,
|
|
319
|
+
fixNode: target,
|
|
320
|
+
});
|
|
321
|
+
},
|
|
322
|
+
|
|
323
|
+
UnaryExpression: (node) => {
|
|
324
|
+
if (
|
|
325
|
+
node.operator === "!" &&
|
|
326
|
+
node.argument.type === "UnaryExpression" &&
|
|
327
|
+
node.argument.operator === "!"
|
|
328
|
+
) {
|
|
329
|
+
if (shouldTreatAsBooleanCondition(node.argument.argument)) {
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
report({
|
|
334
|
+
node,
|
|
335
|
+
messageId: "nonNullish",
|
|
336
|
+
replacementFn: "nonNullish",
|
|
337
|
+
fixNode: node.argument.argument,
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (node.operator === "!") {
|
|
344
|
+
if (shouldTreatAsBooleanCondition(node.argument)) {
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const { parent } = node;
|
|
349
|
+
|
|
350
|
+
if (parent?.type === "UnaryExpression" && parent.operator === "!") {
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
report({
|
|
355
|
+
node,
|
|
356
|
+
messageId: "isNullish",
|
|
357
|
+
replacementFn: "isNullish",
|
|
358
|
+
fixNode: node.argument,
|
|
359
|
+
});
|
|
47
360
|
}
|
|
48
361
|
},
|
|
362
|
+
|
|
363
|
+
IfStatement: checkTestCondition,
|
|
364
|
+
WhileStatement: checkTestCondition,
|
|
365
|
+
DoWhileStatement: checkTestCondition,
|
|
366
|
+
ForStatement: checkTestCondition,
|
|
367
|
+
ConditionalExpression: checkTestCondition,
|
|
49
368
|
};
|
|
50
369
|
},
|
|
51
370
|
};
|
|
@@ -2,19 +2,19 @@ module.exports = {
|
|
|
2
2
|
meta: {
|
|
3
3
|
type: "suggestion",
|
|
4
4
|
docs: {
|
|
5
|
-
description: "Enforce use of
|
|
5
|
+
description: "Enforce use of Nullish<T> instead of T | null | undefined",
|
|
6
6
|
category: "Best Practices",
|
|
7
7
|
recommended: false,
|
|
8
8
|
},
|
|
9
9
|
messages: {
|
|
10
|
-
|
|
11
|
-
"Use
|
|
10
|
+
useNullish:
|
|
11
|
+
"Use Nullish<{{ type }}> instead of {{ type }} | null | undefined.",
|
|
12
12
|
},
|
|
13
13
|
fixable: "code",
|
|
14
14
|
schema: [],
|
|
15
15
|
},
|
|
16
16
|
create: (context) => {
|
|
17
|
-
const
|
|
17
|
+
const checkForNullishType = (node) => {
|
|
18
18
|
if (
|
|
19
19
|
node.typeAnnotation.type === "TSUnionType" &&
|
|
20
20
|
node.typeAnnotation.types.length === 3 &&
|
|
@@ -34,12 +34,12 @@ module.exports = {
|
|
|
34
34
|
try {
|
|
35
35
|
context.report({
|
|
36
36
|
node,
|
|
37
|
-
messageId: "
|
|
37
|
+
messageId: "useNullish",
|
|
38
38
|
data: {
|
|
39
39
|
type: typeText,
|
|
40
40
|
},
|
|
41
41
|
fix: (fixer) =>
|
|
42
|
-
fixer.replaceText(node.typeAnnotation, `
|
|
42
|
+
fixer.replaceText(node.typeAnnotation, `Nullish<${typeText}>`),
|
|
43
43
|
});
|
|
44
44
|
} catch (e) {
|
|
45
45
|
console.error(e);
|
|
@@ -52,10 +52,10 @@ module.exports = {
|
|
|
52
52
|
|
|
53
53
|
return {
|
|
54
54
|
TSTypeAnnotation: (node) => {
|
|
55
|
-
|
|
55
|
+
checkForNullishType(node);
|
|
56
56
|
},
|
|
57
57
|
TSTypeAliasDeclaration: (node) => {
|
|
58
|
-
|
|
58
|
+
checkForNullishType(node);
|
|
59
59
|
},
|
|
60
60
|
};
|
|
61
61
|
},
|