diff options
author | Adriaan Moors <adriaan.moors@typesafe.com> | 2012-12-12 15:05:47 -0800 |
---|---|---|
committer | Adriaan Moors <adriaan.moors@typesafe.com> | 2012-12-12 15:05:47 -0800 |
commit | a6f10cf7f4245f2dfcf2fef5fab5255b6fca119f (patch) | |
tree | cbad4b40dc2e471234dad0fec55a643f9bbdae93 | |
parent | 59c2649ae074364a9a2d171d58d47612c92bf1c1 (diff) | |
parent | e5e6d673cf669e3c9a7643aedd02213e4f7bbddf (diff) | |
download | scala-a6f10cf7f4245f2dfcf2fef5fab5255b6fca119f.tar.gz scala-a6f10cf7f4245f2dfcf2fef5fab5255b6fca119f.tar.bz2 scala-a6f10cf7f4245f2dfcf2fef5fab5255b6fca119f.zip |
Merge pull request #1722 from vigdorchik/ide.api
Extract base scaladoc functionality for the IDE.
36 files changed, 664 insertions, 496 deletions
diff --git a/src/compiler/scala/tools/nsc/ast/DocComments.scala b/src/compiler/scala/tools/nsc/ast/DocComments.scala index 5a4be5125d..e635c5e87d 100755 --- a/src/compiler/scala/tools/nsc/ast/DocComments.scala +++ b/src/compiler/scala/tools/nsc/ast/DocComments.scala @@ -19,11 +19,17 @@ import scala.collection.mutable */ trait DocComments { self: Global => - var cookedDocComments = Map[Symbol, String]() + val cookedDocComments = mutable.HashMap[Symbol, String]() /** The raw doc comment map */ val docComments = mutable.HashMap[Symbol, DocComment]() + def clearDocComments() { + cookedDocComments.clear() + docComments.clear() + defs.clear() + } + /** Associate comment with symbol `sym` at position `pos`. */ def docComment(sym: Symbol, docStr: String, pos: Position = NoPosition) = if ((sym ne null) && (sym ne NoSymbol)) @@ -55,25 +61,20 @@ trait DocComments { self: Global => * 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 = ""): String = cookedDocComments.get(sym) match { - case Some(comment) => - comment - case None => - val ownComment = if (docStr.length == 0) docComments get sym map (_.template) getOrElse "" + def cookedDocComment(sym: Symbol, docStr: String = ""): String = cookedDocComments.getOrElseUpdate(sym, { + val ownComment = if (docStr.length == 0) docComments get sym map (_.template) getOrElse "" else DocComment(docStr).template - val comment = superComment(sym) match { - case None => - if (ownComment.indexOf("@inheritdoc") != -1) - reporter.warning(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) - } - cookedDocComments += (sym -> comment) - comment - } + superComment(sym) match { + case None => + if (ownComment.indexOf("@inheritdoc") != -1) + reporter.warning(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) + } + }) /** The cooked doc comment of symbol `sym` after variable expansion, or "" if missing. * diff --git a/src/compiler/scala/tools/nsc/doc/DocFactory.scala b/src/compiler/scala/tools/nsc/doc/DocFactory.scala index 642e330a57..a091b04993 100644 --- a/src/compiler/scala/tools/nsc/doc/DocFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/DocFactory.scala @@ -80,7 +80,7 @@ class DocFactory(val reporter: Reporter, val settings: doc.Settings) { processor with model.ModelFactoryImplicitSupport with model.ModelFactoryTypeSupport with model.diagram.DiagramFactory - with model.comment.CommentFactory + with model.CommentFactory with model.TreeFactory with model.MemberLookup { override def templateShouldDocument(sym: compiler.Symbol, inTpl: DocTemplateImpl) = diff --git a/src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala b/src/compiler/scala/tools/nsc/doc/base/CommentFactoryBase.scala index 40057bbb52..f60d56d9bb 100644..100755 --- a/src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/base/CommentFactoryBase.scala @@ -1,13 +1,13 @@ /* NSC -- new Scala compiler - * Copyright 2007-2013 LAMP/EPFL + * Copyright 2007-2012 LAMP/EPFL * @author Manohar Jonnalagedda */ package scala.tools.nsc package doc -package model -package comment +package base +import base.comment._ import reporters.Reporter import scala.collection._ import scala.util.matching.Regex @@ -23,78 +23,10 @@ import scala.language.postfixOps * * @author Manohar Jonnalagedda * @author Gilles Dubochet */ -trait CommentFactory { thisFactory: ModelFactory with CommentFactory with MemberLookup=> +trait CommentFactoryBase { this: MemberLookupBase => val global: Global - import global.{ reporter, definitions } - - protected val commentCache = mutable.HashMap.empty[(global.Symbol, TemplateImpl), Comment] - - def addCommentBody(sym: global.Symbol, inTpl: TemplateImpl, docStr: String, docPos: global.Position): global.Symbol = { - commentCache += (sym, inTpl) -> parse(docStr, docStr, docPos, None) - sym - } - - def comment(sym: global.Symbol, currentTpl: Option[DocTemplateImpl], inTpl: DocTemplateImpl): Option[Comment] = { - val key = (sym, inTpl) - if (commentCache isDefinedAt key) - Some(commentCache(key)) - else { - val c = defineComment(sym, currentTpl, inTpl) - if (c isDefined) commentCache += (sym, inTpl) -> c.get - c - } - } - - /** A comment is usualy created by the parser, however for some special - * cases we have to give some `inTpl` comments (parent class for example) - * to the comment of the symbol. - * This function manages some of those cases : Param accessor and Primary constructor */ - def defineComment(sym: global.Symbol, currentTpl: Option[DocTemplateImpl], inTpl: DocTemplateImpl):Option[Comment] = { - - //param accessor case - // We just need the @param argument, we put it into the body - if( sym.isParamAccessor && - inTpl.comment.isDefined && - inTpl.comment.get.valueParams.isDefinedAt(sym.encodedName)) { - val comContent = Some(inTpl.comment.get.valueParams(sym.encodedName)) - Some(createComment(body0 = comContent)) - } - - // Primary constructor case - // We need some content of the class definition : @constructor for the body, - // @param and @deprecated, we can add some more if necessary - else if (sym.isPrimaryConstructor && inTpl.comment.isDefined ) { - val tplComment = inTpl.comment.get - // If there is nothing to put into the comment there is no need to create it - if(tplComment.constructor.isDefined || - tplComment.throws != Map.empty || - tplComment.valueParams != Map.empty || - tplComment.typeParams != Map.empty || - tplComment.deprecated.isDefined - ) - Some(createComment( body0 = tplComment.constructor, - throws0 = tplComment.throws, - valueParams0 = tplComment.valueParams, - typeParams0 = tplComment.typeParams, - deprecated0 = tplComment.deprecated - )) - else None - } - - //other comment cases - // parse function will make the comment - else { - val rawComment = global.expandedDocComment(sym, inTpl.sym).trim - if (rawComment != "") { - val tplOpt = if (currentTpl.isDefined) currentTpl else Some(inTpl) - val c = parse(rawComment, global.rawDocComment(sym), global.docCommentPos(sym), tplOpt) - Some(c) - } - else None - } - - } + import global.{ reporter, definitions, Symbol } /* Creates comments with necessary arguments */ def createComment ( @@ -259,9 +191,7 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory with Member * @param comment The expanded comment string (including start and end markers) to be parsed. * @param src The raw comment source string. * @param pos The position of the comment in source. */ - protected def parse(comment: String, src: String, pos: Position, inTplOpt: Option[DocTemplateImpl] = None): Comment = { - assert(!inTplOpt.isDefined || inTplOpt.get != null) - + protected def parseAtSymbol(comment: String, src: String, pos: Position, siteOpt: Option[Symbol] = None): Comment = { /** The cleaned raw comment as a list of lines. Cleaning removes comment * start and end markers, line start markers and unnecessary whitespace. */ def clean(comment: String): List[String] = { @@ -387,7 +317,7 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory with Member val tagsWithoutDiagram = tags.filterNot(pair => stripTags.contains(pair._1)) val bodyTags: mutable.Map[TagKey, List[Body]] = - mutable.Map(tagsWithoutDiagram mapValues {tag => tag map (parseWiki(_, pos, inTplOpt))} toSeq: _*) + mutable.Map(tagsWithoutDiagram mapValues {tag => tag map (parseWikiAtSymbol(_, pos, siteOpt))} toSeq: _*) def oneTag(key: SimpleTagKey): Option[Body] = ((bodyTags remove key): @unchecked) match { @@ -420,7 +350,7 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory with Member } val com = createComment ( - body0 = Some(parseWiki(docBody.toString, pos, inTplOpt)), + body0 = Some(parseWikiAtSymbol(docBody.toString, pos, siteOpt)), authors0 = allTags(SimpleTagKey("author")), see0 = allTags(SimpleTagKey("see")), result0 = oneTag(SimpleTagKey("return")), @@ -460,24 +390,17 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory with Member * - Removed start-of-line star and one whitespace afterwards (if present). * - Removed all end-of-line whitespace. * - Only `endOfLine` is used to mark line endings. */ - def parseWiki(string: String, pos: Position, inTplOpt: Option[DocTemplateImpl]): Body = { - assert(!inTplOpt.isDefined || inTplOpt.get != null) - - new WikiParser(string, pos, inTplOpt).document() - } + def parseWikiAtSymbol(string: String, pos: Position, siteOpt: Option[Symbol]): Body = new WikiParser(string, pos, siteOpt).document() /** TODO * * @author Ingo Maier * @author Manohar Jonnalagedda * @author Gilles Dubochet */ - protected final class WikiParser(val buffer: String, pos: Position, inTplOpt: Option[DocTemplateImpl]) extends CharReader(buffer) { wiki => - assert(!inTplOpt.isDefined || inTplOpt.get != null) - + protected final class WikiParser(val buffer: String, pos: Position, siteOpt: Option[Symbol]) extends CharReader(buffer) { wiki => var summaryParsed = false def document(): Body = { - nextChar() val blocks = new mutable.ListBuffer[Block] while (char != endOfText) blocks += block() @@ -559,21 +482,21 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory with Member def code(): Block = { jumpWhitespace() jump("{{{") - readUntil("}}}") + val str = readUntil("}}}") if (char == endOfText) reportError(pos, "unclosed code block") else jump("}}}") blockEnded("code block") - Code(normalizeIndentation(getRead)) + Code(normalizeIndentation(str)) } /** {{{ title ::= ('=' inline '=' | "==" inline "==" | ...) '\n' }}} */ def title(): Block = { jumpWhitespace() - val inLevel = repeatJump("=") + val inLevel = repeatJump('=') val text = inline(check("=" * inLevel)) - val outLevel = repeatJump("=", inLevel) + val outLevel = repeatJump('=', inLevel) if (inLevel != outLevel) reportError(pos, "unbalanced or unclosed heading") blockEnded("heading") @@ -583,7 +506,7 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory with Member /** {{{ hrule ::= "----" { '-' } '\n' }}} */ def hrule(): Block = { jumpWhitespace() - repeatJump("-") + repeatJump('-') blockEnded("horizontal rule") HorizontalRule() } @@ -621,8 +544,7 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory with Member } do { - readUntil { char == safeTagMarker || char == endOfText } - val str = getRead() + val str = readUntil { char == safeTagMarker || char == endOfText } nextChar() list += str @@ -660,8 +582,8 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory with Member else if (check(",,")) subscript() else if (check("[[")) link() else { - readUntil { char == safeTagMarker || check("''") || char == '`' || check("__") || char == '^' || check(",,") || check("[[") || isInlineEnd || checkParaEnded || char == endOfLine } - Text(getRead()) + val str = readUntil { char == safeTagMarker || check("''") || char == '`' || check("__") || char == '^' || check(",,") || check("[[") || isInlineEnd || checkParaEnded || char == endOfLine } + Text(str) } } @@ -698,9 +620,8 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory with Member def htmlTag(): HtmlTag = { jump(safeTagMarker) - readUntil(safeTagMarker) + val read = readUntil(safeTagMarker) if (char != endOfText) jump(safeTagMarker) - var read = getRead HtmlTag(read) } @@ -762,14 +683,11 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory with Member def link(): Inline = { val SchemeUri = """([a-z]+:.*)""".r jump("[[") - var parens = 1 - readUntil { parens += 1; !check("[") } - getRead // clear the buffer + var parens = 2 + repeatJump('[') val start = "[" * parens val stop = "]" * parens //println("link with " + parens + " matching parens") - readUntil { check(stop) || check(" ") } - val target = getRead() + val target = readUntil { check(stop) || check(" ") } val title = if (!check(stop)) Some({ jump(" ") @@ -782,7 +700,7 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory with Member case (SchemeUri(uri), optTitle) => Link(uri, optTitle getOrElse Text(uri)) case (qualName, optTitle) => - makeEntityLink(optTitle getOrElse Text(target), pos, target, inTplOpt) + makeEntityLink(optTitle getOrElse Text(target), pos, target, siteOpt) } } @@ -860,7 +778,6 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory with Member (char == endOfText) || ((char == endOfLine) && { val poff = offset - val pc = char nextChar() // read EOL val ok = { checkSkipInitWhitespace(endOfLine) || @@ -870,7 +787,6 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory with Member checkSkipInitWhitespace('\u003D') } offset = poff - char = pc ok }) } @@ -882,40 +798,31 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory with Member protected sealed class CharReader(buffer: String) { reader => - var char: Char = _ var offset: Int = 0 + def char: Char = + if (offset >= buffer.length) endOfText else buffer charAt offset final def nextChar() { - if (offset >= buffer.length) - char = endOfText - else { - char = buffer charAt offset - offset += 1 - } + offset += 1 } final def check(chars: String): Boolean = { val poff = offset - val pc = char val ok = jump(chars) offset = poff - char = pc ok } def checkSkipInitWhitespace(c: Char): Boolean = { val poff = offset - val pc = char jumpWhitespace() val ok = jump(c) offset = poff - char = pc ok } def checkSkipInitWhitespace(chars: String): Boolean = { val poff = offset - val pc = char jumpWhitespace() val (ok0, chars0) = if (chars.charAt(0) == ' ') @@ -924,20 +831,17 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory with Member (true, chars) val ok = ok0 && jump(chars0) offset = poff - char = pc ok } def countWhitespace: Int = { var count = 0 val poff = offset - val pc = char while (isWhitespace(char) && char != endOfText) { nextChar() count += 1 } offset = poff - char = pc count } @@ -964,38 +868,10 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory with Member index == chars.length } - final def checkedJump(chars: String): Boolean = { - val poff = offset - val pc = char - val ok = jump(chars) - if (!ok) { - offset = poff - char = pc - } - ok - } - - final def repeatJump(chars: String, max: Int): Int = { - var count = 0 - var more = true - while (more && count < max) { - if (!checkedJump(chars)) - more = false - else - count += 1 - } - count - } - - final def repeatJump(chars: String): Int = { + final def repeatJump(c: Char, max: Int = Int.MaxValue): Int = { var count = 0 - var more = true - while (more) { - if (!checkedJump(chars)) - more = false - else - count += 1 - } + while (jump(c) && count < max) + count += 1 count } @@ -1035,47 +911,41 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory with Member /* READERS */ - private val readBuilder = new mutable.StringBuilder - - final def getRead(): String = { - val bld = readBuilder.toString - readBuilder.clear() - if (bld.length < 6) bld.intern else bld - } - - final def readUntil(ch: Char): Int = { - var count = 0 - while (char != ch && char != endOfText) { - readBuilder += char - nextChar() + final def readUntil(c: Char): String = { + withRead { + while (char != c && char != endOfText) { + nextChar() + } } - count } - final def readUntil(chars: String): Int = { + final def readUntil(chars: String): String = { assert(chars.length > 0) - var count = 0 - val c = chars.charAt(0) - while (!check(chars) && char != endOfText) { - readBuilder += char - nextChar() - while (char != c && char != endOfText) { - readBuilder += char + withRead { + val c = chars.charAt(0) + while (!check(chars) && char != endOfText) { nextChar() + while (char != c && char != endOfText) + nextChar() } } - count } - final def readUntil(pred: => Boolean): Int = { - var count = 0 - while (!pred && char != endOfText) { - readBuilder += char - nextChar() + final def readUntil(pred: => Boolean): String = { + withRead { + while (char != endOfText && !pred) { + nextChar() + } } - count } + private def withRead(read: => Unit): String = { + val start = offset + read + buffer.substring(start, offset) + } + + /* CHARS CLASSES */ def isWhitespace(c: Char) = c == ' ' || c == '\t' diff --git a/src/compiler/scala/tools/nsc/doc/base/LinkTo.scala b/src/compiler/scala/tools/nsc/doc/base/LinkTo.scala new file mode 100755 index 0000000000..c11179800c --- /dev/null +++ b/src/compiler/scala/tools/nsc/doc/base/LinkTo.scala @@ -0,0 +1,15 @@ +/* NSC -- new Scala compiler + * Copyright 2007-2013 LAMP/EPFL + */ + +package scala.tools.nsc +package doc +package base + +import scala.collection._ + +sealed trait LinkTo +final case class LinkToMember[Mbr, Tpl](mbr: Mbr, tpl: Tpl) extends LinkTo +final case class LinkToTpl[Tpl](tpl: Tpl) extends LinkTo +final case class LinkToExternal(name: String, url: String) extends LinkTo +final case class Tooltip(name: String) extends LinkTo diff --git a/src/compiler/scala/tools/nsc/doc/base/MemberLookupBase.scala b/src/compiler/scala/tools/nsc/doc/base/MemberLookupBase.scala new file mode 100755 index 0000000000..f3a5660dc4 --- /dev/null +++ b/src/compiler/scala/tools/nsc/doc/base/MemberLookupBase.scala @@ -0,0 +1,230 @@ +package scala.tools.nsc +package doc +package base + +import comment._ + +/** This trait extracts all required information for documentation from compilation units. + * The base trait has been extracted to allow getting light-weight documentation + * for a particular symbol in the IDE.*/ +trait MemberLookupBase { + + val global: Global + val settings: doc.Settings + + import global._ + def internalLink(sym: Symbol, site: Symbol): Option[LinkTo] + def chooseLink(links: List[LinkTo]): LinkTo + + import global._ + import definitions.{ NothingClass, AnyClass, AnyValClass, AnyRefClass, ListClass } + import rootMirror.{RootPackage, EmptyPackage} + + private def isRoot(s: Symbol) = s.isRootSymbol || s.isEmptyPackage || s.isEmptyPackageClass + + def makeEntityLink(title: Inline, pos: Position, query: String, siteOpt: Option[Symbol]) = + new EntityLink(title) { lazy val link = memberLookup(pos, query, siteOpt) } + + def memberLookup(pos: Position, query: String, siteOpt: Option[Symbol]): LinkTo = { + var members = breakMembers(query) + + // (1) First look in the root package, as most of the links are qualified + val fromRoot = lookupInRootPackage(pos, members) + + // (2) Or recursively go into each containing template. + val fromParents = siteOpt.fold(Stream.empty[Symbol]) { s => + Stream.iterate(s)(_.owner) + }.takeWhile (!isRoot(_)).map { + lookupInTemplate(pos, members, _) + } + + val syms = (fromRoot +: fromParents) find (!_.isEmpty) getOrElse Nil + + val links = syms flatMap { case (sym, site) => internalLink(sym, site) } match { + case Nil => + // (3) Look at external links + syms.flatMap { case (sym, owner) => + // reconstruct the original link + def linkName(sym: Symbol) = { + def nameString(s: Symbol) = s.nameString + (if ((s.isModule || s.isModuleClass) && !s.isPackage) "$" else "") + val packageSuffix = if (sym.isPackage) ".package" else "" + + sym.ownerChain.reverse.filterNot(isRoot(_)).map(nameString(_)).mkString(".") + packageSuffix + } + + if (sym.isClass || sym.isModule || sym.isTrait || sym.isPackage) + findExternalLink(sym, linkName(sym)) + else if (owner.isClass || owner.isModule || owner.isTrait || owner.isPackage) + findExternalLink(sym, linkName(owner) + "@" + externalSignature(sym)) + else + None + } + case links => links + } + links match { + case Nil => + if (!settings.docNoLinkWarnings.value) + reporter.warning(pos, "Could not find any member to link for \"" + query + "\".") + // (4) if we still haven't found anything, create a tooltip + Tooltip(query) + case List(l) => l + case links => + val chosen = chooseLink(links) + def linkToString(link: LinkTo) = { + val chosenInfo = + if (link == chosen) " [chosen]" else "" + link.toString + chosenInfo + "\n" + } + if (!settings.docNoLinkWarnings.value) + reporter.warning(pos, + "The link target \"" + query + "\" is ambiguous. Several (possibly overloaded) members fit the target:\n" + + links.map(linkToString).mkString + + (if (MemberLookup.showExplanation) + "\n\n" + + "Quick crash course on using Scaladoc links\n" + + "==========================================\n" + + "Disambiguating terms and types: Prefix terms with '$' and types with '!' in case both names are in use:\n" + + " - [[scala.collection.immutable.List!.apply class List's apply method]] and\n" + + " - [[scala.collection.immutable.List$.apply object List's apply method]]\n" + + "Disambiguating overloaded members: If a term is overloaded, you can indicate the first part of its signature followed by *:\n" + + " - [[[scala.collection.immutable.List$.fill[A](Int)(⇒A):List[A]* Fill with a single parameter]]]\n" + + " - [[[scala.collection.immutable.List$.fill[A](Int,Int)(⇒A):List[List[A]]* Fill with a two parameters]]]\n" + + "Notes: \n" + + " - you can use any number of matching square brackets to avoid interference with the signature\n" + + " - you can use \\. to escape dots in prefixes (don't forget to use * at the end to match the signature!)\n" + + " - you can use \\# to escape hashes, otherwise they will be considered as delimiters, like dots.\n" + else "") + ) + chosen + } + } + + private abstract class SearchStrategy + private object BothTypeAndTerm extends SearchStrategy + private object OnlyType extends SearchStrategy + private object OnlyTerm extends SearchStrategy + + private def lookupInRootPackage(pos: Position, members: List[String]) = + lookupInTemplate(pos, members, EmptyPackage) ::: lookupInTemplate(pos, members, RootPackage) + + private def lookupInTemplate(pos: Position, members: List[String], container: Symbol): List[(Symbol, Symbol)] = { + // Maintaining compatibility with previous links is a bit tricky here: + // we have a preference for term names for all terms except for the last, where we prefer a class: + // How to do this: + // - at each step we do a DFS search with the prefered strategy + // - if the search doesn't return any members, we backtrack on the last decision + // * we look for terms with the last member's name + // * we look for types with the same name, all the way up + val result = members match { + case Nil => Nil + case mbrName::Nil => + var syms = lookupInTemplate(pos, mbrName, container, OnlyType) map ((_, container)) + if (syms.isEmpty) + syms = lookupInTemplate(pos, mbrName, container, OnlyTerm) map ((_, container)) + syms + + case tplName::rest => + def completeSearch(syms: List[Symbol]) = + syms flatMap (lookupInTemplate(pos, rest, _)) + + completeSearch(lookupInTemplate(pos, tplName, container, OnlyTerm)) match { + case Nil => completeSearch(lookupInTemplate(pos, tplName, container, OnlyType)) + case syms => syms + } + } + //println("lookupInTemplate(" + members + ", " + container + ") => " + result) + result + } + + private def lookupInTemplate(pos: Position, member: String, container: Symbol, strategy: SearchStrategy): List[Symbol] = { + val name = member.stripSuffix("$").stripSuffix("!").stripSuffix("*") + def signatureMatch(sym: Symbol): Boolean = externalSignature(sym).startsWith(name) + + // We need to cleanup the bogus classes created by the .class file parser. For example, [[scala.Predef]] resolves + // to (bogus) class scala.Predef loaded by the class loader -- which we need to eliminate by looking at the info + // and removing NoType classes + def cleanupBogusClasses(syms: List[Symbol]) = { syms.filter(_.info != NoType) } + + def syms(name: Name) = container.info.nonPrivateMember(name.encodedName).alternatives + def termSyms = cleanupBogusClasses(syms(newTermName(name))) + def typeSyms = cleanupBogusClasses(syms(newTypeName(name))) + + val result = if (member.endsWith("$")) + termSyms + else if (member.endsWith("!")) + typeSyms + else if (member.endsWith("*")) + cleanupBogusClasses(container.info.nonPrivateDecls) filter signatureMatch + else + if (strategy == BothTypeAndTerm) + termSyms ::: typeSyms + else if (strategy == OnlyType) + typeSyms + else if (strategy == OnlyTerm) + termSyms + else + Nil + + //println("lookupInTemplate(" + member + ", " + container + ") => " + result) + result + } + + private def breakMembers(query: String): List[String] = { + // Okay, how does this work? Well: you split on . but you don't want to split on \. => thus the ugly regex + // query.split((?<=[^\\\\])\\.).map(_.replaceAll("\\.")) + // The same code, just faster: + var members = List[String]() + var index = 0 + var last_index = 0 + val length = query.length + while (index < length) { + if ((query.charAt(index) == '.' || query.charAt(index) == '#') && + ((index == 0) || (query.charAt(index-1) != '\\'))) { + + val member = query.substring(last_index, index).replaceAll("\\\\([#\\.])", "$1") + // we want to allow javadoc-style links [[#member]] -- which requires us to remove empty members from the first + // elemnt in the list + if ((member != "") || (!members.isEmpty)) + members ::= member + last_index = index + 1 + } + index += 1 + } + if (last_index < length) + members ::= query.substring(last_index, length).replaceAll("\\\\\\.", ".") + members.reverse + } + + + def findExternalLink(sym: Symbol, name: String): Option[LinkToExternal] = { + val sym1 = + if (sym == AnyClass || sym == AnyRefClass || sym == AnyValClass || sym == NothingClass) ListClass + else if (sym.isPackage) + /* Get package object which has associatedFile ne null */ + sym.info.member(newTermName("package")) + else sym + Option(sym1.associatedFile) flatMap (_.underlyingSource) flatMap { src => + val path = src.path + settings.extUrlMapping get path map { url => + LinkToExternal(name, url + "#" + name) + } + } orElse { + // Deprecated option. + settings.extUrlPackageMapping find { + case (pkg, _) => name startsWith pkg + } map { + case (_, url) => LinkToExternal(name, url + "#" + name) + } + } + } + + def externalSignature(sym: Symbol) = { + sym.info // force it, otherwise we see lazy types + (sym.nameString + sym.signatureString).replaceAll("\\s", "") + } +} + +object MemberLookup { + private[this] var _showExplanation = true + def showExplanation: Boolean = if (_showExplanation) { _showExplanation = false; true } else false +} diff --git a/src/compiler/scala/tools/nsc/doc/model/comment/Body.scala b/src/compiler/scala/tools/nsc/doc/base/comment/Body.scala index 3e5e634e18..02e662da85 100644..100755 --- a/src/compiler/scala/tools/nsc/doc/model/comment/Body.scala +++ b/src/compiler/scala/tools/nsc/doc/base/comment/Body.scala @@ -5,7 +5,7 @@ package scala.tools.nsc package doc -package model +package base package comment import scala.collection._ diff --git a/src/compiler/scala/tools/nsc/doc/model/comment/Comment.scala b/src/compiler/scala/tools/nsc/doc/base/comment/Comment.scala index 3e172544dd..2b28164ca4 100644 --- a/src/compiler/scala/tools/nsc/doc/model/comment/Comment.scala +++ b/src/compiler/scala/tools/nsc/doc/base/comment/Comment.scala @@ -5,7 +5,7 @@ package scala.tools.nsc package doc -package model +package base package comment import scala.collection._ @@ -131,5 +131,4 @@ abstract class Comment { (authors map ("@author " + _.toString)).mkString("\n") + (result map ("@return " + _.toString)).mkString("\n") + (version map ("@version " + _.toString)).mkString - } diff --git a/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala b/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala index e8131e242b..69da322418 100644 --- a/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala +++ b/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala @@ -7,8 +7,9 @@ package scala.tools.nsc package doc package html +import base._ +import base.comment._ import model._ -import comment._ import scala.xml.{XML, NodeSeq} import scala.xml.dtd.{DocType, PublicID} @@ -126,12 +127,12 @@ abstract class HtmlPage extends Page { thisPage => } def linkToHtml(text: Inline, link: LinkTo, hasLinks: Boolean) = link match { - case LinkToTpl(dtpl) => + case LinkToTpl(dtpl: TemplateEntity) => if (hasLinks) <a href={ relativeLinkTo(dtpl) } class="extype" name={ dtpl.qualifiedName }>{ inlineToHtml(text) }</a> else <span class="extype" name={ dtpl.qualifiedName }>{ inlineToHtml(text) }</span> - case LinkToMember(mbr, inTpl) => + case LinkToMember(mbr: MemberEntity, inTpl: TemplateEntity) => if (hasLinks) <a href={ relativeLinkTo(inTpl) + "#" + mbr.signature } class="extmbr" name={ mbr.qualifiedName }>{ inlineToHtml(text) }</a> else @@ -140,7 +141,7 @@ abstract class HtmlPage extends Page { thisPage => <span class="extype" name={ tooltip }>{ inlineToHtml(text) }</span> case LinkToExternal(name, url) => <a href={ url } class="extype" target="_top">{ inlineToHtml(text) }</a> - case NoLink => + case _ => inlineToHtml(text) } diff --git a/src/compiler/scala/tools/nsc/doc/html/page/Source.scala b/src/compiler/scala/tools/nsc/doc/html/page/Source.scala index 807a1bc11a..68289b7474 100644 --- a/src/compiler/scala/tools/nsc/doc/html/page/Source.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/Source.scala @@ -9,7 +9,6 @@ package html package page import model._ -import comment._ import scala.xml.{NodeSeq, Unparsed} import java.io.File diff --git a/src/compiler/scala/tools/nsc/doc/html/page/Template.scala b/src/compiler/scala/tools/nsc/doc/html/page/Template.scala index 008999e09f..e885e9c56e 100644 --- a/src/compiler/scala/tools/nsc/doc/html/page/Template.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/Template.scala @@ -8,6 +8,9 @@ package doc package html package page +import base._ +import base.comment._ + import model._ import model.diagram._ import diagram._ diff --git a/src/compiler/scala/tools/nsc/doc/html/page/diagram/DotDiagramGenerator.scala b/src/compiler/scala/tools/nsc/doc/html/page/diagram/DotDiagramGenerator.scala index 304c534bdc..847367838c 100644 --- a/src/compiler/scala/tools/nsc/doc/html/page/diagram/DotDiagramGenerator.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/diagram/DotDiagramGenerator.scala @@ -74,7 +74,7 @@ class DotDiagramGenerator(settings: doc.Settings) extends DiagramGenerator { def textTypeEntity(text: String) = new TypeEntity { val name = text - def refEntity: SortedMap[Int, (LinkTo, Int)] = SortedMap() + def refEntity: SortedMap[Int, (base.LinkTo, Int)] = SortedMap() } // it seems dot chokes on node names over 8000 chars, so let's limit the size of the string diff --git a/src/compiler/scala/tools/nsc/doc/model/CommentFactory.scala b/src/compiler/scala/tools/nsc/doc/model/CommentFactory.scala new file mode 100644 index 0000000000..9ba89146c0 --- /dev/null +++ b/src/compiler/scala/tools/nsc/doc/model/CommentFactory.scala @@ -0,0 +1,114 @@ +/* NSC -- new Scala compiler + * Copyright 2007-2013 LAMP/EPFL + * @author Manohar Jonnalagedda + */ + +package scala.tools.nsc +package doc +package model + +import base.comment._ + +import reporters.Reporter +import scala.collection._ +import scala.reflect.internal.util.{NoPosition, Position} +import scala.language.postfixOps + +/** The comment parser transforms raw comment strings into `Comment` objects. + * Call `parse` to run the parser. Note that the parser is stateless and + * should only be built once for a given Scaladoc run. + * + * @param reporter The reporter on which user messages (error, warnings) should be printed. + * + * @author Manohar Jonnalagedda + * @author Gilles Dubochet */ +trait CommentFactory extends base.CommentFactoryBase { + thisFactory: ModelFactory with CommentFactory with MemberLookup => + + val global: Global + import global.{ reporter, definitions, Symbol } + + protected val commentCache = mutable.HashMap.empty[(Symbol, TemplateImpl), Comment] + + def addCommentBody(sym: Symbol, inTpl: TemplateImpl, docStr: String, docPos: global.Position): Symbol = { + commentCache += (sym, inTpl) -> parse(docStr, docStr, docPos, None) + sym + } + + def comment(sym: Symbol, currentTpl: Option[DocTemplateImpl], inTpl: DocTemplateImpl): Option[Comment] = { + val key = (sym, inTpl) + if (commentCache isDefinedAt key) + Some(commentCache(key)) + else { + val c = defineComment(sym, currentTpl, inTpl) + if (c isDefined) commentCache += (sym, inTpl) -> c.get + c + } + } + + /** A comment is usualy created by the parser, however for some special + * cases we have to give some `inTpl` comments (parent class for example) + * to the comment of the symbol. + * This function manages some of those cases : Param accessor and Primary constructor */ + def defineComment(sym: Symbol, currentTpl: Option[DocTemplateImpl], inTpl: DocTemplateImpl):Option[Comment] = { + + //param accessor case + // We just need the @param argument, we put it into the body + if( sym.isParamAccessor && + inTpl.comment.isDefined && + inTpl.comment.get.valueParams.isDefinedAt(sym.encodedName)) { + val comContent = Some(inTpl.comment.get.valueParams(sym.encodedName)) + Some(createComment(body0 = comContent)) + } + + // Primary constructor case + // We need some content of the class definition : @constructor for the body, + // @param and @deprecated, we can add some more if necessary + else if (sym.isPrimaryConstructor && inTpl.comment.isDefined ) { + val tplComment = inTpl.comment.get + // If there is nothing to put into the comment there is no need to create it + if(tplComment.constructor.isDefined || + tplComment.throws != Map.empty || + tplComment.valueParams != Map.empty || + tplComment.typeParams != Map.empty || + tplComment.deprecated.isDefined + ) + Some(createComment( body0 = tplComment.constructor, + throws0 = tplComment.throws, + valueParams0 = tplComment.valueParams, + typeParams0 = tplComment.typeParams, + deprecated0 = tplComment.deprecated + )) + else None + } + + //other comment cases + // parse function will make the comment + else { + val rawComment = global.expandedDocComment(sym, inTpl.sym).trim + if (rawComment != "") { + val tplOpt = if (currentTpl.isDefined) currentTpl else Some(inTpl) + val c = parse(rawComment, global.rawDocComment(sym), global.docCommentPos(sym), tplOpt) + Some(c) + } + else None + } + + } + + protected def parse(comment: String, src: String, pos: Position, inTplOpt: Option[DocTemplateImpl] = None): Comment = { + assert(!inTplOpt.isDefined || inTplOpt.get != null) + parseAtSymbol(comment, src, pos, inTplOpt map (_.sym)) + } + + /** Parses a string containing wiki syntax into a `Comment` object. + * Note that the string is assumed to be clean: + * - Removed Scaladoc start and end markers. + * - Removed start-of-line star and one whitespace afterwards (if present). + * - Removed all end-of-line whitespace. + * - Only `endOfLine` is used to mark line endings. */ + def parseWiki(string: String, pos: Position, inTplOpt: Option[DocTemplateImpl]): Body = { + assert(!inTplOpt.isDefined || inTplOpt.get != null) + parseWikiAtSymbol(string,pos, inTplOpt map (_.sym)) + } +} diff --git a/src/compiler/scala/tools/nsc/doc/model/Entity.scala b/src/compiler/scala/tools/nsc/doc/model/Entity.scala index c3f9101f17..cbc1a23d44 100644 --- a/src/compiler/scala/tools/nsc/doc/model/Entity.scala +++ b/src/compiler/scala/tools/nsc/doc/model/Entity.scala @@ -9,7 +9,7 @@ package doc package model import scala.collection._ -import comment._ +import base.comment._ import diagram._ /** An entity in a Scaladoc universe. Entities are declarations in the program and correspond to symbols in the diff --git a/src/compiler/scala/tools/nsc/doc/model/LinkTo.scala b/src/compiler/scala/tools/nsc/doc/model/LinkTo.scala deleted file mode 100644 index 6c13d5a6d3..0000000000 --- a/src/compiler/scala/tools/nsc/doc/model/LinkTo.scala +++ /dev/null @@ -1,24 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2007-2013 LAMP/EPFL - */ - -package scala.tools.nsc -package doc -package model - -import scala.collection._ - -abstract sealed class LinkTo -final case class LinkToTpl(tpl: DocTemplateEntity) extends LinkTo -final case class LinkToMember(mbr: MemberEntity, inTpl: DocTemplateEntity) extends LinkTo -final case class Tooltip(name: String) extends LinkTo { def this(tpl: TemplateEntity) = this(tpl.qualifiedName) } -final case class LinkToExternal(name: String, url: String) extends LinkTo -case object NoLink extends LinkTo // you should use Tooltip if you have a name from the user, this is only in case all fails - -object LinkToTpl { - // this makes it easier to create links - def apply(tpl: TemplateEntity) = tpl match { - case dtpl: DocTemplateEntity => new LinkToTpl(dtpl) - case ntpl: TemplateEntity => new Tooltip(ntpl.qualifiedName) - } -} diff --git a/src/compiler/scala/tools/nsc/doc/model/MemberLookup.scala b/src/compiler/scala/tools/nsc/doc/model/MemberLookup.scala index 5257db1610..b1105196b7 100644 --- a/src/compiler/scala/tools/nsc/doc/model/MemberLookup.scala +++ b/src/compiler/scala/tools/nsc/doc/model/MemberLookup.scala @@ -2,227 +2,30 @@ package scala.tools.nsc package doc package model -import comment._ - -import scala.reflect.internal.util.FakePos //Position +import base._ /** This trait extracts all required information for documentation from compilation units */ -trait MemberLookup { +trait MemberLookup extends base.MemberLookupBase { thisFactory: ModelFactory => import global._ - import rootMirror.RootPackage, rootMirror.EmptyPackage - - def makeEntityLink(title: Inline, pos: Position, query: String, inTplOpt: Option[DocTemplateImpl]) = - new EntityLink(title) { lazy val link = memberLookup(pos, query, inTplOpt) } - - def memberLookup(pos: Position, query: String, inTplOpt: Option[DocTemplateImpl]): LinkTo = { - assert(modelFinished) - - var members = breakMembers(query) - //println(query + " => " + members) - - // (1) First look in the root package, as most of the links are qualified - val fromRoot = lookupInRootPackage(pos, members) - - // (2) Or recursively go into each containing template. - val fromParents = inTplOpt.fold(Stream.empty[DocTemplateImpl]) { tpl => - Stream.iterate(tpl)(_.inTemplate) - }.takeWhile (tpl => tpl != null && !tpl.isRootPackage).map { tpl => - lookupInTemplate(pos, members, tpl.asInstanceOf[EntityImpl].sym) - } - - val syms = (fromRoot +: fromParents) find (!_.isEmpty) getOrElse Nil - val linkTo = createLinks(syms) match { - case Nil if !syms.isEmpty => - // (3) Look at external links - syms.flatMap { case (sym, owner) => - - // reconstruct the original link - def linkName(sym: Symbol) = { - def isRoot(s: Symbol) = s.isRootSymbol || s.isEmptyPackage || s.isEmptyPackageClass - def nameString(s: Symbol) = s.nameString + (if ((s.isModule || s.isModuleClass) && !s.isPackage) "$" else "") - val packageSuffix = if (sym.isPackage) ".package" else "" - sym.ownerChain.reverse.filterNot(isRoot(_)).map(nameString(_)).mkString(".") + packageSuffix - } - - if (sym.isClass || sym.isModule || sym.isTrait || sym.isPackage) - findExternalLink(sym, linkName(sym)) - else if (owner.isClass || owner.isModule || owner.isTrait || owner.isPackage) - findExternalLink(sym, linkName(owner) + "@" + externalSignature(sym)) - else - None + override def internalLink(sym: Symbol, site: Symbol): Option[LinkTo] = + findTemplateMaybe(sym) match { + case Some(tpl) => Some(LinkToTpl(tpl)) + case None => + findTemplateMaybe(site) flatMap { inTpl => + inTpl.members find (_.asInstanceOf[EntityImpl].sym == sym) map (LinkToMember(_, inTpl)) } - case links => links } - //println(createLinks(syms)) - //println(linkTo) - - // (4) if we still haven't found anything, create a tooltip, if we found too many, report - if (linkTo.isEmpty){ - if (!settings.docNoLinkWarnings.value) - reporter.warning(pos, "Could not find any member to link for \"" + query + "\".") - Tooltip(query) - } else { - if (linkTo.length > 1) { - - val chosen = - if (linkTo.exists(_.isInstanceOf[LinkToMember])) - linkTo.collect({case lm: LinkToMember => lm}).min(Ordering[MemberEntity].on[LinkToMember](_.mbr)) - else - linkTo.head - - def linkToString(link: LinkTo) = { - val description = - link match { - case lm@LinkToMember(mbr, inTpl) => " * " + mbr.kind + " \"" + mbr.signature + "\" in " + inTpl.kind + " " + inTpl.qualifiedName - case lt@LinkToTpl(tpl) => " * " + tpl.kind + " \"" + tpl.qualifiedName + "\"" - case other => " * " + other.toString - } - val chosenInfo = - if (link == chosen) - " [chosen]" - else - "" - description + chosenInfo + "\n" - } - if (!settings.docNoLinkWarnings.value) - reporter.warning(pos, - "The link target \"" + query + "\" is ambiguous. Several (possibly overloaded) members fit the target:\n" + - linkTo.map(link => linkToString(link)).mkString + - (if (MemberLookup.showExplanation) - "\n\n" + - "Quick crash course on using Scaladoc links\n" + - "==========================================\n" + - "Disambiguating terms and types: Prefix terms with '$' and types with '!' in case both names are in use:\n" + - " - [[scala.collection.immutable.List!.apply class List's apply method]] and\n" + - " - [[scala.collection.immutable.List$.apply object List's apply method]]\n" + - "Disambiguating overloaded members: If a term is overloaded, you can indicate the first part of its signature followed by *:\n" + - " - [[[scala.collection.immutable.List$.fill[A](Int)(⇒A):List[A]* Fill with a single parameter]]]\n" + - " - [[[scala.collection.immutable.List$.fill[A](Int,Int)(⇒A):List[List[A]]* Fill with a two parameters]]]\n" + - "Notes: \n" + - " - you can use any number of matching square brackets to avoid interference with the signature\n" + - " - you can use \\. to escape dots in prefixes (don't forget to use * at the end to match the signature!)\n" + - " - you can use \\# to escape hashes, otherwise they will be considered as delimiters, like dots.\n" - else "") - ) - chosen - } else - linkTo.head + override def chooseLink(links: List[LinkTo]): LinkTo = { + val mbrs = links.collect { + case lm@LinkToMember(mbr: MemberEntity, _) => (mbr, lm) } - } - - private abstract class SearchStrategy - private object BothTypeAndTerm extends SearchStrategy - private object OnlyType extends SearchStrategy - private object OnlyTerm extends SearchStrategy - - private def lookupInRootPackage(pos: Position, members: List[String]) = - lookupInTemplate(pos, members, EmptyPackage) ::: lookupInTemplate(pos, members, RootPackage) - - private def createLinks(syms: List[(Symbol, Symbol)]): List[LinkTo] = - syms.flatMap { case (sym, owner) => - findTemplateMaybe(sym) match { - case Some(tpl) => LinkToTpl(tpl) :: Nil - case None => - findTemplateMaybe(owner) flatMap { inTpl => - inTpl.members find (_.asInstanceOf[EntityImpl].sym == sym) map (LinkToMember(_, inTpl)) - } - } - } - - private def lookupInTemplate(pos: Position, members: List[String], container: Symbol): List[(Symbol, Symbol)] = { - // Maintaining compatibility with previous links is a bit tricky here: - // we have a preference for term names for all terms except for the last, where we prefer a class: - // How to do this: - // - at each step we do a DFS search with the prefered strategy - // - if the search doesn't return any members, we backtrack on the last decision - // * we look for terms with the last member's name - // * we look for types with the same name, all the way up - val result = members match { - case Nil => Nil - case mbrName::Nil => - var syms = lookupInTemplate(pos, mbrName, container, OnlyType) map ((_, container)) - if (syms.isEmpty) - syms = lookupInTemplate(pos, mbrName, container, OnlyTerm) map ((_, container)) - syms - - case tplName::rest => - def completeSearch(syms: List[Symbol]) = - syms flatMap (lookupInTemplate(pos, rest, _)) - - completeSearch(lookupInTemplate(pos, tplName, container, OnlyTerm)) match { - case Nil => completeSearch(lookupInTemplate(pos, tplName, container, OnlyType)) - case syms => syms - } - } - //println("lookupInTemplate(" + members + ", " + container + ") => " + result) - result - } - - private def lookupInTemplate(pos: Position, member: String, container: Symbol, strategy: SearchStrategy): List[Symbol] = { - val name = member.stripSuffix("$").stripSuffix("!").stripSuffix("*") - def signatureMatch(sym: Symbol): Boolean = externalSignature(sym).startsWith(name) - - // We need to cleanup the bogus classes created by the .class file parser. For example, [[scala.Predef]] resolves - // to (bogus) class scala.Predef loaded by the class loader -- which we need to eliminate by looking at the info - // and removing NoType classes - def cleanupBogusClasses(syms: List[Symbol]) = { syms.filter(_.info != NoType) } - - def syms(name: Name) = container.info.nonPrivateMember(name.encodedName).alternatives - def termSyms = cleanupBogusClasses(syms(newTermName(name))) - def typeSyms = cleanupBogusClasses(syms(newTypeName(name))) - - val result = if (member.endsWith("$")) - termSyms - else if (member.endsWith("!")) - typeSyms - else if (member.endsWith("*")) - cleanupBogusClasses(container.info.nonPrivateDecls) filter signatureMatch + if (mbrs.isEmpty) + links.head else - if (strategy == BothTypeAndTerm) - termSyms ::: typeSyms - else if (strategy == OnlyType) - typeSyms - else if (strategy == OnlyTerm) - termSyms - else - Nil - - //println("lookupInTemplate(" + member + ", " + container + ") => " + result) - result - } - - private def breakMembers(query: String): List[String] = { - // Okay, how does this work? Well: you split on . but you don't want to split on \. => thus the ugly regex - // query.split((?<=[^\\\\])\\.).map(_.replaceAll("\\.")) - // The same code, just faster: - var members = List[String]() - var index = 0 - var last_index = 0 - val length = query.length - while (index < length) { - if ((query.charAt(index) == '.' || query.charAt(index) == '#') && - ((index == 0) || (query.charAt(index-1) != '\\'))) { - - val member = query.substring(last_index, index).replaceAll("\\\\([#\\.])", "$1") - // we want to allow javadoc-style links [[#member]] -- which requires us to remove empty members from the first - // elemnt in the list - if ((member != "") || (!members.isEmpty)) - members ::= member - last_index = index + 1 - } - index += 1 - } - if (last_index < length) - members ::= query.substring(last_index, length).replaceAll("\\\\\\.", ".") - members.reverse + mbrs.min(Ordering[MemberEntity].on[(MemberEntity, LinkTo)](_._1))._2 } } - -object MemberLookup { - private[this] var _showExplanation = true - def showExplanation: Boolean = if (_showExplanation) { _showExplanation = false; true } else false -} diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala index 8ced574676..ea074873e8 100644 --- a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala @@ -4,8 +4,8 @@ package scala.tools.nsc package doc package model -import comment._ - +import base._ +import base.comment._ import diagram._ import scala.collection._ @@ -1094,31 +1094,12 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { { val rawComment = global.expandedDocComment(bSym, inTpl.sym) rawComment.contains("@template") || rawComment.contains("@documentable") } - def findExternalLink(sym: Symbol, name: String): Option[LinkTo] = { - val sym1 = - if (sym == AnyClass || sym == AnyRefClass || sym == AnyValClass || sym == NothingClass) ListClass - else if (sym.isPackage) - /* Get package object which has associatedFile ne null */ - sym.info.member(newTermName("package")) - else sym - Option(sym1.associatedFile) flatMap (_.underlyingSource) flatMap { src => - val path = src.path - settings.extUrlMapping get path map { url => - LinkToExternal(name, url + "#" + name) - } - } orElse { - // Deprecated option. - settings.extUrlPackageMapping find { - case (pkg, _) => name startsWith pkg - } map { - case (_, url) => LinkToExternal(name, url + "#" + name) - } + object LinkToTpl { + // this makes it easier to create links + def apply(tpl: TemplateEntity): LinkTo = tpl match { + case dtpl: DocTemplateEntity => new LinkToTpl(dtpl) + case _ => new Tooltip(tpl.qualifiedName) } } - - def externalSignature(sym: Symbol) = { - sym.info // force it, otherwise we see lazy types - (sym.nameString + sym.signatureString).replaceAll("\\s", "") - } } diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala index 5334de3797..f88251b22e 100644 --- a/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala +++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala @@ -10,8 +10,6 @@ package scala.tools.nsc package doc package model -import comment._ - import scala.collection._ import scala.util.matching.Regex @@ -608,4 +606,4 @@ trait ModelFactoryImplicitSupport { false } else true // the member structure is different foo(3, 5) vs foo(3)(5) } -}
\ No newline at end of file +} diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactoryTypeSupport.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactoryTypeSupport.scala index 942ccaf1ba..87dc615c8e 100644 --- a/src/compiler/scala/tools/nsc/doc/model/ModelFactoryTypeSupport.scala +++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactoryTypeSupport.scala @@ -4,8 +4,7 @@ package scala.tools.nsc package doc package model -import comment._ - +import base._ import diagram._ import scala.collection._ @@ -24,7 +23,8 @@ trait ModelFactoryTypeSupport { with ModelFactoryTypeSupport with DiagramFactory with CommentFactory - with TreeFactory => + with TreeFactory + with MemberLookup => import global._ import definitions.{ ObjectClass, NothingClass, AnyClass, AnyValClass, AnyRefClass } diff --git a/src/compiler/scala/tools/nsc/doc/model/TypeEntity.scala b/src/compiler/scala/tools/nsc/doc/model/TypeEntity.scala index e4a053e115..cf5c1fb3fb 100644 --- a/src/compiler/scala/tools/nsc/doc/model/TypeEntity.scala +++ b/src/compiler/scala/tools/nsc/doc/model/TypeEntity.scala @@ -20,7 +20,7 @@ abstract class TypeEntity { /** Maps which parts of this type's name reference entities. The map is indexed by the position of the first * character that reference some entity, and contains the entity and the position of the last referenced * character. The referenced character ranges do not to overlap or nest. The map is sorted by position. */ - def refEntity: SortedMap[Int, (LinkTo, Int)] + def refEntity: SortedMap[Int, (base.LinkTo, Int)] /** The human-readable representation of this type. */ override def toString = name diff --git a/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramDirectiveParser.scala b/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramDirectiveParser.scala index 49cfaffc2e..cd60865ce7 100644 --- a/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramDirectiveParser.scala +++ b/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramDirectiveParser.scala @@ -3,7 +3,6 @@ package model package diagram import model._ -import comment.CommentFactory import java.util.regex.{Pattern, Matcher} import scala.util.matching.Regex @@ -259,4 +258,4 @@ trait DiagramDirectiveParser { result } -}
\ No newline at end of file +} diff --git a/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala b/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala index db2d0c0175..175b4a6472 100644 --- a/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala @@ -3,7 +3,6 @@ package model package diagram import model._ -import comment.CommentFactory import scala.collection.mutable // statistics @@ -25,7 +24,7 @@ trait DiagramFactory extends DiagramDirectiveParser { // the following can used for hardcoding different relations into the diagram, for bootstrapping purposes def aggregationNode(text: String) = - NormalNode(new TypeEntity { val name = text; val refEntity = SortedMap[Int, (LinkTo, Int)]() }, None)() + NormalNode(new TypeEntity { val name = text; val refEntity = SortedMap[Int, (base.LinkTo, Int)]() }, None)() /** Create the inheritance diagram for this template */ def makeInheritanceDiagram(tpl: DocTemplateImpl): Option[Diagram] = { diff --git a/src/compiler/scala/tools/nsc/interactive/Doc.scala b/src/compiler/scala/tools/nsc/interactive/Doc.scala new file mode 100755 index 0000000000..4d02de6198 --- /dev/null +++ b/src/compiler/scala/tools/nsc/interactive/Doc.scala @@ -0,0 +1,50 @@ +/* NSC -- new Scala compiler + * Copyright 2007-2012 LAMP/EPFL + * @author Eugene Vigdorchik + */ + +package scala.tools.nsc +package interactive + +import doc.base._ +import comment._ +import scala.xml.NodeSeq + +sealed trait DocResult +final case class UrlResult(url: String) extends DocResult +final case class HtmlResult(comment: Comment) extends DocResult + +abstract class Doc(val settings: doc.Settings) extends MemberLookupBase with CommentFactoryBase { + + override val global: interactive.Global + import global._ + + def chooseLink(links: List[LinkTo]): LinkTo + + override def internalLink(sym: Symbol, site: Symbol): Option[LinkTo] = + ask { () => + if (sym.isClass || sym.isModule) + Some(LinkToTpl(sym)) + else + if ((site.isClass || site.isModule) && site.info.members.toList.contains(sym)) + Some(LinkToMember(sym, site)) + else + None + } + + def retrieve(sym: Symbol, site: Symbol): Option[DocResult] = { + val sig = ask { () => externalSignature(sym) } + findExternalLink(sym, sig) map { link => UrlResult(link.url) } orElse { + val resp = new Response[Tree] + // Ensure docComment tree is type-checked. + val pos = ask { () => docCommentPos(sym) } + askTypeAt(pos, resp) + resp.get.left.toOption flatMap { _ => + ask { () => + val comment = parseAtSymbol(expandedDocComment(sym), rawDocComment(sym), pos, Some(site)) + Some(HtmlResult(comment)) + } + } + } + } +} diff --git a/src/compiler/scala/tools/nsc/interactive/Global.scala b/src/compiler/scala/tools/nsc/interactive/Global.scala index 2ab389445f..0fc4fcaaf7 100644 --- a/src/compiler/scala/tools/nsc/interactive/Global.scala +++ b/src/compiler/scala/tools/nsc/interactive/Global.scala @@ -74,6 +74,7 @@ class Global(settings: Settings, _reporter: Reporter, projectName: String = "") if (verboseIDE) println("[%s][%s]".format(projectName, msg)) override def forInteractive = true + override def forScaladoc = settings.isScaladoc /** A map of all loaded files to the rich compilation units that correspond to them. */ diff --git a/src/compiler/scala/tools/nsc/interactive/tests/core/PresentationCompilerInstance.scala b/src/compiler/scala/tools/nsc/interactive/tests/core/PresentationCompilerInstance.scala index 5c1837b3bf..ea2333a65b 100644 --- a/src/compiler/scala/tools/nsc/interactive/tests/core/PresentationCompilerInstance.scala +++ b/src/compiler/scala/tools/nsc/interactive/tests/core/PresentationCompilerInstance.scala @@ -8,6 +8,7 @@ import scala.reflect.internal.util.Position /** Trait encapsulating the creation of a presentation compiler's instance.*/ private[tests] trait PresentationCompilerInstance extends TestSettings { protected val settings = new Settings + protected def docSettings: doc.Settings = new doc.Settings(_ => ()) protected val compilerReporter: CompilerReporter = new InteractiveReporter { override def compiler = PresentationCompilerInstance.this.compiler } @@ -28,4 +29,4 @@ private[tests] trait PresentationCompilerInstance extends TestSettings { reporter.println("\tbootClassPath: %s".format(settings.bootclasspath.value)) reporter.println("\tverbose: %b".format(settings.verbose.value)) } -}
\ No newline at end of file +} diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 83ded04c39..76fd07f63a 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -48,6 +48,7 @@ trait Typers extends Modes with Adaptations with Tags { resetContexts() resetImplicits() transformed.clear() + clearDocComments() } object UnTyper extends Traverser { @@ -5261,8 +5262,8 @@ trait Typers extends Modes with Adaptations with Tags { } def typedDocDef(docdef: DocDef) = { - val comment = docdef.comment if (forScaladoc && (sym ne null) && (sym ne NoSymbol)) { + val comment = docdef.comment docComments(sym) = comment comment.defineVariables(sym) val typer1 = newTyper(context.makeNewScope(tree, context.owner)) diff --git a/src/partest/scala/tools/partest/ScaladocModelTest.scala b/src/partest/scala/tools/partest/ScaladocModelTest.scala index e7134d0271..b9abff69d8 100644 --- a/src/partest/scala/tools/partest/ScaladocModelTest.scala +++ b/src/partest/scala/tools/partest/ScaladocModelTest.scala @@ -12,7 +12,7 @@ import scala.tools.nsc.util.CommandLineParser import scala.tools.nsc.doc.{Settings, DocFactory, Universe} import scala.tools.nsc.doc.model._ import scala.tools.nsc.doc.model.diagram._ -import scala.tools.nsc.doc.model.comment._ +import scala.tools.nsc.doc.base.comment._ import scala.tools.nsc.reporters.ConsoleReporter /** A class for testing scaladoc model generation diff --git a/test/files/presentation/doc.check b/test/files/presentation/doc.check new file mode 100755 index 0000000000..62b3bfc2e3 --- /dev/null +++ b/test/files/presentation/doc.check @@ -0,0 +1,48 @@ +body:Body(List(Paragraph(Chain(List(Summary(Chain(List(Text(This is a test comment), Text(.)))), Text( +)))))) +@example:Body(List(Paragraph(Chain(List(Summary(Monospace(Text("abb".permutations = Iterator(abb, bab, bba))))))))) +@version: +@since: +@todo: +@note: +@see: +body:Body(List(Paragraph(Chain(List(Summary(Chain(List(Text(This is a test comment), Text(.)))), Text( +)))))) +@example:Body(List(Paragraph(Chain(List(Summary(Monospace(Text("abb".permutations = Iterator(abb, bab, bba))))))))) +@version:Body(List(Paragraph(Chain(List(Summary(Chain(List(Text(1), Text(.)))), Text(0, 09/07/2012)))))) +@since: +@todo: +@note: +@see: +body:Body(List(Paragraph(Chain(List(Summary(Chain(List(Text(This is a test comment), Text(.)))), Text( +)))))) +@example:Body(List(Paragraph(Chain(List(Summary(Monospace(Text("abb".permutations = Iterator(abb, bab, bba))))))))) +@version:Body(List(Paragraph(Chain(List(Summary(Chain(List(Text(1), Text(.)))), Text(0, 09/07/2012)))))) +@since:Body(List(Paragraph(Chain(List(Summary(Chain(List(Text(2), Text(.)))), Text(10)))))) +@todo: +@note: +@see: +body:Body(List(Paragraph(Chain(List(Summary(Chain(List(Text(This is a test comment), Text(.)))), Text( +)))))) +@example:Body(List(Paragraph(Chain(List(Summary(Monospace(Text("abb".permutations = Iterator(abb, bab, bba))))))))) +@version:Body(List(Paragraph(Chain(List(Summary(Chain(List(Text(1), Text(.)))), Text(0, 09/07/2012)))))) +@since:Body(List(Paragraph(Chain(List(Summary(Chain(List(Text(2), Text(.)))), Text(10)))))) +@todo:Body(List(Paragraph(Chain(List(Summary(Text(this method is unsafe))))))) +@note: +@see: +body:Body(List(Paragraph(Chain(List(Summary(Chain(List(Text(This is a test comment), Text(.)))), Text( +)))))) +@example:Body(List(Paragraph(Chain(List(Summary(Monospace(Text("abb".permutations = Iterator(abb, bab, bba))))))))) +@version:Body(List(Paragraph(Chain(List(Summary(Chain(List(Text(1), Text(.)))), Text(0, 09/07/2012)))))) +@since:Body(List(Paragraph(Chain(List(Summary(Chain(List(Text(2), Text(.)))), Text(10)))))) +@todo:Body(List(Paragraph(Chain(List(Summary(Text(this method is unsafe))))))) +@note:Body(List(Paragraph(Chain(List(Summary(Text(Don't inherit!))))))) +@see: +body:Body(List(Paragraph(Chain(List(Summary(Chain(List(Text(This is a test comment), Text(.)))), Text( +)))))) +@example:Body(List(Paragraph(Chain(List(Summary(Monospace(Text("abb".permutations = Iterator(abb, bab, bba))))))))) +@version:Body(List(Paragraph(Chain(List(Summary(Chain(List(Text(1), Text(.)))), Text(0, 09/07/2012)))))) +@since:Body(List(Paragraph(Chain(List(Summary(Chain(List(Text(2), Text(.)))), Text(10)))))) +@todo:Body(List(Paragraph(Chain(List(Summary(Text(this method is unsafe))))))) +@note:Body(List(Paragraph(Chain(List(Summary(Text(Don't inherit!))))))) +@see:Body(List(Paragraph(Chain(List(Summary(Text(some other method))))))) diff --git a/test/files/presentation/doc.scala b/test/files/presentation/doc.scala new file mode 100755 index 0000000000..4b0d6baa1f --- /dev/null +++ b/test/files/presentation/doc.scala @@ -0,0 +1,71 @@ +import scala.tools.nsc.doc +import scala.tools.nsc.doc.base.LinkTo +import scala.tools.nsc.doc.base.comment._ +import scala.tools.nsc.interactive._ +import scala.tools.nsc.interactive.tests._ +import scala.tools.nsc.util._ +import scala.tools.nsc.io._ + +object Test extends InteractiveTest { + override val settings: doc.Settings = docSettings + + val tags = Seq( + "@example `\"abb\".permutations = Iterator(abb, bab, bba)`", + "@version 1.0, 09/07/2012", + "@since 2.10", + "@todo this method is unsafe", + "@note Don't inherit!", + "@see some other method" + ) + + val comment = "This is a test comment." + val caret = "<caret>" + + def text(nTags: Int) = + """|/** %s + | + | * %s */ + |trait Commented {} + |class User(c: %sCommented)""".stripMargin.format(comment, tags take nTags mkString "\n", caret) + + override def main(args: Array[String]) { + val documenter = new Doc(settings) { + val global: compiler.type = compiler + + def chooseLink(links: List[LinkTo]): LinkTo = links.head + } + for (i <- 1 to tags.length) { + val markedText = text(i) + val idx = markedText.indexOf(caret) + val fileText = markedText.substring(0, idx) + markedText.substring(idx + caret.length) + val source = sourceFiles(0) + val batch = new BatchSourceFile(source.file, fileText.toCharArray) + val reloadResponse = new Response[Unit] + compiler.askReload(List(batch), reloadResponse) + reloadResponse.get.left.toOption match { + case None => + reporter.println("Couldn't reload") + case Some(_) => + val treeResponse = new compiler.Response[compiler.Tree] + val pos = compiler.rangePos(batch, idx, idx, idx) + compiler.askTypeAt(pos, treeResponse) + treeResponse.get.left.toOption match { + case Some(tree) => + val sym = tree.tpe.typeSymbol + documenter.retrieve(sym, sym.owner) match { + case Some(HtmlResult(comment)) => + import comment._ + val tags: List[(String, Iterable[Body])] = + List(("@example", example), ("@version", version), ("@since", since.toList), ("@todo", todo), ("@note", note), ("@see", see)) + val str = ("body:" + body + "\n") + + tags.map{ case (name, bodies) => name + ":" + bodies.mkString("\n") }.mkString("\n") + reporter.println(str) + case Some(_) => reporter.println("Got unexpected result") + case None => reporter.println("Got no result") + } + case None => reporter.println("Couldn't find a typedTree") + } + } + } + } +} diff --git a/test/files/presentation/doc/src/Test.scala b/test/files/presentation/doc/src/Test.scala new file mode 100755 index 0000000000..fcc1554994 --- /dev/null +++ b/test/files/presentation/doc/src/Test.scala @@ -0,0 +1 @@ +object Test
\ No newline at end of file diff --git a/test/files/presentation/memory-leaks/MemoryLeaksTest.scala b/test/files/presentation/memory-leaks/MemoryLeaksTest.scala index a5533a623a..159097cc10 100644 --- a/test/files/presentation/memory-leaks/MemoryLeaksTest.scala +++ b/test/files/presentation/memory-leaks/MemoryLeaksTest.scala @@ -5,6 +5,7 @@ import java.util.Calendar import scala.tools.nsc.interactive.tests._ import scala.tools.nsc.util._ import scala.tools.nsc.io._ +import scala.tools.nsc.doc /** This test runs the presentation compiler on the Scala compiler project itself and records memory consumption. * @@ -24,6 +25,8 @@ import scala.tools.nsc.io._ object Test extends InteractiveTest { final val mega = 1024 * 1024 + override val settings: doc.Settings = docSettings + override def execute(): Unit = memoryConsumptionTest() def batchSource(name: String) = @@ -120,4 +123,4 @@ object Test extends InteractiveTest { r.totalMemory() - r.freeMemory() } -}
\ No newline at end of file +} diff --git a/test/scaladoc/run/SI-191-deprecated.scala b/test/scaladoc/run/SI-191-deprecated.scala index 746aa9c598..4ed24ff8d1 100755 --- a/test/scaladoc/run/SI-191-deprecated.scala +++ b/test/scaladoc/run/SI-191-deprecated.scala @@ -1,5 +1,6 @@ import scala.tools.nsc.doc.model._ -import scala.tools.nsc.doc.model.comment._ +import scala.tools.nsc.doc.base._ +import scala.tools.nsc.doc.base.comment._ import scala.tools.partest.ScaladocModelTest import java.net.{URI, URL} import java.io.File diff --git a/test/scaladoc/run/SI-191.scala b/test/scaladoc/run/SI-191.scala index 0fb28145c3..6fb5339d66 100755 --- a/test/scaladoc/run/SI-191.scala +++ b/test/scaladoc/run/SI-191.scala @@ -1,5 +1,6 @@ import scala.tools.nsc.doc.model._ -import scala.tools.nsc.doc.model.comment._ +import scala.tools.nsc.doc.base._ +import scala.tools.nsc.doc.base.comment._ import scala.tools.partest.ScaladocModelTest import java.net.{URI, URL} import java.io.File diff --git a/test/scaladoc/run/SI-3314.scala b/test/scaladoc/run/SI-3314.scala index fe220b08af..c856fe46a8 100644 --- a/test/scaladoc/run/SI-3314.scala +++ b/test/scaladoc/run/SI-3314.scala @@ -1,3 +1,4 @@ +import scala.tools.nsc.doc.base._ import scala.tools.nsc.doc.model._ import scala.tools.partest.ScaladocModelTest @@ -88,4 +89,4 @@ object Test extends ScaladocModelTest { assert(bar.valueParams(0)(0).resultType.name == "(AnyRef { type Lambda[X] <: Either[A,X] })#Lambda[String]", bar.valueParams(0)(0).resultType.name + " == (AnyRef { type Lambda[X] <: Either[A,X] })#Lambda[String]") } -}
\ No newline at end of file +} diff --git a/test/scaladoc/run/SI-5235.scala b/test/scaladoc/run/SI-5235.scala index 6295fc7786..c6b7922bb8 100644 --- a/test/scaladoc/run/SI-5235.scala +++ b/test/scaladoc/run/SI-5235.scala @@ -1,3 +1,4 @@ +import scala.tools.nsc.doc.base._ import scala.tools.nsc.doc.model._ import scala.tools.nsc.doc.model.diagram._ import scala.tools.partest.ScaladocModelTest @@ -84,4 +85,4 @@ object Test extends ScaladocModelTest { assert(mcReverseType.refEntity(0)._1 == LinkToTpl(MyCollection), mcReverse.qualifiedName + "'s return type has a link to " + MyCollection.qualifiedName) } -}
\ No newline at end of file +} diff --git a/test/scaladoc/run/links.scala b/test/scaladoc/run/links.scala index 0c67857e1c..fde24edb2a 100644 --- a/test/scaladoc/run/links.scala +++ b/test/scaladoc/run/links.scala @@ -1,3 +1,4 @@ +import scala.tools.nsc.doc.base._ import scala.tools.nsc.doc.model._ import scala.tools.partest.ScaladocModelTest @@ -23,9 +24,9 @@ object Test extends ScaladocModelTest { val base = rootPackage._package("scala")._package("test")._package("scaladoc")._package("links") val TEST = base._object("TEST") - val memberLinks = countLinks(TEST.comment.get, _.link.isInstanceOf[LinkToMember]) - val templateLinks = countLinks(TEST.comment.get, _.link.isInstanceOf[LinkToTpl]) + val memberLinks = countLinks(TEST.comment.get, _.link.isInstanceOf[LinkToMember[_, _]]) + val templateLinks = countLinks(TEST.comment.get, _.link.isInstanceOf[LinkToTpl[_]]) assert(memberLinks == 17, memberLinks + " == 17 (the member links in object TEST)") assert(templateLinks == 6, templateLinks + " == 6 (the template links in object TEST)") } -}
\ No newline at end of file +} diff --git a/test/scaladoc/scalacheck/CommentFactoryTest.scala b/test/scaladoc/scalacheck/CommentFactoryTest.scala index 5e3141bdc0..96174d29d1 100644 --- a/test/scaladoc/scalacheck/CommentFactoryTest.scala +++ b/test/scaladoc/scalacheck/CommentFactoryTest.scala @@ -3,8 +3,7 @@ import org.scalacheck.Prop._ import scala.tools.nsc.Global import scala.tools.nsc.doc -import scala.tools.nsc.doc.model._ -import scala.tools.nsc.doc.model.comment._ +import scala.tools.nsc.doc.base.comment._ import scala.tools.nsc.doc.model._ import scala.tools.nsc.doc.model.diagram._ |