private async Task <Document> WithAttributeAsync([NotNull] INamedTypeSymbol attribute, [NotNull] Document document, [NotNull] SyntaxNode syntaxNode, CancellationToken cancellationToken) { DocumentEditor editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); // Add NotNull/CanBeNull/ItemNotNull/ItemCanBeNull attribute. SyntaxNode attributeSyntax = editor.Generator.Attribute(editor.Generator.TypeExpression(attribute)) .WithAdditionalAnnotations(Simplifier.Annotation, Formatter.Annotation, NamespaceImportAnnotation); editor.AddAttribute(syntaxNode, attributeSyntax); Document documentWithAttribute = editor.GetChangedDocument(); // Add namespace import. Document documentWithImport = await ImportAdder.AddImportsAsync(documentWithAttribute, NamespaceImportAnnotation, null, cancellationToken).ConfigureAwait(false); // Simplify and reformat all annotated nodes. Document simplified = await SimplifyAsync(documentWithImport, cancellationToken).ConfigureAwait(false); SyntaxNode formatted = await FormatAsync(simplified, cancellationToken).ConfigureAwait(false); return(simplified.WithSyntaxRoot(formatted)); }
private async Task TestAsync(string initialText, string importsAddedText, string simplifiedText, OptionSet options = null) { var doc = GetDocument(initialText); options = options ?? doc.Options; var imported = await ImportAdder.AddImportsAsync(doc, options); if (importsAddedText != null) { var formatted = await Formatter.FormatAsync(imported, SyntaxAnnotation.ElasticAnnotation, options); var actualText = (await formatted.GetTextAsync()).ToString(); Assert.Equal(importsAddedText, actualText); } if (simplifiedText != null) { var reduced = await Simplifier.ReduceAsync(imported, options); var formatted = await Formatter.FormatAsync(reduced, SyntaxAnnotation.ElasticAnnotation, options); var actualText = (await formatted.GetTextAsync()).ToString(); Assert.Equal(simplifiedText, actualText); } }
internal static async Task <Document> ConvertProperty(Document document, VariableDeclaratorSyntax declarator, CancellationToken cancellationToken) { var editor = await DocumentEditor.CreateAsync(document, cancellationToken); var semanticModel = editor.SemanticModel; var fieldSymbol = semanticModel.GetDeclaredSymbol(declarator); var initializer = (InvocationExpressionSyntax)declarator.Initializer.Value; var originalRegisterMethodName = ((MemberAccessExpressionSyntax)initializer.Expression).Name.ToString(); var avaloniaInvocation = GenerateBasicInvocation(editor.Generator, fieldSymbol, initializer, originalRegisterMethodName); var originalStaticConstructor = fieldSymbol.ContainingType.StaticConstructors.IsEmpty || fieldSymbol.ContainingType.StaticConstructors[0].DeclaringSyntaxReferences.IsEmpty ? null : (ConstructorDeclarationSyntax)await fieldSymbol.ContainingType.StaticConstructors[0].DeclaringSyntaxReferences[0].GetSyntaxAsync(cancellationToken); var staticConstructor = originalStaticConstructor == null?GenerateEmptyStaticConstructor(editor.Generator, fieldSymbol.ContainingType) : originalStaticConstructor; ExpressionSyntax coerceCallbackSyntax = null; ExpressionSyntax validationCallbackSyntax = null; var changeList = new ConverterProcessingResult(); if (initializer.ArgumentList.Arguments.Count > 3) // Have to break down metadata constructor { var results = await ProcessMetadata(editor.Generator, semanticModel, fieldSymbol, cancellationToken); changeList = changeList.AppendResult(results.Item1); coerceCallbackSyntax = results.Item2; } if (initializer.ArgumentList.Arguments.Count > 4) { validationCallbackSyntax = initializer.ArgumentList.Arguments[4].Expression; } if (coerceCallbackSyntax != null || validationCallbackSyntax != null) { var combinedCoerceValidateExpression = CreateCombinedCoerceValidate(editor.Generator, semanticModel, coerceCallbackSyntax, validationCallbackSyntax); changeList = changeList.AddArguments((ArgumentSyntax)editor.Generator.Argument("validate", RefKind.None, combinedCoerceValidateExpression)); } avaloniaInvocation = avaloniaInvocation.AddArgumentListArguments(changeList.AdditionalInvocationArguments.ToArray()) .WithAdditionalAnnotations(Formatter.Annotation); ReplaceMember(editor, semanticModel, declarator, avaloniaInvocation); staticConstructor = staticConstructor.AddBodyStatements(changeList.AdditionalStaticConstructorStatements.ToArray()) .WithAdditionalAnnotations(Formatter.Annotation); if (originalStaticConstructor != null) { if (originalStaticConstructor.Body.Statements.Count < staticConstructor.Body.Statements.Count) { editor.ReplaceNode(originalStaticConstructor, staticConstructor); } } else if (staticConstructor.Body.Statements.Count > 0) { editor.AddMember(await fieldSymbol.ContainingType.DeclaringSyntaxReferences[0].GetSyntaxAsync(cancellationToken), staticConstructor); } return(await ImportAdder.AddImportsAsync(editor.GetChangedDocument(), Annotations.NamespaceImportAnnotation, cancellationToken : cancellationToken)); }
private static async Task <Document> AppendAfterAssignmentAsync(CodeFixContext context, StatementSyntax relativeTo, SyntaxNode presentArgument, CancellationToken cancellationToken) { DocumentEditor editor = await DocumentEditor.CreateAsync(context.Document, cancellationToken); Document document = context.Document; SyntaxNode root = await document.GetSyntaxRootAsync(cancellationToken); SyntaxNode assumesStatement = CreateAssumesPresentStatement(editor.Generator, presentArgument); root = root.InsertNodesAfter(relativeTo, new SyntaxNode[] { assumesStatement }); document = document.WithSyntaxRoot(root); document = await ImportAdder.AddImportsAsync(document, Simplifier.Annotation, cancellationToken : cancellationToken); return(document); }
/// <summary> /// Replaces a syntax node with a new fully qualified name or member accession expression from a given string. /// </summary> /// <param name="document">The document to update.</param> /// <param name="node">The syntax node to be replaced.</param> /// <param name="newIdentifier">A string representation of the new identifier (name of member access expression) to be used in the document.</param> /// <param name="cancellationToken">A cancellation token.</param> /// <returns>An updated document with the given node replaced with the new identifier.</returns> private static async Task <Document> UpdateIdentifierTypeAsync(Document document, SyntaxNode node, string newIdentifier, CancellationToken cancellationToken) { var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); // Create new identifier var updatedNode = GetUpdatedNode(node, editor.Generator, newIdentifier) .WithAdditionalAnnotations(Simplifier.Annotation, Simplifier.AddImportsAnnotation) .WithTriviaFrom(node); editor.ReplaceNode(node, updatedNode); var updatedDocument = editor.GetChangedDocument(); // Add using declaration if needed updatedDocument = await ImportAdder.AddImportsAsync(updatedDocument, Simplifier.AddImportsAnnotation, null, cancellationToken).ConfigureAwait(false); // Simplify the call, if possible updatedDocument = await Simplifier.ReduceAsync(updatedDocument, Simplifier.Annotation, null, cancellationToken).ConfigureAwait(false); return(updatedDocument); }
private static async Task <Document> UpdateMemberAccessAsync(Document document, SyntaxNode node, CancellationToken cancellationToken) { var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); if (editor.OriginalRoot is not CompilationUnitSyntax documentRoot) { return(document); } // Replace the member access expression with Debugger.IsAttached var newExpression = SyntaxFactory.ParseExpression(DebuggerIsAttachedSyntax) .WithTriviaFrom(node) .WithAdditionalAnnotations(Simplifier.Annotation, Simplifier.AddImportsAnnotation); documentRoot = documentRoot.ReplaceNode(node, newExpression) !; editor.ReplaceNode(editor.OriginalRoot, documentRoot); var updatedDocument = editor.GetChangedDocument(); // Add using declaration if needed updatedDocument = await ImportAdder.AddImportsAsync(updatedDocument, Simplifier.AddImportsAnnotation, null, cancellationToken).ConfigureAwait(false); return(updatedDocument); }
private void Test(string initialText, string importsAddedText, string simplifiedText, OptionSet options = null) { var doc = GetDocument(initialText); options = options ?? doc.Project.Solution.Workspace.Options; var imported = ImportAdder.AddImportsAsync(doc, options).Result; if (importsAddedText != null) { var formatted = Formatter.FormatAsync(imported, SyntaxAnnotation.ElasticAnnotation, options).Result; var actualText = formatted.GetTextAsync().Result.ToString(); Assert.Equal(importsAddedText, actualText); } if (simplifiedText != null) { var reduced = Simplifier.ReduceAsync(imported, options).Result; var formatted = Formatter.FormatAsync(reduced, SyntaxAnnotation.ElasticAnnotation, options).Result; var actualText = formatted.GetTextAsync().Result.ToString(); Assert.Equal(simplifiedText, actualText); } }
private async Task <Document> ConvertToAsyncPackageAsync(CodeFixContext context, Diagnostic diagnostic, CancellationToken cancellationToken) { var semanticModel = await context.Document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); var compilation = await context.Document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); var root = await context.Document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var baseTypeSyntax = root.FindNode(diagnostic.Location.SourceSpan).FirstAncestorOrSelf <BaseTypeSyntax>(); var classDeclarationSyntax = baseTypeSyntax.FirstAncestorOrSelf <ClassDeclarationSyntax>(); var initializeMethodSyntax = classDeclarationSyntax.DescendantNodes() .OfType <MethodDeclarationSyntax>() .FirstOrDefault(method => method.Modifiers.Any(modifier => modifier.IsKind(SyntaxKind.OverrideKeyword)) && method.Identifier.Text == Types.Package.Initialize); var baseInitializeInvocationSyntax = initializeMethodSyntax?.Body?.DescendantNodes() .OfType <InvocationExpressionSyntax>() .FirstOrDefault(ies => ies.Expression is MemberAccessExpressionSyntax memberAccess && memberAccess.Name?.Identifier.Text == Types.Package.Initialize && memberAccess.Expression is BaseExpressionSyntax); var getServiceInvocationsSyntax = new List <InvocationExpressionSyntax>(); AttributeSyntax packageRegistrationSyntax = null; { var userClassSymbol = semanticModel.GetDeclaredSymbol(classDeclarationSyntax, context.CancellationToken); var packageRegistrationType = compilation.GetTypeByMetadataName(Types.PackageRegistrationAttribute.FullName); var packageRegistrationInstance = userClassSymbol?.GetAttributes().FirstOrDefault(a => a.AttributeClass == packageRegistrationType); if (packageRegistrationInstance?.ApplicationSyntaxReference != null) { packageRegistrationSyntax = (AttributeSyntax)await packageRegistrationInstance.ApplicationSyntaxReference.GetSyntaxAsync(cancellationToken).ConfigureAwait(false); } } if (initializeMethodSyntax != null) { getServiceInvocationsSyntax.AddRange( from invocation in initializeMethodSyntax.DescendantNodes().OfType <InvocationExpressionSyntax>() let memberBinding = invocation.Expression as MemberAccessExpressionSyntax let identifierName = invocation.Expression as IdentifierNameSyntax where identifierName?.Identifier.Text == Types.Package.GetService || (memberBinding.Name.Identifier.Text == Types.Package.GetService && memberBinding.Expression.IsKind(SyntaxKind.ThisExpression)) select invocation); } // Make it easier to track nodes across changes. var nodesToTrack = new List <SyntaxNode> { baseTypeSyntax, initializeMethodSyntax, baseInitializeInvocationSyntax, packageRegistrationSyntax, }; nodesToTrack.AddRange(getServiceInvocationsSyntax); nodesToTrack.RemoveAll(n => n == null); var updatedRoot = root.TrackNodes(nodesToTrack); // Replace the Package base type with AsyncPackage baseTypeSyntax = updatedRoot.GetCurrentNode(baseTypeSyntax); var asyncPackageBaseTypeSyntax = SyntaxFactory.SimpleBaseType(Types.AsyncPackage.TypeSyntax.WithAdditionalAnnotations(Simplifier.Annotation)) .WithLeadingTrivia(baseTypeSyntax.GetLeadingTrivia()) .WithTrailingTrivia(baseTypeSyntax.GetTrailingTrivia()); updatedRoot = updatedRoot.ReplaceNode(baseTypeSyntax, asyncPackageBaseTypeSyntax); // Update the PackageRegistration attribute if (packageRegistrationSyntax != null) { var trueExpression = SyntaxFactory.LiteralExpression(SyntaxKind.TrueLiteralExpression); packageRegistrationSyntax = updatedRoot.GetCurrentNode(packageRegistrationSyntax); var allowsBackgroundLoadingSyntax = packageRegistrationSyntax.ArgumentList.Arguments.FirstOrDefault(a => a.NameEquals?.Name?.Identifier.Text == Types.PackageRegistrationAttribute.AllowsBackgroundLoading); if (allowsBackgroundLoadingSyntax != null) { updatedRoot = updatedRoot.ReplaceNode( allowsBackgroundLoadingSyntax, allowsBackgroundLoadingSyntax.WithExpression(trueExpression)); } else { updatedRoot = updatedRoot.ReplaceNode( packageRegistrationSyntax, packageRegistrationSyntax.AddArgumentListArguments( SyntaxFactory.AttributeArgument(trueExpression).WithNameEquals(SyntaxFactory.NameEquals(Types.PackageRegistrationAttribute.AllowsBackgroundLoading)))); } } // Find the Initialize override, if present, and update it to InitializeAsync if (initializeMethodSyntax != null) { var cancellationTokenLocalVarName = SyntaxFactory.IdentifierName("cancellationToken"); var progressLocalVarName = SyntaxFactory.IdentifierName("progress"); initializeMethodSyntax = updatedRoot.GetCurrentNode(initializeMethodSyntax); var newBody = initializeMethodSyntax.Body; var leadingTrivia = SyntaxFactory.TriviaList( SyntaxFactory.Comment(@"// When initialized asynchronously, we *may* be on a background thread at this point."), SyntaxFactory.CarriageReturnLineFeed, SyntaxFactory.Comment(@"// Do any initialization that requires the UI thread after switching to the UI thread."), SyntaxFactory.CarriageReturnLineFeed, SyntaxFactory.Comment(@"// Otherwise, remove the switch to the UI thread if you don't need it."), SyntaxFactory.CarriageReturnLineFeed); var switchToMainThreadStatement = SyntaxFactory.ExpressionStatement( SyntaxFactory.AwaitExpression( SyntaxFactory.InvocationExpression( SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, SyntaxFactory.ThisExpression(), SyntaxFactory.IdentifierName(Types.ThreadHelper.JoinableTaskFactory)), SyntaxFactory.IdentifierName(Types.JoinableTaskFactory.SwitchToMainThreadAsync))) .AddArgumentListArguments(SyntaxFactory.Argument(cancellationTokenLocalVarName)))) .WithLeadingTrivia(leadingTrivia) .WithTrailingTrivia(SyntaxFactory.CarriageReturnLineFeed); if (baseInitializeInvocationSyntax != null) { var baseInitializeAsyncInvocationBookmark = new SyntaxAnnotation(); var baseInitializeAsyncInvocationSyntax = SyntaxFactory.AwaitExpression( baseInitializeInvocationSyntax .WithLeadingTrivia() .WithExpression( SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, SyntaxFactory.BaseExpression(), SyntaxFactory.IdentifierName(Types.AsyncPackage.InitializeAsync))) .AddArgumentListArguments( SyntaxFactory.Argument(cancellationTokenLocalVarName), SyntaxFactory.Argument(progressLocalVarName))) .WithLeadingTrivia(baseInitializeInvocationSyntax.GetLeadingTrivia()) .WithAdditionalAnnotations(baseInitializeAsyncInvocationBookmark); newBody = newBody.ReplaceNode(initializeMethodSyntax.GetCurrentNode(baseInitializeInvocationSyntax), baseInitializeAsyncInvocationSyntax); var baseInvocationStatement = newBody.GetAnnotatedNodes(baseInitializeAsyncInvocationBookmark).First().FirstAncestorOrSelf <StatementSyntax>(); newBody = newBody.InsertNodesAfter( baseInvocationStatement, new[] { switchToMainThreadStatement.WithLeadingTrivia(switchToMainThreadStatement.GetLeadingTrivia().Insert(0, SyntaxFactory.LineFeed)) }); } else { newBody = newBody.WithStatements( newBody.Statements.Insert(0, switchToMainThreadStatement)); } var initializeAsyncMethodSyntax = initializeMethodSyntax .WithIdentifier(SyntaxFactory.Identifier(Types.AsyncPackage.InitializeAsync)) .WithReturnType(Types.Task.TypeSyntax.WithAdditionalAnnotations(Simplifier.Annotation)) .AddModifiers(SyntaxFactory.Token(SyntaxKind.AsyncKeyword)) .AddParameterListParameters( SyntaxFactory.Parameter(cancellationTokenLocalVarName.Identifier).WithType(Types.CancellationToken.TypeSyntax.WithAdditionalAnnotations(Simplifier.Annotation)), SyntaxFactory.Parameter(progressLocalVarName.Identifier).WithType(Types.IProgress.TypeSyntaxOf(Types.ServiceProgressData.TypeSyntax).WithAdditionalAnnotations(Simplifier.Annotation))) .WithBody(newBody); updatedRoot = updatedRoot.ReplaceNode(initializeMethodSyntax, initializeAsyncMethodSyntax); // Replace GetService calls with GetServiceAsync getServiceInvocationsSyntax = updatedRoot.GetCurrentNodes <InvocationExpressionSyntax>(getServiceInvocationsSyntax).ToList(); updatedRoot = updatedRoot.ReplaceNodes( getServiceInvocationsSyntax, (orig, node) => { var invocation = node; if (invocation.Expression is IdentifierNameSyntax methodName) { invocation = invocation.WithExpression(SyntaxFactory.IdentifierName(Types.AsyncPackage.GetServiceAsync)); } else if (invocation.Expression is MemberAccessExpressionSyntax memberAccess) { invocation = invocation.WithExpression( memberAccess.WithName(SyntaxFactory.IdentifierName(Types.AsyncPackage.GetServiceAsync))); } return(SyntaxFactory.ParenthesizedExpression(SyntaxFactory.AwaitExpression(invocation)) .WithAdditionalAnnotations(Simplifier.Annotation)); }); updatedRoot = await Utils.AddUsingTaskEqualsDirectiveAsync(updatedRoot, cancellationToken); } var newDocument = context.Document.WithSyntaxRoot(updatedRoot); newDocument = await ImportAdder.AddImportsAsync(newDocument, Simplifier.Annotation, cancellationToken : cancellationToken); return(newDocument); }
private async Task <Document> ConvertType(Document document, SyntaxNode type, CancellationToken c) { var editor = await DocumentEditor.CreateAsync(document, c); if (type is BaseTypeSyntax) { type = ((BaseTypeSyntax)type).Type; } var originalTypeSymbol = editor.SemanticModel.GetTypeInfo(type, c).Type; SyntaxNode newTypeSyntax = null; var avaloniaNamespace = editor.Generator.IdentifierName("Avalonia") .WithAdditionalAnnotations(Annotations.NamespaceImportAnnotation); var avaloniaControlsNamespace = editor.Generator.MemberAccessExpression(avaloniaNamespace, "Controls") .WithAdditionalAnnotations(Annotations.NamespaceImportAnnotation); var avaloniaControlsPrimitivesNamespace = editor.Generator.MemberAccessExpression(avaloniaControlsNamespace, "Primitives") .WithAdditionalAnnotations(Annotations.NamespaceImportAnnotation); var avaloniaControlsShapesNamespace = editor.Generator.MemberAccessExpression(avaloniaControlsNamespace, "Shapes") .WithAdditionalAnnotations(Annotations.NamespaceImportAnnotation); var avaloniaMediaNamespace = editor.Generator.MemberAccessExpression(avaloniaNamespace, "Media") .WithAdditionalAnnotations(Annotations.NamespaceImportAnnotation); if (originalTypeSymbol.ToDisplayString() == "System.Windows.DependencyObject") { newTypeSyntax = editor.Generator.MemberAccessExpression(avaloniaNamespace, "AvaloniaObject"); } else if (originalTypeSymbol.ToDisplayString() == "System.Windows.DependencyProperty") { newTypeSyntax = editor.Generator.MemberAccessExpression(avaloniaNamespace, "AvaloniaProperty"); } else if (originalTypeSymbol.ToDisplayString() == "System.Windows.UIElement" || originalTypeSymbol.ToDisplayString() == "System.Windows.FrameworkElement") { newTypeSyntax = editor.Generator.MemberAccessExpression(avaloniaControlsNamespace, "Control"); } else if (originalTypeSymbol.ToDisplayString() == "System.Windows.Controls.Control") { newTypeSyntax = editor.Generator.MemberAccessExpression(avaloniaControlsPrimitivesNamespace, "TemplatedControl"); } else if (originalTypeSymbol.ToDisplayString() == "System.Windows.Window") { newTypeSyntax = editor.Generator.MemberAccessExpression(avaloniaControlsNamespace, "Window"); } else if (originalTypeSymbol.ContainingNamespace.ToDisplayString() == "System.Windows.Controls") { newTypeSyntax = editor.Generator.MemberAccessExpression(avaloniaControlsNamespace, originalTypeSymbol.Name); } else if (originalTypeSymbol.ContainingNamespace.ToDisplayString() == "System.Windows.Media") { newTypeSyntax = editor.Generator.MemberAccessExpression(avaloniaMediaNamespace, originalTypeSymbol.Name); } else if (originalTypeSymbol.ContainingNamespace.ToDisplayString() == "System.Windows.Shapes") { newTypeSyntax = editor.Generator.MemberAccessExpression(avaloniaControlsShapesNamespace, originalTypeSymbol.Name); } if (newTypeSyntax != null) { var newSymbol = editor.SemanticModel.GetSpeculativeTypeInfo(0, newTypeSyntax, SpeculativeBindingOption.BindAsExpression).Type; if (newSymbol != null) { editor.ReplaceNode(type, editor.Generator.TypeExpression(newSymbol).WithAdditionalAnnotations(Formatter.Annotation)); } } return(await ImportAdder.AddImportsAsync(editor.GetChangedDocument(), Annotations.NamespaceImportAnnotation, cancellationToken : c)); }
public async Task TestDoNotAddDuplicateImportIfNamespaceIsDefinedInSourceAndExternalAssembly() { var externalCode = @"namespace N.M { public class A : System.Attribute { } }"; var code = @"using System; using N.M; class C { public void M1(String p1) { } public void M2([A] String p2) { } }"; var otherAssemblyReference = GetInMemoryAssemblyReferenceForCode(externalCode); var project = _emptyProject .AddMetadataReferences(new[] { otherAssemblyReference }) .WithCompilationOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); project = project.AddDocument("duplicate.cs", externalCode).Project; var document = project.AddDocument("test.cs", code); var options = document.Project.Solution.Workspace.Options; var compilation = await document.Project.Solution.GetCompilationAsync(document.Project, CancellationToken.None); ImmutableArray <Diagnostic> compilerDiagnostics = compilation.GetDiagnostics(CancellationToken.None); Assert.Empty(compilerDiagnostics.Where(d => d.Severity == DiagnosticSeverity.Error)); var attribute = compilation.GetTypeByMetadataName("N.M.A"); var syntaxRoot = await document.GetSyntaxRootAsync(CancellationToken.None).ConfigureAwait(false); SyntaxNode p1SyntaxNode = syntaxRoot.DescendantNodes().OfType <ParameterSyntax>().FirstOrDefault(); // Add N.M.A attribute to p1. var editor = await DocumentEditor.CreateAsync(document, CancellationToken.None).ConfigureAwait(false); SyntaxNode attributeSyntax = editor.Generator.Attribute(editor.Generator.TypeExpression(attribute)); editor.AddAttribute(p1SyntaxNode, attributeSyntax); Document documentWithAttribute = editor.GetChangedDocument(); // Add namespace import. Document imported = await ImportAdder.AddImportsAsync(documentWithAttribute, null, CancellationToken.None).ConfigureAwait(false); var formatted = await Formatter.FormatAsync(imported, options); var actualText = (await formatted.GetTextAsync()).ToString(); Assert.Equal(actualText, @"using System; using N.M; class C { public void M1([global::N.M.A] String p1) { } public void M2([A] String p2) { } }"); }
/// 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); } }