eslint-plugin-unicorn 69.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 +1 -1
- package/readme.md +10 -0
- package/rules/consistent-boolean-name.js +637 -36
- 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 +10 -0
- package/rules/isolated-functions.js +156 -12
- package/rules/no-async-promise-finally.js +111 -0
- package/rules/no-collection-bracket-access.js +27 -4
- 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-return-array-push.js +9 -2
- 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-unsafe-promise-all-settled-values.js +865 -0
- package/rules/no-useless-else.js +32 -1
- package/rules/no-useless-iterator-to-array.js +42 -16
- package/rules/no-useless-spread.js +2 -2
- package/rules/prefer-abort-signal-any.js +1391 -0
- package/rules/prefer-array-flat-map.js +6 -11
- package/rules/prefer-array-from-range.js +98 -0
- package/rules/prefer-block-statement-over-iife.js +159 -0
- package/rules/prefer-continue.js +6 -2
- package/rules/prefer-early-return.js +3 -1
- package/rules/prefer-group-by.js +576 -0
- package/rules/prefer-iterator-helpers.js +196 -0
- package/rules/prefer-number-coercion.js +1 -1
- package/rules/prefer-object-iterable-methods.js +57 -14
- 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/shared/array-range.js +66 -0
- package/rules/shared/iterator-helpers.js +43 -1
- package/rules/utils/comparison.js +65 -0
- package/rules/utils/is-boolean.js +21 -6
- package/rules/utils/track-branch-exits.js +8 -7
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import {isRegExp} from 'node:util/types';
|
|
2
|
-
import {getPropertyName} from '@eslint-community/eslint-utils';
|
|
2
|
+
import {findVariable, getPropertyName, getStaticValue} from '@eslint-community/eslint-utils';
|
|
3
3
|
import {renameVariable} from './fix/index.js';
|
|
4
|
+
import resolveVariableName from './utils/resolve-variable-name.js';
|
|
4
5
|
import {
|
|
5
6
|
getAvailableVariableName,
|
|
6
7
|
getScopes,
|
|
7
8
|
getVariableIdentifiers,
|
|
9
|
+
isNullishType,
|
|
10
|
+
isTypeParameterType,
|
|
11
|
+
isUnknownType,
|
|
8
12
|
upperFirst,
|
|
9
13
|
} from './utils/index.js';
|
|
10
14
|
import {
|
|
@@ -16,9 +20,11 @@ import {
|
|
|
16
20
|
} from './utils/is-boolean.js';
|
|
17
21
|
|
|
18
22
|
const MESSAGE_ID = 'consistent-boolean-name';
|
|
23
|
+
const MESSAGE_ID_NON_BOOLEAN_PREFIX = 'non-boolean-prefix';
|
|
19
24
|
const MESSAGE_ID_SUGGESTION = 'rename';
|
|
20
25
|
const messages = {
|
|
21
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.',
|
|
22
28
|
[MESSAGE_ID_SUGGESTION]: 'Rename to `{{replacement}}`.',
|
|
23
29
|
};
|
|
24
30
|
|
|
@@ -33,6 +39,8 @@ const defaultPrefixes = {
|
|
|
33
39
|
were: true,
|
|
34
40
|
did: true,
|
|
35
41
|
will: true,
|
|
42
|
+
// `requireLogin()` reads like an action or assertion, not a boolean.
|
|
43
|
+
requires: true,
|
|
36
44
|
};
|
|
37
45
|
|
|
38
46
|
const getEnabledPrefixes = ({prefixes = {}} = {}) =>
|
|
@@ -46,6 +54,48 @@ const getEnabledPrefixes = ({prefixes = {}} = {}) =>
|
|
|
46
54
|
const formatPrefixes = prefixes =>
|
|
47
55
|
prefixes.map(prefix => `\`${prefix}\``).join(', ');
|
|
48
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
|
+
]);
|
|
49
99
|
const isUpperCase = string => string === string.toUpperCase();
|
|
50
100
|
const stripLeadingUnderscores = name => name.replace(/^_+/, '');
|
|
51
101
|
|
|
@@ -103,12 +153,12 @@ function isIgnoredName(name, ignore) {
|
|
|
103
153
|
});
|
|
104
154
|
}
|
|
105
155
|
|
|
106
|
-
function
|
|
156
|
+
function getBooleanPrefix(name, prefixes) {
|
|
107
157
|
name = stripLeadingUnderscores(name);
|
|
108
158
|
|
|
109
159
|
for (const prefix of prefixes) {
|
|
110
160
|
if (name.startsWith(`${prefix.toUpperCase()}_`)) {
|
|
111
|
-
return
|
|
161
|
+
return prefix;
|
|
112
162
|
}
|
|
113
163
|
|
|
114
164
|
if (
|
|
@@ -116,11 +166,9 @@ function hasBooleanPrefix(name, prefixes) {
|
|
|
116
166
|
&& name.length > prefix.length
|
|
117
167
|
&& /[\dA-Z_]/.test(name[prefix.length])
|
|
118
168
|
) {
|
|
119
|
-
return
|
|
169
|
+
return prefix;
|
|
120
170
|
}
|
|
121
171
|
}
|
|
122
|
-
|
|
123
|
-
return false;
|
|
124
172
|
}
|
|
125
173
|
|
|
126
174
|
function getReplacementName(name, prefix) {
|
|
@@ -184,45 +232,435 @@ const isBooleanValue = (node, context) => isFunction(node)
|
|
|
184
232
|
? isBooleanFunction(node, context)
|
|
185
233
|
: isBooleanFunctionReference(node, context) || isBooleanExpression(node, context);
|
|
186
234
|
|
|
187
|
-
|
|
188
|
-
|
|
235
|
+
function getSupportedVariableDefinition(variable) {
|
|
236
|
+
if (variable.defs.length !== 1) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const [definition] = variable.defs;
|
|
241
|
+
const {name} = definition;
|
|
189
242
|
|
|
190
243
|
if (
|
|
191
|
-
|
|
192
|
-
|
|
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')
|
|
193
248
|
) {
|
|
194
|
-
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
195
251
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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;
|
|
199
261
|
}
|
|
200
262
|
|
|
201
|
-
|
|
202
|
-
|
|
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;
|
|
203
273
|
}
|
|
204
274
|
|
|
205
|
-
|
|
206
|
-
|
|
275
|
+
return states.every(state => state === boolean) ? boolean : nonBoolean;
|
|
276
|
+
}
|
|
207
277
|
|
|
208
|
-
|
|
209
|
-
|
|
278
|
+
function combineVariableBooleanStates(states) {
|
|
279
|
+
if (states.includes(nonBoolean)) {
|
|
280
|
+
return nonBoolean;
|
|
210
281
|
}
|
|
211
282
|
|
|
212
|
-
|
|
213
|
-
|
|
283
|
+
return combineBooleanStates(states);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function getTypeBooleanState(type, checker, visitedTypes = new Set(), functionTypesAreBoolean = true) {
|
|
287
|
+
if (!type) {
|
|
288
|
+
return unknown;
|
|
214
289
|
}
|
|
215
290
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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;
|
|
543
|
+
}
|
|
544
|
+
|
|
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;
|
|
219
619
|
}
|
|
220
620
|
|
|
221
|
-
|
|
222
|
-
if (
|
|
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) {
|
|
223
659
|
return false;
|
|
224
660
|
}
|
|
225
661
|
|
|
662
|
+
const {name} = definition;
|
|
663
|
+
|
|
226
664
|
const scope = sourceCode.getScope(name);
|
|
227
665
|
|
|
228
666
|
if (name.typeAnnotation) {
|
|
@@ -250,12 +688,91 @@ const isBooleanVariable = (variable, context) => {
|
|
|
250
688
|
return isBooleanValue(definition.node.init, context);
|
|
251
689
|
}
|
|
252
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
|
+
|
|
253
721
|
if (definition.type === 'FunctionName') {
|
|
254
|
-
return
|
|
722
|
+
return functionValuesAreBoolean
|
|
723
|
+
? getFunctionBooleanState(definition.node, context, visitedVariables)
|
|
724
|
+
: nonBoolean;
|
|
255
725
|
}
|
|
256
726
|
|
|
257
|
-
return
|
|
258
|
-
}
|
|
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
|
+
}
|
|
259
776
|
|
|
260
777
|
function getBooleanPropertyName(node, sourceCode) {
|
|
261
778
|
if (
|
|
@@ -324,6 +841,53 @@ function isBooleanProperty(node, context) {
|
|
|
324
841
|
return false;
|
|
325
842
|
}
|
|
326
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
|
+
|
|
327
891
|
function getSuggestions(variable, prefixes, context) {
|
|
328
892
|
if (
|
|
329
893
|
!shouldSuggestRename(variable)
|
|
@@ -414,11 +978,30 @@ const create = context => {
|
|
|
414
978
|
}
|
|
415
979
|
|
|
416
980
|
const checkVariable = variable => {
|
|
417
|
-
|
|
418
|
-
|
|
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
|
+
|
|
419
1003
|
if (
|
|
420
|
-
|
|
421
|
-
|| isIgnoredName(variable.name, ignore)
|
|
1004
|
+
getVariableBooleanState(variable, context) === nonBoolean
|
|
422
1005
|
|| !isBooleanVariable(variable, context)
|
|
423
1006
|
) {
|
|
424
1007
|
return;
|
|
@@ -456,13 +1039,31 @@ const create = context => {
|
|
|
456
1039
|
|
|
457
1040
|
if (
|
|
458
1041
|
!name
|
|
459
|
-
|| hasBooleanPrefix(name, prefixes)
|
|
460
1042
|
|| isIgnoredName(name, ignore)
|
|
461
|
-
|| !isBooleanProperty(node, context)
|
|
462
1043
|
) {
|
|
463
1044
|
return;
|
|
464
1045
|
}
|
|
465
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
|
+
|
|
466
1067
|
context.report({
|
|
467
1068
|
node: node.key,
|
|
468
1069
|
messageId: MESSAGE_ID,
|