eslint-plugin-unicorn 69.0.0 → 71.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 +26 -25
- package/readme.md +15 -1
- package/rules/ast/call-or-new-expression.js +2 -8
- package/rules/ast/is-member-expression.js +2 -8
- package/rules/comment-content.js +4 -4
- package/rules/consistent-boolean-name.js +727 -42
- package/rules/consistent-compound-words.js +1 -2
- package/rules/consistent-json-file-read.js +2 -5
- package/rules/dom-node-dataset.js +3 -7
- package/rules/escape-case.js +4 -4
- package/rules/expiring-todo-comments.js +25 -2
- package/rules/explicit-length-check.js +3 -3
- package/rules/filename-case.js +4 -4
- 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/remove-argument.js +34 -8
- package/rules/fix/remove-statement.js +1 -3
- package/rules/fix/switch-new-expression-to-call-expression.js +2 -2
- package/rules/id-match.js +2 -2
- package/rules/index.js +10 -0
- package/rules/isolated-functions.js +203 -19
- package/rules/name-replacements.js +1 -2
- package/rules/no-anonymous-default-export.js +1 -3
- package/rules/no-array-callback-reference.js +2 -10
- 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 +1 -4
- package/rules/no-declarations-before-early-exit.js +6 -5
- package/rules/no-error-property-assignment.js +3 -3
- package/rules/no-for-loop.js +4 -4
- package/rules/no-keyword-prefix.js +2 -2
- package/rules/no-manually-wrapped-comments.js +5 -1
- package/rules/no-mismatched-map-key.js +13 -20
- package/rules/no-named-default.js +2 -5
- package/rules/no-negated-array-predicate.js +6 -13
- package/rules/no-negated-comparison.js +31 -71
- package/rules/no-negated-condition.js +2 -2
- package/rules/no-negation-in-equality-check.js +4 -7
- package/rules/no-non-function-verb-prefix.js +25 -2
- package/rules/no-process-exit.js +6 -6
- package/rules/no-return-array-push.js +9 -2
- package/rules/no-static-only-class.js +2 -8
- package/rules/no-thenable.js +9 -15
- package/rules/no-typeof-undefined.js +2 -2
- package/rules/no-undeclared-class-members.js +1 -5
- package/rules/no-unnecessary-array-flat-map.js +308 -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 +5 -9
- package/rules/no-unsafe-promise-all-settled-values.js +865 -0
- package/rules/no-unused-properties.js +0 -1
- package/rules/no-useless-coercion.js +1 -2
- 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/no-useless-undefined.js +2 -2
- package/rules/prefer-abort-signal-any.js +1391 -0
- package/rules/prefer-array-find.js +2 -5
- package/rules/prefer-array-flat-map.js +6 -11
- package/rules/prefer-array-from-range.js +98 -0
- package/rules/prefer-bigint-literals.js +1 -0
- package/rules/prefer-block-statement-over-iife.js +159 -0
- package/rules/prefer-boolean-return.js +20 -3
- package/rules/prefer-continue.js +6 -2
- package/rules/prefer-default-parameters.js +2 -2
- package/rules/prefer-early-return.js +3 -1
- package/rules/prefer-else-if.js +5 -9
- package/rules/prefer-group-by.js +576 -0
- package/rules/prefer-has-check.js +6 -3
- package/rules/prefer-https.js +3 -3
- package/rules/prefer-identifier-import-export-specifiers.js +1 -3
- package/rules/prefer-iterator-helpers.js +196 -0
- package/rules/prefer-minimal-ternary.js +4 -4
- package/rules/prefer-number-coercion.js +1 -1
- package/rules/prefer-object-define-properties.js +1 -3
- package/rules/prefer-object-iterable-methods.js +57 -14
- package/rules/prefer-observer-apis.js +2 -5
- package/rules/prefer-private-class-fields.js +1 -3
- package/rules/prefer-simple-sort-comparator.js +6 -6
- package/rules/prefer-simplified-conditions.js +584 -0
- package/rules/prefer-single-object-destructuring.js +2 -5
- package/rules/prefer-single-replace.js +2 -2
- package/rules/prefer-spread.js +25 -32
- package/rules/prefer-string-pad-start-end.js +1 -6
- package/rules/prefer-string-raw.js +2 -2
- package/rules/prefer-string-repeat.js +16 -9
- package/rules/prefer-string-replace-all.js +1 -1
- package/rules/prefer-ternary.js +5 -5
- package/rules/prefer-toggle-attribute.js +3 -3
- package/rules/prefer-top-level-await.js +6 -1
- package/rules/prefer-uint8array-base64.js +3 -1
- package/rules/require-proxy-trap-boolean-return.js +2 -2
- package/rules/rule/to-eslint-create.js +1 -2
- package/rules/rule/to-eslint-listener.js +1 -3
- package/rules/rule/to-eslint-problem.js +1 -3
- package/rules/rule/to-eslint-rule-fixer.js +1 -3
- package/rules/rule/to-eslint-rule.js +1 -3
- package/rules/rule/to-eslint-rules.js +1 -3
- package/rules/rule/unicorn-context.js +1 -3
- package/rules/rule/unicorn-listeners.js +1 -3
- package/rules/shared/array-range.js +66 -0
- package/rules/shared/iterator-helpers.js +43 -1
- package/rules/shared/no-array-mutate-rule.js +1 -3
- package/rules/shared/regexp-escape.js +4 -4
- package/rules/utils/can-tokens-be-adjacent.js +95 -0
- package/rules/utils/comparison.js +65 -0
- package/rules/utils/escape-string.js +2 -8
- package/rules/utils/get-available-variable-name.js +2 -13
- package/rules/utils/get-precedence.js +112 -0
- package/rules/utils/get-token-store.js +1 -3
- package/rules/utils/global-reference-tracker.js +3 -6
- package/rules/utils/index.js +4 -0
- package/rules/utils/is-boolean.js +103 -30
- package/rules/utils/is-identifier-name.js +5 -0
- package/rules/utils/is-promise-type.js +1 -3
- package/rules/utils/is-react-hook-name.js +5 -0
- package/rules/utils/parentheses/get-parent-syntax-opening-parenthesis.js +1 -3
- package/rules/utils/parentheses/iterate-surrounding-parentheses.js +1 -1
- package/rules/utils/should-add-parentheses-to-await-expression-argument.js +4 -9
- package/rules/utils/should-add-parentheses-to-call-expression-callee.js +6 -12
- package/rules/utils/should-add-parentheses-to-unary-expression.js +7 -14
- package/rules/utils/to-location.js +1 -3
- package/rules/utils/track-branch-exits.js +8 -7
|
@@ -1,10 +1,16 @@
|
|
|
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
|
+
isReactHookName,
|
|
10
|
+
isNullishType,
|
|
11
|
+
isTypeParameterType,
|
|
12
|
+
isUnknownType,
|
|
13
|
+
lowerFirst,
|
|
8
14
|
upperFirst,
|
|
9
15
|
} from './utils/index.js';
|
|
10
16
|
import {
|
|
@@ -13,12 +19,15 @@ import {
|
|
|
13
19
|
isBooleanFunctionReference,
|
|
14
20
|
isBooleanFunctionTypeAnnotation,
|
|
15
21
|
isBooleanTypeAnnotation,
|
|
22
|
+
isFunctionTypeAnnotation,
|
|
16
23
|
} from './utils/is-boolean.js';
|
|
17
24
|
|
|
18
25
|
const MESSAGE_ID = 'consistent-boolean-name';
|
|
26
|
+
const MESSAGE_ID_NON_BOOLEAN_PREFIX = 'non-boolean-prefix';
|
|
19
27
|
const MESSAGE_ID_SUGGESTION = 'rename';
|
|
20
28
|
const messages = {
|
|
21
29
|
[MESSAGE_ID]: 'Boolean name `{{name}}` should start with {{prefixes}}.',
|
|
30
|
+
[MESSAGE_ID_NON_BOOLEAN_PREFIX]: '`{{name}}` starts with `{{prefix}}`, so it should be boolean.',
|
|
22
31
|
[MESSAGE_ID_SUGGESTION]: 'Rename to `{{replacement}}`.',
|
|
23
32
|
};
|
|
24
33
|
|
|
@@ -33,6 +42,8 @@ const defaultPrefixes = {
|
|
|
33
42
|
were: true,
|
|
34
43
|
did: true,
|
|
35
44
|
will: true,
|
|
45
|
+
// `requireLogin()` reads like an action or assertion, not a boolean.
|
|
46
|
+
requires: true,
|
|
36
47
|
};
|
|
37
48
|
|
|
38
49
|
const getEnabledPrefixes = ({prefixes = {}} = {}) =>
|
|
@@ -46,8 +57,51 @@ const getEnabledPrefixes = ({prefixes = {}} = {}) =>
|
|
|
46
57
|
const formatPrefixes = prefixes =>
|
|
47
58
|
prefixes.map(prefix => `\`${prefix}\``).join(', ');
|
|
48
59
|
|
|
60
|
+
const booleanBinaryOperators = new Set([
|
|
61
|
+
'>',
|
|
62
|
+
'>=',
|
|
63
|
+
'<',
|
|
64
|
+
'<=',
|
|
65
|
+
'==',
|
|
66
|
+
'===',
|
|
67
|
+
'!=',
|
|
68
|
+
'!==',
|
|
69
|
+
'in',
|
|
70
|
+
'instanceof',
|
|
71
|
+
]);
|
|
72
|
+
const boolean = 'boolean';
|
|
73
|
+
const nonBoolean = 'non-boolean';
|
|
74
|
+
const unknown = 'unknown';
|
|
75
|
+
const nullishTypeAnnotationTypes = new Set([
|
|
76
|
+
'TSNullKeyword',
|
|
77
|
+
'TSUndefinedKeyword',
|
|
78
|
+
]);
|
|
79
|
+
const unknownTypeAnnotationTypes = new Set([
|
|
80
|
+
'TSAnyKeyword',
|
|
81
|
+
'TSNeverKeyword',
|
|
82
|
+
'TSUnknownKeyword',
|
|
83
|
+
]);
|
|
84
|
+
const nonBooleanExpressionTypes = new Set([
|
|
85
|
+
'ArrayExpression',
|
|
86
|
+
'ObjectExpression',
|
|
87
|
+
'ClassExpression',
|
|
88
|
+
'NewExpression',
|
|
89
|
+
'TemplateLiteral',
|
|
90
|
+
'UpdateExpression',
|
|
91
|
+
]);
|
|
92
|
+
const expressionWrapperTypes = new Set([
|
|
93
|
+
'AwaitExpression',
|
|
94
|
+
'TSNonNullExpression',
|
|
95
|
+
'ParenthesizedExpression',
|
|
96
|
+
]);
|
|
97
|
+
const typeScriptExpressionWrapperTypes = new Set([
|
|
98
|
+
'TSAsExpression',
|
|
99
|
+
'TSTypeAssertion',
|
|
100
|
+
'TSSatisfiesExpression',
|
|
101
|
+
]);
|
|
49
102
|
const isUpperCase = string => string === string.toUpperCase();
|
|
50
103
|
const stripLeadingUnderscores = name => name.replace(/^_+/, '');
|
|
104
|
+
const isScreamingCase = name => /^[A-Z][\dA-Z_]*$/.test(stripLeadingUnderscores(name));
|
|
51
105
|
|
|
52
106
|
const isFunction = node => [
|
|
53
107
|
'ArrowFunctionExpression',
|
|
@@ -103,12 +157,12 @@ function isIgnoredName(name, ignore) {
|
|
|
103
157
|
});
|
|
104
158
|
}
|
|
105
159
|
|
|
106
|
-
function
|
|
160
|
+
function getBooleanPrefix(name, prefixes) {
|
|
107
161
|
name = stripLeadingUnderscores(name);
|
|
108
162
|
|
|
109
163
|
for (const prefix of prefixes) {
|
|
110
164
|
if (name.startsWith(`${prefix.toUpperCase()}_`)) {
|
|
111
|
-
return
|
|
165
|
+
return prefix;
|
|
112
166
|
}
|
|
113
167
|
|
|
114
168
|
if (
|
|
@@ -116,11 +170,9 @@ function hasBooleanPrefix(name, prefixes) {
|
|
|
116
170
|
&& name.length > prefix.length
|
|
117
171
|
&& /[\dA-Z_]/.test(name[prefix.length])
|
|
118
172
|
) {
|
|
119
|
-
return
|
|
173
|
+
return prefix;
|
|
120
174
|
}
|
|
121
175
|
}
|
|
122
|
-
|
|
123
|
-
return false;
|
|
124
176
|
}
|
|
125
177
|
|
|
126
178
|
function getReplacementName(name, prefix) {
|
|
@@ -132,6 +184,18 @@ function getReplacementName(name, prefix) {
|
|
|
132
184
|
: `${leadingUnderscores}${prefix}${upperFirst(nameWithoutLeadingUnderscores)}`;
|
|
133
185
|
}
|
|
134
186
|
|
|
187
|
+
function getReactHookReplacementName({name, nameForPrefixCheck}, prefix) {
|
|
188
|
+
if (name === nameForPrefixCheck) {
|
|
189
|
+
return getReplacementName(name, prefix);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const hookName = isScreamingCase(nameForPrefixCheck)
|
|
193
|
+
? getReplacementName(nameForPrefixCheck, prefix)
|
|
194
|
+
: `${prefix}${upperFirst(nameForPrefixCheck)}`;
|
|
195
|
+
|
|
196
|
+
return `use${upperFirst(hookName)}`;
|
|
197
|
+
}
|
|
198
|
+
|
|
135
199
|
const isExportedIdentifier = identifier => {
|
|
136
200
|
if (
|
|
137
201
|
identifier.parent.type === 'VariableDeclarator'
|
|
@@ -175,6 +239,23 @@ const shouldSuggestRename = variable => getVariableIdentifiers(variable)
|
|
|
175
239
|
&& identifier.type !== 'JSXIdentifier',
|
|
176
240
|
);
|
|
177
241
|
|
|
242
|
+
function isDestructuredDefinition(definition) {
|
|
243
|
+
for (let node = definition.name; node && node !== definition.node; node = node.parent) {
|
|
244
|
+
if (
|
|
245
|
+
node.type === 'ObjectPattern'
|
|
246
|
+
|| node.type === 'ArrayPattern'
|
|
247
|
+
) {
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const isDestructuredVariable = variable =>
|
|
256
|
+
variable.defs.length > 0
|
|
257
|
+
&& variable.defs.every(definition => isDestructuredDefinition(definition));
|
|
258
|
+
|
|
178
259
|
const isBooleanFunctionDefinition = (definition, context) =>
|
|
179
260
|
definition.type === 'FunctionName'
|
|
180
261
|
&& isFunction(definition.node)
|
|
@@ -184,45 +265,470 @@ const isBooleanValue = (node, context) => isFunction(node)
|
|
|
184
265
|
? isBooleanFunction(node, context)
|
|
185
266
|
: isBooleanFunctionReference(node, context) || isBooleanExpression(node, context);
|
|
186
267
|
|
|
187
|
-
|
|
188
|
-
|
|
268
|
+
function getSupportedVariableDefinition(variable) {
|
|
269
|
+
if (variable.defs.length !== 1) {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const [definition] = variable.defs;
|
|
274
|
+
const {name} = definition;
|
|
189
275
|
|
|
190
276
|
if (
|
|
191
|
-
|
|
192
|
-
|
|
277
|
+
name?.type !== 'Identifier'
|
|
278
|
+
|| !['Variable', 'Parameter', 'FunctionName'].includes(definition.type)
|
|
279
|
+
|| (definition.type === 'Variable' && definition.node.id.type !== 'Identifier')
|
|
280
|
+
|| (definition.type === 'Parameter' && definition.node.parent?.kind === 'set')
|
|
193
281
|
) {
|
|
194
|
-
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
195
284
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
285
|
+
return definition;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function getFunctionDefinitions(variable) {
|
|
289
|
+
if (
|
|
290
|
+
variable.defs.length <= 1
|
|
291
|
+
|| variable.defs.some(definition => definition.type !== 'FunctionName')
|
|
292
|
+
) {
|
|
293
|
+
return;
|
|
199
294
|
}
|
|
200
295
|
|
|
201
|
-
|
|
296
|
+
const overloadDefinitions = variable.defs.filter(definition => definition.node.type === 'TSDeclareFunction');
|
|
297
|
+
return overloadDefinitions.length > 0 ? overloadDefinitions : variable.defs;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function isReactHookFunctionBinding(variable, context) {
|
|
301
|
+
if (
|
|
302
|
+
variable.name === 'use'
|
|
303
|
+
|| !isReactHookName(variable.name)
|
|
304
|
+
) {
|
|
202
305
|
return false;
|
|
203
306
|
}
|
|
204
307
|
|
|
205
|
-
|
|
206
|
-
|
|
308
|
+
if (getFunctionDefinitions(variable)) {
|
|
309
|
+
return true;
|
|
310
|
+
}
|
|
207
311
|
|
|
208
|
-
|
|
209
|
-
|
|
312
|
+
const definition = getSupportedVariableDefinition(variable);
|
|
313
|
+
|
|
314
|
+
if (definition?.type === 'FunctionName') {
|
|
315
|
+
return isFunction(definition.node);
|
|
210
316
|
}
|
|
211
317
|
|
|
212
|
-
|
|
213
|
-
|
|
318
|
+
return definition?.type === 'Variable'
|
|
319
|
+
&& (
|
|
320
|
+
isFunction(definition.node.init)
|
|
321
|
+
|| isFunctionTypeAnnotation(definition.name.typeAnnotation, context, context.sourceCode.getScope(definition.name))
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function getNameForPrefixCheck(variable, context) {
|
|
326
|
+
if (!isReactHookFunctionBinding(variable, context)) {
|
|
327
|
+
return variable.name;
|
|
214
328
|
}
|
|
215
329
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
330
|
+
const hookName = variable.name.slice(3);
|
|
331
|
+
|
|
332
|
+
return isScreamingCase(hookName) ? hookName : lowerFirst(hookName);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function combineBooleanStates(states) {
|
|
336
|
+
if (
|
|
337
|
+
states.length === 0
|
|
338
|
+
|| states.includes(unknown)
|
|
339
|
+
) {
|
|
340
|
+
return unknown;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return states.every(state => state === boolean) ? boolean : nonBoolean;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function combineVariableBooleanStates(states) {
|
|
347
|
+
if (states.includes(nonBoolean)) {
|
|
348
|
+
return nonBoolean;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return combineBooleanStates(states);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function getTypeBooleanState(type, checker, visitedTypes = new Set(), functionTypesAreBoolean = true) {
|
|
355
|
+
if (!type) {
|
|
356
|
+
return unknown;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (
|
|
360
|
+
isUnknownType(type)
|
|
361
|
+
|| type.intrinsicName === 'never'
|
|
362
|
+
) {
|
|
363
|
+
return unknown;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (visitedTypes.has(type)) {
|
|
367
|
+
return unknown;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
visitedTypes.add(type);
|
|
371
|
+
|
|
372
|
+
if (isTypeParameterType(type)) {
|
|
373
|
+
const constraint = type.getConstraint();
|
|
374
|
+
const result = constraint ? getTypeBooleanState(constraint, checker, visitedTypes, functionTypesAreBoolean) : unknown;
|
|
375
|
+
visitedTypes.delete(type);
|
|
376
|
+
return result;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const nonNullableType = checker.getNonNullableType(type);
|
|
380
|
+
if (nonNullableType !== type) {
|
|
381
|
+
const result = getTypeBooleanState(nonNullableType, checker, visitedTypes, functionTypesAreBoolean);
|
|
382
|
+
visitedTypes.delete(type);
|
|
383
|
+
return result;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (type.isUnion()) {
|
|
387
|
+
const result = combineBooleanStates(
|
|
388
|
+
type.types
|
|
389
|
+
.filter(type => !isNullishType(type))
|
|
390
|
+
.map(type => getTypeBooleanState(type, checker, visitedTypes, functionTypesAreBoolean)),
|
|
391
|
+
);
|
|
392
|
+
visitedTypes.delete(type);
|
|
393
|
+
return result;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const signatures = type.getCallSignatures();
|
|
397
|
+
if (signatures.length > 0) {
|
|
398
|
+
const result = functionTypesAreBoolean
|
|
399
|
+
? combineBooleanStates(signatures.map(signature => getTypeBooleanState(signature.getReturnType(), checker, visitedTypes, false)))
|
|
400
|
+
: nonBoolean;
|
|
401
|
+
visitedTypes.delete(type);
|
|
402
|
+
return result;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const constraint = checker.getBaseConstraintOfType(type);
|
|
406
|
+
if (constraint && constraint !== type) {
|
|
407
|
+
const result = getTypeBooleanState(constraint, checker, visitedTypes, functionTypesAreBoolean);
|
|
408
|
+
visitedTypes.delete(type);
|
|
409
|
+
return result;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (type.getProperties().length === 0) {
|
|
413
|
+
visitedTypes.delete(type);
|
|
414
|
+
return unknown;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const typeString = checker.typeToString(checker.getWidenedType(checker.getBaseTypeOfLiteralType(type)));
|
|
418
|
+
visitedTypes.delete(type);
|
|
419
|
+
|
|
420
|
+
return typeString === 'boolean' ? boolean : nonBoolean;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function getTypeInformationBooleanState(node, context, functionTypesAreBoolean = true) {
|
|
424
|
+
const {parserServices} = context.sourceCode;
|
|
425
|
+
if (!parserServices?.program) {
|
|
426
|
+
return unknown;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
try {
|
|
430
|
+
return getTypeBooleanState(
|
|
431
|
+
parserServices.getTypeAtLocation(node),
|
|
432
|
+
parserServices.program.getTypeChecker(),
|
|
433
|
+
new Set(),
|
|
434
|
+
functionTypesAreBoolean,
|
|
435
|
+
);
|
|
436
|
+
} catch {
|
|
437
|
+
return unknown;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function getTypeReferenceName(typeName) {
|
|
442
|
+
if (typeName?.type === 'Identifier') {
|
|
443
|
+
return typeName.name;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const getTypeState = typeState => ({
|
|
448
|
+
visitedTypeReferenceNames: new Set(),
|
|
449
|
+
functionTypesAreBoolean: true,
|
|
450
|
+
...typeState,
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
function getTypeMembersBooleanState(members, context, scope, typeState) {
|
|
454
|
+
const normalizedTypeState = getTypeState(typeState);
|
|
455
|
+
const callSignatures = members.filter(member => member.type === 'TSCallSignatureDeclaration');
|
|
456
|
+
|
|
457
|
+
if (callSignatures.length > 0) {
|
|
458
|
+
return normalizedTypeState.functionTypesAreBoolean
|
|
459
|
+
? combineBooleanStates(callSignatures.map(member => getTypeAnnotationBooleanState(member.returnType, context, scope, {...normalizedTypeState, functionTypesAreBoolean: false})))
|
|
460
|
+
: nonBoolean;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
return members.length > 0 ? nonBoolean : unknown;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function getTypeReferenceBooleanState(node, context, scope, typeState) {
|
|
467
|
+
const normalizedTypeState = getTypeState(typeState);
|
|
468
|
+
const {visitedTypeReferenceNames} = normalizedTypeState;
|
|
469
|
+
const name = getTypeReferenceName(node.typeName);
|
|
470
|
+
if (!name || visitedTypeReferenceNames.has(name)) {
|
|
471
|
+
return unknown;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
visitedTypeReferenceNames.add(name);
|
|
475
|
+
|
|
476
|
+
const [definition] = resolveVariableName(name, scope)?.defs ?? [];
|
|
477
|
+
let result = unknown;
|
|
478
|
+
|
|
479
|
+
if (definition?.type === 'Type') {
|
|
480
|
+
const definitionScope = context.sourceCode.getScope(definition.node);
|
|
481
|
+
|
|
482
|
+
if (definition.node.type === 'TSTypeAliasDeclaration') {
|
|
483
|
+
result = getTypeAnnotationBooleanState(definition.node.typeAnnotation, context, definitionScope, normalizedTypeState);
|
|
484
|
+
} else if (definition.node.type === 'TSInterfaceDeclaration') {
|
|
485
|
+
result = getTypeMembersBooleanState(definition.node.body.body, context, definitionScope, normalizedTypeState);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
visitedTypeReferenceNames.delete(name);
|
|
490
|
+
return result;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function getUnionTypeAnnotationBooleanState(node, context, scope, typeState) {
|
|
494
|
+
return combineBooleanStates(
|
|
495
|
+
node.types
|
|
496
|
+
.filter(type => !nullishTypeAnnotationTypes.has(type.type))
|
|
497
|
+
.map(type => getTypeAnnotationBooleanState(type, context, scope, typeState)),
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function getSimpleTypeAnnotationBooleanState(node) {
|
|
502
|
+
if (!node) {
|
|
503
|
+
return unknown;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if (
|
|
507
|
+
nullishTypeAnnotationTypes.has(node.type)
|
|
508
|
+
|| unknownTypeAnnotationTypes.has(node.type)
|
|
509
|
+
) {
|
|
510
|
+
return unknown;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
if (node.type === 'TSBooleanKeyword') {
|
|
514
|
+
return boolean;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
if (node.type === 'TSLiteralType') {
|
|
518
|
+
return typeof node.literal.value === 'boolean' ? boolean : nonBoolean;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
if (node.type === 'TSTypePredicate') {
|
|
522
|
+
return node.asserts ? nonBoolean : boolean;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
if (node.type === 'TypeAnnotation') {
|
|
526
|
+
return node.typeAnnotation?.type === 'BooleanTypeAnnotation' ? boolean : nonBoolean;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return nonBoolean;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function getTypeAnnotationBooleanState(node, context, scope, typeState) {
|
|
533
|
+
const normalizedTypeState = getTypeState(typeState);
|
|
534
|
+
|
|
535
|
+
if (
|
|
536
|
+
node?.type === 'TSTypeAnnotation'
|
|
537
|
+
|| node?.type === 'TSParenthesizedType'
|
|
538
|
+
) {
|
|
539
|
+
return getTypeAnnotationBooleanState(node.typeAnnotation, context, scope, normalizedTypeState);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if (node?.type === 'TSFunctionType') {
|
|
543
|
+
return normalizedTypeState.functionTypesAreBoolean
|
|
544
|
+
? getTypeAnnotationBooleanState(node.returnType, context, scope, {...normalizedTypeState, functionTypesAreBoolean: false})
|
|
545
|
+
: nonBoolean;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
if (node?.type === 'TSUnionType') {
|
|
549
|
+
return getUnionTypeAnnotationBooleanState(node, context, scope, normalizedTypeState);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
if (node?.type === 'TSTypeReference') {
|
|
553
|
+
return getTypeReferenceBooleanState(node, context, scope, normalizedTypeState);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
if (node?.type === 'TSTypeLiteral') {
|
|
557
|
+
return getTypeMembersBooleanState(node.members, context, scope, normalizedTypeState);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
return getSimpleTypeAnnotationBooleanState(node);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
function getFunctionBooleanState(node, context, visitedVariables = new Set()) {
|
|
564
|
+
if (node.async || node.generator) {
|
|
565
|
+
return nonBoolean;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
const stateFromTypeInformation = getTypeInformationBooleanState(node, context);
|
|
569
|
+
if (stateFromTypeInformation !== unknown) {
|
|
570
|
+
return stateFromTypeInformation;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const scope = context.sourceCode.getScope(node);
|
|
574
|
+
const stateFromReturnType = getTypeAnnotationBooleanState(node.returnType, context, scope, {functionTypesAreBoolean: false});
|
|
575
|
+
if (stateFromReturnType !== unknown) {
|
|
576
|
+
return stateFromReturnType;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
if (!node.body) {
|
|
580
|
+
return unknown;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
if (node.body.type === 'BlockStatement') {
|
|
584
|
+
if (node.body.body.length === 0) {
|
|
585
|
+
return nonBoolean;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
if (
|
|
589
|
+
node.body.body.length === 1
|
|
590
|
+
&& node.body.body[0].type === 'ReturnStatement'
|
|
591
|
+
) {
|
|
592
|
+
return node.body.body[0].argument
|
|
593
|
+
? getExpressionBooleanState(node.body.body[0].argument, context, visitedVariables, false)
|
|
594
|
+
: nonBoolean;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
return node.type === 'ArrowFunctionExpression' && node.body.type !== 'BlockStatement'
|
|
599
|
+
? getExpressionBooleanState(node.body, context, visitedVariables, false)
|
|
600
|
+
: unknown;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
function getKnownIdentifierBooleanState(node, context, visitedVariables, functionValuesAreBoolean) {
|
|
604
|
+
const variable = findVariable(context.sourceCode.getScope(node), node);
|
|
605
|
+
return variable ? getVariableBooleanState(variable, context, visitedVariables, functionValuesAreBoolean) : unknown;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
function getStaticExpressionBooleanState(node, scope) {
|
|
609
|
+
if (node.type === 'Identifier') {
|
|
610
|
+
return unknown;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
const staticValue = getStaticValue(node, scope)?.value;
|
|
614
|
+
|
|
615
|
+
return staticValue === undefined
|
|
616
|
+
? unknown
|
|
617
|
+
: (typeof staticValue === 'boolean' ? boolean : nonBoolean);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
function getSimpleExpressionBooleanState(node) {
|
|
621
|
+
if (nonBooleanExpressionTypes.has(node.type)) {
|
|
622
|
+
return nonBoolean;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
if (node.type === 'Literal') {
|
|
626
|
+
return node.value === null ? unknown : nonBoolean;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
if (node.type === 'UnaryExpression') {
|
|
630
|
+
return ['!', 'delete'].includes(node.operator) ? boolean : nonBoolean;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
if (node.type === 'BinaryExpression') {
|
|
634
|
+
return booleanBinaryOperators.has(node.operator) ? boolean : nonBoolean;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
return unknown;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
function getWrappedExpression(node) {
|
|
641
|
+
if (expressionWrapperTypes.has(node.type)) {
|
|
642
|
+
return node.argument ?? node.expression;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
if (typeScriptExpressionWrapperTypes.has(node.type)) {
|
|
646
|
+
return node.expression;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
function getDerivedExpressionBooleanState(node, context, visitedVariables, functionValuesAreBoolean) {
|
|
651
|
+
if (node.type === 'Identifier') {
|
|
652
|
+
return getKnownIdentifierBooleanState(node, context, visitedVariables, functionValuesAreBoolean);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
const wrappedExpression = getWrappedExpression(node);
|
|
656
|
+
if (wrappedExpression) {
|
|
657
|
+
return getExpressionBooleanState(wrappedExpression, context, visitedVariables, functionValuesAreBoolean);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
if (node.type === 'AssignmentExpression') {
|
|
661
|
+
return node.operator === '=' ? getExpressionBooleanState(node.right, context, visitedVariables, functionValuesAreBoolean) : unknown;
|
|
219
662
|
}
|
|
220
663
|
|
|
221
|
-
|
|
222
|
-
|
|
664
|
+
if (node.type === 'SequenceExpression') {
|
|
665
|
+
return getExpressionBooleanState(node.expressions.at(-1), context, visitedVariables, functionValuesAreBoolean);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
if (node.type === 'ConditionalExpression') {
|
|
669
|
+
return combineBooleanStates([
|
|
670
|
+
getExpressionBooleanState(node.consequent, context, visitedVariables, functionValuesAreBoolean),
|
|
671
|
+
getExpressionBooleanState(node.alternate, context, visitedVariables, functionValuesAreBoolean),
|
|
672
|
+
]);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
return unknown;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
function getExpressionBooleanState(node, context, visitedVariables = new Set(), functionValuesAreBoolean = true) {
|
|
679
|
+
if (!node) {
|
|
680
|
+
return unknown;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
if (isFunction(node)) {
|
|
684
|
+
return functionValuesAreBoolean
|
|
685
|
+
? getFunctionBooleanState(node, context, visitedVariables)
|
|
686
|
+
: nonBoolean;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
const stateFromTypeInformation = getTypeInformationBooleanState(node, context, functionValuesAreBoolean);
|
|
690
|
+
if (stateFromTypeInformation !== unknown) {
|
|
691
|
+
return stateFromTypeInformation;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
const scope = context.sourceCode.getScope(node);
|
|
695
|
+
const stateFromTypeAnnotation = getTypeAnnotationBooleanState(node.typeAnnotation, context, scope, {functionTypesAreBoolean: functionValuesAreBoolean});
|
|
696
|
+
if (stateFromTypeAnnotation !== unknown) {
|
|
697
|
+
return stateFromTypeAnnotation;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
if (isBooleanExpression(node, context, visitedVariables)) {
|
|
701
|
+
return boolean;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
const stateFromStaticValue = getStaticExpressionBooleanState(node, scope);
|
|
705
|
+
if (stateFromStaticValue !== unknown) {
|
|
706
|
+
return stateFromStaticValue;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
const stateFromSimpleExpression = getSimpleExpressionBooleanState(node);
|
|
710
|
+
if (stateFromSimpleExpression !== unknown) {
|
|
711
|
+
return stateFromSimpleExpression;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
return getDerivedExpressionBooleanState(node, context, visitedVariables, functionValuesAreBoolean);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
const isBooleanVariable = (variable, context) => {
|
|
718
|
+
const {sourceCode} = context;
|
|
719
|
+
|
|
720
|
+
const functionDefinitions = getFunctionDefinitions(variable);
|
|
721
|
+
if (functionDefinitions) {
|
|
722
|
+
return functionDefinitions.every(definition => isBooleanFunctionDefinition(definition, context));
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
const definition = getSupportedVariableDefinition(variable);
|
|
726
|
+
if (!definition) {
|
|
223
727
|
return false;
|
|
224
728
|
}
|
|
225
729
|
|
|
730
|
+
const {name} = definition;
|
|
731
|
+
|
|
226
732
|
const scope = sourceCode.getScope(name);
|
|
227
733
|
|
|
228
734
|
if (name.typeAnnotation) {
|
|
@@ -250,12 +756,91 @@ const isBooleanVariable = (variable, context) => {
|
|
|
250
756
|
return isBooleanValue(definition.node.init, context);
|
|
251
757
|
}
|
|
252
758
|
|
|
759
|
+
return definition.type === 'FunctionName' && isBooleanFunctionDefinition(definition, context);
|
|
760
|
+
};
|
|
761
|
+
|
|
762
|
+
function getParameterBooleanState(definition, context, visitedVariables, functionValuesAreBoolean) {
|
|
763
|
+
if (!isFunction(definition.node)) {
|
|
764
|
+
return unknown;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
const parameter = findParameter(definition.node.params, definition.name);
|
|
768
|
+
|
|
769
|
+
return parameter?.type === 'AssignmentPattern'
|
|
770
|
+
? getExpressionBooleanState(parameter.right, context, visitedVariables, functionValuesAreBoolean)
|
|
771
|
+
: unknown;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
function getDefinitionBooleanState(definition, context, visitedVariables, functionValuesAreBoolean) {
|
|
775
|
+
const scope = context.sourceCode.getScope(definition.name);
|
|
776
|
+
const stateFromTypeAnnotation = getTypeAnnotationBooleanState(definition.name.typeAnnotation, context, scope, {functionTypesAreBoolean: functionValuesAreBoolean});
|
|
777
|
+
if (stateFromTypeAnnotation !== unknown) {
|
|
778
|
+
return stateFromTypeAnnotation;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
if (definition.type === 'Parameter') {
|
|
782
|
+
return getParameterBooleanState(definition, context, visitedVariables, functionValuesAreBoolean);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
if (definition.type === 'Variable') {
|
|
786
|
+
return getExpressionBooleanState(definition.node.init, context, visitedVariables, functionValuesAreBoolean);
|
|
787
|
+
}
|
|
788
|
+
|
|
253
789
|
if (definition.type === 'FunctionName') {
|
|
254
|
-
return
|
|
790
|
+
return functionValuesAreBoolean
|
|
791
|
+
? getFunctionBooleanState(definition.node, context, visitedVariables)
|
|
792
|
+
: nonBoolean;
|
|
255
793
|
}
|
|
256
794
|
|
|
257
|
-
return
|
|
258
|
-
}
|
|
795
|
+
return unknown;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
function getVariableBooleanState(variable, context, visitedVariables = new Set(), functionValuesAreBoolean = true) {
|
|
799
|
+
if (!variable || visitedVariables.has(variable)) {
|
|
800
|
+
return unknown;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
visitedVariables.add(variable);
|
|
804
|
+
|
|
805
|
+
const functionDefinitions = getFunctionDefinitions(variable);
|
|
806
|
+
const definition = getSupportedVariableDefinition(variable);
|
|
807
|
+
if (
|
|
808
|
+
!functionDefinitions
|
|
809
|
+
&& !definition
|
|
810
|
+
) {
|
|
811
|
+
visitedVariables.delete(variable);
|
|
812
|
+
return unknown;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
let result;
|
|
816
|
+
if (functionDefinitions && !functionValuesAreBoolean) {
|
|
817
|
+
result = nonBoolean;
|
|
818
|
+
} else if (functionDefinitions) {
|
|
819
|
+
result = combineBooleanStates(functionDefinitions.map(definition => getFunctionBooleanState(definition.node, context, visitedVariables)));
|
|
820
|
+
} else {
|
|
821
|
+
result = getDefinitionBooleanState(definition, context, visitedVariables, functionValuesAreBoolean);
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
if (variable.references.some(reference => reference.writeExpr)) {
|
|
825
|
+
result = combineVariableBooleanStates([
|
|
826
|
+
result,
|
|
827
|
+
...variable.references
|
|
828
|
+
.filter(reference => reference.writeExpr)
|
|
829
|
+
.map(reference => getExpressionBooleanState(reference.writeExpr, context, visitedVariables, functionValuesAreBoolean)),
|
|
830
|
+
]);
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
if (
|
|
834
|
+
result === unknown
|
|
835
|
+
&& functionValuesAreBoolean
|
|
836
|
+
&& isBooleanVariable(variable, context)
|
|
837
|
+
) {
|
|
838
|
+
result = boolean;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
visitedVariables.delete(variable);
|
|
842
|
+
return result;
|
|
843
|
+
}
|
|
259
844
|
|
|
260
845
|
function getBooleanPropertyName(node, sourceCode) {
|
|
261
846
|
if (
|
|
@@ -324,7 +909,54 @@ function isBooleanProperty(node, context) {
|
|
|
324
909
|
return false;
|
|
325
910
|
}
|
|
326
911
|
|
|
327
|
-
function
|
|
912
|
+
function getExplicitPropertyBooleanState(node, context) {
|
|
913
|
+
const {sourceCode} = context;
|
|
914
|
+
|
|
915
|
+
if (node.type === 'Property') {
|
|
916
|
+
if (
|
|
917
|
+
node.parent.type !== 'ObjectExpression'
|
|
918
|
+
|| node.shorthand
|
|
919
|
+
|| node.kind === 'set'
|
|
920
|
+
) {
|
|
921
|
+
return unknown;
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
return getExpressionBooleanState(node.value, context);
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
if (
|
|
928
|
+
node.type === 'MethodDefinition'
|
|
929
|
+
|| node.type === 'TSAbstractMethodDefinition'
|
|
930
|
+
) {
|
|
931
|
+
return ['constructor', 'set'].includes(node.kind) ? unknown : getFunctionBooleanState(node.value, context);
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
if (propertyDefinitionTypes.has(node.type)) {
|
|
935
|
+
const scope = sourceCode.getScope(node);
|
|
936
|
+
const stateFromTypeAnnotation = getTypeAnnotationBooleanState(node.typeAnnotation, context, scope);
|
|
937
|
+
|
|
938
|
+
return stateFromTypeAnnotation === unknown
|
|
939
|
+
? getExpressionBooleanState(node.value, context)
|
|
940
|
+
: stateFromTypeAnnotation;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
if (node.type === 'TSPropertySignature') {
|
|
944
|
+
return getTypeAnnotationBooleanState(node.typeAnnotation, context, sourceCode.getScope(node));
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
if (node.type === 'TSMethodSignature') {
|
|
948
|
+
return getTypeAnnotationBooleanState(node.returnType, context, sourceCode.getScope(node), {functionTypesAreBoolean: false});
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
return unknown;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
function getPropertyBooleanState(node, context) {
|
|
955
|
+
const state = getExplicitPropertyBooleanState(node, context);
|
|
956
|
+
return state === unknown && isBooleanProperty(node, context) ? boolean : state;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
function getSuggestions(variable, prefixes, context, nameForPrefixCheck) {
|
|
328
960
|
if (
|
|
329
961
|
!shouldSuggestRename(variable)
|
|
330
962
|
|| variable.references.some(reference => reference.vueUsedInTemplate)
|
|
@@ -340,7 +972,7 @@ function getSuggestions(variable, prefixes, context) {
|
|
|
340
972
|
const suggestions = [];
|
|
341
973
|
|
|
342
974
|
for (const prefix of prefixes) {
|
|
343
|
-
const replacement = getAvailableVariableName(
|
|
975
|
+
const replacement = getAvailableVariableName(getReactHookReplacementName({name: variable.name, nameForPrefixCheck}, prefix), scopes);
|
|
344
976
|
|
|
345
977
|
if (!replacement || usedReplacements.has(replacement)) {
|
|
346
978
|
continue;
|
|
@@ -390,7 +1022,13 @@ function isInDeclareContext(node) {
|
|
|
390
1022
|
return false;
|
|
391
1023
|
}
|
|
392
1024
|
|
|
393
|
-
function getAutofix(
|
|
1025
|
+
function getAutofix({
|
|
1026
|
+
variable,
|
|
1027
|
+
prefixes,
|
|
1028
|
+
context,
|
|
1029
|
+
suggestions,
|
|
1030
|
+
nameForPrefixCheck,
|
|
1031
|
+
}) {
|
|
394
1032
|
if (
|
|
395
1033
|
!suggestions
|
|
396
1034
|
|| !isAutofixableVariable(variable, context)
|
|
@@ -399,7 +1037,7 @@ function getAutofix(variable, prefixes, context, suggestions) {
|
|
|
399
1037
|
}
|
|
400
1038
|
|
|
401
1039
|
const [prefix] = prefixes;
|
|
402
|
-
const replacement =
|
|
1040
|
+
const replacement = getReactHookReplacementName({name: variable.name, nameForPrefixCheck}, prefix);
|
|
403
1041
|
const suggestion = suggestions.find(suggestion => suggestion.data.replacement === replacement);
|
|
404
1042
|
|
|
405
1043
|
return suggestion?.fix;
|
|
@@ -414,27 +1052,56 @@ const create = context => {
|
|
|
414
1052
|
}
|
|
415
1053
|
|
|
416
1054
|
const checkVariable = variable => {
|
|
417
|
-
// `hasBooleanPrefix` and `ignore` are cheap string checks, so run them before
|
|
418
|
-
// the expensive `isBooleanVariable` analysis.
|
|
419
1055
|
if (
|
|
420
|
-
|
|
421
|
-
||
|
|
1056
|
+
isIgnoredName(variable.name, ignore)
|
|
1057
|
+
|| isDestructuredVariable(variable)
|
|
1058
|
+
) {
|
|
1059
|
+
return;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
const nameForPrefixCheck = getNameForPrefixCheck(variable, context);
|
|
1063
|
+
const booleanPrefix = getBooleanPrefix(nameForPrefixCheck, prefixes);
|
|
1064
|
+
if (booleanPrefix) {
|
|
1065
|
+
if (getVariableBooleanState(variable, context) === nonBoolean) {
|
|
1066
|
+
const [definition] = variable.defs;
|
|
1067
|
+
|
|
1068
|
+
context.report({
|
|
1069
|
+
node: definition.name,
|
|
1070
|
+
messageId: MESSAGE_ID_NON_BOOLEAN_PREFIX,
|
|
1071
|
+
data: {
|
|
1072
|
+
name: nameForPrefixCheck,
|
|
1073
|
+
prefix: booleanPrefix,
|
|
1074
|
+
},
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
return;
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
if (
|
|
1082
|
+
getVariableBooleanState(variable, context) === nonBoolean
|
|
422
1083
|
|| !isBooleanVariable(variable, context)
|
|
423
1084
|
) {
|
|
424
1085
|
return;
|
|
425
1086
|
}
|
|
426
1087
|
|
|
427
1088
|
const [definition] = variable.defs;
|
|
428
|
-
const suggest = getSuggestions(variable, prefixes, context);
|
|
1089
|
+
const suggest = getSuggestions(variable, prefixes, context, nameForPrefixCheck);
|
|
429
1090
|
|
|
430
1091
|
context.report({
|
|
431
1092
|
node: definition.name,
|
|
432
1093
|
messageId: MESSAGE_ID,
|
|
433
1094
|
data: {
|
|
434
|
-
name:
|
|
1095
|
+
name: nameForPrefixCheck,
|
|
435
1096
|
prefixes: formatPrefixes(prefixes),
|
|
436
1097
|
},
|
|
437
|
-
fix: getAutofix(
|
|
1098
|
+
fix: getAutofix({
|
|
1099
|
+
variable,
|
|
1100
|
+
prefixes,
|
|
1101
|
+
context,
|
|
1102
|
+
suggestions: suggest,
|
|
1103
|
+
nameForPrefixCheck,
|
|
1104
|
+
}),
|
|
438
1105
|
suggest,
|
|
439
1106
|
});
|
|
440
1107
|
};
|
|
@@ -456,13 +1123,31 @@ const create = context => {
|
|
|
456
1123
|
|
|
457
1124
|
if (
|
|
458
1125
|
!name
|
|
459
|
-
|| hasBooleanPrefix(name, prefixes)
|
|
460
1126
|
|| isIgnoredName(name, ignore)
|
|
461
|
-
|| !isBooleanProperty(node, context)
|
|
462
1127
|
) {
|
|
463
1128
|
return;
|
|
464
1129
|
}
|
|
465
1130
|
|
|
1131
|
+
const booleanPrefix = getBooleanPrefix(name, prefixes);
|
|
1132
|
+
if (booleanPrefix) {
|
|
1133
|
+
if (getPropertyBooleanState(node, context) === nonBoolean) {
|
|
1134
|
+
context.report({
|
|
1135
|
+
node: node.key,
|
|
1136
|
+
messageId: MESSAGE_ID_NON_BOOLEAN_PREFIX,
|
|
1137
|
+
data: {
|
|
1138
|
+
name,
|
|
1139
|
+
prefix: booleanPrefix,
|
|
1140
|
+
},
|
|
1141
|
+
});
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
if (!isBooleanProperty(node, context)) {
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
466
1151
|
context.report({
|
|
467
1152
|
node: node.key,
|
|
468
1153
|
messageId: MESSAGE_ID,
|