private async Task <ConstructorDeclarationSyntax> DocumentConstructorAsync(CodeFixContext context, ConstructorDeclarationSyntax constructor, CancellationToken cancellationToken) { if (constructor == null) { return(null); } SemanticModel semanticModel = await context.Document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); INamedTypeSymbol apiCallClass = semanticModel.GetDeclaredSymbol(constructor.FirstAncestorOrSelf <ClassDeclarationSyntax>(), cancellationToken); string parameterName = constructor.ParameterList.Parameters[0].Identifier.ValueText; DocumentationCommentTriviaSyntax documentationComment = XmlSyntaxFactory.DocumentationComment( XmlSyntaxFactory.SummaryElement( XmlSyntaxFactory.Text("Initializes a new instance of the "), XmlSyntaxFactory.SeeElement(SyntaxFactory.TypeCref(SyntaxFactory.ParseTypeName(apiCallClass.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)))), XmlSyntaxFactory.Text(" class"), XmlSyntaxFactory.NewLine(), XmlSyntaxFactory.Text("with the behavior provided by another "), XmlSyntaxFactory.SeeElement(SyntaxFactory.TypeCref(SyntaxFactory.ParseTypeName("global::OpenStack.Net.IHttpApiCall<T>"))), XmlSyntaxFactory.Text(" instance.")), XmlSyntaxFactory.NewLine(), XmlSyntaxFactory.ParamElement( parameterName, XmlSyntaxFactory.List( XmlSyntaxFactory.Text("The "), XmlSyntaxFactory.SeeElement(SyntaxFactory.TypeCref(SyntaxFactory.ParseTypeName("global::OpenStack.Net.IHttpApiCall<T>"))), XmlSyntaxFactory.Text(" providing the behavior for the API call."))), XmlSyntaxFactory.NewLine(), XmlSyntaxFactory.ExceptionElement( SyntaxFactory.TypeCref(SyntaxFactory.ParseTypeName("global::System.ArgumentNullException")), XmlSyntaxFactory.List( XmlSyntaxFactory.Text("If "), XmlSyntaxFactory.ParamRefElement(parameterName), XmlSyntaxFactory.Text(" is "), XmlSyntaxFactory.NullKeywordElement(), XmlSyntaxFactory.Text(".")))) .WithAdditionalAnnotations(Simplifier.Annotation); SyntaxTrivia documentationTrivia = SyntaxFactory.Trivia(documentationComment); return(constructor.WithLeadingTrivia(constructor.GetLeadingTrivia().Add(documentationTrivia))); }
private async Task <Document> CreateChangedDocument(CodeFixContext context, PropertyDeclarationSyntax propertyDeclarationSyntax, CancellationToken cancellationToken) { DocumentationCommentTriviaSyntax documentationComment = propertyDeclarationSyntax.GetDocumentationCommentTriviaSyntax(); if (documentationComment == null) { return(context.Document); } XmlElementSyntax summaryElement = (XmlElementSyntax)documentationComment.Content.GetFirstXmlElement("summary"); if (summaryElement == null) { return(context.Document); } SyntaxList <XmlNodeSyntax> summaryContent = summaryElement.Content; XmlNodeSyntax firstContent = summaryContent.FirstOrDefault(IsContentElement); XmlTextSyntax firstText = firstContent as XmlTextSyntax; if (firstText != null) { string firstTextContent = string.Concat(firstText.DescendantTokens()); if (firstTextContent.TrimStart().StartsWith("Gets ", StringComparison.Ordinal)) { // Find the token containing "Gets " SyntaxToken getsToken = default(SyntaxToken); foreach (SyntaxToken textToken in firstText.TextTokens) { if (textToken.IsMissing) { continue; } if (!textToken.Text.TrimStart().StartsWith("Gets ", StringComparison.Ordinal)) { continue; } getsToken = textToken; break; } if (!getsToken.IsMissing) { string text = getsToken.Text; string valueText = getsToken.ValueText; int index = text.IndexOf("Gets "); if (index >= 0) { bool additionalCharacters = index + 5 < text.Length; text = text.Substring(0, index) + (additionalCharacters ? char.ToUpperInvariant(text[index + 5]).ToString() : string.Empty) + text.Substring(index + (additionalCharacters ? (5 + 1) : 5)); } index = valueText.IndexOf("Gets "); if (index >= 0) { valueText = valueText.Remove(index, 5); } SyntaxToken replaced = SyntaxFactory.Token(getsToken.LeadingTrivia, getsToken.Kind(), text, valueText, getsToken.TrailingTrivia); summaryContent = summaryContent.Replace(firstText, firstText.ReplaceToken(getsToken, replaced)); } } } string defaultValueToken = "NullIfNotIncluded"; SemanticModel semanticModel = await context.Document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); IPropertySymbol propertySymbol = semanticModel.GetDeclaredSymbol(propertyDeclarationSyntax); if (propertySymbol != null) { ITypeSymbol propertyType = propertySymbol.Type; if (propertyType.IsImmutableArray()) { defaultValueToken = "DefaultArrayIfNotIncluded"; } } XmlElementSyntax valueElement = XmlSyntaxFactory.MultiLineElement( "value", XmlSyntaxFactory.List( XmlSyntaxFactory.ParaElement(XmlSyntaxFactory.PlaceholderElement(summaryContent.WithoutFirstAndLastNewlines())), XmlSyntaxFactory.NewLine(), XmlSyntaxFactory.TokenElement(defaultValueToken))); XmlNodeSyntax leadingNewLine = XmlSyntaxFactory.NewLine(); // HACK: The formatter isn't working when contents are added to an existing documentation comment, so we // manually apply the indentation from the last line of the existing comment to each new line of the // generated content. SyntaxTrivia exteriorTrivia = GetLastDocumentationCommentExteriorTrivia(documentationComment); if (!exteriorTrivia.Token.IsMissing) { leadingNewLine = leadingNewLine.ReplaceExteriorTrivia(exteriorTrivia); valueElement = valueElement.ReplaceExteriorTrivia(exteriorTrivia); } DocumentationCommentTriviaSyntax newDocumentationComment = documentationComment.WithContent( documentationComment.Content.InsertRange(documentationComment.Content.Count - 1, XmlSyntaxFactory.List( leadingNewLine, valueElement))); SyntaxNode root = await context.Document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); SyntaxNode newRoot = root.ReplaceNode(documentationComment, newDocumentationComment); return(context.Document.WithSyntaxRoot(newRoot)); }
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); }