private static void HandleSingleLineComment(SyntaxTreeAnalysisContext context, SyntaxTrivia singleLineComment) { int index = 0; // PERF: Explicitly cast to IReadOnlyList so we only box once. IReadOnlyList <SyntaxTrivia> list = TriviaHelper.GetContainingTriviaList(singleLineComment, out index); var firstNonWhiteSpace = TriviaHelper.IndexOfFirstNonWhitespaceTrivia(list); // When we encounter a block of single line comments, we only want to raise this diagnostic // on the first or last line. This ensures that whitespace in code commented out using // the Comment Selection option in Visual Studio will not raise the diagnostic for every // blank line in the code which is commented out. bool isFirst = index == firstNonWhiteSpace; if (!isFirst) { // This is -2 because we need to go back past the end of line trivia as well. var lastNonWhiteSpace = TriviaHelper.IndexOfTrailingWhitespace(list) - 2; if (index != lastNonWhiteSpace) { return; } } if (IsNullOrWhiteSpace(singleLineComment.ToString(), 2)) { var diagnostic = Diagnostic.Create(Descriptor, singleLineComment.GetLocation()); context.ReportDiagnostic(diagnostic); } }
// If you want a full implementation of this analyzer with system tests and a code fix, go to // https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1120CommentsMustContainText.cs private void HandleSyntaxTree(SyntaxTreeAnalysisContext context) { SyntaxNode root = context.Tree.GetCompilationUnitRoot(context.CancellationToken); foreach (var node in root.DescendantTrivia()) { switch (node.Kind()) { case SyntaxKind.SingleLineCommentTrivia: // Remove the leading // from the comment var commentText = node.ToString().Substring(2); int index = 0; var list = TriviaHelper.GetContainingTriviaList(node, out index); bool isFirst = IsFirstComment(list, index); bool isLast = IsLastComment(list, index); if (string.IsNullOrWhiteSpace(commentText) && (isFirst || isLast)) { var diagnostic = Diagnostic.Create(Rule, node.GetLocation()); context.ReportDiagnostic(diagnostic); } break; } } }
private static void HandleSyntaxTree(SyntaxTreeAnalysisContext context) { var syntaxRoot = context.Tree.GetRoot(context.CancellationToken); foreach (var trivia in syntaxRoot.DescendantTrivia().Where(trivia => trivia.IsKind(SyntaxKind.SingleLineCommentTrivia))) { if (trivia.FullSpan.Start == 0) { // skip the trivia if it is at the start of the file continue; } if (trivia.ToString().StartsWith("////", StringComparison.Ordinal)) { // ignore commented out code continue; } int triviaIndex; // PERF: Explicitly cast to IReadOnlyList so we only box once. var triviaList = TriviaHelper.GetContainingTriviaList(trivia, out triviaIndex); if (!IsOnOwnLine(triviaList, triviaIndex)) { // ignore comments after other code elements. continue; } if (IsPrecededByBlankLine(triviaList, triviaIndex)) { // allow properly formatted blank line comments. continue; } if (IsPrecededBySingleLineCommentOrDocumentation(triviaList, triviaIndex)) { // allow consecutive single line comments. continue; } if (IsAtStartOfScope(trivia)) { // allow single line comment at scope start. continue; } if (IsPrecededByDirectiveTrivia(triviaList, triviaIndex)) { // allow single line comment that is preceded by some directive trivia (if, elif, else) continue; } var diagnosticSpan = TextSpan.FromBounds(trivia.SpanStart, trivia.SpanStart + 2); context.ReportDiagnostic(Diagnostic.Create(Descriptor, Location.Create(context.Tree, diagnosticSpan))); } }
private static async Task <Document> GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) { var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var node = root.FindTrivia(diagnostic.Location.SourceSpan.Start, true); int diagnosticIndex = 0; var triviaList = TriviaHelper.GetContainingTriviaList(node, out diagnosticIndex); var nodesToRemove = new List <SyntaxTrivia>(); nodesToRemove.Add(node); // If there is trialing content on the line, we don't want to remove the leading whitespace bool hasTrailingContent = TriviaHasTrailingContentOnLine(root, triviaList); if (diagnosticIndex > 0 && !hasTrailingContent) { var previousStart = triviaList[diagnosticIndex - 1].SpanStart; var previousNode = root.FindTrivia(previousStart, true); nodesToRemove.Add(previousNode); } // If there is leading content on the line, then we don't want to remove the trailing end of lines bool hasLeadingContent = TriviaHasLeadingContentOnLine(root, triviaList); if (diagnosticIndex < triviaList.Count - 1) { var nextStart = triviaList[diagnosticIndex + 1].SpanStart; var nextNode = root.FindTrivia(nextStart, true); if (nextNode.IsKind(SyntaxKind.EndOfLineTrivia) && !hasLeadingContent) { nodesToRemove.Add(nextNode); } } // Replace all roots with an empty node var newRoot = root.ReplaceTrivia(nodesToRemove, (original, rewritten) => { return(new SyntaxTrivia()); }); newRoot = newRoot.NormalizeWhitespace(); Document updatedDocument = document.WithSyntaxRoot(newRoot); return(updatedDocument); }
private static async Task <Document> GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) { var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var trivia = root.FindTrivia(diagnostic.Location.SourceSpan.Start, true); int diagnosticIndex = 0; var triviaList = TriviaHelper.GetContainingTriviaList(trivia, out diagnosticIndex); var triviaToRemove = new List <SyntaxTrivia>(); triviaToRemove.Add(trivia); bool hasTrailingContent = TriviaHasTrailingContentOnLine(root, trivia); if (!hasTrailingContent && diagnosticIndex > 0) { var previousTrivia = triviaList[diagnosticIndex - 1]; if (previousTrivia.IsKind(SyntaxKind.WhitespaceTrivia)) { triviaToRemove.Add(previousTrivia); } } bool hasLeadingContent = TriviaHasLeadingContentOnLine(root, trivia); if (!hasLeadingContent && diagnosticIndex < triviaList.Count - 1) { var nextTrivia = triviaList[diagnosticIndex + 1]; if (nextTrivia.IsKind(SyntaxKind.EndOfLineTrivia)) { triviaToRemove.Add(nextTrivia); } } // Replace all roots with an empty node var newRoot = root.ReplaceTrivia(triviaToRemove, (original, rewritten) => { return(default(SyntaxTrivia)); }); Document updatedDocument = document.WithSyntaxRoot(newRoot); return(updatedDocument); }
private static void HandleSyntaxTreeAnalysis(SyntaxTreeAnalysisContext context, ImmutableDictionary <string, ReportDiagnostic> specificDiagnosticOptions) { var syntaxRoot = context.Tree.GetRoot(context.CancellationToken); foreach (var trivia in syntaxRoot.DescendantTrivia().Where(trivia => trivia.IsKind(SyntaxKind.SingleLineCommentTrivia))) { if (trivia.ToString().StartsWith("////", StringComparison.Ordinal)) { // ignore commented out code continue; } int triviaIndex; // PERF: Explicitly cast to IReadOnlyList so we only box once. var triviaList = TriviaHelper.GetContainingTriviaList(trivia, out triviaIndex); if (!IsOnOwnLine(triviaList, triviaIndex)) { // ignore comments after other code elements. continue; } if (IsPartOfFileHeader(triviaList, triviaIndex)) { // ignore comments that are part of the file header. continue; } var trailingBlankLineCount = GetTrailingBlankLineCount(triviaList, ref triviaIndex); if (trailingBlankLineCount == 0) { // ignore comments that are not followed by a blank line continue; } else if (trailingBlankLineCount > 1) { if (specificDiagnosticOptions.GetValueOrDefault(SA1507CodeMustNotContainMultipleBlankLinesInARow.DiagnosticId, ReportDiagnostic.Default) != ReportDiagnostic.Suppress) { // ignore comments that are followed by multiple blank lines -> the multiple blank lines will be reported by SA1507 continue; } } else { if (triviaIndex < triviaList.Count) { switch (triviaList[triviaIndex].Kind()) { case SyntaxKind.SingleLineCommentTrivia: case SyntaxKind.SingleLineDocumentationCommentTrivia: case SyntaxKind.MultiLineCommentTrivia: case SyntaxKind.MultiLineDocumentationCommentTrivia: // ignore a single blank line in between two comments. continue; } } } var diagnosticSpan = TextSpan.FromBounds(trivia.SpanStart, trivia.SpanStart + 2); context.ReportDiagnostic(Diagnostic.Create(Descriptor, Location.Create(context.Tree, diagnosticSpan))); } }
private static void HandleDeclaration(SyntaxNodeAnalysisContext context) { var nodeTriviaList = context.Node.GetLeadingTrivia(); var documentationHeaderIndex = context.Node.GetLeadingTrivia().IndexOf(SyntaxKind.SingleLineDocumentationCommentTrivia); if (documentationHeaderIndex == -1) { // there is no documentation header. return; } var documentationHeader = nodeTriviaList[documentationHeaderIndex]; var triviaList = TriviaHelper.GetContainingTriviaList(documentationHeader, out documentationHeaderIndex); var eolCount = 0; var done = false; for (var i = documentationHeaderIndex - 1; !done && (i >= 0); i--) { var trivia = triviaList[i]; if (trivia.IsDirective && !trivia.IsKind(SyntaxKind.EndIfDirectiveTrivia) && !trivia.IsKind(SyntaxKind.RegionDirectiveTrivia) && !trivia.IsKind(SyntaxKind.EndRegionDirectiveTrivia)) { return; } switch (trivia.Kind()) { case SyntaxKind.WhitespaceTrivia: break; case SyntaxKind.EndOfLineTrivia: eolCount++; break; case SyntaxKind.EndIfDirectiveTrivia: case SyntaxKind.RegionDirectiveTrivia: case SyntaxKind.EndRegionDirectiveTrivia: eolCount++; done = true; break; default: done = true; break; } } if (eolCount >= 2) { // there is a blank line available return; } if (!done) { var prevToken = documentationHeader.Token.GetPreviousToken(); if (prevToken.IsKind(SyntaxKind.OpenBraceToken)) { // no leading blank line necessary at start of scope. return; } } context.ReportDiagnostic(Diagnostic.Create(Descriptor, GetDiagnosticLocation(documentationHeader))); }