diff --git a/compiler/forget/package.json b/compiler/forget/package.json
index 6721176bd4..07b345f77f 100644
--- a/compiler/forget/package.json
+++ b/compiler/forget/package.json
@@ -49,6 +49,7 @@
"@babel/traverse": "^7.19.1",
"@testing-library/react": "^13.4.0",
"@tsconfig/node16-strictest": "^1.0.3",
+ "@types/glob": "^8.1.0",
"@types/invariant": "^2.2.35",
"@types/jest": "^29.0.3",
"@types/node": "^18.7.18",
diff --git a/compiler/forget/scripts/build-react-hooks-fixures.js b/compiler/forget/scripts/build-react-hooks-fixures.js
new file mode 100644
index 0000000000..04c238278a
--- /dev/null
+++ b/compiler/forget/scripts/build-react-hooks-fixures.js
@@ -0,0 +1,91 @@
+/**
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+"use strict";
+
+const { tests } = require("./eslint-plugin-react-hooks-test-cases");
+const {
+ runReactForgetBabelPlugin,
+} = require("../dist/Babel/RunReactForgetBabelPlugin");
+const fs = require("fs");
+const path = require("path");
+const prettier = require("prettier");
+const prettierConfigPath = require.resolve("../.prettierrc");
+const process = require("process");
+
+const FIXTURES_DIR = path.join(
+ process.cwd(),
+ "src",
+ "__tests__",
+ "fixtures",
+ "compiler",
+ "rules-of-hooks"
+);
+
+const fixtures = [];
+for (const test of tests.valid) {
+ fixtures.push({ code: test.code, valid: true });
+}
+for (const test of tests.invalid) {
+ fixtures.push({ code: test.code, valid: false });
+}
+
+let index = 0;
+for (const fixture of fixtures) {
+ let error = null;
+ let passes = true;
+ try {
+ // Does the fixture pass with hooks validation disabled? if not skip it
+ runReactForgetBabelPlugin(fixture.code, "rules-of-hooks.js", "typescript", {
+ environment: {
+ validateHooksUsage: false,
+ },
+ });
+ // Does the fixture pass with hooks validation enabled?
+ try {
+ runReactForgetBabelPlugin(
+ fixture.code,
+ "rules-of-hooks.js",
+ "typescript",
+ {
+ environment: {
+ validateHooksUsage: true,
+ },
+ }
+ );
+ } catch (e) {
+ passes = false;
+ }
+ } catch (e) {
+ error = e;
+ }
+ let name = `rules-of-hooks-${index}.js`;
+ let code = fixture.code;
+ if (error !== null) {
+ name = `todo.${name}`;
+ code = `// @skip\n${code}`;
+ } else if (fixture.valid === false) {
+ name = `error.${name}`;
+ if (passes) {
+ // oops, passed when we expected an error
+ name = `todo.${name}`;
+ code = `// @skip\n${code}`;
+ }
+ } else if (!passes) {
+ // oops, error when it should have passed
+ name = `todo.${name}`;
+ code = `// @skip\n${code}`;
+ }
+
+ const fixturePath = path.join(FIXTURES_DIR, name);
+ const options = prettier.resolveConfig.sync(fixturePath, {
+ config: prettierConfigPath,
+ });
+ const formatted = prettier.format(code, options);
+ fs.writeFileSync(fixturePath, formatted, "utf8");
+ index++;
+}
diff --git a/compiler/forget/scripts/eslint-plugin-react-hooks-test-cases.js b/compiler/forget/scripts/eslint-plugin-react-hooks-test-cases.js
new file mode 100644
index 0000000000..d47a1d38b7
--- /dev/null
+++ b/compiler/forget/scripts/eslint-plugin-react-hooks-test-cases.js
@@ -0,0 +1,985 @@
+/**
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+"use strict";
+
+// NOTE: Extracted from https://github.com/facebook/react/blob/main/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js
+
+/**
+ * A string template tag that removes padding from the left side of multi-line strings
+ */
+function normalizeIndent(strings) {
+ const codeLines = strings[0].split("\n");
+ const leftPadding = codeLines[1].match(/\s+/)[0];
+ return codeLines.map((line) => line.slice(leftPadding.length)).join("\n");
+}
+
+module.exports.tests = {
+ valid: [
+ {
+ code: normalizeIndent`
+ // Valid because components can use hooks.
+ function ComponentWithHook() {
+ useHook();
+ }
+ `,
+ },
+ {
+ 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();
+ }
+ `,
+ },
+ {
+ 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();
+ }
+ `,
+ },
+ {
+ 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();
+ }
+ `,
+ },
+ {
+ 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();
+ _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)
+ }
+ });
+ `,
+ },
+ {
+ 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();
+ }
+ `,
+ },
+ {
+ 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();
+ }
+ `,
+ },
+ {
+ 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;
+
+ useSomeHook();
+
+ 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}
+
+ );
+ };
+ `,
+ },
+ {
+ 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);
+ }
+ `,
+ },
+ ],
+ invalid: [
+ {
+ code: normalizeIndent`
+ // Invalid because it's dangerous and might not warn otherwise.
+ // This *must* be invalid.
+ function ComponentWithConditionalHook() {
+ if (cond) {
+ useConditionalHook();
+ }
+ }
+ `,
+ errors: [],
+ },
+ {
+ code: normalizeIndent`
+ Hook.useState();
+ Hook._useState();
+ Hook.use42();
+ Hook.useHook();
+ Hook.use_hook();
+ `,
+ errors: [],
+ },
+ {
+ code: normalizeIndent`
+ class C {
+ m() {
+ This.useHook();
+ Super.useHook();
+ }
+ }
+ `,
+ errors: [],
+ },
+ {
+ 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 {
+ render() {
+ if (cond) {
+ FooStore.useFeatureFlag();
+ }
+ }
+ }
+ `,
+ errors: [],
+ },
+ {
+ code: normalizeIndent`
+ // Invalid because it's dangerous and might not warn otherwise.
+ // This *must* be invalid.
+ function ComponentWithConditionalHook() {
+ if (cond) {
+ Namespace.useConditionalHook();
+ }
+ }
+ `,
+ errors: [],
+ },
+ {
+ code: normalizeIndent`
+ // Invalid because it's dangerous and might not warn otherwise.
+ // This *must* be invalid.
+ function createComponent() {
+ return function ComponentWithConditionalHook() {
+ if (cond) {
+ useConditionalHook();
+ }
+ }
+ }
+ `,
+ errors: [],
+ },
+ {
+ code: normalizeIndent`
+ // Invalid because it's dangerous and might not warn otherwise.
+ // This *must* be invalid.
+ function useHookWithConditionalHook() {
+ if (cond) {
+ useConditionalHook();
+ }
+ }
+ `,
+ errors: [],
+ },
+ {
+ code: normalizeIndent`
+ // Invalid because it's dangerous and might not warn otherwise.
+ // This *must* be invalid.
+ function createHook() {
+ return function useHookWithConditionalHook() {
+ if (cond) {
+ useConditionalHook();
+ }
+ }
+ }
+ `,
+ errors: [],
+ },
+ {
+ code: normalizeIndent`
+ // Invalid because it's dangerous and might not warn otherwise.
+ // This *must* be invalid.
+ function ComponentWithTernaryHook() {
+ cond ? useTernaryHook() : null;
+ }
+ `,
+ errors: [],
+ },
+ {
+ code: normalizeIndent`
+ // Invalid because it's a common misunderstanding.
+ // We *could* make it valid but the runtime error could be confusing.
+ function ComponentWithHookInsideCallback() {
+ useEffect(() => {
+ useHookInsideCallback();
+ });
+ }
+ `,
+ errors: [],
+ },
+ {
+ code: normalizeIndent`
+ // Invalid because it's a common misunderstanding.
+ // We *could* make it valid but the runtime error could be confusing.
+ function createComponent() {
+ return function ComponentWithHookInsideCallback() {
+ useEffect(() => {
+ useHookInsideCallback();
+ });
+ }
+ }
+ `,
+ errors: [],
+ },
+ {
+ 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) => {
+ useEffect(() => {
+ useHookInsideCallback();
+ });
+ return
+ });
+ `,
+ errors: [],
+ },
+ {
+ 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 => {
+ useEffect(() => {
+ useHookInsideCallback();
+ });
+ return
+ });
+ `,
+ errors: [],
+ },
+ {
+ code: normalizeIndent`
+ // Invalid because it's a common misunderstanding.
+ // We *could* make it valid but the runtime error could be confusing.
+ function ComponentWithHookInsideCallback() {
+ function handleClick() {
+ useState();
+ }
+ }
+ `,
+ errors: [],
+ },
+ {
+ code: normalizeIndent`
+ // Invalid because it's a common misunderstanding.
+ // We *could* make it valid but the runtime error could be confusing.
+ function createComponent() {
+ return function ComponentWithHookInsideCallback() {
+ function handleClick() {
+ useState();
+ }
+ }
+ }
+ `,
+ errors: [],
+ },
+ {
+ code: normalizeIndent`
+ // Invalid because it's dangerous and might not warn otherwise.
+ // This *must* be invalid.
+ function ComponentWithHookInsideLoop() {
+ while (cond) {
+ useHookInsideLoop();
+ }
+ }
+ `,
+ errors: [],
+ },
+ {
+ code: normalizeIndent`
+ // Invalid because it's dangerous and might not warn otherwise.
+ // This *must* be invalid.
+ function renderItem() {
+ useState();
+ }
+
+ function List(props) {
+ return props.items.map(renderItem);
+ }
+ `,
+ errors: [],
+ },
+ {
+ 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"
+ // cases which must remain invalid.
+ function normalFunctionWithHook() {
+ useHookInsideNormalFunction();
+ }
+ `,
+ errors: [],
+ },
+ {
+ code: normalizeIndent`
+ // These are neither functions nor hooks.
+ function _normalFunctionWithHook() {
+ useHookInsideNormalFunction();
+ }
+ function _useNotAHook() {
+ useHookInsideNormalFunction();
+ }
+ `,
+ errors: [],
+ },
+ {
+ code: normalizeIndent`
+ // Invalid because it's dangerous and might not warn otherwise.
+ // This *must* be invalid.
+ function normalFunctionWithConditionalHook() {
+ if (cond) {
+ useHookInsideNormalFunction();
+ }
+ }
+ `,
+ errors: [],
+ },
+ {
+ code: normalizeIndent`
+ // Invalid because it's dangerous and might not warn otherwise.
+ // This *must* be invalid.
+ function useHookInLoops() {
+ while (a) {
+ useHook1();
+ if (b) return;
+ useHook2();
+ }
+ while (c) {
+ useHook3();
+ if (d) return;
+ useHook4();
+ }
+ }
+ `,
+ errors: [],
+ },
+ {
+ code: normalizeIndent`
+ // Invalid because it's dangerous and might not warn otherwise.
+ // This *must* be invalid.
+ function useHookInLoops() {
+ while (a) {
+ useHook1();
+ if (b) continue;
+ useHook2();
+ }
+ }
+ `,
+ errors: [],
+ },
+ {
+ code: normalizeIndent`
+ // Invalid because it's dangerous and might not warn otherwise.
+ // This *must* be invalid.
+ function useLabeledBlock() {
+ label: {
+ if (a) break label;
+ useHook();
+ }
+ }
+ `,
+ errors: [],
+ },
+ {
+ code: normalizeIndent`
+ // Currently invalid.
+ // These are variations capturing the current heuristic--
+ // we only allow hooks in PascalCase or useFoo functions.
+ // We *could* make some of these valid. But before doing it,
+ // consider specific cases documented above that contain reasoning.
+ function a() { useState(); }
+ const whatever = function b() { useState(); };
+ const c = () => { useState(); };
+ let d = () => useState();
+ e = () => { useState(); };
+ ({f: () => { useState(); }});
+ ({g() { useState(); }});
+ const {j = () => { useState(); }} = {};
+ ({k = () => { useState(); }} = {});
+ `,
+ errors: [],
+ },
+ {
+ code: normalizeIndent`
+ // Invalid because it's dangerous and might not warn otherwise.
+ // This *must* be invalid.
+ function useHook() {
+ if (a) return;
+ useState();
+ }
+ `,
+ errors: [],
+ },
+ {
+ code: normalizeIndent`
+ // Invalid because it's dangerous and might not warn otherwise.
+ // This *must* be invalid.
+ function useHook() {
+ if (a) return;
+ if (b) {
+ console.log('true');
+ } else {
+ console.log('false');
+ }
+ useState();
+ }
+ `,
+ errors: [],
+ },
+ {
+ code: normalizeIndent`
+ // Invalid because it's dangerous and might not warn otherwise.
+ // This *must* be invalid.
+ function useHook() {
+ if (b) {
+ console.log('true');
+ } else {
+ console.log('false');
+ }
+ if (a) return;
+ useState();
+ }
+ `,
+ errors: [],
+ },
+ {
+ code: normalizeIndent`
+ // Invalid because it's dangerous and might not warn otherwise.
+ // This *must* be invalid.
+ function useHook() {
+ a && useHook1();
+ b && useHook2();
+ }
+ `,
+ errors: [],
+ },
+ {
+ code: normalizeIndent`
+ // Invalid because it's dangerous and might not warn otherwise.
+ // This *must* be invalid.
+ function useHook() {
+ try {
+ f();
+ useState();
+ } catch {}
+ }
+ `,
+ errors: [],
+ },
+ {
+ code: normalizeIndent`
+ // Invalid because it's dangerous and might not warn otherwise.
+ // This *must* be invalid.
+ function useHook({ bar }) {
+ let foo1 = bar && useState();
+ let foo2 = bar || useState();
+ let foo3 = bar ?? useState();
+ }
+ `,
+ errors: [],
+ },
+ {
+ code: normalizeIndent`
+ // Invalid because it's dangerous and might not warn otherwise.
+ // This *must* be invalid.
+ const FancyButton = React.forwardRef((props, ref) => {
+ if (props.fancy) {
+ useCustomHook();
+ }
+ return ;
+ });
+ `,
+ errors: [],
+ },
+ {
+ code: normalizeIndent`
+ // Invalid because it's dangerous and might not warn otherwise.
+ // This *must* be invalid.
+ const FancyButton = forwardRef(function(props, ref) {
+ if (props.fancy) {
+ useCustomHook();
+ }
+ return ;
+ });
+ `,
+ errors: [],
+ },
+ {
+ code: normalizeIndent`
+ // Invalid because it's dangerous and might not warn otherwise.
+ // This *must* be invalid.
+ const MemoizedButton = memo(function(props) {
+ if (props.fancy) {
+ useCustomHook();
+ }
+ return ;
+ });
+ `,
+ errors: [],
+ },
+ {
+ code: normalizeIndent`
+ // This is invalid because "use"-prefixed functions used in named
+ // functions are assumed to be hooks.
+ React.unknownFunction(function notAComponent(foo, bar) {
+ useProbablyAHook(bar)
+ });
+ `,
+ errors: [],
+ },
+ {
+ code: normalizeIndent`
+ // Invalid because it's dangerous.
+ // Normally, this would crash, but not if you use inline requires.
+ // This *must* be invalid.
+ // It's expected to have some false positives, but arguably
+ // they are confusing anyway due to the use*() convention
+ // already being associated with Hooks.
+ useState();
+ if (foo) {
+ const foo = React.useCallback(() => {});
+ }
+ useCustomHook();
+ `,
+ errors: [],
+ },
+ {
+ code: normalizeIndent`
+ // Technically this is a false positive.
+ // We *could* make it valid (and it used to be).
+ //
+ // However, top-level Hook-like calls can be very dangerous
+ // in environments with inline requires because they can mask
+ // the runtime error by accident.
+ // So we prefer to disallow it despite the false positive.
+
+ const {createHistory, useBasename} = require('history-2.1.2');
+ const browserHistory = useBasename(createHistory)({
+ basename: '/',
+ });
+ `,
+ errors: [],
+ },
+ {
+ code: normalizeIndent`
+ class ClassComponentWithFeatureFlag extends React.Component {
+ render() {
+ if (foo) {
+ useFeatureFlag();
+ }
+ }
+ }
+ `,
+ errors: [],
+ },
+ {
+ code: normalizeIndent`
+ class ClassComponentWithHook extends React.Component {
+ render() {
+ React.useState();
+ }
+ }
+ `,
+ errors: [],
+ },
+ {
+ code: normalizeIndent`
+ (class {useHook = () => { useState(); }});
+ `,
+ errors: [],
+ },
+ {
+ code: normalizeIndent`
+ (class {useHook() { useState(); }});
+ `,
+ errors: [],
+ },
+ {
+ code: normalizeIndent`
+ (class {h = () => { useState(); }});
+ `,
+ errors: [],
+ },
+ {
+ code: normalizeIndent`
+ (class {i() { useState(); }});
+ `,
+ errors: [],
+ },
+ ],
+};
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-after-early-return.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-after-early-return.expect.md
new file mode 100644
index 0000000000..f8146c8510
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-after-early-return.expect.md
@@ -0,0 +1,21 @@
+
+## Input
+
+```javascript
+function Component(props) {
+ if (props.cond) {
+ return null;
+ }
+ return useHook();
+}
+
+```
+
+
+## Error
+
+```
+[ReactForget] InvalidInput: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (5:5)
+```
+
+
\ No newline at end of file
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/error.invalid-hook-after-early-return.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-after-early-return.js
similarity index 100%
rename from compiler/forget/src/__tests__/fixtures/compiler/error.invalid-hook-after-early-return.js
rename to compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-after-early-return.js
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-for.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-for.expect.md
new file mode 100644
index 0000000000..a9acc25430
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-for.expect.md
@@ -0,0 +1,26 @@
+
+## Input
+
+```javascript
+function Component(props) {
+ let i = 0;
+ for (let x = 0; useHook(x) < 10; useHook(i), x++) {
+ i += useHook(x);
+ }
+ return i;
+}
+
+```
+
+
+## Error
+
+```
+[ReactForget] InvalidInput: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (3:3)
+
+[ReactForget] InvalidInput: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (4:4)
+
+[ReactForget] InvalidInput: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (3:3)
+```
+
+
\ No newline at end of file
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/error.invalid-hook-for.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-for.js
similarity index 100%
rename from compiler/forget/src/__tests__/fixtures/compiler/error.invalid-hook-for.js
rename to compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-for.js
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-if-alternate.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-if-alternate.expect.md
new file mode 100644
index 0000000000..51c2925634
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-if-alternate.expect.md
@@ -0,0 +1,23 @@
+
+## Input
+
+```javascript
+function Component(props) {
+ let x = null;
+ if (props.cond) {
+ } else {
+ x = useHook();
+ }
+ return x;
+}
+
+```
+
+
+## Error
+
+```
+[ReactForget] InvalidInput: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (5:5)
+```
+
+
\ No newline at end of file
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/error.invalid-hook-if-alternate.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-if-alternate.js
similarity index 100%
rename from compiler/forget/src/__tests__/fixtures/compiler/error.invalid-hook-if-alternate.js
rename to compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-if-alternate.js
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-if-consequent.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-if-consequent.expect.md
new file mode 100644
index 0000000000..d21db9859d
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-if-consequent.expect.md
@@ -0,0 +1,22 @@
+
+## Input
+
+```javascript
+function Component(props) {
+ let x = null;
+ if (props.cond) {
+ x = useHook();
+ }
+ return x;
+}
+
+```
+
+
+## Error
+
+```
+[ReactForget] InvalidInput: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (4:4)
+```
+
+
\ No newline at end of file
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/error.invalid-hook-if-consequent.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-if-consequent.js
similarity index 100%
rename from compiler/forget/src/__tests__/fixtures/compiler/error.invalid-hook-if-consequent.js
rename to compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-if-consequent.js
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-32.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-32.expect.md
new file mode 100644
index 0000000000..48f4ab1bd8
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-32.expect.md
@@ -0,0 +1,22 @@
+
+## Input
+
+```javascript
+// Invalid because it's dangerous and might not warn otherwise.
+// This *must* be invalid.
+function ComponentWithConditionalHook() {
+ if (cond) {
+ useConditionalHook();
+ }
+}
+
+```
+
+
+## Error
+
+```
+[ReactForget] InvalidInput: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (5:5)
+```
+
+
\ No newline at end of file
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-32.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-32.js
new file mode 100644
index 0000000000..455b7fb367
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-32.js
@@ -0,0 +1,7 @@
+// Invalid because it's dangerous and might not warn otherwise.
+// This *must* be invalid.
+function ComponentWithConditionalHook() {
+ if (cond) {
+ useConditionalHook();
+ }
+}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-38.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-38.expect.md
new file mode 100644
index 0000000000..0703fedacd
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-38.expect.md
@@ -0,0 +1,22 @@
+
+## Input
+
+```javascript
+// Invalid because it's dangerous and might not warn otherwise.
+// This *must* be invalid.
+function useHookWithConditionalHook() {
+ if (cond) {
+ useConditionalHook();
+ }
+}
+
+```
+
+
+## Error
+
+```
+[ReactForget] InvalidInput: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (5:5)
+```
+
+
\ No newline at end of file
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-38.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-38.js
new file mode 100644
index 0000000000..8b82456138
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-38.js
@@ -0,0 +1,7 @@
+// Invalid because it's dangerous and might not warn otherwise.
+// This *must* be invalid.
+function useHookWithConditionalHook() {
+ if (cond) {
+ useConditionalHook();
+ }
+}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-40.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-40.expect.md
new file mode 100644
index 0000000000..a79a682a9f
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-40.expect.md
@@ -0,0 +1,20 @@
+
+## Input
+
+```javascript
+// Invalid because it's dangerous and might not warn otherwise.
+// This *must* be invalid.
+function ComponentWithTernaryHook() {
+ cond ? useTernaryHook() : null;
+}
+
+```
+
+
+## Error
+
+```
+[ReactForget] InvalidInput: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (4:4)
+```
+
+
\ No newline at end of file
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-40.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-40.js
new file mode 100644
index 0000000000..c5754bd624
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-40.js
@@ -0,0 +1,5 @@
+// Invalid because it's dangerous and might not warn otherwise.
+// This *must* be invalid.
+function ComponentWithTernaryHook() {
+ cond ? useTernaryHook() : null;
+}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-47.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-47.expect.md
new file mode 100644
index 0000000000..a8401e92a7
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-47.expect.md
@@ -0,0 +1,22 @@
+
+## Input
+
+```javascript
+// Invalid because it's dangerous and might not warn otherwise.
+// This *must* be invalid.
+function ComponentWithHookInsideLoop() {
+ while (cond) {
+ useHookInsideLoop();
+ }
+}
+
+```
+
+
+## Error
+
+```
+[ReactForget] InvalidInput: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (5:5)
+```
+
+
\ No newline at end of file
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-47.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-47.js
new file mode 100644
index 0000000000..25177f5938
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-47.js
@@ -0,0 +1,7 @@
+// Invalid because it's dangerous and might not warn otherwise.
+// This *must* be invalid.
+function ComponentWithHookInsideLoop() {
+ while (cond) {
+ useHookInsideLoop();
+ }
+}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-51.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-51.expect.md
new file mode 100644
index 0000000000..9b14df8262
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-51.expect.md
@@ -0,0 +1,22 @@
+
+## Input
+
+```javascript
+// Invalid because it's dangerous and might not warn otherwise.
+// This *must* be invalid.
+function normalFunctionWithConditionalHook() {
+ if (cond) {
+ useHookInsideNormalFunction();
+ }
+}
+
+```
+
+
+## Error
+
+```
+[ReactForget] InvalidInput: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (5:5)
+```
+
+
\ No newline at end of file
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-51.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-51.js
new file mode 100644
index 0000000000..89bfce1eda
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-51.js
@@ -0,0 +1,7 @@
+// Invalid because it's dangerous and might not warn otherwise.
+// This *must* be invalid.
+function normalFunctionWithConditionalHook() {
+ if (cond) {
+ useHookInsideNormalFunction();
+ }
+}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-52.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-52.expect.md
new file mode 100644
index 0000000000..c73794ad22
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-52.expect.md
@@ -0,0 +1,35 @@
+
+## Input
+
+```javascript
+// Invalid because it's dangerous and might not warn otherwise.
+// This *must* be invalid.
+function useHookInLoops() {
+ while (a) {
+ useHook1();
+ if (b) return;
+ useHook2();
+ }
+ while (c) {
+ useHook3();
+ if (d) return;
+ useHook4();
+ }
+}
+
+```
+
+
+## Error
+
+```
+[ReactForget] InvalidInput: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (5:5)
+
+[ReactForget] InvalidInput: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (7:7)
+
+[ReactForget] InvalidInput: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (10:10)
+
+[ReactForget] InvalidInput: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (12:12)
+```
+
+
\ No newline at end of file
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-52.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-52.js
new file mode 100644
index 0000000000..2fb10722a6
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-52.js
@@ -0,0 +1,14 @@
+// Invalid because it's dangerous and might not warn otherwise.
+// This *must* be invalid.
+function useHookInLoops() {
+ while (a) {
+ useHook1();
+ if (b) return;
+ useHook2();
+ }
+ while (c) {
+ useHook3();
+ if (d) return;
+ useHook4();
+ }
+}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-53.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-53.expect.md
new file mode 100644
index 0000000000..33298ad7c6
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-53.expect.md
@@ -0,0 +1,26 @@
+
+## Input
+
+```javascript
+// Invalid because it's dangerous and might not warn otherwise.
+// This *must* be invalid.
+function useHookInLoops() {
+ while (a) {
+ useHook1();
+ if (b) continue;
+ useHook2();
+ }
+}
+
+```
+
+
+## Error
+
+```
+[ReactForget] InvalidInput: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (5:5)
+
+[ReactForget] InvalidInput: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (7:7)
+```
+
+
\ No newline at end of file
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-53.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-53.js
new file mode 100644
index 0000000000..c00a73a62d
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-53.js
@@ -0,0 +1,9 @@
+// Invalid because it's dangerous and might not warn otherwise.
+// This *must* be invalid.
+function useHookInLoops() {
+ while (a) {
+ useHook1();
+ if (b) continue;
+ useHook2();
+ }
+}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-54.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-54.expect.md
new file mode 100644
index 0000000000..bf1bf3336d
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-54.expect.md
@@ -0,0 +1,23 @@
+
+## Input
+
+```javascript
+// Invalid because it's dangerous and might not warn otherwise.
+// This *must* be invalid.
+function useLabeledBlock() {
+ label: {
+ if (a) break label;
+ useHook();
+ }
+}
+
+```
+
+
+## Error
+
+```
+[ReactForget] InvalidInput: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (6:6)
+```
+
+
\ No newline at end of file
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-54.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-54.js
new file mode 100644
index 0000000000..e5f6732143
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-54.js
@@ -0,0 +1,8 @@
+// Invalid because it's dangerous and might not warn otherwise.
+// This *must* be invalid.
+function useLabeledBlock() {
+ label: {
+ if (a) break label;
+ useHook();
+ }
+}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-56.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-56.expect.md
new file mode 100644
index 0000000000..c466eb9725
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-56.expect.md
@@ -0,0 +1,21 @@
+
+## Input
+
+```javascript
+// Invalid because it's dangerous and might not warn otherwise.
+// This *must* be invalid.
+function useHook() {
+ if (a) return;
+ useState();
+}
+
+```
+
+
+## Error
+
+```
+[ReactForget] InvalidInput: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (5:5)
+```
+
+
\ No newline at end of file
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-56.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-56.js
new file mode 100644
index 0000000000..7e77918794
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-56.js
@@ -0,0 +1,6 @@
+// Invalid because it's dangerous and might not warn otherwise.
+// This *must* be invalid.
+function useHook() {
+ if (a) return;
+ useState();
+}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-57.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-57.expect.md
new file mode 100644
index 0000000000..6f3a91949e
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-57.expect.md
@@ -0,0 +1,26 @@
+
+## Input
+
+```javascript
+// Invalid because it's dangerous and might not warn otherwise.
+// This *must* be invalid.
+function useHook() {
+ if (a) return;
+ if (b) {
+ console.log("true");
+ } else {
+ console.log("false");
+ }
+ useState();
+}
+
+```
+
+
+## Error
+
+```
+[ReactForget] InvalidInput: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (10:10)
+```
+
+
\ No newline at end of file
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-57.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-57.js
new file mode 100644
index 0000000000..fb5423937b
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-57.js
@@ -0,0 +1,11 @@
+// Invalid because it's dangerous and might not warn otherwise.
+// This *must* be invalid.
+function useHook() {
+ if (a) return;
+ if (b) {
+ console.log("true");
+ } else {
+ console.log("false");
+ }
+ useState();
+}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-58.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-58.expect.md
new file mode 100644
index 0000000000..95b2471979
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-58.expect.md
@@ -0,0 +1,26 @@
+
+## Input
+
+```javascript
+// Invalid because it's dangerous and might not warn otherwise.
+// This *must* be invalid.
+function useHook() {
+ if (b) {
+ console.log("true");
+ } else {
+ console.log("false");
+ }
+ if (a) return;
+ useState();
+}
+
+```
+
+
+## Error
+
+```
+[ReactForget] InvalidInput: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (10:10)
+```
+
+
\ No newline at end of file
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-58.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-58.js
new file mode 100644
index 0000000000..c2fc30ec88
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-58.js
@@ -0,0 +1,11 @@
+// Invalid because it's dangerous and might not warn otherwise.
+// This *must* be invalid.
+function useHook() {
+ if (b) {
+ console.log("true");
+ } else {
+ console.log("false");
+ }
+ if (a) return;
+ useState();
+}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-59.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-59.expect.md
new file mode 100644
index 0000000000..528da10835
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-59.expect.md
@@ -0,0 +1,23 @@
+
+## Input
+
+```javascript
+// Invalid because it's dangerous and might not warn otherwise.
+// This *must* be invalid.
+function useHook() {
+ a && useHook1();
+ b && useHook2();
+}
+
+```
+
+
+## Error
+
+```
+[ReactForget] InvalidInput: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (4:4)
+
+[ReactForget] InvalidInput: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (5:5)
+```
+
+
\ No newline at end of file
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-59.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-59.js
new file mode 100644
index 0000000000..09245c8884
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.rules-of-hooks-59.js
@@ -0,0 +1,6 @@
+// Invalid because it's dangerous and might not warn otherwise.
+// This *must* be invalid.
+function useHook() {
+ a && useHook1();
+ b && useHook2();
+}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-0.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-0.expect.md
new file mode 100644
index 0000000000..d84e26368f
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-0.expect.md
@@ -0,0 +1,21 @@
+
+## Input
+
+```javascript
+// Valid because components can use hooks.
+function ComponentWithHook() {
+ useHook();
+}
+
+```
+
+## Code
+
+```javascript
+// Valid because components can use hooks.
+function ComponentWithHook() {
+ useHook();
+}
+
+```
+
\ No newline at end of file
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-0.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-0.js
new file mode 100644
index 0000000000..d0a47a7001
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-0.js
@@ -0,0 +1,4 @@
+// Valid because components can use hooks.
+function ComponentWithHook() {
+ useHook();
+}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-1.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-1.expect.md
new file mode 100644
index 0000000000..e4d4d1c66d
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-1.expect.md
@@ -0,0 +1,33 @@
+
+## Input
+
+```javascript
+// Valid because components can use hooks.
+function createComponentWithHook() {
+ return function ComponentWithHook() {
+ useHook();
+ };
+}
+
+```
+
+## Code
+
+```javascript
+import { unstable_useMemoCache as useMemoCache } from "react"; // Valid because components can use hooks.
+function createComponentWithHook() {
+ const $ = useMemoCache(1);
+ let t0;
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
+ t0 = function ComponentWithHook() {
+ useHook();
+ };
+ $[0] = t0;
+ } else {
+ t0 = $[0];
+ }
+ return t0;
+}
+
+```
+
\ No newline at end of file
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-1.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-1.js
new file mode 100644
index 0000000000..9b5bcde4be
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-1.js
@@ -0,0 +1,6 @@
+// Valid because components can use hooks.
+function createComponentWithHook() {
+ return function ComponentWithHook() {
+ useHook();
+ };
+}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-10.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-10.expect.md
new file mode 100644
index 0000000000..50fb6486c9
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-10.expect.md
@@ -0,0 +1,23 @@
+
+## Input
+
+```javascript
+// Valid because hooks can call hooks.
+function useHook() {
+ useHook1();
+ useHook2();
+}
+
+```
+
+## Code
+
+```javascript
+// Valid because hooks can call hooks.
+function useHook() {
+ useHook1();
+ useHook2();
+}
+
+```
+
\ No newline at end of file
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-10.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-10.js
new file mode 100644
index 0000000000..2ab771dc31
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-10.js
@@ -0,0 +1,5 @@
+// Valid because hooks can call hooks.
+function useHook() {
+ useHook1();
+ useHook2();
+}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-11.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-11.expect.md
new file mode 100644
index 0000000000..ca7131796f
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-11.expect.md
@@ -0,0 +1,35 @@
+
+## Input
+
+```javascript
+// Valid because hooks can call hooks.
+function createHook() {
+ return function useHook() {
+ useHook1();
+ useHook2();
+ };
+}
+
+```
+
+## Code
+
+```javascript
+import { unstable_useMemoCache as useMemoCache } from "react"; // Valid because hooks can call hooks.
+function createHook() {
+ const $ = useMemoCache(1);
+ let t0;
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
+ t0 = function useHook() {
+ useHook1();
+ useHook2();
+ };
+ $[0] = t0;
+ } else {
+ t0 = $[0];
+ }
+ return t0;
+}
+
+```
+
\ No newline at end of file
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-11.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-11.js
new file mode 100644
index 0000000000..e2c9e72adf
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-11.js
@@ -0,0 +1,7 @@
+// Valid because hooks can call hooks.
+function createHook() {
+ return function useHook() {
+ useHook1();
+ useHook2();
+ };
+}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-12.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-12.expect.md
new file mode 100644
index 0000000000..6db60d1c8b
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-12.expect.md
@@ -0,0 +1,21 @@
+
+## Input
+
+```javascript
+// Valid because hooks can call hooks.
+function useHook() {
+ useState() && a;
+}
+
+```
+
+## Code
+
+```javascript
+// Valid because hooks can call hooks.
+function useHook() {
+ useState() && a;
+}
+
+```
+
\ No newline at end of file
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-12.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-12.js
new file mode 100644
index 0000000000..6936266e58
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-12.js
@@ -0,0 +1,4 @@
+// Valid because hooks can call hooks.
+function useHook() {
+ useState() && a;
+}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-13.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-13.expect.md
new file mode 100644
index 0000000000..4bcb4dee57
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-13.expect.md
@@ -0,0 +1,21 @@
+
+## Input
+
+```javascript
+// Valid because hooks can call hooks.
+function useHook() {
+ return useHook1() + useHook2();
+}
+
+```
+
+## Code
+
+```javascript
+// Valid because hooks can call hooks.
+function useHook() {
+ return useHook1() + useHook2();
+}
+
+```
+
\ No newline at end of file
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-13.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-13.js
new file mode 100644
index 0000000000..c1356a4b6d
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-13.js
@@ -0,0 +1,4 @@
+// Valid because hooks can call hooks.
+function useHook() {
+ return useHook1() + useHook2();
+}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-14.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-14.expect.md
new file mode 100644
index 0000000000..ae50157e09
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-14.expect.md
@@ -0,0 +1,21 @@
+
+## Input
+
+```javascript
+// Valid because hooks can call hooks.
+function useHook() {
+ return useHook1(useHook2());
+}
+
+```
+
+## Code
+
+```javascript
+// Valid because hooks can call hooks.
+function useHook() {
+ return useHook1(useHook2());
+}
+
+```
+
\ No newline at end of file
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-14.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-14.js
new file mode 100644
index 0000000000..1c16d086bf
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-14.js
@@ -0,0 +1,4 @@
+// Valid because hooks can call hooks.
+function useHook() {
+ return useHook1(useHook2());
+}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-16.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-16.expect.md
new file mode 100644
index 0000000000..978a78e0c1
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-16.expect.md
@@ -0,0 +1,25 @@
+
+## Input
+
+```javascript
+// Valid because hooks can be used in anonymous function arguments to
+// forwardRef.
+const FancyButton = React.forwardRef(function (props, ref) {
+ useHook();
+ return ;
+});
+
+```
+
+## Code
+
+```javascript
+// Valid because hooks can be used in anonymous function arguments to
+// forwardRef.
+const FancyButton = React.forwardRef(function (props, ref) {
+ useHook();
+ return ;
+});
+
+```
+
\ No newline at end of file
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-16.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-16.js
new file mode 100644
index 0000000000..2ce6adcaa6
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-16.js
@@ -0,0 +1,6 @@
+// Valid because hooks can be used in anonymous function arguments to
+// forwardRef.
+const FancyButton = React.forwardRef(function (props, ref) {
+ useHook();
+ return ;
+});
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-17.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-17.expect.md
new file mode 100644
index 0000000000..df628834cc
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-17.expect.md
@@ -0,0 +1,25 @@
+
+## Input
+
+```javascript
+// Valid because hooks can be used in anonymous function arguments to
+// forwardRef.
+const FancyButton = forwardRef(function (props, ref) {
+ useHook();
+ return ;
+});
+
+```
+
+## Code
+
+```javascript
+// Valid because hooks can be used in anonymous function arguments to
+// forwardRef.
+const FancyButton = forwardRef(function (props, ref) {
+ useHook();
+ return ;
+});
+
+```
+
\ No newline at end of file
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-17.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-17.js
new file mode 100644
index 0000000000..4895da63dd
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-17.js
@@ -0,0 +1,6 @@
+// Valid because hooks can be used in anonymous function arguments to
+// forwardRef.
+const FancyButton = forwardRef(function (props, ref) {
+ useHook();
+ return ;
+});
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-19.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-19.expect.md
new file mode 100644
index 0000000000..6682b758c9
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-19.expect.md
@@ -0,0 +1,25 @@
+
+## Input
+
+```javascript
+// Valid because hooks can be used in anonymous function arguments to
+// memo.
+const MemoizedFunction = memo(function (props) {
+ useHook();
+ return ;
+});
+
+```
+
+## Code
+
+```javascript
+// Valid because hooks can be used in anonymous function arguments to
+// memo.
+const MemoizedFunction = memo(function (props) {
+ useHook();
+ return ;
+});
+
+```
+
\ No newline at end of file
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-19.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-19.js
new file mode 100644
index 0000000000..0d26eb583f
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-19.js
@@ -0,0 +1,6 @@
+// Valid because hooks can be used in anonymous function arguments to
+// memo.
+const MemoizedFunction = memo(function (props) {
+ useHook();
+ return ;
+});
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-2.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-2.expect.md
new file mode 100644
index 0000000000..1585d8cce8
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-2.expect.md
@@ -0,0 +1,21 @@
+
+## Input
+
+```javascript
+// Valid because hooks can use hooks.
+function useHookWithHook() {
+ useHook();
+}
+
+```
+
+## Code
+
+```javascript
+// Valid because hooks can use hooks.
+function useHookWithHook() {
+ useHook();
+}
+
+```
+
\ No newline at end of file
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-2.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-2.js
new file mode 100644
index 0000000000..e2073c19e1
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-2.js
@@ -0,0 +1,4 @@
+// Valid because hooks can use hooks.
+function useHookWithHook() {
+ useHook();
+}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-20.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-20.expect.md
new file mode 100644
index 0000000000..73471b1a5c
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-20.expect.md
@@ -0,0 +1,29 @@
+
+## Input
+
+```javascript
+// Valid because classes can call functions.
+// We don't consider these to be hooks.
+class C {
+ m() {
+ this.useHook();
+ super.useHook();
+ }
+}
+
+```
+
+## Code
+
+```javascript
+// Valid because classes can call functions.
+// We don't consider these to be hooks.
+class C {
+ m() {
+ this.useHook();
+ super.useHook();
+ }
+}
+
+```
+
\ No newline at end of file
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-20.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-20.js
new file mode 100644
index 0000000000..76a3d0cf12
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-20.js
@@ -0,0 +1,8 @@
+// Valid because classes can call functions.
+// We don't consider these to be hooks.
+class C {
+ m() {
+ this.useHook();
+ super.useHook();
+ }
+}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-22.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-22.expect.md
new file mode 100644
index 0000000000..116baa88bb
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-22.expect.md
@@ -0,0 +1,27 @@
+
+## Input
+
+```javascript
+// Valid because they're not matching use[A-Z].
+fooState();
+_use();
+_useState();
+use_hook();
+// also valid because it's not matching the PascalCase namespace
+jest.useFakeTimer();
+
+```
+
+## Code
+
+```javascript
+// Valid because they're not matching use[A-Z].
+fooState();
+_use();
+_useState();
+use_hook();
+// also valid because it's not matching the PascalCase namespace
+jest.useFakeTimer();
+
+```
+
\ No newline at end of file
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-22.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-22.js
new file mode 100644
index 0000000000..f5438b9032
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-22.js
@@ -0,0 +1,7 @@
+// Valid because they're not matching use[A-Z].
+fooState();
+_use();
+_useState();
+use_hook();
+// also valid because it's not matching the PascalCase namespace
+jest.useFakeTimer();
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-23.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-23.expect.md
new file mode 100644
index 0000000000..ce1acbb45a
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-23.expect.md
@@ -0,0 +1,35 @@
+
+## Input
+
+```javascript
+// 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
+
+```javascript
+// 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();
+ }
+ });
+}
+
+```
+
\ No newline at end of file
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-23.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-23.js
new file mode 100644
index 0000000000..79f18b9844
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-23.js
@@ -0,0 +1,11 @@
+// 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();
+ }
+ });
+}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-25.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-25.expect.md
new file mode 100644
index 0000000000..60cbea92b8
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-25.expect.md
@@ -0,0 +1,27 @@
+
+## Input
+
+```javascript
+// 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
+
+```javascript
+// 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);
+ }
+});
+
+```
+
\ No newline at end of file
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-25.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-25.js
new file mode 100644
index 0000000000..189de4201d
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-25.js
@@ -0,0 +1,7 @@
+// 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);
+ }
+});
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-29.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-29.expect.md
new file mode 100644
index 0000000000..7761022d62
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-29.expect.md
@@ -0,0 +1,243 @@
+
+## Input
+
+```javascript
+// 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();
+}
+
+```
+
+## Code
+
+```javascript
+// Is valid but hard to compute by brute-forcing
+function MyComponent() {
+ if (c) {
+ }
+ if (c) {
+ }
+ if (c) {
+ }
+ if (c) {
+ }
+ if (c) {
+ }
+ if (c) {
+ }
+ if (c) {
+ }
+ if (c) {
+ }
+ if (c) {
+ }
+ if (c) {
+ }
+ if (c) {
+ }
+ if (c) {
+ }
+ if (c) {
+ }
+ if (c) {
+ }
+ if (c) {
+ }
+ if (c) {
+ }
+ if (c) {
+ }
+ if (c) {
+ }
+ if (c) {
+ }
+ if (c) {
+ }
+ if (c) {
+ }
+ if (c) {
+ }
+ if (c) {
+ }
+ if (c) {
+ }
+ if (c) {
+ }
+ if (c) {
+ }
+ if (c) {
+ }
+ if (c) {
+ }
+ if (c) {
+ }
+ if (c) {
+ }
+ if (c) {
+ }
+ if (c) {
+ }
+ if (c) {
+ }
+ if (c) {
+ }
+ if (c) {
+ }
+ if (c) {
+ }
+ if (c) {
+ }
+ if (c) {
+ }
+ if (c) {
+ }
+ if (c) {
+ }
+
+ useHook();
+ useHook();
+ useHook();
+ useHook();
+ useHook();
+ useHook();
+ useHook();
+ useHook();
+ useHook();
+ useHook();
+}
+
+```
+
\ No newline at end of file
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-29.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-29.js
new file mode 100644
index 0000000000..e19bdb9583
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-29.js
@@ -0,0 +1,136 @@
+// 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();
+}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-3.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-3.expect.md
new file mode 100644
index 0000000000..acc96d1ed4
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-3.expect.md
@@ -0,0 +1,33 @@
+
+## Input
+
+```javascript
+// Valid because hooks can use hooks.
+function createHook() {
+ return function useHookWithHook() {
+ useHook();
+ };
+}
+
+```
+
+## Code
+
+```javascript
+import { unstable_useMemoCache as useMemoCache } from "react"; // Valid because hooks can use hooks.
+function createHook() {
+ const $ = useMemoCache(1);
+ let t0;
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
+ t0 = function useHookWithHook() {
+ useHook();
+ };
+ $[0] = t0;
+ } else {
+ t0 = $[0];
+ }
+ return t0;
+}
+
+```
+
\ No newline at end of file
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-3.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-3.js
new file mode 100644
index 0000000000..405e1e77ea
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-3.js
@@ -0,0 +1,6 @@
+// Valid because hooks can use hooks.
+function createHook() {
+ return function useHookWithHook() {
+ useHook();
+ };
+}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-4.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-4.expect.md
new file mode 100644
index 0000000000..8f59bcc9df
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-4.expect.md
@@ -0,0 +1,21 @@
+
+## Input
+
+```javascript
+// Valid because components can call functions.
+function ComponentWithNormalFunction() {
+ doSomething();
+}
+
+```
+
+## Code
+
+```javascript
+// Valid because components can call functions.
+function ComponentWithNormalFunction() {
+ doSomething();
+}
+
+```
+
\ No newline at end of file
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-4.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-4.js
new file mode 100644
index 0000000000..7e30058715
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-4.js
@@ -0,0 +1,4 @@
+// Valid because components can call functions.
+function ComponentWithNormalFunction() {
+ doSomething();
+}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-5.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-5.expect.md
new file mode 100644
index 0000000000..6d270fd527
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-5.expect.md
@@ -0,0 +1,21 @@
+
+## Input
+
+```javascript
+// Valid because functions can call functions.
+function normalFunctionWithNormalFunction() {
+ doSomething();
+}
+
+```
+
+## Code
+
+```javascript
+// Valid because functions can call functions.
+function normalFunctionWithNormalFunction() {
+ doSomething();
+}
+
+```
+
\ No newline at end of file
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-5.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-5.js
new file mode 100644
index 0000000000..536b797642
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-5.js
@@ -0,0 +1,4 @@
+// Valid because functions can call functions.
+function normalFunctionWithNormalFunction() {
+ doSomething();
+}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-6.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-6.expect.md
new file mode 100644
index 0000000000..16fb57452a
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-6.expect.md
@@ -0,0 +1,25 @@
+
+## Input
+
+```javascript
+// Valid because functions can call functions.
+function normalFunctionWithConditionalFunction() {
+ if (cond) {
+ doSomething();
+ }
+}
+
+```
+
+## Code
+
+```javascript
+// Valid because functions can call functions.
+function normalFunctionWithConditionalFunction() {
+ if (cond) {
+ doSomething();
+ }
+}
+
+```
+
\ No newline at end of file
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-6.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-6.js
new file mode 100644
index 0000000000..7f42ef2dce
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-6.js
@@ -0,0 +1,6 @@
+// Valid because functions can call functions.
+function normalFunctionWithConditionalFunction() {
+ if (cond) {
+ doSomething();
+ }
+}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-8.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-8.expect.md
new file mode 100644
index 0000000000..43f76c9428
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-8.expect.md
@@ -0,0 +1,22 @@
+
+## Input
+
+```javascript
+// 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
+
+```javascript
+// 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() {}
+
+```
+
\ No newline at end of file
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-8.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-8.js
new file mode 100644
index 0000000000..a312b30ff7
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-8.js
@@ -0,0 +1,6 @@
+// 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();
+}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-33.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-33.js
new file mode 100644
index 0000000000..72db4e9ff4
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-33.js
@@ -0,0 +1,7 @@
+// @skip
+
+Hook.useState();
+Hook._useState();
+Hook.use42();
+Hook.useHook();
+Hook.use_hook();
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-34.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-34.js
new file mode 100644
index 0000000000..7980cffb35
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-34.js
@@ -0,0 +1,8 @@
+// @skip
+
+class C {
+ m() {
+ This.useHook();
+ Super.useHook();
+ }
+}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-35.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-35.js
new file mode 100644
index 0000000000..92b575101f
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-35.js
@@ -0,0 +1,11 @@
+// @skip
+
+// 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 {
+ render() {
+ if (cond) {
+ FooStore.useFeatureFlag();
+ }
+ }
+}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-36.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-36.js
new file mode 100644
index 0000000000..5084191574
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-36.js
@@ -0,0 +1,9 @@
+// @skip
+
+// Invalid because it's dangerous and might not warn otherwise.
+// This *must* be invalid.
+function ComponentWithConditionalHook() {
+ if (cond) {
+ Namespace.useConditionalHook();
+ }
+}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-37.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-37.js
new file mode 100644
index 0000000000..2296852857
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-37.js
@@ -0,0 +1,11 @@
+// @skip
+
+// Invalid because it's dangerous and might not warn otherwise.
+// This *must* be invalid.
+function createComponent() {
+ return function ComponentWithConditionalHook() {
+ if (cond) {
+ useConditionalHook();
+ }
+ };
+}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-39.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-39.js
new file mode 100644
index 0000000000..5e2b4ddb20
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-39.js
@@ -0,0 +1,11 @@
+// @skip
+
+// Invalid because it's dangerous and might not warn otherwise.
+// This *must* be invalid.
+function createHook() {
+ return function useHookWithConditionalHook() {
+ if (cond) {
+ useConditionalHook();
+ }
+ };
+}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-41.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-41.js
new file mode 100644
index 0000000000..a9ac98472a
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-41.js
@@ -0,0 +1,9 @@
+// @skip
+
+// Invalid because it's a common misunderstanding.
+// We *could* make it valid but the runtime error could be confusing.
+function ComponentWithHookInsideCallback() {
+ useEffect(() => {
+ useHookInsideCallback();
+ });
+}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-42.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-42.js
new file mode 100644
index 0000000000..5cdab930c1
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-42.js
@@ -0,0 +1,11 @@
+// @skip
+
+// Invalid because it's a common misunderstanding.
+// We *could* make it valid but the runtime error could be confusing.
+function createComponent() {
+ return function ComponentWithHookInsideCallback() {
+ useEffect(() => {
+ useHookInsideCallback();
+ });
+ };
+}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-45.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-45.js
new file mode 100644
index 0000000000..dece00303c
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-45.js
@@ -0,0 +1,9 @@
+// @skip
+
+// Invalid because it's a common misunderstanding.
+// We *could* make it valid but the runtime error could be confusing.
+function ComponentWithHookInsideCallback() {
+ function handleClick() {
+ useState();
+ }
+}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-46.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-46.js
new file mode 100644
index 0000000000..5bc696cdd9
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-46.js
@@ -0,0 +1,11 @@
+// @skip
+
+// Invalid because it's a common misunderstanding.
+// We *could* make it valid but the runtime error could be confusing.
+function createComponent() {
+ return function ComponentWithHookInsideCallback() {
+ function handleClick() {
+ useState();
+ }
+ };
+}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-48.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-48.js
new file mode 100644
index 0000000000..d6a0168af5
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-48.js
@@ -0,0 +1,11 @@
+// @skip
+
+// Invalid because it's dangerous and might not warn otherwise.
+// This *must* be invalid.
+function renderItem() {
+ useState();
+}
+
+function List(props) {
+ return props.items.map(renderItem);
+}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-49.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-49.js
new file mode 100644
index 0000000000..04ecc3564a
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-49.js
@@ -0,0 +1,9 @@
+// @skip
+
+// 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"
+// cases which must remain invalid.
+function normalFunctionWithHook() {
+ useHookInsideNormalFunction();
+}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-50.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-50.js
new file mode 100644
index 0000000000..a2e4bdbb21
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-50.js
@@ -0,0 +1,9 @@
+// @skip
+
+// These are neither functions nor hooks.
+function _normalFunctionWithHook() {
+ useHookInsideNormalFunction();
+}
+function _useNotAHook() {
+ useHookInsideNormalFunction();
+}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-63.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-63.js
new file mode 100644
index 0000000000..140359e3f5
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-63.js
@@ -0,0 +1,10 @@
+// @skip
+
+// Invalid because it's dangerous and might not warn otherwise.
+// This *must* be invalid.
+const FancyButton = forwardRef(function (props, ref) {
+ if (props.fancy) {
+ useCustomHook();
+ }
+ return ;
+});
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-64.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-64.js
new file mode 100644
index 0000000000..a257479288
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-64.js
@@ -0,0 +1,10 @@
+// @skip
+
+// Invalid because it's dangerous and might not warn otherwise.
+// This *must* be invalid.
+const MemoizedButton = memo(function (props) {
+ if (props.fancy) {
+ useCustomHook();
+ }
+ return ;
+});
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-65.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-65.js
new file mode 100644
index 0000000000..b647cb6968
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-65.js
@@ -0,0 +1,7 @@
+// @skip
+
+// This is invalid because "use"-prefixed functions used in named
+// functions are assumed to be hooks.
+React.unknownFunction(function notAComponent(foo, bar) {
+ useProbablyAHook(bar);
+});
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-66.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-66.js
new file mode 100644
index 0000000000..e60fc3c5ec
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-66.js
@@ -0,0 +1,13 @@
+// @skip
+
+// Invalid because it's dangerous.
+// Normally, this would crash, but not if you use inline requires.
+// This *must* be invalid.
+// It's expected to have some false positives, but arguably
+// they are confusing anyway due to the use*() convention
+// already being associated with Hooks.
+useState();
+if (foo) {
+ const foo = React.useCallback(() => {});
+}
+useCustomHook();
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-67.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-67.js
new file mode 100644
index 0000000000..175e9631a2
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-67.js
@@ -0,0 +1,14 @@
+// @skip
+
+// Technically this is a false positive.
+// We *could* make it valid (and it used to be).
+//
+// However, top-level Hook-like calls can be very dangerous
+// in environments with inline requires because they can mask
+// the runtime error by accident.
+// So we prefer to disallow it despite the false positive.
+
+const { createHistory, useBasename } = require("history-2.1.2");
+const browserHistory = useBasename(createHistory)({
+ basename: "/",
+});
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-68.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-68.js
new file mode 100644
index 0000000000..861a9bb241
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-68.js
@@ -0,0 +1,9 @@
+// @skip
+
+class ClassComponentWithFeatureFlag extends React.Component {
+ render() {
+ if (foo) {
+ useFeatureFlag();
+ }
+ }
+}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-69.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-69.js
new file mode 100644
index 0000000000..59b5f2eeee
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-69.js
@@ -0,0 +1,7 @@
+// @skip
+
+class ClassComponentWithHook extends React.Component {
+ render() {
+ React.useState();
+ }
+}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-70.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-70.js
new file mode 100644
index 0000000000..d0217a5ab2
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-70.js
@@ -0,0 +1,7 @@
+// @skip
+
+(class {
+ useHook = () => {
+ useState();
+ };
+});
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-71.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-71.js
new file mode 100644
index 0000000000..17ade6f71f
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-71.js
@@ -0,0 +1,7 @@
+// @skip
+
+(class {
+ useHook() {
+ useState();
+ }
+});
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-72.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-72.js
new file mode 100644
index 0000000000..54cd765991
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-72.js
@@ -0,0 +1,7 @@
+// @skip
+
+(class {
+ h = () => {
+ useState();
+ };
+});
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-73.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-73.js
new file mode 100644
index 0000000000..3500c604ff
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-73.js
@@ -0,0 +1,7 @@
+// @skip
+
+(class {
+ i() {
+ useState();
+ }
+});
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-15.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-15.js
new file mode 100644
index 0000000000..05d81522d1
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-15.js
@@ -0,0 +1,8 @@
+// @skip
+
+// Valid because hooks can be used in anonymous arrow-function arguments
+// to forwardRef.
+const FancyButton = React.forwardRef((props, ref) => {
+ useHook();
+ return ;
+});
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-18.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-18.js
new file mode 100644
index 0000000000..5f846ae822
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-18.js
@@ -0,0 +1,8 @@
+// @skip
+
+// Valid because hooks can be used in anonymous function arguments to
+// React.memo.
+const MemoizedFunction = React.memo((props) => {
+ useHook();
+ return ;
+});
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-21.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-21.js
new file mode 100644
index 0000000000..6b10b05404
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-21.js
@@ -0,0 +1,7 @@
+// @skip
+
+// Valid -- this is a regression test.
+jest.useFakeTimers();
+beforeEach(() => {
+ jest.useRealTimers();
+});
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-24.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-24.js
new file mode 100644
index 0000000000..cd0c2ebf91
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-24.js
@@ -0,0 +1,9 @@
+// @skip
+
+// 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);
+ }
+});
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-26.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-26.js
new file mode 100644
index 0000000000..cd3f938a37
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-26.js
@@ -0,0 +1,7 @@
+// @skip
+
+// Regression test for incorrectly flagged valid code.
+function RegressionTest() {
+ const foo = cond ? a : b;
+ useState();
+}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-27.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-27.js
new file mode 100644
index 0000000000..f6abe0def9
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-27.js
@@ -0,0 +1,9 @@
+// @skip
+
+// Valid because exceptions abort rendering
+function RegressionTest() {
+ if (page == null) {
+ throw new Error("oh no!");
+ }
+ useState();
+}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-28.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-28.js
new file mode 100644
index 0000000000..3a901a444f
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-28.js
@@ -0,0 +1,11 @@
+// @skip
+
+// 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(() => {});
+}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-30.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-30.js
new file mode 100644
index 0000000000..d057227239
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-30.js
@@ -0,0 +1,69 @@
+// @skip
+
+// 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;
+
+ useSomeHook();
+
+ 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}
+
+ );
+};
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-31.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-31.js
new file mode 100644
index 0000000000..bffc90fb13
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-31.js
@@ -0,0 +1,12 @@
+// @skip
+
+// 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);
+}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-43.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-43.js
new file mode 100644
index 0000000000..0a752f5046
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-43.js
@@ -0,0 +1,10 @@
+// @skip
+
+// 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) => {
+ useEffect(() => {
+ useHookInsideCallback();
+ });
+ return ;
+});
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-44.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-44.js
new file mode 100644
index 0000000000..86c29955db
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-44.js
@@ -0,0 +1,10 @@
+// @skip
+
+// Invalid because it's a common misunderstanding.
+// We *could* make it valid but the runtime error could be confusing.
+const ComponentWithHookInsideCallback = React.memo((props) => {
+ useEffect(() => {
+ useHookInsideCallback();
+ });
+ return ;
+});
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-55.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-55.js
new file mode 100644
index 0000000000..f255fcd47b
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-55.js
@@ -0,0 +1,40 @@
+// @skip
+
+// Currently invalid.
+// These are variations capturing the current heuristic--
+// we only allow hooks in PascalCase or useFoo functions.
+// We *could* make some of these valid. But before doing it,
+// consider specific cases documented above that contain reasoning.
+function a() {
+ useState();
+}
+const whatever = function b() {
+ useState();
+};
+const c = () => {
+ useState();
+};
+let d = () => useState();
+e = () => {
+ useState();
+};
+({
+ f: () => {
+ useState();
+ },
+});
+({
+ g() {
+ useState();
+ },
+});
+const {
+ j = () => {
+ useState();
+ },
+} = {};
+({
+ k = () => {
+ useState();
+ },
+} = {});
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-60.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-60.js
new file mode 100644
index 0000000000..8f9bb521aa
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-60.js
@@ -0,0 +1,10 @@
+// @skip
+
+// Invalid because it's dangerous and might not warn otherwise.
+// This *must* be invalid.
+function useHook() {
+ try {
+ f();
+ useState();
+ } catch {}
+}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-61.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-61.js
new file mode 100644
index 0000000000..f808814329
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-61.js
@@ -0,0 +1,9 @@
+// @skip
+
+// Invalid because it's dangerous and might not warn otherwise.
+// This *must* be invalid.
+function useHook({ bar }) {
+ let foo1 = bar && useState();
+ let foo2 = bar || useState();
+ let foo3 = bar ?? useState();
+}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-62.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-62.js
new file mode 100644
index 0000000000..c264c2004d
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-62.js
@@ -0,0 +1,10 @@
+// @skip
+
+// Invalid because it's dangerous and might not warn otherwise.
+// This *must* be invalid.
+const FancyButton = React.forwardRef((props, ref) => {
+ if (props.fancy) {
+ useCustomHook();
+ }
+ return ;
+});
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-7.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-7.js
new file mode 100644
index 0000000000..93f6a55ac5
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-7.js
@@ -0,0 +1,8 @@
+// @skip
+
+// Valid because functions can call functions.
+function functionThatStartsWithUseButIsntAHook() {
+ if (cond) {
+ userFetch();
+ }
+}
diff --git a/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-9.js b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-9.js
new file mode 100644
index 0000000000..c43ac4caae
--- /dev/null
+++ b/compiler/forget/src/__tests__/fixtures/compiler/rules-of-hooks/todo.rules-of-hooks-9.js
@@ -0,0 +1,39 @@
+// @skip
+
+// 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();
+};
diff --git a/compiler/forget/src/__tests__/test-utils/generateTestsFromFixtures.ts b/compiler/forget/src/__tests__/test-utils/generateTestsFromFixtures.ts
index ffaf53ae70..149f4cd65f 100644
--- a/compiler/forget/src/__tests__/test-utils/generateTestsFromFixtures.ts
+++ b/compiler/forget/src/__tests__/test-utils/generateTestsFromFixtures.ts
@@ -10,6 +10,7 @@
/* global expect,test */
import fs from "fs";
+import glob from "glob";
import path from "path";
import { GatingOptions, PluginOptions } from "../../Babel/PluginOptions";
@@ -55,7 +56,9 @@ export default function generateTestsFromFixtures(
) {
let files: Array;
try {
- files = fs.readdirSync(fixturesPath);
+ files = glob.sync("**/*.{js,md}", {
+ cwd: fixturesPath,
+ });
} catch (e) {
if (e.code === "ENOENT") {
files = [];
@@ -233,11 +236,12 @@ function matchInputOutputFixtures(files: string[], fixturesPath: string) {
);
}
entry.input = resolvedPath;
- const outputFile = path.format({
- dir: fixturesPath,
- name: basename,
- ext: EXPECT_SUFFIX,
- });
+ const outputName = `${basename}${EXPECT_SUFFIX}`;
+ const outputFile = path.join(
+ fixturesPath,
+ path.dirname(file),
+ outputName
+ );
entry.output = outputFile;
}
}
diff --git a/compiler/forget/yarn.lock b/compiler/forget/yarn.lock
index b0e6f1b618..d971d03b4b 100644
--- a/compiler/forget/yarn.lock
+++ b/compiler/forget/yarn.lock
@@ -81,7 +81,7 @@
source-map "^0.5.0"
trim-right "^1.0.1"
-"@babel/generator@^7.0.0", "@babel/generator@^7.21.4":
+"@babel/generator@^7.0.0":
version "7.21.4"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.21.4.tgz#64a94b7448989f421f919d5239ef553b37bb26bc"
integrity sha512-NieM3pVIYW2SwGzKoqfPrQsf4xGs9M9AIG3ThppsSRmO+m7eQhmI6amajKMUeIO37wFfsvnvcxQFx6x6iqxDnA==
@@ -100,6 +100,16 @@
"@jridgewell/gen-mapping" "^0.3.2"
jsesc "^2.5.1"
+"@babel/generator@^7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.21.5.tgz#c0c0e5449504c7b7de8236d99338c3e2a340745f"
+ integrity sha512-SrKK/sRv8GesIW1bDagf9cCG38IOMYZusoe1dfg0D8aiUe3Amvoj1QtjTPAWcfrZFvIwlleLb0gxzQidL9w14w==
+ dependencies:
+ "@babel/types" "^7.21.5"
+ "@jridgewell/gen-mapping" "^0.3.2"
+ "@jridgewell/trace-mapping" "^0.3.17"
+ jsesc "^2.5.1"
+
"@babel/helper-annotate-as-pure@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb"
@@ -135,6 +145,11 @@
resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be"
integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==
+"@babel/helper-environment-visitor@^7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.21.5.tgz#c769afefd41d171836f7cb63e295bedf689d48ba"
+ integrity sha512-IYl4gZ3ETsWocUWgsFZLM5i1BYx9SoemminVEXadgLBa9TdeorzgLKm8wWLA6J1N/kT3Kch8XIk1laNzYoHKvQ==
+
"@babel/helper-function-name@^7.1.0", "@babel/helper-function-name@^7.19.0":
version "7.19.0"
resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz#941574ed5390682e872e52d3f38ce9d1bef4648c"
@@ -259,6 +274,11 @@
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63"
integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==
+"@babel/helper-string-parser@^7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz#2b3eea65443c6bdc31c22d037c65f6d323b6b2bd"
+ integrity sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==
+
"@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1":
version "7.19.1"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2"
@@ -307,7 +327,12 @@
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.2.tgz#dacafadfc6d7654c3051a66d6fe55b6cb2f2a0b3"
integrity sha512-URpaIJQwEkEC2T9Kn+Ai6Xe/02iNaVCuT/PtoRz3GPVJVDpPd7mLo+VddTbhCRU9TXqW5mSrQfXZyi8kDKOVpQ==
-"@babel/parser@^7.21.4", "@babel/parser@^7.7.4":
+"@babel/parser@^7.21.5":
+ version "7.21.8"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.8.tgz#642af7d0333eab9c0ad70b14ac5e76dbde7bfdf8"
+ integrity sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA==
+
+"@babel/parser@^7.7.4":
version "7.21.4"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.4.tgz#94003fdfc520bbe2875d4ae557b43ddb6d880f17"
integrity sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw==
@@ -582,18 +607,18 @@
lodash "^4.17.10"
"@babel/traverse@^7.19.1":
- version "7.21.4"
- resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.21.4.tgz#a836aca7b116634e97a6ed99976236b3282c9d36"
- integrity sha512-eyKrRHKdyZxqDm+fV1iqL9UAHMoIg0nDaGqfIOd8rKH17m5snv7Gn4qgjBoFfLz9APvjFU/ICT00NVCv1Epp8Q==
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.21.5.tgz#ad22361d352a5154b498299d523cf72998a4b133"
+ integrity sha512-AhQoI3YjWi6u/y/ntv7k48mcrCXmus0t79J9qPNlk/lAsFlCiJ047RmbfMOawySTHtywXhbXgpx/8nXMYd+oFw==
dependencies:
"@babel/code-frame" "^7.21.4"
- "@babel/generator" "^7.21.4"
- "@babel/helper-environment-visitor" "^7.18.9"
+ "@babel/generator" "^7.21.5"
+ "@babel/helper-environment-visitor" "^7.21.5"
"@babel/helper-function-name" "^7.21.0"
"@babel/helper-hoist-variables" "^7.18.6"
"@babel/helper-split-export-declaration" "^7.18.6"
- "@babel/parser" "^7.21.4"
- "@babel/types" "^7.21.4"
+ "@babel/parser" "^7.21.5"
+ "@babel/types" "^7.21.5"
debug "^4.1.0"
globals "^11.1.0"
@@ -659,6 +684,15 @@
"@babel/helper-validator-identifier" "^7.19.1"
to-fast-properties "^2.0.0"
+"@babel/types@^7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.5.tgz#18dfbd47c39d3904d5db3d3dc2cc80bedb60e5b6"
+ integrity sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q==
+ dependencies:
+ "@babel/helper-string-parser" "^7.21.5"
+ "@babel/helper-validator-identifier" "^7.19.1"
+ to-fast-properties "^2.0.0"
+
"@bcoe/v8-coverage@^0.2.3":
version "0.2.3"
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
@@ -1110,6 +1144,14 @@
dependencies:
"@babel/types" "^7.3.0"
+"@types/glob@^8.1.0":
+ version "8.1.0"
+ resolved "https://registry.yarnpkg.com/@types/glob/-/glob-8.1.0.tgz#b63e70155391b0584dce44e7ea25190bbc38f2fc"
+ integrity sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==
+ dependencies:
+ "@types/minimatch" "^5.1.2"
+ "@types/node" "*"
+
"@types/graceful-fs@^4.1.3":
version "4.1.5"
resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15"
@@ -1171,6 +1213,11 @@
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==
+"@types/minimatch@^5.1.2":
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-5.1.2.tgz#07508b45797cb81ec3f273011b054cd0755eddca"
+ integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==
+
"@types/node@*", "@types/node@^18.7.18":
version "18.7.19"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.19.tgz#ad83aa9b7af470fab7e0f562be87e97dc8ffe08e"