Support testing suppressors for compiler diagnostics

Fixes #1090
This commit is contained in:
Sam Harwell 2023-05-01 09:20:12 -05:00
Родитель 64fdcf16db
Коммит 276abf1cc5
5 изменённых файлов: 227 добавлений и 6 удалений

Просмотреть файл

@ -1005,10 +1005,15 @@ namespace Microsoft.CodeAnalysis.Testing
}
}
private static bool IsCompilerDiagnosticId(string id)
{
return id.StartsWith("CS", StringComparison.Ordinal)
|| id.StartsWith("BC", StringComparison.Ordinal);
}
private static bool IsSubjectToExclusion(DiagnosticResult result, ImmutableArray<DiagnosticAnalyzer> analyzers, (string filename, SourceText content)[] sources)
{
if (result.Id.StartsWith("CS", StringComparison.Ordinal)
|| result.Id.StartsWith("BC", StringComparison.Ordinal))
if (IsCompilerDiagnosticId(result.Id))
{
// This is a compiler diagnostic
return false;
@ -1125,12 +1130,13 @@ namespace Microsoft.CodeAnalysis.Testing
foreach (var project in solution.Projects)
{
var (compilation, generatorDiagnostics) = await GetProjectCompilationAsync(project, verifier, cancellationToken).ConfigureAwait(false);
var compilationWithAnalyzers = CreateCompilationWithAnalyzers(compilation, analyzers, GetAnalyzerOptions(project), cancellationToken);
var analyzerOptions = GetAnalyzerOptions(project);
var compilationWithAnalyzers = CreateCompilationWithAnalyzers(compilation, analyzers, analyzerOptions, cancellationToken);
ImmutableArray<Diagnostic> allDiagnostics;
if (AnalysisResultWrapper.WrappedType is not null)
{
var compilationDiagnostics = compilation.GetDiagnostics(cancellationToken);
var compilerReportedDiagnostics = await GetCompilerDiagnosticsAsync(this, compilation, analyzers, analyzerOptions, cancellationToken).ConfigureAwait(false);
var analysisResult = await compilationWithAnalyzers.GetAnalysisResultAsync(cancellationToken).ConfigureAwait(false);
foreach (var (analyzer, analyzerNonLocalDiagnostics) in analysisResult.CompilationDiagnostics)
{
@ -1140,7 +1146,7 @@ namespace Microsoft.CodeAnalysis.Testing
}
}
allDiagnostics = compilationDiagnostics.AddRange(analysisResult.GetAllDiagnostics());
allDiagnostics = compilerReportedDiagnostics.AddRange(analysisResult.GetAllDiagnostics());
}
else
{
@ -1154,6 +1160,39 @@ namespace Microsoft.CodeAnalysis.Testing
diagnostics.AddRange(additionalDiagnostics);
var results = SortDistinctDiagnostics(diagnostics);
return results;
static async Task<ImmutableArray<Diagnostic>> GetCompilerDiagnosticsAsync(AnalyzerTest<TVerifier> self, Compilation compilation, ImmutableArray<DiagnosticAnalyzer> analyzers, AnalyzerOptions analyzerOptions, CancellationToken cancellationToken)
{
if (!analyzers.Any(static analyzer => IsCompilerDiagnosticSuppressor(analyzer)))
{
return compilation.GetDiagnostics(cancellationToken);
}
// Need to get the compiler diagnostics through a new CompilationWithAnalyzers instance to ensure
// suppressions are applied.
var compilerSuppressors = analyzers.Where(static analyzer => IsCompilerDiagnosticSuppressor(analyzer)).ToImmutableArray();
var compilationWithAnalyzers = self.CreateCompilationWithAnalyzers(compilation, compilerSuppressors, analyzerOptions, cancellationToken);
return await compilationWithAnalyzers.GetAllDiagnosticsAsync().ConfigureAwait(false);
}
static bool IsCompilerDiagnosticSuppressor(DiagnosticAnalyzer analyzer)
{
if (!DiagnosticSuppressorWrapper.IsInstance(analyzer))
{
return false;
}
var wrapper = DiagnosticSuppressorWrapper.FromInstance(analyzer);
foreach (var descriptor in wrapper.SupportedSuppressions)
{
if (IsCompilerDiagnosticId(descriptor.SuppressedDiagnosticId))
{
return true;
}
}
return false;
}
}
private protected static bool IsNonLocalDiagnostic(Diagnostic diagnostic)

Просмотреть файл

@ -0,0 +1,71 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections;
using System.Collections.Immutable;
using System.Linq;
using System.Reflection;
using Microsoft.CodeAnalysis.Diagnostics;
namespace Microsoft.CodeAnalysis.Testing.Lightup
{
internal readonly struct DiagnosticSuppressorWrapper
{
internal const string WrappedTypeName = "Microsoft.CodeAnalysis.Diagnostics.DiagnosticSuppressor";
internal static readonly Type? WrappedType = typeof(Diagnostic).GetTypeInfo().Assembly.GetType(WrappedTypeName);
private static readonly Func<DiagnosticAnalyzer, IEnumerable> s_supportedSuppressions;
private readonly DiagnosticAnalyzer _instance;
static DiagnosticSuppressorWrapper()
{
s_supportedSuppressions = LightupHelpers.CreatePropertyAccessor<DiagnosticAnalyzer, IEnumerable>(WrappedType, nameof(SupportedSuppressions), Enumerable.Empty<object>());
}
private DiagnosticSuppressorWrapper(DiagnosticAnalyzer instance)
{
_instance = instance;
}
public ImmutableArray<SuppressionDescriptorWrapper> SupportedSuppressions
{
get
{
var suppressions = s_supportedSuppressions(_instance).Cast<object>();
return ImmutableArray.CreateRange(suppressions.Select(SuppressionDescriptorWrapper.FromInstance));
}
}
public static DiagnosticSuppressorWrapper FromInstance(DiagnosticAnalyzer instance)
{
if (instance == null)
{
return default;
}
if (!IsInstance(instance))
{
throw new InvalidCastException($"Cannot cast '{instance.GetType().FullName}' to '{WrappedTypeName}'");
}
return new DiagnosticSuppressorWrapper(instance);
}
public static bool IsInstance(object value)
{
if (value is null)
{
return false;
}
if (WrappedType is null)
{
return false;
}
return WrappedType.IsAssignableFrom(value.GetType());
}
}
}

Просмотреть файл

@ -61,7 +61,7 @@ namespace Microsoft.CodeAnalysis.Testing.Lightup
Expression<Func<T, TResult>> expression =
Expression.Lambda<Func<T, TResult>>(
Expression.Call(instance, property.GetMethod),
Expression.Convert(Expression.Call(instance, property.GetMethod), typeof(TResult)),
parameter);
return expression.Compile();
}

Просмотреть файл

@ -0,0 +1,68 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Reflection;
namespace Microsoft.CodeAnalysis.Testing.Lightup
{
internal readonly struct SuppressionDescriptorWrapper
{
internal const string WrappedTypeName = "Microsoft.CodeAnalysis.SuppressionDescriptor";
internal static readonly Type? WrappedType = typeof(Diagnostic).GetTypeInfo().Assembly.GetType(WrappedTypeName);
private static readonly Func<object, string> s_id;
private static readonly Func<object, string> s_suppressedDiagnosticId;
private static readonly Func<object, LocalizableString> s_justification;
private readonly object _instance;
static SuppressionDescriptorWrapper()
{
s_id = LightupHelpers.CreatePropertyAccessor<object, string>(WrappedType, nameof(Id), string.Empty);
s_suppressedDiagnosticId = LightupHelpers.CreatePropertyAccessor<object, string>(WrappedType, nameof(SuppressedDiagnosticId), string.Empty);
s_justification = LightupHelpers.CreatePropertyAccessor<object, LocalizableString>(WrappedType, nameof(Justification), string.Empty);
}
private SuppressionDescriptorWrapper(object instance)
{
_instance = instance;
}
public string Id => s_id(_instance);
public string SuppressedDiagnosticId => s_suppressedDiagnosticId(_instance);
public LocalizableString Justification => s_justification(_instance);
public static SuppressionDescriptorWrapper FromInstance(object instance)
{
if (instance == null)
{
return default;
}
if (!IsInstance(instance))
{
throw new InvalidCastException($"Cannot cast '{instance.GetType().FullName}' to '{WrappedTypeName}'");
}
return new SuppressionDescriptorWrapper(instance);
}
public static bool IsInstance(object value)
{
if (value is null)
{
return false;
}
if (WrappedType is null)
{
return false;
}
return WrappedType.IsAssignableFrom(value.GetType());
}
}
}

Просмотреть файл

@ -12,6 +12,9 @@ using Microsoft.CodeAnalysis.Testing.TestAnalyzers;
using Xunit;
using CSharpAnalyzerTest = Microsoft.CodeAnalysis.Testing.TestAnalyzers.CSharpAnalyzerTest<
Microsoft.CodeAnalysis.Testing.TestAnalyzers.HighlightBracesAnalyzer>;
using CSharpCompilerTest = Microsoft.CodeAnalysis.Testing.TestAnalyzers.CSharpSuppressorTest<
Microsoft.CodeAnalysis.Testing.EmptyDiagnosticAnalyzer,
Microsoft.CodeAnalysis.Testing.DiagnosticSuppressorTests.NonNullableFieldSuppressor>;
using CSharpTest = Microsoft.CodeAnalysis.Testing.TestAnalyzers.CSharpSuppressorTest<
Microsoft.CodeAnalysis.Testing.TestAnalyzers.HighlightBracesAnalyzer,
Microsoft.CodeAnalysis.Testing.DiagnosticSuppressorTests.HighlightBracesSuppressor>;
@ -54,6 +57,29 @@ namespace Microsoft.CodeAnalysis.Testing
}.RunAsync();
}
[Fact]
[WorkItem(1090, "https://github.com/dotnet/roslyn-sdk/issues/1090")]
public async Task TestSuppressionOfCompilerDiagnostic()
{
await new CSharpCompilerTest
{
CompilerDiagnostics = CompilerDiagnostics.Warnings,
TestState =
{
Sources =
{
@"#nullable enable
class Sample { string {|#0:_value|}; }",
},
ExpectedDiagnostics =
{
DiagnosticResult.CompilerWarning("CS8618").WithLocation(0).WithIsSuppressed(true),
DiagnosticResult.CompilerWarning("CS0169").WithLocation(0),
},
},
}.RunAsync();
}
[Fact]
public async Task TestUnexpectedSuppressionPresent()
{
@ -134,6 +160,23 @@ namespace Microsoft.CodeAnalysis.Testing
}
}
}
[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal class NonNullableFieldSuppressor : DiagnosticSuppressor
{
internal static readonly SuppressionDescriptor Descriptor =
new SuppressionDescriptor("FieldIsAssigned", "CS8618", "justification");
public override ImmutableArray<SuppressionDescriptor> SupportedSuppressions => ImmutableArray.Create(Descriptor);
public override void ReportSuppressions(SuppressionAnalysisContext context)
{
foreach (var diagnostic in context.ReportedDiagnostics)
{
context.ReportSuppression(Suppression.Create(Descriptor, diagnostic));
}
}
}
}
}