|
// 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
}
|