eslint 9.21.0 → 9.23.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 (425) hide show
  1. package/README.md +52 -48
  2. package/bin/eslint.js +92 -90
  3. package/conf/default-cli-options.js +22 -22
  4. package/conf/ecma-version.js +1 -1
  5. package/conf/globals.js +97 -98
  6. package/conf/replacements.json +24 -20
  7. package/conf/rule-type-list.json +88 -92
  8. package/lib/api.js +12 -12
  9. package/lib/cli-engine/cli-engine.js +828 -808
  10. package/lib/cli-engine/file-enumerator.js +381 -387
  11. package/lib/cli-engine/formatters/formatters-meta.json +16 -16
  12. package/lib/cli-engine/formatters/html.js +107 -99
  13. package/lib/cli-engine/formatters/json-with-metadata.js +5 -5
  14. package/lib/cli-engine/formatters/json.js +2 -2
  15. package/lib/cli-engine/formatters/stylish.js +96 -75
  16. package/lib/cli-engine/hash.js +1 -1
  17. package/lib/cli-engine/index.js +1 -1
  18. package/lib/cli-engine/lint-result-cache.js +144 -145
  19. package/lib/cli-engine/load-rules.js +16 -16
  20. package/lib/cli.js +541 -457
  21. package/lib/config/config-loader.js +648 -618
  22. package/lib/config/config.js +247 -221
  23. package/lib/config/default-config.js +54 -45
  24. package/lib/config/flat-config-array.js +167 -172
  25. package/lib/config/flat-config-helpers.js +65 -68
  26. package/lib/config/flat-config-schema.js +375 -368
  27. package/lib/config/rule-validator.js +139 -144
  28. package/lib/config-api.js +12 -0
  29. package/lib/eslint/eslint-helpers.js +709 -679
  30. package/lib/eslint/eslint.js +944 -886
  31. package/lib/eslint/index.js +2 -2
  32. package/lib/eslint/legacy-eslint.js +576 -532
  33. package/lib/languages/js/index.js +263 -264
  34. package/lib/languages/js/source-code/index.js +1 -1
  35. package/lib/languages/js/source-code/source-code.js +1129 -1054
  36. package/lib/languages/js/source-code/token-store/backward-token-comment-cursor.js +39 -35
  37. package/lib/languages/js/source-code/token-store/backward-token-cursor.js +35 -36
  38. package/lib/languages/js/source-code/token-store/cursor.js +36 -36
  39. package/lib/languages/js/source-code/token-store/cursors.js +80 -52
  40. package/lib/languages/js/source-code/token-store/decorative-cursor.js +17 -18
  41. package/lib/languages/js/source-code/token-store/filter-cursor.js +19 -20
  42. package/lib/languages/js/source-code/token-store/forward-token-comment-cursor.js +40 -32
  43. package/lib/languages/js/source-code/token-store/forward-token-cursor.js +40 -41
  44. package/lib/languages/js/source-code/token-store/index.js +592 -498
  45. package/lib/languages/js/source-code/token-store/limit-cursor.js +17 -18
  46. package/lib/languages/js/source-code/token-store/padded-token-cursor.js +23 -16
  47. package/lib/languages/js/source-code/token-store/skip-cursor.js +19 -20
  48. package/lib/languages/js/source-code/token-store/utils.js +63 -60
  49. package/lib/languages/js/validate-language-options.js +104 -89
  50. package/lib/linter/apply-disable-directives.js +467 -383
  51. package/lib/linter/code-path-analysis/code-path-analyzer.js +650 -672
  52. package/lib/linter/code-path-analysis/code-path-segment.js +215 -216
  53. package/lib/linter/code-path-analysis/code-path-state.js +2118 -2096
  54. package/lib/linter/code-path-analysis/code-path.js +307 -319
  55. package/lib/linter/code-path-analysis/debug-helpers.js +183 -163
  56. package/lib/linter/code-path-analysis/fork-context.js +296 -271
  57. package/lib/linter/code-path-analysis/id-generator.js +22 -23
  58. package/lib/linter/file-context.js +119 -120
  59. package/lib/linter/index.js +3 -3
  60. package/lib/linter/interpolate.js +16 -16
  61. package/lib/linter/linter.js +2402 -2044
  62. package/lib/linter/node-event-generator.js +284 -225
  63. package/lib/linter/report-translator.js +256 -219
  64. package/lib/linter/rule-fixer.js +122 -124
  65. package/lib/linter/rules.js +35 -35
  66. package/lib/linter/safe-emitter.js +18 -18
  67. package/lib/linter/source-code-fixer.js +94 -92
  68. package/lib/linter/timing.js +104 -101
  69. package/lib/linter/vfile.js +70 -73
  70. package/lib/options.js +375 -361
  71. package/lib/rule-tester/index.js +1 -1
  72. package/lib/rule-tester/rule-tester.js +1307 -1045
  73. package/lib/rules/accessor-pairs.js +297 -262
  74. package/lib/rules/array-bracket-newline.js +249 -237
  75. package/lib/rules/array-bracket-spacing.js +262 -223
  76. package/lib/rules/array-callback-return.js +401 -355
  77. package/lib/rules/array-element-newline.js +357 -312
  78. package/lib/rules/arrow-body-style.js +399 -280
  79. package/lib/rules/arrow-parens.js +205 -172
  80. package/lib/rules/arrow-spacing.js +168 -162
  81. package/lib/rules/block-scoped-var.js +124 -122
  82. package/lib/rules/block-spacing.js +185 -175
  83. package/lib/rules/brace-style.js +261 -198
  84. package/lib/rules/callback-return.js +202 -189
  85. package/lib/rules/camelcase.js +402 -391
  86. package/lib/rules/capitalized-comments.js +252 -231
  87. package/lib/rules/class-methods-use-this.js +179 -171
  88. package/lib/rules/comma-dangle.js +378 -345
  89. package/lib/rules/comma-spacing.js +192 -194
  90. package/lib/rules/comma-style.js +374 -315
  91. package/lib/rules/complexity.js +172 -168
  92. package/lib/rules/computed-property-spacing.js +235 -210
  93. package/lib/rules/consistent-return.js +180 -169
  94. package/lib/rules/consistent-this.js +166 -146
  95. package/lib/rules/constructor-super.js +411 -403
  96. package/lib/rules/curly.js +406 -331
  97. package/lib/rules/default-case-last.js +37 -30
  98. package/lib/rules/default-case.js +88 -84
  99. package/lib/rules/default-param-last.js +68 -53
  100. package/lib/rules/dot-location.js +121 -109
  101. package/lib/rules/dot-notation.js +191 -155
  102. package/lib/rules/eol-last.js +121 -119
  103. package/lib/rules/eqeqeq.js +167 -154
  104. package/lib/rules/for-direction.js +145 -120
  105. package/lib/rules/func-call-spacing.js +260 -230
  106. package/lib/rules/func-name-matching.js +292 -208
  107. package/lib/rules/func-names.js +164 -163
  108. package/lib/rules/func-style.js +158 -126
  109. package/lib/rules/function-call-argument-newline.js +151 -128
  110. package/lib/rules/function-paren-newline.js +348 -290
  111. package/lib/rules/generator-star-spacing.js +228 -209
  112. package/lib/rules/getter-return.js +207 -171
  113. package/lib/rules/global-require.js +84 -73
  114. package/lib/rules/grouped-accessor-pairs.js +169 -149
  115. package/lib/rules/guard-for-in.js +71 -62
  116. package/lib/rules/handle-callback-err.js +107 -102
  117. package/lib/rules/id-blacklist.js +181 -198
  118. package/lib/rules/id-denylist.js +167 -186
  119. package/lib/rules/id-length.js +196 -170
  120. package/lib/rules/id-match.js +343 -288
  121. package/lib/rules/implicit-arrow-linebreak.js +101 -78
  122. package/lib/rules/indent-legacy.js +1343 -1117
  123. package/lib/rules/indent.js +2271 -1758
  124. package/lib/rules/index.js +317 -292
  125. package/lib/rules/init-declarations.js +115 -106
  126. package/lib/rules/jsx-quotes.js +93 -81
  127. package/lib/rules/key-spacing.js +749 -632
  128. package/lib/rules/keyword-spacing.js +647 -604
  129. package/lib/rules/line-comment-position.js +141 -127
  130. package/lib/rules/linebreak-style.js +106 -105
  131. package/lib/rules/lines-around-comment.js +539 -447
  132. package/lib/rules/lines-around-directive.js +232 -202
  133. package/lib/rules/lines-between-class-members.js +304 -233
  134. package/lib/rules/logical-assignment-operators.js +581 -398
  135. package/lib/rules/max-classes-per-file.js +68 -67
  136. package/lib/rules/max-depth.js +145 -142
  137. package/lib/rules/max-len.js +472 -433
  138. package/lib/rules/max-lines-per-function.js +200 -175
  139. package/lib/rules/max-lines.js +157 -161
  140. package/lib/rules/max-nested-callbacks.js +101 -103
  141. package/lib/rules/max-params.js +77 -75
  142. package/lib/rules/max-statements-per-line.js +204 -197
  143. package/lib/rules/max-statements.js +167 -163
  144. package/lib/rules/multiline-comment-style.js +636 -478
  145. package/lib/rules/multiline-ternary.js +240 -175
  146. package/lib/rules/new-cap.js +232 -212
  147. package/lib/rules/new-parens.js +87 -78
  148. package/lib/rules/newline-after-var.js +286 -249
  149. package/lib/rules/newline-before-return.js +228 -221
  150. package/lib/rules/newline-per-chained-call.js +141 -126
  151. package/lib/rules/no-alert.js +89 -78
  152. package/lib/rules/no-array-constructor.js +121 -112
  153. package/lib/rules/no-async-promise-executor.js +29 -23
  154. package/lib/rules/no-await-in-loop.js +68 -70
  155. package/lib/rules/no-bitwise.js +123 -99
  156. package/lib/rules/no-buffer-constructor.js +54 -46
  157. package/lib/rules/no-caller.js +38 -32
  158. package/lib/rules/no-case-declarations.js +60 -56
  159. package/lib/rules/no-catch-shadow.js +75 -72
  160. package/lib/rules/no-class-assign.js +50 -47
  161. package/lib/rules/no-compare-neg-zero.js +61 -47
  162. package/lib/rules/no-cond-assign.js +147 -131
  163. package/lib/rules/no-confusing-arrow.js +97 -80
  164. package/lib/rules/no-console.js +201 -190
  165. package/lib/rules/no-const-assign.js +46 -40
  166. package/lib/rules/no-constant-binary-expression.js +499 -404
  167. package/lib/rules/no-constant-condition.js +157 -142
  168. package/lib/rules/no-constructor-return.js +48 -48
  169. package/lib/rules/no-continue.js +24 -26
  170. package/lib/rules/no-control-regex.js +124 -120
  171. package/lib/rules/no-debugger.js +27 -29
  172. package/lib/rules/no-delete-var.js +28 -28
  173. package/lib/rules/no-div-regex.js +46 -40
  174. package/lib/rules/no-dupe-args.js +67 -68
  175. package/lib/rules/no-dupe-class-members.js +92 -88
  176. package/lib/rules/no-dupe-else-if.js +99 -76
  177. package/lib/rules/no-dupe-keys.js +132 -109
  178. package/lib/rules/no-duplicate-case.js +49 -42
  179. package/lib/rules/no-duplicate-imports.js +178 -175
  180. package/lib/rules/no-else-return.js +429 -384
  181. package/lib/rules/no-empty-character-class.js +56 -49
  182. package/lib/rules/no-empty-function.js +126 -127
  183. package/lib/rules/no-empty-pattern.js +62 -57
  184. package/lib/rules/no-empty-static-block.js +36 -34
  185. package/lib/rules/no-empty.js +97 -85
  186. package/lib/rules/no-eq-null.js +36 -31
  187. package/lib/rules/no-eval.js +255 -249
  188. package/lib/rules/no-ex-assign.js +41 -38
  189. package/lib/rules/no-extend-native.js +160 -158
  190. package/lib/rules/no-extra-bind.js +200 -189
  191. package/lib/rules/no-extra-boolean-cast.js +397 -347
  192. package/lib/rules/no-extra-label.js +149 -130
  193. package/lib/rules/no-extra-parens.js +1653 -1324
  194. package/lib/rules/no-extra-semi.js +145 -143
  195. package/lib/rules/no-fallthrough.js +198 -156
  196. package/lib/rules/no-floating-decimal.js +73 -65
  197. package/lib/rules/no-func-assign.js +53 -54
  198. package/lib/rules/no-global-assign.js +77 -72
  199. package/lib/rules/no-implicit-coercion.js +348 -292
  200. package/lib/rules/no-implicit-globals.js +157 -134
  201. package/lib/rules/no-implied-eval.js +139 -111
  202. package/lib/rules/no-import-assign.js +144 -158
  203. package/lib/rules/no-inline-comments.js +100 -94
  204. package/lib/rules/no-inner-declarations.js +114 -100
  205. package/lib/rules/no-invalid-regexp.js +221 -189
  206. package/lib/rules/no-invalid-this.js +122 -116
  207. package/lib/rules/no-irregular-whitespace.js +265 -251
  208. package/lib/rules/no-iterator.js +28 -32
  209. package/lib/rules/no-label-var.js +58 -61
  210. package/lib/rules/no-labels.js +137 -132
  211. package/lib/rules/no-lone-blocks.js +126 -122
  212. package/lib/rules/no-lonely-if.js +107 -76
  213. package/lib/rules/no-loop-func.js +233 -212
  214. package/lib/rules/no-loss-of-precision.js +215 -200
  215. package/lib/rules/no-magic-numbers.js +245 -217
  216. package/lib/rules/no-misleading-character-class.js +498 -445
  217. package/lib/rules/no-mixed-operators.js +187 -181
  218. package/lib/rules/no-mixed-requires.js +252 -239
  219. package/lib/rules/no-mixed-spaces-and-tabs.js +133 -120
  220. package/lib/rules/no-multi-assign.js +45 -43
  221. package/lib/rules/no-multi-spaces.js +162 -142
  222. package/lib/rules/no-multi-str.js +41 -40
  223. package/lib/rules/no-multiple-empty-lines.js +195 -157
  224. package/lib/rules/no-native-reassign.js +89 -84
  225. package/lib/rules/no-negated-condition.js +78 -74
  226. package/lib/rules/no-negated-in-lhs.js +44 -42
  227. package/lib/rules/no-nested-ternary.js +32 -31
  228. package/lib/rules/no-new-func.js +70 -61
  229. package/lib/rules/no-new-native-nonconstructor.js +42 -38
  230. package/lib/rules/no-new-object.js +47 -47
  231. package/lib/rules/no-new-require.js +47 -46
  232. package/lib/rules/no-new-symbol.js +51 -49
  233. package/lib/rules/no-new-wrappers.js +42 -40
  234. package/lib/rules/no-new.js +27 -28
  235. package/lib/rules/no-nonoctal-decimal-escape.js +140 -120
  236. package/lib/rules/no-obj-calls.js +65 -52
  237. package/lib/rules/no-object-constructor.js +103 -96
  238. package/lib/rules/no-octal-escape.js +39 -42
  239. package/lib/rules/no-octal.js +31 -31
  240. package/lib/rules/no-param-reassign.js +234 -216
  241. package/lib/rules/no-path-concat.js +65 -66
  242. package/lib/rules/no-plusplus.js +59 -60
  243. package/lib/rules/no-process-env.js +48 -47
  244. package/lib/rules/no-process-exit.js +53 -49
  245. package/lib/rules/no-promise-executor-return.js +213 -181
  246. package/lib/rules/no-proto.js +25 -28
  247. package/lib/rules/no-prototype-builtins.js +145 -123
  248. package/lib/rules/no-redeclare.js +153 -151
  249. package/lib/rules/no-regex-spaces.js +182 -160
  250. package/lib/rules/no-restricted-exports.js +207 -184
  251. package/lib/rules/no-restricted-globals.js +110 -111
  252. package/lib/rules/no-restricted-imports.js +656 -536
  253. package/lib/rules/no-restricted-modules.js +221 -201
  254. package/lib/rules/no-restricted-properties.js +180 -152
  255. package/lib/rules/no-restricted-syntax.js +55 -51
  256. package/lib/rules/no-return-assign.js +54 -49
  257. package/lib/rules/no-return-await.js +147 -123
  258. package/lib/rules/no-script-url.js +51 -44
  259. package/lib/rules/no-self-assign.js +147 -145
  260. package/lib/rules/no-self-compare.js +62 -45
  261. package/lib/rules/no-sequences.js +134 -115
  262. package/lib/rules/no-setter-return.js +184 -151
  263. package/lib/rules/no-shadow-restricted-names.js +60 -45
  264. package/lib/rules/no-shadow.js +341 -315
  265. package/lib/rules/no-spaced-func.js +81 -76
  266. package/lib/rules/no-sparse-arrays.js +53 -58
  267. package/lib/rules/no-sync.js +60 -59
  268. package/lib/rules/no-tabs.js +82 -71
  269. package/lib/rules/no-template-curly-in-string.js +32 -31
  270. package/lib/rules/no-ternary.js +24 -28
  271. package/lib/rules/no-this-before-super.js +320 -318
  272. package/lib/rules/no-throw-literal.js +30 -35
  273. package/lib/rules/no-trailing-spaces.js +198 -190
  274. package/lib/rules/no-undef-init.js +75 -60
  275. package/lib/rules/no-undef.js +50 -47
  276. package/lib/rules/no-undefined.js +72 -74
  277. package/lib/rules/no-underscore-dangle.js +369 -326
  278. package/lib/rules/no-unexpected-multiline.js +111 -101
  279. package/lib/rules/no-unmodified-loop-condition.js +253 -253
  280. package/lib/rules/no-unneeded-ternary.js +211 -146
  281. package/lib/rules/no-unreachable-loop.js +144 -141
  282. package/lib/rules/no-unreachable.js +254 -247
  283. package/lib/rules/no-unsafe-finally.js +92 -84
  284. package/lib/rules/no-unsafe-negation.js +104 -82
  285. package/lib/rules/no-unsafe-optional-chaining.js +191 -177
  286. package/lib/rules/no-unused-expressions.js +177 -161
  287. package/lib/rules/no-unused-labels.js +138 -123
  288. package/lib/rules/no-unused-private-class-members.js +205 -181
  289. package/lib/rules/no-unused-vars.js +1668 -1448
  290. package/lib/rules/no-use-before-define.js +228 -230
  291. package/lib/rules/no-useless-assignment.js +589 -510
  292. package/lib/rules/no-useless-backreference.js +211 -192
  293. package/lib/rules/no-useless-call.js +57 -52
  294. package/lib/rules/no-useless-catch.js +39 -39
  295. package/lib/rules/no-useless-computed-key.js +143 -114
  296. package/lib/rules/no-useless-concat.js +64 -59
  297. package/lib/rules/no-useless-constructor.js +157 -110
  298. package/lib/rules/no-useless-escape.js +341 -290
  299. package/lib/rules/no-useless-rename.js +182 -155
  300. package/lib/rules/no-useless-return.js +343 -311
  301. package/lib/rules/no-var.js +232 -211
  302. package/lib/rules/no-void.js +49 -47
  303. package/lib/rules/no-warning-comments.js +190 -185
  304. package/lib/rules/no-whitespace-before-property.js +130 -114
  305. package/lib/rules/no-with.js +23 -25
  306. package/lib/rules/nonblock-statement-body-position.js +148 -129
  307. package/lib/rules/object-curly-newline.js +305 -264
  308. package/lib/rules/object-curly-spacing.js +359 -313
  309. package/lib/rules/object-property-newline.js +136 -105
  310. package/lib/rules/object-shorthand.js +606 -501
  311. package/lib/rules/one-var-declaration-per-line.js +103 -99
  312. package/lib/rules/one-var.js +652 -536
  313. package/lib/rules/operator-assignment.js +218 -160
  314. package/lib/rules/operator-linebreak.js +294 -250
  315. package/lib/rules/padded-blocks.js +345 -307
  316. package/lib/rules/padding-line-between-statements.js +442 -438
  317. package/lib/rules/prefer-arrow-callback.js +361 -312
  318. package/lib/rules/prefer-const.js +417 -376
  319. package/lib/rules/prefer-destructuring.js +300 -278
  320. package/lib/rules/prefer-exponentiation-operator.js +175 -132
  321. package/lib/rules/prefer-named-capture-group.js +152 -139
  322. package/lib/rules/prefer-numeric-literals.js +120 -112
  323. package/lib/rules/prefer-object-has-own.js +115 -81
  324. package/lib/rules/prefer-object-spread.js +212 -192
  325. package/lib/rules/prefer-promise-reject-errors.js +139 -121
  326. package/lib/rules/prefer-reflect.js +126 -106
  327. package/lib/rules/prefer-regex-literals.js +577 -465
  328. package/lib/rules/prefer-rest-params.js +78 -79
  329. package/lib/rules/prefer-spread.js +46 -43
  330. package/lib/rules/prefer-template.js +265 -194
  331. package/lib/rules/quote-props.js +372 -306
  332. package/lib/rules/quotes.js +373 -325
  333. package/lib/rules/radix.js +151 -135
  334. package/lib/rules/require-atomic-updates.js +315 -284
  335. package/lib/rules/require-await.js +143 -115
  336. package/lib/rules/require-unicode-regexp.js +281 -176
  337. package/lib/rules/require-yield.js +52 -53
  338. package/lib/rules/rest-spread-spacing.js +127 -115
  339. package/lib/rules/semi-spacing.js +280 -249
  340. package/lib/rules/semi-style.js +175 -133
  341. package/lib/rules/semi.js +455 -435
  342. package/lib/rules/sort-imports.js +305 -232
  343. package/lib/rules/sort-keys.js +218 -187
  344. package/lib/rules/sort-vars.js +126 -92
  345. package/lib/rules/space-before-blocks.js +198 -188
  346. package/lib/rules/space-before-function-paren.js +185 -165
  347. package/lib/rules/space-in-parens.js +358 -287
  348. package/lib/rules/space-infix-ops.js +236 -200
  349. package/lib/rules/space-unary-ops.js +355 -297
  350. package/lib/rules/spaced-comment.js +362 -318
  351. package/lib/rules/strict.js +264 -229
  352. package/lib/rules/switch-colon-spacing.js +129 -121
  353. package/lib/rules/symbol-description.js +44 -47
  354. package/lib/rules/template-curly-spacing.js +147 -141
  355. package/lib/rules/template-tag-spacing.js +97 -87
  356. package/lib/rules/unicode-bom.js +53 -55
  357. package/lib/rules/use-isnan.js +236 -205
  358. package/lib/rules/utils/ast-utils.js +2039 -1860
  359. package/lib/rules/utils/char-source.js +162 -155
  360. package/lib/rules/utils/fix-tracker.js +83 -80
  361. package/lib/rules/utils/keywords.js +59 -59
  362. package/lib/rules/utils/lazy-loading-rule-map.js +79 -76
  363. package/lib/rules/utils/regular-expressions.js +32 -24
  364. package/lib/rules/utils/unicode/index.js +4 -4
  365. package/lib/rules/utils/unicode/is-combining-character.js +1 -1
  366. package/lib/rules/utils/unicode/is-emoji-modifier.js +1 -1
  367. package/lib/rules/utils/unicode/is-regional-indicator-symbol.js +1 -1
  368. package/lib/rules/utils/unicode/is-surrogate-pair.js +1 -1
  369. package/lib/rules/valid-typeof.js +152 -110
  370. package/lib/rules/vars-on-top.js +151 -144
  371. package/lib/rules/wrap-iife.js +203 -190
  372. package/lib/rules/wrap-regex.js +69 -57
  373. package/lib/rules/yield-star-spacing.js +144 -133
  374. package/lib/rules/yoda.js +282 -271
  375. package/lib/services/parser-service.js +35 -35
  376. package/lib/services/processor-service.js +66 -73
  377. package/lib/shared/ajv.js +14 -14
  378. package/lib/shared/assert.js +3 -4
  379. package/lib/shared/ast-utils.js +7 -6
  380. package/lib/shared/deep-merge-arrays.js +24 -22
  381. package/lib/shared/directives.js +3 -2
  382. package/lib/shared/flags.js +46 -17
  383. package/lib/shared/logging.js +24 -25
  384. package/lib/shared/option-utils.js +43 -36
  385. package/lib/shared/runtime-info.js +136 -127
  386. package/lib/shared/serialization.js +27 -27
  387. package/lib/shared/severity.js +22 -22
  388. package/lib/shared/stats.js +5 -5
  389. package/lib/shared/string-utils.js +16 -16
  390. package/lib/shared/text-table.js +28 -27
  391. package/lib/shared/traverser.js +153 -146
  392. package/lib/types/config-api.d.ts +8 -0
  393. package/lib/types/index.d.ts +2010 -1559
  394. package/lib/types/rules.d.ts +5312 -0
  395. package/lib/types/use-at-your-own-risk.d.ts +32 -30
  396. package/lib/unsupported-api.js +5 -5
  397. package/messages/all-files-ignored.js +3 -3
  398. package/messages/all-matched-files-ignored.js +3 -3
  399. package/messages/config-file-missing.js +2 -2
  400. package/messages/config-plugin-missing.js +3 -3
  401. package/messages/config-serialize-function.js +9 -7
  402. package/messages/eslintrc-incompat.js +13 -15
  403. package/messages/eslintrc-plugins.js +3 -4
  404. package/messages/extend-config-missing.js +3 -3
  405. package/messages/failed-to-read-json.js +3 -3
  406. package/messages/file-not-found.js +3 -3
  407. package/messages/invalid-rule-options.js +2 -2
  408. package/messages/invalid-rule-severity.js +2 -2
  409. package/messages/no-config-found.js +3 -3
  410. package/messages/plugin-conflict.js +8 -8
  411. package/messages/plugin-invalid.js +3 -3
  412. package/messages/plugin-missing.js +3 -3
  413. package/messages/print-config-with-directory-path.js +2 -2
  414. package/messages/shared.js +6 -1
  415. package/messages/whitespace-found.js +3 -3
  416. package/package.json +22 -20
  417. package/lib/types/rules/best-practices.d.ts +0 -1143
  418. package/lib/types/rules/deprecated.d.ts +0 -252
  419. package/lib/types/rules/ecmascript-6.d.ts +0 -647
  420. package/lib/types/rules/index.d.ts +0 -50
  421. package/lib/types/rules/node-commonjs.d.ts +0 -171
  422. package/lib/types/rules/possible-errors.d.ts +0 -685
  423. package/lib/types/rules/strict-mode.d.ts +0 -38
  424. package/lib/types/rules/stylistic-issues.d.ts +0 -2043
  425. package/lib/types/rules/variables.d.ts +0 -234
@@ -22,14 +22,16 @@ const baseTypes = new Set(["Identifier", "Super", "ThisExpression"]);
22
22
  * @returns {boolean} True iff "undefined" or "void ..."
23
23
  */
24
24
  function isUndefined(expression, scope) {
25
- if (expression.type === "Identifier" && expression.name === "undefined") {
26
- return astUtils.isReferenceToGlobalVariable(scope, expression);
27
- }
28
-
29
- return expression.type === "UnaryExpression" &&
30
- expression.operator === "void" &&
31
- expression.argument.type === "Literal" &&
32
- expression.argument.value === 0;
25
+ if (expression.type === "Identifier" && expression.name === "undefined") {
26
+ return astUtils.isReferenceToGlobalVariable(scope, expression);
27
+ }
28
+
29
+ return (
30
+ expression.type === "UnaryExpression" &&
31
+ expression.operator === "void" &&
32
+ expression.argument.type === "Literal" &&
33
+ expression.argument.value === 0
34
+ );
33
35
  }
34
36
 
35
37
  /**
@@ -38,8 +40,10 @@ function isUndefined(expression, scope) {
38
40
  * @returns {boolean} True for identifiers and member expressions
39
41
  */
40
42
  function isReference(expression) {
41
- return (expression.type === "Identifier" && expression.name !== "undefined") ||
42
- expression.type === "MemberExpression";
43
+ return (
44
+ (expression.type === "Identifier" && expression.name !== "undefined") ||
45
+ expression.type === "MemberExpression"
46
+ );
43
47
  }
44
48
 
45
49
  /**
@@ -50,15 +54,21 @@ function isReference(expression) {
50
54
  * @returns {boolean} True iff implicit nullish comparison
51
55
  */
52
56
  function isImplicitNullishComparison(expression, scope) {
53
- if (expression.type !== "BinaryExpression" || expression.operator !== "==") {
54
- return false;
55
- }
56
-
57
- const reference = isReference(expression.left) ? "left" : "right";
58
- const nullish = reference === "left" ? "right" : "left";
59
-
60
- return isReference(expression[reference]) &&
61
- (astUtils.isNullLiteral(expression[nullish]) || isUndefined(expression[nullish], scope));
57
+ if (
58
+ expression.type !== "BinaryExpression" ||
59
+ expression.operator !== "=="
60
+ ) {
61
+ return false;
62
+ }
63
+
64
+ const reference = isReference(expression.left) ? "left" : "right";
65
+ const nullish = reference === "left" ? "right" : "left";
66
+
67
+ return (
68
+ isReference(expression[reference]) &&
69
+ (astUtils.isNullLiteral(expression[nullish]) ||
70
+ isUndefined(expression[nullish], scope))
71
+ );
62
72
  }
63
73
 
64
74
  /**
@@ -67,12 +77,14 @@ function isImplicitNullishComparison(expression, scope) {
67
77
  * @returns {boolean} True iff matches ? === ? || ? === ?
68
78
  */
69
79
  function isDoubleComparison(expression) {
70
- return expression.type === "LogicalExpression" &&
71
- expression.operator === "||" &&
72
- expression.left.type === "BinaryExpression" &&
73
- expression.left.operator === "===" &&
74
- expression.right.type === "BinaryExpression" &&
75
- expression.right.operator === "===";
80
+ return (
81
+ expression.type === "LogicalExpression" &&
82
+ expression.operator === "||" &&
83
+ expression.left.type === "BinaryExpression" &&
84
+ expression.left.operator === "===" &&
85
+ expression.right.type === "BinaryExpression" &&
86
+ expression.right.operator === "==="
87
+ );
76
88
  }
77
89
 
78
90
  /**
@@ -83,17 +95,26 @@ function isDoubleComparison(expression) {
83
95
  * @returns {boolean} True iff explicit nullish comparison
84
96
  */
85
97
  function isExplicitNullishComparison(expression, scope) {
86
- if (!isDoubleComparison(expression)) {
87
- return false;
88
- }
89
- const leftReference = isReference(expression.left.left) ? "left" : "right";
90
- const leftNullish = leftReference === "left" ? "right" : "left";
91
- const rightReference = isReference(expression.right.left) ? "left" : "right";
92
- const rightNullish = rightReference === "left" ? "right" : "left";
93
-
94
- return astUtils.isSameReference(expression.left[leftReference], expression.right[rightReference]) &&
95
- ((astUtils.isNullLiteral(expression.left[leftNullish]) && isUndefined(expression.right[rightNullish], scope)) ||
96
- (isUndefined(expression.left[leftNullish], scope) && astUtils.isNullLiteral(expression.right[rightNullish])));
98
+ if (!isDoubleComparison(expression)) {
99
+ return false;
100
+ }
101
+ const leftReference = isReference(expression.left.left) ? "left" : "right";
102
+ const leftNullish = leftReference === "left" ? "right" : "left";
103
+ const rightReference = isReference(expression.right.left)
104
+ ? "left"
105
+ : "right";
106
+ const rightNullish = rightReference === "left" ? "right" : "left";
107
+
108
+ return (
109
+ astUtils.isSameReference(
110
+ expression.left[leftReference],
111
+ expression.right[rightReference],
112
+ ) &&
113
+ ((astUtils.isNullLiteral(expression.left[leftNullish]) &&
114
+ isUndefined(expression.right[rightNullish], scope)) ||
115
+ (isUndefined(expression.left[leftNullish], scope) &&
116
+ astUtils.isNullLiteral(expression.right[rightNullish])))
117
+ );
97
118
  }
98
119
 
99
120
  /**
@@ -103,10 +124,12 @@ function isExplicitNullishComparison(expression, scope) {
103
124
  * @returns {boolean} Whether the expression is a boolean cast
104
125
  */
105
126
  function isBooleanCast(expression, scope) {
106
- return expression.type === "CallExpression" &&
107
- expression.callee.name === "Boolean" &&
108
- expression.arguments.length === 1 &&
109
- astUtils.isReferenceToGlobalVariable(scope, expression.callee);
127
+ return (
128
+ expression.type === "CallExpression" &&
129
+ expression.callee.name === "Boolean" &&
130
+ expression.arguments.length === 1 &&
131
+ astUtils.isReferenceToGlobalVariable(scope, expression.callee)
132
+ );
110
133
  }
111
134
 
112
135
  /**
@@ -119,22 +142,39 @@ function isBooleanCast(expression, scope) {
119
142
  * @returns {?{ reference: ASTNode, operator: '??'|'||'|'&&'}} Null if not a known existence
120
143
  */
121
144
  function getExistence(expression, scope) {
122
- const isNegated = expression.type === "UnaryExpression" && expression.operator === "!";
123
- const base = isNegated ? expression.argument : expression;
124
-
125
- switch (true) {
126
- case isReference(base):
127
- return { reference: base, operator: isNegated ? "||" : "&&" };
128
- case base.type === "UnaryExpression" && base.operator === "!" && isReference(base.argument):
129
- return { reference: base.argument, operator: "&&" };
130
- case isBooleanCast(base, scope) && isReference(base.arguments[0]):
131
- return { reference: base.arguments[0], operator: isNegated ? "||" : "&&" };
132
- case isImplicitNullishComparison(expression, scope):
133
- return { reference: isReference(expression.left) ? expression.left : expression.right, operator: "??" };
134
- case isExplicitNullishComparison(expression, scope):
135
- return { reference: isReference(expression.left.left) ? expression.left.left : expression.left.right, operator: "??" };
136
- default: return null;
137
- }
145
+ const isNegated =
146
+ expression.type === "UnaryExpression" && expression.operator === "!";
147
+ const base = isNegated ? expression.argument : expression;
148
+
149
+ switch (true) {
150
+ case isReference(base):
151
+ return { reference: base, operator: isNegated ? "||" : "&&" };
152
+ case base.type === "UnaryExpression" &&
153
+ base.operator === "!" &&
154
+ isReference(base.argument):
155
+ return { reference: base.argument, operator: "&&" };
156
+ case isBooleanCast(base, scope) && isReference(base.arguments[0]):
157
+ return {
158
+ reference: base.arguments[0],
159
+ operator: isNegated ? "||" : "&&",
160
+ };
161
+ case isImplicitNullishComparison(expression, scope):
162
+ return {
163
+ reference: isReference(expression.left)
164
+ ? expression.left
165
+ : expression.right,
166
+ operator: "??",
167
+ };
168
+ case isExplicitNullishComparison(expression, scope):
169
+ return {
170
+ reference: isReference(expression.left.left)
171
+ ? expression.left.left
172
+ : expression.left.right,
173
+ operator: "??",
174
+ };
175
+ default:
176
+ return null;
177
+ }
138
178
  }
139
179
 
140
180
  /**
@@ -143,11 +183,13 @@ function getExistence(expression, scope) {
143
183
  * @returns {boolean} True iff passed node is inside a with block
144
184
  */
145
185
  function isInsideWithBlock(node) {
146
- if (node.type === "Program") {
147
- return false;
148
- }
186
+ if (node.type === "Program") {
187
+ return false;
188
+ }
149
189
 
150
- return node.parent.type === "WithStatement" && node.parent.body === node ? true : isInsideWithBlock(node.parent);
190
+ return node.parent.type === "WithStatement" && node.parent.body === node
191
+ ? true
192
+ : isInsideWithBlock(node.parent);
151
193
  }
152
194
 
153
195
  /**
@@ -157,22 +199,22 @@ function isInsideWithBlock(node) {
157
199
  * @returns {Expression} Leftmost operand
158
200
  */
159
201
  function getLeftmostOperand(sourceCode, node) {
160
- let left = node.left;
161
-
162
- while (left.type === "LogicalExpression" && left.operator === node.operator) {
163
-
164
- if (astUtils.isParenthesised(sourceCode, left)) {
165
-
166
- /*
167
- * It should have associativity,
168
- * but ignore it if use parentheses to make the evaluation order clear.
169
- */
170
- return left;
171
- }
172
- left = left.left;
173
- }
174
- return left;
175
-
202
+ let left = node.left;
203
+
204
+ while (
205
+ left.type === "LogicalExpression" &&
206
+ left.operator === node.operator
207
+ ) {
208
+ if (astUtils.isParenthesised(sourceCode, left)) {
209
+ /*
210
+ * It should have associativity,
211
+ * but ignore it if use parentheses to make the evaluation order clear.
212
+ */
213
+ return left;
214
+ }
215
+ left = left.left;
216
+ }
217
+ return left;
176
218
  }
177
219
 
178
220
  //------------------------------------------------------------------------------
@@ -180,326 +222,467 @@ function getLeftmostOperand(sourceCode, node) {
180
222
  //------------------------------------------------------------------------------
181
223
  /** @type {import('../shared/types').Rule} */
182
224
  module.exports = {
183
- meta: {
184
- type: "suggestion",
185
-
186
- docs: {
187
- description: "Require or disallow logical assignment operator shorthand",
188
- recommended: false,
189
- frozen: true,
190
- url: "https://eslint.org/docs/latest/rules/logical-assignment-operators"
191
- },
192
-
193
- schema: {
194
- type: "array",
195
- oneOf: [{
196
- items: [
197
- { const: "always" },
198
- {
199
- type: "object",
200
- properties: {
201
- enforceForIfStatements: {
202
- type: "boolean"
203
- }
204
- },
205
- additionalProperties: false
206
- }
207
- ],
208
- minItems: 0, // 0 for allowing passing no options
209
- maxItems: 2
210
- }, {
211
- items: [{ const: "never" }],
212
- minItems: 1,
213
- maxItems: 1
214
- }]
215
- },
216
- fixable: "code",
217
- hasSuggestions: true,
218
- messages: {
219
- assignment: "Assignment (=) can be replaced with operator assignment ({{operator}}).",
220
- useLogicalOperator: "Convert this assignment to use the operator {{ operator }}.",
221
- logical: "Logical expression can be replaced with an assignment ({{ operator }}).",
222
- convertLogical: "Replace this logical expression with an assignment with the operator {{ operator }}.",
223
- if: "'if' statement can be replaced with a logical operator assignment with operator {{ operator }}.",
224
- convertIf: "Replace this 'if' statement with a logical assignment with operator {{ operator }}.",
225
- unexpected: "Unexpected logical operator assignment ({{operator}}) shorthand.",
226
- separate: "Separate the logical assignment into an assignment with a logical operator."
227
- }
228
- },
229
-
230
- create(context) {
231
- const mode = context.options[0] === "never" ? "never" : "always";
232
- const checkIf = mode === "always" && context.options.length > 1 && context.options[1].enforceForIfStatements;
233
- const sourceCode = context.sourceCode;
234
- const isStrict = sourceCode.getScope(sourceCode.ast).isStrict;
235
-
236
- /**
237
- * Returns false if the access could be a getter
238
- * @param {ASTNode} node Assignment expression
239
- * @returns {boolean} True iff the fix is safe
240
- */
241
- function cannotBeGetter(node) {
242
- return node.type === "Identifier" &&
243
- (isStrict || !isInsideWithBlock(node));
244
- }
245
-
246
- /**
247
- * Check whether only a single property is accessed
248
- * @param {ASTNode} node reference
249
- * @returns {boolean} True iff a single property is accessed
250
- */
251
- function accessesSingleProperty(node) {
252
- if (!isStrict && isInsideWithBlock(node)) {
253
- return node.type === "Identifier";
254
- }
255
-
256
- return node.type === "MemberExpression" &&
257
- baseTypes.has(node.object.type) &&
258
- (!node.computed || (node.property.type !== "MemberExpression" && node.property.type !== "ChainExpression"));
259
- }
260
-
261
- /**
262
- * Adds a fixer or suggestion whether on the fix is safe.
263
- * @param {{ messageId: string, node: ASTNode }} descriptor Report descriptor without fix or suggest
264
- * @param {{ messageId: string, fix: Function }} suggestion Adds the fix or the whole suggestion as only element in "suggest" to suggestion
265
- * @param {boolean} shouldBeFixed Fix iff the condition is true
266
- * @returns {Object} Descriptor with either an added fix or suggestion
267
- */
268
- function createConditionalFixer(descriptor, suggestion, shouldBeFixed) {
269
- if (shouldBeFixed) {
270
- return {
271
- ...descriptor,
272
- fix: suggestion.fix
273
- };
274
- }
275
-
276
- return {
277
- ...descriptor,
278
- suggest: [suggestion]
279
- };
280
- }
281
-
282
-
283
- /**
284
- * Returns the operator token for assignments and binary expressions
285
- * @param {ASTNode} node AssignmentExpression or BinaryExpression
286
- * @returns {import('eslint').AST.Token} Operator token between the left and right expression
287
- */
288
- function getOperatorToken(node) {
289
- return sourceCode.getFirstTokenBetween(node.left, node.right, token => token.value === node.operator);
290
- }
291
-
292
- if (mode === "never") {
293
- return {
294
-
295
- // foo ||= bar
296
- "AssignmentExpression"(assignment) {
297
- if (!astUtils.isLogicalAssignmentOperator(assignment.operator)) {
298
- return;
299
- }
300
-
301
- const descriptor = {
302
- messageId: "unexpected",
303
- node: assignment,
304
- data: { operator: assignment.operator }
305
- };
306
- const suggestion = {
307
- messageId: "separate",
308
- *fix(ruleFixer) {
309
- if (sourceCode.getCommentsInside(assignment).length > 0) {
310
- return;
311
- }
312
-
313
- const operatorToken = getOperatorToken(assignment);
314
-
315
- // -> foo = bar
316
- yield ruleFixer.replaceText(operatorToken, "=");
317
-
318
- const assignmentText = sourceCode.getText(assignment.left);
319
- const operator = assignment.operator.slice(0, -1);
320
-
321
- // -> foo = foo || bar
322
- yield ruleFixer.insertTextAfter(operatorToken, ` ${assignmentText} ${operator}`);
323
-
324
- const precedence = astUtils.getPrecedence(assignment.right) <= astUtils.getPrecedence({ type: "LogicalExpression", operator });
325
-
326
- // ?? and || / && cannot be mixed but have same precedence
327
- const mixed = assignment.operator === "??=" && astUtils.isLogicalExpression(assignment.right);
328
-
329
- if (!astUtils.isParenthesised(sourceCode, assignment.right) && (precedence || mixed)) {
330
-
331
- // -> foo = foo || (bar)
332
- yield ruleFixer.insertTextBefore(assignment.right, "(");
333
- yield ruleFixer.insertTextAfter(assignment.right, ")");
334
- }
335
- }
336
- };
337
-
338
- context.report(createConditionalFixer(descriptor, suggestion, cannotBeGetter(assignment.left)));
339
- }
340
- };
341
- }
342
-
343
- return {
344
-
345
- // foo = foo || bar
346
- "AssignmentExpression[operator='='][right.type='LogicalExpression']"(assignment) {
347
- const leftOperand = getLeftmostOperand(sourceCode, assignment.right);
348
-
349
- if (!astUtils.isSameReference(assignment.left, leftOperand)
350
- ) {
351
- return;
352
- }
353
-
354
- const descriptor = {
355
- messageId: "assignment",
356
- node: assignment,
357
- data: { operator: `${assignment.right.operator}=` }
358
- };
359
- const suggestion = {
360
- messageId: "useLogicalOperator",
361
- data: { operator: `${assignment.right.operator}=` },
362
- *fix(ruleFixer) {
363
- if (sourceCode.getCommentsInside(assignment).length > 0) {
364
- return;
365
- }
366
-
367
- // No need for parenthesis around the assignment based on precedence as the precedence stays the same even with changed operator
368
- const assignmentOperatorToken = getOperatorToken(assignment);
369
-
370
- // -> foo ||= foo || bar
371
- yield ruleFixer.insertTextBefore(assignmentOperatorToken, assignment.right.operator);
372
-
373
- // -> foo ||= bar
374
- const logicalOperatorToken = getOperatorToken(leftOperand.parent);
375
- const firstRightOperandToken = sourceCode.getTokenAfter(logicalOperatorToken);
376
-
377
- yield ruleFixer.removeRange([leftOperand.parent.range[0], firstRightOperandToken.range[0]]);
378
- }
379
- };
380
-
381
- context.report(createConditionalFixer(descriptor, suggestion, cannotBeGetter(assignment.left)));
382
- },
383
-
384
- // foo || (foo = bar)
385
- 'LogicalExpression[right.type="AssignmentExpression"][right.operator="="]'(logical) {
386
-
387
- // Right side has to be parenthesized, otherwise would be parsed as (foo || foo) = bar which is illegal
388
- if (isReference(logical.left) && astUtils.isSameReference(logical.left, logical.right.left)) {
389
- const descriptor = {
390
- messageId: "logical",
391
- node: logical,
392
- data: { operator: `${logical.operator}=` }
393
- };
394
- const suggestion = {
395
- messageId: "convertLogical",
396
- data: { operator: `${logical.operator}=` },
397
- *fix(ruleFixer) {
398
- if (sourceCode.getCommentsInside(logical).length > 0) {
399
- return;
400
- }
401
-
402
- const parentPrecedence = astUtils.getPrecedence(logical.parent);
403
- const requiresOuterParenthesis = logical.parent.type !== "ExpressionStatement" && (
404
- parentPrecedence === -1 ||
405
- astUtils.getPrecedence({ type: "AssignmentExpression" }) < parentPrecedence
406
- );
407
-
408
- if (!astUtils.isParenthesised(sourceCode, logical) && requiresOuterParenthesis) {
409
- yield ruleFixer.insertTextBefore(logical, "(");
410
- yield ruleFixer.insertTextAfter(logical, ")");
411
- }
412
-
413
- // Also removes all opening parenthesis
414
- yield ruleFixer.removeRange([logical.range[0], logical.right.range[0]]); // -> foo = bar)
415
-
416
- // Also removes all ending parenthesis
417
- yield ruleFixer.removeRange([logical.right.range[1], logical.range[1]]); // -> foo = bar
418
-
419
- const operatorToken = getOperatorToken(logical.right);
420
-
421
- yield ruleFixer.insertTextBefore(operatorToken, logical.operator); // -> foo ||= bar
422
- }
423
- };
424
- const fix = cannotBeGetter(logical.left) || accessesSingleProperty(logical.left);
425
-
426
- context.report(createConditionalFixer(descriptor, suggestion, fix));
427
- }
428
- },
429
-
430
- // if (foo) foo = bar
431
- "IfStatement[alternate=null]"(ifNode) {
432
- if (!checkIf) {
433
- return;
434
- }
435
-
436
- const hasBody = ifNode.consequent.type === "BlockStatement";
437
-
438
- if (hasBody && ifNode.consequent.body.length !== 1) {
439
- return;
440
- }
441
-
442
- const body = hasBody ? ifNode.consequent.body[0] : ifNode.consequent;
443
- const scope = sourceCode.getScope(ifNode);
444
- const existence = getExistence(ifNode.test, scope);
445
-
446
- if (
447
- body.type === "ExpressionStatement" &&
448
- body.expression.type === "AssignmentExpression" &&
449
- body.expression.operator === "=" &&
450
- existence !== null &&
451
- astUtils.isSameReference(existence.reference, body.expression.left)
452
- ) {
453
- const descriptor = {
454
- messageId: "if",
455
- node: ifNode,
456
- data: { operator: `${existence.operator}=` }
457
- };
458
- const suggestion = {
459
- messageId: "convertIf",
460
- data: { operator: `${existence.operator}=` },
461
- *fix(ruleFixer) {
462
- if (sourceCode.getCommentsInside(ifNode).length > 0) {
463
- return;
464
- }
465
-
466
- const firstBodyToken = sourceCode.getFirstToken(body);
467
- const prevToken = sourceCode.getTokenBefore(ifNode);
468
-
469
- if (
470
- prevToken !== null &&
471
- prevToken.value !== ";" &&
472
- prevToken.value !== "{" &&
473
- firstBodyToken.type !== "Identifier" &&
474
- firstBodyToken.type !== "Keyword"
475
- ) {
476
-
477
- // Do not fix if the fixed statement could be part of the previous statement (eg. fn() if (a == null) (a) = b --> fn()(a) ??= b)
478
- return;
479
- }
480
-
481
-
482
- const operatorToken = getOperatorToken(body.expression);
483
-
484
- yield ruleFixer.insertTextBefore(operatorToken, existence.operator); // -> if (foo) foo ||= bar
485
-
486
- yield ruleFixer.removeRange([ifNode.range[0], body.range[0]]); // -> foo ||= bar
487
-
488
- yield ruleFixer.removeRange([body.range[1], ifNode.range[1]]); // -> foo ||= bar, only present if "if" had a body
489
-
490
- const nextToken = sourceCode.getTokenAfter(body.expression);
491
-
492
- if (hasBody && (nextToken !== null && nextToken.value !== ";")) {
493
- yield ruleFixer.insertTextAfter(ifNode, ";");
494
- }
495
- }
496
- };
497
- const shouldBeFixed = cannotBeGetter(existence.reference) ||
498
- (ifNode.test.type !== "LogicalExpression" && accessesSingleProperty(existence.reference));
499
-
500
- context.report(createConditionalFixer(descriptor, suggestion, shouldBeFixed));
501
- }
502
- }
503
- };
504
- }
225
+ meta: {
226
+ type: "suggestion",
227
+
228
+ docs: {
229
+ description:
230
+ "Require or disallow logical assignment operator shorthand",
231
+ recommended: false,
232
+ frozen: true,
233
+ url: "https://eslint.org/docs/latest/rules/logical-assignment-operators",
234
+ },
235
+
236
+ schema: {
237
+ type: "array",
238
+ oneOf: [
239
+ {
240
+ items: [
241
+ { const: "always" },
242
+ {
243
+ type: "object",
244
+ properties: {
245
+ enforceForIfStatements: {
246
+ type: "boolean",
247
+ },
248
+ },
249
+ additionalProperties: false,
250
+ },
251
+ ],
252
+ minItems: 0, // 0 for allowing passing no options
253
+ maxItems: 2,
254
+ },
255
+ {
256
+ items: [{ const: "never" }],
257
+ minItems: 1,
258
+ maxItems: 1,
259
+ },
260
+ ],
261
+ },
262
+ fixable: "code",
263
+ hasSuggestions: true,
264
+ messages: {
265
+ assignment:
266
+ "Assignment (=) can be replaced with operator assignment ({{operator}}).",
267
+ useLogicalOperator:
268
+ "Convert this assignment to use the operator {{ operator }}.",
269
+ logical:
270
+ "Logical expression can be replaced with an assignment ({{ operator }}).",
271
+ convertLogical:
272
+ "Replace this logical expression with an assignment with the operator {{ operator }}.",
273
+ if: "'if' statement can be replaced with a logical operator assignment with operator {{ operator }}.",
274
+ convertIf:
275
+ "Replace this 'if' statement with a logical assignment with operator {{ operator }}.",
276
+ unexpected:
277
+ "Unexpected logical operator assignment ({{operator}}) shorthand.",
278
+ separate:
279
+ "Separate the logical assignment into an assignment with a logical operator.",
280
+ },
281
+ },
282
+
283
+ create(context) {
284
+ const mode = context.options[0] === "never" ? "never" : "always";
285
+ const checkIf =
286
+ mode === "always" &&
287
+ context.options.length > 1 &&
288
+ context.options[1].enforceForIfStatements;
289
+ const sourceCode = context.sourceCode;
290
+ const isStrict = sourceCode.getScope(sourceCode.ast).isStrict;
291
+
292
+ /**
293
+ * Returns false if the access could be a getter
294
+ * @param {ASTNode} node Assignment expression
295
+ * @returns {boolean} True iff the fix is safe
296
+ */
297
+ function cannotBeGetter(node) {
298
+ return (
299
+ node.type === "Identifier" &&
300
+ (isStrict || !isInsideWithBlock(node))
301
+ );
302
+ }
303
+
304
+ /**
305
+ * Check whether only a single property is accessed
306
+ * @param {ASTNode} node reference
307
+ * @returns {boolean} True iff a single property is accessed
308
+ */
309
+ function accessesSingleProperty(node) {
310
+ if (!isStrict && isInsideWithBlock(node)) {
311
+ return node.type === "Identifier";
312
+ }
313
+
314
+ return (
315
+ node.type === "MemberExpression" &&
316
+ baseTypes.has(node.object.type) &&
317
+ (!node.computed ||
318
+ (node.property.type !== "MemberExpression" &&
319
+ node.property.type !== "ChainExpression"))
320
+ );
321
+ }
322
+
323
+ /**
324
+ * Adds a fixer or suggestion whether on the fix is safe.
325
+ * @param {{ messageId: string, node: ASTNode }} descriptor Report descriptor without fix or suggest
326
+ * @param {{ messageId: string, fix: Function }} suggestion Adds the fix or the whole suggestion as only element in "suggest" to suggestion
327
+ * @param {boolean} shouldBeFixed Fix iff the condition is true
328
+ * @returns {Object} Descriptor with either an added fix or suggestion
329
+ */
330
+ function createConditionalFixer(descriptor, suggestion, shouldBeFixed) {
331
+ if (shouldBeFixed) {
332
+ return {
333
+ ...descriptor,
334
+ fix: suggestion.fix,
335
+ };
336
+ }
337
+
338
+ return {
339
+ ...descriptor,
340
+ suggest: [suggestion],
341
+ };
342
+ }
343
+
344
+ /**
345
+ * Returns the operator token for assignments and binary expressions
346
+ * @param {ASTNode} node AssignmentExpression or BinaryExpression
347
+ * @returns {import('eslint').AST.Token} Operator token between the left and right expression
348
+ */
349
+ function getOperatorToken(node) {
350
+ return sourceCode.getFirstTokenBetween(
351
+ node.left,
352
+ node.right,
353
+ token => token.value === node.operator,
354
+ );
355
+ }
356
+
357
+ if (mode === "never") {
358
+ return {
359
+ // foo ||= bar
360
+ AssignmentExpression(assignment) {
361
+ if (
362
+ !astUtils.isLogicalAssignmentOperator(
363
+ assignment.operator,
364
+ )
365
+ ) {
366
+ return;
367
+ }
368
+
369
+ const descriptor = {
370
+ messageId: "unexpected",
371
+ node: assignment,
372
+ data: { operator: assignment.operator },
373
+ };
374
+ const suggestion = {
375
+ messageId: "separate",
376
+ *fix(ruleFixer) {
377
+ if (
378
+ sourceCode.getCommentsInside(assignment)
379
+ .length > 0
380
+ ) {
381
+ return;
382
+ }
383
+
384
+ const operatorToken = getOperatorToken(assignment);
385
+
386
+ // -> foo = bar
387
+ yield ruleFixer.replaceText(operatorToken, "=");
388
+
389
+ const assignmentText = sourceCode.getText(
390
+ assignment.left,
391
+ );
392
+ const operator = assignment.operator.slice(0, -1);
393
+
394
+ // -> foo = foo || bar
395
+ yield ruleFixer.insertTextAfter(
396
+ operatorToken,
397
+ ` ${assignmentText} ${operator}`,
398
+ );
399
+
400
+ const precedence =
401
+ astUtils.getPrecedence(assignment.right) <=
402
+ astUtils.getPrecedence({
403
+ type: "LogicalExpression",
404
+ operator,
405
+ });
406
+
407
+ // ?? and || / && cannot be mixed but have same precedence
408
+ const mixed =
409
+ assignment.operator === "??=" &&
410
+ astUtils.isLogicalExpression(assignment.right);
411
+
412
+ if (
413
+ !astUtils.isParenthesised(
414
+ sourceCode,
415
+ assignment.right,
416
+ ) &&
417
+ (precedence || mixed)
418
+ ) {
419
+ // -> foo = foo || (bar)
420
+ yield ruleFixer.insertTextBefore(
421
+ assignment.right,
422
+ "(",
423
+ );
424
+ yield ruleFixer.insertTextAfter(
425
+ assignment.right,
426
+ ")",
427
+ );
428
+ }
429
+ },
430
+ };
431
+
432
+ context.report(
433
+ createConditionalFixer(
434
+ descriptor,
435
+ suggestion,
436
+ cannotBeGetter(assignment.left),
437
+ ),
438
+ );
439
+ },
440
+ };
441
+ }
442
+
443
+ return {
444
+ // foo = foo || bar
445
+ "AssignmentExpression[operator='='][right.type='LogicalExpression']"(
446
+ assignment,
447
+ ) {
448
+ const leftOperand = getLeftmostOperand(
449
+ sourceCode,
450
+ assignment.right,
451
+ );
452
+
453
+ if (!astUtils.isSameReference(assignment.left, leftOperand)) {
454
+ return;
455
+ }
456
+
457
+ const descriptor = {
458
+ messageId: "assignment",
459
+ node: assignment,
460
+ data: { operator: `${assignment.right.operator}=` },
461
+ };
462
+ const suggestion = {
463
+ messageId: "useLogicalOperator",
464
+ data: { operator: `${assignment.right.operator}=` },
465
+ *fix(ruleFixer) {
466
+ if (
467
+ sourceCode.getCommentsInside(assignment).length > 0
468
+ ) {
469
+ return;
470
+ }
471
+
472
+ // No need for parenthesis around the assignment based on precedence as the precedence stays the same even with changed operator
473
+ const assignmentOperatorToken =
474
+ getOperatorToken(assignment);
475
+
476
+ // -> foo ||= foo || bar
477
+ yield ruleFixer.insertTextBefore(
478
+ assignmentOperatorToken,
479
+ assignment.right.operator,
480
+ );
481
+
482
+ // -> foo ||= bar
483
+ const logicalOperatorToken = getOperatorToken(
484
+ leftOperand.parent,
485
+ );
486
+ const firstRightOperandToken =
487
+ sourceCode.getTokenAfter(logicalOperatorToken);
488
+
489
+ yield ruleFixer.removeRange([
490
+ leftOperand.parent.range[0],
491
+ firstRightOperandToken.range[0],
492
+ ]);
493
+ },
494
+ };
495
+
496
+ context.report(
497
+ createConditionalFixer(
498
+ descriptor,
499
+ suggestion,
500
+ cannotBeGetter(assignment.left),
501
+ ),
502
+ );
503
+ },
504
+
505
+ // foo || (foo = bar)
506
+ 'LogicalExpression[right.type="AssignmentExpression"][right.operator="="]'(
507
+ logical,
508
+ ) {
509
+ // Right side has to be parenthesized, otherwise would be parsed as (foo || foo) = bar which is illegal
510
+ if (
511
+ isReference(logical.left) &&
512
+ astUtils.isSameReference(logical.left, logical.right.left)
513
+ ) {
514
+ const descriptor = {
515
+ messageId: "logical",
516
+ node: logical,
517
+ data: { operator: `${logical.operator}=` },
518
+ };
519
+ const suggestion = {
520
+ messageId: "convertLogical",
521
+ data: { operator: `${logical.operator}=` },
522
+ *fix(ruleFixer) {
523
+ if (
524
+ sourceCode.getCommentsInside(logical).length > 0
525
+ ) {
526
+ return;
527
+ }
528
+
529
+ const parentPrecedence = astUtils.getPrecedence(
530
+ logical.parent,
531
+ );
532
+ const requiresOuterParenthesis =
533
+ logical.parent.type !== "ExpressionStatement" &&
534
+ (parentPrecedence === -1 ||
535
+ astUtils.getPrecedence({
536
+ type: "AssignmentExpression",
537
+ }) < parentPrecedence);
538
+
539
+ if (
540
+ !astUtils.isParenthesised(
541
+ sourceCode,
542
+ logical,
543
+ ) &&
544
+ requiresOuterParenthesis
545
+ ) {
546
+ yield ruleFixer.insertTextBefore(logical, "(");
547
+ yield ruleFixer.insertTextAfter(logical, ")");
548
+ }
549
+
550
+ // Also removes all opening parenthesis
551
+ yield ruleFixer.removeRange([
552
+ logical.range[0],
553
+ logical.right.range[0],
554
+ ]); // -> foo = bar)
555
+
556
+ // Also removes all ending parenthesis
557
+ yield ruleFixer.removeRange([
558
+ logical.right.range[1],
559
+ logical.range[1],
560
+ ]); // -> foo = bar
561
+
562
+ const operatorToken = getOperatorToken(
563
+ logical.right,
564
+ );
565
+
566
+ yield ruleFixer.insertTextBefore(
567
+ operatorToken,
568
+ logical.operator,
569
+ ); // -> foo ||= bar
570
+ },
571
+ };
572
+ const fix =
573
+ cannotBeGetter(logical.left) ||
574
+ accessesSingleProperty(logical.left);
575
+
576
+ context.report(
577
+ createConditionalFixer(descriptor, suggestion, fix),
578
+ );
579
+ }
580
+ },
581
+
582
+ // if (foo) foo = bar
583
+ "IfStatement[alternate=null]"(ifNode) {
584
+ if (!checkIf) {
585
+ return;
586
+ }
587
+
588
+ const hasBody = ifNode.consequent.type === "BlockStatement";
589
+
590
+ if (hasBody && ifNode.consequent.body.length !== 1) {
591
+ return;
592
+ }
593
+
594
+ const body = hasBody
595
+ ? ifNode.consequent.body[0]
596
+ : ifNode.consequent;
597
+ const scope = sourceCode.getScope(ifNode);
598
+ const existence = getExistence(ifNode.test, scope);
599
+
600
+ if (
601
+ body.type === "ExpressionStatement" &&
602
+ body.expression.type === "AssignmentExpression" &&
603
+ body.expression.operator === "=" &&
604
+ existence !== null &&
605
+ astUtils.isSameReference(
606
+ existence.reference,
607
+ body.expression.left,
608
+ )
609
+ ) {
610
+ const descriptor = {
611
+ messageId: "if",
612
+ node: ifNode,
613
+ data: { operator: `${existence.operator}=` },
614
+ };
615
+ const suggestion = {
616
+ messageId: "convertIf",
617
+ data: { operator: `${existence.operator}=` },
618
+ *fix(ruleFixer) {
619
+ if (
620
+ sourceCode.getCommentsInside(ifNode).length > 0
621
+ ) {
622
+ return;
623
+ }
624
+
625
+ const firstBodyToken =
626
+ sourceCode.getFirstToken(body);
627
+ const prevToken = sourceCode.getTokenBefore(ifNode);
628
+
629
+ if (
630
+ prevToken !== null &&
631
+ prevToken.value !== ";" &&
632
+ prevToken.value !== "{" &&
633
+ firstBodyToken.type !== "Identifier" &&
634
+ firstBodyToken.type !== "Keyword"
635
+ ) {
636
+ // Do not fix if the fixed statement could be part of the previous statement (eg. fn() if (a == null) (a) = b --> fn()(a) ??= b)
637
+ return;
638
+ }
639
+
640
+ const operatorToken = getOperatorToken(
641
+ body.expression,
642
+ );
643
+
644
+ yield ruleFixer.insertTextBefore(
645
+ operatorToken,
646
+ existence.operator,
647
+ ); // -> if (foo) foo ||= bar
648
+
649
+ yield ruleFixer.removeRange([
650
+ ifNode.range[0],
651
+ body.range[0],
652
+ ]); // -> foo ||= bar
653
+
654
+ yield ruleFixer.removeRange([
655
+ body.range[1],
656
+ ifNode.range[1],
657
+ ]); // -> foo ||= bar, only present if "if" had a body
658
+
659
+ const nextToken = sourceCode.getTokenAfter(
660
+ body.expression,
661
+ );
662
+
663
+ if (
664
+ hasBody &&
665
+ nextToken !== null &&
666
+ nextToken.value !== ";"
667
+ ) {
668
+ yield ruleFixer.insertTextAfter(ifNode, ";");
669
+ }
670
+ },
671
+ };
672
+ const shouldBeFixed =
673
+ cannotBeGetter(existence.reference) ||
674
+ (ifNode.test.type !== "LogicalExpression" &&
675
+ accessesSingleProperty(existence.reference));
676
+
677
+ context.report(
678
+ createConditionalFixer(
679
+ descriptor,
680
+ suggestion,
681
+ shouldBeFixed,
682
+ ),
683
+ );
684
+ }
685
+ },
686
+ };
687
+ },
505
688
  };