Roslyn - 获取分组的单行注释

问题描述 投票:0回答:1

我正在用 C# 编写一个程序,用于从代码中提取注释。我正在使用 Roslyn 编译器来做到这一点。这很棒,因为我只是访问整个抽象语法树并从解决方案中的文件中获取 SingleLineComment trivia、MultiLineComment trivia 和 DocumentationComment trivia 语法。但有一个问题,因为程序员经常写这样的注释:

// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.

您可以看到这是三个单行注释,但我希望它们作为一个注释从代码中获取。我可以通过 Roslyn 实现这一目标吗?或者也许还有其他方法?因为当程序员使用单行注释语法编写多行注释时,这是常见的情况。

我提取评论的代码如下所示:

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Collections.Generic;

namespace RoslynPlay
{
    public class CommentStore
    {
        public List<Comment> Comments { get; } = new List<Comment>();

        public void AddCommentTrivia(SyntaxTrivia trivia,
            LocationStore commentLocationstore, string fileName)
        {
            if (trivia.Kind() == SyntaxKind.SingleLineCommentTrivia)
            {
                Comments.Add(new SingleLineComment(trivia.ToString(),
                    trivia.GetLocation().GetLineSpan().EndLinePosition.Line + 1, commentLocationstore)
                {
                    FileName = fileName,
                });
            }
            else if (trivia.Kind() == SyntaxKind.MultiLineCommentTrivia)
            {
                Comments.Add(new MultiLineComment(trivia.ToString(),
                    trivia.GetLocation().GetLineSpan().StartLinePosition.Line + 1,
                    trivia.GetLocation().GetLineSpan().EndLinePosition.Line + 1, commentLocationstore)
                {
                    FileName = fileName,
                });
            }
        }

        public void AddCommentNode(DocumentationCommentTriviaSyntax node,
            LocationStore commentLocationstore, string fileName)
        {
            Comments.Add(new DocComment(node.ToString(),
                node.GetLocation().GetLineSpan().StartLinePosition.Line + 1,
                node.GetLocation().GetLineSpan().EndLinePosition.Line,
                commentLocationstore)
            {
                FileName = fileName,
            });
        }
    }
}

在主文件(Program.cs)中,我正在从这样的代码中启动注释提取:

    string fileContent;
    SyntaxTree tree;
    SyntaxNode root;
    CommentsWalker commentWalker;
    MethodsAndClassesWalker methodWalker;
    string[] files = Directory.GetFiles(projectPath, $"*.cs", SearchOption.AllDirectories);
    var commentStore = new CommentStore();

    Console.WriteLine("Reading files...");
    ProgressBar progressBar = new ProgressBar(files.Length);

    foreach (var file in files)
    {
        fileContent = File.ReadAllText(file);
        string filePath = new Regex($@"{projectPath}\\(.*)$").Match(file).Groups[1].ToString();
        tree = CSharpSyntaxTree.ParseText(fileContent);
        root = tree.GetRoot();
        commentWalker = new CommentsWalker(filePath, commentStore);
        commentWalker.Visit(root);

        progressBar.UpdateAndDisplay();
    }

这也是评论步行者:

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace RoslynPlay
{
    public class CommentsWalker : CSharpSyntaxWalker
    {
        private string _fileName;
        private CommentStore _commentStore;

        public CommentsWalker(string fileName,
            CommentStore commentStore)
            : base(SyntaxWalkerDepth.StructuredTrivia)
        {
            _fileName = fileName;
            _commentStore = commentStore;
        }

        public override void VisitTrivia(SyntaxTrivia trivia)
        {
            if (trivia.Kind() == SyntaxKind.SingleLineCommentTrivia
                || trivia.Kind() == SyntaxKind.MultiLineCommentTrivia)
            {
                _commentStore.AddCommentTrivia(trivia, _commentLocationStore, _fileName);
            }
            base.VisitTrivia(trivia);
        }

        public override void VisitDocumentationCommentTrivia(DocumentationCommentTriviaSyntax node)
        {
            _commentStore.AddCommentNode(node, _commentLocationStore, _fileName);
            base.VisitDocumentationCommentTrivia(node);
        }
    }
}

问题是因为 trivia.Kind() == SyntaxKind.SingleLineCommentTrivia 只提取单行注释,但我想提取单行注释块作为一条注释。

c# comments roslyn
1个回答
0
投票

为了简洁起见,我省略了部分文档注释

这可以通过跟踪前一个注释块的行结尾来完成。

有一些边缘情况需要处理,但这些情况非常罕见(当单行注释与多行注释相邻时)

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;

internal class Program
{
    private static void Main(string[] args)
    {
        // example code to check for comments
        const string programText =
            @"
            using System;
        using System.Collections;
        using System.Linq;
        using System.Text;

            /* Block start 1
             *
             *
             *
             *
             * Block End 2
             */


        // single line comment 0

        // single line comment 1
        // single line comment 2
        // single line comment 3
        namespace HelloWorld
        {
            /* Block start 3
             *
             *
             *
             *
             * Block End 4
             */
            class Program
            {
                /// documentation 1
                /// documentation 2
                /// documentation 3
                /// documentation 4
                static void Main(string[] args)
                {
                    Console.WriteLine(""Hello, World!"");
                }
            }

            // another comment 1


            // another comment 2
            // another comment 3


            /* Adjacent
             *
             */

            // another comment 4
            // another comment 5

            /* Adjacent
             *
             *
             */
        }";


        SyntaxTree tree = CSharpSyntaxTree.ParseText(programText);
        var root = tree.GetRoot();

        var walker = new CommentsWalker();
        walker.Visit(root);

        // printing out all comments in groups that we found
        foreach (var (start, end) in walker.GetCommentBlocks())
        {
            Console.WriteLine($"Comment block: {start} - {end}");
        }
    }
}

public class CommentsWalker : CSharpSyntaxWalker
{
    // keeps track of all comment blocks in code
    private List<(int start, int end)> commentBlocks;

    // keeps track of current continuous comment block
    // using linked list so it is easy to access first and last
    // line in a comment block
    private LinkedList<(int start, int end)> currentBlock;

    public CommentsWalker()
        : base(SyntaxWalkerDepth.StructuredTrivia)
    {
        commentBlocks = new();
        currentBlock = new();
    }

    public List<(int start, int end)> GetCommentBlocks()
    {
        // before returning all comments
        // we have to ensure that all comment blocks were added
        if (currentBlock.First != null)
            commentBlocks.Add((currentBlock.First.Value.start, currentBlock.Last.Value.end));

        return commentBlocks;
    }

    public override void VisitTrivia(SyntaxTrivia trivia)
    {
        var span = trivia.GetLocation()
            .GetLineSpan();

        // get starting and ending line numbers of a current comment 
        // it can be a multi-line and a single-line comment
        (int start, int end) = (span.StartLinePosition.Line, span.EndLinePosition.Line);

        switch (trivia.Kind())
        {
            case SyntaxKind.SingleLineCommentTrivia:
            {
                // if this is a first comment in a block
                // or if this is a continuation of a previous comment
                if (currentBlock.First == null || currentBlock.Last.Value.end + 1 == start)
                    currentBlock.AddLast((start, end));
                else
                {
                    // this is a new single line comment that isn't adjacent to previous one
                    // add previous block and start a new one with current comment
                    commentBlocks.Add((currentBlock.First.Value.start, currentBlock.Last.Value.end));
                    currentBlock = new();
                    currentBlock.AddFirst((start, end));
                }
            }
            break;

            case SyntaxKind.MultiLineCommentTrivia:
            {
                // treat whole comment block at once
                if (currentBlock.First != null)
                {
                    commentBlocks.Add((currentBlock.First.Value.start, currentBlock.Last.Value.end));
                    currentBlock = new();
                }

                currentBlock.AddFirst((start, end));
            }
            break;
        }

        base.VisitTrivia(trivia);
    }
}

输出示例:

Comment block: 6 - 12
Comment block: 15 - 15
Comment block: 17 - 19
Comment block: 22 - 28
Comment block: 41 - 41
Comment block: 44 - 45
Comment block: 48 - 50
Comment block: 52 - 53
Comment block: 55 - 58

链接到dotnet小提琴

© www.soinside.com 2019 - 2024. All rights reserved.