eslint-plugin-unicorn 70.0.0 → 71.1.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 +5 -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 +275 -29
- 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 +2 -2
- package/rules/explicit-length-check.js +3 -3
- package/rules/filename-case.js +4 -4
- package/rules/fix/remove-argument.js +34 -8
- package/rules/fix/remove-statement.js +1 -3
- package/rules/id-match.js +2 -2
- package/rules/isolated-functions.js +47 -7
- 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-chained-comparison.js +1 -1
- 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-mismatched-map-key.js +13 -20
- package/rules/no-named-default.js +2 -5
- package/rules/no-negated-array-predicate.js +4 -11
- package/rules/no-negated-comparison.js +7 -10
- package/rules/no-negation-in-equality-check.js +2 -5
- package/rules/no-process-exit.js +6 -6
- package/rules/no-static-only-class.js +2 -8
- package/rules/no-thenable.js +9 -15
- package/rules/no-undeclared-class-members.js +1 -5
- package/rules/no-unnecessary-array-flat-map.js +6 -5
- package/rules/no-unnecessary-global-this.js +5 -9
- package/rules/no-unused-properties.js +0 -1
- package/rules/no-useless-coercion.js +1 -2
- package/rules/no-useless-undefined.js +2 -2
- package/rules/prefer-array-find.js +2 -5
- package/rules/prefer-bigint-literals.js +1 -0
- package/rules/prefer-boolean-return.js +20 -3
- package/rules/prefer-default-parameters.js +2 -2
- package/rules/prefer-else-if.js +5 -9
- 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-minimal-ternary.js +4 -4
- package/rules/prefer-object-define-properties.js +1 -3
- 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-single-object-destructuring.js +2 -5
- package/rules/prefer-single-replace.js +2 -2
- package/rules/prefer-spread.js +22 -32
- package/rules/prefer-string-pad-start-end.js +1 -6
- 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/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/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 +90 -32
- 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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-unicorn",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "71.1.0",
|
|
4
4
|
"description": "More than 300 powerful ESLint rules",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -62,58 +62,59 @@
|
|
|
62
62
|
"xo"
|
|
63
63
|
],
|
|
64
64
|
"dependencies": {
|
|
65
|
-
"@babel/helper-validator-identifier": "^7.29.7",
|
|
66
65
|
"@eslint-community/eslint-utils": "^4.9.1",
|
|
67
|
-
"browserslist": "^4.28.
|
|
66
|
+
"browserslist": "^4.28.4",
|
|
68
67
|
"change-case": "^5.4.4",
|
|
69
68
|
"ci-info": "^4.4.0",
|
|
70
69
|
"core-js-compat": "^3.49.0",
|
|
71
70
|
"detect-indent": "^7.0.2",
|
|
72
71
|
"find-up-simple": "^1.0.1",
|
|
73
|
-
"globals": "^17.
|
|
72
|
+
"globals": "^17.7.0",
|
|
74
73
|
"indent-string": "^5.0.0",
|
|
75
74
|
"is-builtin-module": "^5.0.0",
|
|
76
|
-
"
|
|
75
|
+
"is-identifier": "^1.1.0",
|
|
77
76
|
"pluralize": "^8.0.0",
|
|
78
|
-
"
|
|
79
|
-
"
|
|
77
|
+
"quote-js-string": "^0.1.0",
|
|
78
|
+
"regjsparser": "^0.13.2",
|
|
79
|
+
"reserved-identifiers": "^1.2.0",
|
|
80
|
+
"semver": "^7.8.5",
|
|
80
81
|
"strip-indent": "^4.1.1"
|
|
81
82
|
},
|
|
82
83
|
"devDependencies": {
|
|
83
|
-
"@babel/code-frame": "^8.0.0
|
|
84
|
-
"@eslint/css": "^1.
|
|
84
|
+
"@babel/code-frame": "^8.0.0",
|
|
85
|
+
"@eslint/css": "^1.4.0",
|
|
85
86
|
"@eslint/eslintrc": "^3.3.5",
|
|
86
|
-
"@eslint/json": "^2.0.
|
|
87
|
-
"@eslint/markdown": "^8.0.
|
|
88
|
-
"@html-eslint/eslint-plugin": "^0.
|
|
89
|
-
"@html-eslint/parser": "^0.
|
|
87
|
+
"@eslint/json": "^2.0.1",
|
|
88
|
+
"@eslint/markdown": "^8.0.3",
|
|
89
|
+
"@html-eslint/eslint-plugin": "^0.63.0",
|
|
90
|
+
"@html-eslint/parser": "^0.63.0",
|
|
90
91
|
"@lubien/fixture-beta-package": "^1.0.0-beta.1",
|
|
91
|
-
"@typescript-eslint/parser": "^8.
|
|
92
|
-
"@typescript-eslint/types": "^8.
|
|
92
|
+
"@typescript-eslint/parser": "^8.62.1",
|
|
93
|
+
"@typescript-eslint/types": "^8.62.1",
|
|
93
94
|
"ava": "^8.0.1",
|
|
94
95
|
"c8": "^11.0.0",
|
|
95
96
|
"enquirer": "^2.4.1",
|
|
96
|
-
"eslint": "^10.
|
|
97
|
+
"eslint": "^10.6.0",
|
|
97
98
|
"eslint-ava-rule-tester": "^5.0.1",
|
|
98
|
-
"eslint-config-xo": "^0.
|
|
99
|
+
"eslint-config-xo": "^0.56.0",
|
|
99
100
|
"eslint-doc-generator": "^3.6.0",
|
|
100
|
-
"eslint-plugin-eslint-plugin": "^7.4.
|
|
101
|
-
"eslint-plugin-jsdoc": "^63.0.
|
|
102
|
-
"eslint-plugin-unicorn": "^
|
|
101
|
+
"eslint-plugin-eslint-plugin": "^7.4.1",
|
|
102
|
+
"eslint-plugin-jsdoc": "^63.0.11",
|
|
103
|
+
"eslint-plugin-unicorn": "^70.0.0",
|
|
103
104
|
"eslint-remote-tester": "^4.0.4",
|
|
104
105
|
"eslint-remote-tester-repositories": "^2.0.2",
|
|
105
106
|
"eslint-scope": "^9.1.2",
|
|
106
107
|
"espree": "^11.2.0",
|
|
107
|
-
"listr2": "^10.2.
|
|
108
|
-
"markdownlint-cli": "^0.
|
|
108
|
+
"listr2": "^10.2.2",
|
|
109
|
+
"markdownlint-cli": "^0.49.0",
|
|
109
110
|
"nano-spawn": "^2.1.0",
|
|
110
111
|
"node-style-text": "^2.1.2",
|
|
111
|
-
"npm-package-json-lint": "^10.4.
|
|
112
|
-
"npm-run-all2": "^9.0.
|
|
112
|
+
"npm-package-json-lint": "^10.4.1",
|
|
113
|
+
"npm-run-all2": "^9.0.2",
|
|
113
114
|
"open-editor": "^6.0.0",
|
|
114
115
|
"outdent": "^0.8.0",
|
|
115
116
|
"pretty-ms": "^9.3.0",
|
|
116
|
-
"svelte": "^5.56.
|
|
117
|
+
"svelte": "^5.56.4",
|
|
117
118
|
"svelte-eslint-parser": "^1.8.0",
|
|
118
119
|
"typescript": "^6.0.3",
|
|
119
120
|
"vue-eslint-parser": "^10.4.1",
|
package/readme.md
CHANGED
|
@@ -391,7 +391,7 @@ export default defineConfig([
|
|
|
391
391
|
| [prefer-top-level-await](docs/rules/prefer-top-level-await.md) | Prefer top-level await over top-level promises and async function calls. | ✅ ☑️ | | 💡 | |
|
|
392
392
|
| [prefer-type-error](docs/rules/prefer-type-error.md) | Enforce throwing `TypeError` in type checking conditions. | ✅ ☑️ | 🔧 | | |
|
|
393
393
|
| [prefer-type-literal-last](docs/rules/prefer-type-literal-last.md) | Require type literals to be last in union types. | ✅ | 🔧 | | |
|
|
394
|
-
| [prefer-uint8array-base64](docs/rules/prefer-uint8array-base64.md) | Prefer `Uint8Array#toBase64()` and `Uint8Array.fromBase64()` over `atob()`, `btoa()`, and `Buffer` base64 conversions. |
|
|
394
|
+
| [prefer-uint8array-base64](docs/rules/prefer-uint8array-base64.md) | Prefer `Uint8Array#toBase64()` and `Uint8Array.fromBase64()` over `atob()`, `btoa()`, and `Buffer` base64 conversions. | | | 💡 | |
|
|
395
395
|
| [prefer-unary-minus](docs/rules/prefer-unary-minus.md) | Prefer the unary minus operator over multiplying or dividing by `-1`. | ✅ ☑️ | 🔧 | | |
|
|
396
396
|
| [prefer-unicode-code-point-escapes](docs/rules/prefer-unicode-code-point-escapes.md) | Prefer Unicode code point escapes over legacy escape sequences. | ✅ ☑️ | 🔧 | 💡 | |
|
|
397
397
|
| [prefer-url-can-parse](docs/rules/prefer-url-can-parse.md) | Prefer `URL.canParse()` over constructing a `URL` in a try/catch for validation. | ✅ ☑️ | 🔧 | | |
|
|
@@ -566,3 +566,7 @@ export default defineConfig([
|
|
|
566
566
|
|
|
567
567
|
- [Sindre Sorhus](https://github.com/sindresorhus)
|
|
568
568
|
- [Fisker Cheung](https://github.com/fisker)
|
|
569
|
+
|
|
570
|
+
## Related
|
|
571
|
+
|
|
572
|
+
- [eslint-node-test](https://github.com/sindresorhus/eslint-node-test) — ESLint rules for the Node.js built-in test runner.
|
|
@@ -79,18 +79,12 @@ function create(node, options, types) {
|
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
Array.isArray(names)
|
|
82
|
+
return !(Array.isArray(names)
|
|
84
83
|
&& names.length > 0
|
|
85
84
|
&& (
|
|
86
85
|
node.callee.type !== 'Identifier'
|
|
87
86
|
|| !names.includes(node.callee.name)
|
|
88
|
-
)
|
|
89
|
-
) {
|
|
90
|
-
return false;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return true;
|
|
87
|
+
));
|
|
94
88
|
}
|
|
95
89
|
|
|
96
90
|
/**
|
|
@@ -83,16 +83,10 @@ export default function isMemberExpression(node, options) {
|
|
|
83
83
|
return false;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
|
|
87
|
-
Array.isArray(objects)
|
|
86
|
+
return !(Array.isArray(objects)
|
|
88
87
|
&& objects.length > 0
|
|
89
88
|
&& (
|
|
90
89
|
node.object.type !== 'Identifier'
|
|
91
90
|
|| !objects.includes(node.object.name)
|
|
92
|
-
)
|
|
93
|
-
) {
|
|
94
|
-
return false;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return true;
|
|
91
|
+
));
|
|
98
92
|
}
|
package/rules/comment-content.js
CHANGED
|
@@ -346,7 +346,7 @@ const pathTerminatorCharacters = '"\'`<>';
|
|
|
346
346
|
const packageSpecifierTerminatorCharacters = '"\'`()[]{}<>,';
|
|
347
347
|
const domainLeadingPunctuation = '([{<';
|
|
348
348
|
const domainTrailingPunctuation = '.,;:!?)]}>';
|
|
349
|
-
const maskCharacter = '\
|
|
349
|
+
const maskCharacter = '\u{FFFF}';
|
|
350
350
|
const openBrackets = '([{';
|
|
351
351
|
const closeBrackets = ')]}';
|
|
352
352
|
const quoteCharacters = '"\'`';
|
|
@@ -1062,14 +1062,14 @@ const create = context => {
|
|
|
1062
1062
|
return;
|
|
1063
1063
|
}
|
|
1064
1064
|
|
|
1065
|
-
let
|
|
1065
|
+
let isChecked = false;
|
|
1066
1066
|
|
|
1067
1067
|
onRoot(context, function * (node) {
|
|
1068
|
-
if (
|
|
1068
|
+
if (isChecked) {
|
|
1069
1069
|
return;
|
|
1070
1070
|
}
|
|
1071
1071
|
|
|
1072
|
-
|
|
1072
|
+
isChecked = true;
|
|
1073
1073
|
|
|
1074
1074
|
const {sourceCode} = context;
|
|
1075
1075
|
const comments = getRuleComments(context);
|
|
@@ -6,9 +6,11 @@ import {
|
|
|
6
6
|
getAvailableVariableName,
|
|
7
7
|
getScopes,
|
|
8
8
|
getVariableIdentifiers,
|
|
9
|
+
isReactHookName,
|
|
9
10
|
isNullishType,
|
|
10
11
|
isTypeParameterType,
|
|
11
12
|
isUnknownType,
|
|
13
|
+
lowerFirst,
|
|
12
14
|
upperFirst,
|
|
13
15
|
} from './utils/index.js';
|
|
14
16
|
import {
|
|
@@ -17,6 +19,7 @@ import {
|
|
|
17
19
|
isBooleanFunctionReference,
|
|
18
20
|
isBooleanFunctionTypeAnnotation,
|
|
19
21
|
isBooleanTypeAnnotation,
|
|
22
|
+
isFunctionTypeAnnotation,
|
|
20
23
|
} from './utils/is-boolean.js';
|
|
21
24
|
|
|
22
25
|
const MESSAGE_ID = 'consistent-boolean-name';
|
|
@@ -78,6 +81,7 @@ const unknownTypeAnnotationTypes = new Set([
|
|
|
78
81
|
'TSNeverKeyword',
|
|
79
82
|
'TSUnknownKeyword',
|
|
80
83
|
]);
|
|
84
|
+
const promiseValueTypeNames = new Set(['Promise', 'PromiseLike']);
|
|
81
85
|
const nonBooleanExpressionTypes = new Set([
|
|
82
86
|
'ArrayExpression',
|
|
83
87
|
'ObjectExpression',
|
|
@@ -98,6 +102,8 @@ const typeScriptExpressionWrapperTypes = new Set([
|
|
|
98
102
|
]);
|
|
99
103
|
const isUpperCase = string => string === string.toUpperCase();
|
|
100
104
|
const stripLeadingUnderscores = name => name.replace(/^_+/, '');
|
|
105
|
+
const isScreamingCase = name => /^[A-Z][\dA-Z_]*$/.test(stripLeadingUnderscores(name));
|
|
106
|
+
const hasReactReferenceSuffix = name => /(?:Ref|Reference)$/.test(stripLeadingUnderscores(name));
|
|
101
107
|
|
|
102
108
|
const isFunction = node => [
|
|
103
109
|
'ArrowFunctionExpression',
|
|
@@ -180,6 +186,18 @@ function getReplacementName(name, prefix) {
|
|
|
180
186
|
: `${leadingUnderscores}${prefix}${upperFirst(nameWithoutLeadingUnderscores)}`;
|
|
181
187
|
}
|
|
182
188
|
|
|
189
|
+
function getReactHookReplacementName({name, nameForPrefixCheck}, prefix) {
|
|
190
|
+
if (name === nameForPrefixCheck) {
|
|
191
|
+
return getReplacementName(name, prefix);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const hookName = isScreamingCase(nameForPrefixCheck)
|
|
195
|
+
? getReplacementName(nameForPrefixCheck, prefix)
|
|
196
|
+
: `${prefix}${upperFirst(nameForPrefixCheck)}`;
|
|
197
|
+
|
|
198
|
+
return `use${upperFirst(hookName)}`;
|
|
199
|
+
}
|
|
200
|
+
|
|
183
201
|
const isExportedIdentifier = identifier => {
|
|
184
202
|
if (
|
|
185
203
|
identifier.parent.type === 'VariableDeclarator'
|
|
@@ -223,6 +241,26 @@ const shouldSuggestRename = variable => getVariableIdentifiers(variable)
|
|
|
223
241
|
&& identifier.type !== 'JSXIdentifier',
|
|
224
242
|
);
|
|
225
243
|
|
|
244
|
+
function isDestructuredDefinition(definition) {
|
|
245
|
+
for (let node = definition.name; node && node !== definition.node; node = node.parent) {
|
|
246
|
+
if (
|
|
247
|
+
node.type === 'ObjectPattern'
|
|
248
|
+
|| node.type === 'ArrayPattern'
|
|
249
|
+
) {
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const isDestructuredVariable = variable =>
|
|
258
|
+
variable.defs.length > 0
|
|
259
|
+
&& variable.defs.every(definition => isDestructuredDefinition(definition));
|
|
260
|
+
|
|
261
|
+
const hasWriteAfterInitialization = variable =>
|
|
262
|
+
variable.references.some(reference => !reference.init && reference.isWrite());
|
|
263
|
+
|
|
226
264
|
const isBooleanFunctionDefinition = (definition, context) =>
|
|
227
265
|
definition.type === 'FunctionName'
|
|
228
266
|
&& isFunction(definition.node)
|
|
@@ -264,6 +302,78 @@ function getFunctionDefinitions(variable) {
|
|
|
264
302
|
return overloadDefinitions.length > 0 ? overloadDefinitions : variable.defs;
|
|
265
303
|
}
|
|
266
304
|
|
|
305
|
+
function hasAsyncFunctionImplementation(definitions) {
|
|
306
|
+
return definitions.some(definition => isFunction(definition.node) && definition.node.async);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function hasGeneratorFunctionImplementation(definitions) {
|
|
310
|
+
return definitions.some(definition => isFunction(definition.node) && definition.node.generator);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function isReactHookFunctionBinding(variable, context) {
|
|
314
|
+
if (
|
|
315
|
+
variable.name === 'use'
|
|
316
|
+
|| !isReactHookName(variable.name)
|
|
317
|
+
) {
|
|
318
|
+
return false;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (getFunctionDefinitions(variable)) {
|
|
322
|
+
return true;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const definition = getSupportedVariableDefinition(variable);
|
|
326
|
+
|
|
327
|
+
if (definition?.type === 'FunctionName') {
|
|
328
|
+
return isFunction(definition.node);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return definition?.type === 'Variable'
|
|
332
|
+
&& (
|
|
333
|
+
isFunction(definition.node.init)
|
|
334
|
+
|| isFunctionTypeAnnotation(definition.name.typeAnnotation, context, context.sourceCode.getScope(definition.name))
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function getNameForPrefixCheck(variable, context) {
|
|
339
|
+
if (!isReactHookFunctionBinding(variable, context)) {
|
|
340
|
+
return variable.name;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const hookName = variable.name.slice(3);
|
|
344
|
+
|
|
345
|
+
return isScreamingCase(hookName) ? hookName : lowerFirst(hookName);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function isReactUseReferenceCall(node) {
|
|
349
|
+
if (node?.type !== 'CallExpression') {
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const {callee} = node;
|
|
354
|
+
if (callee.type === 'Identifier') {
|
|
355
|
+
return callee.name === 'useRef';
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return callee.type === 'MemberExpression'
|
|
359
|
+
&& !callee.computed
|
|
360
|
+
&& !callee.optional
|
|
361
|
+
&& callee.object.type === 'Identifier'
|
|
362
|
+
&& callee.object.name === 'React'
|
|
363
|
+
&& callee.property.type === 'Identifier'
|
|
364
|
+
&& callee.property.name === 'useRef';
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function isBooleanReactReferenceVariable(variable, context) {
|
|
368
|
+
const definition = getSupportedVariableDefinition(variable);
|
|
369
|
+
|
|
370
|
+
return definition?.type === 'Variable'
|
|
371
|
+
&& !hasWriteAfterInitialization(variable)
|
|
372
|
+
&& hasReactReferenceSuffix(variable.name)
|
|
373
|
+
&& isReactUseReferenceCall(definition.node.init)
|
|
374
|
+
&& getExpressionBooleanState(definition.node.init.arguments[0], context) === boolean;
|
|
375
|
+
}
|
|
376
|
+
|
|
267
377
|
function combineBooleanStates(states) {
|
|
268
378
|
if (
|
|
269
379
|
states.length === 0
|
|
@@ -376,6 +486,8 @@ function getTypeReferenceName(typeName) {
|
|
|
376
486
|
}
|
|
377
487
|
}
|
|
378
488
|
|
|
489
|
+
const getTypeArguments = node => node?.typeArguments?.params ?? node?.typeParameters?.params;
|
|
490
|
+
|
|
379
491
|
const getTypeState = typeState => ({
|
|
380
492
|
visitedTypeReferenceNames: new Set(),
|
|
381
493
|
functionTypesAreBoolean: true,
|
|
@@ -412,7 +524,7 @@ function getTypeReferenceBooleanState(node, context, scope, typeState) {
|
|
|
412
524
|
const definitionScope = context.sourceCode.getScope(definition.node);
|
|
413
525
|
|
|
414
526
|
if (definition.node.type === 'TSTypeAliasDeclaration') {
|
|
415
|
-
result =
|
|
527
|
+
result = getDirectTypeAnnotationBooleanState(definition.node.typeAnnotation, context, definitionScope, normalizedTypeState);
|
|
416
528
|
} else if (definition.node.type === 'TSInterfaceDeclaration') {
|
|
417
529
|
result = getTypeMembersBooleanState(definition.node.body.body, context, definitionScope, normalizedTypeState);
|
|
418
530
|
}
|
|
@@ -492,22 +604,119 @@ function getTypeAnnotationBooleanState(node, context, scope, typeState) {
|
|
|
492
604
|
return getSimpleTypeAnnotationBooleanState(node);
|
|
493
605
|
}
|
|
494
606
|
|
|
495
|
-
function
|
|
496
|
-
|
|
497
|
-
|
|
607
|
+
function getPromisedTypeAnnotationBooleanState(node, context, scope, typeState) {
|
|
608
|
+
const normalizedTypeState = getTypeState(typeState);
|
|
609
|
+
|
|
610
|
+
if (
|
|
611
|
+
node?.type === 'TSTypeAnnotation'
|
|
612
|
+
|| node?.type === 'TSParenthesizedType'
|
|
613
|
+
) {
|
|
614
|
+
return getPromisedTypeAnnotationBooleanState(node.typeAnnotation, context, scope, normalizedTypeState);
|
|
498
615
|
}
|
|
499
616
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
617
|
+
if (node?.type !== 'TSTypeReference') {
|
|
618
|
+
return unknown;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
const name = getTypeReferenceName(node.typeName);
|
|
622
|
+
const typeArguments = getTypeArguments(node);
|
|
623
|
+
if (
|
|
624
|
+
name === 'Promise'
|
|
625
|
+
&& typeArguments?.length === 1
|
|
626
|
+
&& isGlobalTypeReferenceName(name, scope)
|
|
627
|
+
) {
|
|
628
|
+
return getTypeAnnotationBooleanState(typeArguments[0], context, scope, normalizedTypeState);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
return unknown;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
function isGlobalTypeReferenceName(name, scope) {
|
|
635
|
+
return (resolveVariableName(name, scope)?.defs ?? []).every(definition => definition.type !== 'Type');
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
function isGlobalPromiseTypeReference(node, scope) {
|
|
639
|
+
if (node?.type !== 'TSTypeReference') {
|
|
640
|
+
return false;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
const typeName = getTypeReferenceName(node.typeName);
|
|
644
|
+
return promiseValueTypeNames.has(typeName)
|
|
645
|
+
&& getTypeArguments(node)?.length === 1
|
|
646
|
+
&& isGlobalTypeReferenceName(typeName, scope);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
function isGlobalPromiseTypeAnnotation(node, scope) {
|
|
650
|
+
if (
|
|
651
|
+
node?.type === 'TSTypeAnnotation'
|
|
652
|
+
|| node?.type === 'TSParenthesizedType'
|
|
653
|
+
) {
|
|
654
|
+
return isGlobalPromiseTypeAnnotation(node.typeAnnotation, scope);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
if (node?.type === 'TSUnionType') {
|
|
658
|
+
const types = node.types.filter(type => !nullishTypeAnnotationTypes.has(type.type));
|
|
659
|
+
return types.length > 0 && types.every(type => isGlobalPromiseTypeAnnotation(type, scope));
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
return isGlobalPromiseTypeReference(node, scope);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
function getPromisedTypeBooleanState(type, checker) {
|
|
666
|
+
const promisedType = checker.getPromisedTypeOfPromise(type);
|
|
667
|
+
return promisedType ? getTypeBooleanState(promisedType, checker, new Set(), false) : unknown;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
function getAsyncFunctionTypeInformationBooleanState(node, context) {
|
|
671
|
+
const {parserServices} = context.sourceCode;
|
|
672
|
+
if (!parserServices?.program) {
|
|
673
|
+
return unknown;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
try {
|
|
677
|
+
const checker = parserServices.program.getTypeChecker();
|
|
678
|
+
const typeScriptNode = parserServices.esTreeNodeToTSNodeMap.get(node);
|
|
679
|
+
const signature = checker.getSignatureFromDeclaration(typeScriptNode);
|
|
680
|
+
if (signature) {
|
|
681
|
+
return getPromisedTypeBooleanState(checker.getReturnTypeOfSignature(signature), checker);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
return combineBooleanStates(
|
|
685
|
+
parserServices.getTypeAtLocation(node)
|
|
686
|
+
.getCallSignatures()
|
|
687
|
+
.map(signature => getPromisedTypeBooleanState(checker.getReturnTypeOfSignature(signature), checker)),
|
|
688
|
+
);
|
|
689
|
+
} catch {
|
|
690
|
+
return unknown;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
function getFunctionBooleanState(node, context, visitedVariables = new Set(), isAsync = node.async) {
|
|
695
|
+
if (node.generator) {
|
|
696
|
+
return nonBoolean;
|
|
503
697
|
}
|
|
504
698
|
|
|
505
699
|
const scope = context.sourceCode.getScope(node);
|
|
506
|
-
|
|
700
|
+
// Only actual async function implementations and their overload signatures get `Promise<T>` unwrapped as a predicate return. Promise-valued variables and unrelated type-only callable signatures stay outside this boundary.
|
|
701
|
+
const stateFromPromisedReturnType = isAsync
|
|
702
|
+
? getPromisedTypeAnnotationBooleanState(node.returnType, context, scope, {functionTypesAreBoolean: false})
|
|
703
|
+
: unknown;
|
|
704
|
+
if (stateFromPromisedReturnType !== unknown) {
|
|
705
|
+
return stateFromPromisedReturnType;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
const stateFromReturnType = isAsync ? unknown : getTypeAnnotationBooleanState(node.returnType, context, scope, {functionTypesAreBoolean: false});
|
|
507
709
|
if (stateFromReturnType !== unknown) {
|
|
508
710
|
return stateFromReturnType;
|
|
509
711
|
}
|
|
510
712
|
|
|
713
|
+
const stateFromTypeInformation = isAsync
|
|
714
|
+
? getAsyncFunctionTypeInformationBooleanState(node, context)
|
|
715
|
+
: getTypeInformationBooleanState(node, context);
|
|
716
|
+
if (stateFromTypeInformation !== unknown) {
|
|
717
|
+
return stateFromTypeInformation;
|
|
718
|
+
}
|
|
719
|
+
|
|
511
720
|
if (!node.body) {
|
|
512
721
|
return unknown;
|
|
513
722
|
}
|
|
@@ -624,7 +833,7 @@ function getExpressionBooleanState(node, context, visitedVariables = new Set(),
|
|
|
624
833
|
}
|
|
625
834
|
|
|
626
835
|
const scope = context.sourceCode.getScope(node);
|
|
627
|
-
const stateFromTypeAnnotation =
|
|
836
|
+
const stateFromTypeAnnotation = getDirectTypeAnnotationBooleanState(node.typeAnnotation, context, scope, {functionTypesAreBoolean: functionValuesAreBoolean});
|
|
628
837
|
if (stateFromTypeAnnotation !== unknown) {
|
|
629
838
|
return stateFromTypeAnnotation;
|
|
630
839
|
}
|
|
@@ -703,9 +912,17 @@ function getParameterBooleanState(definition, context, visitedVariables, functio
|
|
|
703
912
|
: unknown;
|
|
704
913
|
}
|
|
705
914
|
|
|
915
|
+
function getDirectTypeAnnotationBooleanState(node, context, scope, typeState) {
|
|
916
|
+
if (isGlobalPromiseTypeAnnotation(node, scope)) {
|
|
917
|
+
return nonBoolean;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
return getTypeAnnotationBooleanState(node, context, scope, typeState);
|
|
921
|
+
}
|
|
922
|
+
|
|
706
923
|
function getDefinitionBooleanState(definition, context, visitedVariables, functionValuesAreBoolean) {
|
|
707
924
|
const scope = context.sourceCode.getScope(definition.name);
|
|
708
|
-
const stateFromTypeAnnotation =
|
|
925
|
+
const stateFromTypeAnnotation = getDirectTypeAnnotationBooleanState(definition.name.typeAnnotation, context, scope, {functionTypesAreBoolean: functionValuesAreBoolean});
|
|
709
926
|
if (stateFromTypeAnnotation !== unknown) {
|
|
710
927
|
return stateFromTypeAnnotation;
|
|
711
928
|
}
|
|
@@ -748,7 +965,14 @@ function getVariableBooleanState(variable, context, visitedVariables = new Set()
|
|
|
748
965
|
if (functionDefinitions && !functionValuesAreBoolean) {
|
|
749
966
|
result = nonBoolean;
|
|
750
967
|
} else if (functionDefinitions) {
|
|
751
|
-
|
|
968
|
+
if (hasGeneratorFunctionImplementation(variable.defs)) {
|
|
969
|
+
result = nonBoolean;
|
|
970
|
+
} else {
|
|
971
|
+
const hasAsyncImplementation = hasAsyncFunctionImplementation(variable.defs);
|
|
972
|
+
result = combineBooleanStates(functionDefinitions.map(definition =>
|
|
973
|
+
getFunctionBooleanState(definition.node, context, visitedVariables, hasAsyncImplementation || definition.node.async),
|
|
974
|
+
));
|
|
975
|
+
}
|
|
752
976
|
} else {
|
|
753
977
|
result = getDefinitionBooleanState(definition, context, visitedVariables, functionValuesAreBoolean);
|
|
754
978
|
}
|
|
@@ -865,15 +1089,16 @@ function getExplicitPropertyBooleanState(node, context) {
|
|
|
865
1089
|
|
|
866
1090
|
if (propertyDefinitionTypes.has(node.type)) {
|
|
867
1091
|
const scope = sourceCode.getScope(node);
|
|
868
|
-
const stateFromTypeAnnotation =
|
|
1092
|
+
const stateFromTypeAnnotation = getDirectTypeAnnotationBooleanState(node.typeAnnotation, context, scope);
|
|
1093
|
+
if (stateFromTypeAnnotation !== unknown) {
|
|
1094
|
+
return stateFromTypeAnnotation;
|
|
1095
|
+
}
|
|
869
1096
|
|
|
870
|
-
return
|
|
871
|
-
? getExpressionBooleanState(node.value, context)
|
|
872
|
-
: stateFromTypeAnnotation;
|
|
1097
|
+
return getExpressionBooleanState(node.value, context);
|
|
873
1098
|
}
|
|
874
1099
|
|
|
875
1100
|
if (node.type === 'TSPropertySignature') {
|
|
876
|
-
return
|
|
1101
|
+
return getDirectTypeAnnotationBooleanState(node.typeAnnotation, context, sourceCode.getScope(node));
|
|
877
1102
|
}
|
|
878
1103
|
|
|
879
1104
|
if (node.type === 'TSMethodSignature') {
|
|
@@ -888,7 +1113,7 @@ function getPropertyBooleanState(node, context) {
|
|
|
888
1113
|
return state === unknown && isBooleanProperty(node, context) ? boolean : state;
|
|
889
1114
|
}
|
|
890
1115
|
|
|
891
|
-
function getSuggestions(variable, prefixes, context) {
|
|
1116
|
+
function getSuggestions(variable, prefixes, context, nameForPrefixCheck) {
|
|
892
1117
|
if (
|
|
893
1118
|
!shouldSuggestRename(variable)
|
|
894
1119
|
|| variable.references.some(reference => reference.vueUsedInTemplate)
|
|
@@ -904,7 +1129,7 @@ function getSuggestions(variable, prefixes, context) {
|
|
|
904
1129
|
const suggestions = [];
|
|
905
1130
|
|
|
906
1131
|
for (const prefix of prefixes) {
|
|
907
|
-
const replacement = getAvailableVariableName(
|
|
1132
|
+
const replacement = getAvailableVariableName(getReactHookReplacementName({name: variable.name, nameForPrefixCheck}, prefix), scopes);
|
|
908
1133
|
|
|
909
1134
|
if (!replacement || usedReplacements.has(replacement)) {
|
|
910
1135
|
continue;
|
|
@@ -954,7 +1179,13 @@ function isInDeclareContext(node) {
|
|
|
954
1179
|
return false;
|
|
955
1180
|
}
|
|
956
1181
|
|
|
957
|
-
function getAutofix(
|
|
1182
|
+
function getAutofix({
|
|
1183
|
+
variable,
|
|
1184
|
+
prefixes,
|
|
1185
|
+
context,
|
|
1186
|
+
suggestions,
|
|
1187
|
+
nameForPrefixCheck,
|
|
1188
|
+
}) {
|
|
958
1189
|
if (
|
|
959
1190
|
!suggestions
|
|
960
1191
|
|| !isAutofixableVariable(variable, context)
|
|
@@ -963,7 +1194,7 @@ function getAutofix(variable, prefixes, context, suggestions) {
|
|
|
963
1194
|
}
|
|
964
1195
|
|
|
965
1196
|
const [prefix] = prefixes;
|
|
966
|
-
const replacement =
|
|
1197
|
+
const replacement = getReactHookReplacementName({name: variable.name, nameForPrefixCheck}, prefix);
|
|
967
1198
|
const suggestion = suggestions.find(suggestion => suggestion.data.replacement === replacement);
|
|
968
1199
|
|
|
969
1200
|
return suggestion?.fix;
|
|
@@ -978,20 +1209,28 @@ const create = context => {
|
|
|
978
1209
|
}
|
|
979
1210
|
|
|
980
1211
|
const checkVariable = variable => {
|
|
981
|
-
if (
|
|
1212
|
+
if (
|
|
1213
|
+
isIgnoredName(variable.name, ignore)
|
|
1214
|
+
|| isDestructuredVariable(variable)
|
|
1215
|
+
) {
|
|
982
1216
|
return;
|
|
983
1217
|
}
|
|
984
1218
|
|
|
985
|
-
const
|
|
1219
|
+
const nameForPrefixCheck = getNameForPrefixCheck(variable, context);
|
|
1220
|
+
const booleanPrefix = getBooleanPrefix(nameForPrefixCheck, prefixes);
|
|
1221
|
+
const booleanState = getVariableBooleanState(variable, context);
|
|
986
1222
|
if (booleanPrefix) {
|
|
987
|
-
if (
|
|
1223
|
+
if (
|
|
1224
|
+
booleanState === nonBoolean
|
|
1225
|
+
&& !isBooleanReactReferenceVariable(variable, context)
|
|
1226
|
+
) {
|
|
988
1227
|
const [definition] = variable.defs;
|
|
989
1228
|
|
|
990
1229
|
context.report({
|
|
991
1230
|
node: definition.name,
|
|
992
1231
|
messageId: MESSAGE_ID_NON_BOOLEAN_PREFIX,
|
|
993
1232
|
data: {
|
|
994
|
-
name:
|
|
1233
|
+
name: nameForPrefixCheck,
|
|
995
1234
|
prefix: booleanPrefix,
|
|
996
1235
|
},
|
|
997
1236
|
});
|
|
@@ -1001,23 +1240,29 @@ const create = context => {
|
|
|
1001
1240
|
}
|
|
1002
1241
|
|
|
1003
1242
|
if (
|
|
1004
|
-
|
|
1243
|
+
booleanState === nonBoolean
|
|
1005
1244
|
|| !isBooleanVariable(variable, context)
|
|
1006
1245
|
) {
|
|
1007
1246
|
return;
|
|
1008
1247
|
}
|
|
1009
1248
|
|
|
1010
1249
|
const [definition] = variable.defs;
|
|
1011
|
-
const suggest = getSuggestions(variable, prefixes, context);
|
|
1250
|
+
const suggest = getSuggestions(variable, prefixes, context, nameForPrefixCheck);
|
|
1012
1251
|
|
|
1013
1252
|
context.report({
|
|
1014
1253
|
node: definition.name,
|
|
1015
1254
|
messageId: MESSAGE_ID,
|
|
1016
1255
|
data: {
|
|
1017
|
-
name:
|
|
1256
|
+
name: nameForPrefixCheck,
|
|
1018
1257
|
prefixes: formatPrefixes(prefixes),
|
|
1019
1258
|
},
|
|
1020
|
-
fix: getAutofix(
|
|
1259
|
+
fix: getAutofix({
|
|
1260
|
+
variable,
|
|
1261
|
+
prefixes,
|
|
1262
|
+
context,
|
|
1263
|
+
suggestions: suggest,
|
|
1264
|
+
nameForPrefixCheck,
|
|
1265
|
+
}),
|
|
1021
1266
|
suggest,
|
|
1022
1267
|
});
|
|
1023
1268
|
};
|
|
@@ -1045,8 +1290,9 @@ const create = context => {
|
|
|
1045
1290
|
}
|
|
1046
1291
|
|
|
1047
1292
|
const booleanPrefix = getBooleanPrefix(name, prefixes);
|
|
1293
|
+
const booleanState = getPropertyBooleanState(node, context);
|
|
1048
1294
|
if (booleanPrefix) {
|
|
1049
|
-
if (
|
|
1295
|
+
if (booleanState === nonBoolean) {
|
|
1050
1296
|
context.report({
|
|
1051
1297
|
node: node.key,
|
|
1052
1298
|
messageId: MESSAGE_ID_NON_BOOLEAN_PREFIX,
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import helperValidatorIdentifier from '@babel/helper-validator-identifier';
|
|
2
1
|
import {
|
|
3
2
|
getAvailableVariableName,
|
|
4
3
|
getScopes,
|
|
5
4
|
isShorthandPropertyValue,
|
|
6
5
|
lowerFirst,
|
|
7
6
|
upperFirst,
|
|
7
|
+
isIdentifierName,
|
|
8
8
|
} from './utils/index.js';
|
|
9
9
|
import {
|
|
10
10
|
isClassVariable,
|
|
@@ -17,7 +17,6 @@ import {renameVariable} from './fix/index.js';
|
|
|
17
17
|
|
|
18
18
|
const MESSAGE_ID_ERROR = 'consistent-compound-words/error';
|
|
19
19
|
const MESSAGE_ID_RENAME = 'consistent-compound-words/rename';
|
|
20
|
-
const {isIdentifierName} = helperValidatorIdentifier;
|
|
21
20
|
|
|
22
21
|
const messages = {
|
|
23
22
|
[MESSAGE_ID_ERROR]: 'Prefer `{{replacement}}` over `{{name}}`.',
|