/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxNodeExtensions.cs
897 строк
39 KB
Cyrus Najmabadi
Remove redundant comment on IsBreakableConstruct
17 июн 2026, 16:53
17 июн 2026, 16:53
8f41c8c
Код
Авторство
О чём код?
// 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. #pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.LanguageService; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Extensions; internal static partial class SyntaxNodeExtensions { public static LanguageVersion GetLanguageVersion(this SyntaxNode node) => ((CSharpParseOptions)node.SyntaxTree.Options).LanguageVersion; public static void Deconstruct(this SyntaxNode node, out SyntaxKind kind) => kind = node.Kind(); public static bool IsKind<TNode>([NotNullWhen(true)] this SyntaxNode? node, SyntaxKind kind, [NotNullWhen(true)] out TNode? result) where TNode : SyntaxNode { if (node.IsKind(kind)) { result = (TNode)node; return true; } result = null; return false; } public static bool IsParentKind([NotNullWhen(true)] this SyntaxNode? node, SyntaxKind kind) => CodeAnalysis.CSharpExtensions.IsKind(node?.Parent, kind); public static bool IsParentKind<TNode>([NotNullWhen(true)] this SyntaxNode? node, SyntaxKind kind, [NotNullWhen(true)] out TNode? result) where TNode : SyntaxNode { if (node.IsParentKind(kind)) { result = (TNode)node.Parent!; return true; } result = null; return false; } public static IEnumerable<SyntaxTrivia> GetAllPrecedingTriviaToPreviousToken( this SyntaxNode node, SourceText? sourceText = null, bool includePreviousTokenTrailingTriviaOnlyIfOnSameLine = false) => node.GetFirstToken().GetAllPrecedingTriviaToPreviousToken( sourceText, includePreviousTokenTrailingTriviaOnlyIfOnSameLine); /// <summary> /// Returns all of the trivia to the left of this token up to the previous token (concatenates /// the previous token's trailing trivia and this token's leading trivia). /// </summary> public static IEnumerable<SyntaxTrivia> GetAllPrecedingTriviaToPreviousToken( this SyntaxToken token, SourceText? sourceText = null, bool includePreviousTokenTrailingTriviaOnlyIfOnSameLine = false) { var prevToken = token.GetPreviousToken(includeSkipped: true); if (prevToken.Kind() == SyntaxKind.None) { return token.LeadingTrivia; } Contract.ThrowIfTrue(sourceText == null && includePreviousTokenTrailingTriviaOnlyIfOnSameLine, "If we are including previous token trailing trivia, we need the text too."); if (includePreviousTokenTrailingTriviaOnlyIfOnSameLine && !sourceText!.AreOnSameLine(prevToken, token)) { return token.LeadingTrivia; } return prevToken.TrailingTrivia.Concat(token.LeadingTrivia); } public static bool IsAnyArgumentList([NotNullWhen(true)] this SyntaxNode? node) { return node?.Kind() is SyntaxKind.ArgumentList or SyntaxKind.AttributeArgumentList or SyntaxKind.BracketedArgumentList or SyntaxKind.TypeArgumentList; } public static (SyntaxToken openBrace, SyntaxToken closeBrace) GetBraces(this SyntaxNode? node) => node switch { NamespaceDeclarationSyntax namespaceNode => (namespaceNode.OpenBraceToken, namespaceNode.CloseBraceToken), BaseTypeDeclarationSyntax baseTypeNode => (baseTypeNode.OpenBraceToken, baseTypeNode.CloseBraceToken), AccessorListSyntax accessorListNode => (accessorListNode.OpenBraceToken, accessorListNode.CloseBraceToken), BlockSyntax blockNode => (blockNode.OpenBraceToken, blockNode.CloseBraceToken), SwitchStatementSyntax switchStatementNode => (switchStatementNode.OpenBraceToken, switchStatementNode.CloseBraceToken), AnonymousObjectCreationExpressionSyntax anonymousObjectCreationExpression => (anonymousObjectCreationExpression.OpenBraceToken, anonymousObjectCreationExpression.CloseBraceToken), InitializerExpressionSyntax initializeExpressionNode => (initializeExpressionNode.OpenBraceToken, initializeExpressionNode.CloseBraceToken), SwitchExpressionSyntax switchExpression => (switchExpression.OpenBraceToken, switchExpression.CloseBraceToken), PropertyPatternClauseSyntax property => (property.OpenBraceToken, property.CloseBraceToken), WithExpressionSyntax withExpr => (withExpr.Initializer.OpenBraceToken, withExpr.Initializer.CloseBraceToken), ImplicitObjectCreationExpressionSyntax { Initializer: { } initializer } => (initializer.OpenBraceToken, initializer.CloseBraceToken), _ => default, }; public static bool IsEmbeddedStatementOwner([NotNullWhen(true)] this SyntaxNode? node) { return node is DoStatementSyntax or ElseClauseSyntax or FixedStatementSyntax or CommonForEachStatementSyntax or ForStatementSyntax or IfStatementSyntax or LabeledStatementSyntax or LockStatementSyntax or UsingStatementSyntax or WhileStatementSyntax; } public static StatementSyntax? GetEmbeddedStatement(this SyntaxNode? node) => node switch { DoStatementSyntax n => n.Statement, ElseClauseSyntax n => n.Statement, FixedStatementSyntax n => n.Statement, CommonForEachStatementSyntax n => n.Statement, ForStatementSyntax n => n.Statement, IfStatementSyntax n => n.Statement, LabeledStatementSyntax n => n.Statement, LockStatementSyntax n => n.Statement, UsingStatementSyntax n => n.Statement, WhileStatementSyntax n => n.Statement, _ => null, }; public static BaseParameterListSyntax? GetParameterList(this SyntaxNode? declaration) => declaration switch { DelegateDeclarationSyntax delegateDeclaration => delegateDeclaration.ParameterList, BaseMethodDeclarationSyntax methodDeclaration => methodDeclaration.ParameterList, IndexerDeclarationSyntax indexerDeclaration => indexerDeclaration.ParameterList, ParenthesizedLambdaExpressionSyntax parenthesizedLambda => parenthesizedLambda.ParameterList, LocalFunctionStatementSyntax localFunction => localFunction.ParameterList, AnonymousMethodExpressionSyntax anonymousMethod => anonymousMethod.ParameterList, TypeDeclarationSyntax typeDeclaration => typeDeclaration.ParameterList, _ => null, }; public static SyntaxList<AttributeListSyntax> GetAttributeLists(this SyntaxNode? declaration) => declaration switch { MemberDeclarationSyntax memberDecl => memberDecl.AttributeLists, AccessorDeclarationSyntax accessor => accessor.AttributeLists, ParameterSyntax parameter => parameter.AttributeLists, CompilationUnitSyntax compilationUnit => compilationUnit.AttributeLists, StatementSyntax statementSyntax => statementSyntax.AttributeLists, TypeParameterSyntax typeParameter => typeParameter.AttributeLists, LambdaExpressionSyntax lambdaExpressionSyntax => lambdaExpressionSyntax.AttributeLists, _ => default, }; public static ConditionalAccessExpressionSyntax? GetParentConditionalAccessExpression(this SyntaxNode? node) { // Walk upwards based on the grammar/parser rules around ?. expressions (can be seen in // LanguageParser.ParseConsequenceSyntax). // These are the parts of the expression that the ?... expression can end with. Specifically: // // 1. x?.y.M() // invocation // 2. x?.y[...]; // element access // 3. x?.y.z // member access // 4. x?.y // member binding // 5. x?[y] // element binding var current = node; if ((current?.Parent is MemberAccessExpressionSyntax(SyntaxKind.SimpleMemberAccessExpression) memberAccess && memberAccess.Name == current) || (current?.Parent is MemberBindingExpressionSyntax memberBinding && memberBinding.Name == current)) { current = current.Parent; } // Effectively, if we're on the RHS of the ? we have to walk up the RHS spine first until we hit the first // conditional access. while (current is (kind: SyntaxKind.InvocationExpression or SyntaxKind.ElementAccessExpression or SyntaxKind.SimpleMemberAccessExpression or SyntaxKind.MemberBindingExpression or SyntaxKind.ElementBindingExpression or // Optional exclamations might follow the conditional operation. For example: a.b?.$$c!!!!() SyntaxKind.SuppressNullableWarningExpression) && current.Parent is not ConditionalAccessExpressionSyntax) { current = current.Parent; } // Two cases we have to care about: // // 1. a?.b.$$c.d and // 2. a?.b.$$c.d?.e... // // Note that `a?.b.$$c.d?.e.f?.g.h.i` falls into the same bucket as two. i.e. the parts after `.e` are // lower in the tree and are not seen as we walk upwards. // // // To get the root ?. (the one after the `a`) we have to potentially consume the first ?. on the RHS of the // right spine (i.e. the one after `d`). Once we do this, we then see if that itself is on the RHS of a // another conditional, and if so we hten return the one on the left. i.e. for '2' this goes in this direction: // // a?.b.$$c.d?.e // it will do: // -----> // <--------- // // Note that this only one CAE consumption on both sides. GetRootConditionalAccessExpression can be used to // get the root parent in a case like: // // x?.y?.z?.a?.b.$$c.d?.e.f?.g.h.i // it will do: // -----> // <--------- // <--- // <--- // <--- if (current?.Parent is ConditionalAccessExpressionSyntax conditional1 && conditional1.Expression == current) { current = conditional1; } if (current?.Parent is ConditionalAccessExpressionSyntax conditional2 && conditional2.WhenNotNull == current) { current = conditional2; } return current as ConditionalAccessExpressionSyntax; } /// <summary> /// <inheritdoc cref="ISyntaxFacts.GetRootConditionalAccessExpression(SyntaxNode)"/> /// </summary>> public static ConditionalAccessExpressionSyntax? GetRootConditionalAccessExpression(this SyntaxNode? node) { // Once we've walked up the entire RHS, now we continually walk up the conditional accesses until we're at // the root. For example, if we have `a?.b` and we're on the `.b`, this will give `a?.b`. Similarly with // `a?.b?.c` if we're on either `.b` or `.c` this will result in `a?.b?.c` (i.e. the root of this CAE // sequence). var current = node.GetParentConditionalAccessExpression(); while (current?.Parent is ConditionalAccessExpressionSyntax conditional && conditional.WhenNotNull == current) { current = conditional; } return current; } public static ConditionalAccessExpressionSyntax? GetInnerMostConditionalAccessExpression(this SyntaxNode node) { if (node is not ConditionalAccessExpressionSyntax result) return null; while (result.WhenNotNull is ConditionalAccessExpressionSyntax syntax) result = syntax; return result; } public static bool IsAsyncSupportingFunctionSyntax([NotNullWhen(true)] this SyntaxNode? node) => node is MethodDeclarationSyntax or AnonymousFunctionExpressionSyntax or LocalFunctionStatementSyntax; public static bool IsCompoundAssignExpression([NotNullWhen(true)] this SyntaxNode? node) => node is AssignmentExpressionSyntax(kind: not SyntaxKind.SimpleAssignmentExpression); public static bool IsLeftSideOfAssignExpression([NotNullWhen(true)] this SyntaxNode? node) => node?.Parent is AssignmentExpressionSyntax(SyntaxKind.SimpleAssignmentExpression) assignment && assignment.Left == node; public static bool IsLeftSideOfAnyAssignExpression([NotNullWhen(true)] this SyntaxNode? node) => node?.Parent is AssignmentExpressionSyntax assignment && assignment.Left == node; public static bool IsRightSideOfAnyAssignExpression([NotNullWhen(true)] this SyntaxNode? node) => node?.Parent is AssignmentExpressionSyntax assignment && assignment.Right == node; public static bool IsLeftSideOfCompoundAssignExpression([NotNullWhen(true)] this SyntaxNode? node) { return node?.Parent != null && node.Parent.IsCompoundAssignExpression() && ((AssignmentExpressionSyntax)node.Parent).Left == node; } /// <summary> /// Returns the list of using directives that affect <paramref name="node"/>. The list will be returned in /// top down order. /// </summary> public static IEnumerable<UsingDirectiveSyntax> GetEnclosingUsingDirectives(this SyntaxNode node) { return node.GetAncestorOrThis<CompilationUnitSyntax>()!.Usings .Concat(node.GetAncestorsOrThis<BaseNamespaceDeclarationSyntax>() .Reverse() .SelectMany(n => n.Usings)); } public static IEnumerable<ExternAliasDirectiveSyntax> GetEnclosingExternAliasDirectives(this SyntaxNode node) { return node.GetAncestorOrThis<CompilationUnitSyntax>()!.Externs .Concat(node.GetAncestorsOrThis<BaseNamespaceDeclarationSyntax>() .Reverse() .SelectMany(n => n.Externs)); } public static bool IsUnsafeContext(this SyntaxNode node) { if (node.GetAncestor<UnsafeStatementSyntax>() != null) { return true; } return node.GetAncestors<MemberDeclarationSyntax>().Any( m => m.GetModifiers().Any(SyntaxKind.UnsafeKeyword)); } public static bool IsInStaticContext(this SyntaxNode node) { for (var current = node; current != null; current = current.Parent) { switch (current) { // this/base calls are always static. case ConstructorInitializerSyntax: return true; case LocalFunctionStatementSyntax localFunction when localFunction.Modifiers.Any(SyntaxKind.StaticKeyword): return true; case AnonymousFunctionExpressionSyntax anonymousFunction when anonymousFunction.Modifiers.Any(SyntaxKind.StaticKeyword): return true; case BaseMethodDeclarationSyntax or IndexerDeclarationSyntax or EventDeclarationSyntax: return current.GetModifiers().Any(SyntaxKind.StaticKeyword); case PropertyDeclarationSyntax property: return property.Modifiers.Any(SyntaxKind.StaticKeyword) || node.IsFoundUnder((PropertyDeclarationSyntax p) => p.Initializer); case FieldDeclarationSyntax or EventFieldDeclarationSyntax: // Inside a field one can only access static members of a type (unless it's top-level). return !current.Parent.IsKind(SyntaxKind.CompilationUnit); case GlobalStatementSyntax: // Global statements are not a static context. return false; } } // any other location is considered static return true; } public static BaseNamespaceDeclarationSyntax? GetInnermostNamespaceDeclarationWithUsings(this SyntaxNode contextNode) { var usingDirectiveAncestor = contextNode.GetAncestor<UsingDirectiveSyntax>(); if (usingDirectiveAncestor == null) { return contextNode.GetAncestorsOrThis<BaseNamespaceDeclarationSyntax>().FirstOrDefault(n => n.Usings.Count > 0); } else { // We are inside a using directive. In this case, we should find and return the first 'parent' namespace with usings. var containingNamespace = usingDirectiveAncestor.GetAncestor<BaseNamespaceDeclarationSyntax>(); if (containingNamespace == null) { // We are inside a top level using directive (i.e. one that's directly in the compilation unit). return null; } else { return containingNamespace.GetAncestors<BaseNamespaceDeclarationSyntax>().FirstOrDefault(n => n.Usings.Count > 0); } } } public static bool IsBreakableConstruct([NotNullWhen(true)] this SyntaxNode? node) => node.IsContinuableConstruct() || node?.Kind() is SyntaxKind.SwitchStatement; public static bool IsContinuableConstruct([NotNullWhen(true)] this SyntaxNode? node) { switch (node?.Kind()) { case SyntaxKind.DoStatement: case SyntaxKind.WhileStatement: case SyntaxKind.ForStatement: case SyntaxKind.ForEachStatement: case SyntaxKind.ForEachVariableStatement: return true; } return false; } public static bool IsReturnableConstruct(this SyntaxNode node) { switch (node.Kind()) { case SyntaxKind.AnonymousMethodExpression: case SyntaxKind.SimpleLambdaExpression: case SyntaxKind.ParenthesizedLambdaExpression: case SyntaxKind.LocalFunctionStatement: case SyntaxKind.MethodDeclaration: case SyntaxKind.ConstructorDeclaration: case SyntaxKind.DestructorDeclaration: case SyntaxKind.GetAccessorDeclaration: case SyntaxKind.SetAccessorDeclaration: case SyntaxKind.InitAccessorDeclaration: case SyntaxKind.OperatorDeclaration: case SyntaxKind.ConversionOperatorDeclaration: case SyntaxKind.AddAccessorDeclaration: case SyntaxKind.RemoveAccessorDeclaration: return true; } return false; } public static bool ContainsYield(this SyntaxNode node) => node.DescendantNodes(n => n == node || !n.IsReturnableConstruct()).Any(n => n is YieldStatementSyntax); public static bool IsReturnableConstructOrTopLevelCompilationUnit(this SyntaxNode node) => node.IsReturnableConstruct() || (node is CompilationUnitSyntax compilationUnit && compilationUnit.Members.Any(SyntaxKind.GlobalStatement)); public static bool SpansPreprocessorDirective<TSyntaxNode>(this IEnumerable<TSyntaxNode> list) where TSyntaxNode : SyntaxNode => CSharpSyntaxFacts.Instance.SpansPreprocessorDirective(list); [return: NotNullIfNotNull(nameof(node))] public static TNode? ConvertToSingleLine<TNode>(this TNode? node, bool useElasticTrivia = false) where TNode : SyntaxNode { if (node == null) { return node; } var rewriter = new SingleLineRewriter(useElasticTrivia); return (TNode)rewriter.Visit(node); } /// <summary> /// Returns true if the passed in node contains an interleaved pp directive. /// /// i.e. The following returns false: /// /// void Goo() { /// #if true /// #endif /// } /// /// #if true /// void Goo() { /// } /// #endif /// /// but these return true: /// /// #if true /// void Goo() { /// #endif /// } /// /// void Goo() { /// #if true /// } /// #endif /// /// #if true /// void Goo() { /// #else /// } /// #endif /// /// i.e. the method returns true if it contains a PP directive that belongs to a grouping /// constructs (like #if/#endif or #region/#endregion), but the grouping construct isn't /// entirely contained within the span of the node. /// </summary> public static bool ContainsInterleavedDirective(this SyntaxNode syntaxNode, CancellationToken cancellationToken) => CSharpSyntaxFacts.Instance.ContainsInterleavedDirective(syntaxNode, cancellationToken); /// <summary> /// Similar to <see cref="ContainsInterleavedDirective(SyntaxNode, CancellationToken)"/> except that the span to check /// for interleaved directives can be specified separately to the node passed in. /// </summary> public static bool ContainsInterleavedDirective(this SyntaxNode syntaxNode, TextSpan span, CancellationToken cancellationToken) => CSharpSyntaxFacts.Instance.ContainsInterleavedDirective(span, syntaxNode, cancellationToken); public static bool ContainsInterleavedDirective( this SyntaxToken token, TextSpan textSpan, CancellationToken cancellationToken) { return ContainsInterleavedDirective(textSpan, token.LeadingTrivia, cancellationToken) || ContainsInterleavedDirective(textSpan, token.TrailingTrivia, cancellationToken); } private static bool ContainsInterleavedDirective( TextSpan textSpan, SyntaxTriviaList list, CancellationToken cancellationToken) { foreach (var trivia in list) { if (textSpan.Contains(trivia.Span)) { if (ContainsInterleavedDirective(textSpan, trivia, cancellationToken)) { return true; } } } return false; } private static bool ContainsInterleavedDirective( TextSpan textSpan, SyntaxTrivia trivia, CancellationToken cancellationToken) { if (trivia.HasStructure) { var structure = trivia.GetStructure()!; if (trivia.GetStructure() is (kind: SyntaxKind.RegionDirectiveTrivia or SyntaxKind.EndRegionDirectiveTrivia or SyntaxKind.IfDirectiveTrivia or SyntaxKind.EndIfDirectiveTrivia)) { var match = ((DirectiveTriviaSyntax)structure).GetMatchingDirective(cancellationToken); if (match != null) { var matchSpan = match.Span; if (!textSpan.Contains(matchSpan.Start)) { // The match for this pp directive is outside // this node. return true; } } } else if (trivia.GetStructure() is (kind: SyntaxKind.ElseDirectiveTrivia or SyntaxKind.ElifDirectiveTrivia)) { var directives = ((DirectiveTriviaSyntax)structure).GetMatchingConditionalDirectives(cancellationToken); if (directives.Length > 0) { if (!textSpan.Contains(directives[0].SpanStart) || !textSpan.Contains(directives.Last().SpanStart)) { // This else/elif belongs to a pp span that isn't // entirely within this node. return true; } } } } return false; } /// <summary> /// Breaks up the list of provided nodes, based on how they are interspersed with pp /// directives, into groups. Within these groups nodes can be moved around safely, without /// breaking any pp constructs. /// </summary> public static IList<IList<TSyntaxNode>> SplitNodesOnPreprocessorBoundaries<TSyntaxNode>( this IEnumerable<TSyntaxNode> nodes, CancellationToken cancellationToken) where TSyntaxNode : SyntaxNode { var result = new List<IList<TSyntaxNode>>(); var currentGroup = new List<TSyntaxNode>(); foreach (var node in nodes) { var hasUnmatchedInteriorDirective = node.ContainsInterleavedDirective(cancellationToken); var hasLeadingDirective = node.GetLeadingTrivia().Any(t => SyntaxFacts.IsPreprocessorDirective(t.Kind())); if (hasUnmatchedInteriorDirective) { // we have a #if/#endif/#region/#endregion/#else/#elif in // this node that belongs to a span of pp directives that // is not entirely contained within the node. i.e.: // // void Goo() { // #if ... // } // // This node cannot be moved at all. It is in a group that // only contains itself (and thus can never be moved). // add whatever group we've built up to now. And reset the // next group to empty. result.Add(currentGroup); currentGroup = []; result.Add([node]); } else if (hasLeadingDirective) { // We have a PP directive before us. i.e.: // // #if ... // void Goo() { // // That means we start a new group that is contained between // the above directive and the following directive. // add whatever group we've built up to now. And reset the // next group to empty. result.Add(currentGroup); currentGroup = [node]; } else { // simple case. just add ourselves to the current group currentGroup.Add(node); } } // add the remainder of the final group. result.Add(currentGroup); // Now, filter out any empty groups. result = [.. result.Where(group => !group.IsEmpty())]; return result; } public static ImmutableArray<SyntaxTrivia> GetLeadingBlankLines<TSyntaxNode>(this TSyntaxNode node) where TSyntaxNode : SyntaxNode => CSharpFileBannerFacts.Instance.GetLeadingBlankLines(node); public static TSyntaxNode GetNodeWithoutLeadingBlankLines<TSyntaxNode>(this TSyntaxNode node) where TSyntaxNode : SyntaxNode => CSharpFileBannerFacts.Instance.GetNodeWithoutLeadingBlankLines(node); public static TSyntaxNode GetNodeWithoutLeadingBlankLines<TSyntaxNode>(this TSyntaxNode node, out ImmutableArray<SyntaxTrivia> strippedTrivia) where TSyntaxNode : SyntaxNode => CSharpFileBannerFacts.Instance.GetNodeWithoutLeadingBlankLines(node, out strippedTrivia); public static ImmutableArray<SyntaxTrivia> GetLeadingBannerAndPreprocessorDirectives<TSyntaxNode>(this TSyntaxNode node) where TSyntaxNode : SyntaxNode => CSharpFileBannerFacts.Instance.GetLeadingBannerAndPreprocessorDirectives(node); public static TSyntaxNode GetNodeWithoutLeadingBannerAndPreprocessorDirectives<TSyntaxNode>(this TSyntaxNode node) where TSyntaxNode : SyntaxNode => CSharpFileBannerFacts.Instance.GetNodeWithoutLeadingBannerAndPreprocessorDirectives(node); public static TSyntaxNode GetNodeWithoutLeadingBannerAndPreprocessorDirectives<TSyntaxNode>(this TSyntaxNode node, out ImmutableArray<SyntaxTrivia> strippedTrivia) where TSyntaxNode : SyntaxNode => CSharpFileBannerFacts.Instance.GetNodeWithoutLeadingBannerAndPreprocessorDirectives(node, out strippedTrivia); public static bool IsVariableDeclaratorValue([NotNullWhen(true)] this SyntaxNode? node) => node?.Parent is EqualsValueClauseSyntax { Parent: VariableDeclaratorSyntax } equalsValue && equalsValue.Value == node; public static BlockSyntax? FindInnermostCommonBlock(this IEnumerable<SyntaxNode> nodes) => nodes.FindInnermostCommonNode<BlockSyntax>(); public static IEnumerable<SyntaxNode> GetAncestorsOrThis(this SyntaxNode? node, Func<SyntaxNode, bool> predicate) { var current = node; while (current != null) { if (predicate(current)) { yield return current; } current = current.Parent; } } public static (SyntaxToken openParen, SyntaxToken closeParen) GetParentheses(this SyntaxNode node) => node switch { ParenthesizedExpressionSyntax n => (n.OpenParenToken, n.CloseParenToken), MakeRefExpressionSyntax n => (n.OpenParenToken, n.CloseParenToken), RefTypeExpressionSyntax n => (n.OpenParenToken, n.CloseParenToken), RefValueExpressionSyntax n => (n.OpenParenToken, n.CloseParenToken), CheckedExpressionSyntax n => (n.OpenParenToken, n.CloseParenToken), DefaultExpressionSyntax n => (n.OpenParenToken, n.CloseParenToken), TypeOfExpressionSyntax n => (n.OpenParenToken, n.CloseParenToken), SizeOfExpressionSyntax n => (n.OpenParenToken, n.CloseParenToken), ArgumentListSyntax n => (n.OpenParenToken, n.CloseParenToken), CastExpressionSyntax n => (n.OpenParenToken, n.CloseParenToken), WhileStatementSyntax n => (n.OpenParenToken, n.CloseParenToken), DoStatementSyntax n => (n.OpenParenToken, n.CloseParenToken), ForStatementSyntax n => (n.OpenParenToken, n.CloseParenToken), CommonForEachStatementSyntax n => (n.OpenParenToken, n.CloseParenToken), UsingStatementSyntax n => (n.OpenParenToken, n.CloseParenToken), FixedStatementSyntax n => (n.OpenParenToken, n.CloseParenToken), LockStatementSyntax n => (n.OpenParenToken, n.CloseParenToken), IfStatementSyntax n => (n.OpenParenToken, n.CloseParenToken), SwitchStatementSyntax n when n.OpenParenToken != default => (n.OpenParenToken, n.CloseParenToken), TupleExpressionSyntax n => (n.OpenParenToken, n.CloseParenToken), CatchDeclarationSyntax n => (n.OpenParenToken, n.CloseParenToken), AttributeArgumentListSyntax n => (n.OpenParenToken, n.CloseParenToken), ConstructorConstraintSyntax n => (n.OpenParenToken, n.CloseParenToken), ParameterListSyntax n => (n.OpenParenToken, n.CloseParenToken), _ => default, }; public static (SyntaxToken openBracket, SyntaxToken closeBracket) GetBrackets(this SyntaxNode? node) => node switch { ArrayRankSpecifierSyntax n => (n.OpenBracketToken, n.CloseBracketToken), BracketedArgumentListSyntax n => (n.OpenBracketToken, n.CloseBracketToken), ImplicitArrayCreationExpressionSyntax n => (n.OpenBracketToken, n.CloseBracketToken), AttributeListSyntax n => (n.OpenBracketToken, n.CloseBracketToken), BracketedParameterListSyntax n => (n.OpenBracketToken, n.CloseBracketToken), ListPatternSyntax n => (n.OpenBracketToken, n.CloseBracketToken), CollectionExpressionSyntax n => (n.OpenBracketToken, n.CloseBracketToken), _ => default, }; public static SyntaxTokenList GetModifiers(this SyntaxNode? member) => member switch { AccessorDeclarationSyntax accessor => accessor.Modifiers, AnonymousFunctionExpressionSyntax anonymous => anonymous.Modifiers, LocalDeclarationStatementSyntax localDeclaration => localDeclaration.Modifiers, LocalFunctionStatementSyntax localFunction => localFunction.Modifiers, MemberDeclarationSyntax memberDecl => memberDecl.Modifiers, ParameterSyntax parameter => parameter.Modifiers, _ => default, }; public static SyntaxNode? WithModifiers(this SyntaxNode? member, SyntaxTokenList modifiers) => member switch { MemberDeclarationSyntax memberDecl => memberDecl.WithModifiers(modifiers), AccessorDeclarationSyntax accessor => accessor.WithModifiers(modifiers), AnonymousFunctionExpressionSyntax anonymous => anonymous.WithModifiers(modifiers), LocalFunctionStatementSyntax localFunction => localFunction.WithModifiers(modifiers), LocalDeclarationStatementSyntax localDeclaration => localDeclaration.WithModifiers(modifiers), _ => null, }; public static void ForEachMember<TArg>(this SyntaxNode? node, Action<MemberDeclarationSyntax, TArg> callback, TArg arg) { // Separated out to allow for struct-based enumeration. switch (node) { case CompilationUnitSyntax compilation: foreach (var member in compilation.Members) callback(member, arg); break; case BaseNamespaceDeclarationSyntax @namespace: foreach (var member in @namespace.Members) callback(member, arg); break; case TypeDeclarationSyntax type: foreach (var member in type.Members) callback(member, arg); break; case EnumDeclarationSyntax @enum: foreach (var member in @enum.Members) callback(member, arg); break; } } public static bool IsInExpressionTree( [NotNullWhen(true)] this SyntaxNode? node, SemanticModel semanticModel, [NotNullWhen(true)] INamedTypeSymbol? expressionType, CancellationToken cancellationToken) { if (expressionType != null) { for (var current = node; current != null; current = current.Parent) { if (current is LambdaExpressionSyntax) { var typeInfo = semanticModel.GetTypeInfo(current, cancellationToken); if (expressionType.Equals(typeInfo.ConvertedType?.OriginalDefinition)) return true; } else if (current is SelectOrGroupClauseSyntax or OrderingSyntax) { var info = semanticModel.GetSymbolInfo(current, cancellationToken); if (TakesExpressionTree(info, expressionType)) return true; } else if (current is QueryClauseSyntax queryClause) { var info = semanticModel.GetQueryClauseInfo(queryClause, cancellationToken); if (TakesExpressionTree(info.CastInfo, expressionType) || TakesExpressionTree(info.OperationInfo, expressionType)) { return true; } } } } return false; static bool TakesExpressionTree(SymbolInfo info, INamedTypeSymbol expressionType) { foreach (var symbol in info.GetAllSymbols()) { if (symbol is IMethodSymbol { Parameters: [{ Type.OriginalDefinition: var parameterType }, ..] } && expressionType.Equals(parameterType)) { return true; } } return false; } } public static bool IsInDeconstructionLeft( [NotNullWhen(true)] this SyntaxNode? node, [NotNullWhen(true)] out SyntaxNode? deconstructionLeft) { SyntaxNode? previous = null; for (var current = node; current != null; current = current.Parent) { if ((current is AssignmentExpressionSyntax assignment && previous == assignment.Left && assignment.IsDeconstruction()) || (current is ForEachVariableStatementSyntax @foreach && previous == @foreach.Variable)) { deconstructionLeft = previous; return true; } if (current is StatementSyntax) { break; } previous = current; } deconstructionLeft = null; return false; } public static T WithCommentsFrom<T>(this T node, SyntaxToken leadingToken, SyntaxToken trailingToken) where T : SyntaxNode => node.WithCommentsFrom( SyntaxNodeOrTokenExtensions.GetTrivia(leadingToken), SyntaxNodeOrTokenExtensions.GetTrivia(trailingToken)); public static T WithCommentsFrom<T>( this T node, IEnumerable<SyntaxToken> leadingTokens, IEnumerable<SyntaxToken> trailingTokens) where T : SyntaxNode => node.WithCommentsFrom(leadingTokens.GetTrivia(), trailingTokens.GetTrivia()); public static T WithCommentsFrom<T>( this T node, IEnumerable<SyntaxTrivia> leadingTrivia, IEnumerable<SyntaxTrivia> trailingTrivia, params SyntaxNodeOrToken[] trailingNodesOrTokens) where T : SyntaxNode => node .WithLeadingTrivia(leadingTrivia.Concat(node.GetLeadingTrivia()).FilterComments(addElasticMarker: false)) .WithTrailingTrivia( node.GetTrailingTrivia().Concat(SyntaxNodeOrTokenExtensions.GetTrivia(trailingNodesOrTokens).Concat(trailingTrivia)).FilterComments(addElasticMarker: false)); public static T KeepCommentsAndAddElasticMarkers<T>(this T node) where T : SyntaxNode => node .WithTrailingTrivia(node.GetTrailingTrivia().FilterComments(addElasticMarker: true)) .WithLeadingTrivia(node.GetLeadingTrivia().FilterComments(addElasticMarker: true)); public static SyntaxNode WithPrependedNonIndentationTriviaFrom( this SyntaxNode to, SyntaxNode from) { // get all the preceding trivia from the 'from' node, not counting the leading // indentation trivia is has. var finalTrivia = from.GetLeadingTrivia().ToList(); while (finalTrivia is [.., (kind: SyntaxKind.WhitespaceTrivia)]) finalTrivia.RemoveAt(finalTrivia.Count - 1); // Also, add on the trailing trivia if there are trailing comments. var hasTrailingComments = from.GetTrailingTrivia().Any(t => t.IsRegularComment()); if (hasTrailingComments) finalTrivia.AddRange(from.GetTrailingTrivia()); // Merge this trivia with the existing trivia on the node. Format in case // we added comments and need them indented properly. return to.WithPrependedLeadingTrivia(finalTrivia); } }