/// <inheritdoc/> protected override void HandleXmlElement(SyntaxNodeAnalysisContext context, bool needsComment, IEnumerable <XmlNodeSyntax> syntaxList, params Location[] diagnosticLocations) { var objectPool = SharedPools.Default <HashSet <string> >(); HashSet <string> documentationTexts = objectPool.Allocate(); var settings = context.Options.GetStyleCopSettings(context.CancellationToken); var culture = new CultureInfo(settings.DocumentationRules.DocumentationCulture); var resourceManager = DocumentationResources.ResourceManager; foreach (var documentationSyntax in syntaxList) { var documentation = XmlCommentHelper.GetText(documentationSyntax, true)?.Trim(); if (ShouldSkipElement(documentation, resourceManager.GetString(nameof(DocumentationResources.ParameterNotUsed), culture))) { continue; } if (documentationTexts.Contains(documentation)) { // Add violation context.ReportDiagnostic(Diagnostic.Create(Descriptor, documentationSyntax.GetLocation())); } else { documentationTexts.Add(documentation); } } objectPool.ClearAndFree(documentationTexts); }
private static void HandleDocumentationTrivia(SyntaxNodeAnalysisContext context) { DocumentationCommentTriviaSyntax syntax = context.Node as DocumentationCommentTriviaSyntax; var objectPool = SharedPools.Default <HashSet <string> >(); HashSet <string> documentationTexts = objectPool.Allocate(); foreach (var content in syntax.Content) { string text = XmlCommentHelper.GetText(content, true)?.Trim(); if (string.IsNullOrWhiteSpace(text) || string.Equals(text, ParameterNotUsed, StringComparison.Ordinal)) { continue; } if (documentationTexts.Contains(text)) { // Add violation context.ReportDiagnostic(Diagnostic.Create(Descriptor, content.GetLocation())); } else { documentationTexts.Add(text); } } objectPool.ClearAndFree(documentationTexts); }
private static void HandleDocumentation(SyntaxNodeAnalysisContext context) { var documentationTrivia = context.Node as DocumentationCommentTriviaSyntax; if (documentationTrivia != null) { var summaryElement = documentationTrivia.Content.GetFirstXmlElement(XmlCommentHelper.SummaryXmlTag) as XmlElementSyntax; if (summaryElement != null) { var textElement = summaryElement.Content.FirstOrDefault() as XmlTextSyntax; if (textElement != null) { string text = XmlCommentHelper.GetText(textElement, true); if (!string.IsNullOrEmpty(text)) { if (text.TrimStart().StartsWith(DefaultText, StringComparison.Ordinal)) { context.ReportDiagnostic(Diagnostic.Create(Descriptor, summaryElement.GetLocation())); } } } } } }
/// <inheritdoc/> protected override void HandleXmlElement(SyntaxNodeAnalysisContext context, IEnumerable <XmlNodeSyntax> syntaxList, params Location[] diagnosticLocations) { var objectPool = SharedPools.Default <HashSet <string> >(); HashSet <string> documentationTexts = objectPool.Allocate(); foreach (var documentationSyntax in syntaxList) { var documentation = XmlCommentHelper.GetText(documentationSyntax, true)?.Trim(); if (ShouldSkipElement(documentation)) { continue; } if (documentationTexts.Contains(documentation)) { // Add violation context.ReportDiagnostic(Diagnostic.Create(Descriptor, documentationSyntax.GetLocation())); } else { documentationTexts.Add(documentation); } } objectPool.ClearAndFree(documentationTexts); }
/// <summary> /// Analyzes a <see cref="BaseMethodDeclarationSyntax"/> node. If it has a summary it is checked if the text starts with "[firstTextPart]<see cref="[className]"/>[secondTextPart]". /// </summary> /// <param name="context">The <see cref="SyntaxNodeAnalysisContext"/> of this analysis.</param> /// <param name="firstTextPart">The first part of the standard text.</param> /// <param name="secondTextPart">The second part of the standard text.</param> /// <param name="reportDiagnostic">Whether or not a diagnostic should be reported.</param> /// <returns>A <see cref="MatchResult"/> describing the result of the analysis.</returns> protected MatchResult HandleDeclaration(SyntaxNodeAnalysisContext context, string firstTextPart, string secondTextPart, bool reportDiagnostic) { var declarationSyntax = context.Node as BaseMethodDeclarationSyntax; if (declarationSyntax == null) { return(MatchResult.Unknown); } var documentationStructure = declarationSyntax.GetDocumentationCommentTriviaSyntax(); if (documentationStructure == null) { return(MatchResult.Unknown); } var summaryElement = documentationStructure.Content.GetFirstXmlElement(XmlCommentHelper.SummaryXmlTag) as XmlElementSyntax; if (summaryElement == null) { return(MatchResult.Unknown); } // Check if the summary content could be a correct standard text if (summaryElement.Content.Count >= 3) { // Standard text has the form <part1><see><part2> var firstTextPartSyntax = summaryElement.Content[0] as XmlTextSyntax; var classReferencePart = summaryElement.Content[1] as XmlEmptyElementSyntax; var secondTextParSyntaxt = summaryElement.Content[2] as XmlTextSyntax; if (firstTextPartSyntax != null && classReferencePart != null && secondTextParSyntaxt != null) { // Check text parts var firstText = XmlCommentHelper.GetText(firstTextPartSyntax); var secondText = XmlCommentHelper.GetText(secondTextParSyntaxt); if (TextPartsMatch(firstTextPart, secondTextPart, firstTextPartSyntax, secondTextParSyntaxt) && this.SeeTagIsCorrect(context, classReferencePart, declarationSyntax)) { // We found a correct standard text return(MatchResult.FoundMatch); } } } if (reportDiagnostic) { context.ReportDiagnostic(Diagnostic.Create(this.DiagnosticDescriptor, summaryElement.GetLocation())); } // TODO: be more specific about the type of error when possible return(MatchResult.None); }
private static bool TextPartsMatch(string firstText, string secondText, XmlTextSyntax firstTextPart, XmlTextSyntax secondTextPart) { string firstTextPartText = XmlCommentHelper.GetText(firstTextPart, normalizeWhitespace: true); if (firstText != firstTextPartText.TrimStart()) { return(false); } string secondTextPartText = XmlCommentHelper.GetText(secondTextPart, normalizeWhitespace: true); return(secondTextPartText.StartsWith(secondText, StringComparison.Ordinal)); }
/// <inheritdoc/> protected override void HandleXmlElement(SyntaxNodeAnalysisContext context, StyleCopSettings settings, bool needsComment, IEnumerable <XmlNodeSyntax> syntaxList, params Location[] diagnosticLocations) { foreach (var syntax in syntaxList) { var summaryElement = syntax as XmlElementSyntax; if (summaryElement?.Content.FirstOrDefault() is XmlTextSyntax textElement) { string text = XmlCommentHelper.GetText(textElement, true); if (IsDefaultText(text)) { context.ReportDiagnostic(Diagnostic.Create(Descriptor, summaryElement.GetLocation())); return; } } } }
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 = syntax as XmlElementSyntax; if (summaryElement == null) { // 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 = summaryElement.Content.FirstOrDefault() as XmlTextSyntax; var text = textElement == null ? string.Empty : XmlCommentHelper.GetText(textElement, true).TrimStart(); bool startsWithGetOrSet = text.StartsWith(startingTextGetsOrSets, StringComparison.OrdinalIgnoreCase); bool getterVisible, setterVisible; if (getter != null && setter != null) { 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; } } } } else { if (getter != null || expressionBody != null) { getterVisible = true; setterVisible = false; } else { getterVisible = false; setterVisible = setter != null; } } if (getterVisible) { if (setterVisible) { if (!startsWithGetOrSet) { diagnosticProperties.Add(ExpectedTextKey, startingTextGetsOrSets); if (text.StartsWith(startingTextGets, StringComparison.OrdinalIgnoreCase)) { diagnosticProperties.Add(TextToRemoveKey, text.Substring(0, startingTextGets.Length)); } else if (text.StartsWith(startingTextSets, StringComparison.OrdinalIgnoreCase)) { diagnosticProperties.Add(TextToRemoveKey, text.Substring(0, startingTextSets.Length)); } context.ReportDiagnostic(Diagnostic.Create(SA1623Descriptor, diagnosticLocation, diagnosticProperties.ToImmutable(), startingTextGetsOrSets)); } } else { 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.OrdinalIgnoreCase)) { diagnosticProperties.Add(ExpectedTextKey, startingTextGets); context.ReportDiagnostic(Diagnostic.Create(SA1623Descriptor, diagnosticLocation, diagnosticProperties.ToImmutable(), startingTextGets)); } } } else if (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.OrdinalIgnoreCase)) { diagnosticProperties.Add(ExpectedTextKey, startingTextSets); context.ReportDiagnostic(Diagnostic.Create(SA1623Descriptor, diagnosticLocation, diagnosticProperties.ToImmutable(), startingTextSets)); } } }
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); } } } }