public override SyntaxNode VisitCatchClause(CatchClauseSyntax node) { //// TODO: add a declaration only if there is a throws statement //if (node.Declaration == null) //{ // node = node.WithDeclaration( // CatchDeclaration(IdentifierName(Identifier(TriviaList(), "Exception", TriviaList(Space)))) // .WithIdentifier(Identifier("x")) // .WithCloseParenToken(Token(TriviaList(), SyntaxKind.CloseParenToken, TriviaList(_eolTrivia)))); //} // We have to add an identifier when only throw is used (e.g. { throw; }) if (node.Declaration != null && node.Declaration.Identifier.ValueText == null && node.Block?.DescendantNodes().Any(o => o is ThrowStatementSyntax throwStatement && throwStatement.Expression == null) == true) { node = node.ReplaceNode(node.Declaration, node.Declaration .WithType(node.Declaration.Type.WithTrailingTrivia(Space)) .WithIdentifier(Identifier("x"))); } return(base.VisitCatchClause(node)); }
/// TODO: this might be better as a visitor /// offer to break out the continuation into a success event handler the exception block into an error event handler /// TODO: hooking them up where other bus.AddHandlers are called /// and replace RequestAsync with Send /// with TODOs to verify storage of state and retrieval of state private static async Task <Solution> InvokeMp0102(Diagnostic diagnostic, Solution solution, Document document, CancellationToken cancellationToken) { // the diagnostic.Location here will be the span of the RequestAsync<> GenericNameSyntax object var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var model = await document.GetSemanticModelAsync(cancellationToken); var generator = SyntaxGenerator.GetGenerator(document); MemberAccessExpressionSyntax requestAsyncMemberAccess; SyntaxNode requestAsyncInvocationStatement; StatementSyntax[] requestAsyncAndDependantStatements; string fullContainingNamespaceName; SyntaxToken handleMethodParameterName; var containingMemberAnnotation = new SyntaxAnnotation(Guid.NewGuid().ToString("D")); var tryAnnotation = new SyntaxAnnotation(Guid.NewGuid().ToString("D")); var subjectNodeAnnotation = new SyntaxAnnotation(Guid.NewGuid().ToString("D")); INamedTypeSymbol errorEventType; INamedTypeSymbol eventType; BlockSyntax eventHandlerHandleMethodBody; SyntaxToken catchExceptionIdentifier = default(SyntaxToken); CatchClauseSyntax catchStatement = null; SyntaxToken errorEventHandlerMessageParameterIdentifier; { var subjectNode = root.FindNode(diagnostic.Location.SourceSpan); requestAsyncMemberAccess = subjectNode.Parent.AncestorsAndSelf().OfType <MemberAccessExpressionSyntax>().First(); INamedTypeSymbol messageType; Utilities.GetRequestAsyncInfo(requestAsyncMemberAccess, model, out messageType, out eventType, out errorEventType); // Get span of code around RequestAsync for event handler handleMethodParameterName = Identifier( requestAsyncMemberAccess.GetAssignmentSymbol(model, cancellationToken).Name); document = document.ReplaceNode(subjectNode, subjectNode.WithAdditionalAnnotations(subjectNodeAnnotation), out root, out model); subjectNode = root.GetAnnotatedNodes(subjectNodeAnnotation).Single(); var containingMember = subjectNode.GetContainingMemberDeclaration() as MethodDeclarationSyntax; Debug.Assert(containingMember != null, "containingMember != null"); fullContainingNamespaceName = // ReSharper disable once PossibleNullReferenceException model.GetDeclaredSymbol(containingMember.Parent, cancellationToken).ContainingNamespace.GetFullNamespaceName(); document = document.ReplaceNode(containingMember, containingMember .WithAdditionalAnnotations(containingMemberAnnotation) .WithAdditionalAnnotations(Formatter.Annotation), out root, out model); containingMember = (MethodDeclarationSyntax)root.GetAnnotatedNodes(containingMemberAnnotation).Single(); subjectNode = root.GetAnnotatedNodes(subjectNodeAnnotation).Single(); requestAsyncInvocationStatement = subjectNode.GetAncestorStatement(); var eventHandlerStatementsSpan = containingMember.GetSpanOfAssignmentDependenciesAndDeclarationsInSpan(requestAsyncInvocationStatement.FullSpan, model); // Get catch block, and create error event handler // while ancestor parent is not try or member declaration, if try, check for correct catch. // if none found, throw { var tryCandidate = requestAsyncInvocationStatement.Ancestors().First(e => e is BlockSyntax).Parent; do { var tryStatement = tryCandidate as TryStatementSyntax; if (tryStatement != null) { var exceptionType = typeof(ReceivedErrorEventException <>); catchStatement = tryStatement.GetFirstCatchClauseByType(model, exceptionType, cancellationToken); if (catchStatement != null) { var errorType = model.GetTypeInfo(catchStatement.Declaration.Type, cancellationToken).Type as INamedTypeSymbol; // ReSharper disable once PossibleNullReferenceException if (errorEventType.ToString().Equals(errorType.TypeArguments[0].ToString())) { catchExceptionIdentifier = catchStatement.Declaration.Identifier; break; } catchStatement = null; } } tryCandidate = tryCandidate.Parent; } while (tryCandidate != null && !(tryCandidate is MemberDeclarationSyntax)); if (catchStatement == null) { throw new InvalidOperationException(); } errorEventHandlerMessageParameterIdentifier = GenerateUniqueParameterIdentifierForScope(errorEventType, model, catchStatement.Block); catchStatement = catchStatement.ReplaceNodes( catchStatement.Block.DescendantNodes().Where(e => e is ExpressionSyntax), (a, b) => Simplifier.Expand(a, model, document.Project.Solution.Workspace)); var firstStatement = catchStatement.Block.Statements.FirstOrDefault(); if (firstStatement != null) { catchStatement = catchStatement.ReplaceNode(firstStatement, firstStatement.WithLeadingTrivia( Comment("// TODO: Load message information from a repository by CorrelationId"), LineFeed)); } else { catchStatement = catchStatement.WithBlock(catchStatement.Block.WithOpenBraceToken( Token(SyntaxKind.CloseBraceToken) .WithLeadingTrivia(Comment("// TODO: Load message information from a repository by CorrelationId"), LineFeed))); } foreach (var statement in catchStatement.Block.DescendantNodes(_ => true) .OfType <MemberAccessExpressionSyntax>() .Where(a => { var i = a.Expression as IdentifierNameSyntax; return(i != null && i.Identifier.ValueText == catchExceptionIdentifier.ValueText); }) .ToArray()) { catchStatement = catchStatement.ReplaceNode(statement, IdentifierName(errorEventHandlerMessageParameterIdentifier)); } document = document.ReplaceNode(tryCandidate, tryCandidate.WithAdditionalAnnotations(tryAnnotation), out root, out model); } subjectNode = root.GetAnnotatedNodes(subjectNodeAnnotation).Single(); requestAsyncInvocationStatement = subjectNode.GetAncestorStatement(); requestAsyncAndDependantStatements = root.DescendantNodes() .OfType <StatementSyntax>() .Where(x => eventHandlerStatementsSpan.Contains(x.Span) && x != requestAsyncInvocationStatement) .ToArray(); // expand in original document and add to block eventHandlerHandleMethodBody = Block(List(requestAsyncAndDependantStatements .Select(e => Simplifier.Expand(e, model, document.Project.Solution.Workspace)) .ToArray())); eventHandlerHandleMethodBody = eventHandlerHandleMethodBody.ReplaceNode(eventHandlerHandleMethodBody.Statements.First(), eventHandlerHandleMethodBody.Statements.First().WithLeadingTrivia( Comment("// TODO: Load message information from a repository by CorrelationId"), LineFeed)); } var options = document.Project.Solution.Workspace.Options; var namespaceIdentifierName = IdentifierName(fullContainingNamespaceName); // start of modifications ClassDeclarationSyntax eventHandlerDeclaration; { #region create event handler declaration { eventHandlerDeclaration = Utilities.MessageHandlerDeclaration( eventType, generator, eventHandlerHandleMethodBody, handleMethodParameterName); var ns = (NamespaceDeclarationSyntax)generator .NamespaceDeclaration(namespaceIdentifierName) .WithAdditionalAnnotations(Formatter.Annotation); // create event handler document var filename = eventHandlerDeclaration.Identifier.ValueText + ".cs"; // not thrilled about using text here, but some sort of disconnect between documents when // we get to AddImports and Reduce otherwise. var eventHandlerDocument = document.Project.AddDocument(filename, ns.AddMembers(eventHandlerDeclaration).NormalizeWhitespace().ToString()); eventHandlerDocument = await ImportAdder.AddImportsAsync(eventHandlerDocument, options, cancellationToken); eventHandlerDocument = await Simplifier.ReduceAsync(eventHandlerDocument, options, cancellationToken); eventHandlerDocument = await Formatter.FormatAsync(eventHandlerDocument, options, cancellationToken); solution = eventHandlerDocument.Project.Solution.WithDocumentText(eventHandlerDocument.Id, await eventHandlerDocument.GetTextAsync(cancellationToken)); document = solution.GetDocument(document.Id); root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); model = await document.GetSemanticModelAsync(cancellationToken); } #endregion create event handler declaration // replace the call to RequestAsync and dependant statements with call to Sendr root = root.ReplaceNodes(requestAsyncAndDependantStatements, ExpressionStatement( InvocationExpression( MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, requestAsyncMemberAccess.Expression, // "bus" IdentifierName(nameof(BusExtensions.Send)) .WithAdditionalAnnotations(subjectNodeAnnotation))) .WithArgumentList(((InvocationExpressionSyntax)requestAsyncMemberAccess.Parent).ArgumentList)) .WithLeadingTrivia( Comment("// TODO: store information about the message with CorrelationId for loading in handlers"), CarriageReturnLineFeed)); // remove async and change return type var containingMember = (MethodDeclarationSyntax)root.GetAnnotatedNodes(containingMemberAnnotation).Single(); root = root.ReplaceNode(containingMember, containingMember.WithoutAsync() .WithAdditionalAnnotations(Formatter.Annotation)); // remove try/catch var @try = root.DescendantNodes(_ => true).Single(e => e.HasAnnotation(tryAnnotation)); root = root.RemoveNode(@try, SyntaxRemoveOptions.KeepExteriorTrivia); document = document.WithSyntaxRoot(root); } // error event handler class: ClassDeclarationSyntax errorEventHandlerDeclaration; { errorEventHandlerDeclaration = Utilities.MessageHandlerDeclaration( errorEventType, generator, catchStatement.Block, errorEventHandlerMessageParameterIdentifier); var ns = (NamespaceDeclarationSyntax)generator .NamespaceDeclaration(namespaceIdentifierName) .WithAdditionalAnnotations(Formatter.Annotation); // create new document var errorEventHandlerDocument = document.Project.AddDocument(errorEventHandlerDeclaration.Identifier.ValueText + ".cs", ns.AddMembers(errorEventHandlerDeclaration).NormalizeWhitespace().ToString()); document = errorEventHandlerDocument.Project.GetDocument(document.Id); errorEventHandlerDocument = await ImportAdder.AddImportsAsync(errorEventHandlerDocument, options, cancellationToken); errorEventHandlerDocument = await Simplifier.ReduceAsync(errorEventHandlerDocument, options, cancellationToken); errorEventHandlerDocument = await Formatter.FormatAsync(errorEventHandlerDocument, cancellationToken : cancellationToken); solution = errorEventHandlerDocument.Project.Solution.WithDocumentText(errorEventHandlerDocument.Id, await errorEventHandlerDocument.GetTextAsync(cancellationToken)); } { document = solution.GetDocument(document.Id); model = await document.GetSemanticModelAsync(cancellationToken); root = await document .GetSyntaxRootAsync(cancellationToken) .ConfigureAwait(false); // go looking for a reference to c'tor of a IBus type. var busSend = root.GetAnnotatedNodes(subjectNodeAnnotation).Single(); requestAsyncMemberAccess = busSend.Parent.AncestorsAndSelf() .OfType <MemberAccessExpressionSyntax>() .First(); var busSymbol = model.GetTypeInfo(requestAsyncMemberAccess.Expression).Type as INamedTypeSymbol; Debug.Assert(busSymbol != null, "busSymbol != null"); IMethodSymbol ctorSymbol; // ReSharper disable once PossibleNullReferenceException if (busSymbol.TypeKind == TypeKind.Interface) { var busImplementations = await SymbolFinder.FindImplementationsAsync(busSymbol, solution, cancellationToken : cancellationToken); foreach (INamedTypeSymbol impl in busImplementations.OfType <INamedTypeSymbol>()) { // only implementations with public constructors ctorSymbol = impl.Constructors.SingleOrDefault(e => !e.IsStatic && e.Parameters.Length == 0); if (ctorSymbol != null) { busSymbol = impl; break; } } } ctorSymbol = busSymbol.Constructors.SingleOrDefault(e => !e.IsStatic && e.Parameters.Length == 0); var handlerSymbol = busSymbol.GetMembers(nameof(IBus.AddHandler)).Single(); // ReSharper disable once PossibleUnintendedReferenceComparison if (handlerSymbol != default(ISymbol)) { var references = (await SymbolFinder.FindReferencesAsync(handlerSymbol, solution, cancellationToken)).ToArray(); if (references.Any(e => e.Locations.Any())) { // TODO: add AddHandlers at this location var definition = references .GroupBy(e => e.Definition) .OrderByDescending(g => g.Count()) .First(); } else { // no add handlers at the moment, let's just find where it was constructed. var locations = (await SymbolFinder.FindReferencesAsync(ctorSymbol, solution, cancellationToken)) .SelectMany(e => e.Locations).ToArray(); if (locations.Length != 0) { var referencedLocation = locations.First(); { var referencedLocationDocument = referencedLocation.Document; var referencedRoot = await referencedLocationDocument.GetSyntaxRootAsync(cancellationToken); var node = referencedRoot.FindNode(referencedLocation.Location.SourceSpan); var statement = node.GetAncestorStatement(); var busNode = statement.GetAssignmentToken().WithLeadingTrivia().WithTrailingTrivia(); var busNodeName = IdentifierName(busNode); if (busNodeName != null) { var errorEventHandlerName = IdentifierName(errorEventHandlerDeclaration.Identifier); referencedLocationDocument = InsertAddHandlerCall( referencedLocationDocument, referencedLocation, busNodeName, errorEventHandlerName); var eventHandlerName = IdentifierName(eventHandlerDeclaration.Identifier); referencedLocationDocument = InsertAddHandlerCall( referencedLocationDocument, referencedLocation, busNodeName, eventHandlerName); solution = referencedLocationDocument.Project.Solution; } } } } } return(solution); } }