From db6c076599aa3ef34ec9ecc2e3cb6ceba6db5074 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 11:30:10 +0000 Subject: [PATCH] Locate a parse failure in a CR-only metadata file on its real line [patch] System.Text.Json counts lines by \n only, so in a file with CR-only line endings it reports every error on line zero, measured from the start of the file. LocationOf stopped its column walk at the first \r, which clamped the diagnostic to the end of the file's first line. Treat \r as ending a line only when \n follows it, in both the walk and the length check, and let LocationAt map the offset to Roslyn's line and column. Fixes #60 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017vfJW5t9HHcsUTKWJqPdLK --- .../GeneratorBaseTests.cs | 3 ++- SourceGeneratorToolkit/MetadataFile.cs | 18 ++++++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/SourceGeneratorToolkit.Test/GeneratorBaseTests.cs b/SourceGeneratorToolkit.Test/GeneratorBaseTests.cs index 7ff495f..4698c6b 100644 --- a/SourceGeneratorToolkit.Test/GeneratorBaseTests.cs +++ b/SourceGeneratorToolkit.Test/GeneratorBaseTests.cs @@ -78,6 +78,7 @@ public void AMalformedMetadataFileIsReportedRatherThanSwallowed() [TestMethod] [DataRow("\n", DisplayName = "LF")] [DataRow("\r\n", DisplayName = "CRLF")] + [DataRow("\r", DisplayName = "CR")] public void AMalformedMetadataFileIsReportedAtTheBadLine(string newLine) { string json = string.Join(newLine, "{", " \"things\":", " [1,,2]", "}"); @@ -90,7 +91,7 @@ public void AMalformedMetadataFileIsReportedAtTheBadLine(string newLine) Assert.AreEqual(2, span.StartLinePosition.Line); // System.Text.Json reports the position it had reached, which is the first comma of the pair. - Assert.AreEqual(json.Split('\n')[2].IndexOf(",,", StringComparison.Ordinal), span.StartLinePosition.Character); + Assert.AreEqual(" [1,,2]".IndexOf(",,", StringComparison.Ordinal), span.StartLinePosition.Character); } [TestMethod] diff --git a/SourceGeneratorToolkit/MetadataFile.cs b/SourceGeneratorToolkit/MetadataFile.cs index 02ccee2..363f6ff 100644 --- a/SourceGeneratorToolkit/MetadataFile.cs +++ b/SourceGeneratorToolkit/MetadataFile.cs @@ -223,17 +223,31 @@ private Location LocationOf(JsonException ex) } long bytes = ex.BytePositionInLine ?? 0; - while (bytes > 0 && index < Text.Length && Text[index] is not '\r' and not '\n') + while (bytes > 0 && index < Text.Length && !EndsLine(index)) { int width = CharactersAt(index); bytes -= Utf8Bytes(index, width); index += width; } - int length = index < Text.Length && Text[index] is not '\r' and not '\n' ? 1 : 0; + int length = index < Text.Length && !EndsLine(index) ? 1 : 0; return LocationAt(index, length); } + /// + /// Says whether the character at an offset ends a line as System.Text.Json counts lines: a + /// \n, or the \r of a \r\n pair. + /// + /// The offset of the character. + /// when the character ends the line. + /// + /// A lone \r is not a line break to System.Text.Json, so in a file with CR-only line + /// endings it reports every position on line zero, measured from the start of the file. Stopping + /// at that \r would clamp the position to the end of the first line. + /// + private bool EndsLine(int index) => + Text[index] == '\n' || (Text[index] == '\r' && index + 1 < Text.Length && Text[index + 1] == '\n'); + /// /// Finds where a line starts, counting lines by \n line feeds as System.Text.Json does. ///