Skip to content

Commit 426723f

Browse files
committed
Support diff in CLI
1 parent 9798f33 commit 426723f

21 files changed

+3006
-47
lines changed

README.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,19 @@ this command:
120120
dotnet tool install SquiggleCop.Tool
121121
```
122122

123-
then generate a baseline like this:
123+
To generate or diff a new baseline, run the `generate` command like this:
124124

125-
```powershell
126-
dotnet squigglecop generate ./path/to/diagnostics.sarif --auto-baseline
125+
```
126+
Usage: dotnet squigglecop generate [--auto-baseline] [--output <String>] [--context <Int32>] [--help] sarif
127+
128+
Arguments:
129+
0: sarif The SARIF log to generate a baseline for (Required)
130+
131+
Options:
132+
-a, --auto-baseline Automatically update baseline if necessary
133+
-o, --output <String> The output path for the baseline file
134+
-c, --context <Int32> Number of context lines to use in the diff (Default: 3)
135+
-h, --help Show help message
127136
```
128137

129138
### MSBuild Tasks

src/Common/BaselineDiff.cs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,30 @@
33
namespace SquiggleCop.Common;
44

55
/// <summary>
6-
/// A record that represents the differences between two baselines.
6+
/// A class that represents the differences between two baselines.
77
/// </summary>
8-
public record class BaselineDiff
8+
public class BaselineDiff
99
{
10+
private readonly DiffPaneModel _model;
11+
1012
/// <summary>
1113
/// Creates a new instance of <see cref="BaselineDiff"/>.
1214
/// </summary>
1315
/// <param name="model">
14-
/// The <see cref="SideBySideDiffModel"/> model that contains the differences.
16+
/// The <see cref="DiffPaneModel"/> model that contains the differences.
1517
/// </param>
16-
public BaselineDiff(SideBySideDiffModel model)
18+
public BaselineDiff(DiffPaneModel model)
1719
{
18-
HasDifferences = model.OldText.HasDifferences || model.NewText.HasDifferences;
20+
_model = model;
1921
}
2022

2123
/// <summary>
2224
/// <see langword="true"/> if the two baselines have differences; otherwise, <see langword="false"/>.
2325
/// </summary>
24-
public bool HasDifferences { get; }
26+
public bool HasDifferences => _model.HasDifferences;
27+
28+
/// <summary>
29+
/// The lines that represent the differences between the two baselines.
30+
/// </summary>
31+
public IReadOnlyList<DiffPiece> Lines => _model.Lines;
2532
}

src/Common/BaselineDiffer.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
using DiffPlex.DiffBuilder;
1+
using DiffPlex;
2+
using DiffPlex.Chunkers;
3+
using DiffPlex.DiffBuilder;
24
using DiffPlex.DiffBuilder.Model;
35

46
namespace SquiggleCop.Common;
@@ -11,7 +13,8 @@ namespace SquiggleCop.Common;
1113
/// </remarks>
1214
public class BaselineDiffer
1315
{
14-
private readonly SideBySideDiffBuilder _diffBuilder = SideBySideDiffBuilder.Instance;
16+
private readonly InlineDiffBuilder _diffBuilder = InlineDiffBuilder.Instance;
17+
private readonly IChunker _chunker = new LineChunker();
1518

1619
/// <summary>
1720
/// Diffs two baselines.
@@ -21,7 +24,7 @@ public class BaselineDiffer
2124
/// <returns>A <see cref="BaselineDiff"/> that holds the results of the diff.</returns>
2225
public BaselineDiff Diff(string expected, string actual)
2326
{
24-
SideBySideDiffModel diff = _diffBuilder.BuildDiffModel(expected, actual, ignoreWhitespace: false, ignoreCase: false);
27+
DiffPaneModel diff = _diffBuilder.BuildDiffModel(expected, actual, ignoreWhitespace: false, ignoreCase: false, _chunker);
2528
return new BaselineDiff(diff);
2629
}
2730
}

src/Tool/AppBuilder.cs renamed to src/Tool/App.cs

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
using Cocona;
1+
using System.ComponentModel.DataAnnotations;
2+
using System.Globalization;
3+
using System.IO;
4+
5+
using Cocona;
26
using Cocona.Application;
37
using Cocona.Builder;
48

@@ -7,13 +11,14 @@
711
using Spectre.Console;
812

913
using SquiggleCop.Common;
14+
using SquiggleCop.Tool.Rendering;
1015

1116
namespace SquiggleCop.Tool;
1217

1318
/// <summary>
1419
/// Configure and create the <see cref="CoconaApp"/>.
1520
/// </summary>
16-
public static class AppBuilder
21+
public static class App
1722
{
1823
/// <summary>
1924
/// Create a new instance of <see cref="CoconaApp"/>.
@@ -44,6 +49,7 @@ public static CoconaApp Create(Action<CoconaAppBuilder> configure)
4449
builder.Services.AddSingleton<Serializer>();
4550
builder.Services.AddSingleton<BaselineDiffer>();
4651
builder.Services.AddSingleton<BaselineWriter>();
52+
builder.Services.AddRenderers();
4753

4854
configure(builder);
4955

@@ -60,11 +66,14 @@ public static CoconaApp Create(Action<CoconaAppBuilder> configure)
6066
private static async Task<int> GenerateAsync(
6167
SarifParser parser,
6268
Serializer serializer,
69+
BaselineDiffer differ,
6370
BaselineWriter writer,
71+
ReportRenderer renderer,
6472
IAnsiConsole console,
6573
[Option('a', Description = "Automatically update baseline if necessary")] bool autoBaseline,
6674
[Argument(Description = "The SARIF log to generate a baseline for")] string sarif,
67-
[Option('o', Description = "The output path for the baseline file")] string? output)
75+
[Option('o', Description = "The output path for the baseline file")] string? output,
76+
[Option('c', Description = "Number of context lines to use in the diff")][Range(1, 100)] int context = 3)
6877
{
6978
if (!File.Exists(sarif))
7079
{
@@ -73,11 +82,6 @@ private static async Task<int> GenerateAsync(
7382

7483
output = ValidateOutputPath(output);
7584

76-
if (!autoBaseline)
77-
{
78-
throw new NotImplementedException("Manual baseline creation is not yet implemented. Use `--auto-baseline` and your source control system to review changes for now.");
79-
}
80-
8185
try
8286
{
8387
IReadOnlyCollection<DiagnosticConfig> configs;
@@ -88,16 +92,24 @@ private static async Task<int> GenerateAsync(
8892
}
8993

9094
string newBaseline = serializer.Serialize(configs);
91-
await writer.WriteAsync(output, newBaseline).ConfigureAwait(false);
95+
96+
BaselineDiff diff = differ.Diff(await ReadBaselineAsync(output).ConfigureAwait(false), newBaseline);
97+
98+
renderer.Render(diff, showDiff: !autoBaseline, context);
99+
100+
if (autoBaseline)
101+
{
102+
await writer.WriteAsync(output, newBaseline).ConfigureAwait(false);
103+
console.MarkupLineInterpolated(CultureInfo.InvariantCulture, $"[green]Baseline generated[/] @ [link=file://{Path.GetFullPath(output)}]{output}[/]");
104+
return ExitCodes.Success;
105+
}
106+
107+
return !diff.HasDifferences ? ExitCodes.Success : ExitCodes.BaselineMismatch;
92108
}
93109
catch (UnsupportedVersionException ex)
94110
{
95111
throw new SarifInvalidException($"SARIF file is invalid. {ex.Message}", ex);
96112
}
97-
98-
console.MarkupLine($"[green]Baseline generated[/] @ {output}");
99-
100-
return ExitCodes.Success;
101113
}
102114

103115
private static string ValidateOutputPath(string? path)
@@ -112,4 +124,9 @@ private static string ValidateOutputPath(string? path)
112124

113125
return path;
114126
}
127+
128+
private static async Task<string> ReadBaselineAsync(string path)
129+
{
130+
return File.Exists(path) ? await File.ReadAllTextAsync(path).ConfigureAwait(false) : string.Empty;
131+
}
115132
}

src/Tool/ExitCodes.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ internal static class ExitCodes
44
{
55
public static int Success { get; } = 0;
66
public static int UnknownError { get; } = -1;
7+
public static int BaselineMismatch { get; } = 1;
78
}

src/Tool/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
using SquiggleCop.Tool;
22

3-
await AppBuilder.Create().RunAsync().ConfigureAwait(false);
3+
await App.Create().RunAsync().ConfigureAwait(false);
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using DiffPlex.DiffBuilder.Model;
2+
3+
namespace SquiggleCop.Tool.Rendering;
4+
5+
internal static class DiffPieceExtensions
6+
{
7+
public static int SearchForEndOfDiffBlock(this IReadOnlyList<DiffPiece> lines, int index)
8+
{
9+
int i = index;
10+
while (i < lines.Count && lines[i].Type != ChangeType.Unchanged)
11+
{
12+
i++;
13+
}
14+
15+
return i;
16+
}
17+
}

src/Tool/Rendering/DiffRenderer.cs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using DiffPlex.DiffBuilder.Model;
2+
3+
using Spectre.Console;
4+
5+
using SquiggleCop.Common;
6+
7+
namespace SquiggleCop.Tool.Rendering;
8+
9+
internal class DiffRenderer
10+
{
11+
private readonly IAnsiConsole _console;
12+
13+
public DiffRenderer(IAnsiConsole console)
14+
{
15+
_console = console;
16+
}
17+
18+
public void Render(BaselineDiff diff, int context)
19+
{
20+
int i = 0;
21+
while (i < diff.Lines.Count)
22+
{
23+
if (diff.Lines[i].Type == ChangeType.Unchanged)
24+
{
25+
i++;
26+
continue;
27+
}
28+
29+
int begin = Math.Max(0, i - context + 1);
30+
int end = Math.Min(diff.Lines.Count - 1, diff.Lines.SearchForEndOfDiffBlock(i) + context - 1);
31+
32+
for (int j = begin; j < end; j++)
33+
{
34+
RenderLine(diff.Lines[j]);
35+
}
36+
37+
_console.WriteLine();
38+
i = end + 2;
39+
}
40+
}
41+
42+
private void RenderLine(DiffPiece line)
43+
{
44+
if (line.Type == ChangeType.Inserted)
45+
{
46+
_console.MarkupLine($"[green]+{line.Text.EscapeMarkup()}[/]");
47+
}
48+
else if (line.Type == ChangeType.Deleted)
49+
{
50+
_console.MarkupLine($"[red]-{line.Text.EscapeMarkup()}[/]");
51+
}
52+
else
53+
{
54+
_console.MarkupLine($"[grey]{line.Text.EscapeMarkup()}[/]");
55+
}
56+
}
57+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using Spectre.Console;
2+
3+
using SquiggleCop.Common;
4+
5+
namespace SquiggleCop.Tool.Rendering;
6+
7+
internal sealed class ReportRenderer
8+
{
9+
private readonly IAnsiConsole _console;
10+
private readonly DiffRenderer _diffRenderer;
11+
12+
public ReportRenderer(IAnsiConsole console, DiffRenderer diffRenderer)
13+
{
14+
_console = console;
15+
_diffRenderer = diffRenderer;
16+
}
17+
18+
public void Render(BaselineDiff diff, bool showDiff, int context)
19+
{
20+
if (diff.HasDifferences)
21+
{
22+
_console.Markup("[yellow]Baseline is different[/]");
23+
24+
if (!showDiff)
25+
{
26+
_console.MarkupLine("[dim] (auto baselining...)[/]");
27+
}
28+
else
29+
{
30+
_console.WriteLine();
31+
}
32+
}
33+
else
34+
{
35+
_console.MarkupLine("[green]Baseline is up-to-date[/]");
36+
}
37+
_console.WriteLine();
38+
39+
if (showDiff)
40+
{
41+
_diffRenderer.Render(diff, context);
42+
}
43+
}
44+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using Microsoft.Extensions.DependencyInjection;
2+
3+
namespace SquiggleCop.Tool.Rendering;
4+
5+
internal static class ServiceCollectionExtensions
6+
{
7+
public static IServiceCollection AddRenderers(this IServiceCollection services)
8+
{
9+
services.AddSingleton<DiffRenderer>();
10+
services.AddSingleton<ReportRenderer>();
11+
12+
return services;
13+
}
14+
}

0 commit comments

Comments
 (0)