diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/CrefCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/CrefCompletionProviderTests.cs index d47e969d79e9bf4b5db8345b7aafea1faca307db..4b494cf53d127cf83b9b7ae021b5dbaf8f575127 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/CrefCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/CrefCompletionProviderTests.cs @@ -1015,6 +1015,11 @@ public bool IsObjectInitializerNamedAssignmentIdentifier(SyntaxNode node, out Sy { throw new NotImplementedException(); } + + public void AddFirstMissingCloseBrace(SyntaxNode root, SyntaxNode contextNode, out SyntaxNode newRoot, out SyntaxNode newContextNode) + { + throw new NotImplementedException(); + } } } } diff --git a/src/EditorFeatures/CSharpTest/PopulateSwitch/PopulateSwitchTests.cs b/src/EditorFeatures/CSharpTest/PopulateSwitch/PopulateSwitchTests.cs index d5f5b0959bbd6afc939ee3fec557e2a850225d89..385d6e826b63dedfd09cde4b778fde5e0b20888d 100644 --- a/src/EditorFeatures/CSharpTest/PopulateSwitch/PopulateSwitchTests.cs +++ b/src/EditorFeatures/CSharpTest/PopulateSwitch/PopulateSwitchTests.cs @@ -1,4 +1,6 @@ -using System; +// 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.Threading.Tasks; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.Diagnostics; @@ -895,6 +897,7 @@ void Method() } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsPopulateSwitch)] + [WorkItem(13455, "https://github.com/dotnet/roslyn/issues/13455")] public async Task AllMissingTokens() { await TestAsync( @@ -928,7 +931,7 @@ void Method() break; } } -"); +}", compareTokens: false); } } } \ No newline at end of file diff --git a/src/EditorFeatures/CSharpTest/PopulateSwitch/PopulateSwitchTests_FixAllTests.cs b/src/EditorFeatures/CSharpTest/PopulateSwitch/PopulateSwitchTests_FixAllTests.cs index 753889979d01b7b102591f7b193739c3293a78d7..ad405e4ef6899a8c15911c87582913b6e98c5a27 100644 --- a/src/EditorFeatures/CSharpTest/PopulateSwitch/PopulateSwitchTests_FixAllTests.cs +++ b/src/EditorFeatures/CSharpTest/PopulateSwitch/PopulateSwitchTests_FixAllTests.cs @@ -1,4 +1,6 @@ -using Roslyn.Test.Utilities; +// 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 Roslyn.Test.Utilities; using System.Threading.Tasks; using Xunit; diff --git a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/CrefCompletionProviderTests.vb b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/CrefCompletionProviderTests.vb index 8022b6ddf1feae6af780fb245a5f5032801158f7..46106321e40a5216fe01ae6c197897f4e70d5b09 100644 --- a/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/CrefCompletionProviderTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Completion/CompletionProviders/CrefCompletionProviderTests.vb @@ -891,6 +891,10 @@ End Class]]>.Value.NormalizeLineEndings() Public Function ConvertToSingleLine(node As SyntaxNode, Optional useElasticTrivia As Boolean = False) As SyntaxNode Implements ISyntaxFactsService.ConvertToSingleLine Throw New NotImplementedException() End Function + + Public Sub AddFirstMissingCloseBrace(root As SyntaxNode, contextNode As SyntaxNode, ByRef newRoot As SyntaxNode, ByRef newContextNode As SyntaxNode) Implements ISyntaxFactsService.AddFirstMissingCloseBrace + Throw New NotImplementedException() + End Sub End Class End Class End Namespace diff --git a/src/Features/Core/Portable/PopulateSwitch/PopulateSwitchCodeFixProvider.cs b/src/Features/Core/Portable/PopulateSwitch/PopulateSwitchCodeFixProvider.cs index cbbb71907ec67cacee41513667cebd4f218b81eb..f9d0eefb7d8b26347041e1763eebac0a01b977c2 100644 --- a/src/Features/Core/Portable/PopulateSwitch/PopulateSwitchCodeFixProvider.cs +++ b/src/Features/Core/Portable/PopulateSwitch/PopulateSwitchCodeFixProvider.cs @@ -14,7 +14,9 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.Semantics; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Simplification; using Roslyn.Utilities; @@ -111,10 +113,34 @@ public sealed override Task RegisterCodeFixesAsync(CodeFixContext context) var newSwitchNode = generator.InsertSwitchSections(switchNode, insertLocation, newSections) .WithAdditionalAnnotations(Formatter.Annotation); + // Make sure we didn't cause any braces to be imbalanced when we added members + // to the switch. + AddMissingBraces(document, ref root, ref switchNode); + var newRoot = root.ReplaceNode(switchNode, newSwitchNode); + return document.WithSyntaxRoot(newRoot); } + private void AddMissingBraces( + Document document, + ref SyntaxNode root, + ref SyntaxNode switchNode) + { + // Parsing of the switch may have caused imbalanced braces. i.e. the switch + // may have consumed a brace that was intended for a higher level construct. + // So balance the tree first, then do the switch replacement. + var syntaxFacts = document.GetLanguageService(); + + SyntaxNode newRoot; + SyntaxNode newSwitchNode; + syntaxFacts.AddFirstMissingCloseBrace( + root, switchNode, out newRoot, out newSwitchNode); + + root = newRoot; + switchNode = newSwitchNode; + } + private int InsertPosition(ISwitchStatement switchStatement) { // If the last section has a default label, then we want to be above that. diff --git a/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSyntaxFactsService.cs b/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSyntaxFactsService.cs index 34b0afb0f5210aeb14c616c8e28e125cf1770220..f5fd896439ec8f78fda35965866ef2d2aba03480 100644 --- a/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSyntaxFactsService.cs +++ b/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSyntaxFactsService.cs @@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.FindSymbols; +using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; @@ -1760,5 +1761,88 @@ public bool IsDeclaration(SyntaxNode node) { return SyntaxFacts.IsNamespaceMemberDeclaration(node.Kind()) || IsMemberDeclaration(node); } + + private static readonly SyntaxAnnotation s_annotation = new SyntaxAnnotation(); + + public void AddFirstMissingCloseBrace( + SyntaxNode root, SyntaxNode contextNode, + out SyntaxNode newRoot, out SyntaxNode newContextNode) + { + // First, annotate the context node in the tree so that we can find it again + // after we've done all the rewriting. + // var currentRoot = root.ReplaceNode(contextNode, contextNode.WithAdditionalAnnotations(s_annotation)); + newRoot = new AddFirstMissingCloseBaceRewriter(contextNode).Visit(root); + newContextNode = newRoot.GetAnnotatedNodes(s_annotation).Single(); + } + + private class AddFirstMissingCloseBaceRewriter: CSharpSyntaxRewriter + { + private readonly SyntaxNode _contextNode; + private bool _seenContextNode = false; + private bool _addedFirstCloseCurly = false; + + public AddFirstMissingCloseBaceRewriter(SyntaxNode contextNode) + { + _contextNode = contextNode; + } + + public override SyntaxNode Visit(SyntaxNode node) + { + if (node == _contextNode) + { + _seenContextNode = true; + + // Annotate the context node so we can find it again in the new tree + // after we've added the close curly. + return node.WithAdditionalAnnotations(s_annotation); + } + + // rewrite this node normally. + var rewritten = base.Visit(node); + if (rewritten == node) + { + return rewritten; + } + + // This node changed. That means that something underneath us got + // rewritten. (i.e. we added the annotation to the context node). + Debug.Assert(_seenContextNode); + + // Ok, we're past the context node now. See if this is a node with + // curlies. If so, if it has a missing close curly then add in the + // missing curly. Also, even if it doesn't have missing curlies, + // then still ask to format its close curly to make sure all the + // curlies up the stack are properly formatted. + var braces = rewritten.GetBraces(); + if (braces.Item1.Kind() == SyntaxKind.None && + braces.Item2.Kind() == SyntaxKind.None) + { + // Not an item with braces. Just pass it up. + return rewritten; + } + + // See if the close brace is missing. If it's the first missing one + // we're seeing then definitely add it. + if (braces.Item2.IsMissing) + { + if (!_addedFirstCloseCurly) + { + var closeBrace = SyntaxFactory.Token(SyntaxKind.CloseBraceToken) + .WithAdditionalAnnotations(Formatter.Annotation); + rewritten = rewritten.ReplaceToken(braces.Item2, closeBrace); + _addedFirstCloseCurly = true; + } + } + else + { + // Ask for the close brace to be formatted so that all the braces + // up the spine are in the right location. + rewritten = rewritten.ReplaceToken(braces.Item2, + braces.Item2.WithAdditionalAnnotations(Formatter.Annotation)); + } + + return rewritten; + } + } } -} +} \ No newline at end of file diff --git a/src/Workspaces/Core/Portable/LanguageServices/SyntaxFactsService/ISyntaxFactsService.cs b/src/Workspaces/Core/Portable/LanguageServices/SyntaxFactsService/ISyntaxFactsService.cs index f102e232255589a4d304da8642862d3f546a2a2a..d6953b491dfbadc7bf5538e9a7bbe299a1ad7c7a 100644 --- a/src/Workspaces/Core/Portable/LanguageServices/SyntaxFactsService/ISyntaxFactsService.cs +++ b/src/Workspaces/Core/Portable/LanguageServices/SyntaxFactsService/ISyntaxFactsService.cs @@ -185,6 +185,14 @@ internal interface ISyntaxFactsService : ILanguageService /// that arguments name. /// string GetNameForArgument(SyntaxNode argument); + + // Walks the tree, starting from contextNode, looking for the first construct + // with a missing close brace. If found, the close brace will be added and the + // updates root will be returned. The context node in that new tree will also + // be returned. + void AddFirstMissingCloseBrace( + SyntaxNode root, SyntaxNode contextNode, + out SyntaxNode newRoot, out SyntaxNode newContextNode); } [Flags] diff --git a/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSyntaxFactsService.vb b/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSyntaxFactsService.vb index a3e20c349e2bad201c0949bb093c441bc4ed2512..b57f74ef39a46eb94f6fcde65a562d364c68c43b 100644 --- a/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSyntaxFactsService.vb +++ b/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSyntaxFactsService.vb @@ -1456,5 +1456,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Return False End Function + + Public Sub AddFirstMissingCloseBrace(root As SyntaxNode, contextNode As SyntaxNode, ByRef newRoot As SyntaxNode, ByRef newContextNode As SyntaxNode) Implements ISyntaxFactsService.AddFirstMissingCloseBrace + ' Nothing to be done. VB doesn't have close braces + newRoot = root + newContextNode = contextNode + End Sub End Class -End Namespace +End Namespace \ No newline at end of file