eslint-plugin-unicorn 68.0.0 → 70.0.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/package.json +3 -1
- package/readme.md +22 -0
- package/rules/ast/is-empty-object-expression.js +2 -2
- package/rules/comment-content.js +136 -18
- package/rules/consistent-boolean-name.js +665 -37
- package/rules/consistent-conditional-object-spread.js +9 -3
- package/rules/consistent-tuple-labels.js +49 -0
- package/rules/custom-error-definition.js +26 -7
- package/rules/expiring-todo-comments.js +23 -0
- package/rules/fix/{add-parenthesizes-to-return-or-throw-expression.js → add-parentheses-to-return-or-throw-expression.js} +1 -1
- package/rules/fix/index.js +1 -1
- package/rules/fix/switch-new-expression-to-call-expression.js +2 -2
- package/rules/index.js +22 -0
- package/rules/isolated-functions.js +156 -12
- package/rules/no-async-promise-finally.js +111 -0
- package/rules/no-chained-comparison.js +1 -1
- package/rules/no-collection-bracket-access.js +27 -4
- package/rules/no-computed-property-existence-check.js +6 -0
- package/rules/no-incorrect-template-string-interpolation.js +82 -30
- package/rules/no-instanceof-builtins.js +59 -13
- package/rules/no-invalid-well-known-symbol-methods.js +205 -0
- package/rules/no-late-current-target-access.js +9 -71
- package/rules/no-late-event-control.js +127 -0
- package/rules/no-manually-wrapped-comments.js +5 -1
- package/rules/no-negated-array-predicate.js +2 -2
- package/rules/no-negated-comparison.js +24 -61
- package/rules/no-negated-condition.js +2 -2
- package/rules/no-negation-in-equality-check.js +2 -2
- package/rules/no-non-function-verb-prefix.js +25 -2
- package/rules/no-nonstandard-builtin-properties.js +2 -0
- package/rules/no-object-methods-with-collections.js +4 -289
- package/rules/no-return-array-push.js +28 -6
- package/rules/no-top-level-side-effects.js +8 -1
- package/rules/no-typeof-undefined.js +2 -2
- package/rules/no-unnecessary-array-flat-map.js +307 -0
- package/rules/no-unnecessary-await.js +2 -2
- package/rules/no-unnecessary-fetch-options.js +624 -0
- package/rules/no-unnecessary-global-this.js +36 -1
- package/rules/no-unreadable-object-destructuring.js +15 -0
- package/rules/no-unsafe-promise-all-settled-values.js +865 -0
- package/rules/no-unsafe-string-replacement.js +31 -0
- package/rules/no-unused-array-method-return.js +11 -2
- package/rules/no-useless-boolean-cast.js +58 -1
- package/rules/no-useless-concat.js +27 -3
- package/rules/no-useless-else.js +48 -3
- package/rules/no-useless-iterator-to-array.js +194 -118
- package/rules/no-useless-spread.js +2 -2
- package/rules/prefer-abort-signal-any.js +1391 -0
- package/rules/prefer-abort-signal-timeout.js +395 -0
- package/rules/prefer-aggregate-error.js +478 -0
- package/rules/prefer-array-flat-map.js +6 -11
- package/rules/prefer-array-from-range.js +98 -0
- package/rules/prefer-at.js +5 -6
- package/rules/prefer-block-statement-over-iife.js +159 -0
- package/rules/prefer-code-point.js +58 -8
- package/rules/prefer-continue.js +21 -1
- package/rules/prefer-dom-node-replace-children.js +353 -0
- package/rules/prefer-early-return.js +3 -1
- package/rules/prefer-error-is-error.js +181 -0
- package/rules/prefer-group-by.js +576 -0
- package/rules/prefer-has-check.js +1 -4
- package/rules/prefer-hoisting-branch-code.js +9 -7
- package/rules/prefer-iterator-concat.js +21 -1
- package/rules/prefer-iterator-helpers.js +196 -0
- package/rules/prefer-minimal-ternary.js +15 -11
- package/rules/prefer-modern-dom-apis.js +1 -45
- package/rules/prefer-number-coercion.js +1 -1
- package/rules/prefer-object-iterable-methods.js +57 -14
- package/rules/prefer-observer-apis.js +498 -0
- package/rules/prefer-promise-try.js +238 -0
- package/rules/prefer-set-methods.js +337 -0
- package/rules/prefer-simplified-conditions.js +584 -0
- package/rules/prefer-spread.js +3 -0
- package/rules/prefer-string-raw.js +2 -2
- package/rules/prefer-toggle-attribute.js +293 -0
- package/rules/prefer-url-search-parameters.js +411 -0
- package/rules/shared/array-range.js +66 -0
- package/rules/shared/iterator-helpers.js +43 -1
- package/rules/shared/late-event-handler.js +82 -0
- package/rules/shared/name-replacements.js +6 -0
- package/rules/utils/builtin-collection-type.js +267 -0
- package/rules/utils/comparison.js +65 -0
- package/rules/utils/index.js +6 -0
- package/rules/utils/is-boolean.js +24 -7
- package/rules/utils/is-event.js +127 -0
- package/rules/utils/should-report-replace-children-receiver.js +210 -0
- package/rules/utils/track-branch-exits.js +8 -7
- package/rules/utils/type-helpers.js +166 -8
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {isRegExp} from 'node:util/types';
|
|
2
|
+
import {findVariable, getPropertyName, getStaticValue} from '@eslint-community/eslint-utils';
|
|
2
3
|
import {renameVariable} from './fix/index.js';
|
|
4
|
+
import resolveVariableName from './utils/resolve-variable-name.js';
|
|
3
5
|
import {
|
|
4
6
|
getAvailableVariableName,
|
|
5
7
|
getScopes,
|
|
6
8
|
getVariableIdentifiers,
|
|
9
|
+
isNullishType,
|
|
10
|
+
isTypeParameterType,
|
|
11
|
+
isUnknownType,
|
|
7
12
|
upperFirst,
|
|
8
13
|
} from './utils/index.js';
|
|
9
14
|
import {
|
|
@@ -15,20 +20,27 @@ import {
|
|
|
15
20
|
} from './utils/is-boolean.js';
|
|
16
21
|
|
|
17
22
|
const MESSAGE_ID = 'consistent-boolean-name';
|
|
23
|
+
const MESSAGE_ID_NON_BOOLEAN_PREFIX = 'non-boolean-prefix';
|
|
18
24
|
const MESSAGE_ID_SUGGESTION = 'rename';
|
|
19
25
|
const messages = {
|
|
20
26
|
[MESSAGE_ID]: 'Boolean name `{{name}}` should start with {{prefixes}}.',
|
|
27
|
+
[MESSAGE_ID_NON_BOOLEAN_PREFIX]: '`{{name}}` starts with `{{prefix}}`, so it should be boolean.',
|
|
21
28
|
[MESSAGE_ID_SUGGESTION]: 'Rename to `{{replacement}}`.',
|
|
22
29
|
};
|
|
23
30
|
|
|
24
31
|
const defaultPrefixes = {
|
|
25
32
|
is: true,
|
|
33
|
+
are: true,
|
|
26
34
|
has: true,
|
|
35
|
+
have: true,
|
|
27
36
|
can: true,
|
|
28
37
|
should: true,
|
|
29
38
|
was: true,
|
|
39
|
+
were: true,
|
|
30
40
|
did: true,
|
|
31
41
|
will: true,
|
|
42
|
+
// `requireLogin()` reads like an action or assertion, not a boolean.
|
|
43
|
+
requires: true,
|
|
32
44
|
};
|
|
33
45
|
|
|
34
46
|
const getEnabledPrefixes = ({prefixes = {}} = {}) =>
|
|
@@ -42,6 +54,48 @@ const getEnabledPrefixes = ({prefixes = {}} = {}) =>
|
|
|
42
54
|
const formatPrefixes = prefixes =>
|
|
43
55
|
prefixes.map(prefix => `\`${prefix}\``).join(', ');
|
|
44
56
|
|
|
57
|
+
const booleanBinaryOperators = new Set([
|
|
58
|
+
'>',
|
|
59
|
+
'>=',
|
|
60
|
+
'<',
|
|
61
|
+
'<=',
|
|
62
|
+
'==',
|
|
63
|
+
'===',
|
|
64
|
+
'!=',
|
|
65
|
+
'!==',
|
|
66
|
+
'in',
|
|
67
|
+
'instanceof',
|
|
68
|
+
]);
|
|
69
|
+
const boolean = 'boolean';
|
|
70
|
+
const nonBoolean = 'non-boolean';
|
|
71
|
+
const unknown = 'unknown';
|
|
72
|
+
const nullishTypeAnnotationTypes = new Set([
|
|
73
|
+
'TSNullKeyword',
|
|
74
|
+
'TSUndefinedKeyword',
|
|
75
|
+
]);
|
|
76
|
+
const unknownTypeAnnotationTypes = new Set([
|
|
77
|
+
'TSAnyKeyword',
|
|
78
|
+
'TSNeverKeyword',
|
|
79
|
+
'TSUnknownKeyword',
|
|
80
|
+
]);
|
|
81
|
+
const nonBooleanExpressionTypes = new Set([
|
|
82
|
+
'ArrayExpression',
|
|
83
|
+
'ObjectExpression',
|
|
84
|
+
'ClassExpression',
|
|
85
|
+
'NewExpression',
|
|
86
|
+
'TemplateLiteral',
|
|
87
|
+
'UpdateExpression',
|
|
88
|
+
]);
|
|
89
|
+
const expressionWrapperTypes = new Set([
|
|
90
|
+
'AwaitExpression',
|
|
91
|
+
'TSNonNullExpression',
|
|
92
|
+
'ParenthesizedExpression',
|
|
93
|
+
]);
|
|
94
|
+
const typeScriptExpressionWrapperTypes = new Set([
|
|
95
|
+
'TSAsExpression',
|
|
96
|
+
'TSTypeAssertion',
|
|
97
|
+
'TSSatisfiesExpression',
|
|
98
|
+
]);
|
|
45
99
|
const isUpperCase = string => string === string.toUpperCase();
|
|
46
100
|
const stripLeadingUnderscores = name => name.replace(/^_+/, '');
|
|
47
101
|
|
|
@@ -83,17 +137,28 @@ function findParameter(parameters, identifier) {
|
|
|
83
137
|
const prepareOptions = ({
|
|
84
138
|
checkProperties = false,
|
|
85
139
|
prefixes = {},
|
|
140
|
+
ignore = [],
|
|
86
141
|
} = {}) => ({
|
|
87
142
|
checkProperties,
|
|
88
143
|
prefixes: getEnabledPrefixes({prefixes}),
|
|
144
|
+
ignore: ignore.map(pattern => isRegExp(pattern) ? pattern : new RegExp(pattern, 'u')),
|
|
89
145
|
});
|
|
90
146
|
|
|
91
|
-
function
|
|
147
|
+
function isIgnoredName(name, ignore) {
|
|
148
|
+
return ignore.some(regexp => {
|
|
149
|
+
regexp.lastIndex = 0;
|
|
150
|
+
const isIgnored = regexp.test(name);
|
|
151
|
+
regexp.lastIndex = 0;
|
|
152
|
+
return isIgnored;
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function getBooleanPrefix(name, prefixes) {
|
|
92
157
|
name = stripLeadingUnderscores(name);
|
|
93
158
|
|
|
94
159
|
for (const prefix of prefixes) {
|
|
95
160
|
if (name.startsWith(`${prefix.toUpperCase()}_`)) {
|
|
96
|
-
return
|
|
161
|
+
return prefix;
|
|
97
162
|
}
|
|
98
163
|
|
|
99
164
|
if (
|
|
@@ -101,11 +166,9 @@ function hasBooleanPrefix(name, prefixes) {
|
|
|
101
166
|
&& name.length > prefix.length
|
|
102
167
|
&& /[\dA-Z_]/.test(name[prefix.length])
|
|
103
168
|
) {
|
|
104
|
-
return
|
|
169
|
+
return prefix;
|
|
105
170
|
}
|
|
106
171
|
}
|
|
107
|
-
|
|
108
|
-
return false;
|
|
109
172
|
}
|
|
110
173
|
|
|
111
174
|
function getReplacementName(name, prefix) {
|
|
@@ -169,45 +232,435 @@ const isBooleanValue = (node, context) => isFunction(node)
|
|
|
169
232
|
? isBooleanFunction(node, context)
|
|
170
233
|
: isBooleanFunctionReference(node, context) || isBooleanExpression(node, context);
|
|
171
234
|
|
|
172
|
-
|
|
173
|
-
|
|
235
|
+
function getSupportedVariableDefinition(variable) {
|
|
236
|
+
if (variable.defs.length !== 1) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const [definition] = variable.defs;
|
|
241
|
+
const {name} = definition;
|
|
174
242
|
|
|
175
243
|
if (
|
|
176
|
-
|
|
177
|
-
|
|
244
|
+
name?.type !== 'Identifier'
|
|
245
|
+
|| !['Variable', 'Parameter', 'FunctionName'].includes(definition.type)
|
|
246
|
+
|| (definition.type === 'Variable' && definition.node.id.type !== 'Identifier')
|
|
247
|
+
|| (definition.type === 'Parameter' && definition.node.parent?.kind === 'set')
|
|
178
248
|
) {
|
|
179
|
-
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
180
251
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
252
|
+
return definition;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function getFunctionDefinitions(variable) {
|
|
256
|
+
if (
|
|
257
|
+
variable.defs.length <= 1
|
|
258
|
+
|| variable.defs.some(definition => definition.type !== 'FunctionName')
|
|
259
|
+
) {
|
|
260
|
+
return;
|
|
184
261
|
}
|
|
185
262
|
|
|
186
|
-
|
|
187
|
-
|
|
263
|
+
const overloadDefinitions = variable.defs.filter(definition => definition.node.type === 'TSDeclareFunction');
|
|
264
|
+
return overloadDefinitions.length > 0 ? overloadDefinitions : variable.defs;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function combineBooleanStates(states) {
|
|
268
|
+
if (
|
|
269
|
+
states.length === 0
|
|
270
|
+
|| states.includes(unknown)
|
|
271
|
+
) {
|
|
272
|
+
return unknown;
|
|
188
273
|
}
|
|
189
274
|
|
|
190
|
-
|
|
191
|
-
|
|
275
|
+
return states.every(state => state === boolean) ? boolean : nonBoolean;
|
|
276
|
+
}
|
|
192
277
|
|
|
193
|
-
|
|
194
|
-
|
|
278
|
+
function combineVariableBooleanStates(states) {
|
|
279
|
+
if (states.includes(nonBoolean)) {
|
|
280
|
+
return nonBoolean;
|
|
195
281
|
}
|
|
196
282
|
|
|
197
|
-
|
|
198
|
-
|
|
283
|
+
return combineBooleanStates(states);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function getTypeBooleanState(type, checker, visitedTypes = new Set(), functionTypesAreBoolean = true) {
|
|
287
|
+
if (!type) {
|
|
288
|
+
return unknown;
|
|
199
289
|
}
|
|
200
290
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
291
|
+
if (
|
|
292
|
+
isUnknownType(type)
|
|
293
|
+
|| type.intrinsicName === 'never'
|
|
294
|
+
) {
|
|
295
|
+
return unknown;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (visitedTypes.has(type)) {
|
|
299
|
+
return unknown;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
visitedTypes.add(type);
|
|
303
|
+
|
|
304
|
+
if (isTypeParameterType(type)) {
|
|
305
|
+
const constraint = type.getConstraint();
|
|
306
|
+
const result = constraint ? getTypeBooleanState(constraint, checker, visitedTypes, functionTypesAreBoolean) : unknown;
|
|
307
|
+
visitedTypes.delete(type);
|
|
308
|
+
return result;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const nonNullableType = checker.getNonNullableType(type);
|
|
312
|
+
if (nonNullableType !== type) {
|
|
313
|
+
const result = getTypeBooleanState(nonNullableType, checker, visitedTypes, functionTypesAreBoolean);
|
|
314
|
+
visitedTypes.delete(type);
|
|
315
|
+
return result;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (type.isUnion()) {
|
|
319
|
+
const result = combineBooleanStates(
|
|
320
|
+
type.types
|
|
321
|
+
.filter(type => !isNullishType(type))
|
|
322
|
+
.map(type => getTypeBooleanState(type, checker, visitedTypes, functionTypesAreBoolean)),
|
|
323
|
+
);
|
|
324
|
+
visitedTypes.delete(type);
|
|
325
|
+
return result;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const signatures = type.getCallSignatures();
|
|
329
|
+
if (signatures.length > 0) {
|
|
330
|
+
const result = functionTypesAreBoolean
|
|
331
|
+
? combineBooleanStates(signatures.map(signature => getTypeBooleanState(signature.getReturnType(), checker, visitedTypes, false)))
|
|
332
|
+
: nonBoolean;
|
|
333
|
+
visitedTypes.delete(type);
|
|
334
|
+
return result;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const constraint = checker.getBaseConstraintOfType(type);
|
|
338
|
+
if (constraint && constraint !== type) {
|
|
339
|
+
const result = getTypeBooleanState(constraint, checker, visitedTypes, functionTypesAreBoolean);
|
|
340
|
+
visitedTypes.delete(type);
|
|
341
|
+
return result;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (type.getProperties().length === 0) {
|
|
345
|
+
visitedTypes.delete(type);
|
|
346
|
+
return unknown;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const typeString = checker.typeToString(checker.getWidenedType(checker.getBaseTypeOfLiteralType(type)));
|
|
350
|
+
visitedTypes.delete(type);
|
|
351
|
+
|
|
352
|
+
return typeString === 'boolean' ? boolean : nonBoolean;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function getTypeInformationBooleanState(node, context, functionTypesAreBoolean = true) {
|
|
356
|
+
const {parserServices} = context.sourceCode;
|
|
357
|
+
if (!parserServices?.program) {
|
|
358
|
+
return unknown;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
try {
|
|
362
|
+
return getTypeBooleanState(
|
|
363
|
+
parserServices.getTypeAtLocation(node),
|
|
364
|
+
parserServices.program.getTypeChecker(),
|
|
365
|
+
new Set(),
|
|
366
|
+
functionTypesAreBoolean,
|
|
367
|
+
);
|
|
368
|
+
} catch {
|
|
369
|
+
return unknown;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function getTypeReferenceName(typeName) {
|
|
374
|
+
if (typeName?.type === 'Identifier') {
|
|
375
|
+
return typeName.name;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const getTypeState = typeState => ({
|
|
380
|
+
visitedTypeReferenceNames: new Set(),
|
|
381
|
+
functionTypesAreBoolean: true,
|
|
382
|
+
...typeState,
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
function getTypeMembersBooleanState(members, context, scope, typeState) {
|
|
386
|
+
const normalizedTypeState = getTypeState(typeState);
|
|
387
|
+
const callSignatures = members.filter(member => member.type === 'TSCallSignatureDeclaration');
|
|
388
|
+
|
|
389
|
+
if (callSignatures.length > 0) {
|
|
390
|
+
return normalizedTypeState.functionTypesAreBoolean
|
|
391
|
+
? combineBooleanStates(callSignatures.map(member => getTypeAnnotationBooleanState(member.returnType, context, scope, {...normalizedTypeState, functionTypesAreBoolean: false})))
|
|
392
|
+
: nonBoolean;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return members.length > 0 ? nonBoolean : unknown;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function getTypeReferenceBooleanState(node, context, scope, typeState) {
|
|
399
|
+
const normalizedTypeState = getTypeState(typeState);
|
|
400
|
+
const {visitedTypeReferenceNames} = normalizedTypeState;
|
|
401
|
+
const name = getTypeReferenceName(node.typeName);
|
|
402
|
+
if (!name || visitedTypeReferenceNames.has(name)) {
|
|
403
|
+
return unknown;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
visitedTypeReferenceNames.add(name);
|
|
407
|
+
|
|
408
|
+
const [definition] = resolveVariableName(name, scope)?.defs ?? [];
|
|
409
|
+
let result = unknown;
|
|
410
|
+
|
|
411
|
+
if (definition?.type === 'Type') {
|
|
412
|
+
const definitionScope = context.sourceCode.getScope(definition.node);
|
|
413
|
+
|
|
414
|
+
if (definition.node.type === 'TSTypeAliasDeclaration') {
|
|
415
|
+
result = getTypeAnnotationBooleanState(definition.node.typeAnnotation, context, definitionScope, normalizedTypeState);
|
|
416
|
+
} else if (definition.node.type === 'TSInterfaceDeclaration') {
|
|
417
|
+
result = getTypeMembersBooleanState(definition.node.body.body, context, definitionScope, normalizedTypeState);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
visitedTypeReferenceNames.delete(name);
|
|
422
|
+
return result;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function getUnionTypeAnnotationBooleanState(node, context, scope, typeState) {
|
|
426
|
+
return combineBooleanStates(
|
|
427
|
+
node.types
|
|
428
|
+
.filter(type => !nullishTypeAnnotationTypes.has(type.type))
|
|
429
|
+
.map(type => getTypeAnnotationBooleanState(type, context, scope, typeState)),
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function getSimpleTypeAnnotationBooleanState(node) {
|
|
434
|
+
if (!node) {
|
|
435
|
+
return unknown;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (
|
|
439
|
+
nullishTypeAnnotationTypes.has(node.type)
|
|
440
|
+
|| unknownTypeAnnotationTypes.has(node.type)
|
|
441
|
+
) {
|
|
442
|
+
return unknown;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (node.type === 'TSBooleanKeyword') {
|
|
446
|
+
return boolean;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if (node.type === 'TSLiteralType') {
|
|
450
|
+
return typeof node.literal.value === 'boolean' ? boolean : nonBoolean;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (node.type === 'TSTypePredicate') {
|
|
454
|
+
return node.asserts ? nonBoolean : boolean;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if (node.type === 'TypeAnnotation') {
|
|
458
|
+
return node.typeAnnotation?.type === 'BooleanTypeAnnotation' ? boolean : nonBoolean;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return nonBoolean;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function getTypeAnnotationBooleanState(node, context, scope, typeState) {
|
|
465
|
+
const normalizedTypeState = getTypeState(typeState);
|
|
466
|
+
|
|
467
|
+
if (
|
|
468
|
+
node?.type === 'TSTypeAnnotation'
|
|
469
|
+
|| node?.type === 'TSParenthesizedType'
|
|
470
|
+
) {
|
|
471
|
+
return getTypeAnnotationBooleanState(node.typeAnnotation, context, scope, normalizedTypeState);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
if (node?.type === 'TSFunctionType') {
|
|
475
|
+
return normalizedTypeState.functionTypesAreBoolean
|
|
476
|
+
? getTypeAnnotationBooleanState(node.returnType, context, scope, {...normalizedTypeState, functionTypesAreBoolean: false})
|
|
477
|
+
: nonBoolean;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
if (node?.type === 'TSUnionType') {
|
|
481
|
+
return getUnionTypeAnnotationBooleanState(node, context, scope, normalizedTypeState);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
if (node?.type === 'TSTypeReference') {
|
|
485
|
+
return getTypeReferenceBooleanState(node, context, scope, normalizedTypeState);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (node?.type === 'TSTypeLiteral') {
|
|
489
|
+
return getTypeMembersBooleanState(node.members, context, scope, normalizedTypeState);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
return getSimpleTypeAnnotationBooleanState(node);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function getFunctionBooleanState(node, context, visitedVariables = new Set()) {
|
|
496
|
+
if (node.async || node.generator) {
|
|
497
|
+
return nonBoolean;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
const stateFromTypeInformation = getTypeInformationBooleanState(node, context);
|
|
501
|
+
if (stateFromTypeInformation !== unknown) {
|
|
502
|
+
return stateFromTypeInformation;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
const scope = context.sourceCode.getScope(node);
|
|
506
|
+
const stateFromReturnType = getTypeAnnotationBooleanState(node.returnType, context, scope, {functionTypesAreBoolean: false});
|
|
507
|
+
if (stateFromReturnType !== unknown) {
|
|
508
|
+
return stateFromReturnType;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (!node.body) {
|
|
512
|
+
return unknown;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
if (node.body.type === 'BlockStatement') {
|
|
516
|
+
if (node.body.body.length === 0) {
|
|
517
|
+
return nonBoolean;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
if (
|
|
521
|
+
node.body.body.length === 1
|
|
522
|
+
&& node.body.body[0].type === 'ReturnStatement'
|
|
523
|
+
) {
|
|
524
|
+
return node.body.body[0].argument
|
|
525
|
+
? getExpressionBooleanState(node.body.body[0].argument, context, visitedVariables, false)
|
|
526
|
+
: nonBoolean;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
return node.type === 'ArrowFunctionExpression' && node.body.type !== 'BlockStatement'
|
|
531
|
+
? getExpressionBooleanState(node.body, context, visitedVariables, false)
|
|
532
|
+
: unknown;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function getKnownIdentifierBooleanState(node, context, visitedVariables, functionValuesAreBoolean) {
|
|
536
|
+
const variable = findVariable(context.sourceCode.getScope(node), node);
|
|
537
|
+
return variable ? getVariableBooleanState(variable, context, visitedVariables, functionValuesAreBoolean) : unknown;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function getStaticExpressionBooleanState(node, scope) {
|
|
541
|
+
if (node.type === 'Identifier') {
|
|
542
|
+
return unknown;
|
|
204
543
|
}
|
|
205
544
|
|
|
206
|
-
|
|
207
|
-
|
|
545
|
+
const staticValue = getStaticValue(node, scope)?.value;
|
|
546
|
+
|
|
547
|
+
return staticValue === undefined
|
|
548
|
+
? unknown
|
|
549
|
+
: (typeof staticValue === 'boolean' ? boolean : nonBoolean);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
function getSimpleExpressionBooleanState(node) {
|
|
553
|
+
if (nonBooleanExpressionTypes.has(node.type)) {
|
|
554
|
+
return nonBoolean;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
if (node.type === 'Literal') {
|
|
558
|
+
return node.value === null ? unknown : nonBoolean;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
if (node.type === 'UnaryExpression') {
|
|
562
|
+
return ['!', 'delete'].includes(node.operator) ? boolean : nonBoolean;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
if (node.type === 'BinaryExpression') {
|
|
566
|
+
return booleanBinaryOperators.has(node.operator) ? boolean : nonBoolean;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
return unknown;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function getWrappedExpression(node) {
|
|
573
|
+
if (expressionWrapperTypes.has(node.type)) {
|
|
574
|
+
return node.argument ?? node.expression;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
if (typeScriptExpressionWrapperTypes.has(node.type)) {
|
|
578
|
+
return node.expression;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
function getDerivedExpressionBooleanState(node, context, visitedVariables, functionValuesAreBoolean) {
|
|
583
|
+
if (node.type === 'Identifier') {
|
|
584
|
+
return getKnownIdentifierBooleanState(node, context, visitedVariables, functionValuesAreBoolean);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
const wrappedExpression = getWrappedExpression(node);
|
|
588
|
+
if (wrappedExpression) {
|
|
589
|
+
return getExpressionBooleanState(wrappedExpression, context, visitedVariables, functionValuesAreBoolean);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
if (node.type === 'AssignmentExpression') {
|
|
593
|
+
return node.operator === '=' ? getExpressionBooleanState(node.right, context, visitedVariables, functionValuesAreBoolean) : unknown;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
if (node.type === 'SequenceExpression') {
|
|
597
|
+
return getExpressionBooleanState(node.expressions.at(-1), context, visitedVariables, functionValuesAreBoolean);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
if (node.type === 'ConditionalExpression') {
|
|
601
|
+
return combineBooleanStates([
|
|
602
|
+
getExpressionBooleanState(node.consequent, context, visitedVariables, functionValuesAreBoolean),
|
|
603
|
+
getExpressionBooleanState(node.alternate, context, visitedVariables, functionValuesAreBoolean),
|
|
604
|
+
]);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
return unknown;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
function getExpressionBooleanState(node, context, visitedVariables = new Set(), functionValuesAreBoolean = true) {
|
|
611
|
+
if (!node) {
|
|
612
|
+
return unknown;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
if (isFunction(node)) {
|
|
616
|
+
return functionValuesAreBoolean
|
|
617
|
+
? getFunctionBooleanState(node, context, visitedVariables)
|
|
618
|
+
: nonBoolean;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
const stateFromTypeInformation = getTypeInformationBooleanState(node, context, functionValuesAreBoolean);
|
|
622
|
+
if (stateFromTypeInformation !== unknown) {
|
|
623
|
+
return stateFromTypeInformation;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
const scope = context.sourceCode.getScope(node);
|
|
627
|
+
const stateFromTypeAnnotation = getTypeAnnotationBooleanState(node.typeAnnotation, context, scope, {functionTypesAreBoolean: functionValuesAreBoolean});
|
|
628
|
+
if (stateFromTypeAnnotation !== unknown) {
|
|
629
|
+
return stateFromTypeAnnotation;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
if (isBooleanExpression(node, context, visitedVariables)) {
|
|
633
|
+
return boolean;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
const stateFromStaticValue = getStaticExpressionBooleanState(node, scope);
|
|
637
|
+
if (stateFromStaticValue !== unknown) {
|
|
638
|
+
return stateFromStaticValue;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
const stateFromSimpleExpression = getSimpleExpressionBooleanState(node);
|
|
642
|
+
if (stateFromSimpleExpression !== unknown) {
|
|
643
|
+
return stateFromSimpleExpression;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
return getDerivedExpressionBooleanState(node, context, visitedVariables, functionValuesAreBoolean);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
const isBooleanVariable = (variable, context) => {
|
|
650
|
+
const {sourceCode} = context;
|
|
651
|
+
|
|
652
|
+
const functionDefinitions = getFunctionDefinitions(variable);
|
|
653
|
+
if (functionDefinitions) {
|
|
654
|
+
return functionDefinitions.every(definition => isBooleanFunctionDefinition(definition, context));
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
const definition = getSupportedVariableDefinition(variable);
|
|
658
|
+
if (!definition) {
|
|
208
659
|
return false;
|
|
209
660
|
}
|
|
210
661
|
|
|
662
|
+
const {name} = definition;
|
|
663
|
+
|
|
211
664
|
const scope = sourceCode.getScope(name);
|
|
212
665
|
|
|
213
666
|
if (name.typeAnnotation) {
|
|
@@ -216,6 +669,11 @@ const isBooleanVariable = (variable, context) => {
|
|
|
216
669
|
}
|
|
217
670
|
|
|
218
671
|
if (definition.type === 'Parameter') {
|
|
672
|
+
// Some parsers (such as Svelte's `{#each}` bindings) create `Parameter` definitions whose owner is not a function and has no `params` list.
|
|
673
|
+
if (!isFunction(definition.node)) {
|
|
674
|
+
return false;
|
|
675
|
+
}
|
|
676
|
+
|
|
219
677
|
const parameter = findParameter(definition.node.params, name);
|
|
220
678
|
|
|
221
679
|
return parameter?.type === 'AssignmentPattern'
|
|
@@ -230,12 +688,91 @@ const isBooleanVariable = (variable, context) => {
|
|
|
230
688
|
return isBooleanValue(definition.node.init, context);
|
|
231
689
|
}
|
|
232
690
|
|
|
691
|
+
return definition.type === 'FunctionName' && isBooleanFunctionDefinition(definition, context);
|
|
692
|
+
};
|
|
693
|
+
|
|
694
|
+
function getParameterBooleanState(definition, context, visitedVariables, functionValuesAreBoolean) {
|
|
695
|
+
if (!isFunction(definition.node)) {
|
|
696
|
+
return unknown;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
const parameter = findParameter(definition.node.params, definition.name);
|
|
700
|
+
|
|
701
|
+
return parameter?.type === 'AssignmentPattern'
|
|
702
|
+
? getExpressionBooleanState(parameter.right, context, visitedVariables, functionValuesAreBoolean)
|
|
703
|
+
: unknown;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
function getDefinitionBooleanState(definition, context, visitedVariables, functionValuesAreBoolean) {
|
|
707
|
+
const scope = context.sourceCode.getScope(definition.name);
|
|
708
|
+
const stateFromTypeAnnotation = getTypeAnnotationBooleanState(definition.name.typeAnnotation, context, scope, {functionTypesAreBoolean: functionValuesAreBoolean});
|
|
709
|
+
if (stateFromTypeAnnotation !== unknown) {
|
|
710
|
+
return stateFromTypeAnnotation;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
if (definition.type === 'Parameter') {
|
|
714
|
+
return getParameterBooleanState(definition, context, visitedVariables, functionValuesAreBoolean);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
if (definition.type === 'Variable') {
|
|
718
|
+
return getExpressionBooleanState(definition.node.init, context, visitedVariables, functionValuesAreBoolean);
|
|
719
|
+
}
|
|
720
|
+
|
|
233
721
|
if (definition.type === 'FunctionName') {
|
|
234
|
-
return
|
|
722
|
+
return functionValuesAreBoolean
|
|
723
|
+
? getFunctionBooleanState(definition.node, context, visitedVariables)
|
|
724
|
+
: nonBoolean;
|
|
235
725
|
}
|
|
236
726
|
|
|
237
|
-
return
|
|
238
|
-
}
|
|
727
|
+
return unknown;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
function getVariableBooleanState(variable, context, visitedVariables = new Set(), functionValuesAreBoolean = true) {
|
|
731
|
+
if (!variable || visitedVariables.has(variable)) {
|
|
732
|
+
return unknown;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
visitedVariables.add(variable);
|
|
736
|
+
|
|
737
|
+
const functionDefinitions = getFunctionDefinitions(variable);
|
|
738
|
+
const definition = getSupportedVariableDefinition(variable);
|
|
739
|
+
if (
|
|
740
|
+
!functionDefinitions
|
|
741
|
+
&& !definition
|
|
742
|
+
) {
|
|
743
|
+
visitedVariables.delete(variable);
|
|
744
|
+
return unknown;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
let result;
|
|
748
|
+
if (functionDefinitions && !functionValuesAreBoolean) {
|
|
749
|
+
result = nonBoolean;
|
|
750
|
+
} else if (functionDefinitions) {
|
|
751
|
+
result = combineBooleanStates(functionDefinitions.map(definition => getFunctionBooleanState(definition.node, context, visitedVariables)));
|
|
752
|
+
} else {
|
|
753
|
+
result = getDefinitionBooleanState(definition, context, visitedVariables, functionValuesAreBoolean);
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
if (variable.references.some(reference => reference.writeExpr)) {
|
|
757
|
+
result = combineVariableBooleanStates([
|
|
758
|
+
result,
|
|
759
|
+
...variable.references
|
|
760
|
+
.filter(reference => reference.writeExpr)
|
|
761
|
+
.map(reference => getExpressionBooleanState(reference.writeExpr, context, visitedVariables, functionValuesAreBoolean)),
|
|
762
|
+
]);
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
if (
|
|
766
|
+
result === unknown
|
|
767
|
+
&& functionValuesAreBoolean
|
|
768
|
+
&& isBooleanVariable(variable, context)
|
|
769
|
+
) {
|
|
770
|
+
result = boolean;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
visitedVariables.delete(variable);
|
|
774
|
+
return result;
|
|
775
|
+
}
|
|
239
776
|
|
|
240
777
|
function getBooleanPropertyName(node, sourceCode) {
|
|
241
778
|
if (
|
|
@@ -304,6 +841,53 @@ function isBooleanProperty(node, context) {
|
|
|
304
841
|
return false;
|
|
305
842
|
}
|
|
306
843
|
|
|
844
|
+
function getExplicitPropertyBooleanState(node, context) {
|
|
845
|
+
const {sourceCode} = context;
|
|
846
|
+
|
|
847
|
+
if (node.type === 'Property') {
|
|
848
|
+
if (
|
|
849
|
+
node.parent.type !== 'ObjectExpression'
|
|
850
|
+
|| node.shorthand
|
|
851
|
+
|| node.kind === 'set'
|
|
852
|
+
) {
|
|
853
|
+
return unknown;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
return getExpressionBooleanState(node.value, context);
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
if (
|
|
860
|
+
node.type === 'MethodDefinition'
|
|
861
|
+
|| node.type === 'TSAbstractMethodDefinition'
|
|
862
|
+
) {
|
|
863
|
+
return ['constructor', 'set'].includes(node.kind) ? unknown : getFunctionBooleanState(node.value, context);
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
if (propertyDefinitionTypes.has(node.type)) {
|
|
867
|
+
const scope = sourceCode.getScope(node);
|
|
868
|
+
const stateFromTypeAnnotation = getTypeAnnotationBooleanState(node.typeAnnotation, context, scope);
|
|
869
|
+
|
|
870
|
+
return stateFromTypeAnnotation === unknown
|
|
871
|
+
? getExpressionBooleanState(node.value, context)
|
|
872
|
+
: stateFromTypeAnnotation;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
if (node.type === 'TSPropertySignature') {
|
|
876
|
+
return getTypeAnnotationBooleanState(node.typeAnnotation, context, sourceCode.getScope(node));
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
if (node.type === 'TSMethodSignature') {
|
|
880
|
+
return getTypeAnnotationBooleanState(node.returnType, context, sourceCode.getScope(node), {functionTypesAreBoolean: false});
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
return unknown;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
function getPropertyBooleanState(node, context) {
|
|
887
|
+
const state = getExplicitPropertyBooleanState(node, context);
|
|
888
|
+
return state === unknown && isBooleanProperty(node, context) ? boolean : state;
|
|
889
|
+
}
|
|
890
|
+
|
|
307
891
|
function getSuggestions(variable, prefixes, context) {
|
|
308
892
|
if (
|
|
309
893
|
!shouldSuggestRename(variable)
|
|
@@ -387,17 +971,37 @@ function getAutofix(variable, prefixes, context, suggestions) {
|
|
|
387
971
|
|
|
388
972
|
/** @param {import('eslint').Rule.RuleContext} context */
|
|
389
973
|
const create = context => {
|
|
390
|
-
const {checkProperties, prefixes} = prepareOptions(context.options[0]);
|
|
974
|
+
const {checkProperties, prefixes, ignore} = prepareOptions(context.options[0]);
|
|
391
975
|
|
|
392
976
|
if (prefixes.length === 0) {
|
|
393
977
|
return;
|
|
394
978
|
}
|
|
395
979
|
|
|
396
980
|
const checkVariable = variable => {
|
|
397
|
-
|
|
398
|
-
|
|
981
|
+
if (isIgnoredName(variable.name, ignore)) {
|
|
982
|
+
return;
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
const booleanPrefix = getBooleanPrefix(variable.name, prefixes);
|
|
986
|
+
if (booleanPrefix) {
|
|
987
|
+
if (getVariableBooleanState(variable, context) === nonBoolean) {
|
|
988
|
+
const [definition] = variable.defs;
|
|
989
|
+
|
|
990
|
+
context.report({
|
|
991
|
+
node: definition.name,
|
|
992
|
+
messageId: MESSAGE_ID_NON_BOOLEAN_PREFIX,
|
|
993
|
+
data: {
|
|
994
|
+
name: variable.name,
|
|
995
|
+
prefix: booleanPrefix,
|
|
996
|
+
},
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
399
1003
|
if (
|
|
400
|
-
|
|
1004
|
+
getVariableBooleanState(variable, context) === nonBoolean
|
|
401
1005
|
|| !isBooleanVariable(variable, context)
|
|
402
1006
|
) {
|
|
403
1007
|
return;
|
|
@@ -435,12 +1039,31 @@ const create = context => {
|
|
|
435
1039
|
|
|
436
1040
|
if (
|
|
437
1041
|
!name
|
|
438
|
-
||
|
|
439
|
-
|| !isBooleanProperty(node, context)
|
|
1042
|
+
|| isIgnoredName(name, ignore)
|
|
440
1043
|
) {
|
|
441
1044
|
return;
|
|
442
1045
|
}
|
|
443
1046
|
|
|
1047
|
+
const booleanPrefix = getBooleanPrefix(name, prefixes);
|
|
1048
|
+
if (booleanPrefix) {
|
|
1049
|
+
if (getPropertyBooleanState(node, context) === nonBoolean) {
|
|
1050
|
+
context.report({
|
|
1051
|
+
node: node.key,
|
|
1052
|
+
messageId: MESSAGE_ID_NON_BOOLEAN_PREFIX,
|
|
1053
|
+
data: {
|
|
1054
|
+
name,
|
|
1055
|
+
prefix: booleanPrefix,
|
|
1056
|
+
},
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
if (!isBooleanProperty(node, context)) {
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
444
1067
|
context.report({
|
|
445
1068
|
node: node.key,
|
|
446
1069
|
messageId: MESSAGE_ID,
|
|
@@ -494,10 +1117,15 @@ const config = {
|
|
|
494
1117
|
pattern: '^[a-z][a-zA-Z0-9]*$',
|
|
495
1118
|
},
|
|
496
1119
|
},
|
|
1120
|
+
ignore: {
|
|
1121
|
+
type: 'array',
|
|
1122
|
+
uniqueItems: true,
|
|
1123
|
+
description: 'Patterns to ignore.',
|
|
1124
|
+
},
|
|
497
1125
|
},
|
|
498
1126
|
},
|
|
499
1127
|
],
|
|
500
|
-
defaultOptions: [{}],
|
|
1128
|
+
defaultOptions: [{ignore: []}],
|
|
501
1129
|
messages,
|
|
502
1130
|
},
|
|
503
1131
|
};
|