From 911db3d7191fcb67cf0fa89162c4e958c4b308e9 Mon Sep 17 00:00:00 2001 From: fei <204683769+feiiiiii5@users.noreply.github.com> Date: Sat, 10 Oct 2026 22:34:25 +0800 Subject: [PATCH] fix: report the right line and column for CRLF and mixed line endings --- tests/test_parser.py | 23 +++++++++++++++++++++++ tomlkit/source.py | 13 +++++++++---- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/tests/test_parser.py b/tests/test_parser.py index 44d5cd6c..9302bc78 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -41,6 +41,29 @@ def test_parser_should_raise_an_error_for_empty_tables() -> None: assert e.value.col == 1 +@pytest.mark.parametrize("line_ending", ["\n", "\r\n"]) +def test_parser_reports_the_same_position_regardless_of_line_ending( + line_ending: str, +) -> None: + content = line_ending.join(["a = 1", "b = 2", "c = @", "d = 4"]) + line_ending + + with pytest.raises(UnexpectedCharError) as e: + Parser(content).parse() + + assert e.value.line == 3 + assert e.value.col == 4 + + +def test_parser_reports_the_right_position_for_mixed_line_endings() -> None: + content = "a = 1\r\nb = 2\nc = @\r\n" + + with pytest.raises(UnexpectedCharError) as e: + Parser(content).parse() + + assert e.value.line == 3 + assert e.value.col == 4 + + def test_parser_should_raise_an_error_if_equal_not_found() -> None: content = """[foo] a {c = 1, d = 2} diff --git a/tomlkit/source.py b/tomlkit/source.py index b2e9caa0..1256c86a 100644 --- a/tomlkit/source.py +++ b/tomlkit/source.py @@ -230,11 +230,16 @@ def parse_error( return exception(line, col, *args, **kwargs) def _to_linecol(self) -> tuple[int, int]: + # `keepends` keeps each line's own terminator inside its length: a "\r\n" + # break occupies two characters, and assuming a single one made the + # running offset fall one behind per CRLF line, so an error later in the + # document was reported on the wrong line and column. + lines = self.splitlines(keepends=True) cur = 0 - for i, line in enumerate(self.splitlines()): - if cur + len(line) + 1 > self.idx: + for i, line in enumerate(lines): + if cur + len(line) > self.idx: return (i + 1, self.idx - cur) - cur += len(line) + 1 + cur += len(line) - return len(self.splitlines()), 0 + return len(lines), 0