/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/NameSimplifier.vb
460 строк
24 KB
Miha Zupan
Replace string.Compare == 0 patterns with Equals/StartsWith calls (#84036)
16 июн 2026, 19:27
Не верифицирован
16 июн 2026, 19:27
6128161
Код
Авторство
О чём код?
' 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. Imports System.Runtime.InteropServices Imports System.Threading Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.CodeStyle Imports Microsoft.CodeAnalysis.Rename.ConflictEngine Imports Microsoft.CodeAnalysis.Simplification Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Imports Microsoft.CodeAnalysis.VisualBasic.Utilities Namespace Microsoft.CodeAnalysis.VisualBasic.Simplification.Simplifiers Friend Class NameSimplifier Inherits AbstractVisualBasicSimplifier(Of NameSyntax, ExpressionSyntax) Public Shared ReadOnly Instance As New NameSimplifier() Private Sub New() End Sub Public Overrides Function TrySimplify( name As NameSyntax, semanticModel As SemanticModel, options As VisualBasicSimplifierOptions, <Out> ByRef replacementNode As ExpressionSyntax, <Out> ByRef issueSpan As TextSpan, cancellationToken As CancellationToken) As Boolean ' do not simplify names of a namespace declaration If IsPartOfNamespaceDeclarationName(name) Then Return False End If ' see whether binding the name binds to a symbol/type. if not, it is ambiguous and ' nothing we can do here. Dim symbol = SimplificationHelpers.GetOriginalSymbolInfo(semanticModel, name) If SimplificationHelpers.IsValidSymbolInfo(symbol) Then If symbol.Kind = SymbolKind.Method AndAlso symbol.IsConstructor() Then symbol = symbol.ContainingType End If If symbol.Kind = SymbolKind.Method AndAlso name.Kind = SyntaxKind.GenericName Then Dim genericName = DirectCast(name, GenericNameSyntax) replacementNode = SyntaxFactory.IdentifierName(genericName.Identifier).WithLeadingTrivia(genericName.GetLeadingTrivia()).WithTrailingTrivia(genericName.GetTrailingTrivia()) issueSpan = genericName.TypeArgumentList.Span Return CanReplaceWithReducedName(name, replacementNode, semanticModel, cancellationToken) End If If TypeOf symbol IsNot INamespaceOrTypeSymbol Then Return False End If Else Return False End If If name.HasAnnotations(SpecialTypeAnnotation.Kind) Then replacementNode = SyntaxFactory.PredefinedType( SyntaxFactory.Token(name.GetLeadingTrivia(), GetPredefinedKeywordKind(SpecialTypeAnnotation.GetSpecialType(name.GetAnnotations(SpecialTypeAnnotation.Kind).First())), name.GetTrailingTrivia())) issueSpan = name.Span Return CanReplaceWithReducedNameInContext(name, replacementNode) Else If Not name.IsRightSideOfDot() Then Dim aliasReplacement As IAliasSymbol = Nothing If TryReplaceWithAlias(name, semanticModel, aliasReplacement) Then Dim identifierToken = SyntaxFactory.Identifier( name.GetLeadingTrivia(), aliasReplacement.Name, name.GetTrailingTrivia()) identifierToken = TryEscapeIdentifierToken(identifierToken) replacementNode = SyntaxFactory.IdentifierName(identifierToken) Dim annotatedNodesOrTokens = name.GetAnnotatedNodesAndTokens(RenameAnnotation.Kind) For Each annotatedNodeOrToken In annotatedNodesOrTokens If annotatedNodeOrToken.IsToken Then identifierToken = annotatedNodeOrToken.AsToken().CopyAnnotationsTo(identifierToken) Else replacementNode = annotatedNodeOrToken.AsNode().CopyAnnotationsTo(replacementNode) End If Next annotatedNodesOrTokens = name.GetAnnotatedNodesAndTokens(AliasAnnotation.Kind) For Each annotatedNodeOrToken In annotatedNodesOrTokens If annotatedNodeOrToken.IsToken Then identifierToken = annotatedNodeOrToken.AsToken().CopyAnnotationsTo(identifierToken) Else replacementNode = annotatedNodeOrToken.AsNode().CopyAnnotationsTo(replacementNode) End If Next replacementNode = DirectCast(replacementNode, SimpleNameSyntax).WithIdentifier(identifierToken) issueSpan = name.Span ' In case the alias name is the same as the last name of the alias target, we only include ' the left part of the name in the unnecessary span to Not confuse uses. If name.Kind = SyntaxKind.QualifiedName Then Dim qualifiedName As QualifiedNameSyntax = DirectCast(name, QualifiedNameSyntax) If qualifiedName.Right.Identifier.ValueText = identifierToken.ValueText Then issueSpan = qualifiedName.Left.Span End If End If If CanReplaceWithReducedNameInContext(name, replacementNode) Then ' check if the alias name ends with an Attribute suffix that can be omitted. Dim replacementNodeWithoutAttributeSuffix As ExpressionSyntax = Nothing Dim issueSpanWithoutAttributeSuffix As TextSpan = Nothing If TryReduceAttributeSuffix(name, identifierToken, semanticModel, aliasReplacement IsNot Nothing, replacementNodeWithoutAttributeSuffix, issueSpanWithoutAttributeSuffix, cancellationToken) Then If CanReplaceWithReducedName(name, replacementNodeWithoutAttributeSuffix, semanticModel, cancellationToken) Then replacementNode = replacementNode.CopyAnnotationsTo(replacementNodeWithoutAttributeSuffix) issueSpan = issueSpanWithoutAttributeSuffix End If End If Return True End If Return False End If Dim nameHasNoAlias = False If TypeOf name Is SimpleNameSyntax Then Dim simpleName = DirectCast(name, SimpleNameSyntax) If Not simpleName.Identifier.HasAnnotations(AliasAnnotation.Kind) Then nameHasNoAlias = True End If End If If TypeOf name Is QualifiedNameSyntax Then Dim qualifiedNameSyntax = DirectCast(name, QualifiedNameSyntax) If Not qualifiedNameSyntax.Right.Identifier.HasAnnotations(AliasAnnotation.Kind) Then nameHasNoAlias = True End If End If Dim aliasInfo = semanticModel.GetAliasInfo(name, cancellationToken) ' Don't simplify to predefined type if name is part of a QualifiedName. ' QualifiedNames can't contain PredefinedTypeNames (although MemberAccessExpressions can). ' In other words, the left side of a QualifiedName can't be a PredefinedTypeName. If nameHasNoAlias AndAlso aliasInfo Is Nothing AndAlso Not name.Parent.IsKind(SyntaxKind.QualifiedName) AndAlso Not name.Parent.IsKind(SyntaxKind.NameOfExpression) Then Dim type = semanticModel.GetTypeInfo(name, cancellationToken).Type If type IsNot Nothing Then Dim keywordKind = GetPredefinedKeywordKind(type.SpecialType) If keywordKind <> SyntaxKind.None Then ' But do simplify to predefined type if not simplifying results in just the addition of escaping ' brackets. E.g., even if specified otherwise, prefer `String` to `[String]`. Dim token = SyntaxFactory.Token( name.GetLeadingTrivia(), keywordKind, name.GetTrailingTrivia()) Dim valueText = TryCast(name, IdentifierNameSyntax)?.Identifier.ValueText Dim inDeclarationContext = PreferPredefinedTypeKeywordInDeclarations(name, options) Dim inMemberAccessContext = PreferPredefinedTypeKeywordInMemberAccess(name, options) If token.Text = valueText OrElse (inDeclarationContext OrElse inMemberAccessContext) Then Dim codeStyleOptionName As String = Nothing If inDeclarationContext Then codeStyleOptionName = NameOf(CodeStyleOptions2.PreferIntrinsicPredefinedTypeKeywordInDeclaration) ElseIf inMemberAccessContext Then codeStyleOptionName = NameOf(CodeStyleOptions2.PreferIntrinsicPredefinedTypeKeywordInMemberAccess) End If replacementNode = SyntaxFactory.PredefinedType(token) issueSpan = name.Span Dim canReplace = CanReplaceWithReducedNameInContext(name, replacementNode) If canReplace Then replacementNode = replacementNode.WithAdditionalAnnotations(New SyntaxAnnotation(codeStyleOptionName)) End If Return canReplace End If End If End If End If ' Nullable rewrite: Nullable(Of Integer) -> Integer? ' Don't rewrite in the case where Nullable(Of Integer) is part of some qualified name like Nullable(Of Integer).Something If (symbol.Kind = SymbolKind.NamedType) AndAlso (Not name.IsLeftSideOfQualifiedName) Then Dim type = DirectCast(symbol, INamedTypeSymbol) If aliasInfo Is Nothing AndAlso CanSimplifyNullable(type, name) Then Dim genericName As GenericNameSyntax If name.Kind = SyntaxKind.QualifiedName Then genericName = DirectCast(DirectCast(name, QualifiedNameSyntax).Right, GenericNameSyntax) Else genericName = DirectCast(name, GenericNameSyntax) End If Dim oldType = genericName.TypeArgumentList.Arguments.First() replacementNode = SyntaxFactory.NullableType(oldType).WithLeadingTrivia(name.GetLeadingTrivia()) issueSpan = name.Span If CanReplaceWithReducedNameInContext(name, replacementNode) Then Return True End If End If End If End If Select Case name.Kind Case SyntaxKind.QualifiedName ' a module name was inserted by the name expansion, so removing this should be tried first. Dim qualifiedName = DirectCast(name, QualifiedNameSyntax) If qualifiedName.HasAnnotation(SimplificationHelpers.SimplifyModuleNameAnnotation) Then If TryOmitModuleName(qualifiedName, semanticModel, replacementNode, issueSpan, cancellationToken) Then Return True End If End If replacementNode = qualifiedName.Right.WithLeadingTrivia(name.GetLeadingTrivia()) replacementNode = DirectCast(replacementNode, SimpleNameSyntax) _ .WithIdentifier(TryEscapeIdentifierToken(DirectCast(replacementNode, SimpleNameSyntax).Identifier)) issueSpan = qualifiedName.Left.Span If CanReplaceWithReducedName(name, replacementNode, semanticModel, cancellationToken) Then Return True End If If TryOmitModuleName(qualifiedName, semanticModel, replacementNode, issueSpan, cancellationToken) Then Return True End If Case SyntaxKind.IdentifierName Dim identifier = DirectCast(name, IdentifierNameSyntax).Identifier TryReduceAttributeSuffix(name, identifier, semanticModel, False, replacementNode, issueSpan, cancellationToken) End Select End If If replacementNode Is Nothing Then Return False End If Return CanReplaceWithReducedName(name, replacementNode, semanticModel, cancellationToken) End Function ''' <summary> ''' Checks if the SyntaxNode is a name of a namespace declaration. To be a namespace name, the syntax ''' must be parented by an namespace declaration and the node itself must be equal to the declaration's Name ''' property. ''' </summary> Private Shared Function IsPartOfNamespaceDeclarationName(node As SyntaxNode) As Boolean Dim nextNode As SyntaxNode = node Do While nextNode IsNot Nothing Select Case nextNode.Kind Case SyntaxKind.IdentifierName, SyntaxKind.QualifiedName node = nextNode nextNode = nextNode.Parent Case SyntaxKind.NamespaceStatement Dim namespaceStatement = DirectCast(nextNode, NamespaceStatementSyntax) Return namespaceStatement.Name Is node Case Else Return False End Select Loop Return False End Function Private Shared Function CanReplaceWithReducedName( name As NameSyntax, replacementNode As ExpressionSyntax, semanticModel As SemanticModel, cancellationToken As CancellationToken ) As Boolean Dim speculationAnalyzer = New SpeculationAnalyzer(name, replacementNode, semanticModel, cancellationToken) If speculationAnalyzer.ReplacementChangesSemantics() Then Return False End If Return CanReplaceWithReducedNameInContext(name, replacementNode) End Function Private Shared Function CanReplaceWithReducedNameInContext(name As NameSyntax, replacementNode As ExpressionSyntax) As Boolean ' Special case. if this new minimal name parses out to a predefined type, then we ' have to make sure that we're not in a using alias. That's the one place where the ' language doesn't allow predefined types. You have to use the fully qualified name ' instead. Dim invalidTransformation1 = IsNonNameSyntaxInImportsDirective(name, replacementNode) Dim invalidTransformation2 = IsReservedNameInAttribute(name, replacementNode) Dim invalidTransformation3 = IsNullableTypeSyntaxLeftOfDotInMemberAccess(name, replacementNode) Return Not (invalidTransformation1 OrElse invalidTransformation2 OrElse invalidTransformation3) End Function Private Shared Function IsNonNameSyntaxInImportsDirective(expression As ExpressionSyntax, simplifiedNode As ExpressionSyntax) As Boolean Return TypeOf expression.Parent Is ImportsClauseSyntax AndAlso TypeOf simplifiedNode IsNot NameSyntax End Function Private Shared Function IsNullableTypeSyntaxLeftOfDotInMemberAccess(expression As ExpressionSyntax, simplifiedNode As ExpressionSyntax) As Boolean Return expression.IsParentKind(SyntaxKind.SimpleMemberAccessExpression) AndAlso simplifiedNode.Kind = SyntaxKind.NullableType End Function Private Shared Function TryOmitModuleName(name As QualifiedNameSyntax, semanticModel As SemanticModel, <Out()> ByRef replacementNode As ExpressionSyntax, <Out()> ByRef issueSpan As TextSpan, cancellationToken As CancellationToken) As Boolean If name.IsParentKind(SyntaxKind.QualifiedName) Then Dim symbolForName = semanticModel.GetSymbolInfo(DirectCast(name.Parent, QualifiedNameSyntax), cancellationToken).Symbol ' in case this QN is used in a "New NSName.ModuleName.MemberName()" expression ' the returned symbol is a constructor. Then we need to get the containing type. If symbolForName.IsConstructor Then symbolForName = symbolForName.ContainingType End If If symbolForName.IsModuleMember Then replacementNode = name.Left.WithLeadingTrivia(name.GetLeadingTrivia()) issueSpan = name.Right.Span Dim parent = DirectCast(name.Parent, QualifiedNameSyntax) Dim parentReplacement = parent.ReplaceNode(parent.Left, replacementNode) If CanReplaceWithReducedName(parent, parentReplacement, semanticModel, cancellationToken) Then Return True End If End If End If Return False End Function Private Shared Function TryReduceAttributeSuffix( name As NameSyntax, identifierToken As SyntaxToken, semanticModel As SemanticModel, isIdentifierNameFromAlias As Boolean, <Out> ByRef replacementNode As ExpressionSyntax, <Out> ByRef issueSpan As TextSpan, cancellationToken As CancellationToken ) As Boolean If SyntaxFacts.IsAttributeName(name) AndAlso Not isIdentifierNameFromAlias Then ' When the replacement is an Alias we don't want the "Attribute" Suffix to be removed because this will result in symbol change Dim aliasSymbol = semanticModel.GetAliasInfo(name, cancellationToken) If aliasSymbol IsNot Nothing AndAlso String.Equals(aliasSymbol.Name, identifierToken.ValueText, StringComparison.OrdinalIgnoreCase) Then Return False End If If name.Parent.Kind = SyntaxKind.Attribute OrElse name.IsRightSideOfDot() Then Dim newIdentifierText = String.Empty ' an attribute that should keep it (unnecessary "Attribute" suffix should be annotated with a DoNotSimplifyAnnotation If identifierToken.HasAnnotation(SimplificationHelpers.DoNotSimplifyAnnotation) Then newIdentifierText = identifierToken.ValueText + "Attribute" ElseIf identifierToken.ValueText.TryReduceAttributeSuffix(newIdentifierText) Then issueSpan = New TextSpan(name.Span.End - 9, 9) Else Return False End If ' escape it (VB allows escaping even for abbreviated identifiers, C# does not!) Dim newIdentifierToken = identifierToken.CopyAnnotationsTo( SyntaxFactory.Identifier( identifierToken.LeadingTrivia, newIdentifierText, identifierToken.TrailingTrivia)) newIdentifierToken = TryEscapeIdentifierToken(newIdentifierToken) replacementNode = SyntaxFactory.IdentifierName(newIdentifierToken).WithLeadingTrivia(name.GetLeadingTrivia()) Return True End If End If Return False End Function Private Shared Function PreferPredefinedTypeKeywordInDeclarations(name As NameSyntax, options As VisualBasicSimplifierOptions) As Boolean Return (Not IsDirectChildOfMemberAccessExpression(name)) AndAlso (Not InsideCrefReference(name)) AndAlso (Not InsideNameOfExpression(name)) AndAlso options.PreferPredefinedTypeKeywordInDeclaration.Value End Function Private Shared Function CanSimplifyNullable(type As INamedTypeSymbol, name As NameSyntax) As Boolean If Not type.IsNullable Then Return False End If If type.IsUnboundGenericType Then ' Don't simplify unbound generic type "Nullable(Of )". Return False End If If InsideNameOfExpression(name) Then ' Nullable(Of T) can't be simplified to T? in nameof expressions. Return False End If If Not InsideCrefReference(name) Then ' Nullable(Of T) can always be simplified to T? outside crefs. Return True End If ' Inside crefs, if the T in this Nullable(Of T) is being declared right here ' then this Nullable(Of T) is not a constructed generic type and we should ' not offer to simplify this to T?. ' ' For example, we should not offer the simplification in the following cases where ' T does not bind to an existing type / type parameter in the user's code. ' - <see cref="Nullable(Of T)"/> ' - <see cref="System.Nullable(Of T).Value"/> ' ' And we should offer the simplification in the following cases where SomeType and ' SomeMethod bind to a type and method declared elsewhere in the users code. ' - <see cref="SomeType.SomeMethod(Nullable(Of SomeType))"/> If name.IsKind(SyntaxKind.GenericName) Then If (name.IsParentKind(SyntaxKind.CrefReference)) OrElse ' cref="Nullable(Of T)" (name.IsParentKind(SyntaxKind.QualifiedName) AndAlso name.Parent?.IsParentKind(SyntaxKind.CrefReference)) OrElse ' cref="System.Nullable(Of T)" (name.IsParentKind(SyntaxKind.QualifiedName) AndAlso If(name.Parent?.IsParentKind(SyntaxKind.QualifiedName), False) AndAlso name.Parent.Parent?.IsParentKind(SyntaxKind.CrefReference)) Then ' cref="System.Nullable(Of T).Value" ' Unfortunately, unlike in corresponding C# case, we need syntax based checking to detect these cases because of bugs in the VB SemanticModel. ' See https://github.com/dotnet/roslyn/issues/2196, https://github.com/dotnet/roslyn/issues/2197 Return False End If End If Dim argument = type.TypeArguments.SingleOrDefault() If argument Is Nothing OrElse argument.IsErrorType() Then Return False End If Dim argumentDecl = argument.DeclaringSyntaxReferences.FirstOrDefault() If argumentDecl Is Nothing Then ' The type argument is a type from metadata - so this is a constructed generic nullable type that can be simplified (e.g. Nullable(Of Integer)). Return True End If Return Not name.Span.Contains(argumentDecl.Span) End Function End Class End Namespace