/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/EditorFeatures/CSharp/AutomaticCompletion/AutomaticLineEnderCommandHandler_Helpers.cs
1 046 строк
48 KB
DoctorKrolic
Support smart break line for `fixed` statement (#83543)
18 май 2026, 23:18
Не верифицирован
18 май 2026, 23:18
aab8ca0
Код
Авторство
О чём код?
// 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.Collections.Immutable; using System.Linq; using System.Threading; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Editor.CSharp.AutomaticCompletion; using static CSharpSyntaxTokens; using static SyntaxFactory; internal sealed partial class AutomaticLineEnderCommandHandler { #region NodeReplacementHelpers private static (SyntaxNode newRoot, int nextCaretPosition) ReplaceStatementOwnerAndInsertStatement( SolutionServices services, SyntaxNode root, SyntaxNode oldNode, SyntaxNode newNode, SyntaxNode anchorNode, ImmutableArray<StatementSyntax> nodesToInsert, SyntaxFormattingOptions formattingOptions, CancellationToken cancellationToken) { var rootEditor = new SyntaxEditor(root, services); // 1. Insert the node before anchor node rootEditor.InsertAfter(anchorNode, nodesToInsert); // 2. Replace the old node with newNode. (new node is the node with correct braces) rootEditor.ReplaceNode(oldNode, newNode.WithAdditionalAnnotations(s_replacementNodeAnnotation)); var newRoot = rootEditor.GetChangedRoot(); // 4. Format the new node so that the inserted braces/blocks would have correct indentation and formatting. var newNodeAfterInsertion = newRoot.GetAnnotatedNodes(s_replacementNodeAnnotation).Single(); var formattedNewRoot = Formatter.Format( newRoot, newNodeAfterInsertion.Span, services, formattingOptions, cancellationToken); // 4. Use the annotation to find the end of the open brace, it would be the new caret position var nextCaretPosition = formattedNewRoot.GetAnnotatedTokens(s_openBracePositionAnnotation).Single().Span.End; return (formattedNewRoot, nextCaretPosition); } private static SyntaxNode ReplaceNodeAndFormat( SolutionServices services, SyntaxNode root, SyntaxNode oldNode, SyntaxNode newNode, SyntaxFormattingOptions formattingOptions, CancellationToken cancellationToken) { // 1. Tag the new node so that it could be found later. var annotatedNewNode = newNode.WithAdditionalAnnotations(s_replacementNodeAnnotation); // 2. Replace the old node with newNode. (new node is the node with correct braces) var newRoot = root.ReplaceNode( oldNode, annotatedNewNode); // 3. Find the newNode in the new syntax root. var newNodeAfterInsertion = newRoot.GetAnnotatedNodes(s_replacementNodeAnnotation).Single(); // 4. Format the new node so that the inserted braces/blocks would have correct indentation and formatting. var formattedNewRoot = Formatter.Format( newRoot, newNodeAfterInsertion.Span, services, formattingOptions, cancellationToken); return formattedNewRoot; } #endregion #region EmbeddedStatementModificationHelpers private static (SyntaxNode newRoot, int nextCaretPosition) AddBraceToEmbeddedStatementOwner( SolutionServices services, SyntaxNode root, SyntaxNode embeddedStatementOwner, SyntaxFormattingOptions formattingOptions, CancellationToken cancellationToken) { // If there is no inner statement, just add an empty block to it. // e.g. // class Bar // { // if (true)$$ // } // => // class Bar // { // if (true) // { // } // } var statement = embeddedStatementOwner.GetEmbeddedStatement(); if (statement == null || statement.IsMissing) { var newRoot = ReplaceNodeAndFormat( services, root, embeddedStatementOwner, WithBraces(embeddedStatementOwner, formattingOptions), formattingOptions, cancellationToken); // Locate the open brace token, and move the caret after it. var nextCaretPosition = GetOpenBraceSpanEnd(newRoot); return (newRoot, nextCaretPosition); } // There is an inner statement, it needs to be handled differently in addition to adding the block, // For while, ForEach, Lock, Using and Fixed statement, // If there is a statement in the embeddedStatementOwner, // move the old statement next to the statementOwner, // and insert a empty block into the statementOwner, // e.g. // before: // whi$$le(true) // var i = 1; // for this case 'var i = 1;' is thought as the inner statement, // // after: // while(true) // { // $$ // } // var i = 1; return embeddedStatementOwner switch { WhileStatementSyntax or ForEachStatementSyntax or ForStatementSyntax or LockStatementSyntax or UsingStatementSyntax or FixedStatementSyntax => ReplaceStatementOwnerAndInsertStatement( services, root, oldNode: embeddedStatementOwner, newNode: AddBlockToEmbeddedStatementOwner(embeddedStatementOwner, formattingOptions), anchorNode: embeddedStatementOwner, nodesToInsert: [statement], formattingOptions, cancellationToken), DoStatementSyntax doStatementNode => AddBraceToDoStatement(services, root, doStatementNode, formattingOptions, statement, cancellationToken), IfStatementSyntax ifStatementNode => AddBraceToIfStatement(services, root, ifStatementNode, formattingOptions, statement, cancellationToken), ElseClauseSyntax elseClauseNode => AddBraceToElseClause(services, root, elseClauseNode, formattingOptions, statement, cancellationToken), _ => throw ExceptionUtilities.UnexpectedValue(embeddedStatementOwner), }; } private static (SyntaxNode newRoot, int nextCaretPosition) AddBraceToDoStatement( SolutionServices services, SyntaxNode root, DoStatementSyntax doStatementNode, SyntaxFormattingOptions formattingOptions, StatementSyntax innerStatement, CancellationToken cancellationToken) { // If this do statement doesn't end with the 'while' parts // e.g: // before: // d$$o // Print("hello"); // after: // do // { // $$ // } // Print("hello"); if (doStatementNode.WhileKeyword.IsMissing && doStatementNode.SemicolonToken.IsMissing && doStatementNode.OpenParenToken.IsMissing && doStatementNode.CloseParenToken.IsMissing) { return ReplaceStatementOwnerAndInsertStatement( services, root, oldNode: doStatementNode, newNode: AddBlockToEmbeddedStatementOwner(doStatementNode, formattingOptions), anchorNode: doStatementNode, nodesToInsert: [innerStatement], formattingOptions, cancellationToken); } // if the do statement has 'while' as an end // e.g: // before: // d$$o // Print("hello"); // while (true); // after: // do // { // $$ // Print("hello"); // } while(true); var newRoot = ReplaceNodeAndFormat( services, root, doStatementNode, AddBlockToEmbeddedStatementOwner(doStatementNode, formattingOptions, innerStatement), formattingOptions, cancellationToken); var nextCaretPosition = GetOpenBraceSpanEnd(newRoot); return (newRoot, nextCaretPosition); } private static (SyntaxNode newRoot, int nextCaretPosition) AddBraceToIfStatement( SolutionServices services, SyntaxNode root, IfStatementSyntax ifStatementNode, SyntaxFormattingOptions formattingOptions, StatementSyntax innerStatement, CancellationToken cancellationToken) { // This ifStatement doesn't have an else clause, and its parent is a Block. // Insert the innerStatement next to the ifStatement // e.g. // if ($$a) // Print(); // => // if (a) // { // $$ // } // Print(); if (ifStatementNode is { Else: null, Parent: BlockSyntax }) { return ReplaceStatementOwnerAndInsertStatement( services, root, ifStatementNode, AddBlockToEmbeddedStatementOwner(ifStatementNode, formattingOptions), ifStatementNode, [innerStatement], formattingOptions, cancellationToken); } // If this IfStatement has an else statement after // e.g. // before: // if $$(true) // print("Hello"); // else {} // after: // if (true) // { // $$ // print("Hello"); // } // else {} var newRoot = ReplaceNodeAndFormat( services, root, ifStatementNode, AddBlockToEmbeddedStatementOwner(ifStatementNode, formattingOptions, innerStatement), formattingOptions, cancellationToken); var nextCaretPosition = GetOpenBraceSpanEnd(newRoot); return (newRoot, nextCaretPosition); } private static (SyntaxNode newRoot, int nextCaretPosition) AddBraceToElseClause( SolutionServices services, SyntaxNode root, ElseClauseSyntax elseClauseNode, SyntaxFormattingOptions formattingOptions, StatementSyntax innerStatement, CancellationToken cancellationToken) { // If this is an 'els$$e if(true)' statement, // then treat it as the selected node is the nested if statement if (elseClauseNode.Statement is IfStatementSyntax) { return AddBraceToEmbeddedStatementOwner(services, root, elseClauseNode.Statement, formattingOptions, cancellationToken); } // Otherwise, it is just an ending else clause. // if its parent is an ifStatement and the parent of ifStatement is a block, insert the innerStatement after the ifStatement // e.g. before: // if (true) // { // } els$$e // Print(); // after: // if (true) // { // } els$$e // { // $$ // } // Print(); if (elseClauseNode.Parent is IfStatementSyntax { Parent: BlockSyntax }) { return ReplaceStatementOwnerAndInsertStatement( services, root, elseClauseNode, WithBraces(elseClauseNode, formattingOptions), elseClauseNode.Parent, [innerStatement], formattingOptions, cancellationToken); } // For all the other cases, // Put the innerStatement into the block // e.g. // if (a) // if (true) // { // } // else // { // $$ // Print(); // } // => // if (a) // if (true) // { // } // els$$e // Print(); var formattedNewRoot = ReplaceNodeAndFormat( services, root, elseClauseNode, AddBlockToEmbeddedStatementOwner(elseClauseNode, formattingOptions, innerStatement), formattingOptions, cancellationToken); var nextCaretPosition = formattedNewRoot.GetAnnotatedTokens(s_openBracePositionAnnotation).Single().Span.End; return (formattedNewRoot, nextCaretPosition); } #endregion #region ObjectCreationExpressionModificationHelpers private static (SyntaxNode newNode, SyntaxNode oldNode) ModifyObjectCreationExpressionNode( BaseObjectCreationExpressionSyntax baseObjectCreationExpressionNode, bool addOrRemoveInitializer, SyntaxFormattingOptions formattingOptions) { // 1. Add '()' after the type or new keyword. // e.g. // case 1: 'var c = new Bar' becomes 'var c = new Bar()' // case 2: 'Bar b = new' becomes 'Bar b = new()' var objectCreationNodeWithArgumentList = WithArgumentListIfNeeded(baseObjectCreationExpressionNode, addOrRemoveInitializer); // 2. Add or remove initializer // e.g. var c = new Bar() => var c = new Bar() { } var objectCreationNodeWithCorrectInitializer = addOrRemoveInitializer ? WithBraces(objectCreationNodeWithArgumentList, formattingOptions) : WithoutBraces(objectCreationNodeWithArgumentList); // 3. Handle the semicolon. // If the next token is a semicolon, e.g. // var l = new Ba$$r() { } => var l = new Ba$$r() { }; var nextToken = baseObjectCreationExpressionNode.GetLastToken(includeZeroWidth: true).GetNextToken(includeZeroWidth: true); if (nextToken.IsKind(SyntaxKind.SemicolonToken) && nextToken.Parent != null && nextToken.Parent.Contains(baseObjectCreationExpressionNode)) { var objectCreationNodeContainer = nextToken.Parent; // Replace the old object creation node and add the semicolon token. // Note: need to move the trailing trivia of the objectCreationExpressionNode after the semicolon token // e.g. // var l = new Bar() {} // I am some comments // => // var l = new Bar() {}; // I am some comments var replacementContainerNode = objectCreationNodeContainer.ReplaceSyntax( nodes: [baseObjectCreationExpressionNode], (_, _) => objectCreationNodeWithCorrectInitializer.WithoutTrailingTrivia(), tokens: [nextToken], computeReplacementToken: (_, _) => SemicolonToken.WithTrailingTrivia(objectCreationNodeWithCorrectInitializer.GetTrailingTrivia()), trivia: [], computeReplacementTrivia: (_, syntaxTrivia) => syntaxTrivia); return (replacementContainerNode, objectCreationNodeContainer); } else { // No need to change the semicolon, just return the objectCreationExpression with correct initializer return (objectCreationNodeWithCorrectInitializer, baseObjectCreationExpressionNode); } } /// <summary> /// Add argument list to the objectCreationExpression if needed. /// e.g. new Bar; => new Bar(); /// </summary> private static BaseObjectCreationExpressionSyntax WithArgumentListIfNeeded(BaseObjectCreationExpressionSyntax baseObjectCreationExpressionNode, bool addingInitializer) { var argumentList = baseObjectCreationExpressionNode.ArgumentList; if (argumentList is { IsMissing: false }) { return baseObjectCreationExpressionNode; } RoslynDebug.Assert(!baseObjectCreationExpressionNode.NewKeyword.IsMissing); if (baseObjectCreationExpressionNode is ObjectCreationExpressionSyntax objectCreationExpressionNode) { var typeNode = objectCreationExpressionNode.Type; if (typeNode.IsMissing) { // There is only 'new' keyword in the object creation expression. Treat it as an ImplicitObjectCreationExpression. // This could happen because when only type 'new', parser would think it is an ObjectCreationExpression. var newKeywordToken = baseObjectCreationExpressionNode.NewKeyword; var newArgumentList = ArgumentList().WithTrailingTrivia(newKeywordToken.TrailingTrivia); return ImplicitObjectCreationExpression(newKeywordToken.WithoutTrailingTrivia(), newArgumentList, baseObjectCreationExpressionNode.Initializer); } else { if (addingInitializer) { // If we are adding an initializer and user didn't type constructor parenthesis, // in which case argument list might be missing due to incompletely typed statement, // replace it with null so we keep user's intent by not forcing parenthesis on one side // and produce expected tree shape on the other return baseObjectCreationExpressionNode.WithArgumentList(null); } // Make sure the trailing trivia is passed to the argument list // like var l = new List\r\n => // var l = new List()\r\r var newArgumentList = ArgumentList().WithTrailingTrivia(typeNode.GetTrailingTrivia()); var newTypeNode = typeNode.WithoutTrivia(); return objectCreationExpressionNode.WithType(newTypeNode).WithArgumentList(newArgumentList); } } if (baseObjectCreationExpressionNode is ImplicitObjectCreationExpressionSyntax implicitObjectCreationExpressionNode) { var newKeywordToken = implicitObjectCreationExpressionNode.NewKeyword; var newArgumentList = ArgumentList().WithTrailingTrivia(newKeywordToken.TrailingTrivia); return ImplicitObjectCreationExpression(newKeywordToken.WithoutTrailingTrivia(), newArgumentList, baseObjectCreationExpressionNode.Initializer); } RoslynDebug.Assert(false, $"New derived type of {nameof(BaseObjectCreationExpressionSyntax)} is added"); return baseObjectCreationExpressionNode; } #endregion #region ShouldAddBraceCheck private static bool ShouldAddBraces(SyntaxNode node, int caretPosition) => node switch { NamespaceDeclarationSyntax namespaceDeclarationNode => ShouldAddBraceForNamespaceDeclaration(namespaceDeclarationNode, caretPosition), BaseTypeDeclarationSyntax baseTypeDeclarationNode => ShouldAddBraceForBaseTypeDeclaration(baseTypeDeclarationNode, caretPosition), BaseMethodDeclarationSyntax baseMethodDeclarationNode => ShouldAddBraceForBaseMethodDeclaration(baseMethodDeclarationNode, caretPosition), LocalFunctionStatementSyntax localFunctionStatementNode => ShouldAddBraceForLocalFunctionStatement(localFunctionStatementNode, caretPosition), BaseObjectCreationExpressionSyntax baseObjectCreationExpressionNode => ShouldAddBraceForBaseObjectCreationExpression(baseObjectCreationExpressionNode), BaseFieldDeclarationSyntax baseFieldDeclarationNode => ShouldAddBraceForBaseFieldDeclaration(baseFieldDeclarationNode), AccessorDeclarationSyntax accessorDeclarationNode => ShouldAddBraceForAccessorDeclaration(accessorDeclarationNode), IndexerDeclarationSyntax indexerDeclarationNode => ShouldAddBraceForIndexerDeclaration(indexerDeclarationNode, caretPosition), SwitchStatementSyntax switchStatementNode => ShouldAddBraceForSwitchStatement(switchStatementNode), TryStatementSyntax tryStatementNode => ShouldAddBraceForTryStatement(tryStatementNode, caretPosition), CatchClauseSyntax catchClauseNode => ShouldAddBraceForCatchClause(catchClauseNode, caretPosition), FinallyClauseSyntax finallyClauseNode => ShouldAddBraceForFinallyClause(finallyClauseNode, caretPosition), DoStatementSyntax doStatementNode => ShouldAddBraceForDoStatement(doStatementNode, caretPosition), CommonForEachStatementSyntax commonForEachStatementNode => ShouldAddBraceForCommonForEachStatement(commonForEachStatementNode, caretPosition), FixedStatementSyntax fixedStatement => ShouldAddBraceForFixedStatement(fixedStatement, caretPosition), ForStatementSyntax forStatementNode => ShouldAddBraceForForStatement(forStatementNode, caretPosition), IfStatementSyntax ifStatementNode => ShouldAddBraceForIfStatement(ifStatementNode, caretPosition), ElseClauseSyntax elseClauseNode => ShouldAddBraceForElseClause(elseClauseNode, caretPosition), LockStatementSyntax lockStatementNode => ShouldAddBraceForLockStatement(lockStatementNode, caretPosition), UsingStatementSyntax usingStatementNode => ShouldAddBraceForUsingStatement(usingStatementNode, caretPosition), WhileStatementSyntax whileStatementNode => ShouldAddBraceForWhileStatement(whileStatementNode, caretPosition), CheckedStatementSyntax checkedStatementNode => ShouldAddBraceForCheckedStatement(checkedStatementNode, caretPosition), _ => false, }; /// <summary> /// For namespace, make sure it has name there is no braces /// </summary> private static bool ShouldAddBraceForNamespaceDeclaration(NamespaceDeclarationSyntax namespaceDeclarationNode, int caretPosition) => !namespaceDeclarationNode.Name.IsMissing && HasNoBrace(namespaceDeclarationNode) && !WithinAttributeLists(namespaceDeclarationNode, caretPosition) && !WithinBraces(namespaceDeclarationNode, caretPosition); /// <summary> /// For class/struct/enum ..., make sure it has name and there is no braces. /// </summary> private static bool ShouldAddBraceForBaseTypeDeclaration(BaseTypeDeclarationSyntax baseTypeDeclarationNode, int caretPosition) => !baseTypeDeclarationNode.Identifier.IsMissing && HasNoBrace(baseTypeDeclarationNode) && !WithinAttributeLists(baseTypeDeclarationNode, caretPosition) && !WithinBraces(baseTypeDeclarationNode, caretPosition); /// <summary> /// For method, make sure it has a ParameterList, because later braces would be inserted after the Parameterlist /// </summary> private static bool ShouldAddBraceForBaseMethodDeclaration(BaseMethodDeclarationSyntax baseMethodDeclarationNode, int caretPosition) => baseMethodDeclarationNode.ExpressionBody == null && baseMethodDeclarationNode.Body == null && !baseMethodDeclarationNode.ParameterList.IsMissing && baseMethodDeclarationNode.SemicolonToken.IsMissing && !WithinAttributeLists(baseMethodDeclarationNode, caretPosition) && !WithinMethodBody(baseMethodDeclarationNode, caretPosition) // Make sure we don't insert braces for method in Interface. && !baseMethodDeclarationNode.IsParentKind(SyntaxKind.InterfaceDeclaration); /// <summary> /// For local Function, make sure it has a ParameterList, because later braces would be inserted after the Parameterlist /// </summary> private static bool ShouldAddBraceForLocalFunctionStatement(LocalFunctionStatementSyntax localFunctionStatementNode, int caretPosition) => localFunctionStatementNode.ExpressionBody == null && localFunctionStatementNode.Body == null && !localFunctionStatementNode.ParameterList.IsMissing && !WithinAttributeLists(localFunctionStatementNode, caretPosition) && !WithinMethodBody(localFunctionStatementNode, caretPosition); /// <summary> /// Add brace for BaseObjectCreationExpression if it doesn't have initializer /// </summary> private static bool ShouldAddBraceForBaseObjectCreationExpression(BaseObjectCreationExpressionSyntax baseObjectCreationExpressionNode) => baseObjectCreationExpressionNode.Initializer is null; /// <summary> /// Add braces for field and event field if they only have one variable, semicolon is missing and don't have readonly keyword /// Example: /// public int Bar$$ => /// public int Bar /// { /// $$ /// } /// This would change field to property, and change event field to event declaration. /// </summary> private static bool ShouldAddBraceForBaseFieldDeclaration(BaseFieldDeclarationSyntax baseFieldDeclarationNode) => baseFieldDeclarationNode is { Declaration.Variables: [{ Initializer: null }], SemicolonToken.IsMissing: true } && !baseFieldDeclarationNode.Modifiers.Any(SyntaxKind.ReadOnlyKeyword); private static bool ShouldAddBraceForAccessorDeclaration(AccessorDeclarationSyntax accessorDeclarationNode) { if (accessorDeclarationNode.Body == null && accessorDeclarationNode.ExpressionBody == null && accessorDeclarationNode.SemicolonToken.IsMissing) { // If the accessor doesn't have body, expression body and semicolon, let's check this case // for both event and property, // e.g. // int Bar // { // get; // se$$t // } // because if the getter doesn't have a body then setter also shouldn't have any body. // Don't check for indexer because the accessor for indexer should have body. var parent = accessorDeclarationNode.Parent; var parentOfParent = parent?.Parent; if (parent is AccessorListSyntax accessorListNode && parentOfParent is PropertyDeclarationSyntax) { var otherAccessors = accessorListNode.Accessors .Except([accessorDeclarationNode]) .ToImmutableArray(); if (!otherAccessors.IsEmpty) { return !otherAccessors.Any( static accessor => accessor.Body == null && accessor.ExpressionBody == null && !accessor.SemicolonToken.IsMissing); } } return true; } return false; } /// <summary> /// For indexer, switch, try and catch syntax node without braces, if it is the last child of its parent, it would /// use its parent's close brace as its own. /// Example: /// class Bar /// { /// int th$$is[int i] /// } /// In this case, parser would think the last '}' belongs to the indexer, not the class. /// Therefore, only check if the open brace is missing for these 4 types of SyntaxNode /// </summary> private static bool ShouldAddBraceForIndexerDeclaration(IndexerDeclarationSyntax indexerDeclarationNode, int caretPosition) { if (WithinAttributeLists(indexerDeclarationNode, caretPosition) || WithinBraces(indexerDeclarationNode.AccessorList, caretPosition)) { return false; } // Make sure it has brackets var (openBracket, closeBracket) = indexerDeclarationNode.ParameterList.GetBrackets(); if (openBracket.IsMissing || closeBracket.IsMissing) { return false; } // If both accessorList and body is empty if ((indexerDeclarationNode.AccessorList == null || indexerDeclarationNode.AccessorList.IsMissing) && indexerDeclarationNode.ExpressionBody == null) { return true; } return indexerDeclarationNode.AccessorList != null && indexerDeclarationNode.AccessorList.OpenBraceToken.IsMissing; } // For the Switch, Try, Catch, Finally node // e.g. // class Bar // { // void Main() // { // tr$$y // } // } // In this case, the last close brace of 'void Main()' would be thought as a part of the try statement, // and the last close brace of 'Bar' would be thought as a part of Main() // So for these case, just check if the open brace is missing. private static bool ShouldAddBraceForSwitchStatement(SwitchStatementSyntax switchStatementNode) => !switchStatementNode.SwitchKeyword.IsMissing && switchStatementNode.OpenBraceToken.IsMissing; private static bool ShouldAddBraceForTryStatement(TryStatementSyntax tryStatementNode, int caretPosition) => !tryStatementNode.TryKeyword.IsMissing && tryStatementNode.Block.OpenBraceToken.IsMissing && !tryStatementNode.Block.Span.Contains(caretPosition); private static bool ShouldAddBraceForCatchClause(CatchClauseSyntax catchClauseSyntax, int caretPosition) => !catchClauseSyntax.CatchKeyword.IsMissing && catchClauseSyntax.Block.OpenBraceToken.IsMissing && !catchClauseSyntax.Block.Span.Contains(caretPosition); private static bool ShouldAddBraceForFinallyClause(FinallyClauseSyntax finallyClauseNode, int caretPosition) => !finallyClauseNode.FinallyKeyword.IsMissing && finallyClauseNode.Block.OpenBraceToken.IsMissing && !finallyClauseNode.Block.Span.Contains(caretPosition); private static bool ShouldAddBraceForCheckedStatement(CheckedStatementSyntax checkedStatementNode, int caretPosition) => checkedStatementNode.Block.OpenBraceToken.IsMissing && !checkedStatementNode.Block.Span.Contains(caretPosition); // For all the embeddedStatementOwners, // if the embeddedStatement is not block, insert the the braces if its statement is not block. private static bool ShouldAddBraceForDoStatement(DoStatementSyntax doStatementNode, int caretPosition) => !doStatementNode.DoKeyword.IsMissing && doStatementNode.Statement is not BlockSyntax && doStatementNode.DoKeyword.FullSpan.Contains(caretPosition); private static bool ShouldAddBraceForCommonForEachStatement(CommonForEachStatementSyntax commonForEachStatementNode, int caretPosition) => commonForEachStatementNode.Statement is not BlockSyntax && !commonForEachStatementNode.OpenParenToken.IsMissing && !commonForEachStatementNode.CloseParenToken.IsMissing && !WithinEmbeddedStatement(commonForEachStatementNode, caretPosition); private static bool ShouldAddBraceForFixedStatement(FixedStatementSyntax fixedStatementNode, int caretPosition) => fixedStatementNode.Statement is not BlockSyntax && !fixedStatementNode.OpenParenToken.IsMissing && !fixedStatementNode.CloseParenToken.IsMissing && !WithinEmbeddedStatement(fixedStatementNode, caretPosition); private static bool ShouldAddBraceForForStatement(ForStatementSyntax forStatementNode, int caretPosition) => forStatementNode.Statement is not BlockSyntax && !forStatementNode.OpenParenToken.IsMissing && !forStatementNode.CloseParenToken.IsMissing && !WithinEmbeddedStatement(forStatementNode, caretPosition); private static bool ShouldAddBraceForIfStatement(IfStatementSyntax ifStatementNode, int caretPosition) => ifStatementNode.Statement is not BlockSyntax && !ifStatementNode.OpenParenToken.IsMissing && !ifStatementNode.CloseParenToken.IsMissing && !WithinEmbeddedStatement(ifStatementNode, caretPosition); private static bool ShouldAddBraceForElseClause(ElseClauseSyntax elseClauseNode, int caretPosition) { // In case it is an else-if clause, if the statement is IfStatement, use its insertion statement // otherwise, use the end of the else keyword // Example: // Before: if (a) // { // } else i$$f (b) // After: if (a) // { // } else if (b) // { // $$ // } if (elseClauseNode.Statement is IfStatementSyntax ifStatementNode) { return ShouldAddBraceForIfStatement(ifStatementNode, caretPosition); } else { // Here it should be an elseClause // like: // if (a) // { // } els$$e { // } // So only check the its statement return elseClauseNode.Statement is not BlockSyntax && !WithinEmbeddedStatement(elseClauseNode, caretPosition); } } private static bool ShouldAddBraceForLockStatement(LockStatementSyntax lockStatementNode, int caretPosition) => lockStatementNode.Statement is not BlockSyntax && !lockStatementNode.OpenParenToken.IsMissing && !lockStatementNode.CloseParenToken.IsMissing && !WithinEmbeddedStatement(lockStatementNode, caretPosition); private static bool ShouldAddBraceForUsingStatement(UsingStatementSyntax usingStatementNode, int caretPosition) => usingStatementNode.Statement is not BlockSyntax && !usingStatementNode.OpenParenToken.IsMissing && !usingStatementNode.CloseParenToken.IsMissing && !WithinEmbeddedStatement(usingStatementNode, caretPosition); private static bool ShouldAddBraceForWhileStatement(WhileStatementSyntax whileStatementNode, int caretPosition) => whileStatementNode.Statement is not BlockSyntax && !whileStatementNode.OpenParenToken.IsMissing && !whileStatementNode.CloseParenToken.IsMissing && !WithinEmbeddedStatement(whileStatementNode, caretPosition); private static bool WithinAttributeLists(SyntaxNode node, int caretPosition) { var attributeLists = node.GetAttributeLists(); return attributeLists.Span.Contains(caretPosition); } private static bool WithinBraces(SyntaxNode? node, int caretPosition) { var (openBrace, closeBrace) = node.GetBraces(); return TextSpan.FromBounds(openBrace.SpanStart, closeBrace.Span.End).Contains(caretPosition); } private static bool WithinMethodBody(SyntaxNode node, int caretPosition) { if (node is BaseMethodDeclarationSyntax { Body: { } baseMethodBody }) { return baseMethodBody.Span.Contains(caretPosition); } if (node is LocalFunctionStatementSyntax { Body: { } localFunctionBody }) { return localFunctionBody.Span.Contains(caretPosition); } return false; } private static bool HasNoBrace(SyntaxNode node) { var (openBrace, closeBrace) = node.GetBraces(); return openBrace.IsKind(SyntaxKind.None) && closeBrace.IsKind(SyntaxKind.None) || openBrace.IsMissing && closeBrace.IsMissing; } private static bool WithinEmbeddedStatement(SyntaxNode node, int caretPosition) => node.GetEmbeddedStatement()?.Span.Contains(caretPosition) ?? false; #endregion #region ShouldRemoveBraceCheck private static bool ShouldRemoveBraces(SyntaxNode node, int caretPosition) => node switch { BaseObjectCreationExpressionSyntax baseObjectCreationExpressionNode => ShouldRemoveBraceForBaseObjectCreationExpression(baseObjectCreationExpressionNode), AccessorDeclarationSyntax accessorDeclarationNode => ShouldRemoveBraceForAccessorDeclaration(accessorDeclarationNode, caretPosition), PropertyDeclarationSyntax propertyDeclarationNode => ShouldRemoveBraceForPropertyDeclaration(propertyDeclarationNode, caretPosition), EventDeclarationSyntax eventDeclarationNode => ShouldRemoveBraceForEventDeclaration(eventDeclarationNode, caretPosition), _ => false, }; /// <summary> /// Remove the braces if the BaseObjectCreationExpression has an empty Initializer. /// </summary> private static bool ShouldRemoveBraceForBaseObjectCreationExpression(BaseObjectCreationExpressionSyntax baseObjectCreationExpressionNode) => baseObjectCreationExpressionNode.Initializer is { Expressions.Count: 0 }; // Only do this when it is an accessor in property // Since it is illegal to have something like // int this[int i] { get; set;} // event EventHandler Bar {add; remove;} private static bool ShouldRemoveBraceForAccessorDeclaration(AccessorDeclarationSyntax accessorDeclarationNode, int caretPosition) => accessorDeclarationNode.Body != null && accessorDeclarationNode.Body.Statements.IsEmpty() && accessorDeclarationNode.ExpressionBody == null && accessorDeclarationNode.Parent != null && accessorDeclarationNode.Parent.IsParentKind(SyntaxKind.PropertyDeclaration) && accessorDeclarationNode.Body.Span.Contains(caretPosition); private static bool ShouldRemoveBraceForPropertyDeclaration(PropertyDeclarationSyntax propertyDeclarationNode, int caretPosition) { // If a property just has an empty accessorList, like // int i $${ } // then remove the braces and change it to a field // int i; if (propertyDeclarationNode.AccessorList != null && propertyDeclarationNode.ExpressionBody == null) { var accessorList = propertyDeclarationNode.AccessorList; return accessorList.Span.Contains(caretPosition) && accessorList.Accessors.IsEmpty(); } return false; } private static bool ShouldRemoveBraceForEventDeclaration(EventDeclarationSyntax eventDeclarationNode, int caretPosition) { // If an event declaration just has an empty accessorList, // like // event EventHandler e$$ { } // then change it to a event field declaration // event EventHandler e; var accessorList = eventDeclarationNode.AccessorList; return accessorList != null && accessorList.Span.Contains(caretPosition) && accessorList.Accessors.IsEmpty(); } #endregion #region AddBrace private static InitializerExpressionSyntax GetInitializerExpressionNode(SyntaxFormattingOptions formattingOptions) => InitializerExpression(SyntaxKind.ObjectInitializerExpression) .WithOpenBraceToken(GetOpenBrace(formattingOptions)); private static BlockSyntax GetBlockNode(SyntaxFormattingOptions formattingOptions) => Block().WithOpenBraceToken(GetOpenBrace(formattingOptions)).WithCloseBraceToken(GetCloseBrace(formattingOptions)); private static SyntaxToken GetOpenBrace(SyntaxFormattingOptions formattingOptions) => Token( leading: SyntaxTriviaList.Empty, kind: SyntaxKind.OpenBraceToken, trailing: [GetNewLineTrivia(formattingOptions)]) .WithAdditionalAnnotations(s_openBracePositionAnnotation); private static SyntaxToken GetCloseBrace(SyntaxFormattingOptions formattingOptions) => Token( leading: SyntaxTriviaList.Empty, kind: SyntaxKind.CloseBraceToken, trailing: [GetNewLineTrivia(formattingOptions)]); private static SyntaxTrivia GetNewLineTrivia(SyntaxFormattingOptions formattingOptions) { var newLineString = formattingOptions.NewLine; return EndOfLine(newLineString); } /// <summary> /// Add braces to the <param name="node"/>. /// For FieldDeclaration and EventFieldDeclaration, it will change them to PropertyDeclaration and EventDeclaration /// </summary> private static SyntaxNode WithBraces(SyntaxNode node, SyntaxFormattingOptions formattingOptions) => node switch { BaseTypeDeclarationSyntax baseTypeDeclarationNode => WithBracesForBaseTypeDeclaration(baseTypeDeclarationNode, formattingOptions), BaseObjectCreationExpressionSyntax objectCreationExpressionNode => GetObjectCreationExpressionWithInitializer(objectCreationExpressionNode, formattingOptions), BaseMethodDeclarationSyntax baseMethodDeclarationNode => AddBlockToBaseMethodDeclaration(baseMethodDeclarationNode, formattingOptions), LocalFunctionStatementSyntax localFunctionStatementNode => AddBlockToLocalFunctionDeclaration(localFunctionStatementNode, formattingOptions), AccessorDeclarationSyntax accessorDeclarationNode => AddBlockToAccessorDeclaration(accessorDeclarationNode, formattingOptions), _ when node.IsEmbeddedStatementOwner() => AddBlockToEmbeddedStatementOwner(node, formattingOptions), _ => throw ExceptionUtilities.UnexpectedValue(node), }; /// <summary> /// Add braces to <param name="baseTypeDeclarationNode"/>. /// </summary> private static BaseTypeDeclarationSyntax WithBracesForBaseTypeDeclaration( BaseTypeDeclarationSyntax baseTypeDeclarationNode, SyntaxFormattingOptions formattingOptions) => baseTypeDeclarationNode.WithOpenBraceToken(GetOpenBrace(formattingOptions)) .WithCloseBraceToken(CloseBraceToken); /// <summary> /// Add an empty initializer to <param name="objectCreationExpressionNode"/>. /// </summary> private static BaseObjectCreationExpressionSyntax GetObjectCreationExpressionWithInitializer( BaseObjectCreationExpressionSyntax objectCreationExpressionNode, SyntaxFormattingOptions formattingOptions) => objectCreationExpressionNode.WithInitializer(GetInitializerExpressionNode(formattingOptions)); /// <summary> /// Add an empty block to <param name="baseMethodDeclarationNode"/>. /// </summary> private static BaseMethodDeclarationSyntax AddBlockToBaseMethodDeclaration( BaseMethodDeclarationSyntax baseMethodDeclarationNode, SyntaxFormattingOptions formattingOptions) => baseMethodDeclarationNode.WithBody(GetBlockNode(formattingOptions)) // When the method declaration with no body is parsed, it has an invisible trailing semicolon. Make sure it is removed. .WithSemicolonToken(Token(SyntaxKind.None)); /// <summary> /// Add an empty block to <param name="localFunctionStatementNode"/>. /// </summary> private static LocalFunctionStatementSyntax AddBlockToLocalFunctionDeclaration( LocalFunctionStatementSyntax localFunctionStatementNode, SyntaxFormattingOptions formattingOptions) => localFunctionStatementNode.WithBody(GetBlockNode(formattingOptions)) // When the local method declaration with no body is parsed, it has an invisible trailing semicolon. Make sure it is removed. .WithSemicolonToken(Token(SyntaxKind.None)); /// <summary> /// Add an empty block to <param name="accessorDeclarationNode"/>. /// </summary> private static AccessorDeclarationSyntax AddBlockToAccessorDeclaration( AccessorDeclarationSyntax accessorDeclarationNode, SyntaxFormattingOptions formattingOptions) => accessorDeclarationNode.WithBody(GetBlockNode(formattingOptions)) // When the accessor with no body is parsed, it has an invisible trailing semicolon. Make sure it is removed. .WithSemicolonToken(Token(SyntaxKind.None)); /// <summary> /// Add a block with <param name="extraNodeInsertedBetweenBraces"/> to <param name="embeddedStatementOwner"/> /// </summary> private static SyntaxNode AddBlockToEmbeddedStatementOwner( SyntaxNode embeddedStatementOwner, SyntaxFormattingOptions formattingOptions, StatementSyntax? extraNodeInsertedBetweenBraces = null) { var block = extraNodeInsertedBetweenBraces != null ? GetBlockNode(formattingOptions).WithStatements([extraNodeInsertedBetweenBraces]) : GetBlockNode(formattingOptions); return embeddedStatementOwner switch { DoStatementSyntax doStatementNode => doStatementNode.WithStatement(block), CommonForEachStatementSyntax forEachStatementNode => forEachStatementNode.WithStatement(block), FixedStatementSyntax fixedStatementNode => fixedStatementNode.WithStatement(block), ForStatementSyntax forStatementNode => forStatementNode.WithStatement(block), IfStatementSyntax ifStatementNode => ifStatementNode.WithStatement(block), ElseClauseSyntax elseClauseNode => elseClauseNode.WithStatement(block), WhileStatementSyntax whileStatementNode => whileStatementNode.WithStatement(block), UsingStatementSyntax usingStatementNode => usingStatementNode.WithStatement(block), LockStatementSyntax lockStatementNode => lockStatementNode.WithStatement(block), _ => throw ExceptionUtilities.UnexpectedValue(embeddedStatementOwner) }; } #endregion #region RemoveBrace /// <summary> /// Remove the brace for the input syntax node /// For ObjectCreationExpressionSyntax, it would remove the initializer /// For PropertyDeclarationSyntax, it would change it to a FieldDeclaration /// For EventDeclarationSyntax, it would change it to eventFieldDeclaration /// For Accessor, it would change it to the empty version ending with semicolon. /// e.g get {} => get; /// </summary> private static SyntaxNode WithoutBraces(SyntaxNode node) => node switch { BaseObjectCreationExpressionSyntax baseObjectCreationExpressionNode => RemoveInitializerForBaseObjectCreationExpression(baseObjectCreationExpressionNode), PropertyDeclarationSyntax propertyDeclarationNode => ConvertPropertyDeclarationToFieldDeclaration(propertyDeclarationNode), EventDeclarationSyntax eventDeclarationNode => ConvertEventDeclarationToEventFieldDeclaration(eventDeclarationNode), AccessorDeclarationSyntax accessorDeclarationNode => RemoveBodyForAccessorDeclarationNode(accessorDeclarationNode), _ => throw ExceptionUtilities.UnexpectedValue(node), }; /// <summary> /// Remove the initializer for <param name="baseObjectCreationExpressionNode"/>. /// </summary> private static BaseObjectCreationExpressionSyntax RemoveInitializerForBaseObjectCreationExpression( BaseObjectCreationExpressionSyntax baseObjectCreationExpressionNode) { var objectCreationNodeWithoutInitializer = baseObjectCreationExpressionNode.WithInitializer(null); // Filter the non-comments trivia // e.g. // Bar(new Foo() // I am some comments // { // $$ // }); // => // Bar(new Foo() // I am some comments); // In this case, 'I am somme comments' has an end of line triva, if not removed, it would make // the final result becomes // Bar(new Foo() // I am some comments // ); var trivia = objectCreationNodeWithoutInitializer.GetTrailingTrivia().Where(trivia => trivia.IsSingleOrMultiLineComment()); return objectCreationNodeWithoutInitializer.WithTrailingTrivia(trivia); } /// <summary> /// Convert <param name="propertyDeclarationNode"/> to fieldDeclaration. /// </summary> private static FieldDeclarationSyntax ConvertPropertyDeclarationToFieldDeclaration( PropertyDeclarationSyntax propertyDeclarationNode) => FieldDeclaration( propertyDeclarationNode.AttributeLists, propertyDeclarationNode.Modifiers, VariableDeclaration( propertyDeclarationNode.Type, [VariableDeclarator(propertyDeclarationNode.Identifier)]), SemicolonToken); /// <summary> /// Convert <param name="eventDeclarationNode"/> to EventFieldDeclaration. /// </summary> private static EventFieldDeclarationSyntax ConvertEventDeclarationToEventFieldDeclaration( EventDeclarationSyntax eventDeclarationNode) => EventFieldDeclaration( eventDeclarationNode.AttributeLists, eventDeclarationNode.Modifiers, VariableDeclaration( eventDeclarationNode.Type, [VariableDeclarator(eventDeclarationNode.Identifier)])); /// <summary> /// Remove the body of <param name="accessorDeclarationNode"/>. /// </summary> private static AccessorDeclarationSyntax RemoveBodyForAccessorDeclarationNode(AccessorDeclarationSyntax accessorDeclarationNode) => accessorDeclarationNode .WithBody(null).WithoutTrailingTrivia().WithSemicolonToken( Token(SyntaxTriviaList.Empty, SyntaxKind.SemicolonToken, SyntaxTriviaList.Empty)); #endregion }