Skip to content

NormalizeLineEndings(Mac) leaves a CRLF in the output and drops a line break when a CRLF is followed by an LF #137

Description

@matt-edmondson

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions