mirror of
https://github.com/python/cpython.git
synced 2025-08-30 05:35:08 +00:00
bpo-43914: Highlight invalid ranges in SyntaxErrors (#25525)
To improve the user experience understanding what part of the error messages associated with SyntaxErrors is wrong, we can highlight the whole error range and not only place the caret at the first character. In this way: >>> foo(x, z for z in range(10), t, w) File "<stdin>", line 1 foo(x, z for z in range(10), t, w) ^ SyntaxError: Generator expression must be parenthesized becomes >>> foo(x, z for z in range(10), t, w) File "<stdin>", line 1 foo(x, z for z in range(10), t, w) ^^^^^^^^^^^^^^^^^^^^ SyntaxError: Generator expression must be parenthesized
This commit is contained in:
parent
91b69b77cf
commit
a77aac4fca
17 changed files with 1687 additions and 1219 deletions
|
@ -8,6 +8,7 @@ import unittest
|
|||
import pickle
|
||||
import weakref
|
||||
import errno
|
||||
from textwrap import dedent
|
||||
|
||||
from test.support import (captured_stderr, check_impl_detail,
|
||||
cpython_only, gc_collect,
|
||||
|
@ -255,13 +256,13 @@ class ExceptionTests(unittest.TestCase):
|
|||
check('from __future__ import doesnt_exist', 1, 1)
|
||||
check('from __future__ import braces', 1, 1)
|
||||
check('x=1\nfrom __future__ import division', 2, 1)
|
||||
check('foo(1=2)', 1, 6)
|
||||
check('foo(1=2)', 1, 5)
|
||||
check('def f():\n x, y: int', 2, 3)
|
||||
check('[*x for x in xs]', 1, 2)
|
||||
check('foo(x for x in range(10), 100)', 1, 5)
|
||||
check('for 1 in []: pass', 1, 5)
|
||||
check('(yield i) = 2', 1, 11)
|
||||
check('def f(*):\n pass', 1, 8)
|
||||
check('(yield i) = 2', 1, 2)
|
||||
check('def f(*):\n pass', 1, 7)
|
||||
|
||||
@cpython_only
|
||||
def testSettingException(self):
|
||||
|
@ -395,25 +396,31 @@ class ExceptionTests(unittest.TestCase):
|
|||
'filename' : 'filenameStr', 'filename2' : None}),
|
||||
(SyntaxError, (), {'msg' : None, 'text' : None,
|
||||
'filename' : None, 'lineno' : None, 'offset' : None,
|
||||
'print_file_and_line' : None}),
|
||||
'end_offset': None, 'print_file_and_line' : None}),
|
||||
(SyntaxError, ('msgStr',),
|
||||
{'args' : ('msgStr',), 'text' : None,
|
||||
'print_file_and_line' : None, 'msg' : 'msgStr',
|
||||
'filename' : None, 'lineno' : None, 'offset' : None}),
|
||||
'filename' : None, 'lineno' : None, 'offset' : None,
|
||||
'end_offset': None}),
|
||||
(SyntaxError, ('msgStr', ('filenameStr', 'linenoStr', 'offsetStr',
|
||||
'textStr')),
|
||||
'textStr', 'endLinenoStr', 'endOffsetStr')),
|
||||
{'offset' : 'offsetStr', 'text' : 'textStr',
|
||||
'args' : ('msgStr', ('filenameStr', 'linenoStr',
|
||||
'offsetStr', 'textStr')),
|
||||
'offsetStr', 'textStr',
|
||||
'endLinenoStr', 'endOffsetStr')),
|
||||
'print_file_and_line' : None, 'msg' : 'msgStr',
|
||||
'filename' : 'filenameStr', 'lineno' : 'linenoStr'}),
|
||||
'filename' : 'filenameStr', 'lineno' : 'linenoStr',
|
||||
'end_lineno': 'endLinenoStr', 'end_offset': 'endOffsetStr'}),
|
||||
(SyntaxError, ('msgStr', 'filenameStr', 'linenoStr', 'offsetStr',
|
||||
'textStr', 'print_file_and_lineStr'),
|
||||
'textStr', 'endLinenoStr', 'endOffsetStr',
|
||||
'print_file_and_lineStr'),
|
||||
{'text' : None,
|
||||
'args' : ('msgStr', 'filenameStr', 'linenoStr', 'offsetStr',
|
||||
'textStr', 'print_file_and_lineStr'),
|
||||
'textStr', 'endLinenoStr', 'endOffsetStr',
|
||||
'print_file_and_lineStr'),
|
||||
'print_file_and_line' : None, 'msg' : 'msgStr',
|
||||
'filename' : None, 'lineno' : None, 'offset' : None}),
|
||||
'filename' : None, 'lineno' : None, 'offset' : None,
|
||||
'end_lineno': None, 'end_offset': None}),
|
||||
(UnicodeError, (), {'args' : (),}),
|
||||
(UnicodeEncodeError, ('ascii', 'a', 0, 1,
|
||||
'ordinal not in range'),
|
||||
|
@ -459,7 +466,7 @@ class ExceptionTests(unittest.TestCase):
|
|||
e = exc(*args)
|
||||
except:
|
||||
print("\nexc=%r, args=%r" % (exc, args), file=sys.stderr)
|
||||
raise
|
||||
# raise
|
||||
else:
|
||||
# Verify module name
|
||||
if not type(e).__name__.endswith('NaiveException'):
|
||||
|
@ -1827,6 +1834,130 @@ class ImportErrorTests(unittest.TestCase):
|
|||
self.assertEqual(exc.name, orig.name)
|
||||
self.assertEqual(exc.path, orig.path)
|
||||
|
||||
class SyntaxErrorTests(unittest.TestCase):
|
||||
def test_range_of_offsets(self):
|
||||
cases = [
|
||||
# Basic range from 2->7
|
||||
(("bad.py", 1, 2, "abcdefg", 1, 7),
|
||||
dedent(
|
||||
"""
|
||||
File "bad.py", line 1
|
||||
abcdefg
|
||||
^^^^^
|
||||
SyntaxError: bad bad
|
||||
""")),
|
||||
# end_offset = start_offset + 1
|
||||
(("bad.py", 1, 2, "abcdefg", 1, 3),
|
||||
dedent(
|
||||
"""
|
||||
File "bad.py", line 1
|
||||
abcdefg
|
||||
^
|
||||
SyntaxError: bad bad
|
||||
""")),
|
||||
# Negative end offset
|
||||
(("bad.py", 1, 2, "abcdefg", 1, -2),
|
||||
dedent(
|
||||
"""
|
||||
File "bad.py", line 1
|
||||
abcdefg
|
||||
^
|
||||
SyntaxError: bad bad
|
||||
""")),
|
||||
# end offset before starting offset
|
||||
(("bad.py", 1, 4, "abcdefg", 1, 2),
|
||||
dedent(
|
||||
"""
|
||||
File "bad.py", line 1
|
||||
abcdefg
|
||||
^
|
||||
SyntaxError: bad bad
|
||||
""")),
|
||||
# Both offsets negative
|
||||
(("bad.py", 1, -4, "abcdefg", 1, -2),
|
||||
dedent(
|
||||
"""
|
||||
File "bad.py", line 1
|
||||
abcdefg
|
||||
SyntaxError: bad bad
|
||||
""")),
|
||||
# Both offsets negative and the end more negative
|
||||
(("bad.py", 1, -4, "abcdefg", 1, -5),
|
||||
dedent(
|
||||
"""
|
||||
File "bad.py", line 1
|
||||
abcdefg
|
||||
SyntaxError: bad bad
|
||||
""")),
|
||||
# Both offsets 0
|
||||
(("bad.py", 1, 0, "abcdefg", 1, 0),
|
||||
dedent(
|
||||
"""
|
||||
File "bad.py", line 1
|
||||
abcdefg
|
||||
SyntaxError: bad bad
|
||||
""")),
|
||||
# Start offset 0 and end offset not 0
|
||||
(("bad.py", 1, 0, "abcdefg", 1, 5),
|
||||
dedent(
|
||||
"""
|
||||
File "bad.py", line 1
|
||||
abcdefg
|
||||
SyntaxError: bad bad
|
||||
""")),
|
||||
# End offset pass the source lenght
|
||||
(("bad.py", 1, 2, "abcdefg", 1, 100),
|
||||
dedent(
|
||||
"""
|
||||
File "bad.py", line 1
|
||||
abcdefg
|
||||
^^^^^^
|
||||
SyntaxError: bad bad
|
||||
""")),
|
||||
]
|
||||
for args, expected in cases:
|
||||
with self.subTest(args=args):
|
||||
try:
|
||||
raise SyntaxError("bad bad", args)
|
||||
except SyntaxError as exc:
|
||||
with support.captured_stderr() as err:
|
||||
sys.__excepthook__(*sys.exc_info())
|
||||
the_exception = exc
|
||||
|
||||
def test_attributes_new_constructor(self):
|
||||
args = ("bad.py", 1, 2, "abcdefg", 1, 100)
|
||||
the_exception = SyntaxError("bad bad", args)
|
||||
filename, lineno, offset, error, end_lineno, end_offset = args
|
||||
self.assertEqual(filename, the_exception.filename)
|
||||
self.assertEqual(lineno, the_exception.lineno)
|
||||
self.assertEqual(end_lineno, the_exception.end_lineno)
|
||||
self.assertEqual(offset, the_exception.offset)
|
||||
self.assertEqual(end_offset, the_exception.end_offset)
|
||||
self.assertEqual(error, the_exception.text)
|
||||
self.assertEqual("bad bad", the_exception.msg)
|
||||
|
||||
def test_attributes_old_constructor(self):
|
||||
args = ("bad.py", 1, 2, "abcdefg")
|
||||
the_exception = SyntaxError("bad bad", args)
|
||||
filename, lineno, offset, error = args
|
||||
self.assertEqual(filename, the_exception.filename)
|
||||
self.assertEqual(lineno, the_exception.lineno)
|
||||
self.assertEqual(None, the_exception.end_lineno)
|
||||
self.assertEqual(offset, the_exception.offset)
|
||||
self.assertEqual(None, the_exception.end_offset)
|
||||
self.assertEqual(error, the_exception.text)
|
||||
self.assertEqual("bad bad", the_exception.msg)
|
||||
|
||||
def test_incorrect_constructor(self):
|
||||
args = ("bad.py", 1, 2)
|
||||
self.assertRaises(TypeError, SyntaxError, "bad bad", args)
|
||||
|
||||
args = ("bad.py", 1, 2, 4, 5, 6, 7)
|
||||
self.assertRaises(TypeError, SyntaxError, "bad bad", args)
|
||||
|
||||
args = ("bad.py", 1, 2, "abcdefg", 1)
|
||||
self.assertRaises(TypeError, SyntaxError, "bad bad", args)
|
||||
|
||||
|
||||
class PEP626Tests(unittest.TestCase):
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue