| File: src\roslyn\src\Analyzers\Core\Analyzers\IDEDiagnosticIdToOptionMappingHelper.cs | Web Access |
| Project: src\roslyn\src\Features\Core\Portable\Microsoft.CodeAnalysis.Features.csproj (Microsoft.CodeAnalysis.Features) |
// 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.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(); 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 ImmutableHashSet<string> KnownIDEDiagnosticIds => [.. s_diagnosticIdToOptionMap.Keys, .. s_diagnosticIdToLanguageSpecificOptionsMap.Values.SelectMany(map => map.Keys)]; 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]); } } } } 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); } }