/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxTokenExtensions.cs
349 строк
13 KB
Rikki Gibson
IDE test pass for closed class (#83682)
16 май 2026, 06:40
Не верифицирован
16 май 2026, 06:40
fc99275
Код
Авторство
О чём код?
// 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.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; 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 SyntaxTokenExtensions { public static void Deconstruct(this SyntaxToken token, out SyntaxKind kind) => kind = token.Kind(); public static bool IsLastTokenOfNode<T>(this SyntaxToken token) where T : SyntaxNode => token.IsLastTokenOfNode<T>(out _); public static bool IsLastTokenOfNode<T>(this SyntaxToken token, [NotNullWhen(true)] out T? node) where T : SyntaxNode { var ancestor = token.GetAncestor<T>(); if (ancestor == null || token != ancestor.GetLastToken(includeZeroWidth: true)) { node = null; return false; } node = ancestor; return true; } public static bool IsKindOrHasMatchingText(this SyntaxToken token, SyntaxKind kind) => token.Kind() == kind || token.HasMatchingText(kind); public static bool HasMatchingText(this SyntaxToken token, SyntaxKind kind) => token.ToString() == SyntaxFacts.GetText(kind); public static bool IsOpenBraceOrCommaOfObjectInitializer(this SyntaxToken token) => token.Kind() is SyntaxKind.OpenBraceToken or SyntaxKind.CommaToken && token.Parent.IsKind(SyntaxKind.ObjectInitializerExpression); public static bool IsOpenBraceOfAccessorList(this SyntaxToken token) => token.IsKind(SyntaxKind.OpenBraceToken) && token.Parent.IsKind(SyntaxKind.AccessorList); /// <summary> /// Returns true if this token is something that looks like a C# keyword. This includes /// actual keywords, contextual keywords, and even 'var' and 'dynamic' /// </summary> /// <param name="token"></param> public static bool CouldBeKeyword(this SyntaxToken token) { if (token.IsKeyword()) { return true; } if (token.Kind() == SyntaxKind.IdentifierToken) { var simpleNameText = token.ValueText; return simpleNameText == "var" || simpleNameText == "dynamic" || SyntaxFacts.GetContextualKeywordKind(simpleNameText) != SyntaxKind.None; } return false; } public static bool IsPotentialModifier(this SyntaxToken token, out SyntaxKind modifierKind) { var tokenKind = token.Kind(); modifierKind = SyntaxKind.None; switch (tokenKind) { case SyntaxKind.PublicKeyword: case SyntaxKind.InternalKeyword: case SyntaxKind.ProtectedKeyword: case SyntaxKind.PrivateKeyword: case SyntaxKind.SealedKeyword: case SyntaxKind.AbstractKeyword: #if !OLDER_ROSLYN case SyntaxKind.ClosedKeyword: #endif case SyntaxKind.StaticKeyword: case SyntaxKind.VirtualKeyword: case SyntaxKind.ExternKeyword: case SyntaxKind.NewKeyword: case SyntaxKind.OverrideKeyword: case SyntaxKind.ReadOnlyKeyword: case SyntaxKind.VolatileKeyword: case SyntaxKind.UnsafeKeyword: case SyntaxKind.AsyncKeyword: case SyntaxKind.RefKeyword: case SyntaxKind.OutKeyword: case SyntaxKind.InKeyword: case SyntaxKind.RequiredKeyword: case SyntaxKind.FileKeyword: case SyntaxKind.PartialKeyword: modifierKind = tokenKind; return true; case SyntaxKind.IdentifierToken: if (token.HasMatchingText(SyntaxKind.AsyncKeyword)) { modifierKind = SyntaxKind.AsyncKeyword; } if (token.HasMatchingText(SyntaxKind.FileKeyword)) { modifierKind = SyntaxKind.FileKeyword; } #if !OLDER_ROSLYN if (token.HasMatchingText(SyntaxKind.ClosedKeyword)) { modifierKind = SyntaxKind.ClosedKeyword; } #endif if (token.HasMatchingText(SyntaxKind.PartialKeyword)) { modifierKind = SyntaxKind.PartialKeyword; } return modifierKind != SyntaxKind.None; default: return false; } } public static bool IsLiteral(this SyntaxToken token) { switch (token.Kind()) { case SyntaxKind.CharacterLiteralToken: case SyntaxKind.FalseKeyword: case SyntaxKind.NumericLiteralToken: case SyntaxKind.StringLiteralToken: case SyntaxKind.TrueKeyword: return true; default: return false; } } public static bool IntersectsWith(this SyntaxToken token, int position) => token.Span.IntersectsWith(position); public static SyntaxToken GetPreviousTokenIfTouchingWord(this SyntaxToken token, int position) { return token.IntersectsWith(position) && IsWord(token) ? token.GetPreviousToken(includeSkipped: true) : token; } private static bool IsWord(SyntaxToken token) => CSharpSyntaxFacts.Instance.IsWord(token); public static SyntaxToken GetNextNonZeroWidthTokenOrEndOfFile(this SyntaxToken token) => token.GetNextTokenOrEndOfFile(); /// <summary> /// Determines whether the given SyntaxToken is the first token on a line in the specified SourceText. /// </summary> public static bool IsFirstTokenOnLine(this SyntaxToken token, SourceText text) { var previousToken = token.GetPreviousToken(includeSkipped: true, includeDirectives: true, includeDocumentationComments: true); if (previousToken.Kind() == SyntaxKind.None) { return true; } var tokenLine = text.Lines.IndexOf(token.SpanStart); var previousTokenLine = text.Lines.IndexOf(previousToken.SpanStart); return tokenLine > previousTokenLine; } public static bool SpansPreprocessorDirective(this IEnumerable<SyntaxToken> tokens) => CSharpSyntaxFacts.Instance.SpansPreprocessorDirective(tokens); /// <summary> /// Retrieves all trivia after this token, including it's trailing trivia and /// the leading trivia of the next token. /// </summary> public static IEnumerable<SyntaxTrivia> GetAllTrailingTrivia(this SyntaxToken token) { foreach (var trivia in token.TrailingTrivia) { yield return trivia; } var nextToken = token.GetNextTokenOrEndOfFile(includeZeroWidth: true, includeSkipped: true, includeDirectives: true, includeDocumentationComments: true); foreach (var trivia in nextToken.LeadingTrivia) { yield return trivia; } } public static bool IsRegularStringLiteral(this SyntaxToken token) => token.Kind() == SyntaxKind.StringLiteralToken && !token.IsVerbatimStringLiteral(); public static bool IsValidAttributeTarget(this SyntaxToken token) { switch (token.Kind()) { case SyntaxKind.AssemblyKeyword: case SyntaxKind.ModuleKeyword: case SyntaxKind.FieldKeyword: case SyntaxKind.EventKeyword: case SyntaxKind.MethodKeyword: case SyntaxKind.ParamKeyword: case SyntaxKind.PropertyKeyword: case SyntaxKind.ReturnKeyword: case SyntaxKind.TypeKeyword: return true; default: return false; } } public static SyntaxToken WithCommentsFrom( this SyntaxToken token, IEnumerable<SyntaxTrivia> leadingTrivia, IEnumerable<SyntaxTrivia> trailingTrivia, params SyntaxNodeOrToken[] trailingNodesOrTokens) => token .WithPrependedLeadingTrivia(leadingTrivia) .WithTrailingTrivia(( token.TrailingTrivia.Concat(SyntaxNodeOrTokenExtensions.GetTrivia(trailingNodesOrTokens).Concat(trailingTrivia))).FilterComments(addElasticMarker: false)); public static SyntaxToken KeepCommentsAndAddElasticMarkers(this SyntaxToken token) => token.WithTrailingTrivia(token.TrailingTrivia.FilterComments(addElasticMarker: true)) .WithLeadingTrivia(token.LeadingTrivia.FilterComments(addElasticMarker: true)); public static bool TryParseGenericName(this SyntaxToken genericIdentifier, CancellationToken cancellationToken, [NotNullWhen(true)] out GenericNameSyntax? genericName) { if (genericIdentifier.GetNextToken(includeSkipped: true).Kind() == SyntaxKind.LessThanToken) { var lastToken = genericIdentifier.FindLastTokenOfPartialGenericName(); var syntaxTree = genericIdentifier.SyntaxTree!; var name = SyntaxFactory.ParseName(syntaxTree.GetText(cancellationToken).ToString(TextSpan.FromBounds(genericIdentifier.SpanStart, lastToken.Span.End))); genericName = name as GenericNameSyntax; return genericName != null; } genericName = null; return false; } /// <summary> /// Lexically, find the last token that looks like it's part of this generic name. /// </summary> /// <param name="genericIdentifier">The "name" of the generic identifier, last token before /// the "&"</param> /// <returns>The last token in the name</returns> /// <remarks>This is related to the code in SyntaxTreeExtensions.IsInPartiallyWrittenGeneric</remarks> public static SyntaxToken FindLastTokenOfPartialGenericName(this SyntaxToken genericIdentifier) { Contract.ThrowIfFalse(genericIdentifier.Kind() == SyntaxKind.IdentifierToken); // advance to the "<" token var token = genericIdentifier.GetNextToken(includeSkipped: true); Contract.ThrowIfFalse(token.Kind() == SyntaxKind.LessThanToken); var stack = 0; do { // look forward one token { var next = token.GetNextToken(includeSkipped: true); if (next.Kind() == SyntaxKind.None) { return token; } token = next; } if (token.Kind() == SyntaxKind.GreaterThanToken) { if (stack == 0) { return token; } else { stack--; continue; } } switch (token.Kind()) { case SyntaxKind.LessThanLessThanToken: stack++; goto case SyntaxKind.LessThanToken; // fall through case SyntaxKind.LessThanToken: stack++; break; case SyntaxKind.AsteriskToken: // for int* case SyntaxKind.QuestionToken: // for int? case SyntaxKind.ColonToken: // for global:: (so we don't dismiss help as you type the first :) case SyntaxKind.ColonColonToken: // for global:: case SyntaxKind.CloseBracketToken: case SyntaxKind.OpenBracketToken: case SyntaxKind.DotToken: case SyntaxKind.IdentifierToken: case SyntaxKind.CommaToken: break; // If we see a member declaration keyword, we know we've gone too far case SyntaxKind.ClassKeyword: case SyntaxKind.StructKeyword: case SyntaxKind.InterfaceKeyword: case SyntaxKind.DelegateKeyword: case SyntaxKind.EnumKeyword: case SyntaxKind.PrivateKeyword: case SyntaxKind.PublicKeyword: case SyntaxKind.InternalKeyword: case SyntaxKind.ProtectedKeyword: case SyntaxKind.VoidKeyword: return token.GetPreviousToken(includeSkipped: true); default: // user might have typed "in" on the way to typing "int" // don't want to disregard this genericname because of that if (SyntaxFacts.IsKeywordKind(token.Kind())) { break; } // anything else and we're sunk. Go back to the token before. return token.GetPreviousToken(includeSkipped: true); } } while (true); } }