From d388800791c088a929a3d3c315a352091c9b261a Mon Sep 17 00:00:00 2001 From: Manish Vasani Date: Fri, 20 Jan 2023 14:45:41 +0530 Subject: [PATCH 1/2] Ensure that StructuredAnalyzerConfigOptions contain the diagnostic severity configuration key-values Existing DiagnosticDescriptorExtensions helper methods rely on these key-value pairs being part of the project analyzer config options, and they seem to have been broken by recent code refactoring that added StructuredAnalyzerConfigOptions. I am also adding a new GetEffectiveSeverity extension method, which is needed by #58835 --- .../Workspace/Solution/AnalyzerConfigData.cs | 22 +++++++- .../Options/DiagnosticSeverityOptionsTests.cs | 55 +++++++++++++++++++ .../DiagnosticDescriptorExtensions.cs | 35 ++++++++++++ 3 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 src/Workspaces/CoreTest/Options/DiagnosticSeverityOptionsTests.cs diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/AnalyzerConfigData.cs b/src/Workspaces/Core/Portable/Workspace/Solution/AnalyzerConfigData.cs index 99371f49965bb..75836c406e1c4 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/AnalyzerConfigData.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/AnalyzerConfigData.cs @@ -4,6 +4,7 @@ using System.Collections.Immutable; using Microsoft.CodeAnalysis.Diagnostics; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis; @@ -18,8 +19,27 @@ internal readonly struct AnalyzerConfigData public AnalyzerConfigData(AnalyzerConfigOptionsResult result) { - ConfigOptions = StructuredAnalyzerConfigOptions.Create(result.AnalyzerOptions); + ConfigOptions = StructuredAnalyzerConfigOptions.Create(GetAggregatedOptions(result)); AnalyzerOptions = result.AnalyzerOptions; TreeOptions = result.TreeOptions; } + + private static ImmutableDictionary GetAggregatedOptions(AnalyzerConfigOptionsResult result) + { + if (result.TreeOptions.IsEmpty) + return result.AnalyzerOptions; + + var builder = ImmutableDictionary.CreateBuilder(result.AnalyzerOptions.KeyComparer, result.AnalyzerOptions.ValueComparer); + builder.AddRange(result.AnalyzerOptions); + + foreach (var (id, severity) in result.TreeOptions) + { + var key = $"dotnet_diagnostic.{id}.severity"; + var value = severity.ToEditorConfigString(); + builder.Add(key, value); + } + + return builder.ToImmutable(); + + } } diff --git a/src/Workspaces/CoreTest/Options/DiagnosticSeverityOptionsTests.cs b/src/Workspaces/CoreTest/Options/DiagnosticSeverityOptionsTests.cs new file mode 100644 index 0000000000000..00debd6f2f61b --- /dev/null +++ b/src/Workspaces/CoreTest/Options/DiagnosticSeverityOptionsTests.cs @@ -0,0 +1,55 @@ +// 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.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Test.Utilities; +using Xunit; +using static Microsoft.CodeAnalysis.UnitTests.SolutionTestHelpers; + +namespace Microsoft.CodeAnalysis.UnitTests.Options +{ + [UseExportProvider] + [Trait(Traits.Feature, Traits.Features.Workspace)] + public class DiagnosticSeverityOptionsTests : TestBase + { + [Theory, CombinatorialData] + public async Task TestGetEffectiveSeverityFromProjectOptions(bool testGlobalConfig, bool testBulkConfiguration) + { + using var workspace = CreateWorkspace(); + + var projectId = ProjectId.CreateNewId(); + var project = workspace.CurrentSolution + .AddProject(projectId, "proj1", "proj1.dll", LanguageNames.CSharp) + .AddDocument(DocumentId.CreateNewId(projectId), "goo.cs", SourceText.From("public class Goo { }", Encoding.UTF8, SourceHashAlgorithms.Default), filePath: @"z:\\goo.cs") + .Projects.Single(); + + var descriptor = new DiagnosticDescriptor("ID1000", "Title", "Message", "Category", DiagnosticSeverity.Warning, isEnabledByDefault: true); + var editorConfigHeader = testGlobalConfig ? "is_global = true" : "[*.cs]"; + var editorConfigEntry = testBulkConfiguration + ? "dotnet_analyzer_diagnostic.severity = error" + : "dotnet_diagnostic.ID1000.severity = error"; + var analyzerConfigText = $@" +{editorConfigHeader} +{editorConfigEntry} +"; + project = project.AddAnalyzerConfigDocument(".editorconfig", SourceText.From(analyzerConfigText), filePath: @"z:\\.editorconfig").Project; + + workspace.TryApplyChanges(project.Solution); + + var document = project.Documents.Single(); + var tree = await document.GetSyntaxTreeAsync(); + var optionsProvider = project.AnalyzerOptions.AnalyzerConfigOptionsProvider; + var analyzerConfigOptions = testGlobalConfig ? optionsProvider.GlobalOptions : optionsProvider.GetOptions(tree); + Assert.Equal(ReportDiagnostic.Error, descriptor.GetEffectiveSeverity(analyzerConfigOptions)); + + var compilation = await project.GetCompilationAsync(); + Assert.Equal(ReportDiagnostic.Error, descriptor.GetEffectiveSeverity(compilation.Options, tree, project.AnalyzerOptions)); + } + } +} diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/DiagnosticDescriptorExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/DiagnosticDescriptorExtensions.cs index eada350447e61..292fab1b059ef 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/DiagnosticDescriptorExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/DiagnosticDescriptorExtensions.cs @@ -55,6 +55,41 @@ public static ReportDiagnostic GetEffectiveSeverity( return effectiveSeverity; } + /// + /// Gets document-level effective severity of the given accounting for severity configurations from both the following sources: + /// 1. Compilation options from ruleset file, if any, and command line options such as /nowarn, /warnaserror, etc. + /// 2. Analyzer config documents at the document root directory or in ancestor directories. + /// + public static ReportDiagnostic GetEffectiveSeverity(this DiagnosticDescriptor descriptor, CompilationOptions compilationOptions, SyntaxTree tree, AnalyzerOptions analyzerOptions) + { + var effectiveSeverity = descriptor.GetEffectiveSeverity(compilationOptions); + + // Apply analyzer config options, unless configured with a non-default value in compilation options. + // Note that compilation options (/nowarn, /warnaserror) override analyzer config options. + if (!compilationOptions.SpecificDiagnosticOptions.TryGetValue(descriptor.Id, out var reportDiagnostic) || + reportDiagnostic == ReportDiagnostic.Default) + { + // First check for tree-level analyzer config options. + var analyzerConfigOptions = analyzerOptions.AnalyzerConfigOptionsProvider.GetOptions(tree); + var severityInEditorConfig = descriptor.GetEffectiveSeverity(analyzerConfigOptions); + if (severityInEditorConfig != ReportDiagnostic.Default) + { + effectiveSeverity = severityInEditorConfig; + } + else + { + // If not found, check for global analyzer config options. + var severityInGlobalConfig = descriptor.GetEffectiveSeverity(analyzerOptions.AnalyzerConfigOptionsProvider.GlobalOptions); + if (severityInGlobalConfig != ReportDiagnostic.Default) + { + effectiveSeverity = severityInGlobalConfig; + } + } + } + + return effectiveSeverity; + } + public static bool IsDefinedInEditorConfig(this DiagnosticDescriptor descriptor, AnalyzerConfigOptions analyzerConfigOptions) { // Check if the option is defined explicitly in the editorconfig From ab2db0018c37c12bc2bd1c54046518257b53ccf9 Mon Sep 17 00:00:00 2001 From: Manish Vasani Date: Fri, 20 Jan 2023 15:17:46 +0530 Subject: [PATCH 2/2] Fix nullability warnings --- .../CoreTest/Options/DiagnosticSeverityOptionsTests.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/CoreTest/Options/DiagnosticSeverityOptionsTests.cs b/src/Workspaces/CoreTest/Options/DiagnosticSeverityOptionsTests.cs index 00debd6f2f61b..f9912f04608d6 100644 --- a/src/Workspaces/CoreTest/Options/DiagnosticSeverityOptionsTests.cs +++ b/src/Workspaces/CoreTest/Options/DiagnosticSeverityOptionsTests.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text; +using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Test.Utilities; @@ -43,12 +44,12 @@ public async Task TestGetEffectiveSeverityFromProjectOptions(bool testGlobalConf workspace.TryApplyChanges(project.Solution); var document = project.Documents.Single(); - var tree = await document.GetSyntaxTreeAsync(); + var tree = await document.GetRequiredSyntaxTreeAsync(CancellationToken.None).ConfigureAwait(false); var optionsProvider = project.AnalyzerOptions.AnalyzerConfigOptionsProvider; var analyzerConfigOptions = testGlobalConfig ? optionsProvider.GlobalOptions : optionsProvider.GetOptions(tree); Assert.Equal(ReportDiagnostic.Error, descriptor.GetEffectiveSeverity(analyzerConfigOptions)); - var compilation = await project.GetCompilationAsync(); + var compilation = await project.GetRequiredCompilationAsync(CancellationToken.None).ConfigureAwait(false); Assert.Equal(ReportDiagnostic.Error, descriptor.GetEffectiveSeverity(compilation.Options, tree, project.AnalyzerOptions)); } }