Skip to content

Commit

Permalink
Fix crash of type snippets after accessibility modifier (#67055)
Browse files Browse the repository at this point in the history
  • Loading branch information
DoctorKrolic authored Mar 3, 2023
1 parent 83631c4 commit eb719b7
Show file tree
Hide file tree
Showing 8 changed files with 191 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
Expand Down Expand Up @@ -234,5 +230,58 @@ public Program()
}";
await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit);
}

[WpfTheory]
[InlineData("public")]
[InlineData("private")]
[InlineData("protected")]
[InlineData("private protected")]
[InlineData("protected internal")]
public async Task AfterAccessibilityModifier(string modifier)
{
var markupBeforeCommit = $"{modifier} $$";

var expectedCodeAfterCommit = $$"""
{{modifier}} class MyClass
{
$$
}
""";

await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit);
}

[WpfTheory]
[InlineData("public")]
[InlineData("private")]
[InlineData("protected")]
[InlineData("private protected")]
[InlineData("protected internal")]
public async Task AfterAccessibilityModifier_RequireAccessibilityModifiers(string modifier)
{
var markupBeforeCommit = $$"""
<Workspace>
<Project Language="C#" AssemblyName="Assembly1" CommonReferences="true">
<Document FilePath="/0/Test0.cs">{{modifier}} $$</Document>
<AnalyzerConfigDocument FilePath="/.editorconfig">
root = true
[*]
# IDE0008: Use explicit type
dotnet_style_require_accessibility_modifiers = always
</AnalyzerConfigDocument>
</Project>
</Workspace>
""";

var expectedCodeAfterCommit = $$"""
{{modifier}} class MyClass
{
$$
}
""";

await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
Expand Down Expand Up @@ -234,5 +230,58 @@ public Program()
}";
await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit);
}

[WpfTheory]
[InlineData("public")]
[InlineData("private")]
[InlineData("protected")]
[InlineData("private protected")]
[InlineData("protected internal")]
public async Task AfterAccessibilityModifier(string modifier)
{
var markupBeforeCommit = $"{modifier} $$";

var expectedCodeAfterCommit = $$"""
{{modifier}} interface MyInterface
{
$$
}
""";

await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit);
}

[WpfTheory]
[InlineData("public")]
[InlineData("private")]
[InlineData("protected")]
[InlineData("private protected")]
[InlineData("protected internal")]
public async Task AfterAccessibilityModifier_RequireAccessibilityModifiers(string modifier)
{
var markupBeforeCommit = $$"""
<Workspace>
<Project Language="C#" AssemblyName="Assembly1" CommonReferences="true">
<Document FilePath="/0/Test0.cs">{{modifier}} $$</Document>
<AnalyzerConfigDocument FilePath="/.editorconfig">
root = true
[*]
# IDE0008: Use explicit type
dotnet_style_require_accessibility_modifiers = always
</AnalyzerConfigDocument>
</Project>
</Workspace>
""";

var expectedCodeAfterCommit = $$"""
{{modifier}} interface MyInterface
{
$$
}
""";

await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
Expand Down Expand Up @@ -245,5 +241,58 @@ public Program()
;
await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit);
}

[WpfTheory]
[InlineData("public")]
[InlineData("private")]
[InlineData("protected")]
[InlineData("private protected")]
[InlineData("protected internal")]
public async Task AfterAccessibilityModifier(string modifier)
{
var markupBeforeCommit = $"{modifier} $$";

var expectedCodeAfterCommit = $$"""
{{modifier}} struct MyStruct
{
$$
}
""";

await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit);
}

[WpfTheory]
[InlineData("public")]
[InlineData("private")]
[InlineData("protected")]
[InlineData("private protected")]
[InlineData("protected internal")]
public async Task AfterAccessibilityModifier_RequireAccessibilityModifiers(string modifier)
{
var markupBeforeCommit = $$"""
<Workspace>
<Project Language="C#" AssemblyName="Assembly1" CommonReferences="true">
<Document FilePath="/0/Test0.cs">{{modifier}} $$</Document>
<AnalyzerConfigDocument FilePath="/.editorconfig">
root = true
[*]
# IDE0008: Use explicit type
dotnet_style_require_accessibility_modifiers = always
</AnalyzerConfigDocument>
</Project>
</Workspace>
""";

var expectedCodeAfterCommit = $$"""
{{modifier}} struct MyStruct
{
$$
}
""";

await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp.Utilities;
Expand All @@ -19,20 +21,26 @@

namespace Microsoft.CodeAnalysis.CSharp.Snippets
{
internal abstract class CSharpTypeSnippetProvider : AbstractTypeSnippetProvider
internal abstract class AbstractCSharpTypeSnippetProvider : AbstractTypeSnippetProvider
{
private static readonly ISet<SyntaxKind> s_validModifiers = new HashSet<SyntaxKind>(SyntaxFacts.EqualityComparer)
{
SyntaxKind.NewKeyword,
SyntaxKind.PublicKeyword,
SyntaxKind.ProtectedKeyword,
SyntaxKind.InternalKeyword,
SyntaxKind.PrivateKeyword,
SyntaxKind.AbstractKeyword,
SyntaxKind.SealedKeyword,
SyntaxKind.StaticKeyword,
SyntaxKind.UnsafeKeyword
};
{
SyntaxKind.NewKeyword,
SyntaxKind.PublicKeyword,
SyntaxKind.ProtectedKeyword,
SyntaxKind.InternalKeyword,
SyntaxKind.PrivateKeyword,
SyntaxKind.AbstractKeyword,
SyntaxKind.SealedKeyword,
SyntaxKind.StaticKeyword,
SyntaxKind.UnsafeKeyword
};

protected override async Task<bool> HasPrecedingAccessibilityModifiersAsync(Document document, int position, CancellationToken cancellationToken)
{
var tree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
return tree.GetPrecedingModifiers(position, cancellationToken).Any(SyntaxFacts.IsAccessibilityModifier);
}

protected override async Task<bool> IsValidSnippetLocationAsync(Document document, int position, CancellationToken cancellationToken)
{
Expand Down Expand Up @@ -73,6 +81,12 @@ private static string GetIndentation(Document document, TypeDeclarationSyntax ty
return newIndentation.GetIndentationString(parsedDocument.Text, syntaxFormattingOptions.UseTabs, syntaxFormattingOptions.TabSize) + newLine;
}

protected override SyntaxNode? FindAddedSnippetSyntaxNode(SyntaxNode root, int position, Func<SyntaxNode?, bool> isCorrectContainer)
{
var node = root.FindNode(TextSpan.FromBounds(position, position));
return node.GetAncestorOrThis<TypeDeclarationSyntax>();
}

protected override async Task<Document> AddIndentationToDocumentAsync(Document document, int position, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken)
{
var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
namespace Microsoft.CodeAnalysis.CSharp.Snippets
{
[ExportSnippetProvider(nameof(ISnippetProvider), LanguageNames.CSharp), Shared]
internal sealed class CSharpClassSnippetProvider : CSharpTypeSnippetProvider
internal sealed class CSharpClassSnippetProvider : AbstractCSharpTypeSnippetProvider
{
[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
namespace Microsoft.CodeAnalysis.CSharp.Snippets
{
[ExportSnippetProvider(nameof(ISnippetProvider), LanguageNames.CSharp), Shared]
internal sealed class CSharpInterfaceSnippetProvider : CSharpTypeSnippetProvider
internal sealed class CSharpInterfaceSnippetProvider : AbstractCSharpTypeSnippetProvider
{
[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
namespace Microsoft.CodeAnalysis.CSharp.Snippets
{
[ExportSnippetProvider(nameof(ISnippetProvider), LanguageNames.CSharp), Shared]
internal sealed class CSharpStructSnippetProvider : CSharpTypeSnippetProvider
internal sealed class CSharpStructSnippetProvider : AbstractCSharpTypeSnippetProvider
{
[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,26 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Microsoft.CodeAnalysis.Text;

namespace Microsoft.CodeAnalysis.Snippets.SnippetProviders
{
internal abstract class AbstractTypeSnippetProvider : AbstractSnippetProvider
{
protected abstract Task<bool> HasPrecedingAccessibilityModifiersAsync(Document document, int position, CancellationToken cancellationToken);
protected abstract void GetTypeDeclarationIdentifier(SyntaxNode node, out SyntaxToken identifier);
protected abstract Task<SyntaxNode> GenerateTypeDeclarationAsync(Document document, int position, bool useAccessibility, CancellationToken cancellationToken);

protected override async Task<ImmutableArray<TextChange>> GenerateSnippetTextChangesAsync(Document document, int position, CancellationToken cancellationToken)
{
var useAccessibility = await AreAccessibilityModifiersRequiredAsync(document, cancellationToken).ConfigureAwait(false);
var hasAccessibilityModifiers = await HasPrecedingAccessibilityModifiersAsync(document, position, cancellationToken).ConfigureAwait(false);
var useAccessibility = !hasAccessibilityModifiers && await AreAccessibilityModifiersRequiredAsync(document, cancellationToken).ConfigureAwait(false);

var typeDeclaration = await GenerateTypeDeclarationAsync(document, position, useAccessibility, cancellationToken).ConfigureAwait(false);

Expand Down

0 comments on commit eb719b7

Please sign in to comment.