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.
Files changed (124) hide show
  1. package/package.json +26 -25
  2. package/readme.md +15 -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 +727 -42
  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 +25 -2
  12. package/rules/explicit-length-check.js +3 -3
  13. package/rules/filename-case.js +4 -4
  14. package/rules/fix/{add-parenthesizes-to-return-or-throw-expression.js → add-parentheses-to-return-or-throw-expression.js} +1 -1
  15. package/rules/fix/index.js +1 -1
  16. package/rules/fix/remove-argument.js +34 -8
  17. package/rules/fix/remove-statement.js +1 -3
  18. package/rules/fix/switch-new-expression-to-call-expression.js +2 -2
  19. package/rules/id-match.js +2 -2
  20. package/rules/index.js +10 -0
  21. package/rules/isolated-functions.js +203 -19
  22. package/rules/name-replacements.js +1 -2
  23. package/rules/no-anonymous-default-export.js +1 -3
  24. package/rules/no-array-callback-reference.js +2 -10
  25. package/rules/no-async-promise-finally.js +111 -0
  26. package/rules/no-chained-comparison.js +1 -1
  27. package/rules/no-collection-bracket-access.js +27 -4
  28. package/rules/no-computed-property-existence-check.js +1 -4
  29. package/rules/no-declarations-before-early-exit.js +6 -5
  30. package/rules/no-error-property-assignment.js +3 -3
  31. package/rules/no-for-loop.js +4 -4
  32. package/rules/no-keyword-prefix.js +2 -2
  33. package/rules/no-manually-wrapped-comments.js +5 -1
  34. package/rules/no-mismatched-map-key.js +13 -20
  35. package/rules/no-named-default.js +2 -5
  36. package/rules/no-negated-array-predicate.js +6 -13
  37. package/rules/no-negated-comparison.js +31 -71
  38. package/rules/no-negated-condition.js +2 -2
  39. package/rules/no-negation-in-equality-check.js +4 -7
  40. package/rules/no-non-function-verb-prefix.js +25 -2
  41. package/rules/no-process-exit.js +6 -6
  42. package/rules/no-return-array-push.js +9 -2
  43. package/rules/no-static-only-class.js +2 -8
  44. package/rules/no-thenable.js +9 -15
  45. package/rules/no-typeof-undefined.js +2 -2
  46. package/rules/no-undeclared-class-members.js +1 -5
  47. package/rules/no-unnecessary-array-flat-map.js +308 -0
  48. package/rules/no-unnecessary-await.js +2 -2
  49. package/rules/no-unnecessary-fetch-options.js +624 -0
  50. package/rules/no-unnecessary-global-this.js +5 -9
  51. package/rules/no-unsafe-promise-all-settled-values.js +865 -0
  52. package/rules/no-unused-properties.js +0 -1
  53. package/rules/no-useless-coercion.js +1 -2
  54. package/rules/no-useless-else.js +32 -1
  55. package/rules/no-useless-iterator-to-array.js +42 -16
  56. package/rules/no-useless-spread.js +2 -2
  57. package/rules/no-useless-undefined.js +2 -2
  58. package/rules/prefer-abort-signal-any.js +1391 -0
  59. package/rules/prefer-array-find.js +2 -5
  60. package/rules/prefer-array-flat-map.js +6 -11
  61. package/rules/prefer-array-from-range.js +98 -0
  62. package/rules/prefer-bigint-literals.js +1 -0
  63. package/rules/prefer-block-statement-over-iife.js +159 -0
  64. package/rules/prefer-boolean-return.js +20 -3
  65. package/rules/prefer-continue.js +6 -2
  66. package/rules/prefer-default-parameters.js +2 -2
  67. package/rules/prefer-early-return.js +3 -1
  68. package/rules/prefer-else-if.js +5 -9
  69. package/rules/prefer-group-by.js +576 -0
  70. package/rules/prefer-has-check.js +6 -3
  71. package/rules/prefer-https.js +3 -3
  72. package/rules/prefer-identifier-import-export-specifiers.js +1 -3
  73. package/rules/prefer-iterator-helpers.js +196 -0
  74. package/rules/prefer-minimal-ternary.js +4 -4
  75. package/rules/prefer-number-coercion.js +1 -1
  76. package/rules/prefer-object-define-properties.js +1 -3
  77. package/rules/prefer-object-iterable-methods.js +57 -14
  78. package/rules/prefer-observer-apis.js +2 -5
  79. package/rules/prefer-private-class-fields.js +1 -3
  80. package/rules/prefer-simple-sort-comparator.js +6 -6
  81. package/rules/prefer-simplified-conditions.js +584 -0
  82. package/rules/prefer-single-object-destructuring.js +2 -5
  83. package/rules/prefer-single-replace.js +2 -2
  84. package/rules/prefer-spread.js +25 -32
  85. package/rules/prefer-string-pad-start-end.js +1 -6
  86. package/rules/prefer-string-raw.js +2 -2
  87. package/rules/prefer-string-repeat.js +16 -9
  88. package/rules/prefer-string-replace-all.js +1 -1
  89. package/rules/prefer-ternary.js +5 -5
  90. package/rules/prefer-toggle-attribute.js +3 -3
  91. package/rules/prefer-top-level-await.js +6 -1
  92. package/rules/prefer-uint8array-base64.js +3 -1
  93. package/rules/require-proxy-trap-boolean-return.js +2 -2
  94. package/rules/rule/to-eslint-create.js +1 -2
  95. package/rules/rule/to-eslint-listener.js +1 -3
  96. package/rules/rule/to-eslint-problem.js +1 -3
  97. package/rules/rule/to-eslint-rule-fixer.js +1 -3
  98. package/rules/rule/to-eslint-rule.js +1 -3
  99. package/rules/rule/to-eslint-rules.js +1 -3
  100. package/rules/rule/unicorn-context.js +1 -3
  101. package/rules/rule/unicorn-listeners.js +1 -3
  102. package/rules/shared/array-range.js +66 -0
  103. package/rules/shared/iterator-helpers.js +43 -1
  104. package/rules/shared/no-array-mutate-rule.js +1 -3
  105. package/rules/shared/regexp-escape.js +4 -4
  106. package/rules/utils/can-tokens-be-adjacent.js +95 -0
  107. package/rules/utils/comparison.js +65 -0
  108. package/rules/utils/escape-string.js +2 -8
  109. package/rules/utils/get-available-variable-name.js +2 -13
  110. package/rules/utils/get-precedence.js +112 -0
  111. package/rules/utils/get-token-store.js +1 -3
  112. package/rules/utils/global-reference-tracker.js +3 -6
  113. package/rules/utils/index.js +4 -0
  114. package/rules/utils/is-boolean.js +103 -30
  115. package/rules/utils/is-identifier-name.js +5 -0
  116. package/rules/utils/is-promise-type.js +1 -3
  117. package/rules/utils/is-react-hook-name.js +5 -0
  118. package/rules/utils/parentheses/get-parent-syntax-opening-parenthesis.js +1 -3
  119. package/rules/utils/parentheses/iterate-surrounding-parentheses.js +1 -1
  120. package/rules/utils/should-add-parentheses-to-await-expression-argument.js +4 -9
  121. package/rules/utils/should-add-parentheses-to-call-expression-callee.js +6 -12
  122. package/rules/utils/should-add-parentheses-to-unary-expression.js +7 -14
  123. package/rules/utils/to-location.js +1 -3
  124. package/rules/utils/track-branch-exits.js +8 -7
@@ -1,19 +1,95 @@
1
1
  import globals from 'globals';
2
- import {functionTypes} from './ast/index.js';
2
+ import {functionTypes, isMemberExpression, isMethodCall} from './ast/index.js';
3
3
 
4
4
  const MESSAGE_ID_EXTERNALLY_SCOPED_VARIABLE = 'externally-scoped-variable';
5
+ const MESSAGE_ID_SUPER = 'super';
6
+ const MESSAGE_ID_THIS_EXPRESSION = 'this-expression';
7
+ const functionContextReferenceMessageIds = new Map([
8
+ ['Super', MESSAGE_ID_SUPER],
9
+ ['ThisExpression', MESSAGE_ID_THIS_EXPRESSION],
10
+ ]);
11
+
5
12
  const messages = {
6
13
  [MESSAGE_ID_EXTERNALLY_SCOPED_VARIABLE]: 'Variable {{name}} not defined in scope of isolated function. Function is isolated because: {{reason}}.',
14
+ [MESSAGE_ID_SUPER]: 'Unexpected `super` in isolated function. Function is isolated because: {{reason}}.',
15
+ [MESSAGE_ID_THIS_EXPRESSION]: 'Unexpected `this` in isolated function. Function is isolated because: {{reason}}.',
7
16
  };
8
17
 
9
18
  /** @type {{functions: string[], selectors: string[], comments: string[], overrideGlobals?: import('eslint').Linter.Globals}} */
10
19
  const defaultOptions = {
11
- functions: ['makeSynchronous'],
20
+ functions: ['makeSynchronous', 'workerize'],
12
21
  selectors: [],
13
22
  comments: ['@isolated'],
14
23
  overrideGlobals: {},
15
24
  };
16
25
 
26
+ const scriptingObjects = ['browser', 'chrome'];
27
+
28
+ const getObjectPropertyName = node => {
29
+ if (node.computed && node.key.type !== 'Literal') {
30
+ return;
31
+ }
32
+
33
+ if (node.key.type === 'Identifier') {
34
+ return node.key.name;
35
+ }
36
+
37
+ if (node.key.type === 'Literal' && typeof node.key.value === 'string') {
38
+ return node.key.value;
39
+ }
40
+ };
41
+
42
+ const getDefaultArgumentCallReason = node => {
43
+ if (
44
+ node.parent.type !== 'CallExpression'
45
+ || node.parent.arguments[0] !== node
46
+ ) {
47
+ return;
48
+ }
49
+
50
+ if (isMethodCall(node.parent, {object: 'browser', method: 'execute'})) {
51
+ return 'callee of method named "browser.execute"';
52
+ }
53
+
54
+ if (isMethodCall(node.parent, {object: 'page', method: 'evaluate'})) {
55
+ return 'callee of method named "page.evaluate"';
56
+ }
57
+ };
58
+
59
+ const getScriptingExecuteScriptObjectName = node => {
60
+ if (
61
+ !isMethodCall(node, {method: 'executeScript'})
62
+ || !isMemberExpression(node.callee.object, {
63
+ objects: scriptingObjects,
64
+ property: 'scripting',
65
+ })
66
+ ) {
67
+ return;
68
+ }
69
+
70
+ return node.callee.object.object.name;
71
+ };
72
+
73
+ const getExecuteScriptPropertyReason = node => {
74
+ if (
75
+ node.parent.type !== 'Property'
76
+ || node.parent.kind !== 'init'
77
+ || getObjectPropertyName(node.parent) !== 'func'
78
+ || node.parent.parent.type !== 'ObjectExpression'
79
+ || node.parent.parent.parent.type !== 'CallExpression'
80
+ || node.parent.parent.parent.arguments[0] !== node.parent.parent
81
+ ) {
82
+ return;
83
+ }
84
+
85
+ const scriptingObjectName = getScriptingExecuteScriptObjectName(node.parent.parent.parent);
86
+ if (!scriptingObjectName) {
87
+ return;
88
+ }
89
+
90
+ return `property "func" passed to "${scriptingObjectName}.scripting.executeScript"`;
91
+ };
92
+
17
93
  /** @param {import('eslint').Rule.RuleContext} context */
18
94
  const create = context => {
19
95
  const {sourceCode} = context;
@@ -22,15 +98,103 @@ const create = context => {
22
98
 
23
99
  options.comments = options.comments.map(comment => comment.toLowerCase());
24
100
 
25
- const allowedGlobals = {
101
+ const configuredGlobals = {
26
102
  ...(globals[`es${context.languageOptions.ecmaVersion}`] ?? globals.builtins),
27
103
  ...context.languageOptions.globals,
28
- ...options.overrideGlobals,
29
104
  };
30
105
  const checked = new WeakSet();
31
106
 
107
+ const getAllowedGlobalValue = reference => {
108
+ const {identifier, resolved} = reference;
109
+ const {name} = identifier;
110
+
111
+ if (Object.hasOwn(options.overrideGlobals, name)) {
112
+ const overrideGlobalValue = options.overrideGlobals[name];
113
+
114
+ if (overrideGlobalValue === 'off') {
115
+ return 'off';
116
+ }
117
+
118
+ if (resolved && !sourceCode.isGlobalReference(identifier)) {
119
+ return;
120
+ }
121
+
122
+ return overrideGlobalValue;
123
+ }
124
+
125
+ if (
126
+ Object.hasOwn(configuredGlobals, name)
127
+ && configuredGlobals[name] === 'off'
128
+ ) {
129
+ return 'off';
130
+ }
131
+
132
+ if (!sourceCode.isGlobalReference(identifier)) {
133
+ return;
134
+ }
135
+
136
+ if (
137
+ !Object.hasOwn(configuredGlobals, name)
138
+ && !resolved?.eslintExplicitGlobal
139
+ ) {
140
+ return;
141
+ }
142
+
143
+ return resolved?.writeable ? 'writable' : 'readonly';
144
+ };
145
+
146
+ function * getFunctionContextProblems(node, reason, root = node) {
147
+ const messageId = functionContextReferenceMessageIds.get(node.type);
148
+ if (messageId) {
149
+ yield {
150
+ node,
151
+ messageId,
152
+ data: {reason},
153
+ };
154
+ return;
155
+ }
156
+
157
+ if (node !== root) {
158
+ if (functionTypes.includes(node.type) && node.type !== 'ArrowFunctionExpression') {
159
+ return;
160
+ }
161
+
162
+ if (node.type === 'ClassDeclaration' || node.type === 'ClassExpression') {
163
+ if (node.superClass) {
164
+ yield * getFunctionContextProblems(node.superClass, reason, root);
165
+ }
166
+
167
+ for (const classElement of node.body.body) {
168
+ if (classElement.computed) {
169
+ yield * getFunctionContextProblems(classElement.key, reason, root);
170
+ }
171
+ }
172
+
173
+ return;
174
+ }
175
+ }
176
+
177
+ for (const key of sourceCode.visitorKeys[node.type] ?? []) {
178
+ const value = node[key];
179
+
180
+ if (Array.isArray(value)) {
181
+ for (const childNode of value) {
182
+ if (childNode?.type) {
183
+ yield * getFunctionContextProblems(childNode, reason, root);
184
+ }
185
+ }
186
+
187
+ continue;
188
+ }
189
+
190
+ if (value?.type) {
191
+ yield * getFunctionContextProblems(value, reason, root);
192
+ }
193
+ }
194
+ }
195
+
32
196
  /** @param {import('estree').Node} node */
33
- const reportExternallyScopedVariables = (node, reason) => {
197
+ const reportIsolatedFunctionProblems = (node, reason) => {
34
198
  if (checked.has(node) || !functionTypes.includes(node.type)) {
35
199
  return;
36
200
  }
@@ -47,18 +211,20 @@ const create = context => {
47
211
  continue;
48
212
  }
49
213
 
50
- if (Object.hasOwn(allowedGlobals, identifier.name) && allowedGlobals[identifier.name] !== 'off') {
214
+ const allowedGlobalValue = getAllowedGlobalValue(reference);
215
+ let problemReason = reason;
216
+
217
+ if (allowedGlobalValue !== undefined && allowedGlobalValue !== 'off') {
51
218
  if (reference.isReadOnly()) {
52
219
  continue;
53
220
  }
54
221
 
55
- const globalsValue = allowedGlobals[identifier.name];
56
- const isGlobalWritable = [true, 'writable', 'writeable'].includes(globalsValue);
222
+ const isGlobalWritable = [true, 'writable', 'writeable'].includes(allowedGlobalValue);
57
223
  if (isGlobalWritable) {
58
224
  continue;
59
225
  }
60
226
 
61
- reason += ' (global variable is not writable)';
227
+ problemReason += ' (global variable is not writable)';
62
228
  }
63
229
 
64
230
  // Could consider checking for typeof operator here, like in no-undef?
@@ -66,15 +232,36 @@ const create = context => {
66
232
  context.report({
67
233
  node: identifier,
68
234
  messageId: MESSAGE_ID_EXTERNALLY_SCOPED_VARIABLE,
69
- data: {name: identifier.name, reason},
235
+ data: {name: identifier.name, reason: problemReason},
70
236
  });
71
237
  }
238
+
239
+ for (const problem of getFunctionContextProblems(node, reason)) {
240
+ context.report(problem);
241
+ }
72
242
  };
73
243
 
74
244
  const isComment = token => token?.type === 'Block' || token?.type === 'Line';
75
245
 
246
+ const canCommentApplyToParent = (node, parent) =>
247
+ parent && (
248
+ [
249
+ 'VariableDeclarator',
250
+ 'VariableDeclaration',
251
+ 'ExportNamedDeclaration',
252
+ 'ExportDefaultDeclaration',
253
+ ].includes(parent.type)
254
+ || (
255
+ (
256
+ parent.type === 'Property'
257
+ || parent.type === 'MethodDefinition'
258
+ )
259
+ && parent.value === node
260
+ )
261
+ );
262
+
76
263
  /**
77
- Find a comment on this node or its parent, in cases where the node passed is part of a variable or export declaration.
264
+ Find a comment on this node or its parent, in cases where the node passed is part of a variable, export, property, or method declaration.
78
265
  @param {import('estree').Node} node
79
266
  */
80
267
  const findComment = node => {
@@ -82,12 +269,7 @@ const create = context => {
82
269
  let commentableNode = node;
83
270
  while (
84
271
  !isComment(previousToken)
85
- && [
86
- 'VariableDeclarator',
87
- 'VariableDeclaration',
88
- 'ExportNamedDeclaration',
89
- 'ExportDefaultDeclaration',
90
- ].includes(commentableNode.parent.type)
272
+ && canCommentApplyToParent(commentableNode, commentableNode.parent)
91
273
  ) {
92
274
  commentableNode = commentableNode.parent;
93
275
  previousToken = sourceCode.getTokenBefore(commentableNode, {includeComments: true});
@@ -127,6 +309,8 @@ const create = context => {
127
309
  ) {
128
310
  return `callee of function named ${JSON.stringify(node.parent.callee.name)}`;
129
311
  }
312
+
313
+ return getDefaultArgumentCallReason(node) ?? getExecuteScriptPropertyReason(node);
130
314
  };
131
315
 
132
316
  context.onExit(
@@ -137,7 +321,7 @@ const create = context => {
137
321
  return;
138
322
  }
139
323
 
140
- return reportExternallyScopedVariables(node, reason);
324
+ return reportIsolatedFunctionProblems(node, reason);
141
325
  },
142
326
  );
143
327
 
@@ -146,7 +330,7 @@ const create = context => {
146
330
  selector,
147
331
  node => {
148
332
  const reason = `matches selector ${JSON.stringify(selector)}`;
149
- return reportExternallyScopedVariables(node, reason);
333
+ return reportIsolatedFunctionProblems(node, reason);
150
334
  },
151
335
  );
152
336
  }
@@ -1,6 +1,5 @@
1
1
  import path from 'node:path';
2
2
  import {isRegExp} from 'node:util/types';
3
- import helperValidatorIdentifier from '@babel/helper-validator-identifier';
4
3
  import {
5
4
  getAvailableVariableName,
6
5
  cartesianProductSamples,
@@ -9,6 +8,7 @@ import {
9
8
  upperFirst,
10
9
  lowerFirst,
11
10
  isVirtualFilename,
11
+ isIdentifierName,
12
12
  } from './utils/index.js';
13
13
  import {defaultReplacements, defaultAllowList, defaultIgnore} from './shared/name-replacements.js';
14
14
  import {
@@ -24,7 +24,6 @@ import {functionTypes} from './ast/index.js';
24
24
  const MESSAGE_ID_REPLACE = 'replace';
25
25
  const MESSAGE_ID_SUGGESTION = 'suggestion';
26
26
  const MESSAGE_ID_RENAME = 'rename';
27
- const {isIdentifierName} = helperValidatorIdentifier;
28
27
  const anotherNameMessage = 'A more descriptive name will do too.';
29
28
  const messages = {
30
29
  [MESSAGE_ID_REPLACE]: `The {{nameTypeText}} \`{{discouragedName}}\` should be named \`{{replacement}}\`. ${anotherNameMessage}`,
@@ -1,6 +1,5 @@
1
1
  import path from 'node:path';
2
2
  import {getFunctionHeadLocation, getFunctionNameWithKind, isOpeningParenToken} from '@eslint-community/eslint-utils';
3
- import helperValidatorIdentifier from '@babel/helper-validator-identifier';
4
3
  import {camelCase} from 'change-case';
5
4
  import {
6
5
  getClassHeadLocation,
@@ -9,11 +8,10 @@ import {
9
8
  getAvailableVariableName,
10
9
  upperFirst,
11
10
  isVirtualFilename,
11
+ isIdentifierName,
12
12
  } from './utils/index.js';
13
13
  import {isMemberExpression} from './ast/index.js';
14
14
 
15
- const {isIdentifierName} = helperValidatorIdentifier;
16
-
17
15
  const MESSAGE_ID_ERROR = 'no-anonymous-default-export/error';
18
16
  const MESSAGE_ID_SUGGESTION = 'no-anonymous-default-export/suggestion';
19
17
  const messages = {
@@ -1,4 +1,4 @@
1
- import helperValidatorIdentifier from '@babel/helper-validator-identifier';
1
+ import isIdentifier from 'is-identifier';
2
2
  import {findVariable} from '@eslint-community/eslint-utils';
3
3
  import typedArray from './shared/typed-array.js';
4
4
  import {isMethodCall, isUndefined} from './ast/index.js';
@@ -22,11 +22,6 @@ const ERROR_WITH_NAME_MESSAGE_ID = 'error-with-name';
22
22
  const ERROR_WITHOUT_NAME_MESSAGE_ID = 'error-without-name';
23
23
  const REPLACE_WITH_NAME_MESSAGE_ID = 'replace-with-name';
24
24
  const REPLACE_WITHOUT_NAME_MESSAGE_ID = 'replace-without-name';
25
- const {
26
- isIdentifierName,
27
- isKeyword,
28
- isStrictBindReservedWord,
29
- } = helperValidatorIdentifier;
30
25
  const messages = {
31
26
  [ERROR_WITH_NAME_MESSAGE_ID]: 'Do not pass function `{{name}}` directly to `.{{method}}(…)`.',
32
27
  [ERROR_WITHOUT_NAME_MESSAGE_ID]: 'Do not pass function directly to `.{{method}}(…)`.',
@@ -301,10 +296,7 @@ function isDefinitelyNotFunctionValue(node, context, visitedVariables = new Set(
301
296
  : false;
302
297
  }
303
298
 
304
- const isValidParameterName = name =>
305
- isIdentifierName(name)
306
- && !isKeyword(name)
307
- && !isStrictBindReservedWord(name, true);
299
+ const isValidParameterName = isIdentifier;
308
300
 
309
301
  function getSuggestionParameters(callExpression, callback, parameters) {
310
302
  if (callback.type !== 'Identifier') {
@@ -0,0 +1,111 @@
1
+ import {findVariable, getPropertyName} from '@eslint-community/eslint-utils';
2
+ import {isFunction} from './ast/index.js';
3
+ import {
4
+ getConstVariableInitializer,
5
+ isPromiseType,
6
+ unwrapTypeScriptExpression,
7
+ } from './utils/index.js';
8
+
9
+ const MESSAGE_ID = 'no-async-promise-finally';
10
+ const messages = {
11
+ [MESSAGE_ID]: 'Do not pass an async function to `Promise#finally()`.',
12
+ };
13
+
14
+ const isAsyncNonGeneratorFunction = node =>
15
+ isFunction(node)
16
+ && node.async
17
+ && !node.generator;
18
+
19
+ function isPromiseObject(node, context) {
20
+ const {parserServices} = context.sourceCode;
21
+ if (!parserServices?.program) {
22
+ return;
23
+ }
24
+
25
+ try {
26
+ return isPromiseType(
27
+ parserServices.getTypeAtLocation(node),
28
+ parserServices.program.getTypeChecker(),
29
+ );
30
+ } catch {
31
+ // TypeScript can throw while resolving incomplete projects; keep this rule best-effort.
32
+ }
33
+ }
34
+
35
+ function isAsyncFunctionDeclarationReference(node, context) {
36
+ if (node.type !== 'Identifier') {
37
+ return false;
38
+ }
39
+
40
+ const variable = findVariable(context.sourceCode.getScope(node), node);
41
+ if (variable?.defs.length !== 1) {
42
+ return false;
43
+ }
44
+
45
+ const [definition] = variable.defs;
46
+ return definition.type === 'FunctionName'
47
+ && isAsyncNonGeneratorFunction(definition.node);
48
+ }
49
+
50
+ function isAsyncFinallyCallback(node, context) {
51
+ node = unwrapTypeScriptExpression(node);
52
+
53
+ if (isAsyncNonGeneratorFunction(node)) {
54
+ return true;
55
+ }
56
+
57
+ const initializer = getConstVariableInitializer(node, context);
58
+ if (initializer && isAsyncNonGeneratorFunction(unwrapTypeScriptExpression(initializer))) {
59
+ return true;
60
+ }
61
+
62
+ return isAsyncFunctionDeclarationReference(node, context);
63
+ }
64
+
65
+ /** @param {import('eslint').Rule.RuleContext} context */
66
+ const create = context => {
67
+ context.on('CallExpression', callExpression => {
68
+ const {callee} = callExpression;
69
+ if (callee.type !== 'MemberExpression') {
70
+ return;
71
+ }
72
+
73
+ const method = getPropertyName(callee, context.sourceCode.getScope(callExpression));
74
+ if (method !== 'finally') {
75
+ return;
76
+ }
77
+
78
+ const promiseObject = isPromiseObject(callee.object, context);
79
+ if (promiseObject === false) {
80
+ return;
81
+ }
82
+
83
+ const [callback] = callExpression.arguments;
84
+ if (!callback || !isAsyncFinallyCallback(callback, context)) {
85
+ return;
86
+ }
87
+
88
+ return {
89
+ node: callback,
90
+ messageId: MESSAGE_ID,
91
+ };
92
+ });
93
+ };
94
+
95
+ /** @type {import('eslint').Rule.RuleModule} */
96
+ const config = {
97
+ create,
98
+ meta: {
99
+ type: 'problem',
100
+ docs: {
101
+ description: 'Disallow async functions as `Promise#finally()` callbacks.',
102
+ recommended: 'unopinionated',
103
+ },
104
+ messages,
105
+ languages: [
106
+ 'js/js',
107
+ ],
108
+ },
109
+ };
110
+
111
+ export default config;
@@ -1,4 +1,5 @@
1
1
  import {hasSideEffect} from '@eslint-community/eslint-utils';
2
+ import {isBooleanLiteral} from './ast/index.js';
2
3
  import {getParenthesizedText, isParenthesized, isBoolean} from './utils/index.js';
3
4
 
4
5
  const MESSAGE_ID_ERROR = 'no-chained-comparison/error';
@@ -13,7 +14,6 @@ const equalityOperators = new Set(['===', '!==', '==', '!=']);
13
14
  const comparisonOperators = orderingOperators.union(equalityOperators);
14
15
 
15
16
  const isComparison = node => node.type === 'BinaryExpression' && comparisonOperators.has(node.operator);
16
- const isBooleanLiteral = node => node.type === 'Literal' && typeof node.value === 'boolean';
17
17
 
18
18
  // The `&&` rewrite is a `LogicalExpression`, which binds looser than any comparison. It can only
19
19
  // replace `node` where a surrounding operator won't recapture it: at the top of an expression or
@@ -56,6 +56,28 @@ const collectionTypes = [
56
56
  // `.method(…)` without parentheses, so suggestions are limited to them.
57
57
  const simpleObjectTypes = new Set(['Identifier', 'ThisExpression', 'MemberExpression']);
58
58
 
59
+ function getStaticPropertyValues(node, sourceCode) {
60
+ const staticResult = getStaticValue(node, sourceCode.getScope(node));
61
+ if (staticResult) {
62
+ return [staticResult.value];
63
+ }
64
+
65
+ if (node.type !== 'ConditionalExpression') {
66
+ return;
67
+ }
68
+
69
+ const consequentValues = getStaticPropertyValues(node.consequent, sourceCode);
70
+ const alternateValues = getStaticPropertyValues(node.alternate, sourceCode);
71
+ if (!consequentValues || !alternateValues) {
72
+ return;
73
+ }
74
+
75
+ return [
76
+ ...consequentValues,
77
+ ...alternateValues,
78
+ ];
79
+ }
80
+
59
81
  function getProblem(node, collection, context) {
60
82
  const {sourceCode} = context;
61
83
  const {name: type} = collection;
@@ -132,11 +154,12 @@ const create = context => {
132
154
  }
133
155
 
134
156
  // Allow accessing a real member, including `Symbol` ones like `map[Symbol.iterator]`.
135
- const staticResult = getStaticValue(node.property, context.sourceCode.getScope(node.property));
157
+ const staticPropertyValues = getStaticPropertyValues(node.property, context.sourceCode);
136
158
  if (
137
- staticResult
138
- && (typeof staticResult.value === 'string' || typeof staticResult.value === 'symbol')
139
- && Reflect.has(collection.prototype, staticResult.value)
159
+ staticPropertyValues?.every(value =>
160
+ (typeof value === 'string' || typeof value === 'symbol')
161
+ && Reflect.has(collection.prototype, value),
162
+ )
140
163
  ) {
141
164
  return;
142
165
  }
@@ -134,10 +134,7 @@ const create = context => {
134
134
  });
135
135
 
136
136
  context.on('BinaryExpression', node => {
137
- if (!(
138
- node.operator === 'in'
139
- && !isStaticPropertyKey(node.left)
140
- )) {
137
+ if (node.operator !== 'in' || isStaticPropertyKey(node.left)) {
141
138
  return;
142
139
  }
143
140
 
@@ -1,5 +1,10 @@
1
1
  import {isCommentToken, hasSideEffect} from '@eslint-community/eslint-utils';
2
- import {containsSuspensionPoint, getReferences, trackBranchExits} from './utils/index.js';
2
+ import {
3
+ containsSuspensionPoint,
4
+ getReferences,
5
+ isReactHookName,
6
+ trackBranchExits,
7
+ } from './utils/index.js';
3
8
 
4
9
  const MESSAGE_ID = 'no-declarations-before-early-exit';
5
10
  const messages = {
@@ -23,10 +28,6 @@ function isGuardStatement(node, branchAlwaysExits) {
23
28
  return consequentExits !== alternateExits;
24
29
  }
25
30
 
26
- // https://github.com/facebook/react/blob/main/packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts
27
- const isReactHookName = name =>
28
- name === 'use' || /^use[\dA-Z]/.test(name);
29
-
30
31
  // Matches `useFoo(…)` and `Namespace.useFoo(…)` (for example, `React.useMemo(…)`).
31
32
  const isReactHookCall = node => {
32
33
  if (node?.type !== 'CallExpression') {
@@ -142,7 +142,7 @@ const getLocalUpdateFrame = knownErrorVariableFrames =>
142
142
  ?? knownErrorVariableFrames.at(-1);
143
143
 
144
144
  const updateExistingKnownErrorVariable = (knownErrorVariableFrames, variable, constructorName) => {
145
- let crossesFunctionBoundary = false;
145
+ let isCrossesFunctionBoundary = false;
146
146
 
147
147
  for (const frame of knownErrorVariableFrames.toReversed()) {
148
148
  if (frame.knownErrorVariables.has(variable)) {
@@ -151,7 +151,7 @@ const updateExistingKnownErrorVariable = (knownErrorVariableFrames, variable, co
151
151
  return true;
152
152
  }
153
153
 
154
- if (!crossesFunctionBoundary) {
154
+ if (!isCrossesFunctionBoundary) {
155
155
  frame.knownErrorVariables.set(variable, undefined);
156
156
  }
157
157
 
@@ -160,7 +160,7 @@ const updateExistingKnownErrorVariable = (knownErrorVariableFrames, variable, co
160
160
  }
161
161
 
162
162
  if (frame.isFunctionBoundary) {
163
- crossesFunctionBoundary = true;
163
+ isCrossesFunctionBoundary = true;
164
164
  }
165
165
  }
166
166
 
@@ -876,14 +876,14 @@ const create = context => {
876
876
 
877
877
  let declarationElement = element;
878
878
  let declarationType = 'const';
879
- let removeDeclaration = true;
879
+ let isRemoveDeclaration = true;
880
880
 
881
881
  if (elementNode) {
882
882
  if (elementNode.id.type === 'ObjectPattern' || elementNode.id.type === 'ArrayPattern') {
883
- removeDeclaration = arrayReferences.length === 1;
883
+ isRemoveDeclaration = arrayReferences.length === 1;
884
884
  }
885
885
 
886
- if (removeDeclaration) {
886
+ if (isRemoveDeclaration) {
887
887
  declarationType = elementNode.parent.kind;
888
888
  declarationElement = sourceCode.getText(elementNode.id);
889
889
  }
@@ -911,7 +911,7 @@ const create = context => {
911
911
  }
912
912
 
913
913
  if (elementNode) {
914
- yield removeDeclaration
914
+ yield isRemoveDeclaration
915
915
  ? fixer.removeRange(getRemovalRange(elementNode, sourceCode))
916
916
  : fixer.replaceText(elementNode.init, element);
917
917
  }
@@ -90,12 +90,12 @@ function reportObjectPatternAndShouldSkipPropertyCheck(report, node, options) {
90
90
  return true;
91
91
  }
92
92
 
93
- const assignmentKeyEqualsValue = parent.key.name === parent.value.name;
93
+ const isAssignmentKeyEqualsValue = parent.key.name === parent.value.name;
94
94
 
95
95
  const valueIsInvalid = parent.value.name && Boolean(keyword);
96
96
 
97
97
  // Ignore destructuring if the option is set, unless a new identifier is created
98
- if (valueIsInvalid && !assignmentKeyEqualsValue) {
98
+ if (valueIsInvalid && !isAssignmentKeyEqualsValue) {
99
99
  report(node, keyword);
100
100
  }
101
101
 
@@ -11,12 +11,14 @@ const messages = {
11
11
  };
12
12
 
13
13
  // Trailing `:` marks a complete line (heading, label, list intro), not a wrapped sentence.
14
- const sentenceEndPattern = /[!.:?]$/v;
14
+ const sentenceEndPattern = /(?:[\p{Extended_Pictographic}!.:?]\p{Variation_Selector}?|\p{RGI_Emoji})$/v;
15
15
  const directiveCommentPattern = /^(?:[#\/@]|eslint(?:$|\s|-)|globals?\b|exported\b|no default$|noinspection\b|(?:c8|istanbul|v8)\s+ignore\b|(?:biome|deno|dprint|oxlint|prettier)-|(?:cspell|spell-checker):)/v;
16
+ const annotationCommentPattern = /^[A-Z]{2,}(?:\([^\)]+\))?:/v;
16
17
  const spdxCommentPattern = /^SPDX-/v;
17
18
  const copyrightCommentPattern = /^(?:©|copyright\b)/iv;
18
19
  // Editor modelines: vim (`vim:`, `vi:`) and Emacs (`-*- … -*-`).
19
20
  const modelineCommentPattern = /^(?:vim?:|-\*-)/v;
21
+ const structuredCommentPattern = /^(?:language=\S+|(?:end)?region(?:$|\s)|<\/?editor-fold\b)/v;
20
22
  const listCommentPattern = /^(?:[*+\-]\s|\d+(?:\.|\))\s)/v;
21
23
  const separatorCommentPattern = /^[#*\-=_~]{3,}$/v;
22
24
  const urlPattern = /\bhttps?:\/\/|www\./v;
@@ -28,9 +30,11 @@ const endsWithSentencePunctuation = comment => sentenceEndPattern.test(getCommen
28
30
 
29
31
  const isIgnoredCommentText = text =>
30
32
  directiveCommentPattern.test(text)
33
+ || annotationCommentPattern.test(text)
31
34
  || spdxCommentPattern.test(text)
32
35
  || copyrightCommentPattern.test(text)
33
36
  || modelineCommentPattern.test(text)
37
+ || structuredCommentPattern.test(text)
34
38
  || listCommentPattern.test(text)
35
39
  || separatorCommentPattern.test(text)
36
40
  || urlPattern.test(text)