diff --git a/.pkgs/configs/package.json b/.pkgs/configs/package.json index a409ea1926..3c253155ba 100644 --- a/.pkgs/configs/package.json +++ b/.pkgs/configs/package.json @@ -23,7 +23,7 @@ "@stylistic/eslint-plugin": "^5.3.1", "eslint-plugin-de-morgan": "^1.3.1", "eslint-plugin-function": "^0.0.25", - "eslint-plugin-jsdoc": "^54.4.0", + "eslint-plugin-jsdoc": "^54.5.0", "eslint-plugin-perfectionist": "^4.15.0", "eslint-plugin-regexp": "^2.10.0", "eslint-plugin-unicorn": "^61.0.1", diff --git a/.pkgs/eslint-plugin-local/package.json b/.pkgs/eslint-plugin-local/package.json index 7b9dc09c42..c2a5445841 100644 --- a/.pkgs/eslint-plugin-local/package.json +++ b/.pkgs/eslint-plugin-local/package.json @@ -33,7 +33,7 @@ "@typescript-eslint/types": "^8.42.0", "@typescript-eslint/utils": "^8.42.0", "eslint-plugin-de-morgan": "^1.3.1", - "eslint-plugin-jsdoc": "^54.4.0", + "eslint-plugin-jsdoc": "^54.5.0", "eslint-plugin-perfectionist": "^4.15.0", "eslint-plugin-regexp": "^2.10.0", "eslint-plugin-unicorn": "^61.0.1", diff --git a/apps/website/content/docs/configuration/configure-analyzer.mdx b/apps/website/content/docs/configuration/configure-analyzer.mdx index 226a2f3fed..6d7c991507 100644 --- a/apps/website/content/docs/configuration/configure-analyzer.mdx +++ b/apps/website/content/docs/configuration/configure-analyzer.mdx @@ -56,26 +56,6 @@ Example with `polymorphicPropName` set to `as`: // Evaluated as an h3 element ``` -### `additionalHooks` (Experimental) - - - Intended for edge cases. We suggest to use this option **very sparingly, if at - all**. Generally saying, we recommend most custom Hooks do not vary the - built-in React Hooks, and instead provide a higher-level API that is more - focused around a specific use case. - - -Alias variants to built-in React Hooks for rule compatibility: - -```js -additionalHooks: { - useEffect: ["useIsomorphicLayoutEffect"], - useLayoutEffect: ["useIsomorphicLayoutEffect"] -} -``` - -Treats `useIsomorphicLayoutEffect` as both `useEffect` and `useLayoutEffect` in rule checks. - ## Complete Configuration Example ```ts title="eslint.config.js" @@ -89,12 +69,6 @@ export default [ version: "19.1.0", // Specify the React version for semantic analysis (can be "detect" for auto-detection) importSource: "react", // Customize the import source for the React module (defaults to "react") polymorphicPropName: "as", // Define the prop name used for polymorphic components (e.g., ) - - // (Experimental) Alias custom Hooks to built-in React Hooks for rule compatibility - additionalHooks: { - useEffect: ["useIsomorphicLayoutEffect"], - useLayoutEffect: ["useIsomorphicLayoutEffect"], - }, }, }, }, diff --git a/apps/website/content/docs/configuration/configure-analyzer.tsx b/apps/website/content/docs/configuration/configure-analyzer.tsx index 46ece4721a..c45755bb3f 100644 --- a/apps/website/content/docs/configuration/configure-analyzer.tsx +++ b/apps/website/content/docs/configuration/configure-analyzer.tsx @@ -1,43 +1,9 @@ /* eslint-disable perfectionist/sort-objects */ -import { CodeBlock } from "#/components/ui/CodeBlock"; -import dedent from "dedent"; import { TypeTable } from "fumadocs-ui/components/type-table"; import { Link } from "next-view-transitions"; const properties = { - additionalHooks: { - type: "Record", - description: An object of aliases for React built-in Hooks ⤵, - default: "{}", - typeDescription: ( - - ), - }, polymorphicPropName: { type: "string", description: The prop your code uses to create polymorphic components ⤵, diff --git a/apps/website/content/docs/deprecated.md b/apps/website/content/docs/deprecated.md index c4ea452439..e8a87baf29 100644 --- a/apps/website/content/docs/deprecated.md +++ b/apps/website/content/docs/deprecated.md @@ -6,19 +6,20 @@ full: true ## Rules -| Rule | Replaced by | Deprecated in | -| :--------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------- | :------------ | -| [`ensure-custom-hooks-using-other-hooks`](/docs/rules/hooks-extra-no-unnecessary-use-prefix) | [`no-unnecessary-use-prefix`](/docs/rules/hooks-extra-no-unnecessary-use-prefix) | 1.13.0 | -| [`ensure-forward-ref-using-ref`](/docs/rules/ensure-forward-ref-using-ref) | [`no-useless-forward-ref`](/docs/rules/no-useless-forward-ref) | 1.33.0 | -| [`ensure-use-callback-has-non-empty-deps`](/docs/rules/hooks-extra-ensure-use-callback-has-non-empty-deps) | [`no-unnecessary-use-callback`](/docs/rules/hooks-extra-no-unnecessary-use-callback) | 1.13.0 | -| [`ensure-use-memo-has-non-empty-deps`](/docs/rules/hooks-extra-ensure-use-memo-has-non-empty-deps) | [`no-unnecessary-use-memo`](/docs/rules/hooks-extra-no-unnecessary-use-memo) | 1.13.0 | -| [`no-children-in-void-dom-elements`](/docs/rules/dom-no-children-in-void-dom-elements) | [`no-void-elements-with-children`](/docs/rules/dom-no-void-elements-with-children) | 1.22.0 | -| [`no-complicated-conditional-rendering`](/docs/rules/no-complicated-conditional-rendering) | [`no-complex-conditional-rendering`](/docs/rules/no-complex-conditional-rendering) | 1.6.0 | -| [`jsx-no-duplicate-props`](/docs/rules/jsx-no-duplicate-props) | [`jsx-no-duplicate-props`](/docs/rules/jsx-no-duplicate-props) | 1.22.0 | -| [`no-nested-components`](/docs/rules/no-nested-components) | [`no-nested-component-definitions`](/docs/rules/no-nested-component-definitions) | 1.34.0 | -| [`no-redundant-custom-hook`](/docs/rules/hooks-extra-no-unnecessary-use-prefix) | [`no-unnecessary-use-prefix`](/docs/rules/hooks-extra-no-unnecessary-use-prefix) | 1.21.0 | -| [`no-useless-custom-hooks`](/docs/rules/hooks-extra-no-unnecessary-use-prefix) | [`no-unnecessary-use-prefix`](/docs/rules/hooks-extra-no-unnecessary-use-prefix) | 1.21.0 | -| [`use-jsx-vars`](/docs/rules/use-jsx-vars) | [`jsx-uses-vars`](/docs/rules/jsx-uses-vars) | 1.22.0 | +| Rule | Replaced by | Deprecated in | +| :------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------- | :------------ | +| [`ensure-forward-ref-using-ref`](/docs/rules/ensure-forward-ref-using-ref) | [`no-useless-forward-ref`](/docs/rules/no-useless-forward-ref) | 1.33.0 | +| [`jsx-no-duplicate-props`](/docs/rules/jsx-no-duplicate-props) | [`jsx-no-duplicate-props`](/docs/rules/jsx-no-duplicate-props) | 1.22.0 | +| [`no-complicated-conditional-rendering`](/docs/rules/no-complicated-conditional-rendering) | [`no-complex-conditional-rendering`](/docs/rules/no-complex-conditional-rendering) | 1.6.0 | +| [`no-nested-components`](/docs/rules/no-nested-components) | [`no-nested-component-definitions`](/docs/rules/no-nested-component-definitions) | 1.34.0 | +| [`use-jsx-vars`](/docs/rules/use-jsx-vars) | [`jsx-uses-vars`](/docs/rules/jsx-uses-vars) | 1.22.0 | +| [`dom/no-children-in-void-dom-elements`](/docs/rules/dom-no-children-in-void-dom-elements) | [`dom/no-void-elements-with-children`](/docs/rules/dom-no-void-elements-with-children) | 1.22.0 | +| [`hooks-extra/ensure-custom-hooks-using-other-hooks`](/docs/rules/hooks-extra-no-unnecessary-use-prefix) | [`hooks-extra/no-unnecessary-use-prefix`](/docs/rules/hooks-extra-no-unnecessary-use-prefix) | 1.13.0 | +| [`hooks-extra/ensure-use-callback-has-non-empty-deps`](/docs/rules/hooks-extra-ensure-use-callback-has-non-empty-deps) | [`hooks-extra/no-unnecessary-use-callback`](/docs/rules/hooks-extra-no-unnecessary-use-callback) | 1.13.0 | +| [`hooks-extra/ensure-use-memo-has-non-empty-deps`](/docs/rules/hooks-extra-ensure-use-memo-has-non-empty-deps) | [`hooks-extra/no-unnecessary-use-memo`](/docs/rules/hooks-extra-no-unnecessary-use-memo) | 1.13.0 | +| [`hooks-extra/no-direct-set-state-in-use-layout-effect`](/docs/rules/hooks-extra-no-direct-set-state-in-use-layout-effect) | [`hooks-extra/no-direct-set-state-in-use-effect`](/docs/rules/hooks-extra-no-direct-set-state-in-use-effect) | 2.0.0 | +| [`hooks-extra/no-redundant-custom-hook`](/docs/rules/hooks-extra-no-unnecessary-use-prefix) | [`hooks-extra/no-unnecessary-use-prefix`](/docs/rules/hooks-extra-no-unnecessary-use-prefix) | 1.21.0 | +| [`hooks-extra/no-useless-custom-hooks`](/docs/rules/hooks-extra-no-unnecessary-use-prefix) | [`hooks-extra/no-unnecessary-use-prefix`](/docs/rules/hooks-extra-no-unnecessary-use-prefix) | 1.21.0 | ## Presets diff --git a/apps/website/content/docs/rules/meta.json b/apps/website/content/docs/rules/meta.json index eef74041e5..153f03a9a1 100644 --- a/apps/website/content/docs/rules/meta.json +++ b/apps/website/content/docs/rules/meta.json @@ -90,7 +90,6 @@ "web-api-no-leaked-timeout", "---Hooks Extra Rules---", "hooks-extra-no-direct-set-state-in-use-effect", - "hooks-extra-no-direct-set-state-in-use-layout-effect", "---Naming Convention Rules---", "naming-convention-component-name", "naming-convention-context-name", diff --git a/apps/website/content/docs/rules/overview.mdx b/apps/website/content/docs/rules/overview.mdx index c73bf4de21..4c3ae0759e 100644 --- a/apps/website/content/docs/rules/overview.mdx +++ b/apps/website/content/docs/rules/overview.mdx @@ -128,10 +128,9 @@ full: true This section contains rules that are not part of the official `eslint-plugin-react-hooks` plugin but are useful for specific use cases or patterns. -| Rule | ✅ | 🌟 | Description | -| :--------------------------------------------------------------------------------------------------- | :-- | :--: | :----------------------------------------------------------------------------- | -| [`no-direct-set-state-in-use-effect`](./hooks-extra-no-direct-set-state-in-use-effect) | 1️⃣ | `🧪` | Disallow direct calls to the `set` function of `useState` in `useEffect` | -| [`no-direct-set-state-in-use-layout-effect`](./hooks-extra-no-direct-set-state-in-use-layout-effect) | 0️⃣ | `🧪` | Disallow direct calls to the `set` function of `useState` in `useLayoutEffect` | +| Rule | ✅ | 🌟 | Description | +| :------------------------------------------------------------------------------------- | :-- | :--: | :----------------------------------------------------------------------- | +| [`no-direct-set-state-in-use-effect`](./hooks-extra-no-direct-set-state-in-use-effect) | 1️⃣ | `🧪` | Disallow direct calls to the `set` function of `useState` in `useEffect` | ## Naming Convention Rules diff --git a/examples/react-dom-js/package.json b/examples/react-dom-js/package.json index 291177e891..00b9be9b5a 100644 --- a/examples/react-dom-js/package.json +++ b/examples/react-dom-js/package.json @@ -25,7 +25,7 @@ "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", "globals": "^16.3.0", - "vite": "^7.1.4" + "vite": "^7.1.5" }, "engines": { "node": ">=20.19.0" diff --git a/examples/react-dom-v1/.gitignore b/examples/react-dom-v1/.gitignore deleted file mode 100644 index 8b7e50214d..0000000000 --- a/examples/react-dom-v1/.gitignore +++ /dev/null @@ -1,22 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local - -# Editor directories and files -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? diff --git a/examples/react-dom-v1/.vscode/extensions.json b/examples/react-dom-v1/.vscode/extensions.json deleted file mode 100644 index b308e58914..0000000000 --- a/examples/react-dom-v1/.vscode/extensions.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "recommendations": [ - "dbaeumer.vscode-eslint" - ] -} diff --git a/examples/react-dom-v1/.vscode/settings.json b/examples/react-dom-v1/.vscode/settings.json deleted file mode 100644 index 802c5083af..0000000000 --- a/examples/react-dom-v1/.vscode/settings.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "eslint.rules.customizations": [ - { "rule": "@eslint-react/debug/*", "severity": "info" } - ] -} diff --git a/examples/react-dom-v1/eslint.config.d.ts b/examples/react-dom-v1/eslint.config.d.ts deleted file mode 100644 index d05344c1ac..0000000000 --- a/examples/react-dom-v1/eslint.config.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare module "@eslint/js"; diff --git a/examples/react-dom-v1/eslint.config.js b/examples/react-dom-v1/eslint.config.js deleted file mode 100644 index dccada4829..0000000000 --- a/examples/react-dom-v1/eslint.config.js +++ /dev/null @@ -1,159 +0,0 @@ -import eslintReact from "@eslint-react/eslint-plugin"; -import eslintJs from "@eslint/js"; -import eslintPluginReactHooks from "eslint-plugin-react-hooks"; -import eslintPluginReactRefresh from "eslint-plugin-react-refresh"; -import { defineConfig } from "eslint/config"; -import tseslint from "typescript-eslint"; - -import TSCONFIG_APP from "./tsconfig.app.json" with { type: "json" }; -import TSCONFIG_NODE from "./tsconfig.node.json" with { type: "json" }; - -const GLOB_TS = ["**/*.ts", "**/*.tsx"]; - -export default defineConfig([ - { - files: GLOB_TS, - extends: [ - eslintJs.configs.recommended, - tseslint.configs.recommended, - ], - }, - // base configuration for browser environment source files - { - files: TSCONFIG_APP.include, - extends: [ - tseslint.configs.recommendedTypeChecked, - ], - languageOptions: { - parser: tseslint.parser, - parserOptions: { - project: "./tsconfig.app.json", - tsconfigRootDir: import.meta.dirname, - }, - }, - }, - // base configuration for node environment source files (*.config.ts, etc.) - { - files: TSCONFIG_NODE.include, - ignores: TSCONFIG_NODE.exclude, - extends: [tseslint.configs.disableTypeChecked], - languageOptions: { - parserOptions: { - project: "./tsconfig.node.json", - projectService: false, - }, - }, - rules: { - "no-console": "off", - }, - }, - // react specific configurations - { - files: TSCONFIG_APP.include, - extends: [ - eslintReact.configs["recommended-type-checked"], - eslintPluginReactRefresh.configs.recommended, - ], - plugins: { - "react-hooks": eslintPluginReactHooks, - }, - rules: { - ...eslintPluginReactHooks.configs.recommended.rules, - - // Place the v1 ruleset here to test the compatibility in the v2 branch - "@eslint-react/avoid-shorthand-boolean": "warn", - "@eslint-react/avoid-shorthand-fragment": "warn", - "@eslint-react/jsx-key-before-spread": "warn", - "@eslint-react/jsx-no-duplicate-props": "warn", - "@eslint-react/jsx-no-iife": "warn", - "@eslint-react/jsx-no-undef": "error", - "@eslint-react/jsx-uses-react": "warn", - "@eslint-react/jsx-uses-vars": "warn", - "@eslint-react/no-access-state-in-setstate": "error", - "@eslint-react/no-array-index-key": "warn", - "@eslint-react/no-children-count": "warn", - "@eslint-react/no-children-for-each": "warn", - "@eslint-react/no-children-map": "warn", - "@eslint-react/no-children-only": "warn", - "@eslint-react/no-children-prop": "warn", - "@eslint-react/no-children-to-array": "warn", - "@eslint-react/no-class-component": "warn", - "@eslint-react/no-clone-element": "warn", - "@eslint-react/no-comment-textnodes": "warn", - "@eslint-react/no-complex-conditional-rendering": "warn", - "@eslint-react/no-component-will-mount": "error", - "@eslint-react/no-component-will-receive-props": "error", - "@eslint-react/no-component-will-update": "error", - "@eslint-react/no-context-provider": "warn", - "@eslint-react/no-create-ref": "error", - "@eslint-react/no-default-props": "error", - "@eslint-react/no-direct-mutation-state": "error", - "@eslint-react/no-duplicate-key": "warn", - "@eslint-react/no-forward-ref": "warn", - "@eslint-react/no-implicit-key": "warn", - "@eslint-react/no-leaked-conditional-rendering": "warn", - "@eslint-react/no-missing-component-display-name": "warn", - "@eslint-react/no-missing-context-display-name": "warn", - "@eslint-react/no-missing-key": "error", - "@eslint-react/no-misused-capture-owner-stack": "error", - "@eslint-react/no-nested-component-definitions": "error", - "@eslint-react/no-nested-lazy-component-declarations": "warn", - "@eslint-react/no-prop-types": "error", - "@eslint-react/no-redundant-should-component-update": "error", - "@eslint-react/no-set-state-in-component-did-mount": "warn", - "@eslint-react/no-set-state-in-component-did-update": "warn", - "@eslint-react/no-set-state-in-component-will-update": "warn", - "@eslint-react/no-string-refs": "error", - "@eslint-react/no-unsafe-component-will-mount": "warn", - "@eslint-react/no-unsafe-component-will-receive-props": "warn", - "@eslint-react/no-unsafe-component-will-update": "warn", - "@eslint-react/no-unstable-context-value": "warn", - "@eslint-react/no-unstable-default-props": "warn", - "@eslint-react/no-unused-class-component-members": "warn", - "@eslint-react/no-unused-state": "warn", - "@eslint-react/no-use-context": "warn", - "@eslint-react/no-useless-forward-ref": "warn", - "@eslint-react/no-useless-fragment": "warn", - "@eslint-react/prefer-destructuring-assignment": "warn", - "@eslint-react/prefer-react-namespace-import": "warn", - "@eslint-react/prefer-read-only-props": "warn", - "@eslint-react/prefer-shorthand-boolean": "off", - "@eslint-react/prefer-shorthand-fragment": "off", - - "@eslint-react/dom/no-dangerously-set-innerhtml": "warn", - "@eslint-react/dom/no-dangerously-set-innerhtml-with-children": "error", - "@eslint-react/dom/no-find-dom-node": "error", - "@eslint-react/dom/no-flush-sync": "error", - "@eslint-react/dom/no-hydrate": "error", - "@eslint-react/dom/no-missing-button-type": "warn", - "@eslint-react/dom/no-missing-iframe-sandbox": "warn", - "@eslint-react/dom/no-namespace": "error", - "@eslint-react/dom/no-render": "error", - "@eslint-react/dom/no-render-return-value": "error", - "@eslint-react/dom/no-script-url": "warn", - "@eslint-react/dom/no-unknown-property": "warn", - "@eslint-react/dom/no-unsafe-iframe-sandbox": "warn", - "@eslint-react/dom/no-unsafe-target-blank": "warn", - "@eslint-react/dom/no-use-form-state": "error", - "@eslint-react/dom/no-void-elements-with-children": "error", - - "@eslint-react/web-api/no-leaked-event-listener": "warn", - "@eslint-react/web-api/no-leaked-interval": "warn", - "@eslint-react/web-api/no-leaked-resize-observer": "warn", - "@eslint-react/web-api/no-leaked-timeout": "warn", - - "@eslint-react/hooks-extra/no-direct-set-state-in-use-effect": "warn", - "@eslint-react/hooks-extra/no-direct-set-state-in-use-layout-effect": "warn", - "@eslint-react/hooks-extra/no-unnecessary-use-callback": "warn", - "@eslint-react/hooks-extra/no-unnecessary-use-memo": "warn", - "@eslint-react/hooks-extra/no-unnecessary-use-prefix": "warn", - "@eslint-react/hooks-extra/prefer-use-state-lazy-initialization": "warn", - - "@eslint-react/naming-convention/component-name": "warn", - "@eslint-react/naming-convention/context-name": "warn", - "@eslint-react/naming-convention/filename": "warn", - "@eslint-react/naming-convention/filename-extension": "warn", - "@eslint-react/naming-convention/use-state": "warn", - }, - }, -]); diff --git a/examples/react-dom-v1/index.html b/examples/react-dom-v1/index.html deleted file mode 100644 index c4a704fdbf..0000000000 --- a/examples/react-dom-v1/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - eslint-react-example - - - -
- - - diff --git a/examples/react-dom-v1/package.json b/examples/react-dom-v1/package.json deleted file mode 100644 index 02a1a835f1..0000000000 --- a/examples/react-dom-v1/package.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "@examples/react-dom-v1", - "version": "0.0.0", - "private": true, - "type": "module", - "scripts": { - "build": "tsc && vite build", - "dev": "vite", - "inspect:eslint-config": "eslint-config-inspector", - "lint": "eslint . --concurrency=auto", - "preview": "vite preview" - }, - "dependencies": { - "react": "^19.1.1", - "react-dom": "^19.1.1" - }, - "devDependencies": { - "@eslint-react/eslint-plugin": "workspace:*", - "@eslint/config-inspector": "^1.2.0", - "@eslint/js": "^9.35.0", - "@tsconfig/node22": "^22.0.2", - "@tsconfig/strictest": "^2.0.5", - "@tsconfig/vite-react": "^7.0.0", - "@types/react": "^19.1.12", - "@types/react-dom": "^19.1.9", - "@vitejs/plugin-react": "^5.0.2", - "eslint": "^9.35.0", - "eslint-plugin-react-hooks": "^5.2.0", - "eslint-plugin-react-refresh": "^0.4.20", - "typescript": "^5.9.2", - "typescript-eslint": "^8.42.0", - "vite": "^7.1.4" - }, - "engines": { - "node": ">=20.19.0" - } -} diff --git a/examples/react-dom-v1/src/App.css b/examples/react-dom-v1/src/App.css deleted file mode 100644 index 655c06f48a..0000000000 --- a/examples/react-dom-v1/src/App.css +++ /dev/null @@ -1,41 +0,0 @@ -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -.logo { - height: 8em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} - -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} - -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); -} - -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - - to { - transform: rotate(360deg); - } -} - -@media (prefers-reduced-motion: no-preference) { - a>.logo { - animation: logo-spin infinite 20s linear; - } -} - -.card { - padding: 2em; -} diff --git a/examples/react-dom-v1/src/App.tsx b/examples/react-dom-v1/src/App.tsx deleted file mode 100644 index 3af45273b2..0000000000 --- a/examples/react-dom-v1/src/App.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import "./App.css"; - -import { useState } from "react"; - -import logo from "./assets/eslint-react.svg"; - -function App() { - const [count, setCount] = useState(0n); - - return ( -
-
- - logo - -
-
- -
-
- ); -} - -export default App; diff --git a/examples/react-dom-v1/src/assets/eslint-react.svg b/examples/react-dom-v1/src/assets/eslint-react.svg deleted file mode 100644 index f08dbbca02..0000000000 --- a/examples/react-dom-v1/src/assets/eslint-react.svg +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - diff --git a/examples/react-dom-v1/src/assets/react.svg b/examples/react-dom-v1/src/assets/react.svg deleted file mode 100644 index a847997b69..0000000000 --- a/examples/react-dom-v1/src/assets/react.svg +++ /dev/null @@ -1,17 +0,0 @@ - diff --git a/examples/react-dom-v1/src/index.css b/examples/react-dom-v1/src/index.css deleted file mode 100644 index f5ac7a3860..0000000000 --- a/examples/react-dom-v1/src/index.css +++ /dev/null @@ -1,88 +0,0 @@ -:root { - font-family: ui-sans-serif, - system-ui, - -apple-system, - BlinkMacSystemFont, - 'Segoe UI', - Roboto, - 'Helvetica Neue', - Arial, - 'Noto Sans', - sans-serif, - 'Apple Color Emoji', - 'Segoe UI Emoji', - 'Segoe UI Symbol', - 'Noto Color Emoji'; - - line-height: 1.5; - font-weight: 400; - - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - -webkit-text-size-adjust: 100%; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} - -a:hover { - color: #535bf2; -} - -body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; -} - -h1 { - font-size: 3.2em; - line-height: 1.1; -} - -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} - -button:hover { - border-color: #646cff; -} - -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - - a:hover { - color: #747bff; - } - - button { - background-color: #f9f9f9; - } -} diff --git a/examples/react-dom-v1/src/main.ts b/examples/react-dom-v1/src/main.ts deleted file mode 100644 index 18c08d5d89..0000000000 --- a/examples/react-dom-v1/src/main.ts +++ /dev/null @@ -1,7 +0,0 @@ -import "./index.css"; - -import ReactDOM from "react-dom/client"; - -import { root } from "./root"; - -ReactDOM.createRoot(document.querySelector("#root")!).render(root); diff --git a/examples/react-dom-v1/src/root.tsx b/examples/react-dom-v1/src/root.tsx deleted file mode 100644 index df78caa030..0000000000 --- a/examples/react-dom-v1/src/root.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import React from "react"; - -import App from "./App"; - -export const root = ( - - - -); diff --git a/examples/react-dom-v1/tsconfig.app.json b/examples/react-dom-v1/tsconfig.app.json deleted file mode 100644 index 57ac7498dc..0000000000 --- a/examples/react-dom-v1/tsconfig.app.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "extends": [ - "@tsconfig/strictest/tsconfig.json", - "@tsconfig/vite-react/tsconfig.json" - ], - "compilerOptions": { - "target": "ES2021", - "lib": [ - "ES2021", - "DOM", - "DOM.Iterable" - ], - "types": [ - "vite/client" - ], - "erasableSyntaxOnly": true - }, - "include": [ - "src/**/*.ts", - "src/**/*.tsx" - ] -} diff --git a/examples/react-dom-v1/tsconfig.json b/examples/react-dom-v1/tsconfig.json deleted file mode 100644 index 1ffef600d9..0000000000 --- a/examples/react-dom-v1/tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "files": [], - "references": [ - { "path": "./tsconfig.app.json" }, - { "path": "./tsconfig.node.json" } - ] -} diff --git a/examples/react-dom-v1/tsconfig.node.json b/examples/react-dom-v1/tsconfig.node.json deleted file mode 100644 index 9614e693fc..0000000000 --- a/examples/react-dom-v1/tsconfig.node.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "extends": [ - "@tsconfig/strictest/tsconfig.json", - "@tsconfig/node22/tsconfig.json" - ], - "compilerOptions": { - "incremental": false, - "skipLibCheck": true, - "module": "ESNext", - "moduleDetection": "force", - "moduleResolution": "bundler", - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "allowJs": true, - "noEmit": true - }, - "include": [ - "*.ts", - "*.cts", - "*.mts", - "*.d.ts" - ], - "exclude": [ - "node_modules", - "dist", - "src", - "benchmark" - ] -} diff --git a/examples/react-dom-v1/vite.config.ts b/examples/react-dom-v1/vite.config.ts deleted file mode 100644 index a88da26ab2..0000000000 --- a/examples/react-dom-v1/vite.config.ts +++ /dev/null @@ -1,9 +0,0 @@ -import react from "@vitejs/plugin-react"; -import { defineConfig } from "vite"; - -// https://vitejs.dev/config/ -export default defineConfig({ - plugins: [ - react(), - ], -}); diff --git a/examples/react-dom/package.json b/examples/react-dom/package.json index 9a35c1748b..662214f47e 100644 --- a/examples/react-dom/package.json +++ b/examples/react-dom/package.json @@ -20,7 +20,7 @@ "@eslint/js": "^9.35.0", "@tsconfig/node22": "^22.0.2", "@tsconfig/strictest": "^2.0.5", - "@tsconfig/vite-react": "^7.0.0", + "@tsconfig/vite-react": "^7.0.1", "@types/react": "^19.1.12", "@types/react-dom": "^19.1.9", "@vitejs/plugin-react": "^5.0.2", @@ -29,7 +29,7 @@ "eslint-plugin-react-refresh": "^0.4.20", "typescript": "^5.9.2", "typescript-eslint": "^8.42.0", - "vite": "^7.1.4" + "vite": "^7.1.5" }, "engines": { "node": ">=20.19.0" diff --git a/examples/with-babel-eslint-parser/package.json b/examples/with-babel-eslint-parser/package.json index f29da9c6ec..e3fc128f48 100644 --- a/examples/with-babel-eslint-parser/package.json +++ b/examples/with-babel-eslint-parser/package.json @@ -35,7 +35,7 @@ "eslint-plugin-react-web-api": "workspace:*", "eslint-plugin-react-x": "workspace:*", "globals": "^16.3.0", - "vite": "^7.1.4" + "vite": "^7.1.5" }, "engines": { "node": ">=20.19.0" diff --git a/examples/with-ts-blank-eslint-parser/package.json b/examples/with-ts-blank-eslint-parser/package.json index 350f0a6f5e..c7338d020e 100644 --- a/examples/with-ts-blank-eslint-parser/package.json +++ b/examples/with-ts-blank-eslint-parser/package.json @@ -22,7 +22,7 @@ "@eslint/js": "^9.35.0", "@tsconfig/node22": "^22.0.2", "@tsconfig/strictest": "^2.0.5", - "@tsconfig/vite-react": "^7.0.0", + "@tsconfig/vite-react": "^7.0.1", "@types/react": "^19.1.12", "@types/react-dom": "^19.1.9", "@vitejs/plugin-react": "^5.0.2", @@ -33,7 +33,7 @@ "ts-blank-eslint-parser": "^0.4.4", "typescript": "^5.9.2", "typescript-eslint": "^8.42.0", - "vite": "^7.1.4" + "vite": "^7.1.5" }, "engines": { "node": ">=20.19.0" diff --git a/package.json b/package.json index 636a386610..1aeb113dde 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "eslint-plugin-fast-import": "^1.3.0", "eslint-plugin-vitest": "^0.5.4", "jiti": "^2.5.1", - "lefthook": "^1.12.3", + "lefthook": "^1.12.4", "markdownlint": "^0.38.0", "mdxlint": "^1.0.0", "publint": "^0.3.12", diff --git a/packages/core/docs/README.md b/packages/core/docs/README.md index 18ce30d96a..e50b525e04 100644 --- a/packages/core/docs/README.md +++ b/packages/core/docs/README.md @@ -143,14 +143,13 @@ - [isReactHookCall](functions/isReactHookCall.md) - [isReactHookCallWithName](functions/isReactHookCallWithName.md) - [isReactHookCallWithNameAlias](functions/isReactHookCallWithNameAlias.md) -- [isReactHookCallWithNameLoose](functions/isReactHookCallWithNameLoose.md) - [isReactHookId](functions/isReactHookId.md) - [isReactHookName](functions/isReactHookName.md) - [isRenderFunctionLoose](functions/isRenderFunctionLoose.md) - [isRenderMethodLike](functions/isRenderMethodLike.md) - [isRenderPropLoose](functions/isRenderPropLoose.md) - [isThisSetState](functions/isThisSetState.md) -- [isUseEffectCallLoose](functions/isUseEffectCallLoose.md) +- [isUseEffectLikeCall](functions/isUseEffectLikeCall.md) - [resolveAttributeValue](functions/resolveAttributeValue.md) - [stringifyJsx](functions/stringifyJsx.md) - [useComponentCollector](functions/useComponentCollector.md) diff --git a/packages/core/docs/functions/isReactHookCall.md b/packages/core/docs/functions/isReactHookCall.md index dc5ab88fa8..e0adc038f8 100644 --- a/packages/core/docs/functions/isReactHookCall.md +++ b/packages/core/docs/functions/isReactHookCall.md @@ -6,7 +6,7 @@ # Function: isReactHookCall() -> **isReactHookCall**(`node`): `boolean` +> **isReactHookCall**(`node`): `node is CallExpression` Check if the given node is a React Hook call by its name. @@ -20,6 +20,6 @@ The node to check. ## Returns -`boolean` +`node is CallExpression` `true` if the node is a React Hook call, `false` otherwise. diff --git a/packages/core/docs/functions/isReactHookCallWithName.md b/packages/core/docs/functions/isReactHookCallWithName.md index 1898ce48f5..192d755c98 100644 --- a/packages/core/docs/functions/isReactHookCallWithName.md +++ b/packages/core/docs/functions/isReactHookCallWithName.md @@ -6,19 +6,13 @@ # Function: isReactHookCallWithName() -> **isReactHookCallWithName**(`context`, `node`): (`name`) => `boolean` +> **isReactHookCallWithName**(`node`): (`name`) => `boolean` -Checks if a node is a call to a specific React hook, with React import validation. +Checks if a node is a call to a specific React hook. Returns a function that accepts a hook name to check against. ## Parameters -### context - -`RuleContext` - -The rule context - ### node The AST node to check diff --git a/packages/core/docs/functions/isReactHookCallWithNameAlias.md b/packages/core/docs/functions/isReactHookCallWithNameAlias.md index c6906d4763..2f5a84d86f 100644 --- a/packages/core/docs/functions/isReactHookCallWithNameAlias.md +++ b/packages/core/docs/functions/isReactHookCallWithNameAlias.md @@ -6,18 +6,12 @@ # Function: isReactHookCallWithNameAlias() -> **isReactHookCallWithNameAlias**(`context`, `name`, `alias`): (`node`) => `boolean` +> **isReactHookCallWithNameAlias**(`name`, `alias`): (`node`) => `boolean` Checks if a node is a call to a specific React hook or one of its aliases. ## Parameters -### context - -`RuleContext` - -The rule context - ### name `string` diff --git a/packages/core/docs/functions/isReactHookCallWithNameLoose.md b/packages/core/docs/functions/isReactHookCallWithNameLoose.md deleted file mode 100644 index 7b34a16078..0000000000 --- a/packages/core/docs/functions/isReactHookCallWithNameLoose.md +++ /dev/null @@ -1,35 +0,0 @@ -[**@eslint-react/core**](../README.md) - -*** - -[@eslint-react/core](../README.md) / isReactHookCallWithNameLoose - -# Function: isReactHookCallWithNameLoose() - -> **isReactHookCallWithNameLoose**(`node`): (`name`) => `boolean` - -Lightweight version of isReactHookCallWithName that doesn't check imports. - -## Parameters - -### node - -The AST node to check - -`undefined` | `Node` - -## Returns - -A function that takes a hook name and returns boolean - -> (`name`): `boolean` - -### Parameters - -#### name - -`string` - -### Returns - -`boolean` diff --git a/packages/core/docs/functions/isUseEffectCallLoose.md b/packages/core/docs/functions/isUseEffectLikeCall.md similarity index 65% rename from packages/core/docs/functions/isUseEffectCallLoose.md rename to packages/core/docs/functions/isUseEffectLikeCall.md index 819edbf111..4f023fb957 100644 --- a/packages/core/docs/functions/isUseEffectCallLoose.md +++ b/packages/core/docs/functions/isUseEffectLikeCall.md @@ -2,11 +2,11 @@ *** -[@eslint-react/core](../README.md) / isUseEffectCallLoose +[@eslint-react/core](../README.md) / isUseEffectLikeCall -# Function: isUseEffectCallLoose() +# Function: isUseEffectLikeCall() -> **isUseEffectCallLoose**(`node`): `boolean` +> **isUseEffectLikeCall**(`node`): `boolean` Detects useEffect calls and variations (useLayoutEffect, etc.) using regex pattern. diff --git a/packages/core/docs/variables/isUseActionStateCall.md b/packages/core/docs/variables/isUseActionStateCall.md index 50966944f6..60806ba761 100644 --- a/packages/core/docs/variables/isUseActionStateCall.md +++ b/packages/core/docs/variables/isUseActionStateCall.md @@ -12,7 +12,7 @@ ### a -...\[`RuleContext`, `Node`\] +...\[`Node`\] ## Returns diff --git a/packages/core/docs/variables/isUseCall.md b/packages/core/docs/variables/isUseCall.md index 08b215349d..c6f4038598 100644 --- a/packages/core/docs/variables/isUseCall.md +++ b/packages/core/docs/variables/isUseCall.md @@ -12,7 +12,7 @@ ### a -...\[`RuleContext`, `Node`\] +...\[`Node`\] ## Returns diff --git a/packages/core/docs/variables/isUseCallbackCall.md b/packages/core/docs/variables/isUseCallbackCall.md index 4c2ac72aa6..89c23fcb8b 100644 --- a/packages/core/docs/variables/isUseCallbackCall.md +++ b/packages/core/docs/variables/isUseCallbackCall.md @@ -12,7 +12,7 @@ ### a -...\[`RuleContext`, `Node`\] +...\[`Node`\] ## Returns diff --git a/packages/core/docs/variables/isUseContextCall.md b/packages/core/docs/variables/isUseContextCall.md index 24be904f51..1490849d29 100644 --- a/packages/core/docs/variables/isUseContextCall.md +++ b/packages/core/docs/variables/isUseContextCall.md @@ -12,7 +12,7 @@ ### a -...\[`RuleContext`, `Node`\] +...\[`Node`\] ## Returns diff --git a/packages/core/docs/variables/isUseDebugValueCall.md b/packages/core/docs/variables/isUseDebugValueCall.md index a580b0e0ab..4d63418cda 100644 --- a/packages/core/docs/variables/isUseDebugValueCall.md +++ b/packages/core/docs/variables/isUseDebugValueCall.md @@ -12,7 +12,7 @@ ### a -...\[`RuleContext`, `Node`\] +...\[`Node`\] ## Returns diff --git a/packages/core/docs/variables/isUseDeferredValueCall.md b/packages/core/docs/variables/isUseDeferredValueCall.md index e48289ded3..74cd6ccd13 100644 --- a/packages/core/docs/variables/isUseDeferredValueCall.md +++ b/packages/core/docs/variables/isUseDeferredValueCall.md @@ -12,7 +12,7 @@ ### a -...\[`RuleContext`, `Node`\] +...\[`Node`\] ## Returns diff --git a/packages/core/docs/variables/isUseEffectCall.md b/packages/core/docs/variables/isUseEffectCall.md index 8fd6e8059a..971dc04c32 100644 --- a/packages/core/docs/variables/isUseEffectCall.md +++ b/packages/core/docs/variables/isUseEffectCall.md @@ -12,7 +12,7 @@ ### a -...\[`RuleContext`, `Node`\] +...\[`Node`\] ## Returns diff --git a/packages/core/docs/variables/isUseFormStatusCall.md b/packages/core/docs/variables/isUseFormStatusCall.md index aba6c42f89..8e3307ba8e 100644 --- a/packages/core/docs/variables/isUseFormStatusCall.md +++ b/packages/core/docs/variables/isUseFormStatusCall.md @@ -12,7 +12,7 @@ ### a -...\[`RuleContext`, `Node`\] +...\[`Node`\] ## Returns diff --git a/packages/core/docs/variables/isUseIdCall.md b/packages/core/docs/variables/isUseIdCall.md index 3418eff652..f46a0c9780 100644 --- a/packages/core/docs/variables/isUseIdCall.md +++ b/packages/core/docs/variables/isUseIdCall.md @@ -12,7 +12,7 @@ ### a -...\[`RuleContext`, `Node`\] +...\[`Node`\] ## Returns diff --git a/packages/core/docs/variables/isUseImperativeHandleCall.md b/packages/core/docs/variables/isUseImperativeHandleCall.md index 41c8ccbe21..43b25661cb 100644 --- a/packages/core/docs/variables/isUseImperativeHandleCall.md +++ b/packages/core/docs/variables/isUseImperativeHandleCall.md @@ -12,7 +12,7 @@ ### a -...\[`RuleContext`, `Node`\] +...\[`Node`\] ## Returns diff --git a/packages/core/docs/variables/isUseInsertionEffectCall.md b/packages/core/docs/variables/isUseInsertionEffectCall.md index 1c765f67f0..3d06dc32b7 100644 --- a/packages/core/docs/variables/isUseInsertionEffectCall.md +++ b/packages/core/docs/variables/isUseInsertionEffectCall.md @@ -12,7 +12,7 @@ ### a -...\[`RuleContext`, `Node`\] +...\[`Node`\] ## Returns diff --git a/packages/core/docs/variables/isUseLayoutEffectCall.md b/packages/core/docs/variables/isUseLayoutEffectCall.md index 8cd32120c5..dadfe4e3a4 100644 --- a/packages/core/docs/variables/isUseLayoutEffectCall.md +++ b/packages/core/docs/variables/isUseLayoutEffectCall.md @@ -12,7 +12,7 @@ ### a -...\[`RuleContext`, `Node`\] +...\[`Node`\] ## Returns diff --git a/packages/core/docs/variables/isUseMemoCall.md b/packages/core/docs/variables/isUseMemoCall.md index 99600a87d1..76cffdd8be 100644 --- a/packages/core/docs/variables/isUseMemoCall.md +++ b/packages/core/docs/variables/isUseMemoCall.md @@ -12,7 +12,7 @@ ### a -...\[`RuleContext`, `Node`\] +...\[`Node`\] ## Returns diff --git a/packages/core/docs/variables/isUseOptimisticCall.md b/packages/core/docs/variables/isUseOptimisticCall.md index 2e5f381ba2..4a995dc156 100644 --- a/packages/core/docs/variables/isUseOptimisticCall.md +++ b/packages/core/docs/variables/isUseOptimisticCall.md @@ -12,7 +12,7 @@ ### a -...\[`RuleContext`, `Node`\] +...\[`Node`\] ## Returns diff --git a/packages/core/docs/variables/isUseReducerCall.md b/packages/core/docs/variables/isUseReducerCall.md index 6a82958f42..d9d27f2b03 100644 --- a/packages/core/docs/variables/isUseReducerCall.md +++ b/packages/core/docs/variables/isUseReducerCall.md @@ -12,7 +12,7 @@ ### a -...\[`RuleContext`, `Node`\] +...\[`Node`\] ## Returns diff --git a/packages/core/docs/variables/isUseRefCall.md b/packages/core/docs/variables/isUseRefCall.md index 8d22b067a8..2b1dba72db 100644 --- a/packages/core/docs/variables/isUseRefCall.md +++ b/packages/core/docs/variables/isUseRefCall.md @@ -12,7 +12,7 @@ ### a -...\[`RuleContext`, `Node`\] +...\[`Node`\] ## Returns diff --git a/packages/core/docs/variables/isUseStateCall.md b/packages/core/docs/variables/isUseStateCall.md index bf2e9e60d4..7d51061e10 100644 --- a/packages/core/docs/variables/isUseStateCall.md +++ b/packages/core/docs/variables/isUseStateCall.md @@ -12,7 +12,7 @@ ### a -...\[`RuleContext`, `Node`\] +...\[`Node`\] ## Returns diff --git a/packages/core/docs/variables/isUseSyncExternalStoreCall.md b/packages/core/docs/variables/isUseSyncExternalStoreCall.md index 831de3ccd6..b765606568 100644 --- a/packages/core/docs/variables/isUseSyncExternalStoreCall.md +++ b/packages/core/docs/variables/isUseSyncExternalStoreCall.md @@ -12,7 +12,7 @@ ### a -...\[`RuleContext`, `Node`\] +...\[`Node`\] ## Returns diff --git a/packages/core/docs/variables/isUseTransitionCall.md b/packages/core/docs/variables/isUseTransitionCall.md index 8b946c8909..947d269ec9 100644 --- a/packages/core/docs/variables/isUseTransitionCall.md +++ b/packages/core/docs/variables/isUseTransitionCall.md @@ -12,7 +12,7 @@ ### a -...\[`RuleContext`, `Node`\] +...\[`Node`\] ## Returns diff --git a/packages/core/src/component/component-wrapper.ts b/packages/core/src/component/component-wrapper.ts index cdc40fadf5..5584856036 100644 --- a/packages/core/src/component/component-wrapper.ts +++ b/packages/core/src/component/component-wrapper.ts @@ -24,5 +24,5 @@ export function isComponentWrapperCall(context: RuleContext, node: TSESTree.Node */ export function isComponentWrapperCallLoose(context: RuleContext, node: TSESTree.Node) { if (node.type !== T.CallExpression) return false; - return isComponentWrapperCall(context, node) || isUseCallbackCall(context, node); + return isComponentWrapperCall(context, node) || isUseCallbackCall(node); } diff --git a/packages/core/src/hook/hook-hierarchy.ts b/packages/core/src/hook/hook-hierarchy.ts index ad06127a5e..23e9975c84 100644 --- a/packages/core/src/hook/hook-hierarchy.ts +++ b/packages/core/src/hook/hook-hierarchy.ts @@ -2,7 +2,7 @@ import * as AST from "@eslint-react/ast"; import type { unit } from "@eslint-react/eff"; import type { TSESTree } from "@typescript-eslint/types"; import { AST_NODE_TYPES as T } from "@typescript-eslint/types"; -import { isUseEffectCallLoose } from "./hook-is"; +import { isUseEffectLikeCall } from "./hook-is"; /** * Determines if the node is the setup function of a useEffect hook @@ -15,7 +15,7 @@ export function isFunctionOfUseEffectSetup(node: TSESTree.Node | unit) { && node.parent.callee !== node && node.parent.callee.type === T.Identifier && node.parent.arguments.at(0) === node - && isUseEffectCallLoose(node.parent); + && isUseEffectLikeCall(node.parent); } /** diff --git a/packages/core/src/hook/hook-is.ts b/packages/core/src/hook/hook-is.ts index b973903f3f..e97dab7e76 100644 --- a/packages/core/src/hook/hook-is.ts +++ b/packages/core/src/hook/hook-is.ts @@ -1,12 +1,9 @@ import * as AST from "@eslint-react/ast"; import type { unit } from "@eslint-react/eff"; import { constFalse, flip } from "@eslint-react/eff"; -import type { RuleContext } from "@eslint-react/kit"; -import { coerceSettings, DEFAULT_ESLINT_REACT_SETTINGS } from "@eslint-react/shared"; import type { TSESTree } from "@typescript-eslint/types"; import { AST_NODE_TYPES as T } from "@typescript-eslint/types"; -import { isInitializedFromReact } from "../utils"; import { isReactHookName } from "./hook-name"; /** @@ -25,7 +22,7 @@ export function isReactHook(node: AST.TSESTreeFunction | unit) { * @param node The node to check. * @returns `true` if the node is a React Hook call, `false` otherwise. */ -export function isReactHookCall(node: TSESTree.Node | unit) { +export function isReactHookCall(node: TSESTree.Node | unit): node is TSESTree.CallExpression { if (node == null) return false; if (node.type !== T.CallExpression) { return false; @@ -40,42 +37,13 @@ export function isReactHookCall(node: TSESTree.Node | unit) { } /** - * Checks if a node is a call to a specific React hook, with React import validation. + * Checks if a node is a call to a specific React hook. * Returns a function that accepts a hook name to check against. - * @param context The rule context * @param node The AST node to check * @returns A function that takes a hook name and returns boolean */ /* eslint-disable function/function-return-boolean */ -export function isReactHookCallWithName(context: RuleContext, node: TSESTree.Node | unit) { - if (node == null || node.type !== T.CallExpression) return constFalse; - const { - importSource = DEFAULT_ESLINT_REACT_SETTINGS.importSource, - skipImportCheck = true, - } = coerceSettings(context.settings); - const initialScope = context.sourceCode.getScope(node); - return (name: string) => { - switch (true) { - case node.callee.type === T.Identifier - && node.callee.name === name: - return skipImportCheck || isInitializedFromReact(name, importSource, initialScope); - case node.callee.type === T.MemberExpression - && node.callee.property.type === T.Identifier - && node.callee.property.name === name - && "name" in node.callee.object: - return skipImportCheck || isInitializedFromReact(node.callee.object.name, importSource, initialScope); - default: - return false; - } - }; -} - -/** - * Lightweight version of isReactHookCallWithName that doesn't check imports. - * @param node The AST node to check - * @returns A function that takes a hook name and returns boolean - */ -export function isReactHookCallWithNameLoose(node: TSESTree.Node | unit) { +export function isReactHookCallWithName(node: TSESTree.Node | unit) { if (node == null || node.type !== T.CallExpression) return constFalse; return (name: string) => { switch (node.callee.type) { @@ -91,29 +59,23 @@ export function isReactHookCallWithNameLoose(node: TSESTree.Node | unit) { /** * Checks if a node is a call to a specific React hook or one of its aliases. - * @param context The rule context * @param name The primary hook name to check * @param alias Optional array of alias names to also accept * @returns Function that checks if a node matches the hook name or aliases */ -export function isReactHookCallWithNameAlias(context: RuleContext, name: string, alias: unit | string[] = []) { - const { - importSource = DEFAULT_ESLINT_REACT_SETTINGS.importSource, - skipImportCheck = true, - } = coerceSettings(context.settings); +export function isReactHookCallWithNameAlias(name: string, alias: unit | string[] = []) { return (node: TSESTree.CallExpression) => { - const initialScope = context.sourceCode.getScope(node); switch (true) { case node.callee.type === T.Identifier && node.callee.name === name: - return skipImportCheck || isInitializedFromReact(name, importSource, initialScope); + return true; case node.callee.type === T.MemberExpression && node.callee.property.type === T.Identifier && node.callee.property.name === name && "name" in node.callee.object: - return skipImportCheck || isInitializedFromReact(node.callee.object.name, importSource, initialScope); + return true; default: - return alias.some(isReactHookCallWithNameLoose(node)); + return alias.some(isReactHookCallWithName(node)); } }; } @@ -124,7 +86,7 @@ export function isReactHookCallWithNameAlias(context: RuleContext, name: string, * @param node The AST node to check * @returns True if the node is a useEffect-like call */ -export function isUseEffectCallLoose(node: TSESTree.Node | unit) { +export function isUseEffectLikeCall(node: TSESTree.Node | unit) { if (node == null) return false; if (node.type !== T.CallExpression) { return false; diff --git a/packages/plugins/eslint-plugin-react-hooks-extra/src/plugin.ts b/packages/plugins/eslint-plugin-react-hooks-extra/src/plugin.ts index a505396851..0c638e1052 100644 --- a/packages/plugins/eslint-plugin-react-hooks-extra/src/plugin.ts +++ b/packages/plugins/eslint-plugin-react-hooks-extra/src/plugin.ts @@ -3,12 +3,6 @@ import type { CompatiblePlugin } from "@eslint-react/kit"; import { name, version } from "../package.json"; import noDirectSetStateInUseEffect from "./rules/no-direct-set-state-in-use-effect"; -import noDirectSetStateInUseLayoutEffect from "./rules/no-direct-set-state-in-use-layout-effect"; - -import noUnnecessaryUseCallback from "./rules-removed/no-unnecessary-use-callback"; -import noUnnecessaryUseMemo from "./rules-removed/no-unnecessary-use-memo"; -import noUnnecessaryUsePrefix from "./rules-removed/no-unnecessary-use-prefix"; -import preferUseStateLazyInitialization from "./rules-removed/prefer-use-state-lazy-initialization"; export const plugin: CompatiblePlugin = { meta: { @@ -17,27 +11,5 @@ export const plugin: CompatiblePlugin = { }, rules: { "no-direct-set-state-in-use-effect": noDirectSetStateInUseEffect, - "no-direct-set-state-in-use-layout-effect": noDirectSetStateInUseLayoutEffect, - - /** - * @deprecated Use the same rule from `eslint-plugin-react-x` or `@eslint-react/eslint-plugin` instead. - * @see https://eslint-react.xyz/docs/rules/no-unnecessary-use-callback - */ - "no-unnecessary-use-callback": noUnnecessaryUseCallback, - /** - * @deprecated Use the same rule from `eslint-plugin-react-x` or `@eslint-react/eslint-plugin` instead. - * @see https://eslint-react.xyz/docs/rules/no-unnecessary-use-memo - */ - "no-unnecessary-use-memo": noUnnecessaryUseMemo, - /** - * @deprecated Use the same rule from `eslint-plugin-react-x` or `@eslint-react/eslint-plugin` instead. - * @see https://eslint-react.xyz/docs/rules/no-unnecessary-use-prefix - */ - "no-unnecessary-use-prefix": noUnnecessaryUsePrefix, - /** - * @deprecated Use the same rule from `eslint-plugin-react-x` or `@eslint-react/eslint-plugin` instead. - * @see https://eslint-react.xyz/docs/rules/prefer-use-state-lazy-initialization - */ - "prefer-use-state-lazy-initialization": preferUseStateLazyInitialization, }, }; diff --git a/packages/plugins/eslint-plugin-react-hooks-extra/src/rules-hooks/use-no-direct-set-state-in-use-effect.ts b/packages/plugins/eslint-plugin-react-hooks-extra/src/rules-hooks/use-no-direct-set-state-in-use-effect.ts index ffa03132a5..6402c9e1d5 100644 --- a/packages/plugins/eslint-plugin-react-hooks-extra/src/rules-hooks/use-no-direct-set-state-in-use-effect.ts +++ b/packages/plugins/eslint-plugin-react-hooks-extra/src/rules-hooks/use-no-direct-set-state-in-use-effect.ts @@ -2,7 +2,6 @@ import * as AST from "@eslint-react/ast"; import * as ER from "@eslint-react/core"; import { constVoid, getOrElseUpdate, not } from "@eslint-react/eff"; import type { RuleContext } from "@eslint-react/kit"; -import { getSettingsFromContext } from "@eslint-react/shared"; import * as VAR from "@eslint-react/var"; import { AST_NODE_TYPES as T } from "@typescript-eslint/types"; import type { ESLintUtils, TSESTree } from "@typescript-eslint/utils"; @@ -39,15 +38,8 @@ export function useNoDirectSetStateInUseEffect( options: useNoDirectSetStateInUseEffect.Options, ): useNoDirectSetStateInUseEffect.ReturnType { const { onViolation, useEffectKind } = options; - const settings = getSettingsFromContext(context); - const hooks = settings.additionalHooks; const getText = (n: TSESTree.Node) => context.sourceCode.getText(n); - const isUseEffectLikeCall = ER.isReactHookCallWithNameAlias(context, useEffectKind, hooks[useEffectKind]); - const isUseStateCall = ER.isReactHookCallWithNameAlias(context, "useState", hooks.useState); - const isUseMemoCall = ER.isReactHookCallWithNameAlias(context, "useMemo", hooks.useMemo); - const isUseCallbackCall = ER.isReactHookCallWithNameAlias(context, "useCallback", hooks.useCallback); - const functionEntries: { kind: FunctionKind; node: AST.TSESTreeFunction }[] = []; const setupFnRef: { current: AST.TSESTreeFunction | null } = { current: null }; @@ -72,7 +64,7 @@ export function useNoDirectSetStateInUseEffect( function isFunctionOfUseEffectSetup(node: TSESTree.Node) { return node.parent?.type === T.CallExpression && node.parent.callee !== node - && isUseEffectLikeCall(node.parent); + && ER.isUseEffectCall(node.parent); } function getCallName(node: TSESTree.Node) { @@ -84,8 +76,8 @@ export function useNoDirectSetStateInUseEffect( function getCallKind(node: TSESTree.CallExpression) { return match(node) - .when(isUseStateCall, () => "useState") - .when(isUseEffectLikeCall, () => useEffectKind) + .when(ER.isUseStateCall, () => "useState") + .when(ER.isUseEffectCall, () => useEffectKind) .when(isSetStateCall, () => "setState") .when(AST.isThenCall, () => "then") .otherwise(() => "other"); @@ -114,7 +106,7 @@ export function useNoDirectSetStateInUseEffect( const variableNode = VAR.getVariableDefinitionNode(variable, 0); if (variableNode == null) return false; if (variableNode.type !== T.CallExpression) return false; - if (!ER.isReactHookCallWithNameAlias(context, "useState", hooks.useState)(variableNode)) return false; + if (!ER.isUseStateCall(variableNode)) return false; const variableNodeParent = variableNode.parent; if (!("id" in variableNodeParent) || variableNodeParent.id?.type !== T.ArrayPattern) { return true; @@ -237,7 +229,7 @@ export function useNoDirectSetStateInUseEffect( // const [state, setState] = useState(); // const set = useMemo(() => setState, []); // useEffect(set, []); - if (!isUseMemoCall(parent)) { + if (!ER.isUseMemoCall(parent)) { break; } const vd = AST.findParentNode(parent, isVariableDeclaratorFromHookCall); @@ -253,7 +245,7 @@ export function useNoDirectSetStateInUseEffect( // const [state, setState] = useState(); // const set = useCallback(setState, []); // useEffect(set, []); - if (isUseCallbackCall(node.parent)) { + if (ER.isUseCallbackCall(node.parent)) { const vd = AST.findParentNode(node.parent, isVariableDeclaratorFromHookCall); if (vd != null) { getOrElseUpdate(setStateInEffectArg, vd.init, () => []).push(node); @@ -262,7 +254,7 @@ export function useNoDirectSetStateInUseEffect( } // const [state, setState] = useState(); // useEffect(setState); - if (isUseEffectLikeCall(node.parent)) { + if (ER.isUseEffectCall(node.parent)) { getOrElseUpdate(setStateInEffectSetup, node.parent, () => []).push(node); } } diff --git a/packages/plugins/eslint-plugin-react-hooks-extra/src/rules-removed/no-unnecessary-use-callback.md b/packages/plugins/eslint-plugin-react-hooks-extra/src/rules-removed/no-unnecessary-use-callback.md deleted file mode 100644 index ba236cb1bf..0000000000 --- a/packages/plugins/eslint-plugin-react-hooks-extra/src/rules-removed/no-unnecessary-use-callback.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -title: no-unnecessary-use-callback ---- - -**Full Name in `eslint-plugin-react-hooks-extra`** - -```sh copy -react-hooks-extra/no-unnecessary-use-callback -``` - -**Full Name in `@eslint-react/eslint-plugin`** - -```sh copy -@eslint-react/hooks-extra/no-unnecessary-use-callback -``` - -**Features** - -`🧪` - -## Description - -Disallow unnecessary usage of `useCallback`. - -React Hooks `useCallback` has empty dependencies array like what's in the examples, are unnecessary. The hook can be removed and it's value can be created in the component body or hoisted to the outer scope of the component. - -## Examples - -### Failing - -```tsx -import React, { useCallback } from "react"; - -function MyComponent() { - const onClick = useCallback(() => { - console.log("clicked"); - }, []); - - return