/// <inheritdoc/> protected override void HandleXmlElement(SyntaxNodeAnalysisContext context, XmlNodeSyntax syntax, Location diagnosticLocation) { if (syntax != null && XmlCommentHelper.IsConsideredEmpty(syntax)) { context.ReportDiagnostic(Diagnostic.Create(Descriptor, diagnosticLocation)); } }
/// <inheritdoc/> protected override void HandleXmlElement(SyntaxNodeAnalysisContext context, DocumentationCommentTriviaSyntax documentation, XmlNodeSyntax syntax, XElement completeDocumentation, Location[] diagnosticLocations) { if (syntax == null) { return; } if (completeDocumentation != null) { XElement summaryNode = completeDocumentation.Nodes().OfType<XElement>().FirstOrDefault(element => element.Name == XmlCommentHelper.SummaryXmlTag); if (summaryNode == null) { // Handled by SA1604 return; } if (!XmlCommentHelper.IsConsideredEmpty(summaryNode)) { return; } } else { if (!XmlCommentHelper.IsConsideredEmpty(syntax)) { return; } } foreach (var location in diagnosticLocations) { context.ReportDiagnostic(Diagnostic.Create(Descriptor, location)); } }
/// <inheritdoc/> protected override void HandleXmlElement(SyntaxNodeAnalysisContext context, DocumentationCommentTriviaSyntax documentation, XmlNodeSyntax syntax, XElement completeDocumentation, Location[] diagnosticLocations) { if (completeDocumentation != null) { // We are working with an <include> element if (completeDocumentation.Nodes().OfType<XElement>().Any(element => element.Name == XmlCommentHelper.SummaryXmlTag)) { return; } if (completeDocumentation.Nodes().OfType<XElement>().Any(element => element.Name == XmlCommentHelper.InheritdocXmlTag)) { // Ignore nodes with an <inheritdoc/> tag in the included XML. return; } } else { if (syntax != null) { return; } if (documentation?.Content.GetFirstXmlElement(XmlCommentHelper.InheritdocXmlTag) != null) { // Ignore nodes with an <inheritdoc/> tag. return; } } foreach (var location in diagnosticLocations) { context.ReportDiagnostic(Diagnostic.Create(Descriptor, location)); } }
/// <inheritdoc/> protected override void HandleXmlElement(SyntaxNodeAnalysisContext context, XmlNodeSyntax syntax, Location diagnosticLocation) { if (syntax == null) { context.ReportDiagnostic(Diagnostic.Create(Descriptor, diagnosticLocation)); } }
public XmlAttributeSyntax(XmlNameSyntax name, PunctuationSyntax equals, XmlNodeSyntax value) : base(SyntaxKind.XmlAttribute) { this.NameNode = name; this.Equals = equals; this.ValueNode = value; SlotCount = 3; }
/// <inheritdoc/> protected override void HandleXmlElement(SyntaxNodeAnalysisContext context, XmlNodeSyntax syntax, params Location[] diagnosticLocations) { if (syntax == null) { foreach (var location in diagnosticLocations) { context.ReportDiagnostic(Diagnostic.Create(Descriptor, location)); } } }
private static void HandleElement(SyntaxNodeAnalysisContext context, XmlNodeSyntax element, XmlNameSyntax name, Location alternativeDiagnosticLocation) { if (string.Equals(name.ToString(), XmlCommentHelper.ParamXmlTag)) { var nameAttribute = XmlCommentHelper.GetFirstAttributeOrDefault<XmlNameAttributeSyntax>(element); if (string.IsNullOrWhiteSpace(nameAttribute?.Identifier?.Identifier.ValueText)) { context.ReportDiagnostic(Diagnostic.Create(Descriptor, nameAttribute?.GetLocation() ?? alternativeDiagnosticLocation)); } } }
private static bool IsContentElement(XmlNodeSyntax syntax) { switch (syntax.Kind()) { case SyntaxKind.XmlCDataSection: case SyntaxKind.XmlElement: case SyntaxKind.XmlEmptyElement: case SyntaxKind.XmlText: return true; default: return false; } }
/// <inheritdoc/> protected override void HandleXmlElement(SyntaxNodeAnalysisContext context, XmlNodeSyntax syntax, Location diagnosticLocation) { var propertyDeclaration = (PropertyDeclarationSyntax)context.Node; var propertyType = context.SemanticModel.GetTypeInfo(propertyDeclaration.Type); if (propertyType.Type.SpecialType == SpecialType.System_Boolean) { AnalyzeSummaryElement(context, syntax, diagnosticLocation, propertyDeclaration, StartingTextGetsWhether, StartingTextSetsWhether, StartingTextGetsOrSetsWhether); } else { AnalyzeSummaryElement(context, syntax, diagnosticLocation, propertyDeclaration, StartingTextGets, StartingTextSets, StartingTextGetsOrSets); } }
public XmlDocumentSyntax( SyntaxKind kind, XmlDeclarationSyntax prologue, SyntaxNode precedingMisc, XmlNodeSyntax body, SyntaxNode followingMisc, SyntaxToken eof) : base(kind) { this.Prologue = prologue; this.PrecedingMisc = precedingMisc; this.Body = body; this.FollowingMisc = followingMisc; this.Eof = eof; SlotCount = 5; }
private void ClassifyXmlNode(XmlNodeSyntax node) { switch (node.Kind()) { case SyntaxKind.XmlElement: ClassifyXmlElement((XmlElementSyntax)node); break; case SyntaxKind.XmlEmptyElement: ClassifyXmlEmptyElement((XmlEmptyElementSyntax)node); break; case SyntaxKind.XmlText: ClassifyXmlText((XmlTextSyntax)node); break; case SyntaxKind.XmlComment: ClassifyXmlComment((XmlCommentSyntax)node); break; case SyntaxKind.XmlCDataSection: ClassifyXmlCDataSection((XmlCDataSectionSyntax)node); break; case SyntaxKind.XmlProcessingInstruction: ClassifyXmlProcessingInstruction((XmlProcessingInstructionSyntax)node); break; } }
private string Clean(XmlNodeSyntax xmlNodeSyntax) => xmlNodeSyntax.ToString().Replace("\r", "").Replace("\n", "").Replace("///", "").Trim();
public XmlNodeSyntax Visit(XmlNodeSyntax node) { return node; }
private static void HandleMemberDeclaration(SyntaxNodeAnalysisContext context) { MemberDeclarationSyntax memberSyntax = (MemberDeclarationSyntax)context.Node; var modifiers = memberSyntax.GetModifiers(); if (modifiers.Any(SyntaxKind.OverrideKeyword)) { return; } DocumentationCommentTriviaSyntax documentation = memberSyntax.GetDocumentationCommentTriviaSyntax(); if (documentation == null) { return; } Location location; ISymbol declaredSymbol = context.SemanticModel.GetDeclaredSymbol(memberSyntax, context.CancellationToken); if (declaredSymbol == null && memberSyntax.IsKind(SyntaxKind.EventFieldDeclaration)) { var eventFieldDeclarationSyntax = (EventFieldDeclarationSyntax)memberSyntax; VariableDeclaratorSyntax firstVariable = eventFieldDeclarationSyntax.Declaration?.Variables.FirstOrDefault(); if (firstVariable != null) { declaredSymbol = context.SemanticModel.GetDeclaredSymbol(firstVariable, context.CancellationToken); } } if (documentation.Content.GetFirstXmlElement(XmlCommentHelper.IncludeXmlTag) is XmlEmptyElementSyntax includeElement) { if (declaredSymbol == null) { return; } var rawDocumentation = declaredSymbol.GetDocumentationCommentXml(expandIncludes: true, cancellationToken: context.CancellationToken); var completeDocumentation = XElement.Parse(rawDocumentation, LoadOptions.None); var inheritDocElement = completeDocumentation.Nodes().OfType <XElement>().FirstOrDefault(element => element.Name == XmlCommentHelper.InheritdocXmlTag); if (inheritDocElement == null) { return; } if (HasXmlCrefAttribute(inheritDocElement)) { return; } location = includeElement.GetLocation(); } else { XmlNodeSyntax inheritDocElement = documentation.Content.GetFirstXmlElement(XmlCommentHelper.InheritdocXmlTag); if (inheritDocElement == null) { return; } if (HasXmlCrefAttribute(inheritDocElement)) { return; } location = inheritDocElement.GetLocation(); } // If we don't have a declared symbol we have some kind of field declaration. A field can not override or // implement anything so we want to report a diagnostic. if (declaredSymbol == null || !NamedTypeHelpers.IsImplementingAnInterfaceMember(declaredSymbol)) { context.ReportDiagnostic(Diagnostic.Create(Descriptor, location)); } }
private static void AnalyzeSummaryElement(SyntaxNodeAnalysisContext context, XmlNodeSyntax syntax, Location diagnosticLocation, PropertyDeclarationSyntax propertyDeclaration, string startingTextGets, string startingTextSets, string startingTextGetsOrSets) { var diagnosticProperties = ImmutableDictionary.CreateBuilder<string, string>(); ArrowExpressionClauseSyntax expressionBody = propertyDeclaration.ExpressionBody; AccessorDeclarationSyntax getter = null; AccessorDeclarationSyntax setter = null; if (propertyDeclaration.AccessorList != null) { foreach (var accessor in propertyDeclaration.AccessorList.Accessors) { switch (accessor.Keyword.Kind()) { case SyntaxKind.GetKeyword: getter = accessor; break; case SyntaxKind.SetKeyword: setter = accessor; break; } } } XmlElementSyntax summaryElement = (XmlElementSyntax)syntax; if (summaryElement == null) { // This is reported by SA1604. return; } // Add a no code fix tag when the summary element is empty. // This will only impact SA1623, because SA1624 cannot trigger with an empty summary. if (summaryElement.Content.Count == 0) { diagnosticProperties.Add(NoCodeFixKey, string.Empty); } var textElement = summaryElement.Content.FirstOrDefault() as XmlTextSyntax; var text = textElement == null ? string.Empty : XmlCommentHelper.GetText(textElement, true).TrimStart(); if (getter != null || expressionBody != null) { bool startsWithGetOrSet = text.StartsWith(startingTextGetsOrSets, StringComparison.Ordinal); if (setter != null) { // There is no way getter is null (can't have expression body and accessor list) bool getterVisible; bool setterVisible; if (!getter.Modifiers.Any() && !setter.Modifiers.Any()) { // Case 1: The getter and setter have the same declared accessibility getterVisible = true; setterVisible = true; } else if (getter.Modifiers.Any(SyntaxKind.PrivateKeyword)) { // Case 3 getterVisible = false; setterVisible = true; } else if (setter.Modifiers.Any(SyntaxKind.PrivateKeyword)) { // Case 3 getterVisible = true; setterVisible = false; } else { var propertyAccessibility = propertyDeclaration.GetEffectiveAccessibility(context.SemanticModel, context.CancellationToken); bool propertyOnlyInternal = propertyAccessibility == Accessibility.Internal || propertyAccessibility == Accessibility.ProtectedAndInternal || propertyAccessibility == Accessibility.Private; if (propertyOnlyInternal) { // Case 2: Property only internal and no accessor is explicitly private getterVisible = true; setterVisible = true; } else { var getterAccessibility = getter.GetEffectiveAccessibility(context.SemanticModel, context.CancellationToken); var setterAccessibility = setter.GetEffectiveAccessibility(context.SemanticModel, context.CancellationToken); switch (getterAccessibility) { case Accessibility.Public: case Accessibility.ProtectedOrInternal: case Accessibility.Protected: // Case 4 getterVisible = true; break; case Accessibility.Internal: case Accessibility.ProtectedAndInternal: case Accessibility.Private: default: // The property is externally accessible, so the setter must be more accessible. getterVisible = false; break; } switch (setterAccessibility) { case Accessibility.Public: case Accessibility.ProtectedOrInternal: case Accessibility.Protected: // Case 4 setterVisible = true; break; case Accessibility.Internal: case Accessibility.ProtectedAndInternal: case Accessibility.Private: default: // The property is externally accessible, so the getter must be more accessible. setterVisible = false; break; } } } if (getterVisible && !setterVisible) { if (startsWithGetOrSet) { diagnosticProperties.Add(ExpectedTextKey, startingTextGets); diagnosticProperties.Add(TextToRemoveKey, startingTextGetsOrSets); context.ReportDiagnostic(Diagnostic.Create(SA1624Descriptor, diagnosticLocation, diagnosticProperties.ToImmutable(), "get", startingTextGets)); } else if (!text.StartsWith(startingTextGets, StringComparison.Ordinal)) { diagnosticProperties.Add(ExpectedTextKey, startingTextGets); context.ReportDiagnostic(Diagnostic.Create(SA1623Descriptor, diagnosticLocation, diagnosticProperties.ToImmutable(), startingTextGets)); } } else if (!getterVisible && setterVisible) { if (startsWithGetOrSet) { diagnosticProperties.Add(ExpectedTextKey, startingTextSets); diagnosticProperties.Add(TextToRemoveKey, startingTextGetsOrSets); context.ReportDiagnostic(Diagnostic.Create(SA1624Descriptor, diagnosticLocation, diagnosticProperties.ToImmutable(), "set", startingTextSets)); } else if (!text.StartsWith(startingTextSets, StringComparison.Ordinal)) { diagnosticProperties.Add(ExpectedTextKey, startingTextSets); context.ReportDiagnostic(Diagnostic.Create(SA1623Descriptor, diagnosticLocation, diagnosticProperties.ToImmutable(), startingTextSets)); } } else { if (!startsWithGetOrSet) { diagnosticProperties.Add(ExpectedTextKey, startingTextGetsOrSets); context.ReportDiagnostic(Diagnostic.Create(SA1623Descriptor, diagnosticLocation, diagnosticProperties.ToImmutable(), startingTextGetsOrSets)); } } } else { if (startsWithGetOrSet || !text.StartsWith(startingTextGets, StringComparison.Ordinal)) { diagnosticProperties.Add(ExpectedTextKey, startingTextGets); context.ReportDiagnostic(Diagnostic.Create(SA1623Descriptor, diagnosticLocation, diagnosticProperties.ToImmutable(), startingTextGets)); } } } else if (setter != null) { if (!text.StartsWith(startingTextSets, StringComparison.Ordinal)) { diagnosticProperties.Add(ExpectedTextKey, startingTextSets); context.ReportDiagnostic(Diagnostic.Create(SA1623Descriptor, diagnosticLocation, diagnosticProperties.ToImmutable(), startingTextSets)); } } }
private string ExpandDocumentation(Compilation compilation, DocumentationCommentTriviaSyntax documentCommentTrivia, XmlNodeSyntax includeTag) { var sb = new StringBuilder(); sb.AppendLine("<member>"); foreach (XmlNodeSyntax xmlNode in documentCommentTrivia.Content) { if (xmlNode == includeTag) { this.ExpandIncludeTag(compilation, sb, xmlNode); } else { sb.AppendLine(xmlNode.ToString()); } } sb.AppendLine("</member>"); return(sb.ToString()); }
/// <summary> /// Analyzes the top-level <c><summary></c> element of a documentation comment. /// </summary> /// <param name="context">The current analysis context.</param> /// <param name="syntax">The <see cref="XmlElementSyntax"/> or <see cref="XmlEmptyElementSyntax"/> of the node /// to examine.</param> /// <param name="diagnosticLocations">The location(s) where diagnostics, if any, should be reported.</param> protected abstract void HandleXmlElement(SyntaxNodeAnalysisContext context, XmlNodeSyntax syntax, params Location[] diagnosticLocations);
/// <summary> /// Analyzes the top-level <c><summary></c> or <c><content></c> element of a documentation comment. /// </summary> /// <param name="context">The current analysis context.</param> /// <param name="syntax">The <see cref="XmlElementSyntax"/> or <see cref="XmlEmptyElementSyntax"/> of the node /// to examine.</param> /// <param name="diagnosticLocations">The location(s) where diagnostics, if any, should be reported.</param> abstract protected void HandleXmlElement(SyntaxNodeAnalysisContext context, XmlNodeSyntax syntax, params Location[] diagnosticLocations);
private static SyntaxList <XmlNodeSyntax> SortElements <TNode>( SeparatedSyntaxList <TNode> nodes, SyntaxList <XmlNodeSyntax> content, int firstIndex, XmlElementKind kind, Func <SeparatedSyntaxList <TNode>, string, int> indexOf) where TNode : SyntaxNode { var xmlNodes = new List <XmlNodeSyntax>(); var ranks = new Dictionary <XmlNodeSyntax, int>(); for (int i = firstIndex; i < content.Count; i++) { XmlElementInfo elementInfo = SyntaxInfo.XmlElementInfo(content[i]); if (elementInfo.Success) { if (!elementInfo.IsEmptyElement && elementInfo.IsElementKind(kind)) { var element = (XmlElementSyntax)elementInfo.Element; string value = element.GetAttributeValue("name"); if (value != null) { ranks[element] = indexOf(nodes, value); } else { break; } } else { break; } } xmlNodes.Add(content[i]); } for (int i = 0; i < xmlNodes.Count - 1; i++) { for (int j = 0; j < xmlNodes.Count - i - 1; j++) { XmlNodeSyntax node1 = xmlNodes[j]; if (ranks.TryGetValue(node1, out int rank1)) { int k = j + 1; while (k < xmlNodes.Count - i - 1) { XmlNodeSyntax node2 = xmlNodes[k]; if (ranks.TryGetValue(node2, out int rank2)) { if (rank1 > rank2) { xmlNodes[k] = node1; xmlNodes[j] = node2; } break; } k++; } } } } return(content.ReplaceRange(firstIndex, xmlNodes.Count, xmlNodes)); }
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)); }
private bool TryRemoveSummaryPrefix(ref SyntaxList <XmlNodeSyntax> summaryContent, string prefix) { XmlNodeSyntax firstContent = summaryContent.FirstOrDefault(IsContentElement); if (!(firstContent is XmlTextSyntax firstText)) { return(false); } string firstTextContent = string.Concat(firstText.DescendantTokens()); if (!firstTextContent.TrimStart().StartsWith(prefix, StringComparison.Ordinal)) { return(false); } // Find the token containing the prefix, such as "Gets or sets " SyntaxToken prefixToken = default; foreach (SyntaxToken textToken in firstText.TextTokens) { if (textToken.IsMissing) { continue; } if (!textToken.Text.TrimStart().StartsWith(prefix, StringComparison.Ordinal)) { continue; } prefixToken = textToken; break; } if (prefixToken.IsMissingOrDefault()) { return(false); } string text = prefixToken.Text; string valueText = prefixToken.ValueText; int index = text.IndexOf(prefix); if (index >= 0) { bool additionalCharacters = index + prefix.Length < text.Length; text = text.Substring(0, index) + (additionalCharacters ? char.ToUpperInvariant(text[index + prefix.Length]).ToString() : string.Empty) + text.Substring(index + (additionalCharacters ? (prefix.Length + 1) : prefix.Length)); } index = valueText.IndexOf(prefix); if (index >= 0) { valueText = valueText.Remove(index, prefix.Length); } SyntaxToken replaced = SyntaxFactory.Token(prefixToken.LeadingTrivia, prefixToken.Kind(), text, valueText, prefixToken.TrailingTrivia); summaryContent = summaryContent.Replace(firstText, firstText.ReplaceToken(prefixToken, replaced)); return(true); }
/// <inheritdoc/> protected override void HandleXmlElement(SyntaxNodeAnalysisContext context, bool needsComment, XmlNodeSyntax syntax, XElement completeDocumentation, Location diagnosticLocation) { var properties = ImmutableDictionary.Create <string, string>(); if (completeDocumentation != null) { var valueTag = completeDocumentation.Nodes().OfType <XElement>().FirstOrDefault(element => element.Name == XmlCommentHelper.ValueXmlTag); if (valueTag == null) { // handled by SA1609 return; } if (!XmlCommentHelper.IsConsideredEmpty(valueTag)) { return; } properties = properties.Add(NoCodeFixKey, string.Empty); } else { if (syntax == null) { // Handled by SA1609 return; } if (!XmlCommentHelper.IsConsideredEmpty(syntax)) { return; } } context.ReportDiagnostic(Diagnostic.Create(Descriptor, diagnosticLocation, properties)); }
private static 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); } MethodDeclarationSyntax methodDeclarationSyntax = syntax.FirstAncestorOrSelf <MethodDeclarationSyntax>(); DelegateDeclarationSyntax delegateDeclarationSyntax = syntax.FirstAncestorOrSelf <DelegateDeclarationSyntax>(); if (methodDeclarationSyntax == null && delegateDeclarationSyntax == null) { return(document); } DocumentationCommentTriviaSyntax documentationComment = methodDeclarationSyntax?.GetDocumentationCommentTriviaSyntax() ?? delegateDeclarationSyntax?.GetDocumentationCommentTriviaSyntax(); bool canIgnoreDocumentation = documentationComment == null || documentationComment.Content .Where(x => x is XmlElementSyntax || x is XmlEmptyElementSyntax) .All(x => string.Equals(x.GetName()?.ToString(), XmlCommentHelper.IncludeXmlTag)); if (canIgnoreDocumentation) { return(document); } SemanticModel semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); bool isTask; bool isAsynchronousTestMethod; if (methodDeclarationSyntax != null) { isTask = TaskHelper.IsTaskReturningMethod(semanticModel, methodDeclarationSyntax, cancellationToken); isAsynchronousTestMethod = isTask && IsAsynchronousTestMethod(semanticModel, methodDeclarationSyntax, cancellationToken); } else { isTask = TaskHelper.IsTaskReturningMethod(semanticModel, delegateDeclarationSyntax, cancellationToken); isAsynchronousTestMethod = false; } XmlNodeSyntax returnsElement = documentationComment.Content.GetFirstXmlElement(XmlCommentHelper.ReturnsXmlTag); if (returnsElement != null && !isTask) { // This code fix doesn't know how to do anything more than document Task-returning methods. return(document); } SyntaxList <XmlNodeSyntax> content = XmlSyntaxFactory.List(); if (isTask) { content = content.Add(XmlSyntaxFactory.Text("A ")); content = content.Add(XmlSyntaxFactory.SeeElement(SyntaxFactory.TypeCref(SyntaxFactory.ParseTypeName("global::System.Threading.Tasks.Task"))).WithAdditionalAnnotations(Simplifier.Annotation)); string operationKind = isAsynchronousTestMethod ? "unit test" : "operation"; content = content.Add(XmlSyntaxFactory.Text($" representing the asynchronous {operationKind}.")); // wrap the generated content in a <placeholder> element for review. content = XmlSyntaxFactory.List(XmlSyntaxFactory.PlaceholderElement(content)); } // Try to replace an existing <returns> element if the comment contains one. Otherwise, add it as a new element. SyntaxNode root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); SyntaxNode newRoot; if (returnsElement != null) { if (returnsElement is XmlEmptyElementSyntax emptyElement) { XmlElementSyntax updatedReturns = XmlSyntaxFactory.Element(XmlCommentHelper.ReturnsXmlTag, content) .WithLeadingTrivia(returnsElement.GetLeadingTrivia()) .WithTrailingTrivia(returnsElement.GetTrailingTrivia()); newRoot = root.ReplaceNode(returnsElement, updatedReturns); } else { XmlElementSyntax updatedReturns = ((XmlElementSyntax)returnsElement).WithContent(content); newRoot = root.ReplaceNode(returnsElement, updatedReturns); } } else { string newLineText = document.Project.Solution.Workspace.Options.GetOption(FormattingOptions.NewLine, LanguageNames.CSharp); returnsElement = XmlSyntaxFactory.Element(XmlCommentHelper.ReturnsXmlTag, 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); returnsElement = returnsElement.ReplaceExteriorTrivia(exteriorTrivia); } DocumentationCommentTriviaSyntax newDocumentationComment = documentationComment.WithContent( documentationComment.Content.InsertRange( documentationComment.Content.Count - 1, XmlSyntaxFactory.List(leadingNewLine, returnsElement))); newRoot = root.ReplaceNode(documentationComment, newDocumentationComment); } return(document.WithSyntaxRoot(newRoot)); }
private XmlElementInfo(XmlNodeSyntax element, string localName) { Element = element; LocalName = localName; }
/// <inheritdoc/> protected override void HandleXmlElement(SyntaxNodeAnalysisContext context, bool needsComment, DocumentationCommentTriviaSyntax documentation, XmlNodeSyntax syntax, XElement completeDocumentation, Location[] diagnosticLocations) { if (!needsComment) { // A missing summary is allowed for this element. return; } if (completeDocumentation != null) { // We are working with an <include> element if (completeDocumentation.Nodes().OfType <XElement>().Any(element => element.Name == XmlCommentHelper.SummaryXmlTag)) { return; } if (completeDocumentation.Nodes().OfType <XElement>().Any(element => element.Name == XmlCommentHelper.InheritdocXmlTag)) { // Ignore nodes with an <inheritdoc/> tag in the included XML. return; } } else { if (syntax != null) { return; } if (documentation?.Content.GetFirstXmlElement(XmlCommentHelper.InheritdocXmlTag) != null) { // Ignore nodes with an <inheritdoc/> tag. return; } } foreach (var location in diagnosticLocations) { context.ReportDiagnostic(Diagnostic.Create(Descriptor, location)); } }
/// <summary> /// Analyzes the top-level <c><summary></c> element of a documentation comment. /// </summary> /// <param name="context">The current analysis context.</param> /// <param name="needsComment"><see langword="true"/> if the current documentation settings indicate that the /// element should be documented; otherwise, <see langword="false"/>.</param> /// <param name="documentation">The documentation syntax associated with the element.</param> /// <param name="syntax">The <see cref="XmlElementSyntax"/> or <see cref="XmlEmptyElementSyntax"/> of the node /// to examine.</param> /// <param name="completeDocumentation">The complete documentation for the declared symbol, with any /// <c><include></c> elements expanded. If the XML documentation comment included a <c><summary></c> /// element, this value will be <see langword="null"/>, even if the XML documentation comment also included an /// <c><include></c> element.</param> /// <param name="diagnosticLocations">The location(s) where diagnostics, if any, should be reported.</param> protected abstract void HandleXmlElement(SyntaxNodeAnalysisContext context, bool needsComment, DocumentationCommentTriviaSyntax documentation, XmlNodeSyntax syntax, XElement completeDocumentation, params Location[] diagnosticLocations);
private static XmlNameSyntax GetName(XmlNodeSyntax element) { return((element as XmlElementSyntax)?.StartTag?.Name ?? (element as XmlEmptyElementSyntax)?.Name); }
/// <summary> /// This helper is used by documentation diagnostics to check if a XML comment should be considered empty. /// A comment is empty if it does not have any text in any XML element and it does not have an empty XML element in it. /// </summary> /// <param name="xmlSyntax">The xmlSyntax that should be checked</param> /// <returns>true, if the comment should be considered empty, false otherwise.</returns> internal static bool IsConsideredEmpty(XmlNodeSyntax xmlSyntax) { var text = xmlSyntax as XmlTextSyntax; if (text != null) { foreach (SyntaxToken token in text.TextTokens) { if (!string.IsNullOrWhiteSpace(token.ToString())) { return(false); } } return(true); } var element = xmlSyntax as XmlElementSyntax; if (element != null) { foreach (XmlNodeSyntax syntax in element.Content) { if (!IsConsideredEmpty(syntax)) { return(false); } } return(true); } var cdataElement = xmlSyntax as XmlCDataSectionSyntax; if (cdataElement != null) { foreach (SyntaxToken token in cdataElement.TextTokens) { if (!string.IsNullOrWhiteSpace(token.ToString())) { return(false); } } return(true); } var emptyElement = xmlSyntax as XmlEmptyElementSyntax; if (emptyElement != null) { // This includes <inheritdoc/> return(false); } var processingElement = xmlSyntax as XmlProcessingInstructionSyntax; if (processingElement != null) { return(false); } return(true); }
/// <inheritdoc/> protected override void HandleXmlElement(SyntaxNodeAnalysisContext context, bool needsComment, XmlNodeSyntax syntax, XElement completeDocumentation, Location diagnosticLocation) { var propertyDeclaration = (PropertyDeclarationSyntax)context.Node; var propertyType = context.SemanticModel.GetTypeInfo(propertyDeclaration.Type.StripRefFromType()); var settings = context.Options.GetStyleCopSettings(context.CancellationToken); var culture = new CultureInfo(settings.DocumentationRules.DocumentationCulture); var resourceManager = DocumentationResources.ResourceManager; if (propertyType.Type.SpecialType == SpecialType.System_Boolean) { AnalyzeSummaryElement( context, syntax, diagnosticLocation, propertyDeclaration, resourceManager.GetString(nameof(DocumentationResources.StartingTextGetsWhether), culture), resourceManager.GetString(nameof(DocumentationResources.StartingTextSetsWhether), culture), resourceManager.GetString(nameof(DocumentationResources.StartingTextGetsOrSetsWhether), culture)); } else { AnalyzeSummaryElement( context, syntax, diagnosticLocation, propertyDeclaration, resourceManager.GetString(nameof(DocumentationResources.StartingTextGets), culture), resourceManager.GetString(nameof(DocumentationResources.StartingTextSets), culture), resourceManager.GetString(nameof(DocumentationResources.StartingTextGetsOrSets), culture)); } }
private static void HandleBaseTypeLikeDeclaration(SyntaxNodeAnalysisContext context) { BaseTypeDeclarationSyntax baseType = context.Node as BaseTypeDeclarationSyntax; // baseType can be null here if we are looking at a delegate declaration if (baseType != null && baseType.BaseList != null && baseType.BaseList.Types.Any()) { return; } DocumentationCommentTriviaSyntax documentation = context.Node.GetDocumentationCommentTriviaSyntax(); if (documentation == null) { return; } Location location; if (documentation.Content.GetFirstXmlElement(XmlCommentHelper.IncludeXmlTag) is XmlEmptyElementSyntax includeElement) { var declaration = context.SemanticModel.GetDeclaredSymbol(context.Node, context.CancellationToken); if (declaration == null) { return; } var rawDocumentation = declaration.GetDocumentationCommentXml(expandIncludes: true, cancellationToken: context.CancellationToken); var completeDocumentation = XElement.Parse(rawDocumentation, LoadOptions.None); var inheritDocElement = completeDocumentation.Nodes().OfType <XElement>().FirstOrDefault(element => element.Name == XmlCommentHelper.InheritdocXmlTag); if (inheritDocElement == null) { return; } if (HasXmlCrefAttribute(inheritDocElement)) { return; } location = includeElement.GetLocation(); } else { XmlNodeSyntax inheritDocElement = documentation.Content.GetFirstXmlElement(XmlCommentHelper.InheritdocXmlTag); if (inheritDocElement == null) { return; } if (HasXmlCrefAttribute(inheritDocElement)) { return; } location = inheritDocElement.GetLocation(); } context.ReportDiagnostic(Diagnostic.Create(Descriptor, location)); }
private static void AnalyzeSummaryElement(SyntaxNodeAnalysisContext context, XmlNodeSyntax syntax, Location diagnosticLocation, PropertyDeclarationSyntax propertyDeclaration, string startingTextGets, string startingTextSets, string startingTextGetsOrSets) { var diagnosticProperties = ImmutableDictionary.CreateBuilder <string, string>(); ArrowExpressionClauseSyntax expressionBody = propertyDeclaration.ExpressionBody; AccessorDeclarationSyntax getter = null; AccessorDeclarationSyntax setter = null; if (propertyDeclaration.AccessorList != null) { foreach (var accessor in propertyDeclaration.AccessorList.Accessors) { switch (accessor.Keyword.Kind()) { case SyntaxKind.GetKeyword: getter = accessor; break; case SyntaxKind.SetKeyword: setter = accessor; break; } } } if (!(syntax is XmlElementSyntax summaryElement)) { // This is reported by SA1604 or SA1606. return; } // Add a no code fix tag when the summary element is empty. // This will only impact SA1623, because SA1624 cannot trigger with an empty summary. if (summaryElement.Content.Count == 0) { diagnosticProperties.Add(NoCodeFixKey, string.Empty); } var textElement = XmlCommentHelper.TryGetFirstTextElementWithContent(summaryElement); string text = textElement is null ? string.Empty : XmlCommentHelper.GetText(textElement, normalizeWhitespace: true).TrimStart(); bool prefixIsGetsOrSets = text.StartsWith(startingTextGetsOrSets, StringComparison.OrdinalIgnoreCase); bool prefixIsGets = !prefixIsGetsOrSets && text.StartsWith(startingTextGets, StringComparison.OrdinalIgnoreCase); bool prefixIsSets = text.StartsWith(startingTextSets, StringComparison.OrdinalIgnoreCase); bool getterVisible, setterVisible; if (getter != null && setter != null) { if (!getter.Modifiers.Any() && !setter.Modifiers.Any()) { // The getter and setter have the same declared accessibility getterVisible = true; setterVisible = true; } else if (getter.Modifiers.Any(SyntaxKind.PrivateKeyword)) { getterVisible = false; setterVisible = true; } else if (setter.Modifiers.Any(SyntaxKind.PrivateKeyword)) { getterVisible = true; setterVisible = false; } else { var propertyAccessibility = propertyDeclaration.GetEffectiveAccessibility(context.SemanticModel, context.CancellationToken); bool propertyOnlyInternal = propertyAccessibility == Accessibility.Internal || propertyAccessibility == Accessibility.ProtectedAndInternal || propertyAccessibility == Accessibility.Private; if (propertyOnlyInternal) { // Property only internal and no accessor is explicitly private getterVisible = true; setterVisible = true; } else { var getterAccessibility = getter.GetEffectiveAccessibility(context.SemanticModel, context.CancellationToken); var setterAccessibility = setter.GetEffectiveAccessibility(context.SemanticModel, context.CancellationToken); switch (getterAccessibility) { case Accessibility.Public: case Accessibility.ProtectedOrInternal: case Accessibility.Protected: getterVisible = true; break; case Accessibility.Internal: case Accessibility.ProtectedAndInternal: case Accessibility.Private: default: // The property is externally accessible, so the setter must be more accessible. getterVisible = false; break; } switch (setterAccessibility) { case Accessibility.Public: case Accessibility.ProtectedOrInternal: case Accessibility.Protected: setterVisible = true; break; case Accessibility.Internal: case Accessibility.ProtectedAndInternal: case Accessibility.Private: default: // The property is externally accessible, so the getter must be more accessible. setterVisible = false; break; } } } } else { if (getter != null || expressionBody != null) { getterVisible = true; setterVisible = false; } else { getterVisible = false; setterVisible = setter != null; } } if (getterVisible) { if (setterVisible) { // Both getter and setter are visible. if (!prefixIsGetsOrSets) { ReportSA1623(context, diagnosticLocation, diagnosticProperties, text, expectedStartingText: startingTextGetsOrSets, unexpectedStartingText1: startingTextGets, unexpectedStartingText2: startingTextSets); } } else if (setter != null) { // Both getter and setter exist, but only getter is visible. if (!prefixIsGets) { if (prefixIsGetsOrSets) { ReportSA1624(context, diagnosticLocation, diagnosticProperties, accessor: "get", expectedStartingText: startingTextGets, startingTextToRemove: startingTextGetsOrSets); } else { ReportSA1623(context, diagnosticLocation, diagnosticProperties, text, expectedStartingText: startingTextGets, unexpectedStartingText1: startingTextSets); } } } else { // Getter exists and is visible. Setter does not exist. if (!prefixIsGets) { ReportSA1623(context, diagnosticLocation, diagnosticProperties, text, expectedStartingText: startingTextGets, unexpectedStartingText1: startingTextSets, unexpectedStartingText2: startingTextGetsOrSets); } } } else if (setterVisible) { if (getter != null) { // Both getter and setter exist, but only setter is visible. if (!prefixIsSets) { if (prefixIsGetsOrSets) { ReportSA1624(context, diagnosticLocation, diagnosticProperties, accessor: "set", expectedStartingText: startingTextSets, startingTextToRemove: startingTextGetsOrSets); } else { ReportSA1623(context, diagnosticLocation, diagnosticProperties, text, expectedStartingText: startingTextSets, unexpectedStartingText1: startingTextGets); } } } else { // Setter exists and is visible. Getter does not exist. if (!prefixIsSets) { ReportSA1623(context, diagnosticLocation, diagnosticProperties, text, expectedStartingText: startingTextSets, unexpectedStartingText1: startingTextGetsOrSets, unexpectedStartingText2: startingTextGets); } } } }
private static bool HasXmlCrefAttribute(XmlNodeSyntax inheritDocElement) { XmlElementSyntax xmlElementSyntax = inheritDocElement as XmlElementSyntax; if (xmlElementSyntax?.StartTag?.Attributes.Any(SyntaxKind.XmlCrefAttribute) ?? false) { return true; } XmlEmptyElementSyntax xmlEmptyElementSyntax = inheritDocElement as XmlEmptyElementSyntax; if (xmlEmptyElementSyntax?.Attributes.Any(SyntaxKind.XmlCrefAttribute) ?? false) { return true; } return false; }
/// <inheritdoc/> protected override void HandleXmlElement(SyntaxNodeAnalysisContext context, bool needsComment, XmlNodeSyntax syntax, XElement completeDocumentation, Location[] diagnosticLocations) { if (completeDocumentation != null) { var summaryTag = completeDocumentation.Nodes().OfType <XElement>().FirstOrDefault(element => element.Name == XmlCommentHelper.SummaryXmlTag); var contentTag = completeDocumentation.Nodes().OfType <XElement>().FirstOrDefault(element => element.Name == XmlCommentHelper.ContentXmlTag); if ((summaryTag == null) && (contentTag == null)) { // handled by SA1605 return; } if (!XmlCommentHelper.IsConsideredEmpty(summaryTag) || !XmlCommentHelper.IsConsideredEmpty(contentTag)) { return; } } else { if (syntax == null) { // handled by SA1605 return; } if (!XmlCommentHelper.IsConsideredEmpty(syntax)) { return; } } foreach (var location in diagnosticLocations) { context.ReportDiagnostic(Diagnostic.Create(Descriptor, location)); } }
/// <summary> /// Add documentation for the property. /// </summary> /// <param name="context">the code fix context.</param> /// <param name="root">the root syntax node.</param> /// <param name="methodDeclaration">the property declaration containing invalid documentation.</param> /// <param name="documentComment">the existing comment.</param> /// <returns>the correct code.</returns> private Task<Document> AddDocumentationAsync( CodeFixContext context, SyntaxNode root, MethodDeclarationSyntax methodDeclaration, DocumentationCommentTriviaSyntax documentComment) { var summary = this._commentNodeFactory.GetExistingSummaryCommentText(documentComment) ?? this._commentNodeFactory.CreateCommentSummaryText(methodDeclaration); var @class = methodDeclaration.Parent as ClassDeclarationSyntax; var first = @class?.DescendantNodes().FirstOrDefault() == methodDeclaration; var parameters = this._commentNodeFactory.CreateParameters(methodDeclaration, documentComment); var typeParameters = this._commentNodeFactory.CreateTypeParameters(methodDeclaration, documentComment); var @return = this._commentNodeFactory.CreateReturnValueDocumentation(methodDeclaration, documentComment); var returns = @return == null ? new XmlElementSyntax[] { } : new[] { @return }; var summaryPlusParameters = new XmlNodeSyntax[] { summary } .Concat(parameters) .Concat(returns) .Concat(typeParameters) .ToArray(); var comment = this._commentNodeFactory .CreateDocumentComment(summaryPlusParameters) .AddLeadingEndOfLineTriviaFrom(methodDeclaration.GetLeadingTrivia()); var trivia = SyntaxFactory.Trivia(comment); var methodTrivia = first ? methodDeclaration.WithLeadingTrivia(trivia) : methodDeclaration.WithLeadingTrivia(SyntaxFactory.CarriageReturnLineFeed, trivia); var result = documentComment != null ? root.ReplaceNode(documentComment, comment.AdjustDocumentationCommentNewLineTrivia()) : root.ReplaceNode(methodDeclaration, methodTrivia); var newDocument = context.Document.WithSyntaxRoot(result); return Task.FromResult(newDocument); }
/// <summary> /// add documentation for the constructor. /// </summary> /// <param name="context">the code fix context.</param> /// <param name="root">the syntax root.</param> /// <param name="constructorDeclaration">the constructor declaration syntax.</param> /// <param name="documentComment">the document content.</param> /// <returns>the resulting document.</returns> private Task<Document> AddDocumentationAsync(CodeFixContext context, SyntaxNode root, ConstructorDeclarationSyntax constructorDeclaration, DocumentationCommentTriviaSyntax documentComment) { var lines = documentComment.GetExistingSummaryCommentDocumentation() ?? new string[] { }; var standardCommentText = this._commentNodeFactory.PrependStandardCommentText(constructorDeclaration, lines); var parameters = this._commentNodeFactory.CreateParameters(constructorDeclaration, documentComment); var summaryPlusParameters = new XmlNodeSyntax[] { standardCommentText } .Concat(parameters) .ToArray(); var comment = this._commentNodeFactory .CreateDocumentComment(summaryPlusParameters) .AddLeadingEndOfLineTriviaFrom(constructorDeclaration.GetLeadingTrivia()); var trivia = SyntaxFactory.Trivia(comment); var result = documentComment != null ? root.ReplaceNode(documentComment, comment.AdjustDocumentationCommentNewLineTrivia()) : root.ReplaceNode(constructorDeclaration, constructorDeclaration.WithLeadingTrivia(trivia)); var newDocument = context.Document.WithSyntaxRoot(result); return Task.FromResult(newDocument); }
/// <summary> /// Analyzes the top-level <c><summary></c> element of a documentation comment. /// </summary> /// <param name="context">The current analysis context.</param> /// <param name="documentation">The documentation syntax associated with the element.</param> /// <param name="syntax">The <see cref="XmlElementSyntax"/> or <see cref="XmlEmptyElementSyntax"/> of the node /// to examine.</param> /// <param name="completeDocumentation">The complete documentation for the declared symbol, with any /// <c><include></c> elements expanded. If the XML documentation comment included a <c><summary></c> /// element, this value will be <see langword="null"/>, even if the XML documentation comment also included an /// <c><include></c> element.</param> /// <param name="diagnosticLocations">The location(s) where diagnostics, if any, should be reported.</param> protected abstract void HandleXmlElement(SyntaxNodeAnalysisContext context, DocumentationCommentTriviaSyntax documentation, XmlNodeSyntax syntax, XElement completeDocumentation, params Location[] diagnosticLocations);
private static XmlNameSyntax GetName(XmlNodeSyntax element) { return (element as XmlElementSyntax)?.StartTag?.Name ?? (element as XmlEmptyElementSyntax)?.Name; }
public static XmlElementInfo XmlElementInfo(XmlNodeSyntax xmlNode) { return(Syntax.XmlElementInfo.Create(xmlNode)); }
public TameXmlNodeSyntax(XmlNodeSyntax node) { Node = node; AddChildren(); }
private static void AnalyzeSummaryElement(SyntaxNodeAnalysisContext context, XmlNodeSyntax syntax, Location diagnosticLocation, PropertyDeclarationSyntax propertyDeclaration, string startingTextGets, string startingTextSets, string startingTextGetsOrSets) { var diagnosticProperties = ImmutableDictionary.CreateBuilder<string, string>(); ArrowExpressionClauseSyntax expressionBody = propertyDeclaration.ExpressionBody; AccessorDeclarationSyntax getter = null; AccessorDeclarationSyntax setter = null; if (propertyDeclaration.AccessorList != null) { foreach (var accessor in propertyDeclaration.AccessorList.Accessors) { switch (accessor.Keyword.Kind()) { case SyntaxKind.GetKeyword: getter = accessor; break; case SyntaxKind.SetKeyword: setter = accessor; break; } } } XmlElementSyntax summaryElement = (XmlElementSyntax)syntax; if (summaryElement == null) { // This is reported by SA1604. return; } var textElement = summaryElement.Content.FirstOrDefault() as XmlTextSyntax; var text = textElement == null ? string.Empty : XmlCommentHelper.GetText(textElement, true).TrimStart(); if (getter != null || expressionBody != null) { bool startsWithGetOrSet = text.StartsWith(startingTextGetsOrSets, StringComparison.Ordinal); if (setter != null) { // There is no way getter is null (can't have expression body and accessor list) var getterAccessibility = getter.GetEffectiveAccessibility(context.SemanticModel, context.CancellationToken); var setterAccessibility = setter.GetEffectiveAccessibility(context.SemanticModel, context.CancellationToken); bool getterVisible; bool setterVisible; switch (getterAccessibility) { case Accessibility.Public: case Accessibility.ProtectedOrInternal: case Accessibility.Protected: getterVisible = true; break; case Accessibility.Internal: getterVisible = setterAccessibility == Accessibility.Private; break; default: getterVisible = false; break; } switch (setterAccessibility) { case Accessibility.Public: case Accessibility.ProtectedOrInternal: case Accessibility.Protected: setterVisible = true; break; case Accessibility.Internal: setterVisible = getterAccessibility == Accessibility.Private; break; default: setterVisible = false; break; } if (getterVisible && !setterVisible) { if (startsWithGetOrSet) { diagnosticProperties.Add(ExpectedTextKey, startingTextGets); diagnosticProperties.Add(TextToRemoveKey, startingTextGetsOrSets); context.ReportDiagnostic(Diagnostic.Create(SA1624Descriptor, diagnosticLocation, diagnosticProperties.ToImmutable(), "get", startingTextGets)); } else if (!text.StartsWith(startingTextGets, StringComparison.Ordinal)) { diagnosticProperties.Add(ExpectedTextKey, startingTextGets); context.ReportDiagnostic(Diagnostic.Create(SA1623Descriptor, diagnosticLocation, diagnosticProperties.ToImmutable(), startingTextGets)); } } else if (!getterVisible && setterVisible) { if (startsWithGetOrSet) { diagnosticProperties.Add(ExpectedTextKey, startingTextSets); diagnosticProperties.Add(TextToRemoveKey, startingTextGetsOrSets); context.ReportDiagnostic(Diagnostic.Create(SA1624Descriptor, diagnosticLocation, diagnosticProperties.ToImmutable(), "set", startingTextSets)); } else if (!text.StartsWith(startingTextSets, StringComparison.Ordinal)) { diagnosticProperties.Add(ExpectedTextKey, startingTextSets); context.ReportDiagnostic(Diagnostic.Create(SA1623Descriptor, diagnosticLocation, diagnosticProperties.ToImmutable(), startingTextSets)); } } else { if (!startsWithGetOrSet) { diagnosticProperties.Add(ExpectedTextKey, startingTextGetsOrSets); context.ReportDiagnostic(Diagnostic.Create(SA1623Descriptor, diagnosticLocation, diagnosticProperties.ToImmutable(), startingTextGetsOrSets)); } } } else { if (startsWithGetOrSet || !text.StartsWith(startingTextGets, StringComparison.Ordinal)) { diagnosticProperties.Add(ExpectedTextKey, startingTextGets); context.ReportDiagnostic(Diagnostic.Create(SA1623Descriptor, diagnosticLocation, diagnosticProperties.ToImmutable(), startingTextGets)); } } } else if (setter != null) { if (!text.StartsWith(startingTextSets, StringComparison.Ordinal)) { diagnosticProperties.Add(ExpectedTextKey, startingTextSets); context.ReportDiagnostic(Diagnostic.Create(SA1623Descriptor, diagnosticLocation, diagnosticProperties.ToImmutable(), startingTextSets)); } } }
public void Accept(XmlNodeSyntax syntaxNode) { _state = _state.Accept(syntaxNode); }
/// <summary> /// Analyzes the top-level <c><summary></c> element of a documentation comment. /// </summary> /// <param name="context">The current analysis context.</param> /// <param name="needsComment"><see langword="true"/> if the current documentation settings indicate that the /// element should be documented; otherwise, <see langword="false"/>.</param> /// <param name="syntax">The <see cref="XmlElementSyntax"/> or <see cref="XmlEmptyElementSyntax"/> of the node /// to examine.</param> /// <param name="completeDocumentation">The complete documentation for the declared symbol, with any /// <c><include></c> elements expanded. If the XML documentation comment included a <c><summary></c> /// element, this value will be <see langword="null"/>, even if the XML documentation comment also included an /// <c><include></c> element.</param> /// <param name="diagnosticLocation">The location where diagnostics, if any, should be reported.</param> protected abstract void HandleXmlElement(SyntaxNodeAnalysisContext context, bool needsComment, XmlNodeSyntax syntax, XElement completeDocumentation, Location diagnosticLocation);
private static void AnalyzeSummaryElement(SyntaxNodeAnalysisContext context, XmlNodeSyntax syntax, Location diagnosticLocation, PropertyDeclarationSyntax propertyDeclaration, string startingTextGets, string startingTextSets, string startingTextGetsOrSets) { var diagnosticProperties = ImmutableDictionary.CreateBuilder <string, string>(); ArrowExpressionClauseSyntax expressionBody = propertyDeclaration.ExpressionBody; AccessorDeclarationSyntax getter = null; AccessorDeclarationSyntax setter = null; if (propertyDeclaration.AccessorList != null) { foreach (var accessor in propertyDeclaration.AccessorList.Accessors) { switch (accessor.Keyword.Kind()) { case SyntaxKind.GetKeyword: getter = accessor; break; case SyntaxKind.SetKeyword: setter = accessor; break; } } } XmlElementSyntax summaryElement = (XmlElementSyntax)syntax; if (summaryElement == null) { // This is reported by SA1604. return; } // Add a no code fix tag when the summary element is empty. // This will only impact SA1623, because SA1624 cannot trigger with an empty summary. if (summaryElement.Content.Count == 0) { diagnosticProperties.Add(NoCodeFixKey, string.Empty); } var textElement = summaryElement.Content.FirstOrDefault() as XmlTextSyntax; var text = textElement == null ? string.Empty : XmlCommentHelper.GetText(textElement, true).TrimStart(); if (getter != null || expressionBody != null) { bool startsWithGetOrSet = text.StartsWith(startingTextGetsOrSets, StringComparison.Ordinal); if (setter != null) { // There is no way getter is null (can't have expression body and accessor list) bool getterVisible; bool setterVisible; if (!getter.Modifiers.Any() && !setter.Modifiers.Any()) { // Case 1: The getter and setter have the same declared accessibility getterVisible = true; setterVisible = true; } else if (getter.Modifiers.Any(SyntaxKind.PrivateKeyword)) { // Case 3 getterVisible = false; setterVisible = true; } else if (setter.Modifiers.Any(SyntaxKind.PrivateKeyword)) { // Case 3 getterVisible = true; setterVisible = false; } else { var propertyAccessibility = propertyDeclaration.GetEffectiveAccessibility(context.SemanticModel, context.CancellationToken); bool propertyOnlyInternal = propertyAccessibility == Accessibility.Internal || propertyAccessibility == Accessibility.ProtectedAndInternal || propertyAccessibility == Accessibility.Private; if (propertyOnlyInternal) { // Case 2: Property only internal and no accessor is explicitly private getterVisible = true; setterVisible = true; } else { var getterAccessibility = getter.GetEffectiveAccessibility(context.SemanticModel, context.CancellationToken); var setterAccessibility = setter.GetEffectiveAccessibility(context.SemanticModel, context.CancellationToken); switch (getterAccessibility) { case Accessibility.Public: case Accessibility.ProtectedOrInternal: case Accessibility.Protected: // Case 4 getterVisible = true; break; case Accessibility.Internal: case Accessibility.ProtectedAndInternal: case Accessibility.Private: default: // The property is externally accessible, so the setter must be more accessible. getterVisible = false; break; } switch (setterAccessibility) { case Accessibility.Public: case Accessibility.ProtectedOrInternal: case Accessibility.Protected: // Case 4 setterVisible = true; break; case Accessibility.Internal: case Accessibility.ProtectedAndInternal: case Accessibility.Private: default: // The property is externally accessible, so the getter must be more accessible. setterVisible = false; break; } } } if (getterVisible && !setterVisible) { if (startsWithGetOrSet) { diagnosticProperties.Add(ExpectedTextKey, startingTextGets); diagnosticProperties.Add(TextToRemoveKey, startingTextGetsOrSets); context.ReportDiagnostic(Diagnostic.Create(SA1624Descriptor, diagnosticLocation, diagnosticProperties.ToImmutable(), "get", startingTextGets)); } else if (!text.StartsWith(startingTextGets, StringComparison.Ordinal)) { diagnosticProperties.Add(ExpectedTextKey, startingTextGets); context.ReportDiagnostic(Diagnostic.Create(SA1623Descriptor, diagnosticLocation, diagnosticProperties.ToImmutable(), startingTextGets)); } } else if (!getterVisible && setterVisible) { if (startsWithGetOrSet) { diagnosticProperties.Add(ExpectedTextKey, startingTextSets); diagnosticProperties.Add(TextToRemoveKey, startingTextGetsOrSets); context.ReportDiagnostic(Diagnostic.Create(SA1624Descriptor, diagnosticLocation, diagnosticProperties.ToImmutable(), "set", startingTextSets)); } else if (!text.StartsWith(startingTextSets, StringComparison.Ordinal)) { diagnosticProperties.Add(ExpectedTextKey, startingTextSets); context.ReportDiagnostic(Diagnostic.Create(SA1623Descriptor, diagnosticLocation, diagnosticProperties.ToImmutable(), startingTextSets)); } } else { if (!startsWithGetOrSet) { diagnosticProperties.Add(ExpectedTextKey, startingTextGetsOrSets); context.ReportDiagnostic(Diagnostic.Create(SA1623Descriptor, diagnosticLocation, diagnosticProperties.ToImmutable(), startingTextGetsOrSets)); } } } else { if (startsWithGetOrSet || !text.StartsWith(startingTextGets, StringComparison.Ordinal)) { diagnosticProperties.Add(ExpectedTextKey, startingTextGets); context.ReportDiagnostic(Diagnostic.Create(SA1623Descriptor, diagnosticLocation, diagnosticProperties.ToImmutable(), startingTextGets)); } } } else if (setter != null) { if (!text.StartsWith(startingTextSets, StringComparison.Ordinal)) { diagnosticProperties.Add(ExpectedTextKey, startingTextSets); context.ReportDiagnostic(Diagnostic.Create(SA1623Descriptor, diagnosticLocation, diagnosticProperties.ToImmutable(), startingTextSets)); } } }
/// <summary> /// Analyzes the top-level <c><summary></c> or <c><content></c> element of a documentation comment. /// </summary> /// <param name="context">The current analysis context.</param> /// <param name="syntax">The <see cref="XmlElementSyntax"/> or <see cref="XmlEmptyElementSyntax"/> of the node /// to examine.</param> /// <param name="completeDocumentation">The complete documentation for the declared symbol, with any /// <c><include></c> elements expanded. If the XML documentation comment included a <c><summary></c> /// element, this value will be <see langword="null"/>, even if the XML documentation comment also included an /// <c><include></c> element.</param> /// <param name="diagnosticLocations">The location(s) where diagnostics, if any, should be reported.</param> protected abstract void HandleXmlElement(SyntaxNodeAnalysisContext context, XmlNodeSyntax syntax, XElement completeDocumentation, params Location[] diagnosticLocations);
/// <inheritdoc/> protected override void HandleXmlElement(SyntaxNodeAnalysisContext context, bool needsComment, XmlNodeSyntax syntax, XElement completeDocumentation, Location[] diagnosticLocations) { if (!needsComment) { // A missing summary is allowed for this element. return; } if (completeDocumentation != null) { var hasSummaryTag = completeDocumentation.Nodes().OfType <XElement>().Any(element => element.Name == XmlCommentHelper.SummaryXmlTag); var hasContentTag = completeDocumentation.Nodes().OfType <XElement>().Any(element => element.Name == XmlCommentHelper.ContentXmlTag); if (hasSummaryTag || hasContentTag) { return; } } else { if (syntax != null) { return; } } foreach (var location in diagnosticLocations) { context.ReportDiagnostic(Diagnostic.Create(Descriptor, location)); } }
/// <inheritdoc/> protected override void HandleXmlElement(SyntaxNodeAnalysisContext context, bool needsComment, XmlNodeSyntax syntax, XElement completeDocumentation, Location diagnosticLocation) { if (!needsComment) { // A missing 'value' documentation is allowed for this element. return; } var properties = ImmutableDictionary.Create <string, string>(); if (completeDocumentation != null) { var hasValueTag = completeDocumentation.Nodes().OfType <XElement>().Any(element => element.Name == XmlCommentHelper.ValueXmlTag); if (hasValueTag) { return; } properties = properties.Add(NoCodeFixKey, string.Empty); } else { if (syntax != null) { return; } } context.ReportDiagnostic(Diagnostic.Create(Descriptor, diagnosticLocation, properties)); }
private string GetElementName(XmlNodeSyntax node) => GetElementNameAndAttributes(node).name;
private static void HandleElement(SyntaxNodeAnalysisContext context, XmlNodeSyntax element, ImmutableArray<string> parentTypeParameters, int index, Location alternativeDiagnosticLocation) { var nameAttribute = XmlCommentHelper.GetFirstAttributeOrDefault<XmlNameAttributeSyntax>(element); // Make sure we ignore violations that should be reported by SA1613 instead. if (string.IsNullOrWhiteSpace(nameAttribute?.Identifier?.Identifier.ValueText)) { return; } if (!parentTypeParameters.Contains(nameAttribute.Identifier.Identifier.ValueText)) { context.ReportDiagnostic(Diagnostic.Create(MissingTypeParameterDescriptor, nameAttribute?.Identifier?.GetLocation() ?? alternativeDiagnosticLocation, nameAttribute.Identifier.Identifier.ValueText)); } else if (parentTypeParameters.Length <= index || parentTypeParameters[index] != nameAttribute.Identifier.Identifier.ValueText) { context.ReportDiagnostic( Diagnostic.Create( OrderDescriptor, nameAttribute?.Identifier?.GetLocation() ?? alternativeDiagnosticLocation, nameAttribute.Identifier.Identifier.ValueText, parentTypeParameters.IndexOf(nameAttribute.Identifier.Identifier.ValueText) + 1)); } }
public IncompatibleException(ITypeSymbol type, CrefSyntax cref) { Type = type; Syntax = cref; }