Files
codeql/csharp/extractor/Semmle.Extraction.CSharp/Entities/CommentLine.cs
2020-10-14 14:44:01 +02:00

151 lines
7.0 KiB
C#

using Semmle.Extraction.CommentProcessing;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Semmle.Extraction.Entities;
using System.IO;
namespace Semmle.Extraction.CSharp.Entities
{
internal class CommentLine : CachedEntity<(Microsoft.CodeAnalysis.Location, string)>, ICommentLine
{
private CommentLine(Context cx, Microsoft.CodeAnalysis.Location loc, CommentLineType type, string text, string raw)
: base(cx, (loc, text))
{
Type = type;
RawText = raw;
}
public Microsoft.CodeAnalysis.Location Location => symbol.Item1;
public CommentLineType Type { get; private set; }
public string Text { get { return symbol.Item2; } }
public string RawText { get; private set; }
public static void Extract(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(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(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(Create(cx, location, commentType, trimmedLine, fullLine));
currentLocation = nextLineLocation;
}
break;
// Strangely, these are reported as SingleLineCommentTrivia.
case SyntaxKind.DocumentationCommentExteriorTrivia:
cx.ModelError($"Unhandled comment type {trivia.Kind()} for {trivia}");
break;
}
}
private Extraction.Entities.Location location;
public override void Populate(TextWriter trapFile)
{
location = Context.Create(Location);
trapFile.commentline(this, Type == CommentLineType.MultilineContinuation ? CommentLineType.Multiline : Type, Text, RawText);
trapFile.commentline_location(this, location);
}
public override Microsoft.CodeAnalysis.Location ReportingLocation => location.symbol;
public override bool NeedsPopulation => true;
public override void WriteId(TextWriter trapFile)
{
trapFile.WriteSubId(Context.Create(Location));
trapFile.Write(";commentline");
}
private static CommentLine Create(Context cx, Microsoft.CodeAnalysis.Location loc, CommentLineType type, string text, string raw)
{
var init = (loc, type, text, raw);
return CommentLineFactory.Instance.CreateEntity(cx, init, init);
}
private class CommentLineFactory : ICachedEntityFactory<(Microsoft.CodeAnalysis.Location, CommentLineType, string, string), CommentLine>
{
public static CommentLineFactory Instance { get; } = new CommentLineFactory();
public CommentLine Create(Context cx, (Microsoft.CodeAnalysis.Location, CommentLineType, string, string) init) =>
new CommentLine(cx, init.Item1, init.Item2, init.Item3, init.Item4);
}
public override TrapStackBehaviour TrapStackBehaviour => TrapStackBehaviour.OptionalLabel;
}
}