Skip to content

Commit

Permalink
Ensure that StructuredAnalyzerConfigOptions contain the diagnostic se…
Browse files Browse the repository at this point in the history
…verity 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 dotnet#58835
  • Loading branch information
mavasani committed Jan 20, 2023
1 parent 8f53ce6 commit d388800
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

using System.Collections.Immutable;
using Microsoft.CodeAnalysis.Diagnostics;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis;

Expand All @@ -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<string, string> GetAggregatedOptions(AnalyzerConfigOptionsResult result)
{
if (result.TreeOptions.IsEmpty)
return result.AnalyzerOptions;

var builder = ImmutableDictionary.CreateBuilder<string, string>(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();

}
}
55 changes: 55 additions & 0 deletions src/Workspaces/CoreTest/Options/DiagnosticSeverityOptionsTests.cs
Original file line number Diff line number Diff line change
@@ -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));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,41 @@ public static ReportDiagnostic GetEffectiveSeverity(
return effectiveSeverity;
}

/// <summary>
/// Gets document-level effective severity of the given <paramref name="descriptor"/> 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.
/// </summary>
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
Expand Down

0 comments on commit d388800

Please sign in to comment.