Beispiel #1
0
        private static TypeSyntax FixMethodReturnType(
            bool keepVoid, IMethodSymbol methodSymbol, TypeSyntax returnType,
            INamedTypeSymbol taskType, INamedTypeSymbol taskOfTType, INamedTypeSymbol valueTaskOfTType)
        {
            var newReturnType = returnType.WithAdditionalAnnotations(Formatter.Annotation);

            if (methodSymbol.ReturnsVoid)
            {
                if (!keepVoid)
                {
                    newReturnType = taskType.GenerateTypeSyntax();
                }
            }
            else
            {
                if (!IsTaskLike(methodSymbol.ReturnType, taskType, taskOfTType, valueTaskOfTType))
                {
                    // If it's not already Task-like, then wrap the existing return type
                    // in Task<>.
                    newReturnType = taskOfTType.Construct(methodSymbol.ReturnType).GenerateTypeSyntax();
                }
            }

            return(newReturnType.WithTriviaFrom(returnType));
        }
        private static TypeSyntax FixMethodReturnType(
            bool keepVoid,
            IMethodSymbol methodSymbol,
            TypeSyntax returnTypeSyntax,
            KnownTypes knownTypes
            )
        {
            var newReturnType = returnTypeSyntax.WithAdditionalAnnotations(Formatter.Annotation);

            if (methodSymbol.ReturnsVoid)
            {
                if (!keepVoid)
                {
                    newReturnType = knownTypes._taskType.GenerateTypeSyntax();
                }
            }
            else
            {
                var returnType = methodSymbol.ReturnType;
                if (IsIEnumerable(returnType, knownTypes) && IsIterator(methodSymbol))
                {
                    newReturnType = knownTypes._iAsyncEnumerableOfTTypeOpt is null
                        ? MakeGenericType("IAsyncEnumerable", methodSymbol.ReturnType)
                        : knownTypes._iAsyncEnumerableOfTTypeOpt
                                    .Construct(methodSymbol.ReturnType.GetTypeArguments()[0])
                                    .GenerateTypeSyntax();
                }
                else if (IsIEnumerator(returnType, knownTypes) && IsIterator(methodSymbol))
                {
                    newReturnType = knownTypes._iAsyncEnumeratorOfTTypeOpt is null
                        ? MakeGenericType("IAsyncEnumerator", methodSymbol.ReturnType)
                        : knownTypes._iAsyncEnumeratorOfTTypeOpt
                                    .Construct(methodSymbol.ReturnType.GetTypeArguments()[0])
                                    .GenerateTypeSyntax();
                }
                else if (IsIAsyncEnumerableOrEnumerator(returnType, knownTypes))
                {
                    // Leave the return type alone
                }
                else if (!IsTaskLike(returnType, knownTypes))
                {
                    // If it's not already Task-like, then wrap the existing return type
                    // in Task<>.
                    newReturnType = knownTypes._taskOfTType
                                    .Construct(methodSymbol.ReturnType)
                                    .GenerateTypeSyntax();
                }
            }

            return(newReturnType
                   .WithTriviaFrom(returnTypeSyntax)
                   .WithAdditionalAnnotations(Simplifier.AddImportsAnnotation));
Beispiel #3
0
        private static async Task <Document> ChangeReturnTypeAsync(
            Document document,
            TypeSyntax type,
            TypeSyntax newType,
            CancellationToken cancellationToken)
        {
            SyntaxNode oldRoot = await document.GetSyntaxRootAsync(cancellationToken);

            SyntaxNode newNode = SetNewType(
                type.Parent,
                newType.WithAdditionalAnnotations(Simplifier.Annotation));

            SyntaxNode newRoot = oldRoot.ReplaceNode(type.Parent, newNode);

            return(document.WithSyntaxRoot(newRoot));
        }
        /// <summary>
        /// Converts a synchronous method to be asynchronous, if it is not already async.
        /// </summary>
        /// <param name="method">The method to convert.</param>
        /// <param name="document">The document.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>
        /// The new Document and method syntax, or the original if it was already async.
        /// </returns>
        /// <exception cref="System.ArgumentNullException">
        /// method
        /// or
        /// document
        /// or
        /// originalMethodSymbol
        /// </exception>
        internal static async Task <Tuple <Document, MethodDeclarationSyntax> > MakeMethodAsync(this MethodDeclarationSyntax method, Document document, CancellationToken cancellationToken = default(CancellationToken))
        {
            if (method == null)
            {
                throw new ArgumentNullException(nameof(method));
            }

            if (document == null)
            {
                throw new ArgumentNullException(nameof(document));
            }

            if (method.Modifiers.Any(SyntaxKind.AsyncKeyword))
            {
                // Already asynchronous.
                return(Tuple.Create(document, method));
            }

            DocumentId documentId    = document.Id;
            var        semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);

            var methodSymbol = semanticModel.GetDeclaredSymbol(method);

            bool       hasReturnValue;
            TypeSyntax returnType = method.ReturnType;

            if (!Utils.HasAsyncCompatibleReturnType(methodSymbol))
            {
                hasReturnValue = (method.ReturnType as PredefinedTypeSyntax)?.Keyword.Kind() != SyntaxKind.VoidKeyword;

                // Determine new return type.
                returnType = hasReturnValue
                    ? QualifyName(
                    Namespaces.SystemThreadingTasks,
                    SyntaxFactory.GenericName(SyntaxFactory.Identifier(nameof(Task)))
                    .AddTypeArgumentListArguments(method.ReturnType))
                    : SyntaxFactory.ParseTypeName(typeof(Task).FullName);
                returnType = returnType
                             .WithAdditionalAnnotations(Simplifier.Annotation)
                             .WithTrailingTrivia(method.ReturnType.GetTrailingTrivia());
            }
            else
            {
                TypeSyntax t = method.ReturnType;
                while (t is QualifiedNameSyntax q)
                {
                    t = q.Right;
                }

                hasReturnValue = t is GenericNameSyntax;
            }

            // Fix up any return statements to await on the Task it would have returned.
            bool        returnTypeChanged = method.ReturnType != returnType;
            BlockSyntax updatedBody       = UpdateStatementsForAsyncMethod(
                method.Body,
                semanticModel,
                hasReturnValue,
                returnTypeChanged,
                cancellationToken);

            // Apply the changes to the document, and null out stale data.
            SyntaxAnnotation methodBookmark;

            (document, method, methodBookmark) = await UpdateDocumentAsync(
                document,
                method,
                m => m
                .WithBody(updatedBody)
                .AddModifiers(SyntaxFactory.Token(SyntaxKind.AsyncKeyword))
                .WithReturnType(returnType),
                cancellationToken).ConfigureAwait(false);

            semanticModel = null;
            methodSymbol  = null;

            // Rename the method to have an Async suffix if we changed the return type,
            // and it doesn't already have that suffix.
            if (returnTypeChanged && !method.Identifier.ValueText.EndsWith(VSTHRD200UseAsyncNamingConventionAnalyzer.MandatoryAsyncSuffix, StringComparison.Ordinal))
            {
                string newName = method.Identifier.ValueText + VSTHRD200UseAsyncNamingConventionAnalyzer.MandatoryAsyncSuffix;

                semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);

                methodSymbol = semanticModel.GetDeclaredSymbol(method, cancellationToken);

                // Don't rename entrypoint (i.e. "Main") methods.
                if (!Utils.IsEntrypointMethod(methodSymbol, semanticModel, cancellationToken))
                {
                    var solution = await Renamer.RenameSymbolAsync(
                        document.Project.Solution,
                        methodSymbol,
                        newName,
                        document.Project.Solution.Workspace.Options,
                        cancellationToken).ConfigureAwait(false);

                    document      = solution.GetDocument(document.Id);
                    semanticModel = null;
                    methodSymbol  = null;
                    var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

                    method = (MethodDeclarationSyntax)root.GetAnnotatedNodes(methodBookmark).Single();
                }
            }

            // Update callers to await calls to this method if we made it awaitable.
            if (returnTypeChanged)
            {
                semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);

                methodSymbol = semanticModel.GetDeclaredSymbol(method, cancellationToken);
                SyntaxAnnotation  callerAnnotation;
                Solution          solution = document.Project.Solution;
                List <DocumentId> annotatedDocumentIds;
                (solution, callerAnnotation, annotatedDocumentIds) = await AnnotateAllCallersAsync(solution, methodSymbol, cancellationToken).ConfigureAwait(false);

                foreach (DocumentId docId in annotatedDocumentIds)
                {
                    document = solution.GetDocument(docId);
                    var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);

                    var root = await tree.GetRootAsync(cancellationToken).ConfigureAwait(false);

                    var rewriter = new AwaitCallRewriter(callerAnnotation);
                    root     = rewriter.Visit(root);
                    solution = solution.GetDocument(tree).WithSyntaxRoot(root).Project.Solution;
                }

                foreach (DocumentId docId in annotatedDocumentIds)
                {
                    document = solution.GetDocument(docId);
                    var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);

                    var root = await tree.GetRootAsync(cancellationToken).ConfigureAwait(false);

                    for (var node = root.GetAnnotatedNodes(callerAnnotation).FirstOrDefault(); node != null; node = root.GetAnnotatedNodes(callerAnnotation).FirstOrDefault())
                    {
                        var callingMethod = node.FirstAncestorOrSelf <MethodDeclarationSyntax>();
                        if (callingMethod != null)
                        {
                            (document, callingMethod) = await MakeMethodAsync(callingMethod, document, cancellationToken).ConfigureAwait(false);

                            // Clear all annotations of callers from this method so we don't revisit it.
                            root = await callingMethod.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false);

                            var annotationRemover = new RemoveAnnotationRewriter(callerAnnotation);
                            root     = root.ReplaceNode(callingMethod, annotationRemover.Visit(callingMethod));
                            document = document.WithSyntaxRoot(root);
                            root     = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
                        }
                        else
                        {
                            // Clear all annotations of callers from this method so we don't revisit it.
                            root = await node.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false);

                            root     = root.ReplaceNode(node, node.WithoutAnnotations(callerAnnotation));
                            document = document.WithSyntaxRoot(root);
                            root     = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
                        }
                    }

                    solution = document.Project.Solution;
                }

                // Make sure we return the latest of everything.
                document = solution.GetDocument(documentId);
                var finalTree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);

                var finalRoot = await finalTree.GetRootAsync(cancellationToken).ConfigureAwait(false);

                method = (MethodDeclarationSyntax)finalRoot.GetAnnotatedNodes(methodBookmark).Single();
            }

            return(Tuple.Create(document, method));
        }
        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);
        }
        private static async Task <Document> MakeConstAsync(Document document, LocalDeclarationStatementSyntax localDeclaration, CancellationToken cancellationToken)
        {
            // Remove the leading trivia from the local declaration.
            SyntaxToken      firstToken    = localDeclaration.GetFirstToken();
            SyntaxTriviaList leadingTrivia = firstToken.LeadingTrivia;
            LocalDeclarationStatementSyntax trimmedLocal = localDeclaration.ReplaceToken(
                firstToken, firstToken.WithLeadingTrivia(SyntaxTriviaList.Empty));

            // Create a const token with the leading trivia.
            SyntaxToken constToken = SyntaxFactory.Token(leadingTrivia, SyntaxKind.ConstKeyword, SyntaxFactory.TriviaList(SyntaxFactory.ElasticMarker));

            // Insert the const token into the modifiers list, creating a new modifiers list.
            SyntaxTokenList newModifiers = trimmedLocal.Modifiers.Insert(0, constToken);

            // If the type of declaration is 'var', create a new type name for the
            // type inferred for 'var'.
            VariableDeclarationSyntax variableDeclaration = localDeclaration.Declaration;
            TypeSyntax variableTypeName = variableDeclaration.Type;

            if (variableTypeName.IsVar)
            {
                SemanticModel semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);

                // Special case: Ensure that 'var' isn't actually an alias to another type
                // (e.g. using var = System.String).
                IAliasSymbol aliasInfo = semanticModel.GetAliasInfo(variableTypeName, cancellationToken);
                if (aliasInfo == null)
                {
                    // Retrieve the type inferred for var.
                    ITypeSymbol type = semanticModel.GetTypeInfo(variableTypeName, cancellationToken).ConvertedType;

                    // Special case: Ensure that 'var' isn't actually a type named 'var'.
                    if (type.Name != "var")
                    {
                        // Create a new TypeSyntax for the inferred type. Be careful
                        // to keep any leading and trailing trivia from the var keyword.
                        TypeSyntax typeName = SyntaxFactory.ParseTypeName(type.ToDisplayString())
                                              .WithLeadingTrivia(variableTypeName.GetLeadingTrivia())
                                              .WithTrailingTrivia(variableTypeName.GetTrailingTrivia());

                        // Add an annotation to simplify the type name.
                        TypeSyntax simplifiedTypeName = typeName.WithAdditionalAnnotations(Simplifier.Annotation);

                        // Replace the type in the variable declaration.
                        variableDeclaration = variableDeclaration.WithType(simplifiedTypeName);
                    }
                }
            }

            // Produce the new local declaration.
            LocalDeclarationStatementSyntax newLocal = trimmedLocal.WithModifiers(newModifiers)
                                                       .WithDeclaration(variableDeclaration);

            // Add an annotation to format the new local declaration.
            LocalDeclarationStatementSyntax formattedLocal = newLocal.WithAdditionalAnnotations(Formatter.Annotation);

            // Replace the old local declaration with the new local declaration.
            SyntaxNode root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

            SyntaxNode newRoot = root.ReplaceNode(localDeclaration, formattedLocal);

            // Return document with transformed tree.
            return(document.WithSyntaxRoot(newRoot));
        }