From d17910d46bcc0f3d5b9c1de607a41836e1c85ce2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Afonso=20Janu=C3=A1rio?= Date: Mon, 14 Sep 2026 22:46:47 +0100 Subject: [PATCH] Fix single-line Array losing its closing bracket after a trailing comment Array.as_string() for a non-multiline array just concatenates the raw text of each item, including any comment added through add_line(). A "#" comment runs to the end of its physical line, so when the comment happens to be the last thing rendered before "]", the bracket gets swallowed into the comment and the array no longer parses. >>> a = tomlkit.array() >>> a.add_line("foo", comment="bar") >>> a.as_string() '[\n "foo", # bar]' # note: no closing bracket outside the comment Force a newline before the bracket whenever the rendered content would otherwise end with an unterminated comment, so the array keeps round-tripping through tomlkit.loads(). Co-Authored-By: Claude Sonnet 5 --- tests/test_items.py | 11 +++++++++++ tomlkit/items.py | 13 ++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/tests/test_items.py b/tests/test_items.py index f6377d0..1fad45b 100644 --- a/tests/test_items.py +++ b/tests/test_items.py @@ -559,6 +559,17 @@ def test_array_add_line() -> None: ) +def test_array_add_line_with_comment_on_last_line_round_trips() -> None: + # A trailing comment on the last (and only) line of a non-multiline array + # used to swallow the closing bracket, since "#" comments run to the end + # of the physical line and nothing forced a newline before the "]". + t = api.array() + t.add_line("foo", comment="bar") + rendered = t.as_string() + assert rendered == '[\n "foo", # bar\n]' + assert parse(f"a = {rendered}")["a"] == ["foo"] + + def test_array_add_line_multiline_comment_is_rejected() -> None: t = api.array() with pytest.raises(ValueError, match="line breaks"): diff --git a/tomlkit/items.py b/tomlkit/items.py index c950e5d..2ba95c5 100644 --- a/tomlkit/items.py +++ b/tomlkit/items.py @@ -1464,7 +1464,18 @@ def multiline(self, multiline: bool) -> Array: def as_string(self) -> str: if not self._multiline or not self._value: - return f"[{''.join(v.as_string() for v in self._iter_items())}]" + s = "".join(v.as_string() for v in self._iter_items()) + # A trailing "# ..." comment swallows anything that follows it on + # the same line, including the closing bracket. If the rendered + # content ends with a comment that isn't already followed by a + # newline, force one so the array still round-trips as valid TOML. + if ( + self._value + and self._value[-1].comment is not None + and not s.endswith(("\n", "\r")) + ): + s += "\n" + self.trivia.indent + return f"[{s}]" s = "[\n" s += "".join(