|
// 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.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.CodeAnalysis.Options;
namespace Microsoft.CodeAnalysis.Diagnostics
{
/// <summary>
/// Helper type to map <see cref="IDEDiagnosticIds"/> to an unique editorconfig code style option, if any,
/// such that diagnostic's severity can be configured in .editorconfig with an entry such as:
/// "%option_name% = %option_value%:%severity%
/// </summary>
internal static class IDEDiagnosticIdToOptionMappingHelper
{
private static readonly ConcurrentDictionary<string, ImmutableHashSet<IOption2>> s_diagnosticIdToOptionMap = new();
private static readonly ConcurrentDictionary<string, ConcurrentDictionary<string, ImmutableHashSet<IOption2>>> s_diagnosticIdToLanguageSpecificOptionsMap = new();
private static readonly ConcurrentDictionary<string, PerLanguageOption2<bool>> s_diagnosticIdToFadingOptionMap = new();
public static bool TryGetMappedOptions(string diagnosticId, string language, [NotNullWhen(true)] out ImmutableHashSet<IOption2>? options)
=> s_diagnosticIdToOptionMap.TryGetValue(diagnosticId, out options) ||
(s_diagnosticIdToLanguageSpecificOptionsMap.TryGetValue(language, out var map) &&
map.TryGetValue(diagnosticId, out options));
public static bool TryGetMappedFadingOption(string diagnosticId, [NotNullWhen(true)] out PerLanguageOption2<bool>? fadingOption)
=> s_diagnosticIdToFadingOptionMap.TryGetValue(diagnosticId, out fadingOption);
public static bool IsKnownIDEDiagnosticId(string diagnosticId)
=> s_diagnosticIdToOptionMap.ContainsKey(diagnosticId) ||
s_diagnosticIdToLanguageSpecificOptionsMap.Values.Any(map => map.ContainsKey(diagnosticId));
public static void AddOptionMapping(string diagnosticId, ImmutableHashSet<IOption2> options)
{
diagnosticId = diagnosticId ?? throw new ArgumentNullException(nameof(diagnosticId));
options = options ?? throw new ArgumentNullException(nameof(options));
var groups = options.GroupBy(o => o.IsPerLanguage);
var multipleLanguagesOptionsBuilder = ImmutableHashSet.CreateBuilder<IOption2>();
foreach (var group in groups)
{
if (group.Key == true)
{
foreach (var perLanguageValuedOption in group)
{
Debug.Assert(perLanguageValuedOption.IsPerLanguage);
multipleLanguagesOptionsBuilder.Add(perLanguageValuedOption);
}
}
else
{
var languageGroups = group.GroupBy(o => ((ISingleValuedOption)o).LanguageName);
foreach (var languageGroup in languageGroups)
{
var language = languageGroup.Key;
if (language is null)
{
foreach (var option in languageGroup)
{
multipleLanguagesOptionsBuilder.Add(option);
}
}
else
{
var map = s_diagnosticIdToLanguageSpecificOptionsMap.GetOrAdd(language, _ => new ConcurrentDictionary<string, ImmutableHashSet<IOption2>>());
AddOptionMapping(map, diagnosticId, languageGroup.ToImmutableHashSet());
}
}
}
}
if (multipleLanguagesOptionsBuilder.Count > 0)
{
AddOptionMapping(s_diagnosticIdToOptionMap, diagnosticId, multipleLanguagesOptionsBuilder.ToImmutableHashSet());
}
}
private static void AddOptionMapping(ConcurrentDictionary<string, ImmutableHashSet<IOption2>> map, string diagnosticId, ImmutableHashSet<IOption2> options)
{
// Verify that the option is either being added for the first time, or the existing option is already the same.
// Latter can happen in tests as we re-instantiate the analyzer for every test, which attempts to add the mapping every time.
Debug.Assert(!map.TryGetValue(diagnosticId, out var existingOptions) || options.SetEquals(existingOptions));
Debug.Assert(options.All(option => option.Definition.IsEditorConfigOption));
map.TryAdd(diagnosticId, options);
}
public static void AddFadingOptionMapping(string diagnosticId, PerLanguageOption2<bool> fadingOption)
{
diagnosticId = diagnosticId ?? throw new ArgumentNullException(nameof(diagnosticId));
fadingOption = fadingOption ?? throw new ArgumentNullException(nameof(fadingOption));
// Verify that the option is either being added for the first time, or the existing option is already the same.
// Latter can happen in tests as we re-instantiate the analyzer for every test, which attempts to add the mapping every time.
Debug.Assert(!s_diagnosticIdToFadingOptionMap.TryGetValue(diagnosticId, out var existingOption) || existingOption.Equals(fadingOption));
s_diagnosticIdToFadingOptionMap.TryAdd(diagnosticId, fadingOption);
}
}
}
|