From da44e1178c1360f15babd21f93167b661ad3197c Mon Sep 17 00:00:00 2001 From: tmat Date: Fri, 12 Jul 2024 15:45:11 -0700 Subject: [PATCH 1/5] Convert ImplementTypeOptions to editorconfig --- .../ImplementInterfaceTests.cs | 18 ++-- .../ImplementAbstractClassCommandHandler.vb | 20 +++-- .../ImplementInterfaceCommandHandler.vb | 19 +++-- ...tAbstractClassOrInterfaceCommandHandler.vb | 8 +- ...ctImplementAbstractClassCodeFixProvider.cs | 3 +- .../ImplementAbstractClassData.cs | 8 +- ...stractImplementInterfaceCodeFixProvider.cs | 4 +- .../ImplementTypeInsertionBehavior.cs | 0 .../ImplementType/ImplementTypeOptions.cs | 82 +++++++++++++++++++ ...ImplementTypePropertyGenerationBehavior.cs | 0 .../Microsoft.CodeAnalysis.Features.csproj | 2 +- .../EditorConfigOptionsEnumerator.cs | 9 +- .../CodeActions/OmniSharpCodeActionOptions.cs | 24 +----- .../OmniSharpCodeFixContextFactory.cs | 13 ++- .../Options/CodeActionOptionsStorage.cs | 3 +- .../Options/ImplementTypeOptionsStorage.cs | 28 ------- ...dChangeConfigurationNotificationHandler.cs | 2 +- ...ngeConfigurationNotificationHandlerTest.cs | 2 +- .../Def/Options/VisualStudioOptionStorage.cs | 2 +- .../BasicEditorConfigGeneratorTests.vb | 4 + .../CSharpEditorConfigGeneratorTests.vb | 4 + .../ImplementType/ImplementTypeOptions.cs | 20 ----- .../Options/EditorConfigValueSerializer.cs | 18 ++++ .../Options/EditorConfigValueSerializer`1.cs | 1 + .../Core/Utilities/BidirectionalMap.cs | 10 ++- .../Core/CodeFixes/CodeActionOptions.cs | 4 - 26 files changed, 187 insertions(+), 121 deletions(-) rename src/{Workspaces => Features}/Core/Portable/ImplementType/ImplementTypeInsertionBehavior.cs (100%) create mode 100644 src/Features/Core/Portable/ImplementType/ImplementTypeOptions.cs rename src/{Workspaces => Features}/Core/Portable/ImplementType/ImplementTypePropertyGenerationBehavior.cs (100%) delete mode 100644 src/LanguageServer/Protocol/Features/Options/ImplementTypeOptionsStorage.cs delete mode 100644 src/Workspaces/Core/Portable/ImplementType/ImplementTypeOptions.cs diff --git a/src/EditorFeatures/CSharpTest/CodeActions/ImplementInterface/ImplementInterfaceTests.cs b/src/EditorFeatures/CSharpTest/CodeActions/ImplementInterface/ImplementInterfaceTests.cs index 3a8f14b625acc..cf28711a00bfa 100644 --- a/src/EditorFeatures/CSharpTest/CodeActions/ImplementInterface/ImplementInterfaceTests.cs +++ b/src/EditorFeatures/CSharpTest/CodeActions/ImplementInterface/ImplementInterfaceTests.cs @@ -8007,10 +8007,13 @@ void M() { } public int Prop => throw new System.NotImplementedException(); } """, - CodeActionOptions = (CodeActionOptions.Default with + Options = { - ImplementTypeOptions = new() { InsertionBehavior = ImplementTypeInsertionBehavior.AtTheEnd } - }).CreateProvider() + new OptionsCollection(LanguageNames.CSharp) + { + { ImplementTypeOptionsStorage.InsertionBehavior, ImplementTypeInsertionBehavior.AtTheEnd } + } + } }.RunAsync(); } @@ -8189,10 +8192,13 @@ class Class : IInterface public int WriteOnlyProp { set => throw new System.NotImplementedException(); } } """, - CodeActionOptions = (CodeActionOptions.Default with + Options = { - ImplementTypeOptions = new() { PropertyGenerationBehavior = ImplementTypePropertyGenerationBehavior.PreferAutoProperties } - }).CreateProvider() + new OptionsCollection(LanguageNames.CSharp) + { + { ImplementTypeOptionsStorage.PropertyGenerationBehavior, ImplementTypePropertyGenerationBehavior.PreferAutoProperties } + } + } }.RunAsync(); } diff --git a/src/EditorFeatures/VisualBasic/ImplementAbstractClass/ImplementAbstractClassCommandHandler.vb b/src/EditorFeatures/VisualBasic/ImplementAbstractClass/ImplementAbstractClassCommandHandler.vb index 50a051022d0e0..d1a469235b50f 100644 --- a/src/EditorFeatures/VisualBasic/ImplementAbstractClass/ImplementAbstractClassCommandHandler.vb +++ b/src/EditorFeatures/VisualBasic/ImplementAbstractClass/ImplementAbstractClassCommandHandler.vb @@ -31,12 +31,11 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.ImplementAbstractClass MyBase.New(editorOperationsFactoryService, globalOptions) End Sub - Protected Overrides Function TryGetNewDocument( + Protected Overrides Async Function TryGetNewDocumentAsync( document As Document, - options As ImplementTypeOptions, typeSyntax As TypeSyntax, cancellationToken As CancellationToken - ) As Document + ) As Task(Of Document) If typeSyntax.Parent.Kind <> SyntaxKind.InheritsStatement Then Return Nothing @@ -47,14 +46,19 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.ImplementAbstractClass Return Nothing End If - Dim updatedDocument = ImplementAbstractClassData.TryImplementAbstractClassAsync( - document, classBlock, classBlock.ClassStatement.Identifier, options, cancellationToken).WaitAndGetResult(cancellationToken) - If updatedDocument IsNot Nothing AndAlso - updatedDocument.GetTextChangesAsync(document, cancellationToken).WaitAndGetResult(cancellationToken).Count = 0 Then + Dim updatedDocument = Await ImplementAbstractClassData.TryImplementAbstractClassAsync( + document, classBlock, classBlock.ClassStatement.Identifier, cancellationToken).ConfigureAwait(False) + + If updatedDocument Is Nothing Then Return Nothing End If - Return updatedDocument + Dim changes = Await updatedDocument.GetTextChangesAsync(document, cancellationToken).ConfigureAwait(False) + If changes.Any() Then + Return updatedDocument + End If + + Return Nothing End Function End Class End Namespace diff --git a/src/EditorFeatures/VisualBasic/ImplementInterface/ImplementInterfaceCommandHandler.vb b/src/EditorFeatures/VisualBasic/ImplementInterface/ImplementInterfaceCommandHandler.vb index a93f14150b908..d0dfc3f1ebe9c 100644 --- a/src/EditorFeatures/VisualBasic/ImplementInterface/ImplementInterfaceCommandHandler.vb +++ b/src/EditorFeatures/VisualBasic/ImplementInterface/ImplementInterfaceCommandHandler.vb @@ -31,28 +31,31 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.ImplementInterface MyBase.New(editorOperationsFactoryService, globalOptions) End Sub - Protected Overrides Function TryGetNewDocument( + Protected Overrides Async Function TryGetNewDocumentAsync( document As Document, - options As ImplementTypeOptions, typeSyntax As TypeSyntax, cancellationToken As CancellationToken - ) As Document + ) As Task(Of Document) If typeSyntax.Parent.Kind <> SyntaxKind.ImplementsStatement Then Return Nothing End If Dim service = document.GetLanguageService(Of IImplementInterfaceService)() - Dim updatedDocument = service.ImplementInterfaceAsync( + Dim options = Await document.GetImplementTypeOptionsAsync(cancellationToken).ConfigureAwait(False) + + Dim updatedDocument = Await service.ImplementInterfaceAsync( document, options, typeSyntax.Parent, - cancellationToken).WaitAndGetResult(cancellationToken) - If updatedDocument.GetTextChangesAsync(document, cancellationToken).WaitAndGetResult(cancellationToken).Count = 0 Then - Return Nothing + cancellationToken).ConfigureAwait(False) + + Dim changes = Await updatedDocument.GetTextChangesAsync(document, cancellationToken).ConfigureAwait(False) + If changes.Any() Then + Return updatedDocument End If - Return updatedDocument + Return Nothing End Function End Class End Namespace diff --git a/src/EditorFeatures/VisualBasic/Utilities/CommandHandlers/AbstractImplementAbstractClassOrInterfaceCommandHandler.vb b/src/EditorFeatures/VisualBasic/Utilities/CommandHandlers/AbstractImplementAbstractClassOrInterfaceCommandHandler.vb index 5469e436a6183..598e16f37a888 100644 --- a/src/EditorFeatures/VisualBasic/Utilities/CommandHandlers/AbstractImplementAbstractClassOrInterfaceCommandHandler.vb +++ b/src/EditorFeatures/VisualBasic/Utilities/CommandHandlers/AbstractImplementAbstractClassOrInterfaceCommandHandler.vb @@ -39,11 +39,10 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.Utilities.CommandHandlers _globalOptions = globalOptions End Sub - Protected MustOverride Overloads Function TryGetNewDocument( + Protected MustOverride Overloads Function TryGetNewDocumentAsync( document As Document, - options As ImplementTypeOptions, typeSyntax As TypeSyntax, - cancellationToken As CancellationToken) As Document + cancellationToken As CancellationToken) As Task(Of Document) Private Function ExecuteCommand(args As ReturnKeyCommandArgs, context As CommandExecutionContext) As Boolean Implements ICommandHandler(Of ReturnKeyCommandArgs).ExecuteCommand Dim caretPointOpt = args.TextView.GetCaretPoint(args.SubjectBuffer) @@ -165,8 +164,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.Utilities.CommandHandlers Return False End If - Dim newDocument = TryGetNewDocument(document, _globalOptions.GetImplementTypeOptions(document.Project.Language), identifier, cancellationToken) - + Dim newDocument = TryGetNewDocumentAsync(document, identifier, cancellationToken).WaitAndGetResult(cancellationToken) If newDocument Is Nothing Then Return False End If diff --git a/src/Features/Core/Portable/ImplementAbstractClass/AbstractImplementAbstractClassCodeFixProvider.cs b/src/Features/Core/Portable/ImplementAbstractClass/AbstractImplementAbstractClassCodeFixProvider.cs index d62200df8bb55..b96844c5298ff 100644 --- a/src/Features/Core/Portable/ImplementAbstractClass/AbstractImplementAbstractClassCodeFixProvider.cs +++ b/src/Features/Core/Portable/ImplementAbstractClass/AbstractImplementAbstractClassCodeFixProvider.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.ImplementType; using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.ImplementAbstractClass; @@ -39,7 +40,7 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) return; var data = await ImplementAbstractClassData.TryGetDataAsync( - document, classNode, GetClassIdentifier(classNode), context.Options.GetOptions(document.Project.Services).ImplementTypeOptions, cancellationToken).ConfigureAwait(false); + document, classNode, GetClassIdentifier(classNode), cancellationToken).ConfigureAwait(false); if (data == null) return; diff --git a/src/Features/Core/Portable/ImplementAbstractClass/ImplementAbstractClassData.cs b/src/Features/Core/Portable/ImplementAbstractClass/ImplementAbstractClassData.cs index e4ae03bb70b93..6b43c78079bc4 100644 --- a/src/Features/Core/Portable/ImplementAbstractClass/ImplementAbstractClassData.cs +++ b/src/Features/Core/Portable/ImplementAbstractClass/ImplementAbstractClassData.cs @@ -38,7 +38,7 @@ internal sealed class ImplementAbstractClassData( public readonly INamedTypeSymbol AbstractClassType = abstractClassType; public static async Task TryGetDataAsync( - Document document, SyntaxNode classNode, SyntaxToken classIdentifier, ImplementTypeOptions options, CancellationToken cancellationToken) + Document document, SyntaxNode classNode, SyntaxToken classIdentifier, CancellationToken cancellationToken) { var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); if (semanticModel.GetDeclaredSymbol(classNode, cancellationToken) is not INamedTypeSymbol classType) @@ -62,15 +62,17 @@ internal sealed class ImplementAbstractClassData( if (unimplementedMembers.IsEmpty) return null; + var options = await document.GetImplementTypeOptionsAsync(cancellationToken).ConfigureAwait(false); + return new ImplementAbstractClassData( document, options, classNode, classIdentifier, classType, abstractClassType, unimplementedMembers); } public static async Task TryImplementAbstractClassAsync( - Document document, SyntaxNode classNode, SyntaxToken classIdentifier, ImplementTypeOptions options, CancellationToken cancellationToken) + Document document, SyntaxNode classNode, SyntaxToken classIdentifier, CancellationToken cancellationToken) { - var data = await TryGetDataAsync(document, classNode, classIdentifier, options, cancellationToken).ConfigureAwait(false); + var data = await TryGetDataAsync(document, classNode, classIdentifier, cancellationToken).ConfigureAwait(false); if (data == null) return null; diff --git a/src/Features/Core/Portable/ImplementInterface/AbstractImplementInterfaceCodeFixProvider.cs b/src/Features/Core/Portable/ImplementInterface/AbstractImplementInterfaceCodeFixProvider.cs index 7b764332d0c5b..8a0f328e31f6f 100644 --- a/src/Features/Core/Portable/ImplementInterface/AbstractImplementInterfaceCodeFixProvider.cs +++ b/src/Features/Core/Portable/ImplementInterface/AbstractImplementInterfaceCodeFixProvider.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.ImplementType; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; @@ -37,12 +38,13 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) if (!token.Span.IntersectsWith(span)) return; + var options = await document.GetImplementTypeOptionsAsync(cancellationToken).ConfigureAwait(false); + foreach (var type in token.Parent.GetAncestorsOrThis()) { if (this.IsTypeInInterfaceBaseList(type)) { var service = document.GetRequiredLanguageService(); - var options = context.Options.GetOptions(document.Project.Services).ImplementTypeOptions; var info = await service.AnalyzeAsync( document, type, cancellationToken).ConfigureAwait(false); diff --git a/src/Workspaces/Core/Portable/ImplementType/ImplementTypeInsertionBehavior.cs b/src/Features/Core/Portable/ImplementType/ImplementTypeInsertionBehavior.cs similarity index 100% rename from src/Workspaces/Core/Portable/ImplementType/ImplementTypeInsertionBehavior.cs rename to src/Features/Core/Portable/ImplementType/ImplementTypeInsertionBehavior.cs diff --git a/src/Features/Core/Portable/ImplementType/ImplementTypeOptions.cs b/src/Features/Core/Portable/ImplementType/ImplementTypeOptions.cs new file mode 100644 index 0000000000000..c939ad99f330b --- /dev/null +++ b/src/Features/Core/Portable/ImplementType/ImplementTypeOptions.cs @@ -0,0 +1,82 @@ +// 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.Immutable; +using System.Diagnostics; +using System.Runtime.Serialization; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Options; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.ImplementType; + +[DataContract] +internal readonly record struct ImplementTypeOptions() +{ + [DataMember] + public ImplementTypeInsertionBehavior InsertionBehavior { get; init; } = ImplementTypeInsertionBehavior.WithOtherMembersOfTheSameKind; + + [DataMember] + public ImplementTypePropertyGenerationBehavior PropertyGenerationBehavior { get; init; } = ImplementTypePropertyGenerationBehavior.PreferThrowingProperties; + + public static readonly ImplementTypeOptions Default = new(); +} + +internal static class ImplementTypeOptionsStorage +{ + public static readonly PerLanguageOption2 InsertionBehavior = new( + "dotnet_member_insertion_location", + defaultValue: ImplementTypeOptions.Default.InsertionBehavior, + group: MemberDisplayOptionsStorage.TypeMemberGroup, + isEditorConfigOption: true, + serializer: EditorConfigValueSerializer.CreateSerializerForEnum( + map: new BidirectionalMap( + [ + ("at_the_end", ImplementTypeInsertionBehavior.AtTheEnd), + ("with_other_members_of_the_same_kind", ImplementTypeInsertionBehavior.WithOtherMembersOfTheSameKind), + ]), + alternative: ImmutableDictionary.Empty + .Add("AtTheEnd", ImplementTypeInsertionBehavior.AtTheEnd) + .Add("WithOtherMembersOfTheSameKind", ImplementTypeInsertionBehavior.WithOtherMembersOfTheSameKind) + )); + + public static readonly PerLanguageOption2 PropertyGenerationBehavior = new( + "dotnet_property_generation_behavior", + defaultValue: ImplementTypeOptions.Default.PropertyGenerationBehavior, + group: MemberDisplayOptionsStorage.TypeMemberGroup, + isEditorConfigOption: true, + serializer: EditorConfigValueSerializer.CreateSerializerForEnum( + map: new BidirectionalMap( + [ + ("prefer_throwing_properties", ImplementTypePropertyGenerationBehavior.PreferThrowingProperties), + ("prefer_auto_properties", ImplementTypePropertyGenerationBehavior.PreferAutoProperties), + ]), + alternative: ImmutableDictionary.Empty + .Add("PreferThrowingProperties", ImplementTypePropertyGenerationBehavior.PreferThrowingProperties) + .Add("PreferAutoProperties", ImplementTypePropertyGenerationBehavior.PreferAutoProperties) + )); + + /// + /// Options that we expect the user to set in editorconfig. + /// + public static readonly ImmutableArray EditorConfigOptions = [InsertionBehavior, PropertyGenerationBehavior]; +} + +internal static class ImplementTypeOptionsProviders +{ + public static ImplementTypeOptions GetImplementTypeOptions(this IOptionsReader reader, string language) + => new() + { + InsertionBehavior = reader.GetOption(ImplementTypeOptionsStorage.InsertionBehavior, language), + PropertyGenerationBehavior = reader.GetOption(ImplementTypeOptionsStorage.PropertyGenerationBehavior, language) + }; + + public static async ValueTask GetImplementTypeOptionsAsync(this Document document, CancellationToken cancellationToken) + { + var configOptions = await document.GetAnalyzerConfigOptionsAsync(cancellationToken).ConfigureAwait(false); + return configOptions.GetImplementTypeOptions(document.Project.Language); + } +} diff --git a/src/Workspaces/Core/Portable/ImplementType/ImplementTypePropertyGenerationBehavior.cs b/src/Features/Core/Portable/ImplementType/ImplementTypePropertyGenerationBehavior.cs similarity index 100% rename from src/Workspaces/Core/Portable/ImplementType/ImplementTypePropertyGenerationBehavior.cs rename to src/Features/Core/Portable/ImplementType/ImplementTypePropertyGenerationBehavior.cs diff --git a/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj b/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj index 8d5472cddbcf7..ad1ceb9784462 100644 --- a/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj +++ b/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj @@ -52,7 +52,7 @@ - + diff --git a/src/Features/Core/Portable/Options/EditorConfig/EditorConfigOptionsEnumerator.cs b/src/Features/Core/Portable/Options/EditorConfig/EditorConfigOptionsEnumerator.cs index f560d9a2097e9..2e7fbb6e9aa0c 100644 --- a/src/Features/Core/Portable/Options/EditorConfig/EditorConfigOptionsEnumerator.cs +++ b/src/Features/Core/Portable/Options/EditorConfig/EditorConfigOptionsEnumerator.cs @@ -8,13 +8,12 @@ using System.Composition; using System.Linq; using Microsoft.CodeAnalysis.CodeStyle; -using Microsoft.CodeAnalysis.Completion; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Features.EmbeddedLanguages.Json.LanguageServices; using Microsoft.CodeAnalysis.Features.EmbeddedLanguages.RegularExpressions.LanguageServices; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.ImplementType; using Microsoft.CodeAnalysis.ValidateFormatString; namespace Microsoft.CodeAnalysis.Options; @@ -43,7 +42,11 @@ internal sealed class EditorConfigOptionsEnumerator( yield return ("unsupported", RegexOptionsStorage.UnsupportedOptions); } - yield return (FeaturesResources.NET_Code_Actions, MemberDisplayOptionsStorage.EditorConfigOptions); + yield return (FeaturesResources.NET_Code_Actions, + [ + .. ImplementTypeOptionsStorage.EditorConfigOptions, + .. MemberDisplayOptionsStorage.EditorConfigOptions + ]); yield return (WorkspacesResources.dot_NET_Coding_Conventions, [ diff --git a/src/Features/ExternalAccess/OmniSharp/CodeActions/OmniSharpCodeActionOptions.cs b/src/Features/ExternalAccess/OmniSharp/CodeActions/OmniSharpCodeActionOptions.cs index b1ece57d5dc27..9ad0bf9fef5f0 100644 --- a/src/Features/ExternalAccess/OmniSharp/CodeActions/OmniSharpCodeActionOptions.cs +++ b/src/Features/ExternalAccess/OmniSharp/CodeActions/OmniSharpCodeActionOptions.cs @@ -2,33 +2,13 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CodeCleanup; +using System; using Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.ImplementType; using Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.Options; -using Microsoft.CodeAnalysis.Formatting; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.ImplementType; -using Microsoft.CodeAnalysis.SymbolSearch; namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.CodeActions { internal readonly record struct OmniSharpCodeActionOptions( OmniSharpImplementTypeOptions ImplementTypeOptions, - OmniSharpLineFormattingOptions LineFormattingOptions) - { -#pragma warning disable IDE0060 // Remove unused parameter - internal CodeActionOptions GetCodeActionOptions(LanguageServices languageServices) -#pragma warning restore IDE0060 // Remove unused parameter - { - return CodeActionOptions.Default with - { - ImplementTypeOptions = new() - { - InsertionBehavior = (ImplementTypeInsertionBehavior)ImplementTypeOptions.InsertionBehavior, - PropertyGenerationBehavior = (ImplementTypePropertyGenerationBehavior)ImplementTypeOptions.PropertyGenerationBehavior - } - }; - } - } + OmniSharpLineFormattingOptions LineFormattingOptions); } diff --git a/src/Features/ExternalAccess/OmniSharp/CodeActions/OmniSharpCodeFixContextFactory.cs b/src/Features/ExternalAccess/OmniSharp/CodeActions/OmniSharpCodeFixContextFactory.cs index a1b17e290d85e..36df7d600bbcd 100644 --- a/src/Features/ExternalAccess/OmniSharp/CodeActions/OmniSharpCodeFixContextFactory.cs +++ b/src/Features/ExternalAccess/OmniSharp/CodeActions/OmniSharpCodeFixContextFactory.cs @@ -8,7 +8,6 @@ using System.Threading; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.CodeActions @@ -20,17 +19,21 @@ public static CodeFixContext CreateCodeFixContext( TextSpan span, ImmutableArray diagnostics, Action> registerCodeFix, +#pragma warning disable IDE0060 // Remove unused parameter OmniSharpCodeActionOptions options, +#pragma warning restore IDE0060 // Remove unused parameter CancellationToken cancellationToken) - => new(document, span, diagnostics, registerCodeFix, new DelegatingCodeActionOptionsProvider(options.GetCodeActionOptions), cancellationToken); + => new(document, span, diagnostics, registerCodeFix, CodeActionOptions.DefaultProvider, cancellationToken); public static CodeAnalysis.CodeRefactorings.CodeRefactoringContext CreateCodeRefactoringContext( Document document, TextSpan span, Action registerRefactoring, +#pragma warning disable IDE0060 // Remove unused parameter OmniSharpCodeActionOptions options, +#pragma warning restore IDE0060 // Remove unused parameter CancellationToken cancellationToken) - => new(document, span, registerRefactoring, new DelegatingCodeActionOptionsProvider(options.GetCodeActionOptions), cancellationToken); + => new(document, span, registerRefactoring, CodeActionOptions.DefaultProvider, cancellationToken); public static FixAllContext CreateFixAllContext( Document? document, @@ -41,7 +44,9 @@ public static FixAllContext CreateFixAllContext( string? codeActionEquivalenceKey, IEnumerable diagnosticIds, FixAllContext.DiagnosticProvider fixAllDiagnosticProvider, +#pragma warning disable IDE0060 // Remove unused parameter Func optionsProvider, +#pragma warning restore IDE0060 // Remove unused parameter CancellationToken cancellationToken) => new(new FixAllState( fixAllProvider: NoOpFixAllProvider.Instance, @@ -53,7 +58,7 @@ public static FixAllContext CreateFixAllContext( codeActionEquivalenceKey, diagnosticIds, fixAllDiagnosticProvider, - new DelegatingCodeActionOptionsProvider(languageServices => optionsProvider(languageServices.Language).GetCodeActionOptions(languageServices))), + CodeActionOptions.DefaultProvider), CodeAnalysisProgress.None, cancellationToken); } } diff --git a/src/LanguageServer/Protocol/Features/Options/CodeActionOptionsStorage.cs b/src/LanguageServer/Protocol/Features/Options/CodeActionOptionsStorage.cs index 50e7ac0609b1b..a6b78f13a58e8 100644 --- a/src/LanguageServer/Protocol/Features/Options/CodeActionOptionsStorage.cs +++ b/src/LanguageServer/Protocol/Features/Options/CodeActionOptionsStorage.cs @@ -17,8 +17,7 @@ internal static class CodeActionOptionsStorage public static CodeActionOptions GetCodeActionOptions(this IGlobalOptionService globalOptions, LanguageServices languageServices) => new() { - SearchOptions = globalOptions.GetSymbolSearchOptions(languageServices.Language), - ImplementTypeOptions = globalOptions.GetImplementTypeOptions(languageServices.Language), + SearchOptions = globalOptions.GetSymbolSearchOptions(languageServices.Language) }; internal static CodeActionOptionsProvider GetCodeActionOptionsProvider(this IGlobalOptionService globalOptions) diff --git a/src/LanguageServer/Protocol/Features/Options/ImplementTypeOptionsStorage.cs b/src/LanguageServer/Protocol/Features/Options/ImplementTypeOptionsStorage.cs deleted file mode 100644 index 4fd63975954af..0000000000000 --- a/src/LanguageServer/Protocol/Features/Options/ImplementTypeOptionsStorage.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 Microsoft.CodeAnalysis.Options; - -namespace Microsoft.CodeAnalysis.ImplementType -{ - internal static class ImplementTypeOptionsStorage - { - public static ImplementTypeOptions GetImplementTypeOptions(this IGlobalOptionService globalOptions, string language) - => new() - { - InsertionBehavior = globalOptions.GetOption(InsertionBehavior, language), - PropertyGenerationBehavior = globalOptions.GetOption(PropertyGenerationBehavior, language) - }; - - private static readonly OptionGroup s_implementTypeGroup = new(name: "implement_type", description: ""); - - public static readonly PerLanguageOption2 InsertionBehavior = - new("dotnet_insertion_behavior", - defaultValue: ImplementTypeOptions.Default.InsertionBehavior, group: s_implementTypeGroup, serializer: EditorConfigValueSerializer.CreateSerializerForEnum()); - - public static readonly PerLanguageOption2 PropertyGenerationBehavior = - new("dotnet_property_generation_behavior", - defaultValue: ImplementTypeOptions.Default.PropertyGenerationBehavior, group: s_implementTypeGroup, serializer: EditorConfigValueSerializer.CreateSerializerForEnum()); - } -} diff --git a/src/LanguageServer/Protocol/Handler/Configuration/DidChangeConfigurationNotificationHandler.cs b/src/LanguageServer/Protocol/Handler/Configuration/DidChangeConfigurationNotificationHandler.cs index 60137a3e1ac92..07786e46a47de 100644 --- a/src/LanguageServer/Protocol/Handler/Configuration/DidChangeConfigurationNotificationHandler.cs +++ b/src/LanguageServer/Protocol/Handler/Configuration/DidChangeConfigurationNotificationHandler.cs @@ -190,7 +190,7 @@ private static ImmutableArray GenerateGlobalConfigurationItem /// /// /// Example:Full name of would be: - /// implement_type.dotnet_insertion_behavior + /// implement_type.dotnet_member_insertion_location /// internal static string GenerateFullNameForOption(IOption2 option) { diff --git a/src/LanguageServer/ProtocolUnitTests/Configuration/DidChangeConfigurationNotificationHandlerTest.cs b/src/LanguageServer/ProtocolUnitTests/Configuration/DidChangeConfigurationNotificationHandlerTest.cs index ea13825ac98b0..394ad082190e0 100644 --- a/src/LanguageServer/ProtocolUnitTests/Configuration/DidChangeConfigurationNotificationHandlerTest.cs +++ b/src/LanguageServer/ProtocolUnitTests/Configuration/DidChangeConfigurationNotificationHandlerTest.cs @@ -112,7 +112,7 @@ public void VerifyLspClientOptionNames() var expectedNames = new[] { "symbol_search.dotnet_search_reference_assemblies", - "implement_type.dotnet_insertion_behavior", + "implement_type.dotnet_member_insertion_location", "implement_type.dotnet_property_generation_behavior", "completion.dotnet_show_name_completion_suggestions", "completion.dotnet_provide_regex_completions", diff --git a/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs b/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs index 37959e4c33d30..29f87408d946e 100644 --- a/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs +++ b/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs @@ -317,7 +317,7 @@ public bool TryFetch(LocalUserRegistryOptionPersister persister, OptionKey2 opti {"dotnet_generate_equality_operators", new RoamingProfileStorage("TextEditor.%LANGUAGE%.Specific.GenerateEqualsAndGetHashCodeFromMembersOptions.GenerateOperators")}, {"dotnet_generate_iequatable_implementation", new RoamingProfileStorage("TextEditor.%LANGUAGE%.Specific.GenerateEqualsAndGetHashCodeFromMembersOptions.ImplementIEquatable")}, {"dotnet_generate_overrides_for_all_members", new RoamingProfileStorage("TextEditor.Specific.GenerateOverridesOptions.SelectAll")}, - {"dotnet_insertion_behavior", new RoamingProfileStorage("TextEditor.%LANGUAGE%.ImplementTypeOptions.InsertionBehavior")}, + {"dotnet_member_insertion_location", new RoamingProfileStorage("TextEditor.%LANGUAGE%.ImplementTypeOptions.InsertionBehavior")}, {"dotnet_property_generation_behavior", new RoamingProfileStorage("TextEditor.%LANGUAGE%.ImplementTypeOptions.PropertyGenerationBehavior")}, #pragma warning disable CS0612 // Type or member is obsolete {"indent_size", new RoamingProfileStorage("TextEditor.%LANGUAGE%.Indent Size", vbKey: "TextEditor.Basic.Indent Size")}, diff --git a/src/VisualStudio/Core/Test/Options/BasicEditorConfigGeneratorTests.vb b/src/VisualStudio/Core/Test/Options/BasicEditorConfigGeneratorTests.vb index 9239f35bce3d3..d90125d210f88 100644 --- a/src/VisualStudio/Core/Test/Options/BasicEditorConfigGeneratorTests.vb +++ b/src/VisualStudio/Core/Test/Options/BasicEditorConfigGeneratorTests.vb @@ -40,6 +40,8 @@ insert_final_newline = false # Type members dotnet_hide_advanced_members = false +dotnet_member_insertion_location = with_other_members_of_the_same_kind +dotnet_property_generation_behavior = prefer_throwing_properties #### .NET Coding Conventions #### @@ -188,6 +190,8 @@ insert_final_newline = false # Type members dotnet_hide_advanced_members = false +dotnet_member_insertion_location = with_other_members_of_the_same_kind +dotnet_property_generation_behavior = prefer_throwing_properties #### .NET Coding Conventions #### diff --git a/src/VisualStudio/Core/Test/Options/CSharpEditorConfigGeneratorTests.vb b/src/VisualStudio/Core/Test/Options/CSharpEditorConfigGeneratorTests.vb index 4ffd7d0f8026c..6162a1350730b 100644 --- a/src/VisualStudio/Core/Test/Options/CSharpEditorConfigGeneratorTests.vb +++ b/src/VisualStudio/Core/Test/Options/CSharpEditorConfigGeneratorTests.vb @@ -38,6 +38,8 @@ insert_final_newline = false # Type members dotnet_hide_advanced_members = false +dotnet_member_insertion_location = with_other_members_of_the_same_kind +dotnet_property_generation_behavior = prefer_throwing_properties #### .NET Coding Conventions #### @@ -292,6 +294,8 @@ insert_final_newline = false # Type members dotnet_hide_advanced_members = false +dotnet_member_insertion_location = with_other_members_of_the_same_kind +dotnet_property_generation_behavior = prefer_throwing_properties #### .NET Coding Conventions #### diff --git a/src/Workspaces/Core/Portable/ImplementType/ImplementTypeOptions.cs b/src/Workspaces/Core/Portable/ImplementType/ImplementTypeOptions.cs deleted file mode 100644 index 06864055b258c..0000000000000 --- a/src/Workspaces/Core/Portable/ImplementType/ImplementTypeOptions.cs +++ /dev/null @@ -1,20 +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.Runtime.Serialization; - -namespace Microsoft.CodeAnalysis.ImplementType; - -[DataContract] -internal readonly record struct ImplementTypeOptions -{ - [DataMember] public ImplementTypeInsertionBehavior InsertionBehavior { get; init; } = ImplementTypeInsertionBehavior.WithOtherMembersOfTheSameKind; - [DataMember] public ImplementTypePropertyGenerationBehavior PropertyGenerationBehavior { get; init; } = ImplementTypePropertyGenerationBehavior.PreferThrowingProperties; - - public ImplementTypeOptions() - { - } - - public static readonly ImplementTypeOptions Default = new(); -} diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/EditorConfigValueSerializer.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/EditorConfigValueSerializer.cs index e65d814e6e0ad..15e37a45c5a01 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/EditorConfigValueSerializer.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/EditorConfigValueSerializer.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using Microsoft.CodeAnalysis.CodeStyle; using Roslyn.Utilities; @@ -93,11 +94,28 @@ public static EditorConfigValueSerializer> CodeStyle(Co => new(parseValue: str => CodeStyleHelpers.TryParseStringEditorConfigCodeStyleOption(str, defaultValue, out var result) ? result : new Optional>(), serializeValue: value => value.Value.ToLowerInvariant() + CodeStyleHelpers.GetEditorConfigStringNotificationPart(value, defaultValue)); + /// + /// Creates a serializer for an enum value that uses the enum field names. + /// public static EditorConfigValueSerializer CreateSerializerForEnum() where T : struct, Enum => new( parseValue: str => TryParseEnum(str, out var result) ? new Optional(result) : new Optional(), serializeValue: value => value.ToString()); + /// + /// Creates a serializer for an enum value given a between value names and the corresponding enum values. + /// + public static EditorConfigValueSerializer CreateSerializerForEnum(BidirectionalMap map) where T : struct, Enum + => CreateSerializerForEnum(map, ImmutableDictionary.Empty); + + /// + /// Creates a serializer for an enum value given a between value names and the corresponding enum values. + /// specifies alternative value representations for backward compatibility. + /// + public static EditorConfigValueSerializer CreateSerializerForEnum(BidirectionalMap map, ImmutableDictionary alternative) where T : struct, Enum + => new(parseValue: str => map.TryGetValue(str, out var result) || alternative.TryGetValue(str, out result) ? new Optional(result) : new Optional(), + serializeValue: value => map.TryGetKey(value, out var key) ? key : throw ExceptionUtilities.UnexpectedValue(value)); + public static EditorConfigValueSerializer CreateSerializerForNullableEnum() where T : struct, Enum { return new EditorConfigValueSerializer( diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/EditorConfigValueSerializer`1.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/EditorConfigValueSerializer`1.cs index 75a7fd29b7226..d6575265c3869 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/EditorConfigValueSerializer`1.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/EditorConfigValueSerializer`1.cs @@ -19,6 +19,7 @@ internal sealed class EditorConfigValueSerializer( public static readonly EditorConfigValueSerializer Unsupported = new( parseValue: _ => throw new NotSupportedException("Option does not support serialization to editorconfig format"), serializeValue: _ => throw new NotSupportedException("Option does not support serialization to editorconfig format")); + private readonly ConcurrentDictionary> _cachedValues = []; bool IEditorConfigValueSerializer.TryParse(string value, out object? result) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/BidirectionalMap.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/BidirectionalMap.cs index bfbef9cf4a522..159adddfd26f0 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/BidirectionalMap.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/BidirectionalMap.cs @@ -21,9 +21,15 @@ internal class BidirectionalMap : IBidirectionalMap private readonly ImmutableDictionary _backwardMap; public BidirectionalMap(IEnumerable> pairs) + : this(forwardMap: ImmutableDictionary.CreateRange(pairs), + backwardMap: ImmutableDictionary.CreateRange(pairs.Select(static p => KeyValuePairUtil.Create(p.Value, p.Key)))) + { + } + + public BidirectionalMap(IEnumerable<(TKey key, TValue value)> pairs) + : this(forwardMap: ImmutableDictionary.CreateRange(pairs.Select(static p => KeyValuePairUtil.Create(p.key, p.value))), + backwardMap: ImmutableDictionary.CreateRange(pairs.Select(static p => KeyValuePairUtil.Create(p.value, p.key)))) { - _forwardMap = ImmutableDictionary.CreateRange(pairs); - _backwardMap = ImmutableDictionary.CreateRange(pairs.Select(p => KeyValuePairUtil.Create(p.Value, p.Key))); } private BidirectionalMap(ImmutableDictionary forwardMap, ImmutableDictionary backwardMap) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeFixes/CodeActionOptions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeFixes/CodeActionOptions.cs index 2aaecca37662f..dcadf01561c48 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeFixes/CodeActionOptions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeFixes/CodeActionOptions.cs @@ -4,12 +4,9 @@ using System; using System.Runtime.Serialization; -using Microsoft.CodeAnalysis.CodeCleanup; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CodeFixesAndRefactorings; -using Microsoft.CodeAnalysis.CodeGeneration; using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.ImplementType; using Microsoft.CodeAnalysis.SymbolSearch; namespace Microsoft.CodeAnalysis.CodeActions; @@ -25,7 +22,6 @@ internal sealed record class CodeActionOptions #if !CODE_STYLE [DataMember] public SymbolSearchOptions SearchOptions { get; init; } = SymbolSearchOptions.Default; - [DataMember] public ImplementTypeOptions ImplementTypeOptions { get; init; } = ImplementTypeOptions.Default; #endif public CodeActionOptionsProvider CreateProvider() => new DelegatingCodeActionOptionsProvider(_ => this); From 1b730254e868a1f09075490c0001188598cc571d Mon Sep 17 00:00:00 2001 From: tmat Date: Mon, 15 Jul 2024 08:54:39 -0700 Subject: [PATCH 2/5] Fixes --- .../ImplementAbstractClassTests.cs | 5 +- ...CodeActionOrUserDiagnosticTest_NoEditor.cs | 3 +- .../ImplementInterfaceTests.vb | 2 +- ...ngeConfigurationNotificationHandlerTest.cs | 12 +-- .../CoreTest/Options/OptionSerializerTests.cs | 76 ++++++++++++++++--- 5 files changed, 76 insertions(+), 22 deletions(-) diff --git a/src/Features/CSharpTest/ImplementAbstractClass/ImplementAbstractClassTests.cs b/src/Features/CSharpTest/ImplementAbstractClass/ImplementAbstractClassTests.cs index ed3df972d2c97..bc4c5048dcc2b 100644 --- a/src/Features/CSharpTest/ImplementAbstractClass/ImplementAbstractClassTests.cs +++ b/src/Features/CSharpTest/ImplementAbstractClass/ImplementAbstractClassTests.cs @@ -13,7 +13,6 @@ using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics; using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; using Microsoft.CodeAnalysis.ImplementType; -using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; using Xunit; @@ -1713,7 +1712,7 @@ void Goo() { } public override int Prop => throw new System.NotImplementedException(); } - """, globalOptions: options); + """, options: options); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/17274")] @@ -1910,7 +1909,7 @@ class C : AbstractClass public override int ReadWriteProp { get; set; } public override int WriteOnlyProp { set => throw new System.NotImplementedException(); } } - """, parameters: new TestParameters(globalOptions: options)); + """, parameters: new TestParameters(options: options)); } [Theory, CombinatorialData] diff --git a/src/Features/DiagnosticsTestUtilities/CodeActionsLegacy/AbstractCodeActionOrUserDiagnosticTest_NoEditor.cs b/src/Features/DiagnosticsTestUtilities/CodeActionsLegacy/AbstractCodeActionOrUserDiagnosticTest_NoEditor.cs index 7d7a155a89239..90e3da1b21eb3 100644 --- a/src/Features/DiagnosticsTestUtilities/CodeActionsLegacy/AbstractCodeActionOrUserDiagnosticTest_NoEditor.cs +++ b/src/Features/DiagnosticsTestUtilities/CodeActionsLegacy/AbstractCodeActionOrUserDiagnosticTest_NoEditor.cs @@ -411,7 +411,6 @@ internal Task TestInRegularAndScriptAsync( CodeActionPriority? priority = null, CompilationOptions compilationOptions = null, OptionsCollectionAlias options = null, - OptionsCollectionAlias globalOptions = null, object fixProviderData = null, ParseOptions parseOptions = null, string title = null, @@ -419,7 +418,7 @@ internal Task TestInRegularAndScriptAsync( { return TestInRegularAndScript1Async( initialMarkup, expectedMarkup, - new TestParameters(parseOptions, compilationOptions, options, globalOptions, fixProviderData, index, priority, title: title, testHost: testHost)); + new TestParameters(parseOptions, compilationOptions, options, globalOptions: null, fixProviderData, index, priority, title: title, testHost: testHost)); } internal Task TestInRegularAndScript1Async( diff --git a/src/Features/VisualBasicTest/ImplementInterface/ImplementInterfaceTests.vb b/src/Features/VisualBasicTest/ImplementInterface/ImplementInterfaceTests.vb index 5ebe68a2a6a6c..423354c18c0ca 100644 --- a/src/Features/VisualBasicTest/ImplementInterface/ImplementInterfaceTests.vb +++ b/src/Features/VisualBasicTest/ImplementInterface/ImplementInterfaceTests.vb @@ -4532,7 +4532,7 @@ class Class Throw New System.NotImplementedException() End Set End Property -end class", parameters:=New TestParameters(globalOptions:=[Option](ImplementTypeOptionsStorage.PropertyGenerationBehavior, ImplementTypePropertyGenerationBehavior.PreferAutoProperties))) +end class", parameters:=New TestParameters(options:=[Option](ImplementTypeOptionsStorage.PropertyGenerationBehavior, ImplementTypePropertyGenerationBehavior.PreferAutoProperties))) End Function End Class End Namespace diff --git a/src/LanguageServer/ProtocolUnitTests/Configuration/DidChangeConfigurationNotificationHandlerTest.cs b/src/LanguageServer/ProtocolUnitTests/Configuration/DidChangeConfigurationNotificationHandlerTest.cs index 394ad082190e0..36898682172ab 100644 --- a/src/LanguageServer/ProtocolUnitTests/Configuration/DidChangeConfigurationNotificationHandlerTest.cs +++ b/src/LanguageServer/ProtocolUnitTests/Configuration/DidChangeConfigurationNotificationHandlerTest.cs @@ -107,13 +107,13 @@ public class A { }"; public void VerifyLspClientOptionNames() { var actualNames = DidChangeConfigurationNotificationHandler.SupportedOptions.Select( - DidChangeConfigurationNotificationHandler.GenerateFullNameForOption).OrderBy(name => name).ToArray(); - // These options are persist in the LSP client. Please make sure also modify the LSP client code if these strings are changed. + DidChangeConfigurationNotificationHandler.GenerateFullNameForOption); + // These options are persisted by the LSP client. Please make sure also modify the LSP client code if these strings are changed. var expectedNames = new[] { "symbol_search.dotnet_search_reference_assemblies", - "implement_type.dotnet_member_insertion_location", - "implement_type.dotnet_property_generation_behavior", + "code_actions.type_members.dotnet_member_insertion_location", + "code_actions.type_members.dotnet_property_generation_behavior", "completion.dotnet_show_name_completion_suggestions", "completion.dotnet_provide_regex_completions", "completion.dotnet_show_completion_items_from_unimported_namespaces", @@ -145,9 +145,9 @@ public void VerifyLspClientOptionNames() "code_lens.dotnet_enable_tests_code_lens", "projects.dotnet_binary_log_path", "projects.dotnet_enable_automatic_restore" - }.OrderBy(name => name); + }; - Assert.Equal(expectedNames, actualNames); + AssertEx.SetEqual(expectedNames, actualNames); } private static void VerifyValuesInServer(EditorTestWorkspace workspace, List expectedValues) diff --git a/src/Workspaces/CoreTest/Options/OptionSerializerTests.cs b/src/Workspaces/CoreTest/Options/OptionSerializerTests.cs index 80dcdc11fd448..6d3d47b0897ab 100644 --- a/src/Workspaces/CoreTest/Options/OptionSerializerTests.cs +++ b/src/Workspaces/CoreTest/Options/OptionSerializerTests.cs @@ -4,10 +4,10 @@ using System; using System.Linq; +using System.Text; using Microsoft.CodeAnalysis.Completion; using Microsoft.CodeAnalysis.Editor.InlineDiagnostics; using Microsoft.CodeAnalysis.Editor.Shared.Options; -using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.ImplementType; using Microsoft.CodeAnalysis.InheritanceMargin; using Microsoft.CodeAnalysis.Options; @@ -69,8 +69,6 @@ public void SerializationAndDeserializationForEnum() InlineDiagnosticsOptionsStorage.Location, SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, SolutionCrawlerOptionsStorage.CompilerDiagnosticsScopeOption, - ImplementTypeOptionsStorage.InsertionBehavior, - ImplementTypeOptionsStorage.PropertyGenerationBehavior, CompletionOptionsStorage.EnterKeyBehavior, CompletionOptionsStorage.SnippetsBehavior, }; @@ -80,7 +78,7 @@ public void SerializationAndDeserializationForEnum() var defaultValue = option.DefaultValue; // The default value for Enum option should not be null. Contract.ThrowIfNull(defaultValue, $"Option: {option.Name}"); - VerifyEnumValues(option, defaultValue.GetType()); + VerifyEnumValues(option, defaultValue.GetType(), allowsPacalCase: true, allowsSnakeCase: false); // Test invalid cases VerifyEnumInvalidParse(option, defaultValue.GetType()); @@ -103,7 +101,7 @@ public void SerializationAndDeserializationForNullableEnum() Contract.ThrowIfNull(enumType, $"Option: {option.Name}"); // Test enum values - VerifyEnumValues(option, enumType); + VerifyEnumValues(option, enumType, allowsPacalCase: true, allowsSnakeCase: false); // Test null var serializer = option.Definition.Serializer; @@ -118,17 +116,75 @@ public void SerializationAndDeserializationForNullableEnum() } } - private static void VerifyEnumValues(IOption2 option, Type enumType) + [Fact] + public void SerializationAndDeserializationForEnum_SnakeCase() + { + var options = new IOption2[] + { + ImplementTypeOptionsStorage.InsertionBehavior, + ImplementTypeOptionsStorage.PropertyGenerationBehavior, + }; + + foreach (var option in options) + { + var defaultValue = option.DefaultValue; + // The default value for Enum option should not be null. + Contract.ThrowIfNull(defaultValue, $"Option: {option.Name}"); + VerifyEnumValues(option, defaultValue.GetType(), allowsPacalCase: true, allowsSnakeCase: true); + + // Test invalid cases + VerifyEnumInvalidParse(option, defaultValue.GetType()); + } + } + + private static string PascalToSnakeCase(string str) + { + var builder = new StringBuilder(); + var prevIsLower = false; + foreach (var c in str) + { + var lower = char.ToLowerInvariant(c); + var isLower = lower == c; + + if (prevIsLower && !isLower && builder.Length > 0) + { + builder.Append('_'); + } + + builder.Append(lower); + prevIsLower = isLower; + } + + return builder.ToString(); + } + + private static void VerifyEnumValues(IOption2 option, Type enumType, bool allowsSnakeCase, bool allowsPacalCase) { var serializer = option.Definition.Serializer; var possibleEnumValues = enumType.GetEnumValues(); foreach (var enumValue in possibleEnumValues) { var serializedValue = serializer.Serialize(enumValue); - Assert.Equal(enumValue.ToString(), serializedValue); - var success = serializer.TryParse(serializedValue, out var deserializedResult); - Assert.True(success, $"Can't parse option: {option.Name}, value: {serializedValue}"); - Assert.Equal(enumValue, deserializedResult); + var expectedPascalCase = enumValue.ToString()!; + var expectedSnakeCase = PascalToSnakeCase(expectedPascalCase); + + // if option allows snake case it should use it for serialization: + Assert.Equal(allowsSnakeCase ? expectedSnakeCase : expectedPascalCase, serializedValue); + + if (allowsPacalCase) + VerifyParsing(expectedPascalCase); + + if (allowsSnakeCase) + VerifyParsing(expectedSnakeCase); + + void VerifyParsing(string value) + { + Assert.True( + serializer.TryParse(value, out var deserializedResult), + $"Can't parse option: {option.Name}, value: {value}"); + + Assert.Equal(enumValue, deserializedResult); + } } } From d3630431eb9bffcdeda3d71eb042374eb1876dbd Mon Sep 17 00:00:00 2001 From: tmat Date: Mon, 15 Jul 2024 12:53:48 -0700 Subject: [PATCH 3/5] FIx --- src/Features/Core/Portable/Options/MemberDisplayOptions.cs | 3 +-- .../ImplementAbstractClass/ImplementAbstractClassTests.vb | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Features/Core/Portable/Options/MemberDisplayOptions.cs b/src/Features/Core/Portable/Options/MemberDisplayOptions.cs index fcdfb9064660a..67bb9061167d2 100644 --- a/src/Features/Core/Portable/Options/MemberDisplayOptions.cs +++ b/src/Features/Core/Portable/Options/MemberDisplayOptions.cs @@ -24,8 +24,7 @@ internal readonly record struct MemberDisplayOptions() /// internal static class MemberDisplayOptionsStorage { - public static readonly OptionGroup CodeActionsGroup = new(name: "code_actions", description: FeaturesResources.NET_Code_Actions, priority: 3); - public static readonly OptionGroup TypeMemberGroup = new(name: "type_members", description: FeaturesResources.Type_members, priority: 3, parent: CodeActionsGroup); + public static readonly OptionGroup TypeMemberGroup = new(name: "type_members", description: FeaturesResources.Type_members, priority: 3, parent: null); public static readonly PerLanguageOption2 HideAdvancedMembers = new( "dotnet_hide_advanced_members", diff --git a/src/Features/VisualBasicTest/ImplementAbstractClass/ImplementAbstractClassTests.vb b/src/Features/VisualBasicTest/ImplementAbstractClass/ImplementAbstractClassTests.vb index ba5a5d140a57b..2e44de7baf4b7 100644 --- a/src/Features/VisualBasicTest/ImplementAbstractClass/ImplementAbstractClassTests.vb +++ b/src/Features/VisualBasicTest/ImplementAbstractClass/ImplementAbstractClassTests.vb @@ -632,7 +632,7 @@ Class C Throw New System.NotImplementedException() End Set End Property -End Class", parameters:=New TestParameters(globalOptions:=[Option](ImplementTypeOptionsStorage.PropertyGenerationBehavior, ImplementTypePropertyGenerationBehavior.PreferAutoProperties))) +End Class", parameters:=New TestParameters(options:=[Option](ImplementTypeOptionsStorage.PropertyGenerationBehavior, ImplementTypePropertyGenerationBehavior.PreferAutoProperties))) End Function End Class End Namespace From 98b3869edc64c8a2cddb7e9e8580cd81583cefd5 Mon Sep 17 00:00:00 2001 From: tmat Date: Mon, 15 Jul 2024 14:26:51 -0700 Subject: [PATCH 4/5] Fix --- .../DidChangeConfigurationNotificationHandlerTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LanguageServer/ProtocolUnitTests/Configuration/DidChangeConfigurationNotificationHandlerTest.cs b/src/LanguageServer/ProtocolUnitTests/Configuration/DidChangeConfigurationNotificationHandlerTest.cs index 36898682172ab..21d93d5e6d634 100644 --- a/src/LanguageServer/ProtocolUnitTests/Configuration/DidChangeConfigurationNotificationHandlerTest.cs +++ b/src/LanguageServer/ProtocolUnitTests/Configuration/DidChangeConfigurationNotificationHandlerTest.cs @@ -112,8 +112,8 @@ public void VerifyLspClientOptionNames() var expectedNames = new[] { "symbol_search.dotnet_search_reference_assemblies", - "code_actions.type_members.dotnet_member_insertion_location", - "code_actions.type_members.dotnet_property_generation_behavior", + "type_members.dotnet_member_insertion_location", + "type_members.dotnet_property_generation_behavior", "completion.dotnet_show_name_completion_suggestions", "completion.dotnet_provide_regex_completions", "completion.dotnet_show_completion_items_from_unimported_namespaces", From 388252da70072c4b1bbb1b3b2368a9abc800daed Mon Sep 17 00:00:00 2001 From: tmat Date: Tue, 16 Jul 2024 10:48:04 -0700 Subject: [PATCH 5/5] Make parsing case insensitive --- .../ImplementType/ImplementTypeOptions.cs | 26 ++++++++++--------- .../CoreTest/Options/OptionSerializerTests.cs | 10 +++++++ .../Options/EditorConfigValueSerializer.cs | 16 +++++++++++- .../Core/Utilities/BidirectionalMap.cs | 12 ++++----- 4 files changed, 45 insertions(+), 19 deletions(-) diff --git a/src/Features/Core/Portable/ImplementType/ImplementTypeOptions.cs b/src/Features/Core/Portable/ImplementType/ImplementTypeOptions.cs index c939ad99f330b..c4d01765cdfde 100644 --- a/src/Features/Core/Portable/ImplementType/ImplementTypeOptions.cs +++ b/src/Features/Core/Portable/ImplementType/ImplementTypeOptions.cs @@ -33,15 +33,16 @@ internal static class ImplementTypeOptionsStorage group: MemberDisplayOptionsStorage.TypeMemberGroup, isEditorConfigOption: true, serializer: EditorConfigValueSerializer.CreateSerializerForEnum( - map: new BidirectionalMap( + entries: [ ("at_the_end", ImplementTypeInsertionBehavior.AtTheEnd), ("with_other_members_of_the_same_kind", ImplementTypeInsertionBehavior.WithOtherMembersOfTheSameKind), - ]), - alternative: ImmutableDictionary.Empty - .Add("AtTheEnd", ImplementTypeInsertionBehavior.AtTheEnd) - .Add("WithOtherMembersOfTheSameKind", ImplementTypeInsertionBehavior.WithOtherMembersOfTheSameKind) - )); + ], + alternativeEntries: + [ + ("AtTheEnd", ImplementTypeInsertionBehavior.AtTheEnd), + ("WithOtherMembersOfTheSameKind", ImplementTypeInsertionBehavior.WithOtherMembersOfTheSameKind), + ])); public static readonly PerLanguageOption2 PropertyGenerationBehavior = new( "dotnet_property_generation_behavior", @@ -49,15 +50,16 @@ internal static class ImplementTypeOptionsStorage group: MemberDisplayOptionsStorage.TypeMemberGroup, isEditorConfigOption: true, serializer: EditorConfigValueSerializer.CreateSerializerForEnum( - map: new BidirectionalMap( + entries: [ ("prefer_throwing_properties", ImplementTypePropertyGenerationBehavior.PreferThrowingProperties), ("prefer_auto_properties", ImplementTypePropertyGenerationBehavior.PreferAutoProperties), - ]), - alternative: ImmutableDictionary.Empty - .Add("PreferThrowingProperties", ImplementTypePropertyGenerationBehavior.PreferThrowingProperties) - .Add("PreferAutoProperties", ImplementTypePropertyGenerationBehavior.PreferAutoProperties) - )); + ], + alternativeEntries: + [ + ("PreferThrowingProperties", ImplementTypePropertyGenerationBehavior.PreferThrowingProperties), + ("PreferAutoProperties", ImplementTypePropertyGenerationBehavior.PreferAutoProperties), + ])); /// /// Options that we expect the user to set in editorconfig. diff --git a/src/Workspaces/CoreTest/Options/OptionSerializerTests.cs b/src/Workspaces/CoreTest/Options/OptionSerializerTests.cs index 6d3d47b0897ab..0abccc54c274e 100644 --- a/src/Workspaces/CoreTest/Options/OptionSerializerTests.cs +++ b/src/Workspaces/CoreTest/Options/OptionSerializerTests.cs @@ -172,11 +172,21 @@ private static void VerifyEnumValues(IOption2 option, Type enumType, bool allows Assert.Equal(allowsSnakeCase ? expectedSnakeCase : expectedPascalCase, serializedValue); if (allowsPacalCase) + { VerifyParsing(expectedPascalCase); + // parsing should be case-insensitive: + VerifyParsing(expectedPascalCase.ToLowerInvariant()); + } + if (allowsSnakeCase) + { VerifyParsing(expectedSnakeCase); + // parsing should be case-insensitive: + VerifyParsing(expectedSnakeCase.ToUpperInvariant()); + } + void VerifyParsing(string value) { Assert.True( diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/EditorConfigValueSerializer.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/EditorConfigValueSerializer.cs index 15e37a45c5a01..56bc217d99dc4 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/EditorConfigValueSerializer.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/EditorConfigValueSerializer.cs @@ -3,8 +3,9 @@ // 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.Linq; using Microsoft.CodeAnalysis.CodeStyle; using Roslyn.Utilities; @@ -116,6 +117,19 @@ public static EditorConfigValueSerializer CreateSerializerForEnum(Bidirect => new(parseValue: str => map.TryGetValue(str, out var result) || alternative.TryGetValue(str, out result) ? new Optional(result) : new Optional(), serializeValue: value => map.TryGetKey(value, out var key) ? key : throw ExceptionUtilities.UnexpectedValue(value)); + /// + /// Creates a serializer for an enum value given a between value names and the corresponding enum values. + /// specifies alternative value representations for backward compatibility. + /// + public static EditorConfigValueSerializer CreateSerializerForEnum(IEnumerable<(string name, T value)> entries, IEnumerable<(string name, T value)> alternativeEntries) where T : struct, Enum + { + var map = new BidirectionalMap(entries, StringComparer.OrdinalIgnoreCase); + var alternativeMap = ImmutableDictionary.Empty.WithComparers(keyComparer: StringComparer.OrdinalIgnoreCase) + .AddRange(alternativeEntries.Select(static p => KeyValuePairUtil.Create(p.name, p.value))); + + return CreateSerializerForEnum(map, alternativeMap); + } + public static EditorConfigValueSerializer CreateSerializerForNullableEnum() where T : struct, Enum { return new EditorConfigValueSerializer( diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/BidirectionalMap.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/BidirectionalMap.cs index 159adddfd26f0..1d1a189392a47 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/BidirectionalMap.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/BidirectionalMap.cs @@ -20,15 +20,15 @@ internal class BidirectionalMap : IBidirectionalMap private readonly ImmutableDictionary _forwardMap; private readonly ImmutableDictionary _backwardMap; - public BidirectionalMap(IEnumerable> pairs) - : this(forwardMap: ImmutableDictionary.CreateRange(pairs), - backwardMap: ImmutableDictionary.CreateRange(pairs.Select(static p => KeyValuePairUtil.Create(p.Value, p.Key)))) + public BidirectionalMap(IEnumerable> pairs, IEqualityComparer? keyComparer = null, IEqualityComparer? valueComparer = null) + : this(forwardMap: ImmutableDictionary.CreateRange(keyComparer, pairs), + backwardMap: ImmutableDictionary.CreateRange(valueComparer, pairs.Select(static p => KeyValuePairUtil.Create(p.Value, p.Key)))) { } - public BidirectionalMap(IEnumerable<(TKey key, TValue value)> pairs) - : this(forwardMap: ImmutableDictionary.CreateRange(pairs.Select(static p => KeyValuePairUtil.Create(p.key, p.value))), - backwardMap: ImmutableDictionary.CreateRange(pairs.Select(static p => KeyValuePairUtil.Create(p.value, p.key)))) + public BidirectionalMap(IEnumerable<(TKey key, TValue value)> pairs, IEqualityComparer? keyComparer = null, IEqualityComparer? valueComparer = null) + : this(forwardMap: ImmutableDictionary.CreateRange(keyComparer, pairs.Select(static p => KeyValuePairUtil.Create(p.key, p.value))), + backwardMap: ImmutableDictionary.CreateRange(valueComparer, pairs.Select(static p => KeyValuePairUtil.Create(p.value, p.key)))) { }