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.
Files changed (91) hide show
  1. package/package.json +26 -25
  2. package/readme.md +5 -1
  3. package/rules/ast/call-or-new-expression.js +2 -8
  4. package/rules/ast/is-member-expression.js +2 -8
  5. package/rules/comment-content.js +4 -4
  6. package/rules/consistent-boolean-name.js +275 -29
  7. package/rules/consistent-compound-words.js +1 -2
  8. package/rules/consistent-json-file-read.js +2 -5
  9. package/rules/dom-node-dataset.js +3 -7
  10. package/rules/escape-case.js +4 -4
  11. package/rules/expiring-todo-comments.js +2 -2
  12. package/rules/explicit-length-check.js +3 -3
  13. package/rules/filename-case.js +4 -4
  14. package/rules/fix/remove-argument.js +34 -8
  15. package/rules/fix/remove-statement.js +1 -3
  16. package/rules/id-match.js +2 -2
  17. package/rules/isolated-functions.js +47 -7
  18. package/rules/name-replacements.js +1 -2
  19. package/rules/no-anonymous-default-export.js +1 -3
  20. package/rules/no-array-callback-reference.js +2 -10
  21. package/rules/no-chained-comparison.js +1 -1
  22. package/rules/no-computed-property-existence-check.js +1 -4
  23. package/rules/no-declarations-before-early-exit.js +6 -5
  24. package/rules/no-error-property-assignment.js +3 -3
  25. package/rules/no-for-loop.js +4 -4
  26. package/rules/no-keyword-prefix.js +2 -2
  27. package/rules/no-mismatched-map-key.js +13 -20
  28. package/rules/no-named-default.js +2 -5
  29. package/rules/no-negated-array-predicate.js +4 -11
  30. package/rules/no-negated-comparison.js +7 -10
  31. package/rules/no-negation-in-equality-check.js +2 -5
  32. package/rules/no-process-exit.js +6 -6
  33. package/rules/no-static-only-class.js +2 -8
  34. package/rules/no-thenable.js +9 -15
  35. package/rules/no-undeclared-class-members.js +1 -5
  36. package/rules/no-unnecessary-array-flat-map.js +6 -5
  37. package/rules/no-unnecessary-global-this.js +5 -9
  38. package/rules/no-unused-properties.js +0 -1
  39. package/rules/no-useless-coercion.js +1 -2
  40. package/rules/no-useless-undefined.js +2 -2
  41. package/rules/prefer-array-find.js +2 -5
  42. package/rules/prefer-bigint-literals.js +1 -0
  43. package/rules/prefer-boolean-return.js +20 -3
  44. package/rules/prefer-default-parameters.js +2 -2
  45. package/rules/prefer-else-if.js +5 -9
  46. package/rules/prefer-has-check.js +6 -3
  47. package/rules/prefer-https.js +3 -3
  48. package/rules/prefer-identifier-import-export-specifiers.js +1 -3
  49. package/rules/prefer-minimal-ternary.js +4 -4
  50. package/rules/prefer-object-define-properties.js +1 -3
  51. package/rules/prefer-observer-apis.js +2 -5
  52. package/rules/prefer-private-class-fields.js +1 -3
  53. package/rules/prefer-simple-sort-comparator.js +6 -6
  54. package/rules/prefer-single-object-destructuring.js +2 -5
  55. package/rules/prefer-single-replace.js +2 -2
  56. package/rules/prefer-spread.js +22 -32
  57. package/rules/prefer-string-pad-start-end.js +1 -6
  58. package/rules/prefer-string-repeat.js +16 -9
  59. package/rules/prefer-string-replace-all.js +1 -1
  60. package/rules/prefer-ternary.js +5 -5
  61. package/rules/prefer-toggle-attribute.js +3 -3
  62. package/rules/prefer-top-level-await.js +6 -1
  63. package/rules/prefer-uint8array-base64.js +3 -1
  64. package/rules/require-proxy-trap-boolean-return.js +2 -2
  65. package/rules/rule/to-eslint-create.js +1 -2
  66. package/rules/rule/to-eslint-listener.js +1 -3
  67. package/rules/rule/to-eslint-problem.js +1 -3
  68. package/rules/rule/to-eslint-rule-fixer.js +1 -3
  69. package/rules/rule/to-eslint-rule.js +1 -3
  70. package/rules/rule/to-eslint-rules.js +1 -3
  71. package/rules/rule/unicorn-context.js +1 -3
  72. package/rules/rule/unicorn-listeners.js +1 -3
  73. package/rules/shared/no-array-mutate-rule.js +1 -3
  74. package/rules/shared/regexp-escape.js +4 -4
  75. package/rules/utils/can-tokens-be-adjacent.js +95 -0
  76. package/rules/utils/escape-string.js +2 -8
  77. package/rules/utils/get-available-variable-name.js +2 -13
  78. package/rules/utils/get-precedence.js +112 -0
  79. package/rules/utils/get-token-store.js +1 -3
  80. package/rules/utils/global-reference-tracker.js +3 -6
  81. package/rules/utils/index.js +4 -0
  82. package/rules/utils/is-boolean.js +90 -32
  83. package/rules/utils/is-identifier-name.js +5 -0
  84. package/rules/utils/is-promise-type.js +1 -3
  85. package/rules/utils/is-react-hook-name.js +5 -0
  86. package/rules/utils/parentheses/get-parent-syntax-opening-parenthesis.js +1 -3
  87. package/rules/utils/parentheses/iterate-surrounding-parentheses.js +1 -1
  88. package/rules/utils/should-add-parentheses-to-await-expression-argument.js +4 -9
  89. package/rules/utils/should-add-parentheses-to-call-expression-callee.js +6 -12
  90. package/rules/utils/should-add-parentheses-to-unary-expression.js +7 -14
  91. 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": "70.0.0",
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.2",
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.6.0",
72
+ "globals": "^17.7.0",
74
73
  "indent-string": "^5.0.0",
75
74
  "is-builtin-module": "^5.0.0",
76
- "jsesc": "^3.1.0",
75
+ "is-identifier": "^1.1.0",
77
76
  "pluralize": "^8.0.0",
78
- "regjsparser": "^0.13.1",
79
- "semver": "^7.8.4",
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-rc.6",
84
- "@eslint/css": "^1.3.0",
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.0",
87
- "@eslint/markdown": "^8.0.2",
88
- "@html-eslint/eslint-plugin": "^0.62.0",
89
- "@html-eslint/parser": "^0.62.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.61.0",
92
- "@typescript-eslint/types": "^8.61.0",
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.4.1",
97
+ "eslint": "^10.6.0",
97
98
  "eslint-ava-rule-tester": "^5.0.1",
98
- "eslint-config-xo": "^0.52.0",
99
+ "eslint-config-xo": "^0.56.0",
99
100
  "eslint-doc-generator": "^3.6.0",
100
- "eslint-plugin-eslint-plugin": "^7.4.0",
101
- "eslint-plugin-jsdoc": "^63.0.2",
102
- "eslint-plugin-unicorn": "^65.0.1",
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.1",
108
- "markdownlint-cli": "^0.48.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.0",
112
- "npm-run-all2": "^9.0.1",
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.3",
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
- if (
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
- if (
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
  }
@@ -346,7 +346,7 @@ const pathTerminatorCharacters = '"\'`<>';
346
346
  const packageSpecifierTerminatorCharacters = '"\'`()[]{}<>,';
347
347
  const domainLeadingPunctuation = '([{<';
348
348
  const domainTrailingPunctuation = '.,;:!?)]}>';
349
- const maskCharacter = '\uFFFF';
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 checked = false;
1065
+ let isChecked = false;
1066
1066
 
1067
1067
  onRoot(context, function * (node) {
1068
- if (checked) {
1068
+ if (isChecked) {
1069
1069
  return;
1070
1070
  }
1071
1071
 
1072
- checked = true;
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 = getTypeAnnotationBooleanState(definition.node.typeAnnotation, context, definitionScope, normalizedTypeState);
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 getFunctionBooleanState(node, context, visitedVariables = new Set()) {
496
- if (node.async || node.generator) {
497
- return nonBoolean;
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
- const stateFromTypeInformation = getTypeInformationBooleanState(node, context);
501
- if (stateFromTypeInformation !== unknown) {
502
- return stateFromTypeInformation;
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
- const stateFromReturnType = getTypeAnnotationBooleanState(node.returnType, context, scope, {functionTypesAreBoolean: false});
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 = getTypeAnnotationBooleanState(node.typeAnnotation, context, scope, {functionTypesAreBoolean: functionValuesAreBoolean});
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 = getTypeAnnotationBooleanState(definition.name.typeAnnotation, context, scope, {functionTypesAreBoolean: functionValuesAreBoolean});
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
- result = combineBooleanStates(functionDefinitions.map(definition => getFunctionBooleanState(definition.node, context, visitedVariables)));
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 = getTypeAnnotationBooleanState(node.typeAnnotation, context, scope);
1092
+ const stateFromTypeAnnotation = getDirectTypeAnnotationBooleanState(node.typeAnnotation, context, scope);
1093
+ if (stateFromTypeAnnotation !== unknown) {
1094
+ return stateFromTypeAnnotation;
1095
+ }
869
1096
 
870
- return stateFromTypeAnnotation === unknown
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 getTypeAnnotationBooleanState(node.typeAnnotation, context, sourceCode.getScope(node));
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(getReplacementName(variable.name, prefix), scopes);
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(variable, prefixes, context, suggestions) {
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 = getReplacementName(variable.name, prefix);
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 (isIgnoredName(variable.name, ignore)) {
1212
+ if (
1213
+ isIgnoredName(variable.name, ignore)
1214
+ || isDestructuredVariable(variable)
1215
+ ) {
982
1216
  return;
983
1217
  }
984
1218
 
985
- const booleanPrefix = getBooleanPrefix(variable.name, prefixes);
1219
+ const nameForPrefixCheck = getNameForPrefixCheck(variable, context);
1220
+ const booleanPrefix = getBooleanPrefix(nameForPrefixCheck, prefixes);
1221
+ const booleanState = getVariableBooleanState(variable, context);
986
1222
  if (booleanPrefix) {
987
- if (getVariableBooleanState(variable, context) === nonBoolean) {
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: variable.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
- getVariableBooleanState(variable, context) === nonBoolean
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: variable.name,
1256
+ name: nameForPrefixCheck,
1018
1257
  prefixes: formatPrefixes(prefixes),
1019
1258
  },
1020
- fix: getAutofix(variable, prefixes, context, suggest),
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 (getPropertyBooleanState(node, context) === nonBoolean) {
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}}`.',