public override bool TrySimplify( NameSyntax name, SemanticModel semanticModel, OptionSet optionSet, out TypeSyntax replacementNode, out TextSpan issueSpan, CancellationToken cancellationToken) { replacementNode = null; issueSpan = default; if (name.IsVar) { return(false); } // we should not simplify a name of a namespace declaration if (IsPartOfNamespaceDeclarationName(name)) { return(false); } // We can simplify Qualified names and AliasQualifiedNames. Generally, if we have // something like "A.B.C.D", we only consider the full thing something we can simplify. // However, in the case of "A.B.C<>.D", then we'll only consider simplifying up to the // first open name. This is because if we remove the open name, we'll often change // meaning as "D" will bind to C<T>.D which is different than C<>.D! if (name is QualifiedNameSyntax qualifiedName) { var left = qualifiedName.Left; if (ContainsOpenName(left)) { // Don't simplify A.B<>.C return(false); } } // 1. see whether binding the name binds to a symbol/type. if not, it is ambiguous and // nothing we can do here. var symbol = SimplificationHelpers.GetOriginalSymbolInfo(semanticModel, name); if (symbol == null) { return(false); } // treat constructor names as types var method = symbol as IMethodSymbol; if (method.IsConstructor()) { symbol = method.ContainingType; } if (symbol.Kind == SymbolKind.Method && name.Kind() == SyntaxKind.GenericName) { var genericName = (GenericNameSyntax)name; replacementNode = SyntaxFactory.IdentifierName(genericName.Identifier) .WithLeadingTrivia(genericName.GetLeadingTrivia()) .WithTrailingTrivia(genericName.GetTrailingTrivia()); issueSpan = genericName.TypeArgumentList.Span; return(CanReplaceWithReducedName( name, replacementNode, semanticModel, cancellationToken)); } if (!(symbol is INamespaceOrTypeSymbol)) { return(false); } if (name.HasAnnotations(SpecialTypeAnnotation.Kind)) { replacementNode = SyntaxFactory.PredefinedType( SyntaxFactory.Token( name.GetLeadingTrivia(), GetPredefinedKeywordKind(SpecialTypeAnnotation.GetSpecialType(name.GetAnnotations(SpecialTypeAnnotation.Kind).First())), name.GetTrailingTrivia())); issueSpan = name.Span; return(CanReplaceWithReducedNameInContext(name, replacementNode, semanticModel)); } else { if (!name.IsRightSideOfDotOrColonColon()) { if (TryReplaceExpressionWithAlias(name, semanticModel, symbol, cancellationToken, out var aliasReplacement)) { // get the token text as it appears in source code to preserve e.g. Unicode character escaping var text = aliasReplacement.Name; var syntaxRef = aliasReplacement.DeclaringSyntaxReferences.FirstOrDefault(); if (syntaxRef != null) { var declIdentifier = ((UsingDirectiveSyntax)syntaxRef.GetSyntax(cancellationToken)).Alias.Name.Identifier; text = declIdentifier.IsVerbatimIdentifier() ? declIdentifier.ToString().Substring(1) : declIdentifier.ToString(); } var identifierToken = SyntaxFactory.Identifier( name.GetLeadingTrivia(), SyntaxKind.IdentifierToken, text, aliasReplacement.Name, name.GetTrailingTrivia()); identifierToken = CSharpSimplificationService.TryEscapeIdentifierToken(identifierToken, name); replacementNode = SyntaxFactory.IdentifierName(identifierToken); // Merge annotation to new syntax node var annotatedNodesOrTokens = name.GetAnnotatedNodesAndTokens(RenameAnnotation.Kind); foreach (var annotatedNodeOrToken in annotatedNodesOrTokens) { if (annotatedNodeOrToken.IsToken) { identifierToken = annotatedNodeOrToken.AsToken().CopyAnnotationsTo(identifierToken); } else { replacementNode = annotatedNodeOrToken.AsNode().CopyAnnotationsTo(replacementNode); } } annotatedNodesOrTokens = name.GetAnnotatedNodesAndTokens(AliasAnnotation.Kind); foreach (var annotatedNodeOrToken in annotatedNodesOrTokens) { if (annotatedNodeOrToken.IsToken) { identifierToken = annotatedNodeOrToken.AsToken().CopyAnnotationsTo(identifierToken); } else { replacementNode = annotatedNodeOrToken.AsNode().CopyAnnotationsTo(replacementNode); } } replacementNode = ((SimpleNameSyntax)replacementNode).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) { var qualifiedName3 = (QualifiedNameSyntax)name; if (qualifiedName3.Right.Identifier.ValueText == identifierToken.ValueText) { issueSpan = qualifiedName3.Left.Span; } } // first check if this would be a valid reduction if (CanReplaceWithReducedNameInContext(name, replacementNode, semanticModel)) { // in case this alias name ends with "Attribute", we're going to see if we can also // remove that suffix. if (TryReduceAttributeSuffix( name, identifierToken, out var replacementNodeWithoutAttributeSuffix, out var issueSpanWithoutAttributeSuffix)) { if (CanReplaceWithReducedName(name, replacementNodeWithoutAttributeSuffix, semanticModel, cancellationToken)) { replacementNode = replacementNode.CopyAnnotationsTo(replacementNodeWithoutAttributeSuffix); issueSpan = issueSpanWithoutAttributeSuffix; } } return(true); } return(false); } var nameHasNoAlias = false; if (name is SimpleNameSyntax simpleName) { if (!simpleName.Identifier.HasAnnotations(AliasAnnotation.Kind)) { nameHasNoAlias = true; } } if (name is QualifiedNameSyntax qualifiedName2) { if (!qualifiedName2.Right.HasAnnotation(Simplifier.SpecialTypeAnnotation)) { nameHasNoAlias = true; } } if (name is AliasQualifiedNameSyntax aliasQualifiedName) { if (aliasQualifiedName.Name is SimpleNameSyntax && !aliasQualifiedName.Name.Identifier.HasAnnotations(AliasAnnotation.Kind) && !aliasQualifiedName.Name.HasAnnotation(Simplifier.SpecialTypeAnnotation)) { nameHasNoAlias = true; } } var aliasInfo = semanticModel.GetAliasInfo(name, cancellationToken); if (nameHasNoAlias && aliasInfo == null) { // 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. var inDeclarationContext = PreferPredefinedTypeKeywordInDeclarations(name, optionSet, semanticModel); var inMemberAccessContext = PreferPredefinedTypeKeywordInMemberAccess(name, optionSet, semanticModel); if (!name.Parent.IsKind(SyntaxKind.QualifiedName) && (inDeclarationContext || inMemberAccessContext)) { // See if we can simplify this name (like System.Int32) to a built-in type (like 'int'). // If not, we'll still fall through and see if we can convert it to Int32. var codeStyleOptionName = inDeclarationContext ? nameof(CodeStyleOptions.PreferIntrinsicPredefinedTypeKeywordInDeclaration) : nameof(CodeStyleOptions.PreferIntrinsicPredefinedTypeKeywordInMemberAccess); var type = semanticModel.GetTypeInfo(name, cancellationToken).Type; if (type != null) { var keywordKind = GetPredefinedKeywordKind(type.SpecialType); if (keywordKind != SyntaxKind.None && CanReplaceWithPredefinedTypeKeywordInContext(name, semanticModel, out replacementNode, ref issueSpan, keywordKind, codeStyleOptionName)) { return(true); } } else { var typeSymbol = semanticModel.GetSymbolInfo(name, cancellationToken).Symbol; if (typeSymbol.IsKind(SymbolKind.NamedType)) { var keywordKind = GetPredefinedKeywordKind(((INamedTypeSymbol)typeSymbol).SpecialType); if (keywordKind != SyntaxKind.None && CanReplaceWithPredefinedTypeKeywordInContext(name, semanticModel, out replacementNode, ref issueSpan, keywordKind, codeStyleOptionName)) { return(true); } } } } } // Nullable rewrite: Nullable<int> -> int? // Don't rewrite in the case where Nullable<int> is part of some qualified name like Nullable<int>.Something if (!name.IsVar && symbol.Kind == SymbolKind.NamedType && !name.IsLeftSideOfQualifiedName()) { var type = (INamedTypeSymbol)symbol; if (aliasInfo == null && CanSimplifyNullable(type, name, semanticModel)) { GenericNameSyntax genericName; if (name.Kind() == SyntaxKind.QualifiedName) { genericName = (GenericNameSyntax)((QualifiedNameSyntax)name).Right; } else { genericName = (GenericNameSyntax)name; } var oldType = genericName.TypeArgumentList.Arguments.First(); if (oldType.Kind() == SyntaxKind.OmittedTypeArgument) { return(false); } replacementNode = SyntaxFactory.NullableType(oldType) .WithLeadingTrivia(name.GetLeadingTrivia()) .WithTrailingTrivia(name.GetTrailingTrivia()); issueSpan = name.Span; // we need to simplify the whole qualified name at once, because replacing the identifier on the left in // System.Nullable<int> alone would be illegal. // If this fails we want to continue to try at least to remove the System if possible. if (CanReplaceWithReducedNameInContext(name, replacementNode, semanticModel)) { return(true); } } } } SyntaxToken identifier; switch (name.Kind()) { case SyntaxKind.AliasQualifiedName: var simpleName = ((AliasQualifiedNameSyntax)name).Name .WithLeadingTrivia(name.GetLeadingTrivia()); simpleName = simpleName.ReplaceToken(simpleName.Identifier, ((AliasQualifiedNameSyntax)name).Name.Identifier.CopyAnnotationsTo( simpleName.Identifier.WithLeadingTrivia( ((AliasQualifiedNameSyntax)name).Alias.Identifier.LeadingTrivia))); replacementNode = simpleName; issueSpan = ((AliasQualifiedNameSyntax)name).Alias.Span; break; case SyntaxKind.QualifiedName: replacementNode = ((QualifiedNameSyntax)name).Right.WithLeadingTrivia(name.GetLeadingTrivia()); issueSpan = ((QualifiedNameSyntax)name).Left.Span; break; case SyntaxKind.IdentifierName: identifier = ((IdentifierNameSyntax)name).Identifier; // we can try to remove the Attribute suffix if this is the attribute name TryReduceAttributeSuffix(name, identifier, out replacementNode, out issueSpan); break; } } if (replacementNode == null) { return(false); } // We may be looking at a name `X.Y` seeing if we can replace it with `Y`. However, in // order to know for sure, we actually have to look slightly higher at `X.Y.Z` to see if // it can simplify to `Y.Z`. This is because in the `Color Color` case we can only tell // if we can reduce by looking by also looking at what comes next to see if it will // cause the simplified name to bind to the instance or static side. if (TryReduceCrefColorColor(name, replacementNode, semanticModel, cancellationToken)) { return(true); } return(CanReplaceWithReducedName(name, replacementNode, semanticModel, cancellationToken)); }
private static bool TryReduceMemberAccessExpression( MemberAccessExpressionSyntax memberAccess, SemanticModel semanticModel, out TypeSyntax replacementNode, out TextSpan issueSpan, OptionSet optionSet, CancellationToken cancellationToken) { replacementNode = null; issueSpan = default; if (memberAccess.Name == null || memberAccess.Expression == null) return false; // if this node is annotated as being a SpecialType, let's use this information. if (memberAccess.HasAnnotations(SpecialTypeAnnotation.Kind)) { replacementNode = SyntaxFactory.PredefinedType( SyntaxFactory.Token( memberAccess.GetLeadingTrivia(), GetPredefinedKeywordKind(SpecialTypeAnnotation.GetSpecialType(memberAccess.GetAnnotations(SpecialTypeAnnotation.Kind).First())), memberAccess.GetTrailingTrivia())); issueSpan = memberAccess.Span; return true; } // See https://github.com/dotnet/roslyn/issues/40974 // // To be very safe, we only support simplifying code that bound to a symbol without any // sort of problems. We could potentially relax this in the future. However, we would // need to be very careful about the implications of us offering to fixup 'broken' code // in a manner that might end up making things worse or confusing the user. var symbol = SimplificationHelpers.GetOriginalSymbolInfo(semanticModel, memberAccess); if (symbol == null) return false; if (memberAccess.Expression.IsKind(SyntaxKind.ThisExpression) && !SimplificationHelpers.ShouldSimplifyThisOrMeMemberAccessExpression(semanticModel, optionSet, symbol)) { return false; } // if this node is on the left side, we could simplify to aliases if (!memberAccess.IsRightSideOfDot()) { // Check if we need to replace this syntax with an alias identifier if (TryReplaceExpressionWithAlias( memberAccess, semanticModel, symbol, cancellationToken, out var aliasReplacement)) { // get the token text as it appears in source code to preserve e.g. unicode character escaping var text = aliasReplacement.Name; var syntaxRef = aliasReplacement.DeclaringSyntaxReferences.FirstOrDefault(); if (syntaxRef != null) { var declIdentifier = ((UsingDirectiveSyntax)syntaxRef.GetSyntax(cancellationToken)).Alias.Name.Identifier; text = declIdentifier.IsVerbatimIdentifier() ? declIdentifier.ToString().Substring(1) : declIdentifier.ToString(); } replacementNode = SyntaxFactory.IdentifierName( memberAccess.Name.Identifier.CopyAnnotationsTo(SyntaxFactory.Identifier( memberAccess.GetLeadingTrivia(), SyntaxKind.IdentifierToken, text, aliasReplacement.Name, memberAccess.GetTrailingTrivia()))); replacementNode = memberAccess.CopyAnnotationsTo(replacementNode); replacementNode = memberAccess.Name.CopyAnnotationsTo(replacementNode); issueSpan = memberAccess.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 (memberAccess.Name.Identifier.ValueText == ((IdentifierNameSyntax)replacementNode).Identifier.ValueText) { issueSpan = memberAccess.Expression.Span; } return true; } // Check if the Expression can be replaced by Predefined Type keyword if (PreferPredefinedTypeKeywordInMemberAccess(memberAccess, optionSet, semanticModel)) { if (symbol != null && symbol.IsKind(SymbolKind.NamedType)) { var keywordKind = GetPredefinedKeywordKind(((INamedTypeSymbol)symbol).SpecialType); if (keywordKind != SyntaxKind.None) { replacementNode = CreatePredefinedTypeSyntax(memberAccess, keywordKind); replacementNode = replacementNode .WithAdditionalAnnotations<TypeSyntax>(new SyntaxAnnotation( nameof(CodeStyleOptions2.PreferIntrinsicPredefinedTypeKeywordInMemberAccess))); issueSpan = memberAccess.Span; // we want to show the whole expression as unnecessary return true; } } } } // Try to eliminate cases without actually calling CanReplaceWithReducedName. For expressions of the form // 'this.Name' or 'base.Name', no additional check here is required. if (!memberAccess.Expression.IsKind(SyntaxKind.ThisExpression, SyntaxKind.BaseExpression)) { GetReplacementCandidates( semanticModel, memberAccess, symbol, out var speculativeSymbols, out var speculativeNamespacesAndTypes); if (!IsReplacementCandidate(symbol, speculativeSymbols, speculativeNamespacesAndTypes)) { return false; } } replacementNode = memberAccess.GetNameWithTriviaMoved(); issueSpan = memberAccess.Expression.Span; return CanReplaceWithReducedName( memberAccess, replacementNode, semanticModel, symbol, cancellationToken); }