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
52 changes: 45 additions & 7 deletions src/DiffEngine.Tests/InlineApplierTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ static byte[] Utf8(string text, bool bom)
return result;
}

const string source = "class C\n{\n void M() => VerifyInline(value, \"old\");\n}";
const string source = "class C\n{\n void M() => Verify(value).Snapshot(\"old\");\n}";

[Test]
public async Task Utf8BomPreserved()
Expand Down Expand Up @@ -193,7 +193,7 @@ public async Task AlreadyAppliedDoesNotWrite()
[Test]
public async Task ParallelAppliesToSameFile()
{
var multi = "class C\n{\n void A() => VerifyInline(a, \"oldA\");\n void B() => VerifyInline(b, \"oldB\");\n}";
var multi = "class C\n{\n void A() => Verify(a).Snapshot(\"oldA\");\n void B() => Verify(b).Snapshot(\"oldB\");\n}";
var path = WriteTemp(Utf8(multi, bom: false));
try
{
Expand Down Expand Up @@ -234,7 +234,7 @@ static int Count(string text, string value)
[Test]
public async Task ParallelAppliesWithIdenticalLiterals()
{
var multi = "class C\n{\n void A() => VerifyInline(a, \"old\");\n void B() => VerifyInline(b, \"old\");\n}";
var multi = "class C\n{\n void A() => Verify(a).Snapshot(\"old\");\n void B() => Verify(b).Snapshot(\"old\");\n}";
var path = WriteTemp(Utf8(multi, bom: false));
try
{
Expand All @@ -258,7 +258,7 @@ public async Task ParallelAppliesWithIdenticalLiterals()
[Test]
public async Task SequentialAppliesWithIdenticalLiterals()
{
var multi = "class C\n{\n void A() => VerifyInline(a, \"old\");\n void B() => VerifyInline(b, \"old\");\n}";
var multi = "class C\n{\n void A() => Verify(a).Snapshot(\"old\");\n void B() => Verify(b).Snapshot(\"old\");\n}";
var path = WriteTemp(Utf8(multi, bom: false));
try
{
Expand All @@ -270,8 +270,8 @@ public async Task SequentialAppliesWithIdenticalLiterals()

var text = await File.ReadAllTextAsync(path);
await Assert.That(text).DoesNotContain("old");
var indexA = text.IndexOf("VerifyInline(a", StringComparison.Ordinal);
var indexB = text.IndexOf("VerifyInline(b", StringComparison.Ordinal);
var indexA = text.IndexOf("Verify(a)", StringComparison.Ordinal);
var indexB = text.IndexOf("Verify(b)", StringComparison.Ordinal);
var segmentA = text.Substring(indexA, indexB - indexA);
await Assert.That(segmentA).Contains("newA");
await Assert.That(segmentA).DoesNotContain("newB");
Expand Down Expand Up @@ -341,6 +341,44 @@ public async Task RoundTripNullExpression()
}
}

[Test]
[Arguments(InlinePatchMode.Set)]
[Arguments(InlinePatchMode.Append)]
[Arguments(InlinePatchMode.Remove)]
public async Task RoundTripMode(InlinePatchMode mode)
{
var patch = new InlinePatch("Tests.cs", 1, null, "content", mode);

var read = InlinePatchFile.TryParse(InlinePatchFile.Build(patch), out var result);

await Assert.That(read).IsTrue();
await Assert.That(result!.Mode).IsEqualTo(mode);
}

[Test]
public async Task DefaultModeIsSet()
{
var read = InlinePatchFile.TryParse(InlinePatchFile.Build(new("Tests.cs", 1, null, "content")), out var result);

await Assert.That(read).IsTrue();
await Assert.That(result!.Mode).IsEqualTo(InlinePatchMode.Set);
}

// The version 1 shape, which had no mode line
[Test]
public async Task PreviousVersionFails()
{
var read = InlinePatchFile.TryParse("version: 1\nsourceFile: x\nlineHint: 1\noriginalExpression:\nnewContent: YQ==\n", out _);
await Assert.That(read).IsFalse();
}

[Test]
public async Task UnknownModeFails()
{
var read = InlinePatchFile.TryParse("version: 2\nsourceFile: x\nlineHint: 1\nmode: Sideways\noriginalExpression:\nnewContent: YQ==\n", out _);
await Assert.That(read).IsFalse();
}

[Test]
public async Task MissingFileFails()
{
Expand All @@ -358,7 +396,7 @@ public async Task GarbageFails()
[Test]
public async Task WrongVersionFails()
{
var read = InlinePatchFile.TryParse("version: 2\nsourceFile: x\nlineHint: 1\noriginalExpression:\nnewContent: YQ==\n", out _);
var read = InlinePatchFile.TryParse("version: 3\nsourceFile: x\nlineHint: 1\nmode: Set\noriginalExpression:\nnewContent: YQ==\n", out _);
await Assert.That(read).IsFalse();
}
}
355 changes: 260 additions & 95 deletions src/DiffEngine.Tests/InlinePatcherTests.cs

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions src/DiffEngine/DiffRunner_Inline.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,17 @@ public static partial class DiffRunner
/// thread per call.
/// </para>
/// </summary>
/// <exception cref="ArgumentException">
/// <see cref="InlinePatchMode.Remove"/>, which has nothing for a user to review. Apply it with
/// <see cref="InlineApplier"/> instead.
/// </exception>
public static async Task<InlineResult> AddInlineAsync(InlinePatch patch, Cancel cancel = default)
{
if (patch.Mode == InlinePatchMode.Remove)
{
throw new ArgumentException($"{InlinePatchMode.Remove} patches are not reviewable. Use InlineApplier.", nameof(patch));
}

var check = CheckInline();
if (check != InlineResult.Queued)
{
Expand Down
8 changes: 5 additions & 3 deletions src/DiffEngine/Inline/InlineApplier.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Security.Cryptography;
using System.Security.Cryptography;

namespace DiffEngine;

Expand All @@ -21,7 +21,8 @@ public static InlineApplyResult Apply(InlinePatch patch)
return InlineApplyResult.Failed("InlinePatch.SourceFile is empty");
}

if (patch.NewContent is null)
if (patch.NewContent is null &&
patch.Mode != InlinePatchMode.Remove)
{
return InlineApplyResult.Failed("InlinePatch.NewContent is null");
}
Expand All @@ -46,7 +47,7 @@ public static InlineApplyResult Apply(InlinePatch patch)
return InlineApplyResult.Failed($"Source file does not exist: {fullPath}");
}

var newContent = CsStringLiteral.NormalizeNewlines(patch.NewContent);
var newContent = patch.NewContent is null ? "" : CsStringLiteral.NormalizeNewlines(patch.NewContent);
var normalizedPath = fullPath.ToLowerInvariant();
lock (gates.GetOrAdd(normalizedPath, static _ => new()))
{
Expand Down Expand Up @@ -106,6 +107,7 @@ static InlineApplyResult LockedApply(string fullPath, InlinePatch patch, string
var status = InlinePatcher.TryApply(
source,
patch.LineHint,
patch.Mode,
patch.OriginalExpression,
newContent,
out var newSource,
Expand Down
16 changes: 12 additions & 4 deletions src/DiffEngine/Inline/InlinePatch.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace DiffEngine;
namespace DiffEngine;

/// <summary>
/// Describes a pending inline-snapshot edit to a C# source file.
Expand All @@ -10,12 +10,18 @@ public InlinePatch()
{
}

public InlinePatch(string sourceFile, int lineHint, string? originalExpression, string newContent)
public InlinePatch(
string sourceFile,
int lineHint,
string? originalExpression,
string newContent,
InlinePatchMode mode = InlinePatchMode.Set)
{
SourceFile = sourceFile;
LineHint = lineHint;
OriginalExpression = originalExpression;
NewContent = newContent;
Mode = mode;
}

/// <summary>
Expand All @@ -24,7 +30,7 @@ public InlinePatch(string sourceFile, int lineHint, string? originalExpression,
public string SourceFile { get; set; } = null!;

/// <summary>
/// 1 based line of the VerifyInline call. A hint only; content search is the locator.
/// 1 based line of the verify or Snapshot call. A hint only; content search is the locator.
/// </summary>
public int LineHint { get; set; }

Expand All @@ -35,7 +41,9 @@ public InlinePatch(string sourceFile, int lineHint, string? originalExpression,
public string? OriginalExpression { get; set; }

/// <summary>
/// The new snapshot text. Newlines are \n.
/// The new snapshot text. Newlines are \n. Ignored for <see cref="InlinePatchMode.Remove"/>.
/// </summary>
public string NewContent { get; set; } = null!;

public InlinePatchMode Mode { get; set; }
}
18 changes: 10 additions & 8 deletions src/DiffEngine/Inline/InlinePatchFile.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace DiffEngine;
namespace DiffEngine;

/// <summary>
/// Reads and writes the staged inline patch file. Plain text with base64 encoded
Expand All @@ -22,8 +22,8 @@ public static string Build(InlinePatch patch)
var expression = patch.OriginalExpression is null
? ""
: Convert.ToBase64String(Encoding.UTF8.GetBytes(patch.OriginalExpression));
var content = Convert.ToBase64String(Encoding.UTF8.GetBytes(patch.NewContent));
return $"version: 1\nsourceFile: {patch.SourceFile}\nlineHint: {patch.LineHint}\noriginalExpression: {expression}\nnewContent: {content}\n";
var content = Convert.ToBase64String(Encoding.UTF8.GetBytes(patch.NewContent ?? ""));
return $"version: 2\nsourceFile: {patch.SourceFile}\nlineHint: {patch.LineHint}\nmode: {patch.Mode}\noriginalExpression: {expression}\nnewContent: {content}\n";
}

public static bool TryRead(string path, [NotNullWhen(true)] out InlinePatch? patch)
Expand Down Expand Up @@ -53,15 +53,17 @@ public static bool TryParse(string text, [NotNullWhen(true)] out InlinePatch? pa
var lines = text
.Replace("\r\n", "\n")
.Split('\n');
if (lines.Length < 5 ||
if (lines.Length < 6 ||
!TryValue(lines[0], "version", out var version) ||
version != "1" ||
version != "2" ||
!TryValue(lines[1], "sourceFile", out var sourceFile) ||
sourceFile.Length == 0 ||
!TryValue(lines[2], "lineHint", out var lineText) ||
!int.TryParse(lineText, out var lineHint) ||
!TryValue(lines[3], "originalExpression", out var expressionBase64) ||
!TryValue(lines[4], "newContent", out var contentBase64))
!TryValue(lines[3], "mode", out var modeText) ||
!Enum.TryParse<InlinePatchMode>(modeText, out var mode) ||
!TryValue(lines[4], "originalExpression", out var expressionBase64) ||
!TryValue(lines[5], "newContent", out var contentBase64))
{
return false;
}
Expand All @@ -80,7 +82,7 @@ public static bool TryParse(string text, [NotNullWhen(true)] out InlinePatch? pa
return false;
}

patch = new(sourceFile, lineHint, expression, content);
patch = new(sourceFile, lineHint, expression, content, mode);
return true;
}

Expand Down
25 changes: 25 additions & 0 deletions src/DiffEngine/Inline/InlinePatchMode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
namespace DiffEngine;

/// <summary>
/// What an <see cref="InlinePatch"/> does to the source.
/// </summary>
public enum InlinePatchMode
{
/// <summary>
/// Set the expected argument of an existing Snapshot call: replace
/// <see cref="InlinePatch.OriginalExpression"/> when it is set, otherwise insert an argument.
/// </summary>
Set,

/// <summary>
/// Append a Snapshot call after the verify invocation. Used for a snapshot that has never been
/// accepted, where there is no Snapshot call to set an argument on yet.
/// </summary>
Append,

/// <summary>
/// Remove the Snapshot call. Used when inline is switched off and the snapshot migrates back
/// to a file.
/// </summary>
Remove
}
Loading
Loading