File: src\Workspaces\SharedUtilitiesAndExtensions\Compiler\Core\Formatting\Context\FormattingContext.InitialContextFinder.cs
Web Access
Project: src\src\CodeStyle\Core\Analyzers\Microsoft.CodeAnalysis.CodeStyle.csproj (Microsoft.CodeAnalysis.CodeStyle)
// 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.
 
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Formatting.Rules;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Formatting;
 
internal partial class FormattingContext
{
    private class InitialContextFinder
    {
        private readonly TokenStream _tokenStream;
        private readonly ChainedFormattingRules _formattingRules;
        private readonly SyntaxNode _rootNode;
 
        public InitialContextFinder(
            TokenStream tokenStream,
            ChainedFormattingRules formattingRules,
            SyntaxNode rootNode)
        {
            Contract.ThrowIfNull(tokenStream);
            Contract.ThrowIfNull(formattingRules);
            Contract.ThrowIfNull(rootNode);
 
            _tokenStream = tokenStream;
            _formattingRules = formattingRules;
            _rootNode = rootNode;
        }
 
        public (List<IndentBlockOperation> indentOperations, ImmutableArray<SuppressOperation> suppressOperations) Do(SyntaxToken startToken, SyntaxToken endToken)
        {
            // we are formatting part of document, try to find initial context that formatting will be based on such as
            // initial indentation and etc.
            using (Logger.LogBlock(FunctionId.Formatting_ContextInitialization, CancellationToken.None))
            {
                // first try to set initial indentation information
                var initialIndentationOperations = this.GetInitialIndentBlockOperations(startToken, endToken);
 
                // second try to set suppress wrapping regions
                var initialSuppressOperations = GetInitialSuppressOperations(startToken, endToken);
                if (initialSuppressOperations != null)
                {
                    Debug.Assert(
                        initialSuppressOperations.All(
                            o => o.TextSpan.Contains(startToken.SpanStart) ||
                                 o.TextSpan.Contains(endToken.SpanStart)));
                }
 
                return (initialIndentationOperations, initialSuppressOperations);
            }
        }
 
        private List<IndentBlockOperation> GetInitialIndentBlockOperations(SyntaxToken startToken, SyntaxToken endToken)
        {
            var span = TextSpan.FromBounds(startToken.SpanStart, endToken.Span.End);
            var node = startToken.GetCommonRoot(endToken)!.GetParentWithBiggerSpan();
            var previous = (SyntaxNode?)null;
 
            // starting from the common node, move up to the parent
            var operations = new List<IndentBlockOperation>();
            var list = new List<IndentBlockOperation>();
            while (node != null)
            {
                // get all operations for the nodes that contains the formatting span, but not ones contained by the span
                node.DescendantNodesAndSelf(n => n != previous && n.Span.IntersectsWith(span) && !span.Contains(n.Span))
                    .Do(n =>
                        {
                            _formattingRules.AddIndentBlockOperations(list, n);
                            foreach (var element in list)
                            {
                                if (element != null)
                                {
                                    operations.Add(element);
                                }
                            }
 
                            list.Clear();
                        });
 
                // found some. use these as initial indentation
                if (operations.Any(o => o.TextSpan.Contains(span)))
                {
                    break;
                }
 
                previous = node;
                node = node.Parent;
            }
 
            // make sure operations we have has effects over the formatting span
            operations.RemoveAll(o => o == null || !o.TextSpan.IntersectsWith(span));
 
            // we couldn't find anything
            // return initial location so that we can get base indentation correctly
            if (operations.Count == 0)
            {
                operations.Add(new IndentBlockOperation(
                    startToken: _rootNode.GetFirstToken(includeZeroWidth: true),
                    endToken: _rootNode.GetLastToken(includeZeroWidth: true),
                    textSpan: _rootNode.FullSpan,
                    indentationDelta: 0,
                    option: IndentBlockOption.AbsolutePosition));
 
                return operations;
            }
 
            operations.Sort(CommonFormattingHelpers.IndentBlockOperationComparer);
            return operations;
        }
 
        private ImmutableArray<SuppressOperation> GetInitialSuppressOperations(SyntaxToken startToken, SyntaxToken endToken)
        {
            using var _ = ArrayBuilder<SuppressOperation>.GetInstance(out var result);
 
            this.AddInitialSuppressOperations(startToken, endToken, SuppressOption.NoWrapping, result);
            this.AddInitialSuppressOperations(startToken, endToken, SuppressOption.NoSpacing, result);
 
            result.Sort(CommonFormattingHelpers.SuppressOperationComparer);
            return result.ToImmutable();
        }
 
        private void AddInitialSuppressOperations(
            SyntaxToken startToken, SyntaxToken endToken, SuppressOption mask, ArrayBuilder<SuppressOperation> result)
        {
            this.AddInitialSuppressOperations(startToken, mask, result);
            this.AddInitialSuppressOperations(endToken, mask, result);
        }
 
        private void AddInitialSuppressOperations(SyntaxToken token, SuppressOption mask, ArrayBuilder<SuppressOperation> result)
        {
            var startNode = token.Parent;
            var startPosition = token.SpanStart;
 
            // starting from given token, move up to root until the first meaningful
            // operation has found
            using var _ = ArrayBuilder<SuppressOperation>.GetInstance(out var buffer);
 
            var currentIndentationNode = startNode;
            while (currentIndentationNode != null)
            {
                _formattingRules.AddSuppressOperations(buffer, currentIndentationNode);
 
                buffer.RemoveAll(Predicate, (startPosition, _tokenStream, mask));
                if (buffer.Count > 0)
                {
                    result.AddRange(buffer);
                    return;
                }
 
                currentIndentationNode = currentIndentationNode.Parent;
            }
 
            return;
 
            static bool Predicate(SuppressOperation operation, (int startPosition, TokenStream tokenStream, SuppressOption mask) tuple)
            {
                if (!operation.TextSpan.Contains(tuple.startPosition))
                    return true;
 
                if (operation.ContainsElasticTrivia(tuple.tokenStream) && !operation.Option.IsOn(SuppressOption.IgnoreElasticWrapping))
                    return true;
 
                if (!operation.Option.IsMaskOn(tuple.mask))
                    return true;
 
                return false;
            }
        }
    }
}