eslint 8.57.1 → 9.39.1

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 (458) hide show
  1. package/README.md +165 -115
  2. package/bin/eslint.js +112 -89
  3. package/conf/default-cli-options.js +22 -22
  4. package/conf/ecma-version.js +16 -0
  5. package/conf/globals.js +109 -94
  6. package/conf/replacements.json +24 -20
  7. package/conf/rule-type-list.json +89 -26
  8. package/lib/api.js +16 -20
  9. package/lib/cli-engine/cli-engine.js +841 -810
  10. package/lib/cli-engine/file-enumerator.js +384 -390
  11. package/lib/cli-engine/formatters/formatters-meta.json +17 -45
  12. package/lib/cli-engine/formatters/html.js +110 -102
  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 +97 -76
  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 +165 -148
  19. package/lib/cli-engine/load-rules.js +17 -17
  20. package/lib/cli.js +481 -399
  21. package/lib/config/config-loader.js +816 -0
  22. package/lib/config/config.js +674 -0
  23. package/lib/config/default-config.js +57 -46
  24. package/lib/config/flat-config-array.js +170 -333
  25. package/lib/config/flat-config-schema.js +389 -389
  26. package/lib/config-api.js +12 -0
  27. package/lib/eslint/eslint-helpers.js +1196 -663
  28. package/lib/eslint/eslint.js +1262 -607
  29. package/lib/eslint/index.js +3 -3
  30. package/lib/eslint/legacy-eslint.js +786 -0
  31. package/lib/eslint/worker.js +173 -0
  32. package/lib/languages/js/index.js +336 -0
  33. package/lib/languages/js/source-code/index.js +7 -0
  34. package/lib/languages/js/source-code/source-code.js +1364 -0
  35. package/lib/languages/js/source-code/token-store/backward-token-comment-cursor.js +61 -0
  36. package/lib/languages/js/source-code/token-store/backward-token-cursor.js +57 -0
  37. package/lib/{source-code → languages/js/source-code}/token-store/cursor.js +36 -36
  38. package/lib/languages/js/source-code/token-store/cursors.js +120 -0
  39. package/lib/{source-code → languages/js/source-code}/token-store/decorative-cursor.js +17 -18
  40. package/lib/{source-code → languages/js/source-code}/token-store/filter-cursor.js +19 -20
  41. package/lib/languages/js/source-code/token-store/forward-token-comment-cursor.js +65 -0
  42. package/lib/languages/js/source-code/token-store/forward-token-cursor.js +62 -0
  43. package/lib/languages/js/source-code/token-store/index.js +721 -0
  44. package/lib/{source-code → languages/js/source-code}/token-store/limit-cursor.js +17 -18
  45. package/lib/languages/js/source-code/token-store/padded-token-cursor.js +45 -0
  46. package/lib/{source-code → languages/js/source-code}/token-store/skip-cursor.js +19 -20
  47. package/lib/languages/js/source-code/token-store/utils.js +110 -0
  48. package/lib/languages/js/validate-language-options.js +196 -0
  49. package/lib/linter/apply-disable-directives.js +490 -371
  50. package/lib/linter/code-path-analysis/code-path-analyzer.js +650 -674
  51. package/lib/linter/code-path-analysis/code-path-segment.js +215 -216
  52. package/lib/linter/code-path-analysis/code-path-state.js +2118 -2096
  53. package/lib/linter/code-path-analysis/code-path.js +307 -317
  54. package/lib/linter/code-path-analysis/debug-helpers.js +183 -163
  55. package/lib/linter/code-path-analysis/fork-context.js +297 -272
  56. package/lib/linter/code-path-analysis/id-generator.js +22 -23
  57. package/lib/linter/esquery.js +332 -0
  58. package/lib/linter/file-context.js +144 -0
  59. package/lib/linter/file-report.js +608 -0
  60. package/lib/linter/index.js +3 -5
  61. package/lib/linter/interpolate.js +38 -16
  62. package/lib/linter/linter.js +2328 -1785
  63. package/lib/linter/rule-fixer.js +136 -107
  64. package/lib/linter/rules.js +37 -46
  65. package/lib/linter/source-code-fixer.js +96 -94
  66. package/lib/linter/source-code-traverser.js +333 -0
  67. package/lib/linter/source-code-visitor.js +81 -0
  68. package/lib/linter/timing.js +145 -97
  69. package/lib/linter/vfile.js +115 -0
  70. package/lib/options.js +464 -326
  71. package/lib/rule-tester/index.js +3 -1
  72. package/lib/rule-tester/rule-tester.js +1371 -998
  73. package/lib/rules/accessor-pairs.js +333 -259
  74. package/lib/rules/array-bracket-newline.js +250 -220
  75. package/lib/rules/array-bracket-spacing.js +286 -229
  76. package/lib/rules/array-callback-return.js +401 -354
  77. package/lib/rules/array-element-newline.js +358 -295
  78. package/lib/rules/arrow-body-style.js +400 -278
  79. package/lib/rules/arrow-parens.js +206 -155
  80. package/lib/rules/arrow-spacing.js +169 -145
  81. package/lib/rules/block-scoped-var.js +125 -123
  82. package/lib/rules/block-spacing.js +186 -158
  83. package/lib/rules/brace-style.js +262 -181
  84. package/lib/rules/callback-return.js +203 -174
  85. package/lib/rules/camelcase.js +403 -380
  86. package/lib/rules/capitalized-comments.js +253 -228
  87. package/lib/rules/class-methods-use-this.js +231 -168
  88. package/lib/rules/comma-dangle.js +379 -328
  89. package/lib/rules/comma-spacing.js +193 -177
  90. package/lib/rules/comma-style.js +375 -298
  91. package/lib/rules/complexity.js +180 -144
  92. package/lib/rules/computed-property-spacing.js +236 -193
  93. package/lib/rules/consistent-return.js +181 -170
  94. package/lib/rules/consistent-this.js +167 -141
  95. package/lib/rules/constructor-super.js +418 -411
  96. package/lib/rules/curly.js +407 -468
  97. package/lib/rules/default-case-last.js +39 -32
  98. package/lib/rules/default-case.js +89 -83
  99. package/lib/rules/default-param-last.js +69 -53
  100. package/lib/rules/dot-location.js +122 -92
  101. package/lib/rules/dot-notation.js +193 -153
  102. package/lib/rules/eol-last.js +122 -102
  103. package/lib/rules/eqeqeq.js +191 -155
  104. package/lib/rules/for-direction.js +150 -122
  105. package/lib/rules/func-call-spacing.js +261 -213
  106. package/lib/rules/func-name-matching.js +294 -209
  107. package/lib/rules/func-names.js +165 -164
  108. package/lib/rules/func-style.js +209 -86
  109. package/lib/rules/function-call-argument-newline.js +152 -111
  110. package/lib/rules/function-paren-newline.js +349 -273
  111. package/lib/rules/generator-star-spacing.js +229 -192
  112. package/lib/rules/getter-return.js +208 -170
  113. package/lib/rules/global-require.js +85 -58
  114. package/lib/rules/grouped-accessor-pairs.js +201 -148
  115. package/lib/rules/guard-for-in.js +72 -63
  116. package/lib/rules/handle-callback-err.js +108 -87
  117. package/lib/rules/id-blacklist.js +182 -187
  118. package/lib/rules/id-denylist.js +174 -179
  119. package/lib/rules/id-length.js +197 -157
  120. package/lib/rules/id-match.js +350 -286
  121. package/lib/rules/implicit-arrow-linebreak.js +102 -61
  122. package/lib/rules/indent-legacy.js +1345 -1102
  123. package/lib/rules/indent.js +2272 -1741
  124. package/lib/rules/index.js +320 -294
  125. package/lib/rules/init-declarations.js +139 -106
  126. package/lib/rules/jsx-quotes.js +94 -64
  127. package/lib/rules/key-spacing.js +750 -615
  128. package/lib/rules/keyword-spacing.js +648 -587
  129. package/lib/rules/line-comment-position.js +143 -108
  130. package/lib/rules/linebreak-style.js +115 -88
  131. package/lib/rules/lines-around-comment.js +540 -430
  132. package/lib/rules/lines-around-directive.js +233 -185
  133. package/lib/rules/lines-between-class-members.js +305 -216
  134. package/lib/rules/logical-assignment-operators.js +582 -398
  135. package/lib/rules/max-classes-per-file.js +69 -68
  136. package/lib/rules/max-depth.js +146 -143
  137. package/lib/rules/max-len.js +473 -416
  138. package/lib/rules/max-lines-per-function.js +201 -176
  139. package/lib/rules/max-lines.js +158 -162
  140. package/lib/rules/max-nested-callbacks.js +102 -104
  141. package/lib/rules/max-params.js +102 -75
  142. package/lib/rules/max-statements-per-line.js +205 -180
  143. package/lib/rules/max-statements.js +168 -164
  144. package/lib/rules/multiline-comment-style.js +638 -460
  145. package/lib/rules/multiline-ternary.js +241 -158
  146. package/lib/rules/new-cap.js +233 -232
  147. package/lib/rules/new-parens.js +88 -61
  148. package/lib/rules/newline-after-var.js +287 -233
  149. package/lib/rules/newline-before-return.js +229 -204
  150. package/lib/rules/newline-per-chained-call.js +142 -109
  151. package/lib/rules/no-alert.js +90 -79
  152. package/lib/rules/no-array-constructor.js +175 -113
  153. package/lib/rules/no-async-promise-executor.js +30 -24
  154. package/lib/rules/no-await-in-loop.js +79 -70
  155. package/lib/rules/no-bitwise.js +113 -87
  156. package/lib/rules/no-buffer-constructor.js +61 -37
  157. package/lib/rules/no-caller.js +39 -33
  158. package/lib/rules/no-case-declarations.js +61 -45
  159. package/lib/rules/no-catch-shadow.js +76 -62
  160. package/lib/rules/no-class-assign.js +51 -48
  161. package/lib/rules/no-compare-neg-zero.js +62 -48
  162. package/lib/rules/no-cond-assign.js +148 -132
  163. package/lib/rules/no-confusing-arrow.js +98 -63
  164. package/lib/rules/no-console.js +202 -188
  165. package/lib/rules/no-const-assign.js +58 -41
  166. package/lib/rules/no-constant-binary-expression.js +501 -407
  167. package/lib/rules/no-constant-condition.js +158 -131
  168. package/lib/rules/no-constructor-return.js +49 -49
  169. package/lib/rules/no-continue.js +25 -26
  170. package/lib/rules/no-control-regex.js +125 -121
  171. package/lib/rules/no-debugger.js +28 -30
  172. package/lib/rules/no-delete-var.js +29 -29
  173. package/lib/rules/no-div-regex.js +47 -40
  174. package/lib/rules/no-dupe-args.js +79 -69
  175. package/lib/rules/no-dupe-class-members.js +102 -89
  176. package/lib/rules/no-dupe-else-if.js +100 -77
  177. package/lib/rules/no-dupe-keys.js +133 -110
  178. package/lib/rules/no-duplicate-case.js +50 -43
  179. package/lib/rules/no-duplicate-imports.js +266 -188
  180. package/lib/rules/no-else-return.js +430 -385
  181. package/lib/rules/no-empty-character-class.js +57 -50
  182. package/lib/rules/no-empty-function.js +197 -128
  183. package/lib/rules/no-empty-pattern.js +63 -56
  184. package/lib/rules/no-empty-static-block.js +61 -35
  185. package/lib/rules/no-empty.js +135 -85
  186. package/lib/rules/no-eq-null.js +37 -32
  187. package/lib/rules/no-eval.js +258 -249
  188. package/lib/rules/no-ex-assign.js +42 -39
  189. package/lib/rules/no-extend-native.js +161 -160
  190. package/lib/rules/no-extra-bind.js +201 -190
  191. package/lib/rules/no-extra-boolean-cast.js +398 -295
  192. package/lib/rules/no-extra-label.js +150 -130
  193. package/lib/rules/no-extra-parens.js +1654 -1307
  194. package/lib/rules/no-extra-semi.js +146 -126
  195. package/lib/rules/no-fallthrough.js +200 -136
  196. package/lib/rules/no-floating-decimal.js +74 -48
  197. package/lib/rules/no-func-assign.js +54 -55
  198. package/lib/rules/no-global-assign.js +78 -72
  199. package/lib/rules/no-implicit-coercion.js +350 -262
  200. package/lib/rules/no-implicit-globals.js +174 -133
  201. package/lib/rules/no-implied-eval.js +150 -112
  202. package/lib/rules/no-import-assign.js +145 -159
  203. package/lib/rules/no-inline-comments.js +101 -96
  204. package/lib/rules/no-inner-declarations.js +115 -78
  205. package/lib/rules/no-invalid-regexp.js +223 -174
  206. package/lib/rules/no-invalid-this.js +145 -117
  207. package/lib/rules/no-irregular-whitespace.js +266 -250
  208. package/lib/rules/no-iterator.js +29 -33
  209. package/lib/rules/no-label-var.js +59 -61
  210. package/lib/rules/no-labels.js +138 -131
  211. package/lib/rules/no-lone-blocks.js +127 -123
  212. package/lib/rules/no-lonely-if.js +105 -67
  213. package/lib/rules/no-loop-func.js +245 -184
  214. package/lib/rules/no-loss-of-precision.js +236 -201
  215. package/lib/rules/no-magic-numbers.js +339 -217
  216. package/lib/rules/no-misleading-character-class.js +548 -253
  217. package/lib/rules/no-mixed-operators.js +188 -164
  218. package/lib/rules/no-mixed-requires.js +253 -224
  219. package/lib/rules/no-mixed-spaces-and-tabs.js +135 -103
  220. package/lib/rules/no-multi-assign.js +46 -47
  221. package/lib/rules/no-multi-spaces.js +163 -125
  222. package/lib/rules/no-multi-str.js +42 -40
  223. package/lib/rules/no-multiple-empty-lines.js +196 -140
  224. package/lib/rules/no-native-reassign.js +90 -74
  225. package/lib/rules/no-negated-condition.js +79 -74
  226. package/lib/rules/no-negated-in-lhs.js +45 -32
  227. package/lib/rules/no-nested-ternary.js +33 -31
  228. package/lib/rules/no-new-func.js +71 -62
  229. package/lib/rules/no-new-native-nonconstructor.js +43 -39
  230. package/lib/rules/no-new-object.js +48 -39
  231. package/lib/rules/no-new-require.js +48 -31
  232. package/lib/rules/no-new-symbol.js +61 -43
  233. package/lib/rules/no-new-wrappers.js +43 -41
  234. package/lib/rules/no-new.js +28 -29
  235. package/lib/rules/no-nonoctal-decimal-escape.js +149 -121
  236. package/lib/rules/no-obj-calls.js +66 -53
  237. package/lib/rules/no-object-constructor.js +104 -97
  238. package/lib/rules/no-octal-escape.js +40 -43
  239. package/lib/rules/no-octal.js +29 -32
  240. package/lib/rules/no-param-reassign.js +236 -218
  241. package/lib/rules/no-path-concat.js +66 -51
  242. package/lib/rules/no-plusplus.js +60 -63
  243. package/lib/rules/no-process-env.js +49 -32
  244. package/lib/rules/no-process-exit.js +48 -28
  245. package/lib/rules/no-promise-executor-return.js +205 -204
  246. package/lib/rules/no-proto.js +26 -29
  247. package/lib/rules/no-prototype-builtins.js +146 -124
  248. package/lib/rules/no-redeclare.js +154 -155
  249. package/lib/rules/no-regex-spaces.js +183 -161
  250. package/lib/rules/no-restricted-exports.js +208 -174
  251. package/lib/rules/no-restricted-globals.js +254 -112
  252. package/lib/rules/no-restricted-imports.js +824 -384
  253. package/lib/rules/no-restricted-modules.js +222 -186
  254. package/lib/rules/no-restricted-properties.js +218 -153
  255. package/lib/rules/no-restricted-syntax.js +56 -52
  256. package/lib/rules/no-return-assign.js +56 -49
  257. package/lib/rules/no-return-await.js +147 -120
  258. package/lib/rules/no-script-url.js +53 -46
  259. package/lib/rules/no-self-assign.js +148 -145
  260. package/lib/rules/no-self-compare.js +63 -46
  261. package/lib/rules/no-sequences.js +135 -115
  262. package/lib/rules/no-setter-return.js +176 -178
  263. package/lib/rules/no-shadow-restricted-names.js +84 -36
  264. package/lib/rules/no-shadow.js +598 -310
  265. package/lib/rules/no-spaced-func.js +82 -60
  266. package/lib/rules/no-sparse-arrays.js +46 -28
  267. package/lib/rules/no-sync.js +61 -44
  268. package/lib/rules/no-tabs.js +83 -54
  269. package/lib/rules/no-template-curly-in-string.js +33 -32
  270. package/lib/rules/no-ternary.js +25 -28
  271. package/lib/rules/no-this-before-super.js +332 -298
  272. package/lib/rules/no-throw-literal.js +31 -36
  273. package/lib/rules/no-trailing-spaces.js +208 -174
  274. package/lib/rules/no-unassigned-vars.js +80 -0
  275. package/lib/rules/no-undef-init.js +86 -60
  276. package/lib/rules/no-undef.js +52 -47
  277. package/lib/rules/no-undefined.js +73 -74
  278. package/lib/rules/no-underscore-dangle.js +370 -322
  279. package/lib/rules/no-unexpected-multiline.js +112 -102
  280. package/lib/rules/no-unmodified-loop-condition.js +254 -254
  281. package/lib/rules/no-unneeded-ternary.js +212 -146
  282. package/lib/rules/no-unreachable-loop.js +145 -140
  283. package/lib/rules/no-unreachable.js +255 -248
  284. package/lib/rules/no-unsafe-finally.js +93 -85
  285. package/lib/rules/no-unsafe-negation.js +105 -81
  286. package/lib/rules/no-unsafe-optional-chaining.js +193 -177
  287. package/lib/rules/no-unused-expressions.js +199 -158
  288. package/lib/rules/no-unused-labels.js +139 -124
  289. package/lib/rules/no-unused-private-class-members.js +206 -182
  290. package/lib/rules/no-unused-vars.js +1708 -687
  291. package/lib/rules/no-use-before-define.js +327 -229
  292. package/lib/rules/no-useless-assignment.js +654 -0
  293. package/lib/rules/no-useless-backreference.js +212 -143
  294. package/lib/rules/no-useless-call.js +58 -53
  295. package/lib/rules/no-useless-catch.js +40 -40
  296. package/lib/rules/no-useless-computed-key.js +144 -108
  297. package/lib/rules/no-useless-concat.js +65 -59
  298. package/lib/rules/no-useless-constructor.js +160 -97
  299. package/lib/rules/no-useless-escape.js +364 -291
  300. package/lib/rules/no-useless-rename.js +183 -153
  301. package/lib/rules/no-useless-return.js +344 -307
  302. package/lib/rules/no-var.js +245 -212
  303. package/lib/rules/no-void.js +51 -46
  304. package/lib/rules/no-warning-comments.js +191 -183
  305. package/lib/rules/no-whitespace-before-property.js +131 -97
  306. package/lib/rules/no-with.js +24 -26
  307. package/lib/rules/nonblock-statement-body-position.js +149 -112
  308. package/lib/rules/object-curly-newline.js +306 -247
  309. package/lib/rules/object-curly-spacing.js +360 -296
  310. package/lib/rules/object-property-newline.js +137 -88
  311. package/lib/rules/object-shorthand.js +632 -500
  312. package/lib/rules/one-var-declaration-per-line.js +104 -82
  313. package/lib/rules/one-var.js +686 -536
  314. package/lib/rules/operator-assignment.js +219 -158
  315. package/lib/rules/operator-linebreak.js +295 -233
  316. package/lib/rules/padded-blocks.js +346 -290
  317. package/lib/rules/padding-line-between-statements.js +443 -421
  318. package/lib/rules/prefer-arrow-callback.js +371 -315
  319. package/lib/rules/prefer-const.js +418 -373
  320. package/lib/rules/prefer-destructuring.js +309 -278
  321. package/lib/rules/prefer-exponentiation-operator.js +176 -132
  322. package/lib/rules/prefer-named-capture-group.js +160 -141
  323. package/lib/rules/prefer-numeric-literals.js +121 -112
  324. package/lib/rules/prefer-object-has-own.js +116 -82
  325. package/lib/rules/prefer-object-spread.js +214 -193
  326. package/lib/rules/prefer-promise-reject-errors.js +140 -118
  327. package/lib/rules/prefer-reflect.js +126 -103
  328. package/lib/rules/prefer-regex-literals.js +561 -463
  329. package/lib/rules/prefer-rest-params.js +79 -80
  330. package/lib/rules/prefer-spread.js +47 -43
  331. package/lib/rules/prefer-template.js +266 -194
  332. package/lib/rules/preserve-caught-error.js +535 -0
  333. package/lib/rules/quote-props.js +373 -289
  334. package/lib/rules/quotes.js +374 -308
  335. package/lib/rules/radix.js +152 -134
  336. package/lib/rules/require-atomic-updates.js +316 -282
  337. package/lib/rules/require-await.js +153 -82
  338. package/lib/rules/require-unicode-regexp.js +296 -108
  339. package/lib/rules/require-yield.js +53 -54
  340. package/lib/rules/rest-spread-spacing.js +128 -98
  341. package/lib/rules/semi-spacing.js +281 -232
  342. package/lib/rules/semi-style.js +176 -116
  343. package/lib/rules/semi.js +456 -418
  344. package/lib/rules/sort-imports.js +307 -229
  345. package/lib/rules/sort-keys.js +219 -181
  346. package/lib/rules/sort-vars.js +127 -91
  347. package/lib/rules/space-before-blocks.js +199 -171
  348. package/lib/rules/space-before-function-paren.js +186 -148
  349. package/lib/rules/space-in-parens.js +359 -270
  350. package/lib/rules/space-infix-ops.js +237 -183
  351. package/lib/rules/space-unary-ops.js +356 -280
  352. package/lib/rules/spaced-comment.js +363 -301
  353. package/lib/rules/strict.js +266 -229
  354. package/lib/rules/switch-colon-spacing.js +130 -104
  355. package/lib/rules/symbol-description.js +45 -48
  356. package/lib/rules/template-curly-spacing.js +148 -124
  357. package/lib/rules/template-tag-spacing.js +98 -70
  358. package/lib/rules/unicode-bom.js +54 -54
  359. package/lib/rules/use-isnan.js +237 -110
  360. package/lib/rules/utils/ast-utils.js +2139 -1688
  361. package/lib/rules/utils/char-source.js +247 -0
  362. package/lib/rules/utils/fix-tracker.js +99 -88
  363. package/lib/rules/utils/keywords.js +59 -59
  364. package/lib/rules/utils/lazy-loading-rule-map.js +81 -78
  365. package/lib/rules/utils/regular-expressions.js +35 -19
  366. package/lib/rules/utils/unicode/index.js +9 -4
  367. package/lib/rules/utils/unicode/is-combining-character.js +1 -1
  368. package/lib/rules/utils/unicode/is-emoji-modifier.js +1 -1
  369. package/lib/rules/utils/unicode/is-regional-indicator-symbol.js +1 -1
  370. package/lib/rules/utils/unicode/is-surrogate-pair.js +1 -1
  371. package/lib/rules/valid-typeof.js +153 -109
  372. package/lib/rules/vars-on-top.js +152 -144
  373. package/lib/rules/wrap-iife.js +204 -173
  374. package/lib/rules/wrap-regex.js +77 -47
  375. package/lib/rules/yield-star-spacing.js +145 -116
  376. package/lib/rules/yoda.js +283 -274
  377. package/lib/services/parser-service.js +65 -0
  378. package/lib/services/processor-service.js +101 -0
  379. package/lib/services/suppressions-service.js +302 -0
  380. package/lib/services/warning-service.js +98 -0
  381. package/lib/shared/ajv.js +14 -14
  382. package/lib/shared/assert.js +21 -0
  383. package/lib/shared/ast-utils.js +7 -6
  384. package/lib/shared/deep-merge-arrays.js +62 -0
  385. package/lib/shared/directives.js +3 -2
  386. package/lib/shared/flags.js +108 -0
  387. package/lib/shared/logging.js +24 -16
  388. package/lib/shared/naming.js +109 -0
  389. package/lib/shared/option-utils.js +63 -0
  390. package/lib/shared/relative-module-resolver.js +18 -40
  391. package/lib/shared/runtime-info.js +138 -128
  392. package/lib/shared/serialization.js +78 -0
  393. package/lib/shared/severity.js +22 -22
  394. package/lib/shared/stats.js +30 -0
  395. package/lib/shared/string-utils.js +19 -21
  396. package/lib/shared/text-table.js +68 -0
  397. package/lib/shared/translate-cli-options.js +281 -0
  398. package/lib/shared/traverser.js +153 -146
  399. package/lib/types/config-api.d.ts +12 -0
  400. package/lib/types/index.d.ts +1473 -0
  401. package/lib/types/rules.d.ts +5589 -0
  402. package/lib/types/universal.d.ts +6 -0
  403. package/lib/types/use-at-your-own-risk.d.ts +87 -0
  404. package/lib/universal.js +10 -0
  405. package/lib/unsupported-api.js +8 -9
  406. package/messages/all-files-ignored.js +3 -3
  407. package/messages/all-matched-files-ignored.js +21 -0
  408. package/messages/config-file-missing.js +16 -0
  409. package/messages/config-plugin-missing.js +14 -0
  410. package/messages/config-serialize-function.js +30 -0
  411. package/messages/eslintrc-incompat.js +35 -16
  412. package/messages/eslintrc-plugins.js +8 -5
  413. package/messages/extend-config-missing.js +3 -3
  414. package/messages/failed-to-read-json.js +3 -3
  415. package/messages/file-not-found.js +3 -3
  416. package/messages/invalid-rule-options.js +2 -2
  417. package/messages/invalid-rule-severity.js +2 -2
  418. package/messages/no-config-found.js +4 -4
  419. package/messages/plugin-conflict.js +9 -9
  420. package/messages/plugin-invalid.js +4 -4
  421. package/messages/plugin-missing.js +4 -4
  422. package/messages/print-config-with-directory-path.js +2 -2
  423. package/messages/shared.js +6 -1
  424. package/messages/whitespace-found.js +3 -3
  425. package/package.json +105 -60
  426. package/conf/config-schema.js +0 -93
  427. package/lib/cli-engine/formatters/checkstyle.js +0 -60
  428. package/lib/cli-engine/formatters/compact.js +0 -60
  429. package/lib/cli-engine/formatters/jslint-xml.js +0 -41
  430. package/lib/cli-engine/formatters/junit.js +0 -82
  431. package/lib/cli-engine/formatters/tap.js +0 -95
  432. package/lib/cli-engine/formatters/unix.js +0 -58
  433. package/lib/cli-engine/formatters/visualstudio.js +0 -63
  434. package/lib/cli-engine/xml-escape.js +0 -34
  435. package/lib/config/flat-config-helpers.js +0 -111
  436. package/lib/config/rule-validator.js +0 -158
  437. package/lib/eslint/flat-eslint.js +0 -1159
  438. package/lib/linter/config-comment-parser.js +0 -185
  439. package/lib/linter/node-event-generator.js +0 -354
  440. package/lib/linter/report-translator.js +0 -369
  441. package/lib/linter/safe-emitter.js +0 -52
  442. package/lib/rule-tester/flat-rule-tester.js +0 -1131
  443. package/lib/rules/require-jsdoc.js +0 -122
  444. package/lib/rules/utils/patterns/letters.js +0 -36
  445. package/lib/rules/valid-jsdoc.js +0 -516
  446. package/lib/shared/config-validator.js +0 -347
  447. package/lib/shared/deprecation-warnings.js +0 -58
  448. package/lib/shared/types.js +0 -216
  449. package/lib/source-code/index.js +0 -5
  450. package/lib/source-code/source-code.js +0 -1055
  451. package/lib/source-code/token-store/backward-token-comment-cursor.js +0 -57
  452. package/lib/source-code/token-store/backward-token-cursor.js +0 -58
  453. package/lib/source-code/token-store/cursors.js +0 -90
  454. package/lib/source-code/token-store/forward-token-comment-cursor.js +0 -57
  455. package/lib/source-code/token-store/forward-token-cursor.js +0 -63
  456. package/lib/source-code/token-store/index.js +0 -627
  457. package/lib/source-code/token-store/padded-token-cursor.js +0 -38
  458. package/lib/source-code/token-store/utils.js +0 -107
@@ -1,7 +1,6 @@
1
1
  /**
2
- * @fileoverview Main API Class
3
- * @author Kai Cataldo
4
- * @author Toru Nagashima
2
+ * @fileoverview Main class using flat config
3
+ * @author Nicholas C. Zakas
5
4
  */
6
5
 
7
6
  "use strict";
@@ -10,698 +9,1354 @@
10
9
  // Requirements
11
10
  //------------------------------------------------------------------------------
12
11
 
13
- const path = require("path");
14
- const fs = require("fs");
15
- const { promisify } = require("util");
16
- const { CLIEngine, getCLIEngineInternalSlots } = require("../cli-engine/cli-engine");
17
- const BuiltinRules = require("../rules");
18
- const {
19
- Legacy: {
20
- ConfigOps: {
21
- getRuleSeverity
22
- }
23
- }
24
- } = require("@eslint/eslintrc");
12
+ const { existsSync } = require("node:fs");
13
+ const fs = require("node:fs/promises");
14
+ const os = require("node:os");
15
+ const path = require("node:path");
16
+ const { pathToFileURL } = require("node:url");
17
+ const { SHARE_ENV, Worker } = require("node:worker_threads");
25
18
  const { version } = require("../../package.json");
19
+ const { defaultConfig } = require("../config/default-config");
20
+ const timing = require("../linter/timing");
21
+
22
+ const {
23
+ createDebug,
24
+
25
+ findFiles,
26
+ getCacheFile,
27
+
28
+ isNonEmptyString,
29
+ isArrayOfNonEmptyString,
30
+
31
+ createIgnoreResult,
32
+ isErrorMessage,
33
+ getPlaceholderPath,
34
+
35
+ processOptions,
36
+ loadOptionsFromModule,
37
+
38
+ getFixerForFixTypes,
39
+ verifyText,
40
+ lintFile,
41
+ createLinter,
42
+ createLintResultCache,
43
+ createDefaultConfigs,
44
+ createConfigLoader,
45
+ } = require("./eslint-helpers");
46
+ const { Retrier } = require("@humanwhocodes/retry");
47
+ const { ConfigLoader } = require("../config/config-loader");
48
+ const { WarningService } = require("../services/warning-service");
49
+ const { Config } = require("../config/config.js");
50
+ const {
51
+ getShorthandName,
52
+ getNamespaceFromTerm,
53
+ normalizePackageName,
54
+ } = require("../shared/naming.js");
55
+ const { resolve } = require("../shared/relative-module-resolver.js");
26
56
 
27
57
  //------------------------------------------------------------------------------
28
58
  // Typedefs
29
59
  //------------------------------------------------------------------------------
30
60
 
31
- /** @typedef {import("../cli-engine/cli-engine").LintReport} CLIEngineLintReport */
32
- /** @typedef {import("../shared/types").DeprecatedRuleInfo} DeprecatedRuleInfo */
33
- /** @typedef {import("../shared/types").ConfigData} ConfigData */
34
- /** @typedef {import("../shared/types").LintMessage} LintMessage */
35
- /** @typedef {import("../shared/types").SuppressedLintMessage} SuppressedLintMessage */
36
- /** @typedef {import("../shared/types").Plugin} Plugin */
37
- /** @typedef {import("../shared/types").Rule} Rule */
38
- /** @typedef {import("../shared/types").LintResult} LintResult */
39
- /** @typedef {import("../shared/types").ResultsMeta} ResultsMeta */
40
-
61
+ // For VSCode IntelliSense
41
62
  /**
42
- * The main formatter object.
43
- * @typedef LoadedFormatter
44
- * @property {(results: LintResult[], resultsMeta: ResultsMeta) => string | Promise<string>} format format function.
63
+ * @import { Config as CalculatedConfig } from "../config/config.js";
64
+ * @import { FlatConfigArray } from "../config/flat-config-array.js";
65
+ * @import { RuleDefinition, RulesMeta } from "@eslint/core";
66
+ * @import { WorkerLintResults } from "./worker.js";
45
67
  */
46
68
 
69
+ /** @typedef {import("../types").Linter.Config} Config */
70
+ /** @typedef {import("../types").ESLint.DeprecatedRuleUse} DeprecatedRuleInfo */
71
+ /** @typedef {import("../types").ESLint.LintResult} LintResult */
72
+ /** @typedef {import("../types").ESLint.Plugin} Plugin */
73
+ /** @typedef {import("../types").ESLint.ResultsMeta} ResultsMeta */
74
+
47
75
  /**
48
76
  * The options with which to configure the ESLint instance.
49
77
  * @typedef {Object} ESLintOptions
50
78
  * @property {boolean} [allowInlineConfig] Enable or disable inline configuration comments.
51
- * @property {ConfigData} [baseConfig] Base config object, extended by all configs used with this instance
79
+ * @property {Config|Array<Config>} [baseConfig] Base config, extended by all configs used with this instance
52
80
  * @property {boolean} [cache] Enable result caching.
53
81
  * @property {string} [cacheLocation] The cache file to use instead of .eslintcache.
54
82
  * @property {"metadata" | "content"} [cacheStrategy] The strategy used to detect changed files.
83
+ * @property {number | "auto" | "off"} [concurrency] Maximum number of linting threads, "auto" to choose automatically, "off" for no multithreading.
55
84
  * @property {string} [cwd] The value to use for the current working directory.
56
85
  * @property {boolean} [errorOnUnmatchedPattern] If `false` then `ESLint#lintFiles()` doesn't throw even if no target files found. Defaults to `true`.
57
- * @property {string[]} [extensions] An array of file extensions to check.
58
86
  * @property {boolean|Function} [fix] Execute in autofix mode. If a function, should return a boolean.
59
87
  * @property {string[]} [fixTypes] Array of rule types to apply fixes for.
88
+ * @property {string[]} [flags] Array of feature flags to enable.
60
89
  * @property {boolean} [globInputPaths] Set to false to skip glob resolution of input file paths to lint (default: true). If false, each input file paths is assumed to be a non-glob path to an existing file.
61
- * @property {boolean} [ignore] False disables use of .eslintignore.
62
- * @property {string} [ignorePath] The ignore file to use instead of .eslintignore.
63
- * @property {ConfigData} [overrideConfig] Override config object, overrides all configs used with this instance
64
- * @property {string} [overrideConfigFile] The configuration file to use.
65
- * @property {Record<string,Plugin>|null} [plugins] Preloaded plugins. This is a map-like object, keys are plugin IDs and each value is implementation.
66
- * @property {"error" | "warn" | "off"} [reportUnusedDisableDirectives] the severity to report unused eslint-disable directives.
67
- * @property {string} [resolvePluginsRelativeTo] The folder where plugins should be resolved from, defaulting to the CWD.
68
- * @property {string[]} [rulePaths] An array of directories to load custom rules from.
69
- * @property {boolean} [useEslintrc] False disables looking for .eslintrc.* files.
70
- */
71
-
72
- /**
73
- * A rules metadata object.
74
- * @typedef {Object} RulesMeta
75
- * @property {string} id The plugin ID.
76
- * @property {Object} definition The plugin definition.
77
- */
78
-
79
- /**
80
- * Private members for the `ESLint` instance.
81
- * @typedef {Object} ESLintPrivateMembers
82
- * @property {CLIEngine} cliEngine The wrapped CLIEngine instance.
83
- * @property {ESLintOptions} options The options used to instantiate the ESLint instance.
90
+ * @property {boolean} [ignore] False disables all ignore patterns except for the default ones.
91
+ * @property {string[]} [ignorePatterns] Ignore file patterns to use in addition to config ignores. These patterns are relative to `cwd`.
92
+ * @property {Config|Array<Config>} [overrideConfig] Override config, overrides all configs used with this instance
93
+ * @property {boolean|string} [overrideConfigFile] Searches for default config file when falsy;
94
+ * doesn't do any config file lookup when `true`; considered to be a config filename
95
+ * when a string.
96
+ * @property {boolean} [passOnNoPatterns=false] When set to true, missing patterns cause
97
+ * the linting operation to short circuit and not report any failures.
98
+ * @property {Record<string,Plugin>} [plugins] An array of plugin implementations.
99
+ * @property {boolean} [stats] True enables added statistics on lint results.
100
+ * @property {boolean} [warnIgnored] Show warnings when the file list includes ignored files
84
101
  */
85
102
 
86
103
  //------------------------------------------------------------------------------
87
104
  // Helpers
88
105
  //------------------------------------------------------------------------------
89
106
 
90
- const writeFile = promisify(fs.writeFile);
107
+ const hrtimeBigint = process.hrtime.bigint;
108
+
109
+ const debug = createDebug("eslint:eslint");
110
+ const privateMembers = new WeakMap();
111
+ const removedFormatters = new Set([
112
+ "checkstyle",
113
+ "codeframe",
114
+ "compact",
115
+ "jslint-xml",
116
+ "junit",
117
+ "table",
118
+ "tap",
119
+ "unix",
120
+ "visualstudio",
121
+ ]);
122
+ const fileRetryCodes = new Set(["ENFILE", "EMFILE"]);
91
123
 
92
124
  /**
93
- * The map with which to store private class members.
94
- * @type {WeakMap<ESLint, ESLintPrivateMembers>}
125
+ * Create rulesMeta object.
126
+ * @param {Map<string, RuleDefinition>} rules a map of rules from which to generate the object.
127
+ * @returns {Record<string, RulesMeta>} metadata for all enabled rules.
95
128
  */
96
- const privateMembersMap = new WeakMap();
129
+ function createRulesMeta(rules) {
130
+ return Array.from(rules).reduce((retVal, [id, rule]) => {
131
+ retVal[id] = rule.meta;
132
+ return retVal;
133
+ }, {});
134
+ }
135
+
136
+ /** @type {WeakMap<CalculatedConfig, DeprecatedRuleInfo[]>} */
137
+ const usedDeprecatedRulesCache = new WeakMap();
97
138
 
98
139
  /**
99
- * Check if a given value is a non-empty string or not.
100
- * @param {any} x The value to check.
101
- * @returns {boolean} `true` if `x` is a non-empty string.
140
+ * Create used deprecated rule list.
141
+ * @param {ESLint} eslint The ESLint instance.
142
+ * @param {string} maybeFilePath The absolute path to a lint target file or `"<text>"`.
143
+ * @returns {DeprecatedRuleInfo[]} The used deprecated rule list.
102
144
  */
103
- function isNonEmptyString(x) {
104
- return typeof x === "string" && x.trim() !== "";
145
+ function getOrFindUsedDeprecatedRules(eslint, maybeFilePath) {
146
+ const {
147
+ options: { cwd },
148
+ configLoader,
149
+ } = privateMembers.get(eslint);
150
+ const filePath = path.isAbsolute(maybeFilePath)
151
+ ? maybeFilePath
152
+ : getPlaceholderPath(cwd);
153
+ const configs = configLoader.getCachedConfigArrayForFile(filePath);
154
+ const config = configs.getConfig(filePath);
155
+
156
+ // Most files use the same config, so cache it.
157
+ if (config && !usedDeprecatedRulesCache.has(config)) {
158
+ const retv = [];
159
+
160
+ if (config.rules) {
161
+ for (const [ruleId, ruleConf] of Object.entries(config.rules)) {
162
+ if (Config.getRuleNumericSeverity(ruleConf) === 0) {
163
+ continue;
164
+ }
165
+ const rule = config.getRuleDefinition(ruleId);
166
+ const meta = rule && rule.meta;
167
+
168
+ if (meta && meta.deprecated) {
169
+ const usesNewFormat = typeof meta.deprecated === "object";
170
+
171
+ retv.push({
172
+ ruleId,
173
+ replacedBy: usesNewFormat
174
+ ? (meta.deprecated.replacedBy?.map(
175
+ replacement =>
176
+ `${replacement.plugin?.name !== void 0 ? `${getShorthandName(replacement.plugin.name, "eslint-plugin")}/` : ""}${replacement.rule?.name ?? ""}`,
177
+ ) ?? [])
178
+ : meta.replacedBy || [],
179
+ info: usesNewFormat ? meta.deprecated : void 0,
180
+ });
181
+ }
182
+ }
183
+ }
184
+
185
+ usedDeprecatedRulesCache.set(config, Object.freeze(retv));
186
+ }
187
+
188
+ return config ? usedDeprecatedRulesCache.get(config) : Object.freeze([]);
105
189
  }
106
190
 
107
191
  /**
108
- * Check if a given value is an array of non-empty strings or not.
109
- * @param {any} x The value to check.
110
- * @returns {boolean} `true` if `x` is an array of non-empty strings.
192
+ * Processes the linting results generated by a CLIEngine linting report to
193
+ * match the ESLint class's API.
194
+ * @param {ESLint} eslint The ESLint instance.
195
+ * @param {LintResult[]} results The linting results to process.
196
+ * @returns {LintResult[]} The processed linting results.
111
197
  */
112
- function isArrayOfNonEmptyString(x) {
113
- return Array.isArray(x) && x.every(isNonEmptyString);
198
+ function processLintReport(eslint, results) {
199
+ const descriptor = {
200
+ configurable: true,
201
+ enumerable: true,
202
+ get() {
203
+ return getOrFindUsedDeprecatedRules(eslint, this.filePath);
204
+ },
205
+ };
206
+
207
+ for (const result of results) {
208
+ Object.defineProperty(result, "usedDeprecatedRules", descriptor);
209
+ }
210
+
211
+ return results;
114
212
  }
115
213
 
116
214
  /**
117
- * Check if a given value is a valid fix type or not.
118
- * @param {any} x The value to check.
119
- * @returns {boolean} `true` if `x` is valid fix type.
215
+ * An Array.prototype.sort() compatible compare function to order results by their file path.
216
+ * @param {LintResult} a The first lint result.
217
+ * @param {LintResult} b The second lint result.
218
+ * @returns {number} An integer representing the order in which the two results should occur.
120
219
  */
121
- function isFixType(x) {
122
- return x === "directive" || x === "problem" || x === "suggestion" || x === "layout";
220
+ function compareResultsByFilePath(a, b) {
221
+ if (a.filePath < b.filePath) {
222
+ return -1;
223
+ }
224
+
225
+ if (a.filePath > b.filePath) {
226
+ return 1;
227
+ }
228
+
229
+ return 0;
123
230
  }
124
231
 
125
232
  /**
126
- * Check if a given value is an array of fix types or not.
127
- * @param {any} x The value to check.
128
- * @returns {boolean} `true` if `x` is an array of fix types.
233
+ * Determines which config file to use. This is determined by seeing if an
234
+ * override config file was passed, and if so, using it; otherwise, as long
235
+ * as override config file is not explicitly set to `false`, it will search
236
+ * upwards from the cwd for a file named `eslint.config.js`.
237
+ *
238
+ * This function is used primarily by the `--inspect-config` option. For now,
239
+ * we will maintain the existing behavior, which is to search up from the cwd.
240
+ * @param {ESLintOptions} options The ESLint instance options.
241
+ * @returns {Promise<{configFilePath:string|undefined;basePath:string}>} Location information for
242
+ * the config file.
129
243
  */
130
- function isFixTypeArray(x) {
131
- return Array.isArray(x) && x.every(isFixType);
244
+ async function locateConfigFileToUse({ configFile, cwd }) {
245
+ const configLoader = new ConfigLoader({
246
+ cwd,
247
+ configFile,
248
+ });
249
+
250
+ const configFilePath = await configLoader.findConfigFileForPath(
251
+ path.join(cwd, "__placeholder__.js"),
252
+ );
253
+
254
+ if (!configFilePath) {
255
+ throw new Error("No ESLint configuration file was found.");
256
+ }
257
+
258
+ return {
259
+ configFilePath,
260
+ basePath: configFile ? cwd : path.dirname(configFilePath),
261
+ };
132
262
  }
133
263
 
134
264
  /**
135
- * The error for invalid options.
265
+ * Creates an error to be thrown when an array of results passed to `getRulesMetaForResults` was not created by the current engine.
266
+ * @param {Error|undefined} cause The original error that led to this symptom error being thrown. Might not always be available.
267
+ * @returns {TypeError} An error object.
136
268
  */
137
- class ESLintInvalidOptionsError extends Error {
138
- constructor(messages) {
139
- super(`Invalid Options:\n- ${messages.join("\n- ")}`);
140
- this.code = "ESLINT_INVALID_OPTIONS";
141
- Error.captureStackTrace(this, ESLintInvalidOptionsError);
142
- }
269
+ function createExtraneousResultsError(cause) {
270
+ return new TypeError(
271
+ "Results object was not created from this ESLint instance.",
272
+ {
273
+ cause,
274
+ },
275
+ );
143
276
  }
144
277
 
145
278
  /**
146
- * Validates and normalizes options for the wrapped CLIEngine instance.
147
- * @param {ESLintOptions} options The options to process.
148
- * @throws {ESLintInvalidOptionsError} If of any of a variety of type errors.
149
- * @returns {ESLintOptions} The normalized options.
279
+ * Maximum number of files assumed to be best handled by one worker thread.
280
+ * This value is a heuristic estimation that can be adjusted if required.
150
281
  */
151
- function processOptions({
152
- allowInlineConfig = true, // ← we cannot use `overrideConfig.noInlineConfig` instead because `allowInlineConfig` has side-effect that suppress warnings that show inline configs are ignored.
153
- baseConfig = null,
154
- cache = false,
155
- cacheLocation = ".eslintcache",
156
- cacheStrategy = "metadata",
157
- cwd = process.cwd(),
158
- errorOnUnmatchedPattern = true,
159
- extensions = null, // ← should be null by default because if it's an array then it suppresses RFC20 feature.
160
- fix = false,
161
- fixTypes = null, // ← should be null by default because if it's an array then it suppresses rules that don't have the `meta.type` property.
162
- globInputPaths = true,
163
- ignore = true,
164
- ignorePath = null, // ← should be null by default because if it's a string then it may throw ENOENT.
165
- overrideConfig = null,
166
- overrideConfigFile = null,
167
- plugins = {},
168
- reportUnusedDisableDirectives = null, // ← should be null by default because if it's a string then it overrides the 'reportUnusedDisableDirectives' setting in config files. And we cannot use `overrideConfig.reportUnusedDisableDirectives` instead because we cannot configure the `error` severity with that.
169
- resolvePluginsRelativeTo = null, // ← should be null by default because if it's a string then it suppresses RFC47 feature.
170
- rulePaths = [],
171
- useEslintrc = true,
172
- ...unknownOptions
173
- }) {
174
- const errors = [];
175
- const unknownOptionKeys = Object.keys(unknownOptions);
176
-
177
- if (unknownOptionKeys.length >= 1) {
178
- errors.push(`Unknown options: ${unknownOptionKeys.join(", ")}`);
179
- if (unknownOptionKeys.includes("cacheFile")) {
180
- errors.push("'cacheFile' has been removed. Please use the 'cacheLocation' option instead.");
181
- }
182
- if (unknownOptionKeys.includes("configFile")) {
183
- errors.push("'configFile' has been removed. Please use the 'overrideConfigFile' option instead.");
184
- }
185
- if (unknownOptionKeys.includes("envs")) {
186
- errors.push("'envs' has been removed. Please use the 'overrideConfig.env' option instead.");
187
- }
188
- if (unknownOptionKeys.includes("globals")) {
189
- errors.push("'globals' has been removed. Please use the 'overrideConfig.globals' option instead.");
190
- }
191
- if (unknownOptionKeys.includes("ignorePattern")) {
192
- errors.push("'ignorePattern' has been removed. Please use the 'overrideConfig.ignorePatterns' option instead.");
193
- }
194
- if (unknownOptionKeys.includes("parser")) {
195
- errors.push("'parser' has been removed. Please use the 'overrideConfig.parser' option instead.");
196
- }
197
- if (unknownOptionKeys.includes("parserOptions")) {
198
- errors.push("'parserOptions' has been removed. Please use the 'overrideConfig.parserOptions' option instead.");
199
- }
200
- if (unknownOptionKeys.includes("rules")) {
201
- errors.push("'rules' has been removed. Please use the 'overrideConfig.rules' option instead.");
202
- }
203
- }
204
- if (typeof allowInlineConfig !== "boolean") {
205
- errors.push("'allowInlineConfig' must be a boolean.");
206
- }
207
- if (typeof baseConfig !== "object") {
208
- errors.push("'baseConfig' must be an object or null.");
209
- }
210
- if (typeof cache !== "boolean") {
211
- errors.push("'cache' must be a boolean.");
212
- }
213
- if (!isNonEmptyString(cacheLocation)) {
214
- errors.push("'cacheLocation' must be a non-empty string.");
215
- }
216
- if (
217
- cacheStrategy !== "metadata" &&
218
- cacheStrategy !== "content"
219
- ) {
220
- errors.push("'cacheStrategy' must be any of \"metadata\", \"content\".");
221
- }
222
- if (!isNonEmptyString(cwd) || !path.isAbsolute(cwd)) {
223
- errors.push("'cwd' must be an absolute path.");
224
- }
225
- if (typeof errorOnUnmatchedPattern !== "boolean") {
226
- errors.push("'errorOnUnmatchedPattern' must be a boolean.");
227
- }
228
- if (!isArrayOfNonEmptyString(extensions) && extensions !== null) {
229
- errors.push("'extensions' must be an array of non-empty strings or null.");
230
- }
231
- if (typeof fix !== "boolean" && typeof fix !== "function") {
232
- errors.push("'fix' must be a boolean or a function.");
233
- }
234
- if (fixTypes !== null && !isFixTypeArray(fixTypes)) {
235
- errors.push("'fixTypes' must be an array of any of \"directive\", \"problem\", \"suggestion\", and \"layout\".");
236
- }
237
- if (typeof globInputPaths !== "boolean") {
238
- errors.push("'globInputPaths' must be a boolean.");
239
- }
240
- if (typeof ignore !== "boolean") {
241
- errors.push("'ignore' must be a boolean.");
242
- }
243
- if (!isNonEmptyString(ignorePath) && ignorePath !== null) {
244
- errors.push("'ignorePath' must be a non-empty string or null.");
245
- }
246
- if (typeof overrideConfig !== "object") {
247
- errors.push("'overrideConfig' must be an object or null.");
248
- }
249
- if (!isNonEmptyString(overrideConfigFile) && overrideConfigFile !== null) {
250
- errors.push("'overrideConfigFile' must be a non-empty string or null.");
251
- }
252
- if (typeof plugins !== "object") {
253
- errors.push("'plugins' must be an object or null.");
254
- } else if (plugins !== null && Object.keys(plugins).includes("")) {
255
- errors.push("'plugins' must not include an empty string.");
256
- }
257
- if (Array.isArray(plugins)) {
258
- errors.push("'plugins' doesn't add plugins to configuration to load. Please use the 'overrideConfig.plugins' option instead.");
259
- }
260
- if (
261
- reportUnusedDisableDirectives !== "error" &&
262
- reportUnusedDisableDirectives !== "warn" &&
263
- reportUnusedDisableDirectives !== "off" &&
264
- reportUnusedDisableDirectives !== null
265
- ) {
266
- errors.push("'reportUnusedDisableDirectives' must be any of \"error\", \"warn\", \"off\", and null.");
267
- }
268
- if (
269
- !isNonEmptyString(resolvePluginsRelativeTo) &&
270
- resolvePluginsRelativeTo !== null
271
- ) {
272
- errors.push("'resolvePluginsRelativeTo' must be a non-empty string or null.");
273
- }
274
- if (!isArrayOfNonEmptyString(rulePaths)) {
275
- errors.push("'rulePaths' must be an array of non-empty strings.");
276
- }
277
- if (typeof useEslintrc !== "boolean") {
278
- errors.push("'useEslintrc' must be a boolean.");
279
- }
280
-
281
- if (errors.length > 0) {
282
- throw new ESLintInvalidOptionsError(errors);
283
- }
284
-
285
- return {
286
- allowInlineConfig,
287
- baseConfig,
288
- cache,
289
- cacheLocation,
290
- cacheStrategy,
291
- configFile: overrideConfigFile,
292
- cwd: path.normalize(cwd),
293
- errorOnUnmatchedPattern,
294
- extensions,
295
- fix,
296
- fixTypes,
297
- globInputPaths,
298
- ignore,
299
- ignorePath,
300
- reportUnusedDisableDirectives,
301
- resolvePluginsRelativeTo,
302
- rulePaths,
303
- useEslintrc
304
- };
305
- }
282
+ const AUTO_FILES_PER_WORKER = 50;
306
283
 
307
284
  /**
308
- * Check if a value has one or more properties and that value is not undefined.
309
- * @param {any} obj The value to check.
310
- * @returns {boolean} `true` if `obj` has one or more properties that that value is not undefined.
285
+ * Calculates the number of worker threads to run for "auto" concurrency depending on the number of
286
+ * files that need to be processed.
287
+ *
288
+ * The number of worker threads is calculated as the number of files that need to be processed
289
+ * (`processableFileCount`) divided by the number of files assumed to be best handled by one worker
290
+ * thread (`AUTO_FILES_PER_WORKER`), rounded up to the next integer.
291
+ * Two adjustments are made to this calculation: first, the number of workers is capped at half the
292
+ * number of available CPU cores (`maxWorkers`); second, a value of 1 is converted to 0.
293
+ * The following table shows the relationship between the number of files to be processed and the
294
+ * number of workers:
295
+ *
296
+ * Files to be processed | Workers
297
+ * -------------------------------------------------------------------|-----------------
298
+ * 0 | 0
299
+ * 1, 2, …, AUTO_FILES_PER_WORKER | 0 (there's no 1)
300
+ * AUTO_FILES_PER_WORKER + 1, …, AUTO_FILES_PER_WORKER * 2 | 2
301
+ * AUTO_FILES_PER_WORKER * 2 + 1, …, AUTO_FILES_PER_WORKER * 3 | 3
302
+ * ⋯ | ⋯
303
+ * AUTO_FILES_PER_WORKER * (𝑛 - 1) + 1, …, AUTO_FILES_PER_WORKER * 𝑛 | 𝑛
304
+ * ⋯ | ⋯
305
+ * AUTO_FILES_PER_WORKER * (maxWorkers - 1) + 1, … | maxWorkers
306
+ *
307
+ * The number of files to be processed should be determined by the calling function.
308
+ * @param {number} processableFileCount The number of files that need to be processed.
309
+ * @param {number} maxWorkers The maximum number of workers to run.
310
+ * @returns {number} The number of worker threads to run.
311
311
  */
312
- function hasDefinedProperty(obj) {
313
- if (typeof obj === "object" && obj !== null) {
314
- for (const key in obj) {
315
- if (typeof obj[key] !== "undefined") {
316
- return true;
317
- }
318
- }
319
- }
320
- return false;
312
+ function getWorkerCountFor(processableFileCount, maxWorkers) {
313
+ let workerCount = Math.ceil(processableFileCount / AUTO_FILES_PER_WORKER);
314
+ if (workerCount > maxWorkers) {
315
+ workerCount = maxWorkers;
316
+ }
317
+ if (workerCount <= 1) {
318
+ workerCount = 0;
319
+ }
320
+ return workerCount;
321
321
  }
322
322
 
323
323
  /**
324
- * Create rulesMeta object.
325
- * @param {Map<string,Rule>} rules a map of rules from which to generate the object.
326
- * @returns {Object} metadata for all enabled rules.
324
+ * Returns true if a file has no valid cached results or if it needs to be reprocessed because there are violations that may need fixing.
325
+ * This function will access the filesystem.
326
+ * @param {LintResultCache} lintResultCache The lint result cache.
327
+ * @param {boolean} fix The fix option.
328
+ * @param {string} filePath The file for which to retrieve lint results.
329
+ * @param {Config} config The config of the file.
330
+ * @returns {boolean} True if the file needs to be reprocessed.
327
331
  */
328
- function createRulesMeta(rules) {
329
- return Array.from(rules).reduce((retVal, [id, rule]) => {
330
- retVal[id] = rule.meta;
331
- return retVal;
332
- }, {});
332
+ function needsReprocessing(lintResultCache, fix, filePath, config) {
333
+ const results = lintResultCache.getValidCachedLintResults(filePath, config);
334
+
335
+ // This reflects the reprocessing logic of the `lintFile` helper function.
336
+ return !results || (fix && results.messages && results.messages.length > 0);
333
337
  }
334
338
 
335
- /** @type {WeakMap<ExtractedConfig, DeprecatedRuleInfo[]>} */
336
- const usedDeprecatedRulesCache = new WeakMap();
339
+ /**
340
+ * Calculates the number of worker threads to run for "auto" concurrency.
341
+ *
342
+ * The number of worker threads depends on the number files that need to be processed.
343
+ * Typically, this includes all non-ignored files.
344
+ * In a cached run with "metadata" strategy, files with a valid cached result aren't counted.
345
+ * @param {ESLint} eslint ESLint instance.
346
+ * @param {string[]} filePaths File paths to lint.
347
+ * @param {number} maxWorkers The maximum number of workers to run.
348
+ * @returns {number} The number of worker threads to run for "auto" concurrency.
349
+ */
350
+ function calculateAutoWorkerCount(eslint, filePaths, maxWorkers) {
351
+ const startTime = hrtimeBigint();
352
+ const {
353
+ configLoader,
354
+ lintResultCache,
355
+ options: { cacheStrategy, fix },
356
+ } = privateMembers.get(eslint);
357
+ /** True if cache is not used or if strategy is "content". */
358
+ const countAllMatched = !lintResultCache || cacheStrategy === "content";
359
+
360
+ let processableFileCount = 0;
361
+ let remainingFiles = filePaths.length;
362
+
363
+ /** The number of workers if none of the remaining files were to be counted. */
364
+ let lowWorkerCount = 0;
365
+
366
+ /*
367
+ * Rather than counting all files to be processed in advance, we stop iterating as soon as we reach
368
+ * a point where adding more files doesn't change the number of workers anymore.
369
+ */
370
+ for (const filePath of filePaths) {
371
+ /** The number of workers if all of the remaining files were to be counted. */
372
+ const highWorkerCount = getWorkerCountFor(
373
+ processableFileCount + remainingFiles,
374
+ maxWorkers,
375
+ );
376
+ if (lowWorkerCount >= highWorkerCount) {
377
+ // The highest possible number of workers has been reached, so stop counting.
378
+ break;
379
+ }
380
+ remainingFiles--;
381
+ const configs = configLoader.getCachedConfigArrayForFile(filePath);
382
+ const config = configs.getConfig(filePath);
383
+ if (!config) {
384
+ // file is ignored
385
+ continue;
386
+ }
387
+ if (
388
+ countAllMatched ||
389
+ needsReprocessing(lintResultCache, fix, filePath, config)
390
+ ) {
391
+ processableFileCount++;
392
+ lowWorkerCount = getWorkerCountFor(
393
+ processableFileCount,
394
+ maxWorkers,
395
+ );
396
+ }
397
+ }
398
+ debug(
399
+ "%d file(s) to process counted in %t",
400
+ processableFileCount,
401
+ hrtimeBigint() - startTime,
402
+ );
403
+ return lowWorkerCount;
404
+ }
337
405
 
338
406
  /**
339
- * Create used deprecated rule list.
340
- * @param {CLIEngine} cliEngine The CLIEngine instance.
341
- * @param {string} maybeFilePath The absolute path to a lint target file or `"<text>"`.
342
- * @returns {DeprecatedRuleInfo[]} The used deprecated rule list.
407
+ * Calculates the number of workers to run based on the concurrency setting and the number of files to lint.
408
+ * @param {ESLint} eslint The ESLint instance.
409
+ * @param {string[]} filePaths File paths to lint.
410
+ * @param {{ availableParallelism: () => number }} [os] Node.js `os` module, or a mock for testing.
411
+ * @returns {number} The effective number of worker threads to be started. A value of zero disables multithread linting.
343
412
  */
344
- function getOrFindUsedDeprecatedRules(cliEngine, maybeFilePath) {
345
- const {
346
- configArrayFactory,
347
- options: { cwd }
348
- } = getCLIEngineInternalSlots(cliEngine);
349
- const filePath = path.isAbsolute(maybeFilePath)
350
- ? maybeFilePath
351
- : path.join(cwd, "__placeholder__.js");
352
- const configArray = configArrayFactory.getConfigArrayForFile(filePath);
353
- const config = configArray.extractConfig(filePath);
354
-
355
- // Most files use the same config, so cache it.
356
- if (!usedDeprecatedRulesCache.has(config)) {
357
- const pluginRules = configArray.pluginRules;
358
- const retv = [];
359
-
360
- for (const [ruleId, ruleConf] of Object.entries(config.rules)) {
361
- if (getRuleSeverity(ruleConf) === 0) {
362
- continue;
363
- }
364
- const rule = pluginRules.get(ruleId) || BuiltinRules.get(ruleId);
365
- const meta = rule && rule.meta;
366
-
367
- if (meta && meta.deprecated) {
368
- retv.push({ ruleId, replacedBy: meta.replacedBy || [] });
369
- }
370
- }
371
-
372
- usedDeprecatedRulesCache.set(config, Object.freeze(retv));
373
- }
374
-
375
- return usedDeprecatedRulesCache.get(config);
413
+ function calculateWorkerCount(
414
+ eslint,
415
+ filePaths,
416
+ { availableParallelism } = os,
417
+ ) {
418
+ const { concurrency } = privateMembers.get(eslint).options;
419
+ switch (concurrency) {
420
+ case "off":
421
+ return 0;
422
+ case "auto": {
423
+ const maxWorkers = availableParallelism() >> 1;
424
+ return calculateAutoWorkerCount(eslint, filePaths, maxWorkers);
425
+ }
426
+ default: {
427
+ const workerCount = Math.min(concurrency, filePaths.length);
428
+ return workerCount > 1 ? workerCount : 0;
429
+ }
430
+ }
376
431
  }
377
432
 
433
+ // Used internally. Do not expose.
434
+ const disableCloneabilityCheck = Symbol(
435
+ "Do not check for uncloneable options.",
436
+ );
437
+
378
438
  /**
379
- * Processes the linting results generated by a CLIEngine linting report to
380
- * match the ESLint class's API.
381
- * @param {CLIEngine} cliEngine The CLIEngine instance.
382
- * @param {CLIEngineLintReport} report The CLIEngine linting report to process.
383
- * @returns {LintResult[]} The processed linting results.
439
+ * The smallest net linting ratio that doesn't trigger a poor concurrency warning.
440
+ * The net linting ratio is defined as the net linting duration divided by the thread's total runtime,
441
+ * where the net linting duration is the total linting time minus the time spent on I/O-intensive operations:
442
+ * **Net Linting Ratio** = (**Linting Time** **I/O Time**) / **Thread Runtime**.
443
+ * - **Linting Time**: Total time spent linting files
444
+ * - **I/O Time**: Portion of linting time spent loading configs and reading files
445
+ * - **Thread Runtime**: End-to-end execution time of the thread
446
+ *
447
+ * This value is a heuristic estimation that can be adjusted if required.
384
448
  */
385
- function processCLIEngineLintReport(cliEngine, { results }) {
386
- const descriptor = {
387
- configurable: true,
388
- enumerable: true,
389
- get() {
390
- return getOrFindUsedDeprecatedRules(cliEngine, this.filePath);
391
- }
392
- };
393
-
394
- for (const result of results) {
395
- Object.defineProperty(result, "usedDeprecatedRules", descriptor);
396
- }
397
-
398
- return results;
449
+ const LOW_NET_LINTING_RATIO = 0.7;
450
+
451
+ /**
452
+ * Runs worker threads to lint files.
453
+ * @param {string[]} filePaths File paths to lint.
454
+ * @param {number} workerCount The number of worker threads to run.
455
+ * @param {ESLintOptions | string} eslintOptionsOrURL The unprocessed ESLint options or the URL of the options module.
456
+ * @param {() => void} warnOnLowNetLintingRatio A function to call if the net linting ratio is low.
457
+ * @returns {Promise<LintResult[]>} Lint results.
458
+ */
459
+ async function runWorkers(
460
+ filePaths,
461
+ workerCount,
462
+ eslintOptionsOrURL,
463
+ warnOnLowNetLintingRatio,
464
+ ) {
465
+ const fileCount = filePaths.length;
466
+ const results = Array(fileCount);
467
+ const workerURL = pathToFileURL(path.join(__dirname, "./worker.js"));
468
+ const filePathIndexArray = new Uint32Array(
469
+ new SharedArrayBuffer(Uint32Array.BYTES_PER_ELEMENT),
470
+ );
471
+ const abortController = new AbortController();
472
+ const abortSignal = abortController.signal;
473
+ const workerOptions = {
474
+ env: SHARE_ENV,
475
+ workerData: {
476
+ eslintOptionsOrURL,
477
+ filePathIndexArray,
478
+ filePaths,
479
+ },
480
+ };
481
+
482
+ let worstNetLintingRatio = 1;
483
+
484
+ /**
485
+ * A promise executor function that starts a worker thread on each invocation.
486
+ * @param {() => void} resolve_ Called when the worker thread terminates successfully.
487
+ * @param {(error: Error) => void} reject Called when the worker thread terminates with an error.
488
+ * @returns {void}
489
+ */
490
+ function workerExecutor(resolve_, reject) {
491
+ const workerStartTime = hrtimeBigint();
492
+ const worker = new Worker(workerURL, workerOptions);
493
+ worker.once(
494
+ "message",
495
+ (/** @type {WorkerLintResults} */ indexedResults) => {
496
+ const workerDuration = hrtimeBigint() - workerStartTime;
497
+
498
+ // The net linting ratio provides an approximate measure of worker thread efficiency, defined as the net linting duration divided by the thread's total runtime.
499
+ const netLintingRatio =
500
+ Number(indexedResults.netLintingDuration) /
501
+ Number(workerDuration);
502
+
503
+ worstNetLintingRatio = Math.min(
504
+ worstNetLintingRatio,
505
+ netLintingRatio,
506
+ );
507
+
508
+ if (timing.enabled && indexedResults.timings) {
509
+ timing.mergeData(indexedResults.timings);
510
+ }
511
+
512
+ for (const result of indexedResults) {
513
+ const { index } = result;
514
+ delete result.index;
515
+ results[index] = result;
516
+ }
517
+ resolve_();
518
+ },
519
+ );
520
+ worker.once("error", error => {
521
+ abortController.abort(error);
522
+ reject(error);
523
+ });
524
+ abortSignal.addEventListener("abort", () => worker.terminate());
525
+ }
526
+
527
+ const promises = Array(workerCount);
528
+ for (let index = 0; index < workerCount; ++index) {
529
+ promises[index] = new Promise(workerExecutor);
530
+ }
531
+
532
+ try {
533
+ await Promise.all(promises);
534
+ } catch (error) {
535
+ /*
536
+ * If any worker fails, suppress timing display in the main thread
537
+ * to avoid printing partial or misleading timing output.
538
+ */
539
+ timing.disableDisplay();
540
+ throw error;
541
+ }
542
+
543
+ if (worstNetLintingRatio < LOW_NET_LINTING_RATIO) {
544
+ warnOnLowNetLintingRatio();
545
+ }
546
+
547
+ return results;
399
548
  }
400
549
 
401
550
  /**
402
- * An Array.prototype.sort() compatible compare function to order results by their file path.
403
- * @param {LintResult} a The first lint result.
404
- * @param {LintResult} b The second lint result.
405
- * @returns {number} An integer representing the order in which the two results should occur.
551
+ * Lint files in multithread mode.
552
+ * @param {ESLint} eslint ESLint instance.
553
+ * @param {string[]} filePaths File paths to lint.
554
+ * @param {number} workerCount The number of worker threads to run.
555
+ * @param {ESLintOptions | string} eslintOptionsOrURL The unprocessed ESLint options or the URL of the options module.
556
+ * @param {() => void} warnOnLowNetLintingRatio A function to call if the net linting ratio is low.
557
+ * @returns {Promise<LintResult[]>} Lint results.
406
558
  */
407
- function compareResultsByFilePath(a, b) {
408
- if (a.filePath < b.filePath) {
409
- return -1;
410
- }
559
+ async function lintFilesWithMultithreading(
560
+ eslint,
561
+ filePaths,
562
+ workerCount,
563
+ eslintOptionsOrURL,
564
+ warnOnLowNetLintingRatio,
565
+ ) {
566
+ const { configLoader, lintResultCache } = privateMembers.get(eslint);
567
+
568
+ const results = await runWorkers(
569
+ filePaths,
570
+ workerCount,
571
+ eslintOptionsOrURL,
572
+ warnOnLowNetLintingRatio,
573
+ );
574
+ // Persist the cache to disk.
575
+ if (lintResultCache) {
576
+ results.forEach((result, index) => {
577
+ if (result) {
578
+ const filePath = filePaths[index];
579
+ const configs =
580
+ configLoader.getCachedConfigArrayForFile(filePath);
581
+ const config = configs.getConfig(filePath);
582
+
583
+ if (config) {
584
+ /*
585
+ * Store the lint result in the LintResultCache.
586
+ * NOTE: The LintResultCache will remove the file source and any
587
+ * other properties that are difficult to serialize, and will
588
+ * hydrate those properties back in on future lint runs.
589
+ */
590
+ lintResultCache.setCachedLintResults(
591
+ filePath,
592
+ config,
593
+ result,
594
+ );
595
+ }
596
+ }
597
+ });
598
+ }
599
+ return results;
600
+ }
411
601
 
412
- if (a.filePath > b.filePath) {
413
- return 1;
414
- }
602
+ /**
603
+ * Lint files in single-thread mode.
604
+ * @param {ESLint} eslint ESLint instance.
605
+ * @param {string[]} filePaths File paths to lint.
606
+ * @returns {Promise<LintResult[]>} Lint results.
607
+ */
608
+ async function lintFilesWithoutMultithreading(eslint, filePaths) {
609
+ const {
610
+ configLoader,
611
+ linter,
612
+ lintResultCache,
613
+ options: eslintOptions,
614
+ } = privateMembers.get(eslint);
615
+
616
+ const controller = new AbortController();
617
+ const retrier = new Retrier(error => fileRetryCodes.has(error.code), {
618
+ concurrency: 100,
619
+ });
620
+
621
+ /*
622
+ * Because we need to process multiple files, including reading from disk,
623
+ * it is most efficient to start by reading each file via promises so that
624
+ * they can be done in parallel. Then, we can lint the returned text. This
625
+ * ensures we are waiting the minimum amount of time in between lints.
626
+ */
627
+ const results = await Promise.all(
628
+ filePaths.map(async filePath => {
629
+ const configs = configLoader.getCachedConfigArrayForFile(filePath);
630
+ const config = configs.getConfig(filePath);
631
+
632
+ const result = await lintFile(
633
+ filePath,
634
+ configs,
635
+ eslintOptions,
636
+ linter,
637
+ lintResultCache,
638
+ null,
639
+ retrier,
640
+ controller,
641
+ );
642
+
643
+ if (config) {
644
+ /*
645
+ * Store the lint result in the LintResultCache.
646
+ * NOTE: The LintResultCache will remove the file source and any
647
+ * other properties that are difficult to serialize, and will
648
+ * hydrate those properties back in on future lint runs.
649
+ */
650
+ lintResultCache?.setCachedLintResults(filePath, config, result);
651
+ }
652
+
653
+ return result;
654
+ }),
655
+ );
656
+ return results;
657
+ }
415
658
 
416
- return 0;
659
+ /**
660
+ * Throws an error if the given options are not cloneable.
661
+ * @param {ESLintOptions} options The options to check.
662
+ * @returns {void}
663
+ * @throws {TypeError} If the options are not cloneable.
664
+ */
665
+ function validateOptionCloneability(options) {
666
+ try {
667
+ structuredClone(options);
668
+ return;
669
+ } catch {
670
+ // continue
671
+ }
672
+ const uncloneableOptionKeys = Object.keys(options)
673
+ .filter(key => {
674
+ try {
675
+ structuredClone(options[key]);
676
+ } catch {
677
+ return true;
678
+ }
679
+ return false;
680
+ })
681
+ .sort();
682
+ const error = new TypeError(
683
+ `The ${uncloneableOptionKeys.length === 1 ? "option" : "options"} ${new Intl.ListFormat("en-US").format(uncloneableOptionKeys.map(key => `"${key}"`))} cannot be cloned. When concurrency is enabled, all options must be cloneable values (JSON values). Remove uncloneable options or use an options module.`,
684
+ );
685
+ error.code = "ESLINT_UNCLONEABLE_OPTIONS";
686
+ throw error;
417
687
  }
418
688
 
689
+ //-----------------------------------------------------------------------------
690
+ // Main API
691
+ //-----------------------------------------------------------------------------
692
+
419
693
  /**
420
- * Main API.
694
+ * Primary Node.js API for ESLint.
421
695
  */
422
696
  class ESLint {
423
-
424
- /**
425
- * Creates a new instance of the main ESLint API.
426
- * @param {ESLintOptions} options The options for this instance.
427
- */
428
- constructor(options = {}) {
429
- const processedOptions = processOptions(options);
430
- const cliEngine = new CLIEngine(processedOptions, { preloadedPlugins: options.plugins });
431
- const {
432
- configArrayFactory,
433
- lastConfigArrays
434
- } = getCLIEngineInternalSlots(cliEngine);
435
- let updated = false;
436
-
437
- /*
438
- * Address `overrideConfig` to set override config.
439
- * Operate the `configArrayFactory` internal slot directly because this
440
- * functionality doesn't exist as the public API of CLIEngine.
441
- */
442
- if (hasDefinedProperty(options.overrideConfig)) {
443
- configArrayFactory.setOverrideConfig(options.overrideConfig);
444
- updated = true;
445
- }
446
-
447
- // Update caches.
448
- if (updated) {
449
- configArrayFactory.clearCache();
450
- lastConfigArrays[0] = configArrayFactory.getConfigArrayForFile();
451
- }
452
-
453
- // Initialize private properties.
454
- privateMembersMap.set(this, {
455
- cliEngine,
456
- options: processedOptions
457
- });
458
- }
459
-
460
- /**
461
- * The version text.
462
- * @type {string}
463
- */
464
- static get version() {
465
- return version;
466
- }
467
-
468
- /**
469
- * Outputs fixes from the given results to files.
470
- * @param {LintResult[]} results The lint results.
471
- * @returns {Promise<void>} Returns a promise that is used to track side effects.
472
- */
473
- static async outputFixes(results) {
474
- if (!Array.isArray(results)) {
475
- throw new Error("'results' must be an array");
476
- }
477
-
478
- await Promise.all(
479
- results
480
- .filter(result => {
481
- if (typeof result !== "object" || result === null) {
482
- throw new Error("'results' must include only objects");
483
- }
484
- return (
485
- typeof result.output === "string" &&
486
- path.isAbsolute(result.filePath)
487
- );
488
- })
489
- .map(r => writeFile(r.filePath, r.output))
490
- );
491
- }
492
-
493
- /**
494
- * Returns results that only contains errors.
495
- * @param {LintResult[]} results The results to filter.
496
- * @returns {LintResult[]} The filtered results.
497
- */
498
- static getErrorResults(results) {
499
- return CLIEngine.getErrorResults(results);
500
- }
501
-
502
- /**
503
- * Returns meta objects for each rule represented in the lint results.
504
- * @param {LintResult[]} results The results to fetch rules meta for.
505
- * @returns {Object} A mapping of ruleIds to rule meta objects.
506
- */
507
- getRulesMetaForResults(results) {
508
-
509
- const resultRuleIds = new Set();
510
-
511
- // first gather all ruleIds from all results
512
-
513
- for (const result of results) {
514
- for (const { ruleId } of result.messages) {
515
- resultRuleIds.add(ruleId);
516
- }
517
- for (const { ruleId } of result.suppressedMessages) {
518
- resultRuleIds.add(ruleId);
519
- }
520
- }
521
-
522
- // create a map of all rules in the results
523
-
524
- const { cliEngine } = privateMembersMap.get(this);
525
- const rules = cliEngine.getRules();
526
- const resultRules = new Map();
527
-
528
- for (const [ruleId, rule] of rules) {
529
- if (resultRuleIds.has(ruleId)) {
530
- resultRules.set(ruleId, rule);
531
- }
532
- }
533
-
534
- return createRulesMeta(resultRules);
535
-
536
- }
537
-
538
- /**
539
- * Executes the current configuration on an array of file and directory names.
540
- * @param {string[]} patterns An array of file and directory names.
541
- * @returns {Promise<LintResult[]>} The results of linting the file patterns given.
542
- */
543
- async lintFiles(patterns) {
544
- if (!isNonEmptyString(patterns) && !isArrayOfNonEmptyString(patterns)) {
545
- throw new Error("'patterns' must be a non-empty string or an array of non-empty strings");
546
- }
547
- const { cliEngine } = privateMembersMap.get(this);
548
-
549
- return processCLIEngineLintReport(
550
- cliEngine,
551
- cliEngine.executeOnFiles(patterns)
552
- );
553
- }
554
-
555
- /**
556
- * Executes the current configuration on text.
557
- * @param {string} code A string of JavaScript code to lint.
558
- * @param {Object} [options] The options.
559
- * @param {string} [options.filePath] The path to the file of the source code.
560
- * @param {boolean} [options.warnIgnored] When set to true, warn if given filePath is an ignored path.
561
- * @returns {Promise<LintResult[]>} The results of linting the string of code given.
562
- */
563
- async lintText(code, options = {}) {
564
- if (typeof code !== "string") {
565
- throw new Error("'code' must be a string");
566
- }
567
- if (typeof options !== "object") {
568
- throw new Error("'options' must be an object, null, or undefined");
569
- }
570
- const {
571
- filePath,
572
- warnIgnored = false,
573
- ...unknownOptions
574
- } = options || {};
575
-
576
- const unknownOptionKeys = Object.keys(unknownOptions);
577
-
578
- if (unknownOptionKeys.length > 0) {
579
- throw new Error(`'options' must not include the unknown option(s): ${unknownOptionKeys.join(", ")}`);
580
- }
581
-
582
- if (filePath !== void 0 && !isNonEmptyString(filePath)) {
583
- throw new Error("'options.filePath' must be a non-empty string or undefined");
584
- }
585
- if (typeof warnIgnored !== "boolean") {
586
- throw new Error("'options.warnIgnored' must be a boolean or undefined");
587
- }
588
-
589
- const { cliEngine } = privateMembersMap.get(this);
590
-
591
- return processCLIEngineLintReport(
592
- cliEngine,
593
- cliEngine.executeOnText(code, filePath, warnIgnored)
594
- );
595
- }
596
-
597
- /**
598
- * Returns the formatter representing the given formatter name.
599
- * @param {string} [name] The name of the formatter to load.
600
- * The following values are allowed:
601
- * - `undefined` ... Load `stylish` builtin formatter.
602
- * - A builtin formatter name ... Load the builtin formatter.
603
- * - A third-party formatter name:
604
- * - `foo` → `eslint-formatter-foo`
605
- * - `@foo` → `@foo/eslint-formatter`
606
- * - `@foo/bar` → `@foo/eslint-formatter-bar`
607
- * - A file path ... Load the file.
608
- * @returns {Promise<LoadedFormatter>} A promise resolving to the formatter object.
609
- * This promise will be rejected if the given formatter was not found or not
610
- * a function.
611
- */
612
- async loadFormatter(name = "stylish") {
613
- if (typeof name !== "string") {
614
- throw new Error("'name' must be a string");
615
- }
616
-
617
- const { cliEngine, options } = privateMembersMap.get(this);
618
- const formatter = cliEngine.getFormatter(name);
619
-
620
- if (typeof formatter !== "function") {
621
- throw new Error(`Formatter must be a function, but got a ${typeof formatter}.`);
622
- }
623
-
624
- return {
625
-
626
- /**
627
- * The main formatter method.
628
- * @param {LintResult[]} results The lint results to format.
629
- * @param {ResultsMeta} resultsMeta Warning count and max threshold.
630
- * @returns {string | Promise<string>} The formatted lint results.
631
- */
632
- format(results, resultsMeta) {
633
- let rulesMeta = null;
634
-
635
- results.sort(compareResultsByFilePath);
636
-
637
- return formatter(results, {
638
- ...resultsMeta,
639
- get cwd() {
640
- return options.cwd;
641
- },
642
- get rulesMeta() {
643
- if (!rulesMeta) {
644
- rulesMeta = createRulesMeta(cliEngine.getRules());
645
- }
646
-
647
- return rulesMeta;
648
- }
649
- });
650
- }
651
- };
652
- }
653
-
654
- /**
655
- * Returns a configuration object for the given file based on the CLI options.
656
- * This is the same logic used by the ESLint CLI executable to determine
657
- * configuration for each file it processes.
658
- * @param {string} filePath The path of the file to retrieve a config object for.
659
- * @returns {Promise<ConfigData>} A configuration object for the file.
660
- */
661
- async calculateConfigForFile(filePath) {
662
- if (!isNonEmptyString(filePath)) {
663
- throw new Error("'filePath' must be a non-empty string");
664
- }
665
- const { cliEngine } = privateMembersMap.get(this);
666
-
667
- return cliEngine.getConfigForFile(filePath);
668
- }
669
-
670
- /**
671
- * Checks if a given path is ignored by ESLint.
672
- * @param {string} filePath The path of the file to check.
673
- * @returns {Promise<boolean>} Whether or not the given path is ignored.
674
- */
675
- async isPathIgnored(filePath) {
676
- if (!isNonEmptyString(filePath)) {
677
- throw new Error("'filePath' must be a non-empty string");
678
- }
679
- const { cliEngine } = privateMembersMap.get(this);
680
-
681
- return cliEngine.isPathIgnored(filePath);
682
- }
697
+ /**
698
+ * The type of configuration used by this class.
699
+ * @type {string}
700
+ */
701
+ static configType = "flat";
702
+
703
+ /**
704
+ * The loader to use for finding config files.
705
+ * @type {ConfigLoader}
706
+ */
707
+ #configLoader;
708
+
709
+ /**
710
+ * The unprocessed options or the URL of the options module. Only set when concurrency is enabled.
711
+ * @type {ESLintOptions | string | undefined}
712
+ */
713
+ #optionsOrURL;
714
+
715
+ /**
716
+ * Creates a new instance of the main ESLint API.
717
+ * @param {ESLintOptions} options The options for this instance.
718
+ */
719
+ constructor(options = {}) {
720
+ const processedOptions = processOptions(options);
721
+ if (
722
+ !options[disableCloneabilityCheck] &&
723
+ processedOptions.concurrency !== "off"
724
+ ) {
725
+ validateOptionCloneability(options);
726
+
727
+ // Save the unprocessed options in an instance field to pass to worker threads in `lintFiles()`.
728
+ this.#optionsOrURL = options;
729
+ }
730
+ const warningService = new WarningService();
731
+ const linter = createLinter(processedOptions, warningService);
732
+
733
+ const cacheFilePath = getCacheFile(
734
+ processedOptions.cacheLocation,
735
+ processedOptions.cwd,
736
+ );
737
+
738
+ const lintResultCache = createLintResultCache(
739
+ processedOptions,
740
+ cacheFilePath,
741
+ );
742
+ const defaultConfigs = createDefaultConfigs(options.plugins);
743
+
744
+ this.#configLoader = createConfigLoader(
745
+ processedOptions,
746
+ defaultConfigs,
747
+ linter,
748
+ warningService,
749
+ );
750
+
751
+ debug(`Using config loader ${this.#configLoader.constructor.name}`);
752
+
753
+ privateMembers.set(this, {
754
+ options: processedOptions,
755
+ linter,
756
+ cacheFilePath,
757
+ lintResultCache,
758
+ defaultConfigs,
759
+ configs: null,
760
+ configLoader: this.#configLoader,
761
+ warningService,
762
+ });
763
+
764
+ // Check for the .eslintignore file, and warn if it's present.
765
+ if (existsSync(path.resolve(processedOptions.cwd, ".eslintignore"))) {
766
+ warningService.emitESLintIgnoreWarning();
767
+ }
768
+ }
769
+
770
+ /**
771
+ * The version text.
772
+ * @type {string}
773
+ */
774
+ static get version() {
775
+ return version;
776
+ }
777
+
778
+ /**
779
+ * The default configuration that ESLint uses internally. This is provided for tooling that wants to calculate configurations using the same defaults as ESLint.
780
+ * Keep in mind that the default configuration may change from version to version, so you shouldn't rely on any particular keys or values to be present.
781
+ * @type {FlatConfigArray}
782
+ */
783
+ static get defaultConfig() {
784
+ return defaultConfig;
785
+ }
786
+
787
+ /**
788
+ * Outputs fixes from the given results to files.
789
+ * @param {LintResult[]} results The lint results.
790
+ * @returns {Promise<void>} Returns a promise that is used to track side effects.
791
+ */
792
+ static async outputFixes(results) {
793
+ if (!Array.isArray(results)) {
794
+ throw new Error("'results' must be an array");
795
+ }
796
+
797
+ const retrier = new Retrier(error => fileRetryCodes.has(error.code), {
798
+ concurrency: 100,
799
+ });
800
+
801
+ await Promise.all(
802
+ results
803
+ .filter(result => {
804
+ if (typeof result !== "object" || result === null) {
805
+ throw new Error("'results' must include only objects");
806
+ }
807
+ return (
808
+ typeof result.output === "string" &&
809
+ path.isAbsolute(result.filePath)
810
+ );
811
+ })
812
+ .map(r =>
813
+ retrier.retry(() => fs.writeFile(r.filePath, r.output)),
814
+ ),
815
+ );
816
+ }
817
+
818
+ /**
819
+ * Returns results that only contains errors.
820
+ * @param {LintResult[]} results The results to filter.
821
+ * @returns {LintResult[]} The filtered results.
822
+ */
823
+ static getErrorResults(results) {
824
+ const filtered = [];
825
+
826
+ results.forEach(result => {
827
+ const filteredMessages = result.messages.filter(isErrorMessage);
828
+ const filteredSuppressedMessages =
829
+ result.suppressedMessages.filter(isErrorMessage);
830
+
831
+ if (filteredMessages.length > 0) {
832
+ filtered.push({
833
+ ...result,
834
+ messages: filteredMessages,
835
+ suppressedMessages: filteredSuppressedMessages,
836
+ errorCount: filteredMessages.length,
837
+ warningCount: 0,
838
+ fixableErrorCount: result.fixableErrorCount,
839
+ fixableWarningCount: 0,
840
+ });
841
+ }
842
+ });
843
+
844
+ return filtered;
845
+ }
846
+
847
+ /**
848
+ * Creates a new ESLint instance using options loaded from a module.
849
+ * @param {URL} optionsURL The URL of the options module.
850
+ * @returns {ESLint} The new ESLint instance.
851
+ */
852
+ static async fromOptionsModule(optionsURL) {
853
+ if (!(optionsURL instanceof URL)) {
854
+ throw new TypeError("Argument must be a URL object");
855
+ }
856
+ const optionsURLString = optionsURL.href;
857
+ const loadedOptions = await loadOptionsFromModule(optionsURLString);
858
+ const options = { ...loadedOptions, [disableCloneabilityCheck]: true };
859
+ const eslint = new ESLint(options);
860
+
861
+ if (options.concurrency !== "off") {
862
+ // Save the options module URL in an instance field to pass to worker threads in `lintFiles()`.
863
+ eslint.#optionsOrURL = optionsURLString;
864
+ }
865
+ return eslint;
866
+ }
867
+
868
+ /**
869
+ * Returns meta objects for each rule represented in the lint results.
870
+ * @param {LintResult[]} results The results to fetch rules meta for.
871
+ * @returns {Record<string, RulesMeta>} A mapping of ruleIds to rule meta objects.
872
+ * @throws {TypeError} When the results object wasn't created from this ESLint instance.
873
+ * @throws {TypeError} When a plugin or rule is missing.
874
+ */
875
+ getRulesMetaForResults(results) {
876
+ // short-circuit simple case
877
+ if (results.length === 0) {
878
+ return {};
879
+ }
880
+
881
+ const resultRules = new Map();
882
+ const {
883
+ configLoader,
884
+ options: { cwd },
885
+ } = privateMembers.get(this);
886
+
887
+ for (const result of results) {
888
+ /*
889
+ * Normalize filename for <text>.
890
+ */
891
+ const filePath =
892
+ result.filePath === "<text>"
893
+ ? getPlaceholderPath(cwd)
894
+ : result.filePath;
895
+ const allMessages = result.messages.concat(
896
+ result.suppressedMessages,
897
+ );
898
+
899
+ for (const { ruleId } of allMessages) {
900
+ if (!ruleId) {
901
+ continue;
902
+ }
903
+
904
+ /*
905
+ * All of the plugin and rule information is contained within the
906
+ * calculated config for the given file.
907
+ */
908
+ let configs;
909
+
910
+ try {
911
+ configs =
912
+ configLoader.getCachedConfigArrayForFile(filePath);
913
+ } catch (err) {
914
+ throw createExtraneousResultsError(err);
915
+ }
916
+
917
+ const config = configs.getConfig(filePath);
918
+
919
+ if (!config) {
920
+ throw createExtraneousResultsError();
921
+ }
922
+ const rule = config.getRuleDefinition(ruleId);
923
+
924
+ // ignore unknown rules
925
+ if (rule) {
926
+ resultRules.set(ruleId, rule);
927
+ }
928
+ }
929
+ }
930
+
931
+ return createRulesMeta(resultRules);
932
+ }
933
+
934
+ /**
935
+ * Indicates if the given feature flag is enabled for this instance.
936
+ * @param {string} flag The feature flag to check.
937
+ * @returns {boolean} `true` if the feature flag is enabled, `false` if not.
938
+ */
939
+ hasFlag(flag) {
940
+ // note: Linter does validation of the flags
941
+ return privateMembers.get(this).linter.hasFlag(flag);
942
+ }
943
+
944
+ /**
945
+ * Executes the current configuration on an array of file and directory names.
946
+ * @param {string|string[]} patterns An array of file and directory names.
947
+ * @returns {Promise<LintResult[]>} The results of linting the file patterns given.
948
+ */
949
+ async lintFiles(patterns) {
950
+ let normalizedPatterns = patterns;
951
+ const {
952
+ cacheFilePath,
953
+ lintResultCache,
954
+ options: eslintOptions,
955
+ warningService,
956
+ } = privateMembers.get(this);
957
+
958
+ /*
959
+ * Special cases:
960
+ * 1. `patterns` is an empty string
961
+ * 2. `patterns` is an empty array
962
+ *
963
+ * In both cases, we use the cwd as the directory to lint.
964
+ */
965
+ if (
966
+ patterns === "" ||
967
+ (Array.isArray(patterns) && patterns.length === 0)
968
+ ) {
969
+ /*
970
+ * Special case: If `passOnNoPatterns` is true, then we just exit
971
+ * without doing any work.
972
+ */
973
+ if (eslintOptions.passOnNoPatterns) {
974
+ return [];
975
+ }
976
+
977
+ normalizedPatterns = ["."];
978
+ } else {
979
+ if (
980
+ !isNonEmptyString(patterns) &&
981
+ !isArrayOfNonEmptyString(patterns)
982
+ ) {
983
+ throw new Error(
984
+ "'patterns' must be a non-empty string or an array of non-empty strings",
985
+ );
986
+ }
987
+
988
+ if (typeof patterns === "string") {
989
+ normalizedPatterns = [patterns];
990
+ }
991
+ }
992
+
993
+ debug(`Using file patterns: ${normalizedPatterns}`);
994
+
995
+ const { concurrency, cwd, globInputPaths, errorOnUnmatchedPattern } =
996
+ eslintOptions;
997
+
998
+ // Delete cache file; should this be done here?
999
+ if (!lintResultCache && cacheFilePath) {
1000
+ debug(`Deleting cache file at ${cacheFilePath}`);
1001
+
1002
+ try {
1003
+ if (existsSync(cacheFilePath)) {
1004
+ await fs.unlink(cacheFilePath);
1005
+ }
1006
+ } catch (error) {
1007
+ if (existsSync(cacheFilePath)) {
1008
+ throw error;
1009
+ }
1010
+ }
1011
+ }
1012
+
1013
+ const startTime = hrtimeBigint();
1014
+ const filePaths = await findFiles({
1015
+ patterns: normalizedPatterns,
1016
+ cwd,
1017
+ globInputPaths,
1018
+ configLoader: this.#configLoader,
1019
+ errorOnUnmatchedPattern,
1020
+ });
1021
+ debug(
1022
+ "%d file(s) found in %t",
1023
+ filePaths.length,
1024
+ hrtimeBigint() - startTime,
1025
+ );
1026
+
1027
+ /** @type {LintResult[]} */
1028
+ let results;
1029
+
1030
+ // The value of `module.exports.calculateWorkerCount` can be overridden in tests.
1031
+ const workerCount = module.exports.calculateWorkerCount(
1032
+ this,
1033
+ filePaths,
1034
+ );
1035
+ if (workerCount) {
1036
+ debug(`Linting using ${workerCount} worker thread(s).`);
1037
+ let poorConcurrencyNotice;
1038
+ if (workerCount <= 2) {
1039
+ poorConcurrencyNotice = "disable concurrency";
1040
+ } else {
1041
+ if (concurrency === "auto") {
1042
+ poorConcurrencyNotice =
1043
+ "disable concurrency or use a numeric concurrency setting";
1044
+ } else {
1045
+ poorConcurrencyNotice = "reduce or disable concurrency";
1046
+ }
1047
+ }
1048
+ results = await lintFilesWithMultithreading(
1049
+ this,
1050
+ filePaths,
1051
+ workerCount,
1052
+ this.#optionsOrURL,
1053
+ () =>
1054
+ warningService.emitPoorConcurrencyWarning(
1055
+ poorConcurrencyNotice,
1056
+ ),
1057
+ );
1058
+ } else {
1059
+ debug(`Linting in single-thread mode.`);
1060
+ results = await lintFilesWithoutMultithreading(this, filePaths);
1061
+ }
1062
+
1063
+ // Persist the cache to disk.
1064
+ if (lintResultCache) {
1065
+ lintResultCache.reconcile();
1066
+ }
1067
+
1068
+ const finalResults = results.filter(result => !!result);
1069
+
1070
+ return processLintReport(this, finalResults);
1071
+ }
1072
+
1073
+ /**
1074
+ * Executes the current configuration on text.
1075
+ * @param {string} code A string of JavaScript code to lint.
1076
+ * @param {Object} [options] The options.
1077
+ * @param {string} [options.filePath] The path to the file of the source code.
1078
+ * @param {boolean} [options.warnIgnored] When set to true, warn if given filePath is an ignored path.
1079
+ * @returns {Promise<LintResult[]>} The results of linting the string of code given.
1080
+ */
1081
+ async lintText(code, options = {}) {
1082
+ // Parameter validation
1083
+
1084
+ if (typeof code !== "string") {
1085
+ throw new Error("'code' must be a string");
1086
+ }
1087
+
1088
+ if (typeof options !== "object") {
1089
+ throw new Error("'options' must be an object, null, or undefined");
1090
+ }
1091
+
1092
+ // Options validation
1093
+
1094
+ const { filePath, warnIgnored, ...unknownOptions } = options || {};
1095
+
1096
+ const unknownOptionKeys = Object.keys(unknownOptions);
1097
+
1098
+ if (unknownOptionKeys.length > 0) {
1099
+ throw new Error(
1100
+ `'options' must not include the unknown option(s): ${unknownOptionKeys.join(", ")}`,
1101
+ );
1102
+ }
1103
+
1104
+ if (filePath !== void 0 && !isNonEmptyString(filePath)) {
1105
+ throw new Error(
1106
+ "'options.filePath' must be a non-empty string or undefined",
1107
+ );
1108
+ }
1109
+
1110
+ if (
1111
+ typeof warnIgnored !== "boolean" &&
1112
+ typeof warnIgnored !== "undefined"
1113
+ ) {
1114
+ throw new Error(
1115
+ "'options.warnIgnored' must be a boolean or undefined",
1116
+ );
1117
+ }
1118
+
1119
+ // Now we can get down to linting
1120
+
1121
+ const { linter, options: eslintOptions } = privateMembers.get(this);
1122
+ const {
1123
+ allowInlineConfig,
1124
+ cwd,
1125
+ fix,
1126
+ fixTypes,
1127
+ warnIgnored: constructorWarnIgnored,
1128
+ ruleFilter,
1129
+ stats,
1130
+ } = eslintOptions;
1131
+ const results = [];
1132
+ const startTime = hrtimeBigint();
1133
+ const fixTypesSet = fixTypes ? new Set(fixTypes) : null;
1134
+ const resolvedFilename = path.resolve(
1135
+ cwd,
1136
+ filePath || "__placeholder__.js",
1137
+ );
1138
+ const configs =
1139
+ await this.#configLoader.loadConfigArrayForFile(resolvedFilename);
1140
+ const configStatus =
1141
+ configs?.getConfigStatus(resolvedFilename) ?? "unconfigured";
1142
+
1143
+ // Clear the last used config arrays.
1144
+ if (resolvedFilename && configStatus !== "matched") {
1145
+ const shouldWarnIgnored =
1146
+ typeof warnIgnored === "boolean"
1147
+ ? warnIgnored
1148
+ : constructorWarnIgnored;
1149
+
1150
+ if (shouldWarnIgnored) {
1151
+ results.push(
1152
+ createIgnoreResult(resolvedFilename, cwd, configStatus),
1153
+ );
1154
+ }
1155
+ } else {
1156
+ const config = configs.getConfig(resolvedFilename);
1157
+ const fixer = getFixerForFixTypes(fix, fixTypesSet, config);
1158
+
1159
+ // Do lint.
1160
+ results.push(
1161
+ verifyText({
1162
+ text: code,
1163
+ filePath: resolvedFilename.endsWith("__placeholder__.js")
1164
+ ? "<text>"
1165
+ : resolvedFilename,
1166
+ configs,
1167
+ cwd,
1168
+ fix: fixer,
1169
+ allowInlineConfig,
1170
+ ruleFilter,
1171
+ stats,
1172
+ linter,
1173
+ }),
1174
+ );
1175
+ }
1176
+
1177
+ debug("Linting complete in %t", hrtimeBigint() - startTime);
1178
+
1179
+ return processLintReport(this, results);
1180
+ }
1181
+
1182
+ /**
1183
+ * Returns the formatter representing the given formatter name.
1184
+ * @param {string} [name] The name of the formatter to load.
1185
+ * The following values are allowed:
1186
+ * - `undefined` ... Load `stylish` builtin formatter.
1187
+ * - A builtin formatter name ... Load the builtin formatter.
1188
+ * - A third-party formatter name:
1189
+ * - `foo` → `eslint-formatter-foo`
1190
+ * - `@foo` → `@foo/eslint-formatter`
1191
+ * - `@foo/bar` → `@foo/eslint-formatter-bar`
1192
+ * - A file path ... Load the file.
1193
+ * @returns {Promise<Formatter>} A promise resolving to the formatter object.
1194
+ * This promise will be rejected if the given formatter was not found or not
1195
+ * a function.
1196
+ */
1197
+ async loadFormatter(name = "stylish") {
1198
+ if (typeof name !== "string") {
1199
+ throw new Error("'name' must be a string");
1200
+ }
1201
+
1202
+ // replace \ with / for Windows compatibility
1203
+ const normalizedFormatName = name.replace(/\\/gu, "/");
1204
+ const namespace = getNamespaceFromTerm(normalizedFormatName);
1205
+
1206
+ // grab our options
1207
+ const { cwd } = privateMembers.get(this).options;
1208
+
1209
+ let formatterPath;
1210
+
1211
+ // if there's a slash, then it's a file (TODO: this check seems dubious for scoped npm packages)
1212
+ if (!namespace && normalizedFormatName.includes("/")) {
1213
+ formatterPath = path.resolve(cwd, normalizedFormatName);
1214
+ } else {
1215
+ try {
1216
+ const npmFormat = normalizePackageName(
1217
+ normalizedFormatName,
1218
+ "eslint-formatter",
1219
+ );
1220
+
1221
+ // TODO: This is pretty dirty...would be nice to clean up at some point.
1222
+ formatterPath = resolve(npmFormat, getPlaceholderPath(cwd));
1223
+ } catch {
1224
+ formatterPath = path.resolve(
1225
+ __dirname,
1226
+ "../",
1227
+ "cli-engine",
1228
+ "formatters",
1229
+ `${normalizedFormatName}.js`,
1230
+ );
1231
+ }
1232
+ }
1233
+
1234
+ let formatter;
1235
+
1236
+ try {
1237
+ formatter = (await import(pathToFileURL(formatterPath))).default;
1238
+ } catch (ex) {
1239
+ // check for formatters that have been removed
1240
+ if (removedFormatters.has(name)) {
1241
+ ex.message = `The ${name} formatter is no longer part of core ESLint. Install it manually with \`npm install -D eslint-formatter-${name}\``;
1242
+ } else {
1243
+ ex.message = `There was a problem loading formatter: ${formatterPath}\nError: ${ex.message}`;
1244
+ }
1245
+
1246
+ throw ex;
1247
+ }
1248
+
1249
+ if (typeof formatter !== "function") {
1250
+ throw new TypeError(
1251
+ `Formatter must be a function, but got a ${typeof formatter}.`,
1252
+ );
1253
+ }
1254
+
1255
+ const eslint = this;
1256
+
1257
+ return {
1258
+ /**
1259
+ * The main formatter method.
1260
+ * @param {LintResult[]} results The lint results to format.
1261
+ * @param {ResultsMeta} resultsMeta Warning count and max threshold.
1262
+ * @returns {string} The formatted lint results.
1263
+ */
1264
+ format(results, resultsMeta) {
1265
+ let rulesMeta = null;
1266
+
1267
+ results.sort(compareResultsByFilePath);
1268
+
1269
+ return formatter(results, {
1270
+ ...resultsMeta,
1271
+ cwd,
1272
+ get rulesMeta() {
1273
+ if (!rulesMeta) {
1274
+ rulesMeta = eslint.getRulesMetaForResults(results);
1275
+ }
1276
+
1277
+ return rulesMeta;
1278
+ },
1279
+ });
1280
+ },
1281
+ };
1282
+ }
1283
+
1284
+ /**
1285
+ * Returns a configuration object for the given file based on the CLI options.
1286
+ * This is the same logic used by the ESLint CLI executable to determine
1287
+ * configuration for each file it processes.
1288
+ * @param {string} filePath The path of the file to retrieve a config object for.
1289
+ * @returns {Promise<CalculatedConfig|undefined>} A configuration object for the file
1290
+ * or `undefined` if there is no configuration data for the object.
1291
+ */
1292
+ async calculateConfigForFile(filePath) {
1293
+ if (!isNonEmptyString(filePath)) {
1294
+ throw new Error("'filePath' must be a non-empty string");
1295
+ }
1296
+ const options = privateMembers.get(this).options;
1297
+ const absolutePath = path.resolve(options.cwd, filePath);
1298
+ const configs =
1299
+ await this.#configLoader.loadConfigArrayForFile(absolutePath);
1300
+
1301
+ if (!configs) {
1302
+ const error = new Error("Could not find config file.");
1303
+
1304
+ error.messageTemplate = "config-file-missing";
1305
+ throw error;
1306
+ }
1307
+
1308
+ return configs.getConfig(absolutePath);
1309
+ }
1310
+
1311
+ /**
1312
+ * Finds the config file being used by this instance based on the options
1313
+ * passed to the constructor.
1314
+ * @param {string} [filePath] The path of the file to find the config file for.
1315
+ * @returns {Promise<string|undefined>} The path to the config file being used or
1316
+ * `undefined` if no config file is being used.
1317
+ */
1318
+ async findConfigFile(filePath) {
1319
+ const options = privateMembers.get(this).options;
1320
+
1321
+ /*
1322
+ * Because the new config lookup scheme skips the current directory
1323
+ * and looks into the parent directories, we need to use a placeholder
1324
+ * directory to ensure the file in cwd is checked.
1325
+ */
1326
+ const fakeCwd = path.join(options.cwd, "__placeholder__");
1327
+
1328
+ return this.#configLoader
1329
+ .findConfigFileForPath(filePath ?? fakeCwd)
1330
+ .catch(() => void 0);
1331
+ }
1332
+
1333
+ /**
1334
+ * Checks if a given path is ignored by ESLint.
1335
+ * @param {string} filePath The path of the file to check.
1336
+ * @returns {Promise<boolean>} Whether or not the given path is ignored.
1337
+ */
1338
+ async isPathIgnored(filePath) {
1339
+ const config = await this.calculateConfigForFile(filePath);
1340
+
1341
+ return config === void 0;
1342
+ }
683
1343
  }
684
1344
 
685
1345
  /**
686
- * The type of configuration used by this class.
687
- * @type {string}
688
- * @static
1346
+ * Returns whether flat config should be used.
1347
+ * @returns {Promise<boolean>} Whether flat config should be used.
689
1348
  */
690
- ESLint.configType = "eslintrc";
1349
+ async function shouldUseFlatConfig() {
1350
+ return process.env.ESLINT_USE_FLAT_CONFIG !== "false";
1351
+ }
691
1352
 
692
1353
  //------------------------------------------------------------------------------
693
1354
  // Public Interface
694
1355
  //------------------------------------------------------------------------------
695
1356
 
696
1357
  module.exports = {
697
- ESLint,
698
-
699
- /**
700
- * Get the private class members of a given ESLint instance for tests.
701
- * @param {ESLint} instance The ESLint instance to get.
702
- * @returns {ESLintPrivateMembers} The instance's private class members.
703
- */
704
- getESLintPrivateMembers(instance) {
705
- return privateMembersMap.get(instance);
706
- }
1358
+ ESLint,
1359
+ shouldUseFlatConfig,
1360
+ locateConfigFileToUse,
1361
+ calculateWorkerCount,
707
1362
  };