Repository: ipython/ipython

Issue:
Transformer for `pinfo` line magic incorrectly sets next input when the line contains parentheses
In JupyterLab we experience an annoyance with pinfo magic which results in some expressions creating a new cell instead of providing help (https://github.com/jupyterlab/jupyterlab/issues/12269). The following example creates extra cells:

```python
sum([1]).denominator?
```

or:

```
y = lambda x: int?
```

While the direct call to `pinfo` line magic, or to underlying `_inspect` method does not have such an effect:

```python
?sum([1]).denominator
```

```python
%pinfo sum([1]).denominator
```

```python
ipython._inspect('pinfo', 'sum([1]).denominator?')
```

```python
get_ipython().run_line_magic('pinfo', 'sum([1]).denominator?')
```


![extra-cells](https://user-images.githubusercontent.com/5832902/159914796-e8b05f77-c368-4df4-b665-226dbd8ebaaa.gif)

This was narrowed down to a special construct in the input transformer which for some reason wants to set next input in case if the `?` is placed mid-command:

https://github.com/ipython/ipython/blob/0c5563497fe94a8a24ec88912433959908f2506e/IPython/core/inputtransformer.py#L196-L210

identically in transformer2:

https://github.com/ipython/ipython/blob/0c5563497fe94a8a24ec88912433959908f2506e/IPython/core/inputtransformer2.py#L328-L342

This code existed like that since 2012 when it was introduced during magics and splitter refactoring in https://github.com/ipython/ipython/commit/3f192dc6a1873b0d2e5345fc475ffab0fa403466. Interestingly the commit is titled `Remove next_input nonsense in magic calls (but keep functionality).`.

Now, the `set_next_input` will only get introduced if `next_input` is given which gets decided in `help_end` utility:

https://github.com/ipython/ipython/blob/0c5563497fe94a8a24ec88912433959908f2506e/IPython/core/inputtransformer.py#L342-L355

which also happens in transformer2:

https://github.com/ipython/ipython/blob/0c5563497fe94a8a24ec88912433959908f2506e/IPython/core/inputtransformer2.py#L468-L487

What follows:
- I do not see any use-case for the `next_input` to be ever returned. Can we remove it, or am I missing something?
- regardless, the regular expression `_help_end_re` disallows parentheses, only allows strings like `\w+(\.\w+)*` thus it thinks (possibly incorrectly) that it should set the next input to `sum([1]).denominator`; in more detail, the `_help_end_re.search` does not capture the `sum([])` part, and only captures `denominator?` which is different from `line.strip()` therefore `line.rstrip('?')` is assigned as `next_input`. Can we modify the regular expression to allow for parens, brackets and others?
- in `inputtransformer2` a syntax error will be raised when we try to use `DataFrame()?` or `data['key']?, however I would very much like to be able to get help for those as well; this might also require a slight change in the `_ofind` method to allow evaluation of code for pinfo magics (around here https://github.com/ipython/ipython/blob/47ccec77b41b010091ec18a20ff7d2bedcc674da/IPython/core/interactiveshell.py#L1525-L1527 where exception for other magics is already present)

Discussion:

Patch:
diff --git a/IPython/core/inputtransformer.py b/IPython/core/inputtransformer.py
index f668f466a..77f69f388 100644
--- a/IPython/core/inputtransformer.py
+++ b/IPython/core/inputtransformer.py
@@ -193,7 +193,7 @@ def assemble_logical_lines():
         line = ''.join(parts)
 
 # Utilities
-def _make_help_call(target, esc, lspace, next_input=None):
+def _make_help_call(target, esc, lspace):
     """Prepares a pinfo(2)/psearch call from a target name and the escape
     (i.e. ? or ??)"""
     method  = 'pinfo2' if esc == '??' \
@@ -203,12 +203,13 @@ def _make_help_call(target, esc, lspace, next_input=None):
     #Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args)
     t_magic_name, _, t_magic_arg_s = arg.partition(' ')
     t_magic_name = t_magic_name.lstrip(ESC_MAGIC)
-    if next_input is None:
-        return '%sget_ipython().run_line_magic(%r, %r)' % (lspace, t_magic_name, t_magic_arg_s)
-    else:
-        return '%sget_ipython().set_next_input(%r);get_ipython().run_line_magic(%r, %r)' % \
-           (lspace, next_input, t_magic_name, t_magic_arg_s)
-    
+    return "%sget_ipython().run_line_magic(%r, %r)" % (
+        lspace,
+        t_magic_name,
+        t_magic_arg_s,
+    )
+
+
 # These define the transformations for the different escape characters.
 def _tr_system(line_info):
     "Translate lines escaped with: !"
@@ -349,10 +350,7 @@ def help_end(line):
     esc = m.group(3)
     lspace = _initial_space_re.match(line).group(0)
 
-    # If we're mid-command, put it back on the next prompt for the user.
-    next_input = line.rstrip('?') if line.strip() != m.group(0) else None
-
-    return _make_help_call(target, esc, lspace, next_input)
+    return _make_help_call(target, esc, lspace)
 
 
 @CoroutineInputTransformer.wrap
diff --git a/IPython/core/inputtransformer2.py b/IPython/core/inputtransformer2.py
index 3a560073b..a8f676f49 100644
--- a/IPython/core/inputtransformer2.py
+++ b/IPython/core/inputtransformer2.py
@@ -325,7 +325,7 @@ def transform(self, lines: List[str]):
 ESCAPE_SINGLES = {'!', '?', '%', ',', ';', '/'}
 ESCAPE_DOUBLES = {'!!', '??'}  # %% (cell magic) is handled separately
 
-def _make_help_call(target, esc, next_input=None):
+def _make_help_call(target, esc):
     """Prepares a pinfo(2)/psearch call from a target name and the escape
     (i.e. ? or ??)"""
     method  = 'pinfo2' if esc == '??' \
@@ -335,11 +335,8 @@ def _make_help_call(target, esc, next_input=None):
     #Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args)
     t_magic_name, _, t_magic_arg_s = arg.partition(' ')
     t_magic_name = t_magic_name.lstrip(ESC_MAGIC)
-    if next_input is None:
-        return 'get_ipython().run_line_magic(%r, %r)' % (t_magic_name, t_magic_arg_s)
-    else:
-        return 'get_ipython().set_next_input(%r);get_ipython().run_line_magic(%r, %r)' % \
-           (next_input, t_magic_name, t_magic_arg_s)
+    return "get_ipython().run_line_magic(%r, %r)" % (t_magic_name, t_magic_arg_s)
+
 
 def _tr_help(content):
     """Translate lines escaped with: ?
@@ -480,13 +477,8 @@ def transform(self, lines):
         target = m.group(1)
         esc = m.group(3)
 
-        # If we're mid-command, put it back on the next prompt for the user.
-        next_input = None
-        if (not lines_before) and (not lines_after) \
-                and content.strip() != m.group(0):
-            next_input = content.rstrip('?\n')
 
-        call = _make_help_call(target, esc, next_input=next_input)
+        call = _make_help_call(target, esc)
         new_line = indent + call + '\n'
 
         return lines_before + [new_line] + lines_after
diff --git a/docs/source/whatsnew/version8.rst b/docs/source/whatsnew/version8.rst
index b557eb193..24fed4b2f 100644
--- a/docs/source/whatsnew/version8.rst
+++ b/docs/source/whatsnew/version8.rst
@@ -8,6 +8,11 @@
 IPython 8.3.0
 -------------
 
+ - :ghpull:`13625`, using ``?``, ``??``, ``*?`` will not call
+   ``set_next_input`` as most frontend allow proper multiline editing and it was
+   causing issues for many users of multi-cell frontends.
+
+
  - :ghpull:`13600`, ``pre_run_*``-hooks will now have a ``cell_id`` attribute on
    the info object when frontend provide it.
 


Test patch:
diff --git a/IPython/core/tests/test_inputtransformer.py b/IPython/core/tests/test_inputtransformer.py
index 4de97b87c..bfc936d31 100644
--- a/IPython/core/tests/test_inputtransformer.py
+++ b/IPython/core/tests/test_inputtransformer.py
@@ -59,108 +59,93 @@ def transform_checker(tests, transformer, **kwargs):
         ('x=1', 'x=1'), # normal input is unmodified
         ('    ','    '),  # blank lines are kept intact
         ("a, b = %foo", "a, b = get_ipython().run_line_magic('foo', '')"),
-        ],
-
-       classic_prompt =
-       [('>>> x=1', 'x=1'),
-        ('x=1', 'x=1'), # normal input is unmodified
-        ('    ', '    '),  # blank lines are kept intact
-        ],
-
-       ipy_prompt =
-       [('In [1]: x=1', 'x=1'),
-        ('x=1', 'x=1'), # normal input is unmodified
-        ('    ','    '),  # blank lines are kept intact
-        ],
-
-       # Tests for the escape transformer to leave normal code alone
-       escaped_noesc =
-       [ ('    ', '    '),
-         ('x=1', 'x=1'),
-         ],
-
-       # System calls
-       escaped_shell =
-       [ ('!ls', "get_ipython().system('ls')"),
-         # Double-escape shell, this means to capture the output of the
-         # subprocess and return it
-         ('!!ls', "get_ipython().getoutput('ls')"),
-         ],
-
-       # Help/object info
-       escaped_help =
-       [ ('?', 'get_ipython().show_usage()'),
-         ('?x1', "get_ipython().run_line_magic('pinfo', 'x1')"),
-         ('??x2', "get_ipython().run_line_magic('pinfo2', 'x2')"),
-         ('?a.*s', "get_ipython().run_line_magic('psearch', 'a.*s')"),
-         ('?%hist1', "get_ipython().run_line_magic('pinfo', '%hist1')"),
-         ('?%%hist2', "get_ipython().run_line_magic('pinfo', '%%hist2')"),
-         ('?abc = qwe', "get_ipython().run_line_magic('pinfo', 'abc')"),
-         ],
-
-      end_help =
-      [ ('x3?', "get_ipython().run_line_magic('pinfo', 'x3')"),
-        ('x4??', "get_ipython().run_line_magic('pinfo2', 'x4')"),
-        ('%hist1?', "get_ipython().run_line_magic('pinfo', '%hist1')"),
-        ('%hist2??', "get_ipython().run_line_magic('pinfo2', '%hist2')"),
-        ('%%hist3?', "get_ipython().run_line_magic('pinfo', '%%hist3')"),
-        ('%%hist4??', "get_ipython().run_line_magic('pinfo2', '%%hist4')"),
-        ('π.foo?', "get_ipython().run_line_magic('pinfo', 'π.foo')"),
-        ('f*?', "get_ipython().run_line_magic('psearch', 'f*')"),
-        ('ax.*aspe*?', "get_ipython().run_line_magic('psearch', 'ax.*aspe*')"),
-        ('a = abc?', "get_ipython().set_next_input('a = abc');"
-                      "get_ipython().run_line_magic('pinfo', 'abc')"),
-        ('a = abc.qe??', "get_ipython().set_next_input('a = abc.qe');"
-                          "get_ipython().run_line_magic('pinfo2', 'abc.qe')"),
-        ('a = *.items?', "get_ipython().set_next_input('a = *.items');"
-                          "get_ipython().run_line_magic('psearch', '*.items')"),
-        ('plot(a?', "get_ipython().set_next_input('plot(a');"
-                     "get_ipython().run_line_magic('pinfo', 'a')"),
-        ('a*2 #comment?', 'a*2 #comment?'),
-        ],
-
-       # Explicit magic calls
-       escaped_magic =
-       [ ('%cd', "get_ipython().run_line_magic('cd', '')"),
-         ('%cd /home', "get_ipython().run_line_magic('cd', '/home')"),
-         # Backslashes need to be escaped.
-         ('%cd C:\\User', "get_ipython().run_line_magic('cd', 'C:\\\\User')"),
-         ('    %magic', "    get_ipython().run_line_magic('magic', '')"),
-         ],
-
-       # Quoting with separate arguments
-       escaped_quote =
-       [ (',f', 'f("")'),
-         (',f x', 'f("x")'),
-         ('  ,f y', '  f("y")'),
-         (',f a b', 'f("a", "b")'),
-         ],
-
-       # Quoting with single argument
-       escaped_quote2 =
-       [ (';f', 'f("")'),
-         (';f x', 'f("x")'),
-         ('  ;f y', '  f("y")'),
-         (';f a b', 'f("a b")'),
-         ],
-
-       # Simply apply parens
-       escaped_paren =
-       [ ('/f', 'f()'),
-         ('/f x', 'f(x)'),
-         ('  /f y', '  f(y)'),
-         ('/f a b', 'f(a, b)'),
-         ],
-
-       # Check that we transform prompts before other transforms
-       mixed =
-       [ ('In [1]: %lsmagic', "get_ipython().run_line_magic('lsmagic', '')"),
-         ('>>> %lsmagic', "get_ipython().run_line_magic('lsmagic', '')"),
-         ('In [2]: !ls', "get_ipython().system('ls')"),
-         ('In [3]: abs?', "get_ipython().run_line_magic('pinfo', 'abs')"),
-         ('In [4]: b = %who', "b = get_ipython().run_line_magic('who', '')"),
-         ],
-       )
+    ],
+    classic_prompt=[
+        (">>> x=1", "x=1"),
+        ("x=1", "x=1"),  # normal input is unmodified
+        ("    ", "    "),  # blank lines are kept intact
+    ],
+    ipy_prompt=[
+        ("In [1]: x=1", "x=1"),
+        ("x=1", "x=1"),  # normal input is unmodified
+        ("    ", "    "),  # blank lines are kept intact
+    ],
+    # Tests for the escape transformer to leave normal code alone
+    escaped_noesc=[
+        ("    ", "    "),
+        ("x=1", "x=1"),
+    ],
+    # System calls
+    escaped_shell=[
+        ("!ls", "get_ipython().system('ls')"),
+        # Double-escape shell, this means to capture the output of the
+        # subprocess and return it
+        ("!!ls", "get_ipython().getoutput('ls')"),
+    ],
+    # Help/object info
+    escaped_help=[
+        ("?", "get_ipython().show_usage()"),
+        ("?x1", "get_ipython().run_line_magic('pinfo', 'x1')"),
+        ("??x2", "get_ipython().run_line_magic('pinfo2', 'x2')"),
+        ("?a.*s", "get_ipython().run_line_magic('psearch', 'a.*s')"),
+        ("?%hist1", "get_ipython().run_line_magic('pinfo', '%hist1')"),
+        ("?%%hist2", "get_ipython().run_line_magic('pinfo', '%%hist2')"),
+        ("?abc = qwe", "get_ipython().run_line_magic('pinfo', 'abc')"),
+    ],
+    end_help=[
+        ("x3?", "get_ipython().run_line_magic('pinfo', 'x3')"),
+        ("x4??", "get_ipython().run_line_magic('pinfo2', 'x4')"),
+        ("%hist1?", "get_ipython().run_line_magic('pinfo', '%hist1')"),
+        ("%hist2??", "get_ipython().run_line_magic('pinfo2', '%hist2')"),
+        ("%%hist3?", "get_ipython().run_line_magic('pinfo', '%%hist3')"),
+        ("%%hist4??", "get_ipython().run_line_magic('pinfo2', '%%hist4')"),
+        ("π.foo?", "get_ipython().run_line_magic('pinfo', 'π.foo')"),
+        ("f*?", "get_ipython().run_line_magic('psearch', 'f*')"),
+        ("ax.*aspe*?", "get_ipython().run_line_magic('psearch', 'ax.*aspe*')"),
+        ("a = abc?", "get_ipython().run_line_magic('pinfo', 'abc')"),
+        ("a = abc.qe??", "get_ipython().run_line_magic('pinfo2', 'abc.qe')"),
+        ("a = *.items?", "get_ipython().run_line_magic('psearch', '*.items')"),
+        ("plot(a?", "get_ipython().run_line_magic('pinfo', 'a')"),
+        ("a*2 #comment?", "a*2 #comment?"),
+    ],
+    # Explicit magic calls
+    escaped_magic=[
+        ("%cd", "get_ipython().run_line_magic('cd', '')"),
+        ("%cd /home", "get_ipython().run_line_magic('cd', '/home')"),
+        # Backslashes need to be escaped.
+        ("%cd C:\\User", "get_ipython().run_line_magic('cd', 'C:\\\\User')"),
+        ("    %magic", "    get_ipython().run_line_magic('magic', '')"),
+    ],
+    # Quoting with separate arguments
+    escaped_quote=[
+        (",f", 'f("")'),
+        (",f x", 'f("x")'),
+        ("  ,f y", '  f("y")'),
+        (",f a b", 'f("a", "b")'),
+    ],
+    # Quoting with single argument
+    escaped_quote2=[
+        (";f", 'f("")'),
+        (";f x", 'f("x")'),
+        ("  ;f y", '  f("y")'),
+        (";f a b", 'f("a b")'),
+    ],
+    # Simply apply parens
+    escaped_paren=[
+        ("/f", "f()"),
+        ("/f x", "f(x)"),
+        ("  /f y", "  f(y)"),
+        ("/f a b", "f(a, b)"),
+    ],
+    # Check that we transform prompts before other transforms
+    mixed=[
+        ("In [1]: %lsmagic", "get_ipython().run_line_magic('lsmagic', '')"),
+        (">>> %lsmagic", "get_ipython().run_line_magic('lsmagic', '')"),
+        ("In [2]: !ls", "get_ipython().system('ls')"),
+        ("In [3]: abs?", "get_ipython().run_line_magic('pinfo', 'abs')"),
+        ("In [4]: b = %who", "b = get_ipython().run_line_magic('who', '')"),
+    ],
+)
 
 # multiline syntax examples.  Each of these should be a list of lists, with
 # each entry itself having pairs of raw/transformed input.  The union (with
diff --git a/IPython/core/tests/test_inputtransformer2.py b/IPython/core/tests/test_inputtransformer2.py
index abc63031d..0613dc02c 100644
--- a/IPython/core/tests/test_inputtransformer2.py
+++ b/IPython/core/tests/test_inputtransformer2.py
@@ -14,45 +14,65 @@
 from IPython.core import inputtransformer2 as ipt2
 from IPython.core.inputtransformer2 import _find_assign_op, make_tokens_by_line
 
-MULTILINE_MAGIC = ("""\
+MULTILINE_MAGIC = (
+    """\
 a = f()
 %foo \\
 bar
 g()
-""".splitlines(keepends=True), (2, 0), """\
+""".splitlines(
+        keepends=True
+    ),
+    (2, 0),
+    """\
 a = f()
 get_ipython().run_line_magic('foo', ' bar')
 g()
-""".splitlines(keepends=True))
+""".splitlines(
+        keepends=True
+    ),
+)
 
-INDENTED_MAGIC = ("""\
+INDENTED_MAGIC = (
+    """\
 for a in range(5):
     %ls
-""".splitlines(keepends=True), (2, 4), """\
+""".splitlines(
+        keepends=True
+    ),
+    (2, 4),
+    """\
 for a in range(5):
     get_ipython().run_line_magic('ls', '')
-""".splitlines(keepends=True))
+""".splitlines(
+        keepends=True
+    ),
+)
 
-CRLF_MAGIC = ([
-    "a = f()\n",
-    "%ls\r\n",
-    "g()\n"
-], (2, 0), [
-    "a = f()\n",
-    "get_ipython().run_line_magic('ls', '')\n",
-    "g()\n"
-])
-
-MULTILINE_MAGIC_ASSIGN = ("""\
+CRLF_MAGIC = (
+    ["a = f()\n", "%ls\r\n", "g()\n"],
+    (2, 0),
+    ["a = f()\n", "get_ipython().run_line_magic('ls', '')\n", "g()\n"],
+)
+
+MULTILINE_MAGIC_ASSIGN = (
+    """\
 a = f()
 b = %foo \\
   bar
 g()
-""".splitlines(keepends=True), (2, 4), """\
+""".splitlines(
+        keepends=True
+    ),
+    (2, 4),
+    """\
 a = f()
 b = get_ipython().run_line_magic('foo', '   bar')
 g()
-""".splitlines(keepends=True))
+""".splitlines(
+        keepends=True
+    ),
+)
 
 MULTILINE_SYSTEM_ASSIGN = ("""\
 a = f()
@@ -72,68 +92,70 @@ def test():
   for i in range(1):
     print(i)
   res =! ls
-""".splitlines(keepends=True), (4, 7), '''\
+""".splitlines(
+        keepends=True
+    ),
+    (4, 7),
+    """\
 def test():
   for i in range(1):
     print(i)
   res =get_ipython().getoutput(\' ls\')
-'''.splitlines(keepends=True))
+""".splitlines(
+        keepends=True
+    ),
+)
 
 ######
 
-AUTOCALL_QUOTE = (
-    [",f 1 2 3\n"], (1, 0),
-    ['f("1", "2", "3")\n']
-)
+AUTOCALL_QUOTE = ([",f 1 2 3\n"], (1, 0), ['f("1", "2", "3")\n'])
 
-AUTOCALL_QUOTE2 = (
-    [";f 1 2 3\n"], (1, 0),
-    ['f("1 2 3")\n']
-)
+AUTOCALL_QUOTE2 = ([";f 1 2 3\n"], (1, 0), ['f("1 2 3")\n'])
 
-AUTOCALL_PAREN = (
-    ["/f 1 2 3\n"], (1, 0),
-    ['f(1, 2, 3)\n']
-)
+AUTOCALL_PAREN = (["/f 1 2 3\n"], (1, 0), ["f(1, 2, 3)\n"])
 
-SIMPLE_HELP = (
-    ["foo?\n"], (1, 0),
-    ["get_ipython().run_line_magic('pinfo', 'foo')\n"]
-)
+SIMPLE_HELP = (["foo?\n"], (1, 0), ["get_ipython().run_line_magic('pinfo', 'foo')\n"])
 
 DETAILED_HELP = (
-    ["foo??\n"], (1, 0),
-    ["get_ipython().run_line_magic('pinfo2', 'foo')\n"]
+    ["foo??\n"],
+    (1, 0),
+    ["get_ipython().run_line_magic('pinfo2', 'foo')\n"],
 )
 
-MAGIC_HELP = (
-    ["%foo?\n"], (1, 0),
-    ["get_ipython().run_line_magic('pinfo', '%foo')\n"]
-)
+MAGIC_HELP = (["%foo?\n"], (1, 0), ["get_ipython().run_line_magic('pinfo', '%foo')\n"])
 
 HELP_IN_EXPR = (
-    ["a = b + c?\n"], (1, 0),
-    ["get_ipython().set_next_input('a = b + c');"
-     "get_ipython().run_line_magic('pinfo', 'c')\n"]
+    ["a = b + c?\n"],
+    (1, 0),
+    ["get_ipython().run_line_magic('pinfo', 'c')\n"],
 )
 
-HELP_CONTINUED_LINE = ("""\
+HELP_CONTINUED_LINE = (
+    """\
 a = \\
 zip?
-""".splitlines(keepends=True), (1, 0),
-[r"get_ipython().set_next_input('a = \\\nzip');get_ipython().run_line_magic('pinfo', 'zip')" + "\n"]
+""".splitlines(
+        keepends=True
+    ),
+    (1, 0),
+    [r"get_ipython().run_line_magic('pinfo', 'zip')" + "\n"],
 )
 
-HELP_MULTILINE = ("""\
+HELP_MULTILINE = (
+    """\
 (a,
 b) = zip?
-""".splitlines(keepends=True), (1, 0),
-[r"get_ipython().set_next_input('(a,\nb) = zip');get_ipython().run_line_magic('pinfo', 'zip')" + "\n"]
+""".splitlines(
+        keepends=True
+    ),
+    (1, 0),
+    [r"get_ipython().run_line_magic('pinfo', 'zip')" + "\n"],
 )
 
 HELP_UNICODE = (
-    ["π.foo?\n"], (1, 0),
-    ["get_ipython().run_line_magic('pinfo', 'π.foo')\n"]
+    ["π.foo?\n"],
+    (1, 0),
+    ["get_ipython().run_line_magic('pinfo', 'π.foo')\n"],
 )
 
 
@@ -149,6 +171,7 @@ def test_check_make_token_by_line_never_ends_empty():
     Check that not sequence of single or double characters ends up leading to en empty list of tokens
     """
     from string import printable
+
     for c in printable:
         assert make_tokens_by_line(c)[-1] != []
         for k in printable:
@@ -156,7 +179,7 @@ def test_check_make_token_by_line_never_ends_empty():
 
 
 def check_find(transformer, case, match=True):
-    sample, expected_start, _  = case
+    sample, expected_start, _ = case
     tbl = make_tokens_by_line(sample)
     res = transformer.find(tbl)
     if match:
@@ -166,25 +189,30 @@ def check_find(transformer, case, match=True):
     else:
         assert res is None
 
+
 def check_transform(transformer_cls, case):
     lines, start, expected = case
     transformer = transformer_cls(start)
     assert transformer.transform(lines) == expected
 
+
 def test_continued_line():
     lines = MULTILINE_MAGIC_ASSIGN[0]
     assert ipt2.find_end_of_continued_line(lines, 1) == 2
 
     assert ipt2.assemble_continued_line(lines, (1, 5), 2) == "foo    bar"
 
+
 def test_find_assign_magic():
     check_find(ipt2.MagicAssign, MULTILINE_MAGIC_ASSIGN)
     check_find(ipt2.MagicAssign, MULTILINE_SYSTEM_ASSIGN, match=False)
     check_find(ipt2.MagicAssign, MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT, match=False)
 
+
 def test_transform_assign_magic():
     check_transform(ipt2.MagicAssign, MULTILINE_MAGIC_ASSIGN)
 
+
 def test_find_assign_system():
     check_find(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN)
     check_find(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT)
@@ -192,30 +220,36 @@ def test_find_assign_system():
     check_find(ipt2.SystemAssign, (["a=!ls\n"], (1, 2), None))
     check_find(ipt2.SystemAssign, MULTILINE_MAGIC_ASSIGN, match=False)
 
+
 def test_transform_assign_system():
     check_transform(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN)
     check_transform(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT)
 
+
 def test_find_magic_escape():
     check_find(ipt2.EscapedCommand, MULTILINE_MAGIC)
     check_find(ipt2.EscapedCommand, INDENTED_MAGIC)
     check_find(ipt2.EscapedCommand, MULTILINE_MAGIC_ASSIGN, match=False)
 
+
 def test_transform_magic_escape():
     check_transform(ipt2.EscapedCommand, MULTILINE_MAGIC)
     check_transform(ipt2.EscapedCommand, INDENTED_MAGIC)
     check_transform(ipt2.EscapedCommand, CRLF_MAGIC)
 
+
 def test_find_autocalls():
     for case in [AUTOCALL_QUOTE, AUTOCALL_QUOTE2, AUTOCALL_PAREN]:
         print("Testing %r" % case[0])
         check_find(ipt2.EscapedCommand, case)
 
+
 def test_transform_autocall():
     for case in [AUTOCALL_QUOTE, AUTOCALL_QUOTE2, AUTOCALL_PAREN]:
         print("Testing %r" % case[0])
         check_transform(ipt2.EscapedCommand, case)
 
+
 def test_find_help():
     for case in [SIMPLE_HELP, DETAILED_HELP, MAGIC_HELP, HELP_IN_EXPR]:
         check_find(ipt2.HelpEnd, case)
@@ -233,6 +267,7 @@ def test_find_help():
     # Nor in a string
     check_find(ipt2.HelpEnd, (["foo = '''bar?\n"], None, None), match=False)
 
+
 def test_transform_help():
     tf = ipt2.HelpEnd((1, 0), (1, 9))
     assert tf.transform(HELP_IN_EXPR[0]) == HELP_IN_EXPR[2]
@@ -246,10 +281,12 @@ def test_transform_help():
     tf = ipt2.HelpEnd((1, 0), (1, 0))
     assert tf.transform(HELP_UNICODE[0]) == HELP_UNICODE[2]
 
+
 def test_find_assign_op_dedent():
     """
     be careful that empty token like dedent are not counted as parens
     """
+
     class Tk:
         def __init__(self, s):
             self.string = s
@@ -302,21 +339,23 @@ def test_check_complete_param(code, expected, number):
 def test_check_complete():
     cc = ipt2.TransformerManager().check_complete
 
-    example = dedent("""
+    example = dedent(
+        """
         if True:
-            a=1""" )
+            a=1"""
+    )
 
     assert cc(example) == ("incomplete", 4)
     assert cc(example + "\n") == ("complete", None)
     assert cc(example + "\n    ") == ("complete", None)
 
     # no need to loop on all the letters/numbers.
-    short = '12abAB'+string.printable[62:]
+    short = "12abAB" + string.printable[62:]
     for c in short:
         # test does not raise:
         cc(c)
         for k in short:
-            cc(c+k)
+            cc(c + k)
 
     assert cc("def f():\n  x=0\n  \\\n  ") == ("incomplete", 2)
 
@@ -371,10 +410,9 @@ def test_null_cleanup_transformer():
     assert manager.transform_cell("") == ""
 
 
-
-
 def test_side_effects_I():
     count = 0
+
     def counter(lines):
         nonlocal count
         count += 1
@@ -384,14 +422,13 @@ def counter(lines):
 
     manager = ipt2.TransformerManager()
     manager.cleanup_transforms.insert(0, counter)
-    assert manager.check_complete("a=1\n") == ('complete', None)
+    assert manager.check_complete("a=1\n") == ("complete", None)
     assert count == 0
 
 
-
-
 def test_side_effects_II():
     count = 0
+
     def counter(lines):
         nonlocal count
         count += 1
@@ -401,5 +438,5 @@ def counter(lines):
 
     manager = ipt2.TransformerManager()
     manager.line_transforms.insert(0, counter)
-    assert manager.check_complete("b=1\n") == ('complete', None)
+    assert manager.check_complete("b=1\n") == ("complete", None)
     assert count == 0