|
// 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.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Shared.Extensions;
namespace Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery;
internal static partial class SyntaxTokenExtensions
{
public static bool IsUsingOrExternKeyword(this SyntaxToken token)
{
return
token.Kind() is SyntaxKind.UsingKeyword or
SyntaxKind.ExternKeyword;
}
public static bool IsUsingKeywordInUsingDirective(this SyntaxToken token)
{
if (token.IsKind(SyntaxKind.UsingKeyword))
{
var usingDirective = token.GetAncestor<UsingDirectiveSyntax>();
if (usingDirective != null &&
usingDirective.UsingKeyword == token)
{
return true;
}
}
return false;
}
public static bool IsStaticKeywordContextInUsingDirective(this SyntaxToken token)
{
// using static |
if (token is { RawKind: (int)SyntaxKind.StaticKeyword, Parent: UsingDirectiveSyntax })
{
return true;
}
// using static unsafe |
if (token.IsKind(SyntaxKind.UnsafeKeyword) &&
token.GetPreviousToken() is { RawKind: (int)SyntaxKind.StaticKeyword, Parent: UsingDirectiveSyntax })
{
return true;
}
return false;
}
public static bool IsBeginningOfStatementContext(this SyntaxToken token)
{
// cases:
// {
// |
// }
// |
// Note, the following is *not* a legal statement context:
// do { } |
// ...;
// |
// case 0:
// |
// default:
// |
// label:
// |
// if (goo)
// |
// while (true)
// |
// do
// |
// for (;;)
// |
// foreach (var v in c)
// |
// else
// |
// using (expr)
// |
// fixed (void* v = &expr)
// |
// lock (expr)
// |
// for ( ; ; Goo(), |
// After attribute lists on a statement:
// [Bar]
// |
switch (token.Kind())
{
case SyntaxKind.OpenBraceToken when token.Parent.IsKind(SyntaxKind.Block):
return true;
case SyntaxKind.SemicolonToken:
var statement = token.GetAncestor<StatementSyntax>();
return statement != null && !statement.IsParentKind(SyntaxKind.GlobalStatement) &&
statement.GetLastToken(includeZeroWidth: true) == token;
case SyntaxKind.CloseBraceToken:
if (token.Parent.IsKind(SyntaxKind.Block))
{
if (token.Parent.Parent is StatementSyntax)
{
// Most blocks that are the child of statement are places
// that we can follow with another statement. i.e.:
// if { }
// while () { }
// There are two exceptions.
// try {}
// do {}
if (token.Parent.Parent.Kind() is not SyntaxKind.TryStatement and not SyntaxKind.DoStatement)
return true;
}
else if (token.Parent.Parent?.Kind()
is SyntaxKind.ElseClause
or SyntaxKind.FinallyClause
or SyntaxKind.CatchClause
or SyntaxKind.SwitchSection)
{
return true;
}
}
if (token.Parent.IsKind(SyntaxKind.SwitchStatement))
{
return true;
}
return false;
case SyntaxKind.ColonToken:
return token.Parent is (kind: SyntaxKind.CaseSwitchLabel or SyntaxKind.DefaultSwitchLabel or SyntaxKind.CasePatternSwitchLabel or SyntaxKind.LabeledStatement);
case SyntaxKind.DoKeyword when token.Parent.IsKind(SyntaxKind.DoStatement):
return true;
case SyntaxKind.CloseParenToken:
var parent = token.Parent;
return parent?.Kind()
is SyntaxKind.ForStatement
or SyntaxKind.ForEachStatement
or SyntaxKind.ForEachVariableStatement
or SyntaxKind.WhileStatement
or SyntaxKind.IfStatement
or SyntaxKind.LockStatement
or SyntaxKind.UsingStatement
or SyntaxKind.FixedStatement;
case SyntaxKind.ElseKeyword:
return token.Parent.IsKind(SyntaxKind.ElseClause);
case SyntaxKind.CloseBracketToken:
if (token.Parent.IsKind(SyntaxKind.AttributeList))
{
// attributes can belong to a statement
var container = token.Parent.Parent;
if (container is StatementSyntax)
return true;
}
return false;
}
return false;
}
public static bool IsBeginningOfGlobalStatementContext(this SyntaxToken token)
{
// cases:
// }
// |
// ...;
// |
// extern alias Goo;
// using System;
// |
// [assembly: Goo]
// |
if (token.Kind() == SyntaxKind.CloseBraceToken)
{
var memberDeclaration = token.GetAncestor<MemberDeclarationSyntax>();
if (memberDeclaration != null && memberDeclaration.GetLastToken(includeZeroWidth: true) == token &&
memberDeclaration.IsParentKind(SyntaxKind.CompilationUnit))
{
return true;
}
}
if (token.Kind() == SyntaxKind.SemicolonToken)
{
var globalStatement = token.GetAncestor<GlobalStatementSyntax>();
if (globalStatement != null && globalStatement.GetLastToken(includeZeroWidth: true) == token)
return true;
// Need this check to check file scoped namespace declarations prior to a catch-all of
// member declarations since otherwise it would return true.
if (token.Parent is FileScopedNamespaceDeclarationSyntax namespaceDeclaration && namespaceDeclaration.SemicolonToken == token)
return false;
var memberDeclaration = token.GetAncestor<MemberDeclarationSyntax>();
if (memberDeclaration != null && memberDeclaration.GetLastToken(includeZeroWidth: true) == token &&
memberDeclaration.IsParentKind(SyntaxKind.CompilationUnit))
{
return true;
}
var compUnit = token.GetAncestor<CompilationUnitSyntax>();
if (compUnit != null)
{
if (compUnit.Usings.Count > 0 && compUnit.Usings.Last().GetLastToken(includeZeroWidth: true) == token)
{
return true;
}
if (compUnit.Externs.Count > 0 && compUnit.Externs.Last().GetLastToken(includeZeroWidth: true) == token)
{
return true;
}
}
}
if (token.Kind() == SyntaxKind.CloseBracketToken)
{
var compUnit = token.GetAncestor<CompilationUnitSyntax>();
if (compUnit != null)
{
if (compUnit.AttributeLists.Count > 0 && compUnit.AttributeLists.Last().GetLastToken(includeZeroWidth: true) == token)
return true;
}
if (token.Parent.IsKind(SyntaxKind.AttributeList))
{
var container = token.Parent.Parent;
if (container is IncompleteMemberSyntax && container.Parent is CompilationUnitSyntax)
return true;
}
}
return false;
}
public static bool IsAfterPossibleCast(this SyntaxToken token)
{
if (token.Kind() == SyntaxKind.CloseParenToken)
{
if (token.Parent.IsKind(SyntaxKind.CastExpression))
{
return true;
}
if (token.Parent is ParenthesizedExpressionSyntax parenExpr)
{
var expr = parenExpr.Expression;
if (expr is TypeSyntax)
{
return true;
}
}
}
return false;
}
public static bool IsLastTokenOfQueryClause(this SyntaxToken token)
{
if (token.IsLastTokenOfNode<QueryClauseSyntax>())
{
return true;
}
if (token.Kind() == SyntaxKind.IdentifierToken &&
token.GetPreviousToken(includeSkipped: true).Kind() == SyntaxKind.IntoKeyword)
{
return true;
}
return false;
}
public static bool IsPreProcessorExpressionContext(this SyntaxToken targetToken)
{
// cases:
// #if |
// #if goo || |
// #if goo && |
// #if ( |
// #if ! |
// Same for elif
if (targetToken.GetAncestor<ConditionalDirectiveTriviaSyntax>() == null)
{
return false;
}
// #if
// #elif
if (targetToken.Kind() is SyntaxKind.IfKeyword or
SyntaxKind.ElifKeyword)
{
return true;
}
// ( |
if (targetToken.Kind() == SyntaxKind.OpenParenToken &&
targetToken.Parent.IsKind(SyntaxKind.ParenthesizedExpression))
{
return true;
}
// ! |
if (targetToken.Parent is PrefixUnaryExpressionSyntax prefix)
{
return prefix.OperatorToken == targetToken;
}
// a &&
// a ||
if (targetToken.Parent is BinaryExpressionSyntax binary)
{
return binary.OperatorToken == targetToken;
}
return false;
}
public static bool IsOrderByDirectionContext(this SyntaxToken targetToken)
{
// cases:
// orderby a |
// orderby a a|
// orderby a, b |
// orderby a, b a|
if (targetToken.Kind() is not (SyntaxKind.IdentifierToken or SyntaxKind.CloseParenToken or SyntaxKind.CloseBracketToken))
{
return false;
}
var ordering = targetToken.GetAncestor<OrderingSyntax>();
if (ordering == null)
{
return false;
}
// orderby a |
// orderby a, b |
var lastToken = ordering.Expression.GetLastToken(includeSkipped: true);
if (targetToken == lastToken)
{
return true;
}
return false;
}
public static bool IsSwitchLabelContext(this SyntaxToken targetToken)
{
// cases:
// case X: |
// default: |
// switch (e) { |
//
// case X: Statement(); |
if (targetToken.Kind() == SyntaxKind.OpenBraceToken &&
targetToken.Parent.IsKind(SyntaxKind.SwitchStatement))
{
return true;
}
if (targetToken.Kind() == SyntaxKind.ColonToken)
{
if (targetToken.Parent is (kind: SyntaxKind.CaseSwitchLabel or SyntaxKind.DefaultSwitchLabel or SyntaxKind.CasePatternSwitchLabel))
{
return true;
}
}
if (targetToken.Kind() is SyntaxKind.SemicolonToken or
SyntaxKind.CloseBraceToken)
{
var section = targetToken.GetAncestor<SwitchSectionSyntax>();
if (section != null)
{
foreach (var statement in section.Statements)
{
if (targetToken == statement.GetLastToken(includeSkipped: true))
{
return true;
}
}
}
}
return false;
}
public static bool IsXmlCrefParameterModifierContext(this SyntaxToken targetToken)
{
return targetToken.Kind() is SyntaxKind.CommaToken or SyntaxKind.OpenParenToken &&
targetToken.Parent is (kind: SyntaxKind.CrefBracketedParameterList or SyntaxKind.CrefParameterList);
}
public static bool IsConstructorOrMethodParameterArgumentContext(this SyntaxToken targetToken)
{
// cases:
// Goo( |
// Goo(expr, |
// Goo(bar: |
// new Goo( |
// new Goo(expr, |
// new Goo(bar: |
// Goo : base( |
// Goo : base(bar: |
// Goo : this( |
// Goo : this(bar: |
// Goo(bar: |
if (targetToken.Kind() == SyntaxKind.ColonToken &&
targetToken.Parent.IsKind(SyntaxKind.NameColon) &&
targetToken.Parent.Parent.IsKind(SyntaxKind.Argument) &&
targetToken.Parent.Parent.Parent.IsKind(SyntaxKind.ArgumentList))
{
var owner = targetToken.Parent.Parent.Parent.Parent;
if (owner?.Kind()
is SyntaxKind.InvocationExpression
or SyntaxKind.ObjectCreationExpression
or SyntaxKind.BaseConstructorInitializer
or SyntaxKind.ThisConstructorInitializer)
{
return true;
}
}
if (targetToken.Kind() is SyntaxKind.OpenParenToken or
SyntaxKind.CommaToken)
{
if (targetToken.Parent.IsKind(SyntaxKind.ArgumentList))
{
if (targetToken.Parent?.Parent?.Kind()
is SyntaxKind.ObjectCreationExpression
or SyntaxKind.BaseConstructorInitializer
or SyntaxKind.ThisConstructorInitializer)
{
return true;
}
// var( |
// var(expr, |
// Those are more likely to be deconstruction-declarations being typed than invocations a method "var"
if (targetToken.Parent.IsParentKind(SyntaxKind.InvocationExpression) && !targetToken.IsInvocationOfVarExpression())
{
return true;
}
}
}
return false;
}
public static bool IsUnaryOperatorContext(this SyntaxToken targetToken)
{
if (targetToken.Kind() == SyntaxKind.OperatorKeyword &&
targetToken.GetPreviousToken(includeSkipped: true).IsLastTokenOfNode<TypeSyntax>())
{
return true;
}
return false;
}
public static bool IsUnsafeContext(this SyntaxToken targetToken)
{
return
targetToken.GetAncestors<StatementSyntax>().Any(s => s.IsKind(SyntaxKind.UnsafeStatement)) ||
targetToken.GetAncestors<MemberDeclarationSyntax>().Any(m => m.GetModifiers().Any(SyntaxKind.UnsafeKeyword) ||
targetToken.GetAncestors<LocalFunctionStatementSyntax>().Any(f => f.GetModifiers().Any(SyntaxKind.UnsafeKeyword))) ||
targetToken.GetAncestors<UsingDirectiveSyntax>().Any(d => d.UnsafeKeyword.IsKind(SyntaxKind.UnsafeKeyword));
}
public static bool IsAfterYieldKeyword(this SyntaxToken targetToken)
{
// yield |
// yield r|
return targetToken.IsKindOrHasMatchingText(SyntaxKind.YieldKeyword);
}
public static bool IsAnyAccessorDeclarationContext(this SyntaxToken targetToken, int position, SyntaxKind kind = SyntaxKind.None)
{
return targetToken.IsAccessorDeclarationContext<EventDeclarationSyntax>(position, kind) ||
targetToken.IsAccessorDeclarationContext<PropertyDeclarationSyntax>(position, kind) ||
targetToken.IsAccessorDeclarationContext<IndexerDeclarationSyntax>(position, kind);
}
public static bool IsAccessorDeclarationContext<TMemberNode>(this SyntaxToken targetToken, int position, SyntaxKind kind = SyntaxKind.None)
where TMemberNode : SyntaxNode
{
if (!IsAccessorDeclarationContextWorker(ref targetToken))
{
return false;
}
var list = targetToken.GetAncestor<AccessorListSyntax>();
if (list == null)
{
return false;
}
// Check if we already have this accessor. (however, don't count it
// if the user is *on* that accessor.
var existingAccessor = list.Accessors
.Select(a => a.Keyword)
.FirstOrDefault(a => !a.IsMissing && a.IsKindOrHasMatchingText(kind));
if (existingAccessor.Kind() != SyntaxKind.None)
{
var existingAccessorSpan = existingAccessor.Span;
if (!existingAccessorSpan.IntersectsWith(position))
{
return false;
}
}
var decl = targetToken.GetAncestor<TMemberNode>();
return decl != null;
}
private static bool IsAccessorDeclarationContextWorker(ref SyntaxToken targetToken)
{
// cases:
// int Goo { |
// int Goo { private |
// int Goo { set { } |
// int Goo { set; |
// int Goo { [Bar]|
// int Goo { readonly |
// Consume all preceding access modifiers
while (targetToken.Kind() is SyntaxKind.InternalKeyword or
SyntaxKind.PublicKeyword or
SyntaxKind.ProtectedKeyword or
SyntaxKind.PrivateKeyword or
SyntaxKind.ReadOnlyKeyword)
{
targetToken = targetToken.GetPreviousToken(includeSkipped: true);
}
// int Goo { |
// int Goo { private |
if (targetToken.Kind() == SyntaxKind.OpenBraceToken &&
targetToken.Parent.IsKind(SyntaxKind.AccessorList))
{
return true;
}
// int Goo { set { } |
// int Goo { set { } private |
if (targetToken.Kind() == SyntaxKind.CloseBraceToken &&
targetToken.Parent.IsKind(SyntaxKind.Block) &&
targetToken.Parent.Parent is AccessorDeclarationSyntax)
{
return true;
}
// int Goo { set; |
if (targetToken.Kind() == SyntaxKind.SemicolonToken &&
targetToken.Parent is AccessorDeclarationSyntax)
{
return true;
}
// int Goo { [Bar]|
if (targetToken.Kind() == SyntaxKind.CloseBracketToken &&
targetToken.Parent.IsKind(SyntaxKind.AttributeList) &&
targetToken.Parent.Parent is AccessorDeclarationSyntax)
{
return true;
}
return false;
}
private static bool IsGenericInterfaceOrDelegateTypeParameterList([NotNullWhen(true)] SyntaxNode? node)
{
if (node.IsKind(SyntaxKind.TypeParameterList))
{
if (node?.Parent is TypeDeclarationSyntax(SyntaxKind.InterfaceDeclaration) typeDecl)
return typeDecl.TypeParameterList == node;
else if (node?.Parent is DelegateDeclarationSyntax delegateDecl)
return delegateDecl.TypeParameterList == node;
}
return false;
}
public static bool IsTypeParameterVarianceContext(this SyntaxToken targetToken)
{
// cases:
// interface IGoo<|
// interface IGoo<A,|
// interface IGoo<[Bar]|
// delegate X D<|
// delegate X D<A,|
// delegate X D<[Bar]|
if (targetToken.Kind() == SyntaxKind.LessThanToken &&
IsGenericInterfaceOrDelegateTypeParameterList(targetToken.Parent!))
{
return true;
}
if (targetToken.Kind() == SyntaxKind.CommaToken &&
IsGenericInterfaceOrDelegateTypeParameterList(targetToken.Parent!))
{
return true;
}
if (targetToken.Kind() == SyntaxKind.CloseBracketToken &&
targetToken.Parent.IsKind(SyntaxKind.AttributeList) &&
targetToken.Parent.Parent.IsKind(SyntaxKind.TypeParameter) &&
IsGenericInterfaceOrDelegateTypeParameterList(targetToken.Parent.Parent.Parent))
{
return true;
}
return false;
}
public static bool IsMandatoryNamedParameterPosition(this SyntaxToken token)
{
if (token.Kind() == SyntaxKind.CommaToken && token.Parent is BaseArgumentListSyntax)
{
var argumentList = (BaseArgumentListSyntax)token.Parent;
foreach (var item in argumentList.Arguments.GetWithSeparators())
{
if (item.IsToken && item.AsToken() == token)
{
return false;
}
if (item.IsNode)
{
if (item.AsNode() is ArgumentSyntax node && node.NameColon != null)
{
return true;
}
}
}
}
return false;
}
public static bool IsNumericTypeContext(this SyntaxToken token, SemanticModel semanticModel, CancellationToken cancellationToken)
{
if (token.Parent is not MemberAccessExpressionSyntax memberAccessExpression)
{
return false;
}
var typeInfo = semanticModel.GetTypeInfo(memberAccessExpression.Expression, cancellationToken);
return typeInfo.Type.IsNumericType();
}
public static bool IsTypeNamedDynamic(this SyntaxToken token)
=> token.Parent is IdentifierNameSyntax typedParent &&
SyntaxFacts.IsInTypeOnlyContext(typedParent) &&
token.Text == "dynamic";
}
|