Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .changeset/eslint-react-v4.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"@bfra.me/eslint-config": minor
---

Upgrade `@eslint-react/eslint-plugin` from v2 to v4

Adopts the unified plugin architecture introduced in v4. Key changes:

- **Unified plugin**: Sub-plugins (`@eslint-react/dom`, `@eslint-react/hooks-extra`, `@eslint-react/naming-convention`, `@eslint-react/web-api`) are now merged into a single `@eslint-react` plugin
- **Recommended ruleset**: Uses `pluginReact.configs.recommended.rules` spread instead of manually listing ~40 individual rules, keeping the config aligned with upstream defaults
- **Removed rules**: Rules deleted in v4 (`no-default-props`, `no-prop-types`, `jsx-no-duplicate-props`, `jsx-uses-vars`, `no-string-refs`, `no-useless-forward-ref`, `prefer-use-state-lazy-initialization`) are no longer configured
- **Hooks coverage**: v4's unified plugin includes hooks rules (`rules-of-hooks`, `exhaustive-deps`) via the recommended config; `eslint-plugin-react-hooks` remains as a peer dependency for consumers using it directly
- **Peer dependency**: Updated from `^2.2.3` to `^4.2.3`
4 changes: 2 additions & 2 deletions packages/eslint-config/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
"@bfra.me/prettier-config": "workspace:*",
"@bfra.me/tsconfig": "workspace:*",
"@bfra.me/works": "workspace:*",
"@eslint-react/eslint-plugin": "2.13.0",
"@eslint-react/eslint-plugin": "4.2.3",
"@eslint/config-inspector": "1.5.0",
"@eslint/core": "1.2.1",
"@next/eslint-plugin-next": "16.2.3",
Expand All @@ -97,7 +97,7 @@
"eslint-typegen": "2.3.1"
},
"peerDependencies": {
"@eslint-react/eslint-plugin": "^2.2.3",
"@eslint-react/eslint-plugin": "^4.2.3",
"@next/eslint-plugin-next": ">=15.5.3",
"@vitest/eslint-plugin": "^1.1.21",
"astro-eslint-parser": "^1.2.2",
Expand Down
89 changes: 7 additions & 82 deletions packages/eslint-config/src/configs/react.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type {Plugin} from '@eslint/core'
import type {Config} from '../config'
import type {
Flatten,
Expand All @@ -25,8 +24,8 @@ const ReactRouterPackages = [
const NextJsPackages = ['next']

const ReactTypeAwareRules: Config['rules'] = {
'react/no-implicit-key': 'warn',
'react/no-leaked-conditional-rendering': 'warn',
'@eslint-react/no-implicit-key': 'warn',
'@eslint-react/no-leaked-conditional-rendering': 'warn',
}

/**
Expand Down Expand Up @@ -77,15 +76,14 @@ export async function react(options: ReactOptions = {}): Promise<Config[]> {
const isTypeAware = typeof tsconfigPath === 'string' && tsconfigPath.trim().length > 0

return requireOf(
['@eslint-react/eslint-plugin', 'eslint-plugin-react-hooks', 'eslint-plugin-react-refresh'],
['@eslint-react/eslint-plugin', 'eslint-plugin-react-refresh'],
async () => {
const [pluginReact, pluginReactHooks, pluginReactRefresh] = await Promise.all([
const [pluginReact, pluginReactRefresh] = await Promise.all([
interopDefault(import('@eslint-react/eslint-plugin')),
interopDefault(import('eslint-plugin-react-hooks')),
import('eslint-plugin-react-refresh').then(m => m.reactRefresh),
] as const)

const plugins = (pluginReact.configs.all as {plugins: Record<string, Plugin>}).plugins
const plugins = pluginReact.configs.all.plugins as NonNullable<Config['plugins']>
const isAllowConstantExport = ReactRefreshAllowConstantExportPackages.some(i =>
isPackageExists(i),
)
Expand All @@ -97,13 +95,8 @@ export async function react(options: ReactOptions = {}): Promise<Config[]> {
{
name: '@bfra.me/react/setup',
plugins: {
react: plugins['@eslint-react'],
'react-dom': plugins['@eslint-react/dom'],
'react-hooks': pluginReactHooks,
'react-hooks-extra': plugins['@eslint-react/hooks-extra'],
'react-naming-convention': plugins['@eslint-react/naming-convention'],
...plugins,
'react-refresh': pluginReactRefresh.plugin,
'react-web-api': plugins['@eslint-react/web-api'],
} as Config['plugins'],
},
{
Expand All @@ -118,28 +111,7 @@ export async function react(options: ReactOptions = {}): Promise<Config[]> {
sourceType: 'module',
},
rules: {
// recommended rules from eslint-plugin-react-dom https://eslint-react.xyz/docs/rules/overview#dom-rules
'react-dom/no-namespace': 'error',
'react-dom/no-dangerously-set-innerhtml': 'warn',
'react-dom/no-dangerously-set-innerhtml-with-children': 'error',
'react-dom/no-find-dom-node': 'error',
'react-dom/no-flush-sync': 'error',
'react-dom/no-hydrate': 'error',
'react-dom/no-missing-button-type': 'warn',
'react-dom/no-missing-iframe-sandbox': 'warn',
'react-dom/no-render': 'error',
'react-dom/no-render-return-value': 'error',
'react-dom/no-script-url': 'warn',
'react-dom/no-unsafe-iframe-sandbox': 'warn',
'react-dom/no-unsafe-target-blank': 'warn',
'react-dom/no-use-form-state': 'error',
'react-dom/no-void-elements-with-children': 'error',

// recommended rules from eslint-plugin-react-hooks-extra https://eslint-react.xyz/docs/rules/overview#hooks-extra-rules
'react-hooks-extra/no-direct-set-state-in-use-effect': 'warn',

// recommended rules eslint-plugin-react-hooks https://github.com/facebook/react/tree/main/packages/eslint-plugin-react-hooks/src/rules
...pluginReactHooks.configs.recommended.rules,
...pluginReact.configs.recommended.rules,

// preconfigured rules from eslint-plugin-react-refresh https://github.com/ArnaudBarre/eslint-plugin-react-refresh/tree/main/src
'react-refresh/only-export-components': [
Expand Down Expand Up @@ -181,53 +153,6 @@ export async function react(options: ReactOptions = {}): Promise<Config[]> {
},
],

// recommended rules from eslint-plugin-react-web-api https://eslint-react.xyz/docs/rules/overview#web-api-rules
'react-web-api/no-leaked-event-listener': 'warn',
'react-web-api/no-leaked-interval': 'warn',
'react-web-api/no-leaked-resize-observer': 'warn',
'react-web-api/no-leaked-timeout': 'warn',

// recommended rules from eslint-plugin-react-x https://eslint-react.xyz/docs/rules/overview#core-rules
'react/jsx-no-comment-textnodes': 'warn',
'react/jsx-no-duplicate-props': 'warn',
'react/jsx-uses-vars': 'warn',
'react/no-access-state-in-setstate': 'error',
'react/no-array-index-key': 'warn',
'react/no-children-count': 'warn',
'react/no-children-for-each': 'warn',
'react/no-children-map': 'warn',
'react/no-children-only': 'warn',
'react/no-children-to-array': 'warn',
'react/no-clone-element': 'warn',
'react/no-component-will-mount': 'error',
'react/no-component-will-receive-props': 'error',
'react/no-component-will-update': 'error',
'react/no-context-provider': 'warn',
'react/no-create-ref': 'error',
'react/no-default-props': 'error',
'react/no-direct-mutation-state': 'error',
'react/no-duplicate-key': 'warn',
'react/no-forward-ref': 'warn',
'react/no-missing-key': 'error',
'react/no-nested-component-definitions': 'error',
'react/no-prop-types': 'error',
'react/no-redundant-should-component-update': 'error',
'react/no-set-state-in-component-did-mount': 'warn',
'react/no-set-state-in-component-did-update': 'warn',
'react/no-set-state-in-component-will-update': 'warn',
'react/no-string-refs': 'error',
'react/no-unnecessary-use-prefix': 'warn',
'react/no-unsafe-component-will-mount': 'warn',
'react/no-unsafe-component-will-receive-props': 'warn',
'react/no-unsafe-component-will-update': 'warn',
'react/no-unstable-context-value': 'warn',
'react/no-unstable-default-props': 'warn',
'react/no-unused-class-component-members': 'warn',
'react/no-unused-state': 'warn',
'react/no-use-context': 'warn',
'react/no-useless-forward-ref': 'warn',
'react/prefer-use-state-lazy-initialization': 'warn',

...overrides,
},
},
Expand Down
Loading
Loading