diff --git a/CHANGELOG.md b/CHANGELOG.md index f414f212..d36550d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Arithmetic and `replace()` results for `Integer`, `Float`, `String`, `DateTime`, `Date` and `Time` no longer share the original item's `Trivia`, so changing the result's comment or indentation no longer rewrites the item still stored in the document. + ## [0.15.2] - 2026-10-16 ### Changed diff --git a/tests/test_items.py b/tests/test_items.py index 27a1c6e2..36f4afd2 100644 --- a/tests/test_items.py +++ b/tests/test_items.py @@ -701,6 +701,31 @@ def test_item_array_of_dicts_converted_to_aot() -> None: ) +@pytest.mark.parametrize( + ("content", "op"), + [ + ("my_int = 1 # keep\n", lambda item: item + 1), + ("my_float = 1.5 # keep\n", lambda item: item + 1), + ('my_str = "a" # keep\n', lambda item: item + "b"), + ( + "my_dt = 1979-05-27T07:32:00Z # keep\n", + lambda item: item + timedelta(days=1), + ), + ("my_date = 1979-05-27 # keep\n", lambda item: item + timedelta(days=1)), + ("my_time = 07:32:00 # keep\n", lambda item: item.replace(second=1)), + ], +) +def test_arithmetic_result_does_not_share_trivia(content: str, op: Callable) -> None: + doc = parse(content) + original = doc[next(iter(doc))] + + result = op(original) + assert result.trivia is not original.trivia + + result.trivia.comment = "# MUTATED" + assert doc.as_string() == content + + def test_add_float_to_int() -> None: content = "[table]\nmy_int = 2043" doc = parse(content) diff --git a/tomlkit/items.py b/tomlkit/items.py index 30d8a4f2..f8f0d1dd 100644 --- a/tomlkit/items.py +++ b/tomlkit/items.py @@ -671,7 +671,7 @@ def _new(self, result: int) -> Integer: if self._sign and result >= 0: raw = f"+{raw}" - return Integer(result, self._trivia, raw) + return Integer(result, self._trivia.copy(), raw) def _getstate(self, protocol: int = 3) -> tuple[int, Trivia, str]: return int(self), self._trivia, self._raw @@ -896,7 +896,7 @@ def _new(self, result: float) -> Float: if self._sign and result >= 0 and not raw.startswith("-"): raw = f"+{raw}" - return Float(result, self._trivia, raw) + return Float(result, self._trivia.copy(), raw) def _getstate(self, protocol: int = 3) -> tuple[float, Trivia, str]: return float(self), self._trivia, self._raw @@ -1202,7 +1202,7 @@ def _new(self, result: datetime) -> DateTime: result.second, result.microsecond, result.tzinfo, - self._trivia, + self._trivia.copy(), raw, fold=result.fold, ) @@ -1297,7 +1297,7 @@ def replace(self, *args: object, **kwargs: object) -> Date: def _new(self, result: date) -> Date: raw = result.isoformat() - return Date(result.year, result.month, result.day, self._trivia, raw) + return Date(result.year, result.month, result.day, self._trivia.copy(), raw) def _getstate(self, protocol: int = 3) -> tuple[int, int, int, Trivia, str]: return (self.year, self.month, self.day, self._trivia, self._raw) @@ -1363,7 +1363,7 @@ def _new(self, result: time) -> Time: result.second, result.microsecond, result.tzinfo, - self._trivia, + self._trivia.copy(), raw, fold=result.fold, ) @@ -2291,7 +2291,7 @@ def __add__(self, other: str) -> String: return self._new(result, original) def _new(self, result: str, original: str) -> String: - return String(self._t, result, original, self._trivia) + return String(self._t, result, original, self._trivia.copy()) def _getstate(self, protocol: int = 3) -> tuple[StringType, str, str, Trivia]: return self._t, str(self), self._original, self._trivia