IndentBlockFormattingRule.cs 16.8 KB
Newer Older
J
Jonathon Marolf 已提交
1 2 3
// 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.
P
Pilchie 已提交
4

5 6
#nullable enable

A
Andy Gocke 已提交
7
using System;
P
Pilchie 已提交
8
using System.Collections.Generic;
9
using System.Diagnostics;
P
Pilchie 已提交
10
using Microsoft.CodeAnalysis.CSharp.Syntax;
S
Sam Harwell 已提交
11
using Microsoft.CodeAnalysis.Diagnostics;
P
Pilchie 已提交
12
using Microsoft.CodeAnalysis.Formatting.Rules;
13
using Microsoft.CodeAnalysis.Options;
P
Pilchie 已提交
14 15
using Microsoft.CodeAnalysis.Shared.Utilities;
using Microsoft.CodeAnalysis.Text;
16
using Roslyn.Utilities;
17

P
Pilchie 已提交
18 19
namespace Microsoft.CodeAnalysis.CSharp.Formatting
{
20
    internal sealed class IndentBlockFormattingRule : BaseFormattingRule
P
Pilchie 已提交
21 22 23
    {
        internal const string Name = "CSharp IndentBlock Formatting Rule";

24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
        private readonly CachedOptions _options;

        public IndentBlockFormattingRule()
            : this(new CachedOptions(null))
        {
        }

        private IndentBlockFormattingRule(CachedOptions options)
        {
            _options = options;
        }

        public override AbstractFormattingRule WithOptions(AnalyzerConfigOptions options)
        {
            var cachedOptions = new CachedOptions(options);

            if (cachedOptions == _options)
            {
                return this;
            }

            return new IndentBlockFormattingRule(cachedOptions);
        }

        public override void AddIndentBlockOperations(List<IndentBlockOperation> list, SyntaxNode node, in NextIndentBlockOperationAction nextOperation)
P
Pilchie 已提交
49
        {
50
            nextOperation.Invoke();
P
Pilchie 已提交
51

C
Cyrus Najmabadi 已提交
52
            AddAlignmentBlockOperation(list, node);
53

54
            AddBlockIndentationOperation(list, node);
P
Pilchie 已提交
55

56
            AddLabelIndentationOperation(list, node);
P
Pilchie 已提交
57

58
            AddSwitchIndentationOperation(list, node);
P
Pilchie 已提交
59 60 61

            AddEmbeddedStatementsIndentationOperation(list, node);

62
            AddTypeParameterConstraintClauseOperation(list, node);
P
Pilchie 已提交
63 64
        }

65
        private void AddTypeParameterConstraintClauseOperation(List<IndentBlockOperation> list, SyntaxNode node)
P
Pilchie 已提交
66
        {
67
            if (node is TypeParameterConstraintClauseSyntax { Parent: { } declaringNode })
P
Pilchie 已提交
68 69 70 71 72 73
            {
                var baseToken = declaringNode.GetFirstToken();
                AddIndentBlockOperation(list, baseToken, node.GetFirstToken(), node.GetLastToken());
            }
        }

74
        private void AddSwitchIndentationOperation(List<IndentBlockOperation> list, SyntaxNode node)
P
Pilchie 已提交
75
        {
C
Cyrus Najmabadi 已提交
76
            if (!(node is SwitchSectionSyntax section))
P
Pilchie 已提交
77 78 79 80 81 82 83 84 85 86 87
            {
                return;
            }

            // can this ever happen?
            if (section.Labels.Count == 0 &&
                section.Statements.Count == 0)
            {
                return;
            }

88
            if (!_options.IndentSwitchCaseSection && !_options.IndentSwitchCaseSectionWhenBlock)
89 90 91 92 93
            {
                // Never indent
                return;
            }

94
            var alwaysIndent = _options.IndentSwitchCaseSection && _options.IndentSwitchCaseSectionWhenBlock;
95 96 97
            if (!alwaysIndent)
            {
                // Only one of these values can be true at this point.
98
                Debug.Assert(_options.IndentSwitchCaseSection != _options.IndentSwitchCaseSectionWhenBlock);
99 100 101 102 103

                var firstStatementIsBlock =
                    section.Statements.Count > 0 &&
                    section.Statements[0].IsKind(SyntaxKind.Block);

104
                if (_options.IndentSwitchCaseSectionWhenBlock != firstStatementIsBlock)
105 106 107 108 109
                {
                    return;
                }
            }

P
Pilchie 已提交
110
            // see whether we are the last statement
111 112
            RoslynDebug.AssertNotNull(node.Parent);
            var switchStatement = (SwitchStatementSyntax)node.Parent;
P
Pilchie 已提交
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
            var lastSection = switchStatement.Sections.Last() == node;

            if (section.Statements.Count == 0)
            {
                // even if there is no statement under section, we still want indent operation
                var lastTokenOfLabel = section.Labels.Last().GetLastToken(includeZeroWidth: true);
                var nextToken = lastTokenOfLabel.GetNextToken(includeZeroWidth: true);

                AddIndentBlockOperation(list, lastTokenOfLabel, lastTokenOfLabel,
                    lastSection ?
                        TextSpan.FromBounds(lastTokenOfLabel.FullSpan.End, nextToken.SpanStart) : TextSpan.FromBounds(lastTokenOfLabel.FullSpan.End, lastTokenOfLabel.FullSpan.End));
                return;
            }

            var startToken = section.Statements.First().GetFirstToken(includeZeroWidth: true);
            var endToken = section.Statements.Last().GetLastToken(includeZeroWidth: true);

            // see whether we are the last statement
            var span = CommonFormattingHelpers.GetSpanIncludingTrailingAndLeadingTriviaOfAdjacentTokens(startToken, endToken);
            span = lastSection ? span : TextSpan.FromBounds(span.Start, endToken.FullSpan.End);

            AddIndentBlockOperation(list, startToken, endToken, span);
        }

137
        private void AddLabelIndentationOperation(List<IndentBlockOperation> list, SyntaxNode node)
P
Pilchie 已提交
138 139
        {
            // label statement
C
CyrusNajmabadi 已提交
140
            if (node is LabeledStatementSyntax labeledStatement)
P
Pilchie 已提交
141
            {
142
                if (_options.LabelPositioning == LabelPositionOptions.OneLess)
P
Pilchie 已提交
143
                {
144
                    AddUnindentBlockOperation(list, labeledStatement.Identifier, labeledStatement.ColonToken);
P
Pilchie 已提交
145
                }
146
                else if (_options.LabelPositioning == LabelPositionOptions.LeftMost)
P
Pilchie 已提交
147
                {
148
                    AddAbsoluteZeroIndentBlockOperation(list, labeledStatement.Identifier, labeledStatement.ColonToken);
P
Pilchie 已提交
149 150 151 152
                }
            }
        }

C
Cyrus Najmabadi 已提交
153
        private void AddAlignmentBlockOperation(List<IndentBlockOperation> list, SyntaxNode node)
P
Pilchie 已提交
154
        {
C
CyrusNajmabadi 已提交
155
            switch (node)
P
Pilchie 已提交
156
            {
C
CyrusNajmabadi 已提交
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
                case SimpleLambdaExpressionSyntax simpleLambda:
                    SetAlignmentBlockOperation(list, simpleLambda, simpleLambda.Body);
                    return;
                case ParenthesizedLambdaExpressionSyntax parenthesizedLambda:
                    SetAlignmentBlockOperation(list, parenthesizedLambda, parenthesizedLambda.Body);
                    return;
                case AnonymousMethodExpressionSyntax anonymousMethod:
                    SetAlignmentBlockOperation(list, anonymousMethod, anonymousMethod.Block);
                    return;
                case ObjectCreationExpressionSyntax objectCreation when objectCreation.Initializer != null:
                    SetAlignmentBlockOperation(list, objectCreation, objectCreation.Initializer);
                    return;
                case AnonymousObjectCreationExpressionSyntax anonymousObjectCreation:
                    SetAlignmentBlockOperation(list, anonymousObjectCreation.NewKeyword, anonymousObjectCreation.OpenBraceToken, anonymousObjectCreation.CloseBraceToken, IndentBlockOption.RelativeToFirstTokenOnBaseTokenLine);
                    return;
                case ArrayCreationExpressionSyntax arrayCreation when arrayCreation.Initializer != null:
                    SetAlignmentBlockOperation(list, arrayCreation.NewKeyword, arrayCreation.Initializer.OpenBraceToken, arrayCreation.Initializer.CloseBraceToken, IndentBlockOption.RelativeToFirstTokenOnBaseTokenLine);
                    return;
                case ImplicitArrayCreationExpressionSyntax implicitArrayCreation when implicitArrayCreation.Initializer != null:
                    SetAlignmentBlockOperation(list, implicitArrayCreation.NewKeyword, implicitArrayCreation.Initializer.OpenBraceToken, implicitArrayCreation.Initializer.CloseBraceToken, IndentBlockOption.RelativeToFirstTokenOnBaseTokenLine);
                    return;
178 179
                case SwitchExpressionSyntax switchExpression:
                    SetAlignmentBlockOperation(list, switchExpression.GetFirstToken(), switchExpression.OpenBraceToken, switchExpression.CloseBraceToken, IndentBlockOption.RelativeToFirstTokenOnBaseTokenLine);
180
                    return;
181
            }
P
Pilchie 已提交
182 183
        }

184
        private void SetAlignmentBlockOperation(List<IndentBlockOperation> list, SyntaxNode baseNode, SyntaxNode body)
P
Pilchie 已提交
185
        {
186
            var option = IndentBlockOption.RelativeToFirstTokenOnBaseTokenLine;
P
Pilchie 已提交
187

188 189 190
            var baseToken = baseNode.GetFirstToken(includeZeroWidth: true);
            var firstToken = body.GetFirstToken(includeZeroWidth: true);
            var lastToken = body.GetLastToken(includeZeroWidth: true);
P
Pilchie 已提交
191

192
            SetAlignmentBlockOperation(list, baseToken, firstToken, lastToken, option);
P
Pilchie 已提交
193 194
        }

195
        private void AddBlockIndentationOperation(List<IndentBlockOperation> list, SyntaxNode node)
P
Pilchie 已提交
196
        {
197
            var bracePair = node.GetBracePair();
P
Pilchie 已提交
198

199 200
            // don't put block indentation operation if the block only contains label statement
            if (!bracePair.IsValidBracePair())
P
Pilchie 已提交
201
            {
202 203 204 205
                return;
            }

            // for lambda, set alignment around braces so that users can put brace wherever they want
206
            if (node.IsLambdaBodyBlock() || node.IsAnonymousMethodBlock() || node.IsKind(SyntaxKind.PropertyPatternClause) || node.IsKind(SyntaxKind.SwitchExpression))
207
            {
B
Basoundr_ms 已提交
208 209 210 211 212 213 214
                AddAlignmentBlockOperationRelativeToFirstTokenOnBaseTokenLine(list, bracePair);
            }

            // For ArrayInitializationExpression, set indent to relative to the open brace so the content is properly indented
            if (node.IsKind(SyntaxKind.ArrayInitializerExpression) && node.Parent != null && node.Parent.IsKind(SyntaxKind.ArrayCreationExpression))
            {
                AddAlignmentBlockOperationRelativeToFirstTokenOnBaseTokenLine(list, bracePair);
P
Pilchie 已提交
215 216
            }

217
            if (node is BlockSyntax && !_options.IndentBlock)
P
Pilchie 已提交
218
            {
219 220
                // do not add indent operation for block
                return;
P
Pilchie 已提交
221 222
            }

223
            if (node is SwitchStatementSyntax && !_options.IndentSwitchSection)
224 225 226 227 228 229
            {
                // do not add indent operation for switch statement
                return;
            }

            AddIndentBlockOperation(list, bracePair.Item1.GetNextToken(includeZeroWidth: true), bracePair.Item2.GetPreviousToken(includeZeroWidth: true));
P
Pilchie 已提交
230 231
        }

A
Andy Gocke 已提交
232
        private void AddAlignmentBlockOperationRelativeToFirstTokenOnBaseTokenLine(List<IndentBlockOperation> list, ValueTuple<SyntaxToken, SyntaxToken> bracePair)
B
Basoundr_ms 已提交
233 234 235 236 237
        {
            var option = IndentBlockOption.RelativeToFirstTokenOnBaseTokenLine;
            SetAlignmentBlockOperation(list, bracePair.Item1, bracePair.Item1.GetNextToken(includeZeroWidth: true), bracePair.Item2, option);
        }

P
Pilchie 已提交
238 239
        private void AddEmbeddedStatementsIndentationOperation(List<IndentBlockOperation> list, SyntaxNode node)
        {
P
Pharring 已提交
240
            // increase indentation - embedded statement cases
C
CyrusNajmabadi 已提交
241
            if (node is IfStatementSyntax ifStatement && ifStatement.Statement != null && !(ifStatement.Statement is BlockSyntax))
P
Pilchie 已提交
242 243 244 245 246
            {
                AddEmbeddedStatementsIndentationOperation(list, ifStatement.Statement);
                return;
            }

C
CyrusNajmabadi 已提交
247
            if (node is ElseClauseSyntax elseClause && elseClause.Statement != null)
P
Pilchie 已提交
248 249 250 251 252 253 254 255 256
            {
                if (!(elseClause.Statement is BlockSyntax || elseClause.Statement is IfStatementSyntax))
                {
                    AddEmbeddedStatementsIndentationOperation(list, elseClause.Statement);
                }

                return;
            }

C
CyrusNajmabadi 已提交
257
            if (node is WhileStatementSyntax whileStatement && whileStatement.Statement != null && !(whileStatement.Statement is BlockSyntax))
P
Pilchie 已提交
258 259 260 261 262
            {
                AddEmbeddedStatementsIndentationOperation(list, whileStatement.Statement);
                return;
            }

C
CyrusNajmabadi 已提交
263
            if (node is ForStatementSyntax forStatement && forStatement.Statement != null && !(forStatement.Statement is BlockSyntax))
P
Pilchie 已提交
264 265 266 267 268
            {
                AddEmbeddedStatementsIndentationOperation(list, forStatement.Statement);
                return;
            }

C
CyrusNajmabadi 已提交
269
            if (node is CommonForEachStatementSyntax foreachStatement && foreachStatement.Statement != null && !(foreachStatement.Statement is BlockSyntax))
P
Pilchie 已提交
270 271 272 273 274
            {
                AddEmbeddedStatementsIndentationOperation(list, foreachStatement.Statement);
                return;
            }

C
CyrusNajmabadi 已提交
275
            if (node is UsingStatementSyntax usingStatement && usingStatement.Statement != null && !(usingStatement.Statement is BlockSyntax || usingStatement.Statement is UsingStatementSyntax))
P
Pilchie 已提交
276 277 278 279 280
            {
                AddEmbeddedStatementsIndentationOperation(list, usingStatement.Statement);
                return;
            }

C
CyrusNajmabadi 已提交
281
            if (node is FixedStatementSyntax fixedStatement && fixedStatement.Statement != null && !(fixedStatement.Statement is BlockSyntax || fixedStatement.Statement is FixedStatementSyntax))
282 283 284 285 286
            {
                AddEmbeddedStatementsIndentationOperation(list, fixedStatement.Statement);
                return;
            }

C
CyrusNajmabadi 已提交
287
            if (node is DoStatementSyntax doStatement && doStatement.Statement != null && !(doStatement.Statement is BlockSyntax))
P
Pilchie 已提交
288 289 290 291 292
            {
                AddEmbeddedStatementsIndentationOperation(list, doStatement.Statement);
                return;
            }

C
CyrusNajmabadi 已提交
293
            if (node is LockStatementSyntax lockStatement && lockStatement.Statement != null && !(lockStatement.Statement is BlockSyntax))
P
Pilchie 已提交
294 295 296 297 298 299 300 301 302 303 304 305 306
            {
                AddEmbeddedStatementsIndentationOperation(list, lockStatement.Statement);
                return;
            }
        }

        private void AddEmbeddedStatementsIndentationOperation(List<IndentBlockOperation> list, StatementSyntax statement)
        {
            var firstToken = statement.GetFirstToken(includeZeroWidth: true);
            var lastToken = statement.GetLastToken(includeZeroWidth: true);

            if (lastToken.IsMissing)
            {
307
                // embedded statement is not done, consider following as part of embedded statement
P
Pilchie 已提交
308 309 310 311
                AddIndentBlockOperation(list, firstToken, lastToken);
            }
            else
            {
P
Pharring 已提交
312
                // embedded statement is done
P
Pilchie 已提交
313 314 315
                AddIndentBlockOperation(list, firstToken, lastToken, TextSpan.FromBounds(firstToken.FullSpan.Start, lastToken.FullSpan.End));
            }
        }
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370

        private readonly struct CachedOptions : IEquatable<CachedOptions>
        {
            public readonly LabelPositionOptions LabelPositioning;
            public readonly bool IndentBlock;
            public readonly bool IndentSwitchCaseSection;
            public readonly bool IndentSwitchCaseSectionWhenBlock;
            public readonly bool IndentSwitchSection;

            public CachedOptions(AnalyzerConfigOptions? options)
            {
                LabelPositioning = GetOptionOrDefault(options, CSharpFormattingOptions2.LabelPositioning);
                IndentBlock = GetOptionOrDefault(options, CSharpFormattingOptions2.IndentBlock);
                IndentSwitchCaseSection = GetOptionOrDefault(options, CSharpFormattingOptions2.IndentSwitchCaseSection);
                IndentSwitchCaseSectionWhenBlock = GetOptionOrDefault(options, CSharpFormattingOptions2.IndentSwitchCaseSectionWhenBlock);
                IndentSwitchSection = GetOptionOrDefault(options, CSharpFormattingOptions2.IndentSwitchSection);
            }

            public static bool operator ==(CachedOptions left, CachedOptions right)
                => left.Equals(right);

            public static bool operator !=(CachedOptions left, CachedOptions right)
                => !(left == right);

            private static T GetOptionOrDefault<T>(AnalyzerConfigOptions? options, Option2<T> option)
            {
                if (options is null)
                    return option.DefaultValue;

                return options.GetOption(option);
            }

            public override bool Equals(object? obj)
                => obj is CachedOptions options && Equals(options);

            public bool Equals(CachedOptions other)
            {
                return LabelPositioning == other.LabelPositioning
                    && IndentBlock == other.IndentBlock
                    && IndentSwitchCaseSection == other.IndentSwitchCaseSection
                    && IndentSwitchCaseSectionWhenBlock == other.IndentSwitchCaseSectionWhenBlock
                    && IndentSwitchSection == other.IndentSwitchSection;
            }

            public override int GetHashCode()
            {
                var hashCode = 0;
                hashCode = (hashCode << 2) + (int)LabelPositioning;
                hashCode = (hashCode << 1) + (IndentBlock ? 1 : 0);
                hashCode = (hashCode << 1) + (IndentSwitchCaseSection ? 1 : 0);
                hashCode = (hashCode << 1) + (IndentSwitchCaseSectionWhenBlock ? 1 : 0);
                hashCode = (hashCode << 1) + (IndentSwitchSection ? 1 : 0);
                return hashCode;
            }
        }
P
Pilchie 已提交
371
    }
372
}