public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) { SyntaxNode root = await context.GetSyntaxRootAsync().ConfigureAwait(false); if (!TryFindFirstAncestorOrSelf(root, context.Span, out XmlNodeSyntax xmlNode, findInsideTrivia: true)) { return; } foreach (Diagnostic diagnostic in context.Diagnostics) { switch (diagnostic.Id) { case DiagnosticIdentifiers.UnusedElementInDocumentationComment: { XmlElementInfo elementInfo = SyntaxInfo.XmlElementInfo(xmlNode); string name = elementInfo.LocalName; CodeAction codeAction = CodeAction.Create( $"Remove element '{name}'", cancellationToken => RemoveUnusedElementInDocumentationCommentAsync(context.Document, elementInfo, cancellationToken), GetEquivalenceKey(diagnostic)); context.RegisterCodeFix(codeAction, diagnostic); break; } } } }
private static void Analyze <TNode>( SyntaxNodeAnalysisContext context, SyntaxList <XmlNodeSyntax> xmlNodes, SeparatedSyntaxList <TNode> nodes, XmlTag tag, Func <SeparatedSyntaxList <TNode>, string, int> indexOf) where TNode : SyntaxNode { XmlNodeSyntax firstElement = null; int firstIndex = -1; for (int i = 0; i < xmlNodes.Count; i++) { XmlElementInfo elementInfo = SyntaxInfo.XmlElementInfo(xmlNodes[i]); if (!elementInfo.Success) { continue; } if (!elementInfo.HasTag(tag)) { firstIndex = -1; continue; } XmlNodeSyntax element = elementInfo.Element; string name = (element.IsKind(SyntaxKind.XmlElement)) ? ((XmlElementSyntax)element).GetAttributeValue("name") : ((XmlEmptyElementSyntax)element).GetAttributeValue("name"); if (name == null) { firstIndex = -1; continue; } int index = indexOf(nodes, name); if (index == -1) { ReportUnusedElement(context, element, i, xmlNodes); } else if (index < firstIndex) { ReportDiagnosticIfNotSuppressed(context, DiagnosticRules.OrderElementsInDocumentationComment, firstElement); return; } else { firstElement = element; } firstIndex = index; } }
private static void AnalyzeList(SyntaxNodeAnalysisContext context, XmlElementInfo elementInfo) { if (elementInfo.IsEmptyElement) { return; } var element = (XmlElementSyntax)elementInfo.Element; foreach (XmlNodeSyntax node in element.Content) { XmlElementInfo elementInfo2 = SyntaxInfo.XmlElementInfo(node); if (!elementInfo2.Success) { continue; } if (elementInfo2.IsEmptyElement) { continue; } if (!elementInfo2.HasLocalName("listheader", "item")) { continue; } var element2 = (XmlElementSyntax)elementInfo2.Element; foreach (XmlNodeSyntax node2 in element2.Content) { XmlElementInfo elementInfo3 = SyntaxInfo.XmlElementInfo(node2); if (!elementInfo3.Success) { continue; } if (elementInfo3.IsEmptyElement) { continue; } if (elementInfo3.HasLocalName("term", "description")) { Analyze(context, elementInfo3); } } } }
public static void Analyze(SyntaxNodeAnalysisContext context, XmlElementInfo elementInfo) { if (elementInfo.IsEmptyElement) { return; } var element = (XmlElementSyntax)elementInfo.Element; foreach (XmlNodeSyntax node in element.Content) { XmlElementInfo elementInfo2 = SyntaxInfo.XmlElementInfo(node); if (elementInfo2.Success) { switch (elementInfo2.GetTag()) { case XmlTag.C: { AnalyzeCElement(context, elementInfo2); break; } case XmlTag.Code: { AnalyzeCodeElement(context, elementInfo2); break; } case XmlTag.List: { AnalyzeList(context, elementInfo2); break; } case XmlTag.Para: case XmlTag.ParamRef: case XmlTag.See: case XmlTag.TypeParamRef: { Analyze(context, elementInfo2); break; } } } } }
private static Task <Document> OrderElementsAsync( Document document, XmlNodeSyntax xmlNode, CancellationToken cancellationToken) { var documentationComment = (DocumentationCommentTriviaSyntax)xmlNode.Parent; SyntaxList <XmlNodeSyntax> content = documentationComment.Content; MemberDeclarationSyntax memberDeclaration = xmlNode.FirstAncestor <MemberDeclarationSyntax>(); int firstIndex = content.IndexOf(xmlNode); SyntaxList <XmlNodeSyntax> newContent = GetNewContent(); DocumentationCommentTriviaSyntax newDocumentationComment = documentationComment.WithContent(newContent); return(document.ReplaceNodeAsync(documentationComment, newDocumentationComment, cancellationToken)); SyntaxList <XmlNodeSyntax> GetNewContent() { switch (SyntaxInfo.XmlElementInfo(xmlNode).GetElementKind()) { case XmlElementKind.Param: { SeparatedSyntaxList <ParameterSyntax> parameters = ParameterListInfo.Create(memberDeclaration).Parameters; return(SortElements(parameters, content, firstIndex, XmlElementKind.Param, (nodes, name) => nodes.IndexOf(name))); } case XmlElementKind.TypeParam: { SeparatedSyntaxList <TypeParameterSyntax> typeParameters = TypeParameterListInfo.Create(memberDeclaration).Parameters; return(SortElements(typeParameters, content, firstIndex, XmlElementKind.TypeParam, (nodes, name) => nodes.IndexOf(name))); } default: { throw new InvalidOperationException(); } } } }
private static bool IsMissing(DocumentationCommentTriviaSyntax documentationComment, TypeParameterSyntax typeParameter) { foreach (XmlNodeSyntax xmlNode in documentationComment.Content) { XmlElementInfo elementInfo = SyntaxInfo.XmlElementInfo(xmlNode); if (elementInfo.Success && !elementInfo.IsEmptyElement && elementInfo.HasTag(XmlTag.TypeParam)) { var element = (XmlElementSyntax)elementInfo.Element; string value = element.GetAttributeValue("name"); if (value != null && string.Equals(typeParameter.Identifier.ValueText, value, StringComparison.Ordinal)) { return(false); } } } return(true); }
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.IsElementKind(kind)) { XmlNodeSyntax element = elementInfo.Element; string value = (element.IsKind(SyntaxKind.XmlElement)) ? ((XmlElementSyntax)element).GetAttributeValue("name") : ((XmlEmptyElementSyntax)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 static bool CanAddExceptionToComment( DocumentationCommentTriviaSyntax comment, INamedTypeSymbol exceptionSymbol, SemanticModel semanticModel, CancellationToken cancellationToken) { var containsException = false; var containsIncludeOrExclude = false; var isFirst = true; foreach (XmlNodeSyntax node in comment.Content) { XmlElementInfo info = SyntaxInfo.XmlElementInfo(node); if (info.Success) { switch (info.GetTag()) { case XmlTag.Include: case XmlTag.Exclude: { if (isFirst) { containsIncludeOrExclude = true; } break; } case XmlTag.InheritDoc: { return(false); } case XmlTag.Exception: { if (!containsException) { if (info.IsEmptyElement) { containsException = ContainsException((XmlEmptyElementSyntax)info.Element, exceptionSymbol, semanticModel, cancellationToken); } else { containsException = ContainsException((XmlElementSyntax)info.Element, exceptionSymbol, semanticModel, cancellationToken); } } break; } } if (isFirst) { isFirst = false; } else { containsIncludeOrExclude = false; } } } return(!containsIncludeOrExclude && !containsException); }
private static void AnalyzeSingleLineDocumentationCommentTrivia(SyntaxNodeAnalysisContext context) { var documentationComment = (DocumentationCommentTriviaSyntax)context.Node; if (!documentationComment.IsPartOfMemberDeclaration()) { return; } bool?useCorrectDocumentationTagEnabled = null; var containsInheritDoc = false; var containsIncludeOrExclude = false; var containsSummaryElement = false; var containsContentElement = false; var isFirst = true; CancellationToken cancellationToken = context.CancellationToken; SyntaxList <XmlNodeSyntax> content = documentationComment.Content; for (int i = 0; i < content.Count; i++) { cancellationToken.ThrowIfCancellationRequested(); XmlElementInfo info = SyntaxInfo.XmlElementInfo(content[i]); if (info.Success) { switch (info.GetTag()) { case XmlTag.Include: case XmlTag.Exclude: { if (isFirst) { containsIncludeOrExclude = true; } break; } case XmlTag.InheritDoc: { containsInheritDoc = true; break; } case XmlTag.Content: { containsContentElement = true; break; } case XmlTag.Summary: { if (info.IsContentEmptyOrWhitespace) { ReportDiagnosticIfNotSuppressed(context, DiagnosticRules.AddSummaryToDocumentationComment, info.Element); } containsSummaryElement = true; if (useCorrectDocumentationTagEnabled ??= DiagnosticRules.FixDocumentationCommentTag.IsEffective(context)) { FixDocumentationCommentTagAnalysis.Analyze(context, info); } break; } case XmlTag.Example: case XmlTag.Remarks: case XmlTag.Returns: case XmlTag.Value: { if (info.IsContentEmptyOrWhitespace) { ReportUnusedElement(context, info.Element, i, content); } if (useCorrectDocumentationTagEnabled ??= DiagnosticRules.FixDocumentationCommentTag.IsEffective(context)) { FixDocumentationCommentTagAnalysis.Analyze(context, info); } break; } case XmlTag.Exception: case XmlTag.List: case XmlTag.Param: case XmlTag.Permission: case XmlTag.TypeParam: { if (useCorrectDocumentationTagEnabled ??= DiagnosticRules.FixDocumentationCommentTag.IsEffective(context)) { FixDocumentationCommentTagAnalysis.Analyze(context, info); } break; } case XmlTag.C: case XmlTag.Code: case XmlTag.Para: case XmlTag.ParamRef: case XmlTag.See: case XmlTag.SeeAlso: case XmlTag.TypeParamRef: { break; } default: { Debug.Fail(info.GetTag().ToString()); break; } } if (isFirst) { isFirst = false; } else { containsIncludeOrExclude = false; } } } if (containsInheritDoc || containsIncludeOrExclude) { return; } if (!containsSummaryElement && !containsContentElement) { ReportDiagnosticIfNotSuppressed(context, DiagnosticRules.AddSummaryElementToDocumentationComment, documentationComment); } SyntaxNode parent = documentationComment.ParentTrivia.Token.Parent; bool unusedElement = DiagnosticRules.UnusedElementInDocumentationComment.IsEffective(context); bool orderParams = DiagnosticRules.OrderElementsInDocumentationComment.IsEffective(context); bool addParam = DiagnosticRules.AddParamElementToDocumentationComment.IsEffective(context); bool addTypeParam = DiagnosticRules.AddTypeParamElementToDocumentationComment.IsEffective(context); if (addParam || orderParams || unusedElement) { SeparatedSyntaxList <ParameterSyntax> parameters = CSharpUtility.GetParameters( (parent is MemberDeclarationSyntax) ? parent : parent.Parent); if (addParam && parameters.Any()) { foreach (ParameterSyntax parameter in parameters) { if (IsMissing(documentationComment, parameter)) { ReportDiagnostic(context, DiagnosticRules.AddParamElementToDocumentationComment, documentationComment); break; } } } if (orderParams || unusedElement) { Analyze(context, documentationComment.Content, parameters, XmlTag.Param, (nodes, name) => nodes.IndexOf(name)); } } if (addTypeParam || orderParams || unusedElement) { SeparatedSyntaxList <TypeParameterSyntax> typeParameters = CSharpUtility.GetTypeParameters( (parent is MemberDeclarationSyntax) ? parent : parent.Parent); if (addTypeParam && typeParameters.Any()) { foreach (TypeParameterSyntax typeParameter in typeParameters) { if (IsMissing(documentationComment, typeParameter)) { ReportDiagnostic(context, DiagnosticRules.AddTypeParamElementToDocumentationComment, documentationComment); break; } } } if (orderParams || unusedElement) { Analyze(context, documentationComment.Content, typeParameters, XmlTag.TypeParam, (nodes, name) => nodes.IndexOf(name)); } } }
public static void AnalyzeSingleLineDocumentationCommentTrivia(SyntaxNodeAnalysisContext context) { var documentationComment = (DocumentationCommentTriviaSyntax)context.Node; if (!IsPartOfMemberDeclaration(documentationComment)) { return; } bool containsInheritDoc = false; bool containsIncludeOrExclude = false; bool containsSummaryElement = false; bool isFirst = true; foreach (XmlNodeSyntax node in documentationComment.Content) { XmlElementInfo info = SyntaxInfo.XmlElementInfo(node); if (info.Success) { switch (info.ElementKind) { case XmlElementKind.Include: case XmlElementKind.Exclude: { if (isFirst) { containsIncludeOrExclude = true; } break; } case XmlElementKind.InheritDoc: { containsInheritDoc = true; break; } case XmlElementKind.Summary: { if (info.IsXmlEmptyElement || IsSummaryMissing((XmlElementSyntax)info.Element)) { context.ReportDiagnostic( DiagnosticDescriptors.AddSummaryToDocumentationComment, info.Element); } containsSummaryElement = true; break; } } if (isFirst) { isFirst = false; } else { containsIncludeOrExclude = false; } if (containsInheritDoc && containsSummaryElement) { break; } } } if (!containsSummaryElement && !containsInheritDoc && !containsIncludeOrExclude) { context.ReportDiagnostic( DiagnosticDescriptors.AddSummaryElementToDocumentationComment, documentationComment); } }
public static ImmutableArray <string> GetAttributeValues(DocumentationCommentTriviaSyntax comment, string elementName1, string elementName2, string attributeName) { HashSet <string> values = null; bool containsIncludeOrExclude = false; bool isFirst = true; foreach (XmlNodeSyntax node in comment.Content) { XmlElementInfo info = SyntaxInfo.XmlElementInfo(node); if (info.Success) { switch (info.ElementKind) { case XmlElementKind.Include: case XmlElementKind.Exclude: { if (isFirst) { containsIncludeOrExclude = true; } break; } case XmlElementKind.InheritDoc: { return(default(ImmutableArray <string>)); } default: { if (!info.IsEmptyElement && info.IsLocalName(elementName1, elementName2)) { string value = GetAttributeValue((XmlElementSyntax)info.Element, attributeName); if (value != null) { (values ?? (values = new HashSet <string>())).Add(value); } } break; } } if (isFirst) { isFirst = false; } else { containsIncludeOrExclude = false; } } } if (!containsIncludeOrExclude) { return(values?.ToImmutableArray() ?? ImmutableArray <string> .Empty); } return(default(ImmutableArray <string>)); }
private static void AnalyzeSingleLineDocumentationCommentTrivia(SyntaxNodeAnalysisContext context) { var documentationComment = (DocumentationCommentTriviaSyntax)context.Node; if (!documentationComment.IsPartOfMemberDeclaration()) { return; } bool containsInheritDoc = false; bool containsIncludeOrExclude = false; bool containsSummaryElement = false; bool containsContentElement = false; bool isFirst = true; CancellationToken cancellationToken = context.CancellationToken; SyntaxList <XmlNodeSyntax> content = documentationComment.Content; for (int i = 0; i < content.Count; i++) { cancellationToken.ThrowIfCancellationRequested(); XmlElementInfo info = SyntaxInfo.XmlElementInfo(content[i]); if (info.Success) { switch (info.GetElementKind()) { case XmlElementKind.Include: case XmlElementKind.Exclude: { if (isFirst) { containsIncludeOrExclude = true; } break; } case XmlElementKind.InheritDoc: { containsInheritDoc = true; break; } case XmlElementKind.Content: { containsContentElement = true; break; } case XmlElementKind.Summary: { if (info.IsContentEmptyOrWhitespace) { ReportDiagnosticIfNotSuppressed(context, DiagnosticDescriptors.AddSummaryToDocumentationComment, info.Element); } containsSummaryElement = true; break; } case XmlElementKind.Code: case XmlElementKind.Example: case XmlElementKind.Remarks: case XmlElementKind.Returns: case XmlElementKind.Value: { if (info.IsContentEmptyOrWhitespace) { ReportUnusedElement(context, info.Element, i, content); } break; } } if (isFirst) { isFirst = false; } else { containsIncludeOrExclude = false; } } } if (containsInheritDoc || containsIncludeOrExclude) { return; } if (!containsSummaryElement && !containsContentElement) { ReportDiagnosticIfNotSuppressed(context, DiagnosticDescriptors.AddSummaryElementToDocumentationComment, documentationComment); } SyntaxNode parent = documentationComment.ParentTrivia.Token.Parent; bool unusedElement = !context.IsAnalyzerSuppressed(DiagnosticDescriptors.UnusedElementInDocumentationComment); bool orderParams = !context.IsAnalyzerSuppressed(DiagnosticDescriptors.OrderElementsInDocumentationComment); bool addParam = !context.IsAnalyzerSuppressed(DiagnosticDescriptors.AddParamElementToDocumentationComment); bool addTypeParam = !context.IsAnalyzerSuppressed(DiagnosticDescriptors.AddTypeParamElementToDocumentationComment); if (addParam || orderParams || unusedElement) { SeparatedSyntaxList <ParameterSyntax> parameters = ParameterListInfo.Create(parent).Parameters; if (addParam && parameters.Any()) { foreach (ParameterSyntax parameter in parameters) { if (IsMissing(documentationComment, parameter)) { ReportDiagnostic(context, DiagnosticDescriptors.AddParamElementToDocumentationComment, documentationComment); break; } } } if (orderParams || unusedElement) { Analyze(context, documentationComment.Content, parameters, XmlElementKind.Param, (nodes, name) => nodes.IndexOf(name)); } } if (addTypeParam || orderParams || unusedElement) { SeparatedSyntaxList <TypeParameterSyntax> typeParameters = TypeParameterListInfo.Create(parent).Parameters; if (addTypeParam && typeParameters.Any()) { foreach (TypeParameterSyntax typeParameter in typeParameters) { if (IsMissing(documentationComment, typeParameter)) { ReportDiagnostic(context, DiagnosticDescriptors.AddTypeParamElementToDocumentationComment, documentationComment); break; } } } if (orderParams || unusedElement) { Analyze(context, documentationComment.Content, typeParameters, XmlElementKind.TypeParam, (nodes, name) => nodes.IndexOf(name)); } } }
public static void AnalyzeSingleLineDocumentationCommentTrivia(SyntaxNodeAnalysisContext context) { var documentationComment = (DocumentationCommentTriviaSyntax)context.Node; if (!documentationComment.IsPartOfMemberDeclaration()) { return; } bool containsInheritDoc = false; bool containsIncludeOrExclude = false; bool containsSummaryElement = false; bool containsContentElement = false; bool isFirst = true; CancellationToken cancellationToken = context.CancellationToken; foreach (XmlNodeSyntax node in documentationComment.Content) { cancellationToken.ThrowIfCancellationRequested(); XmlElementInfo info = SyntaxInfo.XmlElementInfo(node); if (info.Success) { switch (info.GetElementKind()) { case XmlElementKind.Include: case XmlElementKind.Exclude: { if (isFirst) { containsIncludeOrExclude = true; } break; } case XmlElementKind.InheritDoc: { containsInheritDoc = true; break; } case XmlElementKind.Content: { containsContentElement = true; break; } case XmlElementKind.Summary: { if (info.IsContentEmptyOrWhitespace) { context.ReportDiagnostic( DiagnosticDescriptors.AddSummaryToDocumentationComment, info.Element); } containsSummaryElement = true; break; } case XmlElementKind.Code: case XmlElementKind.Example: case XmlElementKind.Remarks: case XmlElementKind.Returns: case XmlElementKind.Value: { if (info.IsContentEmptyOrWhitespace) { context.ReportDiagnostic( DiagnosticDescriptors.UnusedElementInDocumentationComment, info.Element); } break; } } if (isFirst) { isFirst = false; } else { containsIncludeOrExclude = false; } } } if (!containsSummaryElement && !containsInheritDoc && !containsIncludeOrExclude && !containsContentElement) { context.ReportDiagnostic( DiagnosticDescriptors.AddSummaryElementToDocumentationComment, documentationComment); } }
public static void Analyze(SyntaxNodeAnalysisContext context, XmlElementInfo elementInfo) { if (elementInfo.IsEmptyElement) { return; } var element = (XmlElementSyntax)elementInfo.Element; foreach (XmlNodeSyntax node in element.Content) { XmlElementInfo elementInfo2 = SyntaxInfo.XmlElementInfo(node); if (elementInfo2.Success) { switch (elementInfo2.GetTag()) { case XmlTag.C: { AnalyzeCElement(context, elementInfo2); break; } case XmlTag.Code: { AnalyzeCodeElement(context, elementInfo2); break; } case XmlTag.List: { AnalyzeList(context, elementInfo2); break; } case XmlTag.Para: case XmlTag.ParamRef: case XmlTag.See: case XmlTag.TypeParamRef: { Analyze(context, elementInfo2); break; } case XmlTag.Content: case XmlTag.Example: case XmlTag.Exception: case XmlTag.Exclude: case XmlTag.Include: case XmlTag.InheritDoc: case XmlTag.Param: case XmlTag.Permission: case XmlTag.Remarks: case XmlTag.Returns: case XmlTag.SeeAlso: case XmlTag.Summary: case XmlTag.TypeParam: case XmlTag.Value: { break; } default: { break; } } } } }