eslint-plugin-unicorn 70.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 (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 +94 -10
  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
@@ -1,10 +1,11 @@
1
1
  import {hasSideEffect} from '@eslint-community/eslint-utils';
2
- import {isMethodCall} from './ast/index.js';
2
+ import {isEmptyArrayExpression, isMethodCall} from './ast/index.js';
3
3
  import {
4
4
  getParenthesizedText,
5
5
  isKnownNonArray,
6
6
  isParenthesized,
7
7
  isSameIdentifier,
8
+ isTypeScriptFile,
8
9
  shouldAddParenthesesToMemberExpressionObject,
9
10
  wouldRemoveComments,
10
11
  } from './utils/index.js';
@@ -49,10 +50,6 @@ const isSimpleSingleParameterArrowCallback = node =>
49
50
  && !node.params[0].optional
50
51
  && node.body.type !== 'BlockStatement';
51
52
 
52
- const isEmptyArrayExpression = node =>
53
- node.type === 'ArrayExpression'
54
- && node.elements.length === 0;
55
-
56
53
  const getSingleArrayElement = node => {
57
54
  if (
58
55
  node.type !== 'ArrayExpression'
@@ -195,6 +192,10 @@ function getProblemForFilterFlatMap(flatMapCallExpression, callbackResult, conte
195
192
  }
196
193
 
197
194
  function getProblemForConditionalFlatMap(flatMapCallExpression, callback, callbackResult, context) {
195
+ if (isTypeScriptFile(context.physicalFilename)) {
196
+ return;
197
+ }
198
+
198
199
  const method = isSameIdentifier(callbackResult.element, callback.params[0]) ? 'filter' : 'filter().map';
199
200
  const problem = {
200
201
  node: flatMapCallExpression.callee.property,
@@ -1,9 +1,4 @@
1
- import {
2
- isIdentifierName,
3
- isKeyword,
4
- isReservedWord,
5
- isStrictReservedWord,
6
- } from '@babel/helper-validator-identifier';
1
+ import reservedIdentifiers from 'reserved-identifiers';
7
2
  import {findVariable} from '@eslint-community/eslint-utils';
8
3
  import {isStringLiteral} from './ast/index.js';
9
4
  import {
@@ -12,6 +7,7 @@ import {
12
7
  isLeftHandSide,
13
8
  isControlFlowTest,
14
9
  isBooleanExpression,
10
+ isIdentifierName,
15
11
  } from './utils/index.js';
16
12
 
17
13
  const MESSAGE_ID = 'no-unnecessary-global-this';
@@ -53,11 +49,11 @@ function isActiveGlobal(name, node, context) {
53
49
  && variable.defs.length === 0;
54
50
  }
55
51
 
52
+ const reserved = reservedIdentifiers();
53
+
56
54
  function canUseBareIdentifier(name) {
57
55
  return isIdentifierName(name)
58
- && !isKeyword(name)
59
- && !isReservedWord(name, true)
60
- && !isStrictReservedWord(name, true);
56
+ && (!reserved.has(name) || name === 'eval' || name === 'arguments');
61
57
  }
62
58
 
63
59
  function getStaticPropertyName(node) {
@@ -65,7 +65,6 @@ const getReferenceParent = referenceNode => {
65
65
  return referenceNode.parent;
66
66
  };
67
67
 
68
- // eslint-disable-next-line unicorn/name-replacements
69
68
  const specialProtoPropertyKey = {
70
69
  type: 'Identifier',
71
70
  name: '__proto__',
@@ -1,3 +1,4 @@
1
+ import {isEmptyStringLiteral} from './ast/index.js';
1
2
  import {
2
3
  isBoolean,
3
4
  isString,
@@ -25,8 +26,6 @@ const coercions = new Map([
25
26
  ['Number', {type: 'number', isType: (node, context) => isNumber(node, context.sourceCode.getScope(node))}],
26
27
  ]);
27
28
 
28
- const isEmptyStringLiteral = node => node.type === 'Literal' && node.value === '';
29
-
30
29
  /** @param {ESLint.Rule.RuleContext} context */
31
30
  const create = context => {
32
31
  const {sourceCode} = context;
@@ -410,10 +410,10 @@ const create = context => {
410
410
  return;
411
411
  }
412
412
 
413
- const accessWhenTestPasses = !isConsequentUndefined;
413
+ const isAccessWhenTestPasses = !isConsequentUndefined;
414
414
  const testValidity = getIndexedAccessTestValidity(node.test, indexedAccess);
415
415
 
416
- if (testValidity !== accessWhenTestPasses) {
416
+ if (testValidity !== isAccessWhenTestPasses) {
417
417
  return;
418
418
  }
419
419
 
@@ -204,13 +204,10 @@ const create = context => {
204
204
  // `array.filter()[0]`
205
205
  // `array?.filter()[0]`
206
206
  context.on('MemberExpression', node => {
207
- if (!(
208
- node.computed
207
+ if (!(node.computed
209
208
  && node.property.type === 'Literal'
210
209
  && node.property.raw === '0'
211
- && isArrayFilterCall(node.object, context)
212
- && !isLeftHandSide(node)
213
- )) {
210
+ && isArrayFilterCall(node.object, context)) || isLeftHandSide(node)) {
214
211
  return;
215
212
  }
216
213
 
@@ -75,6 +75,7 @@ function getReplacement(valueNode) {
75
75
  const {value, raw} = valueNode;
76
76
 
77
77
  // Intentionally `Number.isInteger`, not `Number.isSafeInteger`: large integers beyond the safe range are exactly what bigint literals are for.
78
+ // eslint-disable-next-line unicorn/prefer-number-is-safe-integer
78
79
  if (!Number.isInteger(value)) {
79
80
  return;
80
81
  }
@@ -3,6 +3,7 @@ import {
3
3
  getLastTrailingCommentOnSameLine,
4
4
  getNextNode,
5
5
  getParenthesizedText,
6
+ getPreviousNode,
6
7
  isGlobalBooleanCall,
7
8
  shouldAddParenthesesToUnaryExpressionArgument,
8
9
  } from './utils/index.js';
@@ -143,11 +144,11 @@ const create = context => {
143
144
  const hasComments = sourceCode.getCommentsInside(node).length > 0
144
145
  || sourceCode.getCommentsInside(alternate).length > 0
145
146
  || getLastTrailingCommentOnSameLine(context, alternate);
146
- const needsGlobalBoolean = !shouldNegateTest
147
+ const isNeedsGlobalBoolean = !shouldNegateTest
147
148
  && !isRuntimeBooleanExpression(node.test, context)
148
149
  && !canUseGlobalBoolean(node);
149
150
 
150
- if (hasComments || needsGlobalBoolean) {
151
+ if (hasComments || isNeedsGlobalBoolean) {
151
152
  return problem;
152
153
  }
153
154
 
@@ -160,6 +161,13 @@ const create = context => {
160
161
  };
161
162
  }
162
163
 
164
+ function isPrecededByFlatIfReturningSameBoolean(node, returnValue) {
165
+ const previousNode = getPreviousNode(node, context);
166
+ return previousNode?.type === 'IfStatement'
167
+ && !previousNode.alternate
168
+ && getBooleanReturnValue(getNodeBody(previousNode.consequent)) === returnValue;
169
+ }
170
+
163
171
  function getFlatProblem(node) {
164
172
  const alternate = getNextNode(node, context);
165
173
  if (
@@ -169,9 +177,18 @@ const create = context => {
169
177
  return;
170
178
  }
171
179
 
180
+ const consequent = getNodeBody(node.consequent);
181
+ const consequentValue = getBooleanReturnValue(consequent);
182
+ if (
183
+ consequentValue !== undefined
184
+ && isPrecededByFlatIfReturningSameBoolean(node, consequentValue)
185
+ ) {
186
+ return;
187
+ }
188
+
172
189
  const problem = getProblem(
173
190
  node,
174
- getNodeBody(node.consequent),
191
+ consequent,
175
192
  alternate,
176
193
  [
177
194
  sourceCode.getRange(node)[0],
@@ -108,8 +108,8 @@ const fixDefaultExpression = (fixer, sourceCode, node) => {
108
108
  ]);
109
109
  }
110
110
 
111
- const endsWithWhitespace = lineText[column] === ' ';
112
- if (endsWithWhitespace) {
111
+ const isEndsWithWhitespace = lineText[column] === ' ';
112
+ if (isEndsWithWhitespace) {
113
113
  const [start, end] = sourceCode.getRange(node);
114
114
  return fixer.removeRange([start, end + 1]);
115
115
  }
@@ -148,13 +148,13 @@ function getEqualityComparisons(node) {
148
148
  */
149
149
  function getBooleanComparisonInfo(node, context) {
150
150
  node = unwrapExpression(node);
151
- let value = true;
151
+ let isValue = true;
152
152
 
153
153
  while (
154
154
  node.type === 'UnaryExpression'
155
155
  && node.operator === '!'
156
156
  ) {
157
- value = !value;
157
+ isValue = !isValue;
158
158
  node = unwrapExpression(node.argument);
159
159
  }
160
160
 
@@ -178,7 +178,7 @@ function getBooleanComparisonInfo(node, context) {
178
178
 
179
179
  return {
180
180
  discriminant: node,
181
- valueKeys: new Set([getStaticEqualityValueKey(value)]),
181
+ valueKeys: new Set([getStaticEqualityValueKey(isValue)]),
182
182
  };
183
183
  }
184
184
 
@@ -444,13 +444,9 @@ function getProblem(previousIfStatement, ifStatement, context, branchAlwaysExits
444
444
  const previous = getComparisonInfo(previousIfStatement.test, context);
445
445
  const current = getComparisonInfo(ifStatement.test, context);
446
446
 
447
- if (!(
448
- previous
447
+ if (!(previous
449
448
  && current
450
- && isSame(previous.discriminant, current.discriminant)
451
- && !hasOverlappingValues(previous.valueKeys, current.valueKeys)
452
- && !hasDirectDiscriminantMutation(previousIfStatement.consequent, previous.discriminant, context)
453
- )) {
449
+ && isSame(previous.discriminant, current.discriminant)) || hasOverlappingValues(previous.valueKeys, current.valueKeys) || hasDirectDiscriminantMutation(previousIfStatement.consequent, previous.discriminant, context)) {
454
450
  return;
455
451
  }
456
452
 
@@ -1,5 +1,10 @@
1
1
  import {findVariable, getStaticValue} from '@eslint-community/eslint-utils';
2
- import {isMethodCall, isNewExpression, isUndefined} from './ast/index.js';
2
+ import {
3
+ isMethodCall,
4
+ isNewExpression,
5
+ isNullLiteral,
6
+ isUndefined,
7
+ } from './ast/index.js';
3
8
  import {
4
9
  getParenthesizedText,
5
10
  getTypeSymbol,
@@ -823,8 +828,6 @@ const getCallKind = (callExpression, comparison, context) => {
823
828
  }
824
829
  };
825
830
 
826
- const isNullLiteral = node => node.type === 'Literal' && node.value === null;
827
-
828
831
  const getComparison = callExpression => {
829
832
  const comparisonTarget = getTransparentExpressionAncestor(callExpression);
830
833
  const {parent} = comparisonTarget;
@@ -82,14 +82,14 @@ function isWellKnownXmlNamespace(text, matchIndex) {
82
82
 
83
83
  /** @param {import('eslint').Rule.RuleContext} context */
84
84
  const create = context => {
85
- let checked = false;
85
+ let isChecked = false;
86
86
 
87
87
  onRoot(context, node => {
88
- if (checked) {
88
+ if (isChecked) {
89
89
  return;
90
90
  }
91
91
 
92
- checked = true;
92
+ isChecked = true;
93
93
 
94
94
  const {sourceCode} = context;
95
95
  const {text} = sourceCode;
@@ -1,6 +1,4 @@
1
- import helperValidatorIdentifier from '@babel/helper-validator-identifier';
2
-
3
- const {isIdentifierName} = helperValidatorIdentifier;
1
+ import {isIdentifierName} from './utils/index.js';
4
2
 
5
3
  const MESSAGE_ID = 'prefer-identifier-import-export-specifiers';
6
4
  const messages = {
@@ -166,14 +166,14 @@ function isMinimalBinaryExpression(left, right, context) {
166
166
  return false;
167
167
  }
168
168
 
169
- const leftSidesAreSame = isSameSourceText(left.left, right.left, context.sourceCode);
170
- const rightSidesAreSame = isSameSourceText(left.right, right.right, context.sourceCode);
169
+ const isLeftSidesAreSame = isSameSourceText(left.left, right.left, context.sourceCode);
170
+ const isRightSidesAreSame = isSameSourceText(left.right, right.right, context.sourceCode);
171
171
 
172
- if (leftSidesAreSame === rightSidesAreSame) {
172
+ if (isLeftSidesAreSame === isRightSidesAreSame) {
173
173
  return false;
174
174
  }
175
175
 
176
- return rightSidesAreSame || isSafeSharedExpression(left.left);
176
+ return isRightSidesAreSame || isSafeSharedExpression(left.left);
177
177
  }
178
178
 
179
179
  function hasSameObjectWithDifferentDynamicKey(left, right, context) {
@@ -1,15 +1,13 @@
1
1
  import {getStaticValue} from '@eslint-community/eslint-utils';
2
- import helperValidatorIdentifier from '@babel/helper-validator-identifier';
3
2
  import {isMethodCall} from './ast/index.js';
4
3
  import {
5
4
  getIndentString,
6
5
  getNextNode,
7
6
  getPreviousNode,
8
7
  isSameReference,
8
+ isIdentifierName,
9
9
  } from './utils/index.js';
10
10
 
11
- const {isIdentifierName} = helperValidatorIdentifier;
12
-
13
11
  const MESSAGE_ID = 'prefer-object-define-properties';
14
12
  const messages = {
15
13
  [MESSAGE_ID]: 'Prefer `Object.defineProperties()` over multiple `Object.defineProperty()` calls.',
@@ -231,11 +231,8 @@ const isGlobalObject = (node, context) =>
231
231
  const isWriteOnlyLeftHandSide = node =>
232
232
  isLeftHandSide(node)
233
233
  && node.parent.type !== 'UpdateExpression'
234
- && !(
235
- node.parent.type === 'AssignmentExpression'
236
- && node.parent.left === node
237
- && node.parent.operator !== '='
238
- );
234
+ && (!(node.parent.type === 'AssignmentExpression'
235
+ && node.parent.left === node) || node.parent.operator === '=');
239
236
 
240
237
  const isGlobalDocument = (node, context) => {
241
238
  node = unwrapTypeScriptExpression(node);
@@ -1,7 +1,5 @@
1
- import helperValidatorIdentifier from '@babel/helper-validator-identifier';
2
1
  import {getStaticStringValue} from './ast/index.js';
3
-
4
- const {isIdentifierName} = helperValidatorIdentifier;
2
+ import {isIdentifierName} from './utils/index.js';
5
3
 
6
4
  const MESSAGE_ID = 'prefer-private-class-fields';
7
5
  const messages = {
@@ -138,14 +138,14 @@ function trueBranchSign(branch) {
138
138
  }
139
139
 
140
140
  function expectedSignForTest(test, minuend, subtrahend) {
141
- const greaterThan = test.operator === '>' || test.operator === '>=';
141
+ const isGreaterThan = test.operator === '>' || test.operator === '>=';
142
142
  const testLeftIsMinuend = isSameReference(test.left, minuend) && isSameReference(test.right, subtrahend);
143
143
 
144
144
  if (testLeftIsMinuend) {
145
- return greaterThan ? 1 : -1;
145
+ return isGreaterThan ? 1 : -1;
146
146
  }
147
147
 
148
- return greaterThan ? -1 : 1;
148
+ return isGreaterThan ? -1 : 1;
149
149
  }
150
150
 
151
151
  function branchSignsMatchSubtraction(branch, minuend, subtrahend) {
@@ -220,9 +220,9 @@ const create = context => {
220
220
 
221
221
  // Orient the subtraction so it matches the comparator's sort direction.
222
222
  const {test} = tree;
223
- const greaterThan = test.operator === '>' || test.operator === '>=';
224
- const leftIsMinuend = greaterThan ? sign > 0 : sign < 0;
225
- const [minuend, subtrahend] = leftIsMinuend ? [test.left, test.right] : [test.right, test.left];
223
+ const isGreaterThan = test.operator === '>' || test.operator === '>=';
224
+ const isLeftIsMinuend = isGreaterThan ? sign > 0 : sign < 0;
225
+ const [minuend, subtrahend] = isLeftIsMinuend ? [test.left, test.right] : [test.right, test.left];
226
226
 
227
227
  if (!branchSignsMatchSubtraction(tree, minuend, subtrahend)) {
228
228
  return;
@@ -68,15 +68,12 @@ const getProblem = (sourceCode, firstNode, secondNode) => {
68
68
  const first = getSupportedDeclaration(sourceCode, firstNode);
69
69
  const second = getSupportedDeclaration(sourceCode, secondNode);
70
70
 
71
- if (!(
72
- first
71
+ if (!(first
73
72
  && second
74
73
  && first.node.kind === second.node.kind
75
74
  && first.source === second.source
76
75
  && sourceCode.getCommentsInside(first.node).length === 0
77
- && sourceCode.getCommentsInside(second.node).length === 0
78
- && !hasCommentsBetween(sourceCode, first.node, second.node)
79
- )) {
76
+ && sourceCode.getCommentsInside(second.node).length === 0) || hasCommentsBetween(sourceCode, first.node, second.node)) {
80
77
  return;
81
78
  }
82
79
 
@@ -21,8 +21,8 @@ const characterClassEscapes = new Map([
21
21
  ['\t', String.raw`\t`],
22
22
  ['\f', String.raw`\f`],
23
23
  ['\v', String.raw`\v`],
24
- ['\u2028', String.raw`\u2028`],
25
- ['\u2029', String.raw`\u2029`],
24
+ ['\u{2028}', String.raw`\u2028`],
25
+ ['\u{2029}', String.raw`\u2029`],
26
26
  ]);
27
27
 
28
28
  // Escapes a single character for use inside a character class. `^` and `-` are only special depending on position, so they are escaped only when necessary (avoids tripping `no-useless-escape` on the output).
@@ -18,6 +18,7 @@ import {
18
18
  isTypeParameterType,
19
19
  hasUnparenthesizedOptionalChainElement,
20
20
  isTypeScriptExpressionWrapper,
21
+ isDefaultLibrarySymbol,
21
22
  } from './utils/index.js';
22
23
  import {removeMethodCall} from './fix/index.js';
23
24
  import {
@@ -155,8 +156,8 @@ function fixConcat(node, context, fixableArguments) {
155
156
  const array = node.callee.object;
156
157
  const concatCallArguments = node.arguments;
157
158
  const arrayParenthesizedRange = getParenthesizedRange(array, context);
158
- const arrayIsArrayLiteral = isArrayLiteral(array);
159
- const arrayHasTrailingComma = arrayIsArrayLiteral && isArrayLiteralHasTrailingComma(array, sourceCode);
159
+ const isArrayIsArrayLiteral = isArrayLiteral(array);
160
+ const arrayHasTrailingComma = isArrayIsArrayLiteral && isArrayLiteralHasTrailingComma(array, sourceCode);
160
161
 
161
162
  const getArrayLiteralElementsText = (node, keepTrailingComma) => {
162
163
  if (
@@ -204,7 +205,7 @@ function fixConcat(node, context, fixableArguments) {
204
205
  return '';
205
206
  }
206
207
 
207
- if (arrayIsArrayLiteral) {
208
+ if (isArrayIsArrayLiteral) {
208
209
  if (!isEmptyArrayExpression(array)) {
209
210
  text = ` ${text}`;
210
211
 
@@ -243,7 +244,7 @@ function fixConcat(node, context, fixableArguments) {
243
244
  return function * (fixer) {
244
245
  // Fixed code always starts with `[`
245
246
  if (
246
- !arrayIsArrayLiteral
247
+ !isArrayIsArrayLiteral
247
248
  && needsSemicolon(sourceCode.getTokenBefore(node), context, '[')
248
249
  ) {
249
250
  yield fixer.insertTextBefore(node, ';');
@@ -257,7 +258,7 @@ function fixConcat(node, context, fixableArguments) {
257
258
 
258
259
  const text = getFixedText();
259
260
 
260
- if (arrayIsArrayLiteral) {
261
+ if (isArrayIsArrayLiteral) {
261
262
  const closingBracketToken = sourceCode.getLastToken(array);
262
263
  yield fixer.insertTextBefore(closingBracketToken, text);
263
264
  } else {
@@ -389,9 +390,6 @@ function isGlobalMemberExpression(node, objectName, propertyName, context) {
389
390
  return staticValue?.value === propertyName;
390
391
  }
391
392
 
392
- const isDefaultLibrarySymbol = (symbol, program) =>
393
- symbol?.declarations?.some(declaration => program.isSourceFileDefaultLibrary(declaration.getSourceFile())) ?? false;
394
-
395
393
  function isBufferModuleImport(definition) {
396
394
  return (
397
395
  definition?.type === 'ImportBinding'
@@ -1339,11 +1337,11 @@ const create = context => {
1339
1337
  };
1340
1338
 
1341
1339
  const fixableArguments = getConcatFixableArguments(node.arguments, scope, context);
1342
- const receiverSafeToSpread = !isArrayConstructorWithOneArgument(object, context);
1340
+ const isReceiverSafeToSpread = !isArrayConstructorWithOneArgument(object, context);
1343
1341
 
1344
1342
  if (fixableArguments.length > 0 || node.arguments.length === 0) {
1345
1343
  if (
1346
- receiverSafeToSpread
1344
+ isReceiverSafeToSpread
1347
1345
  && !hasCommentsOutsideRanges(node, getConcatPreservedRanges(node, fixableArguments.length))
1348
1346
  ) {
1349
1347
  problem.fix = fixConcat(node, context, fixableArguments);
@@ -1357,7 +1355,7 @@ const create = context => {
1357
1355
  return problem;
1358
1356
  }
1359
1357
 
1360
- if (!receiverSafeToSpread) {
1358
+ if (!isReceiverSafeToSpread) {
1361
1359
  return problem;
1362
1360
  }
1363
1361
 
@@ -1422,17 +1420,13 @@ const create = context => {
1422
1420
 
1423
1421
  // `array.slice()`
1424
1422
  context.on('CallExpression', node => {
1425
- if (!(
1426
- isMethodCall(node, {
1427
- method: 'slice',
1428
- minimumArguments: 0,
1429
- maximumArguments: 1,
1430
- optionalCall: false,
1431
- optionalMember: false,
1432
- })
1433
- && !isArrayLiteral(node.callee.object)
1434
- && !hasUnparenthesizedOptionalChainElement(node.callee.object, context)
1435
- )) {
1423
+ if (!isMethodCall(node, {
1424
+ method: 'slice',
1425
+ minimumArguments: 0,
1426
+ maximumArguments: 1,
1427
+ optionalCall: false,
1428
+ optionalMember: false,
1429
+ }) || isArrayLiteral(node.callee.object) || hasUnparenthesizedOptionalChainElement(node.callee.object, context)) {
1436
1430
  return;
1437
1431
  }
1438
1432
 
@@ -1465,16 +1459,12 @@ const create = context => {
1465
1459
 
1466
1460
  // `array.toSpliced()`
1467
1461
  context.on('CallExpression', node => {
1468
- if (!(
1469
- isMethodCall(node, {
1470
- method: 'toSpliced',
1471
- argumentsLength: 0,
1472
- optionalCall: false,
1473
- optionalMember: false,
1474
- })
1475
- && node.callee.object.type !== 'ArrayExpression'
1476
- && !hasUnparenthesizedOptionalChainElement(node.callee.object, context)
1477
- )) {
1462
+ if (!isMethodCall(node, {
1463
+ method: 'toSpliced',
1464
+ argumentsLength: 0,
1465
+ optionalCall: false,
1466
+ optionalMember: false,
1467
+ }) || node.callee.object.type === 'ArrayExpression' || hasUnparenthesizedOptionalChainElement(node.callee.object, context)) {
1478
1468
  return;
1479
1469
  }
1480
1470
 
@@ -1,5 +1,5 @@
1
1
  import {findVariable, getStaticValue} from '@eslint-community/eslint-utils';
2
- import {isMethodCall} from './ast/index.js';
2
+ import {isMethodCall, isNumericLiteral} from './ast/index.js';
3
3
  import {
4
4
  getParenthesizedText,
5
5
  isSameReference,
@@ -78,11 +78,6 @@ const hasCommentsInside = (node, context) => context.sourceCode.getCommentsInsid
78
78
 
79
79
  const isSimpleTarget = node => node.type === 'Identifier';
80
80
 
81
- const isNumericLiteral = node => (
82
- node.type === 'Literal'
83
- && typeof node.value === 'number'
84
- );
85
-
86
81
  const isZeroLiteral = node => (
87
82
  isNumericLiteral(node)
88
83
  && node.value === 0
@@ -1,4 +1,3 @@
1
- import jsesc from 'jsesc';
2
1
  import {isStringLiteral, isDirective} from './ast/index.js';
3
2
  import {fixSpaceAroundKeyword} from './fix/index.js';
4
3
  import isJestInlineSnapshot from './shared/is-jest-inline-snapshot.js';
@@ -10,15 +9,23 @@ const messages = {
10
9
 
11
10
  const singleWhitespace = /^\s$/u;
12
11
 
12
+ const namedEscapes = new Map([
13
+ ['\t', String.raw`\t`],
14
+ ['\n', String.raw`\n`],
15
+ ['\r', String.raw`\r`],
16
+ ['\f', String.raw`\f`],
17
+ ]);
18
+
19
+ // Escape a single whitespace character into a readable single-quoted string literal.
13
20
  function quoteWhitespace(character) {
14
- return jsesc(character, {
15
- quotes: 'single',
16
- wrap: true,
17
- es6: true,
18
- minimal: false,
19
- lowercaseHex: false,
20
- json: true,
21
- });
21
+ if (character === ' ') {
22
+ return '\' \'';
23
+ }
24
+
25
+ const escape = namedEscapes.get(character)
26
+ ?? String.raw`\u{${character.codePointAt(0).toString(16).toUpperCase()}}`;
27
+
28
+ return `'${escape}'`;
22
29
  }
23
30
 
24
31
  function getRepeatedWhitespace(value, minimumRepetitions) {
@@ -40,7 +40,7 @@ function hasSafeGlobalStringReplacementFlags(flags) {
40
40
  return true;
41
41
  }
42
42
 
43
- const getUnicodeEscape = codePoint => String.raw`\u{${codePoint.toString(16)}}`;
43
+ const getUnicodeEscape = codePoint => String.raw`\u{${codePoint.toString(16).toUpperCase()}}`;
44
44
 
45
45
  function getValueReplacement(node) {
46
46
  const {kind, codePoint, raw} = node;
@@ -1,4 +1,5 @@
1
1
  import {hasSideEffect, findVariable} from '@eslint-community/eslint-utils';
2
+ import {isBooleanLiteral} from './ast/index.js';
2
3
  import {
3
4
  needsSemicolon,
4
5
  isSameReference,
@@ -13,7 +14,6 @@ const messageId = 'prefer-ternary';
13
14
  const suggestionMessageId = 'prefer-ternary/suggestion';
14
15
 
15
16
  const isTernary = node => node?.type === 'ConditionalExpression';
16
- const isBooleanLiteral = node => node?.type === 'Literal' && typeof node.value === 'boolean';
17
17
 
18
18
  function getNodeBody(node) {
19
19
  /* c8 ignore next 3 */
@@ -57,7 +57,7 @@ const isMergeableAssignmentExpression = (consequent, alternate) =>
57
57
 
58
58
  /** @param {import('eslint').Rule.RuleContext} context */
59
59
  const create = context => {
60
- const onlySingleLine = context.options[0] === 'only-single-line';
60
+ const isOnlySingleLine = context.options[0] === 'only-single-line';
61
61
  const {sourceCode} = context;
62
62
 
63
63
  const getText = node => {
@@ -134,7 +134,7 @@ const create = context => {
134
134
  }
135
135
 
136
136
  if (
137
- onlySingleLine
137
+ isOnlySingleLine
138
138
  && [node.test, right].some(n => !isSingleLineNode(n, context))
139
139
  ) {
140
140
  return;
@@ -161,7 +161,7 @@ const create = context => {
161
161
  }
162
162
 
163
163
  if (
164
- onlySingleLine
164
+ isOnlySingleLine
165
165
  && !isSingleLineNode(declarator.init, context)
166
166
  ) {
167
167
  return;
@@ -245,7 +245,7 @@ const create = context => {
245
245
  const alternate = getNodeBody(node.alternate);
246
246
 
247
247
  if (
248
- onlySingleLine
248
+ isOnlySingleLine
249
249
  && [consequent, alternate, node.test].some(node => !isSingleLineNode(node, context))
250
250
  ) {
251
251
  return;