diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpConstructorSnippetCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpConstructorSnippetCompletionProviderTests.cs index bc396aef78a07..554a1260b4324 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpConstructorSnippetCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpConstructorSnippetCompletionProviderTests.cs @@ -165,6 +165,260 @@ public MyClass1() $$ } } +}"; + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); + } + + [WpfTheory] + [InlineData("class")] + [InlineData("enum")] + [InlineData("interface")] + [InlineData("record")] + [InlineData("struct")] + [InlineData("record class")] + [InlineData("record struct")] + [Trait(Traits.Feature, Traits.Features.Completion)] + public async Task InsertConstructorSnippetHasNestedTypeTest(string keyword) + { + var markupBeforeCommit = +$$""" +class Outer +{ + $$ + {{keyword}} Inner + { + } +} +"""; + + var expectedCodeAfterCommit = +$$""" +class Outer +{ + public Outer() + { + $$ + } + {{keyword}} Inner + { + } +} +"""; + + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task InsertConstructorSnippetHasNestedClassGluedOuterTest() + { + var markupBeforeCommit_GluedToOuter = +@"class Outer +{$$ + class Inner + { + } +}"; + // The position ($$) is in a strange position because code does not format after insertion. + var expectedCodeAfterCommit_GluedToOuter = +@"class Outer +{ public Outer() + { + +$$ } + class Inner + { + } +}"; + await VerifyCustomCommitProviderAsync(markupBeforeCommit_GluedToOuter, ItemToCommit, expectedCodeAfterCommit_GluedToOuter); + } + + [WpfTheory] + [InlineData("class")] + [InlineData("record")] + [InlineData("struct")] + [InlineData("record class")] + [InlineData("record struct")] + [Trait(Traits.Feature, Traits.Features.Completion)] + public async Task InsertConstructorSnippetInConstructableTypeTest(string keyword) + { + var markupBeforeCommit = +$$""" +{{keyword}} MyName +{ + $$ +} +"""; + + var expectedCodeAfterCommit = +$$""" +{{keyword}} MyName +{ + public MyName() + { + $$ + } +} +"""; + + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task InsertConstructorSnippetInStaticClassTest() + { + var markupBeforeCommit = +$$""" +static class MyClass +{ + $$ +} +"""; + var expectedCodeAfterCommit = +$$""" +static class MyClass +{ + public MyClass() + { + $$ + } +} +"""; + // Current CSharpConstructorSnippetProvider implementation inserts a constructor even for static classes. + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); + } + + [WpfTheory] + [InlineData("enum")] + [InlineData("interface")] + [Trait(Traits.Feature, Traits.Features.Completion)] + public async Task InsertConstructorSnippetInNotConstructableTypeTest(string keyword) + { + var markupBeforeCommit = +$$""" +{{keyword}} MyName +{ + $$ +} +"""; + await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit); + } + + [WpfTheory] + [InlineData("class")] + [InlineData("record")] + [InlineData("struct")] + [InlineData("record class")] + [InlineData("record struct")] + [Trait(Traits.Feature, Traits.Features.Completion)] + public async Task InsertConstructorSnippetBraceNotClosed(string keyword) + { + var markupBeforeCommit = +$$""" +namespace N +{ + {{keyword}} Outer + { + $$ + int i; +"""; + + var expectedCodeAfterCommit = +$$""" +namespace N +{ + {{keyword}} Outer + { + public Outer() + { + $$ + } + int i; +"""; + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); + } + + [WpfTheory] + [InlineData("class")] + [InlineData("record")] + [InlineData("struct")] + [InlineData("record class")] + [InlineData("record struct")] + [Trait(Traits.Feature, Traits.Features.Completion)] + public async Task InsertConstructorSnippetBraceNotClosedAndEndOfFile(string keyword) + { + var markupBeforeCommit = +$$""" +namespace N +{ + {{keyword}} Outer + { + $$ +"""; + + var expectedCodeAfterCommit = +$$""" +namespace N +{ + {{keyword}} Outer + { + public Outer() + { + $$ + } +"""; + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); + } + + [WpfTheory] + [InlineData("class")] + [InlineData("record")] + [InlineData("struct")] + [InlineData("record class")] + [InlineData("record struct")] + [Trait(Traits.Feature, Traits.Features.Completion)] + public async Task InsertConstructorSnippetBraceNotClosedAndEndOfFileAndNested(string keyword) + { + var markupBeforeCommit = +$$""" +namespace N +{ + {{keyword}} Outer + { + class Inner{} + $$ +"""; + + var expectedCodeAfterCommit = +$$""" +namespace N +{ + {{keyword}} Outer + { + class Inner{} + public Outer() + { + $$ + } +"""; + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task InsertConstructorSnippetInGenericClassTest() + { + var markupBeforeCommit = +@"class MyClass : BaseClass +{ + $$ +}"; + + var expectedCodeAfterCommit = +@"class MyClass : BaseClass +{ + public MyClass() + { + $$ + } }"; await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); } diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractConstructorSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractConstructorSnippetProvider.cs index 672480c230b92..6df63729c3297 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractConstructorSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractConstructorSnippetProvider.cs @@ -31,7 +31,16 @@ protected override async Task GenerateSnippetTextChangeAsync(Documen var generator = SyntaxGenerator.GetGenerator(document); var syntaxFacts = document.GetRequiredLanguageService(); var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var nodeAtPosition = root.FindNode(TextSpan.FromBounds(position, position)); + var tokenAtPosition = root.FindTokenOnLeftOfPosition(position); + var nodeAtPosition = root.FindNode(tokenAtPosition.Span); + + // Skip inner class in nested class if position is out of it. + // For example "class Outer{ class Inner {} ctor@ }" + if (tokenAtPosition.RawKind == syntaxFacts.SyntaxKinds.CloseBraceToken) + { + Contract.ThrowIfNull(nodeAtPosition.Parent); + nodeAtPosition = nodeAtPosition.Parent; + } var containingType = nodeAtPosition.FirstAncestorOrSelf(syntaxFacts.IsTypeDeclaration); Contract.ThrowIfNull(containingType); var constructorDeclaration = generator.ConstructorDeclaration(