【问题标题】:Extract source comments from a Scala source file从 Scala 源文件中提取源注释
【发布时间】:2021-11-28 17:54:23
【问题描述】:

我想以编程方式从 Scala 源文件中提取代码 cmets。

我可以访问我感兴趣的 cmets 的源文件和类的对象。我也愿意以特定形式在 Scala 源文件中编写 cmets 以方便提取(尽管仍然遵循 Scaladoc 约定) .

具体来说,我不是在寻找 HTML 或类似的输出。

但是,我可以遍历一个 json 对象以获取每个字段的 cmets 就可以了(尽管 json 不是必需的)。理想情况下,我想获得一个类或类成员的评论,给出它的“完全限定”名称或类的对象。

我怎样才能最好地做到这一点?我希望有一个从 Scala 2.11 到 Scala 3 的可维护(无需太多努力)的解决方案。

感谢所有帮助!

【问题讨论】:

    标签: scala javadoc scaladoc doclet


    【解决方案1】:

    我可以访问两个源文件

    据此,我假设您拥有该文件的路径,我将在我的代码中将其表示为:

    val pathToFile: String = ???
    

    TL;DR

    import scala.io.Source
    
    def comments(pathToFile: String): List[String] = {
      def lines: Iterator[(String, Int)] = Source.fromFile(pathToFile).getLines().zipWithIndex
    
      val singleLineJavaDocStartAndEnds = lines.filter {
        case (line, lineNumber) => line.contains("/*") && line.contains("*/")
      }.map { case (line, _) => line }
    
      val javaDocComments = lines.filter {
        case (line, lineNumber) =>
          (line.contains("/*") && !line.contains("*/")) ||
          (!line.contains("/*") && line.contains("*/"))
      }
      .grouped(2).map {
        case Seq((_, firstLineNumber), (_, secondLineNumber)) =>
          lines
            .map { case (line, _) => line }
            .slice(firstLineNumber, secondLineNumber+1)
            .mkString("\n")
      }
    
      val slashSlashComments = lines
        .filter { case (line, _) => line.contains("//") }
        .map { case (line, _) => line }
    
      (singleLineJavaDocStartAndEnds ++ javaDocComments ++ slashSlashComments).toList
    }
    

    完整解释

    首先要做的是读取文件的内容:

    import scala.io.Source
    
    def lines: Iterator[String]  = Source.fromFile(pathToFile).getLines()
    
    // here we preserve new lines, for Windows you may need to replace "\n" with "\r\n
    val content: String = lines.mkString("\n")
    // where `content` is the whole file as a `String`
    

    我已将lines 设为def,以防止在多次调用lines 时出现意外结果。这是由于Source.fromFile 的返回类型以及它如何处理对文件的迭代。 This comment here adds an explanation。由于您正在读取源代码文件,我认为重新读取文件是一种安全操作,不会导致内存或性能问题。

    现在我们有了文件的content,我们可以开始过滤我们不关心的行。另一种看待问题的方式是,我们只想保留 - 过滤 - 属于 cmets 的行。


    编辑

    正如@jwvh 正确指出的那样,我在哪里使用.trim.startsWith 忽略了cmets,例如:

    val x = 1 //mid-code-comments
    
    /*fullLineComment*/
    

    为了解决这个问题,我将.trim.startsWith 替换为.contains


    对于单行 cmets,这很简单:

    val slashComments: Iterator[String] = lines.filter(line => line.contains("//"))
    

    请注意上面对.trim 的调用,这很重要,因为开发人员经常启动旨在匹配代码缩进的cmets。 trim 删除字符串开头的所有空白字符。 现在使用 .contains 捕获任何从任何位置开始的注释行。


    现在我们将归档多行 cmets 或 JavaDoc;例如(内容不重要):

    /**
     * Class String is special cased within the Serialization Stream Protocol.
     *
     * A String instance is written into an ObjectOutputStream according to
     * .....
     * .....
     */
    

    最安全的做法是细化/**/ 出现的行,并包括其间的所有行:

    def lines: Iterator[(String, Int)] = Source.fromFile(pathToFile).getLines().zipWithIndex
    
    val javaDocStartAndEnds: Iterator[(String, Int)] = lines.filter { 
      case (line, lineNumber) => line.contains("/*") || line.contains("*/")
    }
    

    .zipWithIndex 在每一行旁边给我们一个递增的数字。我们可以使用这些来表示源文件的行号。目前,这将为我们提供包含/**/ 的行列表。我们需要将group 分成 2 个一组,因为所有这些类型的 cmets 都会有一对匹配的 /**/。一旦我们有了这些组,我们就可以使用slice 选择从第一个索引到最后一个索引的所有lines。我们想要包含最后一行,所以我们对它执行+1

    val javaDocComments = javaDocStartAndEnds.grouped(2).map {
      case Seq((_, firstLineNumber), (_, secondLineNumber)) =>
        lines // re-calling `def lines: Iterator[(String, Int)]`
          .map { case (line, _) => line } // here we only care about the `line`, not the `lineNumber`
          .slice(firstLineNumber, secondLineNumber+1)
          .mkString("\n")
      }
    

    最后我们可以结合slashCommentsjavaDocComments

    val comments: List[String] = (slashComments ++ javaDocComments).toList
    

    无论我们加入它们的顺序如何,它们都不会出现在有序列表中。可以在此处进行的改进是在最后保留 lineNumberorder

    我将在顶部添加一个“太长;未阅读”(TL;DR) 版本,这样任何人都可以直接复制完整的代码,而无需逐步解释。


    我怎样才能最好地做到这一点?我希望有一个从 Scala 2.11 到 Scala 3 的可维护(无需太多努力)的解决方案。

    我希望我已经回答了您的问题并提供了有用的解决方案。 您提到了一个 JSON 文件作为输出。我提供的是您可以处理的内存中的List[String]。如果需要输出到 JSON,我可以用这个更新我的答案。

    【讨论】:

    • 所有val x = 1 //mid-code-comments 都被遗漏了,而这个/*fullLineComment*/ 会抛出你的多行计算。
    • 嗨@jwvh。这是非常真实的!我将更新我的答案,将.trim.startsWith 替换为contains
    • @skm 请告诉我你是如何尝试这个解决方案的:)
    • 您确实应该在发布之前测试您的代码。当前迭代因多个代码/注释组合而失败。此外,完全不清楚(现在沉默的)OP 是否需要一个简单的文本解析器。这个关于“给定......类的对象”的业务充其量是令人困惑的。
    • 谢谢@jvwh。你当然是对的。我可以看到 /*fullLineComment*/ 在上一个版本中没有处理。您能否举出任何其他“代码/注释组合” 的示例,我会更新。我打算使示例尽可能简单,因此只处理最常见的情况。有些东西仍然会在网上流传,例如包含// 的 URL,并且会出现。
    猜你喜欢
    • 2010-10-20
    • 2016-04-03
    • 1970-01-01
    • 1970-01-01
    • 2020-12-31
    • 1970-01-01
    • 2015-03-07
    • 1970-01-01
    相关资源
    最近更新 更多