Repository: psf/black

Issue:
Hang when parsing `# fmt: on` in the middle of an expression
**Describe the bug**
`black` hangs when parsing `# fmt: on` in the middle of an expression

**To Reproduce**
1. Take this file
```py
from subprocess import run

path = ["."]
run(
    # fmt: off
    [
        "ls",
        "-la",
    ]
    # fmt: on
    + path,
    check=True,
)
```
2. Run _Black_ on it with these arguments `--target-version py38`
3. Observe that black never exits.

**Expected behavior**
Either black should format the file fine (19.10b0 does not have trouble here) or it should error out and tell me it failed to format.

**Environment (please complete the following information):**

- Version: [e.g. master] 20.8b1
- OS and Python version: [e.g. Linux/Python 3.7.4rc1] Linux/Python 3.8.5

**Does this bug also happen on master?** To answer this, you have two options:

1. Use the online formatter at https://black.now.sh/?version=master, which will use the
   latest master branch.
2. Or run _Black_ on your machine:
   - create a new virtualenv (make sure it's the same Python version);
   - clone this repository;
   - run `pip install -e .`;
   - make sure it's sane by running `python -m unittest`; and
   - run `black` like you did last time.

Yes, it is happening on the online formatter as well.

**Additional context**
If I remove `check=True,` from my example, it works fine. If I move `# fmt: on` after `+ path,` it works fine.

We've modified our code to put `# fmt: on` in a more reasonable location, but still wanted to report it as I wouldn't expect a hang.


Discussion:
Yeah, this is definitely hanging and wow does this (partial) call graph look wrong. Created using 858225d34dc49ca353f9e573dd82dd5845766115 and the code in the bug report.
![Screenshot from 2021-03-02 20-22-21](https://user-images.githubusercontent.com/63936253/109738198-63d24400-7b95-11eb-8de8-b0c99f8e904d.png)


Resolving patch:
diff --git a/src/black/comments.py b/src/black/comments.py
--- a/src/black/comments.py
+++ b/src/black/comments.py
@@ -13,7 +13,7 @@
 from blib2to3.pgen2 import token
 
 from black.nodes import first_leaf_column, preceding_leaf, container_of
-from black.nodes import STANDALONE_COMMENT, WHITESPACE
+from black.nodes import CLOSING_BRACKETS, STANDALONE_COMMENT, WHITESPACE
 
 # types
 LN = Union[Leaf, Node]
@@ -227,6 +227,14 @@ def generate_ignored_nodes(
         # fix for fmt: on in children
         if contains_fmt_on_at_column(container, leaf.column, preview=preview):
             for child in container.children:
+                if isinstance(child, Leaf) and is_fmt_on(child, preview=preview):
+                    if child.type in CLOSING_BRACKETS:
+                        # This means `# fmt: on` is placed at a different bracket level
+                        # than `# fmt: off`. This is an invalid use, but as a courtesy,
+                        # we include this closing bracket in the ignored nodes.
+                        # The alternative is to fail the formatting.
+                        yield child
+                    return
                 if contains_fmt_on_at_column(child, leaf.column, preview=preview):
                     return
                 yield child


Test patch:
diff --git a/tests/data/simple_cases/fmtonoff5.py b/tests/data/simple_cases/fmtonoff5.py
new file mode 100644
--- /dev/null
+++ b/tests/data/simple_cases/fmtonoff5.py
@@ -0,0 +1,36 @@
+# Regression test for https://github.com/psf/black/issues/3129.
+setup(
+    entry_points={
+        # fmt: off
+        "console_scripts": [
+            "foo-bar"
+            "=foo.bar.:main",
+        # fmt: on
+            ]  # Includes an formatted indentation.
+    },
+)
+
+
+# Regression test for https://github.com/psf/black/issues/2015.
+run(
+    # fmt: off
+    [
+        "ls",
+        "-la",
+    ]
+    # fmt: on
+    + path,
+    check=True,
+)
+
+
+# Regression test for https://github.com/psf/black/issues/3026.
+def test_func():
+    # yapf: disable
+    if  unformatted(  args  ):
+        return True
+    # yapf: enable
+    elif b:
+        return True
+
+    return False