// 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.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.UnitTests.Diagnostics; using Roslyn.Test.Utilities; using Roslyn.Utilities; using Xunit; #if CODE_STYLE using Microsoft.CodeAnalysis.Internal.Options; #else using Microsoft.CodeAnalysis.Options; #endif namespace Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics { public abstract partial class AbstractDiagnosticProviderBasedUserDiagnosticTest : AbstractUserDiagnosticTest { private readonly ConcurrentDictionary _analyzerAndFixerMap = new ConcurrentDictionary(); internal abstract (DiagnosticAnalyzer, CodeFixProvider) CreateDiagnosticProviderAndFixer(Workspace workspace); internal virtual (DiagnosticAnalyzer, CodeFixProvider) CreateDiagnosticProviderAndFixer(Workspace workspace, TestParameters parameters) => CreateDiagnosticProviderAndFixer(workspace); private (DiagnosticAnalyzer, CodeFixProvider) GetOrCreateDiagnosticProviderAndFixer( Workspace workspace, TestParameters parameters) { return parameters.fixProviderData == null ? _analyzerAndFixerMap.GetOrAdd(workspace, CreateDiagnosticProviderAndFixer) : CreateDiagnosticProviderAndFixer(workspace, parameters); } internal virtual bool ShouldSkipMessageDescriptionVerification(DiagnosticDescriptor descriptor) { if (descriptor.CustomTags.Contains(WellKnownDiagnosticTags.NotConfigurable)) { if (!descriptor.IsEnabledByDefault || descriptor.DefaultSeverity == DiagnosticSeverity.Hidden) { // The message only displayed if either enabled and not hidden, or configurable return true; } } return false; } [Fact] public void TestSupportedDiagnosticsMessageTitle() { using (var workspace = new AdhocWorkspace()) { var diagnosticAnalyzer = CreateDiagnosticProviderAndFixer(workspace).Item1; if (diagnosticAnalyzer == null) { return; } foreach (var descriptor in diagnosticAnalyzer.SupportedDiagnostics) { if (descriptor.CustomTags.Contains(WellKnownDiagnosticTags.NotConfigurable)) { // The title only displayed for rule configuration continue; } Assert.NotEqual("", descriptor.Title?.ToString() ?? ""); } } } [Fact] public void TestSupportedDiagnosticsMessageDescription() { using (var workspace = new AdhocWorkspace()) { var diagnosticAnalyzer = CreateDiagnosticProviderAndFixer(workspace).Item1; if (diagnosticAnalyzer == null) { return; } foreach (var descriptor in diagnosticAnalyzer.SupportedDiagnostics) { if (ShouldSkipMessageDescriptionVerification(descriptor)) { continue; } Assert.NotEqual("", descriptor.MessageFormat?.ToString() ?? ""); } } } [Fact(Skip = "https://github.com/dotnet/roslyn/issues/26717")] public void TestSupportedDiagnosticsMessageHelpLinkUri() { using (var workspace = new AdhocWorkspace()) { var diagnosticAnalyzer = CreateDiagnosticProviderAndFixer(workspace).Item1; if (diagnosticAnalyzer == null) { return; } foreach (var descriptor in diagnosticAnalyzer.SupportedDiagnostics) { Assert.NotEqual("", descriptor.HelpLinkUri ?? ""); } } } internal async override Task> GetDiagnosticsAsync( TestWorkspace workspace, TestParameters parameters) { var providerAndFixer = GetOrCreateDiagnosticProviderAndFixer(workspace, parameters); var provider = providerAndFixer.Item1; var document = GetDocumentAndSelectSpan(workspace, out var span); var allDiagnostics = await DiagnosticProviderTestUtilities.GetAllDiagnosticsAsync(provider, document, span); AssertNoAnalyzerExceptionDiagnostics(allDiagnostics); return allDiagnostics; } internal override async Task<(ImmutableArray, ImmutableArray, CodeAction actionToInvoke)> GetDiagnosticAndFixesAsync( TestWorkspace workspace, TestParameters parameters) { var providerAndFixer = GetOrCreateDiagnosticProviderAndFixer(workspace, parameters); var provider = providerAndFixer.Item1; string annotation = null; if (!TryGetDocumentAndSelectSpan(workspace, out var document, out var span)) { document = GetDocumentAndAnnotatedSpan(workspace, out annotation, out span); } var testDriver = new TestDiagnosticAnalyzerDriver(document.Project, provider); var filterSpan = parameters.includeDiagnosticsOutsideSelection ? (TextSpan?)null : span; var diagnostics = (await testDriver.GetAllDiagnosticsAsync(document, filterSpan)).ToImmutableArray(); AssertNoAnalyzerExceptionDiagnostics(diagnostics); var fixer = providerAndFixer.Item2; if (fixer == null) { return (diagnostics, ImmutableArray.Empty, null); } var ids = new HashSet(fixer.FixableDiagnosticIds); var dxs = diagnostics.Where(d => ids.Contains(d.Id)).ToList(); var (resultDiagnostics, codeActions, actionToInvoke) = await GetDiagnosticAndFixesAsync( dxs, fixer, testDriver, document, span, annotation, parameters.index); // If we are also testing non-fixable diagnostics, // then the result diagnostics need to include all diagnostics, // not just the fixable ones returned from GetDiagnosticAndFixesAsync. if (parameters.retainNonFixableDiagnostics) { resultDiagnostics = diagnostics; } return (resultDiagnostics, codeActions, actionToInvoke); } protected async Task TestDiagnosticInfoAsync( string initialMarkup, IDictionary options, string diagnosticId, DiagnosticSeverity diagnosticSeverity, LocalizableString diagnosticMessage = null) { await TestDiagnosticInfoAsync(initialMarkup, null, null, options, diagnosticId, diagnosticSeverity, diagnosticMessage); await TestDiagnosticInfoAsync(initialMarkup, GetScriptOptions(), null, options, diagnosticId, diagnosticSeverity, diagnosticMessage); } protected async Task TestDiagnosticInfoAsync( string initialMarkup, ParseOptions parseOptions, CompilationOptions compilationOptions, IDictionary options, string diagnosticId, DiagnosticSeverity diagnosticSeverity, LocalizableString diagnosticMessage = null) { var testOptions = new TestParameters(parseOptions, compilationOptions, options); using (var workspace = CreateWorkspaceFromOptions(initialMarkup, testOptions)) { var diagnostics = (await GetDiagnosticsAsync(workspace, testOptions)).ToImmutableArray(); diagnostics = diagnostics.WhereAsArray(d => d.Id == diagnosticId); Assert.Equal(1, diagnostics.Count()); var hostDocument = workspace.Documents.Single(d => d.SelectedSpans.Any()); var expected = hostDocument.SelectedSpans.Single(); var actual = diagnostics.Single().Location.SourceSpan; Assert.Equal(expected, actual); Assert.Equal(diagnosticSeverity, diagnostics.Single().Severity); if (diagnosticMessage != null) { Assert.Equal(diagnosticMessage, diagnostics.Single().GetMessage()); } } } #pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved /// /// The internal method does /// essentially this, but due to linked files between projects, this project cannot have internals visible /// access to the Microsoft.CodeAnalysis project without the cascading effect of many extern aliases, so it /// is re-implemented here in a way that is potentially overly aggressive with the knowledge that if this method /// starts failing on non-analyzer exception diagnostics, it can be appropriately tuned or re-evaluated. /// private void AssertNoAnalyzerExceptionDiagnostics(IEnumerable diagnostics) #pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved { var analyzerExceptionDiagnostics = diagnostics.Where(diag => diag.Descriptor.CustomTags.Contains(WellKnownDiagnosticTags.AnalyzerException)); AssertEx.Empty(analyzerExceptionDiagnostics, "Found analyzer exception diagnostics"); } } }