File: src\RoslynAnalyzers\Utilities\Compiler\Options\AnalyzerOptionsExtensions.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.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using Analyzer.Utilities.Extensions;
using Analyzer.Utilities.Options;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
 
namespace Analyzer.Utilities
{
#if !TEST_UTILITIES
    public static partial class AnalyzerOptionsExtensions
#else
    internal static partial class AnalyzerOptionsExtensions
#endif
    {
        private static readonly ConditionalWeakTable<AnalyzerOptions, ICategorizedAnalyzerConfigOptions> s_cachedOptions = new();
        private static readonly ImmutableHashSet<OutputKind> s_defaultOutputKinds =
            ImmutableHashSet.CreateRange(Enum.GetValues(typeof(OutputKind)).Cast<OutputKind>());
 
        private static bool TryGetSyntaxTreeForOption(ISymbol symbol, [NotNullWhen(returnValue: true)] out SyntaxTree? tree)
        {
            switch (symbol.Kind)
            {
                case SymbolKind.Assembly:
                case SymbolKind.Namespace when ((INamespaceSymbol)symbol).IsGlobalNamespace:
                    tree = null;
                    return false;
                case SymbolKind.Parameter:
                    return TryGetSyntaxTreeForOption(symbol.ContainingSymbol, out tree);
                default:
                    tree = symbol.Locations[0].SourceTree;
                    return tree != null;
            }
        }
 
        public static SymbolVisibilityGroup GetSymbolVisibilityGroupOption(
            this AnalyzerOptions options,
            DiagnosticDescriptor rule,
            ISymbol symbol,
            Compilation compilation,
            SymbolVisibilityGroup defaultValue)
        => TryGetSyntaxTreeForOption(symbol, out var tree)
            ? options.GetSymbolVisibilityGroupOption(rule, tree, compilation, defaultValue)
            : defaultValue;
 
        private static SymbolVisibilityGroup GetSymbolVisibilityGroupOption(
            this AnalyzerOptions options,
            DiagnosticDescriptor rule,
            SyntaxTree tree,
            Compilation compilation,
            SymbolVisibilityGroup defaultValue)
            => options.GetFlagsEnumOptionValue(EditorConfigOptionNames.ApiSurface, rule, tree, compilation, defaultValue);
 
        private static SymbolModifiers GetRequiredModifiersOption(
            this AnalyzerOptions options,
            DiagnosticDescriptor rule,
            ISymbol symbol,
            Compilation compilation,
            SymbolModifiers defaultValue)
        => TryGetSyntaxTreeForOption(symbol, out var tree)
            ? options.GetRequiredModifiersOption(rule, tree, compilation, defaultValue)
            : defaultValue;
 
        private static SymbolModifiers GetRequiredModifiersOption(
            this AnalyzerOptions options,
            DiagnosticDescriptor rule,
            SyntaxTree tree,
            Compilation compilation,
            SymbolModifiers defaultValue)
            => options.GetFlagsEnumOptionValue(EditorConfigOptionNames.RequiredModifiers, rule, tree, compilation, defaultValue);
 
        public static EnumValuesPrefixTrigger GetEnumValuesPrefixTriggerOption(
            this AnalyzerOptions options,
            DiagnosticDescriptor rule,
            ISymbol symbol,
            Compilation compilation,
            EnumValuesPrefixTrigger defaultValue)
        => TryGetSyntaxTreeForOption(symbol, out var tree)
            ? options.GetEnumValuesPrefixTriggerOption(rule, tree, compilation, defaultValue)
            : defaultValue;
 
        private static EnumValuesPrefixTrigger GetEnumValuesPrefixTriggerOption(
            this AnalyzerOptions options,
            DiagnosticDescriptor rule,
            SyntaxTree tree,
            Compilation compilation,
            EnumValuesPrefixTrigger defaultValue)
            => options.GetNonFlagsEnumOptionValue(EditorConfigOptionNames.EnumValuesPrefixTrigger, rule, tree, compilation, defaultValue);
 
        public static ImmutableHashSet<OutputKind> GetOutputKindsOption(
            this AnalyzerOptions options,
            DiagnosticDescriptor rule,
            SyntaxTree tree,
            Compilation compilation)
            => options.GetOutputKindsOption(rule, tree, compilation, s_defaultOutputKinds);
 
        public static ImmutableHashSet<OutputKind> GetOutputKindsOption(
            this AnalyzerOptions options,
            DiagnosticDescriptor rule,
            SyntaxTree tree,
            Compilation compilation,
            ImmutableHashSet<OutputKind> defaultValue)
            => options.GetNonFlagsEnumOptionValue(EditorConfigOptionNames.OutputKind, rule, tree, compilation, defaultValue);
 
        public static ImmutableHashSet<SymbolKind> GetAnalyzedSymbolKindsOption(
            this AnalyzerOptions options,
            DiagnosticDescriptor rule,
            ISymbol symbol,
            Compilation compilation,
            ImmutableHashSet<SymbolKind> defaultSymbolKinds)
        => TryGetSyntaxTreeForOption(symbol, out var tree)
            ? options.GetAnalyzedSymbolKindsOption(rule, tree, compilation, defaultSymbolKinds)
            : defaultSymbolKinds;
 
        private static ImmutableHashSet<SymbolKind> GetAnalyzedSymbolKindsOption(
            this AnalyzerOptions options,
            DiagnosticDescriptor rule,
            SyntaxTree tree,
            Compilation compilation,
            ImmutableHashSet<SymbolKind> defaultSymbolKinds)
            => options.GetNonFlagsEnumOptionValue(EditorConfigOptionNames.AnalyzedSymbolKinds, rule, tree, compilation, defaultSymbolKinds);
 
        private static TEnum GetFlagsEnumOptionValue<TEnum>(
            this AnalyzerOptions options,
            string optionName,
            DiagnosticDescriptor rule,
            SyntaxTree tree,
            Compilation compilation,
            TEnum defaultValue)
            where TEnum : struct
        {
            var analyzerConfigOptions = options.GetOrComputeCategorizedAnalyzerConfigOptions(compilation);
            return analyzerConfigOptions.GetOptionValue(
                optionName, tree, rule,
                tryParseValue: static (string value, out TEnum result) => Enum.TryParse(value, ignoreCase: true, result: out result),
                defaultValue: defaultValue);
        }
 
        private static ImmutableHashSet<TEnum> GetNonFlagsEnumOptionValue<TEnum>(
            this AnalyzerOptions options,
            string optionName,
            DiagnosticDescriptor rule,
            SyntaxTree tree,
            Compilation compilation,
            ImmutableHashSet<TEnum> defaultValue)
            where TEnum : struct
        {
            var analyzerConfigOptions = options.GetOrComputeCategorizedAnalyzerConfigOptions(compilation);
            return analyzerConfigOptions.GetOptionValue(optionName, tree, rule, TryParseValue, defaultValue);
            static bool TryParseValue(string value, out ImmutableHashSet<TEnum> result)
            {
                var builder = ImmutableHashSet.CreateBuilder<TEnum>();
                foreach (var kindStr in value.Split(','))
                {
                    if (Enum.TryParse(kindStr, ignoreCase: true, result: out TEnum kind))
                    {
                        builder.Add(kind);
                    }
                }
 
                result = builder.ToImmutable();
                return builder.Count > 0;
            }
        }
 
        private static TEnum GetNonFlagsEnumOptionValue<TEnum>(
            this AnalyzerOptions options,
            string optionName,
            DiagnosticDescriptor rule,
            SyntaxTree tree,
            Compilation compilation,
            TEnum defaultValue)
            where TEnum : struct
            => GetFlagsEnumOptionValue(options, optionName, rule, tree, compilation, defaultValue);
 
        public static bool GetBoolOptionValue(
            this AnalyzerOptions options,
            string optionName,
            DiagnosticDescriptor rule,
            ISymbol symbol,
            Compilation compilation,
            bool defaultValue)
        => TryGetSyntaxTreeForOption(symbol, out var tree)
            ? options.GetBoolOptionValue(optionName, rule, tree, compilation, defaultValue)
            : defaultValue;
 
        public static bool GetBoolOptionValue(
            this AnalyzerOptions options,
            string optionName,
            DiagnosticDescriptor? rule,
            SyntaxTree tree,
            Compilation compilation,
            bool defaultValue)
        {
            var analyzerConfigOptions = options.GetOrComputeCategorizedAnalyzerConfigOptions(compilation);
            return analyzerConfigOptions.GetOptionValue(optionName, tree, rule, bool.TryParse, defaultValue);
        }
 
        public static uint GetUnsignedIntegralOptionValue(
            this AnalyzerOptions options,
            string optionName,
            DiagnosticDescriptor rule,
            SyntaxTree tree,
            Compilation compilation,
            uint defaultValue)
        {
            var analyzerConfigOptions = options.GetOrComputeCategorizedAnalyzerConfigOptions(compilation);
            return analyzerConfigOptions.GetOptionValue(optionName, tree, rule, uint.TryParse, defaultValue);
        }
 
        public static string GetStringOptionValue(
            this AnalyzerOptions options,
            string optionName,
            DiagnosticDescriptor rule,
            SyntaxTree tree,
            Compilation compilation)
        {
            var analyzerConfigOptions = options.GetOrComputeCategorizedAnalyzerConfigOptions(compilation);
            return analyzerConfigOptions.GetOptionValue(optionName, tree, rule, TryParseValue, string.Empty);
 
            static bool TryParseValue(string value, out string result)
            {
                result = value;
                return !string.IsNullOrEmpty(value);
            }
        }
 
        public static SymbolNamesWithValueOption<Unit> GetNullCheckValidationMethodsOption(
            this AnalyzerOptions options,
            DiagnosticDescriptor rule,
            SyntaxTree tree,
            Compilation compilation)
            => options.GetSymbolNamesWithValueOption<Unit>(EditorConfigOptionNames.NullCheckValidationMethods, rule, tree, compilation, static name => new SymbolNamesWithValueOption<Unit>.NameParts(name, Unit.Default), namePrefix: "M:");
 
        public static SymbolNamesWithValueOption<Unit> GetAdditionalStringFormattingMethodsOption(
            this AnalyzerOptions options,
            DiagnosticDescriptor rule,
            SyntaxTree tree,
            Compilation compilation)
            => options.GetSymbolNamesWithValueOption<Unit>(EditorConfigOptionNames.AdditionalStringFormattingMethods, rule, tree, compilation, static name => new SymbolNamesWithValueOption<Unit>.NameParts(name, Unit.Default), namePrefix: "M:");
 
        public static bool IsConfiguredToSkipAnalysis(
            this AnalyzerOptions options,
            DiagnosticDescriptor rule,
            ISymbol symbol,
            Compilation compilation)
            => options.IsConfiguredToSkipAnalysis(rule, symbol, symbol, compilation);
 
        public static bool IsConfiguredToSkipAnalysis(
            this AnalyzerOptions options,
            DiagnosticDescriptor rule,
            ISymbol symbol,
            ISymbol containingContextSymbol,
            Compilation compilation)
        {
            var excludedSymbols = GetExcludedSymbolNamesWithValueOption(options, rule, containingContextSymbol, compilation);
            var excludedTypeNamesWithDerivedTypes = GetExcludedTypeNamesWithDerivedTypesOption(options, rule, containingContextSymbol, compilation);
            if (excludedSymbols.IsEmpty && excludedTypeNamesWithDerivedTypes.IsEmpty)
            {
                return false;
            }
 
            while (symbol != null)
            {
                if (excludedSymbols.Contains(symbol))
                {
                    return true;
                }
 
                if (symbol is INamedTypeSymbol namedType && !excludedTypeNamesWithDerivedTypes.IsEmpty)
                {
                    foreach (var type in namedType.GetBaseTypesAndThis())
                    {
                        if (excludedTypeNamesWithDerivedTypes.Contains(type))
                        {
                            return true;
                        }
                    }
                }
 
                symbol = symbol.ContainingSymbol;
            }
 
            return false;
 
            static SymbolNamesWithValueOption<Unit> GetExcludedSymbolNamesWithValueOption(
                AnalyzerOptions options,
                DiagnosticDescriptor rule,
                ISymbol symbol,
                Compilation compilation)
                => TryGetSyntaxTreeForOption(symbol, out var tree)
                    ? options.GetSymbolNamesWithValueOption<Unit>(EditorConfigOptionNames.ExcludedSymbolNames, rule, tree, compilation, static name => new SymbolNamesWithValueOption<Unit>.NameParts(name, Unit.Default))
                    : SymbolNamesWithValueOption<Unit>.Empty;
 
            static SymbolNamesWithValueOption<Unit> GetExcludedTypeNamesWithDerivedTypesOption(
                AnalyzerOptions options,
                DiagnosticDescriptor rule,
                ISymbol symbol,
                Compilation compilation)
                => TryGetSyntaxTreeForOption(symbol, out var tree)
                    ? options.GetSymbolNamesWithValueOption<Unit>(EditorConfigOptionNames.ExcludedTypeNamesWithDerivedTypes, rule, tree, compilation, static name => new SymbolNamesWithValueOption<Unit>.NameParts(name, Unit.Default), namePrefix: "T:")
                    : SymbolNamesWithValueOption<Unit>.Empty;
        }
 
        public static SymbolNamesWithValueOption<Unit> GetDisallowedSymbolNamesWithValueOption(
            this AnalyzerOptions options,
            DiagnosticDescriptor rule,
            ISymbol symbol,
            Compilation compilation)
        => options.GetDisallowedSymbolNamesWithValueOption(rule, symbol.Locations[0].SourceTree, compilation);
 
        private static SymbolNamesWithValueOption<Unit> GetDisallowedSymbolNamesWithValueOption(
            this AnalyzerOptions options,
            DiagnosticDescriptor rule,
            SyntaxTree? tree,
            Compilation compilation)
            => options.GetSymbolNamesWithValueOption<Unit>(EditorConfigOptionNames.DisallowedSymbolNames, rule, tree, compilation, static name => new SymbolNamesWithValueOption<Unit>.NameParts(name, Unit.Default));
 
        public static SymbolNamesWithValueOption<string?> GetAdditionalRequiredSuffixesOption(
            this AnalyzerOptions options,
            DiagnosticDescriptor rule,
            ISymbol symbol,
            Compilation compilation)
        => options.GetAdditionalRequiredSuffixesOption(rule, symbol.Locations[0].SourceTree, compilation);
 
        private static SymbolNamesWithValueOption<string?> GetAdditionalRequiredSuffixesOption(
            this AnalyzerOptions options,
            DiagnosticDescriptor rule,
            SyntaxTree? tree,
            Compilation compilation)
        {
            return options.GetSymbolNamesWithValueOption(EditorConfigOptionNames.AdditionalRequiredSuffixes, rule, tree, compilation, getTypeAndSuffixFunc: GetParts, namePrefix: "T:");
 
            static SymbolNamesWithValueOption<string?>.NameParts GetParts(string name)
            {
                var split = name.Split(new[] { "->" }, StringSplitOptions.RemoveEmptyEntries);
 
                // If we don't find exactly one '->', we assume that there is no given suffix.
                if (split.Length != 2)
                {
                    return new SymbolNamesWithValueOption<string?>.NameParts(name, null);
                }
 
                // Note that we do not validate if the suffix will give a valid class name.
                var trimmedSuffix = split[1].Trim();
 
                // Check if the given suffix is the special suffix symbol "{[ ]*?}" (opening curly brace '{', 0..N spaces and a closing curly brace '}')
                if (trimmedSuffix.Length >= 2 &&
                    trimmedSuffix[0] == '{' &&
                    trimmedSuffix[^1] == '}')
                {
                    for (int i = 1; i < trimmedSuffix.Length - 2; i++)
                    {
                        if (trimmedSuffix[i] != ' ')
                        {
                            return new SymbolNamesWithValueOption<string?>.NameParts(split[0], trimmedSuffix);
                        }
                    }
 
                    // Replace the special empty suffix symbol by an empty string
                    return new SymbolNamesWithValueOption<string?>.NameParts(split[0], string.Empty);
                }
 
                return new SymbolNamesWithValueOption<string?>.NameParts(split[0], trimmedSuffix);
            }
        }
 
        public static SymbolNamesWithValueOption<INamedTypeSymbol?> GetAdditionalRequiredGenericInterfaces(
            this AnalyzerOptions options,
            DiagnosticDescriptor rule,
            ISymbol symbol,
            Compilation compilation)
        => options.GetAdditionalRequiredGenericInterfaces(rule, symbol.Locations[0].SourceTree, compilation);
 
        private static SymbolNamesWithValueOption<INamedTypeSymbol?> GetAdditionalRequiredGenericInterfaces(
            this AnalyzerOptions options,
            DiagnosticDescriptor rule,
            SyntaxTree? tree,
            Compilation compilation)
        {
            return options.GetSymbolNamesWithValueOption(EditorConfigOptionNames.AdditionalRequiredGenericInterfaces, rule, tree, compilation, getTypeAndSuffixFunc: x => GetParts(x, compilation), namePrefix: "T:");
 
            static SymbolNamesWithValueOption<INamedTypeSymbol?>.NameParts GetParts(string name, Compilation compilation)
            {
                var split = name.Split(new[] { "->" }, StringSplitOptions.RemoveEmptyEntries);
 
                // If we don't find exactly one '->', we assume that there is no given suffix.
                if (split.Length != 2)
                {
                    return new SymbolNamesWithValueOption<INamedTypeSymbol?>.NameParts(name, null);
                }
 
                var genericInterfaceFullName = split[1].Trim();
                if (!genericInterfaceFullName.StartsWith("T:", StringComparison.Ordinal))
                {
                    genericInterfaceFullName = $"T:{genericInterfaceFullName}";
                }
 
                var matchingSymbols = DocumentationCommentId.GetSymbolsForDeclarationId(genericInterfaceFullName, compilation);
 
                if (matchingSymbols.Length != 1 ||
                    matchingSymbols[0] is not INamedTypeSymbol namedType ||
                    namedType.TypeKind != TypeKind.Interface ||
                    !namedType.IsGenericType)
                {
                    // Invalid matching type so we assume there was no associated type
                    return new SymbolNamesWithValueOption<INamedTypeSymbol?>.NameParts(split[0], null);
                }
 
                return new SymbolNamesWithValueOption<INamedTypeSymbol?>.NameParts(split[0], namedType);
            }
        }
 
        public static SymbolNamesWithValueOption<Unit> GetInheritanceExcludedSymbolNamesOption(
            this AnalyzerOptions options,
            DiagnosticDescriptor rule,
            SyntaxTree tree,
            Compilation compilation,
            string defaultForcedValue)
            => options.GetSymbolNamesWithValueOption<Unit>(EditorConfigOptionNames.AdditionalInheritanceExcludedSymbolNames, rule, tree, compilation, static name => new SymbolNamesWithValueOption<Unit>.NameParts(name, Unit.Default), optionForcedValue: defaultForcedValue);
 
        public static SymbolNamesWithValueOption<Unit> GetAdditionalUseResultsMethodsOption(
            this AnalyzerOptions options,
            DiagnosticDescriptor rule,
            SyntaxTree tree,
            Compilation compilation)
            => options.GetSymbolNamesWithValueOption<Unit>(EditorConfigOptionNames.AdditionalUseResultsMethods, rule, tree, compilation, static name => new SymbolNamesWithValueOption<Unit>.NameParts(name, Unit.Default), namePrefix: "M:");
 
        public static SymbolNamesWithValueOption<Unit> GetEnumerationMethodsOption(
            this AnalyzerOptions options,
            DiagnosticDescriptor rule,
            SyntaxTree tree,
            Compilation compilation)
            => options.GetSymbolNamesWithValueOption<Unit>(EditorConfigOptionNames.EnumerationMethods, rule, tree, compilation, static name => new SymbolNamesWithValueOption<Unit>.NameParts(name, Unit.Default), namePrefix: "M:");
 
        public static SymbolNamesWithValueOption<Unit> GetLinqChainMethodsOption(
            this AnalyzerOptions options,
            DiagnosticDescriptor rule,
            SyntaxTree tree,
            Compilation compilation)
            => options.GetSymbolNamesWithValueOption<Unit>(EditorConfigOptionNames.LinqChainMethods, rule, tree, compilation, static name => new SymbolNamesWithValueOption<Unit>.NameParts(name, Unit.Default), namePrefix: "M:");
 
        private static SymbolNamesWithValueOption<TValue> GetSymbolNamesWithValueOption<TValue>(
            this AnalyzerOptions options,
            string optionName,
            DiagnosticDescriptor rule,
            SyntaxTree? tree,
            Compilation compilation,
            Func<string, SymbolNamesWithValueOption<TValue>.NameParts> getTypeAndSuffixFunc,
            string? namePrefix = null,
            string? optionDefaultValue = null,
            string? optionForcedValue = null)
        {
            var analyzerConfigOptions = options.GetOrComputeCategorizedAnalyzerConfigOptions(compilation);
            return analyzerConfigOptions.GetOptionValue(
                optionName,
                tree,
                rule,
                TryParse,
                (compilation, getTypeAndSuffixFunc, namePrefix, optionForcedValue),
                defaultValue: GetDefaultValue());
 
            // Local functions.
            static bool TryParse(string s, (Compilation compilation, Func<string, SymbolNamesWithValueOption<TValue>.NameParts> getTypeAndSuffixFunc, string? namePrefix, string? optionForcedValue) arg, out SymbolNamesWithValueOption<TValue> option)
            {
                var optionValue = s;
 
                if (!RoslynString.IsNullOrEmpty(arg.optionForcedValue) &&
                    (optionValue == null || !optionValue.Contains(arg.optionForcedValue, StringComparison.Ordinal)))
                {
                    optionValue = $"{arg.optionForcedValue}|{optionValue}";
                }
 
                if (string.IsNullOrEmpty(optionValue))
                {
                    option = SymbolNamesWithValueOption<TValue>.Empty;
                    return false;
                }
 
                var names = optionValue.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries).ToImmutableArray();
                option = SymbolNamesWithValueOption<TValue>.Create(names, arg.compilation, arg.namePrefix, arg.getTypeAndSuffixFunc);
                return true;
            }
 
            SymbolNamesWithValueOption<TValue> GetDefaultValue()
            {
                string optionValue = string.Empty;
 
                if (!string.IsNullOrEmpty(optionDefaultValue))
                {
                    RoslynDebug.Assert(optionDefaultValue != null);
                    optionValue = optionDefaultValue;
                }
 
                if (!RoslynString.IsNullOrEmpty(optionForcedValue) &&
                    (optionValue == null || !optionValue.Contains(optionForcedValue, StringComparison.Ordinal)))
                {
                    optionValue = $"{optionForcedValue}|{optionValue}";
                }
 
                RoslynDebug.Assert(optionValue != null);
 
                return TryParse(optionValue, (compilation, getTypeAndSuffixFunc, namePrefix, optionForcedValue), out var option)
                    ? option
                    : SymbolNamesWithValueOption<TValue>.Empty;
            }
        }
 
        public static string? GetMSBuildPropertyValue(
            this AnalyzerOptions options,
            string optionName,
            Compilation compilation)
        {
            MSBuildPropertyOptionNamesHelpers.VerifySupportedPropertyOptionName(optionName);
 
            // MSBuild property values should be set at compilation level, and cannot have different values per-tree.
            // So, we default to first syntax tree.
            if (compilation.SyntaxTrees.FirstOrDefault() is not { } tree)
            {
                return null;
            }
 
            var analyzerConfigOptions = options.GetOrComputeCategorizedAnalyzerConfigOptions(compilation);
            return analyzerConfigOptions.GetOptionValue(optionName, tree, rule: null,
                tryParseValue: static (string value, out string? result) =>
                {
                    result = value;
                    return true;
                },
                defaultValue: null, OptionKind.BuildProperty);
        }
 
        public static ImmutableArray<string> GetMSBuildItemMetadataValues(
            this AnalyzerOptions options,
            string itemOptionName,
            Compilation compilation)
        {
            MSBuildItemOptionNamesHelpers.VerifySupportedItemOptionName(itemOptionName);
 
            // MSBuild property values should be set at compilation level, and cannot have different values per-tree.
            // So, we default to first syntax tree.
            if (compilation.SyntaxTrees.FirstOrDefault() is not { } tree)
            {
                return ImmutableArray<string>.Empty;
            }
 
            var propertyOptionName = MSBuildItemOptionNamesHelpers.GetPropertyNameForItemOptionName(itemOptionName);
            var analyzerConfigOptions = options.GetOrComputeCategorizedAnalyzerConfigOptions(compilation);
            var propertyValue = analyzerConfigOptions.GetOptionValue(propertyOptionName, tree, rule: null,
                tryParseValue: static (string value, out string? result) =>
                {
                    result = value;
                    return true;
                },
                defaultValue: null, OptionKind.BuildProperty);
            return MSBuildItemOptionNamesHelpers.ParseItemOptionValue(propertyValue);
        }
 
        /// <summary>
        /// Returns true if the given source symbol has required visibility based on options:
        ///   1. If user has explicitly configured candidate <see cref="SymbolVisibilityGroup"/> in editor config options and
        ///      given symbol's visibility is one of the candidate visibilities.
        ///   2. Otherwise, if user has not configured visibility, and given symbol's visibility
        ///      matches the given default symbol visibility.
        /// </summary>
        public static bool MatchesConfiguredVisibility(
            this AnalyzerOptions options,
            DiagnosticDescriptor rule,
            ISymbol symbol,
            Compilation compilation,
            SymbolVisibilityGroup defaultRequiredVisibility = SymbolVisibilityGroup.Public)
            => options.MatchesConfiguredVisibility(rule, symbol, symbol, compilation, defaultRequiredVisibility);
 
        /// <summary>
        /// Returns true if the given symbol has required visibility based on options in context of the given containing symbol:
        ///   1. If user has explicitly configured candidate <see cref="SymbolVisibilityGroup"/> in editor config options and
        ///      given symbol's visibility is one of the candidate visibilities.
        ///   2. Otherwise, if user has not configured visibility, and given symbol's visibility
        ///      matches the given default symbol visibility.
        /// </summary>
        public static bool MatchesConfiguredVisibility(
            this AnalyzerOptions options,
            DiagnosticDescriptor rule,
            ISymbol symbol,
            ISymbol containingContextSymbol,
            Compilation compilation,
            SymbolVisibilityGroup defaultRequiredVisibility = SymbolVisibilityGroup.Public)
        {
            var allowedVisibilities = options.GetSymbolVisibilityGroupOption(rule, containingContextSymbol, compilation, defaultRequiredVisibility);
            return allowedVisibilities == SymbolVisibilityGroup.All ||
                allowedVisibilities.Contains(symbol.GetResultantVisibility());
        }
 
        /// <summary>
        /// Returns true if the given symbol has required symbol modifiers based on options:
        ///   1. If user has explicitly configured candidate <see cref="SymbolModifiers"/> in editor config options and
        ///      given symbol has all the required modifiers.
        ///   2. Otherwise, if user has not configured modifiers.
        /// </summary>
        public static bool MatchesConfiguredModifiers(
            this AnalyzerOptions options,
            DiagnosticDescriptor rule,
            ISymbol symbol,
            Compilation compilation,
            SymbolModifiers defaultRequiredModifiers = SymbolModifiers.None)
        {
            var requiredModifiers = options.GetRequiredModifiersOption(rule, symbol, compilation, defaultRequiredModifiers);
            return symbol.GetSymbolModifiers().Contains(requiredModifiers);
        }
 
        private static ICategorizedAnalyzerConfigOptions GetOrComputeCategorizedAnalyzerConfigOptions(
            this AnalyzerOptions options, Compilation compilation)
        {
            // TryGetValue upfront to avoid allocating createValueCallback if the entry already exists.
            if (s_cachedOptions.TryGetValue(options, out var categorizedAnalyzerConfigOptions))
            {
                return categorizedAnalyzerConfigOptions;
            }
 
            // Fall back to a slow version of the method, which allocates both for the CreateValueCallback and for
            // capturing the Compilation argument.
            return GetOrComputeCategorizedAnalyzerConfigOptions_Slow(options, compilation);
 
            static ICategorizedAnalyzerConfigOptions GetOrComputeCategorizedAnalyzerConfigOptions_Slow(
                AnalyzerOptions options,
                Compilation compilation)
            {
                return s_cachedOptions.GetValue(
                    options,
                    options => AggregateCategorizedAnalyzerConfigOptions.Create(options.AnalyzerConfigOptionsProvider, compilation));
            }
        }
    }
}
#endif