private void HandlePropertyDeclaration(SyntaxNodeAnalysisContext context)
        {
            PropertyDeclarationSyntax syntax = (PropertyDeclarationSyntax)context.Node;

            if (syntax.Identifier.IsMissing)
            {
                return;
            }

            if (!syntax.Modifiers.Any(SyntaxKind.PublicKeyword) && !syntax.Modifiers.Any(SyntaxKind.ProtectedKeyword))
            {
                return;
            }

            SemanticModel   semanticModel  = context.SemanticModel;
            IPropertySymbol propertySymbol = semanticModel.GetDeclaredSymbol(syntax, context.CancellationToken);

            if (propertySymbol == null)
            {
                return;
            }

            INamedTypeSymbol declaringType = propertySymbol.ContainingType;

            if (!declaringType.IsExtensibleJsonObject())
            {
                return;
            }

            DocumentationCommentTriviaSyntax documentationTriviaSyntax = syntax.GetDocumentationCommentTriviaSyntax();

            if (documentationTriviaSyntax == null)
            {
                return;
            }

            if (documentationTriviaSyntax.Content.GetXmlElements("value").Any())
            {
                return;
            }

            context.ReportDiagnostic(Diagnostic.Create(Descriptor, syntax.Identifier.GetLocation()));
        }
示例#2
0
        private async Task <Document> GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken)
        {
            var documentRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

            SyntaxNode syntax = documentRoot.FindNode(diagnostic.Location.SourceSpan);

            if (syntax == null)
            {
                return(document);
            }

            PropertyDeclarationSyntax propertyDeclarationSyntax = syntax.FirstAncestorOrSelf <PropertyDeclarationSyntax>();

            if (propertyDeclarationSyntax == null)
            {
                return(document);
            }

            DocumentationCommentTriviaSyntax documentationComment = propertyDeclarationSyntax.GetDocumentationCommentTriviaSyntax();

            if (documentationComment == null)
            {
                return(document);
            }

            if (!(documentationComment.Content.GetFirstXmlElement(XmlCommentHelper.SummaryXmlTag) is XmlElementSyntax summaryElement))
            {
                return(document);
            }

            SyntaxList <XmlNodeSyntax> summaryContent = summaryElement.Content;

            if (!this.TryRemoveSummaryPrefix(ref summaryContent, "Gets or sets "))
            {
                if (!this.TryRemoveSummaryPrefix(ref summaryContent, "Gets "))
                {
                    this.TryRemoveSummaryPrefix(ref summaryContent, "Sets ");
                }
            }

            SyntaxList <XmlNodeSyntax> content = summaryContent.WithoutFirstAndLastNewlines();

            if (!string.IsNullOrWhiteSpace(content.ToFullString()))
            {
                // wrap the content in a <placeholder> element for review
                content = XmlSyntaxFactory.List(XmlSyntaxFactory.PlaceholderElement(content));
            }

            string newLineText = document.Project.Solution.Workspace.Options.GetOption(FormattingOptions.NewLine, LanguageNames.CSharp);

            XmlElementSyntax valueElement = XmlSyntaxFactory.MultiLineElement(XmlCommentHelper.ValueXmlTag, newLineText, content);

            XmlNodeSyntax leadingNewLine = XmlSyntaxFactory.NewLine(newLineText);

            // 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);
            }

            // Try to replace an existing <value> element if the comment contains one. Otherwise, add it as a new element.
            SyntaxNode root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

            SyntaxNode    newRoot;
            XmlNodeSyntax existingValue = documentationComment.Content.GetFirstXmlElement(XmlCommentHelper.ValueXmlTag);

            if (existingValue != null)
            {
                newRoot = root.ReplaceNode(existingValue, valueElement);
            }
            else
            {
                DocumentationCommentTriviaSyntax newDocumentationComment = documentationComment.WithContent(
                    documentationComment.Content.InsertRange(
                        documentationComment.Content.Count - 1,
                        XmlSyntaxFactory.List(leadingNewLine, valueElement)));

                newRoot = root.ReplaceNode(documentationComment, newDocumentationComment);
            }

            return(document.WithSyntaxRoot(newRoot));
        }
示例#3
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));
        }