diff --git a/src/Tests/SourceGeneratorTest/Helpers/CSharpSuppressorTest{TSuppressor}.cs b/src/Tests/SourceGeneratorTest/Helpers/CSharpSuppressorTest{TSuppressor}.cs new file mode 100644 index 000000000..844faea85 --- /dev/null +++ b/src/Tests/SourceGeneratorTest/Helpers/CSharpSuppressorTest{TSuppressor}.cs @@ -0,0 +1,150 @@ +using System.Collections.Generic; +using System; +using System.Collections.Immutable; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; +using WinRT; +using Microsoft.CodeAnalysis.CSharp; + +namespace SourceGeneratorTest.Helpers; + +/// +/// A custom for testing diagnostic suppressors. +/// +/// The type of the suppressor to test. +// Adapted from https://github.com/ImmediatePlatform/Immediate.Validations +public sealed class CSharpSuppressorTest : CSharpAnalyzerTest + where TSuppressor : DiagnosticSuppressor, new() +{ + /// + /// The list of analyzers to run on the input code. + /// + private readonly List _analyzers = []; + + /// + /// Whether to enable unsafe blocks. + /// + private readonly bool _allowUnsafeBlocks; + + /// + /// The C# language version to use to parse code. + /// + private readonly LanguageVersion _languageVersion; + + /// + /// Creates a new instance with the specified parameters. + /// + /// The source code to analyze. + /// Whether to enable unsafe blocks. + /// The language version to use to run the test. + public CSharpSuppressorTest( + string source, + bool allowUnsafeBlocks = true, + LanguageVersion languageVersion = LanguageVersion.CSharp12) + { + _allowUnsafeBlocks = allowUnsafeBlocks; + _languageVersion = languageVersion; + + TestCode = source; + TestState.ReferenceAssemblies = ReferenceAssemblies.Net.Net80; + TestState.AdditionalReferences.Add(MetadataReference.CreateFromFile(typeof(ComWrappersSupport).Assembly.Location)); + } + + /// + protected override IEnumerable GetDiagnosticAnalyzers() + { + return base.GetDiagnosticAnalyzers().Concat(_analyzers); + } + + /// + protected override CompilationOptions CreateCompilationOptions() + { + return new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, allowUnsafe: _allowUnsafeBlocks); + } + + /// + protected override ParseOptions CreateParseOptions() + { + return new CSharpParseOptions(_languageVersion, DocumentationMode.Diagnose); + } + + /// + /// Adds a new analyzer to the set of analyzers to run on the input code. + /// + /// The type of analyzer to activate. + /// The current test instance. + public CSharpSuppressorTest WithAnalyzer( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] string assemblyQualifiedTypeName) + { + _analyzers.Add((DiagnosticAnalyzer)Activator.CreateInstance(Type.GetType(assemblyQualifiedTypeName))); + + return this; + } + + /// + /// Specifies the diagnostics to enable. + /// + /// The set of diagnostics. + /// The current test instance. + public CSharpSuppressorTest WithSpecificDiagnostics(params DiagnosticResult[] diagnostics) + { + ImmutableDictionary diagnosticOptions = diagnostics.ToImmutableDictionary( + descriptor => descriptor.Id, + descriptor => descriptor.Severity.ToReportDiagnostic()); + + // Transform to enable the diagnostics + Solution EnableDiagnostics(Solution solution, ProjectId projectId) + { + CompilationOptions options = + solution.GetProject(projectId)?.CompilationOptions + ?? throw new InvalidOperationException("Compilation options missing."); + + return solution.WithProjectCompilationOptions( + projectId, + options.WithSpecificDiagnosticOptions(diagnosticOptions)); + } + + SolutionTransforms.Clear(); + SolutionTransforms.Add(EnableDiagnostics); + + return this; + } + + /// + /// Specifies the diagnostics that should be produced. + /// + /// The set of diagnostics. + /// The current test instance. + public CSharpSuppressorTest WithExpectedDiagnosticsResults(params DiagnosticResult[] diagnostics) + { + ExpectedDiagnostics.AddRange(diagnostics); + + return this; + } +} + +/// +/// Extensions for . +/// +file static class DiagnosticSeverityExtensions +{ + /// + /// Converts a value into a one. + /// + public static ReportDiagnostic ToReportDiagnostic(this DiagnosticSeverity severity) + { + return severity switch + { + DiagnosticSeverity.Hidden => ReportDiagnostic.Hidden, + DiagnosticSeverity.Info => ReportDiagnostic.Info, + DiagnosticSeverity.Warning => ReportDiagnostic.Warn, + DiagnosticSeverity.Error => ReportDiagnostic.Error, + _ => throw new InvalidEnumArgumentException(nameof(severity), (int)severity, typeof(DiagnosticSeverity)), + }; + } +} \ No newline at end of file