diff --git a/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js b/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js
index 7d6f8b87cbc2b..ef8e3c7ee6781 100644
--- a/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js
+++ b/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js
@@ -19,6 +19,16 @@ ESLintTester.setDefaultConfig({
},
});
+/**
+ * A string template tag that removes padding from the left side of multi-line strings
+ * @param {Array} strings array of code strings (only one expected)
+ */
+function normalizeIndent(strings) {
+ const codeLines = strings[0].split('\n');
+ const leftPadding = codeLines[1].match(/\s+/)[0];
+ return codeLines.map(line => line.substr(leftPadding.length)).join('\n');
+}
+
// ***************************************************
// For easier local testing, you can add to any case:
// {
@@ -31,385 +41,449 @@ ESLintTester.setDefaultConfig({
const tests = {
valid: [
- `
- // Valid because components can use hooks.
- function ComponentWithHook() {
- useHook();
- }
- `,
- `
- // Valid because components can use hooks.
- function createComponentWithHook() {
- return function ComponentWithHook() {
+ {
+ code: normalizeIndent`
+ // Valid because components can use hooks.
+ function ComponentWithHook() {
useHook();
- };
- }
- `,
- `
- // Valid because hooks can use hooks.
- function useHookWithHook() {
- useHook();
- }
- `,
- `
- // Valid because hooks can use hooks.
- function createHook() {
- return function useHookWithHook() {
+ }
+ `,
+ },
+ {
+ code: normalizeIndent`
+ // Valid because components can use hooks.
+ function createComponentWithHook() {
+ return function ComponentWithHook() {
+ useHook();
+ };
+ }
+ `,
+ },
+ {
+ code: normalizeIndent`
+ // Valid because hooks can use hooks.
+ function useHookWithHook() {
useHook();
}
- }
- `,
- `
- // Valid because components can call functions.
- function ComponentWithNormalFunction() {
- doSomething();
- }
- `,
- `
- // Valid because functions can call functions.
- function normalFunctionWithNormalFunction() {
- doSomething();
- }
- `,
- `
- // Valid because functions can call functions.
- function normalFunctionWithConditionalFunction() {
- if (cond) {
+ `,
+ },
+ {
+ code: normalizeIndent`
+ // Valid because hooks can use hooks.
+ function createHook() {
+ return function useHookWithHook() {
+ useHook();
+ }
+ }
+ `,
+ },
+ {
+ code: normalizeIndent`
+ // Valid because components can call functions.
+ function ComponentWithNormalFunction() {
+ doSomething();
+ }
+ `,
+ },
+ {
+ code: normalizeIndent`
+ // Valid because functions can call functions.
+ function normalFunctionWithNormalFunction() {
doSomething();
}
- }
- `,
- `
- // Valid because functions can call functions.
- function functionThatStartsWithUseButIsntAHook() {
- if (cond) {
- userFetch();
- }
- }
- `,
- `
- // Valid although unconditional return doesn't make sense and would fail other rules.
- // We could make it invalid but it doesn't matter.
- function useUnreachable() {
- return;
- useHook();
- }
- `,
- `
- // Valid because hooks can call hooks.
- function useHook() { useState(); }
- const whatever = function useHook() { useState(); };
- const useHook1 = () => { useState(); };
- let useHook2 = () => useState();
- useHook2 = () => { useState(); };
- ({useHook: () => { useState(); }});
- ({useHook() { useState(); }});
- const {useHook3 = () => { useState(); }} = {};
- ({useHook = () => { useState(); }} = {});
- Namespace.useHook = () => { useState(); };
- `,
- `
- // Valid because hooks can call hooks.
- function useHook() {
- useHook1();
- useHook2();
- }
- `,
- `
- // Valid because hooks can call hooks.
- function createHook() {
- return function useHook() {
+ `,
+ },
+ {
+ code: normalizeIndent`
+ // Valid because functions can call functions.
+ function normalFunctionWithConditionalFunction() {
+ if (cond) {
+ doSomething();
+ }
+ }
+ `,
+ },
+ {
+ code: normalizeIndent`
+ // Valid because functions can call functions.
+ function functionThatStartsWithUseButIsntAHook() {
+ if (cond) {
+ userFetch();
+ }
+ }
+ `,
+ },
+ {
+ code: normalizeIndent`
+ // Valid although unconditional return doesn't make sense and would fail other rules.
+ // We could make it invalid but it doesn't matter.
+ function useUnreachable() {
+ return;
+ useHook();
+ }
+ `,
+ },
+ {
+ code: normalizeIndent`
+ // Valid because hooks can call hooks.
+ function useHook() { useState(); }
+ const whatever = function useHook() { useState(); };
+ const useHook1 = () => { useState(); };
+ let useHook2 = () => useState();
+ useHook2 = () => { useState(); };
+ ({useHook: () => { useState(); }});
+ ({useHook() { useState(); }});
+ const {useHook3 = () => { useState(); }} = {};
+ ({useHook = () => { useState(); }} = {});
+ Namespace.useHook = () => { useState(); };
+ `,
+ },
+ {
+ code: normalizeIndent`
+ // Valid because hooks can call hooks.
+ function useHook() {
useHook1();
useHook2();
- };
- }
- `,
- `
- // Valid because hooks can call hooks.
- function useHook() {
- useState() && a;
- }
- `,
- `
- // Valid because hooks can call hooks.
- function useHook() {
- return useHook1() + useHook2();
- }
- `,
- `
- // Valid because hooks can call hooks.
- function useHook() {
- return useHook1(useHook2());
- }
- `,
- `
- // Valid because hooks can be used in anonymous arrow-function arguments
- // to forwardRef.
- const FancyButton = React.forwardRef((props, ref) => {
- useHook();
- return
- });
- `,
- `
- // Valid because hooks can be used in anonymous function arguments to
- // forwardRef.
- const FancyButton = React.forwardRef(function (props, ref) {
- useHook();
- return
- });
- `,
- `
- // Valid because hooks can be used in anonymous function arguments to
- // forwardRef.
- const FancyButton = forwardRef(function (props, ref) {
- useHook();
- return
- });
- `,
- `
- // Valid because hooks can be used in anonymous function arguments to
- // React.memo.
- const MemoizedFunction = React.memo(props => {
- useHook();
- return
- });
- `,
- `
- // Valid because hooks can be used in anonymous function arguments to
- // memo.
- const MemoizedFunction = memo(function (props) {
- useHook();
- return
- });
- `,
- `
- // Valid because classes can call functions.
- // We don't consider these to be hooks.
- class C {
- m() {
- this.useHook();
- super.useHook();
- }
- }
- `,
- `
- // Valid -- this is a regression test.
- jest.useFakeTimers();
- beforeEach(() => {
- jest.useRealTimers();
- })
- `,
- `
- // Valid because they're not matching use[A-Z].
- fooState();
- use();
- _use();
- _useState();
- use_hook();
- // also valid because it's not matching the PascalCase namespace
- jest.useFakeTimer()
- `,
- `
- // Regression test for some internal code.
- // This shows how the "callback rule" is more relaxed,
- // and doesn't kick in unless we're confident we're in
- // a component or a hook.
- function makeListener(instance) {
- each(pixelsWithInferredEvents, pixel => {
- if (useExtendedSelector(pixel.id) && extendedButton) {
- foo();
+ }
+ `,
+ },
+ {
+ code: normalizeIndent`
+ // Valid because hooks can call hooks.
+ function createHook() {
+ return function useHook() {
+ useHook1();
+ useHook2();
+ };
+ }
+ `,
+ },
+ {
+ code: normalizeIndent`
+ // Valid because hooks can call hooks.
+ function useHook() {
+ useState() && a;
+ }
+ `,
+ },
+ {
+ code: normalizeIndent`
+ // Valid because hooks can call hooks.
+ function useHook() {
+ return useHook1() + useHook2();
+ }
+ `,
+ },
+ {
+ code: normalizeIndent`
+ // Valid because hooks can call hooks.
+ function useHook() {
+ return useHook1(useHook2());
+ }
+ `,
+ },
+ {
+ code: normalizeIndent`
+ // Valid because hooks can be used in anonymous arrow-function arguments
+ // to forwardRef.
+ const FancyButton = React.forwardRef((props, ref) => {
+ useHook();
+ return
+ });
+ `,
+ },
+ {
+ code: normalizeIndent`
+ // Valid because hooks can be used in anonymous function arguments to
+ // forwardRef.
+ const FancyButton = React.forwardRef(function (props, ref) {
+ useHook();
+ return
+ });
+ `,
+ },
+ {
+ code: normalizeIndent`
+ // Valid because hooks can be used in anonymous function arguments to
+ // forwardRef.
+ const FancyButton = forwardRef(function (props, ref) {
+ useHook();
+ return
+ });
+ `,
+ },
+ {
+ code: normalizeIndent`
+ // Valid because hooks can be used in anonymous function arguments to
+ // React.memo.
+ const MemoizedFunction = React.memo(props => {
+ useHook();
+ return
+ });
+ `,
+ },
+ {
+ code: normalizeIndent`
+ // Valid because hooks can be used in anonymous function arguments to
+ // memo.
+ const MemoizedFunction = memo(function (props) {
+ useHook();
+ return
+ });
+ `,
+ },
+ {
+ code: normalizeIndent`
+ // Valid because classes can call functions.
+ // We don't consider these to be hooks.
+ class C {
+ m() {
+ this.useHook();
+ super.useHook();
+ }
+ }
+ `,
+ },
+ {
+ code: normalizeIndent`
+ // Valid -- this is a regression test.
+ jest.useFakeTimers();
+ beforeEach(() => {
+ jest.useRealTimers();
+ })
+ `,
+ },
+ {
+ code: normalizeIndent`
+ // Valid because they're not matching use[A-Z].
+ fooState();
+ use();
+ _use();
+ _useState();
+ use_hook();
+ // also valid because it's not matching the PascalCase namespace
+ jest.useFakeTimer()
+ `,
+ },
+ {
+ code: normalizeIndent`
+ // Regression test for some internal code.
+ // This shows how the "callback rule" is more relaxed,
+ // and doesn't kick in unless we're confident we're in
+ // a component or a hook.
+ function makeListener(instance) {
+ each(pixelsWithInferredEvents, pixel => {
+ if (useExtendedSelector(pixel.id) && extendedButton) {
+ foo();
+ }
+ });
+ }
+ `,
+ },
+ {
+ code: normalizeIndent`
+ // This is valid because "use"-prefixed functions called in
+ // unnamed function arguments are not assumed to be hooks.
+ React.unknownFunction((foo, bar) => {
+ if (foo) {
+ useNotAHook(bar)
}
});
- }
- `,
- `
- // This is valid because "use"-prefixed functions called in
- // unnamed function arguments are not assumed to be hooks.
- React.unknownFunction((foo, bar) => {
- if (foo) {
- useNotAHook(bar)
- }
- });
- `,
- `
- // This is valid because "use"-prefixed functions called in
- // unnamed function arguments are not assumed to be hooks.
- unknownFunction(function(foo, bar) {
- if (foo) {
- useNotAHook(bar)
- }
- });
- `,
- `
- // Regression test for incorrectly flagged valid code.
- function RegressionTest() {
- const foo = cond ? a : b;
- useState();
- }
- `,
- `
- // Valid because exceptions abort rendering
- function RegressionTest() {
- if (page == null) {
- throw new Error('oh no!');
+ `,
+ },
+ {
+ code: normalizeIndent`
+ // This is valid because "use"-prefixed functions called in
+ // unnamed function arguments are not assumed to be hooks.
+ unknownFunction(function(foo, bar) {
+ if (foo) {
+ useNotAHook(bar)
+ }
+ });
+ `,
+ },
+ {
+ code: normalizeIndent`
+ // Regression test for incorrectly flagged valid code.
+ function RegressionTest() {
+ const foo = cond ? a : b;
+ useState();
}
- useState();
- }
- `,
- `
- // Valid because the loop doesn't change the order of hooks calls.
- function RegressionTest() {
- const res = [];
- const additionalCond = true;
- for (let i = 0; i !== 10 && additionalCond; ++i ) {
- res.push(i);
- }
- React.useLayoutEffect(() => {});
- }
- `,
- `
- // Is valid but hard to compute by brute-forcing
- function MyComponent() {
- // 40 conditions
- if (c) {} else {}
- if (c) {} else {}
- if (c) {} else {}
- if (c) {} else {}
- if (c) {} else {}
- if (c) {} else {}
- if (c) {} else {}
- if (c) {} else {}
- if (c) {} else {}
- if (c) {} else {}
- if (c) {} else {}
- if (c) {} else {}
- if (c) {} else {}
- if (c) {} else {}
- if (c) {} else {}
- if (c) {} else {}
- if (c) {} else {}
- if (c) {} else {}
- if (c) {} else {}
- if (c) {} else {}
- if (c) {} else {}
- if (c) {} else {}
- if (c) {} else {}
- if (c) {} else {}
- if (c) {} else {}
- if (c) {} else {}
- if (c) {} else {}
- if (c) {} else {}
- if (c) {} else {}
- if (c) {} else {}
- if (c) {} else {}
- if (c) {} else {}
- if (c) {} else {}
- if (c) {} else {}
- if (c) {} else {}
- if (c) {} else {}
- if (c) {} else {}
- if (c) {} else {}
- if (c) {} else {}
- if (c) {} else {}
+ `,
+ },
+ {
+ code: normalizeIndent`
+ // Valid because exceptions abort rendering
+ function RegressionTest() {
+ if (page == null) {
+ throw new Error('oh no!');
+ }
+ useState();
+ }
+ `,
+ },
+ {
+ code: normalizeIndent`
+ // Valid because the loop doesn't change the order of hooks calls.
+ function RegressionTest() {
+ const res = [];
+ const additionalCond = true;
+ for (let i = 0; i !== 10 && additionalCond; ++i ) {
+ res.push(i);
+ }
+ React.useLayoutEffect(() => {});
+ }
+ `,
+ },
+ {
+ code: normalizeIndent`
+ // Is valid but hard to compute by brute-forcing
+ function MyComponent() {
+ // 40 conditions
+ if (c) {} else {}
+ if (c) {} else {}
+ if (c) {} else {}
+ if (c) {} else {}
+ if (c) {} else {}
+ if (c) {} else {}
+ if (c) {} else {}
+ if (c) {} else {}
+ if (c) {} else {}
+ if (c) {} else {}
+ if (c) {} else {}
+ if (c) {} else {}
+ if (c) {} else {}
+ if (c) {} else {}
+ if (c) {} else {}
+ if (c) {} else {}
+ if (c) {} else {}
+ if (c) {} else {}
+ if (c) {} else {}
+ if (c) {} else {}
+ if (c) {} else {}
+ if (c) {} else {}
+ if (c) {} else {}
+ if (c) {} else {}
+ if (c) {} else {}
+ if (c) {} else {}
+ if (c) {} else {}
+ if (c) {} else {}
+ if (c) {} else {}
+ if (c) {} else {}
+ if (c) {} else {}
+ if (c) {} else {}
+ if (c) {} else {}
+ if (c) {} else {}
+ if (c) {} else {}
+ if (c) {} else {}
+ if (c) {} else {}
+ if (c) {} else {}
+ if (c) {} else {}
+ if (c) {} else {}
- // 10 hooks
- useHook();
- useHook();
- useHook();
- useHook();
- useHook();
- useHook();
- useHook();
- useHook();
- useHook();
- useHook();
- }
- `,
- `
- // Valid because the neither the conditions before or after the hook affect the hook call
- // Failed prior to implementing BigInt because pathsFromStartToEnd and allPathsFromStartToEnd were too big and had rounding errors
- const useSomeHook = () => {};
+ // 10 hooks
+ useHook();
+ useHook();
+ useHook();
+ useHook();
+ useHook();
+ useHook();
+ useHook();
+ useHook();
+ useHook();
+ useHook();
+ }
+ `,
+ },
+ {
+ code: normalizeIndent`
+ // Valid because the neither the conditions before or after the hook affect the hook call
+ // Failed prior to implementing BigInt because pathsFromStartToEnd and allPathsFromStartToEnd were too big and had rounding errors
+ const useSomeHook = () => {};
- const SomeName = () => {
- const filler = FILLER ?? FILLER ?? FILLER;
- const filler2 = FILLER ?? FILLER ?? FILLER;
- const filler3 = FILLER ?? FILLER ?? FILLER;
- const filler4 = FILLER ?? FILLER ?? FILLER;
- const filler5 = FILLER ?? FILLER ?? FILLER;
- const filler6 = FILLER ?? FILLER ?? FILLER;
- const filler7 = FILLER ?? FILLER ?? FILLER;
- const filler8 = FILLER ?? FILLER ?? FILLER;
+ const SomeName = () => {
+ const filler = FILLER ?? FILLER ?? FILLER;
+ const filler2 = FILLER ?? FILLER ?? FILLER;
+ const filler3 = FILLER ?? FILLER ?? FILLER;
+ const filler4 = FILLER ?? FILLER ?? FILLER;
+ const filler5 = FILLER ?? FILLER ?? FILLER;
+ const filler6 = FILLER ?? FILLER ?? FILLER;
+ const filler7 = FILLER ?? FILLER ?? FILLER;
+ const filler8 = FILLER ?? FILLER ?? FILLER;
- useSomeHook();
+ useSomeHook();
- if (anyConditionCanEvenBeFalse) {
- return null;
- }
+ if (anyConditionCanEvenBeFalse) {
+ return null;
+ }
- return (
-
- {FILLER ? FILLER : FILLER}
- {FILLER ? FILLER : FILLER}
- {FILLER ? FILLER : FILLER}
- {FILLER ? FILLER : FILLER}
- {FILLER ? FILLER : FILLER}
- {FILLER ? FILLER : FILLER}
- {FILLER ? FILLER : FILLER}
- {FILLER ? FILLER : FILLER}
- {FILLER ? FILLER : FILLER}
- {FILLER ? FILLER : FILLER}
- {FILLER ? FILLER : FILLER}
- {FILLER ? FILLER : FILLER}
- {FILLER ? FILLER : FILLER}
- {FILLER ? FILLER : FILLER}
- {FILLER ? FILLER : FILLER}
- {FILLER ? FILLER : FILLER}
- {FILLER ? FILLER : FILLER}
- {FILLER ? FILLER : FILLER}
- {FILLER ? FILLER : FILLER}
- {FILLER ? FILLER : FILLER}
- {FILLER ? FILLER : FILLER}
- {FILLER ? FILLER : FILLER}
- {FILLER ? FILLER : FILLER}
- {FILLER ? FILLER : FILLER}
- {FILLER ? FILLER : FILLER}
- {FILLER ? FILLER : FILLER}
- {FILLER ? FILLER : FILLER}
- {FILLER ? FILLER : FILLER}
- {FILLER ? FILLER : FILLER}
- {FILLER ? FILLER : FILLER}
- {FILLER ? FILLER : FILLER}
- {FILLER ? FILLER : FILLER}
- {FILLER ? FILLER : FILLER}
- {FILLER ? FILLER : FILLER}
- {FILLER ? FILLER : FILLER}
- {FILLER ? FILLER : FILLER}
- {FILLER ? FILLER : FILLER}
- {FILLER ? FILLER : FILLER}
- {FILLER ? FILLER : FILLER}
- {FILLER ? FILLER : FILLER}
- {FILLER ? FILLER : FILLER}
- {FILLER ? FILLER : FILLER}
-
- );
- };
- `,
- `
- // Valid because the neither the condition nor the loop affect the hook call.
- function App(props) {
- const someObject = {propA: true};
- for (const propName in someObject) {
- if (propName === true) {
- } else {
+ return (
+
+ {FILLER ? FILLER : FILLER}
+ {FILLER ? FILLER : FILLER}
+ {FILLER ? FILLER : FILLER}
+ {FILLER ? FILLER : FILLER}
+ {FILLER ? FILLER : FILLER}
+ {FILLER ? FILLER : FILLER}
+ {FILLER ? FILLER : FILLER}
+ {FILLER ? FILLER : FILLER}
+ {FILLER ? FILLER : FILLER}
+ {FILLER ? FILLER : FILLER}
+ {FILLER ? FILLER : FILLER}
+ {FILLER ? FILLER : FILLER}
+ {FILLER ? FILLER : FILLER}
+ {FILLER ? FILLER : FILLER}
+ {FILLER ? FILLER : FILLER}
+ {FILLER ? FILLER : FILLER}
+ {FILLER ? FILLER : FILLER}
+ {FILLER ? FILLER : FILLER}
+ {FILLER ? FILLER : FILLER}
+ {FILLER ? FILLER : FILLER}
+ {FILLER ? FILLER : FILLER}
+ {FILLER ? FILLER : FILLER}
+ {FILLER ? FILLER : FILLER}
+ {FILLER ? FILLER : FILLER}
+ {FILLER ? FILLER : FILLER}
+ {FILLER ? FILLER : FILLER}
+ {FILLER ? FILLER : FILLER}
+ {FILLER ? FILLER : FILLER}
+ {FILLER ? FILLER : FILLER}
+ {FILLER ? FILLER : FILLER}
+ {FILLER ? FILLER : FILLER}
+ {FILLER ? FILLER : FILLER}
+ {FILLER ? FILLER : FILLER}
+ {FILLER ? FILLER : FILLER}
+ {FILLER ? FILLER : FILLER}
+ {FILLER ? FILLER : FILLER}
+ {FILLER ? FILLER : FILLER}
+ {FILLER ? FILLER : FILLER}
+ {FILLER ? FILLER : FILLER}
+ {FILLER ? FILLER : FILLER}
+ {FILLER ? FILLER : FILLER}
+ {FILLER ? FILLER : FILLER}
+
+ );
+ };
+ `,
+ },
+ {
+ code: normalizeIndent`
+ // Valid because the neither the condition nor the loop affect the hook call.
+ function App(props) {
+ const someObject = {propA: true};
+ for (const propName in someObject) {
+ if (propName === true) {
+ } else {
+ }
}
+ const [myState, setMyState] = useState(null);
}
- const [myState, setMyState] = useState(null);
- }
- `,
+ `,
+ },
],
invalid: [
{
- code: `
+ code: normalizeIndent`
// Invalid because it's dangerous and might not warn otherwise.
// This *must* be invalid.
function ComponentWithConditionalHook() {
@@ -421,7 +495,7 @@ const tests = {
errors: [conditionalError('useConditionalHook')],
},
{
- code: `
+ code: normalizeIndent`
Hook.use();
Hook._use();
Hook.useState();
@@ -437,7 +511,7 @@ const tests = {
],
},
{
- code: `
+ code: normalizeIndent`
class C {
m() {
This.useHook();
@@ -448,7 +522,7 @@ const tests = {
errors: [classError('This.useHook'), classError('Super.useHook')],
},
{
- code: `
+ code: normalizeIndent`
// This is a false positive (it's valid) that unfortunately
// we cannot avoid. Prefer to rename it to not start with "use"
class Foo extends Component {
@@ -462,7 +536,7 @@ const tests = {
errors: [classError('FooStore.useFeatureFlag')],
},
{
- code: `
+ code: normalizeIndent`
// Invalid because it's dangerous and might not warn otherwise.
// This *must* be invalid.
function ComponentWithConditionalHook() {
@@ -474,7 +548,7 @@ const tests = {
errors: [conditionalError('Namespace.useConditionalHook')],
},
{
- code: `
+ code: normalizeIndent`
// Invalid because it's dangerous and might not warn otherwise.
// This *must* be invalid.
function createComponent() {
@@ -488,7 +562,7 @@ const tests = {
errors: [conditionalError('useConditionalHook')],
},
{
- code: `
+ code: normalizeIndent`
// Invalid because it's dangerous and might not warn otherwise.
// This *must* be invalid.
function useHookWithConditionalHook() {
@@ -500,7 +574,7 @@ const tests = {
errors: [conditionalError('useConditionalHook')],
},
{
- code: `
+ code: normalizeIndent`
// Invalid because it's dangerous and might not warn otherwise.
// This *must* be invalid.
function createHook() {
@@ -514,7 +588,7 @@ const tests = {
errors: [conditionalError('useConditionalHook')],
},
{
- code: `
+ code: normalizeIndent`
// Invalid because it's dangerous and might not warn otherwise.
// This *must* be invalid.
function ComponentWithTernaryHook() {
@@ -524,7 +598,7 @@ const tests = {
errors: [conditionalError('useTernaryHook')],
},
{
- code: `
+ code: normalizeIndent`
// Invalid because it's a common misunderstanding.
// We *could* make it valid but the runtime error could be confusing.
function ComponentWithHookInsideCallback() {
@@ -536,7 +610,7 @@ const tests = {
errors: [genericError('useHookInsideCallback')],
},
{
- code: `
+ code: normalizeIndent`
// Invalid because it's a common misunderstanding.
// We *could* make it valid but the runtime error could be confusing.
function createComponent() {
@@ -550,7 +624,7 @@ const tests = {
errors: [genericError('useHookInsideCallback')],
},
{
- code: `
+ code: normalizeIndent`
// Invalid because it's a common misunderstanding.
// We *could* make it valid but the runtime error could be confusing.
const ComponentWithHookInsideCallback = React.forwardRef((props, ref) => {
@@ -563,7 +637,7 @@ const tests = {
errors: [genericError('useHookInsideCallback')],
},
{
- code: `
+ code: normalizeIndent`
// Invalid because it's a common misunderstanding.
// We *could* make it valid but the runtime error could be confusing.
const ComponentWithHookInsideCallback = React.memo(props => {
@@ -576,7 +650,7 @@ const tests = {
errors: [genericError('useHookInsideCallback')],
},
{
- code: `
+ code: normalizeIndent`
// Invalid because it's a common misunderstanding.
// We *could* make it valid but the runtime error could be confusing.
function ComponentWithHookInsideCallback() {
@@ -588,7 +662,7 @@ const tests = {
errors: [functionError('useState', 'handleClick')],
},
{
- code: `
+ code: normalizeIndent`
// Invalid because it's a common misunderstanding.
// We *could* make it valid but the runtime error could be confusing.
function createComponent() {
@@ -602,7 +676,7 @@ const tests = {
errors: [functionError('useState', 'handleClick')],
},
{
- code: `
+ code: normalizeIndent`
// Invalid because it's dangerous and might not warn otherwise.
// This *must* be invalid.
function ComponentWithHookInsideLoop() {
@@ -614,7 +688,7 @@ const tests = {
errors: [loopError('useHookInsideLoop')],
},
{
- code: `
+ code: normalizeIndent`
// Invalid because it's dangerous and might not warn otherwise.
// This *must* be invalid.
function renderItem() {
@@ -628,7 +702,7 @@ const tests = {
errors: [functionError('useState', 'renderItem')],
},
{
- code: `
+ code: normalizeIndent`
// Currently invalid because it violates the convention and removes the "taint"
// from a hook. We *could* make it valid to avoid some false positives but let's
// ensure that we don't break the "renderItem" and "normalFunctionWithConditionalHook"
@@ -642,7 +716,7 @@ const tests = {
],
},
{
- code: `
+ code: normalizeIndent`
// These are neither functions nor hooks.
function _normalFunctionWithHook() {
useHookInsideNormalFunction();
@@ -657,7 +731,7 @@ const tests = {
],
},
{
- code: `
+ code: normalizeIndent`
// Invalid because it's dangerous and might not warn otherwise.
// This *must* be invalid.
function normalFunctionWithConditionalHook() {
@@ -674,7 +748,7 @@ const tests = {
],
},
{
- code: `
+ code: normalizeIndent`
// Invalid because it's dangerous and might not warn otherwise.
// This *must* be invalid.
function useHookInLoops() {
@@ -698,7 +772,7 @@ const tests = {
],
},
{
- code: `
+ code: normalizeIndent`
// Invalid because it's dangerous and might not warn otherwise.
// This *must* be invalid.
function useHookInLoops() {
@@ -712,7 +786,7 @@ const tests = {
errors: [loopError('useHook1'), loopError('useHook2', true)],
},
{
- code: `
+ code: normalizeIndent`
// Invalid because it's dangerous and might not warn otherwise.
// This *must* be invalid.
function useLabeledBlock() {
@@ -725,7 +799,7 @@ const tests = {
errors: [conditionalError('useHook')],
},
{
- code: `
+ code: normalizeIndent`
// Currently invalid.
// These are variations capturing the current heuristic--
// we only allow hooks in PascalCase or useFoo functions.
@@ -754,7 +828,7 @@ const tests = {
],
},
{
- code: `
+ code: normalizeIndent`
// Invalid because it's dangerous and might not warn otherwise.
// This *must* be invalid.
function useHook() {
@@ -765,7 +839,7 @@ const tests = {
errors: [conditionalError('useState', true)],
},
{
- code: `
+ code: normalizeIndent`
// Invalid because it's dangerous and might not warn otherwise.
// This *must* be invalid.
function useHook() {
@@ -781,7 +855,7 @@ const tests = {
errors: [conditionalError('useState', true)],
},
{
- code: `
+ code: normalizeIndent`
// Invalid because it's dangerous and might not warn otherwise.
// This *must* be invalid.
function useHook() {
@@ -797,7 +871,7 @@ const tests = {
errors: [conditionalError('useState', true)],
},
{
- code: `
+ code: normalizeIndent`
// Invalid because it's dangerous and might not warn otherwise.
// This *must* be invalid.
function useHook() {
@@ -808,7 +882,7 @@ const tests = {
errors: [conditionalError('useHook1'), conditionalError('useHook2')],
},
{
- code: `
+ code: normalizeIndent`
// Invalid because it's dangerous and might not warn otherwise.
// This *must* be invalid.
function useHook() {
@@ -824,7 +898,7 @@ const tests = {
],
},
{
- code: `
+ code: normalizeIndent`
// Invalid because it's dangerous and might not warn otherwise.
// This *must* be invalid.
function useHook({ bar }) {
@@ -840,7 +914,7 @@ const tests = {
],
},
{
- code: `
+ code: normalizeIndent`
// Invalid because it's dangerous and might not warn otherwise.
// This *must* be invalid.
const FancyButton = React.forwardRef((props, ref) => {
@@ -853,7 +927,7 @@ const tests = {
errors: [conditionalError('useCustomHook')],
},
{
- code: `
+ code: normalizeIndent`
// Invalid because it's dangerous and might not warn otherwise.
// This *must* be invalid.
const FancyButton = forwardRef(function(props, ref) {
@@ -866,7 +940,7 @@ const tests = {
errors: [conditionalError('useCustomHook')],
},
{
- code: `
+ code: normalizeIndent`
// Invalid because it's dangerous and might not warn otherwise.
// This *must* be invalid.
const MemoizedButton = memo(function(props) {
@@ -879,7 +953,7 @@ const tests = {
errors: [conditionalError('useCustomHook')],
},
{
- code: `
+ code: normalizeIndent`
// This is invalid because "use"-prefixed functions used in named
// functions are assumed to be hooks.
React.unknownFunction(function notAComponent(foo, bar) {
@@ -889,7 +963,7 @@ const tests = {
errors: [functionError('useProbablyAHook', 'notAComponent')],
},
{
- code: `
+ code: normalizeIndent`
// Invalid because it's dangerous.
// Normally, this would crash, but not if you use inline requires.
// This *must* be invalid.
@@ -909,7 +983,7 @@ const tests = {
],
},
{
- code: `
+ code: normalizeIndent`
// Technically this is a false positive.
// We *could* make it valid (and it used to be).
//
@@ -926,7 +1000,7 @@ const tests = {
errors: [topLevelError('useBasename')],
},
{
- code: `
+ code: normalizeIndent`
class ClassComponentWithFeatureFlag extends React.Component {
render() {
if (foo) {
@@ -938,7 +1012,7 @@ const tests = {
errors: [classError('useFeatureFlag')],
},
{
- code: `
+ code: normalizeIndent`
class ClassComponentWithHook extends React.Component {
render() {
React.useState();
@@ -948,25 +1022,25 @@ const tests = {
errors: [classError('React.useState')],
},
{
- code: `
+ code: normalizeIndent`
(class {useHook = () => { useState(); }});
`,
errors: [classError('useState')],
},
{
- code: `
+ code: normalizeIndent`
(class {useHook() { useState(); }});
`,
errors: [classError('useState')],
},
{
- code: `
+ code: normalizeIndent`
(class {h = () => { useState(); }});
`,
errors: [classError('useState')],
},
{
- code: `
+ code: normalizeIndent`
(class {i() { useState(); }});
`,
errors: [classError('useState')],
@@ -977,92 +1051,106 @@ const tests = {
if (__EXPERIMENTAL__) {
tests.valid = [
...tests.valid,
- `
- // Valid because functions created with useEvent can be called in a useEffect.
- function MyComponent({ theme }) {
- const onClick = useEvent(() => {
- showNotification(theme);
- });
- useEffect(() => {
- onClick();
- });
- }
- `,
- `
- // Valid because functions created with useEvent can be called in closures.
- function MyComponent({ theme }) {
- const onClick = useEvent(() => {
- showNotification(theme);
- });
- return onClick()}>;
- }
- `,
- `
- // Valid because functions created with useEvent can be called in closures.
- function MyComponent({ theme }) {
- const onClick = useEvent(() => {
- showNotification(theme);
- });
- const onClick2 = () => { onClick() };
- const onClick3 = useCallback(() => onClick(), []);
- return <>
-
-
- >;
- }
- `,
- `
- // Valid because functions created with useEvent can be passed by reference in useEffect
- // and useEvent.
- function MyComponent({ theme }) {
- const onClick = useEvent(() => {
- showNotification(theme);
- });
- const onClick2 = useEvent(() => {
- debounce(onClick);
- });
- useEffect(() => {
- let id = setInterval(onClick, 100);
- return () => clearInterval(onClick);
- }, []);
- return onClick2()} />
- }
- `,
- `
- const MyComponent = ({theme}) => {
- const onClick = useEvent(() => {
- showNotification(theme);
- });
- return onClick()}>;
- };
- `,
- `
- function MyComponent({ theme }) {
- const notificationService = useNotifications();
- const showNotification = useEvent((text) => {
- notificationService.notify(theme, text);
- });
- const onClick = useEvent((text) => {
- showNotification(text);
- });
- return onClick(text)} />
- }
- `,
- `
- function MyComponent({ theme }) {
- useEffect(() => {
- onClick();
- });
- const onClick = useEvent(() => {
- showNotification(theme);
- });
- }
- `,
+ {
+ code: normalizeIndent`
+ // Valid because functions created with useEvent can be called in a useEffect.
+ function MyComponent({ theme }) {
+ const onClick = useEvent(() => {
+ showNotification(theme);
+ });
+ useEffect(() => {
+ onClick();
+ });
+ }
+ `,
+ },
+ {
+ code: normalizeIndent`
+ // Valid because functions created with useEvent can be called in closures.
+ function MyComponent({ theme }) {
+ const onClick = useEvent(() => {
+ showNotification(theme);
+ });
+ return onClick()}>;
+ }
+ `,
+ },
+ {
+ code: normalizeIndent`
+ // Valid because functions created with useEvent can be called in closures.
+ function MyComponent({ theme }) {
+ const onClick = useEvent(() => {
+ showNotification(theme);
+ });
+ const onClick2 = () => { onClick() };
+ const onClick3 = useCallback(() => onClick(), []);
+ return <>
+
+
+ >;
+ }
+ `,
+ },
+ {
+ code: normalizeIndent`
+ // Valid because functions created with useEvent can be passed by reference in useEffect
+ // and useEvent.
+ function MyComponent({ theme }) {
+ const onClick = useEvent(() => {
+ showNotification(theme);
+ });
+ const onClick2 = useEvent(() => {
+ debounce(onClick);
+ });
+ useEffect(() => {
+ let id = setInterval(onClick, 100);
+ return () => clearInterval(onClick);
+ }, []);
+ return onClick2()} />
+ }
+ `,
+ },
+ {
+ code: normalizeIndent`
+ const MyComponent = ({theme}) => {
+ const onClick = useEvent(() => {
+ showNotification(theme);
+ });
+ return onClick()}>;
+ };
+ `,
+ },
+ {
+ code: normalizeIndent`
+ function MyComponent({ theme }) {
+ const notificationService = useNotifications();
+ const showNotification = useEvent((text) => {
+ notificationService.notify(theme, text);
+ });
+ const onClick = useEvent((text) => {
+ showNotification(text);
+ });
+ return onClick(text)} />
+ }
+ `,
+ },
+ {
+ code: normalizeIndent`
+ function MyComponent({ theme }) {
+ useEffect(() => {
+ onClick();
+ });
+ const onClick = useEvent(() => {
+ showNotification(theme);
+ });
+ }
+ `,
+ },
];
tests.invalid = [
...tests.invalid,
{
- code: `
+ code: normalizeIndent`
function MyComponent({ theme }) {
const onClick = useEvent(() => {
showNotification(theme);
@@ -1073,7 +1161,7 @@ if (__EXPERIMENTAL__) {
errors: [useEventError('onClick')],
},
{
- code: `
+ code: normalizeIndent`
// This should error even though it shares an identifier name with the below
function MyComponent({theme}) {
const onClick = useEvent(() => {
@@ -1093,7 +1181,7 @@ if (__EXPERIMENTAL__) {
errors: [{...useEventError('onClick'), line: 7}],
},
{
- code: `
+ code: normalizeIndent`
const MyComponent = ({ theme }) => {
const onClick = useEvent(() => {
showNotification(theme);
@@ -1104,7 +1192,7 @@ if (__EXPERIMENTAL__) {
errors: [useEventError('onClick')],
},
{
- code: `
+ code: normalizeIndent`
// Invalid because onClick is being aliased to foo but not invoked
function MyComponent({ theme }) {
const onClick = useEvent(() => {
@@ -1117,7 +1205,7 @@ if (__EXPERIMENTAL__) {
errors: [{...useEventError('onClick'), line: 7}],
},
{
- code: `
+ code: normalizeIndent`
// Should error because it's being passed down to JSX, although it's been referenced once
// in an effect
function MyComponent({ theme }) {