diff --git a/src/Analyzers/AnalyzerFinderHelpers.cs b/src/Analyzers/AnalyzerFinderHelpers.cs index 21ab8eaa09..cb069816c0 100644 --- a/src/Analyzers/AnalyzerFinderHelpers.cs +++ b/src/Analyzers/AnalyzerFinderHelpers.cs @@ -6,17 +6,15 @@ using System.Reflection; using System.Threading; using System.Threading.Tasks; + using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.Extensions.Logging; namespace Microsoft.CodeAnalysis.Tools.Analyzers { internal static class AnalyzerFinderHelpers { - public static ImmutableArray<(DiagnosticAnalyzer Analyzer, CodeFixProvider? Fixer)> LoadAnalyzersAndFixers( - IEnumerable assemblies, - ILogger logger) + public static ImmutableArray<(DiagnosticAnalyzer Analyzer, CodeFixProvider? Fixer)> LoadAnalyzersAndFixers(IEnumerable assemblies) { var types = assemblies .SelectMany(assembly => assembly.GetTypes() diff --git a/src/Analyzers/AnalyzerReferenceAnalyzerFinder.cs b/src/Analyzers/AnalyzerReferenceAnalyzerFinder.cs index 7e4e053f0a..3d376abb1e 100644 --- a/src/Analyzers/AnalyzerReferenceAnalyzerFinder.cs +++ b/src/Analyzers/AnalyzerReferenceAnalyzerFinder.cs @@ -30,7 +30,7 @@ internal class AnalyzerReferenceAnalyzerFinder : IAnalyzerFinder .Distinct() .Select(path => Assembly.LoadFrom(path)); - return AnalyzerFinderHelpers.LoadAnalyzersAndFixers(assemblies, logger); + return AnalyzerFinderHelpers.LoadAnalyzersAndFixers(assemblies); } public Task>> FilterBySeverityAsync( diff --git a/src/Analyzers/RoslynCodeStyleAnalyzerFinder.cs b/src/Analyzers/RoslynCodeStyleAnalyzerFinder.cs index fea651ba5f..40463d9022 100644 --- a/src/Analyzers/RoslynCodeStyleAnalyzerFinder.cs +++ b/src/Analyzers/RoslynCodeStyleAnalyzerFinder.cs @@ -36,7 +36,7 @@ internal class RoslynCodeStyleAnalyzerFinder : IAnalyzerFinder _featuresVisualBasicPath }.Select(path => Assembly.LoadFrom(path)); - return AnalyzerFinderHelpers.LoadAnalyzersAndFixers(assemblies, logger); + return AnalyzerFinderHelpers.LoadAnalyzersAndFixers(assemblies); } public Task>> FilterBySeverityAsync( diff --git a/tests/Analyzers/AnalyzerAssemblyGenerator.cs b/tests/Analyzers/AnalyzerAssemblyGenerator.cs new file mode 100644 index 0000000000..d0ae19429c --- /dev/null +++ b/tests/Analyzers/AnalyzerAssemblyGenerator.cs @@ -0,0 +1,112 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; + +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; + +namespace Microsoft.CodeAnalysis.Tools.Tests.Analyzers +{ + public static class AnalyzerAssemblyGenerator + { + public static SyntaxTree GenerateCodeFix(string typeName, string diagnosticId) + { + var codefix = $@" +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; +[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof({typeName})), Shared] +public class {typeName} : CodeFixProvider +{{ + public const string DiagnosticId = ""{diagnosticId}""; + + public sealed override ImmutableArray FixableDiagnosticIds + => ImmutableArray.Create(DiagnosticId); + + public sealed override FixAllProvider GetFixAllProvider() + {{ + return WellKnownFixAllProviders.BatchFixer; + }} + + public sealed override Task RegisterCodeFixesAsync(CodeFixContext context) + {{ + throw new NotImplementedException(); + }} +}}"; + return CSharpSyntaxTree.ParseText(codefix); + } + + public static SyntaxTree GenerateAnalyzerCode(string typeName, string diagnosticId) + { + var analyzer = $@" +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public class {typeName} : DiagnosticAnalyzer +{{ + public const string DiagnosticId = ""{diagnosticId}""; + internal static readonly LocalizableString Title = ""{typeName} Title""; + internal static readonly LocalizableString MessageFormat = ""{typeName} '{{0}}'""; + internal const string Category = ""{typeName} Category""; + internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, true); + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + public override void Initialize(AnalysisContext context) + {{ + }} +}}"; + return CSharpSyntaxTree.ParseText(analyzer); + } + + public static async Task GenerateAssemblyAsync(params SyntaxTree[] trees) + { + var assemblyName = Guid.NewGuid().ToString(); + var references = new List() + { + MetadataReference.CreateFromFile(typeof(ImmutableArray).Assembly.Location), + MetadataReference.CreateFromFile(typeof(SharedAttribute).Assembly.Location), + MetadataReference.CreateFromFile(typeof(CSharpCompilation).Assembly.Location), + MetadataReference.CreateFromFile(typeof(DiagnosticAnalyzer).Assembly.Location), + MetadataReference.CreateFromFile(typeof(CodeFixProvider).Assembly.Location), + }; + + var netstandardMetaDataReferences = await ReferenceAssemblies.NetStandard.NetStandard20.ResolveAsync(LanguageNames.CSharp, CancellationToken.None); + references.AddRange(netstandardMetaDataReferences); + var compilation = CSharpCompilation.Create(assemblyName, trees, references, + options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); + using (var ms = new MemoryStream()) + { + var result = compilation.Emit(ms); + if (!result.Success) + { + var failures = result.Diagnostics.Where(diagnostic => + diagnostic.IsWarningAsError || + diagnostic.Severity == DiagnosticSeverity.Error) + .Select(diagnostic => $"{diagnostic.Id}: {diagnostic.GetMessage()}"); + + throw new Exception(string.Join(Environment.NewLine, failures)); + } + else + { + ms.Seek(0, SeekOrigin.Begin); + var assembly = Assembly.Load(ms.ToArray()); + return assembly; + } + } + } + } +} diff --git a/tests/Analyzers/FilterDiagnosticsTests.cs b/tests/Analyzers/FilterDiagnosticsTests.cs new file mode 100644 index 0000000000..65c4ae573e --- /dev/null +++ b/tests/Analyzers/FilterDiagnosticsTests.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Tools.Analyzers; +using Microsoft.CodeAnalysis.Tools.Formatters; +using Microsoft.CodeAnalysis.Tools.Tests.Formatters; + +using Xunit; + +namespace Microsoft.CodeAnalysis.Tools.Tests.Analyzers +{ + using static AnalyzerAssemblyGenerator; + + public class FilterDiagnosticsTests : CSharpFormatterTests + { + [Fact] + public async Task TestFilterWarning() + { + var projects = GetProjects(); + var allAnalyzers = await GetAnalyzersAsync(); + var formattablePaths = ImmutableHashSet.Create(projects.First().Documents.First().FilePath); + var minimumSeverity = DiagnosticSeverity.Warning; + var result = await AnalyzerFinderHelpers.FilterBySeverityAsync(projects, + allAnalyzers, + formattablePaths, + minimumSeverity, + CancellationToken.None); + var (_, analyzers) = Assert.Single(result); + Assert.Single(analyzers); + } + + [Fact] + public async Task TestFilterError() + { + var projects = GetProjects(); + var allAnalyzers = await GetAnalyzersAsync(); + var formattablePaths = ImmutableHashSet.Create(projects.First().Documents.First().FilePath); + var minimumSeverity = DiagnosticSeverity.Error; + var result = await AnalyzerFinderHelpers.FilterBySeverityAsync(projects, + allAnalyzers, + formattablePaths, + minimumSeverity, + CancellationToken.None); + var (_, analyzers) = Assert.Single(result); + Assert.Empty(analyzers); + } + + private async Task> GetAnalyzersAsync() + { + var assemblies = new[] + { + await GenerateAssemblyAsync( + GenerateAnalyzerCode("DiagnosticAnalyzer1", "DiagnosticAnalyzerId"), + GenerateCodeFix("CodeFixProvider1", "DiagnosticAnalyzerId")) + }; + + var analyzersAndFixers = AnalyzerFinderHelpers.LoadAnalyzersAndFixers(assemblies); + return ImmutableArray.Create(analyzersAndFixers[0].Analyzer); + } + + private IEnumerable GetProjects() + { + var text = SourceText.From(""); + TestState.Sources.Add(text); + + var solution = GetSolution(TestState.Sources.ToArray(), + TestState.AdditionalFiles.ToArray(), + TestState.AdditionalReferences.ToArray(), + "root = true"); + return solution.Projects; + } + + private protected override ICodeFormatter Formatter { get; } + } +} diff --git a/tests/Analyzers/LoadAnalyzersAndFixersTests.cs b/tests/Analyzers/LoadAnalyzersAndFixersTests.cs new file mode 100644 index 0000000000..e844945853 --- /dev/null +++ b/tests/Analyzers/LoadAnalyzersAndFixersTests.cs @@ -0,0 +1,141 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading.Tasks; + +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Tools.Analyzers; + +using Xunit; + +namespace Microsoft.CodeAnalysis.Tools.Tests.Analyzers +{ + using static AnalyzerAssemblyGenerator; + + public class LoadAnalyzersAndFixersTests + { + [Fact] + public static async Task TestSingleAnalyzerAndFixerAsync() + { + var assemblies = new[] + { + await GenerateAssemblyAsync( + GenerateAnalyzerCode("DiagnosticAnalyzer1", "DiagnosticAnalyzerId"), + GenerateCodeFix("CodeFixProvider1", "DiagnosticAnalyzerId")) + }; + + var analyzersAndFixers = AnalyzerFinderHelpers.LoadAnalyzersAndFixers(assemblies); + var (analyzer, fixer) = Assert.Single(analyzersAndFixers); + var analyzerDiagnosticDescriptor = Assert.Single(analyzer.SupportedDiagnostics); + var fixerDiagnosticId = Assert.Single(fixer.FixableDiagnosticIds); + Assert.Equal(analyzerDiagnosticDescriptor.Id, fixerDiagnosticId); + } + + [Fact] + public static async Task TestMultipleAnalyzersAndFixersAsync() + { + var assemblies = new[] + { + await GenerateAssemblyAsync( + GenerateAnalyzerCode("DiagnosticAnalyzer1", "DiagnosticAnalyzerId1"), + GenerateAnalyzerCode("DiagnosticAnalyzer2", "DiagnosticAnalyzerId2"), + GenerateCodeFix("CodeFixProvider1", "DiagnosticAnalyzerId1"), + GenerateCodeFix("CodeFixProvider2", "DiagnosticAnalyzerId2")) + }; + + var analyzersAndFixers = AnalyzerFinderHelpers.LoadAnalyzersAndFixers(assemblies); + Assert.Equal(2, analyzersAndFixers.Length); + Assert.Collection(analyzersAndFixers, VerifyAnalyzerCodeFixTuple, VerifyAnalyzerCodeFixTuple); + } + + [Fact] + public static async Task TestMultipleAnalyzersAndFixersFromTwoAssembliesAsync() + { + var assemblies = new[] + { + await GenerateAssemblyAsync( + GenerateAnalyzerCode("DiagnosticAnalyzer1", "DiagnosticAnalyzerId1"), + GenerateCodeFix("CodeFixProvider1", "DiagnosticAnalyzerId1")), + await GenerateAssemblyAsync( + GenerateAnalyzerCode("DiagnosticAnalyzer2", "DiagnosticAnalyzerId2"), + GenerateCodeFix("CodeFixProvider2", "DiagnosticAnalyzerId2")), + }; + var analyzersAndFixers = AnalyzerFinderHelpers.LoadAnalyzersAndFixers(assemblies); + Assert.Equal(2, analyzersAndFixers.Length); + Assert.Collection(analyzersAndFixers, VerifyAnalyzerCodeFixTuple, VerifyAnalyzerCodeFixTuple); + } + + [Fact] + public static async Task NonMatchingIdsAsync() + { + var assemblies = new[] + { + await GenerateAssemblyAsync( + GenerateAnalyzerCode("DiagnosticAnalyzer1", "DiagnosticAnalyzerId"), + GenerateCodeFix("CodeFixProvider1", "CodeFixProviderId")) + }; + + var analyzersAndFixers = AnalyzerFinderHelpers.LoadAnalyzersAndFixers(assemblies); + Assert.Empty(analyzersAndFixers); + } + + [Fact] + public static async Task SomeMatchingIdsAsync() + { + var assemblies = new[] + { + await GenerateAssemblyAsync( + GenerateAnalyzerCode("DiagnosticAnalyzer1", "DiagnosticAnalyzerId1"), + GenerateAnalyzerCode("DiagnosticAnalyzer2", "DiagnosticAnalyzerId2"), + GenerateCodeFix("CodeFixProvider1", "DiagnosticAnalyzerId1"), + GenerateCodeFix("CodeFixProvider2", "CodeFixProviderId")) + }; + + var analyzersAndFixers = AnalyzerFinderHelpers.LoadAnalyzersAndFixers(assemblies); + var (analyzer, fixer) = Assert.Single(analyzersAndFixers); + var analyzerDiagnosticDescriptor = Assert.Single(analyzer.SupportedDiagnostics); + var fixerDiagnosticId = Assert.Single(fixer.FixableDiagnosticIds); + Assert.Equal(analyzerDiagnosticDescriptor.Id, fixerDiagnosticId); + } + + [Fact] + public static async Task SingleIdMapstoMultipleFixersAsync() + { + var assemblies = new[] + { + await GenerateAssemblyAsync( + GenerateAnalyzerCode("DiagnosticAnalyzer1", "DiagnosticAnalyzerId1"), + GenerateAnalyzerCode("DiagnosticAnalyzer2", "DiagnosticAnalyzerId1"), + GenerateCodeFix("CodeFixProvider1", "DiagnosticAnalyzerId1"), + GenerateCodeFix("CodeFixProvider2", "CodeFixProviderId")) + }; + + var analyzersAndFixers = AnalyzerFinderHelpers.LoadAnalyzersAndFixers(assemblies); + Assert.Equal(2, analyzersAndFixers.Length); + Assert.Collection(analyzersAndFixers, VerifyAnalyzerCodeFixTuple, VerifyAnalyzerCodeFixTuple); + } + + [Fact] + public static async Task MultipleIdsMaptoSingleFixerAsync() + { + var assemblies = new[] + { + await GenerateAssemblyAsync( + GenerateAnalyzerCode("DiagnosticAnalyzer1", "DiagnosticAnalyzerId1"), + GenerateAnalyzerCode("DiagnosticAnalyzer2", "DiagnosticAnalyzerId1"), + GenerateCodeFix("CodeFixProvider1", "DiagnosticAnalyzerId1")) + }; + + var analyzersAndFixers = AnalyzerFinderHelpers.LoadAnalyzersAndFixers(assemblies); + Assert.Equal(2, analyzersAndFixers.Length); + Assert.Collection(analyzersAndFixers, VerifyAnalyzerCodeFixTuple, VerifyAnalyzerCodeFixTuple); + } + + private static void VerifyAnalyzerCodeFixTuple((DiagnosticAnalyzer Analyzer, CodeFixProvider Fixer) tuple) + { + var analyzerDiagnosticDescriptor = Assert.Single(tuple.Analyzer.SupportedDiagnostics); + var fixerDiagnosticId = Assert.Single(tuple.Fixer.FixableDiagnosticIds); + Assert.Equal(analyzerDiagnosticDescriptor.Id, fixerDiagnosticId); + } + } +} diff --git a/tests/dotnet-format.UnitTests.csproj b/tests/dotnet-format.UnitTests.csproj index d002e8609c..0c3a8f02fd 100644 --- a/tests/dotnet-format.UnitTests.csproj +++ b/tests/dotnet-format.UnitTests.csproj @@ -11,8 +11,11 @@ + + +