File: src\RoslynAnalyzers\Utilities\Compiler\Options\SyntaxTreeCategorizedAnalyzerConfigOptions.cs
Web Access
Project: src\src\RoslynAnalyzers\Roslyn.Diagnostics.Analyzers\Core\Roslyn.Diagnostics.Analyzers.csproj (Roslyn.Diagnostics.Analyzers)
// 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.
 
#if CODEANALYSIS_V3_OR_BETTER
 
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.CompilerServices;
using Microsoft.CodeAnalysis.Diagnostics;
 
namespace Analyzer.Utilities
{
    /// <summary>
    /// Analyzer configuration options for a given syntax tree from <see cref="AnalyzerConfigOptions"/>
    /// </summary>
    internal sealed class SyntaxTreeCategorizedAnalyzerConfigOptions : AbstractCategorizedAnalyzerConfigOptions
    {
        private readonly AnalyzerConfigOptions? _analyzerConfigOptions;
        private static readonly ConditionalWeakTable<ImmutableDictionary<string, string>, SyntaxTreeCategorizedAnalyzerConfigOptions> s_perTreeOptionsCache = new();
 
        public static readonly SyntaxTreeCategorizedAnalyzerConfigOptions Empty = new(analyzerConfigOptions: null);
 
        private SyntaxTreeCategorizedAnalyzerConfigOptions(AnalyzerConfigOptions? analyzerConfigOptions)
        {
            _analyzerConfigOptions = analyzerConfigOptions;
        }
 
        public static SyntaxTreeCategorizedAnalyzerConfigOptions Create(AnalyzerConfigOptions? analyzerConfigOptions)
        {
            if (analyzerConfigOptions == null)
            {
                return Empty;
            }
 
            var optionsMap = TryGetBackingOptionsDictionary(analyzerConfigOptions);
            if (optionsMap == null)
            {
                return new SyntaxTreeCategorizedAnalyzerConfigOptions(analyzerConfigOptions);
            }
 
            if (optionsMap.IsEmpty)
            {
                return Empty;
            }
 
            return s_perTreeOptionsCache.GetValue(optionsMap, _ => new SyntaxTreeCategorizedAnalyzerConfigOptions(analyzerConfigOptions));
 
            // Local functions.
            static ImmutableDictionary<string, string>? TryGetBackingOptionsDictionary(AnalyzerConfigOptions analyzerConfigOptions)
            {
                // Reflection based optimization for analyzer config options.
                // Ideally 'AnalyzerConfigOptions' would expose such an the API.
                var type = analyzerConfigOptions.GetType();
                const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic;
                return type.GetField("_backing", flags)?.GetValue(analyzerConfigOptions) as ImmutableDictionary<string, string> ??
                    type.GetField("_analyzerOptions", flags)?.GetValue(analyzerConfigOptions) as ImmutableDictionary<string, string>;
            }
        }
 
        public override bool IsEmpty => ReferenceEquals(this, Empty);
 
        protected override bool TryGetOptionValue(string optionKeyPrefix, string? optionKeySuffix, string optionName, [NotNullWhen(returnValue: true)] out string? valueString)
        {
            if (IsEmpty)
            {
                valueString = null;
                return false;
            }
 
            RoslynDebug.Assert(_analyzerConfigOptions != null);
            var key = optionKeyPrefix;
            if (optionKeySuffix != null)
            {
                key += $"{optionKeySuffix}.";
            }
 
            key += optionName;
 
            return _analyzerConfigOptions.TryGetValue(key, out valueString);
        }
    }
}
 
#endif