Exemple #1
0
        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)));
        }
Exemple #2
0
        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));
        }
Exemple #3
0
        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);
        }