Repository: facebook/react
Pull request: Migrate from CLIEngine to the new ESLint class.

## Summary

In ESLint 8, `CLIEngine` was removed. To migrate from version 7 to 8, we need to replace `CLIEngine` with the new `ESLint` class.

More information here:
- [Legacy documentation of the `CLIEngine` class.](https://web.archive.org/web/20190321193128/https://eslint.org/docs/developer-guide/nodejs-api#cliengine)
- [Documentation of the `ESLint` class.](https://eslint.org/docs/developer-guide/nodejs-api#eslint-class)
- [Migration guide.](https://eslint.org/docs/8.0.0/user-guide/migrating-to-8.0.0#the-cliengine-class-has-been-removed)

## How did you test this change?

I tested the following commands before and after the migration:
- For `yarn lint` and `yarn linc`, the output was the same.
- For `yarn lint --fix` and `yarn linc --fix`, the output was the same and the fixable errors got fixed.

**Pass example:**
<details>

```
$ yarn linc
yarn run v1.22.17
$ node ./scripts/tasks/linc.js
Linting changed files...
> git merge-base HEAD main
> git diff --name-only --diff-filter=ACMRTUB c0c71a868560b3042847722659579418bfe2d7e1
> git ls-files --others --exclude-standard

Lint passed for changed files.
✨  Done in 3.38s.
```

</details>

**Error example:**
<details>

```
$ yarn linc
yarn run v1.22.17
$ node ./scripts/tasks/linc.js
Linting changed files...
> git merge-base HEAD main
> git diff --name-only --diff-filter=ACMRTUB c0c71a868560b3042847722659579418bfe2d7e1
> git ls-files --others --exclude-standard

/Users/XXX/projects/react/scripts/devtools/publish-release.js
  60:7   error  'a' is assigned a value but never used  no-unused-vars
  60:11  error  Multiple spaces found before ';'        no-multi-spaces

✖ 2 problems (2 errors, 0 warnings)
  1 error and 0 warnings potentially fixable with the `--fix` option.

Lint failed for changed files.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
```

</details>

Changed files:
filename: scripts/eslint/index.js
patch: @@ -8,16 +8,16 @@
 'use strict';
 
 const minimatch = require('minimatch');
-const CLIEngine = require('eslint').CLIEngine;
+const {ESLint} = require('eslint');
 const listChangedFiles = require('../shared/listChangedFiles');
 
 const allPaths = ['**/*.js'];
 
 let changedFiles = null;
 
-function runESLintOnFilesWithOptions(filePatterns, onlyChanged, options) {
-  const cli = new CLIEngine(options);
-  const formatter = cli.getFormatter();
+async function runESLintOnFilesWithOptions(filePatterns, onlyChanged, options) {
+  const eslint = new ESLint(options);
+  const formatter = await eslint.loadFormatter();
 
   if (onlyChanged && changedFiles === null) {
     // Calculate lazily.
@@ -26,15 +26,15 @@ function runESLintOnFilesWithOptions(filePatterns, onlyChanged, options) {
   const finalFilePatterns = onlyChanged
     ? intersect(changedFiles, filePatterns)
     : filePatterns;
-  const report = cli.executeOnFiles(finalFilePatterns);
+  const results = await eslint.lintFiles(finalFilePatterns);
 
   if (options != null && options.fix === true) {
-    CLIEngine.outputFixes(report);
+    await ESLint.outputFixes(results);
   }
 
   // When using `ignorePattern`, eslint will show `File ignored...` warnings for any ignores.
   // We don't care because we *expect* some passed files will be ignores if `ignorePattern` is used.
-  const messages = report.results.filter(item => {
+  const messages = results.filter(item => {
     if (!onlyChanged) {
       // Don't suppress the message on a full run.
       // We only expect it to happen for "only changed" runs.
@@ -45,11 +45,19 @@ function runESLintOnFilesWithOptions(filePatterns, onlyChanged, options) {
     return !(item.messages[0] && item.messages[0].message === ignoreMessage);
   });
 
-  const ignoredMessageCount = report.results.length - messages.length;
+  const errorCount = results.reduce(
+    (count, result) => count + result.errorCount,
+    0
+  );
+  const warningCount = results.reduce(
+    (count, result) => count + result.warningCount,
+    0
+  );
+  const ignoredMessageCount = results.length - messages.length;
   return {
-    output: formatter(messages),
-    errorCount: report.errorCount,
-    warningCount: report.warningCount - ignoredMessageCount,
+    output: formatter.format(messages),
+    errorCount: errorCount,
+    warningCount: warningCount - ignoredMessageCount,
   };
 }
 
@@ -64,11 +72,11 @@ function intersect(files, patterns) {
   return [...new Set(intersection)];
 }
 
-function runESLint({onlyChanged, ...options}) {
+async function runESLint({onlyChanged, ...options}) {
   if (typeof onlyChanged !== 'boolean') {
     throw new Error('Pass options.onlyChanged as a boolean.');
   }
-  const {errorCount, warningCount, output} = runESLintOnFilesWithOptions(
+  const {errorCount, warningCount, output} = await runESLintOnFilesWithOptions(
     allPaths,
     onlyChanged,
     options

filename: scripts/tasks/eslint.js
patch: @@ -10,16 +10,22 @@
 const minimist = require('minimist');
 const runESLint = require('../eslint');
 
-console.log('Linting all files...');
-// https://circleci.com/docs/2.0/env-vars/#circleci-environment-variable-descriptions
-if (!process.env.CI) {
-  console.log('Hint: run `yarn linc` to only lint changed files.');
-}
+async function main() {
+  console.log('Linting all files...');
+  // https://circleci.com/docs/2.0/env-vars/#circleci-environment-variable-descriptions
+  if (!process.env.CI) {
+    console.log('Hint: run `yarn linc` to only lint changed files.');
+  }
+
+  // eslint-disable-next-line no-unused-vars
+  const {_, ...cliOptions} = minimist(process.argv.slice(2));
 
-const cliOptions = minimist(process.argv.slice(2));
-if (runESLint({onlyChanged: false, ...cliOptions})) {
-  console.log('Lint passed.');
-} else {
-  console.log('Lint failed.');
-  process.exit(1);
+  if (await runESLint({onlyChanged: false, ...cliOptions})) {
+    console.log('Lint passed.');
+  } else {
+    console.log('Lint failed.');
+    process.exit(1);
+  }
 }
+
+main();

filename: scripts/tasks/linc.js
patch: @@ -10,12 +10,18 @@
 const minimist = require('minimist');
 const runESLint = require('../eslint');
 
-console.log('Linting changed files...');
+async function main() {
+  console.log('Linting changed files...');
 
-const cliOptions = minimist(process.argv.slice(2));
-if (runESLint({onlyChanged: true, ...cliOptions})) {
-  console.log('Lint passed for changed files.');
-} else {
-  console.log('Lint failed for changed files.');
-  process.exit(1);
+  // eslint-disable-next-line no-unused-vars
+  const {_, ...cliOptions} = minimist(process.argv.slice(2));
+
+  if (await runESLint({onlyChanged: true, ...cliOptions})) {
+    console.log('Lint passed for changed files.');
+  } else {
+    console.log('Lint failed for changed files.');
+    process.exit(1);
+  }
 }
+
+main();