File: DiagnosticAnalyzer\AnalyzerOptionsExtensions.cs
Web Access
Project: src\src\Compilers\Core\Portable\Microsoft.CodeAnalysis.csproj (Microsoft.CodeAnalysis)
// 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.Threading;
using Microsoft.CodeAnalysis.InternalUtilities;
 
namespace Microsoft.CodeAnalysis.Diagnostics
{
    internal static class AnalyzerOptionsExtensions
    {
        private const string DotnetAnalyzerDiagnosticPrefix = "dotnet_analyzer_diagnostic";
        private const string CategoryPrefix = "category";
        private const string SeveritySuffix = "severity";
 
        private const string DotnetAnalyzerDiagnosticSeverityKey = DotnetAnalyzerDiagnosticPrefix + "." + SeveritySuffix;
        private static readonly ConcurrentLruCache<string, string> s_categoryToSeverityKeyMap = new ConcurrentLruCache<string, string>(50);
 
        private static string GetCategoryBasedDotnetAnalyzerDiagnosticSeverityKey(string category)
            => s_categoryToSeverityKeyMap.GetOrAdd(category, category, static category => $"{DotnetAnalyzerDiagnosticPrefix}.{CategoryPrefix}-{category}.{SeveritySuffix}");
 
        /// <summary>
        /// Tries to get configured severity for the given <paramref name="descriptor"/>
        /// for the given <paramref name="tree"/> from bulk configuration analyzer config options, i.e.
        ///     'dotnet_analyzer_diagnostic.category-%RuleCategory%.severity = %severity%'
        ///         or
        ///     'dotnet_analyzer_diagnostic.severity = %severity%'
        /// </summary>
        public static bool TryGetSeverityFromBulkConfiguration(
            this AnalyzerOptions? analyzerOptions,
            SyntaxTree tree,
            Compilation compilation,
            DiagnosticDescriptor descriptor,
            CancellationToken cancellationToken,
            out ReportDiagnostic severity)
        {
            // Analyzer bulk configuration does not apply to:
            //  1. Disabled by default diagnostics
            //  2. Compiler diagnostics
            //  3. Non-configurable diagnostics
            //  4. Custom-configurable diagnostics
            if (analyzerOptions == null ||
                !descriptor.IsEnabledByDefault ||
                descriptor.IsCompilerOrNotConfigurableOrCustomConfigurable())
            {
                severity = default;
                return false;
            }
 
            // If user has explicitly configured severity for this diagnostic ID, that should be respected and
            // bulk configuration should not be applied.
            // For example, 'dotnet_diagnostic.CA1000.severity = error'
            if (compilation.Options.SpecificDiagnosticOptions.ContainsKey(descriptor.Id) ||
                compilation.Options.SyntaxTreeOptionsProvider?.TryGetDiagnosticValue(tree, descriptor.Id, cancellationToken, out _) == true ||
                compilation.Options.SyntaxTreeOptionsProvider?.TryGetGlobalDiagnosticValue(descriptor.Id, cancellationToken, out _) == true)
            {
                severity = default;
                return false;
            }
 
            var analyzerConfigOptions = analyzerOptions.AnalyzerConfigOptionsProvider.GetOptions(tree);
 
            // 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 = GetCategoryBasedDotnetAnalyzerDiagnosticSeverityKey(descriptor.Category);
            if (analyzerConfigOptions.TryGetValue(categoryBasedKey, out var value) &&
                AnalyzerConfigSet.TryParseSeverity(value, out severity))
            {
                // '/warnaserror' should bump Warning bulk configuration to Error.
                if (severity == ReportDiagnostic.Warn && compilation.Options.GeneralDiagnosticOption == ReportDiagnostic.Error)
                    severity = ReportDiagnostic.Error;
 
                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) &&
                AnalyzerConfigSet.TryParseSeverity(value, out severity))
            {
                // '/warnaserror' should bump Warning bulk configuration to Error.
                if (severity == ReportDiagnostic.Warn && compilation.Options.GeneralDiagnosticOption == ReportDiagnostic.Error)
                    severity = ReportDiagnostic.Error;
 
                return true;
            }
 
            severity = default;
            return false;
        }
    }
}