C#: Separate visitors to dedicated files, rename and reorganize comment extraction related classes

This commit is contained in:
Tamas Vajk
2021-01-19 11:09:30 +01:00
parent cf860f1dac
commit 3be229f097
7 changed files with 205 additions and 195 deletions

View File

@@ -374,9 +374,9 @@ namespace Semmle.Extraction.CSharp
if (!upToDate)
{
var cx = extractor.CreateContext(compilation.Clone(), trapWriter, new SourceScope(tree), AddAssemblyTrapPrefix);
Populators.CompilationUnit.Extract(cx, tree.GetRoot());
CompilationUnitVisitor.Extract(cx, tree.GetRoot());
cx.PopulateAll();
cx.ExtractComments(cx.CommentGenerator);
TriviaPopulator.ExtractCommentBlocks(cx, cx.CommentGenerator);
cx.PopulateAll();
}
}

View File

@@ -21,97 +21,6 @@ namespace Semmle.Extraction.CSharp.Entities
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)
@@ -131,7 +40,7 @@ namespace Semmle.Extraction.CSharp.Entities
trapFile.Write(";commentline");
}
private static CommentLine Create(Context cx, Microsoft.CodeAnalysis.Location loc, CommentLineType type, string text, string raw)
internal 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);

View File

@@ -1,33 +0,0 @@
using Semmle.Extraction.CommentProcessing;
using System;
namespace Semmle.Extraction.CSharp.Populators
{
/// <summary>
/// Populators for comments.
/// </summary>
public static class Comments
{
public static void ExtractComments(this Context cx, ICommentGenerator 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 (<see cref = "Context.BindComments(IEntity, Location)" />).
// We must therefore also guard comment construction.
if (duplicationGuardKey != null)
cx.WithDuplicationGuard(duplicationGuardKey, a);
else
a();
});
});
}
}
}

View File

@@ -0,0 +1,51 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Semmle.Util.Logging;
namespace Semmle.Extraction.CSharp.Populators
{
internal class CompilationUnitVisitor : TypeOrNamespaceVisitor
{
private CompilationUnitVisitor(Context cx)
: base(cx, cx.TrapWriter.Writer, null) { }
public override void VisitExternAliasDirective(ExternAliasDirectiveSyntax node)
{
// This information is not yet extracted.
cx.ExtractionError("Not implemented extern alias directive", node.ToFullString(), Extraction.Entities.Location.Create(cx, node.GetLocation()), "", Severity.Info);
}
public override void VisitCompilationUnit(CompilationUnitSyntax compilationUnit)
{
foreach (var m in compilationUnit.ChildNodes())
{
cx.Try(m, null, () => ((CSharpSyntaxNode)m).Accept(this));
}
// Gather comments:
foreach (var trivia in compilationUnit.DescendantTrivia(compilationUnit.Span))
{
TriviaPopulator.ExtractTrivia(cx, trivia);
}
foreach (var trivia in compilationUnit.GetLeadingTrivia())
{
TriviaPopulator.ExtractTrivia(cx, trivia);
}
foreach (var trivia in compilationUnit.GetTrailingTrivia())
{
TriviaPopulator.ExtractTrivia(cx, trivia);
}
}
public static void Extract(Context cx, SyntaxNode unit)
{
// Ensure that the file itself is populated in case the source file is totally empty
Extraction.Entities.File.Create(cx, unit.SyntaxTree.FilePath);
((CSharpSyntaxNode)unit).Accept(new CompilationUnitVisitor(cx));
}
}
}

View File

@@ -0,0 +1,127 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Semmle.Extraction.CommentProcessing;
using Semmle.Extraction.CSharp.Entities;
using System;
namespace Semmle.Extraction.CSharp.Populators
{
/// <summary>
/// Populators for trivias.
/// </summary>
public static class TriviaPopulator
{
public static void ExtractCommentBlocks(Context cx, ICommentGenerator 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 (<see cref = "Context.BindComments(IEntity, Location)" />).
// We must therefore also guard comment construction.
if (duplicationGuardKey != null)
cx.WithDuplicationGuard(duplicationGuardKey, a);
else
a();
});
});
}
public static void ExtractTrivia(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;
// Strangely, these are reported as SingleLineCommentTrivia.
case SyntaxKind.DocumentationCommentExteriorTrivia:
cx.ModelError($"Unhandled comment type {trivia.Kind()} for {trivia}");
break;
}
}
}
}

View File

@@ -1,10 +1,7 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Semmle.Extraction.CSharp.Entities;
using Semmle.Extraction.Entities;
using Semmle.Util;
using Semmle.Util.Logging;
using System;
using System.Collections.Generic;
using System.IO;
@@ -82,69 +79,4 @@ namespace Semmle.Extraction.CSharp.Populators
}
}
}
internal class TypeOrNamespaceVisitor : TypeContainerVisitor
{
public TypeOrNamespaceVisitor(Context cx, TextWriter trapFile, IEntity parent)
: base(cx, trapFile, parent) { }
public override void VisitUsingDirective(UsingDirectiveSyntax usingDirective)
{
// Only deal with "using namespace" not "using X = Y"
if (usingDirective.Alias == null)
new UsingDirective(cx, usingDirective, (NamespaceDeclaration)parent);
}
public override void VisitNamespaceDeclaration(NamespaceDeclarationSyntax node)
{
NamespaceDeclaration.Create(cx, node, (NamespaceDeclaration)parent);
}
}
internal class CompilationUnitVisitor : TypeOrNamespaceVisitor
{
public CompilationUnitVisitor(Context cx)
: base(cx, cx.TrapWriter.Writer, null) { }
public override void VisitExternAliasDirective(ExternAliasDirectiveSyntax node)
{
// This information is not yet extracted.
cx.ExtractionError("Not implemented extern alias directive", node.ToFullString(), Extraction.Entities.Location.Create(cx, node.GetLocation()), "", Severity.Info);
}
public override void VisitCompilationUnit(CompilationUnitSyntax compilationUnit)
{
foreach (var m in compilationUnit.ChildNodes())
{
cx.Try(m, null, () => ((CSharpSyntaxNode)m).Accept(this));
}
// Gather comments:
foreach (var trivia in compilationUnit.DescendantTrivia(compilationUnit.Span))
{
CommentLine.Extract(cx, trivia);
}
foreach (var trivia in compilationUnit.GetLeadingTrivia())
{
CommentLine.Extract(cx, trivia);
}
foreach (var trivia in compilationUnit.GetTrailingTrivia())
{
CommentLine.Extract(cx, trivia);
}
}
}
public class CompilationUnit
{
public static void Extract(Context cx, SyntaxNode unit)
{
// Ensure that the file itself is populated in case the source file is totally empty
Semmle.Extraction.Entities.File.Create(cx, unit.SyntaxTree.FilePath);
((CSharpSyntaxNode)unit).Accept(new CompilationUnitVisitor(cx));
}
}
}

View File

@@ -0,0 +1,24 @@
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Semmle.Extraction.CSharp.Entities;
using System.IO;
namespace Semmle.Extraction.CSharp.Populators
{
internal class TypeOrNamespaceVisitor : TypeContainerVisitor
{
public TypeOrNamespaceVisitor(Context cx, TextWriter trapFile, IEntity parent)
: base(cx, trapFile, parent) { }
public override void VisitUsingDirective(UsingDirectiveSyntax usingDirective)
{
// Only deal with "using namespace" not "using X = Y"
if (usingDirective.Alias == null)
new UsingDirective(cx, usingDirective, (NamespaceDeclaration)parent);
}
public override void VisitNamespaceDeclaration(NamespaceDeclarationSyntax node)
{
NamespaceDeclaration.Create(cx, node, (NamespaceDeclaration)parent);
}
}
}