File: src\RoslynAnalyzers\Utilities\Compiler\Options\AggregateCategorizedAnalyzerConfigOptions.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;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Analyzer.Utilities.PooledObjects;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
 
namespace Analyzer.Utilities
{
    using static CategorizedAnalyzerConfigOptionsExtensions;
 
    /// <summary>
    /// Aggregate analyzer configuration options:
    ///
    /// <list type="number">
    /// <item><description>Per syntax tree options from <see cref="AnalyzerConfigOptionsProvider"/>.</description></item>
    /// <item><description>Options from an <strong>.editorconfig</strong> file passed in as an additional file (back compat).</description></item>
    /// </list>
    ///
    /// <inheritdoc cref="ICategorizedAnalyzerConfigOptions"/>
    /// </summary>
    internal sealed class AggregateCategorizedAnalyzerConfigOptions : ICategorizedAnalyzerConfigOptions
    {
        public static readonly AggregateCategorizedAnalyzerConfigOptions Empty = new(
            globalOptions: null,
            ImmutableDictionary<SyntaxTree, Lazy<SyntaxTreeCategorizedAnalyzerConfigOptions>>.Empty);
 
        private readonly Lazy<SyntaxTreeCategorizedAnalyzerConfigOptions>? _globalOptions;
        private readonly ImmutableDictionary<SyntaxTree, Lazy<SyntaxTreeCategorizedAnalyzerConfigOptions>> _perTreeOptions;
 
        private AggregateCategorizedAnalyzerConfigOptions(Lazy<SyntaxTreeCategorizedAnalyzerConfigOptions>? globalOptions, ImmutableDictionary<SyntaxTree, Lazy<SyntaxTreeCategorizedAnalyzerConfigOptions>> perTreeOptions)
        {
            _globalOptions = globalOptions;
            _perTreeOptions = perTreeOptions;
        }
 
        public bool IsEmpty
        {
            get
            {
                Debug.Assert(ReferenceEquals(this, Empty) || !_perTreeOptions.IsEmpty);
                return ReferenceEquals(this, Empty);
            }
        }
 
        public static AggregateCategorizedAnalyzerConfigOptions Create(AnalyzerConfigOptionsProvider analyzerConfigOptionsProvider, Compilation compilation)
        {
            analyzerConfigOptionsProvider = analyzerConfigOptionsProvider ?? throw new ArgumentNullException(nameof(analyzerConfigOptionsProvider));
 
            if (analyzerConfigOptionsProvider.IsEmpty())
            {
                return Empty;
            }
 
            Lazy<SyntaxTreeCategorizedAnalyzerConfigOptions>? globalOptions;
#if CODEANALYSIS_V3_7_OR_BETTER
            globalOptions = new Lazy<SyntaxTreeCategorizedAnalyzerConfigOptions>(() => SyntaxTreeCategorizedAnalyzerConfigOptions.Create(analyzerConfigOptionsProvider.GlobalOptions));
#else
            globalOptions = null;
#endif
 
            var perTreeOptionsBuilder = PooledDictionary<SyntaxTree, Lazy<SyntaxTreeCategorizedAnalyzerConfigOptions>>.GetInstance();
            foreach (var tree in compilation.SyntaxTrees)
            {
                perTreeOptionsBuilder.Add(tree, new Lazy<SyntaxTreeCategorizedAnalyzerConfigOptions>(() => Create(tree, analyzerConfigOptionsProvider)));
            }
 
            return new AggregateCategorizedAnalyzerConfigOptions(globalOptions, perTreeOptionsBuilder.ToImmutableDictionaryAndFree());
 
            static SyntaxTreeCategorizedAnalyzerConfigOptions Create(SyntaxTree tree, AnalyzerConfigOptionsProvider analyzerConfigOptionsProvider)
            {
                var options = analyzerConfigOptionsProvider.GetOptions(tree);
                return SyntaxTreeCategorizedAnalyzerConfigOptions.Create(options);
            }
        }
 
        public T GetOptionValue<T>(string optionName, SyntaxTree? tree, DiagnosticDescriptor? rule, TryParseValue<T> tryParseValue, T defaultValue, OptionKind kind = OptionKind.DotnetCodeQuality)
        {
            if (TryGetOptionValue(
                optionName,
                kind,
                tree,
                rule,
                static (string s, TryParseValue<T> tryParseValue, [MaybeNullWhen(returnValue: false)] out T parsedValue) => tryParseValue(s, out parsedValue),
                tryParseValue,
                defaultValue,
                out var value))
            {
                return value;
            }
 
            return defaultValue;
        }
 
        public T GetOptionValue<T, TArg>(string optionName, SyntaxTree? tree, DiagnosticDescriptor? rule, TryParseValue<T, TArg> tryParseValue, TArg arg, T defaultValue, OptionKind kind = OptionKind.DotnetCodeQuality)
        {
            if (TryGetOptionValue(optionName, kind, tree, rule, tryParseValue, arg, defaultValue, out var value))
            {
                return value;
            }
 
            return defaultValue;
        }
 
        private bool TryGetOptionValue<T, TArg>(string optionName, OptionKind kind, SyntaxTree? tree, DiagnosticDescriptor? rule, TryParseValue<T, TArg> tryParseValue, TArg arg, T defaultValue, [MaybeNullWhen(false)] out T value)
        {
            value = defaultValue;
 
            if (ReferenceEquals(this, Empty))
            {
                return false;
            }
 
            if (tree is null)
            {
                if (_globalOptions is null)
                {
                    return false;
                }
 
                return _globalOptions.Value.TryGetOptionValue(optionName, kind, rule, tryParseValue, arg, defaultValue, out value);
            }
 
            return _perTreeOptions.TryGetValue(tree, out var lazyTreeOptions) &&
                lazyTreeOptions.Value.TryGetOptionValue(optionName, kind, rule, tryParseValue, arg, defaultValue, out value);
        }
    }
}
 
#endif