Skip to content

Commit

Permalink
Added 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 ff76c4f commit 9521341
Show file tree
Hide file tree
Showing 8 changed files with 494 additions and 1 deletion.
100 changes: 100 additions & 0 deletions Gu.Roslyn.Asserts.Tests/RoslynAssertTests/Suppressed.Fail.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// ReSharper disable RedundantNameQualifier
namespace Gu.Roslyn.Asserts.Tests.RoslynAssertTests
{
using Gu.Roslyn.Asserts.Tests.TestHelpers.Suppressors;
using Microsoft.CodeAnalysis.Diagnostics;
using NUnit.Framework;

public static partial class VisibleMagicFieldIsAllowed
{
public static class Fail
{
private static readonly DiagnosticSuppressor Suppressor = new AllowUnassignedMagicMembers();

[Test]
public static void FailsINothingToSuppress()
{
const string code = @"
namespace N
{
public class C
{
public string? F;
}
}";
var exception = Assert.Throws<AssertException>(() => RoslynAssert.Suppressed(Suppressor, code));
CodeAssert.AreEqual(exception.Message, "Found no errors to suppress");
}

[Test]
public static void FailsIfDiagnosticIsNotSuppressableBySuppressor()
{
const string code = @"
namespace N
{
public class C
{
public string M()
{
string Magic;
return Magic;
}
}
}";
var exception = Assert.Throws<AssertException>(() => RoslynAssert.Suppressed(Suppressor, code));
Assert.That(exception.Message, Does.Contain("CS0165 Use of unassigned local variable 'Magic'"));
}

[Test]
public static void DoesNotSuppressNonMagicField()
{
const string code = @"
namespace N
{
public class C
{
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);
}
}
}
}
85 changes: 85 additions & 0 deletions Gu.Roslyn.Asserts.Tests/RoslynAssertTests/Suppressed.Success.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// ReSharper disable RedundantNameQualifier
namespace Gu.Roslyn.Asserts.Tests.RoslynAssertTests
{
using Gu.Roslyn.Asserts.Tests.TestHelpers.Suppressors;
using Microsoft.CodeAnalysis.Diagnostics;
using NUnit.Framework;

public static partial class VisibleMagicFieldIsAllowed
{
public static class Success
{
private static readonly DiagnosticSuppressor Suppressor = new AllowUnassignedMagicMembers();

[Test]
public static void DoesNotSuppressAllDiagnostics()
{
const string code = @"
namespace N
{
public class C
{
public string F;
}
}";
RoslynAssert.NotSuppressed(Suppressor, code);
}

[Test]
public static void DoesSuppressDiagnosticAboutMagicField()
{
const string code = @"
namespace N
{
public class C
{
public string Magic;
}
}";
RoslynAssert.Suppressed(Suppressor, code);
}

[Test]
public static void DoesSuppressDiagnosticAboutMagicFieldWithSuppressionDescriptor()
{
const string code = @"
namespace N
{
public class C
{
public string Magic;
}
}";
RoslynAssert.SuppressedBy(Suppressor, AllowUnassignedMagicMembers.FieldNameIsMagic, code);
}

[Test]
public static void DoesSuppressDiagnosticAboutMagicProperties()
{
const string code = @"
namespace N
{
public class C
{
public string Magic { get; set; }
}
}";
RoslynAssert.Suppressed(Suppressor, code);
}

[Test]
public static void DoesSuppressDiagnosticAboutMagicPropertiesWithSuppressionDescriptor()
{
const string code = @"
namespace N
{
public class C
{
public string Magic { get; set; }
}
}";
RoslynAssert.SuppressedBy(Suppressor, AllowUnassignedMagicMembers.PropertyNameIsMagic, code);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
namespace Gu.Roslyn.Asserts.Tests.TestHelpers.Suppressors
{
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;

[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class AllowUnassignedMagicMembers : DiagnosticSuppressor
{
public const string MagicFieldName = "Magic";

public static readonly SuppressionDescriptor FieldNameIsMagic = new(
id: nameof(AllowUnassignedMagicMembers),
suppressedDiagnosticId: "CS8618",
justification: "Field is called " + MagicFieldName);

public static readonly SuppressionDescriptor PropertyNameIsMagic = new(
id: nameof(AllowUnassignedMagicMembers),
suppressedDiagnosticId: "CS8618",
justification: "Property is called " + MagicFieldName);

public override ImmutableArray<SuppressionDescriptor> SupportedSuppressions { get; } =
ImmutableArray.Create(FieldNameIsMagic, PropertyNameIsMagic);

public override void ReportSuppressions(SuppressionAnalysisContext context)
{
foreach (var diagnostic in context.ReportedDiagnostics)
{
var sourceTree = diagnostic.Location.SourceTree;

if (sourceTree is null)
{
continue;
}

var node = sourceTree.GetRoot(context.CancellationToken)
.FindNode(diagnostic.Location.SourceSpan);

if (node.ToString().Contains(MagicFieldName))
{
if (node is PropertyDeclarationSyntax)
{
context.ReportSuppression(Suppression.Create(PropertyNameIsMagic, diagnostic));
}
else if (node is VariableDeclaratorSyntax &&
node.Parent is VariableDeclarationSyntax &&
node.Parent.Parent is FieldDeclarationSyntax)
{
context.ReportSuppression(Suppression.Create(FieldNameIsMagic, diagnostic));
}
}
}
}
}
}
30 changes: 30 additions & 0 deletions Gu.Roslyn.Asserts/Analyze.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
{
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -148,6 +149,35 @@ public static IReadOnlyList<Diagnostic> GetAllDiagnostics(Solution solution)
return results;
}

/// <summary>
/// Creates a solution, compiles it and returns all diagnostics.
/// </summary>
/// <param name="suppressor">The suppressor to run to suppress any error messages.</param>
/// <param name="solution">The solution.</param>
/// <returns>A list with diagnostics.</returns>
public static IReadOnlyList<Diagnostic> GetAllDiagnostics(DiagnosticSuppressor suppressor, Solution solution)
{
if (solution is null)
{
throw new ArgumentNullException(nameof(solution));
}

var results = new List<Diagnostic>();
foreach (var project in solution.Projects)
{
var compilation = project.GetCompilationAsync(CancellationToken.None).GetAwaiter().GetResult() ??
throw new InvalidOperationException("project.GetCompilationAsync() returned null");
var withAnalyzers = compilation.WithAnalyzers(
ImmutableArray.Create<DiagnosticAnalyzer>(suppressor),
project.AnalyzerOptions,
CancellationToken.None);

results.AddRange(withAnalyzers.GetAllDiagnosticsAsync(CancellationToken.None).GetAwaiter().GetResult());
}

return results;
}

/// <summary>
/// Creates a solution, compiles it and returns the diagnostics.
/// </summary>
Expand Down
17 changes: 17 additions & 0 deletions Gu.Roslyn.Asserts/CodeFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -576,5 +576,22 @@ internal static Solution CreateSolution(FileInfo code, DiagnosticAnalyzer analyz
settings.CompilationOptions.WithSpecific(analyzer.SupportedDiagnostics, descriptor),
settings.MetadataReferences);
}

/// <summary>
/// Create a <see cref="Solution"/> for <paramref name="code"/>.
/// </summary>
/// <param name="code">The code to create the solution from with.</param>
/// <param name="suppressor">The <see cref="DiagnosticSuppressor"/> to check <paramref name="code"/> with.</param>
/// <param name="settings">The <see cref="Settings"/>.</param>
/// <returns>A <see cref="Solution"/>.</returns>
internal static Solution CreateSolution(DiagnosticSuppressor suppressor, IEnumerable<string> code, Settings settings)
{
return CreateSolution(
code,
settings.ParseOptions,
settings.CompilationOptions.WithSuppressableAsError(suppressor.SupportedSuppressions)
.WithReportSuppressedDiagnostics(reportSuppressedDiagnostics: true),
settings.MetadataReferences);
}
}
}
38 changes: 38 additions & 0 deletions Gu.Roslyn.Asserts/CsharpCompilationOptionsExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,44 @@ public static CSharpCompilationOptions WithWarningOrError(this CSharpCompilation
public static CSharpCompilationOptions WithWarningOrError(this CSharpCompilationOptions options, params DiagnosticDescriptor[] descriptors)
=> WithWarningOrError(options, (IEnumerable<DiagnosticDescriptor>)descriptors);

/// <summary>
/// Configure all <paramref name="descriptors"/> to report an error for the suppressable diagnostics.
/// </summary>
/// <param name="options">The <see cref="CSharpCompilationOptions"/>.</param>
/// <param name="descriptors">The descriptors.</param>
/// <returns>A <see cref="CSharpCompilationOptions"/></returns>
/// <exception cref="System.ArgumentNullException"></exception>
public static CSharpCompilationOptions WithSuppressableAsError(this CSharpCompilationOptions options, IEnumerable<SuppressionDescriptor> descriptors)
{
if (options is null)
{
throw new System.ArgumentNullException(nameof(options));
}

if (descriptors is null)
{
throw new System.ArgumentNullException(nameof(descriptors));
}

var diagnosticOptions = options.SpecificDiagnosticOptions;
foreach (var descriptor in descriptors)
{
diagnosticOptions = diagnosticOptions.Add(descriptor.SuppressedDiagnosticId, ReportDiagnostic.Error);
}

return options.WithSpecificDiagnosticOptions(diagnosticOptions);
}

/// <summary>
/// Configure all <paramref name="descriptors"/> to report an error for the suppressable diagnostics.
/// </summary>
/// <param name="options">The <see cref="CSharpCompilationOptions"/>.</param>
/// <param name="descriptors">The descriptors.</param>
/// <returns>A <see cref="CSharpCompilationOptions"/></returns>
/// <exception cref="System.ArgumentNullException"></exception>
public static CSharpCompilationOptions WithSuppressableAsError(this CSharpCompilationOptions options, params SuppressionDescriptor[] descriptors)
=> WithSuppressableAsError(options, (IEnumerable<SuppressionDescriptor>)descriptors);

/// <summary>
/// Configure all <paramref name="ids"/> suppressed.
/// </summary>
Expand Down
7 changes: 6 additions & 1 deletion Gu.Roslyn.Asserts/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@

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.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, 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
Loading

0 comments on commit 9521341

Please sign in to comment.