aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--dottydoc/js/src/html/Layout.scala12
-rw-r--r--dottydoc/jvm/src/dotty/tools/dottydoc/core/Phases.scala3
-rw-r--r--dottydoc/jvm/src/dotty/tools/dottydoc/model/CommentParsers.scala18
-rw-r--r--dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/CommentCooker.scala8
-rw-r--r--dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/CommentExpander.scala344
-rw-r--r--dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/CommentParser.scala2
-rw-r--r--dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/CommentUtils.scala224
-rw-r--r--src/dotty/tools/dotc/ast/Trees.scala7
-rw-r--r--src/dotty/tools/dotc/config/Printers.scala2
-rw-r--r--src/dotty/tools/dotc/core/Contexts.scala7
-rw-r--r--src/dotty/tools/dotc/parsing/Parsers.scala23
-rw-r--r--src/dotty/tools/dotc/parsing/Scanners.scala4
12 files changed, 619 insertions, 35 deletions
diff --git a/dottydoc/js/src/html/Layout.scala b/dottydoc/js/src/html/Layout.scala
index 59d27a15d..a57655d3f 100644
--- a/dottydoc/js/src/html/Layout.scala
+++ b/dottydoc/js/src/html/Layout.scala
@@ -33,7 +33,8 @@ object Index {
span(
cls := "mdl-layout-title",
"Packages"
- )
+ ),
+ packageView
),
main(
cls := "mdl-layout__content",
@@ -50,6 +51,15 @@ object Index {
)
)
+ def packageView = nav(
+ cls := "mdl-navigation",
+ ParsedIndex.packages.keys.flatMap { k =>
+ ParsedIndex.packages(k).children.sortBy(_.name).map { c =>
+ a(cls := "mdl-navigation__link", href := "#", c.name)
+ }
+ }.toList
+ )
+
def companionHref(m: Entity): Option[PackageMember] = {
val pack = m.path.dropRight(1).mkString(".")
println(pack)
diff --git a/dottydoc/jvm/src/dotty/tools/dottydoc/core/Phases.scala b/dottydoc/jvm/src/dotty/tools/dottydoc/core/Phases.scala
index a52572363..f76d013a6 100644
--- a/dottydoc/jvm/src/dotty/tools/dottydoc/core/Phases.scala
+++ b/dottydoc/jvm/src/dotty/tools/dottydoc/core/Phases.scala
@@ -36,8 +36,7 @@ object Phases {
case _ => Nil
}
- val comment =
- ctx.base.docstring(tree.symbol).map(c => Comment(wikiParser.parseHtml(c)))
+ val comment = wikiParser.parseHtml(tree.symbol)
tree match {
/** package */
diff --git a/dottydoc/jvm/src/dotty/tools/dottydoc/model/CommentParsers.scala b/dottydoc/jvm/src/dotty/tools/dottydoc/model/CommentParsers.scala
index dce8f4a69..cc7bf6949 100644
--- a/dottydoc/jvm/src/dotty/tools/dottydoc/model/CommentParsers.scala
+++ b/dottydoc/jvm/src/dotty/tools/dottydoc/model/CommentParsers.scala
@@ -2,14 +2,26 @@ package dotty.tools
package dottydoc
package model
+import dotc.core.Symbols.Symbol
+import dotc.core.Contexts.Context
+
object CommentParsers {
import comment._
import BodyParsers._
sealed class WikiParser
- extends CommentCleaner with CommentParser with CommentCooker {
- def parseHtml(str: String): String =
- parse(clean(str), str).toHtml
+ extends CommentCleaner with CommentParser with CommentExpander {
+ def parseHtml(sym: Symbol)(implicit ctx: Context): Option[Comment]= {
+ println("Original ---------------------")
+ println(ctx.base.docstring(sym).map(_.chrs).getOrElse(""))
+ val expanded = expand(sym)
+ println("Expanded ---------------------")
+ println(expanded)
+ parse(clean(expanded), expanded).toHtml match {
+ case "" => None
+ case x => Some(Comment(x))
+ }
+ }
}
val wikiParser = new WikiParser
diff --git a/dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/CommentCooker.scala b/dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/CommentCooker.scala
deleted file mode 100644
index 4d4474d52..000000000
--- a/dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/CommentCooker.scala
+++ /dev/null
@@ -1,8 +0,0 @@
-package dotty.tools.dottydoc
-package model
-package comment
-
-trait CommentCooker {
- trait Context
- def cook(comment: String)(implicit ctx: Context): String = ""
-}
diff --git a/dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/CommentExpander.scala b/dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/CommentExpander.scala
new file mode 100644
index 000000000..f90e95f33
--- /dev/null
+++ b/dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/CommentExpander.scala
@@ -0,0 +1,344 @@
+/*
+ * Port of DocComment.scala from nsc
+ * @author Martin Odersky
+ * @author Felix Mulder
+ */
+
+package dotty.tools
+package dottydoc
+package model
+package comment
+
+import dotc.config.Printers.dottydoc
+import dotc.core.Contexts.Context
+import dotc.core.Symbols._
+import dotc.core.Flags
+import dotc.util.Positions._
+
+import scala.collection.mutable
+
+trait CommentExpander {
+ import CommentUtils._
+
+ def expand(sym: Symbol)(implicit ctx: Context): String = {
+ defineVariables(sym)
+ expandedDocComment(sym, sym)
+ }
+
+ /** The cooked doc comment of symbol `sym` after variable expansion, or "" if missing.
+ *
+ * @param sym The symbol for which doc comment is returned
+ * @param site The class for which doc comments are generated
+ * @throws ExpansionLimitExceeded when more than 10 successive expansions
+ * of the same string are done, which is
+ * interpreted as a recursive variable definition.
+ */
+ def expandedDocComment(sym: Symbol, site: Symbol, docStr: String = "")(implicit ctx: Context): String = {
+ // when parsing a top level class or module, use the (module-)class itself to look up variable definitions
+ val site1 = if ((sym.is(Flags.Module) || sym.isClass) && site.is(Flags.Package)) sym
+ else site
+ expandVariables(cookedDocComment(sym, docStr), sym, site1)
+ }
+
+ private def template(raw: String): String = {
+ val sections = tagIndex(raw)
+
+ val defines = sections filter { startsWithTag(raw, _, "@define") }
+ val usecases = sections filter { startsWithTag(raw, _, "@usecase") }
+
+ val end = startTag(raw, (defines ::: usecases).sortBy(_._1))
+
+ if (end == raw.length - 2) raw else raw.substring(0, end) + "*/"
+ }
+
+ def defines(raw: String): List[String] = {
+ val sections = tagIndex(raw)
+ val defines = sections filter { startsWithTag(raw, _, "@define") }
+ val usecases = sections filter { startsWithTag(raw, _, "@usecase") }
+ val end = startTag(raw, (defines ::: usecases).sortBy(_._1))
+
+ defines map { case (start, end) => raw.substring(start, end) }
+ }
+
+ private def replaceInheritDocToInheritdoc(docStr: String): String =
+ docStr.replaceAll("""\{@inheritDoc\p{Zs}*\}""", "@inheritdoc")
+
+ /** The cooked doc comment of an overridden symbol */
+ protected def superComment(sym: Symbol)(implicit ctx: Context): Option[String] =
+ allInheritedOverriddenSymbols(sym).iterator map (x => cookedDocComment(x)) find (_ != "")
+
+ private val cookedDocComments = mutable.HashMap[Symbol, String]()
+
+ /** The raw doc comment of symbol `sym`, minus usecase and define sections, augmented by
+ * missing sections of an inherited doc comment.
+ * If a symbol does not have a doc comment but some overridden version of it does,
+ * the doc comment of the overridden version is copied instead.
+ */
+
+ def cookedDocComment(sym: Symbol, docStr: String = "")(implicit ctx: Context): String = cookedDocComments.getOrElseUpdate(sym, {
+ var ownComment =
+ if (docStr.length == 0) ctx.base.docstring(sym).map(c => template(c.chrs)).getOrElse("")
+ else template(docStr)
+ ownComment = replaceInheritDocToInheritdoc(ownComment)
+
+ superComment(sym) match {
+ case None =>
+ // SI-8210 - The warning would be false negative when this symbol is a setter
+ if (ownComment.indexOf("@inheritdoc") != -1 && ! sym.isSetter)
+ dottydoc.println(s"${sym.pos}: the comment for ${sym} contains @inheritdoc, but no parent comment is available to inherit from.")
+ ownComment.replaceAllLiterally("@inheritdoc", "<invalid inheritdoc annotation>")
+ case Some(sc) =>
+ if (ownComment == "") sc
+ else expandInheritdoc(sc, merge(sc, ownComment, sym), sym)
+ }
+ })
+
+ private def isMovable(str: String, sec: (Int, Int)): Boolean =
+ startsWithTag(str, sec, "@param") ||
+ startsWithTag(str, sec, "@tparam") ||
+ startsWithTag(str, sec, "@return")
+
+ def merge(src: String, dst: String, sym: Symbol, copyFirstPara: Boolean = false): String = {
+ val srcSections = tagIndex(src)
+ val dstSections = tagIndex(dst)
+ val srcParams = paramDocs(src, "@param", srcSections)
+ val dstParams = paramDocs(dst, "@param", dstSections)
+ val srcTParams = paramDocs(src, "@tparam", srcSections)
+ val dstTParams = paramDocs(dst, "@tparam", dstSections)
+ val out = new StringBuilder
+ var copied = 0
+ var tocopy = startTag(dst, dstSections dropWhile (!isMovable(dst, _)))
+
+ if (copyFirstPara) {
+ val eop = // end of comment body (first para), which is delimited by blank line, or tag, or end of comment
+ (findNext(src, 0)(src.charAt(_) == '\n')) min startTag(src, srcSections)
+ out append src.substring(0, eop).trim
+ copied = 3
+ tocopy = 3
+ }
+
+ def mergeSection(srcSec: Option[(Int, Int)], dstSec: Option[(Int, Int)]) = dstSec match {
+ case Some((start, end)) =>
+ if (end > tocopy) tocopy = end
+ case None =>
+ srcSec match {
+ case Some((start1, end1)) => {
+ out append dst.substring(copied, tocopy).trim
+ out append "\n"
+ copied = tocopy
+ out append src.substring(start1, end1).trim
+ }
+ case None =>
+ }
+ }
+
+ //TODO: enable this once you know how to get `sym.paramss`
+ /*
+ for (params <- sym.paramss; param <- params)
+ mergeSection(srcParams get param.name.toString, dstParams get param.name.toString)
+ for (tparam <- sym.typeParams)
+ mergeSection(srcTParams get tparam.name.toString, dstTParams get tparam.name.toString)
+
+ mergeSection(returnDoc(src, srcSections), returnDoc(dst, dstSections))
+ mergeSection(groupDoc(src, srcSections), groupDoc(dst, dstSections))
+ */
+
+ if (out.length == 0) dst
+ else {
+ out append dst.substring(copied)
+ out.toString
+ }
+ }
+
+ /**
+ * Expand inheritdoc tags
+ * - for the main comment we transform the inheritdoc into the super variable,
+ * and the variable expansion can expand it further
+ * - for the param, tparam and throws sections we must replace comments on the spot
+ *
+ * This is done separately, for two reasons:
+ * 1. It takes longer to run compared to merge
+ * 2. The inheritdoc annotation should not be used very often, as building the comment from pieces severely
+ * impacts performance
+ *
+ * @param parent The source (or parent) comment
+ * @param child The child (overriding member or usecase) comment
+ * @param sym The child symbol
+ * @return The child comment with the inheritdoc sections expanded
+ */
+ def expandInheritdoc(parent: String, child: String, sym: Symbol): String =
+ if (child.indexOf("@inheritdoc") == -1)
+ child
+ else {
+ val parentSections = tagIndex(parent)
+ val childSections = tagIndex(child)
+ val parentTagMap = sectionTagMap(parent, parentSections)
+ val parentNamedParams = Map() +
+ ("@param" -> paramDocs(parent, "@param", parentSections)) +
+ ("@tparam" -> paramDocs(parent, "@tparam", parentSections)) +
+ ("@throws" -> paramDocs(parent, "@throws", parentSections))
+
+ val out = new StringBuilder
+
+ def replaceInheritdoc(childSection: String, parentSection: => String) =
+ if (childSection.indexOf("@inheritdoc") == -1)
+ childSection
+ else
+ childSection.replaceAllLiterally("@inheritdoc", parentSection)
+
+ def getParentSection(section: (Int, Int)): String = {
+
+ def getSectionHeader = extractSectionTag(child, section) match {
+ case param@("@param"|"@tparam"|"@throws") => param + " " + extractSectionParam(child, section)
+ case other => other
+ }
+
+ def sectionString(param: String, paramMap: Map[String, (Int, Int)]): String =
+ paramMap.get(param) match {
+ case Some(section) =>
+ // Cleanup the section tag and parameter
+ val sectionTextBounds = extractSectionText(parent, section)
+ cleanupSectionText(parent.substring(sectionTextBounds._1, sectionTextBounds._2))
+ case None =>
+ dottydoc.println(s"""${sym.pos}: the """" + getSectionHeader + "\" annotation of the " + sym +
+ " comment contains @inheritdoc, but the corresponding section in the parent is not defined.")
+ "<invalid inheritdoc annotation>"
+ }
+
+ child.substring(section._1, section._1 + 7) match {
+ case param@("@param "|"@tparam"|"@throws") =>
+ sectionString(extractSectionParam(child, section), parentNamedParams(param.trim))
+ case _ =>
+ sectionString(extractSectionTag(child, section), parentTagMap)
+ }
+ }
+
+ def mainComment(str: String, sections: List[(Int, Int)]): String =
+ if (str.trim.length > 3)
+ str.trim.substring(3, startTag(str, sections))
+ else
+ ""
+
+ // Append main comment
+ out.append("/**")
+ out.append(replaceInheritdoc(mainComment(child, childSections), mainComment(parent, parentSections)))
+
+ // Append sections
+ for (section <- childSections)
+ out.append(replaceInheritdoc(child.substring(section._1, section._2), getParentSection(section)))
+
+ out.append("*/")
+ out.toString
+ }
+
+ protected def expandVariables(initialStr: String, sym: Symbol, site: Symbol)(implicit ctx: Context): String = {
+ val expandLimit = 10
+
+ def expandInternal(str: String, depth: Int): String = {
+ if (depth >= expandLimit)
+ throw new ExpansionLimitExceeded(str)
+
+ val out = new StringBuilder
+ var copied, idx = 0
+ // excluding variables written as \$foo so we can use them when
+ // necessary to document things like Symbol#decode
+ def isEscaped = idx > 0 && str.charAt(idx - 1) == '\\'
+ while (idx < str.length) {
+ if ((str charAt idx) != '$' || isEscaped)
+ idx += 1
+ else {
+ val vstart = idx
+ idx = skipVariable(str, idx + 1)
+ def replaceWith(repl: String) {
+ out append str.substring(copied, vstart)
+ out append repl
+ copied = idx
+ }
+ variableName(str.substring(vstart + 1, idx)) match {
+ case "super" =>
+ superComment(sym) foreach { sc =>
+ val superSections = tagIndex(sc)
+ replaceWith(sc.substring(3, startTag(sc, superSections)))
+ for (sec @ (start, end) <- superSections)
+ if (!isMovable(sc, sec)) out append sc.substring(start, end)
+ }
+ case "" => idx += 1
+ case vname =>
+ lookupVariable(vname, site) match {
+ case Some(replacement) => replaceWith(replacement)
+ case None =>
+ dottydoc.println(s"Variable $vname undefined in comment for $sym in $site")
+ }
+ }
+ }
+ }
+ if (out.length == 0) str
+ else {
+ out append str.substring(copied)
+ expandInternal(out.toString, depth + 1)
+ }
+ }
+
+ // We suppressed expanding \$ throughout the recursion, and now we
+ // need to replace \$ with $ so it looks as intended.
+ expandInternal(initialStr, 0).replaceAllLiterally("""\$""", "$")
+ }
+
+ def defineVariables(sym: Symbol)(implicit ctx: Context) = {
+ val Trim = "(?s)^[\\s&&[^\n\r]]*(.*?)\\s*$".r
+
+ val raw = ctx.base.docstring(sym).map(_.chrs).getOrElse("")
+ defs(sym) ++= defines(raw).map {
+ str => {
+ val start = skipWhitespace(str, "@define".length)
+ val (key, value) = str.splitAt(skipVariable(str, start))
+ key.drop(start) -> value
+ }
+ } map {
+ case (key, Trim(value)) =>
+ variableName(key) -> value.replaceAll("\\s+\\*+$", "")
+ }
+ }
+
+ /** Maps symbols to the variable -> replacement maps that are defined
+ * in their doc comments
+ */
+ private val defs = mutable.HashMap[Symbol, Map[String, String]]() withDefaultValue Map()
+
+ /** Lookup definition of variable.
+ *
+ * @param vble The variable for which a definition is searched
+ * @param site The class for which doc comments are generated
+ */
+ def lookupVariable(vble: String, site: Symbol)(implicit ctx: Context): Option[String] = site match {
+ case NoSymbol => None
+ case _ =>
+ val searchList =
+ if (site.flags.is(Flags.Module)) site :: site.info.baseClasses
+ else site.info.baseClasses
+
+ searchList collectFirst { case x if defs(x) contains vble => defs(x)(vble) } match {
+ case Some(str) if str startsWith "$" => lookupVariable(str.tail, site)
+ case res => res orElse lookupVariable(vble, site.owner)
+ }
+ }
+
+ /** The position of the raw doc comment of symbol `sym`, or NoPosition if missing
+ * If a symbol does not have a doc comment but some overridden version of it does,
+ * the position of the doc comment of the overridden version is returned instead.
+ */
+ def docCommentPos(sym: Symbol)(implicit ctx: Context): Position =
+ ctx.base.docstring(sym).map(_.pos).getOrElse(NoPosition)
+
+ /** A version which doesn't consider self types, as a temporary measure:
+ * an infinite loop has broken out between superComment and cookedDocComment
+ * since r23926.
+ */
+ private def allInheritedOverriddenSymbols(sym: Symbol)(implicit ctx: Context): List[Symbol] = {
+ if (!sym.owner.isClass) Nil
+ else sym.allOverriddenSymbols.toList.filter(_ != NoSymbol) //TODO: could also be `sym.owner.allOverrid..`
+ //else sym.owner.ancestors map (sym overriddenSymbol _) filter (_ != NoSymbol)
+ }
+
+ class ExpansionLimitExceeded(str: String) extends Exception
+}
diff --git a/dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/CommentParser.scala b/dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/CommentParser.scala
index 5ba05fbfe..b0ea0773a 100644
--- a/dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/CommentParser.scala
+++ b/dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/CommentParser.scala
@@ -562,7 +562,7 @@ trait CommentParser {
case (qualName, optTitle) =>
//TODO: this should be enabled
//makeEntityLink(optTitle getOrElse Text(target), pos, target, site)
- title.getOrElse(Text("broken link"))
+ title.getOrElse(Text(qualName))
}
}
diff --git a/dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/CommentUtils.scala b/dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/CommentUtils.scala
new file mode 100644
index 000000000..e5307bd3c
--- /dev/null
+++ b/dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/CommentUtils.scala
@@ -0,0 +1,224 @@
+/*
+ * Port of DocStrings.scala from nsc
+ * @author Martin Odersky
+ * @author Felix Mulder
+ */
+
+package dotty.tools
+package dottydoc
+package model
+package comment
+
+import scala.reflect.internal.Chars._
+
+object CommentUtils {
+
+ /** Returns index of string `str` following `start` skipping longest
+ * sequence of whitespace characters characters (but no newlines)
+ */
+ def skipWhitespace(str: String, start: Int): Int =
+ if (start < str.length && isWhitespace(str charAt start)) skipWhitespace(str, start + 1)
+ else start
+
+ /** Returns index of string `str` following `start` skipping
+ * sequence of identifier characters.
+ */
+ def skipIdent(str: String, start: Int): Int =
+ if (start < str.length && isIdentifierPart(str charAt start)) skipIdent(str, start + 1)
+ else start
+
+ /** Returns index of string `str` following `start` skipping
+ * sequence of identifier characters.
+ */
+ def skipTag(str: String, start: Int): Int =
+ if (start < str.length && (str charAt start) == '@') skipIdent(str, start + 1)
+ else start
+
+
+ /** Returns index of string `str` after `start` skipping longest
+ * sequence of space and tab characters, possibly also containing
+ * a single `*` character or the `/``**` sequence.
+ * @pre start == str.length || str(start) == `\n`
+ */
+ def skipLineLead(str: String, start: Int): Int =
+ if (start == str.length) start
+ else {
+ val idx = skipWhitespace(str, start + 1)
+ if (idx < str.length && (str charAt idx) == '*') skipWhitespace(str, idx + 1)
+ else if (idx + 2 < str.length && (str charAt idx) == '/' && (str charAt (idx + 1)) == '*' && (str charAt (idx + 2)) == '*')
+ skipWhitespace(str, idx + 3)
+ else idx
+ }
+
+ /** Skips to next occurrence of `\n` or to the position after the `/``**` sequence following index `start`.
+ */
+ def skipToEol(str: String, start: Int): Int =
+ if (start + 2 < str.length && (str charAt start) == '/' && (str charAt (start + 1)) == '*' && (str charAt (start + 2)) == '*') start + 3
+ else if (start < str.length && (str charAt start) != '\n') skipToEol(str, start + 1)
+ else start
+
+ /** Returns first index following `start` and starting a line (i.e. after skipLineLead) or starting the comment
+ * which satisfies predicate `p`.
+ */
+ def findNext(str: String, start: Int)(p: Int => Boolean): Int = {
+ val idx = skipLineLead(str, skipToEol(str, start))
+ if (idx < str.length && !p(idx)) findNext(str, idx)(p)
+ else idx
+ }
+
+ /** Return first index following `start` and starting a line (i.e. after skipLineLead)
+ * which satisfies predicate `p`.
+ */
+ def findAll(str: String, start: Int)(p: Int => Boolean): List[Int] = {
+ val idx = findNext(str, start)(p)
+ if (idx == str.length) List()
+ else idx :: findAll(str, idx)(p)
+ }
+
+ /** Produces a string index, which is a list of `sections`, i.e
+ * pairs of start/end positions of all tagged sections in the string.
+ * Every section starts with an at sign and extends to the next at sign,
+ * or to the end of the comment string, but excluding the final two
+ * characters which terminate the comment.
+ *
+ * Also take usecases into account - they need to expand until the next
+ * usecase or the end of the string, as they might include other sections
+ * of their own
+ */
+ def tagIndex(str: String, p: Int => Boolean = (idx => true)): List[(Int, Int)] = {
+ var indices = findAll(str, 0) (idx => str(idx) == '@' && p(idx))
+ indices = mergeUsecaseSections(str, indices)
+ indices = mergeInheritdocSections(str, indices)
+
+ indices match {
+ case List() => List()
+ case idxs => idxs zip (idxs.tail ::: List(str.length - 2))
+ }
+ }
+
+ /**
+ * Merge sections following an usecase into the usecase comment, so they
+ * can override the parent symbol's sections
+ */
+ def mergeUsecaseSections(str: String, idxs: List[Int]): List[Int] = {
+ idxs.indexWhere(str.startsWith("@usecase", _)) match {
+ case firstUCIndex if firstUCIndex != -1 =>
+ val commentSections = idxs.take(firstUCIndex)
+ val usecaseSections = idxs.drop(firstUCIndex).filter(str.startsWith("@usecase", _))
+ commentSections ::: usecaseSections
+ case _ =>
+ idxs
+ }
+ }
+
+ /**
+ * Merge the inheritdoc sections, as they never make sense on their own
+ */
+ def mergeInheritdocSections(str: String, idxs: List[Int]): List[Int] =
+ idxs.filterNot(str.startsWith("@inheritdoc", _))
+
+ /** Does interval `iv` start with given `tag`?
+ */
+ def startsWithTag(str: String, section: (Int, Int), tag: String): Boolean =
+ startsWithTag(str, section._1, tag)
+
+ def startsWithTag(str: String, start: Int, tag: String): Boolean =
+ str.startsWith(tag, start) && !isIdentifierPart(str charAt (start + tag.length))
+
+ /** The first start tag of a list of tag intervals,
+ * or the end of the whole comment string - 2 if list is empty
+ */
+ def startTag(str: String, sections: List[(Int, Int)]) = sections match {
+ case Nil => str.length - 2
+ case (start, _) :: _ => start
+ }
+
+ /** A map from parameter names to start/end indices describing all parameter
+ * sections in `str` tagged with `tag`, where `sections` is the index of `str`.
+ */
+ def paramDocs(str: String, tag: String, sections: List[(Int, Int)]): Map[String, (Int, Int)] =
+ Map() ++ {
+ for (section <- sections if startsWithTag(str, section, tag)) yield {
+ val start = skipWhitespace(str, section._1 + tag.length)
+ str.substring(start, skipIdent(str, start)) -> section
+ }
+ }
+
+ /** Optionally start and end index of return section in `str`, or `None`
+ * if `str` does not have a @group. */
+ def groupDoc(str: String, sections: List[(Int, Int)]): Option[(Int, Int)] =
+ sections find (startsWithTag(str, _, "@group"))
+
+
+ /** Optionally start and end index of return section in `str`, or `None`
+ * if `str` does not have a @return.
+ */
+ def returnDoc(str: String, sections: List[(Int, Int)]): Option[(Int, Int)] =
+ sections find (startsWithTag(str, _, "@return"))
+
+ /** Extracts variable name from a string, stripping any pair of surrounding braces */
+ def variableName(str: String): String =
+ if (str.length >= 2 && (str charAt 0) == '{' && (str charAt (str.length - 1)) == '}')
+ str.substring(1, str.length - 1)
+ else
+ str
+
+ /** Returns index following variable, or start index if no variable was recognized
+ */
+ def skipVariable(str: String, start: Int): Int = {
+ var idx = start
+ if (idx < str.length && (str charAt idx) == '{') {
+ do idx += 1
+ while (idx < str.length && (str charAt idx) != '}')
+ if (idx < str.length) idx + 1 else start
+ } else {
+ while (idx < str.length && isVarPart(str charAt idx))
+ idx += 1
+ idx
+ }
+ }
+
+ /** A map from the section tag to section parameters */
+ def sectionTagMap(str: String, sections: List[(Int, Int)]): Map[String, (Int, Int)] =
+ Map() ++ {
+ for (section <- sections) yield
+ extractSectionTag(str, section) -> section
+ }
+
+ /** Extract the section tag, treating the section tag as an identifier */
+ def extractSectionTag(str: String, section: (Int, Int)): String =
+ str.substring(section._1, skipTag(str, section._1))
+
+ /** Extract the section parameter */
+ def extractSectionParam(str: String, section: (Int, Int)): String = {
+ val (beg, _) = section
+ assert(str.startsWith("@param", beg) ||
+ str.startsWith("@tparam", beg) ||
+ str.startsWith("@throws", beg))
+
+ val start = skipWhitespace(str, skipTag(str, beg))
+ val finish = skipIdent(str, start)
+
+ str.substring(start, finish)
+ }
+
+ /** Extract the section text, except for the tag and comment newlines */
+ def extractSectionText(str: String, section: (Int, Int)): (Int, Int) = {
+ val (beg, end) = section
+ if (str.startsWith("@param", beg) ||
+ str.startsWith("@tparam", beg) ||
+ str.startsWith("@throws", beg))
+ (skipWhitespace(str, skipIdent(str, skipWhitespace(str, skipTag(str, beg)))), end)
+ else
+ (skipWhitespace(str, skipTag(str, beg)), end)
+ }
+
+ /** Cleanup section text */
+ def cleanupSectionText(str: String) = {
+ var result = str.trim.replaceAll("\n\\s+\\*\\s+", " \n")
+ while (result.endsWith("\n"))
+ result = result.substring(0, str.length - 1)
+ result
+ }
+
+}
diff --git a/src/dotty/tools/dotc/ast/Trees.scala b/src/dotty/tools/dotc/ast/Trees.scala
index 20ae02994..cf11c27fa 100644
--- a/src/dotty/tools/dotc/ast/Trees.scala
+++ b/src/dotty/tools/dotc/ast/Trees.scala
@@ -15,6 +15,7 @@ import printing.Printer
import util.{Stats, Attachment, DotClass}
import annotation.unchecked.uncheckedVariance
import language.implicitConversions
+import parsing.Scanners.Comment
object Trees {
@@ -30,7 +31,7 @@ object Trees {
@sharable var ntrees = 0
/** Attachment key for trees with documentation strings attached */
- val DocComment = new Attachment.Key[String]
+ val DocComment = new Attachment.Key[Comment]
/** Modifiers and annotations for definitions
* @param flags The set flags
@@ -324,7 +325,7 @@ object Trees {
private[ast] def rawMods: Modifiers[T] =
if (myMods == null) genericEmptyModifiers else myMods
- def rawComment: Option[String] = getAttachment(DocComment)
+ def rawComment: Option[Comment] = getAttachment(DocComment)
def withMods(mods: Modifiers[Untyped]): ThisTree[Untyped] = {
val tree = if (myMods == null || (myMods == mods)) this else clone.asInstanceOf[MemberDef[Untyped]]
@@ -334,7 +335,7 @@ object Trees {
def withFlags(flags: FlagSet): ThisTree[Untyped] = withMods(Modifiers(flags))
- def setComment(comment: Option[String]): ThisTree[Untyped] = {
+ def setComment(comment: Option[Comment]): ThisTree[Untyped] = {
comment.map(putAttachment(DocComment, _))
asInstanceOf[ThisTree[Untyped]]
}
diff --git a/src/dotty/tools/dotc/config/Printers.scala b/src/dotty/tools/dotc/config/Printers.scala
index fa36ad12c..31c9d06cd 100644
--- a/src/dotty/tools/dotc/config/Printers.scala
+++ b/src/dotty/tools/dotc/config/Printers.scala
@@ -13,7 +13,7 @@ object Printers {
}
val default: Printer = new Printer
- val dottydoc: Printer = noPrinter
+ val dottydoc: Printer = new Printer
val core: Printer = noPrinter
val typr: Printer = noPrinter
val constr: Printer = noPrinter
diff --git a/src/dotty/tools/dotc/core/Contexts.scala b/src/dotty/tools/dotc/core/Contexts.scala
index 262443314..4a3f7c685 100644
--- a/src/dotty/tools/dotc/core/Contexts.scala
+++ b/src/dotty/tools/dotc/core/Contexts.scala
@@ -29,6 +29,7 @@ import printing._
import config.{Settings, ScalaSettings, Platform, JavaPlatform, SJSPlatform}
import language.implicitConversions
import DenotTransformers.DenotTransformer
+import parsing.Scanners.Comment
import xsbti.AnalysisCallback
object Contexts {
@@ -568,12 +569,12 @@ object Contexts {
allPhases.find(_.period.containsPhaseId(p.id)).getOrElse(NoPhase)
}
- val _docstrings: mutable.Map[Symbol, String] =
+ val _docstrings: mutable.Map[Symbol, Comment] =
mutable.Map.empty
- def docstring(sym: Symbol): Option[String] = _docstrings.get(sym)
+ def docstring(sym: Symbol): Option[Comment] = _docstrings.get(sym)
- def addDocstring(sym: Symbol, doc: Option[String]): Unit =
+ def addDocstring(sym: Symbol, doc: Option[Comment]): Unit =
doc.map(d => _docstrings += (sym -> d))
}
diff --git a/src/dotty/tools/dotc/parsing/Parsers.scala b/src/dotty/tools/dotc/parsing/Parsers.scala
index 600707cbf..378aa6ed7 100644
--- a/src/dotty/tools/dotc/parsing/Parsers.scala
+++ b/src/dotty/tools/dotc/parsing/Parsers.scala
@@ -22,6 +22,7 @@ import ScriptParsers._
import scala.annotation.{tailrec, switch}
import util.DotClass
import rewrite.Rewrites.patch
+import Scanners.Comment
object Parsers {
@@ -1778,13 +1779,13 @@ object Parsers {
*/
def defOrDcl(start: Int, mods: Modifiers): Tree = in.token match {
case VAL =>
- patDefOrDcl(posMods(start, mods), in.getDocString(start))
+ patDefOrDcl(posMods(start, mods), in.getDocComment(start))
case VAR =>
- patDefOrDcl(posMods(start, addFlag(mods, Mutable)), in.getDocString(start))
+ patDefOrDcl(posMods(start, addFlag(mods, Mutable)), in.getDocComment(start))
case DEF =>
- defDefOrDcl(posMods(start, mods), in.getDocString(start))
+ defDefOrDcl(posMods(start, mods), in.getDocComment(start))
case TYPE =>
- typeDefOrDcl(posMods(start, mods), in.getDocString(start))
+ typeDefOrDcl(posMods(start, mods), in.getDocComment(start))
case _ =>
tmplDef(start, mods)
}
@@ -1794,7 +1795,7 @@ object Parsers {
* ValDcl ::= Id {`,' Id} `:' Type
* VarDcl ::= Id {`,' Id} `:' Type
*/
- def patDefOrDcl(mods: Modifiers, docstring: Option[String] = None): Tree = {
+ def patDefOrDcl(mods: Modifiers, docstring: Option[Comment] = None): Tree = {
val lhs = commaSeparated(pattern2)
val tpt = typedOpt()
val rhs =
@@ -1820,7 +1821,7 @@ object Parsers {
* DefDcl ::= DefSig `:' Type
* DefSig ::= id [DefTypeParamClause] ParamClauses
*/
- def defDefOrDcl(mods: Modifiers, docstring: Option[String] = None): Tree = atPos(tokenRange) {
+ def defDefOrDcl(mods: Modifiers, docstring: Option[Comment] = None): Tree = atPos(tokenRange) {
def scala2ProcedureSyntax(resultTypeStr: String) = {
val toInsert =
if (in.token == LBRACE) s"$resultTypeStr ="
@@ -1895,7 +1896,7 @@ object Parsers {
/** TypeDef ::= type Id [TypeParamClause] `=' Type
* TypeDcl ::= type Id [TypeParamClause] TypeBounds
*/
- def typeDefOrDcl(mods: Modifiers, docstring: Option[String] = None): Tree = {
+ def typeDefOrDcl(mods: Modifiers, docstring: Option[Comment] = None): Tree = {
newLinesOpt()
atPos(tokenRange) {
val name = ident().toTypeName
@@ -1917,7 +1918,7 @@ object Parsers {
* | [`case'] `object' ObjectDef
*/
def tmplDef(start: Int, mods: Modifiers): Tree = {
- val docstring = in.getDocString(start)
+ val docstring = in.getDocComment(start)
in.token match {
case TRAIT =>
classDef(posMods(start, addFlag(mods, Trait)), docstring)
@@ -1938,7 +1939,7 @@ object Parsers {
/** ClassDef ::= Id [ClsTypeParamClause]
* [ConstrMods] ClsParamClauses TemplateOpt
*/
- def classDef(mods: Modifiers, docstring: Option[String]): TypeDef = atPos(tokenRange) {
+ def classDef(mods: Modifiers, docstring: Option[Comment]): TypeDef = atPos(tokenRange) {
val name = ident().toTypeName
val constr = atPos(in.offset) {
val tparams = typeParamClauseOpt(ParamOwner.Class)
@@ -1965,7 +1966,7 @@ object Parsers {
/** ObjectDef ::= Id TemplateOpt
*/
- def objectDef(mods: Modifiers, docstring: Option[String] = None): ModuleDef = {
+ def objectDef(mods: Modifiers, docstring: Option[Comment] = None): ModuleDef = {
val name = ident()
val template = templateOpt(emptyConstructor())
@@ -2190,7 +2191,7 @@ object Parsers {
if (in.token == PACKAGE) {
in.nextToken()
if (in.token == OBJECT) {
- val docstring = in.getDocString(start)
+ val docstring = in.getDocComment(start)
ts += objectDef(atPos(start, in.skipToken()) { Modifiers(Package) }, docstring)
if (in.token != EOF) {
acceptStatSep()
diff --git a/src/dotty/tools/dotc/parsing/Scanners.scala b/src/dotty/tools/dotc/parsing/Scanners.scala
index 1355ea386..b46ab6348 100644
--- a/src/dotty/tools/dotc/parsing/Scanners.scala
+++ b/src/dotty/tools/dotc/parsing/Scanners.scala
@@ -193,7 +193,7 @@ object Scanners {
}
/** Returns the closest docstring preceding the position supplied */
- def getDocString(pos: Int): Option[String] = {
+ def getDocComment(pos: Int): Option[Comment] = {
def closest(c: Comment, docstrings: List[Comment]): Comment = docstrings match {
case x :: xs if (c.pos.end < x.pos.end && x.pos.end <= pos) => closest(x, xs)
case Nil => c
@@ -203,7 +203,7 @@ object Scanners {
case (list @ (x :: xs)) :: _ => {
val c = closest(x, xs)
docsPerBlockStack = list.dropWhile(_ != c).tail :: docsPerBlockStack.tail
- Some(c.chrs)
+ Some(c)
}
case _ => None
}