From 9f4afc6a3552554e2cf5a0f96e7684b15f57b1cf Mon Sep 17 00:00:00 2001 From: Lewis Liu Date: Sat, 8 Aug 2020 12:09:54 -0700 Subject: [PATCH 1/8] Add special case handling for render-hooks --- .../__tests__/ESLintRulesOfHooks-test.js | 25 +++++++++++++++++++ .../src/RulesOfHooks.js | 10 +++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js b/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js index 044cc58f40c4c..3e251351ef34d 100644 --- a/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js +++ b/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js @@ -406,6 +406,16 @@ const tests = { const [myState, setMyState] = useState(null); } `, + ` + // Valid because render-hooks acts as a component boundary + function App(props) { + return props.isOpen + ? + {() => props.setIsOpen(false), [props.setIsOpen])} />} + + : null; + } + `, ], invalid: [ { @@ -956,6 +966,21 @@ const tests = { `, errors: [classError('useState')], }, + { + code: ` + // Invalid because rule of hooks still need to be adhered to within render-hooks + function App(props) { + return props.isOpen + ? + {() => { + return props.setIsOpen(false), [props.setIsOpen]) : undefined} />; + }} + + : null; + } + `, + errors: [conditionalError('useCallback')] + } ], }; diff --git a/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js b/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js index 31d54e813ae65..ba530039075da 100644 --- a/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js +++ b/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js @@ -105,6 +105,14 @@ function isInsideComponentOrHook(node) { return false; } +function isDirectlyInsideRenderHooks(node) { + return node.parent && + node.parent.type === 'JSXExpressionContainer' && + node.parent.parent && + node.parent.parent.type === 'JSXElement' && + node.parent.parent.openingElement.name.name === 'Hooks'; +} + export default { meta: { type: 'problem', @@ -352,7 +360,7 @@ export default { const isDirectlyInsideComponentOrHook = codePathFunctionName ? isComponentName(codePathFunctionName) || isHook(codePathFunctionName) - : isForwardRefCallback(codePathNode) || isMemoCallback(codePathNode); + : isForwardRefCallback(codePathNode) || isMemoCallback(codePathNode) || isDirectlyInsideRenderHooks(codePathNode); // Compute the earliest finalizer level using information from the // cache. We expect all reachable final segments to have a cache entry From bfd331f6eadbe02822a2678a3cc8ed2235873bd4 Mon Sep 17 00:00:00 2001 From: Lewis Liu Date: Sat, 8 Aug 2020 12:11:52 -0700 Subject: [PATCH 2/8] Update package name for fork --- packages/eslint-plugin-react-hooks/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin-react-hooks/package.json b/packages/eslint-plugin-react-hooks/package.json index 3c0be3c704dc2..c424049712281 100644 --- a/packages/eslint-plugin-react-hooks/package.json +++ b/packages/eslint-plugin-react-hooks/package.json @@ -1,5 +1,5 @@ { - "name": "eslint-plugin-react-hooks", + "name": "@lewisl9029/eslint-plugin-react-hooks", "description": "ESLint rules for React Hooks", "version": "4.5.0", "repository": { From fbc72a28ca41fdfe05b18ae9dd6f0d7151a9c7b7 Mon Sep 17 00:00:00 2001 From: Lewis Liu Date: Thu, 13 Aug 2020 18:05:50 -0700 Subject: [PATCH 3/8] Update readme, bump version --- packages/eslint-plugin-react-hooks/README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/eslint-plugin-react-hooks/README.md b/packages/eslint-plugin-react-hooks/README.md index e3b7d40431ea2..b749750e531d0 100644 --- a/packages/eslint-plugin-react-hooks/README.md +++ b/packages/eslint-plugin-react-hooks/README.md @@ -1,4 +1,6 @@ -# `eslint-plugin-react-hooks` +# `@lewisl9029/eslint-plugin-react-hooks` + +This is a fork of the official eslint plugin `eslint-plugin-react-hooks`, that adds support for the [render-hooks](https://github.com/lewisl9029/render-hooks#readme) library/pattern. This ESLint plugin enforces the [Rules of Hooks](https://reactjs.org/docs/hooks-rules.html). @@ -12,10 +14,10 @@ Assuming you already have ESLint installed, run: ```sh # npm -npm install eslint-plugin-react-hooks --save-dev +npm install @lewisl9029/eslint-plugin-react-hooks --save-dev # yarn -yarn add eslint-plugin-react-hooks --dev +yarn add @lewisl90290/eslint-plugin-react-hooks --dev ``` Then extend the recommended eslint config: From 473fbdb030193455a8c8c30307924bea8f0d9cc3 Mon Sep 17 00:00:00 2001 From: Lewis Liu Date: Tue, 27 Oct 2020 20:48:42 -0700 Subject: [PATCH 4/8] Update for hooks function support --- .../__tests__/ESLintRulesOfHooks-test.js | 29 ++++++++++++++++--- .../src/RulesOfHooks.js | 23 +++++++++++---- 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js b/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js index 3e251351ef34d..363e34bb21915 100644 --- a/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js +++ b/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js @@ -407,7 +407,7 @@ const tests = { } `, ` - // Valid because render-hooks acts as a component boundary + // Valid because Hooks component acts as a component boundary function App(props) { return props.isOpen ? @@ -416,6 +416,14 @@ const tests = { : null; } `, + ` + // Valid because hooks function acts as a component boundary + function App(props) { + return props.isOpen + ? hooks(() => props.setIsOpen(false), [props.setIsOpen])} />) + : null; + } + `, ], invalid: [ { @@ -968,7 +976,7 @@ const tests = { }, { code: ` - // Invalid because rule of hooks still need to be adhered to within render-hooks + // Invalid because rule of hooks still need to be adhered to within Hooks component function App(props) { return props.isOpen ? @@ -979,8 +987,21 @@ const tests = { : null; } `, - errors: [conditionalError('useCallback')] - } + errors: [conditionalError('useCallback')], + }, + { + code: ` + // Invalid because rule of hooks still need to be adhered to within hooks function + function App(props) { + return props.isOpen + ? hooks(() => { + return props.setIsOpen(false), [props.setIsOpen]) : undefined} />; + }) + : null; + } + `, + errors: [conditionalError('useCallback')], + }, ], }; diff --git a/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js b/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js index ba530039075da..fd2ad1bfe162a 100644 --- a/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js +++ b/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js @@ -106,11 +106,22 @@ function isInsideComponentOrHook(node) { } function isDirectlyInsideRenderHooks(node) { - return node.parent && - node.parent.type === 'JSXExpressionContainer' && - node.parent.parent && - node.parent.parent.type === 'JSXElement' && + if (!node.parent) { + return false; + } + + const isDirectlyInsideHooksComponent = + node.parent.type === 'JSXExpressionContainer' && + node.parent.parent && + node.parent.parent.type === 'JSXElement' && node.parent.parent.openingElement.name.name === 'Hooks'; + const isDirectlyInsideHooksFunction = + node.parent.type === 'CallExpression' && + node.parent.callee && + node.parent.callee.type === 'Identifier' && + node.parent.callee.name === 'hooks'; + + return isDirectlyInsideHooksComponent || isDirectlyInsideHooksFunction; } export default { @@ -360,7 +371,9 @@ export default { const isDirectlyInsideComponentOrHook = codePathFunctionName ? isComponentName(codePathFunctionName) || isHook(codePathFunctionName) - : isForwardRefCallback(codePathNode) || isMemoCallback(codePathNode) || isDirectlyInsideRenderHooks(codePathNode); + : isForwardRefCallback(codePathNode) || + isMemoCallback(codePathNode) || + isDirectlyInsideRenderHooks(codePathNode); // Compute the earliest finalizer level using information from the // cache. We expect all reachable final segments to have a cache entry From 7de74ec9245ba9c1fe1bd54b384e40ac37b0e812 Mon Sep 17 00:00:00 2001 From: Lewis Liu Date: Sun, 25 Apr 2021 17:53:26 -0700 Subject: [PATCH 5/8] Rename for new version of react-anonymous --- .../__tests__/ESLintRulesOfHooks-test.js | 10 +++++----- packages/eslint-plugin-react-hooks/src/RulesOfHooks.js | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js b/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js index 363e34bb21915..1647cf54cc6ef 100644 --- a/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js +++ b/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js @@ -407,20 +407,20 @@ const tests = { } `, ` - // Valid because Hooks component acts as a component boundary + // Valid because called in Anonymous component function App(props) { return props.isOpen - ? + ? {() => props.setIsOpen(false), [props.setIsOpen])} />} - + : null; } `, ` - // Valid because hooks function acts as a component boundary + // Valid because called in anonymous function function App(props) { return props.isOpen - ? hooks(() => props.setIsOpen(false), [props.setIsOpen])} />) + ? anonymous(() => props.setIsOpen(false), [props.setIsOpen])} />) : null; } `, diff --git a/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js b/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js index fd2ad1bfe162a..b79ccdeca0e70 100644 --- a/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js +++ b/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js @@ -105,7 +105,7 @@ function isInsideComponentOrHook(node) { return false; } -function isDirectlyInsideRenderHooks(node) { +function isDirectlyInsideAnonymousComponent(node) { if (!node.parent) { return false; } @@ -114,12 +114,12 @@ function isDirectlyInsideRenderHooks(node) { node.parent.type === 'JSXExpressionContainer' && node.parent.parent && node.parent.parent.type === 'JSXElement' && - node.parent.parent.openingElement.name.name === 'Hooks'; + node.parent.parent.openingElement.name.name === 'Anonymous'; const isDirectlyInsideHooksFunction = node.parent.type === 'CallExpression' && node.parent.callee && node.parent.callee.type === 'Identifier' && - node.parent.callee.name === 'hooks'; + node.parent.callee.name === 'anonymous'; return isDirectlyInsideHooksComponent || isDirectlyInsideHooksFunction; } @@ -373,7 +373,7 @@ export default { isHook(codePathFunctionName) : isForwardRefCallback(codePathNode) || isMemoCallback(codePathNode) || - isDirectlyInsideRenderHooks(codePathNode); + isDirectlyInsideAnonymousComponent(codePathNode); // Compute the earliest finalizer level using information from the // cache. We expect all reachable final segments to have a cache entry From a2760296a61dc2a37b0b2b50656c00ee66284f7d Mon Sep 17 00:00:00 2001 From: Lewis Liu Date: Sun, 25 Apr 2021 17:54:56 -0700 Subject: [PATCH 6/8] Bump version, update readme --- packages/eslint-plugin-react-hooks/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin-react-hooks/README.md b/packages/eslint-plugin-react-hooks/README.md index b749750e531d0..56526dac27dfc 100644 --- a/packages/eslint-plugin-react-hooks/README.md +++ b/packages/eslint-plugin-react-hooks/README.md @@ -1,6 +1,6 @@ # `@lewisl9029/eslint-plugin-react-hooks` -This is a fork of the official eslint plugin `eslint-plugin-react-hooks`, that adds support for the [render-hooks](https://github.com/lewisl9029/render-hooks#readme) library/pattern. +This is a fork of the official eslint plugin `eslint-plugin-react-hooks`, that adds support for the [react-anonymous](https://github.com/lewisl9029/react-anonymous#readme) library/pattern. This ESLint plugin enforces the [Rules of Hooks](https://reactjs.org/docs/hooks-rules.html). From 173d0d032602f2f1c3ec6d0439df3c9aaa37dfec Mon Sep 17 00:00:00 2001 From: Lewis Liu Date: Sun, 15 May 2022 17:46:23 -0700 Subject: [PATCH 7/8] Fix tests that weren't updated with new name --- .../__tests__/ESLintRulesOfHooks-test.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js b/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js index 1647cf54cc6ef..4f2112ac2039e 100644 --- a/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js +++ b/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js @@ -976,14 +976,14 @@ const tests = { }, { code: ` - // Invalid because rule of hooks still need to be adhered to within Hooks component + // Invalid because rule of hooks still need to be adhered to within Anonymous component function App(props) { return props.isOpen - ? + ? {() => { return props.setIsOpen(false), [props.setIsOpen]) : undefined} />; }} - + : null; } `, @@ -991,10 +991,10 @@ const tests = { }, { code: ` - // Invalid because rule of hooks still need to be adhered to within hooks function + // Invalid because rule of hooks still need to be adhered to within anonymous function function App(props) { return props.isOpen - ? hooks(() => { + ? anonymous(() => { return props.setIsOpen(false), [props.setIsOpen]) : undefined} />; }) : null; From f151054d9dbcd823a77bca45cc95c8eb285c501d Mon Sep 17 00:00:00 2001 From: Lewis Liu Date: Sun, 15 May 2022 17:52:19 -0700 Subject: [PATCH 8/8] New name and version --- packages/eslint-plugin-react-hooks/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/eslint-plugin-react-hooks/package.json b/packages/eslint-plugin-react-hooks/package.json index c424049712281..3a22b59695721 100644 --- a/packages/eslint-plugin-react-hooks/package.json +++ b/packages/eslint-plugin-react-hooks/package.json @@ -1,7 +1,7 @@ { - "name": "@lewisl9029/eslint-plugin-react-hooks", - "description": "ESLint rules for React Hooks", - "version": "4.5.0", + "name": "@lewisl9029/eslint-plugin-react-hooks-for-react-anonymous", + "description": "Fork of ESLint rules for React Hooks for react-anonymous", + "version": "0.0.1", "repository": { "type": "git", "url": "https://github.com/facebook/react.git",