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
46 changes: 46 additions & 0 deletions src/DiffEngine.Tests/InlinePatcherTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,52 @@ public async Task ASnapshotBeforeARawInterpolatedStringIsStillFound()
const string rawInterpolated =
" var text = $" + q3 + "{Render(" + q4 + "has " + q3 + " inside" + q4 + ")}" + q3 + ";";

/// <summary>
/// A verbatim string that opens on an escaped quote. Measuring the quote run before asking
/// whether the literal is verbatim lexed this as a 3-quote raw string, which then ran to the
/// end of the file and hid every call after it. There is no verbatim raw form, so a run of
/// quotes after @" is content, not a delimiter.
/// </summary>
[Test]
public async Task AVerbatimStringOpeningOnAnEscapedQuoteIsSteppedOverWhole()
{
var source = Method($"{verbatimEscapedQuote}\n await Snapshot(\"old\");");
var status = TryApply(source, 6, InlinePatchMode.Set, "\"old\"", "new", out var newSource, out var reason);

await Assert.That(status).IsEqualTo(PatchStatus.Applied);
await Assert.That(reason).IsEmpty();
await Assert.That(newSource).Contains("Snapshot(\"new\")");
// And the literal it stepped over is untouched
await Assert.That(newSource).Contains(verbatimEscapedQuote);
}

/// <summary>
/// The same shape with the call before it, so the scan has to get past the literal rather
/// than stop short of it.
/// </summary>
[Test]
public async Task ASnapshotBeforeAVerbatimStringOpeningOnAnEscapedQuoteIsStillFound()
{
var source = Method($" await Snapshot(\"old\");\n{verbatimEscapedQuote}");
var status = TryApply(source, 5, InlinePatchMode.Set, "\"old\"", "new", out var newSource, out _);

await Assert.That(status).IsEqualTo(PatchStatus.Applied);
await Assert.That(newSource).Contains("Snapshot(\"new\")");
await Assert.That(newSource).Contains(verbatimEscapedQuote);
}

// A verbatim string whose first content character is an escaped quote:
//
// var path = @"""C:\tools\run.exe"" --flag";
//
// By concatenation for the same reason rawInterpolated is: the quote runs cannot be written
// in a literal here without a delimiter wider than the thing being described.
const string verbatimEscapedQuote =
" var path = @" + q3 + "C:" + slash + "tools" + slash + "run.exe" + q2 + " --flag" + q1 + ";";

const string q1 = "\"";
const string q2 = "\"\"";
const string slash = "\\";
const string q3 = "\"\"\"";
const string q4 = "\"\"\"\"";

Expand Down
7 changes: 6 additions & 1 deletion src/DiffEngine/Inline/CsLanguage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,12 @@ static bool TrySkipStringLike(string source, ref int index)
}

var quotes = StringLiteral.QuoteRunLength(source, cursor);
if (quotes >= 3)
// Asked before the run is treated as a delimiter, because there is no verbatim raw form:
// a run of quotes after @" is an escaped quote and the start of the content. Measuring
// alone lexed a verbatim string opening on an escaped quote as a 3-quote raw string,
// which then ran to the end of the file and hid every call after it. CsStringLiteral and
// FsLanguage already ask this, and for the same reason
if (quotes >= 3 && !verbatim)
{
// Raw string: scan to a closing run of >= quotes, stepping over any interpolation
// hole whole. Skipping holes as content read the quotes of a literal inside one -
Expand Down
Loading