// 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 Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Formatting.Rules; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Formatting; internal sealed class NewLineUserSettingFormattingRule : BaseFormattingRule { private readonly CSharpSyntaxFormattingOptions _options; public NewLineUserSettingFormattingRule() : this(CSharpSyntaxFormattingOptions.Default) { } private NewLineUserSettingFormattingRule(CSharpSyntaxFormattingOptions options) { _options = options; } public override AbstractFormattingRule WithOptions(SyntaxFormattingOptions options) { var newOptions = options as CSharpSyntaxFormattingOptions ?? CSharpSyntaxFormattingOptions.Default; if (_options.NewLines == newOptions.NewLines && _options.WrappingKeepStatementsOnSingleLine == newOptions.WrappingKeepStatementsOnSingleLine) { return this; } return new NewLineUserSettingFormattingRule(newOptions); } private static bool IsControlBlock(SyntaxNode node) { RoslynDebug.Assert(node != null); if (node.IsKind(SyntaxKind.SwitchStatement)) { return true; } var parentKind = node.Parent?.Kind(); switch (parentKind.GetValueOrDefault()) { case SyntaxKind.IfStatement: case SyntaxKind.ElseClause: case SyntaxKind.WhileStatement: case SyntaxKind.DoStatement: case SyntaxKind.ForEachStatement: case SyntaxKind.ForEachVariableStatement: case SyntaxKind.UsingStatement: case SyntaxKind.ForStatement: case SyntaxKind.TryStatement: case SyntaxKind.CatchClause: case SyntaxKind.FinallyClause: case SyntaxKind.LockStatement: case SyntaxKind.CheckedStatement: case SyntaxKind.UncheckedStatement: case SyntaxKind.SwitchSection: case SyntaxKind.FixedStatement: case SyntaxKind.UnsafeStatement: return true; default: return false; } } public override AdjustSpacesOperation? GetAdjustSpacesOperation(in SyntaxToken previousToken, in SyntaxToken currentToken, in NextGetAdjustSpacesOperation nextOperation) { RoslynDebug.AssertNotNull(currentToken.Parent); var operation = nextOperation.Invoke(in previousToken, in currentToken); // } else in the if else context if (previousToken.IsKind(SyntaxKind.CloseBraceToken) && currentToken.IsKind(SyntaxKind.ElseKeyword) && previousToken.Parent!.Parent == currentToken.Parent.Parent) { if (!_options.NewLines.HasFlag(NewLinePlacement.BeforeElse)) { operation = CreateAdjustSpacesOperation(1, AdjustSpacesOption.ForceSpaces); } } // * catch in the try catch context if (currentToken.IsKind(SyntaxKind.CatchKeyword)) { if (!_options.NewLines.HasFlag(NewLinePlacement.BeforeCatch)) { operation = CreateAdjustSpacesOperation(1, AdjustSpacesOption.ForceSpaces); } } // * finally if (currentToken.IsKind(SyntaxKind.FinallyKeyword)) { if (!_options.NewLines.HasFlag(NewLinePlacement.BeforeFinally)) { operation = CreateAdjustSpacesOperation(1, AdjustSpacesOption.ForceSpaces); } } // * { in the type declaration context if (currentToken.IsKind(SyntaxKind.OpenBraceToken) && currentToken.Parent is BaseTypeDeclarationSyntax or NamespaceDeclarationSyntax) { if (!_options.NewLines.HasFlag(NewLinePlacement.BeforeOpenBraceInTypes)) { operation = CreateAdjustSpacesOperation(1, AdjustSpacesOption.ForceSpaces); } } // new { - Anonymous object creation if (currentToken.IsKind(SyntaxKind.OpenBraceToken) && currentToken.Parent.IsKind(SyntaxKind.AnonymousObjectCreationExpression)) { if (!_options.NewLines.HasFlag(NewLinePlacement.BeforeOpenBraceInAnonymousTypes)) { operation = CreateAdjustSpacesOperation(1, AdjustSpacesOption.ForceSpaces); } } // new { - Object Initialization, or with { - Record with initializer, or is { - property pattern clauses if (currentToken.IsKind(SyntaxKind.OpenBraceToken)) { if (currentToken.Parent.Kind() is SyntaxKind.ObjectInitializerExpression or SyntaxKind.CollectionInitializerExpression or SyntaxKind.ArrayInitializerExpression or SyntaxKind.ImplicitArrayCreationExpression or SyntaxKind.WithInitializerExpression) { if (!_options.NewLines.HasFlag(NewLinePlacement.BeforeOpenBraceInObjectCollectionArrayInitializers)) { operation = CreateAdjustSpacesOperation(1, AdjustSpacesOption.ForceSpaces); } } else if (currentToken.Parent.IsKind(SyntaxKind.PropertyPatternClause)) { if (!_options.NewLines.HasFlag(NewLinePlacement.BeforeOpenBraceInObjectCollectionArrayInitializers)) { // Allow property patterns in switch expressions to start on their own line: // // var x = y switch { // { Value: true } => false, ⬅️ This line starts with an open brace // _ => true, // }; var isFirstTokenOfSwitchArm = currentToken.Parent.IsParentKind(SyntaxKind.RecursivePattern, out RecursivePatternSyntax? recursivePattern) && recursivePattern.IsParentKind(SyntaxKind.SwitchExpressionArm, out SwitchExpressionArmSyntax? switchExpressionArm) && switchExpressionArm.GetFirstToken() == currentToken; var spacesOption = isFirstTokenOfSwitchArm ? AdjustSpacesOption.ForceSpacesIfOnSingleLine : AdjustSpacesOption.ForceSpaces; operation = CreateAdjustSpacesOperation(1, spacesOption); } } } var currentTokenParentParent = currentToken.Parent.Parent; // * { - in the member declaration context if (currentToken.IsKind(SyntaxKind.OpenBraceToken) && currentTokenParentParent is MemberDeclarationSyntax) { var option = currentTokenParentParent is BasePropertyDeclarationSyntax ? _options.NewLines.HasFlag(NewLinePlacement.BeforeOpenBraceInProperties) : _options.NewLines.HasFlag(NewLinePlacement.BeforeOpenBraceInMethods); if (!option) { operation = CreateAdjustSpacesOperation(1, AdjustSpacesOption.ForceSpaces); } } if (currentToken.IsKind(SyntaxKind.OpenBraceToken) && currentTokenParentParent is AccessorDeclarationSyntax) { if (!_options.NewLines.HasFlag(NewLinePlacement.BeforeOpenBraceInAccessors)) { operation = CreateAdjustSpacesOperation(1, AdjustSpacesOption.ForceSpaces); } } // * { - in the anonymous Method context if (currentToken.IsKind(SyntaxKind.OpenBraceToken) && currentTokenParentParent.IsKind(SyntaxKind.AnonymousMethodExpression)) { if (!_options.NewLines.HasFlag(NewLinePlacement.BeforeOpenBraceInAnonymousMethods)) { operation = CreateAdjustSpacesOperation(1, AdjustSpacesOption.ForceSpaces); } } // * { - in the local function context if (currentToken.IsKind(SyntaxKind.OpenBraceToken) && currentTokenParentParent.IsKind(SyntaxKind.LocalFunctionStatement)) { if (!_options.NewLines.HasFlag(NewLinePlacement.BeforeOpenBraceInMethods)) { operation = CreateAdjustSpacesOperation(1, AdjustSpacesOption.ForceSpaces); } } // * { - in the Lambda context if (currentToken.IsKind(SyntaxKind.OpenBraceToken) && currentTokenParentParent is (kind: SyntaxKind.SimpleLambdaExpression or SyntaxKind.ParenthesizedLambdaExpression)) { if (!_options.NewLines.HasFlag(NewLinePlacement.BeforeOpenBraceInLambdaExpressionBody)) { operation = CreateAdjustSpacesOperation(1, AdjustSpacesOption.ForceSpaces); } } // * { - in the switch expression context if (currentToken.IsKind(SyntaxKind.OpenBraceToken) && currentToken.Parent.IsKind(SyntaxKind.SwitchExpression)) { if (!_options.NewLines.HasFlag(NewLinePlacement.BeforeOpenBraceInObjectCollectionArrayInitializers)) { operation = CreateAdjustSpacesOperation(1, AdjustSpacesOption.ForceSpaces); } } // * { - in the control statement context if (currentToken.IsKind(SyntaxKind.OpenBraceToken) && IsControlBlock(currentToken.Parent)) { if (!_options.NewLines.HasFlag(NewLinePlacement.BeforeOpenBraceInControlBlocks)) { operation = CreateAdjustSpacesOperation(1, AdjustSpacesOption.ForceSpaces); } } return operation; } public override AdjustNewLinesOperation? GetAdjustNewLinesOperation(in SyntaxToken previousToken, in SyntaxToken currentToken, in NextGetAdjustNewLinesOperation nextOperation) { RoslynDebug.AssertNotNull(currentToken.Parent); var operation = nextOperation.Invoke(in previousToken, in currentToken); // else condition is actually handled in the GetAdjustSpacesOperation() // For Object Initialization Expression if (previousToken.IsKind(SyntaxKind.CommaToken) && previousToken.Parent.IsKind(SyntaxKind.ObjectInitializerExpression)) { if (_options.NewLines.HasFlag(NewLinePlacement.BeforeMembersInObjectInitializers)) { return CreateAdjustNewLinesOperation(1, AdjustNewLinesOption.PreserveLines); } else { // we never force it to move up unless it is already on same line return CreateAdjustNewLinesOperation(0, AdjustNewLinesOption.PreserveLines); } } // For Anonymous Object Creation Expression if (previousToken.IsKind(SyntaxKind.CommaToken) && previousToken.Parent.IsKind(SyntaxKind.AnonymousObjectCreationExpression)) { if (_options.NewLines.HasFlag(NewLinePlacement.BeforeMembersInAnonymousTypes)) { return CreateAdjustNewLinesOperation(1, AdjustNewLinesOption.PreserveLines); } else { // we never force it to move up unless it is already on same line return CreateAdjustNewLinesOperation(0, AdjustNewLinesOption.PreserveLines); } } // } else in the if else context if (previousToken.IsKind(SyntaxKind.CloseBraceToken) && currentToken.IsKind(SyntaxKind.ElseKeyword)) { if (_options.NewLines.HasFlag(NewLinePlacement.BeforeElse) || previousToken.Parent!.Parent != currentToken.Parent.Parent) { return CreateAdjustNewLinesOperation(1, AdjustNewLinesOption.PreserveLines); } else { return null; } } // * catch in the try catch context if (currentToken.IsKind(SyntaxKind.CatchKeyword)) { if (_options.NewLines.HasFlag(NewLinePlacement.BeforeCatch)) { return CreateAdjustNewLinesOperation(1, AdjustNewLinesOption.PreserveLines); } else { return null; } } // * Finally if (currentToken.IsKind(SyntaxKind.FinallyKeyword)) { if (_options.NewLines.HasFlag(NewLinePlacement.BeforeFinally)) { return CreateAdjustNewLinesOperation(1, AdjustNewLinesOption.PreserveLines); } else { return null; } } // * { - in the type declaration context if (currentToken.IsKind(SyntaxKind.OpenBraceToken) && currentToken.Parent is BaseTypeDeclarationSyntax or NamespaceDeclarationSyntax) { if (_options.NewLines.HasFlag(NewLinePlacement.BeforeOpenBraceInTypes)) { return CreateAdjustNewLinesOperation(1, AdjustNewLinesOption.PreserveLines); } else { return null; } } // new { - Anonymous object creation if (currentToken.IsKind(SyntaxKind.OpenBraceToken) && currentToken.Parent.IsKind(SyntaxKind.AnonymousObjectCreationExpression)) { if (_options.NewLines.HasFlag(NewLinePlacement.BeforeOpenBraceInAnonymousTypes)) { return CreateAdjustNewLinesOperation(1, AdjustNewLinesOption.PreserveLines); } else { return null; } } // new MyObject { - Object Initialization // new List<int> { - Collection Initialization // with { - Record with initializer // is { - property pattern clauses if (currentToken.IsKind(SyntaxKind.OpenBraceToken) && currentToken.Parent.Kind() is SyntaxKind.ObjectInitializerExpression or SyntaxKind.CollectionInitializerExpression or SyntaxKind.WithInitializerExpression or SyntaxKind.PropertyPatternClause) { if (_options.NewLines.HasFlag(NewLinePlacement.BeforeOpenBraceInObjectCollectionArrayInitializers)) { return CreateAdjustNewLinesOperation(1, AdjustNewLinesOption.PreserveLines); } else { return null; } } // Array Initialization Expression // int[] arr = new int[] { // new[] { // { - Implicit Array if (currentToken.IsKind(SyntaxKind.OpenBraceToken) && currentToken.Parent.Kind() is SyntaxKind.ArrayInitializerExpression or SyntaxKind.ImplicitArrayCreationExpression) { return null; } var currentTokenParentParent = currentToken.Parent.Parent; // * { - in the member declaration context if (currentToken.IsKind(SyntaxKind.OpenBraceToken) && currentTokenParentParent is MemberDeclarationSyntax) { var option = currentTokenParentParent is BasePropertyDeclarationSyntax ? _options.NewLines.HasFlag(NewLinePlacement.BeforeOpenBraceInProperties) : _options.NewLines.HasFlag(NewLinePlacement.BeforeOpenBraceInMethods); if (option) { return CreateAdjustNewLinesOperation(1, AdjustNewLinesOption.PreserveLines); } else { return null; } } // * { - in the property accessor context if (currentToken.IsKind(SyntaxKind.OpenBraceToken) && currentTokenParentParent is AccessorDeclarationSyntax) { if (_options.NewLines.HasFlag(NewLinePlacement.BeforeOpenBraceInAccessors)) { return CreateAdjustNewLinesOperation(1, AdjustNewLinesOption.PreserveLines); } else { return null; } } // * { - in the anonymous Method context if (currentToken.IsKind(SyntaxKind.OpenBraceToken) && currentTokenParentParent.IsKind(SyntaxKind.AnonymousMethodExpression)) { if (_options.NewLines.HasFlag(NewLinePlacement.BeforeOpenBraceInAnonymousMethods)) { return CreateAdjustNewLinesOperation(1, AdjustNewLinesOption.ForceLinesIfOnSingleLine); } else { return null; } } // * { - in the local function context if (currentToken.IsKind(SyntaxKind.OpenBraceToken) && currentTokenParentParent.IsKind(SyntaxKind.LocalFunctionStatement)) { if (_options.NewLines.HasFlag(NewLinePlacement.BeforeOpenBraceInMethods)) { return CreateAdjustNewLinesOperation(1, AdjustNewLinesOption.PreserveLines); } else { return null; } } // * { - in the simple Lambda context if (currentToken.IsKind(SyntaxKind.OpenBraceToken) && currentTokenParentParent is (kind: SyntaxKind.SimpleLambdaExpression or SyntaxKind.ParenthesizedLambdaExpression)) { if (_options.NewLines.HasFlag(NewLinePlacement.BeforeOpenBraceInLambdaExpressionBody)) { return CreateAdjustNewLinesOperation(1, AdjustNewLinesOption.ForceLinesIfOnSingleLine); } else { return null; } } // * { - in the switch expression context if (currentToken.IsKind(SyntaxKind.OpenBraceToken) && currentToken.Parent.IsKind(SyntaxKind.SwitchExpression)) { if (_options.NewLines.HasFlag(NewLinePlacement.BeforeOpenBraceInObjectCollectionArrayInitializers)) { return CreateAdjustNewLinesOperation(1, AdjustNewLinesOption.PreserveLines); } else { return null; } } // * { - in the control statement context if (currentToken.IsKind(SyntaxKind.OpenBraceToken) && IsControlBlock(currentToken.Parent)) { if (_options.NewLines.HasFlag(NewLinePlacement.BeforeOpenBraceInControlBlocks)) { return CreateAdjustNewLinesOperation(1, AdjustNewLinesOption.PreserveLines); } else { return null; } } // Wrapping - Leave statements on same line (false): // Insert a newline between the previous statement and this one. // ; * if (previousToken.IsKind(SyntaxKind.SemicolonToken) && (previousToken.Parent is StatementSyntax && !previousToken.Parent.IsKind(SyntaxKind.ForStatement)) && !_options.WrappingKeepStatementsOnSingleLine) { return CreateAdjustNewLinesOperation(1, AdjustNewLinesOption.PreserveLines); } return operation; } } |