File: Options\DocumentOptionSet.cs
Web Access
Project: ..\..\..\src\Workspaces\Core\Portable\Microsoft.CodeAnalysis.Workspaces.csproj (Microsoft.CodeAnalysis.Workspaces)
// 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.
 
#pragma warning disable RS0030 // Do not used banned APIs: DocumentOptionSet, Option<T>, PerLanguageOption<T>
 
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles;
using Microsoft.CodeAnalysis.ErrorReporting;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Options
{
    /// <summary>
    /// An <see cref="OptionSet"/> that comes from <see cref="Document.GetOptionsAsync(System.Threading.CancellationToken)"/>. It behaves just like a normal
    /// <see cref="OptionSet"/> but remembers which language the <see cref="Document"/> is, so you don't have to
    /// pass that information redundantly when calling <see cref="GetOption{T}(PerLanguageOption{T})"/>.
    /// </summary>
    public sealed class DocumentOptionSet : OptionSet
    {
        private readonly OptionSet _underlyingOptions;
        private readonly StructuredAnalyzerConfigOptions? _configOptions;
        private readonly string _language;
 
        /// <summary>
        /// Cached internal values read from <see cref="_configOptions"/> or <see cref="_underlyingOptions"/>.
        /// </summary>
        private ImmutableDictionary<OptionKey, object?> _values;
 
        internal DocumentOptionSet(StructuredAnalyzerConfigOptions? configOptions, OptionSet underlyingOptions, string language)
            : this(configOptions, underlyingOptions, language, ImmutableDictionary<OptionKey, object?>.Empty)
        {
        }
 
        private DocumentOptionSet(StructuredAnalyzerConfigOptions? configOptions, OptionSet underlyingOptions, string language, ImmutableDictionary<OptionKey, object?> values)
        {
            _language = language;
            _configOptions = configOptions;
            _underlyingOptions = underlyingOptions;
            _values = values;
        }
 
        internal string Language => _language;
 
        [PerformanceSensitive("https://github.com/dotnet/roslyn/issues/30819", AllowLocks = false)]
        internal override object? GetInternalOptionValue(OptionKey optionKey)
        {
            // If we already know the document specific value, we're done
            if (_values.TryGetValue(optionKey, out var value))
            {
                return value;
            }
 
            if (TryGetAnalyzerConfigOption(optionKey, out value))
            {
                // Cache and return
                return ImmutableInterlocked.GetOrAdd(ref _values, optionKey, value);
            }
 
            // We don't have a document specific value, so forward
            return _underlyingOptions.GetInternalOptionValue(optionKey);
        }
 
        private bool TryGetAnalyzerConfigOption(OptionKey optionKey, out object? value)
        {
            if (_configOptions == null)
            {
                value = null;
                return false;
            }
 
            if (optionKey.Option is not IOption2 internallyDefinedOption)
            {
                value = null;
                return false;
            }
 
            // Naming style option is not public. We should not call this API internally.
            Contract.ThrowIfTrue(internallyDefinedOption.Type == typeof(NamingStylePreferences));
 
            if (!_configOptions.TryGetValue(internallyDefinedOption.Definition.ConfigName, out var stringValue))
            {
                value = null;
                return false;
            }
 
            // The option is in _configOptions so it must have editorconfig storage location:
            return internallyDefinedOption.Definition.Serializer.TryParse(stringValue, out value);
        }
 
        public T GetOption<T>(PerLanguageOption<T> option)
            => GetOption(option, _language);
 
        internal override OptionSet WithChangedOptionInternal(OptionKey optionKey, object? internalValue)
            => new DocumentOptionSet(_configOptions, _underlyingOptions, _language, _values.SetItem(optionKey, internalValue));
 
        /// <summary>
        /// Creates a new <see cref="DocumentOptionSet" /> that contains the changed value.
        /// </summary>
        public DocumentOptionSet WithChangedOption<T>(PerLanguageOption<T> option, T value)
            => (DocumentOptionSet)WithChangedOption(option, _language, value);
    }
}