// 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; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.CompilerServices; using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles; using Microsoft.CodeAnalysis.Options; namespace Microsoft.CodeAnalysis.Diagnostics; /// <summary> /// <see cref="AnalyzerConfigOptions"/> that memoize structured (parsed) form of certain complex options to avoid parsing them multiple times. /// Storages of these complex options may directly call the specialized getters to reuse the cached values. /// </summary> internal abstract class StructuredAnalyzerConfigOptions : AnalyzerConfigOptions, IOptionsReader { internal sealed class Implementation : StructuredAnalyzerConfigOptions { private readonly AnalyzerConfigOptions _options; private readonly Lazy<NamingStylePreferences> _lazyNamingStylePreferences; private readonly StructuredAnalyzerConfigOptions? _fallback; public Implementation(AnalyzerConfigOptions options, StructuredAnalyzerConfigOptions? fallback) { _options = options; _lazyNamingStylePreferences = new Lazy<NamingStylePreferences>(() => EditorConfigNamingStyleParser.ParseDictionary(_options)); _fallback = fallback; } public override bool TryGetValue(string key, [NotNullWhen(true)] out string? value) => _options.TryGetValue(key, out value) || _fallback?.TryGetValue(key, out value) == true; public override IEnumerable<string> Keys => _fallback == null ? _options.Keys : _options.Keys.Union(_fallback.Keys); public override NamingStylePreferences GetNamingStylePreferences() // Note: this is not equivallent to constructing NamingStylePreferences from merged key-value pair sets. // We look up the fallback naming style preferences only if there is no naming style preference in this set. // We do not mix the preferences from the two key-value pair sets. => _lazyNamingStylePreferences.Value is { IsEmpty: false } nonEmpty ? nonEmpty : _fallback?.GetNamingStylePreferences() ?? NamingStylePreferences.Empty; } public static readonly StructuredAnalyzerConfigOptions Empty = Create(new DictionaryAnalyzerConfigOptions(ImmutableDictionary<string, string>.Empty)); public abstract NamingStylePreferences GetNamingStylePreferences(); public static StructuredAnalyzerConfigOptions Create(AnalyzerConfigOptions options, StructuredAnalyzerConfigOptions? fallback = null) => new Implementation(options, fallback); public bool TryGetOption<T>(OptionKey2 optionKey, out T value) => this.TryGetEditorConfigOption(optionKey.Option, out value); public static bool TryGetStructuredOptions(AnalyzerConfigOptions configOptions, [NotNullWhen(true)] out StructuredAnalyzerConfigOptions? options) { if (configOptions is StructuredAnalyzerConfigOptions structuredOptions) { options = structuredOptions; return true; } #if CODE_STYLE if (TryGetCorrespondingCodeStyleInstance(configOptions, out options)) { return true; } #endif options = null; return false; } #if CODE_STYLE // StructuredAnalyzerConfigOptions is defined in both Workspace and Code Style layers. It is not public and thus can't be shared between these two. // However, Code Style layer is compiled against the shared Workspace APIs. The ProjectState creates and holds onto an instance // of Workspace layer's version of StructuredAnalyzerConfigOptions. This version of the type is not directly usable by Code Style code. // We create a clone of this instance typed to the Code Style's version of StructuredAnalyzerConfigOptions. // The conditional weak table maintains 1:1 correspondence between these instances. // // In addition, we also map Compiler created DictionaryAnalyzerConfigOptions to StructuredAnalyzerConfigOptions for analyzers that are invoked // from command line build. private static readonly ConditionalWeakTable<AnalyzerConfigOptions, StructuredAnalyzerConfigOptions> s_codeStyleStructuredOptions = new(); private static readonly object s_codeStyleStructuredOptionsLock = new(); private static bool TryGetCorrespondingCodeStyleInstance(AnalyzerConfigOptions configOptions, [NotNullWhen(true)] out StructuredAnalyzerConfigOptions? options) { if (s_codeStyleStructuredOptions.TryGetValue(configOptions, out options)) { return true; } lock (s_codeStyleStructuredOptionsLock) { if (!s_codeStyleStructuredOptions.TryGetValue(configOptions, out options)) { options = new Implementation(configOptions, fallback: Empty); s_codeStyleStructuredOptions.Add(configOptions, options); } } return true; } #endif } |