|
// 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;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis.Diagnostics;
namespace Microsoft.CodeAnalysis.Shared.Extensions
{
internal static class DiagnosticDescriptorExtensions
{
private const string DotnetAnalyzerDiagnosticPrefix = "dotnet_analyzer_diagnostic";
private const string DotnetDiagnosticPrefix = "dotnet_diagnostic";
private const string CategoryPrefix = "category";
private const string SeveritySuffix = "severity";
private const string DotnetAnalyzerDiagnosticSeverityKey = DotnetAnalyzerDiagnosticPrefix + "." + SeveritySuffix;
public static ImmutableArray<string> ImmutableCustomTags(this DiagnosticDescriptor descriptor)
{
Debug.Assert(descriptor.CustomTags is ImmutableArray<string>);
return (ImmutableArray<string>)descriptor.CustomTags;
}
/// <summary>
/// Gets project-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 project root directory or in ancestor directories.
/// </summary>
public static ReportDiagnostic GetEffectiveSeverity(
this DiagnosticDescriptor descriptor,
CompilationOptions compilationOptions,
ImmutableDictionary<string, string>? analyzerOptions,
ImmutableDictionary<string, ReportDiagnostic>? treeOptions)
{
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 (treeOptions != null && analyzerOptions != null &&
(!compilationOptions.SpecificDiagnosticOptions.TryGetValue(descriptor.Id, out var reportDiagnostic) ||
reportDiagnostic == ReportDiagnostic.Default))
{
if (treeOptions.TryGetValue(descriptor.Id, out reportDiagnostic) && reportDiagnostic != ReportDiagnostic.Default ||
TryGetSeverityFromBulkConfiguration(descriptor, analyzerOptions, out reportDiagnostic))
{
Debug.Assert(reportDiagnostic != ReportDiagnostic.Default);
effectiveSeverity = reportDiagnostic;
}
}
return effectiveSeverity;
}
public static bool IsDefinedInEditorConfig(this DiagnosticDescriptor descriptor, AnalyzerConfigOptions analyzerConfigOptions)
{
// Check if the option is defined explicitly in the editorconfig
var diagnosticKey = $"{DotnetDiagnosticPrefix}.{descriptor.Id}.{SeveritySuffix}";
if (analyzerConfigOptions.TryGetValue(diagnosticKey, out var value) &&
EditorConfigSeverityStrings.TryParse(value, out var severity))
{
return true;
}
// Check if the option is defined as part of a bulk configuration
// Analyzer bulk configuration does not apply to:
// 1. Disabled by default diagnostics
// 2. Compiler diagnostics
// 3. Non-configurable diagnostics
if (!descriptor.IsEnabledByDefault ||
descriptor.ImmutableCustomTags().Any(static tag => tag is WellKnownDiagnosticTags.Compiler or WellKnownDiagnosticTags.NotConfigurable))
{
return false;
}
// If user has explicitly configured default severity for the diagnostic category, that should be respected.
// For example, 'dotnet_analyzer_diagnostic.category-security.severity = error'
var categoryBasedKey = $"{DotnetAnalyzerDiagnosticPrefix}.{CategoryPrefix}-{descriptor.Category}.{SeveritySuffix}";
if (analyzerConfigOptions.TryGetValue(categoryBasedKey, out value) &&
EditorConfigSeverityStrings.TryParse(value, out severity))
{
return true;
}
// Otherwise, if user has explicitly configured default severity for all analyzer diagnostics, that should be respected.
// For example, 'dotnet_analyzer_diagnostic.severity = error'
if (analyzerConfigOptions.TryGetValue(DotnetAnalyzerDiagnosticSeverityKey, out value) &&
EditorConfigSeverityStrings.TryParse(value, out severity))
{
return true;
}
// option not defined in editorconfig, assumed to be the default
return false;
}
public static ReportDiagnostic GetEffectiveSeverity(this DiagnosticDescriptor descriptor, AnalyzerConfigOptions analyzerConfigOptions)
{
// Check if the option is defined explicitly in the editorconfig
var diagnosticKey = $"{DotnetDiagnosticPrefix}.{descriptor.Id}.{SeveritySuffix}";
if (analyzerConfigOptions.TryGetValue(diagnosticKey, out var value) &&
EditorConfigSeverityStrings.TryParse(value, out var severity))
{
return severity;
}
// Check if the option is defined as part of a bulk configuration
// Analyzer bulk configuration does not apply to:
// 1. Disabled by default diagnostics
// 2. Compiler diagnostics
// 3. Non-configurable diagnostics
if (!descriptor.IsEnabledByDefault ||
descriptor.ImmutableCustomTags().Any(static tag => tag is WellKnownDiagnosticTags.Compiler or WellKnownDiagnosticTags.NotConfigurable))
{
return ReportDiagnostic.Default;
}
// If user has explicitly configured default severity for the diagnostic category, that should be respected.
// For example, 'dotnet_analyzer_diagnostic.category-security.severity = error'
var categoryBasedKey = $"{DotnetAnalyzerDiagnosticPrefix}.{CategoryPrefix}-{descriptor.Category}.{SeveritySuffix}";
if (analyzerConfigOptions.TryGetValue(categoryBasedKey, out value) &&
EditorConfigSeverityStrings.TryParse(value, out severity))
{
return severity;
}
// Otherwise, if user has explicitly configured default severity for all analyzer diagnostics, that should be respected.
// For example, 'dotnet_analyzer_diagnostic.severity = error'
if (analyzerConfigOptions.TryGetValue(DotnetAnalyzerDiagnosticSeverityKey, out value) &&
EditorConfigSeverityStrings.TryParse(value, out severity))
{
return severity;
}
// option not defined in editorconfig, assumed to be the default
return ReportDiagnostic.Default;
}
/// <summary>
/// Tries to get configured severity for the given <paramref name="descriptor"/>
/// from bulk configuration analyzer config options, i.e.
/// 'dotnet_analyzer_diagnostic.category-%RuleCategory%.severity = %severity%'
/// or
/// 'dotnet_analyzer_diagnostic.severity = %severity%'
/// Docs: https://docs.microsoft.com/visualstudio/code-quality/use-roslyn-analyzers?view=vs-2019#set-rule-severity-of-multiple-analyzer-rules-at-once-in-an-editorconfig-file for details
/// </summary>
private static bool TryGetSeverityFromBulkConfiguration(
DiagnosticDescriptor descriptor,
ImmutableDictionary<string, string> analyzerOptions,
out ReportDiagnostic severity)
{
// Analyzer bulk configuration does not apply to:
// 1. Disabled by default diagnostics
// 2. Compiler diagnostics
// 3. Non-configurable diagnostics
if (!descriptor.IsEnabledByDefault ||
descriptor.ImmutableCustomTags().Any(static tag => tag is WellKnownDiagnosticTags.Compiler or WellKnownDiagnosticTags.NotConfigurable))
{
severity = default;
return false;
}
// If user has explicitly configured default severity for the diagnostic category, that should be respected.
// For example, 'dotnet_analyzer_diagnostic.category-security.severity = error'
var categoryBasedKey = $"{DotnetAnalyzerDiagnosticPrefix}.{CategoryPrefix}-{descriptor.Category}.{SeveritySuffix}";
if (analyzerOptions.TryGetValue(categoryBasedKey, out var value) &&
EditorConfigSeverityStrings.TryParse(value, out severity))
{
return true;
}
// Otherwise, if user has explicitly configured default severity for all analyzer diagnostics, that should be respected.
// For example, 'dotnet_analyzer_diagnostic.severity = error'
if (analyzerOptions.TryGetValue(DotnetAnalyzerDiagnosticSeverityKey, out value) &&
EditorConfigSeverityStrings.TryParse(value, out severity))
{
return true;
}
severity = default;
return false;
}
public static bool IsCompilationEnd(this DiagnosticDescriptor descriptor)
=> descriptor.ImmutableCustomTags().Contains(WellKnownDiagnosticTags.CompilationEnd);
// TODO: the value stored in descriptor should already be valid URI (https://github.com/dotnet/roslyn/issues/59205)
internal static Uri? GetValidHelpLinkUri(this DiagnosticDescriptor descriptor)
=> Uri.TryCreate(descriptor.HelpLinkUri, UriKind.Absolute, out var uri) &&
(uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps) ? uri : null;
public static DiagnosticDescriptor WithMessageFormat(this DiagnosticDescriptor descriptor, LocalizableString messageFormat)
{
#pragma warning disable RS0030 // Do not used banned APIs - DiagnosticDescriptor .ctor is banned in this project, but fine to use here.
return new DiagnosticDescriptor(descriptor.Id, descriptor.Title, messageFormat,
descriptor.Category, descriptor.DefaultSeverity, descriptor.IsEnabledByDefault,
descriptor.Description, descriptor.HelpLinkUri, descriptor.CustomTags.ToArray());
#pragma warning restore RS0030 // Do not used banned APIs
}
}
}
|