Repository: facebook/react
Pull request: react-hooks/rules-of-hooks: Improve support for `do/while` loops

## Summary

This is an alternative to #28714. 

Reverts the main fix there and implements a different solution by adding special handling for `DoWhileStatement`.

Also adds a unit test that was breaking the previous fix (props to @skratchdot for it). 

Please, refer to the inline comments for more information.

Fixes #31687. Also see https://github.com/facebook/react/pull/28714#issuecomment-2531264046.

## How did you test this change?

I've added unit tests that cover the case and verified that they pass by running:

```
yarn test packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js --watch
```

I've also verified that the rest of the tests continue to pass by running:

```
yarn test
```

and

```
yarn test --prod
```


Changed files:
filename: packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js
patch: @@ -550,6 +550,18 @@ const tests = {
       // TODO: this should error but doesn't.
       // errors: [genericError('useState')],
     },
+    {
+      code: normalizeIndent`
+        // Valid because the hook is outside of the loop
+        const Component = () => {
+          const [state, setState] = useState(0);
+          for (let i = 0; i < 10; i++) {
+            console.log(i);
+          }
+          return <div></div>;
+        };
+      `,
+    },
   ],
   invalid: [
     {

filename: packages/eslint-plugin-react-hooks/src/RulesOfHooks.js
patch: @@ -100,6 +100,16 @@ function isInsideComponentOrHook(node) {
   return false;
 }
 
+function isInsideDoWhileLoop(node) {
+  while (node) {
+    if (node.type === 'DoWhileStatement') {
+      return true;
+    }
+    node = node.parent;
+  }
+  return false;
+}
+
 function isUseEffectEventIdentifier(node) {
   if (__EXPERIMENTAL__) {
     return node.type === 'Identifier' && node.name === 'useEffectEvent';
@@ -295,7 +305,7 @@ export default {
           if (pathList.has(segment.id)) {
             const pathArray = Array.from(pathList);
             const cyclicSegments = pathArray.slice(
-              pathArray.indexOf(segment.id) - 1,
+              pathArray.indexOf(segment.id) + 1,
             );
             for (const cyclicSegment of cyclicSegments) {
               cyclic.add(cyclicSegment);
@@ -485,7 +495,10 @@ export default {
           for (const hook of reactHooks) {
             // Report an error if a hook may be called more then once.
             // `use(...)` can be called in loops.
-            if (cycled && !isUseIdentifier(hook)) {
+            if (
+              (cycled || isInsideDoWhileLoop(hook)) &&
+              !isUseIdentifier(hook)
+            ) {
               context.report({
                 node: hook,
                 message:
@@ -520,7 +533,8 @@ export default {
               if (
                 !cycled &&
                 pathsFromStartToEnd !== allPathsFromStartToEnd &&
-                !isUseIdentifier(hook) // `use(...)` can be called conditionally.
+                !isUseIdentifier(hook) && // `use(...)` can be called conditionally.
+                !isInsideDoWhileLoop(hook) // wrapping do/while loops are checked separately.
               ) {
                 const message =
                   `React Hook "${getSource(hook)}" is called ` +