diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerConfigOptions.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerConfigOptions.cs
index 1e98c9cccb6ee..55ecf2fda55cf 100644
--- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerConfigOptions.cs
+++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerConfigOptions.cs
@@ -3,6 +3,7 @@
// 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
@@ -19,5 +20,12 @@ 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 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 368b6c4176976..48ac671a064ee 100644
--- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/DictionaryAnalyzerConfigOptions.cs
+++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/DictionaryAnalyzerConfigOptions.cs
@@ -2,6 +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 System.Diagnostics.CodeAnalysis;
@@ -13,6 +14,8 @@ 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)
@@ -20,5 +23,8 @@ 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 54e2766c89e34..c2331cd21ec09 100644
--- a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt
+++ b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt
@@ -66,6 +66,7 @@ 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 fff2e11b97eea..8fb3042f2c572 100644
--- a/src/EditorFeatures/CSharpTest/Diagnostics/NamingStyles/EditorConfigNamingStyleParserTests.cs
+++ b/src/EditorFeatures/CSharpTest/Diagnostics/NamingStyles/EditorConfigNamingStyleParserTests.cs
@@ -7,17 +7,20 @@
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()
{
@@ -469,5 +472,111 @@ 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 1f137a0d8c170..64ec7898fe01e 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 static Microsoft.CodeAnalysis.ProjectState;
+using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings.DataProvider
{
@@ -51,7 +51,7 @@ protected void Update()
return;
}
- var configOptionsProvider = new ProjectAnalyzerConfigOptionsProvider(project.State);
+ var configOptionsProvider = new ProjectState.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 AnalyzerConfigOptionsResult? _result;
+ private readonly AnalyzerConfigData? _result;
- public CombinedAnalyzerConfigOptions(AnalyzerConfigOptions workspaceOptions, AnalyzerConfigOptionsResult? result)
+ public CombinedAnalyzerConfigOptions(AnalyzerConfigOptions workspaceOptions, AnalyzerConfigData? result)
{
_workspaceOptions = workspaceOptions;
_result = result;
@@ -131,6 +131,34 @@ 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 3fec7795a0148..19a21b395b96f 100644
--- a/src/Features/Core/Portable/Diagnostics/AnalyzerConfigOptionSet+AnalyzerConfigOptionsImpl.cs
+++ b/src/Features/Core/Portable/Diagnostics/AnalyzerConfigOptionSet+AnalyzerConfigOptionsImpl.cs
@@ -2,7 +2,10 @@
// 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
{
@@ -28,6 +31,9 @@ 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 bad20aa005ce6..1fe52a5d93159 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, AnalyzerConfigOptionsResult? analyzerConfigOptions)
+ private bool IsCandidateForFullSolutionAnalysis(DiagnosticAnalyzer analyzer, Project project, AnalyzerConfigData? 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) != ReportDiagnostic.Hidden);
+ return descriptors.Any(d => d.GetEffectiveSeverity(project.CompilationOptions!, analyzerConfigOptions?.Result) != ReportDiagnostic.Hidden);
}
private void RaiseProjectDiagnosticsIfNeeded(
diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/AnalyzersCommandHandler.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/AnalyzersCommandHandler.cs
index a2f36a1e8df05..89101c51cfdba 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);
+ var severity = diagnosticItem.Descriptor.GetEffectiveSeverity(project.CompilationOptions, analyzerConfigOptions?.Result);
selectedItemSeverities.Add(severity);
}
}
diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/BaseDiagnosticAndGeneratorItemSource.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/BaseDiagnosticAndGeneratorItemSource.cs
index 621c689c63e31..114541cfd6522 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 AnalyzerConfigOptionsResult? _analyzerConfigOptions;
+ private AnalyzerConfigData? _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, AnalyzerConfigOptionsResult? analyzerConfigOptions)
+ private BulkObservableCollection CreateDiagnosticAndGeneratorItems(ProjectId projectId, string language, CompilationOptions options, AnalyzerConfigData? 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);
+ var effectiveSeverity = selectedDiagnostic.GetEffectiveSeverity(options, analyzerConfigOptions?.Result);
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);
+ var effectiveSeverity = item.Descriptor.GetEffectiveSeverity(project.CompilationOptions, newAnalyzerConfigOptions?.Result);
item.UpdateEffectiveSeverity(effectiveSeverity);
}
}
diff --git a/src/Workspaces/Core/Portable/Options/EditorConfig/EditorConfigDocumentOptionsProviderFactory.cs b/src/Workspaces/Core/Portable/Options/EditorConfig/EditorConfigDocumentOptionsProviderFactory.cs
index 0148466ebcc7e..bacb11927cb04 100644
--- a/src/Workspaces/Core/Portable/Options/EditorConfig/EditorConfigDocumentOptionsProviderFactory.cs
+++ b/src/Workspaces/Core/Portable/Options/EditorConfig/EditorConfigDocumentOptionsProviderFactory.cs
@@ -9,6 +9,7 @@
using System.Threading.Tasks;
using Roslyn.Utilities;
using Microsoft.CodeAnalysis.ErrorReporting;
+using Microsoft.CodeAnalysis.Diagnostics;
namespace Microsoft.CodeAnalysis.Options.EditorConfig
{
@@ -21,19 +22,25 @@ private sealed class EditorConfigDocumentOptionsProvider : IDocumentOptionsProvi
{
public async Task GetOptionsForDocumentAsync(Document document, CancellationToken cancellationToken)
{
- var options = await document.GetAnalyzerOptionsAsync(cancellationToken).ConfigureAwait(false);
-
- return new DocumentOptions(options);
+ var data = await document.GetAnalyzerOptionsAsync(cancellationToken).ConfigureAwait(false);
+ return new DocumentOptions(data?.AnalyzerConfigOptions);
}
private sealed class DocumentOptions : IDocumentOptions
{
- private readonly ImmutableDictionary _options;
- public DocumentOptions(ImmutableDictionary options)
+ private readonly StructuredAnalyzerConfigOptions? _options;
+
+ public DocumentOptions(StructuredAnalyzerConfigOptions? 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)
{
@@ -43,7 +50,7 @@ public bool TryGetDocumentOption(OptionKey option, out object? value)
try
{
- return editorConfigPersistence.TryGetOption(_options.AsNullable(), option.Option.Type, out value);
+ return editorConfigPersistence.TryGetOption(_options, 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 4e0d139840cb3..6e52e3a5db884 100644
--- a/src/Workspaces/Core/Portable/Options/OptionSet+AnalyzerConfigOptionsImpl.cs
+++ b/src/Workspaces/Core/Portable/Options/OptionSet+AnalyzerConfigOptionsImpl.cs
@@ -2,8 +2,11 @@
// 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
@@ -39,6 +42,10 @@ 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
new file mode 100644
index 0000000000000..c417192872bb6
--- /dev/null
+++ b/src/Workspaces/Core/Portable/Workspace/Solution/AnalyzerConfigData.cs
@@ -0,0 +1,28 @@
+// 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 c64163a5a1277..7f4f520e8d25a 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 Task> GetAnalyzerOptionsAsync(CancellationToken cancellationToken)
+ internal async 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,15 +535,12 @@ internal Task> GetAnalyzerOptionsAsync(Cance
}
}
- if (effectiveFilePath != null)
+ if (effectiveFilePath == null)
{
- return Project.State.GetAnalyzerOptionsForPathAsync(effectiveFilePath, cancellationToken);
- }
- else
- {
- // Really no idea where this is going, so bail
- return Task.FromResult(DictionaryAnalyzerConfigOptions.EmptyDictionary);
+ return null;
}
+
+ 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 fa20f2786d59a..5ec37f9535f0e 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 AnalyzerConfigOptionsResult? GetAnalyzerConfigOptions()
+ internal AnalyzerConfigData? 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 d36dd5f83160d..9370c87f3b2c6 100644
--- a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState.cs
+++ b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState.cs
@@ -248,15 +248,13 @@ 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 configSet = await _lazyAnalyzerConfigOptions.GetValueAsync(cancellationToken).ConfigureAwait(false);
- return configSet.GetOptionsForSourcePath(path).AnalyzerOptions;
+ var cache = await _lazyAnalyzerConfigOptions.GetValueAsync(cancellationToken).ConfigureAwait(false);
+ return cache.GetOptionsForSourcePath(path);
}
- public AnalyzerConfigOptionsResult? GetAnalyzerConfigOptions()
+ public AnalyzerConfigData? 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.
@@ -296,8 +294,11 @@ internal sealed class ProjectAnalyzerConfigOptionsProvider : AnalyzerConfigOptio
public ProjectAnalyzerConfigOptionsProvider(ProjectState projectState)
=> _projectState = projectState;
+ private AnalyzerConfigOptionsCache GetCache()
+ => _projectState._lazyAnalyzerConfigOptions.GetValue(CancellationToken.None);
+
public override AnalyzerConfigOptions GlobalOptions
- => GetOptionsForSourcePath(string.Empty);
+ => GetCache().GlobalConfigOptions.AnalyzerConfigOptions;
public override AnalyzerConfigOptions GetOptions(SyntaxTree tree)
=> GetOptionsForSourcePath(tree.FilePath);
@@ -309,7 +310,7 @@ public override AnalyzerConfigOptions GetOptions(AdditionalText textFile)
}
public AnalyzerConfigOptions GetOptionsForSourcePath(string path)
- => new DictionaryAnalyzerConfigOptions(_projectState._lazyAnalyzerConfigOptions.GetValue(CancellationToken.None).GetOptionsForSourcePath(path).AnalyzerOptions);
+ => GetCache().GetOptionsForSourcePath(path).AnalyzerConfigOptions;
}
private sealed class ProjectSyntaxTreeOptionsProvider : SyntaxTreeOptionsProvider
@@ -371,20 +372,20 @@ private static ValueSource ComputeAnalyzerConfigOpti
private readonly struct AnalyzerConfigOptionsCache
{
- private readonly ConcurrentDictionary _sourcePathToResult = new();
- private readonly Func _computeFunction;
- private readonly AnalyzerConfigSet _configSet;
+ private readonly ConcurrentDictionary _sourcePathToResult = new();
+ private readonly Func _computeFunction;
+ private readonly Lazy _global;
public AnalyzerConfigOptionsCache(AnalyzerConfigSet configSet)
{
- _configSet = configSet;
- _computeFunction = _configSet.GetOptionsForSourcePath;
+ _global = new Lazy(() => new AnalyzerConfigData(configSet.GlobalConfigOptions));
+ _computeFunction = path => new AnalyzerConfigData(configSet.GetOptionsForSourcePath(path));
}
- public AnalyzerConfigOptionsResult GlobalConfigOptions
- => _configSet.GlobalConfigOptions;
+ public AnalyzerConfigData GlobalConfigOptions
+ => _global.Value;
- public AnalyzerConfigOptionsResult GetOptionsForSourcePath(string sourcePath)
+ public AnalyzerConfigData GetOptionsForSourcePath(string sourcePath)
=> _sourcePathToResult.GetOrAdd(sourcePath, _computeFunction);
}
diff --git a/src/Workspaces/CoreTest/CodeStyle/EditorConfigCodeStyleParserTests.cs b/src/Workspaces/CoreTest/CodeStyle/EditorConfigCodeStyleParserTests.cs
index 6985c5d8ccbcb..9984dd3cf084d 100644
--- a/src/Workspaces/CoreTest/CodeStyle/EditorConfigCodeStyleParserTests.cs
+++ b/src/Workspaces/CoreTest/CodeStyle/EditorConfigCodeStyleParserTests.cs
@@ -3,11 +3,16 @@
// 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
@@ -62,7 +67,7 @@ public void TestParseEditorConfigAccessibilityModifiers(string args, int value,
var storageLocation = CodeStyleOptions2.RequireAccessibilityModifiers.StorageLocations
.OfType>>()
.Single();
- var allRawConventions = new Dictionary { { storageLocation.KeyName, args } };
+ var allRawConventions = new StructuredAnalyzerConfigOptions(ImmutableDictionary.Empty.Add(storageLocation.KeyName, args));
Assert.True(storageLocation.TryGetOption(allRawConventions, typeof(CodeStyleOption2), out var parsedCodeStyleOption));
var codeStyleOption = (CodeStyleOption2)parsedCodeStyleOption!;
@@ -84,7 +89,7 @@ public void TestParseEditorConfigEndOfLine(string configurationString, string ne
var storageLocation = FormattingOptions.NewLine.StorageLocations
.OfType>()
.Single();
- var allRawConventions = new Dictionary { { storageLocation.KeyName, configurationString } };
+ var allRawConventions = new StructuredAnalyzerConfigOptions(ImmutableDictionary.Empty.Add(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 7e74d51ac4cb4..749b8389a07a4 100644
--- a/src/Workspaces/CoreTest/EditorConfigStorageLocation/NamingStylePreferenceEditorConfigStorageLocationTests.cs
+++ b/src/Workspaces/CoreTest/EditorConfigStorageLocation/NamingStylePreferenceEditorConfigStorageLocationTests.cs
@@ -6,8 +6,12 @@
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
@@ -18,19 +22,7 @@ public class NamingStylePreferenceEditorConfigStorageLocationTests
public static void TestEmptyDictionaryReturnNoNamingStylePreferencesObjectReturnsFalse()
{
var editorConfigStorageLocation = new NamingStylePreferenceEditorConfigStorageLocation();
- 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 _);
-
+ var result = editorConfigStorageLocation.TryGetOption(new StructuredAnalyzerConfigOptions(ImmutableDictionary.Empty), typeof(NamingStylePreferences), out _);
Assert.False(result, "Expected TryParseReadonlyDictionary to return 'false' for empty dictionary");
}
@@ -38,7 +30,7 @@ public static void TestEmptyDictionaryDefaultNamingStylePreferencesObjectReturns
public static void TestNonEmptyDictionaryReturnsTrue()
{
var editorConfigStorageLocation = new NamingStylePreferenceEditorConfigStorageLocation();
- var newDictionary = new Dictionary()
+ var options = new StructuredAnalyzerConfigOptions(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",
@@ -46,155 +38,22 @@ 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());
- var result = editorConfigStorageLocation.TryGetOption(
- newDictionary,
- typeof(NamingStylePreferences),
- out var combinedNamingStyles);
+ var result = editorConfigStorageLocation.TryGetOption(options, typeof(NamingStylePreferences), out var value);
Assert.True(result, "Expected non-empty dictionary to return true");
- var namingStylePreferences = Assert.IsAssignableFrom(combinedNamingStyles);
+ var namingStylePreferences = Assert.IsAssignableFrom(value);
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 Dictionary(), typeof(object), out var @object);
+ editorConfigStorageLocation.TryGetOption(new StructuredAnalyzerConfigOptions(ImmutableDictionary.Empty), typeof(object), out var @object);
});
}
}
diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems
index 53a4405df7ffb..c8b05d5ce6528 100644
--- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems
+++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems
@@ -193,6 +193,7 @@
+
@@ -370,7 +371,6 @@
-
@@ -574,7 +574,7 @@
- Diagnostics\AnalyzerConfigOptionsDictionary.cs
+ Diagnostics\DictionaryAnalyzerConfigOptions.cs
diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Diagnostics/StructuredAnalyzerConfigOptions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Diagnostics/StructuredAnalyzerConfigOptions.cs
new file mode 100644
index 0000000000000..19875f0010f84
--- /dev/null
+++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Diagnostics/StructuredAnalyzerConfigOptions.cs
@@ -0,0 +1,96 @@
+// 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))
+ {
+ }
+
+ 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