What's wrong
StringExtensions.NormalizeLineEndings, Extensions/StringExtensions.cs:208:
LineEndingStyle.Mac => LineEndingRegexUnix.Replace(LineEndingRegexWindows.Replace(s, "\r"), "\r"),
The first pass turns every \r\n into \r. When a lone \n comes right after a \r\n, it then sits directly after the new \r. The second pass uses LineEndingRegexUnix ((?<!\r)\n), whose lookbehind rejects exactly that \n, so it is left alone. The two passes together build a new \r\n.
Repro
"a\r\n\nb".NormalizeLineEndings(LineEndingStyle.Mac) // returns "a\r\nb", expected "a\r\rb"
"a\r\n\nb".NormalizeLineEndings(LineEndingStyle.Mac).DetermineLineEndings() // Windows, not Mac
A local repro confirmed this: a\r\n\nb -> Mac: a\r\nb detect=Windows.
The other styles give correct results for the same input: Unix gives a\n\nb and Windows gives a\r\n\r\nb. Their pass order cannot create a new CRLF.
Why it matters
- The output still contains a Windows line ending, so the result is not Mac-normalized, and
DetermineLineEndings() on it reports Windows.
- One of the two line breaks is lost, which changes the line count of the text.
- A CRLF followed by an LF is common in files with mixed line endings, which is exactly the input a normalizer is for.
- The existing
NormalizeLineEndingsToMac test uses line1\nline2\r\nline3\r, which never puts an LF right after a CRLF, so it does not catch this.
Suggested fix
Do the conversion in one pass with a single alternation regex, so no intermediate string can create new sequences:
private static Regex AnyLineEndingRegex { get; } = new(@"\r\n|\r|\n", RegexOptions.Compiled);
// ...
LineEndingStyle.Mac => AnyLineEndingRegex.Replace(s, "\r"),
The same regex would also simplify the Unix, Windows, Mixed and None branches, which currently need two or three passes each.
Acceptance criteria
"a\r\n\nb".NormalizeLineEndings(LineEndingStyle.Mac) == "a\r\rb"
- A test covers CRLF followed by LF, and CR followed by CRLF, for every
LineEndingStyle.
What's wrong
StringExtensions.NormalizeLineEndings,Extensions/StringExtensions.cs:208:The first pass turns every
\r\ninto\r. When a lone\ncomes right after a\r\n, it then sits directly after the new\r. The second pass usesLineEndingRegexUnix((?<!\r)\n), whose lookbehind rejects exactly that\n, so it is left alone. The two passes together build a new\r\n.Repro
A local repro confirmed this:
a\r\n\nb -> Mac: a\r\nb detect=Windows.The other styles give correct results for the same input: Unix gives
a\n\nband Windows givesa\r\n\r\nb. Their pass order cannot create a new CRLF.Why it matters
DetermineLineEndings()on it reportsWindows.NormalizeLineEndingsToMactest usesline1\nline2\r\nline3\r, which never puts an LF right after a CRLF, so it does not catch this.Suggested fix
Do the conversion in one pass with a single alternation regex, so no intermediate string can create new sequences:
The same regex would also simplify the Unix, Windows, Mixed and None branches, which currently need two or three passes each.
Acceptance criteria
"a\r\n\nb".NormalizeLineEndings(LineEndingStyle.Mac) == "a\r\rb"LineEndingStyle.