From da1774df8d2921073304481e93278ddfd997aae4 Mon Sep 17 00:00:00 2001 From: Jakob Odersky Date: Mon, 25 Apr 2016 15:00:09 -0700 Subject: Parse java comments in scaladoc --- src/compiler/scala/tools/nsc/Global.scala | 4 +- .../tools/nsc/ast/parser/SyntaxAnalyzer.scala | 2 +- .../scala/tools/nsc/javac/JavaParsers.scala | 13 ++- .../scala/tools/nsc/javac/JavaScanners.scala | 38 +++++---- .../scala/tools/nsc/doc/ScaladocAnalyzer.scala | 94 +++++++++++++++------- .../scala/tools/nsc/doc/ScaladocGlobal.scala | 1 + 6 files changed, 103 insertions(+), 49 deletions(-) diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index 7417d9c09d..90532666b2 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -411,7 +411,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter) override val initial = true } - import syntaxAnalyzer.{ UnitScanner, UnitParser } + import syntaxAnalyzer.{ UnitScanner, UnitParser, JavaUnitParser } // !!! I think we're overdue for all these phase objects being lazy vals. // There's no way for a Global subclass to provide a custom typer @@ -1042,6 +1042,8 @@ class Global(var currentSettings: Settings, var reporter: Reporter) def newUnitParser(code: String, filename: String = ""): UnitParser = newUnitParser(newCompilationUnit(code, filename)) + def newJavaUnitParser(unit: CompilationUnit): JavaUnitParser = new JavaUnitParser(unit) + /** A Run is a single execution of the compiler on a set of units. */ class Run extends RunContextApi with RunReporting with RunParsing { diff --git a/src/compiler/scala/tools/nsc/ast/parser/SyntaxAnalyzer.scala b/src/compiler/scala/tools/nsc/ast/parser/SyntaxAnalyzer.scala index df2073785b..e0667b5a3e 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/SyntaxAnalyzer.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/SyntaxAnalyzer.scala @@ -82,7 +82,7 @@ abstract class SyntaxAnalyzer extends SubComponent with Parsers with MarkupParse } private def initialUnitBody(unit: CompilationUnit): Tree = { - if (unit.isJava) new JavaUnitParser(unit).parse() + if (unit.isJava) newJavaUnitParser(unit).parse() else if (currentRun.parsing.incompleteHandled) newUnitParser(unit).parse() else newUnitParser(unit).smartParse() } diff --git a/src/compiler/scala/tools/nsc/javac/JavaParsers.scala b/src/compiler/scala/tools/nsc/javac/JavaParsers.scala index cc851b6330..13ffe631f4 100644 --- a/src/compiler/scala/tools/nsc/javac/JavaParsers.scala +++ b/src/compiler/scala/tools/nsc/javac/JavaParsers.scala @@ -135,6 +135,11 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners { DefDef(Modifiers(Flags.JAVA), nme.CONSTRUCTOR, List(), List(vparams), TypeTree(), blankExpr) } + /** A hook for joining the comment associated with a definition. + * Overridden by scaladoc. + */ + def joinComment(trees: => List[Tree]): List[Tree] = trees + // ------------- general parsing --------------------------- /** skip parent or brace enclosed sequence of things */ @@ -739,7 +744,7 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners { } val sdefs = statics.toList val idefs = members.toList ::: (sdefs flatMap forwarders) - (sdefs, idefs) + (sdefs, joinComment(idefs)) } def annotationParents = List( gen.scalaAnnotationDot(tpnme.Annotation), @@ -833,10 +838,10 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners { } def typeDecl(mods: Modifiers): List[Tree] = in.token match { - case ENUM => enumDecl(mods) - case INTERFACE => interfaceDecl(mods) + case ENUM => joinComment(enumDecl(mods)) + case INTERFACE => joinComment(interfaceDecl(mods)) case AT => annotationDecl(mods) - case CLASS => classDecl(mods) + case CLASS => joinComment(classDecl(mods)) case _ => in.nextToken(); syntaxError("illegal start of type declaration", skipIt = true); List(errorTypeTree) } diff --git a/src/compiler/scala/tools/nsc/javac/JavaScanners.scala b/src/compiler/scala/tools/nsc/javac/JavaScanners.scala index ac86dfd665..d2c58ae5f9 100644 --- a/src/compiler/scala/tools/nsc/javac/JavaScanners.scala +++ b/src/compiler/scala/tools/nsc/javac/JavaScanners.scala @@ -577,21 +577,29 @@ trait JavaScanners extends ast.parser.ScannersCommon { } } - protected def skipComment(): Boolean = { - @tailrec def skipLineComment(): Unit = in.ch match { - case CR | LF | SU => - case _ => in.next; skipLineComment() - } - @tailrec def skipJavaComment(): Unit = in.ch match { - case SU => incompleteInputError("unclosed comment") - case '*' => in.next; if (in.ch == '/') in.next else skipJavaComment() - case _ => in.next; skipJavaComment() - } - in.ch match { - case '/' => in.next ; skipLineComment() ; true - case '*' => in.next ; skipJavaComment() ; true - case _ => false - } + protected def putCommentChar(): Unit = in.next() + + protected def skipBlockComment(isDoc: Boolean): Unit = in.ch match { + case SU => incompleteInputError("unclosed comment") + case '*' => putCommentChar() ; if (in.ch == '/') putCommentChar() else skipBlockComment(isDoc) + case _ => putCommentChar() ; skipBlockComment(isDoc) + } + + protected def skipLineComment(): Unit = in.ch match { + case CR | LF | SU => + case _ => putCommentChar() ; skipLineComment() + } + + protected def skipComment(): Boolean = in.ch match { + case '/' => putCommentChar() ; skipLineComment() ; true + case '*' => + putCommentChar() + in.ch match { + case '*' => skipBlockComment(isDoc = true) + case _ => skipBlockComment(isDoc = false) + } + true + case _ => false } // Identifiers --------------------------------------------------------------- diff --git a/src/scaladoc/scala/tools/nsc/doc/ScaladocAnalyzer.scala b/src/scaladoc/scala/tools/nsc/doc/ScaladocAnalyzer.scala index 8ea8c4deff..a16b306322 100644 --- a/src/scaladoc/scala/tools/nsc/doc/ScaladocAnalyzer.scala +++ b/src/scaladoc/scala/tools/nsc/doc/ScaladocAnalyzer.scala @@ -103,48 +103,85 @@ abstract class ScaladocSyntaxAnalyzer[G <: Global](val global: G) extends Syntax class ScaladocJavaUnitParser(unit: CompilationUnit) extends { override val in = new ScaladocJavaUnitScanner(unit) - } with JavaUnitParser(unit) { } + } with JavaUnitParser(unit) { + + override def joinComment(trees: => List[Tree]): List[Tree] = { + val doc = in.flushDoc() + + if ((doc ne null) && doc.raw.length > 0) { + log(s"joinComment(doc=$doc)") + val joined = trees map { + t => + DocDef(doc, t) setPos { + if (t.pos.isDefined) { + val pos = doc.pos.withEnd(t.pos.end) + // always make the position transparent + pos.makeTransparent + } else { + t.pos + } + } + } + joined.find(_.pos.isOpaqueRange) foreach { + main => + val mains = List(main) + joined foreach { t => if (t ne main) ensureNonOverlapping(t, mains) } + } + joined + } + else trees + + // trees + } + + + } class ScaladocJavaUnitScanner(unit: CompilationUnit) extends JavaUnitScanner(unit) { + + //is the scanner currently in a doc comment + var inDocComment = false + var docStart: Int = 0 + /** buffer for the documentation comment */ - var docBuffer: StringBuilder = null + val docBuffer: StringBuilder = new StringBuilder + + var lastDoc: DocComment = null + def flushDoc() = try lastDoc finally lastDoc = null + /** add the given character to the documentation buffer */ - protected def putDocChar(c: Char) { - if (docBuffer ne null) docBuffer.append(c) + override protected def putCommentChar(): Unit = { + if (inDocComment) docBuffer append in.ch + in.next + } + + override protected def skipBlockComment(isDoc: Boolean): Unit = { + if (!inDocComment && isDoc) { + docBuffer append "/*" // at this point, in.ch == * and will be appended next + docStart = in.cpos + inDocComment = true + } + super.skipBlockComment(isDoc) } override protected def skipComment(): Boolean = { - if (in.ch == '/') { - do { - in.next - } while ((in.ch != CR) && (in.ch != LF) && (in.ch != SU)) - true - } else if (in.ch == '*') { - docBuffer = null - in.next - val scaladoc = ("/**", "*/") - if (in.ch == '*') - docBuffer = new StringBuilder(scaladoc._1) - do { - do { - if (in.ch != '*' && in.ch != SU) { - in.next; putDocChar(in.ch) - } - } while (in.ch != '*' && in.ch != SU) - while (in.ch == '*') { - in.next; putDocChar(in.ch) - } - } while (in.ch != '/' && in.ch != SU) - if (in.ch == '/') in.next - else incompleteInputError("unclosed comment") + val skipped = super.skipComment() + if (skipped && inDocComment) { + val str = docBuffer.toString + docBuffer.setLength(0) //clear buffer + val p = Position.range(unit.source, docStart - 2, docStart - 2, in.cpos) + signalParsedDocComment(str, p) + lastDoc = DocComment(str, p) + inDocComment = false true } else { - false + skipped } } + } class ScaladocUnitScanner(unit0: CompilationUnit, patches0: List[BracePatch]) extends UnitScanner(unit0, patches0) { @@ -230,6 +267,7 @@ abstract class ScaladocSyntaxAnalyzer[G <: Global](val global: G) extends Syntax } } class ScaladocUnitParser(unit: CompilationUnit, patches: List[BracePatch]) extends UnitParser(unit, patches) { + override def newScanner() = new ScaladocUnitScanner(unit, patches) override def withPatches(patches: List[BracePatch]) = new ScaladocUnitParser(unit, patches) diff --git a/src/scaladoc/scala/tools/nsc/doc/ScaladocGlobal.scala b/src/scaladoc/scala/tools/nsc/doc/ScaladocGlobal.scala index 10bfe6f94b..107d81f78a 100644 --- a/src/scaladoc/scala/tools/nsc/doc/ScaladocGlobal.scala +++ b/src/scaladoc/scala/tools/nsc/doc/ScaladocGlobal.scala @@ -13,6 +13,7 @@ trait ScaladocGlobalTrait extends Global { override val useOffsetPositions = false override def newUnitParser(unit: CompilationUnit) = new syntaxAnalyzer.ScaladocUnitParser(unit, Nil) + override def newJavaUnitParser(unit: CompilationUnit) = new syntaxAnalyzer.ScaladocJavaUnitParser(unit) override lazy val syntaxAnalyzer = new ScaladocSyntaxAnalyzer[outer.type](outer) { val runsAfter = List[String]() -- cgit v1.2.3