using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Semmle.Extraction.CSharp.Entities; using System; namespace Semmle.Extraction.CSharp.Populators { /// /// Populators for comments. /// internal static class CommentPopulator { public static void ExtractCommentBlocks(Context cx, CommentProcessor gen) { cx.Try(null, null, () => { gen.GenerateBindings((entity, duplicationGuardKey, block, binding) => { var commentBlock = Entities.CommentBlock.Create(cx, block); Action a = () => { commentBlock.BindTo(entity, binding); }; // When the duplication guard key exists, it means that the entity is guarded against // trap duplication (). // We must therefore also guard comment construction. if (duplicationGuardKey is not null) cx.WithDuplicationGuard(duplicationGuardKey, a); else a(); }); }); } public static void ExtractComment(Context cx, SyntaxTrivia trivia) { switch (trivia.Kind()) { case SyntaxKind.SingleLineDocumentationCommentTrivia: /* This is actually a multi-line comment consisting of /// lines. So split it up. */ var text = trivia.ToFullString(); var split = text.Split('\n'); var currentLocation = trivia.GetLocation().SourceSpan.Start - 3; for (var line = 0; line < split.Length - 1; ++line) { var fullLine = split[line]; var nextLineLocation = currentLocation + fullLine.Length + 1; fullLine = fullLine.TrimEnd('\r'); var trimmedLine = fullLine; var leadingSpaces = trimmedLine.IndexOf('/'); if (leadingSpaces != -1) { fullLine = fullLine.Substring(leadingSpaces); currentLocation += leadingSpaces; trimmedLine = trimmedLine.Substring(leadingSpaces + 3); // Remove leading spaces and the "///" trimmedLine = trimmedLine.Trim(); var span = Microsoft.CodeAnalysis.Text.TextSpan.FromBounds(currentLocation, currentLocation + fullLine.Length); var location = Microsoft.CodeAnalysis.Location.Create(trivia.SyntaxTree!, span); var commentType = CommentLineType.XmlDoc; cx.CommentGenerator.AddComment(CommentLine.Create(cx, location, commentType, trimmedLine, fullLine)); } else { cx.ModelError("Unexpected comment format"); } currentLocation = nextLineLocation; } break; case SyntaxKind.SingleLineCommentTrivia: { var contents = trivia.ToString().Substring(2); var commentType = CommentLineType.Singleline; if (contents.Length > 0 && contents[0] == '/') { commentType = CommentLineType.XmlDoc; contents = contents.Substring(1); // An XML comment. } cx.CommentGenerator.AddComment(CommentLine.Create(cx, trivia.GetLocation(), commentType, contents.Trim(), trivia.ToFullString())); } break; case SyntaxKind.MultiLineDocumentationCommentTrivia: case SyntaxKind.MultiLineCommentTrivia: /* We receive a single SyntaxTrivia for a multiline block spanning several lines. So we split it into separate lines */ text = trivia.ToFullString(); split = text.Split('\n'); currentLocation = trivia.GetLocation().SourceSpan.Start; for (var line = 0; line < split.Length; ++line) { var fullLine = split[line]; var nextLineLocation = currentLocation + fullLine.Length + 1; fullLine = fullLine.TrimEnd('\r'); var trimmedLine = fullLine; if (line == 0) trimmedLine = trimmedLine.Substring(2); if (line == split.Length - 1) trimmedLine = trimmedLine.Substring(0, trimmedLine.Length - 2); trimmedLine = trimmedLine.Trim(); var span = Microsoft.CodeAnalysis.Text.TextSpan.FromBounds(currentLocation, currentLocation + fullLine.Length); var location = Microsoft.CodeAnalysis.Location.Create(trivia.SyntaxTree!, span); var commentType = line == 0 ? CommentLineType.Multiline : CommentLineType.MultilineContinuation; cx.CommentGenerator.AddComment(CommentLine.Create(cx, location, commentType, trimmedLine, fullLine)); currentLocation = nextLineLocation; } break; } } } }