// The rename process and annotation for the bookkeeping is performed in one-step private async Task<Solution> AnnotateAndRename_WorkerAsync( Solution originalSolution, Solution partiallyRenamedSolution, HashSet<DocumentId> documentIdsToRename, IEnumerable<RenameLocation> renameLocations, RenamedSpansTracker renameSpansTracker, bool replacementTextValid) { try { foreach (var documentId in documentIdsToRename.ToList()) { _cancellationToken.ThrowIfCancellationRequested(); // We try to rewrite all locations that are not candidate locations. If there is only one location // it must be the correct one (the symbol is ambiguous to something else) and we always try to rewrite it. var document = originalSolution.GetDocument(documentId); var semanticModel = await document.GetSemanticModelAsync(_cancellationToken).ConfigureAwait(false); var originalSyntaxRoot = await semanticModel.SyntaxTree.GetRootAsync(_cancellationToken).ConfigureAwait(false); // Get all rename locations for the current document. var allTextSpansInSingleSourceTree = renameLocations .Where(l => l.DocumentId == documentId && !l.IsRenameInStringOrComment && (renameLocations.Count() == 1 || !l.IsCandidateLocation || l.IsMethodGroupReference)) .ToDictionary(l => l.Location.SourceSpan); // All textspan in the document documentId, that requires rename in String or Comment var stringAndCommentTextSpansInSingleSourceTree = renameLocations .Where(l => l.DocumentId == documentId && l.IsRenameInStringOrComment) .GroupBy(l => l.ContainingLocationForStringOrComment) .Select(g => g.Key) .ToSet(); var conflictLocationSpans = _conflictLocations .Where(t => t.DocumentId == documentId) .Select(t => t.ComplexifiedSpan).ToSet(); // Annotate all nodes with a RenameLocation annotations to record old locations & old referenced symbols. // Also annotate nodes that should get complexified (nodes for rename locations + conflict locations) var parameters = new RenameRewriterParameters( _renamedSymbolDeclarationAnnotation, document, semanticModel, originalSyntaxRoot, _replacementText, _originalText, _possibleNameConflicts, allTextSpansInSingleSourceTree, stringAndCommentTextSpansInSingleSourceTree, conflictLocationSpans, originalSolution, _renameLocationSet.Symbol, replacementTextValid, renameSpansTracker, _optionSet, _renameAnnotations, _cancellationToken); var renameRewriterLanguageService = document.Project.LanguageServices.GetService<IRenameRewriterLanguageService>(); var newRoot = renameRewriterLanguageService.AnnotateAndRename(parameters); if (newRoot == originalSyntaxRoot) { // Update the list for the current phase, some files with strings containing the original or replacement // text may have been filtered out. documentIdsToRename.Remove(documentId); } else { partiallyRenamedSolution = partiallyRenamedSolution.WithDocumentSyntaxRoot(documentId, newRoot, PreservationMode.PreserveIdentity); } } return partiallyRenamedSolution; } catch (Exception e) when (FatalError.ReportUnlessCanceled(e)) { throw ExceptionUtilities.Unreachable; } }
private static async Task<Solution> UpdateReferencingDocumentsAsync(Document document, string methodClassName, IEnumerable<IGrouping<Document, ReferenceLocation>> documentGroups, Solution newSolution, CancellationToken cancellationToken) { var methodIdentifier = SyntaxFactory.IdentifierName(methodClassName); foreach (var documentGroup in documentGroups) { var referencingDocument = documentGroup.Key; if (referencingDocument.Equals(document)) continue; var newReferencingRoot = await referencingDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var diagnosticNodes = documentGroup.Select(referenceLocation => newReferencingRoot.FindNode(referenceLocation.Location.SourceSpan)).ToList(); newReferencingRoot = newReferencingRoot.TrackNodes(diagnosticNodes); foreach (var diagnosticNode in diagnosticNodes) { var memberAccess = (MemberAccessExpressionSyntax)newReferencingRoot.GetCurrentNode(diagnosticNode).FirstAncestorOrSelfOfType<InvocationExpressionSyntax>().Expression; var newMemberAccess = memberAccess.ReplaceNode(memberAccess.Expression, methodIdentifier) .WithAdditionalAnnotations(Formatter.Annotation); newReferencingRoot = newReferencingRoot.ReplaceNode(memberAccess, newMemberAccess); } newSolution = newSolution.WithDocumentSyntaxRoot(referencingDocument.Id, newReferencingRoot); } return newSolution; }
private Solution AnnotateDocumentInSolution(Document document, Solution solution) { if (document == null) throw new ArgumentNullException("document"); if (solution == null) throw new ArgumentNullException("solution"); var annotater = new APMBeginInvocationAnnotater(); var annotatedDocument = annotater.Annotate(document); NumCandidates += annotater.NumAnnotations; return solution.WithDocumentSyntaxRoot( document.Id, annotatedDocument.GetSyntaxRootAsync().Result ); }
private async Task<Solution> CleanSolutionDocument(Solution solution, DocumentId documentId, CancellationToken cancellationToken) { var document = solution.GetDocument(documentId); var syntaxNode = await document.GetSyntaxRootAsync(cancellationToken); if (syntaxNode == null) { return solution; } var newNode = RemoveRenameAnnotations(syntaxNode); return solution.WithDocumentSyntaxRoot(documentId, newNode); }
private Solution ExecuteRefactoring(Document document, Solution solution, int index) { if (document == null) throw new ArgumentNullException("document"); var syntax = ((SyntaxTree)document.GetSyntaxTreeAsync().Result).GetRoot(); //Logger.Info("Refactoring annotated document: index={0}", index); //Logger.Debug("=== CODE TO REFACTOR ===\n{0}=== END OF CODE ===", syntax); var startTime = DateTime.UtcNow; var refactoredSyntax = APMToAsyncAwait.RefactorAPMToAsyncAwait(document, solution, _workspace, index); var endTime = DateTime.UtcNow; var refactoringTime = endTime.Subtract(startTime).Milliseconds; //Logger.Debug("Refactoring completed in {0} ms.", refactoringTime); //Logger.Debug("=== REFACTORED CODE ===\n{0}=== END OF CODE ===", refactoredSyntax.Format(_workspace)); TempLog2.Info("{0},{1},{2}", index, document.FilePath, refactoringTime); return solution.WithDocumentSyntaxRoot(document.Id, refactoredSyntax); }
/// <summary> /// Execute the APM-to-async/await refactoring for a given APM method invocation. /// </summary> /// <param name="document">The C# Document on which to operate/in which the Begin and End method calls are represented.</param> /// <param name="solution">The solution that contains the C# document.</param> /// <param name="workspace">The workspace to which the code in the syntax tree currently belongs, for formatting purposes.</param> /// <param name="index">The index number </param> /// <returns>The CompilationUnitSyntax node that is the result of the transformation.</returns> public static CompilationUnitSyntax RefactorToTask(Document document, Solution solution, Workspace workspace, int index) { if (document == null) throw new ArgumentNullException("document"); if (workspace == null) throw new ArgumentNullException("workspace"); String message; var numErrorsInSolutionBeforeRewriting = solution.CompilationErrorCount(); var syntaxTree = (SyntaxTree)document.GetSyntaxTreeAsync().Result; var syntax = (CompilationUnitSyntax)syntaxTree.GetRoot(); Logger.Trace("\n### REFACTORING CODE ###\n{0}\n### END OF CODE ###", syntax.Format(workspace)); InvocationExpressionSyntax beginXxxCall; try { beginXxxCall = document.GetAnnotatedInvocation(index); } catch (InvalidOperationException) { throw new ArgumentException( "Syntax tree has no InvocationExpressionSyntax node annotated with RefactorableAPMInstance"); } var model = (SemanticModel)document.GetSemanticModelAsync().Result; var callbackArgument = FindAsyncCallbackInvocationArgument(model, beginXxxCall); var callbackExpression = callbackArgument.Expression; CompilationUnitSyntax rewrittenSyntax; switch (callbackExpression.CSharpKind()) { case SyntaxKind.SimpleLambdaExpression: var lambda = (SimpleLambdaExpressionSyntax)callbackExpression; switch (lambda.Body.CSharpKind()) { case SyntaxKind.Block: var stateArgument = FindAsyncStateInvocationArgument(model, beginXxxCall); switch (stateArgument.Expression.CSharpKind()) { case SyntaxKind.NullLiteralExpression: Logger.Info("Refactoring:\n{0}", beginXxxCall.ContainingMethod()); return RefactorSimpleLambdaInstance(syntax, beginXxxCall, model, workspace, callbackArgument); default: Logger.Info("Rewriting to remove state argument:\n{0}", beginXxxCall); rewrittenSyntax = RewriteStateArgumentToNull(lambda, syntax, stateArgument); break; } break; case SyntaxKind.InvocationExpression: Logger.Info("Rewriting lambda to block form:\n{0}", beginXxxCall); rewrittenSyntax = RewriteInvocationExpressionToBlock(syntax, lambda, model, beginXxxCall); break; default: message = String .Format( "Unsupported lambda body kind: {0}: method:\n{1}", lambda.Body.CSharpKind(), beginXxxCall.ContainingMethod() ); Logger.Error("Not implemented: {0}", message); throw new NotImplementedException(message); } break; case SyntaxKind.IdentifierName: case SyntaxKind.SimpleMemberAccessExpression: Logger.Info("Rewriting method reference to lambda:\n{0}", beginXxxCall); rewrittenSyntax = RewriteMethodReferenceToSimpleLambda(syntax, beginXxxCall, model, callbackArgument, callbackExpression); break; case SyntaxKind.ParenthesizedLambdaExpression: Logger.Info("Rewriting parenthesized lambda to simple lambda:\n{0}", beginXxxCall); rewrittenSyntax = RewriteParenthesizedLambdaToSimpleLambda(syntax, beginXxxCall, model); break; case SyntaxKind.ObjectCreationExpression: Logger.Info("Rewriting object creation expression to simple lambda:\n{0}", beginXxxCall); var objectCreation = (ObjectCreationExpressionSyntax)callbackExpression; rewrittenSyntax = RewriteObjectCreationToSimpleLambda(syntax, objectCreation, workspace); break; case SyntaxKind.AnonymousMethodExpression: Logger.Info("Rewriting anonymous method (delegate) expression to simple lambda:\n{0}", beginXxxCall); var anonymousMethod = (AnonymousMethodExpressionSyntax)callbackExpression; rewrittenSyntax = RewriteAnonymousMethodToSimpleLambda(syntax, anonymousMethod, workspace); break; case SyntaxKind.NullLiteralExpression: message = String.Format("callback is null:\n{0}", beginXxxCall.ContainingMethod()); Logger.Error("Precondition failed: {0}", message); throw new PreconditionException(message); case SyntaxKind.InvocationExpression: message = String .Format( "InvocationExpression as callback is not supported: {0}", beginXxxCall ); Logger.Error("Precondition failed: {0}", message); throw new PreconditionException(message); case SyntaxKind.GenericName: message = String.Format("GenericName syntax kind is not supported"); Logger.Error("Precondition failed: {0}", message); throw new PreconditionException(message); default: message = String.Format( "Unsupported actual argument syntax node kind: {0}: callback argument: {1}: in method:\n{2}", callbackExpression.CSharpKind(), callbackArgument, beginXxxCall.ContainingMethod() ); Logger.Error(message); throw new NotImplementedException(message); } var rewrittenDocument = document.WithSyntaxRoot(rewrittenSyntax); var rewrittenSolution = solution.WithDocumentSyntaxRoot(document.Id, rewrittenSyntax); //if (rewrittenSolution.CompilationErrorCount() > numErrorsInSolutionBeforeRewriting) //{ // Logger.Error( // "Rewritten solution contains more compilation errors than the original solution while refactoring: {0} @ {1}:{2} in method:\n{3}", // beginXxxCall, // beginXxxCall.SyntaxTree.FilePath, // beginXxxCall.GetStartLineNumber(), // beginXxxCall.ContainingMethod() // ); // Logger.Warn("=== SOLUTION ERRORS ==="); // foreach (var diagnostic in rewrittenSolution.GetDiagnostics().Where(d => d.Severity == DiagnosticSeverity.Error)) // { // Logger.Warn(" - {0}", diagnostic); // } // Logger.Warn("=== END OF SOLUTION ERRORS ==="); // Logger.Warn("\n### ORIGINAL CODE ###\n{0}### END OF CODE ###", syntax.Format(workspace)); // Logger.Warn("\n### REWRITTEN CODE ###\n{0}### END OF CODE ###", rewrittenSyntax.Format(workspace)); // throw new RefactoringException("Rewritten solution contains more compilation errors than the original refactoring"); //} return null; }
private static async Task<Solution> UpdateReferencingDocumentsAsync(Document document, string fullMethodName, IEnumerable<IGrouping<Document, ReferenceLocation>> documentGroups, Solution newSolution, CancellationToken cancellationToken) { var newMemberAccess = (MemberAccessExpressionSyntax)SyntaxFactory.ParseExpression(fullMethodName); newMemberAccess = newMemberAccess.WithExpression( newMemberAccess.Expression.WithAdditionalAnnotations(Simplifier.Annotation)); foreach (var documentGroup in documentGroups) { var referencingDocument = documentGroup.Key; if (referencingDocument.Id.Equals(document.Id)) continue; referencingDocument = newSolution.GetDocument(referencingDocument.Id); var root = await referencingDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var diagnosticNodes = documentGroup.Select(referenceLocation => root.FindNode(referenceLocation.Location.SourceSpan)).ToList(); root = root.TrackNodes(diagnosticNodes); foreach (var diagnosticNode in diagnosticNodes) { var trackedNode = root.GetCurrentNode(diagnosticNode); var memberAccess = trackedNode.FirstAncestorOrSelfOfType<MemberAccessExpressionSyntax>(); if (memberAccess?.Expression == null) continue; if (!trackedNode.Equals(memberAccess.Name)) continue; var newMemberAccessParent = memberAccess.Parent.ReplaceNode(memberAccess, newMemberAccess) .WithAdditionalAnnotations(Formatter.Annotation); root = root.ReplaceNode(memberAccess.Parent, newMemberAccessParent); } newSolution = newSolution.WithDocumentSyntaxRoot(referencingDocument.Id, root); } return newSolution; }