Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class CSharpConstructorSnippetCompletionProviderTests : AbstractCSharpSni
{
protected override string ItemToCommit => "ctor";

[WpfFact, Trait(Traits.Feature, Traits.Features.Completion)]
[WpfFact]
public async Task ConstructorSnippetMissingInNamespace()
{
var markupBeforeCommit =
Expand All @@ -28,8 +28,8 @@ namespace Namespace
await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit);
}

[WpfFact, Trait(Traits.Feature, Traits.Features.Completion)]
public async Task ConstructorSnippetMissingInFilescopedNamespace()
[WpfFact]
public async Task ConstructorSnippetMissingInFileScopedNamespace()
{
var markupBeforeCommit =
"""
Expand All @@ -41,7 +41,7 @@ namespace Namespace;
await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit);
}

[WpfFact, Trait(Traits.Feature, Traits.Features.Completion)]
[WpfFact]
public async Task ConstructorSnippetMissingInTopLevelContext()
{
var markupBeforeCommit =
Expand All @@ -53,7 +53,7 @@ public async Task ConstructorSnippetMissingInTopLevelContext()
await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit);
}

[WpfFact, Trait(Traits.Feature, Traits.Features.Completion)]
[WpfFact]
public async Task InsertConstructorSnippetInClassTest()
{
var markupBeforeCommit =
Expand All @@ -77,7 +77,7 @@ public MyClass()
await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit);
}

[WpfFact, Trait(Traits.Feature, Traits.Features.Completion)]
[WpfFact]
public async Task InsertConstructorSnippetInAbstractClassTest()
{
var markupBeforeCommit =
Expand All @@ -101,7 +101,7 @@ protected MyClass()
await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit);
}

[WpfFact, Trait(Traits.Feature, Traits.Features.Completion)]
[WpfFact]
public async Task InsertConstructorSnippetInAbstractClassTest_AbstractModifierInOtherPartialDeclaration()
{
var markupBeforeCommit =
Expand Down Expand Up @@ -133,7 +133,7 @@ abstract partial class MyClass
await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit);
}

[WpfFact, Trait(Traits.Feature, Traits.Features.Completion)]
[WpfFact]
public async Task InsertConstructorSnippetInNestedAbstractClassTest()
{
var markupBeforeCommit =
Expand Down Expand Up @@ -163,7 +163,7 @@ protected NestedClass()
await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit);
}

[WpfFact, Trait(Traits.Feature, Traits.Features.Completion)]
[WpfFact]
public async Task InsertConstructorSnippetInStructTest()
{
var markupBeforeCommit =
Expand All @@ -187,7 +187,7 @@ public MyStruct()
await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit);
}

[WpfFact, Trait(Traits.Feature, Traits.Features.Completion)]
[WpfFact]
public async Task InsertConstructorSnippetInRecordTest()
{
var markupBeforeCommit =
Expand All @@ -211,7 +211,7 @@ public MyRecord()
await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit);
}

[WpfFact, Trait(Traits.Feature, Traits.Features.Completion)]
[WpfFact]
public async Task ConstructorSnippetMissingInInterface()
{
var markupBeforeCommit =
Expand All @@ -225,7 +225,7 @@ interface MyInterface
await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit);
}

[WpfFact, Trait(Traits.Feature, Traits.Features.Completion)]
[WpfFact]
public async Task InsertConstructorSnippetInNestedClassTest()
{
var markupBeforeCommit =
Expand Down Expand Up @@ -254,5 +254,207 @@ public MyClass1()
""";
await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit);
}

[WpfTheory]
[InlineData("public")]
[InlineData("private")]
[InlineData("protected")]
[InlineData("internal")]
[InlineData("private protected")]
[InlineData("protected internal")]
[InlineData("static")]
public async Task InsertConstructorSnippetAfterValidModifiersTest(string modifiers)
{
var markupBeforeCommit = $$"""
class MyClass
{
{{modifiers}} $$
}
""";

var expectedCodeAfterCommit = $$"""
class MyClass
{
{{modifiers}} MyClass()
{
$$
}
}
""";

await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit);
}

[WpfTheory]
[InlineData("abstract")]
[InlineData("sealed")]
[InlineData("virtual")]
[InlineData("override")]
[InlineData("readonly")]
[InlineData("new")]
[InlineData("file")]
public async Task ConstructorSnippetMissingAfterInvalidModifierTest(string modifier)
{
var markupBeforeCommit = $$"""
class MyClass
{
{{modifier}} $$
}
""";

await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit);
}

[WpfTheory]
[InlineData("public")]
[InlineData("private")]
[InlineData("protected")]
[InlineData("internal")]
[InlineData("private protected")]
[InlineData("protected internal")]
public async Task ConstructorSnippetMissingAfterBothAccessibilityModifierAndStaticKeywordTest(string accessibilityModifier)
{
var markupBeforeCommit = $$"""
class MyClass
{
{{accessibilityModifier}} static $$
}
""";

await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit);
}

[WpfFact]
public async Task InsertConstructorSnippetAfterAccessibilityModifierBeforeOtherMemberTest()
{
var markupBeforeCommit = """
class C
{
private $$
readonly int Value = 3;
}
""";

var expectedCodeAfterCommit = """
class C
{
private C()
{
$$
}
readonly int Value = 3;
}
""";

await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit);
}

[WpfFact]
public async Task InsertConstructorSnippetBetweenAccessibilityModifiersBeforeOtherMemberTest()
{
var markupBeforeCommit = """
class C
{
protected $$
internal int Value = 3;
}
""";

var expectedCodeAfterCommit = """
class C
{
protected C()
{
$$
}
internal int Value = 3;
}
""";

await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit);
}

[WpfFact]
public async Task InsertConstructorSnippetAfterAccessibilityModifierBeforeOtherStaticMemberTest()
{
var markupBeforeCommit = """
class C
{
internal $$
static int Value = 3;
}
""";

var expectedCodeAfterCommit = """
class C
{
internal C()
{
$$
}
static int Value = 3;
}
""";

await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit);
}

[WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/68176")]
public async Task InsertCorrectConstructorSnippetInNestedTypeTest_CtorBeforeNestedType()
{
var markupBeforeCommit = """
class Outer
{
$$
class Inner
{
}
}
""";

var expectedCodeAfterCommit = """
class Outer
{
public Outer()
{
$$
}
class Inner
{
}
}
""";

await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit);
}

[WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/68176")]
public async Task InsertCorrectConstructorSnippetInNestedTypeTest_CtorAfterNestedType()
{
var markupBeforeCommit = """
class Outer
{
class Inner
{
}
$$
}
""";

var expectedCodeAfterCommit = """
class Outer
{
class Inner
{
}
public Outer()
{
$$
}
}
""";

await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,38 @@
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp.Utilities;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery;
using Microsoft.CodeAnalysis.Snippets;
using Microsoft.CodeAnalysis.Snippets.SnippetProviders;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.CSharp.Snippets
{
[ExportSnippetProvider(nameof(ISnippetProvider), LanguageNames.CSharp), Shared]
internal sealed class CSharpConstructorSnippetProvider : AbstractConstructorSnippetProvider
{
private static readonly ISet<SyntaxKind> s_validModifiers = new HashSet<SyntaxKind>(SyntaxFacts.EqualityComparer)
{
SyntaxKind.PublicKeyword,
SyntaxKind.PrivateKeyword,
SyntaxKind.ProtectedKeyword,
SyntaxKind.InternalKeyword,
SyntaxKind.StaticKeyword,
};

[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public CSharpConstructorSnippetProvider()
Expand All @@ -30,13 +45,41 @@ protected override bool IsValidSnippetLocation(in SnippetContext context, Cancel
{
var syntaxContext = (CSharpSyntaxContext)context.SyntaxContext;

var precedingModifiers = syntaxContext.PrecedingModifiers;

if (!(precedingModifiers.All(SyntaxFacts.IsAccessibilityModifier) ||
precedingModifiers.Count == 1 && precedingModifiers.Single() == SyntaxKind.StaticKeyword))
{
return false;
}

return
syntaxContext.IsMemberDeclarationContext(
validModifiers: s_validModifiers,
validTypeDeclarations: SyntaxKindSet.ClassStructRecordTypeDeclarations,
canBePartial: true,
cancellationToken: cancellationToken);
}

protected override async Task<TextChange> GenerateSnippetTextChangeAsync(Document document, int position, CancellationToken cancellationToken)
{
var semanticModel = await document.ReuseExistingSpeculativeModelAsync(position, cancellationToken).ConfigureAwait(false);
var syntaxContext = (CSharpSyntaxContext)document.GetRequiredLanguageService<ISyntaxContextService>().CreateContext(document, semanticModel, position, cancellationToken);

var containingType = syntaxContext.ContainingTypeDeclaration;
Contract.ThrowIfNull(containingType);

var containingTypeSymbol = semanticModel.GetDeclaredSymbol(containingType, cancellationToken);
Contract.ThrowIfNull(containingTypeSymbol);

var generator = SyntaxGenerator.GetGenerator(document);
var constructorDeclaration = generator.ConstructorDeclaration(
containingTypeName: containingType.Identifier.ToString(),
accessibility: syntaxContext.PrecedingModifiers.Any() ? Accessibility.NotApplicable : (containingTypeSymbol.IsAbstract ? Accessibility.Protected : Accessibility.Public));

return new TextChange(TextSpan.FromBounds(position, position), constructorDeclaration.NormalizeWhitespace().ToFullString());
}

protected override int GetTargetCaretPosition(ISyntaxFactsService syntaxFacts, SyntaxNode caretTarget, SourceText sourceText)
{
return CSharpSnippetHelpers.GetTargetCaretPositionInBlock<ConstructorDeclarationSyntax>(
Expand Down
Loading