예제 #1
0
        protected override IndentationResult GetDesiredIndentationWorker(
            Indenter indenter, SyntaxToken token, TextLine previousLine, int lastNonWhitespacePosition)
        {
            // okay, now check whether the text we found is trivia or actual token.
            if (token.Span.Contains(lastNonWhitespacePosition))
            {
                // okay, it is a token case, do special work based on type of last token on previous line
                return(GetIndentationBasedOnToken(indenter, token));
            }
            else
            {
                // there must be trivia that contains or touch this position
                Debug.Assert(token.FullSpan.Contains(lastNonWhitespacePosition));

                // okay, now check whether the trivia is at the beginning of the line
                var firstNonWhitespacePosition = previousLine.GetFirstNonWhitespacePosition();
                if (!firstNonWhitespacePosition.HasValue)
                {
                    return(indenter.IndentFromStartOfLine(0));
                }

                var trivia = indenter.Root.FindTrivia(firstNonWhitespacePosition.Value, findInsideTrivia: true);
                if (trivia.Kind() == SyntaxKind.None || indenter.LineToBeIndented.LineNumber > previousLine.LineNumber + 1)
                {
                    // If the token belongs to the next statement and is also the first token of the statement, then it means the user wants
                    // to start type a new statement. So get indentation from the start of the line but not based on the token.
                    // Case:
                    // static void Main(string[] args)
                    // {
                    //     // A
                    //     // B
                    //
                    //     $$
                    //     return;
                    // }

                    var containingStatement = token.GetAncestor <StatementSyntax>();
                    if (containingStatement != null && containingStatement.GetFirstToken() == token)
                    {
                        var position = indenter.GetCurrentPositionNotBelongToEndOfFileToken(indenter.LineToBeIndented.Start);
                        return(indenter.IndentFromStartOfLine(indenter.Finder.GetIndentationOfCurrentPosition(indenter.Tree, token, position, indenter.CancellationToken)));
                    }

                    // If the token previous of the base token happens to be a Comma from a separation list then we need to handle it different
                    // Case:
                    // var s = new List<string>
                    //                 {
                    //                     """",
                    //                             """",/*sdfsdfsdfsdf*/
                    //                                  // dfsdfsdfsdfsdf
                    //
                    //                             $$
                    //                 };
                    var previousToken = token.GetPreviousToken();
                    if (previousToken.IsKind(SyntaxKind.CommaToken))
                    {
                        return(GetIndentationFromCommaSeparatedList(indenter, previousToken));
                    }
                    else if (!previousToken.IsKind(SyntaxKind.None))
                    {
                        // okay, beginning of the line is not trivia, use the last token on the line as base token
                        return(GetIndentationBasedOnToken(indenter, token));
                    }
                }

                // this case we will keep the indentation of this trivia line
                // this trivia can't be preprocessor by the way.
                return(indenter.GetIndentationOfLine(previousLine));
            }
        }
예제 #2
0
        private static IndentationResult GetIndentationBasedOnToken(Indenter indenter, SyntaxToken token)
        {
            Contract.ThrowIfNull(indenter.Tree);
            Contract.ThrowIfTrue(token.Kind() == SyntaxKind.None);

            // special cases
            // case 1: token belongs to verbatim token literal
            // case 2: $@"$${0}"
            // case 3: $@"Comment$$ in-between{0}"
            // case 4: $@"{0}$$"
            if (token.IsVerbatimStringLiteral() ||
                token.IsKind(SyntaxKind.InterpolatedVerbatimStringStartToken) ||
                token.IsKind(SyntaxKind.InterpolatedStringTextToken) ||
                (token.IsKind(SyntaxKind.CloseBraceToken) && token.Parent.IsKind(SyntaxKind.Interpolation)))
            {
                return(indenter.IndentFromStartOfLine(0));
            }

            // if previous statement belong to labeled statement, don't follow label's indentation
            // but its previous one.
            if (token.Parent is LabeledStatementSyntax || token.IsLastTokenInLabelStatement())
            {
                token = token.GetAncestor <LabeledStatementSyntax>() !.GetFirstToken(includeZeroWidth: true).GetPreviousToken(includeZeroWidth: true);
            }

            var position = indenter.GetCurrentPositionNotBelongToEndOfFileToken(indenter.LineToBeIndented.Start);

            // first check operation service to see whether we can determine indentation from it
            var indentation = indenter.Finder.FromIndentBlockOperations(indenter.Tree, token, position, indenter.CancellationToken);

            if (indentation.HasValue)
            {
                return(indenter.IndentFromStartOfLine(indentation.Value));
            }

            var alignmentTokenIndentation = indenter.Finder.FromAlignTokensOperations(indenter.Tree, token);

            if (alignmentTokenIndentation.HasValue)
            {
                return(indenter.IndentFromStartOfLine(alignmentTokenIndentation.Value));
            }

            // if we couldn't determine indentation from the service, use heuristic to find indentation.
            var sourceText = indenter.LineToBeIndented.Text;

            RoslynDebug.AssertNotNull(sourceText);

            // If this is the last token of an embedded statement, walk up to the top-most parenting embedded
            // statement owner and use its indentation.
            //
            // cases:
            //   if (true)
            //     if (false)
            //       Goo();
            //
            //   if (true)
            //     { }

            if (token.IsSemicolonOfEmbeddedStatement() ||
                token.IsCloseBraceOfEmbeddedBlock())
            {
                Debug.Assert(
                    token.Parent != null &&
                    (token.Parent.Parent is StatementSyntax || token.Parent.Parent is ElseClauseSyntax));

                var embeddedStatementOwner = token.Parent.Parent;
                while (embeddedStatementOwner.IsEmbeddedStatement())
                {
                    RoslynDebug.AssertNotNull(embeddedStatementOwner.Parent);
                    embeddedStatementOwner = embeddedStatementOwner.Parent;
                }

                return(indenter.GetIndentationOfLine(sourceText.Lines.GetLineFromPosition(embeddedStatementOwner.GetFirstToken(includeZeroWidth: true).SpanStart)));
            }

            switch (token.Kind())
            {
            case SyntaxKind.SemicolonToken:
            {
                // special cases
                if (token.IsSemicolonInForStatement())
                {
                    return(GetDefaultIndentationFromToken(indenter, token));
                }

                return(indenter.IndentFromStartOfLine(indenter.Finder.GetIndentationOfCurrentPosition(indenter.Tree, token, position, indenter.CancellationToken)));
            }

            case SyntaxKind.CloseBraceToken:
            {
                if (token.Parent.IsKind(SyntaxKind.AccessorList) &&
                    token.Parent.Parent.IsKind(SyntaxKind.PropertyDeclaration))
                {
                    if (token.GetNextToken().IsEqualsTokenInAutoPropertyInitializers())
                    {
                        return(GetDefaultIndentationFromToken(indenter, token));
                    }
                }

                return(indenter.IndentFromStartOfLine(indenter.Finder.GetIndentationOfCurrentPosition(indenter.Tree, token, position, indenter.CancellationToken)));
            }

            case SyntaxKind.OpenBraceToken:
            {
                return(indenter.IndentFromStartOfLine(indenter.Finder.GetIndentationOfCurrentPosition(indenter.Tree, token, position, indenter.CancellationToken)));
            }

            case SyntaxKind.ColonToken:
            {
                var nonTerminalNode = token.Parent;
                Contract.ThrowIfNull(nonTerminalNode, @"Malformed code or bug in parser???");

                if (nonTerminalNode is SwitchLabelSyntax)
                {
                    return(indenter.GetIndentationOfLine(sourceText.Lines.GetLineFromPosition(nonTerminalNode.GetFirstToken(includeZeroWidth: true).SpanStart), indenter.Options.FormattingOptions.IndentationSize));
                }

                goto default;
            }

            case SyntaxKind.CloseBracketToken:
            {
                var nonTerminalNode = token.Parent;
                Contract.ThrowIfNull(nonTerminalNode, @"Malformed code or bug in parser???");

                // if this is closing an attribute, we shouldn't indent.
                if (nonTerminalNode is AttributeListSyntax)
                {
                    return(indenter.GetIndentationOfLine(sourceText.Lines.GetLineFromPosition(nonTerminalNode.GetFirstToken(includeZeroWidth: true).SpanStart)));
                }

                goto default;
            }

            case SyntaxKind.XmlTextLiteralToken:
            {
                return(indenter.GetIndentationOfLine(sourceText.Lines.GetLineFromPosition(token.SpanStart)));
            }

            case SyntaxKind.CommaToken:
            {
                return(GetIndentationFromCommaSeparatedList(indenter, token));
            }

            case SyntaxKind.CloseParenToken:
            {
                if (token.Parent.IsKind(SyntaxKind.ArgumentList))
                {
                    return(GetDefaultIndentationFromToken(indenter, token.Parent.GetFirstToken(includeZeroWidth: true)));
                }

                goto default;
            }

            default:
            {
                return(GetDefaultIndentationFromToken(indenter, token));
            }
            }
        }
        private static IndentationResult GetIndentationBasedOnToken(Indenter indenter, SyntaxToken token)
        {
            Contract.ThrowIfNull(indenter.Tree);
            Contract.ThrowIfTrue(token.Kind() == SyntaxKind.None);

            var sourceText = indenter.LineToBeIndented.Text;

            RoslynDebug.AssertNotNull(sourceText);

            // case: """$$
            //       """
            if (token.IsKind(SyntaxKind.MultiLineRawStringLiteralToken))
            {
                var endLine       = sourceText.Lines.GetLineFromPosition(token.Span.End);
                var minimumOffset = endLine.GetFirstNonWhitespaceOffset();
                Contract.ThrowIfNull(minimumOffset);

                // If possible, indent to match the indentation of the previous non-whitespace line contained in the
                // same raw string. Otherwise, indent to match the ending line of the raw string.
                var startLine = sourceText.Lines.GetLineFromPosition(token.SpanStart);
                for (var currentLineNumber = indenter.LineToBeIndented.LineNumber - 1; currentLineNumber >= startLine.LineNumber + 1; currentLineNumber--)
                {
                    var currentLine = sourceText.Lines[currentLineNumber];
                    if (currentLine.GetFirstNonWhitespaceOffset() is { } priorLineOffset)
                    {
                        if (priorLineOffset >= minimumOffset.Value)
                        {
                            return(indenter.GetIndentationOfLine(currentLine));
                        }
                        else
                        {
                            // The prior line is not sufficiently indented, so use the ending delimiter for the indent
                            break;
                        }
                    }
                }

                return(indenter.GetIndentationOfLine(endLine));
            }

            // case 1: $"""$$
            //          """
            // case 2: $"""
            //          text$$
            //          """
            // case 3: $"""
            //          {value}$$
            //          """
            if (token.Kind() is SyntaxKind.InterpolatedMultiLineRawStringStartToken or SyntaxKind.InterpolatedStringTextToken ||
                (token.IsKind(SyntaxKind.CloseBraceToken) && token.Parent.IsKind(SyntaxKind.Interpolation)))
            {
                var interpolatedExpression = token.GetAncestor <InterpolatedStringExpressionSyntax>();
                Contract.ThrowIfNull(interpolatedExpression);
                if (interpolatedExpression.StringStartToken.IsKind(SyntaxKind.InterpolatedMultiLineRawStringStartToken))
                {
                    var endLine       = sourceText.Lines.GetLineFromPosition(interpolatedExpression.StringEndToken.Span.End);
                    var minimumOffset = endLine.GetFirstNonWhitespaceOffset();
                    Contract.ThrowIfNull(minimumOffset);

                    // If possible, indent to match the indentation of the previous non-whitespace line contained in the
                    // same raw string. Otherwise, indent to match the ending line of the raw string.
                    var startLine = sourceText.Lines.GetLineFromPosition(interpolatedExpression.StringStartToken.SpanStart);
                    for (var currentLineNumber = indenter.LineToBeIndented.LineNumber - 1; currentLineNumber >= startLine.LineNumber + 1; currentLineNumber--)
                    {
                        var currentLine = sourceText.Lines[currentLineNumber];
                        if (!indenter.Root.FindToken(currentLine.Start, findInsideTrivia: true).IsKind(SyntaxKind.InterpolatedStringTextToken))
                        {
                            // Avoid trying to indent to match the content of an interpolation. Example:
                            //
                            // _ = $"""
                            //     {
                            //  0}         <-- the start of this line is not part of the text content
                            //     """
                            //
                            continue;
                        }

                        if (currentLine.GetFirstNonWhitespaceOffset() is { } priorLineOffset)
                        {
                            if (priorLineOffset >= minimumOffset.Value)
                            {
                                return(indenter.GetIndentationOfLine(currentLine));
                            }
                            else
                            {
                                // The prior line is not sufficiently indented, so use the ending delimiter for the indent
                                break;
                            }
                        }
                    }

                    return(indenter.GetIndentationOfLine(endLine));
                }
            }

            // special cases
            // case 1: token belongs to verbatim token literal
            // case 2: $@"$${0}"
            // case 3: $@"Comment$$ in-between{0}"
            // case 4: $@"{0}$$"
            if (token.IsVerbatimStringLiteral() ||
                token.IsKind(SyntaxKind.InterpolatedVerbatimStringStartToken) ||
                token.IsKind(SyntaxKind.InterpolatedStringTextToken) ||
                (token.IsKind(SyntaxKind.CloseBraceToken) && token.Parent.IsKind(SyntaxKind.Interpolation)))
            {
                return(indenter.IndentFromStartOfLine(0));
            }

            // if previous statement belong to labeled statement, don't follow label's indentation
            // but its previous one.
            if (token.Parent is LabeledStatementSyntax || token.IsLastTokenInLabelStatement())
            {
                token = token.GetAncestor <LabeledStatementSyntax>() !.GetFirstToken(includeZeroWidth: true).GetPreviousToken(includeZeroWidth: true);
            }

            var position = indenter.GetCurrentPositionNotBelongToEndOfFileToken(indenter.LineToBeIndented.Start);

            // first check operation service to see whether we can determine indentation from it
            var indentation = indenter.Finder.FromIndentBlockOperations(indenter.Tree, token, position, indenter.CancellationToken);

            if (indentation.HasValue)
            {
                return(indenter.IndentFromStartOfLine(indentation.Value));
            }

            var alignmentTokenIndentation = indenter.Finder.FromAlignTokensOperations(indenter.Tree, token);

            if (alignmentTokenIndentation.HasValue)
            {
                return(indenter.IndentFromStartOfLine(alignmentTokenIndentation.Value));
            }

            // if we couldn't determine indentation from the service, use heuristic to find indentation.

            // If this is the last token of an embedded statement, walk up to the top-most parenting embedded
            // statement owner and use its indentation.
            //
            // cases:
            //   if (true)
            //     if (false)
            //       Goo();
            //
            //   if (true)
            //     { }

            if (token.IsSemicolonOfEmbeddedStatement() ||
                token.IsCloseBraceOfEmbeddedBlock())
            {
                RoslynDebug.Assert(
                    token.Parent != null &&
                    (token.Parent.Parent is StatementSyntax || token.Parent.Parent is ElseClauseSyntax));

                var embeddedStatementOwner = token.Parent.Parent;
                while (embeddedStatementOwner.IsEmbeddedStatement())
                {
                    RoslynDebug.AssertNotNull(embeddedStatementOwner.Parent);
                    embeddedStatementOwner = embeddedStatementOwner.Parent;
                }

                return(indenter.GetIndentationOfLine(sourceText.Lines.GetLineFromPosition(embeddedStatementOwner.GetFirstToken(includeZeroWidth: true).SpanStart)));
            }

            switch (token.Kind())
            {
            case SyntaxKind.SemicolonToken:
            {
                // special cases
                if (token.IsSemicolonInForStatement())
                {
                    return(GetDefaultIndentationFromToken(indenter, token));
                }

                return(indenter.IndentFromStartOfLine(indenter.Finder.GetIndentationOfCurrentPosition(indenter.Tree, token, position, indenter.CancellationToken)));
            }

            case SyntaxKind.CloseBraceToken:
            {
                if (token.Parent.IsKind(SyntaxKind.AccessorList) &&
                    token.Parent.Parent.IsKind(SyntaxKind.PropertyDeclaration))
                {
                    if (token.GetNextToken().IsEqualsTokenInAutoPropertyInitializers())
                    {
                        return(GetDefaultIndentationFromToken(indenter, token));
                    }
                }

                return(indenter.IndentFromStartOfLine(indenter.Finder.GetIndentationOfCurrentPosition(indenter.Tree, token, position, indenter.CancellationToken)));
            }

            case SyntaxKind.OpenBraceToken:
            {
                return(indenter.IndentFromStartOfLine(indenter.Finder.GetIndentationOfCurrentPosition(indenter.Tree, token, position, indenter.CancellationToken)));
            }

            case SyntaxKind.ColonToken:
            {
                var nonTerminalNode = token.Parent;
                Contract.ThrowIfNull(nonTerminalNode, @"Malformed code or bug in parser???");

                if (nonTerminalNode is SwitchLabelSyntax)
                {
                    return(indenter.GetIndentationOfLine(sourceText.Lines.GetLineFromPosition(nonTerminalNode.GetFirstToken(includeZeroWidth: true).SpanStart), indenter.Options.FormattingOptions.IndentationSize));
                }

                goto default;
            }

            case SyntaxKind.CloseBracketToken:
            {
                var nonTerminalNode = token.Parent;
                Contract.ThrowIfNull(nonTerminalNode, @"Malformed code or bug in parser???");

                // if this is closing an attribute, we shouldn't indent.
                if (nonTerminalNode is AttributeListSyntax)
                {
                    return(indenter.GetIndentationOfLine(sourceText.Lines.GetLineFromPosition(nonTerminalNode.GetFirstToken(includeZeroWidth: true).SpanStart)));
                }

                goto default;
            }

            case SyntaxKind.XmlTextLiteralToken:
            {
                return(indenter.GetIndentationOfLine(sourceText.Lines.GetLineFromPosition(token.SpanStart)));
            }

            case SyntaxKind.CommaToken:
            {
                return(GetIndentationFromCommaSeparatedList(indenter, token));
            }

            case SyntaxKind.CloseParenToken:
            {
                if (token.Parent.IsKind(SyntaxKind.ArgumentList))
                {
                    return(GetDefaultIndentationFromToken(indenter, token.Parent.GetFirstToken(includeZeroWidth: true)));
                }

                goto default;
            }

            default:
            {
                return(GetDefaultIndentationFromToken(indenter, token));
            }
            }
        }