eslint-plugin-unicorn 65.0.1 → 67.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 (325) hide show
  1. package/configs/core-rule-replacements.js +9 -0
  2. package/index.js +11 -7
  3. package/package.json +34 -30
  4. package/readme.md +323 -212
  5. package/rules/ast/call-or-new-expression.js +1 -1
  6. package/rules/ast/index.js +4 -0
  7. package/rules/ast/is-loop.js +5 -0
  8. package/rules/ast/literal.js +18 -0
  9. package/rules/ast/loop-types.js +9 -0
  10. package/rules/better-dom-traversing.js +46 -16
  11. package/rules/catch-error-name.js +3 -0
  12. package/rules/class-reference-in-static-methods.js +451 -0
  13. package/rules/comment-content.js +1083 -0
  14. package/rules/consistent-assert.js +3 -0
  15. package/rules/consistent-boolean-name.js +493 -0
  16. package/rules/consistent-class-member-order.js +268 -0
  17. package/rules/consistent-compound-words.js +40 -26
  18. package/rules/consistent-date-clone.js +3 -0
  19. package/rules/consistent-destructuring.js +267 -143
  20. package/rules/consistent-empty-array-spread.js +3 -0
  21. package/rules/consistent-existence-index-check.js +3 -0
  22. package/rules/consistent-export-decorator-position.js +206 -0
  23. package/rules/consistent-function-scoping.js +79 -33
  24. package/rules/consistent-function-style.js +344 -0
  25. package/rules/consistent-json-file-read.js +4 -1
  26. package/rules/consistent-optional-chaining.js +203 -0
  27. package/rules/consistent-template-literal-escape.js +4 -1
  28. package/rules/custom-error-definition.js +222 -20
  29. package/rules/dom-node-dataset.js +234 -47
  30. package/rules/empty-brace-spaces.js +3 -0
  31. package/rules/error-message.js +3 -0
  32. package/rules/escape-case.js +3 -0
  33. package/rules/expiring-todo-comments.js +118 -19
  34. package/rules/explicit-length-check.js +20 -26
  35. package/rules/explicit-timer-delay.js +156 -0
  36. package/rules/filename-case.js +101 -12
  37. package/rules/fix/index.js +3 -2
  38. package/rules/fix/remove-argument.js +18 -5
  39. package/rules/fix/remove-spaces-after.js +3 -4
  40. package/rules/fix/{remove-expression-statement.js → remove-statement.js} +20 -7
  41. package/rules/fix/replace-node-or-token-and-spaces-before.js +1 -1
  42. package/rules/fix/replace-node-with-expression.js +47 -0
  43. package/rules/fix/replace-reference-identifier.js +4 -1
  44. package/rules/id-match.js +77 -0
  45. package/rules/import-style.js +25 -29
  46. package/rules/index.js +91 -2
  47. package/rules/isolated-functions.js +7 -4
  48. package/rules/logical-assignment-operators.js +112 -0
  49. package/rules/max-nested-calls.js +102 -0
  50. package/rules/new-for-builtins.js +14 -7
  51. package/rules/no-abusive-eslint-disable.js +5 -1
  52. package/rules/no-accessor-recursion.js +3 -0
  53. package/rules/no-anonymous-default-export.js +7 -3
  54. package/rules/no-array-callback-reference.js +148 -2
  55. package/rules/no-array-fill-with-reference-type.js +4 -13
  56. package/rules/no-array-from-fill.js +3 -0
  57. package/rules/no-array-method-this-argument.js +45 -20
  58. package/rules/no-array-reduce.js +82 -9
  59. package/rules/no-array-splice.js +232 -0
  60. package/rules/no-asterisk-prefix-in-documentation-comments.js +89 -0
  61. package/rules/no-await-expression-member.js +3 -0
  62. package/rules/no-await-in-promise-methods.js +3 -0
  63. package/rules/no-blob-to-file.js +9 -44
  64. package/rules/no-break-in-nested-loop.js +97 -0
  65. package/rules/no-canvas-to-image.js +3 -0
  66. package/rules/no-computed-property-existence-check.js +164 -0
  67. package/rules/no-confusing-array-splice.js +41 -13
  68. package/rules/no-confusing-array-with.js +103 -0
  69. package/rules/no-console-spaces.js +3 -0
  70. package/rules/no-declarations-before-early-exit.js +276 -0
  71. package/rules/no-document-cookie.js +3 -0
  72. package/rules/no-duplicate-loops.js +60 -0
  73. package/rules/no-duplicate-set-values.js +3 -0
  74. package/rules/no-empty-file.js +109 -3
  75. package/rules/no-error-property-assignment.js +440 -0
  76. package/rules/no-exports-in-scripts.js +3 -0
  77. package/rules/{no-array-for-each.js → no-for-each.js} +21 -11
  78. package/rules/no-for-loop.js +557 -96
  79. package/rules/no-global-object-property-assignment.js +89 -0
  80. package/rules/no-immediate-mutation.js +5 -2
  81. package/rules/no-incorrect-query-selector.js +8 -36
  82. package/rules/no-incorrect-template-string-interpolation.js +93 -0
  83. package/rules/no-instanceof-builtins.js +4 -1
  84. package/rules/no-invalid-argument-count.js +1345 -0
  85. package/rules/no-invalid-fetch-options.js +6 -3
  86. package/rules/no-invalid-file-input-accept.js +12 -5
  87. package/rules/no-invalid-remove-event-listener.js +3 -0
  88. package/rules/no-keyword-prefix.js +27 -22
  89. package/rules/no-late-current-target-access.js +3 -0
  90. package/rules/no-lonely-if.js +3 -0
  91. package/rules/no-magic-array-flat-depth.js +3 -0
  92. package/rules/no-manually-wrapped-comments.js +20 -9
  93. package/rules/no-mismatched-map-key.js +520 -0
  94. package/rules/no-named-default.js +3 -0
  95. package/rules/no-negated-array-predicate.js +212 -0
  96. package/rules/no-negated-comparison.js +347 -0
  97. package/rules/no-negated-condition.js +3 -0
  98. package/rules/no-negation-in-equality-check.js +3 -0
  99. package/rules/no-nested-ternary.js +3 -0
  100. package/rules/no-new-array.js +3 -0
  101. package/rules/no-new-buffer.js +3 -0
  102. package/rules/no-non-function-verb-prefix.js +324 -0
  103. package/rules/no-null.js +3 -0
  104. package/rules/no-object-as-default-parameter.js +3 -0
  105. package/rules/no-object-methods-with-collections.js +381 -0
  106. package/rules/no-optional-chaining-on-undeclared-variable.js +135 -0
  107. package/rules/no-process-exit.js +3 -0
  108. package/rules/no-redundant-comparison.js +305 -0
  109. package/rules/no-return-array-push.js +164 -0
  110. package/rules/no-single-promise-in-promise-methods.js +95 -5
  111. package/rules/no-static-only-class.js +3 -0
  112. package/rules/no-subtraction-comparison.js +111 -0
  113. package/rules/no-thenable.js +3 -0
  114. package/rules/no-this-assignment.js +3 -0
  115. package/rules/no-this-outside-of-class.js +3 -0
  116. package/rules/no-top-level-assignment-in-function.js +76 -0
  117. package/rules/no-top-level-side-effects.js +74 -0
  118. package/rules/no-typeof-undefined.js +5 -3
  119. package/rules/no-uncalled-method.js +308 -0
  120. package/rules/no-undeclared-class-members.js +349 -0
  121. package/rules/no-unnecessary-array-flat-depth.js +3 -0
  122. package/rules/no-unnecessary-array-splice-count.js +3 -0
  123. package/rules/no-unnecessary-await.js +3 -0
  124. package/rules/no-unnecessary-global-this.js +176 -0
  125. package/rules/no-unnecessary-nested-ternary.js +3 -0
  126. package/rules/no-unnecessary-polyfills.js +29 -11
  127. package/rules/no-unnecessary-slice-end.js +3 -0
  128. package/rules/no-unnecessary-splice.js +157 -0
  129. package/rules/no-unreadable-array-destructuring.js +99 -9
  130. package/rules/no-unreadable-for-of-expression.js +130 -0
  131. package/rules/no-unreadable-iife.js +3 -0
  132. package/rules/no-unreadable-new-expression.js +63 -0
  133. package/rules/no-unreadable-object-destructuring.js +140 -0
  134. package/rules/no-unsafe-buffer-conversion.js +532 -0
  135. package/rules/no-unsafe-dom-html.js +172 -0
  136. package/rules/no-unsafe-property-key.js +584 -0
  137. package/rules/no-unsafe-string-replacement.js +78 -0
  138. package/rules/no-unused-array-method-return.js +4 -1
  139. package/rules/no-unused-properties.js +71 -35
  140. package/rules/no-useless-boolean-cast.js +120 -0
  141. package/rules/no-useless-coercion.js +91 -0
  142. package/rules/no-useless-collection-argument.js +3 -0
  143. package/rules/no-useless-concat.js +144 -0
  144. package/rules/no-useless-continue.js +81 -0
  145. package/rules/no-useless-else.js +269 -0
  146. package/rules/no-useless-error-capture-stack-trace.js +3 -0
  147. package/rules/no-useless-fallback-in-spread.js +125 -1
  148. package/rules/no-useless-iterator-to-array.js +3 -0
  149. package/rules/no-useless-length-check.js +20 -8
  150. package/rules/no-useless-override.js +217 -0
  151. package/rules/no-useless-promise-resolve-reject.js +6 -1
  152. package/rules/no-useless-recursion.js +118 -0
  153. package/rules/no-useless-spread.js +189 -73
  154. package/rules/no-useless-switch-case.js +12 -1
  155. package/rules/no-useless-template-literals.js +370 -0
  156. package/rules/no-useless-undefined.js +254 -21
  157. package/rules/no-zero-fractions.js +3 -0
  158. package/rules/number-literal-case.js +3 -0
  159. package/rules/numeric-separators-style.js +12 -11
  160. package/rules/operator-assignment.js +111 -0
  161. package/rules/prefer-add-event-listener-options.js +54 -0
  162. package/rules/prefer-add-event-listener.js +3 -0
  163. package/rules/prefer-array-find.js +16 -11
  164. package/rules/prefer-array-flat-map.js +180 -12
  165. package/rules/prefer-array-flat.js +16 -11
  166. package/rules/prefer-array-from-map.js +465 -0
  167. package/rules/prefer-array-index-of.js +3 -0
  168. package/rules/prefer-array-last-methods.js +4 -1
  169. package/rules/prefer-array-slice.js +97 -0
  170. package/rules/prefer-array-some.js +24 -4
  171. package/rules/prefer-at.js +94 -36
  172. package/rules/prefer-await.js +69 -0
  173. package/rules/prefer-bigint-literals.js +4 -0
  174. package/rules/prefer-blob-reading-methods.js +3 -0
  175. package/rules/prefer-class-fields.js +3 -0
  176. package/rules/prefer-classlist-toggle.js +5 -2
  177. package/rules/prefer-code-point.js +3 -0
  178. package/rules/prefer-date-now.js +3 -0
  179. package/rules/prefer-default-parameters.js +7 -4
  180. package/rules/prefer-direct-iteration.js +424 -0
  181. package/rules/prefer-dispose.js +346 -0
  182. package/rules/prefer-dom-node-append.js +3 -0
  183. package/rules/prefer-dom-node-html-methods.js +136 -0
  184. package/rules/prefer-dom-node-remove.js +3 -0
  185. package/rules/prefer-dom-node-text-content.js +26 -0
  186. package/rules/prefer-early-return.js +267 -0
  187. package/rules/prefer-else-if.js +524 -0
  188. package/rules/prefer-event-target.js +3 -0
  189. package/rules/prefer-export-from.js +5 -3
  190. package/rules/prefer-get-or-insert-computed.js +3 -0
  191. package/rules/prefer-global-number-constants.js +86 -0
  192. package/rules/prefer-global-this.js +35 -19
  193. package/rules/prefer-has-check.js +946 -0
  194. package/rules/prefer-https.js +55 -11
  195. package/rules/prefer-identifier-import-export-specifiers.js +94 -0
  196. package/rules/prefer-import-meta-properties.js +13 -10
  197. package/rules/prefer-includes-over-repeated-comparisons.js +10 -71
  198. package/rules/prefer-includes.js +4 -0
  199. package/rules/prefer-iterable-in-constructor.js +346 -0
  200. package/rules/prefer-iterator-concat.js +3 -0
  201. package/rules/prefer-iterator-to-array-at-end.js +3 -0
  202. package/rules/prefer-iterator-to-array.js +210 -0
  203. package/rules/prefer-keyboard-event-key.js +7 -3
  204. package/rules/prefer-location-assign.js +145 -0
  205. package/rules/prefer-logical-operator-over-ternary.js +253 -2
  206. package/rules/prefer-map-from-entries.js +482 -0
  207. package/rules/prefer-math-abs.js +3 -0
  208. package/rules/prefer-math-min-max.js +3 -0
  209. package/rules/prefer-math-trunc.js +100 -2
  210. package/rules/prefer-minimal-ternary.js +243 -0
  211. package/rules/prefer-modern-dom-apis.js +181 -16
  212. package/rules/prefer-modern-math-apis.js +3 -0
  213. package/rules/prefer-module.js +5 -1
  214. package/rules/prefer-native-coercion-functions.js +5 -4
  215. package/rules/prefer-negative-index.js +4 -12
  216. package/rules/prefer-node-protocol.js +3 -0
  217. package/rules/prefer-number-coercion.js +157 -0
  218. package/rules/prefer-number-is-safe-integer.js +193 -0
  219. package/rules/prefer-number-properties.js +71 -9
  220. package/rules/prefer-object-define-properties.js +208 -0
  221. package/rules/prefer-object-destructuring-defaults.js +177 -0
  222. package/rules/prefer-object-from-entries.js +141 -2
  223. package/rules/prefer-object-iterable-methods.js +609 -0
  224. package/rules/prefer-optional-catch-binding.js +3 -0
  225. package/rules/prefer-path2d.js +842 -0
  226. package/rules/prefer-private-class-fields.js +503 -0
  227. package/rules/prefer-prototype-methods.js +3 -0
  228. package/rules/prefer-query-selector.js +74 -32
  229. package/rules/prefer-queue-microtask.js +7 -12
  230. package/rules/prefer-reflect-apply.js +3 -0
  231. package/rules/prefer-regexp-test.js +489 -84
  232. package/rules/prefer-response-static-json.js +3 -0
  233. package/rules/prefer-scoped-selector.js +189 -0
  234. package/rules/prefer-set-has.js +8 -14
  235. package/rules/prefer-set-size.js +6 -34
  236. package/rules/prefer-short-arrow-method.js +138 -0
  237. package/rules/prefer-simple-condition-first.js +5 -4
  238. package/rules/prefer-simple-sort-comparator.js +270 -0
  239. package/rules/prefer-single-array-predicate.js +209 -0
  240. package/rules/prefer-single-call.js +3 -0
  241. package/rules/prefer-single-object-destructuring.js +145 -0
  242. package/rules/prefer-smaller-scope.js +238 -0
  243. package/rules/prefer-split-limit.js +4 -1
  244. package/rules/prefer-spread.js +915 -86
  245. package/rules/prefer-string-match-all.js +4 -1
  246. package/rules/prefer-string-pad-start-end.js +4 -3
  247. package/rules/prefer-string-raw.js +4 -1
  248. package/rules/prefer-string-repeat.js +3 -0
  249. package/rules/prefer-string-replace-all.js +131 -65
  250. package/rules/prefer-string-slice.js +21 -5
  251. package/rules/prefer-string-starts-ends-with.js +84 -11
  252. package/rules/prefer-string-trim-start-end.js +9 -0
  253. package/rules/prefer-structured-clone.js +3 -0
  254. package/rules/prefer-switch.js +9 -5
  255. package/rules/prefer-temporal.js +254 -0
  256. package/rules/prefer-ternary.js +146 -111
  257. package/rules/prefer-top-level-await.js +3 -0
  258. package/rules/prefer-type-error.js +31 -9
  259. package/rules/prefer-type-literal-last.js +139 -0
  260. package/rules/prefer-uint8array-base64.js +165 -0
  261. package/rules/prefer-unicode-code-point-escapes.js +400 -0
  262. package/rules/prefer-url-href.js +592 -0
  263. package/rules/prevent-abbreviations.js +28 -24
  264. package/rules/relative-url-style.js +6 -1
  265. package/rules/require-array-join-separator.js +9 -1
  266. package/rules/require-array-sort-compare.js +88 -0
  267. package/rules/require-css-escape.js +3 -0
  268. package/rules/require-module-attributes.js +3 -0
  269. package/rules/require-module-specifiers.js +4 -2
  270. package/rules/require-number-to-fixed-digits-argument.js +3 -0
  271. package/rules/require-passive-events.js +4 -5
  272. package/rules/require-post-message-target-origin.js +3 -0
  273. package/rules/require-proxy-trap-boolean-return.js +415 -0
  274. package/rules/rule/to-eslint-rule.js +1 -1
  275. package/rules/shared/abbreviations.js +2 -0
  276. package/rules/shared/iterator-helpers.js +104 -0
  277. package/rules/shared/negative-index.js +13 -4
  278. package/rules/shared/no-array-mutate-rule.js +9 -1
  279. package/rules/shared/simple-array-search-rule.js +135 -3
  280. package/rules/shared/splice-replacements.js +149 -0
  281. package/rules/shared/substring.js +69 -0
  282. package/rules/string-content.js +7 -3
  283. package/rules/switch-case-braces.js +102 -11
  284. package/rules/switch-case-break-position.js +5 -3
  285. package/rules/template-indent.js +3 -0
  286. package/rules/text-encoding-identifier-case.js +77 -0
  287. package/rules/throw-new-error.js +3 -0
  288. package/rules/try-complexity.js +3 -0
  289. package/rules/utils/boolean.js +27 -11
  290. package/rules/utils/cartesian-product-samples.js +1 -1
  291. package/rules/utils/comments.js +47 -0
  292. package/rules/utils/comparison.js +80 -0
  293. package/rules/utils/escape-template-element-raw.js +1 -1
  294. package/rules/utils/get-available-variable-name.js +1 -1
  295. package/rules/utils/get-comments.js +15 -0
  296. package/rules/utils/has-unsafe-arrow-conversion-reference.js +46 -0
  297. package/rules/utils/imports.js +11 -0
  298. package/rules/utils/index.js +43 -1
  299. package/rules/utils/is-array.js +79 -0
  300. package/rules/utils/is-bigint.js +86 -0
  301. package/rules/utils/is-boolean.js +564 -0
  302. package/rules/utils/is-dom-node.js +53 -0
  303. package/rules/utils/is-global-identifier.js +13 -0
  304. package/rules/utils/is-left-hand-side.js +1 -0
  305. package/rules/utils/is-map.js +22 -0
  306. package/rules/utils/is-number.js +51 -14
  307. package/rules/utils/is-promise-type.js +28 -0
  308. package/rules/utils/is-reg-exp.js +24 -0
  309. package/rules/utils/is-same-reference.js +2 -1
  310. package/rules/utils/is-set.js +22 -0
  311. package/rules/utils/is-string.js +119 -0
  312. package/rules/utils/is-strong-precedence-node.js +32 -0
  313. package/rules/utils/is-typescript-file.js +9 -0
  314. package/rules/utils/is-virtual-filename.js +9 -0
  315. package/rules/utils/normalize-comment.js +38 -0
  316. package/rules/utils/numeric.js +1 -1
  317. package/rules/utils/on-root.js +31 -0
  318. package/rules/utils/rule.js +2 -6
  319. package/rules/utils/scope.js +22 -0
  320. package/rules/utils/should-skip-known-non-array-receiver.js +13 -0
  321. package/rules/utils/type-check.js +20 -0
  322. package/rules/utils/type-helpers.js +595 -0
  323. package/rules/utils/types.js +27 -0
  324. package/rules/utils/unwrap-typescript-expression.js +16 -0
  325. package/rules/no-hex-escape.js +0 -55
@@ -12,11 +12,14 @@ import {
12
12
  needsSemicolon,
13
13
  shouldAddParenthesesToMemberExpressionObject,
14
14
  isLeftHandSide,
15
+ isString,
16
+ unwrapTypeScriptExpression as unwrapExpression,
15
17
  } from './utils/index.js';
16
18
  import {
17
19
  getNegativeIndexLengthNode,
18
20
  removeLengthNode,
19
21
  } from './shared/negative-index.js';
22
+ import {getSubstringSingleCharacterIndex} from './shared/substring.js';
20
23
  import {removeMemberExpressionProperty, removeMethodCall} from './fix/index.js';
21
24
  import {
22
25
  isLiteral,
@@ -30,6 +33,7 @@ const MESSAGE_ID_NEGATIVE_INDEX = 'negative-index';
30
33
  const MESSAGE_ID_INDEX = 'index';
31
34
  const MESSAGE_ID_STRING_CHAR_AT_NEGATIVE = 'string-char-at-negative';
32
35
  const MESSAGE_ID_STRING_CHAR_AT = 'string-char-at';
36
+ const MESSAGE_ID_STRING_SUBSTRING = 'string-substring';
33
37
  const MESSAGE_ID_SLICE = 'slice';
34
38
  const MESSAGE_ID_GET_LAST_FUNCTION = 'get-last-function';
35
39
  const SUGGESTION_ID = 'use-at';
@@ -38,6 +42,7 @@ const messages = {
38
42
  [MESSAGE_ID_INDEX]: 'Prefer `.at(…)` over index access.',
39
43
  [MESSAGE_ID_STRING_CHAR_AT_NEGATIVE]: 'Prefer `String#at(…)` over `String#charAt(….length - index)`.',
40
44
  [MESSAGE_ID_STRING_CHAR_AT]: 'Prefer `String#at(…)` over `String#charAt(…)`.',
45
+ [MESSAGE_ID_STRING_SUBSTRING]: 'Prefer `String#at(…)` over `String#substring(…)` when getting one character.',
41
46
  [MESSAGE_ID_SLICE]: 'Prefer `.at(…)` over the first element from `.slice(…)`.',
42
47
  [MESSAGE_ID_GET_LAST_FUNCTION]: 'Prefer `.at(-1)` over `{{description}}(…)` to get the last element.',
43
48
  [SUGGESTION_ID]: 'Use `.at(…)`.',
@@ -45,21 +50,6 @@ const messages = {
45
50
 
46
51
  const isArguments = node => node.type === 'Identifier' && node.name === 'arguments';
47
52
 
48
- function unwrapExpression(node) {
49
- if (
50
- [
51
- 'TSAsExpression',
52
- 'TSTypeAssertion',
53
- 'TSNonNullExpression',
54
- 'TSSatisfiesExpression',
55
- ].includes(node.type)
56
- ) {
57
- return unwrapExpression(node.expression);
58
- }
59
-
60
- return node;
61
- }
62
-
63
53
  const isUnsupportedAtReceiverExpression = node => {
64
54
  node = unwrapExpression(node);
65
55
 
@@ -109,7 +99,7 @@ const isLiteralNegativeInteger = node =>
109
99
  && node.prefix
110
100
  && node.operator === '-'
111
101
  && node.argument.type === 'Literal'
112
- && Number.isInteger(node.argument.value)
102
+ && Number.isSafeInteger(node.argument.value)
113
103
  && node.argument.value > 0;
114
104
  const isZeroIndexAccess = node =>
115
105
  isMemberExpression(node.parent, {
@@ -131,7 +121,21 @@ const isArrayPopOrShiftCall = (node, method) =>
131
121
  const isArrayPopCall = node => isArrayPopOrShiftCall(node, 'pop');
132
122
  const isArrayShiftCall = node => isArrayPopOrShiftCall(node, 'shift');
133
123
 
134
- function checkSliceCall(node) {
124
+ function getFirstElementGetMethod(node) {
125
+ if (isZeroIndexAccess(node)) {
126
+ return isLeftHandSide(node.parent) ? undefined : 'zero-index';
127
+ }
128
+
129
+ if (isArrayShiftCall(node)) {
130
+ return 'shift';
131
+ }
132
+
133
+ if (isArrayPopCall(node)) {
134
+ return 'pop';
135
+ }
136
+ }
137
+
138
+ function getSliceCallResult(node) {
135
139
  const sliceArgumentsLength = node.arguments.length;
136
140
  const [startIndexNode, endIndexNode] = node.arguments;
137
141
 
@@ -139,19 +143,7 @@ function checkSliceCall(node) {
139
143
  return;
140
144
  }
141
145
 
142
- let firstElementGetMethod = '';
143
- if (isZeroIndexAccess(node)) {
144
- if (isLeftHandSide(node.parent)) {
145
- return;
146
- }
147
-
148
- firstElementGetMethod = 'zero-index';
149
- } else if (isArrayShiftCall(node)) {
150
- firstElementGetMethod = 'shift';
151
- } else if (isArrayPopCall(node)) {
152
- firstElementGetMethod = 'pop';
153
- }
154
-
146
+ const firstElementGetMethod = getFirstElementGetMethod(node);
155
147
  if (!firstElementGetMethod) {
156
148
  return;
157
149
  }
@@ -192,6 +184,27 @@ const lodashLastFunctions = [
192
184
  'underscore.last',
193
185
  ];
194
186
 
187
+ // TODO: Remove this once DOM collections like NodeList and HTMLCollection reliably support `.at()` in browsers.
188
+ const domCollectionProperties = [
189
+ 'childNodes',
190
+ 'children',
191
+ ];
192
+
193
+ const domCollectionMethods = [
194
+ 'getElementsByClassName',
195
+ 'getElementsByName',
196
+ 'getElementsByTagName',
197
+ 'getElementsByTagNameNS',
198
+ 'querySelectorAll',
199
+ ];
200
+
201
+ const isDomCollectionReceiver = node => {
202
+ node = unwrapExpression(node);
203
+
204
+ return isMemberExpression(node, {properties: domCollectionProperties})
205
+ || isMethodCall(node, {methods: domCollectionMethods});
206
+ };
207
+
195
208
  /** @param {import('eslint').Rule.RuleContext} context */
196
209
  function create(context) {
197
210
  const {
@@ -210,17 +223,21 @@ function create(context) {
210
223
  return;
211
224
  }
212
225
 
226
+ if (isDomCollectionReceiver(node.object)) {
227
+ return;
228
+ }
229
+
213
230
  const indexNode = node.property;
214
231
  const lengthNode = getNegativeIndexLengthNode(indexNode, node.object);
215
232
 
216
233
  if (!lengthNode) {
217
- if (!checkAllIndexAccess) {
234
+ // Only if we are sure it's a positive integer
235
+ const staticValue = getStaticValue(indexNode, sourceCode.getScope(indexNode));
236
+ if (!staticValue || !Number.isSafeInteger(staticValue.value) || staticValue.value < 0) {
218
237
  return;
219
238
  }
220
239
 
221
- // Only if we are sure it's a positive integer
222
- const staticValue = getStaticValue(indexNode, sourceCode.getScope(indexNode));
223
- if (!staticValue || !Number.isInteger(staticValue.value) || staticValue.value < 0) {
240
+ if (!checkAllIndexAccess && !isString(node.object, context)) {
224
241
  return;
225
242
  }
226
243
 
@@ -304,6 +321,44 @@ function create(context) {
304
321
  };
305
322
  });
306
323
 
324
+ // `string.substring(index, index + 1)`
325
+ context.on('CallExpression', node => {
326
+ const indexNode = getSubstringSingleCharacterIndex(node);
327
+ if (!indexNode) {
328
+ return;
329
+ }
330
+
331
+ const problem = {
332
+ node,
333
+ messageId: MESSAGE_ID_STRING_SUBSTRING,
334
+ };
335
+
336
+ const [firstArgument, secondArgument] = node.arguments;
337
+ const replacementRange = [
338
+ getParenthesizedRange(firstArgument, context)[0],
339
+ getParenthesizedRange(secondArgument, context)[1],
340
+ ];
341
+ const hasCommentsInsideReplacementRange = sourceCode.getAllComments().some(comment => {
342
+ const commentRange = sourceCode.getRange(comment);
343
+ return commentRange[0] >= replacementRange[0] && commentRange[1] <= replacementRange[1];
344
+ });
345
+ if (hasCommentsInsideReplacementRange) {
346
+ return problem;
347
+ }
348
+
349
+ return {
350
+ ...problem,
351
+ suggest: [{
352
+ messageId: SUGGESTION_ID,
353
+ * fix(fixer) {
354
+ yield fixer.replaceText(node.callee.property, 'at');
355
+
356
+ yield fixer.replaceTextRange(replacementRange, getParenthesizedText(indexNode, context));
357
+ },
358
+ }],
359
+ };
360
+ });
361
+
307
362
  // `.slice()`
308
363
  context.on('CallExpression', sliceCall => {
309
364
  if (!isMethodCall(sliceCall, {
@@ -315,7 +370,7 @@ function create(context) {
315
370
  return;
316
371
  }
317
372
 
318
- const result = checkSliceCall(sliceCall);
373
+ const result = getSliceCallResult(sliceCall);
319
374
  if (!result) {
320
375
  return;
321
376
  }
@@ -386,7 +441,7 @@ function create(context) {
386
441
  fixed = `(${fixed})`;
387
442
  }
388
443
 
389
- fixed = `${fixed}.at(-1)`;
444
+ fixed += '.at(-1)';
390
445
 
391
446
  const tokenBefore = sourceCode.getTokenBefore(node);
392
447
  if (needsSemicolon(tokenBefore, context, fixed)) {
@@ -431,6 +486,9 @@ const config = {
431
486
  schema,
432
487
  defaultOptions: [{getLastElementFunctions: [], checkAllIndexAccess: false}],
433
488
  messages,
489
+ languages: [
490
+ 'js/js',
491
+ ],
434
492
  },
435
493
  };
436
494
 
@@ -0,0 +1,69 @@
1
+ import {getPropertyName} from '@eslint-community/eslint-utils';
2
+ import {isPromiseType} from './utils/index.js';
3
+
4
+ const MESSAGE_ID = 'prefer-await';
5
+ const messages = {
6
+ [MESSAGE_ID]: 'Prefer `await` over promise chaining with `.{{method}}()`.',
7
+ };
8
+
9
+ const promiseMethods = new Set(['then', 'catch', 'finally']);
10
+
11
+ function isPromiseObject(node, context) {
12
+ const {parserServices} = context.sourceCode;
13
+ if (!parserServices?.program) {
14
+ return;
15
+ }
16
+
17
+ try {
18
+ return isPromiseType(
19
+ parserServices.getTypeAtLocation(node),
20
+ parserServices.program.getTypeChecker(),
21
+ );
22
+ } catch {
23
+ // TypeScript can throw while resolving incomplete projects; keep this rule best-effort.
24
+ }
25
+ }
26
+
27
+ /** @param {import('eslint').Rule.RuleContext} context */
28
+ const create = context => {
29
+ context.on('CallExpression', callExpression => {
30
+ const {callee} = callExpression;
31
+ if (callee.type !== 'MemberExpression') {
32
+ return;
33
+ }
34
+
35
+ const method = getPropertyName(callee, context.sourceCode.getScope(callExpression));
36
+ if (!promiseMethods.has(method)) {
37
+ return;
38
+ }
39
+
40
+ const promiseObject = isPromiseObject(callee.object, context);
41
+ if (promiseObject === false) {
42
+ return;
43
+ }
44
+
45
+ return {
46
+ node: callee.property,
47
+ messageId: MESSAGE_ID,
48
+ data: {method},
49
+ };
50
+ });
51
+ };
52
+
53
+ /** @type {import('eslint').Rule.RuleModule} */
54
+ const config = {
55
+ create,
56
+ meta: {
57
+ type: 'suggestion',
58
+ docs: {
59
+ description: 'Prefer `await` over promise chaining.',
60
+ recommended: 'unopinionated',
61
+ },
62
+ messages,
63
+ languages: [
64
+ 'js/js',
65
+ ],
66
+ },
67
+ };
68
+
69
+ export default config;
@@ -74,6 +74,7 @@ function getReplacement(valueNode) {
74
74
 
75
75
  const {value, raw} = valueNode;
76
76
 
77
+ // Intentionally `Number.isInteger`, not `Number.isSafeInteger`: large integers beyond the safe range are exactly what bigint literals are for.
77
78
  if (!Number.isInteger(value)) {
78
79
  return;
79
80
  }
@@ -171,6 +172,9 @@ const config = {
171
172
  fixable: 'code',
172
173
  hasSuggestions: true,
173
174
  messages,
175
+ languages: [
176
+ 'js/js',
177
+ ],
174
178
  },
175
179
  };
176
180
 
@@ -41,6 +41,9 @@ const config = {
41
41
  recommended: 'unopinionated',
42
42
  },
43
43
  messages,
44
+ languages: [
45
+ 'js/js',
46
+ ],
44
47
  },
45
48
  };
46
49
 
@@ -148,6 +148,9 @@ const config = {
148
148
  fixable: 'code',
149
149
  hasSuggestions: true,
150
150
  messages,
151
+ languages: [
152
+ 'js/js',
153
+ ],
151
154
  },
152
155
  };
153
156
 
@@ -155,7 +155,7 @@ const create = context => {
155
155
 
156
156
  // `element.classList.add('className');`
157
157
  // `element.classList.remove('className');`
158
- if (!clauses.every(node => isClassListMethodCall(node, ['add', 'remove']))) {
158
+ if (clauses.some(node => !isClassListMethodCall(node, ['add', 'remove']))) {
159
159
  return;
160
160
  }
161
161
 
@@ -181,7 +181,7 @@ const create = context => {
181
181
  let text = `${elementText}${isOptional ? '?' : ''}.classList.toggle(${classNameText}${conditionText ? `, ${conditionText}` : ''})`;
182
182
 
183
183
  if (!isExpression) {
184
- text = `${text};`;
184
+ text += ';';
185
185
  }
186
186
 
187
187
  if (needsSemicolon(sourceCode.getTokenBefore(node), context, text)) {
@@ -249,6 +249,9 @@ const config = {
249
249
  fixable: 'code',
250
250
  hasSuggestions: true,
251
251
  messages,
252
+ languages: [
253
+ 'js/js',
254
+ ],
252
255
  },
253
256
  };
254
257
 
@@ -51,6 +51,9 @@ const config = {
51
51
  },
52
52
  hasSuggestions: true,
53
53
  messages,
54
+ languages: [
55
+ 'js/js',
56
+ ],
54
57
  },
55
58
  };
56
59
 
@@ -130,6 +130,9 @@ const config = {
130
130
  },
131
131
  fixable: 'code',
132
132
  messages,
133
+ languages: [
134
+ 'js/js',
135
+ ],
133
136
  },
134
137
  };
135
138
 
@@ -96,7 +96,6 @@ const fixDefaultExpression = (fixer, sourceCode, node) => {
96
96
  const nodeText = sourceCode.getText(node);
97
97
  const lineText = sourceCode.lines[line - 1];
98
98
  const isOnlyNodeOnLine = lineText.trim() === nodeText;
99
- const endsWithWhitespace = lineText[column] === ' ';
100
99
 
101
100
  if (isOnlyNodeOnLine) {
102
101
  return fixer.removeRange([
@@ -105,6 +104,7 @@ const fixDefaultExpression = (fixer, sourceCode, node) => {
105
104
  ]);
106
105
  }
107
106
 
107
+ const endsWithWhitespace = lineText[column] === ' ';
108
108
  if (endsWithWhitespace) {
109
109
  const [start, end] = sourceCode.getRange(node);
110
110
  return fixer.removeRange([start, end + 1]);
@@ -118,7 +118,7 @@ const create = context => {
118
118
  const {sourceCode} = context;
119
119
  const functionStack = [];
120
120
 
121
- const checkExpression = (node, left, right, assignment) => {
121
+ const getDefaultParameterProblem = (node, left, right, assignment) => {
122
122
  const currentFunction = functionStack.at(-1);
123
123
 
124
124
  if (!currentFunction || !isDefaultExpression(left, right)) {
@@ -186,13 +186,13 @@ const create = context => {
186
186
 
187
187
  context.on('AssignmentExpression', node => {
188
188
  if (node.parent.type === 'ExpressionStatement' && node.parent.expression === node) {
189
- return checkExpression(node.parent, node.left, node.right, true);
189
+ return getDefaultParameterProblem(node.parent, node.left, node.right, true);
190
190
  }
191
191
  });
192
192
 
193
193
  context.on('VariableDeclarator', node => {
194
194
  if (node.parent.type === 'VariableDeclaration' && node.parent.declarations[0] === node) {
195
- return checkExpression(node.parent, node.id, node.init, false);
195
+ return getDefaultParameterProblem(node.parent, node.id, node.init, false);
196
196
  }
197
197
  });
198
198
  };
@@ -211,6 +211,9 @@ const config = {
211
211
  [MESSAGE_ID]: 'Prefer default parameters over reassignment.',
212
212
  [MESSAGE_ID_SUGGEST]: 'Replace reassignment with default parameter.',
213
213
  },
214
+ languages: [
215
+ 'js/js',
216
+ ],
214
217
  },
215
218
  };
216
219