diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/DeclarationNameCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/DeclarationNameCompletionProviderTests.cs index c6c6489f1cf07..80200ff772dc4 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/DeclarationNameCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/DeclarationNameCompletionProviderTests.cs @@ -1,14 +1,22 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; +using System.Collections.Immutable; +using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Completion; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Completion.Providers; +using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles; using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Completion.CompletionProviders; using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; +using Microsoft.CodeAnalysis.NamingStyles; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Simplification; using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; using Xunit; +using static Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles.SymbolSpecification; namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Completion.CompletionSetSources { @@ -32,8 +40,8 @@ public class MyClass MyClass $$ } "; - await VerifyItemExistsAsync(markup, "MyClass", glyph: (int)Glyph.MethodPublic); await VerifyItemExistsAsync(markup, "myClass", glyph: (int)Glyph.FieldPublic); + await VerifyItemExistsAsync(markup, "MyClass", glyph: (int)Glyph.PropertyPublic); await VerifyItemExistsAsync(markup, "GetMyClass", glyph: (int)Glyph.MethodPublic); } @@ -544,7 +552,7 @@ void goo() } [Fact, Trait(Traits.Feature, Traits.Features.Completion)] - public async Task TestDescription() + public async Task TestCorrectOrder() { var markup = @" public class MyClass @@ -552,7 +560,41 @@ public class MyClass MyClass $$ } "; - await VerifyItemExistsAsync(markup, "MyClass", glyph: (int)Glyph.MethodPublic, expectedDescriptionOrNull: CSharpFeaturesResources.Suggested_name); + var items = await GetCompletionItemsAsync(markup, SourceCodeKind.Regular); + Assert.Equal( + new[] { "myClass", "my", "@class", "MyClass", "My", "Class", "GetMyClass", "GetMy", "GetClass" }, + items.Select(item => item.DisplayText)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task TestDescriptionInsideClass() + { + var markup = @" +public class MyClass +{ + MyClass $$ +} +"; + await VerifyItemExistsAsync(markup, "myClass", glyph: (int)Glyph.FieldPublic, expectedDescriptionOrNull: CSharpFeaturesResources.Suggested_name); + await VerifyItemExistsAsync(markup, "MyClass", glyph: (int)Glyph.PropertyPublic, expectedDescriptionOrNull: CSharpFeaturesResources.Suggested_name); + await VerifyItemExistsAsync(markup, "GetMyClass", glyph: (int)Glyph.MethodPublic, expectedDescriptionOrNull: CSharpFeaturesResources.Suggested_name); + } + + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task TestDescriptionInsideMethod() + { + var markup = @" +public class MyClass +{ + void M() + { + MyClass $$ + } +} +"; + await VerifyItemExistsAsync(markup, "myClass", glyph: (int)Glyph.Local, expectedDescriptionOrNull: CSharpFeaturesResources.Suggested_name); + await VerifyItemIsAbsentAsync(markup, "MyClass"); + await VerifyItemIsAbsentAsync(markup, "GetMyClass"); } [WorkItem(20273, "https://github.com/dotnet/roslyn/issues/20273")] @@ -1411,5 +1453,122 @@ public void Method() "; await VerifyItemExistsAsync(markup, "nullables"); } + + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task CustomNamingStyleInsideClass() + { + var workspace = WorkspaceFixture.GetWorkspace(); + var originalOptions = workspace.Options; + + try + { + workspace.Options = workspace.Options.WithChangedOption( + new OptionKey(SimplificationOptions.NamingPreferences, LanguageNames.CSharp), + NamesEndWithSuffixPreferences()); + + var markup = @" +class Configuration +{ + Configuration $$ +} +"; + await VerifyItemExistsAsync(markup, "ConfigurationField", glyph: (int)Glyph.FieldPublic, + expectedDescriptionOrNull: CSharpFeaturesResources.Suggested_name); + await VerifyItemExistsAsync(markup, "ConfigurationProperty", glyph: (int)Glyph.PropertyPublic, + expectedDescriptionOrNull: CSharpFeaturesResources.Suggested_name); + await VerifyItemExistsAsync(markup, "ConfigurationMethod", glyph: (int)Glyph.MethodPublic, + expectedDescriptionOrNull: CSharpFeaturesResources.Suggested_name); + await VerifyItemIsAbsentAsync(markup, "ConfigurationLocal"); + await VerifyItemIsAbsentAsync(markup, "ConfigurationLocalFunction"); + } + finally + { + workspace.Options = originalOptions; + } + } + + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task CustomNamingStyleInsideMethod() + { + var workspace = WorkspaceFixture.GetWorkspace(); + var originalOptions = workspace.Options; + + try + { + workspace.Options = workspace.Options.WithChangedOption( + new OptionKey(SimplificationOptions.NamingPreferences, LanguageNames.CSharp), + NamesEndWithSuffixPreferences()); + + var markup = @" +class Configuration +{ + void M() + { + Configuration $$ + } +} +"; + await VerifyItemExistsAsync(markup, "ConfigurationLocal", glyph: (int)Glyph.Local, + expectedDescriptionOrNull: CSharpFeaturesResources.Suggested_name); + await VerifyItemExistsAsync(markup, "ConfigurationLocalFunction", glyph: (int)Glyph.MethodPublic, + expectedDescriptionOrNull: CSharpFeaturesResources.Suggested_name); + await VerifyItemIsAbsentAsync(markup, "ConfigurationField"); + await VerifyItemIsAbsentAsync(markup, "ConfigurationMethod"); + await VerifyItemIsAbsentAsync(markup, "ConfigurationProperty"); + } + finally + { + workspace.Options = originalOptions; + } + } + + private static NamingStylePreferences NamesEndWithSuffixPreferences() + { + var specificationStyles = new[] + { + SpecificationStyle(new SymbolKindOrTypeKind(SymbolKind.Field), "Field"), + SpecificationStyle(new SymbolKindOrTypeKind(SymbolKind.Property), "Property"), + SpecificationStyle(new SymbolKindOrTypeKind(MethodKind.Ordinary), "Method"), + SpecificationStyle(new SymbolKindOrTypeKind(SymbolKind.Local), "Local"), + SpecificationStyle(new SymbolKindOrTypeKind(MethodKind.LocalFunction), "LocalFunction"), + }; + + return new NamingStylePreferences( + specificationStyles.Select(t => t.specification).ToImmutableArray(), + specificationStyles.Select(t => t.style).ToImmutableArray(), + specificationStyles.Select(t => CreateRule(t.specification, t.style)).ToImmutableArray()); + + // Local functions + + (SymbolSpecification specification, NamingStyle style) SpecificationStyle(SymbolKindOrTypeKind kind, string suffix) + { + var symbolSpecification = new SymbolSpecification( + id: null, + symbolSpecName: suffix, + ImmutableArray.Create(kind), + ImmutableArray.Empty, + ImmutableArray.Empty); + + var namingStyle = new NamingStyle( + Guid.NewGuid(), + name: suffix, + capitalizationScheme: Capitalization.PascalCase, + prefix: "", + suffix: suffix, + wordSeparator: ""); + + return (symbolSpecification, namingStyle); + } + + SerializableNamingRule CreateRule(SymbolSpecification specification, NamingStyle style) + { + return new SerializableNamingRule() + { + SymbolSpecificationID = specification.ID, + NamingStyleID = style.ID, + EnforcementLevel = DiagnosticSeverity.Error + }; + } + } } } diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/DeclarationNameCompletionProviderTests_NameDeclarationInfoTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/DeclarationNameCompletionProviderTests_NameDeclarationInfoTests.cs index 862705c096d20..fc12289b0792f 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/DeclarationNameCompletionProviderTests_NameDeclarationInfoTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/DeclarationNameCompletionProviderTests_NameDeclarationInfoTests.cs @@ -7,6 +7,7 @@ using Roslyn.Test.Utilities; using Xunit; using static Microsoft.CodeAnalysis.CSharp.Completion.Providers.DeclarationNameCompletionProvider; +using static Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles.SymbolSpecification; namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.DeclarationInfoTests { @@ -24,7 +25,10 @@ class C int $$ } "; - await VerifySymbolKinds(markup, SymbolKind.Field, SymbolKind.Method, SymbolKind.Property); + await VerifySymbolKinds(markup, + new SymbolKindOrTypeKind(SymbolKind.Field), + new SymbolKindOrTypeKind(SymbolKind.Property), + new SymbolKindOrTypeKind(MethodKind.Ordinary)); await VerifyNoModifiers(markup); await VerifyTypeName(markup, "int"); await VerifyAccessibility(markup, Accessibility.NotApplicable); @@ -39,7 +43,10 @@ class C public int $$ } "; - await VerifySymbolKinds(markup, SymbolKind.Field, SymbolKind.Method, SymbolKind.Property); + await VerifySymbolKinds(markup, + new SymbolKindOrTypeKind(SymbolKind.Field), + new SymbolKindOrTypeKind(SymbolKind.Property), + new SymbolKindOrTypeKind(MethodKind.Ordinary)); await VerifyNoModifiers(markup); await VerifyTypeName(markup, "int"); await VerifyAccessibility(markup, Accessibility.Public); @@ -54,7 +61,9 @@ class C public virtual int $$ } "; - await VerifySymbolKinds(markup, SymbolKind.Method, SymbolKind.Property); + await VerifySymbolKinds(markup, + new SymbolKindOrTypeKind(SymbolKind.Property), + new SymbolKindOrTypeKind(MethodKind.Ordinary)); await VerifyModifiers(markup, new DeclarationModifiers(isVirtual: true)); await VerifyTypeName(markup, "int"); await VerifyAccessibility(markup, Accessibility.Public); @@ -69,7 +78,10 @@ class C private static int $$ } "; - await VerifySymbolKinds(markup, SymbolKind.Field, SymbolKind.Method, SymbolKind.Property); + await VerifySymbolKinds(markup, + new SymbolKindOrTypeKind(SymbolKind.Field), + new SymbolKindOrTypeKind(SymbolKind.Property), + new SymbolKindOrTypeKind(MethodKind.Ordinary)); await VerifyModifiers(markup, new DeclarationModifiers(isStatic: true)); await VerifyTypeName(markup, "int"); await VerifyAccessibility(markup, Accessibility.Private); @@ -84,7 +96,8 @@ class C private const int $$ } "; - await VerifySymbolKinds(markup, SymbolKind.Field); + await VerifySymbolKinds(markup, + new SymbolKindOrTypeKind(SymbolKind.Field)); await VerifyModifiers(markup, new DeclarationModifiers(isConst: true)); await VerifyTypeName(markup, "int"); await VerifyAccessibility(markup, Accessibility.Private); @@ -102,7 +115,9 @@ void goo() } } "; - await VerifySymbolKinds(markup, SymbolKind.Local); + await VerifySymbolKinds(markup, + new SymbolKindOrTypeKind(SymbolKind.Local), + new SymbolKindOrTypeKind(MethodKind.LocalFunction)); await VerifyModifiers(markup, new DeclarationModifiers()); await VerifyTypeName(markup, "int"); await VerifyAccessibility(markup, Accessibility.NotApplicable); @@ -120,7 +135,8 @@ void goo() } } "; - await VerifySymbolKinds(markup, SymbolKind.Local); + await VerifySymbolKinds(markup, + new SymbolKindOrTypeKind(SymbolKind.Local)); await VerifyModifiers(markup, new DeclarationModifiers()); await VerifyTypeName(markup, "int"); await VerifyAccessibility(markup, Accessibility.NotApplicable); @@ -138,7 +154,9 @@ readonly int $$ } } "; - await VerifySymbolKinds(markup, SymbolKind.Local); + await VerifySymbolKinds(markup, + new SymbolKindOrTypeKind(SymbolKind.Local), + new SymbolKindOrTypeKind(MethodKind.LocalFunction)); await VerifyModifiers(markup, new DeclarationModifiers(isReadOnly: true)); await VerifyTypeName(markup, "int"); await VerifyAccessibility(markup, Accessibility.NotApplicable); @@ -156,7 +174,8 @@ void goo() } } "; - await VerifySymbolKinds(markup, SymbolKind.Local); + await VerifySymbolKinds(markup, + new SymbolKindOrTypeKind(SymbolKind.Local)); await VerifyModifiers(markup, new DeclarationModifiers(isReadOnly: true)); await VerifyTypeName(markup, "int"); await VerifyAccessibility(markup, Accessibility.NotApplicable); @@ -174,7 +193,8 @@ void M() } } "; - await VerifySymbolKinds(markup, SymbolKind.Local); + await VerifySymbolKinds(markup, + new SymbolKindOrTypeKind(SymbolKind.Local)); await VerifyModifiers(markup, new DeclarationModifiers()); await VerifyTypeName(markup, "int"); await VerifyAccessibility(markup, Accessibility.NotApplicable); @@ -192,7 +212,8 @@ void M() } } "; - await VerifySymbolKinds(markup, SymbolKind.Local); + await VerifySymbolKinds(markup, + new SymbolKindOrTypeKind(SymbolKind.Local)); await VerifyModifiers(markup, new DeclarationModifiers()); await VerifyTypeName(markup, "int"); await VerifyAccessibility(markup, Accessibility.NotApplicable); @@ -210,7 +231,8 @@ void M() } } "; - await VerifySymbolKinds(markup, SymbolKind.Local); + await VerifySymbolKinds(markup, + new SymbolKindOrTypeKind(SymbolKind.Local)); await VerifyModifiers(markup, new DeclarationModifiers()); await VerifyTypeName(markup, "int"); await VerifyAccessibility(markup, Accessibility.NotApplicable); @@ -228,7 +250,8 @@ void M() } } "; - await VerifySymbolKinds(markup, SymbolKind.Local); + await VerifySymbolKinds(markup, + new SymbolKindOrTypeKind(SymbolKind.Local)); await VerifyModifiers(markup, new DeclarationModifiers()); await VerifyTypeName(markup, "int"); await VerifyAccessibility(markup, Accessibility.NotApplicable); @@ -246,7 +269,8 @@ void M() } } "; - await VerifySymbolKinds(markup, SymbolKind.Local); + await VerifySymbolKinds(markup, + new SymbolKindOrTypeKind(SymbolKind.Local)); await VerifyModifiers(markup, new DeclarationModifiers()); await VerifyTypeName(markup, "int"); await VerifyAccessibility(markup, Accessibility.NotApplicable); @@ -262,7 +286,8 @@ void goo(C $$ } } "; - await VerifySymbolKinds(markup, SymbolKind.Parameter); + await VerifySymbolKinds(markup, + new SymbolKindOrTypeKind(SymbolKind.Parameter)); await VerifyModifiers(markup, new DeclarationModifiers()); await VerifyTypeName(markup, "global::C"); await VerifyAccessibility(markup, Accessibility.NotApplicable); @@ -278,7 +303,8 @@ void goo(C c1, C $$ } } "; - await VerifySymbolKinds(markup, SymbolKind.Parameter); + await VerifySymbolKinds(markup, + new SymbolKindOrTypeKind(SymbolKind.Parameter)); await VerifyModifiers(markup, new DeclarationModifiers()); await VerifyTypeName(markup, "global::C"); await VerifyAccessibility(markup, Accessibility.NotApplicable); @@ -294,7 +320,8 @@ void goo(string $$ } } "; - await VerifySymbolKinds(markup, SymbolKind.Parameter); + await VerifySymbolKinds(markup, + new SymbolKindOrTypeKind(SymbolKind.Parameter)); await VerifyModifiers(markup, new DeclarationModifiers()); await VerifyTypeName(markup, "string"); await VerifyAccessibility(markup, Accessibility.NotApplicable); @@ -310,7 +337,8 @@ void goo(C c1, string $$ } } "; - await VerifySymbolKinds(markup, SymbolKind.Parameter); + await VerifySymbolKinds(markup, + new SymbolKindOrTypeKind(SymbolKind.Parameter)); await VerifyModifiers(markup, new DeclarationModifiers()); await VerifyTypeName(markup, "string"); await VerifyAccessibility(markup, Accessibility.NotApplicable); @@ -327,7 +355,8 @@ void goo(C c1, List $$ } } "; - await VerifySymbolKinds(markup, SymbolKind.Parameter); + await VerifySymbolKinds(markup, + new SymbolKindOrTypeKind(SymbolKind.Parameter)); await VerifyModifiers(markup, new DeclarationModifiers()); await VerifyTypeName(markup, "global::System.Collections.Generic.List"); await VerifyAccessibility(markup, Accessibility.NotApplicable); @@ -341,7 +370,8 @@ class C<$$ { } "; - await VerifySymbolKinds(markup, SymbolKind.TypeParameter); + await VerifySymbolKinds(markup, + new SymbolKindOrTypeKind(SymbolKind.TypeParameter)); } [Fact, Trait(Traits.Feature, Traits.Features.Completion)] @@ -352,7 +382,8 @@ class C")] + public async Task ModifierExclusionInsideMethod_Const(string type) + { + var markup = $@" +using System.Collections.Generic; +class C +{{ + void M() + {{ + const {type} $$ + }} +}} +"; + await VerifySymbolKinds(markup, + new SymbolKindOrTypeKind(SymbolKind.Local)); + } + + [Theory, Trait(Traits.Feature, Traits.Features.Completion)] + [InlineData("int")] + [InlineData("C")] + [InlineData("List")] + public async Task ModifierExclusionInsideMethod_ConstLocalDeclaration(string type) + { + var markup = $@" +using System.Collections.Generic; +class C +{{ + void M() + {{ + const {type} v$$ = default; + }} +}} +"; + await VerifySymbolKinds(markup, + new SymbolKindOrTypeKind(SymbolKind.Local)); + } + + [Theory, Trait(Traits.Feature, Traits.Features.Completion)] + [InlineData("int")] + [InlineData("C")] + [InlineData("List")] + public async Task ModifierExclusionInsideMethod_ConstLocalFunction(string type) + { + var markup = $@" +using System.Collections.Generic; +class C +{{ + void M() + {{ + const {type} v$$() + {{ + }} + }} +}} +"; + await VerifySymbolKinds(markup, + new SymbolKindOrTypeKind(SymbolKind.Local)); + } + + [Theory, Trait(Traits.Feature, Traits.Features.Completion)] + [InlineData("int")] + [InlineData("C")] + [InlineData("List")] + public async Task ModifierExclusionInsideMethod_Async(string type) + { + // This only works with a partially written name. + // Because async is not a keyword, the syntax tree when the name is missing is completely broken + // in that there can be multiple statements full of missing and skipped tokens depending on the type syntax. + var markup = $@" +using System.Collections.Generic; +class C +{{ + void M() + {{ + async {type} v$$ + }} +}} +"; + await VerifySymbolKinds(markup, + new SymbolKindOrTypeKind(MethodKind.LocalFunction)); + } + + [Theory, Trait(Traits.Feature, Traits.Features.Completion)] + [InlineData("int")] + [InlineData("C")] + [InlineData("List")] + public async Task ModifierExclusionInsideMethod_AsyncLocalDeclaration(string type) + { + var markup = $@" +using System.Collections.Generic; +class C +{{ + void M() + {{ + async {type} v$$ = default; + }} +}} +"; + await VerifySymbolKinds(markup, + new SymbolKindOrTypeKind(MethodKind.LocalFunction)); + } + + [Theory, Trait(Traits.Feature, Traits.Features.Completion)] + [InlineData("int")] + [InlineData("C")] + [InlineData("List")] + public async Task ModifierExclusionInsideMethod_AsyncLocalFunction(string type) + { + var markup = $@" +using System.Collections.Generic; +class C +{{ + void M() + {{ + async {type} v$$() + {{ + }} + }} +}} +"; + await VerifySymbolKinds(markup, + new SymbolKindOrTypeKind(MethodKind.LocalFunction)); + } + + [Theory, Trait(Traits.Feature, Traits.Features.Completion)] + [InlineData("int")] + [InlineData("C")] + [InlineData("List")] + public async Task ModifierExclusionInsideMethod_Unsafe(string type) + { + var markup = $@" +using System.Collections.Generic; +class C +{{ + void M() + {{ + unsafe {type} $$ + }} +}} +"; + await VerifySymbolKinds(markup, + new SymbolKindOrTypeKind(MethodKind.LocalFunction)); + } + + [Theory, Trait(Traits.Feature, Traits.Features.Completion)] + [InlineData("int")] + [InlineData("C")] + [InlineData("List")] + public async Task ModifierExclusionInsideMethod_UnsafeLocalDeclaration(string type) + { + var markup = $@" +using System.Collections.Generic; +class C +{{ + void M() + {{ + unsafe {type} v$$ = default; + }} +}} +"; + await VerifySymbolKinds(markup, + new SymbolKindOrTypeKind(MethodKind.LocalFunction)); + } + + [Theory, Trait(Traits.Feature, Traits.Features.Completion)] + [InlineData("int")] + [InlineData("C")] + [InlineData("List")] + public async Task ModifierExclusionInsideMethod_UnsafeLocalFunction(string type) + { + var markup = $@" +using System.Collections.Generic; +class C +{{ + void M() + {{ + unsafe {type} v$$() + {{ + }} + }} +}} +"; + await VerifySymbolKinds(markup, + new SymbolKindOrTypeKind(MethodKind.LocalFunction)); } [Fact, Trait(Traits.Feature, Traits.Features.Completion)] @@ -467,7 +701,9 @@ static void Main(string[] args) } } "; - await VerifySymbolKinds(markup, SymbolKind.Local); + await VerifySymbolKinds(markup, + new SymbolKindOrTypeKind(SymbolKind.Local), + new SymbolKindOrTypeKind(MethodKind.LocalFunction)); } [Fact, Trait(Traits.Feature, Traits.Features.Completion)] @@ -485,7 +721,9 @@ static void Main(string[] args) } } "; - await VerifySymbolKinds(markup, SymbolKind.Local); + await VerifySymbolKinds(markup, + new SymbolKindOrTypeKind(SymbolKind.Local), + new SymbolKindOrTypeKind(MethodKind.LocalFunction)); } [Fact, Trait(Traits.Feature, Traits.Features.Completion)] @@ -503,7 +741,9 @@ static void Main(string[] args) } } "; - await VerifySymbolKinds(markup, SymbolKind.Local); + await VerifySymbolKinds(markup, + new SymbolKindOrTypeKind(SymbolKind.Local), + new SymbolKindOrTypeKind(MethodKind.LocalFunction)); } [Fact, Trait(Traits.Feature, Traits.Features.Completion)] @@ -520,7 +760,9 @@ static void Main(string[] args) } } "; - await VerifySymbolKinds(markup, SymbolKind.Local); + await VerifySymbolKinds(markup, + new SymbolKindOrTypeKind(SymbolKind.Local), + new SymbolKindOrTypeKind(MethodKind.LocalFunction)); } private async Task VerifyNoType(string markup) @@ -541,7 +783,7 @@ private async Task VerifyNoModifiers(string markup) Assert.Equal(default(DeclarationModifiers), result.Modifiers); } - private async Task VerifySymbolKinds(string markup, params SymbolKind[] expectedSymbolKinds) + private async Task VerifySymbolKinds(string markup, params SymbolKindOrTypeKind[] expectedSymbolKinds) { var result = await GetResultsAsync(markup); Assert.True(expectedSymbolKinds.SequenceEqual(result.PossibleSymbolKinds)); diff --git a/src/EditorFeatures/CSharpTest/Diagnostics/NamingStyles/EditorConfigNamingStyleParserTests.cs b/src/EditorFeatures/CSharpTest/Diagnostics/NamingStyles/EditorConfigNamingStyleParserTests.cs index 02cb26990f1d0..7da4550cf89d6 100644 --- a/src/EditorFeatures/CSharpTest/Diagnostics/NamingStyles/EditorConfigNamingStyleParserTests.cs +++ b/src/EditorFeatures/CSharpTest/Diagnostics/NamingStyles/EditorConfigNamingStyleParserTests.cs @@ -37,7 +37,7 @@ public static void TestPascalCaseRule() Assert.Equal("method_and_property_symbols", symbolSpec.Name); var expectedApplicableSymbolKindList = new[] { - new SymbolKindOrTypeKind(SymbolKind.Method), + new SymbolKindOrTypeKind(MethodKind.Ordinary), new SymbolKindOrTypeKind(SymbolKind.Property) }; AssertEx.SetEqual(expectedApplicableSymbolKindList, symbolSpec.ApplicableSymbolKindList); @@ -59,14 +59,14 @@ public static void TestPascalCaseRule() } [Fact] - public static void TestAsyncMethodsRule() + public static void TestAsyncMethodsAndLocalFunctionsRule() { var dictionary = new Dictionary() { ["dotnet_naming_rule.async_methods_must_end_with_async.severity"] = "error", ["dotnet_naming_rule.async_methods_must_end_with_async.symbols"] = "method_symbols", ["dotnet_naming_rule.async_methods_must_end_with_async.style"] = "end_in_async_style", - ["dotnet_naming_symbols.method_symbols.applicable_kinds"] = "method", + ["dotnet_naming_symbols.method_symbols.applicable_kinds"] = "method,local_function", ["dotnet_naming_symbols.method_symbols.required_modifiers"] = "async", ["dotnet_naming_style.end_in_async_style.capitalization "] = "pascal_case", ["dotnet_naming_style.end_in_async_style.required_suffix"] = "Async", @@ -82,8 +82,12 @@ public static void TestAsyncMethodsRule() Assert.Equal(symbolSpec.ID, namingRule.SymbolSpecificationID); Assert.Equal(DiagnosticSeverity.Error, namingRule.EnforcementLevel); Assert.Equal("method_symbols", symbolSpec.Name); - Assert.Single(symbolSpec.ApplicableSymbolKindList); - Assert.Contains(new SymbolKindOrTypeKind(SymbolKind.Method), symbolSpec.ApplicableSymbolKindList); + var expectedApplicableSymbolKindList = new[] + { + new SymbolKindOrTypeKind(MethodKind.Ordinary), + new SymbolKindOrTypeKind(MethodKind.LocalFunction) + }; + AssertEx.SetEqual(expectedApplicableSymbolKindList, symbolSpec.ApplicableSymbolKindList); Assert.Single(symbolSpec.RequiredModifierList); Assert.Contains(new ModifierKind(ModifierKindEnum.IsAsync), symbolSpec.RequiredModifierList); Assert.Empty(symbolSpec.ApplicableAccessibilityList); @@ -137,7 +141,7 @@ public static void TestPublicMembersCapitalizedRule() var expectedApplicableSymbolKindList = new[] { new SymbolKindOrTypeKind(SymbolKind.Property), - new SymbolKindOrTypeKind(SymbolKind.Method), + new SymbolKindOrTypeKind(MethodKind.Ordinary), new SymbolKindOrTypeKind(SymbolKind.Field), new SymbolKindOrTypeKind(SymbolKind.Event), new SymbolKindOrTypeKind(TypeKind.Delegate) @@ -185,7 +189,7 @@ public static void TestNonPublicMembersLowerCaseRule() var expectedApplicableSymbolKindList = new[] { new SymbolKindOrTypeKind(SymbolKind.Property), - new SymbolKindOrTypeKind(SymbolKind.Method), + new SymbolKindOrTypeKind(MethodKind.Ordinary), new SymbolKindOrTypeKind(SymbolKind.Field), new SymbolKindOrTypeKind(SymbolKind.Event), new SymbolKindOrTypeKind(TypeKind.Delegate) @@ -232,6 +236,45 @@ public static void TestParametersAndLocalsAreCamelCaseRule() new SymbolKindOrTypeKind(SymbolKind.Local), }; AssertEx.SetEqual(expectedApplicableSymbolKindList, symbolSpec.ApplicableSymbolKindList); + Assert.Empty(symbolSpec.ApplicableAccessibilityList); + Assert.Empty(symbolSpec.RequiredModifierList); + + Assert.Equal("camel_case_style", namingStyle.Name); + Assert.Equal("", namingStyle.Prefix); + Assert.Equal("", namingStyle.Suffix); + Assert.Equal("", namingStyle.WordSeparator); + Assert.Equal(Capitalization.CamelCase, namingStyle.CapitalizationScheme); + } + + [Fact] + public static void TestLocalFunctionsAreCamelCaseRule() + { + var dictionary = new Dictionary() + { + ["dotnet_naming_rule.local_functions_are_camel_case.severity"] = "suggestion", + ["dotnet_naming_rule.local_functions_are_camel_case.symbols"] = "local_functions", + ["dotnet_naming_rule.local_functions_are_camel_case.style"] = "camel_case_style", + ["dotnet_naming_symbols.local_functions.applicable_kinds"] = "local_function", + ["dotnet_naming_style.camel_case_style.capitalization"] = "camel_case", + }; + + var result = ParseDictionary(dictionary); + Assert.Single(result.NamingRules); + var namingRule = result.NamingRules.Single(); + Assert.Single(result.NamingStyles); + var namingStyle = result.NamingStyles.Single(); + Assert.Single(result.SymbolSpecifications); + + var symbolSpec = result.SymbolSpecifications.Single(); + Assert.Equal(namingStyle.ID, namingRule.NamingStyleID); + Assert.Equal(symbolSpec.ID, namingRule.SymbolSpecificationID); + Assert.Equal(DiagnosticSeverity.Info, namingRule.EnforcementLevel); + + Assert.Equal("local_functions", symbolSpec.Name); + var expectedApplicableSymbolKindList = new[] { new SymbolKindOrTypeKind(MethodKind.LocalFunction) }; + AssertEx.SetEqual(expectedApplicableSymbolKindList, symbolSpec.ApplicableSymbolKindList); + Assert.Empty(symbolSpec.ApplicableAccessibilityList); + Assert.Empty(symbolSpec.RequiredModifierList); Assert.Equal("camel_case_style", namingStyle.Name); Assert.Equal("", namingStyle.Prefix); diff --git a/src/EditorFeatures/CSharpTest/Diagnostics/NamingStyles/NamingStylesTests.cs b/src/EditorFeatures/CSharpTest/Diagnostics/NamingStyles/NamingStylesTests.cs index 3a6b6c66c9ccf..7feb7f1beaf32 100644 --- a/src/EditorFeatures/CSharpTest/Diagnostics/NamingStyles/NamingStylesTests.cs +++ b/src/EditorFeatures/CSharpTest/Diagnostics/NamingStyles/NamingStylesTests.cs @@ -112,6 +112,21 @@ public int [|this|][int index] }", new TestParameters(options: options.MethodNamesArePascalCase)); } + [Fact, Trait(Traits.Feature, Traits.Features.NamingStyle)] + public async Task TestPascalCaseMethod_LocalFunctionIsIgnored() + { + await TestMissingInRegularAndScriptAsync( +@"class C +{ + void M() + { + void [|f|]() + { + } + } +}", new TestParameters(options: options.MethodNamesArePascalCase)); + } + [Fact, Trait(Traits.Feature, Traits.Features.NamingStyle)] public async Task TestCamelCaseParameters() { @@ -697,6 +712,117 @@ void M() options: options.LocalsAreCamelCaseConstantsAreUpperCase); } + [Fact, Trait(Traits.Feature, Traits.Features.NamingStyle)] + public async Task TestCamelCaseLocalFunctions() + { + await TestInRegularAndScriptAsync( +@"class C +{ + void M() + { + void [|F|]() + { + } + } +}", +@"class C +{ + void M() + { + void f() + { + } + } +}", + options: options.LocalFunctionNamesAreCamelCase); + } + + [Fact, Trait(Traits.Feature, Traits.Features.NamingStyle)] + public async Task TestCamelCaseLocalFunctions_MethodIsIgnored() + { + await TestMissingInRegularAndScriptAsync( +@"class C +{ + void [|M|]() + { + } +}", new TestParameters(options: options.LocalFunctionNamesAreCamelCase)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.NamingStyle)] + public async Task TestAsyncFunctions_AsyncMethod() + { + await TestInRegularAndScriptAsync( +@"class C +{ + async void [|M|]() + { + } +}", +@"class C +{ + async void MAsync() + { + } +}", + options: options.AsyncFunctionNamesEndWithAsync); + } + + [Fact, Trait(Traits.Feature, Traits.Features.NamingStyle)] + public async Task TestAsyncFunctions_AsyncLocalFunction() + { + await TestInRegularAndScriptAsync( +@"class C +{ + void M() + { + async void [|F|]() + { + } + } +}", +@"class C +{ + void M() + { + async void FAsync() + { + } + } +}", + options: options.AsyncFunctionNamesEndWithAsync); + } + + [Fact, Trait(Traits.Feature, Traits.Features.NamingStyle)] + public async Task TestAsyncFunctions_NonAsyncMethodIgnored() + { + await TestMissingInRegularAndScriptAsync( +@"class C +{ + void [|M|]() + { + async void F() + { + } + } +}", new TestParameters(options: options.AsyncFunctionNamesEndWithAsync)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.NamingStyle)] + public async Task TestAsyncFunctions_NonAsyncLocalFunctionIgnored() + { + await TestMissingInRegularAndScriptAsync( +@"class C +{ + async void M() + { + void [|F|]() + { + } + } +}", new TestParameters(options: options.AsyncFunctionNamesEndWithAsync)); + } + [Fact, Trait(Traits.Feature, Traits.Features.NamingStyle)] public async Task TestPascalCaseMethod_InInterfaceWithImplicitImplementation() { diff --git a/src/EditorFeatures/TestUtilities/Completion/AbstractCompletionProviderTests.cs b/src/EditorFeatures/TestUtilities/Completion/AbstractCompletionProviderTests.cs index 2f6b2eb868ce0..b4d83d69ee6d8 100644 --- a/src/EditorFeatures/TestUtilities/Completion/AbstractCompletionProviderTests.cs +++ b/src/EditorFeatures/TestUtilities/Completion/AbstractCompletionProviderTests.cs @@ -817,5 +817,21 @@ protected async Task VerifyCommitCharactersAsync(string initialMarkup, string te } } } + + protected async Task> GetCompletionItemsAsync( + string markup, SourceCodeKind sourceCodeKind, bool usePreviousCharAsTrigger = false) + { + MarkupTestFile.GetPosition(markup.NormalizeLineEndings(), out var code, out int position); + var document = WorkspaceFixture.UpdateDocument(code, sourceCodeKind); + + var trigger = usePreviousCharAsTrigger + ? CompletionTrigger.CreateInsertionTrigger(insertedCharacter: code.ElementAt(position - 1)) + : CompletionTrigger.Invoke; + + var completionService = GetCompletionService(document.Project.Solution.Workspace); + var completionList = await GetCompletionListAsync(completionService, document, position, trigger); + + return completionList == null ? ImmutableArray.Empty : completionList.Items; + } } } diff --git a/src/EditorFeatures/TestUtilities/Diagnostics/NamingStyles/NamingStylesTestOptionSets.cs b/src/EditorFeatures/TestUtilities/Diagnostics/NamingStyles/NamingStylesTestOptionSets.cs index 781a496f46543..38654e194e18f 100644 --- a/src/EditorFeatures/TestUtilities/Diagnostics/NamingStyles/NamingStylesTestOptionSets.cs +++ b/src/EditorFeatures/TestUtilities/Diagnostics/NamingStyles/NamingStylesTestOptionSets.cs @@ -31,6 +31,9 @@ public NamingStylesTestOptionSets(string languageName) public IDictionary LocalNamesAreCamelCase => Options(new OptionKey(SimplificationOptions.NamingPreferences, languageName), LocalNamesAreCamelCaseOption()); + public IDictionary LocalFunctionNamesAreCamelCase => + Options(new OptionKey(SimplificationOptions.NamingPreferences, languageName), LocalFunctionNamesAreCamelCaseOption()); + public IDictionary PropertyNamesArePascalCase => Options(new OptionKey(SimplificationOptions.NamingPreferences, languageName), PropertyNamesArePascalCaseOption()); @@ -43,6 +46,9 @@ public NamingStylesTestOptionSets(string languageName) public IDictionary LocalsAreCamelCaseConstantsAreUpperCase => Options(new OptionKey(SimplificationOptions.NamingPreferences, languageName), LocalsAreCamelCaseConstantsAreUpperCaseOption()); + public IDictionary AsyncFunctionNamesEndWithAsync => + Options(new OptionKey(SimplificationOptions.NamingPreferences, languageName), AsyncFunctionNamesEndWithAsyncOption()); + private static IDictionary Options(OptionKey option, object value) { return new Dictionary @@ -86,7 +92,7 @@ private static NamingStylePreferences MethodNamesArePascalCaseOption() var symbolSpecification = new SymbolSpecification( null, "Name", - ImmutableArray.Create(new SymbolSpecification.SymbolKindOrTypeKind(SymbolKind.Method)), + ImmutableArray.Create(new SymbolSpecification.SymbolKindOrTypeKind(MethodKind.Ordinary)), ImmutableArray.Empty, ImmutableArray.Empty); @@ -176,6 +182,38 @@ private static NamingStylePreferences LocalNamesAreCamelCaseOption() return info; } + private static NamingStylePreferences LocalFunctionNamesAreCamelCaseOption() + { + var symbolSpecification = new SymbolSpecification( + null, + "Name", + ImmutableArray.Create(new SymbolSpecification.SymbolKindOrTypeKind(MethodKind.LocalFunction)), + ImmutableArray.Empty, + ImmutableArray.Empty); + + var namingStyle = new NamingStyle( + Guid.NewGuid(), + capitalizationScheme: Capitalization.CamelCase, + name: "Name", + prefix: "", + suffix: "", + wordSeparator: ""); + + var namingRule = new SerializableNamingRule() + { + SymbolSpecificationID = symbolSpecification.ID, + NamingStyleID = namingStyle.ID, + EnforcementLevel = DiagnosticSeverity.Error + }; + + var info = new NamingStylePreferences( + ImmutableArray.Create(symbolSpecification), + ImmutableArray.Create(namingStyle), + ImmutableArray.Create(namingRule)); + + return info; + } + private static NamingStylePreferences PropertyNamesArePascalCaseOption() { var symbolSpecification = new SymbolSpecification( @@ -327,5 +365,39 @@ private static NamingStylePreferences LocalsAreCamelCaseConstantsAreUpperCaseOpt return info; } + + private static NamingStylePreferences AsyncFunctionNamesEndWithAsyncOption() + { + var symbolSpecification = new SymbolSpecification( + null, + "Name", + ImmutableArray.Create( + new SymbolSpecification.SymbolKindOrTypeKind(MethodKind.Ordinary), + new SymbolSpecification.SymbolKindOrTypeKind(MethodKind.LocalFunction)), + ImmutableArray.Empty, + ImmutableArray.Create(new SymbolSpecification.ModifierKind(SymbolSpecification.ModifierKindEnum.IsAsync))); + + var namingStyle = new NamingStyle( + Guid.NewGuid(), + capitalizationScheme: Capitalization.PascalCase, + name: "Name", + prefix: "", + suffix: "Async", + wordSeparator: ""); + + var namingRule = new SerializableNamingRule() + { + SymbolSpecificationID = symbolSpecification.ID, + NamingStyleID = namingStyle.ID, + EnforcementLevel = DiagnosticSeverity.Error + }; + + var info = new NamingStylePreferences( + ImmutableArray.Create(symbolSpecification), + ImmutableArray.Create(namingStyle), + ImmutableArray.Create(namingRule)); + + return info; + } } } diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/DeclarationNameCompletionProvider.DeclarationInfo.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/DeclarationNameCompletionProvider.DeclarationInfo.cs index 65e5ae3820dc0..e037c152d7ce2 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/DeclarationNameCompletionProvider.DeclarationInfo.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/DeclarationNameCompletionProvider.DeclarationInfo.cs @@ -9,6 +9,7 @@ using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.Shared.Extensions; +using static Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles.SymbolSpecification; namespace Microsoft.CodeAnalysis.CSharp.Completion.Providers { @@ -16,10 +17,11 @@ internal partial class DeclarationNameCompletionProvider { internal struct NameDeclarationInfo { - private static readonly ImmutableArray s_parameterSyntaxKind = ImmutableArray.Create(SymbolKind.Parameter); + private static readonly ImmutableArray s_parameterSyntaxKind = + ImmutableArray.Create(new SymbolKindOrTypeKind(SymbolKind.Parameter)); public NameDeclarationInfo( - ImmutableArray possibleSymbolKinds, + ImmutableArray possibleSymbolKinds, Accessibility accessibility, DeclarationModifiers declarationModifiers, ITypeSymbol type, @@ -32,7 +34,7 @@ public NameDeclarationInfo( Alias = alias; } - public ImmutableArray PossibleSymbolKinds { get; } + public ImmutableArray PossibleSymbolKinds { get; } public DeclarationModifiers Modifiers { get; } public ITypeSymbol Type { get; } public IAliasSymbol Alias { get; } @@ -48,7 +50,9 @@ internal static async Task GetDeclarationInfo(Document docu if (IsTupleTypeElement(token, semanticModel, position, cancellationToken, out var result) || IsParameterDeclaration(token, semanticModel, position, cancellationToken, out result) || IsTypeParameterDeclaration(token, semanticModel, position, cancellationToken, out result) - || IsVariableDeclaration(token, semanticModel, position, cancellationToken, out result) + || IsLocalFunctionDeclaration(token, semanticModel, position, cancellationToken, out result) + || IsLocalVariableDeclaration(token, semanticModel, position, cancellationToken, out result) + || IsEmbeddedVariableDeclaration(token, semanticModel, position, cancellationToken, out result) || IsForEachVariableDeclaration(token, semanticModel, position, cancellationToken, out result) || IsIncompleteMemberDeclaration(token, semanticModel, position, cancellationToken, out result) || IsFieldDeclaration(token, semanticModel, position, cancellationToken, out result) @@ -74,7 +78,7 @@ private static bool IsTupleTypeElement( semanticModel, tupleElement => tupleElement.Type, _ => default(SyntaxTokenList), - _ => ImmutableArray.Create(SymbolKind.Local), cancellationToken); + _ => ImmutableArray.Create(new SymbolKindOrTypeKind(SymbolKind.Local)), cancellationToken); return result.Type != null; } @@ -96,7 +100,7 @@ private static bool IsTupleLiteralElement( semanticModel, GetNodeDenotingTheTypeOfTupleArgument, _ => default(SyntaxTokenList), - _ => ImmutableArray.Create(SymbolKind.Local), cancellationToken); + _ => ImmutableArray.Create(new SymbolKindOrTypeKind(SymbolKind.Local)), cancellationToken); return result.Type != null; } @@ -127,7 +131,7 @@ private static bool IsPossibleOutVariableDeclaration(SyntaxToken token, Semantic if (type != null) { result = new NameDeclarationInfo( - ImmutableArray.Create(SymbolKind.Local), + ImmutableArray.Create(new SymbolKindOrTypeKind(SymbolKind.Local)), Accessibility.NotApplicable, new DeclarationModifiers(), type, @@ -147,7 +151,9 @@ private static bool IsPossibleVariableOrLocalMethodDeclaration( token, semanticModel, e => e.Expression, _ => default, - _ => ImmutableArray.Create(SymbolKind.Local), + _ => ImmutableArray.Create( + new SymbolKindOrTypeKind(SymbolKind.Local), + new SymbolKindOrTypeKind(MethodKind.LocalFunction)), cancellationToken); return result.Type != null; } @@ -160,7 +166,7 @@ private static bool IsPropertyDeclaration(SyntaxToken token, SemanticModel seman semanticModel, m => m.Type, m => m.Modifiers, - GetPossibleDeclarations, + GetPossibleMemberDeclarations, cancellationToken); return result.Type != null; @@ -174,7 +180,7 @@ private static bool IsMethodDeclaration(SyntaxToken token, SemanticModel semanti semanticModel, m => m.ReturnType, m => m.Modifiers, - GetPossibleDeclarations, + GetPossibleMemberDeclarations, cancellationToken); return result.Type != null; @@ -184,7 +190,7 @@ private static NameDeclarationInfo IsFollowingTypeOrComma(SyntaxTok SemanticModel semanticModel, Func typeSyntaxGetter, Func modifierGetter, - Func> possibleDeclarationComputer, + Func> possibleDeclarationComputer, CancellationToken cancellationToken) where TSyntaxNode : SyntaxNode { if (!IsPossibleTypeToken(token) && !token.IsKind(SyntaxKind.CommaToken)) @@ -237,7 +243,7 @@ private static NameDeclarationInfo IsLastTokenOfType( SemanticModel semanticModel, Func typeSyntaxGetter, Func modifierGetter, - Func> possibleDeclarationComputer, + Func> possibleDeclarationComputer, CancellationToken cancellationToken) where TSyntaxNode : SyntaxNode { if (!IsPossibleTypeToken(token)) @@ -273,7 +279,7 @@ private static bool IsFieldDeclaration(SyntaxToken token, SemanticModel semantic result = IsFollowingTypeOrComma(token, semanticModel, v => v.Type, v => v.Parent is FieldDeclarationSyntax f ? f.Modifiers : default(SyntaxTokenList?), - GetPossibleDeclarations, + GetPossibleMemberDeclarations, cancellationToken); return result.Type != null; } @@ -284,26 +290,54 @@ private static bool IsIncompleteMemberDeclaration(SyntaxToken token, SemanticMod result = IsLastTokenOfType(token, semanticModel, i => i.Type, i => i.Modifiers, - GetPossibleDeclarations, + GetPossibleMemberDeclarations, cancellationToken); return result.Type != null; } - private static bool IsVariableDeclaration(SyntaxToken token, SemanticModel semanticModel, + private static bool IsLocalFunctionDeclaration(SyntaxToken token, SemanticModel semanticModel, int position, CancellationToken cancellationToken, out NameDeclarationInfo result) { + result = IsLastTokenOfType(token, semanticModel, + typeSyntaxGetter: f => f.ReturnType, + modifierGetter: f => f.Modifiers, + possibleDeclarationComputer: GetPossibleLocalDeclarations, + cancellationToken); + return result.Type != null; + } + + private static bool IsLocalVariableDeclaration(SyntaxToken token, SemanticModel semanticModel, + int position, CancellationToken cancellationToken, out NameDeclarationInfo result) + { + // If we only have a type, this can still end up being a local function (depending on the modifiers). + var possibleDeclarationComputer = token.IsKind(SyntaxKind.CommaToken) + ? (Func>) + (_ => ImmutableArray.Create(new SymbolKindOrTypeKind(SymbolKind.Local))) + : GetPossibleLocalDeclarations; + result = IsFollowingTypeOrComma(token, semanticModel, typeSyntaxGetter: v => v.Type, - modifierGetter: v => - v.Parent is LocalDeclarationStatementSyntax localDeclaration ? localDeclaration.Modifiers : - v.Parent is UsingStatementSyntax ? default(SyntaxTokenList) : - v.Parent is ForStatementSyntax ? default(SyntaxTokenList) : - default(SyntaxTokenList?), // Return null to bail out. - possibleDeclarationComputer: d => ImmutableArray.Create(SymbolKind.Local), + modifierGetter: v => v.Parent is LocalDeclarationStatementSyntax localDeclaration + ? localDeclaration.Modifiers + : default(SyntaxTokenList?), // Return null to bail out. + possibleDeclarationComputer, cancellationToken); return result.Type != null; } + private static bool IsEmbeddedVariableDeclaration(SyntaxToken token, SemanticModel semanticModel, + int position, CancellationToken cancellationToken, out NameDeclarationInfo result) + { + result = IsFollowingTypeOrComma(token, semanticModel, + typeSyntaxGetter: v => v.Type, + modifierGetter: v => v.Parent is UsingStatementSyntax || v.Parent is ForStatementSyntax + ? default(SyntaxTokenList) + : default(SyntaxTokenList?), // Return null to bail out. + possibleDeclarationComputer: d => ImmutableArray.Create(new SymbolKindOrTypeKind(SymbolKind.Local)), + cancellationToken); + return result.Type != null; + } + private static bool IsForEachVariableDeclaration(SyntaxToken token, SemanticModel semanticModel, int position, CancellationToken cancellationToken, out NameDeclarationInfo result) { @@ -315,7 +349,7 @@ private static bool IsForEachVariableDeclaration(SyntaxToken token, SemanticMode f is ForEachVariableStatementSyntax forEachVariableStatement ? forEachVariableStatement.Variable : null, // Return null to bail out. modifierGetter: f => default, - possibleDeclarationComputer: d => ImmutableArray.Create(SymbolKind.Local), + possibleDeclarationComputer: d => ImmutableArray.Create(new SymbolKindOrTypeKind(SymbolKind.Local)), cancellationToken); return result.Type != null; } @@ -327,7 +361,7 @@ private static bool IsTypeParameterDeclaration(SyntaxToken token, SemanticModel token.Parent.IsKind(SyntaxKind.TypeParameterList)) { result = new NameDeclarationInfo( - ImmutableArray.Create(SymbolKind.TypeParameter), + ImmutableArray.Create(new SymbolKindOrTypeKind(SymbolKind.TypeParameter)), Accessibility.NotApplicable, new DeclarationModifiers(), type: null, @@ -394,29 +428,45 @@ private static bool IsPossibleTypeToken(SyntaxToken token) => SyntaxKind.CloseBracketToken) || token.Parent.IsKind(SyntaxKind.PredefinedType); - private static ImmutableArray GetPossibleDeclarations(DeclarationModifiers modifiers) + private static ImmutableArray GetPossibleMemberDeclarations(DeclarationModifiers modifiers) { if (modifiers.IsConst || modifiers.IsReadOnly) { - return ImmutableArray.Create(SymbolKind.Field); + return ImmutableArray.Create(new SymbolKindOrTypeKind(SymbolKind.Field)); } - var possibleTypes = ImmutableArray.Create(SymbolKind.Field, SymbolKind.Method, SymbolKind.Property); + var possibleTypes = ImmutableArray.Create( + new SymbolKindOrTypeKind(SymbolKind.Field), + new SymbolKindOrTypeKind(SymbolKind.Property), + new SymbolKindOrTypeKind(MethodKind.Ordinary)); + if (modifiers.IsAbstract || modifiers.IsVirtual || modifiers.IsSealed || modifiers.IsOverride) { - possibleTypes = possibleTypes.Remove(SymbolKind.Field); + possibleTypes = possibleTypes.Remove(new SymbolKindOrTypeKind(SymbolKind.Field)); } if (modifiers.IsAsync || modifiers.IsPartial) { // Fields and properties cannot be async or partial. - possibleTypes = possibleTypes.Remove(SymbolKind.Property); - possibleTypes = possibleTypes.Remove(SymbolKind.Field); + possibleTypes = possibleTypes.Remove(new SymbolKindOrTypeKind(SymbolKind.Field)); + possibleTypes = possibleTypes.Remove(new SymbolKindOrTypeKind(SymbolKind.Property)); } return possibleTypes; } + private static ImmutableArray GetPossibleLocalDeclarations(DeclarationModifiers modifiers) + { + return + modifiers.IsConst + ? ImmutableArray.Create(new SymbolKindOrTypeKind(SymbolKind.Local)) : + modifiers.IsAsync || modifiers.IsUnsafe + ? ImmutableArray.Create(new SymbolKindOrTypeKind(MethodKind.LocalFunction)) : + ImmutableArray.Create( + new SymbolKindOrTypeKind(SymbolKind.Local), + new SymbolKindOrTypeKind(MethodKind.LocalFunction)); + } + private static DeclarationModifiers GetDeclarationModifiers(SyntaxTokenList modifiers) { var declarationModifiers = new DeclarationModifiers(); diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/DeclarationNameCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/DeclarationNameCompletionProvider.cs index a3817f45af929..1ae1bde92acc1 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/DeclarationNameCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/DeclarationNameCompletionProvider.cs @@ -221,9 +221,15 @@ private Glyph GetGlyph(SymbolKind kind, Accessibility declaredAccessibility) var namingStyleOptions = options.GetOption(SimplificationOptions.NamingPreferences); var rules = namingStyleOptions.CreateRules().NamingRules.Concat(s_BuiltInRules); var result = new Dictionary(); - foreach (var symbolKind in declarationInfo.PossibleSymbolKinds) + foreach (var kind in declarationInfo.PossibleSymbolKinds) { - var kind = new SymbolKindOrTypeKind(symbolKind); + // There's no special glyph for local functions. + // We don't need to differentiate them at this point. + var symbolKind = + kind.SymbolKind.HasValue ? kind.SymbolKind.Value : + kind.MethodKind.HasValue ? SymbolKind.Method : + throw ExceptionUtilities.Unreachable; + var modifiers = declarationInfo.Modifiers; foreach (var rule in rules) { diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/DeclarationNameCompletionProvider_BuiltInStyles.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/DeclarationNameCompletionProvider_BuiltInStyles.cs index 2c21388d9032b..4f30effdd734f 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/DeclarationNameCompletionProvider_BuiltInStyles.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/DeclarationNameCompletionProvider_BuiltInStyles.cs @@ -23,7 +23,7 @@ static DeclarationNameCompletionProvider() private static NamingRule CreateGetAsyncRule() { - var kinds = ImmutableArray.Create(new SymbolKindOrTypeKind(SymbolKind.Method)); + var kinds = ImmutableArray.Create(new SymbolKindOrTypeKind(MethodKind.Ordinary)); var modifiers = ImmutableArray.Create(new ModifierKind(ModifierKindEnum.IsAsync)); return new NamingRule( new SymbolSpecification(Guid.NewGuid(), "endswithasync", kinds, ImmutableArray.Create(), modifiers), @@ -43,7 +43,7 @@ private static NamingRule CreateCamelCaseFieldsAndParametersRule() private static NamingRule CreateEndWithAsyncRule() { - var kinds = ImmutableArray.Create(new SymbolKindOrTypeKind(SymbolKind.Method)); + var kinds = ImmutableArray.Create(new SymbolKindOrTypeKind(MethodKind.Ordinary)); var modifiers = ImmutableArray.Create(new ModifierKind(ModifierKindEnum.IsAsync)); return new NamingRule( new SymbolSpecification(Guid.NewGuid(), "endswithasynct", kinds, ImmutableArray.Create(), modifiers), @@ -53,7 +53,7 @@ private static NamingRule CreateEndWithAsyncRule() private static NamingRule CreateMethodStartsWithGetRule() { - var kinds = ImmutableArray.Create(new SymbolKindOrTypeKind(SymbolKind.Method)); + var kinds = ImmutableArray.Create(new SymbolKindOrTypeKind(MethodKind.Ordinary)); var modifiers = ImmutableArray.Create(); return new NamingRule( new SymbolSpecification(Guid.NewGuid(), "startswithget", kinds, ImmutableArray.Create(), modifiers), diff --git a/src/Features/CSharp/Portable/Diagnostics/Analyzers/CSharpNamingStyleDiagnosticAnalyzer.cs b/src/Features/CSharp/Portable/Diagnostics/Analyzers/CSharpNamingStyleDiagnosticAnalyzer.cs index 1c28e966c65b1..b85c1914dead8 100644 --- a/src/Features/CSharp/Portable/Diagnostics/Analyzers/CSharpNamingStyleDiagnosticAnalyzer.cs +++ b/src/Features/CSharp/Portable/Diagnostics/Analyzers/CSharpNamingStyleDiagnosticAnalyzer.cs @@ -14,6 +14,7 @@ internal sealed class CSharpNamingStyleDiagnosticAnalyzer : NamingStyleDiagnosti SyntaxKind.VariableDeclarator, SyntaxKind.ForEachStatement, SyntaxKind.CatchDeclaration, - SyntaxKind.SingleVariableDesignation); + SyntaxKind.SingleVariableDesignation, + SyntaxKind.LocalFunctionStatement); } } diff --git a/src/VisualStudio/Core/Def/Implementation/Options/RoamingVisualStudioProfileOptionPersister.cs b/src/VisualStudio/Core/Def/Implementation/Options/RoamingVisualStudioProfileOptionPersister.cs index 6223aaa010a6c..a403e6897a751 100644 --- a/src/VisualStudio/Core/Def/Implementation/Options/RoamingVisualStudioProfileOptionPersister.cs +++ b/src/VisualStudio/Core/Def/Implementation/Options/RoamingVisualStudioProfileOptionPersister.cs @@ -80,6 +80,31 @@ private System.Threading.Tasks.Task OnSettingChangedAsync(object sender, Propert return SpecializedTasks.EmptyTask; } + private object GetFirstOrDefaultValue(OptionKey optionKey, IEnumerable roamingSerializations) + { + // There can be more than 1 roaming location in the order of their priority. + // When fetching a value, we iterate all of them until we find the first one that exists. + // When persisting a value, we always use the first location. + // This functionality exists for breaking changes to persistence of some options. In such a case, there + // will be a new location added to the beginning with a new name. When fetching a value, we might find the old + // location (and can upgrade the value accordingly) but we only write to the new location so that + // we don't interfere with older versions. This will essentially "fork" the user's options at the time of upgrade. + + foreach (var roamingSerialization in roamingSerializations) + { + var storageKey = roamingSerialization.GetKeyNameForLanguage(optionKey.Language); + + RecordObservedValueToWatchForChanges(optionKey, storageKey); + + if (_settingManager.TryGetValue(storageKey, out object value) == GetValueResult.Success) + { + return value; + } + } + + return optionKey.Option.DefaultValue; + } + public bool TryFetch(OptionKey optionKey, out object value) { if (_settingManager == null) @@ -90,19 +115,15 @@ public bool TryFetch(OptionKey optionKey, out object value) } // Do we roam this at all? - var roamingSerialization = optionKey.Option.StorageLocations.OfType().SingleOrDefault(); + var roamingSerializations = optionKey.Option.StorageLocations.OfType(); - if (roamingSerialization == null) + if (!roamingSerializations.Any()) { value = null; return false; } - var storageKey = roamingSerialization.GetKeyNameForLanguage(optionKey.Language); - - RecordObservedValueToWatchForChanges(optionKey, storageKey); - - value = _settingManager.GetValueOrDefault(storageKey, optionKey.Option.DefaultValue); + value = GetFirstOrDefaultValue(optionKey, roamingSerializations); // VS's ISettingsManager has some quirks around storing enums. Specifically, // it *can* persist and retrieve enums, but only if you properly call @@ -222,7 +243,7 @@ public bool TryPersist(OptionKey optionKey, object value) } // Do we roam this at all? - var roamingSerialization = optionKey.Option.StorageLocations.OfType().SingleOrDefault(); + var roamingSerialization = optionKey.Option.StorageLocations.OfType().FirstOrDefault(); if (roamingSerialization == null) { diff --git a/src/VisualStudio/Core/Impl/Options/Style/NamingPreferences/NamingStyleOptionPageViewModel.cs b/src/VisualStudio/Core/Impl/Options/Style/NamingPreferences/NamingStyleOptionPageViewModel.cs index 77142e0081ab6..8dbf130262b5e 100644 --- a/src/VisualStudio/Core/Impl/Options/Style/NamingPreferences/NamingStyleOptionPageViewModel.cs +++ b/src/VisualStudio/Core/Impl/Options/Style/NamingPreferences/NamingStyleOptionPageViewModel.cs @@ -92,7 +92,7 @@ internal void UpdateSpecificationList(ManageSymbolSpecificationsDialogViewModel var symbolSpecifications = viewModel.Items.Cast().Select(n => new SymbolSpecification( n.ID, n.ItemName, - n.SymbolKindList.Where(s => s.IsChecked).Select(k => k.CreateSymbolKindOrTypeKind()).ToImmutableArray(), + n.SymbolKindList.Where(s => s.IsChecked).Select(k => k.CreateSymbolOrTypeOrMethodKind()).ToImmutableArray(), n.AccessibilityList.Where(s => s.IsChecked).Select(a => a._accessibility).ToImmutableArray(), n.ModifierList.Where(s => s.IsChecked).Select(m => new SymbolSpecification.ModifierKind(m._modifier)).ToImmutableArray())); diff --git a/src/VisualStudio/Core/Impl/Options/Style/NamingPreferences/SymbolSpecification/SymbolSpecificationViewModel.cs b/src/VisualStudio/Core/Impl/Options/Style/NamingPreferences/SymbolSpecification/SymbolSpecificationViewModel.cs index 7c3ef60b550ac..4070c17f9adcc 100644 --- a/src/VisualStudio/Core/Impl/Options/Style/NamingPreferences/SymbolSpecification/SymbolSpecificationViewModel.cs +++ b/src/VisualStudio/Core/Impl/Options/Style/NamingPreferences/SymbolSpecification/SymbolSpecificationViewModel.cs @@ -9,6 +9,7 @@ using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Notification; using Microsoft.VisualStudio.LanguageServices.Implementation.Utilities; +using Roslyn.Utilities; using static Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles.SymbolSpecification; namespace Microsoft.VisualStudio.LanguageServices.Implementation.Options.Style.NamingPreferences @@ -48,7 +49,8 @@ public SymbolSpecificationViewModel(string languageName, SymbolSpecification spe new SymbolKindViewModel(TypeKind.Interface, "interface", specification), new SymbolKindViewModel(TypeKind.Enum, "enum", specification), new SymbolKindViewModel(SymbolKind.Property, "property", specification), - new SymbolKindViewModel(SymbolKind.Method, "method", specification), + new SymbolKindViewModel(MethodKind.Ordinary, "method", specification), + new SymbolKindViewModel(MethodKind.LocalFunction, "local function", specification), new SymbolKindViewModel(SymbolKind.Field, "field", specification), new SymbolKindViewModel(SymbolKind.Event, "event", specification), new SymbolKindViewModel(TypeKind.Delegate, "delegate", specification), @@ -127,7 +129,7 @@ internal SymbolSpecification GetSymbolSpecification() return new SymbolSpecification( ID, ItemName, - SymbolKindList.Where(s => s.IsChecked).Select(s => s.CreateSymbolKindOrTypeKind()).ToImmutableArray(), + SymbolKindList.Where(s => s.IsChecked).Select(s => s.CreateSymbolOrTypeOrMethodKind()).ToImmutableArray(), AccessibilityList.Where(a => a.IsChecked).Select(a => a._accessibility).ToImmutableArray(), ModifierList.Where(m => m.IsChecked).Select(m => new ModifierKind(m._modifier)).ToImmutableArray()); } @@ -165,6 +167,7 @@ public bool IsChecked private readonly SymbolKind? _symbolKind; private readonly TypeKind? _typeKind; + private readonly MethodKind? _methodKind; private bool _isChecked; @@ -182,16 +185,20 @@ public SymbolKindViewModel(TypeKind typeKind, string name, SymbolSpecification s IsChecked = specification.ApplicableSymbolKindList.Any(k => k.TypeKind == typeKind); } - internal SymbolKindOrTypeKind CreateSymbolKindOrTypeKind() + public SymbolKindViewModel(MethodKind methodKind, string name, SymbolSpecification specification) { - if (_symbolKind.HasValue) - { - return new SymbolKindOrTypeKind(_symbolKind.Value); - } - else - { - return new SymbolKindOrTypeKind(_typeKind.Value); - } + _methodKind = methodKind; + Name = name; + IsChecked = specification.ApplicableSymbolKindList.Any(k => k.MethodKind == methodKind); + } + + internal SymbolKindOrTypeKind CreateSymbolOrTypeOrMethodKind() + { + return + _symbolKind.HasValue ? new SymbolKindOrTypeKind(_symbolKind.Value) : + _typeKind.HasValue ? new SymbolKindOrTypeKind(_typeKind.Value) : + _methodKind.HasValue ? new SymbolKindOrTypeKind(_methodKind.Value) : + throw ExceptionUtilities.Unreachable; } } diff --git a/src/Workspaces/Core/Portable/NamingStyles/EditorConfig/EditorConfigNamingStyleParser_SymbolSpec.cs b/src/Workspaces/Core/Portable/NamingStyles/EditorConfig/EditorConfigNamingStyleParser_SymbolSpec.cs index 0d8176d9d9ca2..85bccee13b2de 100644 --- a/src/Workspaces/Core/Portable/NamingStyles/EditorConfig/EditorConfigNamingStyleParser_SymbolSpec.cs +++ b/src/Workspaces/Core/Portable/NamingStyles/EditorConfig/EditorConfigNamingStyleParser_SymbolSpec.cs @@ -67,7 +67,8 @@ private static ImmutableArray GetSymbolsApplicableKinds( private static readonly SymbolKindOrTypeKind _interface = new SymbolKindOrTypeKind(TypeKind.Interface); private static readonly SymbolKindOrTypeKind _enum = new SymbolKindOrTypeKind(TypeKind.Enum); private static readonly SymbolKindOrTypeKind _property = new SymbolKindOrTypeKind(SymbolKind.Property); - private static readonly SymbolKindOrTypeKind _method = new SymbolKindOrTypeKind(SymbolKind.Method); + private static readonly SymbolKindOrTypeKind _method = new SymbolKindOrTypeKind(MethodKind.Ordinary); + private static readonly SymbolKindOrTypeKind _localFunction = new SymbolKindOrTypeKind(MethodKind.LocalFunction); private static readonly SymbolKindOrTypeKind _field = new SymbolKindOrTypeKind(SymbolKind.Field); private static readonly SymbolKindOrTypeKind _event = new SymbolKindOrTypeKind(SymbolKind.Event); private static readonly SymbolKindOrTypeKind _delegate = new SymbolKindOrTypeKind(TypeKind.Delegate); @@ -81,6 +82,7 @@ private static ImmutableArray GetSymbolsApplicableKinds( _enum, _property, _method, + _localFunction, _field, _event, _delegate, @@ -122,6 +124,9 @@ private static ImmutableArray ParseSymbolKindList(string s case "method": builder.Add(_method); break; + case "local_function": + builder.Add(_localFunction); + break; case "field": builder.Add(_field); break; diff --git a/src/Workspaces/Core/Portable/NamingStyles/NamingStyleRules.cs b/src/Workspaces/Core/Portable/NamingStyles/NamingStyleRules.cs index 43f876c27674f..5aeda195c2966 100644 --- a/src/Workspaces/Core/Portable/NamingStyles/NamingStyleRules.cs +++ b/src/Workspaces/Core/Portable/NamingStyles/NamingStyleRules.cs @@ -47,14 +47,15 @@ private bool IsSymbolNameAnalyzable(ISymbol symbol) return false; } - if (symbol.Kind == SymbolKind.Method) + if (symbol is IMethodSymbol method) { - return ((IMethodSymbol)symbol).MethodKind == MethodKind.Ordinary; + return method.MethodKind == MethodKind.Ordinary || + method.MethodKind == MethodKind.LocalFunction; } - if (symbol.Kind == SymbolKind.Property) + if (symbol is IPropertySymbol property) { - return !((IPropertySymbol)symbol).IsIndexer; + return !property.IsIndexer; } return true; diff --git a/src/Workspaces/Core/Portable/NamingStyles/Serialization/NamingStylePreferences.cs b/src/Workspaces/Core/Portable/NamingStyles/Serialization/NamingStylePreferences.cs index addf4ac7f98a4..b2ded70b0d239 100644 --- a/src/Workspaces/Core/Portable/NamingStyles/Serialization/NamingStylePreferences.cs +++ b/src/Workspaces/Core/Portable/NamingStyles/Serialization/NamingStylePreferences.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Immutable; +using System.Diagnostics; using System.Linq; using System.Xml.Linq; using Microsoft.CodeAnalysis.NamingStyles; @@ -16,7 +17,7 @@ namespace Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles /// internal class NamingStylePreferences : IEquatable { - private const int s_serializationVersion = 4; + private const int s_serializationVersion = 5; public readonly ImmutableArray SymbolSpecifications; public readonly ImmutableArray NamingStyles; @@ -62,11 +63,7 @@ internal XElement CreateXElement() internal static NamingStylePreferences FromXElement(XElement element) { - var serializationVersion = int.Parse(element.Attribute("SerializationVersion").Value); - if (serializationVersion != s_serializationVersion) - { - element = XElement.Parse(DefaultNamingPreferencesString); - } + element = GetUpgradedSerializationIfNecessary(element); return new NamingStylePreferences( element.Element(nameof(SymbolSpecifications)).Elements(nameof(SymbolSpecification)) @@ -108,7 +105,7 @@ public override int GetHashCode() => CreateXElement().ToString().GetHashCode(); private static readonly string _defaultNamingPreferencesString = $@" - + @@ -190,7 +187,7 @@ public override int GetHashCode() - Method + Ordinary Public @@ -199,7 +196,7 @@ public override int GetHashCode() - Method + Ordinary Private @@ -208,7 +205,7 @@ public override int GetHashCode() - Method + Ordinary Public @@ -223,7 +220,7 @@ public override int GetHashCode() - Method + Ordinary Public @@ -315,8 +312,8 @@ public override int GetHashCode() Property - Method Event + Ordinary Public @@ -339,5 +336,38 @@ public override int GetHashCode() "; + + private static XElement GetUpgradedSerializationIfNecessary(XElement rootElement) + { + var serializationVersion = int.Parse(rootElement.Attribute("SerializationVersion").Value); + + if (serializationVersion == 4) + { + UpgradeSerialization_4To5(rootElement = new XElement(rootElement)); + serializationVersion = 5; + } + + // Add future version checks here. If the version is off by more than 1, these upgrades will run in sequence. + // The next one should check serializationVersion == 5 and update it to 6. + // It is also important to create a new roaming location in SimplificationOptions.NamingPreferences + // so that we never store the new format in an older version. + Debug.Assert(s_serializationVersion == 5, "After increasing the serialization version, add an upgrade path here."); + + return serializationVersion == s_serializationVersion + ? rootElement + : XElement.Parse(DefaultNamingPreferencesString); + } + + private static void UpgradeSerialization_4To5(XElement rootElement) + { + var methodElements = rootElement + .Descendants() + .Where(e => e.Name.LocalName == "SymbolKind" && e.Value == "Method").ToList(); + + foreach (var element in methodElements) + { + element.ReplaceWith(XElement.Parse("Ordinary")); + } + } } } diff --git a/src/Workspaces/Core/Portable/NamingStyles/Serialization/SymbolSpecification.cs b/src/Workspaces/Core/Portable/NamingStyles/Serialization/SymbolSpecification.cs index 8611ee2e3efe5..dd40ff649f307 100644 --- a/src/Workspaces/Core/Portable/NamingStyles/Serialization/SymbolSpecification.cs +++ b/src/Workspaces/Core/Portable/NamingStyles/Serialization/SymbolSpecification.cs @@ -54,7 +54,8 @@ public static SymbolSpecification CreateDefaultSymbolSpecification() new SymbolKindOrTypeKind(TypeKind.Pointer), new SymbolKindOrTypeKind(TypeKind.TypeParameter), new SymbolKindOrTypeKind(SymbolKind.Property), - new SymbolKindOrTypeKind(SymbolKind.Method), + new SymbolKindOrTypeKind(MethodKind.Ordinary), + new SymbolKindOrTypeKind(MethodKind.LocalFunction), new SymbolKindOrTypeKind(SymbolKind.Field), new SymbolKindOrTypeKind(SymbolKind.Event), new SymbolKindOrTypeKind(SymbolKind.Parameter), @@ -252,6 +253,11 @@ private static ImmutableArray GetSymbolKindListFromXElemen applicableSymbolKindList.Add(SymbolKindOrTypeKind.AddTypeKindFromXElement(typeKindElement)); } + foreach (var methodKindElement in symbolKindListElement.Elements(nameof(MethodKind))) + { + applicableSymbolKindList.Add(SymbolKindOrTypeKind.AddMethodKindFromXElement(methodKindElement)); + } + return applicableSymbolKindList.ToImmutableAndFree(); } @@ -285,28 +291,40 @@ public struct SymbolKindOrTypeKind : IEquatable, ISymbolMa { public SymbolKind? SymbolKind { get; } public TypeKind? TypeKind { get; } + public MethodKind? MethodKind { get; } public SymbolKindOrTypeKind(SymbolKind symbolKind) : this() { SymbolKind = symbolKind; TypeKind = null; + MethodKind = null; } public SymbolKindOrTypeKind(TypeKind typeKind) : this() { SymbolKind = null; TypeKind = typeKind; + MethodKind = null; + } + + public SymbolKindOrTypeKind(MethodKind methodKind) : this() + { + SymbolKind = null; + TypeKind = null; + MethodKind = methodKind; } public bool MatchesSymbol(ISymbol symbol) - => SymbolKind.HasValue - ? symbol.IsKind(SymbolKind.Value) - : symbol is ITypeSymbol s && s.TypeKind == TypeKind.Value; + => SymbolKind.HasValue ? symbol.IsKind(SymbolKind.Value) : + TypeKind.HasValue ? symbol is ITypeSymbol type && type.TypeKind == TypeKind.Value : + MethodKind.HasValue ? symbol is IMethodSymbol method && method.MethodKind == MethodKind.Value : + throw ExceptionUtilities.Unreachable; internal XElement CreateXElement() - => SymbolKind.HasValue - ? new XElement(nameof(SymbolKind), SymbolKind) - : new XElement(nameof(TypeKind), TypeKind); + => SymbolKind.HasValue ? new XElement(nameof(SymbolKind), SymbolKind) : + TypeKind.HasValue ? new XElement(nameof(TypeKind), TypeKind) : + MethodKind.HasValue ? new XElement(nameof(MethodKind), MethodKind) : + throw ExceptionUtilities.Unreachable; internal static SymbolKindOrTypeKind AddSymbolKindFromXElement(XElement symbolKindElement) => new SymbolKindOrTypeKind((SymbolKind)Enum.Parse(typeof(SymbolKind), symbolKindElement.Value)); @@ -314,15 +332,21 @@ internal static SymbolKindOrTypeKind AddSymbolKindFromXElement(XElement symbolKi internal static SymbolKindOrTypeKind AddTypeKindFromXElement(XElement typeKindElement) => new SymbolKindOrTypeKind((TypeKind)Enum.Parse(typeof(TypeKind), typeKindElement.Value)); + internal static SymbolKindOrTypeKind AddMethodKindFromXElement(XElement methodKindElement) + => new SymbolKindOrTypeKind((MethodKind)Enum.Parse(typeof(MethodKind), methodKindElement.Value)); + public override bool Equals(object obj) => Equals((SymbolKindOrTypeKind)obj); public bool Equals(SymbolKindOrTypeKind other) - => this.SymbolKind == other.SymbolKind && this.TypeKind == other.TypeKind; + => this.SymbolKind == other.SymbolKind && this.TypeKind == other.TypeKind && this.MethodKind == other.MethodKind; public override int GetHashCode() - => Hash.Combine((int)this.SymbolKind.GetValueOrDefault(), - (int)this.TypeKind.GetValueOrDefault()); + => Hash.CombineValues(new[] { + (int)this.SymbolKind.GetValueOrDefault(), + (int)this.TypeKind.GetValueOrDefault(), + (int)this.MethodKind.GetValueOrDefault() + }); } public struct ModifierKind : ISymbolMatcher diff --git a/src/Workspaces/Core/Portable/Simplification/SimplificationOptions.cs b/src/Workspaces/Core/Portable/Simplification/SimplificationOptions.cs index fb8606040d4c4..11b8b35ac15fb 100644 --- a/src/Workspaces/Core/Portable/Simplification/SimplificationOptions.cs +++ b/src/Workspaces/Core/Portable/Simplification/SimplificationOptions.cs @@ -101,8 +101,10 @@ public static class SimplificationOptions /// and the level to which those rules should be enforced. /// internal static PerLanguageOption NamingPreferences { get; } = new PerLanguageOption(nameof(SimplificationOptions), nameof(NamingPreferences), defaultValue: NamingStylePreferences.Default, - storageLocations: new OptionStorageLocation[]{ + storageLocations: new OptionStorageLocation[] { new NamingStylePreferenceEditorConfigStorageLocation(), - new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.Specific.NamingPreferences")}); + new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.Specific.NamingPreferences5"), + new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.Specific.NamingPreferences") + }); } } diff --git a/src/Workspaces/CoreTest/CodeStyle/NamingStylePreferencesUpgradeTests.cs b/src/Workspaces/CoreTest/CodeStyle/NamingStylePreferencesUpgradeTests.cs new file mode 100644 index 0000000000000..726ecaeff03b7 --- /dev/null +++ b/src/Workspaces/CoreTest/CodeStyle/NamingStylePreferencesUpgradeTests.cs @@ -0,0 +1,177 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Xml.Linq; +using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles; +using Microsoft.CodeAnalysis.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.UnitTests.CodeStyle +{ + public class NamingStylePreferencesUpgradeTests + { + private static string ReserializePreferences(string serializedPreferences) + { + var preferences = NamingStylePreferences.FromXElement(XElement.Parse(serializedPreferences)); + return preferences.CreateXElement().ToString(); + } + + private static void AssertTrimmedEqual(string expected, string actual) + { + Assert.Equal(expected.Trim(), actual.Trim()); + } + + [Fact, Trait(Traits.Feature, Traits.Features.NamingStyle)] + public void TestPreserveDefaultPreferences() + { + AssertTrimmedEqual( + NamingStylePreferences.DefaultNamingPreferencesString, + ReserializePreferences(NamingStylePreferences.DefaultNamingPreferencesString)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.NamingStyle)] + public void TestCannotUpgrade3To5() + { + var serializedPreferences = @" + + + + + Method + + + + + + + Property + Method + + + + + + + + + + + + +"; + + AssertTrimmedEqual( + NamingStylePreferences.DefaultNamingPreferencesString, + ReserializePreferences(serializedPreferences)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.NamingStyle)] + public void TestUpgrade4To5() + { + var serializedPreferences = @" + + + + + Method + + + + + + + Property + Method + + + + + + + + + + + + +"; + + AssertTrimmedEqual( + serializedPreferences + .Replace("SerializationVersion=\"4\"", "SerializationVersion=\"5\"") + .Replace("Method", "Ordinary"), + ReserializePreferences(serializedPreferences)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.NamingStyle)] + public void TestPreserveLatestVersion5() + { + var serializedPreferences = @" + + + + + Ordinary + + + + + + + Property + Ordinary + + + + + + + + + + + + +"; + + AssertTrimmedEqual( + serializedPreferences, + ReserializePreferences(serializedPreferences)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.NamingStyle)] + public void TestCannotDowngradeHigherThanLatestVersion5() + { + var serializedPreferences = @" + + + + + Ordinary + + + + + + + Property + Ordinary + + + + + + + + + + + + +"; + + AssertTrimmedEqual( + NamingStylePreferences.DefaultNamingPreferencesString, + ReserializePreferences(serializedPreferences)); + } + } +}