From 24fede211dd0cb877e7b0bb9d1ad86b449de731e Mon Sep 17 00:00:00 2001 From: Dmytro Kirpa Date: Wed, 7 Jan 2026 15:16:54 +0100 Subject: [PATCH 1/4] feat(eslint-plugin): propagate enforce-use-client workspace rule to public config --- .../eslint.config.js | 2 +- apps/public-docsite-v9/eslint.config.js | 2 +- apps/rit-tests-v9/eslint.config.js | 2 +- .../eslint.config.js | 2 +- ...-71cfa5e8-fb33-4750-9bae-2f68f2541d72.json | 7 +++ packages/eslint-plugin/package.json | 1 + .../src/flat-configs/react/config.js | 2 + .../src/flat-configs/react/index.js | 4 ++ packages/eslint-plugin/src/internal.js | 1 - .../eslint.config.js | 2 +- .../eslint-plugin-react-components/README.md | 57 +++++++++++++++++++ .../etc/eslint-plugin-react-components.api.md | 32 +++++++---- .../package.json | 6 +- .../src/index.ts | 47 ++++++++------- .../src}/rules/enforce-use-client.spec.ts | 0 .../src}/rules/enforce-use-client.ts | 28 ++++++--- .../eslint.config.js | 2 +- .../react-storybook-addon/eslint.config.js | 2 +- .../react-components/recipes/eslint.config.js | 2 +- .../theme-designer/eslint.config.js | 2 +- scripts/monorepo/src/getDependencies.spec.js | 13 +++-- tools/eslint-rules/index.ts | 2 - 22 files changed, 160 insertions(+), 58 deletions(-) create mode 100644 change/@fluentui-eslint-plugin-react-components-71cfa5e8-fb33-4750-9bae-2f68f2541d72.json rename {tools/eslint-rules => packages/react-components/eslint-plugin-react-components/src}/rules/enforce-use-client.spec.ts (100%) rename {tools/eslint-rules => packages/react-components/eslint-plugin-react-components/src}/rules/enforce-use-client.ts (94%) diff --git a/apps/perf-test-react-components/eslint.config.js b/apps/perf-test-react-components/eslint.config.js index b68b359b2cec1..0884d266211dc 100644 --- a/apps/perf-test-react-components/eslint.config.js +++ b/apps/perf-test-react-components/eslint.config.js @@ -10,7 +10,7 @@ module.exports = [ '@typescript-eslint/explicit-module-boundary-types': 'off', 'no-console': 'off', '@nx/workspace-no-restricted-globals': 'off', - '@nx/workspace-enforce-use-client': 'off', + '@fluentui/react-components/enforce-use-client': 'off', }, }, ]; diff --git a/apps/public-docsite-v9/eslint.config.js b/apps/public-docsite-v9/eslint.config.js index b5f149cd9f79b..9daac17059b99 100644 --- a/apps/public-docsite-v9/eslint.config.js +++ b/apps/public-docsite-v9/eslint.config.js @@ -11,7 +11,7 @@ module.exports = [ '@typescript-eslint/explicit-module-boundary-types': 'off', 'import/no-extraneous-dependencies': ['error', { packageDir: ['.', '../..'] }], '@typescript-eslint/no-deprecated': 'off', - '@nx/workspace-enforce-use-client': 'off', + '@fluentui/react-components/enforce-use-client': 'off', }, }, ]; diff --git a/apps/rit-tests-v9/eslint.config.js b/apps/rit-tests-v9/eslint.config.js index 625a2a5a14d21..34a75d1e646fa 100644 --- a/apps/rit-tests-v9/eslint.config.js +++ b/apps/rit-tests-v9/eslint.config.js @@ -9,7 +9,7 @@ module.exports = [ ...fluentPlugin.configs['flat/react'], { rules: { - '@nx/workspace-enforce-use-client': 'off', + '@fluentui/react-components/enforce-use-client': 'off', '@nx/workspace-no-restricted-globals': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', 'import/no-extraneous-dependencies': 'off', diff --git a/apps/vr-tests-react-components/eslint.config.js b/apps/vr-tests-react-components/eslint.config.js index a4da8c2c285c4..317d76524a5fa 100644 --- a/apps/vr-tests-react-components/eslint.config.js +++ b/apps/vr-tests-react-components/eslint.config.js @@ -15,7 +15,7 @@ module.exports = [ 'import/no-extraneous-dependencies': ['error', { packageDir: ['.', '../..'] }], '@nx/workspace-no-restricted-globals': 'off', '@typescript-eslint/no-deprecated': 'off', - '@nx/workspace-enforce-use-client': 'off', + '@fluentui/react-components/enforce-use-client': 'off', }, }, ]; diff --git a/change/@fluentui-eslint-plugin-react-components-71cfa5e8-fb33-4750-9bae-2f68f2541d72.json b/change/@fluentui-eslint-plugin-react-components-71cfa5e8-fb33-4750-9bae-2f68f2541d72.json new file mode 100644 index 0000000000000..e8f9376f0edf3 --- /dev/null +++ b/change/@fluentui-eslint-plugin-react-components-71cfa5e8-fb33-4750-9bae-2f68f2541d72.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "feat: add enforce-use-client workspace rule", + "packageName": "@fluentui/eslint-plugin-react-components", + "email": "dmytrokirpa@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index a89927ccb6ec5..33560ff494e3d 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -12,6 +12,7 @@ "dependencies": { "@eslint/compat": "1.3.0", "@griffel/eslint-plugin": "^2.0.0", + "@fluentui/eslint-plugin-react-components": "^0.1.4", "@rnx-kit/eslint-plugin": "^0.8.4", "@typescript-eslint/type-utils": "^8.46.2", "@typescript-eslint/utils": "^8.46.2", diff --git a/packages/eslint-plugin/src/flat-configs/react/config.js b/packages/eslint-plugin/src/flat-configs/react/config.js index 3e4bbb9c5d501..aed946238c2bf 100644 --- a/packages/eslint-plugin/src/flat-configs/react/config.js +++ b/packages/eslint-plugin/src/flat-configs/react/config.js @@ -6,6 +6,7 @@ const griffelPlugin = require('@griffel/eslint-plugin'); const configHelpers = require('../../utils/configHelpers'); const { fixupPluginRules } = require('@eslint/compat'); const { defineConfig } = require('eslint/config'); +const reactComponentsPlugin = require('@fluentui/eslint-plugin-react-components'); /** @type { import("eslint").Linter.Config } */ module.exports = defineConfig( @@ -16,6 +17,7 @@ module.exports = defineConfig( '@griffel': fixupPluginRules(/** @type {any} */ (griffelPlugin)), 'jsx-a11y': jsxA11yPlugin, 'react-hooks': reactHooksPlugin, + '@fluentui/react-components': reactComponentsPlugin, }, languageOptions: { parserOptions: { diff --git a/packages/eslint-plugin/src/flat-configs/react/index.js b/packages/eslint-plugin/src/flat-configs/react/index.js index b4446812d5155..3d645f08a453f 100644 --- a/packages/eslint-plugin/src/flat-configs/react/index.js +++ b/packages/eslint-plugin/src/flat-configs/react/index.js @@ -42,6 +42,7 @@ module.exports = defineConfig( }, ], '@fluentui/ban-instanceof-html-element': ['error'], + '@fluentui/react-components/enforce-use-client': ['error'], '@fluentui/no-context-default-value': [ 'error', { @@ -78,6 +79,7 @@ module.exports = defineConfig( }, ], 'react-compiler/react-compiler': 'off', + '@fluentui/react-components/enforce-use-client': 'off', }, }, { @@ -98,6 +100,7 @@ module.exports = defineConfig( ], }, ], + '@fluentui/react-components/enforce-use-client': 'off', }, }, @@ -105,6 +108,7 @@ module.exports = defineConfig( files: ['**/*.test.{ts,tsx}'], rules: { 'react-compiler/react-compiler': 'off', + '@fluentui/react-components/enforce-use-client': 'off', }, }, __internal.overrides.react, diff --git a/packages/eslint-plugin/src/internal.js b/packages/eslint-plugin/src/internal.js index 3470ac54a4fee..493ccabd6fbd7 100644 --- a/packages/eslint-plugin/src/internal.js +++ b/packages/eslint-plugin/src/internal.js @@ -33,7 +33,6 @@ const __internal = { '@nx/workspace-consistent-callback-type': 'error', '@nx/workspace-no-restricted-globals': restrictedGlobals.react, '@nx/workspace-no-missing-jsx-pragma': ['error', { runtime: 'automatic' }], - '@nx/workspace-enforce-use-client': 'error', }, } : null, diff --git a/packages/react-components/babel-preset-global-context/eslint.config.js b/packages/react-components/babel-preset-global-context/eslint.config.js index 0e5960e204ae0..7a5c8403ce90c 100644 --- a/packages/react-components/babel-preset-global-context/eslint.config.js +++ b/packages/react-components/babel-preset-global-context/eslint.config.js @@ -6,7 +6,7 @@ module.exports = [ ...fluentPlugin.configs['flat/node'], { rules: { - '@nx/workspace-enforce-use-client': 'off', + '@fluentui/react-components/enforce-use-client': 'off', }, }, ]; diff --git a/packages/react-components/eslint-plugin-react-components/README.md b/packages/react-components/eslint-plugin-react-components/README.md index 0b22544de6553..da8ebc6c7b16b 100644 --- a/packages/react-components/eslint-plugin-react-components/README.md +++ b/packages/react-components/eslint-plugin-react-components/README.md @@ -77,6 +77,63 @@ import { DefaultButton } from '@fluentui/react'; const Component = () => ...; ``` +### enforce-use-client + +Ensures that source files using client-only React features begin with the top-level `'use client'` directive, and flags files that include the directive unnecessarily. + +The rule looks for any of the following client-only features: + +- React client hooks and APIs (e.g. `useState`, `useEffect`, `useRef`, `forwardRef`, `memo`) +- Custom hooks (functions whose name starts with `use` and are not in the safe set: `use`, `useId`) +- JSX event handler props (properties starting with `on` followed by a capital letter, like `onClick`) +- Direct references to browser globals (`window`, `document`, `navigator`, `localStorage`, `sessionStorage`, `history`, `location`) + +If at least one feature is present, the directive must be the very first statement in the file. If no features are found, any existing `'use client'` directive will be reported as unnecessary and auto-fixed. + +#### ❌ Don't (missing directive) + +```ts +import * as React from 'react'; + +export function MyComponent() { + const [value, setValue] = React.useState(''); + return ; +} +``` + +#### ✅ Do (directive present) + +```ts +'use client'; +import * as React from 'react'; + +export function MyComponent() { + const [value, setValue] = React.useState(''); + return ; +} +``` + +#### ❌ Don't (unnecessary directive) + +```ts +'use client'; +// Pure utilities – no client-only APIs +export function add(a: number, b: number) { + return a + b; +} +``` + +#### ✅ Do (directive removed) + +```ts +// Pure utilities – no client-only APIs +export function add(a: number, b: number) { + return a + b; +} +``` + +No options – enable to enforce consistent usage of the directive. + ## License This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. diff --git a/packages/react-components/eslint-plugin-react-components/etc/eslint-plugin-react-components.api.md b/packages/react-components/eslint-plugin-react-components/etc/eslint-plugin-react-components.api.md index d4416ee6720ef..20c09f7dbd67b 100644 --- a/packages/react-components/eslint-plugin-react-components/etc/eslint-plugin-react-components.api.md +++ b/packages/react-components/eslint-plugin-react-components/etc/eslint-plugin-react-components.api.md @@ -4,24 +4,34 @@ ```ts +import type { ESLint } from 'eslint'; import { RuleListener } from '@typescript-eslint/utils/dist/ts-eslint'; import { RuleModule } from '@typescript-eslint/utils/dist/ts-eslint'; // @public (undocumented) -export const plugin: { - meta: { - name: string; - version: string; +export const configs: { + recommended: { + plugins: string[]; + rules: {}; }; - configs: { - recommended: { - plugins: string[]; - rules: {}; + 'flat/recommended': { + plugins: { + [x: string]: ESLint.Plugin; }; + rules: {}; }; - rules: { - "prefer-fluentui-v9": RuleModule<"replaceFluent8With9" | "replaceIconWithJsx" | "replaceStackWithFlex" | "replaceFocusZoneWithTabster", {}[], unknown, RuleListener>; - }; +}; + +// @public (undocumented) +export const meta: { + name: string; + version: string; +}; + +// @public (undocumented) +export const rules: { + "enforce-use-client": RuleModule<"missingUseClient" | "unnecessaryUseClient", [], unknown, RuleListener>; + "prefer-fluentui-v9": RuleModule<"replaceFluent8With9" | "replaceIconWithJsx" | "replaceStackWithFlex" | "replaceFocusZoneWithTabster", {}[], unknown, RuleListener>; }; // (No @packageDocumentation comment for this package) diff --git a/packages/react-components/eslint-plugin-react-components/package.json b/packages/react-components/eslint-plugin-react-components/package.json index 39131a19ccd1b..406ada0515a3f 100644 --- a/packages/react-components/eslint-plugin-react-components/package.json +++ b/packages/react-components/eslint-plugin-react-components/package.json @@ -18,9 +18,9 @@ "@swc/helpers": "^0.5.1" }, "peerDependencies": { - "typescript-eslint": "^8.46.2", - "eslint": "^8.0.0", - "typescript": "^5.0.0" + "typescript-eslint": ">= 8.46.2", + "eslint": ">= 8.0.0", + "typescript": ">= 5.0.0" }, "exports": { ".": { diff --git a/packages/react-components/eslint-plugin-react-components/src/index.ts b/packages/react-components/eslint-plugin-react-components/src/index.ts index e3ed57c99b71f..b3884c69a70d6 100644 --- a/packages/react-components/eslint-plugin-react-components/src/index.ts +++ b/packages/react-components/eslint-plugin-react-components/src/index.ts @@ -1,37 +1,44 @@ +import type { ESLint } from 'eslint'; + import { name, version } from '../package.json'; +import { RULE_NAME as enforceUseClientName, rule as enforceUseClient } from './rules/enforce-use-client'; import { RULE_NAME as preferFluentUIV9Name, rule as preferFluentUIV9 } from './rules/prefer-fluentui-v9'; -const allRules = { +export const meta = { + name, + version, +}; +export const rules = { + [enforceUseClientName]: enforceUseClient, [preferFluentUIV9Name]: preferFluentUIV9, }; -const configs = { +const recommendedRules = { + // Add rules to the recommended config here in the future +}; + +export const configs = { recommended: { plugins: [name], - rules: { - // add all recommended rules here - }, + rules: recommendedRules, + }, + 'flat/recommended': { + // Define plugins as an object to satisfy ESLint v9 flat config format + // the actual plugin will be assigned later to avoid circular dependencies + plugins: { [name]: {} as ESLint.Plugin }, + rules: recommendedRules, }, }; -// Plugin definition -export const plugin = { - meta: { - name, - version, - }, +const plugin = { + meta, configs, - rules: allRules, + rules, }; // Flat config for eslint v9 -Object.assign(configs, { - flat: { - recommended: { - plugins: { [name]: plugin }, - rules: configs.recommended.rules, - }, - }, -}); +configs['flat/recommended'].plugins = { + [name]: plugin as unknown as ESLint.Plugin, +}; module.exports = plugin; diff --git a/tools/eslint-rules/rules/enforce-use-client.spec.ts b/packages/react-components/eslint-plugin-react-components/src/rules/enforce-use-client.spec.ts similarity index 100% rename from tools/eslint-rules/rules/enforce-use-client.spec.ts rename to packages/react-components/eslint-plugin-react-components/src/rules/enforce-use-client.spec.ts diff --git a/tools/eslint-rules/rules/enforce-use-client.ts b/packages/react-components/eslint-plugin-react-components/src/rules/enforce-use-client.ts similarity index 94% rename from tools/eslint-rules/rules/enforce-use-client.ts rename to packages/react-components/eslint-plugin-react-components/src/rules/enforce-use-client.ts index ff585bf3359dd..f25b4b1cf4c1c 100644 --- a/tools/eslint-rules/rules/enforce-use-client.ts +++ b/packages/react-components/eslint-plugin-react-components/src/rules/enforce-use-client.ts @@ -1,6 +1,8 @@ -import { ESLintUtils, AST_NODE_TYPES, TSESTree } from '@typescript-eslint/utils'; +import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/utils'; -// NOTE: The rule will be available in ESLint configs as "@nx/workspace-enforce-use-client" +import { createRule } from './utils/create-rule'; + +// NOTE: The rule will be available in ESLint configs as "@fluentui/react-components/enforce-use-client" export const RULE_NAME = 'enforce-use-client'; type MessageIds = 'missingUseClient' | 'unnecessaryUseClient'; @@ -103,7 +105,7 @@ const isPotentialCustomHook = (name: string): boolean => /** * ESLint rule configuration and metadata */ -export const rule = ESLintUtils.RuleCreator(() => __filename)<[], MessageIds>({ +export const rule = createRule<[], MessageIds>({ name: RULE_NAME, meta: { type: 'problem', @@ -192,7 +194,9 @@ export const rule = ESLintUtils.RuleCreator(() => __filename)<[], MessageIds>({ * Check function calls for React APIs and custom hooks */ CallExpression(node: TSESTree.CallExpression) { - if (shouldSkipAnalysis()) return; + if (shouldSkipAnalysis()) { + return; + } if (node.callee.type === AST_NODE_TYPES.Identifier) { const name = node.callee.name; @@ -218,7 +222,9 @@ export const rule = ESLintUtils.RuleCreator(() => __filename)<[], MessageIds>({ * Check JSX attributes for event handlers */ JSXAttribute(node: TSESTree.JSXAttribute) { - if (shouldSkipAnalysis()) return; + if (shouldSkipAnalysis()) { + return; + } if (node.name.type === AST_NODE_TYPES.JSXIdentifier && isEventHandler(node.name.name)) { recordFirstClientFeature('event_handler', node.name.name, node); @@ -229,7 +235,9 @@ export const rule = ESLintUtils.RuleCreator(() => __filename)<[], MessageIds>({ * Check member expressions for browser APIs */ MemberExpression(node: TSESTree.MemberExpression) { - if (shouldSkipAnalysis()) return; + if (shouldSkipAnalysis()) { + return; + } if (node.object.type === AST_NODE_TYPES.Identifier && BROWSER_GLOBALS.has(node.object.name)) { recordFirstClientFeature('browser_api', node.object.name, node); @@ -275,10 +283,14 @@ export const rule = ESLintUtils.RuleCreator(() => __filename)<[], MessageIds>({ } // If there are no client features and no directive, nothing to do - if (!hasClientFeatures) return; + if (!hasClientFeatures) { + return; + } // Already has correct directive - if (ruleState.topDirectivePresent) return; + if (ruleState.topDirectivePresent) { + return; + } // Report error on the specific problematic API call for better DX const clientFeatureDetection = ruleState.firstClientFeature!; diff --git a/packages/react-components/react-storybook-addon-export-to-sandbox/eslint.config.js b/packages/react-components/react-storybook-addon-export-to-sandbox/eslint.config.js index 106ef65601bec..821668e38a649 100644 --- a/packages/react-components/react-storybook-addon-export-to-sandbox/eslint.config.js +++ b/packages/react-components/react-storybook-addon-export-to-sandbox/eslint.config.js @@ -7,7 +7,7 @@ module.exports = defineConfig([ ...fluentPlugin.configs['flat/node'], { rules: { - '@nx/workspace-enforce-use-client': 'off', + '@fluentui/react-components/enforce-use-client': 'off', }, }, ]); diff --git a/packages/react-components/react-storybook-addon/eslint.config.js b/packages/react-components/react-storybook-addon/eslint.config.js index ac31b0b01860d..d21eda3d690b2 100644 --- a/packages/react-components/react-storybook-addon/eslint.config.js +++ b/packages/react-components/react-storybook-addon/eslint.config.js @@ -8,7 +8,7 @@ module.exports = [ files: ['**/*.ts', '**/*.tsx'], rules: { '@griffel/styles-file': 'off', - '@nx/workspace-enforce-use-client': 'off', + '@fluentui/react-components/enforce-use-client': 'off', }, }, ]; diff --git a/packages/react-components/recipes/eslint.config.js b/packages/react-components/recipes/eslint.config.js index d6a86298a4677..8090526476bdf 100644 --- a/packages/react-components/recipes/eslint.config.js +++ b/packages/react-components/recipes/eslint.config.js @@ -6,7 +6,7 @@ module.exports = [ ...rootConfig, { rules: { - '@nx/workspace-enforce-use-client': 'off', + '@fluentui/react-components/enforce-use-client': 'off', }, }, ]; diff --git a/packages/react-components/theme-designer/eslint.config.js b/packages/react-components/theme-designer/eslint.config.js index bed0583efd44b..bb043204474e5 100644 --- a/packages/react-components/theme-designer/eslint.config.js +++ b/packages/react-components/theme-designer/eslint.config.js @@ -8,7 +8,7 @@ module.exports = [ rules: { '@griffel/styles-file': 'off', '@nx/workspace-no-restricted-globals': 'off', - '@nx/workspace-enforce-use-client': 'off', + '@fluentui/react-components/enforce-use-client': 'off', 'prefer-exponentiation-operator': 'off', }, }, diff --git a/scripts/monorepo/src/getDependencies.spec.js b/scripts/monorepo/src/getDependencies.spec.js index ab3307b419730..71bc109ec3394 100644 --- a/scripts/monorepo/src/getDependencies.spec.js +++ b/scripts/monorepo/src/getDependencies.spec.js @@ -50,17 +50,22 @@ describe(`#getDependencies`, () => { Object { "dependencyType": "devDependencies", "isTopLevel": true, - "name": "eslint-plugin", + "name": "react-conformance", }, Object { "dependencyType": "devDependencies", "isTopLevel": true, - "name": "react-conformance", + "name": "react-conformance-griffel", }, Object { "dependencyType": "devDependencies", - "isTopLevel": true, - "name": "react-conformance-griffel", + "isTopLevel": false, + "name": "eslint-plugin", + }, + Object { + "dependencyType": "dependencies", + "isTopLevel": false, + "name": "eslint-plugin-react-components", }, Object { "dependencyType": "devDependencies", diff --git a/tools/eslint-rules/index.ts b/tools/eslint-rules/index.ts index f7173773b908a..6aeaae6c06da8 100644 --- a/tools/eslint-rules/index.ts +++ b/tools/eslint-rules/index.ts @@ -4,7 +4,6 @@ import { RULE_NAME as consistentCallbackTypeName, rule as consistentCallbackType, } from './rules/consistent-callback-type'; -import { RULE_NAME as enforceUseClientName, rule as enforceUseClient } from './rules/enforce-use-client'; /** * Import your custom workspace rules at the top of this file. @@ -35,6 +34,5 @@ module.exports = { [consistentCallbackTypeName]: consistentCallbackType, [noRestrictedGlobalsName]: noRestrictedGlobals, [noMissingJsxPragmaName]: noMissingJsxPragma, - [enforceUseClientName]: enforceUseClient, }, }; From e2d0357de3bc8efa00615726c15ded103358f51a Mon Sep 17 00:00:00 2001 From: Dmytro Kirpa Date: Wed, 7 Jan 2026 18:55:29 +0100 Subject: [PATCH 2/4] fixup --- packages/eslint-plugin/package.json | 1 - scripts/monorepo/src/getDependencies.spec.js | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 33560ff494e3d..a89927ccb6ec5 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -12,7 +12,6 @@ "dependencies": { "@eslint/compat": "1.3.0", "@griffel/eslint-plugin": "^2.0.0", - "@fluentui/eslint-plugin-react-components": "^0.1.4", "@rnx-kit/eslint-plugin": "^0.8.4", "@typescript-eslint/type-utils": "^8.46.2", "@typescript-eslint/utils": "^8.46.2", diff --git a/scripts/monorepo/src/getDependencies.spec.js b/scripts/monorepo/src/getDependencies.spec.js index 71bc109ec3394..985a36743bcc3 100644 --- a/scripts/monorepo/src/getDependencies.spec.js +++ b/scripts/monorepo/src/getDependencies.spec.js @@ -63,7 +63,7 @@ describe(`#getDependencies`, () => { "name": "eslint-plugin", }, Object { - "dependencyType": "dependencies", + "dependencyType": "devDependencies", "isTopLevel": false, "name": "eslint-plugin-react-components", }, From 7b2e38c1e78fbdf1f862f1502ed086122a1d8a71 Mon Sep 17 00:00:00 2001 From: Dmytro Kirpa Date: Thu, 8 Jan 2026 09:30:32 +0100 Subject: [PATCH 3/4] add comment to clarify import of react-components plugin as a dev dependency --- packages/eslint-plugin/src/flat-configs/react/config.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/eslint-plugin/src/flat-configs/react/config.js b/packages/eslint-plugin/src/flat-configs/react/config.js index aed946238c2bf..1481bb2c4a316 100644 --- a/packages/eslint-plugin/src/flat-configs/react/config.js +++ b/packages/eslint-plugin/src/flat-configs/react/config.js @@ -6,6 +6,8 @@ const griffelPlugin = require('@griffel/eslint-plugin'); const configHelpers = require('../../utils/configHelpers'); const { fixupPluginRules } = require('@eslint/compat'); const { defineConfig } = require('eslint/config'); + +// eslint-disable-next-line import/no-extraneous-dependencies -- dev dependency provided by the Nx workspace const reactComponentsPlugin = require('@fluentui/eslint-plugin-react-components'); /** @type { import("eslint").Linter.Config } */ From 6589b1344e0733adb0276f777d47b97180e199a6 Mon Sep 17 00:00:00 2001 From: Dmytro Kirpa Date: Thu, 8 Jan 2026 09:44:13 +0100 Subject: [PATCH 4/4] update snapshot --- scripts/monorepo/src/getDependencies.spec.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/monorepo/src/getDependencies.spec.js b/scripts/monorepo/src/getDependencies.spec.js index 3e503e3a55422..985a36743bcc3 100644 --- a/scripts/monorepo/src/getDependencies.spec.js +++ b/scripts/monorepo/src/getDependencies.spec.js @@ -62,6 +62,11 @@ describe(`#getDependencies`, () => { "isTopLevel": false, "name": "eslint-plugin", }, + Object { + "dependencyType": "devDependencies", + "isTopLevel": false, + "name": "eslint-plugin-react-components", + }, Object { "dependencyType": "devDependencies", "isTopLevel": false,