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));
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)); }