private async Task <Document> CreateChangedDocument(CodeFixContext context, ClassDeclarationSyntax classDeclarationSyntax, CancellationToken cancellationToken) { string serviceInterfaceName = "IUnknownService"; string serviceExtensionsClassName = "UnknownServiceExtensions"; INamedTypeSymbol serviceInterface = await GetServiceInterfaceAsync(context, classDeclarationSyntax, cancellationToken).ConfigureAwait(false); if (serviceInterface != null) { serviceInterfaceName = serviceInterface.MetadataName; serviceExtensionsClassName = serviceInterfaceName + "Extensions"; if (serviceInterfaceName.StartsWith("I")) { serviceExtensionsClassName = serviceExtensionsClassName.Substring(1); } } string fullServiceName = "Unknown Service"; if (serviceInterface != null) { fullServiceName = ExtractFullServiceName(serviceInterface); } string callName = classDeclarationSyntax.Identifier.ValueText; int apiCallSuffix = callName.IndexOf("ApiCall", StringComparison.Ordinal); if (apiCallSuffix > 0) { callName = callName.Substring(0, apiCallSuffix); } ClassDeclarationSyntax newClassDeclaration = classDeclarationSyntax; ConstructorDeclarationSyntax constructor = await FindApiCallConstructorAsync(context, classDeclarationSyntax, cancellationToken).ConfigureAwait(false); ConstructorDeclarationSyntax newConstructor = await DocumentConstructorAsync(context, constructor, cancellationToken).ConfigureAwait(false); if (newConstructor != null) { newClassDeclaration = newClassDeclaration.ReplaceNode(constructor, newConstructor); } DocumentationCommentTriviaSyntax documentationComment = XmlSyntaxFactory.DocumentationComment( XmlSyntaxFactory.SummaryElement( XmlSyntaxFactory.Text("This class represents an HTTP API call to "), XmlSyntaxFactory.PlaceholderElement(XmlSyntaxFactory.Text(callName)), XmlSyntaxFactory.Text(" with the "), XmlSyntaxFactory.PlaceholderElement(XmlSyntaxFactory.Text(fullServiceName)), XmlSyntaxFactory.Text(".")), XmlSyntaxFactory.NewLine(), XmlSyntaxFactory.SeeAlsoElement(SyntaxFactory.NameMemberCref(SyntaxFactory.ParseName($"{serviceInterfaceName}.Prepare{callName}Async"))), XmlSyntaxFactory.NewLine(), XmlSyntaxFactory.SeeAlsoElement(SyntaxFactory.NameMemberCref(SyntaxFactory.ParseName($"{serviceExtensionsClassName}.{callName}Async"))), XmlSyntaxFactory.NewLine(), XmlSyntaxFactory.ThreadSafetyElement(), XmlSyntaxFactory.NewLine(), XmlSyntaxFactory.PreliminaryElement()) .WithAdditionalAnnotations(Simplifier.Annotation); SyntaxTrivia documentationTrivia = SyntaxFactory.Trivia(documentationComment); newClassDeclaration = newClassDeclaration.WithLeadingTrivia(newClassDeclaration.GetLeadingTrivia().Add(documentationTrivia)); SyntaxNode root = await context.Document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); SyntaxNode newRoot = root.ReplaceNode(classDeclarationSyntax, newClassDeclaration); return(context.Document.WithSyntaxRoot(newRoot)); }
private async Task <Solution> CreateChangedSolution(CodeFixContext context, ClassDeclarationSyntax classDeclarationSyntax, PropertyDeclarationSyntax propertyDeclarationSyntax, CancellationToken cancellationToken) { Solution solution = context.Document.Project.Solution; Solution newSolution = solution; /* * Add the implementation method to the class containing the property. */ AccessorDeclarationSyntax propertyGetter = propertyDeclarationSyntax.AccessorList.Accessors.First(accessor => accessor.Keyword.IsKind(SyntaxKind.GetKeyword)); SyntaxNode root = classDeclarationSyntax.SyntaxTree.GetRoot(cancellationToken); SemanticModel semanticModel = await context.Document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); INamedTypeSymbol declaringType = semanticModel.GetDeclaredSymbol(classDeclarationSyntax); IPropertySymbol property = semanticModel.GetDeclaredSymbol(propertyDeclarationSyntax); SyntaxNode newRoot = root.ReplaceNode( classDeclarationSyntax, classDeclarationSyntax.AddMembers( BuildImplementationMethod(semanticModel, declaringType, property, propertyGetter))); newSolution = newSolution.WithDocumentSyntaxRoot(context.Document.Id, newRoot); /* * Generate the extension method */ MethodDeclarationSyntax extensionMethod = BuildGenericWrapperMethod(declaringType, property); /* * Try to locate an existing extension methods class for the type. */ string extensionClassName = $"{declaringType.Name}Extensions"; INamedTypeSymbol extensionMethodsClass = declaringType.ContainingNamespace.GetTypeMembers(extensionClassName, 0).FirstOrDefault(); if (extensionMethodsClass != null) { // Add the new extension method to the existing class. Location location = extensionMethodsClass.Locations.FirstOrDefault(i => i.IsInSource); if (location == null) { return(solution); } Document extensionsDocument = context.Document.Project.Solution.GetDocument(location.SourceTree); if (extensionsDocument == null) { return(solution); } SyntaxNode extensionsRoot = await extensionsDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); ClassDeclarationSyntax extensionsClass = extensionsRoot.FindNode(location.SourceSpan, getInnermostNodeForTie: true).FirstAncestorOrSelf <ClassDeclarationSyntax>(); if (extensionsClass == null) { return(solution); } SyntaxNode newExtensionsRoot = extensionsRoot.ReplaceNode(extensionsClass, extensionsClass.AddMembers(extensionMethod)); newSolution = newSolution.WithDocumentSyntaxRoot(extensionsDocument.Id, newExtensionsRoot); } else { // Need to add a new class for the extension methods. DocumentationCommentTriviaSyntax documentationComment = XmlSyntaxFactory.DocumentationComment( XmlSyntaxFactory.SummaryElement( XmlSyntaxFactory.Text("This class provides extension methods for the "), XmlSyntaxFactory.SeeElement( SyntaxFactory.TypeCref(SyntaxFactory.ParseTypeName(declaringType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)))), XmlSyntaxFactory.Text(" class.")), XmlSyntaxFactory.NewLine(), XmlSyntaxFactory.ThreadSafetyElement(), XmlSyntaxFactory.NewLine(), XmlSyntaxFactory.PreliminaryElement()); SyntaxNode extensionsClassRoot = SyntaxFactory.CompilationUnit().AddMembers( SyntaxFactory.NamespaceDeclaration(SyntaxFactory.ParseName(declaringType.ContainingNamespace.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat))) .AddUsings(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("global::System"))) .AddMembers( SyntaxFactory.ClassDeclaration(extensionClassName) .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword)) .AddMembers(extensionMethod) .WithLeadingTrivia(SyntaxFactory.TriviaList(SyntaxFactory.Trivia(documentationComment))))) .WithAdditionalAnnotations(Simplifier.Annotation); DocumentId extensionsDocumentId = DocumentId.CreateNewId(context.Document.Project.Id); newSolution = newSolution .AddDocument(extensionsDocumentId, $"{extensionClassName}.cs", extensionsClassRoot, context.Document.Folders); // Make sure to also add the file to linked projects. foreach (var linkedDocumentId in context.Document.GetLinkedDocumentIds()) { DocumentId linkedExtensionDocumentId = DocumentId.CreateNewId(linkedDocumentId.ProjectId); newSolution = newSolution .AddDocument(linkedExtensionDocumentId, $"{extensionClassName}.cs", extensionsClassRoot, context.Document.Folders); } } return(newSolution); }