示例#1
0
        /// <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);
        }
示例#2
0
        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);
        }
示例#3
0
        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()));
                            }
                        }
                    }
                }
            }
        }
示例#4
0
        /// <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 &quot;[firstTextPart]&lt;see cref=&quot;[className]&quot;/&gt;[secondTextPart]&quot;.
        /// </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));
        }
示例#7
0
        /// <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;
                    }
                }
            }
        }
示例#8
0
        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);
                    }
                }
            }
        }