diff --git a/.gitignore b/.gitignore index bf4c207909..34f5128a2e 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ coverage/ .eslintcache # misc +/log .DS_Store *.pem @@ -38,11 +39,9 @@ yarn-error.log* # vercel .vercel - stats.html *.config-*.mjs /eslint-config.json *.bundled_*.mjs *.tgz eslint-results.sarif -.tsup diff --git a/.pkgs/configs/package.json b/.pkgs/configs/package.json index 368a96f441..64841fdc3f 100644 --- a/.pkgs/configs/package.json +++ b/.pkgs/configs/package.json @@ -23,14 +23,14 @@ "@stylistic/eslint-plugin": "^5.4.0", "eslint-plugin-de-morgan": "^1.3.1", "eslint-plugin-function": "^0.0.30", - "eslint-plugin-jsdoc": "^60.0.0", + "eslint-plugin-jsdoc": "^60.3.1", "eslint-plugin-perfectionist": "^4.15.0", "eslint-plugin-regexp": "^2.10.0", "eslint-plugin-unicorn": "^61.0.2", - "typescript-eslint": "^8.44.0" + "typescript-eslint": "^8.44.1" }, "peerDependencies": { "eslint": "^9.36.0", - "typescript": "^4.9.5 || ^5.4.5" + "typescript": "^5.9.2" } } diff --git a/.pkgs/eslint-plugin-local/package.json b/.pkgs/eslint-plugin-local/package.json index 4540256479..ab3aea83a2 100644 --- a/.pkgs/eslint-plugin-local/package.json +++ b/.pkgs/eslint-plugin-local/package.json @@ -28,12 +28,12 @@ "@eslint-react/var": "workspace:*", "@eslint/js": "^9.36.0", "@stylistic/eslint-plugin": "^5.4.0", - "@typescript-eslint/scope-manager": "^8.44.0", - "@typescript-eslint/type-utils": "^8.44.0", - "@typescript-eslint/types": "^8.44.0", - "@typescript-eslint/utils": "^8.44.0", + "@typescript-eslint/scope-manager": "^8.44.1", + "@typescript-eslint/type-utils": "^8.44.1", + "@typescript-eslint/types": "^8.44.1", + "@typescript-eslint/utils": "^8.44.1", "eslint-plugin-de-morgan": "^1.3.1", - "eslint-plugin-jsdoc": "^60.0.0", + "eslint-plugin-jsdoc": "^60.3.1", "eslint-plugin-perfectionist": "^4.15.0", "eslint-plugin-regexp": "^2.10.0", "eslint-plugin-unicorn": "^61.0.2", @@ -44,11 +44,11 @@ "@local/configs": "workspace:*", "@types/react": "^19.1.13", "@types/react-dom": "^19.1.9", - "tsdown": "^0.15.3" + "tsdown": "^0.15.4" }, "peerDependencies": { "eslint": "^9.36.0", - "typescript": "^4.9.5 || ^5.4.5" + "typescript": "^5.9.2" }, "engines": { "node": ">=20.19.0" diff --git a/CHANGELOG.md b/CHANGELOG.md index 17d6ac8ee2..492f2a109b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,118 @@ +## v2.0.0 (TBD) + +### ๐Ÿ’ฅ Breaking Changes + +**Target Environment Updates: Now ESM and ESLint Flat Config Only** + +- Drop support for CommonJS (CJS) module format, packages are now distributed only as ECMAScript Modules (ESM) +- Drop support for ESLint legacy config system, packages now support only ESLint Flat Config (`eslint.config.js`) +- Drop support for Node.js 16 and 18, minimum required version is now Node.js 20 +- Drop support for ESLint 8, minimum required version is now ESLint 9.3.6 +- Drop support for TypeScript 4, minimum required version is now TypeScript 5.9.2 + +**Removed Rules** + +| Rule | Replaced by | Reason | +| :--------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------- | :----------- | +| react-x/avoid-shorthand-boolean | [`react-x/jsx-shorthand-boolean`](/docs/rules/jsx-shorthand-boolean) | consolidated | +| react-x/avoid-shorthand-fragment | [`react-x/jsx-shorthand-fragment`](/docs/rules/jsx-shorthand-fragment) | consolidated | +| react-x/ensure-forward-ref-using-ref | [`react-x/no-useless-forward-ref`](/docs/rules/no-useless-forward-ref) | renamed | +| react-x/jsx-no-duplicate-props | [`react-x/jsx-no-duplicate-props`](/docs/rules/jsx-no-duplicate-props) | renamed | +| react-x/no-comment-textnodes | [`react-x/jsx-no-comment-textnodes`](/docs/rules/jsx-no-comment-textnodes) | renamed | +| react-x/no-complicated-conditional-rendering | | discontinued | +| react-x/no-nested-components | [`react-x/no-nested-component-definitions`](/docs/rules/no-nested-component-definitions) | renamed | +| react-x/prefer-react-namespace-import | [`react-x/prefer-namespace-import`](/docs/rules/prefer-namespace-import) | renamed | +| react-x/prefer-shorthand-boolean | [`react-x/jsx-shorthand-boolean`](/docs/rules/jsx-shorthand-boolean) | consolidated | +| react-x/prefer-shorthand-fragment | [`react-x/jsx-shorthand-fragment`](/docs/rules/jsx-shorthand-fragment) | consolidated | +| react-x/use-jsx-vars | [`react-x/jsx-uses-vars`](/docs/rules/jsx-uses-vars) | renamed | +| react-dom/no-children-in-void-dom-elements | [`react-dom/no-void-elements-with-children`](/docs/rules/dom-no-void-elements-with-children) | renamed | +| react-hooks-extra/no-direct-set-state-in-use-layout-effect | [`react-hooks-extra/no-direct-set-state-in-use-effect`](/docs/rules/hooks-extra-no-direct-set-state-in-use-effect) | consolidated | +| react-hooks-extra/no-unnecessary-use-callback | [`react-x/no-unnecessary-use-callback`](/docs/rules/no-unnecessary-use-callback) | relocated | +| react-hooks-extra/no-unnecessary-use-memo | [`react-x/no-unnecessary-use-memo`](/docs/rules/no-unnecessary-use-memo) | relocated | +| react-hooks-extra/no-unnecessary-use-prefix | [`react-x/no-unnecessary-use-prefix`](/docs/rules/no-unnecessary-use-prefix) | relocated | +| react-hooks-extra/prefer-use-state-lazy-initialization | [`react-x/prefer-use-state-lazy-initialization`](/docs/rules/prefer-use-state-lazy-initialization) | relocated | + +**Removed Presets** + +| Preset | Replaced by | Reason | +| :-------------------------------- | :------------ | :----------- | +| `core` | `x` | renamed | +| `core-legacy` | | discontinued | +| `off-dom` | `disable-dom` | renamed | +| `off-dom-legacy` | | discontinued | +| `x-legacy` | | discontinued | +| `dom-legacy` | | discontinued | +| `web-api-legacy` | | discontinued | +| `recommended-legacy` | | discontinued | +| `recommended-typescript-legacy` | | discontinued | +| `recommended-type-checked-legacy` | | discontinued | + +**Removed Settings** + +| Setting | Replaced by | Reason | +| :--------------------- | :---------- | :----------- | +| `additionalComponents` | | discontinued | +| `additionalHooks` | | discontinued | +| `skipImportCheck` | | discontinued | + +The rule implementations have been refactored to improve performance and maintainability. + +### โœจ New + +**Added the following new rules:** + +- `react-x/jsx-shorthand-boolean`: Enforces a consistent style for boolean attributes +- `react-x/jsx-shorthand-fragment`: Enforces a consistent style for React Fragments +- `react-x/no-forbidden-props`: Disallows specific props on components +- `react-x/no-unnecessary-key`: Reports unnecessary `key` props on elements +- `react-x/no-unused-props`: Reports unused props in components +- `react-dom/no-string-style-prop`: Disallows string values for the `style` prop +- `react-dom/prefer-namespace-import`: Enforces using a namespace import for `react-dom` + +**Added the following new rule to the `recommended-type-checked` preset:** + +- `react-x/no-unused-props`: Reports unused props in components + +**The following rules now support Codemod features:** + +- `react-x/no-component-did-update` +- `react-x/no-component-will-receive-props` +- `react-x/no-component-will-update` +- `react-x/no-context-provider` +- `react-x/no-forward-ref` +- `react-x/no-string-refs` + +**The following rules now support auto-fix:** + +- `react-x/prefer-namespace-import` +- `react-dom/prefer-namespace-import` + +**The following rules now support suggestion fixes:** + +- `react-dom/no-missing-button-type` +- `react-dom/no-missing-iframe-sandbox` +- `react-dom/no-unsafe-target-blank` + +**New configuration preset added:** + +- `disable-conflict-eslint-plugin-react`: Disable rules in `eslint-plugin-react` that conflict with rules in our plugins + +### ๐Ÿž Fixes + +- fix(react-x/no-unnecessary-use-prefix): fix false positive of React Hooks defined within the callback function of `vi.mock(...)` in Vitest test files +- fix(react-web-api/no-leaked-event-listener): fix `useEffect` setup function check to handle `React.useEffect()` calls correctly +- fix(react-naming-convention/filename): fix false positive on well-known filenames like `404.tsx`, `_app.tsx`, `[slug].tsx` + +### ๐Ÿช„ Improvements + +- refactor: simplify React APIs detection logic +- refactor: cleanup utilities and simplify rule implementations +- docs: add comparison table between `eslint-plugin-react` and `eslint-react` rules +- docs: replace `tseslint.config` with `defineConfig` in all examples +- build: migrate build system from `tsup` to `tsdown` for better performance + +**Full Changelog**: https://github.com/Rel1cx/eslint-react/compare/v1.53.1...v2.0.0 + ## v1.53.1 (2025-09-11) ### ๐Ÿž Fixes diff --git a/README.md b/README.md index 21315ceab3..29fb6d4c8d 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ - [TypeScript Specialized](#typescript-specialized) - [Other](#other) - [Rules](#rules) +- [Benchmark](#benchmark) - [FAQ](#faq) - [Roadmap](#roadmap) - [Contributing](#contributing) @@ -29,10 +30,10 @@ ## Features -- **Modern**: First-class support for TypeScript, React 19, and more. -- **Flexible**: Fully customizable rule severity levels, allowing you to enforce or relax rules as needed. -- **Performant**: Built with performance in mind, optimized for large codebases, **4-7x faster** than other ESLint plugins. -- **Context-aware Linting**: Rules that understand the context of your code and project configuration to provide more accurate linting. +- **Modern**: First-class support for **TypeScript**, **React 19**, and more. +- **Flexible**: Fully customizable rule severity levels, allowing you to **enforce** or **relax** rules as needed. +- **Performant**: Built with performance in mind, optimized for large codebases, [**4-7x faster**](https://github.com/Rel1cx/eslint-react-benchmark) than other ESLint plugins. +- **Context-aware Linting**: Rules that understand the context of your code and [project configuration](https://eslint-react.xyz/docs/configuration/configure-project-config) to provide more **accurate** linting. ## Public Packages @@ -53,8 +54,8 @@ > [!NOTE]\ > ESLint React requires the following minimum versions: > -> - Node.js: 18.18.0 -> - ESLint: 8.57.0 +> - Node.js: 20.19.0 +> - ESLint: 9.24.0 > - TypeScript: 4.9.5 ### Install @@ -68,46 +69,46 @@ npm install --save-dev typescript-eslint @eslint-react/eslint-plugin ```js // eslint.config.js -// @ts-check import eslintReact from "@eslint-react/eslint-plugin"; import eslintJs from "@eslint/js"; +import { defineConfig } from "eslint/config"; import tseslint from "typescript-eslint"; -export default tseslint.config({ - files: ["**/*.ts", "**/*.tsx"], - - // Extend recommended rule sets from: - // 1. ESLint JS's recommended rules - // 2. TypeScript ESLint recommended rules - // 3. ESLint React's recommended-typescript rules - extends: [ - eslintJs.configs.recommended, - tseslint.configs.recommended, - eslintReact.configs["recommended-typescript"], - ], - - // Configure language/parsing options - languageOptions: { - // Use TypeScript ESLint parser for TypeScript files - parser: tseslint.parser, - parserOptions: { - // Enable project service for better TypeScript integration - projectService: true, - tsconfigRootDir: import.meta.dirname, +export default defineConfig([ + { + files: ["**/*.ts", "**/*.tsx"], + + // Extend recommended rule sets from: + // 1. ESLint JS's recommended rules + // 2. TypeScript ESLint recommended rules + // 3. ESLint React's recommended-typescript rules + extends: [ + eslintJs.configs.recommended, + tseslint.configs.recommended, + eslintReact.configs["recommended-typescript"], + ], + + // Configure language/parsing options + languageOptions: { + // Use TypeScript ESLint parser for TypeScript files + parser: tseslint.parser, + parserOptions: { + // Enable project service for better TypeScript integration + projectService: true, + tsconfigRootDir: import.meta.dirname, + }, }, - }, - // Custom rule overrides (modify rule levels or disable rules) - rules: { - "@eslint-react/no-missing-key": "warn", + // Custom rule overrides (modify rule levels or disable rules) + rules: { + "@eslint-react/no-missing-key": "warn", + }, }, -}); +]); ``` [Full Installation Guide โ†—](https://eslint-react.xyz/docs/getting-started/typescript) - - ## Presets ### Bare Bones @@ -141,6 +142,8 @@ export default tseslint.config({ Disable rules in the `web-api` preset. - `disable-type-checked`\ Disable rules that require type information. +- `disable-conflict-eslint-plugin-react`\ + Disable rules in `eslint-plugin-react` that conflict with rules in our plugins. - `off`\ Disable all rules in this plugin except for debug rules. @@ -150,6 +153,10 @@ export default tseslint.config({ [Rules Overview โ†—](https://eslint-react.xyz/docs/rules/overview) +## Benchmark + +[Benchmark Results โ†—](https://github.com/Rel1cx/eslint-react-benchmark) + ## FAQ [Frequently Asked Questions โ†—](https://eslint-react.xyz/docs/faq) @@ -162,7 +169,7 @@ export default tseslint.config({ Contributions are welcome! -Please follow our [contributing guidelines](./.github/CONTRIBUTING.md). +Please follow our [contributing guidelines](.github/CONTRIBUTING.md). ## License diff --git a/VERSION b/VERSION index cc6268cca6..1105fa3cbd 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.53.2-beta.1 +2.0.0-beta.194 diff --git a/apps/website/app/base.css b/apps/website/app/app.css similarity index 90% rename from apps/website/app/base.css rename to apps/website/app/app.css index 31935e279f..8d5b98018a 100644 --- a/apps/website/app/base.css +++ b/apps/website/app/app.css @@ -1,6 +1,6 @@ @import "tailwindcss"; @import "tailwindcss-animated"; -@import "./theme.css"; +@import "./theme/theme.css"; @import "fumadocs-ui/css/preset.css"; @import "fumadocs-twoslash/twoslash.css"; diff --git a/apps/website/app/overrides.css b/apps/website/app/app.override.css similarity index 100% rename from apps/website/app/overrides.css rename to apps/website/app/app.override.css diff --git a/apps/website/app/docs/layout.tsx b/apps/website/app/docs/layout.tsx index 34901d384d..84d6545576 100644 --- a/apps/website/app/docs/layout.tsx +++ b/apps/website/app/docs/layout.tsx @@ -15,25 +15,3 @@ export default function Layout({ children }: { children: ReactNode }) { ); } - -// Notebook layout -// import type { ReactNode } from "react"; -// import { baseOptions } from "#/app/layout.config"; -// import { source } from "#/lib/source"; -// import { DocsLayout } from "fumadocs-ui/layouts/notebook"; - -// export default function Layout({ children }: { children: ReactNode }) { -// return ( -// -// {children} -// -// ); -// } diff --git a/apps/website/app/layout.tsx b/apps/website/app/layout.tsx index 1c933563c5..3af13a8f12 100644 --- a/apps/website/app/layout.tsx +++ b/apps/website/app/layout.tsx @@ -5,8 +5,8 @@ import type { ReactNode } from "react"; import { ViewTransitions } from "next-view-transitions"; -import "./base.css"; -import "./overrides.css"; +import "#/app/app.css"; +import "#/app/app.override.css"; const themeOptions = { enabled: true, diff --git a/apps/website/app/theme.css b/apps/website/app/theme.css deleted file mode 100644 index 5e793e9975..0000000000 --- a/apps/website/app/theme.css +++ /dev/null @@ -1,185 +0,0 @@ -@theme { - --color-fd-background: hsl(0, 0%, 100%); - --color-fd-foreground: hsl(240, 6%, 25%); - --color-fd-card: hsl(0, 0%, 100%); - --color-fd-card-foreground: hsl(0, 0%, 3.9%); - --color-fd-popover: hsl(0, 0%, 100%); - --color-fd-popover-foreground: hsl(0, 0%, 15.1%); - --color-fd-primary: hsl(210, 100%, 44%); - --color-fd-primary-foreground: hsl(0, 0%, 98%); - --color-fd-secondary: hsl(240, 6%, 97%); - --color-fd-secondary-foreground: hsl(0, 0%, 9%); - --color-fd-border: hsl(0, 0%, 89.8%); - --color-fd-ring: hsl(0, 0%, 63.9%); - --color-fd-muted: hsl(0, 0%, 96%); - --color-fd-muted-foreground: hsl(240, 6%, 50%); - --color-fd-accent: hsl(0, 0%, 94.1%); - --color-fd-accent-foreground: hsl(240, 6%, 25%); - - --color-fd-prose-body: - color-mix(in oklab, var(--color-fd-foreground) 80%, transparent); -} - -.dark { - --color-fd-background: hsl(0, 0%, 7.04%); - --color-fd-foreground: hsl(0, 0%, 92%); - --color-fd-card: hsl(0, 0%, 9.8%); - --color-fd-card-foreground: hsl(0, 0%, 98%); - --color-fd-popover: hsl(0, 0%, 9.8%); - --color-fd-popover-foreground: hsl(0, 0%, 88%); - --color-fd-primary: hsl(0, 0%, 98%); - --color-fd-primary-foreground: hsl(0, 0%, 9%); - --color-fd-secondary: hsl(0, 0%, 12.9%); - --color-fd-secondary-foreground: hsl(0, 0%, 98%); - --color-fd-border: hsl(0, 0%, 14%); - --color-fd-ring: hsl(0, 0%, 54.9%); - --color-fd-muted: hsl(0, 0%, 12.9%); - --color-fd-muted-foreground: hsl(0, 0%, 60.9%); - --color-fd-accent: hsl(0, 0%, 16.9%); - --color-fd-accent-foreground: hsl(0, 0%, 90%); - - --color-fd-prose-body: - color-mix(in oklab, var(--color-fd-foreground) 80%, transparent); -} - -:root { - --font-family-body: - "SF Pro Text", - "SF Pro Icons", - system-ui, - -apple-system, - BlinkMacSystemFont, - "Segoe UI", - Roboto, - "Noto Sans", - Ubuntu, - Cantarell, - "Helvetica Neue", - Arial, - sans-serif, - "Apple Color Emoji", - "Segoe UI Emoji", - "Segoe UI Symbol", - "Noto Color Emoji"; - - --font-family-ui: - system-ui, - -apple-system, - BlinkMacSystemFont, - "Segoe UI", - Roboto, - "Noto Sans", - Ubuntu, - Cantarell, - "Helvetica Neue", - Arial, - sans-serif, - "Apple Color Emoji", - "Segoe UI Emoji", - "Segoe UI Symbol", - "Noto Color Emoji"; - - --font-family-mono: - var(--font-ibm_plex_mono), - ui-monospace, - Menlo, - Monaco, - Consolas, - monospace; -} - -body { - font-family: var(--font-family-body); -} - -button { - cursor: pointer; -} - -:focus-visible { - outline: none; -} - -:focus-visible:not([class*="outline-none"]) { - outline: 2px solid var(--color-fd-primary); - outline-offset: -2px; -} - -[role="tabpanel"]:focus-visible { - outline: none; -} - -#nd-docs-layout #nd-sidebar { - a[data-active] { - font-weight: 400; - } - - p.mt-8.mb-2[style^="padding-inline-start:"] { - margin-top: 28px; - margin-bottom: 16px; - } - - div[data-state], - a[data-active] { - margin-top: 4px; - margin-bottom: 4px; - border-radius: 6px; - } -} - -.dark #nd-sidebar { - --color-fd-muted: hsl(0, 0%, 16%); - --color-fd-secondary: hsl(0, 0%, 18%); - --color-fd-muted-foreground: hsl(0, 0%, 72%); -} - -.prose { - font-family: var(--font-family-body); - --tw-prose-links: var(--color-fd-primary); - - .fd-codeblock.shiki { - background-color: color-mix(in oklab, var(--color-fd-secondary) 25%, transparent); - } - - .fd-codeblock, - [data-radix-scroll-area-viewport], - [data-radix-scroll-area-viewport]>div { - outline: none; - } - - :where(code):not(:where([class~='not-prose'], [class~='not-prose'] *)) { - border: none; - padding: .125rem .25em; - font-size: 14px; - } - - table { - font-size: 1em; - - thead tr th code, - tbody tr td code { - white-space: nowrap; - } - } -} - -.dark .prose { - --tw-prose-body: color-mix(in oklab, var(--color-fd-foreground) 80%, transparent); - - :where(code):not(:where([class~="not-prose"], [class~="not-prose"] *)) { - color: #ffffff; - } -} - -.dark { - - .shadow-2xs, - .shadow-xs, - .shadow-sm, - .shadow-md, - .shadow-lg, - .shadow-xl, - .shadow-2xl { - box-shadow: 0 0 #0000; - } -} \ No newline at end of file diff --git a/apps/website/atoms/location.ts b/apps/website/atoms/location.ts new file mode 100644 index 0000000000..9a6f75d509 --- /dev/null +++ b/apps/website/atoms/location.ts @@ -0,0 +1,21 @@ +import { Atom } from "@effect-atom/atom-react"; +import * as Option from "effect/Option"; + +function getHash() { + const hash = location.hash.slice(1); + if (hash.length > 0) { + return Option.some(hash); + } + return Option.none(); +} + +export const hashAtom = Atom.make>((get) => { + function onHashChange() { + get.setSelf(getHash()); + } + window.addEventListener("hashchange", onHashChange); + get.addFinalizer(() => { + window.removeEventListener("hashchange", onHashChange); + }); + return getHash(); +}); diff --git a/apps/website/atoms/theme.ts b/apps/website/atoms/theme.ts new file mode 100644 index 0000000000..5b8ec9de87 --- /dev/null +++ b/apps/website/atoms/theme.ts @@ -0,0 +1,22 @@ +import { Atom } from "@effect-atom/atom-react"; + +function getTheme(): "light" | "dark" { + const selected = localStorage?.getItem("starlight-theme") ?? "system"; + if (selected === "light" || selected === "dark") { + return selected; + } + return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"; +} + +export const themeAtom = Atom.make<"light" | "dark">((get) => { + const observer = new MutationObserver(function() { + get.setSelf(getTheme()); + }); + get.addFinalizer(() => { + observer.disconnect(); + }); + observer.observe(document.documentElement, { + attributeFilter: ["data-theme"], + }); + return getTheme(); +}); diff --git a/apps/website/content/docs/changelog.md b/apps/website/content/docs/changelog.md index 5105860448..84ea84c802 100644 --- a/apps/website/content/docs/changelog.md +++ b/apps/website/content/docs/changelog.md @@ -2,6 +2,121 @@ title: Changelog --- +## v2.0.0 (TBD) + +### ๐Ÿ’ฅ Breaking Changes + +**Target Environment Updates: Now ESM and ESLint Flat Config Only** + +- Drop support for CommonJS (CJS) module format, packages are now distributed only as ECMAScript Modules (ESM) +- Drop support for ESLint legacy config system, packages now support only ESLint Flat Config (`eslint.config.js`) +- Drop support for Node.js 16 and 18, minimum required version is now Node.js 20 +- Drop support for ESLint 8, minimum required version is now ESLint 9.3.6 +- Drop support for TypeScript 4, minimum required version is now TypeScript 5.9.2 + +**Removed Rules** + +| Rule | Replaced by | Reason | +| :--------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------- | :----------- | +| react-x/avoid-shorthand-boolean | [`react-x/jsx-shorthand-boolean`](/docs/rules/jsx-shorthand-boolean) | consolidated | +| react-x/avoid-shorthand-fragment | [`react-x/jsx-shorthand-fragment`](/docs/rules/jsx-shorthand-fragment) | consolidated | +| react-x/ensure-forward-ref-using-ref | [`react-x/no-useless-forward-ref`](/docs/rules/no-useless-forward-ref) | renamed | +| react-x/jsx-no-duplicate-props | [`react-x/jsx-no-duplicate-props`](/docs/rules/jsx-no-duplicate-props) | renamed | +| react-x/no-comment-textnodes | [`react-x/jsx-no-comment-textnodes`](/docs/rules/jsx-no-comment-textnodes) | renamed | +| react-x/no-complicated-conditional-rendering | | discontinued | +| react-x/no-nested-components | [`react-x/no-nested-component-definitions`](/docs/rules/no-nested-component-definitions) | renamed | +| react-x/prefer-react-namespace-import | [`react-x/prefer-namespace-import`](/docs/rules/prefer-namespace-import) | renamed | +| react-x/prefer-shorthand-boolean | [`react-x/jsx-shorthand-boolean`](/docs/rules/jsx-shorthand-boolean) | consolidated | +| react-x/prefer-shorthand-fragment | [`react-x/jsx-shorthand-fragment`](/docs/rules/jsx-shorthand-fragment) | consolidated | +| react-x/use-jsx-vars | [`react-x/jsx-uses-vars`](/docs/rules/jsx-uses-vars) | renamed | +| react-dom/no-children-in-void-dom-elements | [`react-dom/no-void-elements-with-children`](/docs/rules/dom-no-void-elements-with-children) | renamed | +| react-hooks-extra/no-direct-set-state-in-use-layout-effect | [`react-hooks-extra/no-direct-set-state-in-use-effect`](/docs/rules/hooks-extra-no-direct-set-state-in-use-effect) | consolidated | +| react-hooks-extra/no-unnecessary-use-callback | [`react-x/no-unnecessary-use-callback`](/docs/rules/no-unnecessary-use-callback) | relocated | +| react-hooks-extra/no-unnecessary-use-memo | [`react-x/no-unnecessary-use-memo`](/docs/rules/no-unnecessary-use-memo) | relocated | +| react-hooks-extra/no-unnecessary-use-prefix | [`react-x/no-unnecessary-use-prefix`](/docs/rules/no-unnecessary-use-prefix) | relocated | +| react-hooks-extra/prefer-use-state-lazy-initialization | [`react-x/prefer-use-state-lazy-initialization`](/docs/rules/prefer-use-state-lazy-initialization) | relocated | + +**Removed Presets** + +| Preset | Replaced by | Reason | +| :-------------------------------- | :------------ | :----------- | +| `core` | `x` | renamed | +| `core-legacy` | | discontinued | +| `off-dom` | `disable-dom` | renamed | +| `off-dom-legacy` | | discontinued | +| `x-legacy` | | discontinued | +| `dom-legacy` | | discontinued | +| `web-api-legacy` | | discontinued | +| `recommended-legacy` | | discontinued | +| `recommended-typescript-legacy` | | discontinued | +| `recommended-type-checked-legacy` | | discontinued | + +**Removed Settings** + +| Setting | Replaced by | Reason | +| :--------------------- | :---------- | :----------- | +| `additionalComponents` | | discontinued | +| `additionalHooks` | | discontinued | +| `skipImportCheck` | | discontinued | + +The rule implementations have been refactored to improve performance and maintainability. + +### โœจ New + +**Added the following new rules:** + +- `react-x/jsx-shorthand-boolean`: Enforces a consistent style for boolean attributes +- `react-x/jsx-shorthand-fragment`: Enforces a consistent style for React Fragments +- `react-x/no-forbidden-props`: Disallows specific props on components +- `react-x/no-unnecessary-key`: Reports unnecessary `key` props on elements +- `react-x/no-unused-props`: Reports unused props in components +- `react-dom/no-string-style-prop`: Disallows string values for the `style` prop +- `react-dom/prefer-namespace-import`: Enforces using a namespace import for `react-dom` + +**Added the following new rule to the `recommended-type-checked` preset:** + +- `react-x/no-unused-props`: Reports unused props in components + +**The following rules now support Codemod features:** + +- `react-x/no-component-did-update` +- `react-x/no-component-will-receive-props` +- `react-x/no-component-will-update` +- `react-x/no-context-provider` +- `react-x/no-forward-ref` +- `react-x/no-string-refs` + +**The following rules now support auto-fix:** + +- `react-x/prefer-namespace-import` +- `react-dom/prefer-namespace-import` + +**The following rules now support suggestion fixes:** + +- `react-dom/no-missing-button-type` +- `react-dom/no-missing-iframe-sandbox` +- `react-dom/no-unsafe-target-blank` + +**New configuration preset added:** + +- `disable-conflict-eslint-plugin-react`: Disable rules in `eslint-plugin-react` that conflict with rules in our plugins + +### ๐Ÿž Fixes + +- fix(react-x/no-unnecessary-use-prefix): fix false positive of React Hooks defined within the callback function of `vi.mock(...)` in Vitest test files +- fix(react-web-api/no-leaked-event-listener): fix `useEffect` setup function check to handle `React.useEffect()` calls correctly +- fix(react-naming-convention/filename): fix false positive on well-known filenames like `404.tsx`, `_app.tsx`, `[slug].tsx` + +### ๐Ÿช„ Improvements + +- refactor: simplify React APIs detection logic +- refactor: cleanup utilities and simplify rule implementations +- docs: add comparison table between `eslint-plugin-react` and `eslint-react` rules +- docs: replace `tseslint.config` with `defineConfig` in all examples +- build: migrate build system from `tsup` to `tsdown` for better performance + +**Full Changelog**: https://github.com/Rel1cx/eslint-react/compare/v1.53.1...v2.0.0 + ## v1.53.1 (2025-09-11) ### ๐Ÿž Fixes diff --git a/apps/website/content/docs/configuration/configure-analyzer.mdx b/apps/website/content/docs/configuration/configure-analyzer.mdx index e16a4596c5..6d7c991507 100644 --- a/apps/website/content/docs/configuration/configure-analyzer.mdx +++ b/apps/website/content/docs/configuration/configure-analyzer.mdx @@ -56,58 +56,6 @@ Example with `polymorphicPropName` set to `as`: // Evaluated as an h3 element ``` -### `additionalComponents` (Experimental) - - - Consider using `polymorphicPropName` instead when possible, as it's simpler - and more efficient. - - - - Experimental feature that may lack stability and documentation. - - -Maps components and their attributes for comprehensive analysis. Supports default attribute values. - -Example configuration: - -```json -[ - { - "name": "EmbedContent", - "as": "iframe", - "attributes": [ - { - "name": "sandbox", - "defaultValue": "" - } - ] - } -] -``` - -This makes `{:tsx}` evaluate as `