예제 #1
0
        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));
        }
예제 #2
0
        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);
        }