Skip to content

Commit 3f59841

Browse files
committed
Initialize naming style preferences when language is added to workspace (#76795)
1 parent 398fa9b commit 3f59841

File tree

7 files changed

+68
-30
lines changed

7 files changed

+68
-30
lines changed

src/Features/Core/Portable/Options/EditorConfig/EditorConfigFileGenerator.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,10 @@ public static string Generate(
4040

4141
foreach ((var feature, var options) in groupedOptions)
4242
{
43-
AppendOptionsToEditorConfig(configOptions, feature, options, language, editorconfig);
43+
if (!options.Contains(NamingStyleOptions.NamingPreferences))
44+
{
45+
AppendOptionsToEditorConfig(configOptions, feature, options, language, editorconfig);
46+
}
4447
}
4548

4649
if (configOptions.TryGetOption(new OptionKey2(NamingStyleOptions.NamingPreferences, language), out NamingStylePreferences namingStylePreferences))

src/Features/Core/Portable/Options/EditorConfig/EditorConfigOptionsEnumerator.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,9 @@ internal sealed class EditorConfigOptionsEnumerator(
5454
yield return (WorkspacesResources.dot_NET_Coding_Conventions,
5555
[
5656
.. GenerationOptions.EditorConfigOptions,
57-
.. CodeStyleOptions2.EditorConfigOptions
57+
.. CodeStyleOptions2.EditorConfigOptions,
5858
]);
59+
60+
yield return (CompilerExtensionsResources.Naming_styles, NamingStyleOptions.EditorConfigOptions);
5961
}
6062
}

src/LanguageServer/Protocol/Features/Options/ClientFallbackAnalyzerConfigOptionsProvider.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,7 @@ public StructuredAnalyzerConfigOptions GetOptions(string language)
3333
foreach (var option in options)
3434
{
3535
var value = globalOptions.GetOption<object>(new OptionKey2(option, option.IsPerLanguage ? language : null));
36-
37-
var configName = option.Definition.ConfigName;
38-
var configValue = option.Definition.Serializer.Serialize(value);
39-
40-
builder.Add(configName, configValue);
36+
EditorConfigValueSerializer.Serialize(builder, option, language, value);
4137
}
4238
}
4339

src/LanguageServer/Protocol/Features/Options/SolutionAnalyzerConfigOptionsUpdater.cs

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -84,23 +84,8 @@ Solution UpdateOptions(Solution oldSolution)
8484
}
8585
}
8686

87-
// update changed values:
88-
var configName = key.Option.Definition.ConfigName;
89-
if (value is NamingStylePreferences preferences)
90-
{
91-
NamingStylePreferencesEditorConfigSerializer.WriteNamingStylePreferencesToEditorConfig(
92-
preferences.SymbolSpecifications,
93-
preferences.NamingStyles,
94-
preferences.NamingRules,
95-
language,
96-
entryWriter: (name, value) => lazyBuilder[name] = value,
97-
triviaWriter: null,
98-
setPrioritiesToPreserveOrder: true);
99-
}
100-
else
101-
{
102-
lazyBuilder[configName] = key.Option.Definition.Serializer.Serialize(value);
103-
}
87+
// update changed value:
88+
EditorConfigValueSerializer.Serialize(lazyBuilder, key.Option, language, value);
10489
}
10590

10691
if (lazyBuilder != null)

src/LanguageServer/ProtocolUnitTests/Options/SolutionAnalyzerConfigOptionsUpdaterTests.cs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,14 @@ public void FlowsNamingStylePreferencesToWorkspace()
107107
{
108108
using var workspace = CreateWorkspace();
109109

110+
Assert.Empty(workspace.CurrentSolution.Projects);
111+
var globalOptions = workspace.GetService<IGlobalOptionService>();
112+
113+
var initialPeferences = OptionsTestHelpers.CreateNamingStylePreferences(
114+
([SymbolKind.Property], Capitalization.AllUpper, ReportDiagnostic.Error));
115+
116+
globalOptions.SetGlobalOption(NamingStyleOptions.NamingPreferences, LanguageNames.CSharp, initialPeferences);
117+
110118
var testProjectWithoutConfig = new TestHostProject(workspace, "proj_without_config", LanguageNames.CSharp);
111119

112120
testProjectWithoutConfig.AddDocument(new TestHostDocument("""
@@ -135,21 +143,28 @@ class MyClass2;
135143
""",
136144
filePath: Path.Combine(TempRoot.Root, "proj_with_config", "test.cs")));
137145

146+
// No fallback options before a project is added.
147+
Assert.False(workspace.CurrentSolution.FallbackAnalyzerOptions.TryGetValue(LanguageNames.CSharp, out _));
148+
138149
workspace.AddTestProject(testProjectWithoutConfig);
139150
workspace.AddTestProject(testProjectWithConfig);
140151

141-
var globalOptions = workspace.GetService<IGlobalOptionService>();
152+
// Once a C# project is added the preferences stored in global options should be applied to fallback options:
153+
Assert.True(workspace.CurrentSolution.FallbackAnalyzerOptions.TryGetValue(LanguageNames.CSharp, out var fallbackOptions));
154+
AssertEx.SequenceEqual(
155+
initialPeferences.Rules.NamingRules.Select(r => r.Inspect()),
156+
fallbackOptions.GetNamingStylePreferences().Rules.NamingRules.Select(r => r.Inspect()));
142157

143158
var hostPeferences = OptionsTestHelpers.CreateNamingStylePreferences(
144159
([MethodKind.Ordinary], Capitalization.PascalCase, ReportDiagnostic.Error),
145160
([MethodKind.Ordinary, SymbolKind.Field], Capitalization.PascalCase, ReportDiagnostic.Error));
146161

147162
globalOptions.SetGlobalOption(NamingStyleOptions.NamingPreferences, LanguageNames.CSharp, hostPeferences);
148163

149-
Assert.True(workspace.CurrentSolution.FallbackAnalyzerOptions.TryGetValue(LanguageNames.CSharp, out var fallbackOptions));
150-
164+
// Initial preferences should be replaced by host preferences.
151165
// Note: rules are ordered but symbol and naming style specifications are not.
152-
AssertEx.Equal(
166+
Assert.True(workspace.CurrentSolution.FallbackAnalyzerOptions.TryGetValue(LanguageNames.CSharp, out fallbackOptions));
167+
AssertEx.SequenceEqual(
153168
hostPeferences.Rules.NamingRules.Select(r => r.Inspect()),
154169
fallbackOptions.GetNamingStylePreferences().Rules.NamingRules.Select(r => r.Inspect()));
155170

src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/NamingStyleOptions.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5-
using System.Threading;
6-
using System.Threading.Tasks;
75
using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles;
86
using Microsoft.CodeAnalysis.Options;
7+
using System.Collections.Immutable;
98

109
#if !CODE_STYLE
1110
using Microsoft.CodeAnalysis.Host;
@@ -26,6 +25,11 @@ internal static class NamingStyleOptions
2625
defaultValue: NamingStylePreferences.Default,
2726
isEditorConfigOption: true,
2827
serializer: EditorConfigValueSerializer<NamingStylePreferences>.Unsupported);
28+
29+
/// <summary>
30+
/// Options that we expect the user to set in editorconfig.
31+
/// </summary>
32+
internal static readonly ImmutableArray<IOption2> EditorConfigOptions = [NamingPreferences];
2933
}
3034

3135
internal interface NamingStylePreferencesProvider

src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/EditorConfigValueSerializer.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Collections.Immutable;
88
using System.Linq;
99
using Microsoft.CodeAnalysis.CodeStyle;
10+
using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles;
1011
using Roslyn.Utilities;
1112

1213
namespace Microsoft.CodeAnalysis.Options;
@@ -170,4 +171,36 @@ private static bool TryParseEnum<T>(string str, out T result) where T : struct,
170171

171172
return Enum.TryParse(str, ignoreCase: true, out result);
172173
}
174+
175+
/// <summary>
176+
/// Serializes arbitrary editorconfig option value (including naming style preferences) into a given builder.
177+
/// Replaces existing value if present.
178+
/// </summary>
179+
public static void Serialize(IDictionary<string, string> builder, IOption2 option, string language, object? value)
180+
{
181+
if (value is NamingStylePreferences preferences)
182+
{
183+
// remove existing naming style values:
184+
foreach (var name in builder.Keys)
185+
{
186+
if (name.StartsWith("dotnet_naming_rule.") || name.StartsWith("dotnet_naming_symbols.") || name.StartsWith("dotnet_naming_style."))
187+
{
188+
builder.Remove(name);
189+
}
190+
}
191+
192+
NamingStylePreferencesEditorConfigSerializer.WriteNamingStylePreferencesToEditorConfig(
193+
preferences.SymbolSpecifications,
194+
preferences.NamingStyles,
195+
preferences.Rules.NamingRules,
196+
language,
197+
entryWriter: (name, value) => builder[name] = value,
198+
triviaWriter: null,
199+
setPrioritiesToPreserveOrder: true);
200+
}
201+
else
202+
{
203+
builder[option.Definition.ConfigName] = option.Definition.Serializer.Serialize(value);
204+
}
205+
}
173206
}

0 commit comments

Comments
 (0)