Skip to content

Commit

Permalink
Better matching support for DiagnosticSuppressors
Browse files Browse the repository at this point in the history
  • Loading branch information
manfred-brands authored and JohanLarsson committed Apr 3, 2022
1 parent 9521341 commit 5dfd98f
Show file tree
Hide file tree
Showing 11 changed files with 534 additions and 218 deletions.
48 changes: 7 additions & 41 deletions Gu.Roslyn.Asserts.Tests/RoslynAssertTests/Suppressed.Fail.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// ReSharper disable RedundantNameQualifier
namespace Gu.Roslyn.Asserts.Tests.RoslynAssertTests
{
using System;
using Gu.Roslyn.Asserts.Tests.TestHelpers.Suppressors;
using Microsoft.CodeAnalysis.Diagnostics;
using NUnit.Framework;
Expand All @@ -12,7 +13,7 @@ public static class Fail
private static readonly DiagnosticSuppressor Suppressor = new AllowUnassignedMagicMembers();

[Test]
public static void FailsINothingToSuppress()
public static void FailsIfNothingToSuppress()
{
const string code = @"
namespace N
Expand All @@ -22,8 +23,9 @@ public class C
public string? F;
}
}";
var exception = Assert.Throws<AssertException>(() => RoslynAssert.Suppressed(Suppressor, code));
CodeAssert.AreEqual(exception.Message, "Found no errors to suppress");
var expected = "Expected code to have at least one error position indicated with '↓'";
var exception = Assert.Throws<InvalidOperationException>(() => RoslynAssert.Suppressed(Suppressor, code));
CodeAssert.AreEqual(expected, exception.Message);
}

[Test]
Expand All @@ -37,7 +39,7 @@ public class C
public string M()
{
string Magic;
return Magic;
return Magic;
}
}
}";
Expand All @@ -53,48 +55,12 @@ namespace N
{
public class C
{
public string F;
public string F;
}
}";
var exception = Assert.Throws<AssertException>(() => RoslynAssert.Suppressed(Suppressor, code));
Assert.That(exception.Message, Does.Contain(AllowUnassignedMagicMembers.FieldNameIsMagic.SuppressedDiagnosticId));
}

[Test]
public static void DoesFailIfSuppressedByWrongSuppressionDescriptor()
{
const string code = @"
namespace N
{
public class C
{
public string Magic;
}
}";
var expected = "Expected diagnostic to be suppressed by AllowUnassignedMagicMembers:Property is called Magic but was:\r\n" +
"AllowUnassignedMagicMembers:Field is called Magic\r\n";
var exception = Assert.Throws<AssertException>(() =>
RoslynAssert.SuppressedBy(Suppressor, AllowUnassignedMagicMembers.PropertyNameIsMagic, code));
CodeAssert.AreEqual(expected, exception.Message);
}

[Test]
public static void DoesSuppressDiagnosticAboutMagicPropertiesWithWrongSuppressionDescriptor()
{
const string code = @"
namespace N
{
public class C
{
public string Magic { get; set; }
}
}";
var expected = "Expected diagnostic to be suppressed by AllowUnassignedMagicMembers:Field is called Magic but was:\r\n" +
"AllowUnassignedMagicMembers:Property is called Magic\r\n";
var exception = Assert.Throws<AssertException>(() =>
RoslynAssert.SuppressedBy(Suppressor, AllowUnassignedMagicMembers.FieldNameIsMagic, code));
CodeAssert.AreEqual(expected, exception.Message);
}
}
}
}
16 changes: 9 additions & 7 deletions Gu.Roslyn.Asserts.Tests/RoslynAssertTests/Suppressed.Success.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ namespace N
{
public class C
{
public string F;
public string F;
}
}";
RoslynAssert.NotSuppressed(Suppressor, code);
Expand All @@ -33,7 +33,7 @@ namespace N
{
public class C
{
public string Magic;
public string Magic;
}
}";
RoslynAssert.Suppressed(Suppressor, code);
Expand All @@ -47,10 +47,11 @@ namespace N
{
public class C
{
public string Magic;
public string Magic;
}
}";
RoslynAssert.SuppressedBy(Suppressor, AllowUnassignedMagicMembers.FieldNameIsMagic, code);
var expectedDiagnostic = ExpectedDiagnostic.Create(AllowUnassignedMagicMembers.FieldNameIsMagic);
RoslynAssert.Suppressed(Suppressor, expectedDiagnostic, code);
}

[Test]
Expand All @@ -61,7 +62,7 @@ namespace N
{
public class C
{
public string Magic { get; set; }
public string Magic { get; set; }
}
}";
RoslynAssert.Suppressed(Suppressor, code);
Expand All @@ -75,10 +76,11 @@ namespace N
{
public class C
{
public string Magic { get; set; }
public string Magic { get; set; }
}
}";
RoslynAssert.SuppressedBy(Suppressor, AllowUnassignedMagicMembers.PropertyNameIsMagic, code);
var expectedDiagnostic = ExpectedDiagnostic.Create(AllowUnassignedMagicMembers.PropertyNameIsMagic);
RoslynAssert.Suppressed(Suppressor, expectedDiagnostic, code);
}
}
}
Expand Down
45 changes: 44 additions & 1 deletion Gu.Roslyn.Asserts/DiagnosticsAndSources.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public static DiagnosticsAndSources FromMarkup(DiagnosticAnalyzer analyzer, stri
/// <summary>
/// Get the expected diagnostics and cleaned sources.
/// </summary>
/// <param name="analyzer">The descriptor that is expected to produce diagnostics.</param>
/// <param name="analyzer">The analyzer that is expected to produce diagnostics.</param>
/// <param name="markup">The code with diagnostic positions indicated with ↓ (alt + 25).</param>
/// <returns>An instance of <see cref="DiagnosticsAndSources"/>.</returns>
public static DiagnosticsAndSources FromMarkup(DiagnosticAnalyzer analyzer, IReadOnlyList<string> markup)
Expand All @@ -94,6 +94,49 @@ public static DiagnosticsAndSources FromMarkup(DiagnosticAnalyzer analyzer, IRea
return FromMarkup(descriptor.Id, null, markup);
}

/// <summary>
/// Get the expected diagnostics and cleaned sources.
/// </summary>
/// <param name="suppressor">The descriptor that is expected to suppress diagnostics.</param>
/// <param name="markup">The code with diagnostic positions indicated with ↓ (alt + 25).</param>
/// <returns>An instance of <see cref="DiagnosticsAndSources"/>.</returns>
public static DiagnosticsAndSources FromMarkup(DiagnosticSuppressor suppressor, string markup)
{
if (suppressor is null)
{
throw new ArgumentNullException(nameof(suppressor));
}

if (markup is null)
{
throw new ArgumentNullException(nameof(markup));
}

return FromMarkup(suppressor, new[] { markup });
}

/// <summary>
/// Get the expected diagnostics and cleaned sources.
/// </summary>
/// <param name="suppressor">The suppressor that is expected to suppress diagnostics.</param>
/// <param name="markup">The code with diagnostic positions indicated with ↓ (alt + 25).</param>
/// <returns>An instance of <see cref="DiagnosticsAndSources"/>.</returns>
public static DiagnosticsAndSources FromMarkup(DiagnosticSuppressor suppressor, IReadOnlyList<string> markup)
{
if (suppressor is null)
{
throw new ArgumentNullException(nameof(suppressor));
}

if (markup is null)
{
throw new ArgumentNullException(nameof(markup));
}

RoslynAssert.VerifySingleSupportedSuppression(suppressor, out var descriptor);
return FromMarkup(descriptor.SuppressedDiagnosticId, null, markup);
}

/// <summary>
/// Get the expected diagnostics and cleaned sources.
/// </summary>
Expand Down
17 changes: 16 additions & 1 deletion Gu.Roslyn.Asserts/ExpectedDiagnostic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public ExpectedDiagnostic(string id, string? message, FileLinePositionSpan span)
/// <summary>
/// Create a new instance of <see cref="ExpectedDiagnostic"/> and use the id from <paramref name="descriptor"/>.
/// </summary>
/// <param name="descriptor">The expected diagnostic id.</param>
/// <param name="descriptor">The expected diagnostic descriptor.</param>
/// <returns>A new instance of <see cref="ExpectedDiagnostic"/>.</returns>
public static ExpectedDiagnostic Create(DiagnosticDescriptor descriptor)
{
Expand All @@ -95,6 +95,21 @@ public static ExpectedDiagnostic Create(DiagnosticDescriptor descriptor)
return new ExpectedDiagnostic(descriptor.Id, null, NoPosition);
}

/// <summary>
/// Create a new instance of <see cref="ExpectedDiagnostic"/> and use the suppressed id from <paramref name="descriptor"/>.
/// </summary>
/// <param name="descriptor">The expected suppression descriptor.</param>
/// <returns>A new instance of <see cref="ExpectedDiagnostic"/>.</returns>
public static ExpectedDiagnostic Create(SuppressionDescriptor descriptor)
{
if (descriptor is null)
{
throw new ArgumentNullException(nameof(descriptor));
}

return new ExpectedDiagnostic(descriptor.SuppressedDiagnosticId, null, NoPosition);
}

/// <summary>
/// Create a new instance of <see cref="ExpectedDiagnostic"/>.
/// </summary>
Expand Down
4 changes: 3 additions & 1 deletion Gu.Roslyn.Asserts/Gu.Roslyn.Asserts.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
</PropertyGroup>

<PropertyGroup>
<Version>4.0.4</Version>
<Version>4.1.0-beta01</Version>
<Authors>Johan Larsson, milleniumbug</Authors>
<Copyright>Johan Larsson 2017</Copyright>
<PackageProjectUrl>https://github.com/GuOrg/Gu.Roslyn.Asserts</PackageProjectUrl>
Expand All @@ -27,6 +27,8 @@
<PackageTags>Roslyn Diagnostic Analyzer Test</PackageTags>
<NeutralLanguage>en</NeutralLanguage>
<PackageReleaseNotes>
4.1.0
FEATURE: add support for DiagnostisSuppressor with RoslynAssert.Suppressed
4.0.4
BUGFIX: stop infinite loop in fix all when fix updates the code without fixing the issue.
4.0.3
Expand Down
25 changes: 19 additions & 6 deletions Gu.Roslyn.Asserts/ProjectDiagnostics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,25 @@ public static async Task<ProjectDiagnostics> CreateAsync(DiagnosticAnalyzer anal
ImmutableArray.Create(analyzer),
project.AnalyzerOptions,
CancellationToken.None);
var analyzerDiagnostics = await withAnalyzers.GetAnalyzerDiagnosticsAsync(CancellationToken.None).ConfigureAwait(false);
return new ProjectDiagnostics(
analyzer,
project,
compilation.GetDiagnostics(CancellationToken.None),
analyzerDiagnostics);

if (analyzer is DiagnosticSuppressor)
{
var compilerDiagnostics = await withAnalyzers.GetAllDiagnosticsAsync(CancellationToken.None).ConfigureAwait(false);
return new ProjectDiagnostics(
analyzer,
project,
compilerDiagnostics,
ImmutableArray<Diagnostic>.Empty);
}
else
{
var analyzerDiagnostics = await withAnalyzers.GetAnalyzerDiagnosticsAsync(CancellationToken.None).ConfigureAwait(false);
return new ProjectDiagnostics(
analyzer,
project,
compilation.GetDiagnostics(CancellationToken.None),
analyzerDiagnostics);
}
}
}

Expand Down
11 changes: 10 additions & 1 deletion Gu.Roslyn.Asserts/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
static Gu.Roslyn.Asserts.Analyze.GetAllDiagnostics(Microsoft.CodeAnalysis.Diagnostics.DiagnosticSuppressor! suppressor, Microsoft.CodeAnalysis.Solution! solution) -> System.Collections.Generic.IReadOnlyList<Microsoft.CodeAnalysis.Diagnostic!>!
static Gu.Roslyn.Asserts.CsharpCompilationOptionsExtensions.WithSuppressableAsError(this Microsoft.CodeAnalysis.CSharp.CSharpCompilationOptions! options, params Microsoft.CodeAnalysis.SuppressionDescriptor![]! descriptors) -> Microsoft.CodeAnalysis.CSharp.CSharpCompilationOptions!
static Gu.Roslyn.Asserts.CsharpCompilationOptionsExtensions.WithSuppressableAsError(this Microsoft.CodeAnalysis.CSharp.CSharpCompilationOptions! options, System.Collections.Generic.IEnumerable<Microsoft.CodeAnalysis.SuppressionDescriptor!>! descriptors) -> Microsoft.CodeAnalysis.CSharp.CSharpCompilationOptions!
static Gu.Roslyn.Asserts.DiagnosticsAndSources.FromMarkup(Microsoft.CodeAnalysis.Diagnostics.DiagnosticSuppressor! suppressor, string! markup) -> Gu.Roslyn.Asserts.DiagnosticsAndSources!
static Gu.Roslyn.Asserts.DiagnosticsAndSources.FromMarkup(Microsoft.CodeAnalysis.Diagnostics.DiagnosticSuppressor! suppressor, System.Collections.Generic.IReadOnlyList<string!>! markup) -> Gu.Roslyn.Asserts.DiagnosticsAndSources!
static Gu.Roslyn.Asserts.ExpectedDiagnostic.Create(Microsoft.CodeAnalysis.SuppressionDescriptor! descriptor) -> Gu.Roslyn.Asserts.ExpectedDiagnostic!
static Gu.Roslyn.Asserts.RoslynAssert.NotSuppressed(Microsoft.CodeAnalysis.Diagnostics.DiagnosticSuppressor! suppressor, string! code, Gu.Roslyn.Asserts.Settings? settings = null) -> void
static Gu.Roslyn.Asserts.RoslynAssert.Suppressed(Microsoft.CodeAnalysis.Diagnostics.DiagnosticSuppressor! suppressor, Gu.Roslyn.Asserts.DiagnosticsAndSources! diagnosticsAndSources, Gu.Roslyn.Asserts.Settings? settings = null) -> void
static Gu.Roslyn.Asserts.RoslynAssert.Suppressed(Microsoft.CodeAnalysis.Diagnostics.DiagnosticSuppressor! suppressor, Gu.Roslyn.Asserts.ExpectedDiagnostic! expectedDiagnostic, params string![]! code) -> void
static Gu.Roslyn.Asserts.RoslynAssert.Suppressed(Microsoft.CodeAnalysis.Diagnostics.DiagnosticSuppressor! suppressor, Gu.Roslyn.Asserts.ExpectedDiagnostic! expectedDiagnostic, string! code, Gu.Roslyn.Asserts.Settings? settings = null) -> void
static Gu.Roslyn.Asserts.RoslynAssert.Suppressed(Microsoft.CodeAnalysis.Diagnostics.DiagnosticSuppressor! suppressor, Gu.Roslyn.Asserts.ExpectedDiagnostic! expectedDiagnostic, System.Collections.Generic.IReadOnlyList<string!>! code, Gu.Roslyn.Asserts.Settings? settings = null) -> void
static Gu.Roslyn.Asserts.RoslynAssert.Suppressed(Microsoft.CodeAnalysis.Diagnostics.DiagnosticSuppressor! suppressor, params string![]! code) -> void
static Gu.Roslyn.Asserts.RoslynAssert.Suppressed(Microsoft.CodeAnalysis.Diagnostics.DiagnosticSuppressor! suppressor, string! code, Gu.Roslyn.Asserts.Settings? settings = null) -> void
static Gu.Roslyn.Asserts.RoslynAssert.SuppressedBy(Microsoft.CodeAnalysis.Diagnostics.DiagnosticSuppressor! suppressor, Microsoft.CodeAnalysis.SuppressionDescriptor! suppressionDescriptor, string! code, Gu.Roslyn.Asserts.Settings? settings = null) -> void
static Gu.Roslyn.Asserts.RoslynAssert.Suppressed(Microsoft.CodeAnalysis.Diagnostics.DiagnosticSuppressor! suppressor, System.Collections.Generic.IReadOnlyList<Gu.Roslyn.Asserts.ExpectedDiagnostic!>! expectedDiagnostics, params string![]! code) -> void
static Gu.Roslyn.Asserts.RoslynAssert.Suppressed(Microsoft.CodeAnalysis.Diagnostics.DiagnosticSuppressor! suppressor, System.Collections.Generic.IReadOnlyList<string!>! code, Gu.Roslyn.Asserts.Settings? settings = null) -> void
84 changes: 5 additions & 79 deletions Gu.Roslyn.Asserts/RoslynAssert.Diagnostics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -229,86 +229,12 @@ public static void Diagnostics(
NoDiagnostics(diagnostics.SelectMany(x => x.FilterCompilerDiagnostics(settings.AllowedCompilerDiagnostics)));
}

private static void VerifyDiagnostics(DiagnosticsAndSources diagnosticsAndSources, IReadOnlyList<ProjectDiagnostics> diagnostics, string? expectedMessage = null)
private static void VerifyDiagnostics(DiagnosticsAndSources diagnosticsAndSources, IReadOnlyList<ProjectDiagnostics> diagnostics)
{
if (diagnosticsAndSources.ExpectedDiagnostics.Count == 0)
{
throw new AssertException("Expected code to have at least one error position indicated with '↓'");
}

if (AnyMatch(diagnosticsAndSources.ExpectedDiagnostics, diagnostics.SelectMany(x => x.AnalyzerDiagnostics)))
{
if (expectedMessage != null)
{
foreach (var actual in diagnostics.SelectMany(x => x.AnalyzerDiagnostics))
{
var actualMessage = actual.GetMessage(CultureInfo.InvariantCulture);
TextAssert.AreEqual(expectedMessage, actualMessage, $"Expected and actual diagnostic message for the diagnostic {actual} does not match");
}
}

return;
}

var error = StringBuilderPool.Borrow();
if (diagnostics.SelectMany(x => x.AnalyzerDiagnostics).TrySingle(out var single) &&
diagnosticsAndSources.ExpectedDiagnostics.Count == 1 &&
diagnosticsAndSources.ExpectedDiagnostics[0].Id == single.Id)
{
if (diagnosticsAndSources.ExpectedDiagnostics[0].PositionMatches(single) &&
!diagnosticsAndSources.ExpectedDiagnostics[0].MessageMatches(single))
{
CodeAssert.AreEqual(diagnosticsAndSources.ExpectedDiagnostics[0].Message!, single.GetMessage(CultureInfo.InvariantCulture), "Expected and actual messages do not match.");
}
}

var allDiagnostics = diagnostics.SelectMany(x => x.All()).ToArray();
error.AppendLine("Expected and actual diagnostics do not match.")
.AppendLine("Expected:");
foreach (var expected in diagnosticsAndSources.ExpectedDiagnostics.OrderBy(x => x.Span.StartLinePosition))
{
error.AppendLine(expected.ToString(diagnosticsAndSources.Code, " "));
}

if (allDiagnostics.Length == 0)
{
error.AppendLine("Actual: <no diagnostics>");
}
else
{
error.AppendLine("Actual:");
foreach (var diagnostic in allDiagnostics.OrderBy(x => x.Location.SourceSpan.Start))
{
error.AppendLine(diagnostic.ToErrorString(" "));
}
}

throw new AssertException(error.Return());

static bool AnyMatch(IReadOnlyList<ExpectedDiagnostic> expectedDiagnostics, IEnumerable<Diagnostic> diagnostics)
{
foreach (var diagnostic in diagnostics)
{
if (expectedDiagnostics.Any(x => x.Matches(diagnostic)))
{
continue;
}

return false;
}

foreach (var expected in expectedDiagnostics)
{
if (diagnostics.Any(x => expected.Matches(x)))
{
continue;
}

return false;
}

return true;
}
VerifyDiagnostics(
diagnosticsAndSources,
diagnostics.SelectMany(x => x.AnalyzerDiagnostics).ToList(),
diagnostics.SelectMany(x => x.All()).ToList());
}
}
}
Loading

0 comments on commit 5dfd98f

Please sign in to comment.