diff --git a/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.MoveTypeEditor.cs b/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.MoveTypeEditor.cs index f45b6f960247be6ac4e15fe79ae7f13f92669f0b..79312e06df0c27965fa89ce3a33a1e374220f26d 100644 --- a/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.MoveTypeEditor.cs +++ b/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.MoveTypeEditor.cs @@ -1,5 +1,6 @@ // 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.Collections.Generic; using System.Linq; using System.Threading; @@ -139,43 +140,43 @@ private async Task> MoveTypeToNewFileAsync(stri private static IEnumerable GetMembersToRemove( SyntaxNode root, TTypeDeclarationSyntax typeNode) { - // the type node being moved and its container declarations should - // be kept. - var ancestorsAndSelfToKeep = typeNode - .AncestorsAndSelf() - .Where(n => n is TNamespaceDeclarationSyntax || n is TTypeDeclarationSyntax); - - // while we need the ancestor container declarations, - // we do not need other declarations inside of these containers, - // that are not the type being moved. - var ancestorsKept = ancestorsAndSelfToKeep - .OfType() - .Except(new[] { typeNode }); - - var membersOfAncestorsKept = ancestorsKept - .SelectMany(a => a.DescendantNodes() - .OfType() - .Where(t => !t.Equals(typeNode) && t.Parent.Equals(a))); - - // top level nodes other that are not in the ancestor chain - // of the type being moved should be removed. - var topLevelMembersToRemove = root - .DescendantNodesAndSelf(descendIntoChildren: _ => true, descendIntoTrivia: false) - .Where(n => IsTopLevelNamespaceOrTypeNode(n) && !ancestorsAndSelfToKeep.Contains(n)); - - return topLevelMembersToRemove.Concat(membersOfAncestorsKept); + HashSet spine = new HashSet(); + + // collect the parent chain of declarations to keep. + SyntaxNode node = typeNode; + while (node.Parent != null && !(node.Parent is TCompilationUnitSyntax)) + { + spine.Add(node.Parent); + node = node.Parent; + } + + // get potential namespace, types and members to remove. + var removableCandidates = root + .DescendantNodes(n => DescendIntoChildren(n, spine.Contains(n))) + .Where(n => FilterToTopLevelMembers(n, typeNode)); + + // diff candidates with items we want to keep. + return removableCandidates.Except(spine); + } + + private static bool DescendIntoChildren(SyntaxNode node, bool shouldDescendIntoType) + { + // 1. get top level types and namespaces to remove. + // 2. descend into types and get members to remove, only if type is part of spine, which means + // we'll be keeping the type declaration but not other members, in the new file. + return node is TCompilationUnitSyntax + || node is TNamespaceDeclarationSyntax + || (node is TTypeDeclarationSyntax && shouldDescendIntoType); } - private static bool IsTopLevelNamespaceOrTypeNode(SyntaxNode node) + private static bool FilterToTopLevelMembers(SyntaxNode node, SyntaxNode typeNode) { - // check only for top level namespaces and types in the file. - // top level types are parented by either a namespace or compilation unit - // top level namespaces are parented by compilation unit. - return node is TNamespaceDeclarationSyntax - ? node.Parent is TCompilationUnitSyntax - : node is TTypeDeclarationSyntax - ? node.Parent is TNamespaceDeclarationSyntax || node.Parent is TCompilationUnitSyntax - : false; + // It is a type declaration that is not the node we've moving + // or its a container namespace, or a member declaration that is not a type, + // thereby ignoring other stuff like statements and identifiers. + return node is TTypeDeclarationSyntax + ? !node.Equals(typeNode) + : (node is TNamespaceDeclarationSyntax || node is TMemberDeclarationSyntax); } /// diff --git a/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.cs b/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.cs index dc538abbbe92ddf72743df77e15294fddbb0d045..d09dbc7140165af1015d270112ce046789113183 100644 --- a/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.cs +++ b/src/Features/Core/Portable/CodeRefactorings/MoveType/AbstractMoveTypeService.cs @@ -16,6 +16,7 @@ internal abstract partial class AbstractMoveTypeService typeNode.Parent is TTypeDeclarationSyntax; + /// + /// checks if there is a single top level type declaration in a document + /// private bool IsSingleTypeDeclarationInSourceDocument(SyntaxNode root) => - root.DescendantNodes().OfType().Count() == 1; + root.DescendantNodes() + .OfType() + .Count() == 1; public async Task GetRefactoringAsync(Document document, TextSpan textSpan, CancellationToken cancellationToken) {