From 2ac227526a16df68887a7ca03a4bbacf3f871ae0 Mon Sep 17 00:00:00 2001 From: Simon Mollweide Date: Sat, 6 Jun 2020 00:59:13 +0200 Subject: [PATCH] feat: add plugin react hooks #23 --- README.md | 1 + bin/write-documentation.js | 6 +- configurations/es6-react-disable-styles.js | 1 + configurations/es6-react.js | 2 + configurations/es7-react-disable-styles.js | 1 + configurations/es7-react.js | 2 + configurations/es8-react-disable-styles.js | 1 + configurations/es8-react.js | 2 + .../typescript-react-disable-styles.js | 1 + configurations/typescript-react.js | 2 + documentation/react-hooks.md | 99 +++++++++++++++++++ documentation/typescript.md | 79 +++++++-------- package-lock.json | 5 + package.json | 3 +- rules/react-hooks-disable-styles.js | 5 + rules/react-hooks.js | 11 +++ .../rules/react-hooks/exhaustive-deps.js | 41 ++++++++ .../rules/react-hooks/rules-of-hooks.js | 70 +++++++++++++ 18 files changed, 291 insertions(+), 41 deletions(-) create mode 100644 documentation/react-hooks.md create mode 100644 rules/react-hooks-disable-styles.js create mode 100644 rules/react-hooks.js create mode 100644 test/es6-react/rules/react-hooks/exhaustive-deps.js create mode 100644 test/es6-react/rules/react-hooks/rules-of-hooks.js diff --git a/README.md b/README.md index a8433db..af63273 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,7 @@ then run `npm run lint` - [Imports](./documentation/imports.md) (ES6/7/8) - [React](./documentation/react.md) (ES6/7/8) - [React A11y](./documentation/react-a11y.md) (ES6/7/8) +- [React hooks](./documentation/react-hooks.md) (ES6/7/8) - [Typescript](./documentation/typescript.md) (typescript) ## Thanks to diff --git a/bin/write-documentation.js b/bin/write-documentation.js index b774316..215dcb5 100644 --- a/bin/write-documentation.js +++ b/bin/write-documentation.js @@ -27,6 +27,10 @@ function getESLintUrl(name, groupName) { return 'https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/' + name + '.md'; case 'react-a11y': return 'https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/' + name + '.md'; + case 'react-hooks': + return 'https://reactjs.org/docs/hooks-rules.html'; + case 'typescript': + return 'https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/' + name.replace(/.ts$/, '') + '.md'; default: return 'http://eslint.org/docs/rules/' + name; } @@ -133,7 +137,7 @@ function addSnippet(arr, fileData) { function addRule(arr, name, path, groupName) { var fileData = utils.readFile(path); - var nameCleaned = name.replace(/\.js$/, ''); + var nameCleaned = name.replace(/\.js$/, '').replace(/\.ts$/, ''); var title = utils.capitalize(nameCleaned.replace(/-/g, ' ')); arr.push('### [' + title + '](' + getESLintUrl(nameCleaned, groupName) + ')'); diff --git a/configurations/es6-react-disable-styles.js b/configurations/es6-react-disable-styles.js index 07b6cab..64c6ef1 100644 --- a/configurations/es6-react-disable-styles.js +++ b/configurations/es6-react-disable-styles.js @@ -4,6 +4,7 @@ module.exports = { './es6-disable-styles.js', '../rules/react-disable-styles.js', '../rules/react-a11y-disable-styles.js', + '../rules/react-hooks-disable-styles.js', ].map(require.resolve), }; diff --git a/configurations/es6-react.js b/configurations/es6-react.js index 5817580..e3b9478 100644 --- a/configurations/es6-react.js +++ b/configurations/es6-react.js @@ -3,11 +3,13 @@ module.exports = { 'plugins': [ 'jsx-a11y', 'react', + 'react-hooks', ], 'extends': [ './es6.js', '../rules/react.js', '../rules/react-a11y.js', + '../rules/react-hooks.js', ].map(require.resolve), 'env': { 'browser': true, diff --git a/configurations/es7-react-disable-styles.js b/configurations/es7-react-disable-styles.js index 27bd774..8e4dfce 100644 --- a/configurations/es7-react-disable-styles.js +++ b/configurations/es7-react-disable-styles.js @@ -4,6 +4,7 @@ module.exports = { './es7-disable-styles.js', '../rules/react-disable-styles.js', '../rules/react-a11y-disable-styles.js', + '../rules/react-hooks-disable-styles.js', ].map(require.resolve), }; diff --git a/configurations/es7-react.js b/configurations/es7-react.js index 8dc9cca..54ab186 100644 --- a/configurations/es7-react.js +++ b/configurations/es7-react.js @@ -3,11 +3,13 @@ module.exports = { 'plugins': [ 'jsx-a11y', 'react', + 'react-hooks', ], 'extends': [ './es7.js', '../rules/react.js', '../rules/react-a11y.js', + '../rules/react-hooks.js', ].map(require.resolve), 'env': { 'browser': true, diff --git a/configurations/es8-react-disable-styles.js b/configurations/es8-react-disable-styles.js index 1e51b40..3aea3f3 100644 --- a/configurations/es8-react-disable-styles.js +++ b/configurations/es8-react-disable-styles.js @@ -4,6 +4,7 @@ module.exports = { './es8-disable-styles.js', '../rules/react-disable-styles.js', '../rules/react-a11y-disable-styles.js', + '../rules/react-hooks-disable-styles.js', ].map(require.resolve), }; diff --git a/configurations/es8-react.js b/configurations/es8-react.js index 472ff4b..88ca291 100644 --- a/configurations/es8-react.js +++ b/configurations/es8-react.js @@ -4,11 +4,13 @@ module.exports = { 'plugins': [ 'jsx-a11y', 'react', + 'react-hooks', ], 'extends': [ './es8.js', '../rules/react.js', '../rules/react-a11y.js', + '../rules/react-hooks.js', ].map(require.resolve), 'env': { 'browser': true, diff --git a/configurations/typescript-react-disable-styles.js b/configurations/typescript-react-disable-styles.js index f00355e..80be09e 100644 --- a/configurations/typescript-react-disable-styles.js +++ b/configurations/typescript-react-disable-styles.js @@ -3,5 +3,6 @@ module.exports = { './typescript-disable-styles.js', '../rules/react-disable-styles.js', '../rules/react-a11y-disable-styles.js', + '../rules/react-hooks-disable-styles.js', ].map(require.resolve), }; diff --git a/configurations/typescript-react.js b/configurations/typescript-react.js index 5cf9b7f..89f0a3a 100644 --- a/configurations/typescript-react.js +++ b/configurations/typescript-react.js @@ -3,11 +3,13 @@ module.exports = { '@typescript-eslint', 'jsx-a11y', 'react', + 'react-hooks', ], extends: [ './typescript.js', '../rules/react.js', '../rules/react-a11y.js', + '../rules/react-hooks.js', ].map(require.resolve), env: { browser: true, diff --git a/documentation/react-hooks.md b/documentation/react-hooks.md new file mode 100644 index 0000000..d8b516f --- /dev/null +++ b/documentation/react-hooks.md @@ -0,0 +1,99 @@ + +## React hooks + + +### [Exhaustive deps](https://reactjs.org/docs/hooks-rules.html) + +> Checks effect dependencies + + +:white_check_mark: Enabled (error) + +```javascript + +// Bad +/* +// https://github.com/facebook/react/blob/master/packages/eslint-plugin-react-hooks/__tests__/ESLintRuleExhaustiveDeps-test.js +function MyComponent() { + const local = {}; + useEffect(() => { + console.log(local); + }, []); +} +*/ + +// Good +function MyComponent2() { + const local = {}; + useEffect(() => { + console.log(local); + }, [local]); +} + +``` +
+ + + +### [Rules of hooks](https://reactjs.org/docs/hooks-rules.html) + +> Checks rules of Hooks + + +:white_check_mark: Enabled (error) + +```javascript + +// Bad +/* +function getFriendStatus(friendID) { + const [isOnline, setIsOnline] = useState(null); + // ... + return isOnline; +} + +function Foo({ alt, src }) { + const [name, setName] = useState('Mary'); + const [surname, setSurname] = useState('Poppins'); + + // 🔴 We're breaking the first rule by using a Hook in a condition + if (name !== '') { + useEffect(function persistForm() { + localStorage.setItem('formData', name); + }); + } + + return {alt}; +} +*/ + +// Good +function useFriendStatus(friendID) { + const [isOnline, setIsOnline] = useState(null); + // ... + return isOnline; +} + +function Foo2({ alt, src }) { + // 1. Use the name state variable + const [name, setName] = useState('Mary'); + + // 2. Use an effect for persisting the form + useEffect(function persistForm() { + localStorage.setItem('formData', name); + }); + + // 3. Use the surname state variable + const [surname, setSurname] = useState('Poppins'); + + // 4. Use an effect for updating the title + useEffect(function updateTitle() { + document.title = name + ' ' + surname; + }); + return {alt}; +} + +``` +
+ + diff --git a/documentation/typescript.md b/documentation/typescript.md index 0df2d7b..259a526 100644 --- a/documentation/typescript.md +++ b/documentation/typescript.md @@ -2,7 +2,7 @@ ## Typescript -### [Adjacent overload signatures]( https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/adjacent-overload-signatures.md) +### [Adjacent overload signatures](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/adjacent-overload-signatures.md) > Require that member overloads be consecutive (adjacent-overload-signatures from TSLint) @@ -32,7 +32,8 @@ interface IFoo {
-### [Array type]( https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/array-type.md) + +### [Array type](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/array-type.md) > Requires using either T[] or Array for arrays (array-type) @@ -57,7 +58,7 @@ interface IFoo { -### [Ban ts comment]( https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/ban-ts-comment.md) +### [Ban ts comment](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/ban-ts-comment.md) > Bans // @ts- comments from being used or requires descriptions after directive (ban-ts-comment) @@ -87,7 +88,7 @@ interface IFoo { -### [Ban types]( https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/ban-types.md) +### [Ban types](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/ban-types.md) > Enforces that types will not to be used (ban-types) @@ -114,7 +115,7 @@ interface IFoo { -### [Consistent type assertions]( https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/consistent-type-assertions.md) +### [Consistent type assertions](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/consistent-type-assertions.md) > Enforces consistent usage of type assertions. (consistent-type-assertions) @@ -149,7 +150,7 @@ interface IFoo { -### [Explicit function return type]( https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/explicit-function-return-type.md) +### [Explicit function return type](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/explicit-function-return-type.md) > Require explicit return types on functions and class methods (explicit-function-return-type) @@ -184,7 +185,7 @@ class Test { -### [Explicit member accessibility]( https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/explicit-member-accessibility.md) +### [Explicit member accessibility](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/explicit-member-accessibility.md) > Require explicit return types on functions and class methods (explicit-function-return-type) @@ -233,7 +234,7 @@ class Animal { -### [Generic type naming]( https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/generic-type-naming.md) +### [Generic type naming](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/generic-type-naming.md) > Enforces naming of generic type variables (generic-type-naming) @@ -262,7 +263,7 @@ class Animal { -### [Indent]( https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/indent.md) +### [Indent](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/indent.md) > Enforce consistent indentation (indent) @@ -287,7 +288,7 @@ type ReadOnly = { -### [Interface name prefix]( https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/interface-name-prefix.md) +### [Interface name prefix](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/interface-name-prefix.md) > Require that interface names be prefixed with I (interface-name-prefix) @@ -312,7 +313,7 @@ interface IAnimal { -### [Member delimiter style]( https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/member-delimiter-style.md) +### [Member delimiter style](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/member-delimiter-style.md) > Require a specific member delimiter style for interfaces and type literals (member-delimiter-style) @@ -339,7 +340,7 @@ interface IFoo { -### [Member ordering]( https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/member-ordering.md) +### [Member ordering](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/member-ordering.md) > Require a consistent member declaration order (member-ordering) @@ -382,7 +383,7 @@ class Foo { -### [Naming convention]( https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/naming-convention.md) +### [Naming convention](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/naming-convention.md) > Enforces naming conventions for everything across a codebase (naming-convention) @@ -505,7 +506,7 @@ import { no_camelcased as noCamelcased } from 'eslint'; -### [No array constructor]( https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-array-constructor.md) +### [No array constructor](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-array-constructor.md) > Disallow generic Array constructors (no-array-constructor) @@ -536,7 +537,7 @@ import { no_camelcased as noCamelcased } from 'eslint'; -### [No empty interface]( https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-empty-interface.md) +### [No empty interface](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-empty-interface.md) > Disallow the declaration of empty interfaces (no-empty-interface) @@ -576,7 +577,7 @@ import { no_camelcased as noCamelcased } from 'eslint'; -### [No explicit any]( https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-explicit-any.md) +### [No explicit any](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-explicit-any.md) > Disallow usage of the any type (no-explicit-any) @@ -599,7 +600,7 @@ const age: any = 'seventeen'; -### [No extraneous class]( https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-extraneous-class.md) +### [No extraneous class](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-extraneous-class.md) > Forbids the use of classes as namespaces (no-extraneous-class) @@ -629,7 +630,7 @@ class ConstructorOnly { -### [No for in array]( https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-for-in-array.md) +### [No for in array](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-for-in-array.md) > Disallow iterating over an array with a for-in loop (no-for-in-array) @@ -655,7 +656,7 @@ for (const x in { a: 3, b: 4, c: 5 }) { -### [No inferrable types]( https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-inferrable-types.md) +### [No inferrable types](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-inferrable-types.md) > Disallows explicit type declarations for variables or parameters initialized to a number, string, or boolean. @@ -684,7 +685,7 @@ for (const x in { a: 3, b: 4, c: 5 }) { -### [No misused new]( https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-misused-new.md) +### [No misused new](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-misused-new.md) > Enforce valid definition of new and constructor. (no-misused-new) @@ -721,7 +722,7 @@ for (const x in { a: 3, b: 4, c: 5 }) { -### [No namespace]( https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-namespace.md) +### [No namespace](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-namespace.md) > Disallow the use of custom TypeScript modules and namespaces (no-namespace) @@ -745,7 +746,7 @@ declare module 'foo' {} -### [No non null assertion]( https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-non-null-assertion.md) +### [No non null assertion](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-non-null-assertion.md) > Disallows non-null assertions using the ! postfix operator (no-non-null-assertion) @@ -780,7 +781,7 @@ declare module 'foo' {} -### [No parameter properties]( https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-parameter-properties.md) +### [No parameter properties](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-parameter-properties.md) > Disallow the use of parameter properties in class constructors. (no-parameter-properties) @@ -818,7 +819,7 @@ declare module 'foo' {} -### [No require imports]( https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-require-imports.md) +### [No require imports](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-require-impo.md) > Disallows invocation of require() (no-require-imports) @@ -839,7 +840,7 @@ import eslint from 'eslint'; -### [No this alias]( https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-this-alias.md) +### [No this alias](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-this-alias.md) > Disallow aliasing this (no-this-alias) @@ -873,7 +874,7 @@ import eslint from 'eslint'; -### [No type alias]( https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-type-alias.md) +### [No type alias](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-type-alias.md) > Disallow the use of type aliases (no-type-alias) @@ -903,7 +904,7 @@ import eslint from 'eslint'; -### [No unnecessary qualifier]( https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unnecessary-qualifier.md) +### [No unnecessary qualifier](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unnecessary-qualifier.md) > Warns when a namespace qualifier is unnecessary. (no-unnecessary-qualifier) @@ -932,7 +933,7 @@ namespace Y { -### [No unnecessary type assertion]( https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unnecessary-type-assertion.md) +### [No unnecessary type assertion](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unnecessary-type-assertion.md) > Warns if a type assertion does not change the type of an expression (no-unnecessary-type-assertion) @@ -961,7 +962,7 @@ namespace Y { -### [No unused vars]( https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unused-vars.md) +### [No unused vars](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unused-vars.md) > Disallow unused variables (no-unused-vars) @@ -996,7 +997,7 @@ console.log(z); -### [No use before define]( https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-use-before-define.md) +### [No use before define](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-use-before-define.md) > Disallow the use of variables before they are defined (no-use-before-define) @@ -1023,7 +1024,7 @@ console.log(z); -### [No useless constructor]( https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-useless-constructor.md) +### [No useless constructor](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-useless-constructor.md) > Disallow the use of variables before they are defined (no-use-before-define) @@ -1057,7 +1058,7 @@ console.log(z); -### [No var requires]( https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-var-requires.md) +### [No var requires](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-var-requires.md) > Disallows the use of require statements except in import statements (no-var-requires) @@ -1082,7 +1083,7 @@ import eslint from 'eslint'; -### [Prefer function type]( https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-function-type.md) +### [Prefer function type](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-function-type.md) > Use function types instead of interfaces with call signatures (prefer-function-type) @@ -1131,7 +1132,7 @@ import eslint from 'eslint'; -### [Prefer interface]( https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-interface.md) +### [Prefer interface](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-interface.md) > Prefer an interface declaration over a type literal (type T = { ... }) (prefer-interface) @@ -1158,7 +1159,7 @@ import eslint from 'eslint'; -### [Promise function async]( https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/promise-function-async.md) +### [Promise function async](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/promise-function-async.md) > Functions that return promises must be async (promise-function-async) @@ -1191,7 +1192,7 @@ import eslint from 'eslint'; -### [Restrict plus operands]( https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/restrict-plus-operands.md) +### [Restrict plus operands](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/restrict-plus-operands.md) > When adding two variables, operands must both be of type number or of type string. @@ -1216,7 +1217,7 @@ import eslint from 'eslint'; -### [Triple slash reference]( https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/triple-slash-reference.md) +### [Triple slash reference](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/triple-slash-reference.md) > Disallow /// comments (triple-slash-reference) @@ -1235,7 +1236,7 @@ import eslint from 'eslint'; -### [Type annotation spacing]( https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/type-annotation-spacing.md) +### [Type annotation spacing](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/type-annotation-spacing.md) > Require consistent spacing around type annotations @@ -1260,7 +1261,7 @@ import eslint from 'eslint'; -### [Unified signatures]( https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/unified-signatures.md) +### [Unified signatures](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/unified-signatures.md) > Warns for any two overloads that could be unified into one by using a union or an optional/rest parameter. diff --git a/package-lock.json b/package-lock.json index 7627ad0..13ec447 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2173,6 +2173,11 @@ "xregexp": "^4.3.0" } }, + "eslint-plugin-react-hooks": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.0.4.tgz", + "integrity": "sha512-equAdEIsUETLFNCmmCkiCGq6rkSK5MoJhXFPFYeUebcjKgBmWWcgVOqZyQC8Bv1BwVCnTq9tBxgJFgAJTWoJtA==" + }, "eslint-scope": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.0.tgz", diff --git a/package.json b/package.json index 1869d44..bc328b8 100644 --- a/package.json +++ b/package.json @@ -74,6 +74,7 @@ "@typescript-eslint/parser": "3.1.0", "babel-eslint": "10.1.0", "eslint-plugin-jsx-a11y": "6.2.3", - "eslint-plugin-react": "7.20.0" + "eslint-plugin-react": "7.20.0", + "eslint-plugin-react-hooks": "4.0.4" } } diff --git a/rules/react-hooks-disable-styles.js b/rules/react-hooks-disable-styles.js new file mode 100644 index 0000000..fc041e4 --- /dev/null +++ b/rules/react-hooks-disable-styles.js @@ -0,0 +1,5 @@ +module.exports = { + rules: { + // there aren't style rules for node + }, +}; diff --git a/rules/react-hooks.js b/rules/react-hooks.js new file mode 100644 index 0000000..cfc9812 --- /dev/null +++ b/rules/react-hooks.js @@ -0,0 +1,11 @@ +module.exports = { + rules: { + // Checks rules of Hooks + // https://reactjs.org/docs/hooks-rules.html + 'react-hooks/rules-of-hooks': 2, + + // Checks effect dependencies + // https://reactjs.org/docs/hooks-rules.html + 'react-hooks/exhaustive-deps': 2, + }, +}; diff --git a/test/es6-react/rules/react-hooks/exhaustive-deps.js b/test/es6-react/rules/react-hooks/exhaustive-deps.js new file mode 100644 index 0000000..5fb25be --- /dev/null +++ b/test/es6-react/rules/react-hooks/exhaustive-deps.js @@ -0,0 +1,41 @@ +// DESCRIPTION = Checks effect dependencies +// STATUS = 2 + +/* eslint-disable no-console */ +/* eslint-disable prefer-template */ +/* eslint-disable prefer-arrow-callback */ +/* eslint require-jsdoc: 0*/ +/* eslint no-use-before-define: 0*/ +/* eslint no-undef: 0*/ +/* eslint no-unused-vars: 0*/ +/* eslint no-unreachable: 0*/ +/* eslint no-empty: 0*/ +/* eslint no-empty-function: 0*/ +/* eslint no-shadow: 0*/ +/* eslint no-redeclare: 0*/ +/* eslint no-unused-expressions: 0*/ +/* eslint react/react-in-jsx-scope: 0*/ +/* eslint react/prefer-stateless-function: 0*/ +/* eslint react/prefer-es6-class: 0*/ +/* eslint react/prop-types: 0*/ +/* eslint object-shorthand: 0*/ +// { + console.log(local); + }, []); +} +*/ + +// Good +function MyComponent2() { + const local = {}; + useEffect(() => { + console.log(local); + }, [local]); +} +// END!> diff --git a/test/es6-react/rules/react-hooks/rules-of-hooks.js b/test/es6-react/rules/react-hooks/rules-of-hooks.js new file mode 100644 index 0000000..366780f --- /dev/null +++ b/test/es6-react/rules/react-hooks/rules-of-hooks.js @@ -0,0 +1,70 @@ +// DESCRIPTION = Checks rules of Hooks +// STATUS = 2 + +/* eslint-disable prefer-template */ +/* eslint-disable prefer-arrow-callback */ +/* eslint require-jsdoc: 0*/ +/* eslint no-use-before-define: 0*/ +/* eslint no-undef: 0*/ +/* eslint no-unused-vars: 0*/ +/* eslint no-unreachable: 0*/ +/* eslint no-empty: 0*/ +/* eslint no-empty-function: 0*/ +/* eslint no-shadow: 0*/ +/* eslint no-redeclare: 0*/ +/* eslint no-unused-expressions: 0*/ +/* eslint react/react-in-jsx-scope: 0*/ +/* eslint react/prefer-stateless-function: 0*/ +/* eslint react/prefer-es6-class: 0*/ +/* eslint react/prop-types: 0*/ +/* eslint object-shorthand: 0*/ +// ; +} +*/ + +// Good +function useFriendStatus(friendID) { + const [isOnline, setIsOnline] = useState(null); + // ... + return isOnline; +} + +function Foo2({ alt, src }) { + // 1. Use the name state variable + const [name, setName] = useState('Mary'); + + // 2. Use an effect for persisting the form + useEffect(function persistForm() { + localStorage.setItem('formData', name); + }); + + // 3. Use the surname state variable + const [surname, setSurname] = useState('Poppins'); + + // 4. Use an effect for updating the title + useEffect(function updateTitle() { + document.title = name + ' ' + surname; + }); + return {alt}; +} +// END!>