Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions src/DiffEngine.Tests/InlinePatcherTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,33 @@ public async Task ASnapshotBeforeAVerbatimStringOpeningOnAnEscapedQuoteIsStillFo
const string q3 = "\"\"\"";
const string q4 = "\"\"\"\"";

/// <summary>
/// A CRLF file holding a literal whose own lines are LF. The anchor is normalised to the
/// file's dominant ending before the search, which is right for writing and wrong for
/// matching: the literal is the same expression and compared byte for byte it is not, so the
/// snapshot could not be patched at all.
/// </summary>
[Test]
public async Task AnAnchorMatchesAcrossMixedLineEndings()
{
var literal = "\"\"\"\n old\n \"\"\"";
var source =
"class Tests\r\n" +
"{\r\n" +
" async Task Test()\r\n" +
" {\r\n" +
$" await Snapshot({literal});\r\n" +
" }\r\n" +
"}";

var status = TryApply(source, 5, InlinePatchMode.Set, literal, "new", out var newSource, out var reason);

await Assert.That(status).IsEqualTo(PatchStatus.Applied);
await Assert.That(reason).IsEmpty();
await Assert.That(newSource).Contains("new");
await Assert.That(newSource).DoesNotContain("old");
}

/// <summary>
/// A comment between the argument and the closing paren. A line comment's span includes the
/// newline that ends it, so trimming trailing whitespace first ate that newline and the
Expand Down
33 changes: 27 additions & 6 deletions src/DiffEngine/Inline/InlinePatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -296,13 +296,34 @@ readonly struct ExpectedArgument(int start, int end, int listStart, bool blocked
public bool IsAbsent => Start == End;

/// <summary>
/// True when the argument is character for character the given expression.
/// True when the argument is the given expression, character for character or once both
/// have had their newlines normalised.
/// <para>
/// The needle arrives normalised to the file's dominant line ending, which is the right
/// thing to write but the wrong thing to search for: a literal whose own lines use the
/// other ending is the same expression and did not match, so the snapshot could not be
/// patched at all. Mixed endings inside one file are ordinary - a merge, an editor that
/// only fixes what it touches, a generator.
/// </para>
/// </summary>
public bool Matches(string source, string expression) =>
!IsAbsent &&
!BlockedByName &&
End - Start == expression.Length &&
string.CompareOrdinal(source, Start, expression, 0, expression.Length) == 0;
public bool Matches(string source, string expression)
{
if (IsAbsent ||
BlockedByName)
{
return false;
}

if (End - Start == expression.Length &&
string.CompareOrdinal(source, Start, expression, 0, expression.Length) == 0)
{
return true;
}

var argument = source.Substring(Start, End - Start);
return SourceLanguage.NormalizeNewlines(argument) ==
SourceLanguage.NormalizeNewlines(expression);
}
}

static bool TryReadArguments(string source, SourceScan scan, int openParen, out ExpectedArgument expected)
Expand Down
Loading