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
@@ -113,15 +113,12 @@ function isBufferEncoding(node, scope) {
113
113
 
114
114
  function isJsonReadFileCall(node, scope) {
115
115
  if (
116
- !(
117
- node
116
+ !(node
118
117
  && node.type === 'CallExpression'
119
118
  && !node.optional
120
119
  && (node.arguments.length === 1 || node.arguments.length === 2)
121
120
  && node.arguments.every(node => node.type !== 'SpreadElement')
122
- && node.callee.type === 'MemberExpression'
123
- && !node.callee.optional
124
- )
121
+ && node.callee.type === 'MemberExpression') || node.callee.optional
125
122
  ) {
126
123
  return false;
127
124
  }
@@ -1,4 +1,3 @@
1
- import helperValidatorIdentifier from '@babel/helper-validator-identifier';
2
1
  import {findVariable} from '@eslint-community/eslint-utils';
3
2
  import {
4
3
  escapeString,
@@ -12,6 +11,7 @@ import {
12
11
  needsSemicolon,
13
12
  shouldAddParenthesesToMemberExpressionObject,
14
13
  wouldRemoveComments,
14
+ isIdentifierName,
15
15
  } from './utils/index.js';
16
16
  import {removeStatement} from './fix/index.js';
17
17
  import {
@@ -21,7 +21,6 @@ import {
21
21
  isExpressionStatement,
22
22
  } from './ast/index.js';
23
23
 
24
- const {isIdentifierName} = helperValidatorIdentifier;
25
24
  const MESSAGE_ID = 'prefer-dataset';
26
25
  const INVERSE_MESSAGE_ID = 'prefer-attributes';
27
26
  const messages = {
@@ -106,11 +105,8 @@ function getDatasetVariableReadMembers(variable) {
106
105
 
107
106
  const {identifier} = reference;
108
107
  const member = identifier.parent;
109
- if (!(
110
- member.type === 'MemberExpression'
111
- && member.object === identifier
112
- && !member.optional
113
- )) {
108
+ if (!(member.type === 'MemberExpression'
109
+ && member.object === identifier) || member.optional) {
114
110
  return;
115
111
  }
116
112
 
@@ -24,14 +24,14 @@ const getProblem = ({node, original, regex = escapeCase, lowercase, fix}) => {
24
24
 
25
25
  /** @param {import('eslint').Rule.RuleContext} context */
26
26
  const create = context => {
27
- const lowercase = context.options[0] === 'lowercase';
27
+ const isLowercase = context.options[0] === 'lowercase';
28
28
 
29
29
  context.on('Literal', node => {
30
30
  if (isStringLiteral(node)) {
31
31
  return getProblem({
32
32
  node,
33
33
  original: node.raw,
34
- lowercase,
34
+ lowercase: isLowercase,
35
35
  });
36
36
  }
37
37
  });
@@ -42,7 +42,7 @@ const create = context => {
42
42
  node,
43
43
  original: node.raw,
44
44
  regex: escapePatternCase,
45
- lowercase,
45
+ lowercase: isLowercase,
46
46
  });
47
47
  }
48
48
  });
@@ -55,7 +55,7 @@ const create = context => {
55
55
  return getProblem({
56
56
  node,
57
57
  original: node.value.raw,
58
- lowercase,
58
+ lowercase: isLowercase,
59
59
  fix: (fixer, fixed) => replaceTemplateElement(node, fixed, context, fixer),
60
60
  });
61
61
  });
@@ -191,8 +191,8 @@ function getPackageHelpers(dirname) {
191
191
 
192
192
  // Check if have to skip colon
193
193
  // @example "TODO [...]: message here"
194
- const dropColon = afterArguments[0] === ':';
195
- if (dropColon) {
194
+ const isDropColon = afterArguments[0] === ':';
195
+ if (isDropColon) {
196
196
  return afterArguments.slice(1).trim();
197
197
  }
198
198
 
@@ -250,7 +250,7 @@ function create(context) {
250
250
  }
251
251
 
252
252
  let node;
253
- let autoFix = true;
253
+ let isAutoFix = true;
254
254
  let {isZeroLengthCheck, node: lengthCheckNode} = getLengthCheckNode(lengthNode);
255
255
  if (lengthCheckNode) {
256
256
  const {isNegative, node: ancestor} = getBooleanAncestor(lengthCheckNode, context);
@@ -266,7 +266,7 @@ function create(context) {
266
266
  } else if (isLogicalExpression(lengthNode.parent) && lengthNode.parent.operator === '&&') {
267
267
  isZeroLengthCheck = isNegative;
268
268
  node = lengthNode;
269
- autoFix = false;
269
+ isAutoFix = false;
270
270
  }
271
271
  }
272
272
 
@@ -287,7 +287,7 @@ function create(context) {
287
287
  node,
288
288
  isZeroLengthCheck,
289
289
  lengthNode,
290
- autoFix: autoFix && !isUnsafeNegationInBinaryExpression,
290
+ autoFix: isAutoFix && !isUnsafeNegationInBinaryExpression,
291
291
  shouldSuggest: !isUnsafeNegationInBinaryExpression,
292
292
  });
293
293
  }
@@ -280,8 +280,8 @@ const create = context => {
280
280
 
281
281
  return new RegExp(item, 'u');
282
282
  });
283
- const multipleFileExtensions = options.multipleFileExtensions !== false;
284
- const checkDirectories = options.checkDirectories !== false;
283
+ const isMultipleFileExtensions = options.multipleFileExtensions !== false;
284
+ const isCheckDirectories = options.checkDirectories !== false;
285
285
  const chosenCasesFunctions = chosenCases.map(case_ => cases[case_].fn);
286
286
 
287
287
  onRoot(context, () => {
@@ -292,13 +292,13 @@ const create = context => {
292
292
  filename,
293
293
  middle,
294
294
  extension,
295
- } = getFilenameParts(basenameWithExtension, {multipleFileExtensions});
295
+ } = getFilenameParts(basenameWithExtension, {multipleFileExtensions: isMultipleFileExtensions});
296
296
 
297
297
  if (pathSegments.some(segment => ignore.some(regexp => regexp.test(segment)))) {
298
298
  return;
299
299
  }
300
300
 
301
- if (checkDirectories) {
301
+ if (isCheckDirectories) {
302
302
  for (const directory of pathSegments.slice(0, -1)) {
303
303
  const report = getInvalidDirectoryReport(directory, chosenCases, chosenCasesFunctions);
304
304
 
@@ -1,5 +1,5 @@
1
1
  import {isCommaToken} from '@eslint-community/eslint-utils';
2
- import {getParentheses} from '../utils/index.js';
2
+ import {getParentheses, hasCommentInRange} from '../utils/index.js';
3
3
 
4
4
  /**
5
5
  @import {TSESTree as ESTree} from '@typescript-eslint/types';
@@ -22,17 +22,22 @@ function getArgumentRemovalRange(node, context) {
22
22
  let [start] = sourceCode.getRange(firstToken);
23
23
  let [, end] = sourceCode.getRange(lastToken);
24
24
 
25
- if (index !== 0) {
26
- const commaToken = sourceCode.getTokenBefore(firstToken);
27
- [start] = sourceCode.getRange(commaToken);
28
- }
29
-
30
- // If the removed argument is the only argument, the trailing comma must be removed too
31
25
  if (callOrNewExpression.arguments.length === 1) {
26
+ // The only argument: also drop a dangling trailing comma if present (`fn(a,)`).
32
27
  const tokenAfter = sourceCode.getTokenAfter(lastToken);
33
28
  if (isCommaToken(tokenAfter)) {
34
29
  [, end] = sourceCode.getRange(tokenAfter);
35
30
  }
31
+ } else if (index === 0) {
32
+ // First of several: remove it through the following comma and the gap after it, so
33
+ // `fn(a, b)` becomes `fn(b)` rather than `fn( b)`.
34
+ const commaToken = sourceCode.getTokenAfter(lastToken);
35
+ const tokenAfterComma = sourceCode.getTokenAfter(commaToken, {includeComments: true});
36
+ [end] = sourceCode.getRange(tokenAfterComma);
37
+ } else {
38
+ // Otherwise remove the comma that precedes it.
39
+ const commaToken = sourceCode.getTokenBefore(firstToken);
40
+ [start] = sourceCode.getRange(commaToken);
36
41
  }
37
42
 
38
43
  return [start, end];
@@ -45,7 +50,28 @@ function getArgumentRemovalRange(node, context) {
45
50
  @returns {ESLint.Rule.Fix}
46
51
  */
47
52
  export default function removeArgument(fixer, node, context) {
48
- return fixer.removeRange(getArgumentRemovalRange(node, context));
53
+ const removalRange = getArgumentRemovalRange(node, context);
54
+ const callOrNewExpression = node.parent;
55
+ const index = callOrNewExpression.arguments.indexOf(node);
56
+
57
+ if (
58
+ index === 0
59
+ && callOrNewExpression.arguments.length > 1
60
+ && hasCommentInRange(context, removalRange)
61
+ ) {
62
+ const {sourceCode} = context;
63
+ const parentheses = getParentheses(node, context);
64
+ const lastToken = parentheses.at(-1) || node;
65
+ const [, argumentEnd] = sourceCode.getRange(lastToken);
66
+ const commaToken = sourceCode.getTokenAfter(lastToken);
67
+ const [commaStart, commaEnd] = sourceCode.getRange(commaToken);
68
+ const rangeText = sourceCode.text.slice(...removalRange);
69
+ const replacement = rangeText.slice(argumentEnd - removalRange[0], commaStart - removalRange[0]) + rangeText.slice(commaEnd - removalRange[0]);
70
+
71
+ return fixer.replaceTextRange(removalRange, replacement);
72
+ }
73
+
74
+ return fixer.removeRange(removalRange);
49
75
  }
50
76
 
51
77
  export {
@@ -3,7 +3,7 @@ import {isSemicolonToken} from '@eslint-community/eslint-utils';
3
3
  const isWhitespaceOnly = text => /^\s*$/.test(text);
4
4
 
5
5
  // Removes a statement node along with its surrounding whitespace, while preserving comments.
6
- function removeStatement(statement, context, fixer, preserveSemiColon = false) {
6
+ export default function removeStatement(statement, context, fixer, preserveSemiColon = false) {
7
7
  const {sourceCode} = context;
8
8
  const {lines} = sourceCode;
9
9
  let endToken = statement;
@@ -44,5 +44,3 @@ function removeStatement(statement, context, fixer, preserveSemiColon = false) {
44
44
 
45
45
  return fixer.removeRange([start, end]);
46
46
  }
47
-
48
- export default removeStatement;
package/rules/id-match.js CHANGED
@@ -26,11 +26,11 @@ const shouldIgnoreNamedSpecifierReport = (problem, checkNamedSpecifiers) =>
26
26
  @param {import('eslint').Rule.RuleContext} context
27
27
  */
28
28
  const create = context => {
29
- const checkNamedSpecifiers = context.options[1]?.checkNamedSpecifiers !== false;
29
+ const isCheckNamedSpecifiers = context.options[1]?.checkNamedSpecifiers !== false;
30
30
  const fakeContext = Object.create(context, {
31
31
  report: {
32
32
  value(problem) {
33
- if (shouldIgnoreNamedSpecifierReport(problem, checkNamedSpecifiers)) {
33
+ if (shouldIgnoreNamedSpecifierReport(problem, isCheckNamedSpecifiers)) {
34
34
  return;
35
35
  }
36
36
 
@@ -98,13 +98,51 @@ const create = context => {
98
98
 
99
99
  options.comments = options.comments.map(comment => comment.toLowerCase());
100
100
 
101
- const allowedGlobals = {
101
+ const configuredGlobals = {
102
102
  ...(globals[`es${context.languageOptions.ecmaVersion}`] ?? globals.builtins),
103
103
  ...context.languageOptions.globals,
104
- ...options.overrideGlobals,
105
104
  };
106
105
  const checked = new WeakSet();
107
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
+
108
146
  function * getFunctionContextProblems(node, reason, root = node) {
109
147
  const messageId = functionContextReferenceMessageIds.get(node.type);
110
148
  if (messageId) {
@@ -173,18 +211,20 @@ const create = context => {
173
211
  continue;
174
212
  }
175
213
 
176
- 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') {
177
218
  if (reference.isReadOnly()) {
178
219
  continue;
179
220
  }
180
221
 
181
- const globalsValue = allowedGlobals[identifier.name];
182
- const isGlobalWritable = [true, 'writable', 'writeable'].includes(globalsValue);
222
+ const isGlobalWritable = [true, 'writable', 'writeable'].includes(allowedGlobalValue);
183
223
  if (isGlobalWritable) {
184
224
  continue;
185
225
  }
186
226
 
187
- reason += ' (global variable is not writable)';
227
+ problemReason += ' (global variable is not writable)';
188
228
  }
189
229
 
190
230
  // Could consider checking for typeof operator here, like in no-undef?
@@ -192,7 +232,7 @@ const create = context => {
192
232
  context.report({
193
233
  node: identifier,
194
234
  messageId: MESSAGE_ID_EXTERNALLY_SCOPED_VARIABLE,
195
- data: {name: identifier.name, reason},
235
+ data: {name: identifier.name, reason: problemReason},
196
236
  });
197
237
  }
198
238
 
@@ -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') {
@@ -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
@@ -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
 
@@ -1,7 +1,7 @@
1
1
  import {findVariable, getStaticValue} from '@eslint-community/eslint-utils';
2
2
  import {isMethodCall} from './ast/index.js';
3
3
  import {isReference, isSame, unwrapExpression} from './utils/comparison.js';
4
- import {isKnownNonMap} from './utils/index.js';
4
+ import {isComparableStaticValue, isKnownNonMap} from './utils/index.js';
5
5
 
6
6
  /**
7
7
  @import {TSESTree as ESTree} from '@typescript-eslint/types';
@@ -21,13 +21,6 @@ const skippedNodeTypes = new Set([
21
21
  'FunctionExpression',
22
22
  ]);
23
23
 
24
- const isComparableStaticValue = value =>
25
- value === null
26
- || (
27
- typeof value !== 'object'
28
- && typeof value !== 'function'
29
- );
30
-
31
24
  const isSameValueZero = (left, right) =>
32
25
  left === right
33
26
  || (
@@ -132,7 +125,7 @@ function patternContainsSameBinding(node, mapHasRoot, context) {
132
125
 
133
126
  function hasCallExpression(node, context) {
134
127
  const {visitorKeys} = context.sourceCode;
135
- let result = false;
128
+ let isResult = false;
136
129
 
137
130
  function visit(node) {
138
131
  if (!node) {
@@ -142,14 +135,14 @@ function hasCallExpression(node, context) {
142
135
  node = unwrapExpression(node);
143
136
 
144
137
  if (
145
- result
138
+ isResult
146
139
  || skippedNodeTypes.has(node.type)
147
140
  ) {
148
141
  return;
149
142
  }
150
143
 
151
144
  if (node.type === 'CallExpression') {
152
- result = true;
145
+ isResult = true;
153
146
  return;
154
147
  }
155
148
 
@@ -170,7 +163,7 @@ function hasCallExpression(node, context) {
170
163
 
171
164
  visit(node);
172
165
 
173
- return result;
166
+ return isResult;
174
167
  }
175
168
 
176
169
  function areDifferentKeys(left, right, context) {
@@ -353,11 +346,11 @@ function isNestedMapHasGuard(node, mapHasCall, context) {
353
346
 
354
347
  function hasSameMapHasCall(node, mapHasCall, context) {
355
348
  const {visitorKeys} = context.sourceCode;
356
- let result = false;
349
+ let isResult = false;
357
350
 
358
351
  function visit(node) {
359
352
  if (
360
- result
353
+ isResult
361
354
  || !node
362
355
  || skippedNodeTypes.has(node.type)
363
356
  ) {
@@ -370,7 +363,7 @@ function hasSameMapHasCall(node, mapHasCall, context) {
370
363
  nestedMapHasCall
371
364
  && isSameMapReceiver(unwrapExpression(mapHasCall).callee.object, unwrapExpression(nestedMapHasCall).callee.object, context)
372
365
  ) {
373
- result = true;
366
+ isResult = true;
374
367
  return;
375
368
  }
376
369
 
@@ -391,16 +384,16 @@ function hasSameMapHasCall(node, mapHasCall, context) {
391
384
 
392
385
  visit(node);
393
386
 
394
- return result;
387
+ return isResult;
395
388
  }
396
389
 
397
390
  function hasMapReceiverWrite(node, mapHasCall, context) {
398
391
  const {visitorKeys} = context.sourceCode;
399
- let result = false;
392
+ let isResult = false;
400
393
 
401
394
  function visit(node) {
402
395
  if (
403
- result
396
+ isResult
404
397
  || !node
405
398
  || skippedNodeTypes.has(node.type)
406
399
  || isNestedMapHasGuard(node, mapHasCall, context)
@@ -409,7 +402,7 @@ function hasMapReceiverWrite(node, mapHasCall, context) {
409
402
  }
410
403
 
411
404
  if (isMapReceiverWrite(node, mapHasCall, context)) {
412
- result = true;
405
+ isResult = true;
413
406
  return;
414
407
  }
415
408
 
@@ -430,7 +423,7 @@ function hasMapReceiverWrite(node, mapHasCall, context) {
430
423
 
431
424
  visit(node);
432
425
 
433
- return result;
426
+ return isResult;
434
427
  }
435
428
 
436
429
  function getMapAccessProblems(node, mapHasCall, context, reportedAccessKeys) {
@@ -97,12 +97,9 @@ const create = context => {
97
97
  });
98
98
 
99
99
  context.on('ExportSpecifier', specifier => {
100
- if (!(
101
- isValueExport(specifier)
100
+ if (!(isValueExport(specifier)
102
101
  && specifier.exported.name === 'default'
103
- && isValueExport(specifier.parent)
104
- && !specifier.parent.source
105
- )) {
102
+ && isValueExport(specifier.parent)) || specifier.parent.source) {
106
103
  return;
107
104
  }
108
105