| File: Organizing\Organizers\MemberDeclarationsOrganizer.cs | Web Access |
| Project: src\roslyn\src\Features\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.Features.csproj (Microsoft.CodeAnalysis.CSharp.Features) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. #nullable disable using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Organizing.Organizers; internal static partial class MemberDeclarationsOrganizer { public static SyntaxList<MemberDeclarationSyntax> Organize( SyntaxList<MemberDeclarationSyntax> members, CancellationToken cancellationToken) { // Break the list of members up into groups based on the PP // directives between them. var groups = members.SplitNodesOnPreprocessorBoundaries(cancellationToken); // Go into each group and organize them. We'll then have a list of // lists. Flatten that list and return that. var sortedGroups = groups.Select(OrganizeMemberGroup).Flatten().ToList(); if (sortedGroups.SequenceEqual(members)) { return members; } return [.. sortedGroups]; } private static void TransferTrivia<TSyntaxNode>( IList<TSyntaxNode> originalList, IList<TSyntaxNode> finalList) where TSyntaxNode : SyntaxNode { Debug.Assert(originalList.Count == finalList.Count); if (originalList.Count >= 2) { // Ok, we wanted to reorder the list. But we're definitely not done right now. While // most of the list will look fine, we will have issues with the first node. First, // we don't want to move any pp directives or banners that are on the first node. // Second, it can often be the case that the node doesn't even have any trivia. We // want to follow the user's style. So we find the node that was in the index that // the first node moved to, and we attempt to keep an appropriate amount of // whitespace based on that. // If the first node didn't move, then we don't need to do any of this special fixup // logic. if (originalList[0] != finalList[0]) { // First. Strip any pp directives or banners on the first node. They have to // move to the first node in the final list. CopyBanner(originalList, finalList); // Now, we need to fix up the first node wherever it is in the final list. We // need to strip it of its banner, and we need to add additional whitespace to // match the user's style FixupOriginalFirstNode(originalList, finalList); } } } private static void FixupOriginalFirstNode<TSyntaxNode>(IList<TSyntaxNode> originalList, IList<TSyntaxNode> finalList) where TSyntaxNode : SyntaxNode { // Now, find the original node in the final list. var originalFirstNode = originalList[0]; var indexInFinalList = finalList.IndexOf(originalFirstNode); // Find the initial node we had at that same index. var originalNodeAtThatIndex = originalList[indexInFinalList]; // If that node had blank lines above it, then place that number of blank // lines before the first node in the final list. var blankLines = originalNodeAtThatIndex.GetLeadingBlankLines(); originalFirstNode = originalFirstNode.GetNodeWithoutLeadingBannerAndPreprocessorDirectives() .WithPrependedLeadingTrivia(blankLines); finalList[indexInFinalList] = originalFirstNode; } private static void CopyBanner<TSyntaxNode>( IList<TSyntaxNode> originalList, IList<TSyntaxNode> finalList) where TSyntaxNode : SyntaxNode { // First. Strip any pp directives or banners on the first node. They // have to stay at the top of the list. var banner = originalList[0].GetLeadingBannerAndPreprocessorDirectives(); // Now, we want to remove any blank lines from the new first node and then // reattach the banner. var finalFirstNode = finalList[0]; finalFirstNode = finalFirstNode.GetNodeWithoutLeadingBlankLines(); finalFirstNode = finalFirstNode.WithLeadingTrivia(banner.Concat(finalFirstNode.GetLeadingTrivia())); // Place the updated first node back in the result list finalList[0] = finalFirstNode; } private static IList<MemberDeclarationSyntax> OrganizeMemberGroup(IList<MemberDeclarationSyntax> members) { if (members.Count > 1) { var initialList = new List<MemberDeclarationSyntax>(members); var finalList = initialList.OrderBy(new Comparer()).ToList(); if (!finalList.SequenceEqual(initialList)) { // Ok, we wanted to reorder the list. But we're definitely not done right now. // While most of the list will look fine, we will have issues with the first // node. First, we don't want to move any pp directives or banners that are on // the first node. Second, it can often be the case that the node doesn't even // have any trivia. We want to follow the user's style. So we find the node that // was in the index that the first node moved to, and we attempt to keep an // appropriate amount of whitespace based on that. TransferTrivia(initialList, finalList); return finalList; } } return members; } }