private static async Task <Document> GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) { var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var node = syntaxRoot.FindNode(diagnostic.Location.SourceSpan); var documentation = node.GetDocumentationCommentTriviaSyntax(); var summaryElement = (XmlElementSyntax)documentation.Content.GetFirstXmlElement(XmlCommentHelper.SummaryXmlTag); var textElement = XmlCommentHelper.TryGetFirstTextElementWithContent(summaryElement); if (textElement == null) { return(document); } var textToken = textElement.TextTokens.First(token => token.IsKind(SyntaxKind.XmlTextLiteralToken)); var text = textToken.ValueText; // preserve leading whitespace int index = 0; while (text.Length > index && char.IsWhiteSpace(text, index)) { index++; } var preservedWhitespace = text.Substring(0, index); // process the current documentation string string modifiedText; string textToRemove; if (diagnostic.Properties.TryGetValue(PropertySummaryDocumentationAnalyzer.TextToRemoveKey, out textToRemove)) { modifiedText = text.Substring(text.IndexOf(textToRemove) + textToRemove.Length).TrimStart(); } else { modifiedText = text.Substring(index); } if (modifiedText.Length > 0) { modifiedText = char.ToLowerInvariant(modifiedText[0]) + modifiedText.Substring(1); } // create the new text string var textToAdd = diagnostic.Properties[PropertySummaryDocumentationAnalyzer.ExpectedTextKey]; var newText = $"{preservedWhitespace}{textToAdd} {modifiedText}"; // replace the token var newXmlTextLiteral = SyntaxFactory.XmlTextLiteral(textToken.LeadingTrivia, newText, newText, textToken.TrailingTrivia); var newTextTokens = textElement.TextTokens.Replace(textToken, newXmlTextLiteral); var newTextElement = textElement.WithTextTokens(newTextTokens); var newSyntaxRoot = syntaxRoot.ReplaceNode(textElement, newTextElement); var newDocument = document.WithSyntaxRoot(newSyntaxRoot); return(newDocument); }
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); } } } }