diff --git a/src/Compilers/Core/CodeAnalysisTest/Analyzers/DictionaryAnalyzerConfigOptionsTests.cs b/src/Compilers/Core/CodeAnalysisTest/Analyzers/DictionaryAnalyzerConfigOptionsTests.cs deleted file mode 100644 index 6e80809c66e6e..0000000000000 --- a/src/Compilers/Core/CodeAnalysisTest/Analyzers/DictionaryAnalyzerConfigOptionsTests.cs +++ /dev/null @@ -1,29 +0,0 @@ -// 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.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Diagnostics; -using Xunit; - -namespace Microsoft.CodeAnalysis.UnitTests -{ - public class DictionaryAnalyzerConfigOptionsTests - { - [Fact] - public void BackwardCompatibility() - { - // Note: Older versions of analyzers access this field via reflection. - // https://github.com/dotnet/roslyn/blob/8e3d62a30b833631baaa4e84c5892298f16a8c9e/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/EditorConfig/EditorConfigStorageLocationExtensions.cs#L21 - Assert.Equal( - typeof(ImmutableDictionary), - typeof(DictionaryAnalyzerConfigOptions).GetField("Options", BindingFlags.Instance | BindingFlags.NonPublic)?.FieldType); - } - } -} diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerConfigOptions.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerConfigOptions.cs index ea5f6b0944e4c..1e98c9cccb6ee 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerConfigOptions.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerConfigOptions.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; namespace Microsoft.CodeAnalysis.Diagnostics @@ -20,12 +19,5 @@ public abstract class AnalyzerConfigOptions /// Get an analyzer config value for the given key, using the . /// public abstract bool TryGetValue(string key, [NotNullWhen(true)] out string? value); - - /// - /// Enumerates unique keys of all available options in no specific order. - /// - /// Not implemented by the derived type. - public virtual IEnumerable Keys - => throw new NotImplementedException(); } } diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/DictionaryAnalyzerConfigOptions.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/DictionaryAnalyzerConfigOptions.cs index 48ac671a064ee..368b6c4176976 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/DictionaryAnalyzerConfigOptions.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/DictionaryAnalyzerConfigOptions.cs @@ -2,7 +2,6 @@ // 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.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; @@ -14,8 +13,6 @@ internal sealed class DictionaryAnalyzerConfigOptions : AnalyzerConfigOptions public static DictionaryAnalyzerConfigOptions Empty { get; } = new DictionaryAnalyzerConfigOptions(EmptyDictionary); - // Note: Do not rename. Older versions of analyzers access this field via reflection. - // https://github.com/dotnet/roslyn/blob/8e3d62a30b833631baaa4e84c5892298f16a8c9e/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/EditorConfig/EditorConfigStorageLocationExtensions.cs#L21 internal readonly ImmutableDictionary Options; public DictionaryAnalyzerConfigOptions(ImmutableDictionary options) @@ -23,8 +20,5 @@ public DictionaryAnalyzerConfigOptions(ImmutableDictionary optio public override bool TryGetValue(string key, [NotNullWhen(true)] out string? value) => Options.TryGetValue(key, out value); - - public override IEnumerable Keys - => Options.Keys; } } diff --git a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt index c2331cd21ec09..54e2766c89e34 100644 --- a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt @@ -66,7 +66,6 @@ const Microsoft.CodeAnalysis.WellKnownMemberNames.CheckedExplicitConversionName Microsoft.CodeAnalysis.OperationKind.UTF8String = 124 -> Microsoft.CodeAnalysis.OperationKind Microsoft.CodeAnalysis.Operations.IUTF8StringOperation Microsoft.CodeAnalysis.Operations.IUTF8StringOperation.Value.get -> string! -virtual Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptions.Keys.get -> System.Collections.Generic.IEnumerable! virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitUTF8String(Microsoft.CodeAnalysis.Operations.IUTF8StringOperation! operation) -> void virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitUTF8String(Microsoft.CodeAnalysis.Operations.IUTF8StringOperation! operation, TArgument argument) -> TResult? virtual Microsoft.CodeAnalysis.SymbolVisitor.DefaultVisit(Microsoft.CodeAnalysis.ISymbol! symbol, TArgument argument) -> TResult diff --git a/src/EditorFeatures/CSharpTest/Diagnostics/NamingStyles/EditorConfigNamingStyleParserTests.cs b/src/EditorFeatures/CSharpTest/Diagnostics/NamingStyles/EditorConfigNamingStyleParserTests.cs index 8fb3042f2c572..fff2e11b97eea 100644 --- a/src/EditorFeatures/CSharpTest/Diagnostics/NamingStyles/EditorConfigNamingStyleParserTests.cs +++ b/src/EditorFeatures/CSharpTest/Diagnostics/NamingStyles/EditorConfigNamingStyleParserTests.cs @@ -7,20 +7,17 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; -using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles; using Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics.NamingStyles; using Roslyn.Test.Utilities; using Xunit; +using static Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles.EditorConfigNamingStyleParser; using static Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles.SymbolSpecification; namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics.NamingStyles { public class EditorConfigNamingStyleParserTests { - private static NamingStylePreferences ParseDictionary(Dictionary options) - => EditorConfigNamingStyleParser.ParseDictionary(new DictionaryAnalyzerConfigOptions(options.ToImmutableDictionary())); - [Fact] public static void TestPascalCaseRule() { @@ -472,111 +469,5 @@ public static void TestEditorConfigParseForApplicableSymbolKinds() Assert.True(!string.IsNullOrEmpty(editorConfigString)); } } - - [Theory] - [InlineData("a", "b", "a", "public", "public, private")] - [InlineData("b", "a", "a", "public, private", "public")] - [InlineData("b", "a", "b", "public", "public, private")] - [InlineData("a", "b", "b", "public, private", "public")] - [InlineData("a", "b", "a", "*", "*")] - [InlineData("b", "a", "a", "*", "*")] - [InlineData("A", "b", "A", "*", "*")] - [InlineData("b", "A", "A", "*", "*")] - [InlineData("a", "B", "a", "*", "*")] - [InlineData("B", "a", "a", "*", "*")] - [InlineData("A", "B", "A", "*", "*")] - [InlineData("B", "A", "A", "*", "*")] - public static void TestOrderedByAccessibilityBeforeName(string firstName, string secondName, string firstNameAfterOrdering, string firstAccessibility, string secondAccessibility) - { - var namingStylePreferences = ParseDictionary(new Dictionary() - { - [$"dotnet_naming_rule.{firstName}.severity"] = "error", - [$"dotnet_naming_rule.{firstName}.symbols"] = "first_symbols", - [$"dotnet_naming_rule.{firstName}.style"] = $"{firstName}_style", - ["dotnet_naming_symbols.first_symbols.applicable_kinds"] = "method,property", - ["dotnet_naming_symbols.first_symbols.applicable_accessibilities"] = firstAccessibility, - [$"dotnet_naming_style.{firstName}_style.capitalization"] = "pascal_case", - [$"dotnet_naming_style.{secondName}_style.capitalization"] = "camel_case", - [$"dotnet_naming_rule.{secondName}.severity"] = "error", - [$"dotnet_naming_rule.{secondName}.symbols"] = "second_symbols", - [$"dotnet_naming_rule.{secondName}.style"] = $"{secondName}_style", - ["dotnet_naming_symbols.second_symbols.applicable_kinds"] = "method,property", - ["dotnet_naming_symbols.second_symbols.applicable_accessibilities"] = secondAccessibility, - }); - - var secondNameAfterOrdering = firstNameAfterOrdering == firstName ? secondName : firstName; - Assert.Equal($"{firstNameAfterOrdering}_style", namingStylePreferences.Rules.NamingRules[0].NamingStyle.Name); - Assert.Equal($"{secondNameAfterOrdering}_style", namingStylePreferences.Rules.NamingRules[1].NamingStyle.Name); - } - - [Theory] - [InlineData("a", "b", "a", "static, readonly", "static")] - [InlineData("b", "a", "a", "static", "static, readonly")] - [InlineData("b", "a", "b", "static, readonly", "static")] - [InlineData("a", "b", "b", "static", "static, readonly")] - [InlineData("a", "b", "a", "", "")] - [InlineData("b", "a", "a", "", "")] - [InlineData("A", "b", "A", "", "")] - [InlineData("b", "A", "A", "", "")] - [InlineData("a", "B", "a", "", "")] - [InlineData("B", "a", "a", "", "")] - [InlineData("A", "B", "A", "", "")] - [InlineData("B", "A", "A", "", "")] - public static void TestOrderedByModifiersBeforeName(string firstName, string secondName, string firstNameAfterOrdering, string firstModifiers, string secondModifiers) - { - var namingStylePreferences = ParseDictionary(new Dictionary() - { - [$"dotnet_naming_rule.{firstName}.severity"] = "error", - [$"dotnet_naming_rule.{firstName}.symbols"] = "first_symbols", - [$"dotnet_naming_rule.{firstName}.style"] = $"{firstName}_style", - ["dotnet_naming_symbols.first_symbols.applicable_kinds"] = "method,property", - ["dotnet_naming_symbols.first_symbols.required_modifiers"] = firstModifiers, - [$"dotnet_naming_style.{firstName}_style.capitalization"] = "pascal_case", - [$"dotnet_naming_style.{secondName}_style.capitalization"] = "camel_case", - [$"dotnet_naming_rule.{secondName}.severity"] = "error", - [$"dotnet_naming_rule.{secondName}.symbols"] = "second_symbols", - [$"dotnet_naming_rule.{secondName}.style"] = $"{secondName}_style", - ["dotnet_naming_symbols.second_symbols.applicable_kinds"] = "method,property", - ["dotnet_naming_symbols.second_symbols.required_modifiers"] = secondModifiers, - }); - - var secondNameAfterOrdering = firstNameAfterOrdering == firstName ? secondName : firstName; - Assert.Equal($"{firstNameAfterOrdering}_style", namingStylePreferences.Rules.NamingRules[0].NamingStyle.Name); - Assert.Equal($"{secondNameAfterOrdering}_style", namingStylePreferences.Rules.NamingRules[1].NamingStyle.Name); - } - - [Theory] - [InlineData("a", "b", "a", "method", "method, property")] - [InlineData("b", "a", "a", "method, property", "method")] - [InlineData("b", "a", "b", "method", "method, property")] - [InlineData("a", "b", "b", "method, property", "method")] - [InlineData("a", "b", "a", "*", "*")] - [InlineData("b", "a", "a", "*", "*")] - [InlineData("A", "b", "A", "*", "*")] - [InlineData("b", "A", "A", "*", "*")] - [InlineData("a", "B", "a", "*", "*")] - [InlineData("B", "a", "a", "*", "*")] - [InlineData("A", "B", "A", "*", "*")] - [InlineData("B", "A", "A", "*", "*")] - public static void TestOrderedBySymbolsBeforeName(string firstName, string secondName, string firstNameAfterOrdering, string firstSymbols, string secondSymbols) - { - var namingStylePreferences = ParseDictionary(new Dictionary() - { - [$"dotnet_naming_rule.{firstName}.severity"] = "error", - [$"dotnet_naming_rule.{firstName}.symbols"] = "first_symbols", - [$"dotnet_naming_rule.{firstName}.style"] = $"{firstName}_style", - ["dotnet_naming_symbols.first_symbols.applicable_kinds"] = firstSymbols, - [$"dotnet_naming_style.{firstName}_style.capitalization"] = "pascal_case", - [$"dotnet_naming_style.{secondName}_style.capitalization"] = "camel_case", - [$"dotnet_naming_rule.{secondName}.severity"] = "error", - [$"dotnet_naming_rule.{secondName}.symbols"] = "second_symbols", - [$"dotnet_naming_rule.{secondName}.style"] = $"{secondName}_style", - ["dotnet_naming_symbols.second_symbols.applicable_kinds"] = secondSymbols, - }); - - var secondNameAfterOrdering = firstNameAfterOrdering == firstName ? secondName : firstName; - Assert.Equal($"{firstNameAfterOrdering}_style", namingStylePreferences.Rules.NamingRules[0].NamingStyle.Name); - Assert.Equal($"{secondNameAfterOrdering}_style", namingStylePreferences.Rules.NamingRules[1].NamingStyle.Name); - } } } diff --git a/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/SettingsProviderBase.cs b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/SettingsProviderBase.cs index 64ec7898fe01e..1f137a0d8c170 100644 --- a/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/SettingsProviderBase.cs +++ b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/SettingsProviderBase.cs @@ -16,7 +16,7 @@ using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Utilities; -using Roslyn.Utilities; +using static Microsoft.CodeAnalysis.ProjectState; namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings.DataProvider { @@ -51,7 +51,7 @@ protected void Update() return; } - var configOptionsProvider = new ProjectState.ProjectAnalyzerConfigOptionsProvider(project.State); + var configOptionsProvider = new ProjectAnalyzerConfigOptionsProvider(project.State); var workspaceOptions = configOptionsProvider.GetOptionsForSourcePath(givenFolder.FullName); var result = project.GetAnalyzerConfigOptions(); var options = new CombinedAnalyzerConfigOptions(workspaceOptions, result); @@ -93,9 +93,9 @@ public void RegisterViewModel(ISettingsEditorViewModel viewModel) private sealed class CombinedAnalyzerConfigOptions : AnalyzerConfigOptions { private readonly AnalyzerConfigOptions _workspaceOptions; - private readonly AnalyzerConfigData? _result; + private readonly AnalyzerConfigOptionsResult? _result; - public CombinedAnalyzerConfigOptions(AnalyzerConfigOptions workspaceOptions, AnalyzerConfigData? result) + public CombinedAnalyzerConfigOptions(AnalyzerConfigOptions workspaceOptions, AnalyzerConfigOptionsResult? result) { _workspaceOptions = workspaceOptions; _result = result; @@ -131,34 +131,6 @@ public override bool TryGetValue(string key, [NotNullWhen(true)] out string? val value = null; return false; } - - public override IEnumerable Keys - { - get - { - foreach (var key in _workspaceOptions.Keys) - yield return key; - - if (!_result.HasValue) - yield break; - - foreach (var key in _result.Value.AnalyzerOptions.Keys) - { - if (!_workspaceOptions.TryGetValue(key, out _)) - yield return key; - } - - foreach (var (key, severity) in _result.Value.TreeOptions) - { - var diagnosticKey = "dotnet_diagnostic." + key + ".severity"; - if (!_workspaceOptions.TryGetValue(diagnosticKey, out _) && - !_result.Value.AnalyzerOptions.TryGetKey(diagnosticKey, out _)) - { - yield return diagnosticKey; - } - } - } - } } } } diff --git a/src/Features/Core/Portable/Diagnostics/AnalyzerConfigOptionSet+AnalyzerConfigOptionsImpl.cs b/src/Features/Core/Portable/Diagnostics/AnalyzerConfigOptionSet+AnalyzerConfigOptionsImpl.cs index 19a21b395b96f..3fec7795a0148 100644 --- a/src/Features/Core/Portable/Diagnostics/AnalyzerConfigOptionSet+AnalyzerConfigOptionsImpl.cs +++ b/src/Features/Core/Portable/Diagnostics/AnalyzerConfigOptionSet+AnalyzerConfigOptionsImpl.cs @@ -2,10 +2,7 @@ // 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.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Linq; -using Microsoft.CodeAnalysis.PooledObjects; namespace Microsoft.CodeAnalysis.Diagnostics { @@ -31,9 +28,6 @@ public override bool TryGetValue(string key, [NotNullWhen(true)] out string? val return _fallbackOptions.TryGetValue(key, out value); } - - public override IEnumerable Keys - => _options.Keys.Concat(_fallbackOptions.Keys.Where(key => !_options.TryGetValue(key, out _))); } } } diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs index 034940b2462f6..bf2546750d42c 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs @@ -381,7 +381,7 @@ private IReadOnlyList GetStateSetsForFullSolutionAnalysis(IEnumerable< return stateSets.Where(s => IsCandidateForFullSolutionAnalysis(s.Analyzer, project, analyzerConfigOptions)).ToList(); } - private bool IsCandidateForFullSolutionAnalysis(DiagnosticAnalyzer analyzer, Project project, AnalyzerConfigData? analyzerConfigOptions) + private bool IsCandidateForFullSolutionAnalysis(DiagnosticAnalyzer analyzer, Project project, AnalyzerConfigOptionsResult? analyzerConfigOptions) { // PERF: Don't query descriptors for compiler analyzer or workspace load analyzer, always execute them. if (analyzer == FileContentLoadAnalyzer.Instance || @@ -413,7 +413,7 @@ private bool IsCandidateForFullSolutionAnalysis(DiagnosticAnalyzer analyzer, Pro // For most of analyzers, the number of diagnostic descriptors is small, so this should be cheap. var descriptors = DiagnosticAnalyzerInfoCache.GetDiagnosticDescriptors(analyzer); - return descriptors.Any(d => d.GetEffectiveSeverity(project.CompilationOptions!, analyzerConfigOptions?.Result) != ReportDiagnostic.Hidden); + return descriptors.Any(d => d.GetEffectiveSeverity(project.CompilationOptions!, analyzerConfigOptions) != ReportDiagnostic.Hidden); } private void RaiseProjectDiagnosticsIfNeeded( diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/AnalyzersCommandHandler.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/AnalyzersCommandHandler.cs index 89101c51cfdba..a2f36a1e8df05 100644 --- a/src/VisualStudio/Core/Impl/SolutionExplorer/AnalyzersCommandHandler.cs +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/AnalyzersCommandHandler.cs @@ -296,7 +296,7 @@ private void UpdateSeverityMenuItemsChecked() foreach (var diagnosticItem in group) { - var severity = diagnosticItem.Descriptor.GetEffectiveSeverity(project.CompilationOptions, analyzerConfigOptions?.Result); + var severity = diagnosticItem.Descriptor.GetEffectiveSeverity(project.CompilationOptions, analyzerConfigOptions); selectedItemSeverities.Add(severity); } } diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/BaseDiagnosticAndGeneratorItemSource.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/BaseDiagnosticAndGeneratorItemSource.cs index 114541cfd6522..621c689c63e31 100644 --- a/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/BaseDiagnosticAndGeneratorItemSource.cs +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/BaseDiagnosticAndGeneratorItemSource.cs @@ -26,7 +26,7 @@ internal abstract partial class BaseDiagnosticAndGeneratorItemSource : IAttached private BulkObservableCollection? _items; private ReportDiagnostic _generalDiagnosticOption; private ImmutableDictionary? _specificDiagnosticOptions; - private AnalyzerConfigData? _analyzerConfigOptions; + private AnalyzerConfigOptionsResult? _analyzerConfigOptions; public BaseDiagnosticAndGeneratorItemSource(Workspace workspace, ProjectId projectId, IAnalyzersCommandHandler commandHandler, IDiagnosticAnalyzerService diagnosticAnalyzerService) { @@ -95,7 +95,7 @@ public IEnumerable Items } } - private BulkObservableCollection CreateDiagnosticAndGeneratorItems(ProjectId projectId, string language, CompilationOptions options, AnalyzerConfigData? analyzerConfigOptions) + private BulkObservableCollection CreateDiagnosticAndGeneratorItems(ProjectId projectId, string language, CompilationOptions options, AnalyzerConfigOptionsResult? analyzerConfigOptions) { // Within an analyzer assembly, an individual analyzer may report multiple different diagnostics // with the same ID. Or, multiple analyzers may report diagnostics with the same ID. Or a @@ -117,7 +117,7 @@ private BulkObservableCollection CreateDiagnosticAndGeneratorItems(Pro .Select(g => { var selectedDiagnostic = g.OrderBy(d => d, s_comparer).First(); - var effectiveSeverity = selectedDiagnostic.GetEffectiveSeverity(options, analyzerConfigOptions?.Result); + var effectiveSeverity = selectedDiagnostic.GetEffectiveSeverity(options, analyzerConfigOptions); return new DiagnosticItem(projectId, AnalyzerReference, selectedDiagnostic, effectiveSeverity, CommandHandler); })); @@ -183,7 +183,7 @@ void OnProjectConfigurationChanged() foreach (var item in _items.OfType()) { - var effectiveSeverity = item.Descriptor.GetEffectiveSeverity(project.CompilationOptions, newAnalyzerConfigOptions?.Result); + var effectiveSeverity = item.Descriptor.GetEffectiveSeverity(project.CompilationOptions, newAnalyzerConfigOptions); item.UpdateEffectiveSeverity(effectiveSeverity); } } diff --git a/src/Workspaces/Core/Portable/Options/EditorConfig/EditorConfigDocumentOptionsProviderFactory.cs b/src/Workspaces/Core/Portable/Options/EditorConfig/EditorConfigDocumentOptionsProviderFactory.cs index bacb11927cb04..0148466ebcc7e 100644 --- a/src/Workspaces/Core/Portable/Options/EditorConfig/EditorConfigDocumentOptionsProviderFactory.cs +++ b/src/Workspaces/Core/Portable/Options/EditorConfig/EditorConfigDocumentOptionsProviderFactory.cs @@ -9,7 +9,6 @@ using System.Threading.Tasks; using Roslyn.Utilities; using Microsoft.CodeAnalysis.ErrorReporting; -using Microsoft.CodeAnalysis.Diagnostics; namespace Microsoft.CodeAnalysis.Options.EditorConfig { @@ -22,25 +21,19 @@ private sealed class EditorConfigDocumentOptionsProvider : IDocumentOptionsProvi { public async Task GetOptionsForDocumentAsync(Document document, CancellationToken cancellationToken) { - var data = await document.GetAnalyzerOptionsAsync(cancellationToken).ConfigureAwait(false); - return new DocumentOptions(data?.AnalyzerConfigOptions); + var options = await document.GetAnalyzerOptionsAsync(cancellationToken).ConfigureAwait(false); + + return new DocumentOptions(options); } private sealed class DocumentOptions : IDocumentOptions { - private readonly StructuredAnalyzerConfigOptions? _options; - - public DocumentOptions(StructuredAnalyzerConfigOptions? options) + private readonly ImmutableDictionary _options; + public DocumentOptions(ImmutableDictionary options) => _options = options; public bool TryGetDocumentOption(OptionKey option, out object? value) { - if (_options == null) - { - value = null; - return false; - } - var editorConfigPersistence = (IEditorConfigStorageLocation?)option.Option.StorageLocations.SingleOrDefault(static location => location is IEditorConfigStorageLocation); if (editorConfigPersistence == null) { @@ -50,7 +43,7 @@ public bool TryGetDocumentOption(OptionKey option, out object? value) try { - return editorConfigPersistence.TryGetOption(_options, option.Option.Type, out value); + return editorConfigPersistence.TryGetOption(_options.AsNullable(), option.Option.Type, out value); } catch (Exception e) when (FatalError.ReportAndCatch(e)) { diff --git a/src/Workspaces/Core/Portable/Options/OptionSet+AnalyzerConfigOptionsImpl.cs b/src/Workspaces/Core/Portable/Options/OptionSet+AnalyzerConfigOptionsImpl.cs index 6e52e3a5db884..4e0d139840cb3 100644 --- a/src/Workspaces/Core/Portable/Options/OptionSet+AnalyzerConfigOptionsImpl.cs +++ b/src/Workspaces/Core/Portable/Options/OptionSet+AnalyzerConfigOptionsImpl.cs @@ -2,11 +2,8 @@ // 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.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Linq; using Microsoft.CodeAnalysis.Diagnostics; namespace Microsoft.CodeAnalysis.Options @@ -42,10 +39,6 @@ public override bool TryGetValue(string key, [NotNullWhen(true)] out string? val value = storageLocation.GetEditorConfigStringValue(typedValue, _optionSet); return true; } - - // no way to enumerate OptionSet - public override IEnumerable Keys - => throw new NotImplementedException(); } } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/AnalyzerConfigData.cs b/src/Workspaces/Core/Portable/Workspace/Solution/AnalyzerConfigData.cs deleted file mode 100644 index c417192872bb6..0000000000000 --- a/src/Workspaces/Core/Portable/Workspace/Solution/AnalyzerConfigData.cs +++ /dev/null @@ -1,28 +0,0 @@ -// 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.Collections.Immutable; -using Microsoft.CodeAnalysis.Diagnostics; - -namespace Microsoft.CodeAnalysis; - -/// -/// Aggregate analyzer config options for a specific path. -/// -internal readonly struct AnalyzerConfigData -{ - private readonly AnalyzerConfigOptionsResult _result; - private readonly StructuredAnalyzerConfigOptions _configOptions; - - public AnalyzerConfigData(AnalyzerConfigOptionsResult result) - { - _result = result; - _configOptions = new StructuredAnalyzerConfigOptions(result.AnalyzerOptions); - } - - public AnalyzerConfigOptionsResult Result => _result; - public StructuredAnalyzerConfigOptions AnalyzerConfigOptions => _configOptions; - public ImmutableDictionary AnalyzerOptions => _result.AnalyzerOptions; - public ImmutableDictionary TreeOptions => _result.TreeOptions; -} diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Document.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Document.cs index 7f4f520e8d25a..c64163a5a1277 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Document.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Document.cs @@ -513,7 +513,7 @@ private void InitializeCachedOptions(OptionSet solutionOptions) Interlocked.CompareExchange(ref _cachedOptions, newAsyncLazy, comparand: null); } - internal async Task GetAnalyzerOptionsAsync(CancellationToken cancellationToken) + internal Task> GetAnalyzerOptionsAsync(CancellationToken cancellationToken) { var projectFilePath = Project.FilePath; // We need to work out path to this document. Documents may not have a "real" file path if they're something created @@ -535,12 +535,15 @@ private void InitializeCachedOptions(OptionSet solutionOptions) } } - if (effectiveFilePath == null) + if (effectiveFilePath != null) { - return null; + return Project.State.GetAnalyzerOptionsForPathAsync(effectiveFilePath, cancellationToken); + } + else + { + // Really no idea where this is going, so bail + return Task.FromResult(DictionaryAnalyzerConfigOptions.EmptyDictionary); } - - return await Project.State.GetAnalyzerOptionsForPathAsync(effectiveFilePath, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs index 5ec37f9535f0e..fa20f2786d59a 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs @@ -774,7 +774,7 @@ private void CheckIdsContainedInProject(ImmutableArray documentIds) } } - internal AnalyzerConfigData? GetAnalyzerConfigOptions() + internal AnalyzerConfigOptionsResult? GetAnalyzerConfigOptions() => _projectState.GetAnalyzerConfigOptions(); private string GetDebuggerDisplay() diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState.cs index 9370c87f3b2c6..d36dd5f83160d 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState.cs @@ -248,13 +248,15 @@ public AnalyzerOptions AnalyzerOptions additionalFiles: AdditionalDocumentStates.SelectAsArray(static documentState => documentState.AdditionalText), optionsProvider: new ProjectAnalyzerConfigOptionsProvider(this)); - public async Task GetAnalyzerOptionsForPathAsync(string path, CancellationToken cancellationToken) + public async Task> GetAnalyzerOptionsForPathAsync( + string path, + CancellationToken cancellationToken) { - var cache = await _lazyAnalyzerConfigOptions.GetValueAsync(cancellationToken).ConfigureAwait(false); - return cache.GetOptionsForSourcePath(path); + var configSet = await _lazyAnalyzerConfigOptions.GetValueAsync(cancellationToken).ConfigureAwait(false); + return configSet.GetOptionsForSourcePath(path).AnalyzerOptions; } - public AnalyzerConfigData? GetAnalyzerConfigOptions() + public AnalyzerConfigOptionsResult? GetAnalyzerConfigOptions() { // We need to find the analyzer config options at the root of the project. // Currently, there is no compiler API to query analyzer config options for a directory in a language agnostic fashion. @@ -294,11 +296,8 @@ internal sealed class ProjectAnalyzerConfigOptionsProvider : AnalyzerConfigOptio public ProjectAnalyzerConfigOptionsProvider(ProjectState projectState) => _projectState = projectState; - private AnalyzerConfigOptionsCache GetCache() - => _projectState._lazyAnalyzerConfigOptions.GetValue(CancellationToken.None); - public override AnalyzerConfigOptions GlobalOptions - => GetCache().GlobalConfigOptions.AnalyzerConfigOptions; + => GetOptionsForSourcePath(string.Empty); public override AnalyzerConfigOptions GetOptions(SyntaxTree tree) => GetOptionsForSourcePath(tree.FilePath); @@ -310,7 +309,7 @@ public override AnalyzerConfigOptions GetOptions(AdditionalText textFile) } public AnalyzerConfigOptions GetOptionsForSourcePath(string path) - => GetCache().GetOptionsForSourcePath(path).AnalyzerConfigOptions; + => new DictionaryAnalyzerConfigOptions(_projectState._lazyAnalyzerConfigOptions.GetValue(CancellationToken.None).GetOptionsForSourcePath(path).AnalyzerOptions); } private sealed class ProjectSyntaxTreeOptionsProvider : SyntaxTreeOptionsProvider @@ -372,20 +371,20 @@ private static ValueSource ComputeAnalyzerConfigOpti private readonly struct AnalyzerConfigOptionsCache { - private readonly ConcurrentDictionary _sourcePathToResult = new(); - private readonly Func _computeFunction; - private readonly Lazy _global; + private readonly ConcurrentDictionary _sourcePathToResult = new(); + private readonly Func _computeFunction; + private readonly AnalyzerConfigSet _configSet; public AnalyzerConfigOptionsCache(AnalyzerConfigSet configSet) { - _global = new Lazy(() => new AnalyzerConfigData(configSet.GlobalConfigOptions)); - _computeFunction = path => new AnalyzerConfigData(configSet.GetOptionsForSourcePath(path)); + _configSet = configSet; + _computeFunction = _configSet.GetOptionsForSourcePath; } - public AnalyzerConfigData GlobalConfigOptions - => _global.Value; + public AnalyzerConfigOptionsResult GlobalConfigOptions + => _configSet.GlobalConfigOptions; - public AnalyzerConfigData GetOptionsForSourcePath(string sourcePath) + public AnalyzerConfigOptionsResult GetOptionsForSourcePath(string sourcePath) => _sourcePathToResult.GetOrAdd(sourcePath, _computeFunction); } diff --git a/src/Workspaces/CoreTest/CodeStyle/EditorConfigCodeStyleParserTests.cs b/src/Workspaces/CoreTest/CodeStyle/EditorConfigCodeStyleParserTests.cs index fa91961a9e6ac..6985c5d8ccbcb 100644 --- a/src/Workspaces/CoreTest/CodeStyle/EditorConfigCodeStyleParserTests.cs +++ b/src/Workspaces/CoreTest/CodeStyle/EditorConfigCodeStyleParserTests.cs @@ -3,16 +3,11 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.CodeAnalysis.CodeStyle; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Options; using Roslyn.Test.Utilities; -using Roslyn.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.UnitTests.CodeStyle @@ -67,7 +62,7 @@ public void TestParseEditorConfigAccessibilityModifiers(string args, int value, var storageLocation = CodeStyleOptions2.RequireAccessibilityModifiers.StorageLocations .OfType>>() .Single(); - var allRawConventions = new StructuredAnalyzerConfigOptions(DictionaryAnalyzerConfigOptions.EmptyDictionary.Add(storageLocation.KeyName, args)); + var allRawConventions = new Dictionary { { storageLocation.KeyName, args } }; Assert.True(storageLocation.TryGetOption(allRawConventions, typeof(CodeStyleOption2), out var parsedCodeStyleOption)); var codeStyleOption = (CodeStyleOption2)parsedCodeStyleOption!; @@ -89,7 +84,7 @@ public void TestParseEditorConfigEndOfLine(string configurationString, string ne var storageLocation = FormattingOptions.NewLine.StorageLocations .OfType>() .Single(); - var allRawConventions = new StructuredAnalyzerConfigOptions(DictionaryAnalyzerConfigOptions.EmptyDictionary.Add(storageLocation.KeyName, configurationString)); + var allRawConventions = new Dictionary { { storageLocation.KeyName, configurationString } }; Assert.True(storageLocation.TryGetOption(allRawConventions, typeof(string), out var parsedNewLine)); Assert.Equal(newLine, (string?)parsedNewLine); diff --git a/src/Workspaces/CoreTest/EditorConfigStorageLocation/NamingStylePreferenceEditorConfigStorageLocationTests.cs b/src/Workspaces/CoreTest/EditorConfigStorageLocation/NamingStylePreferenceEditorConfigStorageLocationTests.cs index 2fd9b59b756f7..7e74d51ac4cb4 100644 --- a/src/Workspaces/CoreTest/EditorConfigStorageLocation/NamingStylePreferenceEditorConfigStorageLocationTests.cs +++ b/src/Workspaces/CoreTest/EditorConfigStorageLocation/NamingStylePreferenceEditorConfigStorageLocationTests.cs @@ -6,12 +6,8 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles; using Microsoft.CodeAnalysis.Options; -using Roslyn.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.UnitTests.EditorConfig.StorageLocation @@ -22,7 +18,19 @@ public class NamingStylePreferenceEditorConfigStorageLocationTests public static void TestEmptyDictionaryReturnNoNamingStylePreferencesObjectReturnsFalse() { var editorConfigStorageLocation = new NamingStylePreferenceEditorConfigStorageLocation(); - var result = editorConfigStorageLocation.TryGetOption(new StructuredAnalyzerConfigOptions(DictionaryAnalyzerConfigOptions.Empty), typeof(NamingStylePreferences), out _); + var result = editorConfigStorageLocation.TryGetOption(new Dictionary(), typeof(NamingStylePreferences), out _); + Assert.False(result, "Expected TryParseReadonlyDictionary to return 'false' for empty dictionary"); + } + + [Fact] + public static void TestEmptyDictionaryDefaultNamingStylePreferencesObjectReturnsFalse() + { + var editorConfigStorageLocation = new NamingStylePreferenceEditorConfigStorageLocation(); + var result = editorConfigStorageLocation.TryGetOption( + new Dictionary(), + typeof(NamingStylePreferences), + out _); + Assert.False(result, "Expected TryParseReadonlyDictionary to return 'false' for empty dictionary"); } @@ -30,7 +38,7 @@ public static void TestEmptyDictionaryReturnNoNamingStylePreferencesObjectReturn public static void TestNonEmptyDictionaryReturnsTrue() { var editorConfigStorageLocation = new NamingStylePreferenceEditorConfigStorageLocation(); - var options = new StructuredAnalyzerConfigOptions(new Dictionary() + var newDictionary = new Dictionary() { ["dotnet_naming_rule.methods_and_properties_must_be_pascal_case.severity"] = "error", ["dotnet_naming_rule.methods_and_properties_must_be_pascal_case.symbols"] = "method_and_property_symbols", @@ -38,22 +46,155 @@ public static void TestNonEmptyDictionaryReturnsTrue() ["dotnet_naming_symbols.method_and_property_symbols.applicable_kinds"] = "method,property", ["dotnet_naming_symbols.method_and_property_symbols.applicable_accessibilities"] = "*", ["dotnet_naming_style.pascal_case_style.capitalization"] = "pascal_case" - }.ToImmutableDictionary(AnalyzerConfigOptions.KeyComparer)); + }; - var result = editorConfigStorageLocation.TryGetOption(options, typeof(NamingStylePreferences), out var value); + var result = editorConfigStorageLocation.TryGetOption( + newDictionary, + typeof(NamingStylePreferences), + out var combinedNamingStyles); Assert.True(result, "Expected non-empty dictionary to return true"); - var namingStylePreferences = Assert.IsAssignableFrom(value); + var namingStylePreferences = Assert.IsAssignableFrom(combinedNamingStyles); Assert.Equal(ReportDiagnostic.Error, namingStylePreferences.Rules.NamingRules[0].EnforcementLevel); } + [Theory] + [InlineData("a", "b", "a", "public", "public, private")] + [InlineData("b", "a", "a", "public, private", "public")] + [InlineData("b", "a", "b", "public", "public, private")] + [InlineData("a", "b", "b", "public, private", "public")] + [InlineData("a", "b", "a", "*", "*")] + [InlineData("b", "a", "a", "*", "*")] + [InlineData("A", "b", "A", "*", "*")] + [InlineData("b", "A", "A", "*", "*")] + [InlineData("a", "B", "a", "*", "*")] + [InlineData("B", "a", "a", "*", "*")] + [InlineData("A", "B", "A", "*", "*")] + [InlineData("B", "A", "A", "*", "*")] + public static void TestOrderedByAccessibilityBeforeName(string firstName, string secondName, string firstNameAfterOrdering, string firstAccessibility, string secondAccessibility) + { + var editorConfigStorageLocation = new NamingStylePreferenceEditorConfigStorageLocation(); + var newDictionary = new Dictionary() + { + [$"dotnet_naming_rule.{firstName}.severity"] = "error", + [$"dotnet_naming_rule.{firstName}.symbols"] = "first_symbols", + [$"dotnet_naming_rule.{firstName}.style"] = $"{firstName}_style", + ["dotnet_naming_symbols.first_symbols.applicable_kinds"] = "method,property", + ["dotnet_naming_symbols.first_symbols.applicable_accessibilities"] = firstAccessibility, + [$"dotnet_naming_style.{firstName}_style.capitalization"] = "pascal_case", + [$"dotnet_naming_style.{secondName}_style.capitalization"] = "camel_case", + [$"dotnet_naming_rule.{secondName}.severity"] = "error", + [$"dotnet_naming_rule.{secondName}.symbols"] = "second_symbols", + [$"dotnet_naming_rule.{secondName}.style"] = $"{secondName}_style", + ["dotnet_naming_symbols.second_symbols.applicable_kinds"] = "method,property", + ["dotnet_naming_symbols.second_symbols.applicable_accessibilities"] = secondAccessibility, + }; + + var result = editorConfigStorageLocation.TryGetOption( + newDictionary, + typeof(NamingStylePreferences), + out var combinedNamingStyles); + + var secondNameAfterOrdering = firstNameAfterOrdering == firstName ? secondName : firstName; + Assert.True(result, "Expected non-empty dictionary to return true"); + var namingStylePreferences = Assert.IsAssignableFrom(combinedNamingStyles); + Assert.Equal($"{firstNameAfterOrdering}_style", namingStylePreferences.Rules.NamingRules[0].NamingStyle.Name); + Assert.Equal($"{secondNameAfterOrdering}_style", namingStylePreferences.Rules.NamingRules[1].NamingStyle.Name); + } + + [Theory] + [InlineData("a", "b", "a", "static, readonly", "static")] + [InlineData("b", "a", "a", "static", "static, readonly")] + [InlineData("b", "a", "b", "static, readonly", "static")] + [InlineData("a", "b", "b", "static", "static, readonly")] + [InlineData("a", "b", "a", "", "")] + [InlineData("b", "a", "a", "", "")] + [InlineData("A", "b", "A", "", "")] + [InlineData("b", "A", "A", "", "")] + [InlineData("a", "B", "a", "", "")] + [InlineData("B", "a", "a", "", "")] + [InlineData("A", "B", "A", "", "")] + [InlineData("B", "A", "A", "", "")] + public static void TestOrderedByModifiersBeforeName(string firstName, string secondName, string firstNameAfterOrdering, string firstModifiers, string secondModifiers) + { + var editorConfigStorageLocation = new NamingStylePreferenceEditorConfigStorageLocation(); + var newDictionary = new Dictionary() + { + [$"dotnet_naming_rule.{firstName}.severity"] = "error", + [$"dotnet_naming_rule.{firstName}.symbols"] = "first_symbols", + [$"dotnet_naming_rule.{firstName}.style"] = $"{firstName}_style", + ["dotnet_naming_symbols.first_symbols.applicable_kinds"] = "method,property", + ["dotnet_naming_symbols.first_symbols.required_modifiers"] = firstModifiers, + [$"dotnet_naming_style.{firstName}_style.capitalization"] = "pascal_case", + [$"dotnet_naming_style.{secondName}_style.capitalization"] = "camel_case", + [$"dotnet_naming_rule.{secondName}.severity"] = "error", + [$"dotnet_naming_rule.{secondName}.symbols"] = "second_symbols", + [$"dotnet_naming_rule.{secondName}.style"] = $"{secondName}_style", + ["dotnet_naming_symbols.second_symbols.applicable_kinds"] = "method,property", + ["dotnet_naming_symbols.second_symbols.required_modifiers"] = secondModifiers, + }; + + var result = editorConfigStorageLocation.TryGetOption( + newDictionary, + typeof(NamingStylePreferences), + out var combinedNamingStyles); + + var secondNameAfterOrdering = firstNameAfterOrdering == firstName ? secondName : firstName; + Assert.True(result, "Expected non-empty dictionary to return true"); + var namingStylePreferences = Assert.IsAssignableFrom(combinedNamingStyles); + Assert.Equal($"{firstNameAfterOrdering}_style", namingStylePreferences.Rules.NamingRules[0].NamingStyle.Name); + Assert.Equal($"{secondNameAfterOrdering}_style", namingStylePreferences.Rules.NamingRules[1].NamingStyle.Name); + } + + [Theory] + [InlineData("a", "b", "a", "method", "method, property")] + [InlineData("b", "a", "a", "method, property", "method")] + [InlineData("b", "a", "b", "method", "method, property")] + [InlineData("a", "b", "b", "method, property", "method")] + [InlineData("a", "b", "a", "*", "*")] + [InlineData("b", "a", "a", "*", "*")] + [InlineData("A", "b", "A", "*", "*")] + [InlineData("b", "A", "A", "*", "*")] + [InlineData("a", "B", "a", "*", "*")] + [InlineData("B", "a", "a", "*", "*")] + [InlineData("A", "B", "A", "*", "*")] + [InlineData("B", "A", "A", "*", "*")] + public static void TestOrderedBySymbolsBeforeName(string firstName, string secondName, string firstNameAfterOrdering, string firstSymbols, string secondSymbols) + { + var editorConfigStorageLocation = new NamingStylePreferenceEditorConfigStorageLocation(); + var newDictionary = new Dictionary() + { + [$"dotnet_naming_rule.{firstName}.severity"] = "error", + [$"dotnet_naming_rule.{firstName}.symbols"] = "first_symbols", + [$"dotnet_naming_rule.{firstName}.style"] = $"{firstName}_style", + ["dotnet_naming_symbols.first_symbols.applicable_kinds"] = firstSymbols, + [$"dotnet_naming_style.{firstName}_style.capitalization"] = "pascal_case", + [$"dotnet_naming_style.{secondName}_style.capitalization"] = "camel_case", + [$"dotnet_naming_rule.{secondName}.severity"] = "error", + [$"dotnet_naming_rule.{secondName}.symbols"] = "second_symbols", + [$"dotnet_naming_rule.{secondName}.style"] = $"{secondName}_style", + ["dotnet_naming_symbols.second_symbols.applicable_kinds"] = secondSymbols, + }; + + var result = editorConfigStorageLocation.TryGetOption( + newDictionary, + typeof(NamingStylePreferences), + out var combinedNamingStyles); + + var secondNameAfterOrdering = firstNameAfterOrdering == firstName ? secondName : firstName; + Assert.True(result, "Expected non-empty dictionary to return true"); + var namingStylePreferences = Assert.IsAssignableFrom(combinedNamingStyles); + Assert.Equal($"{firstNameAfterOrdering}_style", namingStylePreferences.Rules.NamingRules[0].NamingStyle.Name); + Assert.Equal($"{secondNameAfterOrdering}_style", namingStylePreferences.Rules.NamingRules[1].NamingStyle.Name); + } + [Fact] public static void TestObjectTypeThrowsInvalidOperationException() { var editorConfigStorageLocation = new NamingStylePreferenceEditorConfigStorageLocation(); Assert.Throws(() => { - editorConfigStorageLocation.TryGetOption(new StructuredAnalyzerConfigOptions(DictionaryAnalyzerConfigOptions.Empty), typeof(object), out var @object); + editorConfigStorageLocation.TryGetOption(new Dictionary(), typeof(object), out var @object); }); } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems index c17730357f3ff..b3c2638f021c2 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems @@ -193,7 +193,6 @@ - @@ -372,6 +371,7 @@ + @@ -575,7 +575,7 @@ - Diagnostics\DictionaryAnalyzerConfigOptions.cs + Diagnostics\AnalyzerConfigOptionsDictionary.cs diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Diagnostics/StructuredAnalyzerConfigOptions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Diagnostics/StructuredAnalyzerConfigOptions.cs deleted file mode 100644 index 6fb0cb022c226..0000000000000 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Diagnostics/StructuredAnalyzerConfigOptions.cs +++ /dev/null @@ -1,97 +0,0 @@ -// 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.Reflection; -using System.Runtime.CompilerServices; -using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Diagnostics; - -/// -/// 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. -/// -internal sealed class StructuredAnalyzerConfigOptions : AnalyzerConfigOptions -{ - private readonly AnalyzerConfigOptions _options; - private readonly Lazy _lazyNamingStylePreferences; - - public StructuredAnalyzerConfigOptions(AnalyzerConfigOptions options) - { - _options = options; - _lazyNamingStylePreferences = new Lazy(() => EditorConfigNamingStyleParser.ParseDictionary(_options)); - } - - public StructuredAnalyzerConfigOptions(ImmutableDictionary options) - : this(new DictionaryAnalyzerConfigOptions(options)) - { - Contract.ThrowIfFalse(options.KeyComparer == KeyComparer); - } - - public override bool TryGetValue(string key, [NotNullWhen(true)] out string? value) - => _options.TryGetValue(key, out value); - - public override IEnumerable Keys - => _options.Keys; - - public NamingStylePreferences GetNamingStylePreferences() - => _lazyNamingStylePreferences.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 Worksapce 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 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 StructuredAnalyzerConfigOptions(configOptions); - s_codeStyleStructuredOptions.Add(configOptions, options); - } - } - - return true; - } -#endif -} diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/EditorConfig/Parsing/EditorConfigParser.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/EditorConfig/Parsing/EditorConfigParser.cs index b48bb5a04538c..51386f9495f64 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/EditorConfig/Parsing/EditorConfigParser.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/EditorConfig/Parsing/EditorConfigParser.cs @@ -5,7 +5,6 @@ using System.Collections.Immutable; using System.Diagnostics; using System.Text.RegularExpressions; -using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.EditorConfig.Parsing @@ -16,9 +15,8 @@ internal static class EditorConfigParser private static readonly Regex s_sectionMatcher = new(@"^\s*\[(([^#;]|\\#|\\;)+)\]\s*([#;].*)?$", RegexOptions.Compiled); // Matches EditorConfig property such as "indent_style = space", see https://editorconfig.org for details private static readonly Regex s_propertyMatcher = new(@"^\s*([\w\.\-_]+)\s*[=:]\s*(.*?)\s*([#;].*)?$", RegexOptions.Compiled); - private static ImmutableHashSet ReservedKeys { get; } - = ImmutableHashSet.CreateRange(AnalyzerConfigOptions.KeyComparer, new[] { + = ImmutableHashSet.CreateRange(CaseInsensitiveComparison.Comparer, new[] { "root", "indent_style", "indent_size", @@ -45,7 +43,8 @@ public static TEditorConfigFile Parse where TEditorConfigOption : EditorConfigOption { - var activeSectionProperties = ImmutableDictionary.CreateBuilder(AnalyzerConfigOptions.KeyComparer); + var activeSectionProperties = ImmutableDictionary.CreateBuilder( + CaseInsensitiveComparison.Comparer); var activeSectionName = ""; var activeSectionStart = 0; var activeSectionEnd = 0; @@ -75,7 +74,8 @@ public static TEditorConfigFile Parse( + CaseInsensitiveComparison.Comparer); continue; } @@ -121,7 +121,7 @@ void ProcessActiveSection() var fullText = activeLine.ToString(); var sectionSpan = new TextSpan(activeSectionStart, activeSectionEnd); var previousSection = new Section(pathToFile, isGlobal, sectionSpan, activeSectionName, fullText); - accumulator.ProcessSection(previousSection, activeSectionProperties); + accumulator.ProcessSection(previousSection, activeSectionProperties.ToImmutable()); } static bool IsComment(string line) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/EditorConfig/Parsing/IEditorConfigOptionAccumulator.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/EditorConfig/Parsing/IEditorConfigOptionAccumulator.cs index 1b103e4b8a3e5..c750a40f155fc 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/EditorConfig/Parsing/IEditorConfigOptionAccumulator.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/EditorConfig/Parsing/IEditorConfigOptionAccumulator.cs @@ -2,7 +2,6 @@ // 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.Collections.Generic; using System.Collections.Immutable; using Microsoft.CodeAnalysis.Text; @@ -12,7 +11,7 @@ internal interface IEditorConfigOptionAccumulator where TResults : EditorConfigFile where TResult : EditorConfigOption { - void ProcessSection(Section section, IReadOnlyDictionary properties); + void ProcessSection(Section section, ImmutableDictionary properties); TResults Complete(string? filePath); } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/EditorConfig/Parsing/NamingStyles/NamingStyleOptionAccumulator.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/EditorConfig/Parsing/NamingStyles/NamingStyleOptionAccumulator.cs index d873387893390..51bae3ea22c83 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/EditorConfig/Parsing/NamingStyles/NamingStyleOptionAccumulator.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/EditorConfig/Parsing/NamingStyles/NamingStyleOptionAccumulator.cs @@ -2,9 +2,7 @@ // 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.Collections.Generic; using System.Collections.Immutable; -using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; using static Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles.EditorConfigNamingStyleParser; @@ -22,9 +20,11 @@ public EditorConfigNamingStyles Complete(string? fileName) return editorConfigNamingStyles; } - public void ProcessSection(Section section, IReadOnlyDictionary properties) + public void ProcessSection( + Section section, + ImmutableDictionary properties) { - foreach (var ruleTitle in GetRuleTitles(properties)) + foreach (var ruleTitle in GetRuleTitles(TrimDictionary(properties))) { if (TryGetSymbolSpec(section, ruleTitle, properties, out var applicableSymbolInfo) && TryGetNamingStyleData(section, ruleTitle, properties, out var namingScheme) && diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/AnalyzerConfigOptionsExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/AnalyzerConfigOptionsExtensions.cs index 9006335cb9933..31d34a2ff238f 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/AnalyzerConfigOptionsExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/AnalyzerConfigOptionsExtensions.cs @@ -84,8 +84,7 @@ private static bool TryGetEditorConfigOption(this AnalyzerConfigOptions analy // This option has .editorconfig storage defined, even if the current configuration does not provide a // value for it. hasEditorConfigStorage = true; - if (StructuredAnalyzerConfigOptions.TryGetStructuredOptions(analyzerConfigOptions, out var structuredOptions) && - configStorageLocation.TryGetOption(structuredOptions, option.Type, out var objectValue)) + if (configStorageLocation.TryGetOption(analyzerConfigOptions, option.Type, out var objectValue)) { value = (T?)objectValue; return true; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/EditorConfig/EditorConfigNamingStyleParser.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/EditorConfig/EditorConfigNamingStyleParser.cs index f63d5d5684323..567882578d52f 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/EditorConfig/EditorConfigNamingStyleParser.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/EditorConfig/EditorConfigNamingStyleParser.cs @@ -7,7 +7,6 @@ using System.Linq; using System.Runtime.CompilerServices; using Microsoft.CodeAnalysis.NamingStyles; -using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; @@ -15,15 +14,43 @@ namespace Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles { internal static partial class EditorConfigNamingStyleParser { - public static NamingStylePreferences ParseDictionary(AnalyzerConfigOptions allRawConventions) + /// + /// The dictionary we get from the VS editorconfig API uses the same dictionary object if there are no changes, so we can cache based on dictionary + /// + // TODO: revisit this cache. The assumption that the dictionary doesn't change in the exact instance is terribly fragile, + // and with the new .editorconfig support won't hold as well as we'd like: a single tree will have a stable instance but + // that won't necessarily be the same across files and projects. + private static readonly ConditionalWeakTable, NamingStylePreferences> _cache = new(); + private static readonly object _cacheLock = new(); + + public static NamingStylePreferences GetNamingStylesFromDictionary(IReadOnlyDictionary rawOptions) { - var trimmedDictionary = TrimDictionary(allRawConventions); + if (_cache.TryGetValue(rawOptions, out var value)) + { + return value; + } + + lock (_cacheLock) + { + if (!_cache.TryGetValue(rawOptions, out value)) + { + value = ParseDictionary(rawOptions); + _cache.Add(rawOptions, value); + } + + return value; + } + } + public static NamingStylePreferences ParseDictionary(IReadOnlyDictionary allRawConventions) + { var symbolSpecifications = ArrayBuilder.GetInstance(); var namingStyles = ArrayBuilder.GetInstance(); var namingRules = ArrayBuilder.GetInstance(); var ruleNames = new Dictionary<(Guid symbolSpecificationID, Guid namingStyleID, ReportDiagnostic enforcementLevel), string>(); + var trimmedDictionary = TrimDictionary(allRawConventions); + foreach (var namingRuleTitle in GetRuleTitles(trimmedDictionary)) { if (TryGetSymbolSpec(namingRuleTitle, trimmedDictionary, out var symbolSpec) && @@ -100,12 +127,19 @@ public static NamingStylePreferences ParseDictionary(AnalyzerConfigOptions allRa })); } - internal static Dictionary TrimDictionary(AnalyzerConfigOptions allRawConventions) + internal static Dictionary TrimDictionary(IReadOnlyDictionary allRawConventions) { - var trimmedDictionary = new Dictionary(AnalyzerConfigOptions.KeyComparer); - foreach (var key in allRawConventions.Keys) + // Keys have been lowercased, but values have not. Because values here reference key + // names we need any comparisons to ignore case. + // For example, to make a naming style called "Pascal_Case_style" match up correctly + // with the key "dotnet_naming_style.pascal_case_style.capitalization", we have to + // ignore casing for that lookup. + var trimmedDictionary = new Dictionary(allRawConventions.Count, AnalyzerConfigOptions.KeyComparer); + foreach (var item in allRawConventions) { - trimmedDictionary[key.Trim()] = allRawConventions.TryGetValue(key, out var value) ? value : throw new InvalidOperationException(); + var key = item.Key.Trim(); + var value = item.Value; + trimmedDictionary[key] = value; } return trimmedDictionary; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/EditorConfig/EditorConfigNamingStyleParser_NamingRule.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/EditorConfig/EditorConfigNamingStyleParser_NamingRule.cs index 4b0d78792aae8..894beddc04a6e 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/EditorConfig/EditorConfigNamingStyleParser_NamingRule.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/EditorConfig/EditorConfigNamingStyleParser_NamingRule.cs @@ -16,7 +16,7 @@ private static bool TryGetSerializableNamingRule( string namingRuleTitle, SymbolSpecification symbolSpec, NamingStyle namingStyle, - IReadOnlyDictionary conventionsDictionary, + IReadOnlyDictionary conventionsDictionary, [NotNullWhen(true)] out SerializableNamingRule? serializableNamingRule) { if (!TryGetRuleSeverity(namingRuleTitle, conventionsDictionary, out var severity)) @@ -43,13 +43,13 @@ internal static bool TryGetRuleSeverity( private static bool TryGetRuleSeverity( string namingRuleName, - IReadOnlyDictionary conventionsDictionary, + IReadOnlyDictionary conventionsDictionary, out ReportDiagnostic severity) { - var result = TryGetRuleSeverity( + var result = TryGetRuleSeverity( namingRuleName, conventionsDictionary, - x => x, + x => x!, x => null, // we don't have a tuple out var tuple); severity = tuple.severity; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/EditorConfig/EditorConfigNamingStyleParser_NamingStyle.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/EditorConfig/EditorConfigNamingStyleParser_NamingStyle.cs index 50e5e709b87c4..80099a64b1c3a 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/EditorConfig/EditorConfigNamingStyleParser_NamingStyle.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/EditorConfig/EditorConfigNamingStyleParser_NamingStyle.cs @@ -47,13 +47,13 @@ internal static bool TryGetNamingStyleData( private static bool TryGetNamingStyleData( string namingRuleName, - IReadOnlyDictionary rawOptions, + IReadOnlyDictionary rawOptions, out NamingStyle namingStyle) { - return TryGetNamingStyleData( + return TryGetNamingStyleData( namingRuleName, rawOptions, - s => s, + s => s ?? string.Empty, x => null, s => (s ?? string.Empty, null), (nameTuple, prefixTuple, suffixTuple, wordSeparatorTuple, capitalizationTuple) => diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/EditorConfig/EditorConfigNamingStyleParser_SymbolSpec.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/EditorConfig/EditorConfigNamingStyleParser_SymbolSpec.cs index a299f9e7fe7a6..67e3176c0cd5c 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/EditorConfig/EditorConfigNamingStyleParser_SymbolSpec.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/EditorConfig/EditorConfigNamingStyleParser_SymbolSpec.cs @@ -46,13 +46,13 @@ internal static bool TryGetSymbolSpec( private static bool TryGetSymbolSpec( string namingRuleTitle, - IReadOnlyDictionary conventionsDictionary, + IReadOnlyDictionary conventionsDictionary, [NotNullWhen(true)] out SymbolSpecification? symbolSpec) { - return TryGetSymbolSpec( + return TryGetSymbolSpec( namingRuleTitle, conventionsDictionary, - s => (s, null), + s => (s ?? string.Empty, null), () => null, (t0, t1, t2, t3) => new SymbolSpecification( Guid.NewGuid(), diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/Serialization/NamingStylePreferences.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/Serialization/NamingStylePreferences.cs index 980a1801b3be1..bf1d0e1124e80 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/Serialization/NamingStylePreferences.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/Serialization/NamingStylePreferences.cs @@ -56,13 +56,9 @@ public NamingStylePreferences( } public static NamingStylePreferences Default => FromXElement(XElement.Parse(DefaultNamingPreferencesString)); - public static NamingStylePreferences Empty => new(ImmutableArray.Empty, ImmutableArray.Empty, ImmutableArray.Empty); public static string DefaultNamingPreferencesString => _defaultNamingPreferencesString; - public bool IsEmpty - => SymbolSpecifications.IsEmpty && NamingStyles.IsEmpty && NamingRules.IsEmpty; - internal NamingStyle GetNamingStyle(Guid namingStyleID) => NamingStyles.Single(s => s.ID == namingStyleID); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/Serialization/SymbolSpecification.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/Serialization/SymbolSpecification.cs index 0463567afcbc3..c2cb04b627680 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/Serialization/SymbolSpecification.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/Serialization/SymbolSpecification.cs @@ -24,7 +24,6 @@ namespace Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles { [DataContract] - [DebuggerDisplay("{GetDebuggerDisplay(),nq}")] internal sealed class SymbolSpecification : IEquatable, IObjectWritable { private static readonly SymbolSpecification DefaultSymbolSpecificationTemplate = CreateDefaultSymbolSpecification(); @@ -58,9 +57,6 @@ public SymbolSpecification( RequiredModifierList = modifiers.IsDefault ? DefaultSymbolSpecificationTemplate.RequiredModifierList : modifiers; } - private string GetDebuggerDisplay() - => Name; - public static SymbolSpecification CreateDefaultSymbolSpecification() { // This is used to create new, empty symbol specifications for users to then customize. diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/EditorConfig/EditorConfigStorageLocationExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/EditorConfig/EditorConfigStorageLocationExtensions.cs new file mode 100644 index 0000000000000..a850af2038898 --- /dev/null +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/EditorConfig/EditorConfigStorageLocationExtensions.cs @@ -0,0 +1,34 @@ +// 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.Reflection; +using System.Collections.Generic; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis.Diagnostics; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Options +{ + internal static class EditorConfigStorageLocationExtensions + { + public static bool TryGetOption(this IEditorConfigStorageLocation editorConfigStorageLocation, AnalyzerConfigOptions analyzerConfigOptions, Type type, out object? value) + { + // This is a workaround until we have an API for enumeratings AnalyzerConfigOptions. See https://github.com/dotnet/roslyn/issues/41840 + if (analyzerConfigOptions.GetType().FullName == typeof(DictionaryAnalyzerConfigOptions).FullName) + { + var optionsField = analyzerConfigOptions.GetType().GetField(nameof(DictionaryAnalyzerConfigOptions.Options), BindingFlags.NonPublic | BindingFlags.Instance); + Contract.ThrowIfNull(optionsField); + + var options = optionsField.GetValue(analyzerConfigOptions); + Contract.ThrowIfNull(options); + + return editorConfigStorageLocation.TryGetOption((ImmutableDictionary)options, type, out value); + } + + value = null; + return false; + } + } +} diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/EditorConfig/EditorConfigStorageLocation`1.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/EditorConfig/EditorConfigStorageLocation`1.cs index 0e349c86bd476..fe92fbb888eff 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/EditorConfig/EditorConfigStorageLocation`1.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/EditorConfig/EditorConfigStorageLocation`1.cs @@ -8,8 +8,6 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.CodeAnalysis.CodeStyle; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles; using Roslyn.Utilities; #if CODE_STYLE @@ -61,9 +59,10 @@ public EditorConfigStorageLocation(string keyName, Func> par _getEditorConfigStringForValue = getEditorConfigStringForValue ?? throw new ArgumentNullException(nameof(getEditorConfigStringForValue)); } - public bool TryGetOption(StructuredAnalyzerConfigOptions options, Type type, out object? result) + public bool TryGetOption(IReadOnlyDictionary rawOptions, Type type, out object? result) { - if (options.TryGetValue(KeyName, out var value)) + if (rawOptions.TryGetValue(KeyName, out var value) + && value is object) { var ret = TryGetOption(value, type, out var typedResult); result = typedResult; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/EditorConfig/IEditorConfigStorageLocation.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/EditorConfig/IEditorConfigStorageLocation.cs index 9d6d0521f2b27..fdaca1ff999d2 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/EditorConfig/IEditorConfigStorageLocation.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/EditorConfig/IEditorConfigStorageLocation.cs @@ -4,15 +4,11 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles; namespace Microsoft.CodeAnalysis.Options { internal interface IEditorConfigStorageLocation { - bool TryGetOption(StructuredAnalyzerConfigOptions options, Type type, out object? value); + bool TryGetOption(IReadOnlyDictionary rawOptions, Type type, out object? value); } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/EditorConfig/NamingStylePreferenceEditorConfigStorageLocation.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/EditorConfig/NamingStylePreferenceEditorConfigStorageLocation.cs index 650356dd5b95d..e3dd6c3e96ae9 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/EditorConfig/NamingStylePreferenceEditorConfigStorageLocation.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/EditorConfig/NamingStylePreferenceEditorConfigStorageLocation.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles; using Roslyn.Utilities; @@ -13,13 +12,30 @@ namespace Microsoft.CodeAnalysis.Options { internal sealed class NamingStylePreferenceEditorConfigStorageLocation : OptionStorageLocation2, IEditorConfigStorageLocation { - public bool TryGetOption(StructuredAnalyzerConfigOptions options, Type type, out object result) + public bool TryGetOption(IReadOnlyDictionary rawOptions, Type type, out object result) + { + var tuple = ParseDictionary(rawOptions, type); + result = tuple.result; + return tuple.succeeded; + } + + private static (object result, bool succeeded) ParseDictionary( + IReadOnlyDictionary allRawConventions, Type type) { if (type == typeof(NamingStylePreferences)) { - var preferences = options.GetNamingStylePreferences(); - result = preferences; - return !preferences.IsEmpty; + var editorconfigNamingStylePreferences = EditorConfigNamingStyleParser.GetNamingStylesFromDictionary(allRawConventions); + + if (!editorconfigNamingStylePreferences.NamingRules.Any() && + !editorconfigNamingStylePreferences.NamingStyles.Any() && + !editorconfigNamingStylePreferences.SymbolSpecifications.Any()) + { + // We were not able to parse any rules from editorconfig, tell the caller that the parse failed + return (result: editorconfigNamingStylePreferences, succeeded: false); + } + + // no existing naming styles were passed so just return the set of styles that were parsed from editorconfig + return (result: editorconfigNamingStylePreferences, succeeded: true); } throw ExceptionUtilities.UnexpectedValue(type);