File: Services\VisualStudioDiagnosticAnalyzerExecutorTests.cs
Web Access
Project: src\src\VisualStudio\Core\Test.Next\Roslyn.VisualStudio.Next.UnitTests.csproj (Roslyn.VisualStudio.Next.UnitTests)
// 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.
 
#nullable disable
 
using System;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.Diagnostics.TypeStyle;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editor.UnitTests;
using Microsoft.CodeAnalysis.Remote;
using Microsoft.CodeAnalysis.Remote.Testing;
using Microsoft.CodeAnalysis.Serialization;
using Microsoft.CodeAnalysis.SolutionCrawler;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.VisualBasic.UseNullPropagation;
using Microsoft.CodeAnalysis.Workspaces.Diagnostics;
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
using Xunit;
 
namespace Roslyn.VisualStudio.Next.UnitTests.Remote
{
    [UseExportProvider]
    [Trait(Traits.Feature, Traits.Features.RemoteHost)]
    public class VisualStudioDiagnosticAnalyzerExecutorTests
    {
        [Theory]
        [CombinatorialData]
        public async Task TestCSharpAnalyzerOptions(bool isHostAnalyzer)
        {
            var code = @"class Test
{
    void Method()
    {
        var t = new Test();
    }
}";
 
            using var workspace = CreateWorkspace(LanguageNames.CSharp, code);
            var analyzerType = typeof(CSharpUseExplicitTypeDiagnosticAnalyzer);
            var analyzerResult = await AnalyzeAsync(workspace, workspace.CurrentSolution.ProjectIds.First(), analyzerType, isHostAnalyzer);
 
            var diagnostics = analyzerResult.GetDocumentDiagnostics(analyzerResult.DocumentIds.First(), AnalysisKind.Semantic);
            Assert.Equal(IDEDiagnosticIds.UseExplicitTypeDiagnosticId, diagnostics[0].Id);
            Assert.Equal(DiagnosticSeverity.Hidden, diagnostics[0].Severity);
 
            workspace.SetAnalyzerFallbackOptions(LanguageNames.CSharp, ("csharp_style_var_when_type_is_apparent", "false:suggestion"));
 
            analyzerResult = await AnalyzeAsync(workspace, workspace.CurrentSolution.ProjectIds.First(), analyzerType, isHostAnalyzer);
 
            diagnostics = analyzerResult.GetDocumentDiagnostics(analyzerResult.DocumentIds.First(), AnalysisKind.Semantic);
            Assert.Equal(IDEDiagnosticIds.UseExplicitTypeDiagnosticId, diagnostics[0].Id);
            Assert.Equal(isHostAnalyzer ? DiagnosticSeverity.Info : DiagnosticSeverity.Hidden, diagnostics[0].Severity);
        }
 
        [Theory]
        [CombinatorialData]
        public async Task TestVisualBasicAnalyzerOptions(bool isHostAnalyzer)
        {
            var code = @"Class Test
    Sub Method()
        Dim b = Nothing
        Dim a = If(b Is Nothing, Nothing, b.ToString())
    End Sub
End Class";
 
            using var workspace = CreateWorkspace(LanguageNames.VisualBasic, code);
 
            workspace.SetAnalyzerFallbackOptions(LanguageNames.VisualBasic, ("dotnet_style_null_propagation", "false:silent"));
 
            var analyzerType = typeof(VisualBasicUseNullPropagationDiagnosticAnalyzer);
            var analyzerResult = await AnalyzeAsync(workspace, workspace.CurrentSolution.ProjectIds.First(), analyzerType, isHostAnalyzer);
 
            ImmutableArray<DiagnosticData> diagnostics;
            if (isHostAnalyzer)
            {
                Assert.True(analyzerResult.IsEmpty);
            }
            else
            {
                diagnostics = analyzerResult.GetDocumentDiagnostics(analyzerResult.DocumentIds.First(), AnalysisKind.Semantic);
                Assert.Equal(IDEDiagnosticIds.UseNullPropagationDiagnosticId, diagnostics[0].Id);
                Assert.Equal(DiagnosticSeverity.Info, diagnostics[0].Severity);
            }
 
            workspace.SetAnalyzerFallbackOptions(LanguageNames.VisualBasic, ("dotnet_style_null_propagation", "true:error"));
 
            analyzerResult = await AnalyzeAsync(workspace, workspace.CurrentSolution.ProjectIds.First(), analyzerType, isHostAnalyzer);
 
            diagnostics = analyzerResult.GetDocumentDiagnostics(analyzerResult.DocumentIds.First(), AnalysisKind.Semantic);
            Assert.Equal(IDEDiagnosticIds.UseNullPropagationDiagnosticId, diagnostics[0].Id);
            Assert.Equal(isHostAnalyzer ? DiagnosticSeverity.Error : DiagnosticSeverity.Info, diagnostics[0].Severity);
        }
 
        [Fact]
        public async Task TestCancellation()
        {
            var code = @"class Test { void Method() { } }";
 
            using var workspace = CreateWorkspace(LanguageNames.CSharp, code);
 
            var analyzerType = typeof(MyAnalyzer);
 
            for (var i = 0; i < 5; i++)
            {
                var source = new CancellationTokenSource();
 
                try
                {
                    var task = Task.Run(() => AnalyzeAsync(workspace, workspace.CurrentSolution.ProjectIds.First(), analyzerType, isHostAnalyzer: false, source.Token));
 
                    // wait random milli-second
                    var random = new Random(Environment.TickCount);
                    var next = random.Next(1000);
                    await Task.Delay(next);
 
                    source.Cancel();
 
                    // let it throw
                    var result = await task;
                }
                catch (Exception ex)
                {
                    // only cancellation is expected
                    Assert.True(ex is OperationCanceledException, $"cancellationToken : {source.Token.IsCancellationRequested}/r/n{ex}");
                }
            }
        }
 
        [Fact]
        public async Task TestHostAnalyzers_OutOfProc()
        {
            var code = @"class Test
{
    void Method()
    {
        var t = new Test();
    }
}";
            using var workspace = CreateWorkspace(LanguageNames.CSharp, code);
            var analyzerType = typeof(CSharpUseExplicitTypeDiagnosticAnalyzer);
 
            var analyzerReference = new AnalyzerFileReference(analyzerType.Assembly.Location, new TestAnalyzerAssemblyLoader());
            workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences([analyzerReference]));
 
            workspace.GlobalOptions.SetGlobalOption(CSharpCodeStyleOptions.VarWhenTypeIsApparent, new CodeStyleOption<bool>(false, NotificationOption.Suggestion));
 
            // run analysis
            var project = workspace.CurrentSolution.Projects.First();
 
            var runner = CreateAnalyzerRunner();
 
            var compilationWithAnalyzers = new CompilationWithAnalyzersPair(
                projectCompilationWithAnalyzers: null,
                (await project.GetCompilationAsync()).WithAnalyzers(
                    analyzerReference.GetAnalyzers(project.Language).Where(a => a.GetType() == analyzerType).ToImmutableArray(),
                    project.AnalyzerOptions));
 
            var result = await runner.AnalyzeProjectAsync(project, compilationWithAnalyzers, logPerformanceInfo: false, getTelemetryInfo: false, cancellationToken: CancellationToken.None);
            var analyzerResult = result.AnalysisResult[compilationWithAnalyzers.HostAnalyzers[0]];
 
            // check result
            var diagnostics = analyzerResult.GetDocumentDiagnostics(analyzerResult.DocumentIds.First(), AnalysisKind.Semantic);
            Assert.Equal(IDEDiagnosticIds.UseExplicitTypeDiagnosticId, diagnostics[0].Id);
        }
 
        [Fact]
        public async Task TestDuplicatedAnalyzers()
        {
            var code = @"class Test
{
    void Method()
    {
        var t = new Test();
    }
}";
 
            using var workspace = CreateWorkspace(LanguageNames.CSharp, code);
            var analyzerType = typeof(DuplicateAnalyzer);
            var analyzerReference = new AnalyzerFileReference(analyzerType.Assembly.Location, new TestAnalyzerAssemblyLoader());
 
            // add host analyzer as global assets
            var remotableDataService = workspace.Services.GetService<ISolutionAssetStorageProvider>();
            var serializer = workspace.Services.GetRequiredService<ISerializerService>();
 
            // run analysis
            var project = workspace.CurrentSolution.Projects.First().AddAnalyzerReference(analyzerReference);
 
            var runner = CreateAnalyzerRunner();
            var analyzers = analyzerReference.GetAnalyzers(project.Language).Where(a => a.GetType() == analyzerType).ToImmutableArray();
 
            var compilationWithAnalyzers = new CompilationWithAnalyzersPair(
                (await project.GetCompilationAsync()).WithAnalyzers(analyzers, project.AnalyzerOptions),
                hostCompilationWithAnalyzers: null);
 
            var result = await runner.AnalyzeProjectAsync(project, compilationWithAnalyzers,
                logPerformanceInfo: false, getTelemetryInfo: false, cancellationToken: CancellationToken.None);
 
            var analyzerResult = result.AnalysisResult[compilationWithAnalyzers.ProjectAnalyzers[0]];
 
            // check result
            var diagnostics = analyzerResult.GetDocumentDiagnostics(analyzerResult.DocumentIds.First(), AnalysisKind.Syntax);
            Assert.Equal("test", diagnostics[0].Id);
        }
 
        private static InProcOrRemoteHostAnalyzerRunner CreateAnalyzerRunner()
            => new(enabled: true, new DiagnosticAnalyzerInfoCache());
 
        private static async Task<DiagnosticAnalysisResult> AnalyzeAsync(TestWorkspace workspace, ProjectId projectId, Type analyzerType, bool isHostAnalyzer, CancellationToken cancellationToken = default)
        {
            var executor = CreateAnalyzerRunner();
 
            var analyzerReference = new AnalyzerFileReference(analyzerType.Assembly.Location, new TestAnalyzerAssemblyLoader());
            var solution = workspace.CurrentSolution;
            if (isHostAnalyzer)
            {
                solution = solution.AddAnalyzerReference(analyzerReference);
            }
 
            var project = solution.GetProject(projectId);
            if (!isHostAnalyzer)
            {
                project = project.AddAnalyzerReference(analyzerReference);
            }
 
            var compilationWithAnalyzers = (await project.GetCompilationAsync()).WithAnalyzers(
                analyzerReference.GetAnalyzers(project.Language).Where(a => a.GetType() == analyzerType).ToImmutableArray(),
                project.AnalyzerOptions);
            var analyzerDriver = isHostAnalyzer
                ? new CompilationWithAnalyzersPair(projectCompilationWithAnalyzers: null, compilationWithAnalyzers)
                : new CompilationWithAnalyzersPair(compilationWithAnalyzers, hostCompilationWithAnalyzers: null);
 
            var result = await executor.AnalyzeProjectAsync(
                project, analyzerDriver, logPerformanceInfo: false, getTelemetryInfo: false, cancellationToken);
 
            return result.AnalysisResult[(isHostAnalyzer ? analyzerDriver.HostAnalyzers : analyzerDriver.ProjectAnalyzers)[0]];
        }
 
        private static TestWorkspace CreateWorkspace(string language, string code, ParseOptions options = null)
        {
            var composition = EditorTestCompositions.EditorFeatures.WithTestHostParts(TestHost.OutOfProcess);
 
            var workspace = (language == LanguageNames.CSharp)
                ? TestWorkspace.CreateCSharp(code, parseOptions: options, composition: composition)
                : TestWorkspace.CreateVisualBasic(code, parseOptions: options, composition: composition);
 
            workspace.GlobalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp, BackgroundAnalysisScope.FullSolution);
            workspace.GlobalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.VisualBasic, BackgroundAnalysisScope.FullSolution);
 
            return workspace;
        }
 
        [DiagnosticAnalyzer(LanguageNames.CSharp)]
        private class MyAnalyzer : DiagnosticAnalyzer
        {
            private readonly ImmutableArray<DiagnosticDescriptor> _supportedDiagnostics =
                ImmutableArray.Create(new DiagnosticDescriptor("test", "test", "test", "test", DiagnosticSeverity.Error, isEnabledByDefault: true));
 
            public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => _supportedDiagnostics;
 
            public override void Initialize(AnalysisContext context)
            {
                context.RegisterSyntaxTreeAction(c =>
                {
                    for (var i = 0; i < 10000; i++)
                    {
                        c.ReportDiagnostic(Diagnostic.Create(_supportedDiagnostics[0], c.Tree.GetLocation(TextSpan.FromBounds(0, 1))));
                    }
                });
            }
        }
 
        [DiagnosticAnalyzer(LanguageNames.CSharp)]
        private class DuplicateAnalyzer : DiagnosticAnalyzer
        {
            private readonly ImmutableArray<DiagnosticDescriptor> _supportedDiagnostics =
                ImmutableArray.Create(new DiagnosticDescriptor("test", "test", "test", "test", DiagnosticSeverity.Error, isEnabledByDefault: true));
 
            public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => _supportedDiagnostics;
 
            public override void Initialize(AnalysisContext context)
            {
                context.RegisterSyntaxTreeAction(c =>
                {
                    c.ReportDiagnostic(Diagnostic.Create(_supportedDiagnostics[0], c.Tree.GetLocation(TextSpan.FromBounds(0, 1))));
                });
            }
        }
    }
}