Repository: psf/black

Issue:
Crash on conditional inside # fmt: off, depending on comment indentation
**Describe the bug**

If a condition statement is wrapped in `# fmt: off`/`# fmt: on`, Black crashes with a parse error on the subsequent line.
The crash only occurs if the `# fmt: off` comment is indented the same as the wrapped condition.

**To Reproduce**

The following minimal code breaks parsing with Black 22.10 and later:
```python
def do_something(x, y):
    if x:
        return x
    # fmt: off
    elif y:
    # fmt: on
        return y
```

And run it with these arguments:

```sh
$ black test.py
```

The resulting error is:

> error: cannot format test.py: Cannot parse: 7:4:     return y

**Expected behavior**

Black can parse this valid Python snippet regardless of how the `# fmt: off` comment is indented.
This is the case for Black 22.6 and older.

**Environment**

- Black's version: 22.10
- OS and Python version: Linux/Python 3.8

**Additional context**

Interestingly, Black doesn't crash if the fmt-off comment is indented one more level.
This is parsed fine:
```python
def do_something(x, y):
    if x:
        return x
        # fmt: off
    elif y:
    # fmt: on
        return y
```
However, the instruction is also ignored, ie. the `# fmt: off` command isn't recognised as such.


Discussion:
This is a similar case to `# fmt: skip` applied on the `:` line (https://github.com/psf/black/pull/3148), I'll take a look.

Resolving patch:
diff --git a/src/black/comments.py b/src/black/comments.py
--- a/src/black/comments.py
+++ b/src/black/comments.py
@@ -232,7 +232,7 @@ def generate_ignored_nodes(
 
         # fix for fmt: on in children
         if children_contains_fmt_on(container, preview=preview):
-            for child in container.children:
+            for index, child in enumerate(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
@@ -241,6 +241,16 @@ def generate_ignored_nodes(
                         # The alternative is to fail the formatting.
                         yield child
                     return
+                if (
+                    child.type == token.INDENT
+                    and index < len(container.children) - 1
+                    and children_contains_fmt_on(
+                        container.children[index + 1], preview=preview
+                    )
+                ):
+                    # This means `# fmt: on` is placed right after an indentation
+                    # level, and we shouldn't swallow the previous INDENT token.
+                    return
                 if children_contains_fmt_on(child, preview=preview):
                     return
                 yield child


Test patch:
diff --git a/tests/data/simple_cases/fmtonoff5.py b/tests/data/simple_cases/fmtonoff5.py
--- a/tests/data/simple_cases/fmtonoff5.py
+++ b/tests/data/simple_cases/fmtonoff5.py
@@ -64,7 +64,7 @@ async def call(param):
         print ( "This will be formatted" )
 
 
-# Regression test for https://github.com/psf/black/issues/2985
+# Regression test for https://github.com/psf/black/issues/2985.
 class Named(t.Protocol):
     # fmt: off
     @property
@@ -75,6 +75,15 @@ def  this_will_be_formatted ( self, **kwargs ) -> Named: ...
     # fmt: on
 
 
+# Regression test for https://github.com/psf/black/issues/3436.
+if x:
+    return x
+# fmt: off
+elif   unformatted:
+# fmt: on
+    will_be_formatted  ()
+
+
 # output
 
 
@@ -144,7 +153,7 @@ async def call(param):
         print("This will be formatted")
 
 
-# Regression test for https://github.com/psf/black/issues/2985
+# Regression test for https://github.com/psf/black/issues/2985.
 class Named(t.Protocol):
     # fmt: off
     @property
@@ -156,3 +165,12 @@ def this_will_be_formatted(self, **kwargs) -> Named:
         ...
 
     # fmt: on
+
+
+# Regression test for https://github.com/psf/black/issues/3436.
+if x:
+    return x
+# fmt: off
+elif   unformatted:
+    # fmt: on
+    will_be_formatted()