From 80a2672a817ce40b3111269d6008e5fa8d010f01 Mon Sep 17 00:00:00 2001 From: mainframev Date: Fri, 16 Jan 2026 10:27:39 +0100 Subject: [PATCH 1/8] chore(eslint-plugin): remove legacy configs --- packages/eslint-plugin/.eslintrc.json | 20 - packages/eslint-plugin/package.json | 1 - .../eslint-plugin/src/configs/base-legacy.js | 26 - packages/eslint-plugin/src/configs/base.js | 54 -- .../{flat-configs => configs}/base/index.js | 0 .../{flat-configs => configs}/base/legacy.js | 0 packages/eslint-plugin/src/configs/core.js | 536 +++++++++--------- packages/eslint-plugin/src/configs/imports.js | 12 +- .../eslint-plugin/src/configs/node-legacy.js | 9 - packages/eslint-plugin/src/configs/node.js | 11 - .../{flat-configs => configs}/node/index.js | 0 .../{flat-configs => configs}/node/legacy.js | 0 .../eslint-plugin/src/configs/react-config.js | 151 ----- .../eslint-plugin/src/configs/react-legacy.js | 49 -- packages/eslint-plugin/src/configs/react.js | 101 ---- .../{flat-configs => configs}/react/config.js | 0 .../{flat-configs => configs}/react/index.js | 2 +- .../{flat-configs => configs}/react/legacy.js | 0 .../eslint-plugin/src/flat-configs/core.js | 357 ------------ .../eslint-plugin/src/flat-configs/imports.js | 36 -- packages/eslint-plugin/src/index.js | 28 +- packages/eslint-plugin/src/internal-flat.js | 45 -- packages/eslint-plugin/src/internal.js | 9 +- 23 files changed, 291 insertions(+), 1156 deletions(-) delete mode 100644 packages/eslint-plugin/.eslintrc.json delete mode 100644 packages/eslint-plugin/src/configs/base-legacy.js delete mode 100644 packages/eslint-plugin/src/configs/base.js rename packages/eslint-plugin/src/{flat-configs => configs}/base/index.js (100%) rename packages/eslint-plugin/src/{flat-configs => configs}/base/legacy.js (100%) delete mode 100644 packages/eslint-plugin/src/configs/node-legacy.js delete mode 100644 packages/eslint-plugin/src/configs/node.js rename packages/eslint-plugin/src/{flat-configs => configs}/node/index.js (100%) rename packages/eslint-plugin/src/{flat-configs => configs}/node/legacy.js (100%) delete mode 100644 packages/eslint-plugin/src/configs/react-config.js delete mode 100644 packages/eslint-plugin/src/configs/react-legacy.js delete mode 100644 packages/eslint-plugin/src/configs/react.js rename packages/eslint-plugin/src/{flat-configs => configs}/react/config.js (100%) rename packages/eslint-plugin/src/{flat-configs => configs}/react/index.js (98%) rename packages/eslint-plugin/src/{flat-configs => configs}/react/legacy.js (100%) delete mode 100644 packages/eslint-plugin/src/flat-configs/core.js delete mode 100644 packages/eslint-plugin/src/flat-configs/imports.js delete mode 100644 packages/eslint-plugin/src/internal-flat.js diff --git a/packages/eslint-plugin/.eslintrc.json b/packages/eslint-plugin/.eslintrc.json deleted file mode 100644 index 4dec40ba8c32be..00000000000000 --- a/packages/eslint-plugin/.eslintrc.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - // eslint config for the package itself - "extends": ["plugin:@fluentui/eslint-plugin/node"], - "root": true, - "overrides": [ - { - "files": ["src/rules/*.js"], - "rules": { - // too many false positives on node types - "@typescript-eslint/naming-convention": "off" - } - }, - { - "files": ["src/rules/**/fixtures/**/*.{js,ts}", "src/**/*.{test,spec}.{js,ts}"], - "rules": { - "import/no-extraneous-dependencies": "off" - } - } - ] -} diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index a89927ccb6ec54..d06be22e35cbdd 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -16,7 +16,6 @@ "@typescript-eslint/type-utils": "^8.46.2", "@typescript-eslint/utils": "^8.46.2", "@nx/eslint-plugin": "21.6.10", - "eslint-config-airbnb": "^18.2.1", "eslint-config-prettier": "^10.1.8", "eslint-plugin-react-compiler": "0.0.0-experimental-a97cca1-20240529", "eslint-config-airbnb-extended": "2.1.2", diff --git a/packages/eslint-plugin/src/configs/base-legacy.js b/packages/eslint-plugin/src/configs/base-legacy.js deleted file mode 100644 index e173928a48006c..00000000000000 --- a/packages/eslint-plugin/src/configs/base-legacy.js +++ /dev/null @@ -1,26 +0,0 @@ -// @ts-check - -const path = require('path'); - -const { getNamingConventionRule } = require('../utils/configHelpers'); - -/** @type {import("eslint").Linter.LegacyConfig} */ -module.exports = { - extends: [path.join(__dirname, 'core')], - rules: { - /** - * `@typescript-eslint`plugin eslint rules - * @see https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin - */ - ...getNamingConventionRule({ prefixInterface: true }), - }, - overrides: [ - { - files: '**/src/index.{ts,tsx,js}', - rules: { - // TODO: propagate to `error` once all packages barrel files have been fixed - '@rnx-kit/no-export-all': ['warn', { expand: 'all' }], - }, - }, - ], -}; diff --git a/packages/eslint-plugin/src/configs/base.js b/packages/eslint-plugin/src/configs/base.js deleted file mode 100644 index 87a81faebc2cd2..00000000000000 --- a/packages/eslint-plugin/src/configs/base.js +++ /dev/null @@ -1,54 +0,0 @@ -// @ts-check - -const path = require('path'); - -const { getNamingConventionRule, testFiles, storyFiles } = require('../utils/configHelpers'); - -/** @type {import("eslint").Linter.LegacyConfig} */ -module.exports = { - extends: [path.join(__dirname, 'core')], - rules: { - /** - * `@typescript-eslint`plugin eslint rules - * @see https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin - */ - ...getNamingConventionRule(), - '@fluentui/max-len': 'off', - // @typescript-eslint rules - '@typescript-eslint/triple-slash-reference': ['error', { lib: 'always', path: 'never', types: 'never' }], - }, - overrides: [ - { - files: '**/src/**/*.{ts,tsx,js}', - rules: { - '@rnx-kit/no-export-all': ['error', { expand: 'all' }], - }, - }, - { - files: '**/src/**/*.{ts,tsx,js}', - excludedFiles: [...testFiles, ...storyFiles], - extends: ['plugin:compat/recommended'], - plugins: ['compat'], - settings: { - /** - * Browser matrix support - * @see https://react.fluentui.dev/?path=/docs/concepts-developer-browser-support-matrix--docs#partial-browser-support-matrix - **/ - targets: [ - // Desktop browsers - 'edge >= 79', - 'firefox >= 69', - 'chrome >= 79', - 'safari >= 13.1', - 'opera >= 64', - 'not ie <= 11', - // Mobile browsers - 'ios_saf >= 13.4', - 'android >= 79', - 'samsung >= 14', - 'not op_mini all', - ], - }, - }, - ], -}; diff --git a/packages/eslint-plugin/src/flat-configs/base/index.js b/packages/eslint-plugin/src/configs/base/index.js similarity index 100% rename from packages/eslint-plugin/src/flat-configs/base/index.js rename to packages/eslint-plugin/src/configs/base/index.js diff --git a/packages/eslint-plugin/src/flat-configs/base/legacy.js b/packages/eslint-plugin/src/configs/base/legacy.js similarity index 100% rename from packages/eslint-plugin/src/flat-configs/base/legacy.js rename to packages/eslint-plugin/src/configs/base/legacy.js diff --git a/packages/eslint-plugin/src/configs/core.js b/packages/eslint-plugin/src/configs/core.js index aaa07a3ee58edf..c728acdf03783c 100644 --- a/packages/eslint-plugin/src/configs/core.js +++ b/packages/eslint-plugin/src/configs/core.js @@ -1,237 +1,190 @@ // @ts-check +const tseslint = require('typescript-eslint'); +const globals = require('globals'); const configHelpers = require('../utils/configHelpers'); +const importPlugin = require('eslint-plugin-import'); +const rnxPlugin = require('@rnx-kit/eslint-plugin'); +const jestPlugin = require('eslint-plugin-jest'); +const jsDocPlugin = require('eslint-plugin-jsdoc'); +const prettierConfig = require('eslint-config-prettier/flat'); const { __internal } = require('../internal'); +const { globalIgnores } = require('eslint/config'); +const airbnbConfig = require('eslint-config-airbnb-extended/legacy'); +const rules = require('../rules'); +const { defineConfig } = require('eslint/config'); -/** @type {import("eslint").Linter.LegacyConfig} */ -const config = { - root: true, - extends: [ - // Provides both rules and some parser options and other settings - 'airbnb/base', - // add typescript support for import plugin - https://github.com/import-js/eslint-plugin-import/blob/main/config/typescript.js - 'plugin:import/typescript', - // Extended configs are applied in order, so these configs that turn other rules off should come last - 'prettier', +const IGNORES = [ + '**/coverage', + '**/dist', + '**/dist-storybook', + '**/etc', + '**/lib', + '**/lib-amd', + '**/lib-commonjs', + '**/temp', + '**/bundle-size', + '**/__snapshots__', + '**/*.scss.ts', +]; + +/** @type {import('eslint').Linter.RulesRecord} */ +const coreRules = { + curly: ['error', 'all'], + 'dot-notation': 'error', + eqeqeq: ['error', 'always'], + 'guard-for-in': 'error', + 'no-alert': 'error', + 'no-bitwise': 'error', + 'no-caller': 'error', + 'no-console': 'error', + 'no-constant-condition': 'error', + 'no-debugger': 'error', + 'no-duplicate-case': 'error', + 'no-empty': 'error', + 'no-eval': 'error', + 'no-new-wrappers': 'error', + 'no-restricted-globals': [ + 'error', + ...['blur', 'close', 'focus', 'length', 'name', 'parent', 'self', 'stop'].map(name => ({ + name, + message: `"${name}" refers to a DOM global. Did you mean to reference a local value instead?`, + })), ], - parser: '@typescript-eslint/parser', - plugins: ['import', '@fluentui', '@rnx-kit', '@typescript-eslint', 'jest', 'jsdoc', ...__internal.plugins], - settings: { - 'import/resolver': { - // @see https://github.com/alexgorbatchev/eslint-import-resolver-typescript#configuration - typescript: { - alwaysTryTypes: true, - project: './tsconfig.json', - }, - }, - jsdoc: { - ignoreInternal: true, - tagNamePreference: { - // Allow any of @default, @defaultvalue, @defaultValue until we settle on a preferred one - default: 'default', - defaultvalue: 'defaultvalue', - defaultValue: 'defaultValue', - // Allow either @return or @returns until we settle on a preferred one - return: 'return', - returns: 'returns', - }, + 'no-restricted-properties': [ + 'error', + { object: 'describe', property: 'only', message: 'describe.only should only be used during test development' }, + { object: 'it', property: 'only', message: 'it.only should only be used during test development' }, + { + object: 'React', + property: 'useLayoutEffect', + message: '`useLayoutEffect` causes a warning in SSR. Use `useIsomorphicLayoutEffect`', }, - }, - env: { - browser: true, - 'jest/globals': true, - }, - // We have to disable this when running lint-staged, or it will incorrectly flag eslint-disable - // directives for rules which are disabled only in that context. - reportUnusedDisableDirectives: !configHelpers.isLintStaged, - // matched relative to cwd - ignorePatterns: [ - 'coverage', - 'dist', - 'dist-storybook', - 'etc', - 'lib', - 'lib-amd', - 'lib-commonjs', - 'node_modules', - 'temp', - 'bundle-size', - '**/__snapshots__', - '**/*.scss.ts', ], - rules: { - /** - * core eslint rules - * @see https://eslint.org/docs/rules - */ - curly: ['error', 'all'], - 'dot-notation': 'error', - eqeqeq: ['error', 'always'], - 'guard-for-in': 'error', - 'no-alert': 'error', - 'no-bitwise': 'error', - 'no-caller': 'error', - 'no-console': 'error', - 'no-constant-condition': 'error', - 'no-debugger': 'error', - 'no-duplicate-case': 'error', - 'no-empty': 'error', - 'no-eval': 'error', - 'no-new-wrappers': 'error', - 'no-restricted-globals': [ - 'error', - ...['blur', 'close', 'focus', 'length', 'name', 'parent', 'self', 'stop'].map(name => ({ - name, - message: `"${name}" refers to a DOM global. Did you mean to reference a local value instead?`, - })), - ], - 'no-restricted-properties': [ - 'error', - { object: 'describe', property: 'only', message: 'describe.only should only be used during test development' }, - { object: 'it', property: 'only', message: 'it.only should only be used during test development' }, - { - object: 'React', - property: 'useLayoutEffect', - message: '`useLayoutEffect` causes a warning in SSR. Use `useIsomorphicLayoutEffect`', - }, - ], - 'no-shadow': ['error', { hoist: 'all' }], - 'no-var': 'error', - 'prefer-const': 'error', - 'prefer-arrow-callback': 'error', // tslint: no-function-expression - radix: ['error', 'always'], - 'lines-between-class-members': 'off', - 'max-classes-per-file': 'off', - 'no-case-declarations': 'off', - 'no-cond-assign': 'off', - 'no-continue': 'off', - 'no-control-regex': 'off', - 'no-else-return': 'off', - 'no-lonely-if': 'off', - 'no-loop-func': 'off', - 'no-multi-assign': 'off', - 'no-nested-ternary': 'off', - 'no-param-reassign': 'off', - 'no-plusplus': 'off', - 'no-prototype-builtins': 'off', - 'no-return-assign': 'off', - 'no-template-curly-in-string': 'off', - 'no-undef-init': 'off', - 'no-underscore-dangle': 'off', - 'no-unneeded-ternary': 'off', - 'no-unused-expressions': 'off', - 'no-use-before-define': 'off', - 'no-useless-computed-key': 'off', - 'no-useless-concat': 'off', - 'no-useless-constructor': 'off', - 'no-useless-escape': 'off', - 'no-useless-rename': 'off', - 'no-useless-return': 'off', - 'object-shorthand': 'warn', - 'operator-assignment': 'off', - 'prefer-destructuring': 'off', - 'prefer-template': 'off', - // airbnb or other config overrides (some temporary) - // TODO: determine which rules we want to enable, and make needed changes (separate PR) - 'arrow-body-style': 'off', - 'class-methods-use-this': 'off', - 'consistent-return': 'off', - 'default-case': 'off', - 'func-names': 'off', - 'global-require': 'off', - 'spaced-comment': 'off', - // airbnb options ban for-of which is unnecessary for TS and modern node (https://github.com/airbnb/javascript/blob/master/packages/eslint-config-airbnb-base/rules/style.js#L334) - // but this is a very powerful rule we may want to use in other ways - 'no-restricted-syntax': 'off', - // permanently disable because we disagree with these rules - 'no-await-in-loop': 'off', // contrary to rule docs, awaited things often are NOT parallelizable - // permanently disable due to performance issues (using custom rule `@fluentui/max-len` instead) - 'max-len': 'off', - // permanently disable due to perf problems and limited benefit - // see here for perf testing (note that you must run eslint directly) - // https://eslint.org/docs/developer-guide/working-with-rules#per-rule-performance - 'no-empty-character-class': 'off', + 'no-shadow': ['error', { hoist: 'all' }], + 'no-var': 'error', + 'prefer-const': 'error', + 'prefer-arrow-callback': 'error', + radix: ['error', 'always'], +}; - /** - * `@typescript-eslint`plugin eslint rules - * @see https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin - */ - '@typescript-eslint/no-empty-function': 'error', - '@typescript-eslint/no-explicit-any': 'error', - '@typescript-eslint/prefer-namespace-keyword': 'error', +/** @type {import('eslint').Linter.RulesRecord} */ +const disabledRules = { + 'lines-between-class-members': 'off', + 'max-classes-per-file': 'off', + 'no-case-declarations': 'off', + 'no-cond-assign': 'off', + 'no-continue': 'off', + 'no-control-regex': 'off', + 'no-else-return': 'off', + 'no-lonely-if': 'off', + 'no-loop-func': 'off', + 'no-multi-assign': 'off', + 'no-nested-ternary': 'off', + 'no-param-reassign': 'off', + 'no-plusplus': 'off', + 'no-prototype-builtins': 'off', + 'no-return-assign': 'off', + 'no-template-curly-in-string': 'off', + 'no-undef-init': 'off', + 'no-underscore-dangle': 'off', + 'no-unneeded-ternary': 'off', + 'no-unused-expressions': 'off', + 'no-use-before-define': 'off', + 'no-useless-computed-key': 'off', + 'no-useless-concat': 'off', + 'no-useless-constructor': 'off', + 'no-useless-escape': 'off', + 'no-useless-rename': 'off', + 'no-useless-return': 'off', + 'object-shorthand': 'warn', + 'operator-assignment': 'off', + 'prefer-destructuring': 'off', + 'prefer-template': 'off', + 'arrow-body-style': 'off', + 'class-methods-use-this': 'off', + 'consistent-return': 'off', + 'default-case': 'off', + 'func-names': 'off', + 'global-require': 'off', + 'spaced-comment': 'off', + 'no-restricted-syntax': 'off', + 'no-await-in-loop': 'off', + 'max-len': 'off', + 'no-empty-character-class': 'off', + 'default-param-last': 'off', +}; - /** - * `@rnx-kit` eslint rules - * @see https://github.com/microsoft/rnx-kit/tree/main/packages/eslint-plugin - */ - '@rnx-kit/no-export-all': ['error', { expand: 'external-only' }], +/** @type {import('eslint').Linter.RulesRecord} */ +const fluentRules = { + '@fluentui/ban-imports': [ + 'error', + { + path: 'react', + names: ['useLayoutEffect'], + message: '`useLayoutEffect` causes a warning in SSR. Use `useIsomorphicLayoutEffect`', + }, + ], + '@fluentui/no-global-react': 'error', + '@fluentui/max-len': [ + 'error', + { + ignorePatterns: [ + 'require(<.*?>)?\\(', + 'https?:\\/\\/', + '^(import|export) ', + '^\\s+()?\\(', - 'https?:\\/\\/', - '^(import|export) ', - '^\\s+( [ - // Enable rules requiring type info only for appropriate files/circumstances - ...configHelpers.getTypeInfoRuleOverrides(typeAwareRules), +/** @type { import("eslint").Linter.Config } */ +module.exports = defineConfig( + globalIgnores(IGNORES), + ...airbnbConfig.configs.base.legacy, + importPlugin.flatConfigs.typescript, + prettierConfig, + { + plugins: { + '@typescript-eslint': tseslint.plugin, + '@fluentui': { + rules, + }, + '@rnx-kit': rnxPlugin, + import: importPlugin, + jsdoc: /** @type {import('eslint').ESLint.Plugin} */ (jsDocPlugin), + ...__internal.plugins, + }, + settings: { + jsdoc: { + ignoreInternal: true, + tagNamePreference: { + default: 'default', + defaultvalue: 'defaultvalue', + defaultValue: 'defaultValue', + return: 'return', + returns: 'returns', + }, + }, + }, + linterOptions: { + reportUnusedDisableDirectives: !configHelpers.isLintStaged, + }, + languageOptions: { + globals: { + ...globals.browser, + ...globals.jest, + }, + }, + rules: { + ...coreRules, + ...disabledRules, + ...typescriptRules, + ...rnxRules, + ...fluentRules, + ...jsDocRules, + ...importRules, + }, + }, { - files: '**/*.{ts,tsx}', - // This turns off a few rules that don't work or are unnecessary for TS, and enables a few - // that make sense for TS: no-var, prefer-const, prefer-rest-params, prefer-spread - // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/src/configs/eslint-recommended.ts - extends: ['plugin:@typescript-eslint/eslint-recommended'], - // and manually enable rules that only work on TS + ...tseslint.configs.eslintRecommended, + languageOptions: { + parser: tseslint.parser, + }, rules: { + /** + * `@typescript-eslint`plugin eslint rules + * @see https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin + */ + ...tseslint.configs.eslintRecommended.rules, + ...configHelpers.getNamingConventionRule(), '@typescript-eslint/ban-ts-comment': 'error', '@typescript-eslint/explicit-member-accessibility': [ 'error', - { - accessibility: 'explicit', - overrides: { constructors: 'off' }, - }, + { accessibility: 'explicit', overrides: { constructors: 'off' } }, ], '@typescript-eslint/member-ordering': [ 'error', @@ -288,43 +283,63 @@ const getOverrides = () => [ }, ], '@typescript-eslint/no-shadow': 'error', - - // permanently disable due to using other rules which do the same thing - camelcase: 'off', // redundant with @typescript-eslint/naming-convention - - // permanently disable due to improper TS handling or unnecessary for TS - // (and not covered by plugin:@typescript-eslint/eslint-recommended) + camelcase: 'off', 'no-empty-function': 'off', 'no-shadow': 'off', 'no-unused-vars': 'off', }, + settings: { + 'import/resolver': { + // @see https://github.com/alexgorbatchev/eslint-import-resolver-typescript#configuration + typescript: { + alwaysTryTypes: true, + project: './tsconfig.json', + }, + }, + }, + }, + { + files: ['**/*.{ts,tsx}'], + languageOptions: { + parserOptions: { + projectService: true, + }, + }, + rules: { + ...typeAwareRules, + }, }, { - // Test overrides files: [...configHelpers.testFiles], + plugins: { + jest: jestPlugin, + }, + languageOptions: { + globals: { + ...globals.jest, + }, + }, rules: { 'no-console': 'off', }, }, { - files: 'src/**/*.deprecated.test.{ts,tsx}', + files: ['src/**/*.deprecated.test.{ts,tsx}'], rules: { '@typescript-eslint/no-deprecated': 'off', }, }, { - // Example overrides - files: '**/*.{Example,stories}.tsx', + files: ['**/*.{Example,stories}.tsx'], rules: { 'no-alert': 'off', 'no-console': 'off', }, }, { - // Docs overrides (excluding examples) files: [...configHelpers.docsFiles], rules: { - 'import/no-webpack-loader-syntax': 'off', // this is ok in docs + 'import/no-webpack-loader-syntax': 'off', }, }, { @@ -336,24 +351,7 @@ const getOverrides = () => [ { files: [...configHelpers.devDependenciesFiles], rules: { - // TODO: https://github.com/microsoft/fluentui/issues/21999 'import/no-extraneous-dependencies': 'off', }, }, -]; - -// Why use `defineProperty` for `overrides`? -// -// By default, any logic in this file will be run every time the plugin is loaded (even if this -// config is not used) due to it being included by necessity in the package index file. -// These overrides include some more complex logic which should only run when requested, since it's -// more costly and can cause build errors if run in a package it wasn't designed for. -// If ESLint supported exporting a function from a config file, that would be an easy solution. -// Since that's not supported, we work around it by defining overrides as a property with getter. -// @ts-ignore -- `overrides?` is declared in `eslint.Linter.Config` but our `config` object doesn't define it until now -Object.defineProperty(config, 'overrides', { - enumerable: true, - get: getOverrides, -}); - -module.exports = config; +); diff --git a/packages/eslint-plugin/src/configs/imports.js b/packages/eslint-plugin/src/configs/imports.js index bf0e86da9b832f..97075ba93a8d0d 100644 --- a/packages/eslint-plugin/src/configs/imports.js +++ b/packages/eslint-plugin/src/configs/imports.js @@ -1,7 +1,13 @@ // @ts-check +const importPlugin = require('eslint-plugin-import'); +const { defineConfig } = require('eslint/config'); -/** @type {import("eslint").Linter.LegacyConfig} */ -module.exports = { +/** @type { import("eslint").Linter.Config } */ +module.exports = defineConfig({ + files: ['**/*.{ts,tsx,js,jsx,cjs,mjs}'], + plugins: { + import: importPlugin, + }, rules: { /** * core eslint rules @@ -27,4 +33,4 @@ module.exports = { }, ], }, -}; +}); diff --git a/packages/eslint-plugin/src/configs/node-legacy.js b/packages/eslint-plugin/src/configs/node-legacy.js deleted file mode 100644 index 84c0052e819d4d..00000000000000 --- a/packages/eslint-plugin/src/configs/node-legacy.js +++ /dev/null @@ -1,9 +0,0 @@ -// @ts-check - -const path = require('path'); - -/** @type {import("eslint").Linter.LegacyConfig} */ -module.exports = { - extends: [path.join(__dirname, 'base-legacy')], - rules: {}, -}; diff --git a/packages/eslint-plugin/src/configs/node.js b/packages/eslint-plugin/src/configs/node.js deleted file mode 100644 index b862173067260e..00000000000000 --- a/packages/eslint-plugin/src/configs/node.js +++ /dev/null @@ -1,11 +0,0 @@ -// @ts-check - -const path = require('path'); - -/** @type {import("eslint").Linter.LegacyConfig} */ -module.exports = { - extends: [path.join(__dirname, 'base')], - rules: { - 'no-console': 'off', - }, -}; diff --git a/packages/eslint-plugin/src/flat-configs/node/index.js b/packages/eslint-plugin/src/configs/node/index.js similarity index 100% rename from packages/eslint-plugin/src/flat-configs/node/index.js rename to packages/eslint-plugin/src/configs/node/index.js diff --git a/packages/eslint-plugin/src/flat-configs/node/legacy.js b/packages/eslint-plugin/src/configs/node/legacy.js similarity index 100% rename from packages/eslint-plugin/src/flat-configs/node/legacy.js rename to packages/eslint-plugin/src/configs/node/legacy.js diff --git a/packages/eslint-plugin/src/configs/react-config.js b/packages/eslint-plugin/src/configs/react-config.js deleted file mode 100644 index aab43ff8c37c91..00000000000000 --- a/packages/eslint-plugin/src/configs/react-config.js +++ /dev/null @@ -1,151 +0,0 @@ -const configHelpers = require('../utils/configHelpers'); - -/** @type {import("eslint").Linter.LegacyConfig} */ -module.exports = { - plugins: ['jsx-a11y', 'react', 'react-hooks', '@griffel'], - parserOptions: { - ecmaFeatures: { - jsx: true, - }, - }, - settings: { - react: { - pragma: 'React', - version: 'detect', - }, - // copied from https://github.com/airbnb/javascript/blob/master/packages/eslint-config-airbnb/rules/react.js#L575 - propWrapperFunctions: [ - 'forbidExtraProps', // https://www.npmjs.com/package/airbnb-prop-types - 'exact', // https://www.npmjs.com/package/prop-types-exact - 'Object.freeze', // https://tc39.github.io/ecma262/#sec-object.freeze - ], - }, - rules: { - /** - * Enforce consistent importing from React by resticting named imports. - */ - 'no-restricted-syntax': [ - 'error', - { - selector: "ImportDeclaration[source.value='react'] ImportSpecifier", - message: "Avoid named imports from 'react'. Use 'import * as React from \"react\"' instead.", - }, - ], - /** - * griffel eslint rules - * @see https://github.com/microsoft/griffel/tree/main/packages/eslint-plugin - */ - '@griffel/hook-naming': 'error', - '@griffel/no-shorthands': 'error', - '@griffel/pseudo-element-naming': 'error', - '@griffel/styles-file': 'error', - /** - * react eslint rules - * @see https://github.com/yannickcr/eslint-plugin-react - */ - 'react/jsx-key': 'error', - 'react/jsx-no-bind': [ - 'error', - { - allowArrowFunctions: false, // tslint: jsx-no-lambda - allowFunctions: false, - allowBind: false, - ignoreDOMComponents: true, - ignoreRefs: true, - }, - ], - 'react/no-string-refs': 'error', - 'react/self-closing-comp': 'error', - 'react/no-danger': 'warn', - // NOTE: following rules has been turned off to override `airbnb/rules/react`, which we don't use anymore - // TODO: needs to be checked against core react eslint plugin if we need those turn off explicitly - // @see https://github.com/airbnb/javascript/blob/master/packages/eslint-config-airbnb/rules/react.js - 'react/button-has-type': 'off', - 'react/destructuring-assignment': 'off', - 'react/forbid-prop-types': 'off', - 'react/jsx-boolean-value': 'off', - 'react/jsx-curly-brace-presence': 'off', - 'react/jsx-no-target-blank': 'off', - 'react/jsx-pascal-case': 'off', // Doesn't handle lowercase slot names - 'react/no-access-state-in-setstate': 'off', - 'react/no-array-index-key': 'off', - 'react/no-did-update-set-state': 'off', - 'react/no-find-dom-node': 'off', - 'react/no-render-return-value': 'off', - 'react/no-unescaped-entities': 'off', - 'react/no-will-update-set-state': 'off', - 'react/prefer-stateless-function': 'off', - 'react/sort-comp': 'off', - 'react/state-in-constructor': 'off', - 'react/static-property-placement': 'off', - 'react/require-default-props': 'off', - 'react/no-unknown-property': 'off', // expensive, limited benefit with TS - // these ones have minor negative perf impact and are unnecessary - 'react/default-props-match-prop-types': 'off', - 'react/no-unused-prop-types': 'off', - 'react/prefer-es6-class': 'off', - // permanently disable because we disagree with these rules - 'react/jsx-props-no-spreading': 'off', - 'react/prop-types': 'off', - 'react/jsx-filename-extension': 'off', - - /** - * react-hooks rules - * @see https://github.com/facebook/react/tree/main/packages/eslint-plugin-react-hooks - */ - 'react-hooks/exhaustive-deps': [ - 'error', - { - additionalHooks: 'useIsomorphicLayoutEffect', - }, - ], - 'react-hooks/rules-of-hooks': 'error', - - /** - * jsx-a11y rules - * @see https://github.com/jsx-eslint/eslint-plugin-jsx-a11y - */ - 'jsx-a11y/tabindex-no-positive': 'error', - // NOTE: following rules has been turned off to override `airbnb/rules/react-a11y` - // TODO: needs to be checked against core jsx-a11y eslint plugin if we need those turn off explicitly - // @see https://github.com/airbnb/javascript/blob/master/packages/eslint-config-airbnb/rules/react-a11y.js - 'jsx-a11y/alt-text': 'off', - 'jsx-a11y/anchor-is-valid': 'off', - 'jsx-a11y/aria-activedescendant-has-tabindex': 'off', - 'jsx-a11y/aria-role': 'off', - 'jsx-a11y/click-events-have-key-events': 'off', - 'jsx-a11y/control-has-associated-label': 'off', - 'jsx-a11y/role-has-required-aria-props': 'off', - 'jsx-a11y/interactive-supports-focus': 'off', - 'jsx-a11y/label-has-associated-control': 'off', - 'jsx-a11y/media-has-caption': 'off', - 'jsx-a11y/mouse-events-have-key-events': 'off', - 'jsx-a11y/no-interactive-element-to-noninteractive-role': 'off', - 'jsx-a11y/no-noninteractive-element-interactions': 'off', - 'jsx-a11y/no-noninteractive-tabindex': 'off', - 'jsx-a11y/no-redundant-roles': 'off', - 'jsx-a11y/no-static-element-interactions': 'off', - 'jsx-a11y/role-supports-aria-props': 'off', - }, - overrides: [ - { - // Test overrides - files: [...configHelpers.testFiles], - rules: { - 'react/jsx-no-bind': 'off', - }, - }, - { - files: '**/*.stories.tsx', - rules: { - // allow makeStyles calls in stories as examples should be defined in a single file - '@griffel/styles-file': 'off', - // allow arrow functions in stories for now (may want to change this later since using - // constantly-mutating functions can be an anti-pattern which we may not want to demonstrate - // in our converged components docs; it happened to be allowed starting out because .stories - // files were being linted as tests) - 'react/jsx-no-bind': 'off', - }, - }, - ], -}; diff --git a/packages/eslint-plugin/src/configs/react-legacy.js b/packages/eslint-plugin/src/configs/react-legacy.js deleted file mode 100644 index 105ef83ee940e8..00000000000000 --- a/packages/eslint-plugin/src/configs/react-legacy.js +++ /dev/null @@ -1,49 +0,0 @@ -// @ts-check -const path = require('path'); - -const configHelpers = require('../utils/configHelpers'); -const { reactLegacy: restrictedGlobals } = require('../shared/restricted-globals'); -const { createReactCrossVersionRules } = require('../shared/react-cross-version-rules'); - -/** @type {import("eslint").Linter.LegacyConfig} */ -module.exports = { - extends: [path.join(__dirname, 'base-legacy'), path.join(__dirname, 'react-config')], - - rules: { - 'jsdoc/check-tag-names': 'off', - '@griffel/no-shorthands': 'off', - 'no-restricted-globals': restrictedGlobals, - ...createReactCrossVersionRules({ - crossCompatTypePackage: '@fluentui/utilities', - }), - }, - overrides: [ - { - // Test overrides - files: [...configHelpers.testFiles, '**/*.stories.tsx'], - rules: { - 'no-restricted-globals': 'off', - 'react/jsx-no-bind': 'off', - '@typescript-eslint/no-deprecated': 'off', - '@typescript-eslint/explicit-module-boundary-types': 'off', - }, - }, - { - files: ['**/*.e2e.{ts,tsx,js}', '**/*.cy.{ts,tsx,js}', 'isConformant.{ts,tsx,js}'], - rules: { - '@typescript-eslint/no-restricted-imports': [ - 'error', - { - patterns: [ - { - group: ['@cypress/react'], - importNames: ['mount'], - message: "Use 'mount' from @fluentui/scripts-cypress instead.", - }, - ], - }, - ], - }, - }, - ], -}; diff --git a/packages/eslint-plugin/src/configs/react.js b/packages/eslint-plugin/src/configs/react.js deleted file mode 100644 index c98ef8697cedda..00000000000000 --- a/packages/eslint-plugin/src/configs/react.js +++ /dev/null @@ -1,101 +0,0 @@ -// @ts-check - -const path = require('path'); -const configHelpers = require('../utils/configHelpers'); -const { __internal } = require('../internal'); - -const { createReactCrossVersionRules } = require('../shared/react-cross-version-rules'); - -/** @type {import("eslint").Linter.RulesRecord} */ -const typeAwareRules = { - '@fluentui/ban-context-export': ['error', { exclude: ['**/react-shared-contexts/**'] }], -}; - -const root = configHelpers.findGitRoot(); -const unstableV9Packages = configHelpers.getV9UnstablePackages(root); -const v9PackageDeps = Object.keys(configHelpers.getPackageJson({ root, name: 'react-components' }).dependencies).filter( - pkg => !unstableV9Packages.has(pkg), -); - -/** @type {import("eslint").Linter.LegacyConfig} */ -module.exports = { - extends: [path.join(__dirname, 'base'), path.join(__dirname, 'react-config')], - plugins: ['react-compiler'], - rules: { - 'jsdoc/check-tag-names': [ - 'error', - { - // Allow TSDoc tags - definedTags: ['remarks'], - jsxTags: true, - }, - ], - '@fluentui/ban-instanceof-html-element': ['error'], - '@fluentui/no-context-default-value': [ - 'error', - { - // nx-ignore-next-line - this is a valid use case to ignore workspace packages. keeping them part of the project dependencies would be wrong assumption - imports: ['react', '@fluentui/react-context-selector', '@fluentui/global-context'], - }, - ], - 'react-compiler/react-compiler': ['error'], - ...createReactCrossVersionRules({ - crossCompatTypePackage: '@fluentui/react-utilities', - extraTypeRestrictions: { - 'React.RefAttributes': { - message: - '`React.RefAttributes` is leaking string starting @types/react@18.2.61 creating invalid type contracts. Use `RefAttributes` from @fluentui/react-utilities instead', - fixWith: 'RefAttributes', - }, - }, - }), - }, - overrides: [ - // Enable rules requiring type info only for appropriate files/circumstances - ...configHelpers.getTypeInfoRuleOverrides(typeAwareRules), - { - files: '**/*.stories.tsx', - rules: { - '@fluentui/no-restricted-imports': [ - 'error', - { - paths: [ - { - forbidden: v9PackageDeps, - preferred: '@fluentui/react-components', - }, - ], - }, - ], - 'react-compiler/react-compiler': 'off', - }, - }, - { - files: ['**/*.cy.{ts,tsx,js}', 'isConformant.{ts,tsx,js}'], - rules: { - 'import/no-extraneous-dependencies': 'off', - 'react/jsx-no-bind': 'off', - 'react-compiler/react-compiler': 'off', - '@typescript-eslint/no-restricted-imports': [ - 'error', - { - patterns: [ - { - group: ['@cypress/react'], - importNames: ['mount'], - message: "Use 'mount' from @fluentui/scripts-cypress instead.", - }, - ], - }, - ], - }, - }, - { - files: '**/*.test.{ts,tsx}', - rules: { - 'react-compiler/react-compiler': 'off', - }, - }, - __internal.overrides.react, - ].filter(Boolean), -}; diff --git a/packages/eslint-plugin/src/flat-configs/react/config.js b/packages/eslint-plugin/src/configs/react/config.js similarity index 100% rename from packages/eslint-plugin/src/flat-configs/react/config.js rename to packages/eslint-plugin/src/configs/react/config.js diff --git a/packages/eslint-plugin/src/flat-configs/react/index.js b/packages/eslint-plugin/src/configs/react/index.js similarity index 98% rename from packages/eslint-plugin/src/flat-configs/react/index.js rename to packages/eslint-plugin/src/configs/react/index.js index 3d645f08a453f6..36952ef4015d5f 100644 --- a/packages/eslint-plugin/src/flat-configs/react/index.js +++ b/packages/eslint-plugin/src/configs/react/index.js @@ -3,7 +3,7 @@ const configHelpers = require('../../utils/configHelpers'); const baseConfig = require('../base/index'); const reactConfig = require('./config'); const reactCompilerPlugin = require('eslint-plugin-react-compiler'); -const { __internal } = require('../../internal-flat'); +const { __internal } = require('../../internal'); const { createReactCrossVersionRules } = require('../../shared/react-cross-version-rules'); const { defineConfig } = require('eslint/config'); diff --git a/packages/eslint-plugin/src/flat-configs/react/legacy.js b/packages/eslint-plugin/src/configs/react/legacy.js similarity index 100% rename from packages/eslint-plugin/src/flat-configs/react/legacy.js rename to packages/eslint-plugin/src/configs/react/legacy.js diff --git a/packages/eslint-plugin/src/flat-configs/core.js b/packages/eslint-plugin/src/flat-configs/core.js deleted file mode 100644 index b2e80823981bb6..00000000000000 --- a/packages/eslint-plugin/src/flat-configs/core.js +++ /dev/null @@ -1,357 +0,0 @@ -// @ts-check -const tseslint = require('typescript-eslint'); -const globals = require('globals'); -const configHelpers = require('../utils/configHelpers'); -const importPlugin = require('eslint-plugin-import'); -const rnxPlugin = require('@rnx-kit/eslint-plugin'); -const jestPlugin = require('eslint-plugin-jest'); -const jsDocPlugin = require('eslint-plugin-jsdoc'); -const prettierConfig = require('eslint-config-prettier/flat'); -const { __internal } = require('../internal-flat'); -const { globalIgnores } = require('eslint/config'); -const airbnbConfig = require('eslint-config-airbnb-extended/legacy'); -const rules = require('../rules'); -const { defineConfig } = require('eslint/config'); - -const IGNORES = [ - '**/coverage', - '**/dist', - '**/dist-storybook', - '**/etc', - '**/lib', - '**/lib-amd', - '**/lib-commonjs', - '**/temp', - '**/bundle-size', - '**/__snapshots__', - '**/*.scss.ts', -]; - -/** @type {import('eslint').Linter.RulesRecord} */ -const coreRules = { - curly: ['error', 'all'], - 'dot-notation': 'error', - eqeqeq: ['error', 'always'], - 'guard-for-in': 'error', - 'no-alert': 'error', - 'no-bitwise': 'error', - 'no-caller': 'error', - 'no-console': 'error', - 'no-constant-condition': 'error', - 'no-debugger': 'error', - 'no-duplicate-case': 'error', - 'no-empty': 'error', - 'no-eval': 'error', - 'no-new-wrappers': 'error', - 'no-restricted-globals': [ - 'error', - ...['blur', 'close', 'focus', 'length', 'name', 'parent', 'self', 'stop'].map(name => ({ - name, - message: `"${name}" refers to a DOM global. Did you mean to reference a local value instead?`, - })), - ], - 'no-restricted-properties': [ - 'error', - { object: 'describe', property: 'only', message: 'describe.only should only be used during test development' }, - { object: 'it', property: 'only', message: 'it.only should only be used during test development' }, - { - object: 'React', - property: 'useLayoutEffect', - message: '`useLayoutEffect` causes a warning in SSR. Use `useIsomorphicLayoutEffect`', - }, - ], - 'no-shadow': ['error', { hoist: 'all' }], - 'no-var': 'error', - 'prefer-const': 'error', - 'prefer-arrow-callback': 'error', - radix: ['error', 'always'], -}; - -/** @type {import('eslint').Linter.RulesRecord} */ -const disabledRules = { - 'lines-between-class-members': 'off', - 'max-classes-per-file': 'off', - 'no-case-declarations': 'off', - 'no-cond-assign': 'off', - 'no-continue': 'off', - 'no-control-regex': 'off', - 'no-else-return': 'off', - 'no-lonely-if': 'off', - 'no-loop-func': 'off', - 'no-multi-assign': 'off', - 'no-nested-ternary': 'off', - 'no-param-reassign': 'off', - 'no-plusplus': 'off', - 'no-prototype-builtins': 'off', - 'no-return-assign': 'off', - 'no-template-curly-in-string': 'off', - 'no-undef-init': 'off', - 'no-underscore-dangle': 'off', - 'no-unneeded-ternary': 'off', - 'no-unused-expressions': 'off', - 'no-use-before-define': 'off', - 'no-useless-computed-key': 'off', - 'no-useless-concat': 'off', - 'no-useless-constructor': 'off', - 'no-useless-escape': 'off', - 'no-useless-rename': 'off', - 'no-useless-return': 'off', - 'object-shorthand': 'warn', - 'operator-assignment': 'off', - 'prefer-destructuring': 'off', - 'prefer-template': 'off', - 'arrow-body-style': 'off', - 'class-methods-use-this': 'off', - 'consistent-return': 'off', - 'default-case': 'off', - 'func-names': 'off', - 'global-require': 'off', - 'spaced-comment': 'off', - 'no-restricted-syntax': 'off', - 'no-await-in-loop': 'off', - 'max-len': 'off', - 'no-empty-character-class': 'off', - 'default-param-last': 'off', -}; - -/** @type {import('eslint').Linter.RulesRecord} */ -const fluentRules = { - '@fluentui/ban-imports': [ - 'error', - { - path: 'react', - names: ['useLayoutEffect'], - message: '`useLayoutEffect` causes a warning in SSR. Use `useIsomorphicLayoutEffect`', - }, - ], - '@fluentui/no-global-react': 'error', - '@fluentui/max-len': [ - 'error', - { - ignorePatterns: [ - 'require(<.*?>)?\\(', - 'https?:\\/\\/', - '^(import|export) ', - '^\\s+(} */ -const legacy = { - node: require('./configs/node'), - 'node--legacy': require('./configs/node-legacy'), - react: require('./configs/react'), - imports: require('./configs/imports'), - 'react--legacy': require('./configs/react-legacy'), -}; - /** @type {Record} */ -const flat = { - 'flat/core': require('./flat-configs/core'), - 'flat/react': require('./flat-configs/react'), - 'flat/react-legacy': require('./flat-configs/react/legacy'), - 'flat/node': require('./flat-configs/node'), - 'flat/node-legacy': require('./flat-configs/node/legacy'), - 'flat/imports': require('./flat-configs/imports'), +const configs = { + 'flat/core': require('./configs/core'), + 'flat/react': require('./configs/react'), + 'flat/react-legacy': require('./configs/react/legacy'), + 'flat/node': require('./configs/node'), + 'flat/node-legacy': require('./configs/node/legacy'), + 'flat/imports': require('./configs/imports'), }; const plugin = { namespace: '@fluentui', - configs: { - ...legacy, - ...flat, - }, + configs, rules, configHelpers, }; diff --git a/packages/eslint-plugin/src/internal-flat.js b/packages/eslint-plugin/src/internal-flat.js deleted file mode 100644 index 063640580f45ce..00000000000000 --- a/packages/eslint-plugin/src/internal-flat.js +++ /dev/null @@ -1,45 +0,0 @@ -const restrictedGlobals = require('./shared/restricted-globals'); -const nxPlugin = require('@nx/eslint-plugin'); - -function shouldRegisterInternal() { - try { - const hasNxEslintPlugin = require.resolve('@nx/eslint-plugin'); - return Boolean(hasNxEslintPlugin); - } catch { - return false; - } -} - -const shouldRegister = shouldRegisterInternal(); - -/** - * @internal - * - * this will be removed after https://github.com/microsoft/fluentui/issues/30332 - * - * expands this with rulesets/overrides that are necessary for specific configs - */ -const __internal = { - /** - * `@nx/eslint-plugin` is necessary in order to register custom lint rules that live within tools/eslint-rules - */ - /** @type {Record} */ - plugins: shouldRegister ? { '@nx': nxPlugin } : {}, - // extend this object with your rule overrides - overrides: { - react: shouldRegister - ? { - files: ['**/src/**/*.{ts,tsx}'], - ignores: ['**/*.{test,spec,cy,stories}.{ts,tsx}'], - /** @type {import('eslint').Linter.RulesRecord} */ - rules: { - '@nx/workspace-consistent-callback-type': 'error', - '@nx/workspace-no-restricted-globals': restrictedGlobals.react, - '@nx/workspace-no-missing-jsx-pragma': ['error', { runtime: 'automatic' }], - }, - } - : {}, - }, -}; - -exports.__internal = __internal; diff --git a/packages/eslint-plugin/src/internal.js b/packages/eslint-plugin/src/internal.js index 493ccabd6fbd7d..063640580f45ce 100644 --- a/packages/eslint-plugin/src/internal.js +++ b/packages/eslint-plugin/src/internal.js @@ -1,4 +1,5 @@ const restrictedGlobals = require('./shared/restricted-globals'); +const nxPlugin = require('@nx/eslint-plugin'); function shouldRegisterInternal() { try { @@ -22,20 +23,22 @@ const __internal = { /** * `@nx/eslint-plugin` is necessary in order to register custom lint rules that live within tools/eslint-rules */ - plugins: shouldRegister ? ['@nx'] : [], + /** @type {Record} */ + plugins: shouldRegister ? { '@nx': nxPlugin } : {}, // extend this object with your rule overrides overrides: { react: shouldRegister ? { files: ['**/src/**/*.{ts,tsx}'], - excludedFiles: ['*.{test,spec,cy,stories}.{ts,tsx}'], + ignores: ['**/*.{test,spec,cy,stories}.{ts,tsx}'], + /** @type {import('eslint').Linter.RulesRecord} */ rules: { '@nx/workspace-consistent-callback-type': 'error', '@nx/workspace-no-restricted-globals': restrictedGlobals.react, '@nx/workspace-no-missing-jsx-pragma': ['error', { runtime: 'automatic' }], }, } - : null, + : {}, }, }; From 3ce5b5994f3e5c8f888863a35170360fce1740bf Mon Sep 17 00:00:00 2001 From: mainframev Date: Fri, 16 Jan 2026 10:28:08 +0100 Subject: [PATCH 2/8] chore(public-docsite-setup): do not use FlatCompat for fluent plugin --- packages/public-docsite-setup/eslint.config.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/public-docsite-setup/eslint.config.js b/packages/public-docsite-setup/eslint.config.js index 60040a9f6a5436..784b9a108f7f9c 100644 --- a/packages/public-docsite-setup/eslint.config.js +++ b/packages/public-docsite-setup/eslint.config.js @@ -1,7 +1,8 @@ +// @ts-check const js = require('@eslint/js'); const { fixupConfigRules } = require('@eslint/compat'); - const { FlatCompat } = require('@eslint/eslintrc'); +const fluentPlugin = require('@fluentui/eslint-plugin'); const compat = new FlatCompat({ baseDirectory: __dirname, @@ -9,13 +10,12 @@ const compat = new FlatCompat({ allConfig: js.configs.all, }); +/** @type {import("eslint").Linter.Config[]} */ module.exports = [ - ...compat.extends('plugin:@fluentui/eslint-plugin/react--legacy'), + ...fluentPlugin.configs['flat/react-legacy'], { files: ['bin/*.js', 'scripts/*.js'], - rules: { - ...fixupConfigRules(compat.extends('plugin:es/restrict-to-es2017')).rules, - }, + ...fixupConfigRules(compat.extends('plugin:es/restrict-to-es2017'))[0], }, { files: ['bin/*.js', 'src/loadSite.ts'], From 9cb2b42f0c9de32539c2d1a7fc387d0f3da71868 Mon Sep 17 00:00:00 2001 From: mainframev Date: Fri, 16 Jan 2026 10:29:06 +0100 Subject: [PATCH 3/8] chore(pr-deploy-site): remove FlatCompat --- apps/pr-deploy-site/eslint.config.js | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/apps/pr-deploy-site/eslint.config.js b/apps/pr-deploy-site/eslint.config.js index b5f3fe55818431..3f9549993fe245 100644 --- a/apps/pr-deploy-site/eslint.config.js +++ b/apps/pr-deploy-site/eslint.config.js @@ -1,23 +1,13 @@ -const js = require('@eslint/js'); +// @ts-check +const fluentPlugin = require('@fluentui/eslint-plugin'); const sdl = require('@microsoft/eslint-plugin-sdl'); -const es = require('eslint-plugin-es'); -const { FlatCompat } = require('@eslint/eslintrc'); - -const compat = new FlatCompat({ - baseDirectory: __dirname, - recommendedConfig: js.configs.recommended, - allConfig: js.configs.all, -}); +/** @type {import("eslint").Linter.Config[]} */ module.exports = [ - ...compat.extends('plugin:@fluentui/eslint-plugin/node'), + ...fluentPlugin.configs['flat/node'], ...sdl.configs.recommended, { files: ['**/pr-deploy-site.js'], - plugins: { - es, - }, - rules: { curly: 'off', 'no-var': 'off', From b540638016b20d25e2f1fc141c9f828ec8afe968 Mon Sep 17 00:00:00 2001 From: mainframev Date: Fri, 16 Jan 2026 10:31:01 +0100 Subject: [PATCH 4/8] docs(react-components): update README with flat example --- .../eslint-plugin-react-components/README.md | 36 +++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/packages/react-components/eslint-plugin-react-components/README.md b/packages/react-components/eslint-plugin-react-components/README.md index da8ebc6c7b16b7..9600e6ca8909c5 100644 --- a/packages/react-components/eslint-plugin-react-components/README.md +++ b/packages/react-components/eslint-plugin-react-components/README.md @@ -31,7 +31,39 @@ pnpm add --save-dev @fluentui/eslint-plugin-react-components ## Usage -1. Add the plugin to your `.eslintrc.js` or equivalent configuration file: +### Flat Config (ESLint 9+, recommended) + +Add the plugin to your `eslint.config.js`: + +```js +const fluentuiReactComponents = require('@fluentui/eslint-plugin-react-components'); + +module.exports = [ + fluentuiReactComponents.configs.recommended, + // Your other configs... +]; +``` + +Or configure individual rules manually: + +```js +const fluentuiReactComponents = require('@fluentui/eslint-plugin-react-components'); + +module.exports = [ + { + plugins: { + '@fluentui/react-components': fluentuiReactComponents, + }, + rules: { + '@fluentui/react-components/prefer-fluentui-v9': 'warn', + }, + }, +]; +``` + +### Legacy Config (ESLint 8 and below) + +Add the plugin to your `.eslintrc.js`: ```js module.exports = { @@ -40,7 +72,7 @@ module.exports = { }; ``` -2. Or configure individual rules manually: +Or configure individual rules manually: ```js module.exports = { From 4eae98afc26b24c3c367d05da36dcb44dab9e0e1 Mon Sep 17 00:00:00 2001 From: mainframev Date: Fri, 16 Jan 2026 10:31:12 +0100 Subject: [PATCH 5/8] chore: remove redundant eslintrc --- packages/utilities/.eslintrc.json | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 packages/utilities/.eslintrc.json diff --git a/packages/utilities/.eslintrc.json b/packages/utilities/.eslintrc.json deleted file mode 100644 index 87c5bcca52ca04..00000000000000 --- a/packages/utilities/.eslintrc.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": ["plugin:@fluentui/eslint-plugin/react--legacy"], - "root": true, - "rules": { - "@rnx-kit/no-export-all": ["error", { "expand": "all" }], - "prefer-const": "off" - } -} From 07e7f12a4b7d7e96ad583414947ceff19dbd8699 Mon Sep 17 00:00:00 2001 From: mainframev Date: Fri, 16 Jan 2026 10:41:10 +0100 Subject: [PATCH 6/8] chore: update change files --- ...ct-components-56d5c0bd-5960-4e24-8bb6-3e8efe5ffc34.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 change/@fluentui-eslint-plugin-react-components-56d5c0bd-5960-4e24-8bb6-3e8efe5ffc34.json diff --git a/change/@fluentui-eslint-plugin-react-components-56d5c0bd-5960-4e24-8bb6-3e8efe5ffc34.json b/change/@fluentui-eslint-plugin-react-components-56d5c0bd-5960-4e24-8bb6-3e8efe5ffc34.json new file mode 100644 index 00000000000000..b324d8deb5dcda --- /dev/null +++ b/change/@fluentui-eslint-plugin-react-components-56d5c0bd-5960-4e24-8bb6-3e8efe5ffc34.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "docs: update README", + "packageName": "@fluentui/eslint-plugin-react-components", + "email": "vgenaev@gmail.com", + "dependentChangeType": "none" +} From cea02aa4896b4ae540fe1c83bc0c21bcb1e4d8ed Mon Sep 17 00:00:00 2001 From: mainframev Date: Thu, 22 Jan 2026 11:13:23 +0100 Subject: [PATCH 7/8] chore: update deps --- package.json | 2 +- syncpack.config.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index e55f7afb4f707f..3b325ece6840b9 100644 --- a/package.json +++ b/package.json @@ -202,7 +202,6 @@ "esbuild": "0.25.0", "esbuild-loader": "4.1.0", "eslint": "9.31.0", - "eslint-config-airbnb": "18.2.1", "eslint-config-airbnb-extended": "2.1.2", "eslint-config-prettier": "10.1.8", "eslint-import-resolver-typescript": "3.7.0", @@ -357,6 +356,7 @@ "**/prismjs": "^1.30.0", "**/@tensile-perf/runner/express": "^4.21.2", "**/tar-fs": "^2.1.4", + "jackspeak": "2.1.1", "**/micromatch/braces": "^3.0.3" }, "nx": { diff --git a/syncpack.config.js b/syncpack.config.js index 6fbd804075c49f..de292844fbc124 100644 --- a/syncpack.config.js +++ b/syncpack.config.js @@ -44,7 +44,7 @@ const config = { 'codesandbox-import-utils', 'copy-to-clipboard', 'del', - 'eslint-config-airbnb', + 'eslint-config-airbnb-extended', 'eslint-config-prettier', 'eslint-import-resolver-typescript', 'eslint-plugin-compat', From d745ebb12c9d004914116e8e5e35c6eeed318ddb Mon Sep 17 00:00:00 2001 From: mainframev Date: Thu, 22 Jan 2026 11:17:31 +0100 Subject: [PATCH 8/8] chore: update deps --- yarn.lock | 109 ++++-------------------------------------------------- 1 file changed, 8 insertions(+), 101 deletions(-) diff --git a/yarn.lock b/yarn.lock index c7ad81f786ea91..1853fe0bbb15bb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2076,18 +2076,6 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.3.tgz#c2b9d2e374ee62c586d3adbea87199b1d7a7a6ba" integrity sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ== -"@isaacs/cliui@^8.0.2": - version "8.0.2" - resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" - integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== - dependencies: - string-width "^5.1.2" - string-width-cjs "npm:string-width@^4.2.0" - strip-ansi "^7.0.1" - strip-ansi-cjs "npm:strip-ansi@^6.0.1" - wrap-ansi "^8.1.0" - wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" - "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -5914,11 +5902,6 @@ ansi-styles@^5.0.0, ansi-styles@^5.2.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== -ansi-styles@^6.1.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" - integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== - ansicolors@~0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.3.2.tgz#665597de86a9ffe3aa9bfbe6cae5c6ea426b4979" @@ -7751,7 +7734,7 @@ configstore@^3.0.0: write-file-atomic "^2.0.0" xdg-basedir "^3.0.0" -confusing-browser-globals@^1.0.10, confusing-browser-globals@^1.0.11, confusing-browser-globals@^1.0.9: +confusing-browser-globals@^1.0.11, confusing-browser-globals@^1.0.9: version "1.0.11" resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz#ae40e9b57cdd3915408a2805ebd3a5585608dc81" integrity sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA== @@ -8909,11 +8892,6 @@ duplexer@^0.1.2: resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== -eastasianwidth@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" - integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== - ecc-jsbn@~0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" @@ -9358,15 +9336,6 @@ eslint-compat-utils@^0.5.1: dependencies: semver "^7.5.4" -eslint-config-airbnb-base@^14.2.1: - version "14.2.1" - resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.2.1.tgz#8a2eb38455dc5a312550193b319cdaeef042cd1e" - integrity sha512-GOrQyDtVEc1Xy20U7vsB2yAoB4nBlfH5HZJeatRXHleO+OS5Ot+MWij4Dpltw4/DyIkqUfqz1epfhVR5XWWQPA== - dependencies: - confusing-browser-globals "^1.0.10" - object.assign "^4.1.2" - object.entries "^1.1.2" - eslint-config-airbnb-extended@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/eslint-config-airbnb-extended/-/eslint-config-airbnb-extended-2.1.2.tgz#ad36d05c7dc18a7729a603c2dbf4783342272d29" @@ -9375,15 +9344,6 @@ eslint-config-airbnb-extended@2.1.2: confusing-browser-globals "^1.0.11" globals "^16.3.0" -eslint-config-airbnb@18.2.1, eslint-config-airbnb@^18.2.1: - version "18.2.1" - resolved "https://registry.yarnpkg.com/eslint-config-airbnb/-/eslint-config-airbnb-18.2.1.tgz#b7fe2b42f9f8173e825b73c8014b592e449c98d9" - integrity sha512-glZNDEZ36VdlZWoxn/bUR1r/sdFKPd1mHPbqUtkctgNG4yT2DLLtJ3D+yCV+jzZCc2V1nBVkmdknOJBZ5Hc0fg== - dependencies: - eslint-config-airbnb-base "^14.2.1" - object.assign "^4.1.2" - object.entries "^1.1.2" - eslint-config-prettier@10.1.8, eslint-config-prettier@^10.1.8: version "10.1.8" resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz#15734ce4af8c2778cc32f0b01b37b0b5cd1ecb97" @@ -12672,22 +12632,15 @@ iterator.prototype@^1.1.4: has-symbols "^1.1.0" set-function-name "^2.0.2" -jackspeak@^3.1.2: - version "3.4.3" - resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" - integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== +jackspeak@2.1.1, jackspeak@^3.1.2, jackspeak@^4.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.1.1.tgz#2a42db4cfbb7e55433c28b6f75d8b796af9669cd" + integrity sha512-juf9stUEwUaILepraGOWIJTLwg48bUnBmRqd2ln2Os1sW987zeoj/hzhbvRB95oMuS2ZTpjULmdwHNX4rzZIZw== dependencies: - "@isaacs/cliui" "^8.0.2" + cliui "^8.0.1" optionalDependencies: "@pkgjs/parseargs" "^0.11.0" -jackspeak@^4.0.1: - version "4.1.0" - resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-4.1.0.tgz#c489c079f2b636dc4cbe9b0312a13ff1282e561b" - integrity sha512-9DDdhb5j6cpeitCbvLO7n7J4IxnbM6hoF6O1g4HQ5TfhvvKN8ywDM7668ZhMHRqVmxqhps/F6syWK2KcPxYlkw== - dependencies: - "@isaacs/cliui" "^8.0.2" - jake@^10.8.5: version "10.8.5" resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.5.tgz#f2183d2c59382cb274226034543b9c03b8164c46" @@ -15712,7 +15665,7 @@ object-visit@^1.0.0: dependencies: isobject "^3.0.0" -object.assign@^4.1.0, object.assign@^4.1.2, object.assign@^4.1.4, object.assign@^4.1.7: +object.assign@^4.1.0, object.assign@^4.1.4, object.assign@^4.1.7: version "4.1.7" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.7.tgz#8c14ca1a424c6a561b0bb2a22f66f5049a945d3d" integrity sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw== @@ -15734,7 +15687,7 @@ object.defaults@^1.0.0, object.defaults@^1.1.0: for-own "^1.0.0" isobject "^3.0.0" -object.entries@^1.1.2, object.entries@^1.1.8, object.entries@^1.1.9: +object.entries@^1.1.8, object.entries@^1.1.9: version "1.1.9" resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.9.tgz#e4770a6a1444afb61bd39f984018b5bede25f8b3" integrity sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw== @@ -18683,16 +18636,6 @@ string-length@^6.0.0: dependencies: strip-ansi "^7.1.0" -"string-width-cjs@npm:string-width@^4.2.0": - name string-width-cjs - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - string-width@^1.0.1, string-width@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" @@ -18728,15 +18671,6 @@ string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string-width@^5.0.1, string-width@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" - integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== - dependencies: - eastasianwidth "^0.2.0" - emoji-regex "^9.2.2" - strip-ansi "^7.0.1" - string.prototype.includes@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz#eceef21283640761a81dbe16d6c7171a4edf7d92" @@ -18829,14 +18763,6 @@ stringify-object@^3.3.0: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - name strip-ansi-cjs - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" @@ -20871,16 +20797,6 @@ workspace-tools@^0.27.0: js-yaml "^4.1.0" micromatch "^4.0.0" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": - name wrap-ansi-cjs - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" @@ -20916,15 +20832,6 @@ wrap-ansi@^7.0.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" - integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== - dependencies: - ansi-styles "^6.1.0" - string-width "^5.0.1" - strip-ansi "^7.0.1" - wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"