diff --git a/src/EditorFeatures/CSharpTest/Diagnostics/NamingStyles/EditorConfigNamingStyleParserTests.cs b/src/EditorFeatures/CSharpTest/Diagnostics/NamingStyles/EditorConfigNamingStyleParserTests.cs index 3ff40ab2b25c2..930e3cd84ca7d 100644 --- a/src/EditorFeatures/CSharpTest/Diagnostics/NamingStyles/EditorConfigNamingStyleParserTests.cs +++ b/src/EditorFeatures/CSharpTest/Diagnostics/NamingStyles/EditorConfigNamingStyleParserTests.cs @@ -2,14 +2,14 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - 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 Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.CodeAnalysis.UnitTests; using Roslyn.Test.Utilities; using Xunit; using static Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles.SymbolSpecification; @@ -18,11 +18,8 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics.NamingStyle public class EditorConfigNamingStyleParserTests { - private static NamingStylePreferences ParseDictionary(Dictionary options) - => EditorConfigNamingStyleParser.ParseDictionary(new DictionaryAnalyzerConfigOptions(options.ToImmutableDictionary())); - [Fact] - public static void TestPascalCaseRule() + public void TestPascalCaseRule() { var dictionary = new Dictionary() { @@ -33,7 +30,7 @@ public static void TestPascalCaseRule() ["dotnet_naming_symbols.method_and_property_symbols.applicable_accessibilities"] = "*", ["dotnet_naming_style.pascal_case_style.capitalization"] = "pascal_case" }; - var result = ParseDictionary(dictionary); + var result = OptionsTestHelpers.ParseNamingStylePreferences(dictionary); Assert.Single(result.NamingRules); var namingRule = result.NamingRules.Single(); Assert.Single(result.NamingStyles); @@ -70,7 +67,7 @@ public static void TestPascalCaseRule() } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/40705")] - public static void TestPascalCaseRuleWithKeyCapitalization() + public void TestPascalCaseRuleWithKeyCapitalization() { var dictionary = new Dictionary() { @@ -81,7 +78,7 @@ public static void TestPascalCaseRuleWithKeyCapitalization() ["dotnet_naming_symbols.method_and_property_symbols.applicable_accessibilities"] = "*", ["dotnet_naming_style.pascal_case_style.capitalization"] = "pascal_case" }; - var result = ParseDictionary(dictionary); + var result = OptionsTestHelpers.ParseNamingStylePreferences(dictionary); var namingRule = Assert.Single(result.NamingRules); var namingStyle = Assert.Single(result.NamingStyles); var symbolSpec = Assert.Single(result.SymbolSpecifications); @@ -91,7 +88,7 @@ public static void TestPascalCaseRuleWithKeyCapitalization() } [Fact] - public static void TestAsyncMethodsAndLocalFunctionsRule() + public void TestAsyncMethodsAndLocalFunctionsRule() { var dictionary = new Dictionary() { @@ -103,7 +100,7 @@ public static void TestAsyncMethodsAndLocalFunctionsRule() ["dotnet_naming_style.end_in_async_style.capitalization "] = "pascal_case", ["dotnet_naming_style.end_in_async_style.required_suffix"] = "Async", }; - var result = ParseDictionary(dictionary); + var result = OptionsTestHelpers.ParseNamingStylePreferences(dictionary); Assert.Single(result.NamingRules); var namingRule = result.NamingRules.Single(); Assert.Single(result.NamingStyles); @@ -133,7 +130,7 @@ public static void TestAsyncMethodsAndLocalFunctionsRule() } [Fact] - public static void TestRuleWithoutCapitalization() + public void TestRuleWithoutCapitalization() { var dictionary = new Dictionary() { @@ -145,12 +142,12 @@ public static void TestRuleWithoutCapitalization() ["dotnet_naming_symbols.any_async_methods.required_modifiers"] = "async", ["dotnet_naming_style.end_in_async.required_suffix"] = "Async", }; - var result = ParseDictionary(dictionary); + var result = OptionsTestHelpers.ParseNamingStylePreferences(dictionary); Assert.Empty(result.NamingStyles); } [Fact] - public static void TestPublicMembersCapitalizedRule() + public void TestPublicMembersCapitalizedRule() { var dictionary = new Dictionary() { @@ -161,7 +158,7 @@ public static void TestPublicMembersCapitalizedRule() ["dotnet_naming_symbols.public_symbols.applicable_accessibilities"] = "public,internal,protected,protected_internal", ["dotnet_naming_style.first_word_upper_case_style.capitalization"] = "first_word_upper", }; - var result = ParseDictionary(dictionary); + var result = OptionsTestHelpers.ParseNamingStylePreferences(dictionary); Assert.Single(result.NamingRules); var namingRule = result.NamingRules.Single(); Assert.Single(result.NamingStyles); @@ -198,7 +195,7 @@ public static void TestPublicMembersCapitalizedRule() } [Fact] - public static void TestNonPublicMembersLowerCaseRule() + public void TestNonPublicMembersLowerCaseRule() { var dictionary = new Dictionary() { @@ -209,7 +206,7 @@ public static void TestNonPublicMembersLowerCaseRule() ["dotnet_naming_symbols.non_public_symbols.applicable_accessibilities"] = "private", ["dotnet_naming_style.all_lower_case_style.capitalization"] = "all_lower", }; - var result = ParseDictionary(dictionary); + var result = OptionsTestHelpers.ParseNamingStylePreferences(dictionary); Assert.Single(result.NamingRules); var namingRule = result.NamingRules.Single(); Assert.Single(result.NamingStyles); @@ -240,7 +237,7 @@ public static void TestNonPublicMembersLowerCaseRule() } [Fact] - public static void TestParametersAndLocalsAreCamelCaseRule() + public void TestParametersAndLocalsAreCamelCaseRule() { var dictionary = new Dictionary() { @@ -251,7 +248,7 @@ public static void TestParametersAndLocalsAreCamelCaseRule() ["dotnet_naming_style.camel_case_style.capitalization"] = "camel_case", }; - var result = ParseDictionary(dictionary); + var result = OptionsTestHelpers.ParseNamingStylePreferences(dictionary); Assert.Single(result.NamingRules); var namingRule = result.NamingRules.Single(); Assert.Single(result.NamingStyles); @@ -283,7 +280,7 @@ public static void TestParametersAndLocalsAreCamelCaseRule() } [Fact] - public static void TestLocalFunctionsAreCamelCaseRule() + public void TestLocalFunctionsAreCamelCaseRule() { var dictionary = new Dictionary() { @@ -294,7 +291,7 @@ public static void TestLocalFunctionsAreCamelCaseRule() ["dotnet_naming_style.camel_case_style.capitalization"] = "camel_case", }; - var result = ParseDictionary(dictionary); + var result = OptionsTestHelpers.ParseNamingStylePreferences(dictionary); Assert.Single(result.NamingRules); var namingRule = result.NamingRules.Single(); Assert.Single(result.NamingStyles); @@ -322,7 +319,7 @@ public static void TestLocalFunctionsAreCamelCaseRule() } [Fact] - public static void TestNoRulesAreReturned() + public void TestNoRulesAreReturned() { var dictionary = new Dictionary() { @@ -330,7 +327,7 @@ public static void TestNoRulesAreReturned() ["dotnet_naming_symbols.non_public_symbols.applicable_accessibilities"] = "private", ["dotnet_naming_style.all_lower_case_style.capitalization"] = "all_lower", }; - var result = ParseDictionary(dictionary); + var result = OptionsTestHelpers.ParseNamingStylePreferences(dictionary); Assert.Empty(result.NamingRules); Assert.Empty(result.NamingStyles); Assert.Empty(result.SymbolSpecifications); @@ -347,7 +344,7 @@ public static void TestNoRulesAreReturned() [InlineData("invalid", new object[] { })] [InlineData("", new object[] { })] [WorkItem("https://github.com/dotnet/roslyn/issues/20907")] - public static void TestApplicableKindsParse(string specification, object[] typeOrSymbolKinds) + public void TestApplicableKindsParse(string specification, object[] typeOrSymbolKinds) { var rule = new Dictionary() { @@ -363,7 +360,7 @@ public static void TestApplicableKindsParse(string specification, object[] typeO } var kinds = typeOrSymbolKinds.Select(NamingStylesTestOptionSets.ToSymbolKindOrTypeKind).ToArray(); - var result = ParseDictionary(rule); + var result = OptionsTestHelpers.ParseNamingStylePreferences(rule); Assert.Equal(kinds, result.SymbolSpecifications.SelectMany(x => x.ApplicableSymbolKindList)); } @@ -378,7 +375,7 @@ public static void TestApplicableKindsParse(string specification, object[] typeO [InlineData("invalid", new Accessibility[] { })] [InlineData("", new Accessibility[] { })] [WorkItem("https://github.com/dotnet/roslyn/issues/20907")] - public static void TestApplicableAccessibilitiesParse(string specification, Accessibility[] accessibilities) + public void TestApplicableAccessibilitiesParse(string specification, Accessibility[] accessibilities) { var rule = new Dictionary() { @@ -393,12 +390,12 @@ public static void TestApplicableAccessibilitiesParse(string specification, Acce rule["dotnet_naming_symbols.accessibilities.applicable_accessibilities"] = specification; } - var result = ParseDictionary(rule); + var result = OptionsTestHelpers.ParseNamingStylePreferences(rule); Assert.Equal(accessibilities, result.SymbolSpecifications.SelectMany(x => x.ApplicableAccessibilityList)); } [Fact] - public static void TestRequiredModifiersParse() + public void TestRequiredModifiersParse() { var charpRule = new Dictionary() { @@ -417,8 +414,8 @@ public static void TestRequiredModifiersParse() ["dotnet_naming_style.pascal_case.capitalization "] = "pascal_case", }; - var csharpResult = ParseDictionary(charpRule); - var vbResult = ParseDictionary(vbRule); + var csharpResult = OptionsTestHelpers.ParseNamingStylePreferences(charpRule); + var vbResult = OptionsTestHelpers.ParseNamingStylePreferences(vbRule); Assert.Equal(csharpResult.SymbolSpecifications.SelectMany(x => x.RequiredModifierList.Select(y => y.Modifier)), vbResult.SymbolSpecifications.SelectMany(x => x.RequiredModifierList.Select(y => y.Modifier))); @@ -427,7 +424,7 @@ public static void TestRequiredModifiersParse() } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/38513")] - public static void TestPrefixParse() + public void TestPrefixParse() { var rule = new Dictionary() { @@ -440,7 +437,7 @@ public static void TestPrefixParse() ["dotnet_naming_rule.must_be_pascal_cased_and_prefixed.severity"] = "warning", }; - var result = ParseDictionary(rule); + var result = OptionsTestHelpers.ParseNamingStylePreferences(rule); Assert.Single(result.NamingRules); var namingRule = result.NamingRules.Single(); Assert.Single(result.NamingStyles); @@ -461,7 +458,7 @@ public static void TestPrefixParse() } [Fact] - public static void TestEditorConfigParseForApplicableSymbolKinds() + public void TestEditorConfigParseForApplicableSymbolKinds() { var symbolSpecifications = CreateDefaultSymbolSpecification(); foreach (var applicableSymbolKind in symbolSpecifications.ApplicableSymbolKindList) @@ -484,9 +481,9 @@ public static void TestEditorConfigParseForApplicableSymbolKinds() [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) + public void TestOrderedByAccessibilityBeforeName(string firstName, string secondName, string firstNameAfterOrdering, string firstAccessibility, string secondAccessibility) { - var namingStylePreferences = ParseDictionary(new Dictionary() + var namingStylePreferences = OptionsTestHelpers.ParseNamingStylePreferences(new Dictionary() { [$"dotnet_naming_rule.{firstName}.severity"] = "error", [$"dotnet_naming_rule.{firstName}.symbols"] = "first_symbols", @@ -520,9 +517,9 @@ public static void TestOrderedByAccessibilityBeforeName(string firstName, string [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) + public void TestOrderedByModifiersBeforeName(string firstName, string secondName, string firstNameAfterOrdering, string firstModifiers, string secondModifiers) { - var namingStylePreferences = ParseDictionary(new Dictionary() + var namingStylePreferences = OptionsTestHelpers.ParseNamingStylePreferences(new Dictionary() { [$"dotnet_naming_rule.{firstName}.severity"] = "error", [$"dotnet_naming_rule.{firstName}.symbols"] = "first_symbols", @@ -556,9 +553,9 @@ public static void TestOrderedByModifiersBeforeName(string firstName, string sec [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) + public void TestOrderedBySymbolsBeforeName(string firstName, string secondName, string firstNameAfterOrdering, string firstSymbols, string secondSymbols) { - var namingStylePreferences = ParseDictionary(new Dictionary() + var namingStylePreferences = OptionsTestHelpers.ParseNamingStylePreferences(new Dictionary() { [$"dotnet_naming_rule.{firstName}.severity"] = "error", [$"dotnet_naming_rule.{firstName}.symbols"] = "first_symbols", @@ -576,4 +573,123 @@ public static void TestOrderedBySymbolsBeforeName(string firstName, string secon Assert.Equal($"{firstNameAfterOrdering}_style", namingStylePreferences.Rules.NamingRules[0].NamingStyle.Name); Assert.Equal($"{secondNameAfterOrdering}_style", namingStylePreferences.Rules.NamingRules[1].NamingStyle.Name); } + + /// + /// Two rules with different names but same specification. + /// + [Fact(Skip = "https://github.com/dotnet/roslyn/issues/76381")] + public void DuplicateRuleKeys() + { + var dictionary = new Dictionary() + { + ["dotnet_naming_rule.R1.severity"] = "warning", + ["dotnet_naming_rule.R1.symbols"] = "SYMBOLS", + ["dotnet_naming_rule.R1.style"] = "STYLE", + ["dotnet_naming_rule.R1.priority"] = "0", + + ["dotnet_naming_rule.R2.severity"] = "warning", + ["dotnet_naming_rule.R2.symbols"] = "SYMBOLS", + ["dotnet_naming_rule.R2.style"] = "STYLE", + ["dotnet_naming_rule.R2.priority"] = "1", + + ["dotnet_naming_symbols.SYMBOLS.applicable_kinds"] = "method", + ["dotnet_naming_symbols.SYMBOLS.applicable_accessibilities"] = "*", + ["dotnet_naming_style.STYLE.capitalization"] = "pascal_case", + }; + + var result = OptionsTestHelpers.ParseNamingStylePreferences(dictionary); + + AssertEx.AssertEqualToleratingWhitespaceDifferences(""" + + + + + Ordinary + + + NotApplicable + Public + Internal + Private + Protected + ProtectedAndInternal + ProtectedOrInternal + + + + + + Ordinary + + + NotApplicable + Public + Internal + Private + Protected + ProtectedAndInternal + ProtectedOrInternal + + + + + + + + + + + + + + """, + result.Inspect()); + } + + [Fact] + public void Priorities() + { + var dictionary = new Dictionary() + { + ["dotnet_naming_rule.R1.severity"] = "warning", + ["dotnet_naming_rule.R1.symbols"] = "SYMBOLS1", + ["dotnet_naming_rule.R1.style"] = "STYLE1", + + ["dotnet_naming_rule.R2.severity"] = "error", + ["dotnet_naming_rule.R2.symbols"] = "SYMBOLS2", + ["dotnet_naming_rule.R2.style"] = "STYLE2", + + ["dotnet_naming_symbols.SYMBOLS1.applicable_kinds"] = "method", + ["dotnet_naming_symbols.SYMBOLS1.applicable_accessibilities"] = "*", + ["dotnet_naming_style.STYLE1.capitalization"] = "pascal_case", + + ["dotnet_naming_symbols.SYMBOLS2.applicable_kinds"] = "method, field", + ["dotnet_naming_symbols.SYMBOLS2.applicable_accessibilities"] = "*", + ["dotnet_naming_style.STYLE2.capitalization"] = "pascal_case", + }; + + AssertEx.AssertEqualToleratingWhitespaceDifferences(""" + + + + + + + """, + OptionsTestHelpers.ParseNamingStylePreferences(dictionary).Inspect(excludeNodes: ["SymbolSpecifications", "NamingStyles"])); + + // adding priorities reverses the order - R2 (P0) is now ordered before R1 (P1): + dictionary.Add("dotnet_naming_rule.R2.priority", "0"); + dictionary.Add("dotnet_naming_rule.R1.priority", "1"); + + AssertEx.AssertEqualToleratingWhitespaceDifferences(""" + + + + + + + """, + OptionsTestHelpers.ParseNamingStylePreferences(dictionary).Inspect(excludeNodes: ["SymbolSpecifications", "NamingStyles"])); + } } diff --git a/src/Features/TestUtilities/Options/NamingStyleTestUtilities.cs b/src/Features/TestUtilities/Options/NamingStyleTestUtilities.cs new file mode 100644 index 0000000000000..f5f322047840b --- /dev/null +++ b/src/Features/TestUtilities/Options/NamingStyleTestUtilities.cs @@ -0,0 +1,70 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Xml.Linq; +using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles; +using Microsoft.CodeAnalysis.NamingStyles; + +namespace Microsoft.CodeAnalysis.Test.Utilities; + +internal static class NamingStyleTestUtilities +{ + public static string Inspect(this NamingRule rule) + => $"{rule.NamingStyle.Inspect()} {rule.SymbolSpecification.Inspect()} {rule.EnforcementLevel}"; + + public static string Inspect(this NamingStyle style) + => $"{style.Name} prefix='{style.Prefix}' suffix='{style.Suffix}' separator='{style.WordSeparator}'"; + + public static string Inspect(this SymbolSpecification symbol) + => $"{symbol.Name} {Inspect(symbol.ApplicableSymbolKindList)} {Inspect(symbol.ApplicableAccessibilityList)} {Inspect(symbol.RequiredModifierList)}"; + + public static string Inspect(ImmutableArray items) where T : notnull + => string.Join(",", items.Select(item => item.ToString())); + + public static string Inspect(this NamingStylePreferences preferences, string[]? excludeNodes = null) + { + var xml = preferences.CreateXElement(); + + // filter out insignificant elements: + var elementsToRemove = new List(); + foreach (var element in xml.DescendantsAndSelf()) + { + if (excludeNodes != null && excludeNodes.Contains(element.Name.LocalName)) + { + elementsToRemove.Add(element); + } + } + + foreach (var element in elementsToRemove) + { + element.Remove(); + } + + // replaces GUIDs with unique deterministic numbers: + var ordinal = 0; + var guidMap = new Dictionary(); + foreach (var element in xml.DescendantsAndSelf()) + { + foreach (var attribute in element.Attributes()) + { + if (Guid.TryParse(attribute.Value, out var guid)) + { + if (!guidMap.TryGetValue(guid, out var existingOrdinal)) + { + existingOrdinal = ordinal++; + guidMap.Add(guid, existingOrdinal); + } + + attribute.Value = existingOrdinal.ToString(); + } + } + } + + return xml.ToString(); + } +} diff --git a/src/LanguageServer/Protocol/Features/Options/SolutionAnalyzerConfigOptionsUpdater.cs b/src/LanguageServer/Protocol/Features/Options/SolutionAnalyzerConfigOptionsUpdater.cs index d72125d612ed4..c9584c9fb7c9c 100644 --- a/src/LanguageServer/Protocol/Features/Options/SolutionAnalyzerConfigOptionsUpdater.cs +++ b/src/LanguageServer/Protocol/Features/Options/SolutionAnalyzerConfigOptionsUpdater.cs @@ -8,9 +8,9 @@ using System.Composition; using System.Diagnostics; using System.Linq; -using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; @@ -86,8 +86,21 @@ Solution UpdateOptions(Solution oldSolution) // update changed values: var configName = key.Option.Definition.ConfigName; - var configValue = key.Option.Definition.Serializer.Serialize(value); - lazyBuilder[configName] = configValue; + if (value is NamingStylePreferences preferences) + { + NamingStylePreferencesEditorConfigSerializer.WriteNamingStylePreferencesToEditorConfig( + preferences.SymbolSpecifications, + preferences.NamingStyles, + preferences.NamingRules, + language, + entryWriter: (name, value) => lazyBuilder[name] = value, + triviaWriter: null, + setPrioritiesToPreserveOrder: true); + } + else + { + lazyBuilder[configName] = key.Option.Definition.Serializer.Serialize(value); + } } if (lazyBuilder != null) diff --git a/src/LanguageServer/ProtocolUnitTests/Options/SolutionAnalyzerConfigOptionsUpdaterTests.cs b/src/LanguageServer/ProtocolUnitTests/Options/SolutionAnalyzerConfigOptionsUpdaterTests.cs index 9adfc0ca5040a..ef1141dc0f8e2 100644 --- a/src/LanguageServer/ProtocolUnitTests/Options/SolutionAnalyzerConfigOptionsUpdaterTests.cs +++ b/src/LanguageServer/ProtocolUnitTests/Options/SolutionAnalyzerConfigOptionsUpdaterTests.cs @@ -3,11 +3,17 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Threading; +using Microsoft.CodeAnalysis.CodeStyle; +using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Test.Utilities; -using Roslyn.Utilities; +using Microsoft.CodeAnalysis.UnitTests; +using Roslyn.Test.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.Options.UnitTests; @@ -95,6 +101,154 @@ void AssertOptionValue(IOption2 option, string language, string expectedValue) } } + [Fact] + [WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2297536")] + public void FlowsNamingStylePreferencesToWorkspace() + { + using var workspace = CreateWorkspace(); + + var testProjectWithoutConfig = new TestHostProject(workspace, "proj_without_config", LanguageNames.CSharp); + + testProjectWithoutConfig.AddDocument(new TestHostDocument(""" + class MyClass1; + """, + filePath: Path.Combine(TempRoot.Root, "proj_without_config", "test.cs"))); + + var testProjectWithConfig = new TestHostProject(workspace, "proj_with_config", LanguageNames.CSharp); + + // explicitly specified style should override style specified in the fallback: + testProjectWithConfig.AddAnalyzerConfigDocument(new TestHostDocument( + """ + [*.cs] + dotnet_naming_rule.rule1.severity = warning + dotnet_naming_rule.rule1.symbols = symbols1 + dotnet_naming_rule.rule1.style = style1 + + dotnet_naming_symbols.symbols1.applicable_kinds = class + dotnet_naming_symbols.symbols1.applicable_accessibilities = * + dotnet_naming_style.style1.capitalization = camel_case + """, + filePath: Path.Combine(TempRoot.Root, "proj_with_config", ".editorconfig"))); + + testProjectWithConfig.AddDocument(new TestHostDocument(""" + class MyClass2; + """, + filePath: Path.Combine(TempRoot.Root, "proj_with_config", "test.cs"))); + + workspace.AddTestProject(testProjectWithoutConfig); + workspace.AddTestProject(testProjectWithConfig); + + var globalOptions = workspace.GetService(); + + var hostPeferences = OptionsTestHelpers.CreateNamingStylePreferences( + ([MethodKind.Ordinary], Capitalization.PascalCase, ReportDiagnostic.Error), + ([MethodKind.Ordinary, SymbolKind.Field], Capitalization.PascalCase, ReportDiagnostic.Error)); + + globalOptions.SetGlobalOption(NamingStyleOptions.NamingPreferences, LanguageNames.CSharp, hostPeferences); + + Assert.True(workspace.CurrentSolution.FallbackAnalyzerOptions.TryGetValue(LanguageNames.CSharp, out var fallbackOptions)); + + // Note: rules are ordered but symbol and naming style specifications are not. + AssertEx.Equal( + hostPeferences.Rules.NamingRules.Select(r => r.Inspect()), + fallbackOptions.GetNamingStylePreferences().Rules.NamingRules.Select(r => r.Inspect())); + + var projectWithConfig = workspace.CurrentSolution.GetRequiredProject(testProjectWithConfig.Id); + var treeWithConfig = projectWithConfig.Documents.Single().GetSyntaxTreeSynchronously(CancellationToken.None); + Assert.NotNull(treeWithConfig); + var documentOptions = projectWithConfig.HostAnalyzerOptions.AnalyzerConfigOptionsProvider.GetOptions(treeWithConfig); + + Assert.True(documentOptions.TryGetEditorConfigOption(NamingStyleOptions.NamingPreferences, out var documentPreferences)); + Assert.NotNull(documentPreferences); + + // Only naming styles specified in the editorconfig are present. + // Host preferences are ignored. This behavior is consistent with VS 16.11. + AssertEx.EqualOrDiff(""" + + + + + Class + + + NotApplicable + Public + Internal + Private + Protected + ProtectedAndInternal + ProtectedOrInternal + + + + + + + + + + + + """, + documentPreferences.Inspect()); + + var projectWithoutConfig = workspace.CurrentSolution.GetRequiredProject(testProjectWithoutConfig.Id); + var treeWithoutConfig = projectWithoutConfig.Documents.Single().GetSyntaxTreeSynchronously(CancellationToken.None); + Assert.NotNull(treeWithoutConfig); + documentOptions = projectWithoutConfig.HostAnalyzerOptions.AnalyzerConfigOptionsProvider.GetOptions(treeWithoutConfig); + + Assert.True(documentOptions.TryGetEditorConfigOption(NamingStyleOptions.NamingPreferences, out documentPreferences)); + Assert.NotNull(documentPreferences); + + // Host preferences: + AssertEx.EqualOrDiff(""" + + + + + Ordinary + Field + + + NotApplicable + Public + Internal + Private + Protected + ProtectedAndInternal + ProtectedOrInternal + + + + + + Ordinary + + + NotApplicable + Public + Internal + Private + Protected + ProtectedAndInternal + ProtectedOrInternal + + + + + + + + + + + + + + """, + documentPreferences.Inspect()); + } + [Fact] public void IgnoresNonEditorConfigOptions() { diff --git a/src/VisualStudio/Core/Test.Next/Options/VisualStudioOptionStorageTests.cs b/src/VisualStudio/Core/Test.Next/Options/VisualStudioOptionStorageTests.cs index 1fd21661a34c0..6b095dba38db1 100644 --- a/src/VisualStudio/Core/Test.Next/Options/VisualStudioOptionStorageTests.cs +++ b/src/VisualStudio/Core/Test.Next/Options/VisualStudioOptionStorageTests.cs @@ -107,9 +107,18 @@ public void LanguageSpecificOptionsHaveCorrectPrefix(string configName) return; } - if (!info.Option.Definition.IsEditorConfigOption) + // TODO: https://github.com/dotnet/roslyn/issues/65787 + if (info.Option.Name is + "csharp_format_on_return" or + "csharp_format_on_typing" or + "csharp_format_on_semicolon" or + "csharp_format_on_close_brace" or + "csharp_enable_inlay_hints_for_types" or + "csharp_enable_inlay_hints_for_implicit_variable_types" or + "csharp_enable_inlay_hints_for_lambda_parameter_types" or + "csharp_enable_inlay_hints_for_implicit_object_creation" or + "csharp_enable_inlay_hints_for_collection_expressions") { - // TODO: remove condition once all options have config name https://github.com/dotnet/roslyn/issues/65787 return; } diff --git a/src/Workspaces/CoreTestUtilities/Options/OptionsTestHelpers.cs b/src/Workspaces/CoreTestUtilities/Options/OptionsTestHelpers.cs index 02656f58ba6a6..9e285851f4198 100644 --- a/src/Workspaces/CoreTestUtilities/Options/OptionsTestHelpers.cs +++ b/src/Workspaces/CoreTestUtilities/Options/OptionsTestHelpers.cs @@ -9,6 +9,7 @@ using System.Collections.Immutable; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.CSharp.Formatting; +using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.NamingStyles; @@ -147,33 +148,53 @@ private static object GetDifferentEnumValue(Type type, object defaultValue) } public static NamingStylePreferences GetNonDefaultNamingStylePreference() + => CreateNamingStylePreferences(([TypeKind.Class], Capitalization.PascalCase, ReportDiagnostic.Error)); + + public static NamingStylePreferences CreateNamingStylePreferences( + params (SymbolSpecification.SymbolKindOrTypeKind[], Capitalization capitalization, ReportDiagnostic severity)[] rules) { - var symbolSpecification = new SymbolSpecification( - Guid.NewGuid(), - "Name", - ImmutableArray.Create(new SymbolSpecification.SymbolKindOrTypeKind(TypeKind.Class)), - accessibilityList: default, - modifiers: default); - - var namingStyle = new NamingStyle( - Guid.NewGuid(), - capitalizationScheme: Capitalization.PascalCase, - name: "Name", - prefix: "", - suffix: "", - wordSeparator: ""); - - var namingRule = new SerializableNamingRule() + var symbolSpecifications = new List(); + var namingStyles = new List(); + var namingRules = new List(); + + foreach (var (kinds, capitalization, severity) in rules) { - SymbolSpecificationID = symbolSpecification.ID, - NamingStyleID = namingStyle.ID, - EnforcementLevel = ReportDiagnostic.Error - }; + var id = namingRules.Count; + + var symbolSpecification = new SymbolSpecification( + Guid.NewGuid(), + name: $"symbols{id}", + [.. kinds], + accessibilityList: default, + modifiers: default); + + symbolSpecifications.Add(symbolSpecification); + + var namingStyle = new NamingStyle( + Guid.NewGuid(), + capitalizationScheme: capitalization, + name: $"style{id}", + prefix: "", + suffix: "", + wordSeparator: ""); + + namingStyles.Add(namingStyle); + + namingRules.Add(new SerializableNamingRule() + { + SymbolSpecificationID = symbolSpecification.ID, + NamingStyleID = namingStyle.ID, + EnforcementLevel = severity + }); + } return new NamingStylePreferences( - ImmutableArray.Create(symbolSpecification), - ImmutableArray.Create(namingStyle), - ImmutableArray.Create(namingRule)); + [.. symbolSpecifications], + [.. namingStyles], + [.. namingRules]); } + + public static NamingStylePreferences ParseNamingStylePreferences(Dictionary options) + => EditorConfigNamingStyleParser.ParseDictionary(new DictionaryAnalyzerConfigOptions(options.ToImmutableDictionary())); } } diff --git a/src/Workspaces/CoreTestUtilities/OptionsCollection.cs b/src/Workspaces/CoreTestUtilities/OptionsCollection.cs index 2dd25d10a1461..fdf9d9e610026 100644 --- a/src/Workspaces/CoreTestUtilities/OptionsCollection.cs +++ b/src/Workspaces/CoreTestUtilities/OptionsCollection.cs @@ -109,7 +109,8 @@ public StructuredAnalyzerConfigOptions ToAnalyzerConfigOptions() namingPreferences.NamingRules, LanguageName, entryWriter: builder.Add, - triviaWriter: null); + triviaWriter: null, + setPrioritiesToPreserveOrder: false); } else { diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/FormattingOptions2.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/FormattingOptions2.cs index b67f7eb02c15a..42ab66ec74a49 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/FormattingOptions2.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Formatting/FormattingOptions2.cs @@ -62,7 +62,8 @@ internal sealed partial class FormattingOptions2 public static PerLanguageOption2 SmartIndent = new PerLanguageOption2( "smart_indent", defaultValue: IndentationOptions.DefaultIndentStyle, - group: FormattingOptionGroups.IndentationAndSpacing) + group: FormattingOptionGroups.IndentationAndSpacing, + serializer: EditorConfigValueSerializer.CreateSerializerForEnum()) .WithPublicOption(PublicFeatureName, "SmartIndent", static value => (PublicIndentStyle)value, static value => (IndentStyle)value); /// diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/EditorConfig/EditorConfigNamingStyleParser.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/EditorConfig/EditorConfigNamingStyleParser.cs index c008d352e2eac..c982592d60689 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/EditorConfig/EditorConfigNamingStyleParser.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/EditorConfig/EditorConfigNamingStyleParser.cs @@ -5,9 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Runtime.CompilerServices; using Microsoft.CodeAnalysis.NamingStyles; -using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; @@ -22,38 +20,22 @@ public static NamingStylePreferences ParseDictionary(AnalyzerConfigOptions allRa var symbolSpecifications = ArrayBuilder.GetInstance(); var namingStyles = ArrayBuilder.GetInstance(); var namingRules = ArrayBuilder.GetInstance(); - var ruleNames = new Dictionary<(Guid symbolSpecificationID, Guid namingStyleID, ReportDiagnostic enforcementLevel), string>(); + var ruleNamesAndPriorities = new Dictionary<(Guid symbolSpecificationID, Guid namingStyleID, ReportDiagnostic enforcementLevel), (string title, int priority)>(); foreach (var namingRuleTitle in GetRuleTitles(trimmedDictionary)) { if (TryGetSymbolSpec(namingRuleTitle, trimmedDictionary, out var symbolSpec) && TryGetNamingStyleData(namingRuleTitle, trimmedDictionary, out var namingStyle) && - TryGetSerializableNamingRule(namingRuleTitle, symbolSpec, namingStyle, trimmedDictionary, out var serializableNamingRule)) + TryGetSerializableNamingRule(namingRuleTitle, symbolSpec, namingStyle, trimmedDictionary, out var serializableNamingRule, out var priority)) { symbolSpecifications.Add(symbolSpec); namingStyles.Add(namingStyle); namingRules.Add(serializableNamingRule); - var ruleKey = (serializableNamingRule.SymbolSpecificationID, serializableNamingRule.NamingStyleID, serializableNamingRule.EnforcementLevel); - if (ruleNames.TryGetValue(ruleKey, out var existingName)) - { - // For duplicated rules, only preserve the one with a name that would sort first - var ordinalIgnoreCaseOrdering = StringComparer.OrdinalIgnoreCase.Compare(namingRuleTitle, existingName); - if (ordinalIgnoreCaseOrdering > 0) - { - continue; - } - else if (ordinalIgnoreCaseOrdering == 0) - { - var ordinalOrdering = StringComparer.Ordinal.Compare(namingRuleTitle, existingName); - if (ordinalOrdering > 0) - { - continue; - } - } - } - - ruleNames[ruleKey] = namingRuleTitle; + // the key comprises of newly generated guids and is thus unique: + ruleNamesAndPriorities.Add( + (serializableNamingRule.SymbolSpecificationID, serializableNamingRule.NamingStyleID, serializableNamingRule.EnforcementLevel), + (namingRuleTitle, priority)); } } @@ -62,7 +44,9 @@ public static NamingStylePreferences ParseDictionary(AnalyzerConfigOptions allRa namingStyles.ToImmutableAndFree(), namingRules.ToImmutableAndFree()); - // Deterministically order the naming style rules according to the symbols matched by the rule. The rules + // Deterministically order the naming style rules. + // + // Rules of the same priority are ordered according to the symbols matched by the rule. The rules // are applied in order; later rules are only relevant if earlier rules fail to specify an order. // // 1. If the modifiers required by rule 'x' are a strict superset of the modifiers required by rule 'y', @@ -82,11 +66,12 @@ public static NamingStylePreferences ParseDictionary(AnalyzerConfigOptions allRa // which a user has trouble ordering, the intersection of the two rules can be broken out into a new rule // will always match earlier than the broader rules it was derived from. var orderedRules = preferences.Rules.NamingRules - .OrderBy(rule => rule, NamingRuleModifierListComparer.Instance) + .OrderBy(rule => ruleNamesAndPriorities[(rule.SymbolSpecification.ID, rule.NamingStyle.ID, rule.EnforcementLevel)].priority) + .ThenBy(rule => rule, NamingRuleModifierListComparer.Instance) .ThenBy(rule => rule, NamingRuleAccessibilityListComparer.Instance) .ThenBy(rule => rule, NamingRuleSymbolListComparer.Instance) - .ThenBy(rule => ruleNames[(rule.SymbolSpecification.ID, rule.NamingStyle.ID, rule.EnforcementLevel)], StringComparer.OrdinalIgnoreCase) - .ThenBy(rule => ruleNames[(rule.SymbolSpecification.ID, rule.NamingStyle.ID, rule.EnforcementLevel)], StringComparer.Ordinal); + .ThenBy(rule => ruleNamesAndPriorities[(rule.SymbolSpecification.ID, rule.NamingStyle.ID, rule.EnforcementLevel)].title, StringComparer.OrdinalIgnoreCase) + .ThenBy(rule => ruleNamesAndPriorities[(rule.SymbolSpecification.ID, rule.NamingStyle.ID, rule.EnforcementLevel)].title, StringComparer.Ordinal); return new NamingStylePreferences( preferences.SymbolSpecifications, diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/EditorConfig/EditorConfigNamingStyleParser_NamingRule.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/EditorConfig/EditorConfigNamingStyleParser_NamingRule.cs index 2fd36e8770b78..f59336871a650 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/EditorConfig/EditorConfigNamingStyleParser_NamingRule.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/EditorConfig/EditorConfigNamingStyleParser_NamingRule.cs @@ -17,8 +17,11 @@ private static bool TryGetSerializableNamingRule( SymbolSpecification symbolSpec, NamingStyle namingStyle, IReadOnlyDictionary conventionsDictionary, - [NotNullWhen(true)] out SerializableNamingRule? serializableNamingRule) + [NotNullWhen(true)] out SerializableNamingRule? serializableNamingRule, + out int priority) { + priority = GetRulePriority(namingRuleTitle, conventionsDictionary); + if (!TryGetRuleSeverity(namingRuleTitle, conventionsDictionary, out var severity)) { serializableNamingRule = null; @@ -35,6 +38,12 @@ private static bool TryGetSerializableNamingRule( return true; } + private static int GetRulePriority(string namingRuleName, IReadOnlyDictionary conventionsDictionary) + => conventionsDictionary.TryGetValue($"dotnet_naming_rule.{namingRuleName}.priority", out var value) && + int.TryParse(value, out var result) + ? result + : 0; + internal static bool TryGetRuleSeverity( string namingRuleName, IReadOnlyDictionary conventionsDictionary, diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/NamingRule.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/NamingRule.cs index bb8f149dc204c..52029cf4d2875 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/NamingRule.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/NamingRule.cs @@ -2,13 +2,14 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using Microsoft.CodeAnalysis.NamingStyles; namespace Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles; -internal readonly struct NamingRule(SymbolSpecification symbolSpecification, NamingStyle namingStyle, ReportDiagnostic enforcementLevel) +internal readonly struct NamingRule( + SymbolSpecification symbolSpecification, + NamingStyle namingStyle, + ReportDiagnostic enforcementLevel) { public readonly SymbolSpecification SymbolSpecification = symbolSpecification; public readonly NamingStyle NamingStyle = namingStyle; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/NamingStyleOptions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/NamingStyleOptions.cs index fbeeeceb2af82..cd5ce07b1828d 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/NamingStyleOptions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/NamingStyleOptions.cs @@ -24,7 +24,8 @@ internal static class NamingStyleOptions internal static PerLanguageOption2 NamingPreferences { get; } = new( NamingPreferencesOptionName, defaultValue: NamingStylePreferences.Default, - isEditorConfigOption: true); + isEditorConfigOption: true, + serializer: EditorConfigValueSerializer.Unsupported); } internal interface NamingStylePreferencesProvider diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/Serialization/NamingStylePreferencesEditorConfigSerializer.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/Serialization/NamingStylePreferencesEditorConfigSerializer.cs index f27600ca2941d..6b6d3349e001c 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/Serialization/NamingStylePreferencesEditorConfigSerializer.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/Serialization/NamingStylePreferencesEditorConfigSerializer.cs @@ -39,35 +39,47 @@ public static void AppendNamingStylePreferencesToEditorConfig( serializableNamingRules, language, entryWriter: (name, value) => builder.AppendLine($"{name} = {value}"), - triviaWriter: trivia => builder.AppendLine(trivia)); + triviaWriter: trivia => builder.AppendLine(trivia), + setPrioritiesToPreserveOrder: false); } public static void WriteNamingStylePreferencesToEditorConfig( ImmutableArray symbolSpecifications, ImmutableArray namingStyles, - ImmutableArray serializableNamingRules, + ImmutableArray rules, string language, Action entryWriter, - Action? triviaWriter) + Action? triviaWriter, + bool setPrioritiesToPreserveOrder) { triviaWriter?.Invoke($"#### {CompilerExtensionsResources.Naming_styles} ####"); var serializedNameMap = AssignNamesToNamingStyleElements(symbolSpecifications, namingStyles); - var ruleNameMap = AssignNamesToNamingStyleRules(serializableNamingRules, serializedNameMap); + var ruleNameMap = AssignNamesToNamingStyleRules(rules, serializedNameMap); var referencedElements = new HashSet(); triviaWriter?.Invoke(""); triviaWriter?.Invoke($"# {CompilerExtensionsResources.Naming_rules}"); - foreach (var namingRule in serializableNamingRules) + var priority = 0; + foreach (var namingRule in rules) { referencedElements.Add(namingRule.SymbolSpecificationID); referencedElements.Add(namingRule.NamingStyleID); triviaWriter?.Invoke(""); - entryWriter($"dotnet_naming_rule.{ruleNameMap[namingRule]}.severity", namingRule.EnforcementLevel.ToNotificationOption(defaultSeverity: DiagnosticSeverity.Hidden).ToEditorConfigString()); - entryWriter($"dotnet_naming_rule.{ruleNameMap[namingRule]}.symbols", serializedNameMap[namingRule.SymbolSpecificationID]); - entryWriter($"dotnet_naming_rule.{ruleNameMap[namingRule]}.style", serializedNameMap[namingRule.NamingStyleID]); + var ruleName = ruleNameMap[namingRule]; + + if (setPrioritiesToPreserveOrder) + { + entryWriter($"dotnet_naming_rule.{ruleName}.priority", priority.ToString()); + } + + entryWriter($"dotnet_naming_rule.{ruleName}.severity", namingRule.EnforcementLevel.ToNotificationOption(defaultSeverity: DiagnosticSeverity.Hidden).ToEditorConfigString()); + entryWriter($"dotnet_naming_rule.{ruleName}.symbols", serializedNameMap[namingRule.SymbolSpecificationID]); + entryWriter($"dotnet_naming_rule.{ruleName}.style", serializedNameMap[namingRule.NamingStyleID]); + + priority++; } triviaWriter?.Invoke(""); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/Serialization/SerializableNamingRule.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/Serialization/SerializableNamingRule.cs index b6b55db3c0361..85d54b4ea1aa6 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/Serialization/SerializableNamingRule.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/Serialization/SerializableNamingRule.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Runtime.Serialization; using System.Xml.Linq; @@ -45,9 +43,9 @@ internal static SerializableNamingRule FromXElement(XElement namingRuleElement) { return new SerializableNamingRule() { - EnforcementLevel = ((DiagnosticSeverity)Enum.Parse(typeof(DiagnosticSeverity), namingRuleElement.Attribute(nameof(EnforcementLevel)).Value)).ToReportDiagnostic(), - NamingStyleID = Guid.Parse(namingRuleElement.Attribute(nameof(NamingStyleID)).Value), - SymbolSpecificationID = Guid.Parse(namingRuleElement.Attribute(nameof(SymbolSpecificationID)).Value) + EnforcementLevel = ((DiagnosticSeverity)Enum.Parse(typeof(DiagnosticSeverity), namingRuleElement.Attribute(nameof(EnforcementLevel))!.Value)).ToReportDiagnostic(), + NamingStyleID = Guid.Parse(namingRuleElement.Attribute(nameof(NamingStyleID))!.Value), + SymbolSpecificationID = Guid.Parse(namingRuleElement.Attribute(nameof(SymbolSpecificationID))!.Value) }; } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/Serialization/SymbolSpecification.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/Serialization/SymbolSpecification.cs index 7a586fc8f4ee9..053076aea4699 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/Serialization/SymbolSpecification.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/Serialization/SymbolSpecification.cs @@ -439,6 +439,15 @@ internal static SymbolKindOrTypeKind AddTypeKindFromXElement(XElement typeKindEl internal static SymbolKindOrTypeKind AddMethodKindFromXElement(XElement methodKindElement) => new((MethodKind)Enum.Parse(typeof(MethodKind), methodKindElement.Value)); + + public static implicit operator SymbolKindOrTypeKind(SymbolKind symbolKind) + => new(symbolKind); + + public static implicit operator SymbolKindOrTypeKind(TypeKind symbolKind) + => new(symbolKind); + + public static implicit operator SymbolKindOrTypeKind(MethodKind symbolKind) + => new(symbolKind); } [DataContract] diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/EditorConfigValueSerializer.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/EditorConfigValueSerializer.cs index 56bc217d99dc4..b80653fc5651d 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/EditorConfigValueSerializer.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/EditorConfigValueSerializer.cs @@ -52,7 +52,7 @@ private static string SerializeBoolean(bool value) return optionalBool.HasValue ? new Optional(optionalBool.Value) : new Optional(); } - public static EditorConfigValueSerializer Default() + public static EditorConfigValueSerializer GetDefault(bool isEditorConfigOption) { if (typeof(T) == typeof(bool)) return (EditorConfigValueSerializer)(object)s_bool; @@ -66,9 +66,10 @@ public static EditorConfigValueSerializer Default() if (typeof(T) == typeof(bool?)) return (EditorConfigValueSerializer)(object)s_nullableBoolean; - // TODO: https://github.com/dotnet/roslyn/issues/65787 - // Once all global options define a serializer this should be changed to: - // throw ExceptionUtilities.UnexpectedValue(typeof(T)); + // editorconfig options must have a serializer: + if (isEditorConfigOption) + throw ExceptionUtilities.UnexpectedValue(typeof(T)); + return EditorConfigValueSerializer.Unsupported; } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/OptionDefinition.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/OptionDefinition.cs index fb7627e47a7e0..8f0c4f3347445 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/OptionDefinition.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/OptionDefinition.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Options; @@ -116,7 +117,7 @@ internal sealed class OptionDefinition( bool isEditorConfigOption) : OptionDefinition(group, configName, defaultValue, storageMapping, isEditorConfigOption) { public new T DefaultValue { get; } = defaultValue; - public new EditorConfigValueSerializer Serializer { get; } = serializer ?? EditorConfigValueSerializer.Default(); + public new EditorConfigValueSerializer Serializer { get; } = serializer ?? EditorConfigValueSerializer.GetDefault(isEditorConfigOption); public override Type Type => typeof(T);