|
// 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.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis;
using Roslyn.Utilities;
namespace Analyzer.Utilities
{
using static CategorizedAnalyzerConfigOptionsExtensions;
internal abstract class AbstractCategorizedAnalyzerConfigOptions : ICategorizedAnalyzerConfigOptions
{
private const string DotnetCodeQualityKeyPrefix = "dotnet_code_quality.";
private const string BuildPropertyKeyPrefix = "build_property.";
private readonly ConcurrentDictionary<OptionKey, (bool found, object? value)> _computedOptionValuesMap;
protected AbstractCategorizedAnalyzerConfigOptions()
{
_computedOptionValuesMap = new ConcurrentDictionary<OptionKey, (bool found, object? value)>();
}
public abstract bool IsEmpty { get; }
protected abstract bool TryGetOptionValue(string optionKeyPrefix, string? optionKeySuffix, string optionName, [NotNullWhen(returnValue: true)] out string? valueString);
public T GetOptionValue<T>(string optionName, SyntaxTree? tree, DiagnosticDescriptor? rule, TryParseValue<T> tryParseValue, T defaultValue, OptionKind kind = OptionKind.DotnetCodeQuality)
{
if (TryGetOptionValue(
optionName,
kind,
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, rule, tryParseValue, arg, defaultValue, out var value))
{
return value;
}
return defaultValue;
}
private static string MapOptionKindToKeyPrefix(OptionKind optionKind)
=> optionKind switch
{
OptionKind.DotnetCodeQuality => DotnetCodeQualityKeyPrefix,
OptionKind.BuildProperty => BuildPropertyKeyPrefix,
_ => throw new NotImplementedException()
};
[PerformanceSensitive("https://github.com/dotnet/roslyn-analyzers/issues/4905", AllowCaptures = false)]
public bool TryGetOptionValue<T, TArg>(string optionName, OptionKind kind, DiagnosticDescriptor? rule, TryParseValue<T, TArg> tryParseValue, TArg arg, T defaultValue, out T value)
{
if (this.IsEmpty)
{
value = defaultValue;
return false;
}
var key = OptionKey.GetOrCreate(rule?.Id, optionName);
if (!_computedOptionValuesMap.TryGetValue(key, out var computedValue))
{
computedValue = _computedOptionValuesMap.GetOrAdd(key, ComputeOptionValue(optionName, kind, rule, tryParseValue, arg));
}
if (computedValue.found)
{
value = (T)computedValue.value!;
return true;
}
else
{
value = defaultValue;
return false;
}
}
private (bool found, object? value) ComputeOptionValue<T, TArg>(string optionName, OptionKind kind, DiagnosticDescriptor? rule, TryParseValue<T, TArg> tryParseValue, TArg arg)
{
var optionKeyPrefix = MapOptionKindToKeyPrefix(kind);
if (rule != null
&& (TryGetSpecificOptionValue(rule.Id, optionKeyPrefix, out T? optionValue)
|| TryGetSpecificOptionValue(rule.Category, optionKeyPrefix, out optionValue)
|| TryGetAnySpecificOptionValue(rule.CustomTags, optionKeyPrefix, out optionValue)))
{
return (true, optionValue);
}
if (TryGetGeneralOptionValue(optionKeyPrefix, out optionValue))
{
return (true, optionValue);
}
return (false, null);
// Local functions.
bool TryGetSpecificOptionValue(string specificOptionKey, string optionKeyPrefix, [MaybeNullWhen(false)] out T specificOptionValue)
{
if (TryGetOptionValue(optionKeyPrefix, specificOptionKey, optionName, out var valueString))
{
return tryParseValue(valueString, arg, out specificOptionValue);
}
specificOptionValue = default;
return false;
}
bool TryGetAnySpecificOptionValue(IEnumerable<string> specificOptionKeys, string optionKeyPrefix, [MaybeNullWhen(false)] out T specificOptionValue)
{
foreach (var specificOptionKey in specificOptionKeys)
{
if (TryGetSpecificOptionValue(specificOptionKey, optionKeyPrefix, out specificOptionValue))
{
return true;
}
}
specificOptionValue = default;
return false;
}
bool TryGetGeneralOptionValue(string optionKeyPrefix, [MaybeNullWhen(false)] out T generalOptionValue)
{
if (TryGetOptionValue(optionKeyPrefix, optionKeySuffix: null, optionName, out var valueString))
{
return tryParseValue(valueString, arg, out generalOptionValue);
}
generalOptionValue = default;
return false;
}
}
}
}
|