From 43bf16483051bcde351ecb987cb125675712a7a0 Mon Sep 17 00:00:00 2001 From: logonoff Date: Wed, 22 Jul 2026 13:14:58 -0400 Subject: [PATCH 1/7] CONSOLE-5439: Migrate frontend ESLint to flat config Replace legacy .eslintrc/.eslintignore files across frontend, e2e, i18n scripts, SDK, and integration test folders with a single `frontend/eslint.config.js` flat config. The new config preserves existing scoped rule sets (root app, packages, Cypress tests, Playwright e2e, SDK node dirs, eslint plugin package), centralizes ignore patterns, and adds compatibility helpers for FlatCompat plugin/rule handling. Update the frontend eslint script to drop `--ext` so file matching is driven by flat config. --- frontend/.eslintignore | 16 - frontend/.eslintrc.js | 149 ------ frontend/e2e/.eslintrc.cjs | 35 -- frontend/eslint.config.js | 471 ++++++++++++++++++ frontend/i18n-scripts/.eslintrc.js | 9 - frontend/package.json | 2 +- frontend/packages/.eslintrc.js | 4 - .../scripts/.eslintrc.js | 7 - .../src/webpack/.eslintrc.js | 4 - .../src/codegen/.eslintrc.js | 7 - .../src/webpack/.eslintrc.js | 7 - .../dev-console/integration-tests/.eslintrc | 26 - .../packages/eslint-plugin-console/.eslintrc | 9 - .../helm-plugin/integration-tests/.eslintrc | 26 - frontend/packages/integration-tests/.eslintrc | 16 - .../integration-tests/.eslintrc | 16 - 16 files changed, 472 insertions(+), 332 deletions(-) delete mode 100644 frontend/.eslintignore delete mode 100644 frontend/.eslintrc.js delete mode 100644 frontend/e2e/.eslintrc.cjs create mode 100644 frontend/eslint.config.js delete mode 100644 frontend/i18n-scripts/.eslintrc.js delete mode 100644 frontend/packages/.eslintrc.js delete mode 100644 frontend/packages/console-dynamic-plugin-sdk/scripts/.eslintrc.js delete mode 100644 frontend/packages/console-dynamic-plugin-sdk/src/webpack/.eslintrc.js delete mode 100644 frontend/packages/console-plugin-sdk/src/codegen/.eslintrc.js delete mode 100644 frontend/packages/console-plugin-sdk/src/webpack/.eslintrc.js delete mode 100644 frontend/packages/dev-console/integration-tests/.eslintrc delete mode 100644 frontend/packages/eslint-plugin-console/.eslintrc delete mode 100644 frontend/packages/helm-plugin/integration-tests/.eslintrc delete mode 100644 frontend/packages/integration-tests/.eslintrc delete mode 100644 frontend/packages/knative-plugin/integration-tests/.eslintrc diff --git a/frontend/.eslintignore b/frontend/.eslintignore deleted file mode 100644 index 035b50201ba..00000000000 --- a/frontend/.eslintignore +++ /dev/null @@ -1,16 +0,0 @@ -.puppeteer -.yarn -__coverage__ -**/node_modules -**/dist -**/generated -*.min.js -gui_test_screenshots -public/lib -Godeps -@types -dynamic-demo-plugin -.eslintrc.js -tsconfig.json -e2e/tsconfig.json -e2e/package.json diff --git a/frontend/.eslintrc.js b/frontend/.eslintrc.js deleted file mode 100644 index 312e544c699..00000000000 --- a/frontend/.eslintrc.js +++ /dev/null @@ -1,149 +0,0 @@ -module.exports = { - root: true, - env: { - browser: true, - es6: true, - jest: true, - node: true, - }, - extends: [ - 'eslint:recommended', - 'plugin:import/errors', - 'plugin:import/warnings', - 'plugin:react/recommended', - 'plugin:console/json', - 'plugin:console/prettier', - 'plugin:console/testing-library-tests', - ], - parser: '@typescript-eslint/parser', - parserOptions: { - project: './tsconfig.json', - ecmaFeatures: { - jsx: true, - }, - ecmaVersion: 2018, - extraFileExtensions: ['.json'], - tsconfigRootDir: __dirname, - }, - plugins: ['react', 'react-hooks', '@typescript-eslint', 'eslint-plugin-tsdoc', 'no-barrel-files'], - rules: { - camelcase: [ - 'error', - { allow: ['UNSAFE_componentWillReceiveProps', 'UNSAFE_componentWillMount'] }, - ], - 'consistent-return': 'off', - 'consistent-this': ['warn', 'that'], - curly: ['error', 'all'], - 'default-case': ['error'], - 'dot-notation': ['error'], - 'no-multiple-empty-lines': ['error', { max: 2, maxEOF: 0 }], - eqeqeq: ['error', 'always', { null: 'ignore' }], - 'guard-for-in': 'error', - 'import/no-duplicates': ['error'], - 'max-nested-callbacks': ['warn', 4], - 'no-alert': 'error', - 'no-caller': 'error', - 'no-console': 'error', - 'no-constant-condition': 'error', - 'no-debugger': 'error', - 'no-else-return': ['error'], - 'no-irregular-whitespace': ['error'], - 'no-prototype-builtins': 'off', - 'no-shadow': 'off', - '@typescript-eslint/no-shadow': 'error', - 'no-underscore-dangle': 'off', - '@typescript-eslint/no-unused-vars': [ - 'error', - { varsIgnorePattern: 'React', args: 'after-used', caughtErrors: 'none' }, - ], - '@typescript-eslint/no-use-before-define': 'error', - 'no-restricted-imports': [ - 'error', - { - paths: [ - { - name: 'lodash-es', - message: 'Use lodash instead. Bundler is configured to use lodash-es automatically.', - }, - { - name: 'react', - importNames: ['default', '*'], - // Future ESM-only React versions will remove the default export, - // and namespace imports may negatively impact tree-shaking - message: 'Use named imports instead.', - }, - { - name: 'react', - importNames: ['act'], - // https://testing-library.com/docs/react-testing-library/api/#act - message: "For consistency, import { act } from '@testing-library/react'", - }, - ], - patterns: [ - { - group: ['@patternfly/react-icons'], - importNamePattern: '^(?!Rh|createIcon)', - message: 'Use RhMicron, RhUi, or RhStandard icon variants instead of Font Awesome icons.', - }, - ], - }, - ], - 'no-var': 'error', - 'object-shorthand': ['error', 'properties'], - 'prefer-const': ['error', { destructuring: 'all' }], - 'prefer-template': 'error', - radix: 'error', - 'react/react-in-jsx-scope': 'off', // React 17 doesn't require this anymore - 'react/jsx-fragments': 'error', - 'react/jsx-no-duplicate-props': 'error', - 'react/jsx-uses-react': 'error', - 'react/jsx-uses-vars': 'error', - 'react/no-string-refs': 'warn', - 'react/no-unknown-property': 'error', - 'react/prop-types': 'off', // TODO: enable and fully specify component prop types - 'react/self-closing-comp': ['error', { component: true, html: false }], - 'react-hooks/rules-of-hooks': 'error', - 'react-hooks/exhaustive-deps': 'warn', - 'react/display-name': 'off', - 'react/no-unescaped-entities': 'off', - 'require-atomic-updates': 'off', - 'tsdoc/syntax': 'warn', - // Disable import rules with too many false positives in TypeScript - 'import/no-named-as-default-member': 'off', - 'import/named': 'off', - // New rules from eslint:recommended v8 / updated plugins - disable for now, enable in follow-up PRs - 'no-unsafe-optional-chaining': 'off', - 'no-import-assign': 'off', - 'no-constructor-return': 'off', - 'prefer-regex-literals': 'off', - 'no-restricted-exports': 'off', - 'no-barrel-files/no-barrel-files': 'error', - 'no-restricted-syntax': [ - 'warn', - { - selector: - "CallExpression[callee.name='useTranslation'][arguments.length=0]", - message: - 'Pass the i18n namespace to useTranslation(). Example: useTranslation(\'public\') instead of useTranslation().', - }, - ], - }, - settings: { - 'import/extensions': ['.js', '.jsx'], - 'import/resolver': { typescript: { extensions: ['.js', '.jsx', '.ts', '.tsx'] } }, - react: { - version: 'detect', - }, - }, - globals: { - process: 'readonly', - React: true, - JSX: 'readonly', - NodeJS: 'readonly', - Subject: 'readonly', - Diff: 'readonly', - BlobPropertyBag: 'readonly', - VoidFunction: 'readonly', - RequestInit: 'readonly', - }, -}; diff --git a/frontend/e2e/.eslintrc.cjs b/frontend/e2e/.eslintrc.cjs deleted file mode 100644 index 6dbe2c08652..00000000000 --- a/frontend/e2e/.eslintrc.cjs +++ /dev/null @@ -1,35 +0,0 @@ -module.exports = { - root: true, - parser: '@typescript-eslint/parser', - parserOptions: { - project: './tsconfig.json', - tsconfigRootDir: __dirname, - }, - extends: ['plugin:console/playwright'], - ignorePatterns: ['**/testData/**/*.json'], - rules: { - 'no-console': 'off', - 'no-empty-pattern': 'off', - 'no-restricted-syntax': [ - 'warn', - { - selector: 'CallExpression[callee.property.name="waitFor"]', - message: - 'Playwright actions (click, fill, check, clear) auto-wait for actionability. ' + - 'Do not call waitFor() before an action on the same locator. ' + - 'If this waitFor() is intentional (waiting for state without a subsequent action), ' + - 'add // eslint-disable-next-line no-restricted-syntax', - }, - ], - 'playwright/no-conditional-in-test': 'off', - 'playwright/no-skipped-test': ['warn', { allowConditional: true }], - }, - overrides: [ - { - files: ['setup/**/*.ts'], - rules: { - 'playwright/expect-expect': 'off', - }, - }, - ], -}; diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js new file mode 100644 index 00000000000..54e2fce1975 --- /dev/null +++ b/frontend/eslint.config.js @@ -0,0 +1,471 @@ +const { FlatCompat } = require('@eslint/eslintrc'); +const js = require('@eslint/js'); +const globals = require('globals'); +const path = require('path'); +const tsParser = require('@typescript-eslint/parser'); +const tsPlugin = require('@typescript-eslint/eslint-plugin'); +const cypressPlugin = require('eslint-plugin-cypress'); +const promisePlugin = require('eslint-plugin-promise'); +const nPlugin = require('eslint-plugin-n'); +const playwrightPlugin = require('eslint-plugin-playwright'); + +const compat = new FlatCompat({ + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, + allConfig: js.configs.all, +}); + +const SDK_NODE_DIRS = [ + 'packages/console-plugin-sdk/src/codegen', + 'packages/console-plugin-sdk/src/webpack', + 'packages/console-dynamic-plugin-sdk/scripts', + 'packages/console-dynamic-plugin-sdk/src/webpack', +]; + +const PACKAGES_EXCLUDE = [ + 'packages/eslint-plugin-console/**', + ...SDK_NODE_DIRS.map((d) => `${d}/**`), +]; + +/** + * ESLint 8 flat config validates plugins per config object — a rule like + * `prettier/prettier` fails if that config object doesn't carry the `prettier` + * plugin, even when another config in the array defines it. + * + * FlatCompat override configs often omit the plugin. Additionally, + * `eslint-config-prettier` disables rules from ~20 plugins (babel, vue, + * flowtype …) that may not be loaded — those are harmless `off` entries in + * eslintrc but fail flat config validation. + * + * This function: + * 1. Propagates loaded plugins to config objects that reference their rules. + * 2. Strips rules whose plugin is not available in any config in the group. + */ +function propagatePlugins(flatConfigs) { + const allPlugins = {}; + for (const config of flatConfigs) { + if (config.plugins) { + Object.assign(allPlugins, config.plugins); + } + } + + return flatConfigs.map((config) => { + if (!config.rules) { + return config; + } + + const addPlugins = {}; + const dropRules = []; + + for (const rule of Object.keys(config.rules)) { + const sep = rule.indexOf('/'); + if (sep <= 0) { + continue; + } + const prefix = rule.slice(0, sep); + const alreadyDefined = config.plugins && config.plugins[prefix]; + if (alreadyDefined) { + continue; + } + if (allPlugins[prefix]) { + addPlugins[prefix] = allPlugins[prefix]; + } else { + dropRules.push(rule); + } + } + + const needsChange = Object.keys(addPlugins).length > 0 || dropRules.length > 0; + if (!needsChange) { + return config; + } + + const result = { ...config }; + if (Object.keys(addPlugins).length > 0) { + result.plugins = { ...(config.plugins || {}), ...addPlugins }; + } + if (dropRules.length > 0) { + result.rules = { ...config.rules }; + for (const rule of dropRules) { + delete result.rules[rule]; + } + } + return result; + }); +} + +/** + * Restrict FlatCompat config objects to a directory scope. + * + * - Base configs (no `files`) get `files: ['{scopeDir}/**']`. + * - Override configs with function matchers get the function wrapped to also + * require the file to be inside `scopeDir`. + * - Override configs with glob strings get prefixed with `scopeDir/`. + * + * Also propagates plugins so every config that references a plugin rule + * carries that plugin (required by ESLint 8 flat config). + */ +function scopeTo(flatConfigs, scopeDir, extraIgnores) { + const absScope = path.resolve(__dirname, scopeDir) + '/'; + const scoped = flatConfigs.map((config) => { + const result = { ...config }; + + if (!config.files) { + const base = scopeDir === '.' ? '**' : `${scopeDir}/**`; + result.files = [`${base}/*.{js,jsx,ts,tsx,json}`]; + } else { + result.files = config.files.map((entry) => { + if (typeof entry === 'function') { + return (filePath) => filePath.startsWith(absScope) && entry(filePath); + } + return scopeDir === '.' ? entry : `${scopeDir}/${entry}`; + }); + } + + if (extraIgnores && extraIgnores.length > 0) { + result.ignores = [...(config.ignores || []), ...extraIgnores]; + } + + return result; + }); + + return propagatePlugins(scoped); +} + +module.exports = [ + // ──────────────────────────────────────────────── + // Global ignores (replaces .eslintignore) + // ──────────────────────────────────────────────── + { + ignores: [ + '.puppeteer/**', + '.yarn/**', + '__coverage__/**', + '**/node_modules/**', + '**/dist/**', + '**/generated/**', + '**/*.min.js', + 'gui_test_screenshots/**', + 'public/lib/**', + 'Godeps/**', + '@types/**', + 'dynamic-demo-plugin/**', + 'eslint.config.js', + '**/tsconfig.json', + 'e2e/package.json', + 'test-results/**', + 'yarn.lock', + '.vscode/**', + '**/.*', + 'e2e/**/testData/**', + ], + }, + + // ──────────────────────────────────────────────── + // Scope: Root frontend (non-packages, non-i18n-scripts, non-e2e) + // Replaces: frontend/.eslintrc.js + // ──────────────────────────────────────────────── + ...scopeTo( + compat.config({ + extends: [ + 'eslint:recommended', + 'plugin:import/errors', + 'plugin:import/warnings', + 'plugin:react/recommended', + 'plugin:console/json', + 'plugin:console/prettier', + 'plugin:console/testing-library-tests', + ], + env: { + browser: true, + es6: true, + jest: true, + node: true, + }, + parser: '@typescript-eslint/parser', + parserOptions: { + project: './tsconfig.json', + ecmaFeatures: { jsx: true }, + ecmaVersion: 2018, + extraFileExtensions: ['.json'], + tsconfigRootDir: __dirname, + }, + plugins: ['react', 'react-hooks', '@typescript-eslint', 'eslint-plugin-tsdoc', 'no-barrel-files'], + rules: { + camelcase: [ + 'error', + { allow: ['UNSAFE_componentWillReceiveProps', 'UNSAFE_componentWillMount'] }, + ], + 'consistent-return': 'off', + 'consistent-this': ['warn', 'that'], + curly: ['error', 'all'], + 'default-case': ['error'], + 'dot-notation': ['error'], + 'no-multiple-empty-lines': ['error', { max: 2, maxEOF: 0 }], + eqeqeq: ['error', 'always', { null: 'ignore' }], + 'guard-for-in': 'error', + 'import/no-duplicates': ['error'], + 'max-nested-callbacks': ['warn', 4], + 'no-alert': 'error', + 'no-caller': 'error', + 'no-console': 'error', + 'no-constant-condition': 'error', + 'no-debugger': 'error', + 'no-else-return': ['error'], + 'no-irregular-whitespace': ['error'], + 'no-prototype-builtins': 'off', + 'no-unused-vars': 'off', + 'no-shadow': 'off', + '@typescript-eslint/no-shadow': 'error', + 'no-underscore-dangle': 'off', + '@typescript-eslint/no-unused-vars': [ + 'error', + { varsIgnorePattern: 'React', args: 'after-used', caughtErrors: 'none' }, + ], + '@typescript-eslint/no-use-before-define': 'error', + 'no-restricted-imports': [ + 'error', + { + paths: [ + { + name: 'lodash-es', + message: 'Use lodash instead. The bundler is configured to use lodash-es automatically.', + }, + { + name: 'react', + importNames: ['default', '*'], + message: 'Use named imports instead.', + }, + { + name: 'react', + importNames: ['act'], + message: "For consistency, import { act } from '@testing-library/react'", + }, + ], + patterns: [ + { + group: ['@patternfly/react-icons'], + importNamePattern: '^(?!Rh|createIcon)', + message: + 'Use RhMicron, RhUi, or RhStandard icon variants instead of Font Awesome icons.', + }, + ], + }, + ], + 'no-var': 'error', + 'object-shorthand': ['error', 'properties'], + 'prefer-const': ['error', { destructuring: 'all' }], + 'prefer-template': 'error', + radix: 'error', + 'react/react-in-jsx-scope': 'off', + 'react/jsx-fragments': 'error', + 'react/jsx-no-duplicate-props': 'error', + 'react/jsx-uses-react': 'error', + 'react/jsx-uses-vars': 'error', + 'react/no-string-refs': 'warn', + 'react/no-unknown-property': 'error', + 'react/prop-types': 'off', + 'react/self-closing-comp': ['error', { component: true, html: false }], + 'react-hooks/rules-of-hooks': 'error', + 'react-hooks/exhaustive-deps': 'warn', + 'react/display-name': 'off', + 'react/no-unescaped-entities': 'off', + 'require-atomic-updates': 'off', + 'tsdoc/syntax': 'warn', + 'import/no-named-as-default-member': 'off', + 'import/named': 'off', + 'no-unsafe-optional-chaining': 'off', + 'no-import-assign': 'off', + 'no-constructor-return': 'off', + 'prefer-regex-literals': 'off', + 'no-restricted-exports': 'off', + 'no-barrel-files/no-barrel-files': 'error', + 'no-restricted-syntax': [ + 'warn', + { + selector: "CallExpression[callee.name='useTranslation'][arguments.length=0]", + message: + "Pass the i18n namespace to useTranslation(). Example: useTranslation('public') instead of useTranslation().", + }, + ], + }, + settings: { + 'import/extensions': ['.js', '.jsx'], + 'import/resolver': { typescript: { extensions: ['.js', '.jsx', '.ts', '.tsx'] } }, + react: { version: 'detect' }, + }, + globals: { + process: 'readonly', + React: true, + JSX: 'readonly', + NodeJS: 'readonly', + Subject: 'readonly', + Diff: 'readonly', + BlobPropertyBag: 'readonly', + VoidFunction: 'readonly', + RequestInit: 'readonly', + }, + }), + '.', + ['packages/**', 'i18n-scripts/**', 'e2e/**'], + ), + + // ──────────────────────────────────────────────── + // Scope: packages/ (react-typescript-prettier) + // Replaces: frontend/packages/.eslintrc.js + // ──────────────────────────────────────────────── + ...scopeTo( + compat.extends('plugin:console/react-typescript-prettier'), + 'packages', + PACKAGES_EXCLUDE, + ), + + // ──────────────────────────────────────────────── + // Scope: Cypress integration tests (overlay on packages config) + // Replaces: 4x integration-tests/.eslintrc files + // ──────────────────────────────────────────────── + ...compat.extends('plugin:cypress/recommended').map((c) => ({ + ...c, + files: c.files || [ + 'packages/integration-tests/**/*.{js,jsx,ts,tsx}', + 'packages/dev-console/integration-tests/**/*.{js,jsx,ts,tsx}', + 'packages/knative-plugin/integration-tests/**/*.{js,jsx,ts,tsx}', + 'packages/helm-plugin/integration-tests/**/*.{js,jsx,ts,tsx}', + ], + })), + { + files: [ + 'packages/integration-tests/**/*.{js,jsx,ts,tsx}', + 'packages/dev-console/integration-tests/**/*.{js,jsx,ts,tsx}', + 'packages/knative-plugin/integration-tests/**/*.{js,jsx,ts,tsx}', + 'packages/helm-plugin/integration-tests/**/*.{js,jsx,ts,tsx}', + ], + plugins: { + promise: promisePlugin, + cypress: cypressPlugin, + }, + languageOptions: { + globals: { + ...globals.node, + }, + }, + rules: { + 'no-console': 'off', + 'no-namespace': 'off', + 'no-redeclare': 'off', + 'promise/catch-or-return': 'off', + 'promise/no-nesting': 'off', + 'cypress/unsafe-to-chain-command': 'off', + }, + }, + { + files: [ + 'packages/dev-console/integration-tests/**/*.{js,jsx,ts,tsx}', + 'packages/helm-plugin/integration-tests/**/*.{js,jsx,ts,tsx}', + ], + plugins: { + '@typescript-eslint': tsPlugin, + cypress: cypressPlugin, + }, + rules: { + '@typescript-eslint/ban-ts-comment': 'off', + 'cypress/no-unnecessary-waiting': 'off', + }, + settings: { + 'import/resolver': { + node: { + extensions: ['.js', '.jsx', '.ts', '.tsx'], + moduleDirectory: ['node_modules', 'integration-tests/'], + }, + }, + }, + }, + + // ──────────────────────────────────────────────── + // Scope: SDK Node.js directories (node-typescript-prettier) + // Replaces: 4x .eslintrc.js in codegen/webpack/scripts dirs + // ──────────────────────────────────────────────── + ...SDK_NODE_DIRS.flatMap((dir) => + scopeTo(compat.extends('plugin:console/node-typescript-prettier'), dir), + ), + { + files: [ + 'packages/console-plugin-sdk/src/codegen/**/*.{js,ts}', + 'packages/console-plugin-sdk/src/webpack/**/*.{js,ts}', + ], + rules: { 'no-underscore-dangle': 'off' }, + }, + { + files: ['packages/console-dynamic-plugin-sdk/scripts/**/*.{js,ts}'], + rules: { 'no-console': 'off' }, + }, + + // ──────────────────────────────────────────────── + // Scope: eslint-plugin-console (base + node + json + prettier) + // Replaces: packages/eslint-plugin-console/.eslintrc + // ──────────────────────────────────────────────── + ...scopeTo( + compat.extends( + 'plugin:console/base', + 'plugin:console/node', + 'plugin:console/json', + 'plugin:console/prettier', + ), + 'packages/eslint-plugin-console', + ), + + // ──────────────────────────────────────────────── + // Scope: i18n-scripts (node-typescript-prettier) + // Replaces: frontend/i18n-scripts/.eslintrc.js + // ──────────────────────────────────────────────── + ...scopeTo(compat.extends('plugin:console/node-typescript-prettier'), 'i18n-scripts'), + { + files: ['i18n-scripts/**/*.{js,ts}'], + plugins: { n: nPlugin }, + rules: { + 'no-console': 'off', + 'n/no-unsupported-features/node-builtins': 'off', + }, + }, + + // ──────────────────────────────────────────────── + // Scope: e2e (Playwright) + // Replaces: e2e/.eslintrc.cjs + // ──────────────────────────────────────────────── + ...scopeTo(compat.extends('plugin:console/playwright'), 'e2e'), + { + files: ['e2e/**/*.{js,ts}'], + ignores: ['e2e/**/testData/**'], + languageOptions: { + parser: tsParser, + parserOptions: { + project: './tsconfig.json', + tsconfigRootDir: path.join(__dirname, 'e2e'), + }, + }, + plugins: { playwright: playwrightPlugin }, + rules: { + 'no-console': 'off', + 'no-empty-pattern': 'off', + 'no-restricted-syntax': [ + 'warn', + { + selector: 'CallExpression[callee.property.name="waitFor"]', + message: + 'Playwright actions (click, fill, check, clear) auto-wait for actionability. ' + + 'Do not call waitFor() before an action on the same locator. ' + + 'If this waitFor() is intentional (waiting for state without a subsequent action), ' + + 'add // eslint-disable-next-line no-restricted-syntax', + }, + ], + 'playwright/no-conditional-in-test': 'off', + 'playwright/no-skipped-test': ['warn', { allowConditional: true }], + }, + }, + { + files: ['e2e/setup/**/*.ts'], + plugins: { playwright: playwrightPlugin }, + rules: { + 'playwright/expect-expect': 'off', + }, + }, +]; diff --git a/frontend/i18n-scripts/.eslintrc.js b/frontend/i18n-scripts/.eslintrc.js deleted file mode 100644 index 8848b0a45c2..00000000000 --- a/frontend/i18n-scripts/.eslintrc.js +++ /dev/null @@ -1,9 +0,0 @@ -module.exports = { - root: true, - extends: ['plugin:console/node-typescript-prettier'], - rules: { - 'no-console': 'off', - // fs.promises requires a newer version of node however our compliance is set to node >=10 - 'n/no-unsupported-features/node-builtins': 'off', - }, -}; diff --git a/frontend/package.json b/frontend/package.json index 538d492dbd1..f314e0ae546 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -19,7 +19,7 @@ "build": "yarn clean && yarn build-plugin-sdk && NODE_ENV=production NODE_OPTIONS=--max-old-space-size=4096 yarn ts-node ./node_modules/.bin/rspack --mode=production", "check-cycles": "CHECK_CYCLES=true yarn dev-once", "coverage": "jest --coverage .", - "eslint": "node ./node_modules/.bin/eslint --max-warnings 0 --ext .js,.jsx,.ts,.tsx,.json --color", + "eslint": "node ./node_modules/.bin/eslint --max-warnings 0 --color", "lint": "NODE_OPTIONS=--max-old-space-size=4096 yarn eslint .", "gherkin-lint": "./node_modules/.bin/gherkin-lint -c ./packages/dev-console/integration-tests/.gherkin-lintrc ./packages/*/integration-tests/features", "test": "LANG=en_US.UTF-8 jest", diff --git a/frontend/packages/.eslintrc.js b/frontend/packages/.eslintrc.js deleted file mode 100644 index 61c092a2923..00000000000 --- a/frontend/packages/.eslintrc.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - root: true, - extends: ['plugin:console/react-typescript-prettier'], -}; diff --git a/frontend/packages/console-dynamic-plugin-sdk/scripts/.eslintrc.js b/frontend/packages/console-dynamic-plugin-sdk/scripts/.eslintrc.js deleted file mode 100644 index 92c383099a1..00000000000 --- a/frontend/packages/console-dynamic-plugin-sdk/scripts/.eslintrc.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - root: true, - extends: ['plugin:console/node-typescript-prettier'], - rules: { - 'no-console': 'off', - }, -}; diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/webpack/.eslintrc.js b/frontend/packages/console-dynamic-plugin-sdk/src/webpack/.eslintrc.js deleted file mode 100644 index 4db26f35661..00000000000 --- a/frontend/packages/console-dynamic-plugin-sdk/src/webpack/.eslintrc.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - root: true, - extends: ['plugin:console/node-typescript-prettier'], -}; diff --git a/frontend/packages/console-plugin-sdk/src/codegen/.eslintrc.js b/frontend/packages/console-plugin-sdk/src/codegen/.eslintrc.js deleted file mode 100644 index 862bcfabe40..00000000000 --- a/frontend/packages/console-plugin-sdk/src/codegen/.eslintrc.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - root: true, - extends: ['plugin:console/node-typescript-prettier'], - rules: { - 'no-underscore-dangle': 'off', - }, -}; diff --git a/frontend/packages/console-plugin-sdk/src/webpack/.eslintrc.js b/frontend/packages/console-plugin-sdk/src/webpack/.eslintrc.js deleted file mode 100644 index 862bcfabe40..00000000000 --- a/frontend/packages/console-plugin-sdk/src/webpack/.eslintrc.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - root: true, - extends: ['plugin:console/node-typescript-prettier'], - rules: { - 'no-underscore-dangle': 'off', - }, -}; diff --git a/frontend/packages/dev-console/integration-tests/.eslintrc b/frontend/packages/dev-console/integration-tests/.eslintrc deleted file mode 100644 index 92c0aa87f70..00000000000 --- a/frontend/packages/dev-console/integration-tests/.eslintrc +++ /dev/null @@ -1,26 +0,0 @@ -{ - "env": { - "cypress/globals": true, - "node": true - }, - "extends": ["../../.eslintrc", "plugin:cypress/recommended", "plugin:prettier/recommended"], - "plugins": ["cypress"], - "rules": { - "no-console": "off", - "no-namespace": "off", - "no-redeclare": "off", - "promise/catch-or-return": "off", - "promise/no-nesting": "off", - "@typescript-eslint/ban-ts-comment": "off", - "cypress/no-unnecessary-waiting": "off", - "cypress/unsafe-to-chain-command": "off" - }, - "settings": { - "import/resolver": { - "node": { - "extensions": [".js", ".jsx", ".ts", ".tsx"], - "moduleDirectory": ["node_modules", "integration-tests/"] - } - } - } -} diff --git a/frontend/packages/eslint-plugin-console/.eslintrc b/frontend/packages/eslint-plugin-console/.eslintrc deleted file mode 100644 index f73e3b722bc..00000000000 --- a/frontend/packages/eslint-plugin-console/.eslintrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "root": true, - "extends": [ - "plugin:console/base", - "plugin:console/node", - "plugin:console/json", - "plugin:console/prettier" - ] -} diff --git a/frontend/packages/helm-plugin/integration-tests/.eslintrc b/frontend/packages/helm-plugin/integration-tests/.eslintrc deleted file mode 100644 index 92c0aa87f70..00000000000 --- a/frontend/packages/helm-plugin/integration-tests/.eslintrc +++ /dev/null @@ -1,26 +0,0 @@ -{ - "env": { - "cypress/globals": true, - "node": true - }, - "extends": ["../../.eslintrc", "plugin:cypress/recommended", "plugin:prettier/recommended"], - "plugins": ["cypress"], - "rules": { - "no-console": "off", - "no-namespace": "off", - "no-redeclare": "off", - "promise/catch-or-return": "off", - "promise/no-nesting": "off", - "@typescript-eslint/ban-ts-comment": "off", - "cypress/no-unnecessary-waiting": "off", - "cypress/unsafe-to-chain-command": "off" - }, - "settings": { - "import/resolver": { - "node": { - "extensions": [".js", ".jsx", ".ts", ".tsx"], - "moduleDirectory": ["node_modules", "integration-tests/"] - } - } - } -} diff --git a/frontend/packages/integration-tests/.eslintrc b/frontend/packages/integration-tests/.eslintrc deleted file mode 100644 index 0124cd40b1b..00000000000 --- a/frontend/packages/integration-tests/.eslintrc +++ /dev/null @@ -1,16 +0,0 @@ -{ - "env": { - "cypress/globals": true, - "node": true - }, - "extends": ["../.eslintrc", "plugin:cypress/recommended"], - "plugins": ["cypress"], - "rules": { - "no-console": "off", - "no-namespace": "off", - "no-redeclare": "off", - "promise/catch-or-return": "off", - "promise/no-nesting": "off", - "cypress/unsafe-to-chain-command": "off" - } -} diff --git a/frontend/packages/knative-plugin/integration-tests/.eslintrc b/frontend/packages/knative-plugin/integration-tests/.eslintrc deleted file mode 100644 index 495f83312ee..00000000000 --- a/frontend/packages/knative-plugin/integration-tests/.eslintrc +++ /dev/null @@ -1,16 +0,0 @@ -{ - "env": { - "cypress/globals": true, - "node": true - }, - "extends": ["../../.eslintrc", "plugin:cypress/recommended", "plugin:prettier/recommended"], - "plugins": ["cypress"], - "rules": { - "no-console": "off", - "no-namespace": "off", - "no-redeclare": "off", - "promise/catch-or-return": "off", - "promise/no-nesting": "off", - "cypress/unsafe-to-chain-command": "off" - } -} From 792cf5dc6cd48217802093dbdf57a3f0e15047e1 Mon Sep 17 00:00:00 2001 From: logonoff Date: Fri, 24 Jul 2026 15:26:47 -0400 Subject: [PATCH 2/7] CONSOLE-5439: Migrate to eslint 9 --- frontend/__mocks__/fetch.ts | 1 - frontend/__mocks__/i18next.ts | 1 - frontend/__mocks__/react-i18next.ts | 1 - frontend/__mocks__/styleMock.js | 1 - .../{eslint.config.js => eslint.config.ts} | 248 ++++------ .../eslint-plugin-console/lib/config/json.js | 2 +- .../eslint-plugin-console/package.json | 8 +- frontend/test-puppeteer-csp.ts | 1 - frontend/webpack.circular-deps.ts | 1 - frontend/yarn.lock | 460 ++++++++---------- 10 files changed, 320 insertions(+), 404 deletions(-) rename frontend/{eslint.config.js => eslint.config.ts} (58%) diff --git a/frontend/__mocks__/fetch.ts b/frontend/__mocks__/fetch.ts index 956ee9c1cc5..98888fa001f 100644 --- a/frontend/__mocks__/fetch.ts +++ b/frontend/__mocks__/fetch.ts @@ -1,4 +1,3 @@ -/* eslint-env node */ import fetch from 'node-fetch'; // Polyfill window.fetch with node-fetch for nock v11 compatibility. diff --git a/frontend/__mocks__/i18next.ts b/frontend/__mocks__/i18next.ts index d6f7e2f0951..ffc740633da 100644 --- a/frontend/__mocks__/i18next.ts +++ b/frontend/__mocks__/i18next.ts @@ -1,4 +1,3 @@ -/* eslint-env node */ import { TFunction } from 'i18next'; const i18next = jest.requireActual('i18next'); diff --git a/frontend/__mocks__/react-i18next.ts b/frontend/__mocks__/react-i18next.ts index accda37f86d..d6b23d71b64 100644 --- a/frontend/__mocks__/react-i18next.ts +++ b/frontend/__mocks__/react-i18next.ts @@ -1,4 +1,3 @@ -/* eslint-env node */ const i18next = require('i18next'); const react = jest.requireActual('react'); const reactI18next = jest.requireActual('react-i18next'); diff --git a/frontend/__mocks__/styleMock.js b/frontend/__mocks__/styleMock.js index 1475d137437..f053ebf7976 100644 --- a/frontend/__mocks__/styleMock.js +++ b/frontend/__mocks__/styleMock.js @@ -1,2 +1 @@ -/* eslint-env node */ module.exports = {}; diff --git a/frontend/eslint.config.js b/frontend/eslint.config.ts similarity index 58% rename from frontend/eslint.config.js rename to frontend/eslint.config.ts index 54e2fce1975..7be5676f837 100644 --- a/frontend/eslint.config.js +++ b/frontend/eslint.config.ts @@ -1,16 +1,13 @@ -const { FlatCompat } = require('@eslint/eslintrc'); -const js = require('@eslint/js'); -const globals = require('globals'); -const path = require('path'); -const tsParser = require('@typescript-eslint/parser'); -const tsPlugin = require('@typescript-eslint/eslint-plugin'); -const cypressPlugin = require('eslint-plugin-cypress'); -const promisePlugin = require('eslint-plugin-promise'); -const nPlugin = require('eslint-plugin-n'); -const playwrightPlugin = require('eslint-plugin-playwright'); +import path from 'path'; +import { defineConfig, globalIgnores } from 'eslint/config'; +import { FlatCompat } from '@eslint/eslintrc'; +import js from '@eslint/js'; +import globals from 'globals'; +import tsParser from '@typescript-eslint/parser'; +import type { Linter } from 'eslint'; const compat = new FlatCompat({ - baseDirectory: __dirname, + baseDirectory: import.meta.dirname, recommendedConfig: js.configs.recommended, allConfig: js.configs.all, }); @@ -20,29 +17,32 @@ const SDK_NODE_DIRS = [ 'packages/console-plugin-sdk/src/webpack', 'packages/console-dynamic-plugin-sdk/scripts', 'packages/console-dynamic-plugin-sdk/src/webpack', -]; +] as const; const PACKAGES_EXCLUDE = [ 'packages/eslint-plugin-console/**', ...SDK_NODE_DIRS.map((d) => `${d}/**`), ]; +const CYPRESS_INTEGRATION_DIRS = [ + 'packages/integration-tests', + 'packages/dev-console/integration-tests', + 'packages/knative-plugin/integration-tests', + 'packages/helm-plugin/integration-tests', +]; + +const CYPRESS_FILES = CYPRESS_INTEGRATION_DIRS.map((d) => `${d}/**/*.{js,jsx,ts,tsx}`); + /** - * ESLint 8 flat config validates plugins per config object — a rule like - * `prettier/prettier` fails if that config object doesn't carry the `prettier` - * plugin, even when another config in the array defines it. - * - * FlatCompat override configs often omit the plugin. Additionally, * `eslint-config-prettier` disables rules from ~20 plugins (babel, vue, - * flowtype …) that may not be loaded — those are harmless `off` entries in - * eslintrc but fail flat config validation. - * - * This function: - * 1. Propagates loaded plugins to config objects that reference their rules. - * 2. Strips rules whose plugin is not available in any config in the group. + * flowtype …) that may not be loaded. FlatCompat converts these into config + * objects with rules like `babel/quotes: 0`, which fail flat config validation + * because the plugin isn't registered. Strip any rule whose plugin prefix + * doesn't appear in any config in the group. Also strips options from the + * `json/*` catch-all rule, whose schema rejects options under ESLint 9. */ -function propagatePlugins(flatConfigs) { - const allPlugins = {}; +function stripOrphanedPluginRules(flatConfigs: Linter.Config[]): Linter.Config[] { + const allPlugins: Record = {}; for (const config of flatConfigs) { if (config.plugins) { Object.assign(allPlugins, config.plugins); @@ -53,41 +53,26 @@ function propagatePlugins(flatConfigs) { if (!config.rules) { return config; } - - const addPlugins = {}; - const dropRules = []; - + const dropRules: string[] = []; + const fixRules: Linter.RulesRecord = {}; for (const rule of Object.keys(config.rules)) { const sep = rule.indexOf('/'); - if (sep <= 0) { - continue; - } - const prefix = rule.slice(0, sep); - const alreadyDefined = config.plugins && config.plugins[prefix]; - if (alreadyDefined) { - continue; + if (sep > 0) { + const prefix = rule.slice(0, sep); + if (!allPlugins[prefix]) { + dropRules.push(rule); + } } - if (allPlugins[prefix]) { - addPlugins[prefix] = allPlugins[prefix]; - } else { - dropRules.push(rule); + if (rule === 'json/*' && Array.isArray(config.rules[rule])) { + fixRules[rule] = (config.rules[rule] as Linter.RuleEntry[])[0]; } } - - const needsChange = Object.keys(addPlugins).length > 0 || dropRules.length > 0; - if (!needsChange) { + if (dropRules.length === 0 && Object.keys(fixRules).length === 0) { return config; } - - const result = { ...config }; - if (Object.keys(addPlugins).length > 0) { - result.plugins = { ...(config.plugins || {}), ...addPlugins }; - } - if (dropRules.length > 0) { - result.rules = { ...config.rules }; - for (const rule of dropRules) { - delete result.rules[rule]; - } + const result: Linter.Config = { ...config, rules: { ...config.rules, ...fixRules } }; + for (const rule of dropRules) { + delete result.rules![rule]; } return result; }); @@ -96,29 +81,34 @@ function propagatePlugins(flatConfigs) { /** * Restrict FlatCompat config objects to a directory scope. * - * - Base configs (no `files`) get `files: ['{scopeDir}/**']`. + * - Base configs (no `files`) get `files: ['{scopeDir}/**\/*.{js,jsx,ts,tsx,json}']`. * - Override configs with function matchers get the function wrapped to also * require the file to be inside `scopeDir`. * - Override configs with glob strings get prefixed with `scopeDir/`. * - * Also propagates plugins so every config that references a plugin rule - * carries that plugin (required by ESLint 8 flat config). + * Also strips rules from unloaded plugins (see `stripOrphanedPluginRules`). */ -function scopeTo(flatConfigs, scopeDir, extraIgnores) { - const absScope = path.resolve(__dirname, scopeDir) + '/'; - const scoped = flatConfigs.map((config) => { - const result = { ...config }; +function scopeTo( + flatConfigs: Linter.Config[], + scopeDir: string, + extraIgnores?: string[], +): Linter.Config[] { + const absScope = path.resolve(import.meta.dirname, scopeDir) + '/'; + const scoped = flatConfigs.map((config): Linter.Config => { + const result: Linter.Config = { ...config }; if (!config.files) { const base = scopeDir === '.' ? '**' : `${scopeDir}/**`; result.files = [`${base}/*.{js,jsx,ts,tsx,json}`]; } else { - result.files = config.files.map((entry) => { - if (typeof entry === 'function') { - return (filePath) => filePath.startsWith(absScope) && entry(filePath); - } - return scopeDir === '.' ? entry : `${scopeDir}/${entry}`; - }); + result.files = (config.files as Array boolean)>).map( + (entry) => { + if (typeof entry === 'function') { + return (filePath: string) => filePath.startsWith(absScope) && entry(filePath); + } + return scopeDir === '.' ? entry : `${scopeDir}/${entry}`; + }, + ); } if (extraIgnores && extraIgnores.length > 0) { @@ -128,42 +118,42 @@ function scopeTo(flatConfigs, scopeDir, extraIgnores) { return result; }); - return propagatePlugins(scoped); + return stripOrphanedPluginRules(scoped); } -module.exports = [ - // ──────────────────────────────────────────────── - // Global ignores (replaces .eslintignore) - // ──────────────────────────────────────────────── +export default defineConfig([ + globalIgnores([ + '.puppeteer/**', + '.yarn/**', + '__coverage__/**', + '**/node_modules/**', + '**/dist/**', + '**/generated/**', + '**/*.min.js', + 'gui_test_screenshots/**', + 'public/lib/**', + 'Godeps/**', + '@types/**', + 'dynamic-demo-plugin/**', + 'eslint.config.ts', + '**/tsconfig.json', + 'e2e/package.json', + 'test-results/**', + 'yarn.lock', + '.vscode/**', + '**/.*', + 'e2e/**/testData/**', + ]), + { - ignores: [ - '.puppeteer/**', - '.yarn/**', - '__coverage__/**', - '**/node_modules/**', - '**/dist/**', - '**/generated/**', - '**/*.min.js', - 'gui_test_screenshots/**', - 'public/lib/**', - 'Godeps/**', - '@types/**', - 'dynamic-demo-plugin/**', - 'eslint.config.js', - '**/tsconfig.json', - 'e2e/package.json', - 'test-results/**', - 'yarn.lock', - '.vscode/**', - '**/.*', - 'e2e/**/testData/**', - ], + linterOptions: { + reportUnusedDisableDirectives: 'off', + }, }, - // ──────────────────────────────────────────────── + // ------------------------------------------------ // Scope: Root frontend (non-packages, non-i18n-scripts, non-e2e) - // Replaces: frontend/.eslintrc.js - // ──────────────────────────────────────────────── + // ------------------------------------------------ ...scopeTo( compat.config({ extends: [ @@ -187,14 +177,11 @@ module.exports = [ ecmaFeatures: { jsx: true }, ecmaVersion: 2018, extraFileExtensions: ['.json'], - tsconfigRootDir: __dirname, + tsconfigRootDir: import.meta.dirname, }, plugins: ['react', 'react-hooks', '@typescript-eslint', 'eslint-plugin-tsdoc', 'no-barrel-files'], rules: { - camelcase: [ - 'error', - { allow: ['UNSAFE_componentWillReceiveProps', 'UNSAFE_componentWillMount'] }, - ], + camelcase: 'error', 'consistent-return': 'off', 'consistent-this': ['warn', 'that'], curly: ['error', 'all'], @@ -309,40 +296,24 @@ module.exports = [ ['packages/**', 'i18n-scripts/**', 'e2e/**'], ), - // ──────────────────────────────────────────────── + // ------------------------------------------------ // Scope: packages/ (react-typescript-prettier) - // Replaces: frontend/packages/.eslintrc.js - // ──────────────────────────────────────────────── + // ------------------------------------------------ ...scopeTo( compat.extends('plugin:console/react-typescript-prettier'), 'packages', PACKAGES_EXCLUDE, ), - // ──────────────────────────────────────────────── + // ------------------------------------------------ // Scope: Cypress integration tests (overlay on packages config) - // Replaces: 4x integration-tests/.eslintrc files - // ──────────────────────────────────────────────── + // ------------------------------------------------ ...compat.extends('plugin:cypress/recommended').map((c) => ({ ...c, - files: c.files || [ - 'packages/integration-tests/**/*.{js,jsx,ts,tsx}', - 'packages/dev-console/integration-tests/**/*.{js,jsx,ts,tsx}', - 'packages/knative-plugin/integration-tests/**/*.{js,jsx,ts,tsx}', - 'packages/helm-plugin/integration-tests/**/*.{js,jsx,ts,tsx}', - ], + files: c.files || CYPRESS_FILES, })), { - files: [ - 'packages/integration-tests/**/*.{js,jsx,ts,tsx}', - 'packages/dev-console/integration-tests/**/*.{js,jsx,ts,tsx}', - 'packages/knative-plugin/integration-tests/**/*.{js,jsx,ts,tsx}', - 'packages/helm-plugin/integration-tests/**/*.{js,jsx,ts,tsx}', - ], - plugins: { - promise: promisePlugin, - cypress: cypressPlugin, - }, + files: CYPRESS_FILES, languageOptions: { globals: { ...globals.node, @@ -362,10 +333,6 @@ module.exports = [ 'packages/dev-console/integration-tests/**/*.{js,jsx,ts,tsx}', 'packages/helm-plugin/integration-tests/**/*.{js,jsx,ts,tsx}', ], - plugins: { - '@typescript-eslint': tsPlugin, - cypress: cypressPlugin, - }, rules: { '@typescript-eslint/ban-ts-comment': 'off', 'cypress/no-unnecessary-waiting': 'off', @@ -380,10 +347,9 @@ module.exports = [ }, }, - // ──────────────────────────────────────────────── + // ------------------------------------------------ // Scope: SDK Node.js directories (node-typescript-prettier) - // Replaces: 4x .eslintrc.js in codegen/webpack/scripts dirs - // ──────────────────────────────────────────────── + // ------------------------------------------------ ...SDK_NODE_DIRS.flatMap((dir) => scopeTo(compat.extends('plugin:console/node-typescript-prettier'), dir), ), @@ -399,10 +365,9 @@ module.exports = [ rules: { 'no-console': 'off' }, }, - // ──────────────────────────────────────────────── + // ------------------------------------------------ // Scope: eslint-plugin-console (base + node + json + prettier) - // Replaces: packages/eslint-plugin-console/.eslintrc - // ──────────────────────────────────────────────── + // ------------------------------------------------ ...scopeTo( compat.extends( 'plugin:console/base', @@ -413,24 +378,21 @@ module.exports = [ 'packages/eslint-plugin-console', ), - // ──────────────────────────────────────────────── + // ------------------------------------------------ // Scope: i18n-scripts (node-typescript-prettier) - // Replaces: frontend/i18n-scripts/.eslintrc.js - // ──────────────────────────────────────────────── + // ------------------------------------------------ ...scopeTo(compat.extends('plugin:console/node-typescript-prettier'), 'i18n-scripts'), { files: ['i18n-scripts/**/*.{js,ts}'], - plugins: { n: nPlugin }, rules: { 'no-console': 'off', 'n/no-unsupported-features/node-builtins': 'off', }, }, - // ──────────────────────────────────────────────── + // ------------------------------------------------ // Scope: e2e (Playwright) - // Replaces: e2e/.eslintrc.cjs - // ──────────────────────────────────────────────── + // ------------------------------------------------ ...scopeTo(compat.extends('plugin:console/playwright'), 'e2e'), { files: ['e2e/**/*.{js,ts}'], @@ -439,10 +401,9 @@ module.exports = [ parser: tsParser, parserOptions: { project: './tsconfig.json', - tsconfigRootDir: path.join(__dirname, 'e2e'), + tsconfigRootDir: path.join(import.meta.dirname, 'e2e'), }, }, - plugins: { playwright: playwrightPlugin }, rules: { 'no-console': 'off', 'no-empty-pattern': 'off', @@ -463,9 +424,8 @@ module.exports = [ }, { files: ['e2e/setup/**/*.ts'], - plugins: { playwright: playwrightPlugin }, rules: { 'playwright/expect-expect': 'off', }, }, -]; +]); diff --git a/frontend/packages/eslint-plugin-console/lib/config/json.js b/frontend/packages/eslint-plugin-console/lib/config/json.js index a2a97563b01..08ff06c0eca 100644 --- a/frontend/packages/eslint-plugin-console/lib/config/json.js +++ b/frontend/packages/eslint-plugin-console/lib/config/json.js @@ -1,5 +1,5 @@ module.exports = { - extends: ['plugin:json/recommended'], + extends: ['plugin:json/recommended-legacy'], plugins: ['json'], diff --git a/frontend/packages/eslint-plugin-console/package.json b/frontend/packages/eslint-plugin-console/package.json index 2de158010cb..986e9dc0d3c 100644 --- a/frontend/packages/eslint-plugin-console/package.json +++ b/frontend/packages/eslint-plugin-console/package.json @@ -11,15 +11,15 @@ "dependencies": { "@typescript-eslint/eslint-plugin": "^8.0.0", "@typescript-eslint/parser": "^8.0.0", - "eslint": "^8.57.0", + "eslint": "^9.39.5", "eslint-config-airbnb": "19.0.4", "eslint-config-airbnb-base": "15.0.0", "eslint-config-prettier": "^8.10.0", "eslint-import-resolver-typescript": "^3.7.0", - "eslint-plugin-cypress": "^3.6.0", + "eslint-plugin-cypress": "^4.0.0", "eslint-plugin-import": "^2.31.0", "eslint-plugin-jest": "^29.0.0", - "eslint-plugin-json": "^2.0.1", + "eslint-plugin-json": "^5.0.0", "eslint-plugin-jsx-a11y": "^6.8.0", "eslint-plugin-n": "^17.24.0", "eslint-plugin-no-barrel-files": "^1.1.0", @@ -27,7 +27,7 @@ "eslint-plugin-prettier": "^4.2.1", "eslint-plugin-promise": "^6.6.0", "eslint-plugin-react": "^7.37.0", - "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-hooks": "^5.0.0", "eslint-plugin-sort-class-members": "^1.20.0", "eslint-plugin-testing-library": "^7.2.0", "eslint-plugin-tsdoc": "^0.4.0", diff --git a/frontend/test-puppeteer-csp.ts b/frontend/test-puppeteer-csp.ts index 540fc342b87..478b7b26825 100644 --- a/frontend/test-puppeteer-csp.ts +++ b/frontend/test-puppeteer-csp.ts @@ -1,4 +1,3 @@ -/* eslint-env node */ /* eslint-disable no-console */ import * as fs from 'fs'; diff --git a/frontend/webpack.circular-deps.ts b/frontend/webpack.circular-deps.ts index dab43c6e027..796a5643183 100644 --- a/frontend/webpack.circular-deps.ts +++ b/frontend/webpack.circular-deps.ts @@ -1,4 +1,3 @@ -/* eslint-env node */ /* eslint-disable no-console */ import type { Configuration, Compiler } from '@rspack/core'; diff --git a/frontend/yarn.lock b/frontend/yarn.lock index f6ef3a6228c..ab02e237281 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -2083,7 +2083,7 @@ __metadata: languageName: node linkType: hard -"@eslint-community/eslint-utils@npm:^4.1.2, @eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.5.0, @eslint-community/eslint-utils@npm:^4.9.1": +"@eslint-community/eslint-utils@npm:^4.1.2, @eslint-community/eslint-utils@npm:^4.5.0, @eslint-community/eslint-utils@npm:^4.8.0, @eslint-community/eslint-utils@npm:^4.9.1": version: 4.9.1 resolution: "@eslint-community/eslint-utils@npm:4.9.1" dependencies: @@ -2094,34 +2094,80 @@ __metadata: languageName: node linkType: hard -"@eslint-community/regexpp@npm:^4.11.0, @eslint-community/regexpp@npm:^4.12.2, @eslint-community/regexpp@npm:^4.6.1": +"@eslint-community/regexpp@npm:^4.11.0, @eslint-community/regexpp@npm:^4.12.1, @eslint-community/regexpp@npm:^4.12.2": version: 4.12.2 resolution: "@eslint-community/regexpp@npm:4.12.2" checksum: 10c0/fddcbc66851b308478d04e302a4d771d6917a0b3740dc351513c0da9ca2eab8a1adf99f5e0aa7ab8b13fa0df005c81adeee7e63a92f3effd7d367a163b721c2d languageName: node linkType: hard -"@eslint/eslintrc@npm:^2.1.4": - version: 2.1.4 - resolution: "@eslint/eslintrc@npm:2.1.4" +"@eslint/config-array@npm:^0.21.2": + version: 0.21.2 + resolution: "@eslint/config-array@npm:0.21.2" + dependencies: + "@eslint/object-schema": "npm:^2.1.7" + debug: "npm:^4.3.1" + minimatch: "npm:^3.1.5" + checksum: 10c0/89dfe815d18456177c0a1f238daf4593107fd20298b3598e0103054360d3b8d09d967defd8318f031185d68df1f95cfa68becf1390a9c5c6887665f1475142e3 + languageName: node + linkType: hard + +"@eslint/config-helpers@npm:^0.4.2": + version: 0.4.2 + resolution: "@eslint/config-helpers@npm:0.4.2" + dependencies: + "@eslint/core": "npm:^0.17.0" + checksum: 10c0/92efd7a527b2d17eb1a148409d71d80f9ac160b565ac73ee092252e8bf08ecd08670699f46b306b94f13d22e88ac88a612120e7847570dd7cdc72f234d50dcb4 + languageName: node + linkType: hard + +"@eslint/core@npm:^0.17.0": + version: 0.17.0 + resolution: "@eslint/core@npm:0.17.0" dependencies: - ajv: "npm:^6.12.4" + "@types/json-schema": "npm:^7.0.15" + checksum: 10c0/9a580f2246633bc752298e7440dd942ec421860d1946d0801f0423830e67887e4aeba10ab9a23d281727a978eb93d053d1922a587d502942a713607f40ed704e + languageName: node + linkType: hard + +"@eslint/eslintrc@npm:^3.3.6": + version: 3.3.6 + resolution: "@eslint/eslintrc@npm:3.3.6" + dependencies: + ajv: "npm:^6.14.0" debug: "npm:^4.3.2" - espree: "npm:^9.6.0" - globals: "npm:^13.19.0" + espree: "npm:^10.0.1" + globals: "npm:^14.0.0" ignore: "npm:^5.2.0" import-fresh: "npm:^3.2.1" - js-yaml: "npm:^4.1.0" - minimatch: "npm:^3.1.2" + js-yaml: "npm:^4.3.0" + minimatch: "npm:^3.1.5" strip-json-comments: "npm:^3.1.1" - checksum: 10c0/32f67052b81768ae876c84569ffd562491ec5a5091b0c1e1ca1e0f3c24fb42f804952fdd0a137873bc64303ba368a71ba079a6f691cee25beee9722d94cc8573 + checksum: 10c0/349697171ee116f501a2f483bf47cd38937a61880aeb1c23481a69afc95e95859430a0947acbe1f5507f223180bf57530ecf2989e73bcbea763a6dcffdf1d010 languageName: node linkType: hard -"@eslint/js@npm:8.57.1": - version: 8.57.1 - resolution: "@eslint/js@npm:8.57.1" - checksum: 10c0/b489c474a3b5b54381c62e82b3f7f65f4b8a5eaaed126546520bf2fede5532a8ed53212919fed1e9048dcf7f37167c8561d58d0ba4492a4244004e7793805223 +"@eslint/js@npm:9.39.5": + version: 9.39.5 + resolution: "@eslint/js@npm:9.39.5" + checksum: 10c0/49894e98ba313a7d3bfb1b20d55c0f9826be45a7db876fd84e533ac7f101b1d95b51cd22c6a6db3a45066b9c0d068c31b4a22fa0db8a6cc8744a25540efdb824 + languageName: node + linkType: hard + +"@eslint/object-schema@npm:^2.1.7": + version: 2.1.7 + resolution: "@eslint/object-schema@npm:2.1.7" + checksum: 10c0/936b6e499853d1335803f556d526c86f5fe2259ed241bc665000e1d6353828edd913feed43120d150adb75570cae162cf000b5b0dfc9596726761c36b82f4e87 + languageName: node + linkType: hard + +"@eslint/plugin-kit@npm:^0.4.1": + version: 0.4.1 + resolution: "@eslint/plugin-kit@npm:0.4.1" + dependencies: + "@eslint/core": "npm:^0.17.0" + levn: "npm:^0.4.1" + checksum: 10c0/51600f78b798f172a9915dffb295e2ffb44840d583427bc732baf12ecb963eb841b253300e657da91d890f4b323d10a1bd12934bf293e3018d8bb66fdce5217b languageName: node linkType: hard @@ -2138,14 +2184,30 @@ __metadata: languageName: unknown linkType: soft -"@humanwhocodes/config-array@npm:^0.13.0": - version: 0.13.0 - resolution: "@humanwhocodes/config-array@npm:0.13.0" +"@humanfs/core@npm:^0.19.2": + version: 0.19.2 + resolution: "@humanfs/core@npm:0.19.2" dependencies: - "@humanwhocodes/object-schema": "npm:^2.0.3" - debug: "npm:^4.3.1" - minimatch: "npm:^3.0.5" - checksum: 10c0/205c99e756b759f92e1f44a3dc6292b37db199beacba8f26c2165d4051fe73a4ae52fdcfd08ffa93e7e5cb63da7c88648f0e84e197d154bbbbe137b2e0dd332e + "@humanfs/types": "npm:^0.15.0" + checksum: 10c0/d0a1d52d7b30c27d49475a53072d1510b81c5803e44b342fb8faf3887f1aa27593a1e6dc76a45268e7892d3f4e198146659281f6b6d55eacf3fd5a38bac30c5c + languageName: node + linkType: hard + +"@humanfs/node@npm:^0.16.6": + version: 0.16.8 + resolution: "@humanfs/node@npm:0.16.8" + dependencies: + "@humanfs/core": "npm:^0.19.2" + "@humanfs/types": "npm:^0.15.0" + "@humanwhocodes/retry": "npm:^0.4.0" + checksum: 10c0/56140579db811af4e160b195d45d0f29acf644d192c93fe24c9e594ebf06f19dfc157494a07c84540b8a071c0e4b37209c2362765d31734f4d0be869c2422e25 + languageName: node + linkType: hard + +"@humanfs/types@npm:^0.15.0": + version: 0.15.0 + resolution: "@humanfs/types@npm:0.15.0" + checksum: 10c0/fc26b9a024b0e55f7eaf64036df94345bf5d36d6a41ef80ef38e78f1f7430ce26cf435af736adae58913baae18eac3f38c18739054a3d379102015978eae862e languageName: node linkType: hard @@ -2156,10 +2218,10 @@ __metadata: languageName: node linkType: hard -"@humanwhocodes/object-schema@npm:^2.0.3": - version: 2.0.3 - resolution: "@humanwhocodes/object-schema@npm:2.0.3" - checksum: 10c0/80520eabbfc2d32fe195a93557cef50dfe8c8905de447f022675aaf66abc33ae54098f5ea78548d925aa671cd4ab7c7daa5ad704fe42358c9b5e7db60f80696c +"@humanwhocodes/retry@npm:^0.4.0, @humanwhocodes/retry@npm:^0.4.2": + version: 0.4.3 + resolution: "@humanwhocodes/retry@npm:0.4.3" + checksum: 10c0/3775bb30087d4440b3f7406d5a057777d90e4b9f435af488a4923ef249e93615fb78565a85f173a186a076c7706a81d0d57d563a2624e4de2c5c9c66c486ce42 languageName: node linkType: hard @@ -3269,33 +3331,6 @@ __metadata: languageName: node linkType: hard -"@nodelib/fs.scandir@npm:2.1.5": - version: 2.1.5 - resolution: "@nodelib/fs.scandir@npm:2.1.5" - dependencies: - "@nodelib/fs.stat": "npm:2.0.5" - run-parallel: "npm:^1.1.9" - checksum: 10c0/732c3b6d1b1e967440e65f284bd06e5821fedf10a1bea9ed2bb75956ea1f30e08c44d3def9d6a230666574edbaf136f8cfd319c14fd1f87c66e6a44449afb2eb - languageName: node - linkType: hard - -"@nodelib/fs.stat@npm:2.0.5": - version: 2.0.5 - resolution: "@nodelib/fs.stat@npm:2.0.5" - checksum: 10c0/88dafe5e3e29a388b07264680dc996c17f4bda48d163a9d4f5c1112979f0ce8ec72aa7116122c350b4e7976bc5566dc3ddb579be1ceaacc727872eb4ed93926d - languageName: node - linkType: hard - -"@nodelib/fs.walk@npm:^1.2.8": - version: 1.2.8 - resolution: "@nodelib/fs.walk@npm:1.2.8" - dependencies: - "@nodelib/fs.scandir": "npm:2.1.5" - fastq: "npm:^1.6.0" - checksum: 10c0/db9de047c3bb9b51f9335a7bb46f4fcfb6829fb628318c12115fbaf7d369bfce71c15b103d1fc3b464812d936220ee9bc1c8f762d032c9f6be9acc99249095b1 - languageName: node - linkType: hard - "@nolyfill/is-core-module@npm:1.0.39": version: 1.0.39 resolution: "@nolyfill/is-core-module@npm:1.0.39" @@ -5363,10 +5398,10 @@ __metadata: languageName: node linkType: hard -"@types/estree@npm:*, @types/estree@npm:^1.0.8": - version: 1.0.8 - resolution: "@types/estree@npm:1.0.8" - checksum: 10c0/39d34d1afaa338ab9763f37ad6066e3f349444f9052b9676a7cc0252ef9485a41c6d81c9c4e0d26e9077993354edf25efc853f3224dd4b447175ef62bdcc86a5 +"@types/estree@npm:*, @types/estree@npm:^1.0.6, @types/estree@npm:^1.0.8": + version: 1.0.9 + resolution: "@types/estree@npm:1.0.9" + checksum: 10c0/3ad3286ca2988cd550dafb8f2ad599c8474868e954fa601a36655bdfefd8039f7c714b8c1c7f2ae219ffbd58bd4660e66fa7479a0120fc02d4777057d4865387 languageName: node linkType: hard @@ -5870,7 +5905,7 @@ __metadata: languageName: node linkType: hard -"@ungap/structured-clone@npm:^1.2.0, @ungap/structured-clone@npm:^1.3.0": +"@ungap/structured-clone@npm:^1.3.0": version: 1.3.0 resolution: "@ungap/structured-clone@npm:1.3.0" checksum: 10c0/0fc3097c2540ada1fc340ee56d58d96b5b536a2a0dab6e3ec17d4bfc8c4c86db345f61a375a8185f9da96f01c69678f836a2b57eeaa9e4b8eeafd26428e57b0a @@ -6311,7 +6346,7 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.10.0, acorn@npm:^8.11.0, acorn@npm:^8.15.0, acorn@npm:^8.4.1, acorn@npm:^8.9.0": +"acorn@npm:^8.10.0, acorn@npm:^8.11.0, acorn@npm:^8.15.0, acorn@npm:^8.4.1": version: 8.17.0 resolution: "acorn@npm:8.17.0" bin: @@ -6395,7 +6430,7 @@ __metadata: languageName: node linkType: hard -"ajv@npm:^6.12.3, ajv@npm:^6.12.4, ajv@npm:^6.12.5, ajv@npm:^6.7.0": +"ajv@npm:^6.12.3, ajv@npm:^6.12.5, ajv@npm:^6.14.0, ajv@npm:^6.7.0": version: 6.15.0 resolution: "ajv@npm:6.15.0" dependencies: @@ -8578,7 +8613,7 @@ __metadata: languageName: node linkType: hard -"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3, cross-spawn@npm:^7.0.6": +"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.3, cross-spawn@npm:^7.0.6": version: 7.0.6 resolution: "cross-spawn@npm:7.0.6" dependencies: @@ -9714,15 +9749,6 @@ __metadata: languageName: node linkType: hard -"doctrine@npm:^3.0.0": - version: 3.0.0 - resolution: "doctrine@npm:3.0.0" - dependencies: - esutils: "npm:^2.0.2" - checksum: 10c0/c96bdccabe9d62ab6fea9399fdff04a66e6563c1d6fb3a3a063e8d53c3bb136ba63e84250bbf63d00086a769ad53aef92d2bd483f03f837fc97b71cbee6b2520 - languageName: node - linkType: hard - "dom-accessibility-api@npm:^0.5.9": version: 0.5.16 resolution: "dom-accessibility-api@npm:0.5.16" @@ -10618,15 +10644,15 @@ __metadata: dependencies: "@typescript-eslint/eslint-plugin": "npm:^8.0.0" "@typescript-eslint/parser": "npm:^8.0.0" - eslint: "npm:^8.57.0" + eslint: "npm:^9.39.5" eslint-config-airbnb: "npm:19.0.4" eslint-config-airbnb-base: "npm:15.0.0" eslint-config-prettier: "npm:^8.10.0" eslint-import-resolver-typescript: "npm:^3.7.0" - eslint-plugin-cypress: "npm:^3.6.0" + eslint-plugin-cypress: "npm:^4.0.0" eslint-plugin-import: "npm:^2.31.0" eslint-plugin-jest: "npm:^29.0.0" - eslint-plugin-json: "npm:^2.0.1" + eslint-plugin-json: "npm:^5.0.0" eslint-plugin-jsx-a11y: "npm:^6.8.0" eslint-plugin-n: "npm:^17.24.0" eslint-plugin-no-barrel-files: "npm:^1.1.0" @@ -10634,7 +10660,7 @@ __metadata: eslint-plugin-prettier: "npm:^4.2.1" eslint-plugin-promise: "npm:^6.6.0" eslint-plugin-react: "npm:^7.37.0" - eslint-plugin-react-hooks: "npm:^4.6.0" + eslint-plugin-react-hooks: "npm:^5.0.0" eslint-plugin-sort-class-members: "npm:^1.20.0" eslint-plugin-testing-library: "npm:^7.2.0" eslint-plugin-tsdoc: "npm:^0.4.0" @@ -10642,14 +10668,14 @@ __metadata: languageName: unknown linkType: soft -"eslint-plugin-cypress@npm:^3.6.0": - version: 3.6.0 - resolution: "eslint-plugin-cypress@npm:3.6.0" +"eslint-plugin-cypress@npm:^4.0.0": + version: 4.3.0 + resolution: "eslint-plugin-cypress@npm:4.3.0" dependencies: - globals: "npm:^13.20.0" + globals: "npm:^15.15.0" peerDependencies: - eslint: ">=7" - checksum: 10c0/8e04baccbfdd6e255aa51ec5ecca29638387ec14833b33398009015bd220d5742c6cc3fea888f957832b00afd21dff1ef5d76ed5b3b69117e48acd45db8f8f1e + eslint: ">=9" + checksum: 10c0/76960cd9629f307a858b704629e12207dc80e13be719dd0fafe1f8e1b82617307677c5b027fe7a055b406d0c1ab70f2cb6a75643312dc44d3547eb983aa29083 languageName: node linkType: hard @@ -10716,13 +10742,13 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-json@npm:^2.0.1": - version: 2.0.1 - resolution: "eslint-plugin-json@npm:2.0.1" +"eslint-plugin-json@npm:^5.0.0": + version: 5.0.0 + resolution: "eslint-plugin-json@npm:5.0.0" dependencies: - lodash: "npm:^4.17.15" - vscode-json-languageservice: "npm:^3.3.5" - checksum: 10c0/aa4935097f0b282406d6713c81115766034d0350a434a25c1e229c046dbb34d8df6bbabd2b77aa3af9c4643d29ee19f3388ae0d2e8e9426ae125e337828d4231 + lodash: "npm:^4.17.21" + vscode-json-languageservice: "npm:^4.1.6" + checksum: 10c0/43d0dc0d8055b24270980ffe5b7cf9b9f591a872bc1ec3bc5178d55441242ebfa6f0db80e14fe3c947a776fd9b60fb493f5d21f0c887b2e969a0bf753a64185e languageName: node linkType: hard @@ -10816,12 +10842,12 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-react-hooks@npm:^4.6.0": - version: 4.6.2 - resolution: "eslint-plugin-react-hooks@npm:4.6.2" +"eslint-plugin-react-hooks@npm:^5.0.0": + version: 5.2.0 + resolution: "eslint-plugin-react-hooks@npm:5.2.0" peerDependencies: - eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 - checksum: 10c0/4844e58c929bc05157fb70ba1e462e34f1f4abcbc8dd5bbe5b04513d33e2699effb8bca668297976ceea8e7ebee4e8fc29b9af9d131bcef52886feaa2308b2cc + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + checksum: 10c0/1c8d50fa5984c6dea32470651807d2922cc3934cf3425e78f84a24c2dfd972e7f019bee84aefb27e0cf2c13fea0ac1d4473267727408feeb1c56333ca1489385 languageName: node linkType: hard @@ -10894,23 +10920,30 @@ __metadata: languageName: node linkType: hard -"eslint-scope@npm:^7.2.2": - version: 7.2.2 - resolution: "eslint-scope@npm:7.2.2" +"eslint-scope@npm:^8.4.0": + version: 8.4.0 + resolution: "eslint-scope@npm:8.4.0" dependencies: esrecurse: "npm:^4.3.0" estraverse: "npm:^5.2.0" - checksum: 10c0/613c267aea34b5a6d6c00514e8545ef1f1433108097e857225fed40d397dd6b1809dffd11c2fde23b37ca53d7bf935fe04d2a18e6fc932b31837b6ad67e1c116 + checksum: 10c0/407f6c600204d0f3705bd557f81bd0189e69cd7996f408f8971ab5779c0af733d1af2f1412066b40ee1588b085874fc37a2333986c6521669cdbdd36ca5058e0 languageName: node linkType: hard -"eslint-visitor-keys@npm:^3.4.1, eslint-visitor-keys@npm:^3.4.3": +"eslint-visitor-keys@npm:^3.4.3": version: 3.4.3 resolution: "eslint-visitor-keys@npm:3.4.3" checksum: 10c0/92708e882c0a5ffd88c23c0b404ac1628cf20104a108c745f240a13c332a11aac54f49a22d5762efbffc18ecbc9a580d1b7ad034bf5f3cc3307e5cbff2ec9820 languageName: node linkType: hard +"eslint-visitor-keys@npm:^4.2.1": + version: 4.2.1 + resolution: "eslint-visitor-keys@npm:4.2.1" + checksum: 10c0/fcd43999199d6740db26c58dbe0c2594623e31ca307e616ac05153c9272f12f1364f5a0b1917a8e962268fdecc6f3622c1c2908b4fcc2e047a106fe6de69dc43 + languageName: node + linkType: hard + "eslint-visitor-keys@npm:^5.0.0": version: 5.0.1 resolution: "eslint-visitor-keys@npm:5.0.1" @@ -10918,62 +10951,63 @@ __metadata: languageName: node linkType: hard -"eslint@npm:^8.57.0": - version: 8.57.1 - resolution: "eslint@npm:8.57.1" +"eslint@npm:^9.39.5": + version: 9.39.5 + resolution: "eslint@npm:9.39.5" dependencies: - "@eslint-community/eslint-utils": "npm:^4.2.0" - "@eslint-community/regexpp": "npm:^4.6.1" - "@eslint/eslintrc": "npm:^2.1.4" - "@eslint/js": "npm:8.57.1" - "@humanwhocodes/config-array": "npm:^0.13.0" + "@eslint-community/eslint-utils": "npm:^4.8.0" + "@eslint-community/regexpp": "npm:^4.12.1" + "@eslint/config-array": "npm:^0.21.2" + "@eslint/config-helpers": "npm:^0.4.2" + "@eslint/core": "npm:^0.17.0" + "@eslint/eslintrc": "npm:^3.3.6" + "@eslint/js": "npm:9.39.5" + "@eslint/plugin-kit": "npm:^0.4.1" + "@humanfs/node": "npm:^0.16.6" "@humanwhocodes/module-importer": "npm:^1.0.1" - "@nodelib/fs.walk": "npm:^1.2.8" - "@ungap/structured-clone": "npm:^1.2.0" - ajv: "npm:^6.12.4" + "@humanwhocodes/retry": "npm:^0.4.2" + "@types/estree": "npm:^1.0.6" + ajv: "npm:^6.14.0" chalk: "npm:^4.0.0" - cross-spawn: "npm:^7.0.2" + cross-spawn: "npm:^7.0.6" debug: "npm:^4.3.2" - doctrine: "npm:^3.0.0" escape-string-regexp: "npm:^4.0.0" - eslint-scope: "npm:^7.2.2" - eslint-visitor-keys: "npm:^3.4.3" - espree: "npm:^9.6.1" - esquery: "npm:^1.4.2" + eslint-scope: "npm:^8.4.0" + eslint-visitor-keys: "npm:^4.2.1" + espree: "npm:^10.4.0" + esquery: "npm:^1.5.0" esutils: "npm:^2.0.2" fast-deep-equal: "npm:^3.1.3" - file-entry-cache: "npm:^6.0.1" + file-entry-cache: "npm:^8.0.0" find-up: "npm:^5.0.0" glob-parent: "npm:^6.0.2" - globals: "npm:^13.19.0" - graphemer: "npm:^1.4.0" ignore: "npm:^5.2.0" imurmurhash: "npm:^0.1.4" is-glob: "npm:^4.0.0" - is-path-inside: "npm:^3.0.3" - js-yaml: "npm:^4.1.0" json-stable-stringify-without-jsonify: "npm:^1.0.1" - levn: "npm:^0.4.1" lodash.merge: "npm:^4.6.2" - minimatch: "npm:^3.1.2" + minimatch: "npm:^3.1.5" natural-compare: "npm:^1.4.0" optionator: "npm:^0.9.3" - strip-ansi: "npm:^6.0.1" - text-table: "npm:^0.2.0" + peerDependencies: + jiti: "*" + peerDependenciesMeta: + jiti: + optional: true bin: eslint: bin/eslint.js - checksum: 10c0/1fd31533086c1b72f86770a4d9d7058ee8b4643fd1cfd10c7aac1ecb8725698e88352a87805cf4b2ce890aa35947df4b4da9655fb7fdfa60dbb448a43f6ebcf1 + checksum: 10c0/46335bfcf180ffea9955b38122b578ac9e075b6220e5695be1c988354ea429b04f5db05edc132aa9019ce9847736e9ca0c9ea6d6cedda3c06209c9b52dbd0fd5 languageName: node linkType: hard -"espree@npm:^9.6.0, espree@npm:^9.6.1": - version: 9.6.1 - resolution: "espree@npm:9.6.1" +"espree@npm:^10.0.1, espree@npm:^10.4.0": + version: 10.4.0 + resolution: "espree@npm:10.4.0" dependencies: - acorn: "npm:^8.9.0" + acorn: "npm:^8.15.0" acorn-jsx: "npm:^5.3.2" - eslint-visitor-keys: "npm:^3.4.1" - checksum: 10c0/1a2e9b4699b715347f62330bcc76aee224390c28bb02b31a3752e9d07549c473f5f986720483c6469cf3cfb3c9d05df612ffc69eb1ee94b54b739e67de9bb460 + eslint-visitor-keys: "npm:^4.2.1" + checksum: 10c0/c63fe06131c26c8157b4083313cb02a9a54720a08e21543300e55288c40e06c3fc284bdecf108d3a1372c5934a0a88644c98714f38b6ae8ed272b40d9ea08d6b languageName: node linkType: hard @@ -10987,7 +11021,7 @@ __metadata: languageName: node linkType: hard -"esquery@npm:^1.4.2": +"esquery@npm:^1.5.0": version: 1.7.0 resolution: "esquery@npm:1.7.0" dependencies: @@ -11356,15 +11390,6 @@ __metadata: languageName: node linkType: hard -"fastq@npm:^1.6.0": - version: 1.15.0 - resolution: "fastq@npm:1.15.0" - dependencies: - reusify: "npm:^1.0.4" - checksum: 10c0/5ce4f83afa5f88c9379e67906b4d31bc7694a30826d6cc8d0f0473c966929017fda65c2174b0ec89f064ede6ace6c67f8a4fe04cef42119b6a55b0d465554c24 - languageName: node - linkType: hard - "fb-watchman@npm:^2.0.2": version: 2.0.2 resolution: "fb-watchman@npm:2.0.2" @@ -11431,12 +11456,12 @@ __metadata: languageName: node linkType: hard -"file-entry-cache@npm:^6.0.1": - version: 6.0.1 - resolution: "file-entry-cache@npm:6.0.1" +"file-entry-cache@npm:^8.0.0": + version: 8.0.0 + resolution: "file-entry-cache@npm:8.0.0" dependencies: - flat-cache: "npm:^3.0.4" - checksum: 10c0/58473e8a82794d01b38e5e435f6feaf648e3f36fdb3a56e98f417f4efae71ad1c0d4ebd8a9a7c50c3ad085820a93fc7494ad721e0e4ebc1da3573f4e1c3c7cdd + flat-cache: "npm:^4.0.0" + checksum: 10c0/9e2b5938b1cd9b6d7e3612bdc533afd4ac17b2fc646569e9a8abbf2eb48e5eb8e316bc38815a3ef6a1b456f4107f0d0f055a614ca613e75db6bf9ff4d72c1638 languageName: node linkType: hard @@ -11522,14 +11547,13 @@ __metadata: languageName: node linkType: hard -"flat-cache@npm:^3.0.4": - version: 3.2.0 - resolution: "flat-cache@npm:3.2.0" +"flat-cache@npm:^4.0.0": + version: 4.0.1 + resolution: "flat-cache@npm:4.0.1" dependencies: flatted: "npm:^3.2.9" - keyv: "npm:^4.5.3" - rimraf: "npm:^3.0.2" - checksum: 10c0/b76f611bd5f5d68f7ae632e3ae503e678d205cf97a17c6ab5b12f6ca61188b5f1f7464503efae6dc18683ed8f0b41460beb48ac4b9ac63fe6201296a91ba2f75 + keyv: "npm:^4.5.4" + checksum: 10c0/2c59d93e9faa2523e4fda6b4ada749bed432cfa28c8e251f33b25795e426a1c6dbada777afb1f74fcfff33934fdbdea921ee738fcc33e71adc9d6eca984a1cfc languageName: node linkType: hard @@ -12072,7 +12096,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:7.1.6, glob@npm:^7.0.0, glob@npm:^7.1.0, glob@npm:^7.1.2, glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:^7.1.6": +"glob@npm:7.1.6, glob@npm:^7.0.0, glob@npm:^7.1.0, glob@npm:^7.1.2, glob@npm:^7.1.4, glob@npm:^7.1.6": version: 7.2.3 resolution: "glob@npm:7.2.3" dependencies: @@ -12122,16 +12146,14 @@ __metadata: languageName: node linkType: hard -"globals@npm:^13.19.0, globals@npm:^13.20.0": - version: 13.24.0 - resolution: "globals@npm:13.24.0" - dependencies: - type-fest: "npm:^0.20.2" - checksum: 10c0/d3c11aeea898eb83d5ec7a99508600fbe8f83d2cf00cbb77f873dbf2bcb39428eff1b538e4915c993d8a3b3473fa71eeebfe22c9bb3a3003d1e26b1f2c8a42cd +"globals@npm:^14.0.0": + version: 14.0.0 + resolution: "globals@npm:14.0.0" + checksum: 10c0/b96ff42620c9231ad468d4c58ff42afee7777ee1c963013ff8aabe095a451d0ceeb8dcd8ef4cbd64d2538cef45f787a78ba3a9574f4a634438963e334471302d languageName: node linkType: hard -"globals@npm:^15.11.0": +"globals@npm:^15.11.0, globals@npm:^15.15.0": version: 15.15.0 resolution: "globals@npm:15.15.0" checksum: 10c0/f9ae80996392ca71316495a39bec88ac43ae3525a438b5626cd9d5ce9d5500d0a98a266409605f8cd7241c7acf57c354a48111ea02a767ba4f374b806d6861fe @@ -12183,13 +12205,6 @@ __metadata: languageName: node linkType: hard -"graphemer@npm:^1.4.0": - version: 1.4.0 - resolution: "graphemer@npm:1.4.0" - checksum: 10c0/e951259d8cd2e0d196c72ec711add7115d42eb9a8146c8eeda5b8d3ac91e5dd816b9cd68920726d9fd4490368e7ed86e9c423f40db87e2d8dfafa00fa17c3a31 - languageName: node - linkType: hard - "graphlib@npm:^2.1.8": version: 2.1.8 resolution: "graphlib@npm:2.1.8" @@ -13307,7 +13322,7 @@ __metadata: languageName: node linkType: hard -"is-path-inside@npm:^3.0.2, is-path-inside@npm:^3.0.3": +"is-path-inside@npm:^3.0.2": version: 3.0.3 resolution: "is-path-inside@npm:3.0.3" checksum: 10c0/cf7d4ac35fb96bab6a1d2c3598fe5ebb29aafb52c0aaa482b5a3ed9d8ba3edc11631e3ec2637660c44b3ce0e61a08d54946e8af30dec0b60a7c27296c68ffd05 @@ -14277,14 +14292,14 @@ __metadata: languageName: node linkType: hard -"js-yaml@npm:^4.1.0": - version: 4.1.1 - resolution: "js-yaml@npm:4.1.1" +"js-yaml@npm:^4.1.0, js-yaml@npm:^4.3.0": + version: 4.3.0 + resolution: "js-yaml@npm:4.3.0" dependencies: argparse: "npm:^2.0.1" bin: js-yaml: bin/js-yaml.js - checksum: 10c0/561c7d7088c40a9bb53cc75becbfb1df6ae49b34b5e6e5a81744b14ae8667ec564ad2527709d1a6e7d5e5fa6d483aa0f373a50ad98d42fde368ec4a190d4fae7 + checksum: 10c0/058b30473d6915ca5b4feb11e2f7d4d97242f98d00a798ed48dd90b46b7c640398afe9128c5db22c5300f8c6528fe2a174b9a93f351a70ebc28c6203938d8bff languageName: node linkType: hard @@ -14463,13 +14478,6 @@ __metadata: languageName: node linkType: hard -"jsonc-parser@npm:^2.3.1": - version: 2.3.1 - resolution: "jsonc-parser@npm:2.3.1" - checksum: 10c0/b5e823612f6518a4d35e65d3c642e87b994c52a71b6d83d306d59f9b57003a1f6c64659808f0f1c3448991c28916d56faca45222f31ddb1a32effecdef0f0485 - languageName: node - linkType: hard - "jsonc-parser@npm:^3.0.0, jsonc-parser@npm:^3.2.0, jsonc-parser@npm:^3.3.1": version: 3.3.1 resolution: "jsonc-parser@npm:3.3.1" @@ -14723,7 +14731,7 @@ __metadata: languageName: node linkType: hard -"keyv@npm:^4.5.3": +"keyv@npm:^4.5.4": version: 4.5.4 resolution: "keyv@npm:4.5.4" dependencies: @@ -15535,7 +15543,7 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": +"minimatch@npm:^3.0.4, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2, minimatch@npm:^3.1.5": version: 3.1.5 resolution: "minimatch@npm:3.1.5" dependencies: @@ -18451,13 +18459,6 @@ __metadata: languageName: node linkType: hard -"reusify@npm:^1.0.4": - version: 1.0.4 - resolution: "reusify@npm:1.0.4" - checksum: 10c0/c19ef26e4e188f408922c46f7ff480d38e8dfc55d448310dfb518736b23ed2c4f547fb64a6ed5bdba92cd7e7ddc889d36ff78f794816d5e71498d645ef476107 - languageName: node - linkType: hard - "rfc4648@npm:^1.3.0": version: 1.5.4 resolution: "rfc4648@npm:1.5.4" @@ -18465,17 +18466,6 @@ __metadata: languageName: node linkType: hard -"rimraf@npm:^3.0.2": - version: 3.0.2 - resolution: "rimraf@npm:3.0.2" - dependencies: - glob: "npm:^7.1.3" - bin: - rimraf: bin.js - checksum: 10c0/9cb7757acb489bd83757ba1a274ab545eafd75598a9d817e0c3f8b164238dd90eba50d6b848bd4dcc5f3040912e882dc7ba71653e35af660d77b25c381d402e8 - languageName: node - linkType: hard - "ripemd160@npm:^2.0.0, ripemd160@npm:^2.0.1": version: 2.0.2 resolution: "ripemd160@npm:2.0.2" @@ -18514,13 +18504,6 @@ __metadata: languageName: node linkType: hard -"run-parallel@npm:^1.1.9": - version: 1.1.9 - resolution: "run-parallel@npm:1.1.9" - checksum: 10c0/942f4da08912c0f6029e29bcea87a6e7b07618ab4e640eb6755906e12f8ebfaf7afa15da9eb1adedb6a69e19291323aa290a58dce0f47d1ab9045d1e1c34675d - languageName: node - linkType: hard - "rw@npm:1": version: 1.3.3 resolution: "rw@npm:1.3.3" @@ -20004,13 +19987,6 @@ __metadata: languageName: node linkType: hard -"text-table@npm:^0.2.0": - version: 0.2.0 - resolution: "text-table@npm:0.2.0" - checksum: 10c0/02805740c12851ea5982686810702e2f14369a5f4c5c40a836821e3eefc65ffeec3131ba324692a37608294b0fd8c1e55a2dd571ffed4909822787668ddbee5c - languageName: node - linkType: hard - "textextensions@npm:^6.11.0": version: 6.11.0 resolution: "textextensions@npm:6.11.0" @@ -20467,13 +20443,6 @@ __metadata: languageName: node linkType: hard -"type-fest@npm:^0.20.2": - version: 0.20.2 - resolution: "type-fest@npm:0.20.2" - checksum: 10c0/dea9df45ea1f0aaa4e2d3bed3f9a0bfe9e5b2592bddb92eb1bf06e50bcf98dbb78189668cd8bc31a0511d3fc25539b4cd5c704497e53e93e2d40ca764b10bfc3 - languageName: node - linkType: hard - "type-fest@npm:^0.21.3": version: 0.21.3 resolution: "type-fest@npm:0.21.3" @@ -21573,16 +21542,16 @@ __metadata: languageName: node linkType: hard -"vscode-json-languageservice@npm:^3.3.5": - version: 3.10.0 - resolution: "vscode-json-languageservice@npm:3.10.0" +"vscode-json-languageservice@npm:^4.1.6": + version: 4.2.1 + resolution: "vscode-json-languageservice@npm:4.2.1" dependencies: - jsonc-parser: "npm:^2.3.1" - vscode-languageserver-textdocument: "npm:^1.0.1" - vscode-languageserver-types: "npm:3.16.0-next.2" + jsonc-parser: "npm:^3.0.0" + vscode-languageserver-textdocument: "npm:^1.0.3" + vscode-languageserver-types: "npm:^3.16.0" vscode-nls: "npm:^5.0.0" - vscode-uri: "npm:^2.1.2" - checksum: 10c0/83a984cd4a5623864e24747a1b2afe3a711b4daded4456673274caf20e491ea0c65295aedb006dca9794ee5cc5744589ef8b31d5508730e3bab7653feb7e35fa + vscode-uri: "npm:^3.0.3" + checksum: 10c0/8d91731f75e2cba0d0971be54b1e648590c03287b75115171e964b7b935e20c293d169a20499d36377a7853b2fc6b858fb01645f2fb6a0d4ce2c4b6b1f802b73 languageName: node linkType: hard @@ -21603,27 +21572,27 @@ __metadata: languageName: node linkType: hard -"vscode-languageserver-textdocument@npm:^1.0.0, vscode-languageserver-textdocument@npm:^1.0.1": - version: 1.0.12 - resolution: "vscode-languageserver-textdocument@npm:1.0.12" - checksum: 10c0/534349894b059602c4d97615a1147b6c4c031141c2093e59657f54e38570f5989c21b376836f13b9375419869242e9efb4066643208b21ab1e1dee111a0f00fb +"vscode-languageserver-textdocument@npm:^1.0.0, vscode-languageserver-textdocument@npm:^1.0.3": + version: 1.0.13 + resolution: "vscode-languageserver-textdocument@npm:1.0.13" + checksum: 10c0/1de174f1de3bfa9e1660a4b3c1b1c711497196d761e33f4d00785fdbfc556ae5a1f440db04771cbc0f6bd66c2a6f303062bc17d8dc46e76820e39fa2d5626564 languageName: node linkType: hard -"vscode-languageserver-types@npm:3.16.0-next.2": - version: 3.16.0-next.2 - resolution: "vscode-languageserver-types@npm:3.16.0-next.2" - checksum: 10c0/783b1b593655413eb657c4238fe93532b1eb299f6100b85ac74e2a36cf808e2719813240da85eb3d0a6adf30e922b3bcba5d174ea5d45cfb463ea6698576cf33 - languageName: node - linkType: hard - -"vscode-languageserver-types@npm:3.17.5, vscode-languageserver-types@npm:^3.0.0": +"vscode-languageserver-types@npm:3.17.5": version: 3.17.5 resolution: "vscode-languageserver-types@npm:3.17.5" checksum: 10c0/1e1260de79a2cc8de3e46f2e0182cdc94a7eddab487db5a3bd4ee716f67728e685852707d72c059721ce500447be9a46764a04f0611e94e4321ffa088eef36f8 languageName: node linkType: hard +"vscode-languageserver-types@npm:^3.0.0, vscode-languageserver-types@npm:^3.16.0": + version: 3.18.0 + resolution: "vscode-languageserver-types@npm:3.18.0" + checksum: 10c0/d8c51cd286cc55b4c1f333e87fa3cefc66d2aae8c65f6209e7ecf88032d3326308b333d62b05243c194be8a3304760445b87630b298116c6c612607f011b4de0 + languageName: node + linkType: hard + "vscode-nls@npm:^5.0.0": version: 5.0.0 resolution: "vscode-nls@npm:5.0.0" @@ -21631,17 +21600,10 @@ __metadata: languageName: node linkType: hard -"vscode-uri@npm:^2.1.2": - version: 2.1.2 - resolution: "vscode-uri@npm:2.1.2" - checksum: 10c0/4ed01e79f8caee5518d7dce567280001a00c87ff75c29421ac3693c735834f17950e79f818981c591e58c6efe681e13928470037b6ae75c948bec9b398e4c8db - languageName: node - linkType: hard - -"vscode-uri@npm:^3.0.0": - version: 3.0.8 - resolution: "vscode-uri@npm:3.0.8" - checksum: 10c0/f7f217f526bf109589969fe6e66b71e70b937de1385a1d7bb577ca3ee7c5e820d3856a86e9ff2fa9b7a0bc56a3dd8c3a9a557d3fedd7df414bc618d5e6b567f9 +"vscode-uri@npm:^3.0.0, vscode-uri@npm:^3.0.3": + version: 3.1.0 + resolution: "vscode-uri@npm:3.1.0" + checksum: 10c0/5f6c9c10fd9b1664d71fab4e9fbbae6be93c7f75bb3a1d9d74399a88ab8649e99691223fd7cef4644376cac6e94fa2c086d802521b9a8e31c5af3e60f0f35624 languageName: node linkType: hard From 298b55c944f7fef6b94db394a6a1da62278777ef Mon Sep 17 00:00:00 2001 From: logonoff Date: Tue, 28 Jul 2026 16:33:45 -0400 Subject: [PATCH 3/7] CONSOLE-5439: Use defineConfig extends for directory scoping in ESLint config Replace custom scopeTo() and stripOrphanedPluginRules() helpers with defineConfig's built-in extends key (available since ESLint 9.22.0). defineConfig handles directory scoping via files/ignores, function matcher intersection, and ignores propagation natively. stripOrphanedPluginRules was unnecessary because ESLint 9 silently ignores "off" rules from unregistered plugins. Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend/eslint.config.ts | 139 ++++++++------------------------------ 1 file changed, 27 insertions(+), 112 deletions(-) diff --git a/frontend/eslint.config.ts b/frontend/eslint.config.ts index 7be5676f837..af74881d31c 100644 --- a/frontend/eslint.config.ts +++ b/frontend/eslint.config.ts @@ -4,7 +4,6 @@ import { FlatCompat } from '@eslint/eslintrc'; import js from '@eslint/js'; import globals from 'globals'; import tsParser from '@typescript-eslint/parser'; -import type { Linter } from 'eslint'; const compat = new FlatCompat({ baseDirectory: import.meta.dirname, @@ -33,94 +32,6 @@ const CYPRESS_INTEGRATION_DIRS = [ const CYPRESS_FILES = CYPRESS_INTEGRATION_DIRS.map((d) => `${d}/**/*.{js,jsx,ts,tsx}`); -/** - * `eslint-config-prettier` disables rules from ~20 plugins (babel, vue, - * flowtype …) that may not be loaded. FlatCompat converts these into config - * objects with rules like `babel/quotes: 0`, which fail flat config validation - * because the plugin isn't registered. Strip any rule whose plugin prefix - * doesn't appear in any config in the group. Also strips options from the - * `json/*` catch-all rule, whose schema rejects options under ESLint 9. - */ -function stripOrphanedPluginRules(flatConfigs: Linter.Config[]): Linter.Config[] { - const allPlugins: Record = {}; - for (const config of flatConfigs) { - if (config.plugins) { - Object.assign(allPlugins, config.plugins); - } - } - - return flatConfigs.map((config) => { - if (!config.rules) { - return config; - } - const dropRules: string[] = []; - const fixRules: Linter.RulesRecord = {}; - for (const rule of Object.keys(config.rules)) { - const sep = rule.indexOf('/'); - if (sep > 0) { - const prefix = rule.slice(0, sep); - if (!allPlugins[prefix]) { - dropRules.push(rule); - } - } - if (rule === 'json/*' && Array.isArray(config.rules[rule])) { - fixRules[rule] = (config.rules[rule] as Linter.RuleEntry[])[0]; - } - } - if (dropRules.length === 0 && Object.keys(fixRules).length === 0) { - return config; - } - const result: Linter.Config = { ...config, rules: { ...config.rules, ...fixRules } }; - for (const rule of dropRules) { - delete result.rules![rule]; - } - return result; - }); -} - -/** - * Restrict FlatCompat config objects to a directory scope. - * - * - Base configs (no `files`) get `files: ['{scopeDir}/**\/*.{js,jsx,ts,tsx,json}']`. - * - Override configs with function matchers get the function wrapped to also - * require the file to be inside `scopeDir`. - * - Override configs with glob strings get prefixed with `scopeDir/`. - * - * Also strips rules from unloaded plugins (see `stripOrphanedPluginRules`). - */ -function scopeTo( - flatConfigs: Linter.Config[], - scopeDir: string, - extraIgnores?: string[], -): Linter.Config[] { - const absScope = path.resolve(import.meta.dirname, scopeDir) + '/'; - const scoped = flatConfigs.map((config): Linter.Config => { - const result: Linter.Config = { ...config }; - - if (!config.files) { - const base = scopeDir === '.' ? '**' : `${scopeDir}/**`; - result.files = [`${base}/*.{js,jsx,ts,tsx,json}`]; - } else { - result.files = (config.files as Array boolean)>).map( - (entry) => { - if (typeof entry === 'function') { - return (filePath: string) => filePath.startsWith(absScope) && entry(filePath); - } - return scopeDir === '.' ? entry : `${scopeDir}/${entry}`; - }, - ); - } - - if (extraIgnores && extraIgnores.length > 0) { - result.ignores = [...(config.ignores || []), ...extraIgnores]; - } - - return result; - }); - - return stripOrphanedPluginRules(scoped); -} - export default defineConfig([ globalIgnores([ '.puppeteer/**', @@ -154,8 +65,10 @@ export default defineConfig([ // ------------------------------------------------ // Scope: Root frontend (non-packages, non-i18n-scripts, non-e2e) // ------------------------------------------------ - ...scopeTo( - compat.config({ + { + files: ['**/*.{js,jsx,ts,tsx,json}'], + ignores: ['packages/**', 'i18n-scripts/**', 'e2e/**'], + extends: compat.config({ extends: [ 'eslint:recommended', 'plugin:import/errors', @@ -292,28 +205,23 @@ export default defineConfig([ RequestInit: 'readonly', }, }), - '.', - ['packages/**', 'i18n-scripts/**', 'e2e/**'], - ), + }, // ------------------------------------------------ // Scope: packages/ (react-typescript-prettier) // ------------------------------------------------ - ...scopeTo( - compat.extends('plugin:console/react-typescript-prettier'), - 'packages', - PACKAGES_EXCLUDE, - ), + { + files: ['packages/**/*.{js,jsx,ts,tsx,json}'], + ignores: PACKAGES_EXCLUDE, + extends: compat.extends('plugin:console/react-typescript-prettier'), + }, // ------------------------------------------------ // Scope: Cypress integration tests (overlay on packages config) // ------------------------------------------------ - ...compat.extends('plugin:cypress/recommended').map((c) => ({ - ...c, - files: c.files || CYPRESS_FILES, - })), { files: CYPRESS_FILES, + extends: compat.extends('plugin:cypress/recommended'), languageOptions: { globals: { ...globals.node, @@ -350,9 +258,10 @@ export default defineConfig([ // ------------------------------------------------ // Scope: SDK Node.js directories (node-typescript-prettier) // ------------------------------------------------ - ...SDK_NODE_DIRS.flatMap((dir) => - scopeTo(compat.extends('plugin:console/node-typescript-prettier'), dir), - ), + { + files: SDK_NODE_DIRS.map((dir) => `${dir}/**/*.{js,jsx,ts,tsx,json}`), + extends: compat.extends('plugin:console/node-typescript-prettier'), + }, { files: [ 'packages/console-plugin-sdk/src/codegen/**/*.{js,ts}', @@ -368,20 +277,23 @@ export default defineConfig([ // ------------------------------------------------ // Scope: eslint-plugin-console (base + node + json + prettier) // ------------------------------------------------ - ...scopeTo( - compat.extends( + { + files: ['packages/eslint-plugin-console/**/*.{js,jsx,ts,tsx,json}'], + extends: compat.extends( 'plugin:console/base', 'plugin:console/node', 'plugin:console/json', 'plugin:console/prettier', ), - 'packages/eslint-plugin-console', - ), + }, // ------------------------------------------------ // Scope: i18n-scripts (node-typescript-prettier) // ------------------------------------------------ - ...scopeTo(compat.extends('plugin:console/node-typescript-prettier'), 'i18n-scripts'), + { + files: ['i18n-scripts/**/*.{js,jsx,ts,tsx,json}'], + extends: compat.extends('plugin:console/node-typescript-prettier'), + }, { files: ['i18n-scripts/**/*.{js,ts}'], rules: { @@ -393,7 +305,10 @@ export default defineConfig([ // ------------------------------------------------ // Scope: e2e (Playwright) // ------------------------------------------------ - ...scopeTo(compat.extends('plugin:console/playwright'), 'e2e'), + { + files: ['e2e/**/*.{js,jsx,ts,tsx,json}'], + extends: compat.extends('plugin:console/playwright'), + }, { files: ['e2e/**/*.{js,ts}'], ignores: ['e2e/**/testData/**'], From cfacbc36509171cdee5e24c17f140b3d2706ad2e Mon Sep 17 00:00:00 2001 From: logonoff Date: Tue, 28 Jul 2026 22:11:30 -0400 Subject: [PATCH 4/7] CONSOLE-5439: Merge public/ and packages/ ESLint scopes into shared react-typescript-prettier preset Move root-frontend-specific rules (no-restricted-imports, no-restricted-syntax, react-hooks/exhaustive-deps, etc.) into the react-typescript-prettier preset in eslint-plugin-console so public/ and packages/ share a single config entry. A public/-only override disables ~45 airbnb/RTP rules not previously enforced there. These can be removed incrementally to tighten public/ linting over time. Other changes: - Add eslint-disable comments for pre-existing PF Font Awesome icon imports now caught by the no-restricted-imports rule in packages/ - Remove stale eslint-disable tsdoc/syntax from .js/.jsx files where the tsdoc plugin is no longer loaded - Scope tsdoc/syntax to public/**/*.{ts,tsx} only - Add scripts/ to globalIgnores Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend/eslint.config.ts | 248 ++++++++---------- frontend/get-local-plugins.js | 1 - .../console-shared/src/utils/icon-utils.ts | 1 + .../packages/eslint-plugin-console/index.js | 52 ++++ .../alertmanager/alertmanager-config.tsx | 2 +- .../alert-manager-receiver-forms.tsx | 2 +- frontend/public/components/namespace.jsx | 1 - frontend/public/components/row-filter.jsx | 1 - frontend/public/module/k8s/label-selector.js | 2 +- 9 files changed, 164 insertions(+), 146 deletions(-) diff --git a/frontend/eslint.config.ts b/frontend/eslint.config.ts index af74881d31c..08b195e0db0 100644 --- a/frontend/eslint.config.ts +++ b/frontend/eslint.config.ts @@ -1,9 +1,12 @@ -import path from 'path'; +import * as path from 'path'; import { defineConfig, globalIgnores } from 'eslint/config'; import { FlatCompat } from '@eslint/eslintrc'; -import js from '@eslint/js'; -import globals from 'globals'; -import tsParser from '@typescript-eslint/parser'; +import * as js from '@eslint/js'; +import * as globals from 'globals'; +// TODO: change moduleResolution to "bundler" +// @ts-expect-error types not resolvable under moduleResolution "node" +import * as tsParser from '@typescript-eslint/parser'; +import * as tsPlugin from '@typescript-eslint/eslint-plugin'; const compat = new FlatCompat({ baseDirectory: import.meta.dirname, @@ -32,7 +35,7 @@ const CYPRESS_INTEGRATION_DIRS = [ const CYPRESS_FILES = CYPRESS_INTEGRATION_DIRS.map((d) => `${d}/**/*.{js,jsx,ts,tsx}`); -export default defineConfig([ +const config = defineConfig([ globalIgnores([ '.puppeteer/**', '.yarn/**', @@ -54,6 +57,7 @@ export default defineConfig([ '.vscode/**', '**/.*', 'e2e/**/testData/**', + 'scripts/**', ]), { @@ -63,136 +67,13 @@ export default defineConfig([ }, // ------------------------------------------------ - // Scope: Root frontend (non-packages, non-i18n-scripts, non-e2e) + // Scope: public/ + packages/ (react-typescript-prettier) // ------------------------------------------------ { - files: ['**/*.{js,jsx,ts,tsx,json}'], - ignores: ['packages/**', 'i18n-scripts/**', 'e2e/**'], - extends: compat.config({ - extends: [ - 'eslint:recommended', - 'plugin:import/errors', - 'plugin:import/warnings', - 'plugin:react/recommended', - 'plugin:console/json', - 'plugin:console/prettier', - 'plugin:console/testing-library-tests', - ], - env: { - browser: true, - es6: true, - jest: true, - node: true, - }, - parser: '@typescript-eslint/parser', - parserOptions: { - project: './tsconfig.json', - ecmaFeatures: { jsx: true }, - ecmaVersion: 2018, - extraFileExtensions: ['.json'], - tsconfigRootDir: import.meta.dirname, - }, - plugins: ['react', 'react-hooks', '@typescript-eslint', 'eslint-plugin-tsdoc', 'no-barrel-files'], - rules: { - camelcase: 'error', - 'consistent-return': 'off', - 'consistent-this': ['warn', 'that'], - curly: ['error', 'all'], - 'default-case': ['error'], - 'dot-notation': ['error'], - 'no-multiple-empty-lines': ['error', { max: 2, maxEOF: 0 }], - eqeqeq: ['error', 'always', { null: 'ignore' }], - 'guard-for-in': 'error', - 'import/no-duplicates': ['error'], - 'max-nested-callbacks': ['warn', 4], - 'no-alert': 'error', - 'no-caller': 'error', - 'no-console': 'error', - 'no-constant-condition': 'error', - 'no-debugger': 'error', - 'no-else-return': ['error'], - 'no-irregular-whitespace': ['error'], - 'no-prototype-builtins': 'off', - 'no-unused-vars': 'off', - 'no-shadow': 'off', - '@typescript-eslint/no-shadow': 'error', - 'no-underscore-dangle': 'off', - '@typescript-eslint/no-unused-vars': [ - 'error', - { varsIgnorePattern: 'React', args: 'after-used', caughtErrors: 'none' }, - ], - '@typescript-eslint/no-use-before-define': 'error', - 'no-restricted-imports': [ - 'error', - { - paths: [ - { - name: 'lodash-es', - message: 'Use lodash instead. The bundler is configured to use lodash-es automatically.', - }, - { - name: 'react', - importNames: ['default', '*'], - message: 'Use named imports instead.', - }, - { - name: 'react', - importNames: ['act'], - message: "For consistency, import { act } from '@testing-library/react'", - }, - ], - patterns: [ - { - group: ['@patternfly/react-icons'], - importNamePattern: '^(?!Rh|createIcon)', - message: - 'Use RhMicron, RhUi, or RhStandard icon variants instead of Font Awesome icons.', - }, - ], - }, - ], - 'no-var': 'error', - 'object-shorthand': ['error', 'properties'], - 'prefer-const': ['error', { destructuring: 'all' }], - 'prefer-template': 'error', - radix: 'error', - 'react/react-in-jsx-scope': 'off', - 'react/jsx-fragments': 'error', - 'react/jsx-no-duplicate-props': 'error', - 'react/jsx-uses-react': 'error', - 'react/jsx-uses-vars': 'error', - 'react/no-string-refs': 'warn', - 'react/no-unknown-property': 'error', - 'react/prop-types': 'off', - 'react/self-closing-comp': ['error', { component: true, html: false }], - 'react-hooks/rules-of-hooks': 'error', - 'react-hooks/exhaustive-deps': 'warn', - 'react/display-name': 'off', - 'react/no-unescaped-entities': 'off', - 'require-atomic-updates': 'off', - 'tsdoc/syntax': 'warn', - 'import/no-named-as-default-member': 'off', - 'import/named': 'off', - 'no-unsafe-optional-chaining': 'off', - 'no-import-assign': 'off', - 'no-constructor-return': 'off', - 'prefer-regex-literals': 'off', - 'no-restricted-exports': 'off', - 'no-barrel-files/no-barrel-files': 'error', - 'no-restricted-syntax': [ - 'warn', - { - selector: "CallExpression[callee.name='useTranslation'][arguments.length=0]", - message: - "Pass the i18n namespace to useTranslation(). Example: useTranslation('public') instead of useTranslation().", - }, - ], - }, - settings: { - 'import/extensions': ['.js', '.jsx'], - 'import/resolver': { typescript: { extensions: ['.js', '.jsx', '.ts', '.tsx'] } }, - react: { version: 'detect' }, - }, + files: ['public/**/*.{js,jsx,ts,tsx,json}', 'packages/**/*.{js,jsx,ts,tsx,json}'], + ignores: PACKAGES_EXCLUDE, + extends: compat.extends('plugin:console/react-typescript-prettier'), + languageOptions: { globals: { process: 'readonly', React: true, @@ -204,16 +85,101 @@ export default defineConfig([ VoidFunction: 'readonly', RequestInit: 'readonly', }, + }, + }, + { + files: ['public/**/*.{ts,tsx}', 'packages/**/*.{ts,tsx}'], + ignores: PACKAGES_EXCLUDE, + rules: { + '@typescript-eslint/no-unused-vars': [ + 'error', + { + varsIgnorePattern: '^React$', + args: 'after-used', + ignoreRestSiblings: true, + caughtErrors: 'none', + }, + ], + }, + }, + // TSDoc linting for public/ TypeScript files + { + files: ['public/**/*.{ts,tsx}'], + extends: compat.config({ + plugins: ['eslint-plugin-tsdoc'], + rules: { 'tsdoc/syntax': 'warn' }, }), }, + // Use TS parser and plugin for .js/.jsx in public/ (matches old config behavior) + { + files: ['public/**/*.{js,jsx}'], + plugins: { + '@typescript-eslint': tsPlugin, + }, + languageOptions: { + parser: tsParser, + }, + }, - // ------------------------------------------------ - // Scope: packages/ (react-typescript-prettier) - // ------------------------------------------------ + // TODO: Rules disabled here were never enforced in public/. Remove entries + // incrementally to align public/ with the stricter packages/ linting baseline. { - files: ['packages/**/*.{js,jsx,ts,tsx,json}'], - ignores: PACKAGES_EXCLUDE, - extends: compat.extends('plugin:console/react-typescript-prettier'), + files: ['public/**/*.{js,jsx,ts,tsx}'], + rules: { + '@typescript-eslint/no-use-before-define': 'off', + 'no-unused-expressions': 'off', + 'no-unused-vars': 'off', + 'promise/catch-or-return': 'off', + 'promise/no-nesting': 'off', + '@typescript-eslint/naming-convention': 'off', + 'react/prop-types': 'off', + 'react/no-array-index-key': 'off', + 'no-param-reassign': 'off', + 'no-restricted-globals': 'off', + 'react/jsx-pascal-case': 'off', + 'react/jsx-boolean-value': 'off', + 'prefer-destructuring': 'off', + 'no-useless-computed-key': 'off', + 'spaced-comment': 'off', + 'one-var': 'off', + 'lines-between-class-members': 'off', + '@typescript-eslint/no-require-imports': 'off', + 'no-await-in-loop': 'off', + 'no-template-curly-in-string': 'off', + 'no-bitwise': 'off', + 'no-multi-assign': 'off', + 'no-throw-literal': 'off', + 'no-lonely-if': 'off', + 'array-callback-return': 'off', + 'import/no-named-as-default': 'off', + 'jsx-a11y/no-noninteractive-tabindex': 'off', + 'jsx-a11y/no-autofocus': 'off', + 'jsx-a11y/control-has-associated-label': 'off', + 'react/jsx-no-bind': 'off', + 'react/button-has-type': 'off', + 'react/prefer-stateless-function': 'off', + 'import/no-useless-path-segments': 'off', + 'no-unneeded-ternary': 'off', + 'no-useless-return': 'off', + 'prefer-spread': 'off', + 'prefer-exponentiation-operator': 'off', + 'operator-assignment': 'off', + 'no-restricted-properties': 'off', + 'no-else-return': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', + '@typescript-eslint/array-type': 'off', + '@typescript-eslint/prefer-function-type': 'off', + '@typescript-eslint/ban-ts-comment': 'off', + '@typescript-eslint/consistent-type-imports': 'off', + 'import/order': 'off', + 'import/first': 'off', + 'import/newline-after-import': 'off', + 'import/no-unresolved': 'off', + 'import/export': 'off', + 'sort-class-members/sort-class-members': 'off', + 'react/no-unused-class-component-methods': 'off', + 'no-use-before-define': 'off', + }, }, // ------------------------------------------------ @@ -229,7 +195,7 @@ export default defineConfig([ }, rules: { 'no-console': 'off', - 'no-namespace': 'off', + '@typescript-eslint/no-namespace': 'off', 'no-redeclare': 'off', 'promise/catch-or-return': 'off', 'promise/no-nesting': 'off', @@ -344,3 +310,5 @@ export default defineConfig([ }, }, ]); + +export default config; diff --git a/frontend/get-local-plugins.js b/frontend/get-local-plugins.js index 239e359e064..51c016182d7 100644 --- a/frontend/get-local-plugins.js +++ b/frontend/get-local-plugins.js @@ -1,4 +1,3 @@ -/* eslint-disable tsdoc/syntax */ // This file is written in JavaScript, so we use JSDoc here. TSDoc rules don't apply // @ts-check const { getLocalPluginsModuleData } = require('@console/plugin-sdk/src/codegen/local-plugins'); diff --git a/frontend/packages/console-shared/src/utils/icon-utils.ts b/frontend/packages/console-shared/src/utils/icon-utils.ts index de0b713925d..d9e2792d384 100644 --- a/frontend/packages/console-shared/src/utils/icon-utils.ts +++ b/frontend/packages/console-shared/src/utils/icon-utils.ts @@ -1,3 +1,4 @@ +// eslint-disable-next-line no-restricted-imports import type { IconData } from '@patternfly/react-icons/dist/esm/createIcon'; const ICON_OPERATOR = 'icon-operator'; diff --git a/frontend/packages/eslint-plugin-console/index.js b/frontend/packages/eslint-plugin-console/index.js index 251e97a5580..91500476bf6 100644 --- a/frontend/packages/eslint-plugin-console/index.js +++ b/frontend/packages/eslint-plugin-console/index.js @@ -39,9 +39,61 @@ module.exports = { 'plugin:console/testing-library-tests', 'plugin:console/prettier', ], + rules: { // TODO fix for monorepo support 'import/no-extraneous-dependencies': 'off', + + 'consistent-return': 'off', + 'consistent-this': ['warn', 'that'], + 'max-nested-callbacks': ['warn', 4], + 'no-alert': 'error', + 'no-constant-condition': 'error', + 'no-underscore-dangle': 'off', + 'no-restricted-imports': [ + 'error', + { + paths: [ + { + name: 'lodash-es', + message: + 'Use lodash instead. The bundler is configured to use lodash-es automatically.', + }, + { + name: 'react', + importNames: ['default', '*'], + message: 'Use named imports instead.', + }, + { + name: 'react', + importNames: ['act'], + message: "For consistency, import { act } from '@testing-library/react'", + }, + ], + patterns: [ + { + group: ['@patternfly/react-icons'], + importNamePattern: '^(?!Rh|createIcon)', + message: + 'Use RhMicron, RhUi, or RhStandard icon variants instead of Font Awesome icons.', + }, + ], + }, + ], + 'object-shorthand': ['error', 'properties'], + 'prefer-const': ['error', { destructuring: 'all' }], + 'react/no-string-refs': 'warn', + 'react/self-closing-comp': ['error', { component: true, html: false }], + 'react-hooks/exhaustive-deps': 'warn', + 'react/no-unescaped-entities': 'off', + 'no-restricted-syntax': [ + 'warn', + { + selector: "CallExpression[callee.name='useTranslation'][arguments.length=0]", + message: + "Pass the i18n namespace to useTranslation(). Example: useTranslation('public') instead of useTranslation().", + }, + ], }, }, diff --git a/frontend/public/components/monitoring/alertmanager/alertmanager-config.tsx b/frontend/public/components/monitoring/alertmanager/alertmanager-config.tsx index 818e2c7927d..5df3ccdf759 100644 --- a/frontend/public/components/monitoring/alertmanager/alertmanager-config.tsx +++ b/frontend/public/components/monitoring/alertmanager/alertmanager-config.tsx @@ -1,4 +1,4 @@ -/* eslint-disable camelcase, tsdoc/syntax */ +/* eslint-disable camelcase */ import type { FC } from 'react'; import { useMemo, memo, Suspense } from 'react'; diff --git a/frontend/public/components/monitoring/receiver-forms/alert-manager-receiver-forms.tsx b/frontend/public/components/monitoring/receiver-forms/alert-manager-receiver-forms.tsx index d2cce081453..8e86e5c0008 100644 --- a/frontend/public/components/monitoring/receiver-forms/alert-manager-receiver-forms.tsx +++ b/frontend/public/components/monitoring/receiver-forms/alert-manager-receiver-forms.tsx @@ -1,4 +1,4 @@ -/* eslint-disable camelcase, tsdoc/syntax */ +/* eslint-disable camelcase */ import * as _ from 'lodash'; import { FC, memo, useEffect, useReducer, useState, Ref } from 'react'; import { DocumentTitle } from '@console/shared/src/components/document-title/DocumentTitle'; diff --git a/frontend/public/components/namespace.jsx b/frontend/public/components/namespace.jsx index 04ad14bf029..ee69a4fbd0d 100644 --- a/frontend/public/components/namespace.jsx +++ b/frontend/public/components/namespace.jsx @@ -1,4 +1,3 @@ -/* eslint-disable tsdoc/syntax */ import { Suspense, useEffect, useMemo, useState, useCallback } from 'react'; import * as _ from 'lodash'; import { DocumentTitle } from '@console/shared/src/components/document-title/DocumentTitle'; diff --git a/frontend/public/components/row-filter.jsx b/frontend/public/components/row-filter.jsx index ee3dd001d10..f707dfe41f1 100644 --- a/frontend/public/components/row-filter.jsx +++ b/frontend/public/components/row-filter.jsx @@ -1,4 +1,3 @@ -/* eslint-disable tsdoc/syntax */ import { Button, Divider, Flex, FlexItem } from '@patternfly/react-core'; import { useTranslation } from 'react-i18next'; diff --git a/frontend/public/module/k8s/label-selector.js b/frontend/public/module/k8s/label-selector.js index 05110a69048..846b04fe68f 100644 --- a/frontend/public/module/k8s/label-selector.js +++ b/frontend/public/module/k8s/label-selector.js @@ -1,4 +1,4 @@ -/* eslint-disable guard-for-in, default-case, no-case-declarations, tsdoc/syntax */ +/* eslint-disable guard-for-in, default-case, no-case-declarations */ /** * Converted from Angular based "kubernetes-label-selector" From 32390d4fd7cdc7fdf9150876870a282b7fef350b Mon Sep 17 00:00:00 2001 From: logonoff Date: Wed, 29 Jul 2026 09:39:05 -0400 Subject: [PATCH 5/7] CONSOLE-5439: Move react-typescript-prettier preset rules into constituent configs Distribute rules from the react-typescript-prettier preset into the configs they logically belong to, so they're inherited through the extends chain rather than duplicated at the preset level. - airbnb-base-overrides: curly, consistent-return, consistent-this, max-nested-callbacks, no-alert, no-constant-condition, no-underscore-dangle, object-shorthand, prefer-const, no-restricted-imports, no-restricted-syntax - react: react/no-string-refs, react/self-closing-comp, react/no-unescaped-entities - react-hooks: exhaustive-deps downgraded from error to warn Also restores tsdoc/syntax to eslint-disable comments in two .tsx files where removing it exposed new warnings. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../packages/eslint-plugin-console/index.js | 51 ------------------- .../lib/config/rules/airbnb-base-overrides.js | 27 ++++++++++ .../lib/config/rules/react.js | 50 ++++++++++++++++++ .../alertmanager/alertmanager-config.tsx | 2 +- .../alert-manager-receiver-forms.tsx | 2 +- 5 files changed, 79 insertions(+), 53 deletions(-) diff --git a/frontend/packages/eslint-plugin-console/index.js b/frontend/packages/eslint-plugin-console/index.js index 91500476bf6..8c2789a732d 100644 --- a/frontend/packages/eslint-plugin-console/index.js +++ b/frontend/packages/eslint-plugin-console/index.js @@ -43,57 +43,6 @@ module.exports = { rules: { // TODO fix for monorepo support 'import/no-extraneous-dependencies': 'off', - - 'consistent-return': 'off', - 'consistent-this': ['warn', 'that'], - 'max-nested-callbacks': ['warn', 4], - 'no-alert': 'error', - 'no-constant-condition': 'error', - 'no-underscore-dangle': 'off', - 'no-restricted-imports': [ - 'error', - { - paths: [ - { - name: 'lodash-es', - message: - 'Use lodash instead. The bundler is configured to use lodash-es automatically.', - }, - { - name: 'react', - importNames: ['default', '*'], - message: 'Use named imports instead.', - }, - { - name: 'react', - importNames: ['act'], - message: "For consistency, import { act } from '@testing-library/react'", - }, - ], - patterns: [ - { - group: ['@patternfly/react-icons'], - importNamePattern: '^(?!Rh|createIcon)', - message: - 'Use RhMicron, RhUi, or RhStandard icon variants instead of Font Awesome icons.', - }, - ], - }, - ], - 'object-shorthand': ['error', 'properties'], - 'prefer-const': ['error', { destructuring: 'all' }], - 'react/no-string-refs': 'warn', - 'react/self-closing-comp': ['error', { component: true, html: false }], - 'react-hooks/exhaustive-deps': 'warn', - 'react/no-unescaped-entities': 'off', - 'no-restricted-syntax': [ - 'warn', - { - selector: "CallExpression[callee.name='useTranslation'][arguments.length=0]", - message: - "Pass the i18n namespace to useTranslation(). Example: useTranslation('public') instead of useTranslation().", - }, - ], }, }, diff --git a/frontend/packages/eslint-plugin-console/lib/config/rules/airbnb-base-overrides.js b/frontend/packages/eslint-plugin-console/lib/config/rules/airbnb-base-overrides.js index 252259a3359..d61b559724b 100644 --- a/frontend/packages/eslint-plugin-console/lib/config/rules/airbnb-base-overrides.js +++ b/frontend/packages/eslint-plugin-console/lib/config/rules/airbnb-base-overrides.js @@ -28,6 +28,24 @@ module.exports = { // When there is only a single export from a module, prefer using default export over named export. 'import/prefer-default-export': 'off', + // Require braces around all control flow bodies + curly: ['error', 'all'], + + // Require consistent return statements + 'consistent-return': 'off', + + // Require consistent use of this alias + 'consistent-this': ['warn', 'that'], + + // Enforce a maximum depth that callbacks can be nested + 'max-nested-callbacks': ['warn', 4], + + // Disallow use of alert + 'no-alert': 'error', + + // Disallow use of constant expressions in conditions + 'no-constant-condition': 'error', + // Disallow console statements 'no-console': 'error', @@ -69,6 +87,9 @@ module.exports = { // enforce a maximum number of classes per file 'max-classes-per-file': 'off', + // Disallow dangling underscores in identifiers + 'no-underscore-dangle': 'off', + // Disallow use of Object.prototypes builtins directly 'no-prototype-builtins': 'off', @@ -78,6 +99,12 @@ module.exports = { // Disallow using Object.assign with an object literal as the first argument and prefer the use of object spread instead. 'prefer-object-spread': 'off', + // Require object shorthand for properties only + 'object-shorthand': ['error', 'properties'], + + // Require const for variables that are never reassigned after declared + 'prefer-const': ['error', { destructuring: 'all' }], + // Disallow assignments that can lead to race conditions due to usage of `await` or `yield` 'require-atomic-updates': 'off', diff --git a/frontend/packages/eslint-plugin-console/lib/config/rules/react.js b/frontend/packages/eslint-plugin-console/lib/config/rules/react.js index 7039eca5c1b..c68b2446d36 100644 --- a/frontend/packages/eslint-plugin-console/lib/config/rules/react.js +++ b/frontend/packages/eslint-plugin-console/lib/config/rules/react.js @@ -1,4 +1,45 @@ module.exports = { + // Require i18n namespace argument for useTranslation() + 'no-restricted-syntax': [ + 'warn', + { + selector: "CallExpression[callee.name='useTranslation'][arguments.length=0]", + message: + "Pass the i18n namespace to useTranslation(). Example: useTranslation('public') instead of useTranslation().", + }, + ], + + // Restrict certain imports (lodash-es, React default/act, Font Awesome PF icons) + 'no-restricted-imports': [ + 'error', + { + paths: [ + { + name: 'lodash-es', + message: 'Use lodash instead. The bundler is configured to use lodash-es automatically.', + }, + { + name: 'react', + importNames: ['default', '*'], + message: 'Use named imports instead.', + }, + { + name: 'react', + importNames: ['act'], + message: "For consistency, import { act } from '@testing-library/react'", + }, + ], + patterns: [ + { + group: ['@patternfly/react-icons'], + importNamePattern: + '^(?!Rh|createIcon|GitAltIcon|TerminalIcon|GithubIcon|GitlabIcon|BitbucketIcon)', + message: 'Use RhMicron, RhUi, or RhStandard icon variants instead of Font Awesome icons.', + }, + ], + }, + ], + // Forbid foreign propTypes; forbids using another component's prop types unless they are explicitly imported/exported 'react/forbid-foreign-prop-types': 'error', @@ -51,6 +92,15 @@ module.exports = { /* ---- Rules new/changed in eslint-config-airbnb v19 / eslint-plugin-react v7.37 ---- */ + // Disallow usage of string refs + 'react/no-string-refs': 'warn', + + // Enforce self-closing for components but not HTML elements + 'react/self-closing-comp': ['error', { component: true, html: false }], + + // Disallow unescaped entities in JSX + 'react/no-unescaped-entities': 'off', + // Enforce function component definition style (new in airbnb v19) 'react/function-component-definition': 'off', diff --git a/frontend/public/components/monitoring/alertmanager/alertmanager-config.tsx b/frontend/public/components/monitoring/alertmanager/alertmanager-config.tsx index 5df3ccdf759..818e2c7927d 100644 --- a/frontend/public/components/monitoring/alertmanager/alertmanager-config.tsx +++ b/frontend/public/components/monitoring/alertmanager/alertmanager-config.tsx @@ -1,4 +1,4 @@ -/* eslint-disable camelcase */ +/* eslint-disable camelcase, tsdoc/syntax */ import type { FC } from 'react'; import { useMemo, memo, Suspense } from 'react'; diff --git a/frontend/public/components/monitoring/receiver-forms/alert-manager-receiver-forms.tsx b/frontend/public/components/monitoring/receiver-forms/alert-manager-receiver-forms.tsx index 8e86e5c0008..d2cce081453 100644 --- a/frontend/public/components/monitoring/receiver-forms/alert-manager-receiver-forms.tsx +++ b/frontend/public/components/monitoring/receiver-forms/alert-manager-receiver-forms.tsx @@ -1,4 +1,4 @@ -/* eslint-disable camelcase */ +/* eslint-disable camelcase, tsdoc/syntax */ import * as _ from 'lodash'; import { FC, memo, useEffect, useReducer, useState, Ref } from 'react'; import { DocumentTitle } from '@console/shared/src/components/document-title/DocumentTitle'; From 840e796499049cb3a02ef0e9fb16c744510aa762 Mon Sep 17 00:00:00 2001 From: logonoff Date: Wed, 29 Jul 2026 09:52:04 -0400 Subject: [PATCH 6/7] CONSOLE-5439: Merge Cypress config blocks and suppress max-nested-callbacks Consolidate the two Cypress integration test config objects into one using shared files/rules/settings. Disable max-nested-callbacks for Cypress tests (deeply nested cy.then() chains are idiomatic) and suppress the rule in a unit test with pre-existing deep nesting. Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend/eslint.config.ts | 9 +-------- .../import/__tests__/deployImage-submit-utils.spec.ts | 1 + 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/frontend/eslint.config.ts b/frontend/eslint.config.ts index 08b195e0db0..7c32c29e6a0 100644 --- a/frontend/eslint.config.ts +++ b/frontend/eslint.config.ts @@ -200,14 +200,7 @@ const config = defineConfig([ 'promise/catch-or-return': 'off', 'promise/no-nesting': 'off', 'cypress/unsafe-to-chain-command': 'off', - }, - }, - { - files: [ - 'packages/dev-console/integration-tests/**/*.{js,jsx,ts,tsx}', - 'packages/helm-plugin/integration-tests/**/*.{js,jsx,ts,tsx}', - ], - rules: { + 'max-nested-callbacks': 'off', '@typescript-eslint/ban-ts-comment': 'off', 'cypress/no-unnecessary-waiting': 'off', }, diff --git a/frontend/packages/dev-console/src/components/import/__tests__/deployImage-submit-utils.spec.ts b/frontend/packages/dev-console/src/components/import/__tests__/deployImage-submit-utils.spec.ts index be77de15842..405c8170b30 100644 --- a/frontend/packages/dev-console/src/components/import/__tests__/deployImage-submit-utils.spec.ts +++ b/frontend/packages/dev-console/src/components/import/__tests__/deployImage-submit-utils.spec.ts @@ -1,3 +1,4 @@ +/* eslint-disable max-nested-callbacks */ import * as _ from 'lodash'; import * as k8sResourceModule from '@console/dynamic-plugin-sdk/src/utils/k8s/k8s-resource'; import { From e33ac7d8db2953dee435926a1d93a05babea97c9 Mon Sep 17 00:00:00 2001 From: logonoff Date: Wed, 29 Jul 2026 09:55:16 -0400 Subject: [PATCH 7/7] CONSOLE-5439: Move e2e rules into plugin:console/playwright and merge config blocks Move Playwright-specific rule overrides (no-console, no-empty-pattern, no-restricted-syntax waitFor warning, playwright/* rules) into the plugin:console/playwright config. Merge the two e2e config blocks in eslint.config.ts into one. Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend/eslint.config.ts | 22 +------------------ .../lib/config/playwright.js | 17 ++++++++++++++ 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/frontend/eslint.config.ts b/frontend/eslint.config.ts index 7c32c29e6a0..997be95d4d1 100644 --- a/frontend/eslint.config.ts +++ b/frontend/eslint.config.ts @@ -266,11 +266,8 @@ const config = defineConfig([ // ------------------------------------------------ { files: ['e2e/**/*.{js,jsx,ts,tsx,json}'], - extends: compat.extends('plugin:console/playwright'), - }, - { - files: ['e2e/**/*.{js,ts}'], ignores: ['e2e/**/testData/**'], + extends: compat.extends('plugin:console/playwright'), languageOptions: { parser: tsParser, parserOptions: { @@ -278,23 +275,6 @@ const config = defineConfig([ tsconfigRootDir: path.join(import.meta.dirname, 'e2e'), }, }, - rules: { - 'no-console': 'off', - 'no-empty-pattern': 'off', - 'no-restricted-syntax': [ - 'warn', - { - selector: 'CallExpression[callee.property.name="waitFor"]', - message: - 'Playwright actions (click, fill, check, clear) auto-wait for actionability. ' + - 'Do not call waitFor() before an action on the same locator. ' + - 'If this waitFor() is intentional (waiting for state without a subsequent action), ' + - 'add // eslint-disable-next-line no-restricted-syntax', - }, - ], - 'playwright/no-conditional-in-test': 'off', - 'playwright/no-skipped-test': ['warn', { allowConditional: true }], - }, }, { files: ['e2e/setup/**/*.ts'], diff --git a/frontend/packages/eslint-plugin-console/lib/config/playwright.js b/frontend/packages/eslint-plugin-console/lib/config/playwright.js index 7b237448a64..5225eec1105 100644 --- a/frontend/packages/eslint-plugin-console/lib/config/playwright.js +++ b/frontend/packages/eslint-plugin-console/lib/config/playwright.js @@ -1,4 +1,21 @@ module.exports = { plugins: ['playwright'], extends: ['plugin:playwright/recommended'], + rules: { + 'no-console': 'off', + 'no-empty-pattern': 'off', + 'no-restricted-syntax': [ + 'warn', + { + selector: 'CallExpression[callee.property.name="waitFor"]', + message: + 'Playwright actions (click, fill, check, clear) auto-wait for actionability. ' + + 'Do not call waitFor() before an action on the same locator. ' + + 'If this waitFor() is intentional (waiting for state without a subsequent action), ' + + 'add // eslint-disable-next-line no-restricted-syntax', + }, + ], + 'playwright/no-conditional-in-test': 'off', + 'playwright/no-skipped-test': ['warn', { allowConditional: true }], + }, };