Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added "propr" snippet for required properties #75339

Merged
merged 13 commits into from
Oct 8, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ protected override async Task<PropertyDeclarationSyntax> GenerateSnippetSyntaxAs
modifiers = SyntaxTokenList.Create(PublicKeyword);
}

modifiers = modifiers.AddRange(GetAdditionalPropertyModifiers(syntaxContext));

return SyntaxFactory.PropertyDeclaration(
attributeLists: default,
modifiers: modifiers,
Expand Down Expand Up @@ -87,4 +89,9 @@ protected override ImmutableArray<SnippetPlaceholder> GetPlaceHolderLocationsLis
var node = root.FindNode(TextSpan.FromBounds(position, position));
return node.GetAncestorOrThis<PropertyDeclarationSyntax>();
}

protected virtual SyntaxToken[] GetAdditionalPropertyModifiers(CSharpSyntaxContext? syntaxContext)
victor-pogor marked this conversation as resolved.
Show resolved Hide resolved
{
return [];
victor-pogor marked this conversation as resolved.
Show resolved Hide resolved
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// 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.Composition;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Snippets;
using Microsoft.CodeAnalysis.Snippets.SnippetProviders;
using Roslyn.Utilities;
using static Microsoft.CodeAnalysis.CSharp.CSharpSyntaxTokens;

namespace Microsoft.CodeAnalysis.CSharp.Snippets;

[ExportSnippetProvider(nameof(ISnippetProvider), LanguageNames.CSharp), Shared]
[method: ImportingConstructor]
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
internal sealed class CSharpProprSnippetProvider() : AbstractCSharpAutoPropertySnippetProvider
{
public override string Identifier => CommonSnippetIdentifiers.RequiredProperty;

public override string Description => FeaturesResources.required_property;

Check failure on line 28 in src/Features/CSharp/Portable/Snippets/CSharpProprSnippetProvider.cs

View check run for this annotation

Azure Pipelines / roslyn-CI (Correctness Correctness_Analyzers)

src/Features/CSharp/Portable/Snippets/CSharpProprSnippetProvider.cs#L28

src/Features/CSharp/Portable/Snippets/CSharpProprSnippetProvider.cs(28,1): error IDE0055: (NETCORE_ENGINEERING_TELEMETRY=Build) Fix formatting (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0055)

Check failure on line 28 in src/Features/CSharp/Portable/Snippets/CSharpProprSnippetProvider.cs

View check run for this annotation

Azure Pipelines / roslyn-CI

src/Features/CSharp/Portable/Snippets/CSharpProprSnippetProvider.cs#L28

src/Features/CSharp/Portable/Snippets/CSharpProprSnippetProvider.cs(28,1): error IDE0055: (NETCORE_ENGINEERING_TELEMETRY=Build) Fix formatting (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0055)
protected override bool IsValidSnippetLocationCore(SnippetContext context, CancellationToken cancellationToken)
{
if(!base.IsValidSnippetLocationCore(context, cancellationToken))

Check failure on line 31 in src/Features/CSharp/Portable/Snippets/CSharpProprSnippetProvider.cs

View check run for this annotation

Azure Pipelines / roslyn-CI (Correctness Correctness_Analyzers)

src/Features/CSharp/Portable/Snippets/CSharpProprSnippetProvider.cs#L31

src/Features/CSharp/Portable/Snippets/CSharpProprSnippetProvider.cs(31,11): error IDE0055: (NETCORE_ENGINEERING_TELEMETRY=Build) Fix formatting (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0055)

Check failure on line 31 in src/Features/CSharp/Portable/Snippets/CSharpProprSnippetProvider.cs

View check run for this annotation

Azure Pipelines / roslyn-CI

src/Features/CSharp/Portable/Snippets/CSharpProprSnippetProvider.cs#L31

src/Features/CSharp/Portable/Snippets/CSharpProprSnippetProvider.cs(31,11): error IDE0055: (NETCORE_ENGINEERING_TELEMETRY=Build) Fix formatting (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0055)
return false;

Check failure on line 33 in src/Features/CSharp/Portable/Snippets/CSharpProprSnippetProvider.cs

View check run for this annotation

Azure Pipelines / roslyn-CI (Correctness Correctness_Analyzers)

src/Features/CSharp/Portable/Snippets/CSharpProprSnippetProvider.cs#L33

src/Features/CSharp/Portable/Snippets/CSharpProprSnippetProvider.cs(33,1): error IDE0055: (NETCORE_ENGINEERING_TELEMETRY=Build) Fix formatting (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0055)

Check failure on line 33 in src/Features/CSharp/Portable/Snippets/CSharpProprSnippetProvider.cs

View check run for this annotation

Azure Pipelines / roslyn-CI

src/Features/CSharp/Portable/Snippets/CSharpProprSnippetProvider.cs#L33

src/Features/CSharp/Portable/Snippets/CSharpProprSnippetProvider.cs(33,1): error IDE0055: (NETCORE_ENGINEERING_TELEMETRY=Build) Fix formatting (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0055)
var syntaxContext = (CSharpSyntaxContext)context.SyntaxContext;
var precedingModifiers = syntaxContext.PrecedingModifiers;

Check failure on line 36 in src/Features/CSharp/Portable/Snippets/CSharpProprSnippetProvider.cs

View check run for this annotation

Azure Pipelines / roslyn-CI (Correctness Correctness_Analyzers)

src/Features/CSharp/Portable/Snippets/CSharpProprSnippetProvider.cs#L36

src/Features/CSharp/Portable/Snippets/CSharpProprSnippetProvider.cs(36,1): error IDE0055: (NETCORE_ENGINEERING_TELEMETRY=Build) Fix formatting (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0055)

Check failure on line 36 in src/Features/CSharp/Portable/Snippets/CSharpProprSnippetProvider.cs

View check run for this annotation

Azure Pipelines / roslyn-CI

src/Features/CSharp/Portable/Snippets/CSharpProprSnippetProvider.cs#L36

src/Features/CSharp/Portable/Snippets/CSharpProprSnippetProvider.cs(36,1): error IDE0055: (NETCORE_ENGINEERING_TELEMETRY=Build) Fix formatting (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0055)
if (syntaxContext.PrecedingModifiers.IsEmpty())
return true;
victor-pogor marked this conversation as resolved.
Show resolved Hide resolved

// "private" and "private protected" modifiers are NOT valid for required property
if (precedingModifiers.Any(syntaxKind => syntaxKind == SyntaxKind.PrivateKeyword))
victor-pogor marked this conversation as resolved.
Show resolved Hide resolved
return false;

Check failure on line 43 in src/Features/CSharp/Portable/Snippets/CSharpProprSnippetProvider.cs

View check run for this annotation

Azure Pipelines / roslyn-CI (Correctness Correctness_Analyzers)

src/Features/CSharp/Portable/Snippets/CSharpProprSnippetProvider.cs#L43

src/Features/CSharp/Portable/Snippets/CSharpProprSnippetProvider.cs(43,1): error IDE0055: (NETCORE_ENGINEERING_TELEMETRY=Build) Fix formatting (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0055)
// "protected internal" modifiers are valid for required property
if(precedingModifiers.IsSupersetOf([SyntaxKind.ProtectedKeyword, SyntaxKind.InternalKeyword]))
return true;

// "protected" and "private protected" modifiers are NOT valid for required property
if(precedingModifiers.Any(syntaxKind => syntaxKind == SyntaxKind.ProtectedKeyword))
return false;

return true;
}

protected override AccessorDeclarationSyntax? GenerateSetAccessorDeclaration(CSharpSyntaxContext syntaxContext, SyntaxGenerator generator, CancellationToken cancellationToken)
{
// Having a property with `set` accessor in a readonly struct leads to a compiler error.
// So if user executes snippet inside a readonly struct the right thing to do is to not generate `set` accessor at all
if (syntaxContext.ContainingTypeDeclaration is StructDeclarationSyntax structDeclaration &&
syntaxContext.SemanticModel.GetDeclaredSymbol(structDeclaration, cancellationToken) is { IsReadOnly: true })
{
return null;
}

return base.GenerateSetAccessorDeclaration(syntaxContext, generator, cancellationToken);
}

protected override SyntaxToken[] GetAdditionalPropertyModifiers(CSharpSyntaxContext? syntaxContext)
victor-pogor marked this conversation as resolved.
Show resolved Hide resolved
{
return [RequiredKeyword];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -143,25 +143,13 @@ public Program()
""");
}

[Theory]
[MemberData(nameof(CommonSnippetTestData.AllAccessibilityModifiers), MemberType = typeof(CommonSnippetTestData))]
public async Task InsertSnippetAfterAccessibilityModifierTest(string modifier)
victor-pogor marked this conversation as resolved.
Show resolved Hide resolved
{
await VerifyPropertyAsync($$"""
class Program
{
{{modifier}} $$
}
""", $$"""{|0:int|} {|1:MyProperty|} {{DefaultPropertyBlockText}}""");
}

protected async Task VerifyPropertyAsync([StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string markup, string propertyMarkup)
{
TestFileMarkupParser.GetPosition(markup, out var code, out var position);
var expectedCode = code.Insert(position, propertyMarkup + "$$");
await VerifySnippetAsync(markup, expectedCode);
}

protected Task VerifyDefaultPropertyAsync([StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string markup, string propertyName = "MyProperty")
protected virtual Task VerifyDefaultPropertyAsync([StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string markup, string propertyName = "MyProperty")
=> VerifyPropertyAsync(markup, $$"""public {|0:int|} {|1:{{propertyName}}|} {{DefaultPropertyBlockText}}""");
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.

using System.Threading.Tasks;
using Xunit;

namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Snippets;

Expand Down Expand Up @@ -65,4 +66,16 @@ interface MyInterface
}
""");
}

[Theory]
[MemberData(nameof(CommonSnippetTestData.AllAccessibilityModifiers), MemberType = typeof(CommonSnippetTestData))]
public async Task InsertSnippetAfterAccessibilityModifierTest(string modifier)
{
await VerifyPropertyAsync($$"""
class Program
{
{{modifier}} $$
}
""", $$"""{|0:int|} {|1:MyProperty|} {{DefaultPropertyBlockText}}""");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.

using System.Threading.Tasks;
using Xunit;

namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Snippets;

Expand Down Expand Up @@ -66,4 +67,16 @@ interface MyInterface
}
""", "public {|0:int|} {|1:MyProperty|} { get; }");
}

[Theory]
[MemberData(nameof(CommonSnippetTestData.AllAccessibilityModifiers), MemberType = typeof(CommonSnippetTestData))]
public async Task InsertSnippetAfterAccessibilityModifierTest(string modifier)
{
await VerifyPropertyAsync($$"""
class Program
{
{{modifier}} $$
}
""", $$"""{|0:int|} {|1:MyProperty|} {{DefaultPropertyBlockText}}""");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.

using System.Threading.Tasks;
using Xunit;

namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Snippets;

Expand Down Expand Up @@ -59,4 +60,16 @@ interface MyInterface
}
""");
}

[Theory]
[MemberData(nameof(CommonSnippetTestData.AllAccessibilityModifiers), MemberType = typeof(CommonSnippetTestData))]
public async Task InsertSnippetAfterAccessibilityModifierTest(string modifier)
{
await VerifyPropertyAsync($$"""
class Program
{
{{modifier}} $$
}
""", $$"""{|0:int|} {|1:MyProperty|} {{DefaultPropertyBlockText}}""");
}
}
107 changes: 107 additions & 0 deletions src/Features/CSharpTest/Snippets/CSharpProprSnippetProviderTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// 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.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
using Roslyn.Test.Utilities;
using Xunit;

namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Snippets;

public sealed class CSharpProprSnippetProviderTests : AbstractCSharpAutoPropertySnippetProviderTests
{
protected override string SnippetIdentifier => "propr";

protected override string DefaultPropertyBlockText => "{ get; set; }";

[WorkItem("https://github.com/dotnet/roslyn/issues/75128")]
victor-pogor marked this conversation as resolved.
Show resolved Hide resolved
public override async Task InsertSnippetInReadonlyStructTest()
{
// Ensure we don't generate redundant `set` accessor when executed in readonly struct
await VerifyPropertyAsync("""
readonly struct MyStruct
{
$$
}
""", "public required {|0:int|} {|1:MyProperty|} { get; }");
}

[WorkItem("https://github.com/dotnet/roslyn/issues/75128")]
public override async Task InsertSnippetInReadonlyStructTest_ReadonlyModifierInOtherPartialDeclaration()
{
// Ensure we don't generate redundant `set` accessor when executed in readonly struct
await VerifyPropertyAsync("""
partial struct MyStruct
{
$$
}

readonly partial struct MyStruct
{
}
""", "public required {|0:int|} {|1:MyProperty|} { get; }");
}

[WorkItem("https://github.com/dotnet/roslyn/issues/75128")]
public override async Task InsertSnippetInReadonlyStructTest_ReadonlyModifierInOtherPartialDeclaration_MissingPartialModifier()
{
// Even though there is no `partial` modifier on the first declaration
// compiler still treats the whole type as partial since it is more likely that
// the user's intent was to have a partial type and they just forgot the modifier.
// Thus we still recognize that as `readonly` context and don't generate a setter
await VerifyPropertyAsync("""
struct MyStruct
{
$$
}

readonly partial struct MyStruct
{
}
""", "public required {|0:int|} {|1:MyProperty|} { get; }");
}

[WorkItem("https://github.com/dotnet/roslyn/issues/75128")]
public override async Task InsertSnippetInInterfaceTest()
victor-pogor marked this conversation as resolved.
Show resolved Hide resolved
{
await VerifyDefaultPropertyAsync("""
victor-pogor marked this conversation as resolved.
Show resolved Hide resolved
interface MyInterface
{
$$
}
""");
}

[Theory, WorkItem("https://github.com/dotnet/roslyn/issues/75128")]
[InlineData("public")]
[InlineData("internal")]
[InlineData("protected internal")]
public async Task InsertSnippetAfterAccessibilityModifierTest(string modifier)
{
await VerifyPropertyAsync($$"""
class Program
{
{{modifier}} $$
}
""",
$$"""required {|0:int|} {|1:MyProperty|} {{DefaultPropertyBlockText}}""");
}

[Theory, WorkItem("https://github.com/dotnet/roslyn/issues/75128")]
[InlineData("private")]
[InlineData("protected")]
[InlineData("private protected")]
public async Task DoNotInsertSnippetAfterAccessibilityModifierTest(string modifier)
victor-pogor marked this conversation as resolved.
Show resolved Hide resolved
{
await VerifySnippetIsAbsentAsync($$"""
class Program
{
{{modifier}} $$
}
""");
}

protected override Task VerifyDefaultPropertyAsync([StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string markup, string propertyName = "MyProperty")
=> VerifyPropertyAsync(markup, $$"""public required {|0:int|} {|1:{{propertyName}}|} {{DefaultPropertyBlockText}}""");
}
3 changes: 3 additions & 0 deletions src/Features/Core/Portable/FeaturesResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -3171,4 +3171,7 @@ Zero-width positive lookbehind assertions are typically used at the beginning of
<data name="Symbol_search" xml:space="preserve">
<value>Symbol search</value>
</data>
<data name="required_property" xml:space="preserve">
<value>required property</value>
victor-pogor marked this conversation as resolved.
Show resolved Hide resolved
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ internal static class CommonSnippetIdentifiers
public const string ConsoleWriteLine = "cw";
public const string Constructor = "ctor";
public const string Property = "prop";
public const string RequiredProperty = "propr";
victor-pogor marked this conversation as resolved.
Show resolved Hide resolved
public const string GetOnlyProperty = "propg";
}
5 changes: 5 additions & 0 deletions src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Features/Core/Portable/xlf/FeaturesResources.de.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Features/Core/Portable/xlf/FeaturesResources.es.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Features/Core/Portable/xlf/FeaturesResources.it.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading