DiagnosticAnalyzerDriverTests.cs 15.0 KB
Newer Older
1
// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.
2 3 4 5 6

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
7
using System.Threading.Tasks;
8 9 10 11 12 13 14 15 16 17 18 19 20 21
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.UnitTests.Diagnostics;
using Roslyn.Test.Utilities;
using Xunit;

namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics.UserDiagnosticProviderEngine
{
    public class DiagnosticAnalyzerDriverTests
    {
22
        [Fact]
23
        public async Task DiagnosticAnalyzerDriverAllInOne()
24 25 26 27 28 29 30 31
        {
            var source = TestResource.AllInOneCSharpCode;

            // AllInOneCSharpCode has no properties with initializers or named types with primary constructors.
            var symbolKindsWithNoCodeBlocks = new HashSet<SymbolKind>();
            symbolKindsWithNoCodeBlocks.Add(SymbolKind.Property);
            symbolKindsWithNoCodeBlocks.Add(SymbolKind.NamedType);

32
            var syntaxKindsMissing = new HashSet<SyntaxKind>();
C
Charles Stoner 已提交
33

34
            // AllInOneCSharpCode has no deconstruction or declaration expression
35
            syntaxKindsMissing.Add(SyntaxKind.TypedVariableComponent);
36
            syntaxKindsMissing.Add(SyntaxKind.ParenthesizedVariableComponent);
37
            syntaxKindsMissing.Add(SyntaxKind.SingleVariableDesignation);
38
            syntaxKindsMissing.Add(SyntaxKind.ParenthesizedVariableDesignation);
39
            syntaxKindsMissing.Add(SyntaxKind.DeconstructionDeclarationStatement);
40
            syntaxKindsMissing.Add(SyntaxKind.VariableComponentAssignment);
41
            syntaxKindsMissing.Add(SyntaxKind.ForEachComponentStatement);
42
            syntaxKindsMissing.Add(SyntaxKind.DeclarationExpression);
43

44
            var analyzer = new CSharpTrackingDiagnosticAnalyzer();
G
gafter 已提交
45
            using (var workspace = await TestWorkspace.CreateCSharpAsync(source, TestOptions.Regular))
46 47 48
            {
                var document = workspace.CurrentSolution.Projects.Single().Documents.Single();
                AccessSupportedDiagnostics(analyzer);
49
                await DiagnosticProviderTestUtilities.GetAllDiagnosticsAsync(analyzer, document, new Text.TextSpan(0, document.GetTextAsync().Result.Length));
50 51
                analyzer.VerifyAllAnalyzerMembersWereCalled();
                analyzer.VerifyAnalyzeSymbolCalledForAllSymbolKinds();
52
                analyzer.VerifyAnalyzeNodeCalledForAllSyntaxKinds(syntaxKindsMissing);
53 54 55 56
                analyzer.VerifyOnCodeBlockCalledForAllSymbolAndMethodKinds(symbolKindsWithNoCodeBlocks, true);
            }
        }

J
Jared Parsons 已提交
57
        [Fact, WorkItem(908658, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/908658")]
58
        public async Task DiagnosticAnalyzerDriverVsAnalyzerDriverOnCodeBlock()
59 60 61 62 63 64 65 66 67 68 69 70 71
        {
            var methodNames = new string[] { "Initialize", "AnalyzeCodeBlock" };
            var source = @"
[System.Obsolete]
class C
{
    int P { get; set; }
    delegate void A();
    delegate string F();
}
";

            var ideEngineAnalyzer = new CSharpTrackingDiagnosticAnalyzer();
C
Cyrus Najmabadi 已提交
72
            using (var ideEngineWorkspace = await TestWorkspace.CreateCSharpAsync(source))
73 74
            {
                var ideEngineDocument = ideEngineWorkspace.CurrentSolution.Projects.Single().Documents.Single();
75
                await DiagnosticProviderTestUtilities.GetAllDiagnosticsAsync(ideEngineAnalyzer, ideEngineDocument, new Text.TextSpan(0, ideEngineDocument.GetTextAsync().Result.Length));
76 77 78 79 80 81 82 83 84 85
                foreach (var method in methodNames)
                {
                    Assert.False(ideEngineAnalyzer.CallLog.Any(e => e.CallerName == method && e.MethodKind == MethodKind.DelegateInvoke && e.ReturnsVoid));
                    Assert.False(ideEngineAnalyzer.CallLog.Any(e => e.CallerName == method && e.MethodKind == MethodKind.DelegateInvoke && !e.ReturnsVoid));
                    Assert.False(ideEngineAnalyzer.CallLog.Any(e => e.CallerName == method && e.SymbolKind == SymbolKind.NamedType));
                    Assert.False(ideEngineAnalyzer.CallLog.Any(e => e.CallerName == method && e.SymbolKind == SymbolKind.Property));
                }
            }

            var compilerEngineAnalyzer = new CSharpTrackingDiagnosticAnalyzer();
C
Cyrus Najmabadi 已提交
86
            using (var compilerEngineWorkspace = await TestWorkspace.CreateCSharpAsync(source))
87 88 89 90 91 92 93 94 95 96 97 98 99
            {
                var compilerEngineCompilation = (CSharpCompilation)compilerEngineWorkspace.CurrentSolution.Projects.Single().GetCompilationAsync().Result;
                compilerEngineCompilation.GetAnalyzerDiagnostics(new[] { compilerEngineAnalyzer });
                foreach (var method in methodNames)
                {
                    Assert.False(compilerEngineAnalyzer.CallLog.Any(e => e.CallerName == method && e.MethodKind == MethodKind.DelegateInvoke && e.ReturnsVoid));
                    Assert.False(compilerEngineAnalyzer.CallLog.Any(e => e.CallerName == method && e.MethodKind == MethodKind.DelegateInvoke && !e.ReturnsVoid));
                    Assert.False(compilerEngineAnalyzer.CallLog.Any(e => e.CallerName == method && e.SymbolKind == SymbolKind.NamedType));
                    Assert.False(compilerEngineAnalyzer.CallLog.Any(e => e.CallerName == method && e.SymbolKind == SymbolKind.Property));
                }
            }
        }

100
        [Fact]
101
        [WorkItem(759, "https://github.com/dotnet/roslyn/issues/759")]
102
        public async Task DiagnosticAnalyzerDriverIsSafeAgainstAnalyzerExceptions()
103 104
        {
            var source = TestResource.AllInOneCSharpCode;
C
Cyrus Najmabadi 已提交
105
            using (var workspace = await TestWorkspace.CreateCSharpAsync(source, TestOptions.Regular))
106 107
            {
                var document = workspace.CurrentSolution.Projects.Single().Documents.Single();
108 109
                await ThrowingDiagnosticAnalyzer<SyntaxKind>.VerifyAnalyzerEngineIsSafeAgainstExceptionsAsync(async analyzer =>
                    await DiagnosticProviderTestUtilities.GetAllDiagnosticsAsync(analyzer, document, new Text.TextSpan(0, document.GetTextAsync().Result.Length), logAnalyzerExceptionAsDiagnostics: true));
110 111 112
            }
        }

J
Jared Parsons 已提交
113
        [WorkItem(908621, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/908621")]
114
        [Fact]
115 116 117 118 119 120 121
        public void DiagnosticServiceIsSafeAgainstAnalyzerExceptions_1()
        {
            var analyzer = new ThrowingDiagnosticAnalyzer<SyntaxKind>();
            analyzer.ThrowOn(typeof(DiagnosticAnalyzer).GetProperties().Single().Name);
            AccessSupportedDiagnostics(analyzer);
        }

J
Jared Parsons 已提交
122
        [WorkItem(908621, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/908621")]
123
        [Fact]
124 125 126 127 128 129 130 131 132 133 134 135 136 137
        public void DiagnosticServiceIsSafeAgainstAnalyzerExceptions_2()
        {
            var analyzer = new ThrowingDoNotCatchDiagnosticAnalyzer<SyntaxKind>();
            analyzer.ThrowOn(typeof(DiagnosticAnalyzer).GetProperties().Single().Name);
            var exceptions = new List<Exception>();
            try
            {
                AccessSupportedDiagnostics(analyzer);
            }
            catch (Exception e)
            {
                exceptions.Add(e);
            }

138
            Assert.True(exceptions.Count == 0);
139 140
        }

141
        [Fact]
142
        public async Task AnalyzerOptionsArePassedToAllAnalyzers()
143
        {
C
Cyrus Najmabadi 已提交
144
            using (var workspace = await TestWorkspace.CreateCSharpAsync(TestResource.AllInOneCSharpCode, TestOptions.Regular))
145 146 147 148 149 150 151
            {
                var currentProject = workspace.CurrentSolution.Projects.Single();

                var additionalDocId = DocumentId.CreateNewId(currentProject.Id);
                var newSln = workspace.CurrentSolution.AddAdditionalDocument(additionalDocId, "add.config", SourceText.From("random text"));
                currentProject = newSln.Projects.Single();
                var additionalDocument = currentProject.GetAdditionalDocument(additionalDocId);
152
                AdditionalText additionalStream = new AdditionalTextDocument(additionalDocument.State);
153 154 155 156
                AnalyzerOptions options = new AnalyzerOptions(ImmutableArray.Create(additionalStream));
                var analyzer = new OptionsDiagnosticAnalyzer<SyntaxKind>(expectedOptions: options);

                var sourceDocument = currentProject.Documents.Single();
157
                await DiagnosticProviderTestUtilities.GetAllDiagnosticsAsync(analyzer, sourceDocument, new Text.TextSpan(0, sourceDocument.GetTextAsync().Result.Length));
158 159 160 161 162 163
                analyzer.VerifyAnalyzerOptions();
            }
        }

        private void AccessSupportedDiagnostics(DiagnosticAnalyzer analyzer)
        {
164
            var diagnosticService = new TestDiagnosticAnalyzerService(LanguageNames.CSharp, analyzer);
H
heejaechang 已提交
165
            diagnosticService.GetDiagnosticDescriptors(projectOpt: null);
166 167 168 169
        }

        private class ThrowingDoNotCatchDiagnosticAnalyzer<TLanguageKindEnum> : ThrowingDiagnosticAnalyzer<TLanguageKindEnum>, IBuiltInAnalyzer where TLanguageKindEnum : struct
        {
170
            public bool OpenFileOnly(Workspace workspace) => true;
171

172 173 174 175
            public DiagnosticAnalyzerCategory GetAnalyzerCategory()
            {
                return DiagnosticAnalyzerCategory.SyntaxAnalysis | DiagnosticAnalyzerCategory.SemanticDocumentAnalysis | DiagnosticAnalyzerCategory.ProjectAnalysis;
            }
176 177
        }

178
        [Fact]
179
        public async Task AnalyzerCreatedAtCompilationLevelNeedNotBeCompilationAnalyzer()
180 181 182 183
        {
            var source = @"x";

            var analyzer = new CompilationAnalyzerWithSyntaxTreeAnalyzer();
C
Cyrus Najmabadi 已提交
184
            using (var ideEngineWorkspace = await TestWorkspace.CreateCSharpAsync(source))
185 186
            {
                var ideEngineDocument = ideEngineWorkspace.CurrentSolution.Projects.Single().Documents.Single();
187
                var diagnostics = await DiagnosticProviderTestUtilities.GetAllDiagnosticsAsync(analyzer, ideEngineDocument, new Text.TextSpan(0, ideEngineDocument.GetTextAsync().Result.Length));
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228

                var diagnosticsFromAnalyzer = diagnostics.Where(d => d.Id == "SyntaxDiagnostic");

                Assert.Equal(1, diagnosticsFromAnalyzer.Count());
            }
        }

        private class CompilationAnalyzerWithSyntaxTreeAnalyzer : DiagnosticAnalyzer
        {
            private const string ID = "SyntaxDiagnostic";

            private static readonly DiagnosticDescriptor s_syntaxDiagnosticDescriptor =
                new DiagnosticDescriptor(ID, title: "Syntax", messageFormat: "Syntax", category: "Test", defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true);

            public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
            {
                get
                {
                    return ImmutableArray.Create(s_syntaxDiagnosticDescriptor);
                }
            }

            public override void Initialize(AnalysisContext context)
            {
                context.RegisterCompilationStartAction(CreateAnalyzerWithinCompilation);
            }

            public void CreateAnalyzerWithinCompilation(CompilationStartAnalysisContext context)
            {
                context.RegisterSyntaxTreeAction(new SyntaxTreeAnalyzer().AnalyzeSyntaxTree);
            }

            private class SyntaxTreeAnalyzer
            {
                public void AnalyzeSyntaxTree(SyntaxTreeAnalysisContext context)
                {
                    context.ReportDiagnostic(Diagnostic.Create(s_syntaxDiagnosticDescriptor, context.Tree.GetRoot().GetFirstToken().GetLocation()));
                }
            }
        }

229
        [Fact]
230
        public async Task CodeBlockAnalyzersOnlyAnalyzeExecutableCode()
231 232 233 234 235 236 237 238 239 240 241 242 243
        {
            var source = @"
using System;
class C
{
    void F(int x = 0)
    {
        Console.WriteLine(0);
    }
}
";

            var analyzer = new CodeBlockAnalyzerFactory();
C
Cyrus Najmabadi 已提交
244
            using (var ideEngineWorkspace = await TestWorkspace.CreateCSharpAsync(source))
245 246
            {
                var ideEngineDocument = ideEngineWorkspace.CurrentSolution.Projects.Single().Documents.Single();
247
                var diagnostics = await DiagnosticProviderTestUtilities.GetAllDiagnosticsAsync(analyzer, ideEngineDocument, new Text.TextSpan(0, ideEngineDocument.GetTextAsync().Result.Length));
C
Charles Stoner 已提交
248
                var diagnosticsFromAnalyzer = diagnostics.Where(d => d.Id == CodeBlockAnalyzerFactory.Descriptor.Id);
249 250 251 252 253 254 255 256 257 258 259 260 261 262
                Assert.Equal(2, diagnosticsFromAnalyzer.Count());
            }

            source = @"
using System;
class C
{
    void F(int x = 0, int y = 1, int z = 2)
    {
        Console.WriteLine(0);
    }
}
";

C
Cyrus Najmabadi 已提交
263
            using (var compilerEngineWorkspace = await TestWorkspace.CreateCSharpAsync(source))
264 265 266
            {
                var compilerEngineCompilation = (CSharpCompilation)compilerEngineWorkspace.CurrentSolution.Projects.Single().GetCompilationAsync().Result;
                var diagnostics = compilerEngineCompilation.GetAnalyzerDiagnostics(new[] { analyzer });
C
Charles Stoner 已提交
267
                var diagnosticsFromAnalyzer = diagnostics.Where(d => d.Id == CodeBlockAnalyzerFactory.Descriptor.Id);
268 269 270 271 272 273
                Assert.Equal(4, diagnosticsFromAnalyzer.Count());
            }
        }

        private class CodeBlockAnalyzerFactory : DiagnosticAnalyzer
        {
C
Charles Stoner 已提交
274
            public static DiagnosticDescriptor Descriptor = DescriptorFactory.CreateSimpleDescriptor("DummyDiagnostic");
275 276 277 278 279

            public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
            {
                get
                {
C
Charles Stoner 已提交
280
                    return ImmutableArray.Create(Descriptor);
281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305
                }
            }

            public override void Initialize(AnalysisContext context)
            {
                context.RegisterCodeBlockStartAction<SyntaxKind>(CreateAnalyzerWithinCodeBlock);
            }

            public void CreateAnalyzerWithinCodeBlock(CodeBlockStartAnalysisContext<SyntaxKind> context)
            {
                var blockAnalyzer = new CodeBlockAnalyzer();
                context.RegisterCodeBlockEndAction(blockAnalyzer.AnalyzeCodeBlock);
                context.RegisterSyntaxNodeAction(blockAnalyzer.AnalyzeNode, blockAnalyzer.SyntaxKindsOfInterest.ToArray());
            }

            private class CodeBlockAnalyzer
            {
                public ImmutableArray<SyntaxKind> SyntaxKindsOfInterest
                {
                    get
                    {
                        return ImmutableArray.Create(SyntaxKind.MethodDeclaration, SyntaxKind.ExpressionStatement, SyntaxKind.EqualsValueClause);
                    }
                }

306
                public void AnalyzeCodeBlock(CodeBlockAnalysisContext context)
307 308 309 310 311 312 313
                {
                }

                public void AnalyzeNode(SyntaxNodeAnalysisContext context)
                {
                    // Ensure only executable nodes are analyzed.
                    Assert.NotEqual(SyntaxKind.MethodDeclaration, context.Node.Kind());
C
Charles Stoner 已提交
314
                    context.ReportDiagnostic(Diagnostic.Create(Descriptor, context.Node.GetLocation()));
315 316 317 318 319
                }
            }
        }
    }
}