From c3a608e4c1e4632924970c77f4f57aa2ad7a9c28 Mon Sep 17 00:00:00 2001 From: jdymitarai Date: Thu, 10 Sep 2026 13:51:47 +0800 Subject: [PATCH] fix(errors): handle parse.ParseError without IndexError in FormatErrorMsg When code raises a parse.ParseError (such as unsupported syntax or grammar mismatches where ast.parse succeeds), FormatErrorMsg attempted to index e.args[1][0], e.args[1][1], e.args[1][2], assuming e.args[1] was a 3-tuple (filename, lineno, column). However, ParseError.args contains ('bad input: ...',) while line and column information are stored in e.context (('', (lineno, column))), resulting in an unhandled IndexError: tuple index out of range. 1. Add explicit support for parse.ParseError in FormatErrorMsg, extracting lineno and column from e.context and the message from e.msg. 2. Use getattr(e, 'filename', None) consistently across error formatters. 3. Add a safe fallback in FormatErrorMsg to handle any unexpected exception structures cleanly without crashing. 4. Add unit tests for ParseError formatting and FormatErrorMsg in yapftests/yapf_test.py. Fixes #1303, #1250, #1215, #1256 --- yapf/yapflib/errors.py | 23 +++++++++++++++++++---- yapftests/yapf_test.py | 26 ++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/yapf/yapflib/errors.py b/yapf/yapflib/errors.py index 3a0102368..4e636c075 100644 --- a/yapf/yapflib/errors.py +++ b/yapf/yapflib/errors.py @@ -13,6 +13,7 @@ # limitations under the License. """YAPF error objects.""" +from yapf_third_party._ylib2to3.pgen2 import parse from yapf_third_party._ylib2to3.pgen2 import tokenize @@ -29,12 +30,26 @@ def FormatErrorMsg(e): Returns: A properly formatted error message string. """ + filename = getattr(e, 'filename', None) if isinstance(e, SyntaxError): - return '{}:{}:{}: {}'.format(e.filename, e.lineno, e.offset, e.msg) + return '{}:{}:{}: {}'.format(filename, e.lineno, e.offset, e.msg) if isinstance(e, tokenize.TokenError): - return '{}:{}:{}: {}'.format(e.filename, e.args[1][0], e.args[1][1], - e.args[0]) - return '{}:{}:{}: {}'.format(e.args[1][0], e.args[1][1], e.args[1][2], e.msg) + lineno = e.args[1][0] if len(e.args) > 1 and len(e.args[1]) > 0 else 1 + col = e.args[1][1] if len(e.args) > 1 and len(e.args[1]) > 1 else 0 + msg = e.args[0] if e.args else str(e) + return '{}:{}:{}: {}'.format(filename, lineno, col, msg) + if isinstance(e, parse.ParseError): + lineno = e.context[1][0] if e.context and len(e.context) > 1 and len( + e.context[1]) > 0 else 1 + col = e.context[1][1] if e.context and len(e.context) > 1 and len( + e.context[1]) > 1 else 0 + msg = e.msg if hasattr(e, 'msg') else str(e) + return '{}:{}:{}: {}'.format(filename, lineno, col, msg) + try: + return '{}:{}:{}: {}'.format(e.args[1][0], e.args[1][1], e.args[1][2], + e.msg) + except (AttributeError, IndexError, TypeError): + return '{}: {}'.format(filename, e) if filename else str(e) class YapfError(Exception): diff --git a/yapftests/yapf_test.py b/yapftests/yapf_test.py index a9ca01188..d06f5b1ec 100644 --- a/yapftests/yapf_test.py +++ b/yapftests/yapf_test.py @@ -25,6 +25,7 @@ import unittest from io import StringIO +from yapf_third_party._ylib2to3.pgen2 import parse from yapf_third_party._ylib2to3.pgen2 import tokenize from yapf.yapflib import errors @@ -1565,6 +1566,31 @@ def testBadCode(self): code = 'x = """hello\n' self.assertRaises(errors.YapfError, yapf_api.FormatCode, code) + def testParseError(self): + code = 'f"{tab["SOME_STRING"]}"\n' + with self.assertRaisesRegex(errors.YapfError, r'bad input'): + yapf_api.FormatCode(code, filename='test.py') + + def testFormatErrorMsg(self): + s = SyntaxError('invalid syntax') + s.filename = 'foo.py' + s.lineno = 10 + s.offset = 5 + self.assertEqual(errors.FormatErrorMsg(s), 'foo.py:10:5: invalid syntax') + + t = tokenize.TokenError('EOF in multi-line string', (2, 4)) + t.filename = 'bar.py' + self.assertEqual( + errors.FormatErrorMsg(t), 'bar.py:2:4: EOF in multi-line string') + + p = parse.ParseError('bad input', 1, 'SOME_STRING', ('', (1, 8))) + p.filename = 'test.py' + self.assertEqual(errors.FormatErrorMsg(p), 'test.py:1:8: bad input') + + g = RuntimeError('unknown failure') + g.filename = 'baz.py' + self.assertEqual(errors.FormatErrorMsg(g), 'baz.py: unknown failure') + class DiffIndentTest(yapf_test_helper.YAPFTest):