Repository: ipython/ipython

Issue:
Add line number to error messages
As suggested in #13169, it adds line number to error messages, in order to make them more friendly.

![image](https://user-images.githubusercontent.com/20190646/139513782-ea8d42ab-9c73-4452-b607-5c54ca50a125.png)

That was the file used in the test
![image](https://user-images.githubusercontent.com/20190646/139513827-0aa4bed3-682f-40ee-a8ea-4f0e6e3fbc34.png)



Discussion:

Resolving patch:
diff --git a/IPython/core/ultratb.py b/IPython/core/ultratb.py
--- a/IPython/core/ultratb.py
+++ b/IPython/core/ultratb.py
@@ -169,7 +169,7 @@ def _format_traceback_lines(lines, Colors, has_colors, lvals):
     return res
 
 
-def _format_filename(file, ColorFilename, ColorNormal):
+def _format_filename(file, ColorFilename, ColorNormal, *, lineno=None):
     """
     Format filename lines with `In [n]` if it's the nth code cell or `File *.py` if it's a module.
 
@@ -185,14 +185,17 @@ def _format_filename(file, ColorFilename, ColorNormal):
 
     if ipinst is not None and file in ipinst.compile._filename_map:
         file = "[%s]" % ipinst.compile._filename_map[file]
-        tpl_link = "Input %sIn %%s%s" % (ColorFilename, ColorNormal)
+        tpl_link = f"Input {ColorFilename}In {{file}}{ColorNormal}"
     else:
         file = util_path.compress_user(
             py3compat.cast_unicode(file, util_path.fs_encoding)
         )
-        tpl_link = "File %s%%s%s" % (ColorFilename, ColorNormal)
+        if lineno is None:
+            tpl_link = f"File {ColorFilename}{{file}}{ColorNormal}"
+        else:
+            tpl_link = f"File {ColorFilename}{{file}}:{{lineno}}{ColorNormal}"
 
-    return tpl_link % file
+    return tpl_link.format(file=file, lineno=lineno)
 
 #---------------------------------------------------------------------------
 # Module classes
@@ -439,11 +442,10 @@ def _format_list(self, extracted_list):
         Colors = self.Colors
         list = []
         for filename, lineno, name, line in extracted_list[:-1]:
-            item = "  %s, line %s%d%s, in %s%s%s\n" % (
-                _format_filename(filename, Colors.filename, Colors.Normal),
-                Colors.lineno,
-                lineno,
-                Colors.Normal,
+            item = "  %s in %s%s%s\n" % (
+                _format_filename(
+                    filename, Colors.filename, Colors.Normal, lineno=lineno
+                ),
                 Colors.name,
                 name,
                 Colors.Normal,
@@ -453,12 +455,11 @@ def _format_list(self, extracted_list):
             list.append(item)
         # Emphasize the last entry
         filename, lineno, name, line = extracted_list[-1]
-        item = "%s  %s, line %s%d%s, in %s%s%s%s\n" % (
-            Colors.normalEm,
-            _format_filename(filename, Colors.filenameEm, Colors.normalEm),
-            Colors.linenoEm,
-            lineno,
+        item = "%s  %s in %s%s%s%s\n" % (
             Colors.normalEm,
+            _format_filename(
+                filename, Colors.filenameEm, Colors.normalEm, lineno=lineno
+            ),
             Colors.nameEm,
             name,
             Colors.normalEm,
@@ -501,14 +502,15 @@ def _format_exception_only(self, etype, value):
                     lineno = "unknown"
                     textline = ""
                 list.append(
-                    "%s  %s, line %s%s%s\n"
+                    "%s  %s%s\n"
                     % (
                         Colors.normalEm,
                         _format_filename(
-                            value.filename, Colors.filenameEm, Colors.normalEm
+                            value.filename,
+                            Colors.filenameEm,
+                            Colors.normalEm,
+                            lineno=(None if lineno == "unknown" else lineno),
                         ),
-                        Colors.linenoEm,
-                        lineno,
                         Colors.Normal,
                     )
                 )
@@ -628,27 +630,35 @@ def format_record(self, frame_info):
             return '    %s[... skipping similar frames: %s]%s\n' % (
                 Colors.excName, frame_info.description, ColorsNormal)
 
-        indent = ' ' * INDENT_SIZE
-        em_normal = '%s\n%s%s' % (Colors.valEm, indent, ColorsNormal)
-        tpl_call = 'in %s%%s%s%%s%s' % (Colors.vName, Colors.valEm,
-                                        ColorsNormal)
-        tpl_call_fail = 'in %s%%s%s(***failed resolving arguments***)%s' % \
-                        (Colors.vName, Colors.valEm, ColorsNormal)
-        tpl_name_val = '%%s %s= %%s%s' % (Colors.valEm, ColorsNormal)
+        indent = " " * INDENT_SIZE
+        em_normal = "%s\n%s%s" % (Colors.valEm, indent, ColorsNormal)
+        tpl_call = f"in {Colors.vName}{{file}}{Colors.valEm}{{scope}}{ColorsNormal}"
+        tpl_call_fail = "in %s%%s%s(***failed resolving arguments***)%s" % (
+            Colors.vName,
+            Colors.valEm,
+            ColorsNormal,
+        )
+        tpl_name_val = "%%s %s= %%s%s" % (Colors.valEm, ColorsNormal)
 
-        link = _format_filename(frame_info.filename, Colors.filenameEm, ColorsNormal)
+        link = _format_filename(
+            frame_info.filename,
+            Colors.filenameEm,
+            ColorsNormal,
+            lineno=frame_info.lineno,
+        )
         args, varargs, varkw, locals_ = inspect.getargvalues(frame_info.frame)
 
         func = frame_info.executing.code_qualname()
-        if func == '<module>':
-            call = tpl_call % (func, '')
+        if func == "<module>":
+            call = tpl_call.format(file=func, scope="")
         else:
             # Decide whether to include variable details or not
             var_repr = eqrepr if self.include_vars else nullrepr
             try:
-                call = tpl_call % (func, inspect.formatargvalues(args,
-                                                                 varargs, varkw,
-                                                                 locals_, formatvalue=var_repr))
+                scope = inspect.formatargvalues(
+                    args, varargs, varkw, locals_, formatvalue=var_repr
+                )
+                call = tpl_call.format(file=func, scope=scope)
             except KeyError:
                 # This happens in situations like errors inside generator
                 # expressions, where local variables are listed in the


Test patch:
diff --git a/IPython/core/tests/test_completer.py b/IPython/core/tests/test_completer.py
--- a/IPython/core/tests/test_completer.py
+++ b/IPython/core/tests/test_completer.py
@@ -419,9 +419,9 @@ def test_all_completions_dups(self):
             with provisionalcompleter():
                 ip.Completer.use_jedi = jedi_status
                 matches = c.all_completions("TestCl")
-                assert matches == ['TestClass'], jedi_status
+                assert matches == ["TestClass"], (jedi_status, matches)
                 matches = c.all_completions("TestClass.")
-                assert len(matches) > 2, jedi_status
+                assert len(matches) > 2, (jedi_status, matches)
                 matches = c.all_completions("TestClass.a")
                 assert matches == ['TestClass.a', 'TestClass.a1'], jedi_status
 
diff --git a/IPython/core/tests/test_debugger.py b/IPython/core/tests/test_debugger.py
--- a/IPython/core/tests/test_debugger.py
+++ b/IPython/core/tests/test_debugger.py
@@ -413,7 +413,7 @@ def test_decorator_skip():
     child.close()
 
 
-@pytest.mark.skip(platform.python_implementation() == "PyPy", reason="issues on PyPy")
+@pytest.mark.skipif(platform.python_implementation() == "PyPy", reason="issues on PyPy")
 @skip_win32
 def test_decorator_skip_disabled():
     """test that decorator frame skipping can be disabled"""
@@ -441,7 +441,7 @@ def test_decorator_skip_disabled():
     child.close()
 
 
-@pytest.mark.skip(platform.python_implementation() == "PyPy", reason="issues on PyPy")
+@pytest.mark.skipif(platform.python_implementation() == "PyPy", reason="issues on PyPy")
 @skip_win32
 def test_decorator_skip_with_breakpoint():
     """test that decorator frame skipping can be disabled"""
diff --git a/IPython/core/tests/test_iplib.py b/IPython/core/tests/test_iplib.py
--- a/IPython/core/tests/test_iplib.py
+++ b/IPython/core/tests/test_iplib.py
@@ -45,11 +45,11 @@ def doctest_tb_plain():
 
     In [19]: run simpleerr.py
     Traceback (most recent call last):
-      ...line ..., in <module>
+      File ...:... in <module>
         bar(mode)
-      ...line ..., in bar
+      File ...:... in bar
         div0()
-      ...line ..., in div0
+      File ...:... in div0
         x/y
     ZeroDivisionError: ...
     """
@@ -132,13 +132,13 @@ def doctest_tb_sysexit():
 
     In [20]: %tb
     Traceback (most recent call last):
-      File ..., in execfile
+      File ...:... in execfile
         exec(compiler(f.read(), fname, "exec"), glob, loc)
-      File ..., in <module>
+      File ...:... in <module>
         bar(mode)
-      File ..., in bar
+      File ...:... in bar
         sysexit(stat, mode)
-      File ..., in sysexit
+      File ...:... in sysexit
         raise SystemExit(stat, f"Mode = {mode}")
     SystemExit: (2, 'Mode = exit')
 
diff --git a/IPython/core/tests/test_ultratb.py b/IPython/core/tests/test_ultratb.py
--- a/IPython/core/tests/test_ultratb.py
+++ b/IPython/core/tests/test_ultratb.py
@@ -188,10 +188,6 @@ def test_indentationerror_shows_line(self):
 """
 
 class SyntaxErrorTest(unittest.TestCase):
-    def test_syntaxerror_without_lineno(self):
-        with tt.AssertNotPrints("TypeError"):
-            with tt.AssertPrints("line unknown"):
-                ip.run_cell("raise SyntaxError()")
 
     def test_syntaxerror_no_stacktrace_at_compile_time(self):
         syntax_error_at_compile_time = """
diff --git a/IPython/testing/decorators.py b/IPython/testing/decorators.py
--- a/IPython/testing/decorators.py
+++ b/IPython/testing/decorators.py
@@ -190,13 +190,13 @@ def onlyif_cmds_exist(*commands):
     """
     Decorator to skip test when at least one of `commands` is not found.
     """
+    assert (
+        os.environ.get("IPTEST_WORKING_DIR", None) is None
+    ), "iptest deprecated since IPython 8.0"
     for cmd in commands:
         reason = f"This test runs only if command '{cmd}' is installed"
         if not shutil.which(cmd):
-            if os.environ.get("IPTEST_WORKING_DIR", None) is not None:
-                return skip(reason)
-            else:
-                import pytest
+            import pytest
 
-                return pytest.mark.skip(reason=reason)
+            return pytest.mark.skip(reason=reason)
     return null_deco
diff --git a/IPython/testing/plugin/pytest_ipdoctest.py b/IPython/testing/plugin/pytest_ipdoctest.py
--- a/IPython/testing/plugin/pytest_ipdoctest.py
+++ b/IPython/testing/plugin/pytest_ipdoctest.py
@@ -503,7 +503,7 @@ def _check_all_skipped(test: "doctest.DocTest") -> None:
 
     all_skipped = all(x.options.get(doctest.SKIP, False) for x in test.examples)
     if all_skipped:
-        pytest.skip("all tests skipped by +SKIP option")
+        pytest.skip("all docstests skipped by +SKIP option")
 
 
 def _is_mocked(obj: object) -> bool: