From 48856ead6ae023b2819ee63cb6ff97a0976a2cc3 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Salgado Date: Mon, 19 Aug 2024 17:09:03 +0200 Subject: [PATCH] gh-123123: Fix display of syntax errors covering multiple lines (#123131) Signed-off-by: Pablo Galindo --- Lib/test/test_traceback.py | 40 +++++++++++++++++-- Lib/traceback.py | 10 +++-- ...-08-18-18-25-54.gh-issue-123123.0ZcaEB.rst | 2 + 3 files changed, 45 insertions(+), 7 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-08-18-18-25-54.gh-issue-123123.0ZcaEB.rst diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 1895c88d23b..b568221212d 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -699,6 +699,35 @@ class TracebackErrorLocationCaretTestBase: result_lines = self.get_exception(f_with_multiline) self.assertEqual(result_lines, expected_f.splitlines()) + # Check custom error messages covering multiple lines + code = textwrap.dedent(""" + dummy_call( + "dummy value" + foo="bar", + ) + """) + + def f_with_multiline(): + # Need to defer the compilation until in self.get_exception(..) + return compile(code, "?", "exec") + + lineno_f = f_with_multiline.__code__.co_firstlineno + + expected_f = ( + 'Traceback (most recent call last):\n' + f' File "{__file__}", line {self.callable_line}, in get_exception\n' + ' callable()\n' + ' ~~~~~~~~^^\n' + f' File "{__file__}", line {lineno_f+2}, in f_with_multiline\n' + ' return compile(code, "?", "exec")\n' + ' File "?", line 3\n' + ' "dummy value"\n' + ' ^^^^^^^^^^^^^' + ) + + result_lines = self.get_exception(f_with_multiline) + self.assertEqual(result_lines, expected_f.splitlines()) + def test_caret_multiline_expression_bin_op(self): # Make sure no carets are printed for expressions spanning multiple # lines. @@ -2312,19 +2341,22 @@ class BaseExceptionReportingTests: def test_syntax_error_various_offsets(self): for offset in range(-5, 10): for add in [0, 2]: - text = " "*add + "text%d" % offset + text = " " * add + "text%d" % offset expected = [' File "file.py", line 1'] if offset < 1: expected.append(" %s" % text.lstrip()) elif offset <= 6: expected.append(" %s" % text.lstrip()) - expected.append(" %s^" % (" "*(offset-1))) + # Set the caret length to match the length of the text minus the offset. + caret_length = max(1, len(text.lstrip()) - offset + 1) + expected.append(" %s%s" % (" " * (offset - 1), "^" * caret_length)) else: + caret_length = max(1, len(text.lstrip()) - 4) expected.append(" %s" % text.lstrip()) - expected.append(" %s^" % (" "*5)) + expected.append(" %s%s" % (" " * 5, "^" * caret_length)) expected.append("SyntaxError: msg") expected.append("") - err = self.get_report(SyntaxError("msg", ("file.py", 1, offset+add, text))) + err = self.get_report(SyntaxError("msg", ("file.py", 1, offset + add, text))) exp = "\n".join(expected) self.assertEqual(exp, err) diff --git a/Lib/traceback.py b/Lib/traceback.py index 6ee1a50ca68..3e708c6f86a 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -1292,11 +1292,15 @@ class TracebackException: yield ' {}\n'.format(ltext) else: offset = self.offset - end_offset = self.end_offset if self.end_offset not in {None, 0} else offset + if self.lineno == self.end_lineno: + end_offset = self.end_offset if self.end_offset not in {None, 0} else offset + else: + end_offset = len(rtext) + 1 + if self.text and offset > len(self.text): - offset = len(self.text) + 1 + offset = len(rtext) + 1 if self.text and end_offset > len(self.text): - end_offset = len(self.text) + 1 + end_offset = len(rtext) + 1 if offset >= end_offset or end_offset < 0: end_offset = offset + 1 diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-08-18-18-25-54.gh-issue-123123.0ZcaEB.rst b/Misc/NEWS.d/next/Core and Builtins/2024-08-18-18-25-54.gh-issue-123123.0ZcaEB.rst new file mode 100644 index 00000000000..824d307bb27 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-08-18-18-25-54.gh-issue-123123.0ZcaEB.rst @@ -0,0 +1,2 @@ +Fix displaying :exc:`SyntaxError` exceptions covering multiple lines. Patch +by Pablo Galindo