Switch to ESLint flat config.

This CL was generated with: `npx @eslint/migrate-config .eslintrc.js`

Several manual changes were made:
* eslint.py was updated to point to the new file (which must end in .mjs
  to be interpretted as a module) and no longer uses legacy mode.
* The migration tool added unnecessary references to the "global" node
  package, which were deleted.
* The migration tool deleted all comments. These were manually copied
  over. A new comment was added describing the subtlety of global
  ignores in flat config.
* The include path for plugins was updated to be relative to
  tools/web_dev_style.

The CL was tested with:
git cl presubmit --files "*.[jt]s" --force

There are no errors. Manually introducing a lint error to a linted .ts
file correctly causes 1 error to show up.

Change-Id: Id4a3351cfd6afc4059f7a6e82b7a9f5e5a18dffb
Bug: 368085620
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5903594
Reviewed-by: Demetrios Papadopoulos <dpapad@chromium.org>
Commit-Queue: Erik Chen <erikchen@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1363321}
diff --git a/tools/web_dev_style/.eslintrc.js b/tools/web_dev_style/.eslintrc.js
deleted file mode 100644
index 918939a..0000000
--- a/tools/web_dev_style/.eslintrc.js
+++ /dev/null
@@ -1,409 +0,0 @@
-// Copyright 2017 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-module.exports = {
-  'root': true,
-  'env': {
-    'browser': true,
-    'es2020': true,
-  },
-  'parserOptions': {
-    'ecmaVersion': 2020,
-    'sourceType': 'module',
-  },
-  'rules': {
-    // Enabled checks.
-    'brace-style': ['error', '1tbs'],
-
-    // https://google.github.io/styleguide/jsguide.html#features-arrays-trailing-comma
-    // https://google.github.io/styleguide/jsguide.html#features-objects-use-trailing-comma
-    'comma-dangle': ['error', 'always-multiline'],
-
-    'curly': ['error', 'multi-line', 'consistent'],
-    'new-parens': 'error',
-    'no-array-constructor': 'error',
-    'no-console': ['error', {allow: ['info', 'warn', 'error', 'assert']}],
-    'no-debugger': 'error',
-    'no-extra-boolean-cast': 'error',
-    'no-extra-semi': 'error',
-    'no-new-wrappers': 'error',
-    'no-restricted-imports': ['error', {
-      'paths': [{
-        'name':  'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js',
-        'importNames': ['Polymer'],
-        'message': 'Use PolymerElement instead.',
-      },
-      {
-        'name':  '//resources/polymer/v3_0/polymer/polymer_bundled.min.js',
-        'importNames': ['Polymer'],
-        'message': 'Use PolymerElement instead.',
-      }],
-    }],
-    'no-restricted-properties': [
-      'error',
-      {
-        'property': '__lookupGetter__',
-        'message': 'Use Object.getOwnPropertyDescriptor',
-      },
-      {
-        'property': '__lookupSetter__',
-        'message': 'Use Object.getOwnPropertyDescriptor',
-      },
-      {
-        'property': '__defineGetter__',
-        'message': 'Use Object.defineProperty',
-      },
-      {
-        'property': '__defineSetter__',
-        'message': 'Use Object.defineProperty',
-      },
-      {
-        'object': 'cr',
-        'property': 'exportPath',
-        'message': 'Use ES modules or cr.define() instead',
-      },
-      {
-        'object': 'MockInteractions',
-        'property': 'tap',
-        'message': 'Do not use on-tap handlers in prod code, and use the ' +
-            'native click() method in tests. See more context at ' +
-            'crbug.com/812035.',
-      },
-      {
-        'object': 'test',
-        'property': 'only',
-        'message': 'test.only() silently disables other tests in the same ' +
-            'suite(). Did you forget deleting it before uploading? Use ' +
-            'test.skip() instead to explicitly disable certain test() cases.',
-      },
-    ],
-    'no-restricted-syntax': ['error', {
-      'selector': 'CallExpression[callee.object.name=JSON][callee.property.name=parse] > CallExpression[callee.object.name=JSON][callee.property.name=stringify]',
-      'message': 'Don\'t use JSON.parse(JSON.stringify(...)) to clone objects. Use structuredClone() instead.',
-    },
-    {
-      // https://google.github.io/styleguide/tsguide.html#return-type-only-generics
-      'selector': 'TSAsExpression > CallExpression > MemberExpression[property.name=/^querySelector$/]',
-      'message': 'Don\'t use \'querySelector(...) as Type\'. Use the type parameter, \'querySelector<Type>(...)\' instead',
-    },
-    {
-      // https://google.github.io/styleguide/tsguide.html#return-type-only-generics
-      'selector': 'TSAsExpression > TSNonNullExpression > CallExpression > MemberExpression[property.name=/^querySelector$/]',
-      'message': 'Don\'t use \'querySelector(...)! as Type\'. Use the type parameter, \'querySelector<Type>(...)\', followed by an assertion instead',
-    },
-    {
-      // https://google.github.io/styleguide/tsguide.html#return-type-only-generics
-      'selector': 'TSAsExpression > CallExpression > MemberExpression[property.name=/^querySelectorAll$/]',
-      'message': 'Don\'t use \'querySelectorAll(...) as Type\'. Use the type parameter, \'querySelectorAll<Type>(...)\' instead',
-    },
-    {
-      // Prevent a common misuse of "!" operator.
-      "selector": "TSNonNullExpression > CallExpression > MemberExpression[property.name=/^querySelectorAll$/]",
-      "message": "Remove unnecessary \"!\" non-null operator after querySelectorAll(). It always returns a non-null result",
-    },
-    {
-      // https://google.github.io/styleguide/jsguide.html#es-module-imports
-      //  1) Matching only import URLs that have at least one '/' slash, to
-      //     avoid false positives for NodeJS imports like
-      //     `import fs from 'fs';`.
-      //     Using '\u002F' instead of '/' as the suggested workaround for
-      //     https://github.com/eslint/eslint/issues/16555
-      //  2) Allowing extensions that have a length between 2-4 characters
-      //     (for example js, css, json)
-      "selector": "ImportDeclaration[source.value=/^.*\\u002F.*(?<!\\.[a-z]{2}|\\.[a-z]{3}|\\.[a-z]{4})$/]",
-      "message": "Disallowed extensionless import. Explicitly specify the extension suffix."
-    }],
-    'no-throw-literal': 'error',
-    'no-trailing-spaces': 'error',
-    'no-var': 'error',
-    'prefer-const': 'error',
-    'quotes': ['error', 'single', {allowTemplateLiterals: true}],
-    'semi': ['error', 'always'],
-
-    // https://google.github.io/styleguide/jsguide.html#features-one-variable-per-declaration
-    'one-var': ['error', {
-      let: 'never',
-      const: 'never',
-    }],
-
-    // TODO(dpapad): Add more checks according to our styleguide.
-  },
-
-  'overrides': [{
-    'files': ['**/*.ts'],
-    'parser': '../../third_party/node/node_modules/@typescript-eslint/parser/dist/index.js',
-    'plugins': [
-      '@typescript-eslint',
-      '@stylistic',
-    ],
-    'rules': {
-      'no-unused-vars': 'off',
-      '@typescript-eslint/no-unused-vars': [
-        'error', {
-          argsIgnorePattern: '^_',
-          varsIgnorePattern: '^_',
-          caughtErrorsIgnorePattern: '.*',
-        }
-      ],
-
-      // https://google.github.io/styleguide/tsguide.html#array-constructor
-      // Note: The rule below only partially enforces the styleguide, since it
-      // it does not flag invocations of the constructor with a single
-      // parameter.
-      'no-array-constructor': 'off',
-      '@typescript-eslint/no-array-constructor': 'error',
-
-      // https://google.github.io/styleguide/tsguide.html#automatic-semicolon-insertion
-      'semi': 'off',
-      '@stylistic/semi': ['error'],
-
-      // https://google.github.io/styleguide/tsguide.html#arrayt-type
-      '@typescript-eslint/array-type': ['error', {
-        default: 'array-simple',
-      }],
-
-      // https://google.github.io/styleguide/tsguide.html#type-assertions-syntax
-      '@typescript-eslint/consistent-type-assertions': ['error', {
-         assertionStyle: 'as',
-      }],
-
-      // https://google.github.io/styleguide/tsguide.html#interfaces-vs-type-aliases
-      '@typescript-eslint/consistent-type-definitions': ['error', 'interface'],
-
-      // https://google.github.io/styleguide/tsguide.html#import-type
-      '@typescript-eslint/consistent-type-imports': 'error',
-
-      // https://google.github.io/styleguide/tsguide.html#visibility
-      '@typescript-eslint/explicit-member-accessibility': ['error', {
-        accessibility: 'no-public',
-        overrides: {
-          parameterProperties: 'off',
-        },
-      }],
-
-      // https://google.github.io/styleguide/jsguide.html#naming
-      '@typescript-eslint/naming-convention': [
-        'error',
-        {
-          selector: ['class', 'interface', 'typeAlias', 'enum', 'typeParameter'],
-          format: ['StrictPascalCase'],
-          filter: {
-            regex: '^(' +
-                // Exclude TypeScript defined interfaces HTMLElementTagNameMap
-                // and HTMLElementEventMap.
-                'HTMLElementTagNameMap|HTMLElementEventMap|' +
-                // Exclude native DOM types which are always named like HTML<Foo>Element.
-                'HTML[A-Za-z]{0,}Element|' +
-                // Exclude native DOM interfaces.
-                'UIEvent|UIEventInit|DOMError|' +
-                // Exclude the deprecated WebUIListenerBehavior interface.
-                'WebUIListenerBehavior)$',
-            match: false,
-          },
-        },
-        {
-          selector: 'enumMember',
-          format: ['UPPER_CASE'],
-        },
-        {
-          selector: 'classMethod',
-          format: ['strictCamelCase'],
-          modifiers: ['public'],
-        },
-        {
-          selector: 'classMethod',
-          format: ['strictCamelCase'],
-          modifiers: ['private'],
-          trailingUnderscore: 'allow',
-
-          // Disallow the 'Tap_' suffix, in favor of 'Click_' in event handlers.
-          // Note: Unfortunately this ESLint rule does not provide a way to
-          // customize the error message to better inform developers.
-          custom: {
-            regex: '^on[a-zA-Z0-9]+Tap$',
-            match: false,
-          },
-        },
-        {
-          selector: 'classProperty',
-          format: ['UPPER_CASE'],
-          modifiers: ['private', 'static', 'readonly'],
-        },
-        {
-          selector: 'classProperty',
-          format: ['UPPER_CASE'],
-          modifiers: ['public', 'static', 'readonly'],
-        },
-        {
-          selector: 'classProperty',
-          format: ['camelCase'],
-          modifiers: ['public'],
-        },
-        {
-          selector: 'classProperty',
-          format: ['camelCase'],
-          modifiers: ['private'],
-          trailingUnderscore: 'allow',
-        },
-        {
-          selector: 'parameter',
-          format: ['camelCase'],
-          leadingUnderscore: 'allow',
-        },
-        {
-          selector: 'function',
-          format: ['camelCase'],
-        },
-      ],
-
-      // https://google.github.io/styleguide/tsguide.html#member-property-declarations
-      '@stylistic/member-delimiter-style': ['error', {
-        multiline: {
-          delimiter: 'comma',
-          requireLast: true,
-        },
-        singleline: {
-          delimiter: 'comma',
-          requireLast: false,
-        },
-        overrides: {
-          interface: {
-            multiline: {
-              delimiter: 'semi',
-              requireLast: true,
-            },
-            singleline: {
-              delimiter: 'semi',
-              requireLast: false,
-            },
-          },
-        },
-      }],
-
-      // https://google.github.io/styleguide/tsguide.html#wrapper-types
-      '@typescript-eslint/no-restricted-types': ['error', {
-        types: {
-          String: {
-            message: 'Use string instead',
-            fixWith: 'string',
-          },
-          Boolean: {
-            message: 'Use boolean instead',
-            fixWith: 'boolean',
-          },
-          Number: {
-            message: 'Use number instead',
-            fixWith: 'number',
-          },
-          Symbol: {
-            message: 'Use symbol instead',
-            fixWith: 'symbol',
-          },
-          BigInt: {
-            message: 'Use bigint instead',
-            fixWith: 'bigint',
-          },
-        }
-      }],
-
-      // https://google.github.io/styleguide/tsguide.html#ts-ignore
-      '@typescript-eslint/ban-ts-comment': ['error', {'ts-ignore': true}],
-    }
-  },
-  // We do not allow per-directory custom eslint rules. This section exists for
-  // rules that are in the process of being applied to the whole code base.
-  {
-    'files': ['chrome/browser/resources/**/*.[jt]s',
-              'chrome/test/data/pdf/**/*.ts',
-              'chrome/test/data/webui/**/*.[jt]s',
-              'content/browser/resources/**/*.[jt]s',
-              'ui/webui/resources/**/*.[jt]s',],
-    'rules': {
-      'eqeqeq': ['error', 'always', {'null': 'ignore'}],
-    }
-  },
-
-  // 1-month exception for //chrome/browser/resources/ash/settings. This can be
-  // removed November 15 2024.
-  {
-    'files' : ['chrome/browser/resources/ash/settings/**/*.[jt]s'],
-    // Disable clang-format because it produces odd formatting for these rules.
-    // clang-format off
-    'rules' : {
-      // Disable due to large number of violations in this folder.
-      '@typescript-eslint/consistent-type-imports': 'off',
-      /**
-       * https://google.github.io/styleguide/tsguide.html#return-types
-       * The Google TS style guide makes no formal rule on enforcing explicit
-       * return types. However, explicit return types have clear advantages in
-       * both readability and maintainability.
-       */
-      '@typescript-eslint/explicit-function-return-type': [
-        'error',
-        {
-          // Function expressions are exempt.
-          allowExpressions: true,
-          // Avoid checking Polymer static getter methods.
-          allowedNames: ['is', 'template', 'properties', 'observers'],
-        },
-      ],
-      /**
-       * https://google.github.io/styleguide/tsguide.html#type-inference
-       */
-      '@typescript-eslint/no-inferrable-types': [
-        'error',
-        {
-          // Function parameters may have explicit types for clearer APIs.
-          ignoreParameters: true,
-          // Class properties may have explicit types for clearer APIs.
-          ignoreProperties: true,
-        },
-      ],
-      /**
-       * https://google.github.io/styleguide/tsguide.html#function-expressions
-       */
-      'prefer-arrow-callback': 'error',
-      'quote-props': ['error', 'consistent-as-needed'],
-    },
-    // clang-format on
-  },
-
-  ],
-
-  // All paths are relative to src/.
-  'ignorePatterns': [
-    // Ignore because eslint doesn't understand // <if expr>
-    'chrome/browser/resources/gaia_auth_host/authenticator.js',
-    'chrome/browser/resources/gaia_auth_host/password_change_authenticator.js',
-    'chrome/browser/resources/gaia_auth_host/saml_username_autofill.js',
-
-
-    // No point linting auto-generated files.
-    'tools/typescript/definitions/**',
-
-    // ESLint is disabled for camera_app_ui and recorder_app_ui as they
-    // used a custom eslint plugin that does not work with the latest eslint,
-    // and they had complex eslint rc files that have not been updated to the
-    // latest eslint. See https://crbug.com/368085620.
-    'ash/webui/camera_app_ui/resources/**',
-    'ash/webui/recorder_app_ui/resources/**',
-
-    // ESLint is disabled for directories that use custom linting rules, which
-    // is no longer supported.
-    // TODO(https://crbug.com/369766161): Bring directories into conformance to
-    // re-enable linting.
-    'ash/webui/**',
-    'chrome/browser/resources/ash/**/*.[jt]s',
-    'chrome/browser/resources/chromeos/**',
-    'chrome/test/data/webui/chromeos/**',
-
-    // TODO(https://crbug.com/41446521): Bring extension test files into
-    // conformance.
-    'chrome/test/data/extensions/**',
-
-    // TODO(https://crbug.com/370730323): 1-month exception. This can be removed
-    // in November 2024.
-    '!chrome/browser/resources/ash/settings/**',
-  ]
-};
diff --git a/tools/web_dev_style/eslint.config.mjs b/tools/web_dev_style/eslint.config.mjs
new file mode 100644
index 0000000..bf4f22e
--- /dev/null
+++ b/tools/web_dev_style/eslint.config.mjs
@@ -0,0 +1,468 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import stylistic from '../../third_party/node/node_modules/@stylistic/eslint-plugin/dist/index.js';
+import typescriptEslint from '../../third_party/node/node_modules/@typescript-eslint/eslint-plugin/dist/index.js';
+import tsParser from '../../third_party/node/node_modules/@typescript-eslint/parser/dist/index.js';
+
+export default [
+  {
+    // In the flat config style, the only way to have global ignores is for the
+    // first configuration to have exactly 1 entry: ignores.
+    // All paths are relative to src/.
+    ignores: [
+      // Ignore because eslint doesn't understand // <if expr>
+      'chrome/browser/resources/gaia_auth_host/authenticator.js',
+      'chrome/browser/resources/gaia_auth_host/password_change_authenticator.js',
+      'chrome/browser/resources/gaia_auth_host/saml_username_autofill.js',
+
+      // No point linting auto-generated files.
+      'tools/typescript/definitions/**/*',
+
+      // ESLint is disabled for camera_app_ui and recorder_app_ui as they used
+      // a custom eslint plugin that does not work with the latest eslint, and
+      // they had complex eslint rc files that have not been updated to the
+      // latest eslint. See https://crbug.com/368085620.
+      'ash/webui/camera_app_ui/resources/**/*',
+      'ash/webui/recorder_app_ui/resources/**/*',
+
+      // ESLint is disabled for directories that use custom linting rules,
+      // which is no longer supported. TODO(https://crbug.com/369766161):
+      // Bring directories into conformance to re-enable linting.
+      'ash/webui/**/*',
+      'chrome/browser/resources/ash/**/*.[jt]s',
+      'chrome/browser/resources/chromeos/**/*',
+      'chrome/test/data/webui/chromeos/**/*',
+
+      // TODO(https://crbug.com/41446521): Bring extension test files into
+      // conformance.
+      'chrome/test/data/extensions/**/*',
+
+      // TODO(https://crbug.com/370730323): 1-month exception. This can be
+      // removed in November 2024.
+      '!chrome/browser/resources/ash/settings/**/*',
+    ],
+  },
+  {
+    languageOptions: {
+      ecmaVersion: 2020,
+      sourceType: 'module',
+    },
+
+    rules: {
+      // Enabled checks.
+      'brace-style': ['error', '1tbs'],
+
+      // https://google.github.io/styleguide/jsguide.html#features-arrays-trailing-comma
+      // https://google.github.io/styleguide/jsguide.html#features-objects-use-trailing-comma
+      'comma-dangle': ['error', 'always-multiline'],
+
+      curly: ['error', 'multi-line', 'consistent'],
+      'new-parens': 'error',
+      'no-array-constructor': 'error',
+
+      'no-console': [
+        'error', {
+          allow: ['info', 'warn', 'error', 'assert'],
+        }
+      ],
+
+      'no-debugger': 'error',
+      'no-extra-boolean-cast': 'error',
+      'no-extra-semi': 'error',
+      'no-new-wrappers': 'error',
+
+      'no-restricted-imports': [
+        'error', {
+          paths: [
+            {
+              name:
+                  'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js',
+              importNames: ['Polymer'],
+              message: 'Use PolymerElement instead.',
+            },
+            {
+              name: '//resources/polymer/v3_0/polymer/polymer_bundled.min.js',
+              importNames: ['Polymer'],
+              message: 'Use PolymerElement instead.',
+            }
+          ],
+        }
+      ],
+
+      'no-restricted-properties': [
+        'error', {
+          property: '__lookupGetter__',
+          message: 'Use Object.getOwnPropertyDescriptor',
+        },
+        {
+          property: '__lookupSetter__',
+          message: 'Use Object.getOwnPropertyDescriptor',
+        },
+        {
+          property: '__defineGetter__',
+          message: 'Use Object.defineProperty',
+        },
+        {
+          property: '__defineSetter__',
+          message: 'Use Object.defineProperty',
+        },
+        {
+          object: 'cr',
+          property: 'exportPath',
+          message: 'Use ES modules or cr.define() instead',
+        },
+        {
+          object: 'MockInteractions',
+          property: 'tap',
+          message:
+              'Do not use on-tap handlers in prod code, and use the native click() method in tests. See more context at crbug.com/812035.',
+        },
+        {
+          object: 'test',
+          property: 'only',
+          message:
+              'test.only() silently disables other tests in the same suite(). Did you forget deleting it before uploading? Use test.skip() instead to explicitly disable certain test() cases.',
+        }
+      ],
+
+      'no-restricted-syntax': [
+        'error', {
+          selector:
+              'CallExpression[callee.object.name=JSON][callee.property.name=parse] > CallExpression[callee.object.name=JSON][callee.property.name=stringify]',
+          message:
+              'Don\'t use JSON.parse(JSON.stringify(...)) to clone objects. Use structuredClone() instead.',
+        },
+        {
+          // https://google.github.io/styleguide/tsguide.html#return-type-only-generics
+          selector:
+              'TSAsExpression > CallExpression > MemberExpression[property.name=/^querySelector$/]',
+          message:
+              'Don\'t use \'querySelector(...) as Type\'. Use the type parameter, \'querySelector<Type>(...)\' instead',
+        },
+        {
+          // https://google.github.io/styleguide/tsguide.html#return-type-only-generics
+          selector:
+              'TSAsExpression > TSNonNullExpression > CallExpression > MemberExpression[property.name=/^querySelector$/]',
+          message:
+              'Don\'t use \'querySelector(...)! as Type\'. Use the type parameter, \'querySelector<Type>(...)\', followed by an assertion instead',
+        },
+        {
+          // https://google.github.io/styleguide/tsguide.html#return-type-only-generics
+          selector:
+              'TSAsExpression > CallExpression > MemberExpression[property.name=/^querySelectorAll$/]',
+          message:
+              'Don\'t use \'querySelectorAll(...) as Type\'. Use the type parameter, \'querySelectorAll<Type>(...)\' instead',
+        },
+        {
+          // Prevent a common misuse of "!" operator.
+          selector:
+              'TSNonNullExpression > CallExpression > MemberExpression[property.name=/^querySelectorAll$/]',
+          message:
+              'Remove unnecessary "!" non-null operator after querySelectorAll(). It always returns a non-null result',
+        },
+        {
+          // https://google.github.io/styleguide/jsguide.html#es-module-imports
+          //  1) Matching only import URLs that have at least one '/' slash,
+          //  to avoid false positives for NodeJS imports like `import fs from
+          //  'fs';`. Using '\u002F' instead of '/' as the suggested
+          //  workaround for https://github.com/eslint/eslint/issues/16555
+          //  2) Allowing extensions that have a length between 2-4 characters
+          //  (for example js, css, json)
+          selector:
+              'ImportDeclaration[source.value=/^.*\\u002F.*(?<!\\.[a-z]{2}|\\.[a-z]{3}|\\.[a-z]{4})$/]',
+          message:
+              'Disallowed extensionless import. Explicitly specify the extension suffix.',
+        }
+      ],
+
+      'no-throw-literal': 'error',
+      'no-trailing-spaces': 'error',
+      'no-var': 'error',
+      'prefer-const': 'error',
+
+      quotes: [
+        'error', 'single', {
+          allowTemplateLiterals: true,
+        }
+      ],
+
+      semi: ['error', 'always'],
+
+      // https://google.github.io/styleguide/jsguide.html#features-one-variable-per-declaration
+      'one-var': [
+        'error', {
+          let : 'never',
+          const : 'never',
+        }
+      ],
+
+      // TODO(dpapad): Add more checks according to our styleguide.
+    },
+  },
+  {
+    files: ['**/*.ts'],
+
+    plugins: {
+      '@typescript-eslint': typescriptEslint,
+      '@stylistic': stylistic,
+    },
+
+    languageOptions: {
+      parser: tsParser,
+    },
+
+    rules: {
+      'no-unused-vars': 'off',
+
+      '@typescript-eslint/no-unused-vars': [
+        'error', {
+          argsIgnorePattern: '^_',
+          varsIgnorePattern: '^_',
+          caughtErrorsIgnorePattern: '.*',
+        }
+      ],
+
+      // https://google.github.io/styleguide/tsguide.html#array-constructor
+      // Note: The rule below only partially enforces the styleguide, since it
+      // it does not flag invocations of the constructor with a single
+      // parameter.
+      'no-array-constructor': 'off',
+      '@typescript-eslint/no-array-constructor': 'error',
+
+      // https://google.github.io/styleguide/tsguide.html#automatic-semicolon-insertion
+      semi: 'off',
+      '@stylistic/semi': ['error'],
+
+      // https://google.github.io/styleguide/tsguide.html#arrayt-type
+      '@typescript-eslint/array-type': [
+        'error', {
+          default: 'array-simple',
+        }
+      ],
+
+      // https://google.github.io/styleguide/tsguide.html#type-assertions-syntax
+      '@typescript-eslint/consistent-type-assertions': [
+        'error', {
+          assertionStyle: 'as',
+        }
+      ],
+
+      // https://google.github.io/styleguide/tsguide.html#interfaces-vs-type-aliases
+      '@typescript-eslint/consistent-type-definitions': ['error', 'interface'],
+
+      // https://google.github.io/styleguide/tsguide.html#import-type
+      '@typescript-eslint/consistent-type-imports': 'error',
+
+      // https://google.github.io/styleguide/tsguide.html#visibility
+      '@typescript-eslint/explicit-member-accessibility': [
+        'error', {
+          accessibility: 'no-public',
+
+          overrides: {
+            parameterProperties: 'off',
+          },
+        }
+      ],
+
+      // https://google.github.io/styleguide/jsguide.html#naming
+      '@typescript-eslint/naming-convention': [
+        'error', {
+          selector:
+              ['class', 'interface', 'typeAlias', 'enum', 'typeParameter'],
+          format: ['StrictPascalCase'],
+
+          filter: {
+            // Exclude TypeScript defined interfaces HTMLElementTagNameMap
+            // and HTMLElementEventMap.
+            // Exclude native DOM types which are always named like
+            // HTML<Foo>Element.
+            // Exclude native DOM interfaces.
+            // Exclude the deprecated WebUIListenerBehavior interface.
+            regex:
+                '^(HTMLElementTagNameMap|HTMLElementEventMap|HTML[A-Za-z]{0,}Element|UIEvent|UIEventInit|DOMError|WebUIListenerBehavior)$',
+            match: false,
+          },
+        },
+        {
+          selector: 'enumMember',
+          format: ['UPPER_CASE'],
+        },
+        {
+          selector: 'classMethod',
+          format: ['strictCamelCase'],
+          modifiers: ['public'],
+        },
+        {
+          selector: 'classMethod',
+          format: ['strictCamelCase'],
+          modifiers: ['private'],
+          trailingUnderscore: 'allow',
+
+          // Disallow the 'Tap_' suffix, in favor of 'Click_' in event
+          // handlers. Note: Unfortunately this ESLint rule does not provide a
+          // way to customize the error message to better inform developers.
+          custom: {
+            regex: '^on[a-zA-Z0-9]+Tap$',
+            match: false,
+          },
+        },
+        {
+          selector: 'classProperty',
+          format: ['UPPER_CASE'],
+          modifiers: ['private', 'static', 'readonly'],
+        },
+        {
+          selector: 'classProperty',
+          format: ['UPPER_CASE'],
+          modifiers: ['public', 'static', 'readonly'],
+        },
+        {
+          selector: 'classProperty',
+          format: ['camelCase'],
+          modifiers: ['public'],
+        },
+        {
+          selector: 'classProperty',
+          format: ['camelCase'],
+          modifiers: ['private'],
+          trailingUnderscore: 'allow',
+        },
+        {
+          selector: 'parameter',
+          format: ['camelCase'],
+          leadingUnderscore: 'allow',
+        },
+        {
+          selector: 'function',
+          format: ['camelCase'],
+        }
+      ],
+
+      // https://google.github.io/styleguide/tsguide.html#member-property-declarations
+      '@stylistic/member-delimiter-style': [
+        'error', {
+          multiline: {
+            delimiter: 'comma',
+            requireLast: true,
+          },
+
+          singleline: {
+            delimiter: 'comma',
+            requireLast: false,
+          },
+
+          overrides: {
+            interface: {
+              multiline: {
+                delimiter: 'semi',
+                requireLast: true,
+              },
+
+              singleline: {
+                delimiter: 'semi',
+                requireLast: false,
+              },
+            },
+          },
+        }
+      ],
+
+      // https://google.github.io/styleguide/tsguide.html#wrapper-types
+      '@typescript-eslint/no-restricted-types': [
+        'error', {
+          types: {
+            String: {
+              message: 'Use string instead',
+              fixWith: 'string',
+            },
+
+            Boolean: {
+              message: 'Use boolean instead',
+              fixWith: 'boolean',
+            },
+
+            Number: {
+              message: 'Use number instead',
+              fixWith: 'number',
+            },
+
+            Symbol: {
+              message: 'Use symbol instead',
+              fixWith: 'symbol',
+            },
+
+            BigInt: {
+              message: 'Use bigint instead',
+              fixWith: 'bigint',
+            },
+          },
+        }
+      ],
+
+      // https://google.github.io/styleguide/tsguide.html#ts-ignore
+      '@typescript-eslint/ban-ts-comment': [
+        'error', {
+          'ts-ignore': true,
+        }
+      ],
+    },
+  },
+  {
+    // We do not allow per-directory custom eslint rules. This section exists
+    // for rules that are in the process of being applied to the whole code
+    // base.
+    files: [
+      'chrome/browser/resources/**/*.[jt]s',
+      'chrome/test/data/pdf/**/*.ts',
+      'chrome/test/data/webui/**/*.[jt]s',
+      'content/browser/resources/**/*.[jt]s',
+      'ui/webui/resources/**/*.[jt]s',
+    ],
+
+    rules: {
+      eqeqeq: [
+        'error', 'always', {
+          null: 'ignore',
+        }
+      ],
+    },
+  },
+  {
+    // 1-month exception for //ui/file_manager. This can be removed in November
+    // 2024. http://b/370371134.
+    files: ['ui/file_manager/**/*.[jt]s'],
+
+    rules: {
+      'no-console': 'off',
+      'no-restricted-syntax': 'off',
+    },
+  },
+  {
+    // 1-month exception for //chrome/browser/resources/ash/settings. This can
+    // be removed November 15 2024.
+    files: ['chrome/browser/resources/ash/settings/**/*.[jt]s'],
+
+    rules: {
+      '@typescript-eslint/consistent-type-imports': 'off',
+
+      '@typescript-eslint/explicit-function-return-type': [
+        'error', {
+          allowExpressions: true,
+          allowedNames: ['is', 'template', 'properties', 'observers'],
+        }
+      ],
+
+      '@typescript-eslint/no-inferrable-types': [
+        'error', {
+          ignoreParameters: true,
+          ignoreProperties: true,
+        }
+      ],
+
+      'prefer-arrow-callback': 'error',
+      'quote-props': ['error', 'consistent-as-needed'],
+    },
+  }
+];
diff --git a/tools/web_dev_style/eslint.py b/tools/web_dev_style/eslint.py
index 172f69a..8f122a0 100755
--- a/tools/web_dev_style/eslint.py
+++ b/tools/web_dev_style/eslint.py
@@ -24,14 +24,11 @@
   # navigate parent directories via '../'. We must set the repository's root as
   # the cwd.
   os.chdir(_SRC_PATH)
-  os.environ["ESLINT_USE_FLAT_CONFIG"] = "false"
   return node.RunNode([
       node_modules.PathToEsLint(),
       '--quiet',
       '--config',
-      os_path.join(_HERE_PATH, '.eslintrc.js'),
-      '--resolve-plugins-relative-to',
-      os_path.join(_NODE_PATH, 'node_modules'),
+      os_path.join(_HERE_PATH, 'eslint.config.mjs'),
   ] + args)