File: Syntax\SyntaxNormalizer.cs
Web Access
Project: src\src\Compilers\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.csproj (Microsoft.CodeAnalysis.CSharp)
// 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.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp.Syntax
{
    internal class SyntaxNormalizer : CSharpSyntaxRewriter
    {
        private readonly TextSpan _consideredSpan;
        private readonly int _initialDepth;
        private readonly string _indentWhitespace;
        private readonly bool _useElasticTrivia;
        private readonly SyntaxTrivia _eolTrivia;
 
        private bool _isInStructuredTrivia;
 
        private SyntaxToken _previousToken;
 
        private bool _afterLineBreak;
        private bool _afterIndentation;
        private bool _inSingleLineInterpolation;
 
        // CONSIDER: if we become concerned about space, we shouldn't actually need any 
        // of the values between indentations[0] and indentations[initialDepth] (exclusive).
        private ArrayBuilder<SyntaxTrivia>? _indentations;
 
        private SyntaxNormalizer(TextSpan consideredSpan, int initialDepth, string indentWhitespace, string eolWhitespace, bool useElasticTrivia)
            : base(visitIntoStructuredTrivia: true)
        {
            _consideredSpan = consideredSpan;
            _initialDepth = initialDepth;
            _indentWhitespace = indentWhitespace;
            _useElasticTrivia = useElasticTrivia;
            _eolTrivia = useElasticTrivia ? SyntaxFactory.ElasticEndOfLine(eolWhitespace) : SyntaxFactory.EndOfLine(eolWhitespace);
            _afterLineBreak = true;
        }
 
        internal static TNode Normalize<TNode>(TNode node, string indentWhitespace, string eolWhitespace, bool useElasticTrivia = false)
            where TNode : SyntaxNode
        {
            var normalizer = new SyntaxNormalizer(node.FullSpan, GetDeclarationDepth(node), indentWhitespace, eolWhitespace, useElasticTrivia);
            var result = (TNode)normalizer.Visit(node);
            normalizer.Free();
            return result;
        }
 
        internal static SyntaxToken Normalize(SyntaxToken token, string indentWhitespace, string eolWhitespace, bool useElasticTrivia = false)
        {
            var normalizer = new SyntaxNormalizer(token.FullSpan, GetDeclarationDepth(token), indentWhitespace, eolWhitespace, useElasticTrivia);
            var result = normalizer.VisitToken(token);
            normalizer.Free();
            return result;
        }
 
        internal static SyntaxTriviaList Normalize(SyntaxTriviaList trivia, string indentWhitespace, string eolWhitespace, bool useElasticTrivia = false)
        {
            var normalizer = new SyntaxNormalizer(trivia.FullSpan, GetDeclarationDepth(trivia.Token), indentWhitespace, eolWhitespace, useElasticTrivia);
            var result = normalizer.RewriteTrivia(
                trivia,
                GetDeclarationDepth((SyntaxToken)trivia.ElementAt(0).Token),
                isTrailing: false,
                indentAfterLineBreak: false,
                mustHaveSeparator: false,
                lineBreaksAfter: 0);
            normalizer.Free();
            return result;
        }
 
        private void Free()
        {
            if (_indentations != null)
            {
                _indentations.Free();
            }
        }
 
        public override SyntaxToken VisitToken(SyntaxToken token)
        {
            if (token.Kind() == SyntaxKind.None || (token.IsMissing && token.FullWidth == 0))
            {
                return token;
            }
 
            try
            {
                var tk = token;
 
                var depth = GetDeclarationDepth(token);
 
                tk = tk.WithLeadingTrivia(RewriteTrivia(
                    token.LeadingTrivia,
                    depth,
                    isTrailing: false,
                    indentAfterLineBreak: NeedsIndentAfterLineBreak(token),
                    mustHaveSeparator: false,
                    lineBreaksAfter: 0));
 
                var nextToken = this.GetNextRelevantToken(token);
 
                _afterLineBreak = IsLineBreak(token);
                _afterIndentation = false;
 
                var lineBreaksAfter = LineBreaksAfter(token, nextToken);
                var needsSeparatorAfter = NeedsSeparator(token, nextToken);
                tk = tk.WithTrailingTrivia(RewriteTrivia(
                    token.TrailingTrivia,
                    depth,
                    isTrailing: true,
                    indentAfterLineBreak: false,
                    mustHaveSeparator: needsSeparatorAfter,
                    lineBreaksAfter: lineBreaksAfter));
 
                return tk;
            }
            finally
            {
                // to help debugging
                _previousToken = token;
            }
        }
 
        private SyntaxToken GetNextRelevantToken(SyntaxToken token)
        {
            // get next token, skipping zero width tokens except for end-of-directive tokens
            var nextToken = token.GetNextToken(
                t => SyntaxToken.NonZeroWidth(t) || t.Kind() == SyntaxKind.EndOfDirectiveToken,
                t => t.Kind() == SyntaxKind.SkippedTokensTrivia);
 
            if (_consideredSpan.Contains(nextToken.FullSpan))
            {
                return nextToken;
            }
            else
            {
                return default(SyntaxToken);
            }
        }
 
        private SyntaxTrivia GetIndentation(int count)
        {
            count = Math.Max(count - _initialDepth, 0);
 
            int capacity = count + 1;
            if (_indentations == null)
            {
                _indentations = ArrayBuilder<SyntaxTrivia>.GetInstance(capacity);
            }
            else
            {
                _indentations.EnsureCapacity(capacity);
            }
 
            // grow indentation collection if necessary
            for (int i = _indentations.Count; i <= count; i++)
            {
                string text = i == 0
                    ? ""
                    : _indentations[i - 1].ToString() + _indentWhitespace;
                _indentations.Add(_useElasticTrivia ? SyntaxFactory.ElasticWhitespace(text) : SyntaxFactory.Whitespace(text));
            }
 
            return _indentations[count];
        }
 
        private static bool NeedsIndentAfterLineBreak(SyntaxToken token)
        {
            return !token.IsKind(SyntaxKind.EndOfFileToken);
        }
 
        private int LineBreaksAfter(SyntaxToken currentToken, SyntaxToken nextToken)
        {
            if (_inSingleLineInterpolation)
            {
                return 0;
            }
 
            if (currentToken.IsKind(SyntaxKind.EndOfDirectiveToken))
            {
                return 1;
            }
 
            if (nextToken.Kind() == SyntaxKind.None)
            {
                return 0;
            }
 
            // none of the following tests currently have meaning for structured trivia
            if (_isInStructuredTrivia)
            {
                return 0;
            }
 
            if (nextToken.IsKind(SyntaxKind.CloseBraceToken))
            {
                if (IsAccessorListWithoutAccessorsWithBlockBody(currentToken.Parent?.Parent))
                {
                    return 0;
                }
 
                if (nextToken.Parent is InitializerExpressionSyntax or AnonymousObjectCreationExpressionSyntax &&
                    !IsSingleLineInitializerContext(nextToken.Parent))
                {
                    return 1;
                }
            }
 
            switch (currentToken.Kind())
            {
                case SyntaxKind.None:
                    return 0;
 
                case SyntaxKind.OpenBraceToken:
                    return LineBreaksAfterOpenBrace(currentToken);
 
                case SyntaxKind.FinallyKeyword:
                    return 1;
 
                case SyntaxKind.CloseBraceToken:
                    return LineBreaksAfterCloseBrace(currentToken, nextToken);
 
                case SyntaxKind.CloseParenToken:
                    if (currentToken.Parent is PositionalPatternClauseSyntax)
                    {
                        // don't break inside a recursive pattern
                        return 0;
                    }
 
                    if (nextToken.IsKind(SyntaxKind.OpenBraceToken) &&
                        IsInitializerInSingleLineContext(nextToken.Parent))
                    {
                        // Don't break before an open brace of an initializer when inside single-line.
                        // Initializers in such context are not expected to be large,
                        // so formatting them in single-line fashion looks more compact.
                        return 0;
                    }
 
                    // Note: the `where` case handles constraints on method declarations
                    //  and also `where` clauses (consistently with other LINQ cases below)
                    return (((currentToken.Parent is StatementSyntax) && nextToken.Parent != currentToken.Parent)
                        || nextToken.Kind() == SyntaxKind.OpenBraceToken
                        || nextToken.Kind() == SyntaxKind.WhereKeyword) ? 1 : 0;
 
                case SyntaxKind.CloseBracketToken:
                    if (currentToken.Parent is AttributeListSyntax && currentToken.Parent.Parent is not ParameterSyntax)
                    {
                        return 1;
                    }
                    break;
 
                case SyntaxKind.SemicolonToken:
                    return LineBreaksAfterSemicolon(currentToken, nextToken);
 
                case SyntaxKind.CommaToken:
                    if (currentToken.Parent is InitializerExpressionSyntax or AnonymousObjectCreationExpressionSyntax &&
                        !IsSingleLineInitializerContext(nextToken.Parent))
                    {
                        return 1;
                    }
                    return currentToken.Parent is EnumDeclarationSyntax or SwitchExpressionSyntax ? 1 : 0;
                case SyntaxKind.ElseKeyword:
                    return nextToken.Kind() != SyntaxKind.IfKeyword ? 1 : 0;
                case SyntaxKind.ColonToken:
                    if (currentToken.Parent is LabeledStatementSyntax || currentToken.Parent is SwitchLabelSyntax)
                    {
                        return 1;
                    }
                    break;
                case SyntaxKind.SwitchKeyword when currentToken.Parent is SwitchExpressionSyntax:
                    return 1;
            }
 
            if ((nextToken.IsKind(SyntaxKind.FromKeyword) && nextToken.Parent.IsKind(SyntaxKind.FromClause)) ||
                (nextToken.IsKind(SyntaxKind.LetKeyword) && nextToken.Parent.IsKind(SyntaxKind.LetClause)) ||
                (nextToken.IsKind(SyntaxKind.WhereKeyword) && nextToken.Parent.IsKind(SyntaxKind.WhereClause)) ||
                (nextToken.IsKind(SyntaxKind.JoinKeyword) && nextToken.Parent.IsKind(SyntaxKind.JoinClause)) ||
                (nextToken.IsKind(SyntaxKind.JoinKeyword) && nextToken.Parent.IsKind(SyntaxKind.JoinIntoClause)) ||
                (nextToken.IsKind(SyntaxKind.OrderByKeyword) && nextToken.Parent.IsKind(SyntaxKind.OrderByClause)) ||
                (nextToken.IsKind(SyntaxKind.SelectKeyword) && nextToken.Parent.IsKind(SyntaxKind.SelectClause)) ||
                (nextToken.IsKind(SyntaxKind.GroupKeyword) && nextToken.Parent.IsKind(SyntaxKind.GroupClause)))
            {
                return 1;
            }
 
            switch (nextToken.Kind())
            {
                case SyntaxKind.OpenBraceToken:
                    return LineBreaksBeforeOpenBrace(nextToken);
                case SyntaxKind.CloseBraceToken:
                    return LineBreaksBeforeCloseBrace(nextToken);
                case SyntaxKind.ElseKeyword:
                case SyntaxKind.FinallyKeyword:
                    return 1;
                case SyntaxKind.OpenBracketToken:
                    return (nextToken.Parent is AttributeListSyntax && !(nextToken.Parent.Parent is ParameterSyntax)) ? 1 : 0;
                case SyntaxKind.WhereKeyword:
                    return currentToken.Parent is TypeParameterListSyntax ? 1 : 0;
            }
 
            return 0;
        }
 
        private static bool IsAccessorListWithoutAccessorsWithBlockBody(SyntaxNode? node)
            => node is AccessorListSyntax accessorList &&
                accessorList.Accessors.All(a => a.Body == null);
 
        private static bool IsAccessorListFollowedByInitializer([NotNullWhen(true)] SyntaxNode? node)
            => node is AccessorListSyntax { Parent: PropertyDeclarationSyntax { Initializer: not null } };
 
        private static int LineBreaksBeforeOpenBrace(SyntaxToken openBraceToken)
        {
            Debug.Assert(openBraceToken.IsKind(SyntaxKind.OpenBraceToken));
            var parent = openBraceToken.Parent;
            if (parent.IsKind(SyntaxKind.Interpolation) ||
                parent is PropertyPatternClauseSyntax ||
                IsAccessorListWithoutAccessorsWithBlockBody(parent) ||
                IsInitializerInSingleLineContext(parent))
            {
                return 0;
            }
            else
            {
                return 1;
            }
        }
 
        private static int LineBreaksBeforeCloseBrace(SyntaxToken closeBraceToken)
        {
            Debug.Assert(closeBraceToken.IsKind(SyntaxKind.CloseBraceToken));
            var parent = closeBraceToken.Parent;
            if (parent.IsKind(SyntaxKind.Interpolation) ||
                parent is PropertyPatternClauseSyntax ||
                IsInitializerInSingleLineContext(parent))
            {
                return 0;
            }
            else
            {
                return 1;
            }
        }
 
        private static int LineBreaksAfterOpenBrace(SyntaxToken openBraceToken)
        {
            var parent = openBraceToken.Parent;
            if (parent is PropertyPatternClauseSyntax ||
                parent.IsKind(SyntaxKind.Interpolation) ||
                IsAccessorListWithoutAccessorsWithBlockBody(parent) ||
                IsInitializerInSingleLineContext(parent))
            {
                return 0;
            }
            else
            {
                return 1;
            }
        }
 
        private static int LineBreaksAfterCloseBrace(SyntaxToken currentToken, SyntaxToken nextToken)
        {
            var currentTokenParent = currentToken.Parent;
            if (currentTokenParent is SwitchExpressionSyntax or PropertyPatternClauseSyntax ||
                currentTokenParent.IsKind(SyntaxKind.Interpolation) ||
                currentTokenParent?.Parent is AnonymousFunctionExpressionSyntax ||
                IsAccessorListFollowedByInitializer(currentTokenParent) ||
                isCloseBraceFollowedByCommaOrSemicolon(currentToken, nextToken) || // Typical case: `var a = new A { X = new B { }, <- here }; <- and here`. Should emit no breaks regardless of whether in multiline mode or not
                nextToken.Parent is MemberAccessExpressionSyntax or BracketedArgumentListSyntax || // Typical cases: `new [] { ... }.Length` or `new [] { ... }[0]`. When in multiline mode still want to keep them on the same line as closing brace
                IsInitializerInSingleLineContext(currentTokenParent))
            {
                return 0;
            }
 
            // If we are at the end of a single-line property followed by another single-line property
            // group them together by having only 1 line break.
            // The current token here is a closing brace of an accessor list:
            // public int Prop { get; } <-- this one
            if (currentTokenParent?.Parent is PropertyDeclarationSyntax property && IsSingleLineProperty(property) &&
                nextToken.Parent is PropertyDeclarationSyntax nextProperty && IsSingleLineProperty(nextProperty))
            {
                return 1;
            }
 
            var kind = nextToken.Kind();
            switch (kind)
            {
                case SyntaxKind.EndOfFileToken:
                case SyntaxKind.CloseBraceToken:
                case SyntaxKind.CatchKeyword:
                case SyntaxKind.FinallyKeyword:
                case SyntaxKind.ElseKeyword:
                    return 1;
                default:
                    if (kind == SyntaxKind.WhileKeyword &&
                        nextToken.Parent.IsKind(SyntaxKind.DoStatement))
                    {
                        return 1;
                    }
                    else
                    {
                        return 2;
                    }
            }
 
            static bool isCloseBraceFollowedByCommaOrSemicolon(SyntaxToken currentToken, SyntaxToken nextToken)
                => currentToken.IsKind(SyntaxKind.CloseBraceToken) &&
                   nextToken.Kind() is SyntaxKind.CommaToken or SyntaxKind.SemicolonToken;
        }
 
        private static int LineBreaksAfterSemicolon(SyntaxToken currentToken, SyntaxToken nextToken)
        {
            if (currentToken.Parent.IsKind(SyntaxKind.ForStatement))
            {
                return 0;
            }
            else if (nextToken.Kind() == SyntaxKind.CloseBraceToken)
            {
                return 1;
            }
            else if (currentToken.Parent.IsKind(SyntaxKind.UsingDirective))
            {
                return nextToken.Parent.IsKind(SyntaxKind.UsingDirective) ? 1 : 2;
            }
            else if (currentToken.Parent.IsKind(SyntaxKind.ExternAliasDirective))
            {
                return nextToken.Parent.IsKind(SyntaxKind.ExternAliasDirective) ? 1 : 2;
            }
            else if (currentToken.Parent is AccessorDeclarationSyntax &&
                IsAccessorListWithoutAccessorsWithBlockBody(currentToken.Parent.Parent))
            {
                return 0;
            }
            else if (currentToken.Parent is PropertyDeclarationSyntax property)
            {
                // If the current semicolon token belongs to a property
                // then it is a semicolon at the end of a property typically (but not always) after a property initializer:
                // public int Prop { get; } = 1; <-- this one
                // public int Prop { get; }; <-- this produces a syntax error, but the semicolon is still attached to the property
                // In such cases we need to have 2 line breaks in order to have proper separation between members of a class, struct etc.
                // The only exception is when the next token starts a new single-line property.
                // In such case we want to group these properties together by having only 1 line break.
                // Note: case, when the property is the last member and needs only 1 line break after it is handled above (the next token is a closing brace then)
                Debug.Assert(((PropertyDeclarationSyntax)currentToken.Parent).SemicolonToken == currentToken);
 
                if (IsSingleLineProperty(property) &&
                    nextToken.Parent is PropertyDeclarationSyntax nextProperty &&
                    IsSingleLineProperty(nextProperty))
                {
                    return 1;
                }
                else
                {
                    return 2;
                }
            }
            else
            {
                return 1;
            }
        }
 
        private static bool NeedsSeparatorForPropertyPattern(SyntaxToken token, SyntaxToken next)
        {
            PropertyPatternClauseSyntax? propPattern;
            if (token.Parent.IsKind(SyntaxKind.PropertyPatternClause))
            {
                propPattern = (PropertyPatternClauseSyntax)token.Parent;
            }
            else if (next.Parent.IsKind(SyntaxKind.PropertyPatternClause))
            {
                propPattern = (PropertyPatternClauseSyntax)next.Parent;
            }
            else
            {
                return false;
            }
 
            var tokenIsOpenBrace = token.IsKind(SyntaxKind.OpenBraceToken);
            var nextIsOpenBrace = next.IsKind(SyntaxKind.OpenBraceToken);
            var tokenIsCloseBrace = token.IsKind(SyntaxKind.CloseBraceToken);
            var nextIsCloseBrace = next.IsKind(SyntaxKind.CloseBraceToken);
 
            //inner
            if (tokenIsOpenBrace)
            {
                return true;
            }
            if (nextIsCloseBrace)
            {
                return true;
            }
 
            if (propPattern.Parent is RecursivePatternSyntax rps)
            {
                //outer
                if (nextIsOpenBrace)
                {
                    if (rps.Type != null || rps.PositionalPatternClause != null)
                    {
                        return true;
                    }
                    else
                    {
                        return false;
                    }
                }
                if (tokenIsCloseBrace)
                {
                    if (rps.Designation is null)
                    {
                        return false;
                    }
                    else
                    {
                        return true;
                    }
                }
            }
            return false;
        }
 
        private static bool NeedsSeparatorForPositionalPattern(SyntaxToken token, SyntaxToken next)
        {
            PositionalPatternClauseSyntax? posPattern;
            if (token.Parent.IsKind(SyntaxKind.PositionalPatternClause))
            {
                posPattern = (PositionalPatternClauseSyntax)token.Parent;
            }
            else if (next.Parent.IsKind(SyntaxKind.PositionalPatternClause))
            {
                posPattern = (PositionalPatternClauseSyntax)next.Parent;
            }
            else
            {
                return false;
            }
 
            var tokenIsOpenParen = token.IsKind(SyntaxKind.OpenParenToken);
            var nextIsOpenParen = next.IsKind(SyntaxKind.OpenParenToken);
            var tokenIsCloseParen = token.IsKind(SyntaxKind.CloseParenToken);
            var nextIsCloseParen = next.IsKind(SyntaxKind.CloseParenToken);
 
            //inner
            if (tokenIsOpenParen)
            {
                return false;
            }
            if (nextIsCloseParen)
            {
                return false;
            }
 
            if (posPattern.Parent is RecursivePatternSyntax rps)
            {
                //outer
                if (nextIsOpenParen)
                {
                    if (rps.Type != null)
                    {
                        return true;
                    }
                    else
                    {
                        return false;
                    }
                }
                if (tokenIsCloseParen)
                {
                    if (rps.PropertyPatternClause is not null)
                    {
                        return false;
                    }
                    if (rps.Designation is null)
                    {
                        return false;
                    }
                    else
                    {
                        return true;
                    }
                }
            }
            return false;
        }
 
        private static bool NeedsSeparatorForListPattern(SyntaxToken token, SyntaxToken next)
        {
            var listPattern = token.Parent as ListPatternSyntax ?? next.Parent as ListPatternSyntax;
            if (listPattern == null)
            {
                return false;
            }
 
            // is$$[1, 2]
            if (next.IsKind(SyntaxKind.OpenBracketToken))
            {
                return true;
            }
 
            // is [1, 2]$$list
            if (token.IsKind(SyntaxKind.OpenBracketToken))
            {
                return listPattern.Designation is not null;
            }
 
            return false;
        }
 
        private static bool NeedsSeparator(SyntaxToken token, SyntaxToken next)
        {
            if (token.Parent == null || next.Parent == null)
            {
                return false;
            }
 
            if (IsAccessorListWithoutAccessorsWithBlockBody(next.Parent) ||
                IsAccessorListWithoutAccessorsWithBlockBody(next.Parent.Parent))
            {
                // when the accessors are formatted inline, the separator is needed
                // unless there is a semicolon. For example: "{ get; set; }" 
                return !next.IsKind(SyntaxKind.SemicolonToken);
            }
 
            if (IsXmlTextToken(token.Kind()) || IsXmlTextToken(next.Kind()))
            {
                return false;
            }
 
            if (next.Kind() == SyntaxKind.EndOfDirectiveToken)
            {
                // In a directive, there's often no token between the directive keyword and 
                // the end-of-directive, so we may need a separator.
                return IsKeyword(token.Kind()) && next.LeadingWidth > 0;
            }
 
            if ((token.Parent is AssignmentExpressionSyntax && AssignmentTokenNeedsSeparator(token.Kind())) ||
                (next.Parent is AssignmentExpressionSyntax && AssignmentTokenNeedsSeparator(next.Kind())) ||
                (token.Parent is BinaryExpressionSyntax && BinaryTokenNeedsSeparator(token.Kind())) ||
                (next.Parent is BinaryExpressionSyntax && BinaryTokenNeedsSeparator(next.Kind())))
            {
                return true;
            }
 
            if (token.IsKind(SyntaxKind.GreaterThanToken) && token.Parent.IsKind(SyntaxKind.TypeArgumentList))
            {
                if (!SyntaxFacts.IsPunctuation(next.Kind()))
                {
                    return true;
                }
            }
 
            if (token.IsKind(SyntaxKind.GreaterThanToken) &&
                token.Parent.IsKind(SyntaxKind.FunctionPointerParameterList) &&
                token.Parent.Parent?.Parent is not UsingDirectiveSyntax)
            {
                return true;
            }
 
            if (token.IsKind(SyntaxKind.CommaToken) &&
                !next.IsKind(SyntaxKind.CommaToken) &&
                !token.Parent.IsKind(SyntaxKind.EnumDeclaration))
            {
                return true;
            }
 
            if (token.Kind() == SyntaxKind.SemicolonToken
                && !(next.Kind() == SyntaxKind.SemicolonToken || next.Kind() == SyntaxKind.CloseParenToken))
            {
                return true;
            }
 
            if (next.IsKind(SyntaxKind.SwitchKeyword) && next.Parent is SwitchExpressionSyntax)
            {
                return true;
            }
 
            if (token.IsKind(SyntaxKind.QuestionToken))
            {
                if (token.Parent.IsKind(SyntaxKind.ConditionalExpression) || token.Parent is TypeSyntax)
                {
                    if (token.Parent.Parent?.Kind() is not SyntaxKind.TypeArgumentList and not SyntaxKind.UsingDirective)
                    {
                        return true;
                    }
                }
            }
 
            if (token.IsKind(SyntaxKind.ColonToken))
            {
                return !token.Parent.IsKind(SyntaxKind.InterpolationFormatClause) &&
                    !token.Parent.IsKind(SyntaxKind.XmlPrefix);
            }
 
            if (next.IsKind(SyntaxKind.ColonToken))
            {
                if (next.Parent.IsKind(SyntaxKind.BaseList) ||
                    next.Parent.IsKind(SyntaxKind.TypeParameterConstraintClause) ||
                    next.Parent is ConstructorInitializerSyntax)
                {
                    return true;
                }
            }
 
            if (token.IsKind(SyntaxKind.CloseBracketToken) && IsWord(next.Kind()))
            {
                return true;
            }
 
            // We don't want to add extra space after cast, we want space only after tuple
            if (token.IsKind(SyntaxKind.CloseParenToken) && IsWord(next.Kind()) && token.Parent.IsKind(SyntaxKind.TupleType) == true)
            {
                return true;
            }
 
            if ((next.IsKind(SyntaxKind.QuestionToken) || next.IsKind(SyntaxKind.ColonToken))
                && (next.Parent.IsKind(SyntaxKind.ConditionalExpression)))
            {
                return true;
            }
 
            if (token.IsKind(SyntaxKind.EqualsToken))
            {
                return !token.Parent.IsKind(SyntaxKind.XmlTextAttribute);
            }
 
            if (next.IsKind(SyntaxKind.EqualsToken))
            {
                return !next.Parent.IsKind(SyntaxKind.XmlTextAttribute);
            }
 
            // Rules for function pointer below are taken from:
            // https://github.com/dotnet/roslyn/blob/1cca63b5d8ea170f8d8e88e1574aa3ebe354c23b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/SpacingFormattingRule.cs#L321-L413
            if (token.Parent.IsKind(SyntaxKind.FunctionPointerType))
            {
                // No spacing between delegate and *
                if (next.IsKind(SyntaxKind.AsteriskToken) && token.IsKind(SyntaxKind.DelegateKeyword))
                {
                    return false;
                }
 
                // Force a space between * and the calling convention
                if (token.IsKind(SyntaxKind.AsteriskToken) && next.Parent.IsKind(SyntaxKind.FunctionPointerCallingConvention))
                {
                    switch (next.Kind())
                    {
                        case SyntaxKind.IdentifierToken:
                        case SyntaxKind.ManagedKeyword:
                        case SyntaxKind.UnmanagedKeyword:
                            return true;
                    }
                }
            }
 
            if (next.Parent.IsKind(SyntaxKind.FunctionPointerParameterList) && next.IsKind(SyntaxKind.LessThanToken))
            {
                switch (token.Kind())
                {
                    // No spacing between the * and < tokens if there is no calling convention
                    case SyntaxKind.AsteriskToken:
                    // No spacing between the calling convention and opening angle bracket of function pointer types:
                    // delegate* managed<
                    case SyntaxKind.ManagedKeyword:
                    case SyntaxKind.UnmanagedKeyword:
                    // No spacing between the calling convention specifier and the opening angle
                    // delegate* unmanaged[Cdecl]<
                    case SyntaxKind.CloseBracketToken when token.Parent.IsKind(SyntaxKind.FunctionPointerUnmanagedCallingConventionList):
                        return false;
                }
            }
 
            // No space between unmanaged and the [
            // delegate* unmanaged[
            if (token.Parent.IsKind(SyntaxKind.FunctionPointerCallingConvention) && next.Parent.IsKind(SyntaxKind.FunctionPointerUnmanagedCallingConventionList) &&
                next.IsKind(SyntaxKind.OpenBracketToken))
            {
                return false;
            }
 
            // Function pointer calling convention adjustments
            if (next.Parent.IsKind(SyntaxKind.FunctionPointerUnmanagedCallingConventionList) && token.Parent.IsKind(SyntaxKind.FunctionPointerUnmanagedCallingConventionList))
            {
                if (next.IsKind(SyntaxKind.IdentifierToken))
                {
                    if (token.IsKind(SyntaxKind.OpenBracketToken))
                    {
                        return false;
                    }
                    // Space after the ,
                    // unmanaged[Cdecl, Thiscall
                    else if (token.IsKind(SyntaxKind.CommaToken))
                    {
                        return true;
                    }
                }
 
                // No space between identifier and comma
                // unmanaged[Cdecl,
                if (next.IsKind(SyntaxKind.CommaToken))
                {
                    return false;
                }
 
                // No space before the ]
                // unmanaged[Cdecl]
                if (next.IsKind(SyntaxKind.CloseBracketToken))
                {
                    return false;
                }
            }
 
            // No space after the < in function pointer parameter lists
            // delegate*<void
            if (token.IsKind(SyntaxKind.LessThanToken) && token.Parent.IsKind(SyntaxKind.FunctionPointerParameterList))
            {
                return false;
            }
 
            // No space before the > in function pointer parameter lists
            // delegate*<void>
            if (next.IsKind(SyntaxKind.GreaterThanToken) && next.Parent.IsKind(SyntaxKind.FunctionPointerParameterList))
            {
                return false;
            }
 
            if (token.IsKind(SyntaxKind.EqualsGreaterThanToken) || next.IsKind(SyntaxKind.EqualsGreaterThanToken))
            {
                return true;
            }
 
            // Can happen in directives (e.g. #line 1 "file")
            if (SyntaxFacts.IsLiteral(token.Kind()) && SyntaxFacts.IsLiteral(next.Kind()))
            {
                return true;
            }
 
            // No space before an asterisk that's part of a PointerTypeSyntax.
            if (next.IsKind(SyntaxKind.AsteriskToken) && next.Parent is PointerTypeSyntax)
            {
                return false;
            }
 
            // The last asterisk of a pointer declaration should be followed by a space.
            if (token.IsKind(SyntaxKind.AsteriskToken) && token.Parent is PointerTypeSyntax &&
                (next.IsKind(SyntaxKind.IdentifierToken) || next.Parent.IsKind(SyntaxKind.IndexerDeclaration)))
            {
                return true;
            }
 
            // Rules for single-line initializer syntax inside single-line context:
            // 1. Separator before open brace token
            // 2. Separator after open brace token
            // 3. Separator before close brace token
            // e.g. `$"{new SomeClass() { A = 2 }}"`, [SomeAttribute(new int[] { 1, 2, 3 })] or `MethodCall(new Arg { A = 1, B = 2 })`
            // Initializers in such context are not expected to be large,
            // so formatting them in single-line fashion looks more compact.
            if (IsSingleLineInitializerContext(token.Parent))
            {
                if (next.Parent is InitializerExpressionSyntax or AnonymousObjectCreationExpressionSyntax &&
                    next.IsKind(SyntaxKind.OpenBraceToken))
                {
                    return true;
                }
 
                if (token.Parent is InitializerExpressionSyntax or AnonymousObjectCreationExpressionSyntax &&
                    token.IsKind(SyntaxKind.OpenBraceToken))
                {
                    return true;
                }
 
                if (next.Parent is InitializerExpressionSyntax or AnonymousObjectCreationExpressionSyntax &&
                    next.IsKind(SyntaxKind.CloseBraceToken))
                {
                    return true;
                }
            }
 
            // Require a separator between a lambda return type and its open paren
            if (next is { RawKind: (int)SyntaxKind.OpenParenToken, Parent.Parent: ParenthesizedLambdaExpressionSyntax lambda } &&
                lambda.ReturnType?.GetLastToken() == token)
            {
                return true;
            }
 
            if (IsKeyword(token.Kind()))
            {
                if (!next.IsKind(SyntaxKind.ColonToken) &&
                    !next.IsKind(SyntaxKind.DotToken) &&
                    !next.IsKind(SyntaxKind.QuestionToken) &&
                    !next.IsKind(SyntaxKind.SemicolonToken) &&
                    !next.IsKind(SyntaxKind.OpenBracketToken) &&
                    (!next.IsKind(SyntaxKind.OpenParenToken) || KeywordNeedsSeparatorBeforeOpenParen(token.Kind()) || next.Parent.IsKind(SyntaxKind.TupleType)) &&
                    !next.IsKind(SyntaxKind.CloseParenToken) &&
                    !next.IsKind(SyntaxKind.CloseBraceToken) &&
                    !next.IsKind(SyntaxKind.ColonColonToken) &&
                    !next.IsKind(SyntaxKind.GreaterThanToken) &&
                    !next.IsKind(SyntaxKind.CommaToken))
                {
                    return true;
                }
            }
 
            if (IsWordOrLiteral(token.Kind()) && IsWordOrLiteral(next.Kind()))
            {
                return true;
            }
            else if (token.Width > 1 && next.Width > 1)
            {
                var tokenLastChar = token.Text.Last();
                var nextFirstChar = next.Text.First();
                if (tokenLastChar == nextFirstChar && TokenCharacterCanBeDoubled(tokenLastChar))
                {
                    return true;
                }
            }
 
            if (token.Parent is RelationalPatternSyntax)
            {
                //>, >=, <, <=
                return true;
            }
 
            switch (next.Kind())
            {
                case SyntaxKind.AndKeyword:
                case SyntaxKind.OrKeyword:
                    return true;
            }
 
            switch (token.Kind())
            {
                case SyntaxKind.AndKeyword:
                case SyntaxKind.OrKeyword:
                case SyntaxKind.NotKeyword:
                    return true;
            }
 
            if (NeedsSeparatorForPropertyPattern(token, next))
            {
                return true;
            }
 
            if (NeedsSeparatorForPositionalPattern(token, next))
            {
                return true;
            }
 
            if (NeedsSeparatorForListPattern(token, next))
            {
                return true;
            }
 
            switch (token.Parent.Kind(), next.Parent.Kind())
            {
                case (SyntaxKind.LineSpanDirectiveTrivia, SyntaxKind.LineDirectivePosition):
                case (SyntaxKind.LineDirectivePosition, SyntaxKind.LineSpanDirectiveTrivia):
                    return true;
            }
 
            return false;
        }
 
        private static bool KeywordNeedsSeparatorBeforeOpenParen(SyntaxKind kind)
        {
            switch (kind)
            {
                case SyntaxKind.TypeOfKeyword:
                case SyntaxKind.DefaultKeyword:
                case SyntaxKind.NewKeyword:
                case SyntaxKind.BaseKeyword:
                case SyntaxKind.ThisKeyword:
                case SyntaxKind.CheckedKeyword:
                case SyntaxKind.UncheckedKeyword:
                case SyntaxKind.SizeOfKeyword:
                case SyntaxKind.ArgListKeyword:
                    return false;
                default:
                    return true;
            }
        }
 
        private static bool IsXmlTextToken(SyntaxKind kind)
        {
            switch (kind)
            {
                case SyntaxKind.XmlTextLiteralNewLineToken:
                case SyntaxKind.XmlTextLiteralToken:
                    return true;
                default:
                    return false;
            }
        }
 
        private static bool BinaryTokenNeedsSeparator(SyntaxKind kind)
        {
            switch (kind)
            {
                case SyntaxKind.DotToken:
                case SyntaxKind.MinusGreaterThanToken:
                    return false;
                default:
                    return SyntaxFacts.GetBinaryExpression(kind) != SyntaxKind.None;
            }
        }
 
        private static bool AssignmentTokenNeedsSeparator(SyntaxKind kind)
        {
            return SyntaxFacts.GetAssignmentExpression(kind) != SyntaxKind.None;
        }
 
        private SyntaxTriviaList RewriteTrivia(
            SyntaxTriviaList triviaList,
            int depth,
            bool isTrailing,
            bool indentAfterLineBreak,
            bool mustHaveSeparator,
            int lineBreaksAfter)
        {
            ArrayBuilder<SyntaxTrivia> currentTriviaList = ArrayBuilder<SyntaxTrivia>.GetInstance(triviaList.Count);
            try
            {
                foreach (var trivia in triviaList)
                {
                    if (trivia.IsKind(SyntaxKind.WhitespaceTrivia) ||
                        trivia.IsKind(SyntaxKind.EndOfLineTrivia) ||
                        trivia.FullWidth == 0)
                    {
                        continue;
                    }
 
                    var needsSeparator =
                        (currentTriviaList.Count > 0 && NeedsSeparatorBetween(currentTriviaList.Last())) ||
                            (currentTriviaList.Count == 0 && isTrailing);
 
                    var needsLineBreak = NeedsLineBreakBefore(trivia, isTrailing)
                        || (currentTriviaList.Count > 0 && NeedsLineBreakBetween(currentTriviaList.Last(), trivia, isTrailing));
 
                    if (needsLineBreak && !_afterLineBreak)
                    {
                        currentTriviaList.Add(GetEndOfLine());
                        _afterLineBreak = true;
                        _afterIndentation = false;
                    }
 
                    if (_afterLineBreak)
                    {
                        if (!_afterIndentation && NeedsIndentAfterLineBreak(trivia))
                        {
                            currentTriviaList.Add(this.GetIndentation(GetDeclarationDepth(trivia)));
                            _afterIndentation = true;
                        }
                    }
                    else if (needsSeparator)
                    {
                        currentTriviaList.Add(GetSpace());
                        _afterLineBreak = false;
                        _afterIndentation = false;
                    }
 
                    if (trivia.HasStructure)
                    {
                        var tr = this.VisitStructuredTrivia(trivia);
                        currentTriviaList.Add(tr);
                    }
                    else if (trivia.IsKind(SyntaxKind.DocumentationCommentExteriorTrivia))
                    {
                        // recreate exterior to remove any leading whitespace
                        currentTriviaList.Add(s_trimmedDocCommentExterior);
                    }
                    else
                    {
                        currentTriviaList.Add(trivia);
                    }
 
                    if (NeedsLineBreakAfter(trivia, isTrailing)
                        && (currentTriviaList.Count == 0 || !EndsInLineBreak(currentTriviaList.Last())))
                    {
                        currentTriviaList.Add(GetEndOfLine());
                        _afterLineBreak = true;
                        _afterIndentation = false;
                    }
                }
 
                if (lineBreaksAfter > 0)
                {
                    if (currentTriviaList.Count > 0
                        && EndsInLineBreak(currentTriviaList.Last()))
                    {
                        lineBreaksAfter--;
                    }
 
                    for (int i = 0; i < lineBreaksAfter; i++)
                    {
                        currentTriviaList.Add(GetEndOfLine());
                        _afterLineBreak = true;
                        _afterIndentation = false;
                    }
                }
                else if (indentAfterLineBreak && _afterLineBreak && !_afterIndentation)
                {
                    currentTriviaList.Add(this.GetIndentation(depth));
                    _afterIndentation = true;
                }
                else if (mustHaveSeparator)
                {
                    currentTriviaList.Add(GetSpace());
                    _afterLineBreak = false;
                    _afterIndentation = false;
                }
 
                if (currentTriviaList.Count == 0)
                {
                    return default(SyntaxTriviaList);
                }
                else if (currentTriviaList.Count == 1)
                {
                    return SyntaxFactory.TriviaList(currentTriviaList.First());
                }
                else
                {
                    return SyntaxFactory.TriviaList(currentTriviaList);
                }
            }
            finally
            {
                currentTriviaList.Free();
            }
        }
 
        private static readonly SyntaxTrivia s_trimmedDocCommentExterior = SyntaxFactory.DocumentationCommentExterior("///");
 
        private SyntaxTrivia GetSpace()
        {
            return _useElasticTrivia ? SyntaxFactory.ElasticSpace : SyntaxFactory.Space;
        }
 
        private SyntaxTrivia GetEndOfLine()
        {
            return _eolTrivia;
        }
 
        private SyntaxTrivia VisitStructuredTrivia(SyntaxTrivia trivia)
        {
            bool oldIsInStructuredTrivia = _isInStructuredTrivia;
            _isInStructuredTrivia = true;
 
            SyntaxToken oldPreviousToken = _previousToken;
            _previousToken = default(SyntaxToken);
 
            SyntaxTrivia result = VisitTrivia(trivia);
 
            _isInStructuredTrivia = oldIsInStructuredTrivia;
            _previousToken = oldPreviousToken;
 
            return result;
        }
 
        private static bool NeedsSeparatorBetween(SyntaxTrivia trivia)
        {
            switch (trivia.Kind())
            {
                case SyntaxKind.None:
                case SyntaxKind.WhitespaceTrivia:
                case SyntaxKind.DocumentationCommentExteriorTrivia:
                    return false;
                default:
                    return !SyntaxFacts.IsPreprocessorDirective(trivia.Kind());
            }
        }
 
        private static bool NeedsLineBreakBetween(SyntaxTrivia trivia, SyntaxTrivia next, bool isTrailingTrivia)
        {
            return NeedsLineBreakAfter(trivia, isTrailingTrivia)
                || NeedsLineBreakBefore(next, isTrailingTrivia);
        }
 
        private static bool NeedsLineBreakBefore(SyntaxTrivia trivia, bool isTrailingTrivia)
        {
            var kind = trivia.Kind();
            switch (kind)
            {
                case SyntaxKind.DocumentationCommentExteriorTrivia:
                    return !isTrailingTrivia;
                default:
                    return SyntaxFacts.IsPreprocessorDirective(kind);
            }
        }
 
        private static bool NeedsLineBreakAfter(SyntaxTrivia trivia, bool isTrailingTrivia)
        {
            var kind = trivia.Kind();
            switch (kind)
            {
                case SyntaxKind.SingleLineCommentTrivia:
                    return true;
                case SyntaxKind.MultiLineCommentTrivia:
                    return !isTrailingTrivia;
                default:
                    return SyntaxFacts.IsPreprocessorDirective(kind);
            }
        }
 
        private static bool NeedsIndentAfterLineBreak(SyntaxTrivia trivia)
        {
            switch (trivia.Kind())
            {
                case SyntaxKind.SingleLineCommentTrivia:
                case SyntaxKind.MultiLineCommentTrivia:
                case SyntaxKind.DocumentationCommentExteriorTrivia:
                case SyntaxKind.SingleLineDocumentationCommentTrivia:
                case SyntaxKind.MultiLineDocumentationCommentTrivia:
                    return true;
                default:
                    return false;
            }
        }
 
        private static bool IsLineBreak(SyntaxToken token)
        {
            return token.Kind() == SyntaxKind.XmlTextLiteralNewLineToken;
        }
 
        private static bool EndsInLineBreak(SyntaxTrivia trivia)
        {
            if (trivia.Kind() == SyntaxKind.EndOfLineTrivia)
            {
                return true;
            }
 
            if (trivia.Kind() == SyntaxKind.PreprocessingMessageTrivia || trivia.Kind() == SyntaxKind.DisabledTextTrivia)
            {
                var text = trivia.ToFullString();
                return text.Length > 0 && SyntaxFacts.IsNewLine(text.Last());
            }
 
            if (trivia.HasStructure)
            {
                var node = trivia.GetStructure()!;
                var trailing = node.GetTrailingTrivia();
                if (trailing.Count > 0)
                {
                    return EndsInLineBreak(trailing.Last());
                }
                else
                {
                    return IsLineBreak(node.GetLastToken());
                }
            }
 
            return false;
        }
 
        private static bool IsWord(SyntaxKind kind)
        {
            return kind == SyntaxKind.IdentifierToken || IsKeyword(kind);
        }
 
        private static bool IsWordOrLiteral(SyntaxKind kind)
        {
            return SyntaxFacts.IsLiteral(kind)
                || IsKeyword(kind)
                || kind == SyntaxKind.InterpolatedStringEndToken
                || kind == SyntaxKind.InterpolatedRawStringEndToken;
        }
 
        private static bool IsKeyword(SyntaxKind kind)
        {
            return SyntaxFacts.IsKeywordKind(kind) || SyntaxFacts.IsPreprocessorKeyword(kind);
        }
 
        private static bool TokenCharacterCanBeDoubled(char c)
        {
            switch (c)
            {
                case '+':
                case '-':
                case '<':
                case ':':
                case '?':
                case '=':
                case '"':
                    return true;
                default:
                    return false;
            }
        }
 
        private static int GetDeclarationDepth(SyntaxToken token)
        {
            return GetDeclarationDepth(token.Parent);
        }
 
        private static int GetDeclarationDepth(SyntaxTrivia trivia)
        {
            if (SyntaxFacts.IsPreprocessorDirective(trivia.Kind()))
            {
                return 0;
            }
 
            return GetDeclarationDepth((SyntaxToken)trivia.Token);
        }
 
        private static int GetDeclarationDepth(SyntaxNode? node)
        {
            if (node is null)
            {
                return 0;
            }
 
            if (node.IsStructuredTrivia)
            {
                var tr = ((StructuredTriviaSyntax)node).ParentTrivia;
                return GetDeclarationDepth(tr);
            }
            else if (node.Parent != null)
            {
                if (node.Parent.IsKind(SyntaxKind.CompilationUnit))
                {
                    return 0;
                }
 
                int parentDepth = GetDeclarationDepth(node.Parent);
 
                if (node.Parent.Kind() is SyntaxKind.GlobalStatement or SyntaxKind.FileScopedNamespaceDeclaration)
                {
                    return parentDepth;
                }
 
                if (node.IsKind(SyntaxKind.IfStatement) && node.Parent.IsKind(SyntaxKind.ElseClause))
                {
                    return parentDepth;
                }
 
                if (node.Parent is BlockSyntax)
                {
                    return parentDepth + 1;
                }
 
                if (node is { Parent: InitializerExpressionSyntax or AnonymousObjectMemberDeclaratorSyntax } ||
                    node is AssignmentExpressionSyntax { Parent: InitializerExpressionSyntax })
                {
                    if (!IsSingleLineInitializerContext(node.Parent))
                    {
                        return parentDepth + 1;
                    }
                }
 
                if (node is StatementSyntax && node is not BlockSyntax)
                {
                    // Nested statements are normally indented one level.
                    //
                    // However, for chains of using-statements or fixed-statements, we'd like to follow the
                    // idiomatic pattern of:
                    //
                    //      using ...
                    //      using ...
                    //          .. embedded statement ..
                    if (node is UsingStatementSyntax { Parent: UsingStatementSyntax })
                        return parentDepth;
 
                    if (node is FixedStatementSyntax { Parent: FixedStatementSyntax })
                        return parentDepth;
 
                    return parentDepth + 1;
                }
 
                if (node is MemberDeclarationSyntax ||
                    node is AccessorDeclarationSyntax ||
                    node is TypeParameterConstraintClauseSyntax ||
                    node is SwitchSectionSyntax ||
                    node is SwitchExpressionArmSyntax ||
                    node is UsingDirectiveSyntax ||
                    node is ExternAliasDirectiveSyntax ||
                    node is QueryExpressionSyntax ||
                    node is QueryContinuationSyntax)
                {
                    return parentDepth + 1;
                }
 
                return parentDepth;
            }
 
            return 0;
        }
 
        /// <summary>
        /// Tells if the given SyntaxNode is inside single-line initializer context.
        /// Initializers in such context are not expected to be large,
        /// so formatting them in single-line fashion looks more compact.
        /// Current cases:
        /// <list type="bullet">
        /// <item>Interpolation holes in strings</item>
        /// <item>Attribute arguments</item>
        /// <item>Normal arguments</item>
        /// </list>
        /// </summary>
        private static bool IsSingleLineInitializerContext(SyntaxNode? node)
        {
            if (node is null)
            {
                return false;
            }
 
            var currentParent = node.Parent;
 
            while (currentParent is not null)
            {
                if (currentParent is InterpolationSyntax
                                  or AttributeArgumentSyntax
                                  or ArgumentSyntax)
                {
                    return true;
                }
 
                if (currentParent is StatementSyntax
                                  or MemberDeclarationSyntax)
                {
                    return false;
                }
 
                currentParent = currentParent.Parent;
            }
 
            return false;
        }
 
        /// <summary>
        /// Tells if given SyntaxNode is an initializer in a single-line initializer context.
        /// See <see cref="IsSingleLineInitializerContext"/>
        /// </summary>
        private static bool IsInitializerInSingleLineContext(SyntaxNode? node)
        {
            if (node is not (InitializerExpressionSyntax or AnonymousObjectCreationExpressionSyntax))
            {
                return false;
            }
 
            return IsSingleLineInitializerContext(node);
        }
 
        private static bool IsSingleLineProperty(PropertyDeclarationSyntax property)
        {
            // SyntaxNormalizer produces single-line properties for
            // expression-bodied properties and auto-properties.
            // In the first case accessor list of a property is null,
            // in the second case all accessors in the accessor list don't have bodies.
            return property.AccessorList is null || IsAccessorListWithoutAccessorsWithBlockBody(property.AccessorList);
        }
 
        public override SyntaxNode? VisitInterpolatedStringExpression(InterpolatedStringExpressionSyntax node)
        {
            if (node.StringStartToken.Kind() == SyntaxKind.InterpolatedStringStartToken)
            {
                //Just for non verbatim strings we want to make sure that the formatting of interpolations does not emit line breaks.
                //See: https://github.com/dotnet/roslyn/issues/50742
                //
                //The flag _inSingleLineInterpolation is set to true while visiting InterpolatedStringExpressionSyntax and checked in LineBreaksAfter
                //to suppress adding newlines.
                var old = _inSingleLineInterpolation;
                _inSingleLineInterpolation = true;
                try
                {
                    return base.VisitInterpolatedStringExpression(node);
                }
                finally
                {
                    _inSingleLineInterpolation = old;
                }
            }
 
            return base.VisitInterpolatedStringExpression(node);
        }
 
        public override SyntaxNode? VisitXmlTextAttribute(XmlTextAttributeSyntax node)
        {
            var attribute = (XmlTextAttributeSyntax?)base.VisitXmlTextAttribute(node);
 
            if (attribute is null or { HasTrailingTrivia: true })
            {
                return attribute;
            }
 
            SyntaxKind nextTokenKind = GetNextRelevantToken(node.EndQuoteToken).Kind();
            return nextTokenKind != SyntaxKind.GreaterThanToken && nextTokenKind != SyntaxKind.SlashGreaterThanToken
                ? attribute.WithTrailingTrivia(GetSpace())
                : attribute;
        }
    }
}