diff options
author | Felix Mulder <felix.mulder@gmail.com> | 2016-04-15 13:44:16 +0200 |
---|---|---|
committer | Felix Mulder <felix.mulder@gmail.com> | 2016-08-19 15:37:18 +0200 |
commit | 9d4df20500e87a679b92cff997c8d8896d949d65 (patch) | |
tree | 659ec67fb062d4807cb6b84531c7afe8cc4b3734 | |
parent | 1b20568bdbf7b561fb836faf095bb67a52895a58 (diff) | |
download | dotty-9d4df20500e87a679b92cff997c8d8896d949d65.tar.gz dotty-9d4df20500e87a679b92cff997c8d8896d949d65.tar.bz2 dotty-9d4df20500e87a679b92cff997c8d8896d949d65.zip |
Add module member lookup
12 files changed, 380 insertions, 170 deletions
diff --git a/dottydoc/jvm/src/dotty/tools/dottydoc/DottyDoc.scala b/dottydoc/jvm/src/dotty/tools/dottydoc/DottyDoc.scala index 1c24b67a7..11957b01a 100644 --- a/dottydoc/jvm/src/dotty/tools/dottydoc/DottyDoc.scala +++ b/dottydoc/jvm/src/dotty/tools/dottydoc/DottyDoc.scala @@ -4,7 +4,7 @@ package dottydoc import core.Phases.DocPhase import dotc.config.CompilerCommand import dotc.config.Printers.dottydoc -import dotc.core.Contexts.Context +import dotc.core.Contexts.{FreshContext, Context} import dotc.core.Phases.Phase import dotc.typer.FrontEnd import dotc.{Compiler, Driver} @@ -17,7 +17,8 @@ import dotc.{Compiler, Driver} * * Example: * 1. Use the existing FrontEnd to typecheck the code being fed to dottydoc - * 2. Create JSON from the results of the FrontEnd phase + * 2. Create an AST that is serializable + * 3. Serialize to JSON */ case object DottyDocCompiler extends Compiler { override def phases: List[List[Phase]] = diff --git a/dottydoc/jvm/src/dotty/tools/dottydoc/core/Phases.scala b/dottydoc/jvm/src/dotty/tools/dottydoc/core/Phases.scala index f76d013a6..2e24d02d7 100644 --- a/dottydoc/jvm/src/dotty/tools/dottydoc/core/Phases.scala +++ b/dottydoc/jvm/src/dotty/tools/dottydoc/core/Phases.scala @@ -19,11 +19,24 @@ object Phases { import model.EntityFactories._ import dotty.tools.dotc.core.Flags import dotty.tools.dotc.ast.tpd._ + import util.Traversing._ def phaseName = "docphase" + private[this] var commentCache: Map[Entity, (Entity, Map[String, Package]) => Option[Comment]] = Map.empty + + /** Saves the commentParser function for later evaluation, for when the AST has been filled */ + def track(symbol: Symbol, ctx: Context)(op: => Entity) = { + val entity = op + val commentParser = { (entity: Entity, packs: Map[String, Package]) => + wikiParser.parseHtml(symbol, entity, packs)(ctx) + } + commentCache = commentCache + (entity -> commentParser) + entity + } + /** Build documentation hierarchy from existing tree */ - def collect(tree: Tree)(implicit ctx: Context): Entity = { + def collect(tree: Tree)(implicit ctx: Context): Entity = track(tree.symbol, ctx) { def collectList(xs: List[Tree])(implicit ctx: Context): List[Entity] = xs.map(collect).filter(_ != NonEntity) @@ -36,41 +49,39 @@ object Phases { case _ => Nil } - val comment = wikiParser.parseHtml(tree.symbol) - tree match { /** package */ case p @ PackageDef(pid, st) => val name = pid.name.toString - Package(name, collectPackageMembers(st), comment, path(p, name)) + Package(name, collectPackageMembers(st), path(p)) /** trait */ case t @ TypeDef(n, rhs) if t.symbol.is(Flags.Trait) => val name = n.toString - Trait(name, collectMembers(rhs), comment, flags(t), path(t, name)) + Trait(name, collectMembers(rhs), flags(t), path(t)) /** objects, on the format "Object$" so drop the last letter */ case o @ TypeDef(n, rhs) if o.symbol.is(Flags.Module) => val name = n.toString.dropRight(1) - Object(name, collectMembers(rhs), comment, flags(o), path(o, name)) + Object(name, collectMembers(rhs), flags(o), path(o)) /** class / case class */ case c @ TypeDef(name, rhs) if c.symbol.isClass => - (name.toString, collectMembers(rhs), comment, flags(c), path(c, name.toString)) match { + (name.toString, collectMembers(rhs), flags(c), path(c), None) match { case x if c.symbol.is(Flags.CaseClass) => CaseClass.tupled(x) case x => Class.tupled(x) } /** def */ case d: DefDef => - Def(d.name.toString, comment, flags(d), path(d, d.name.toString)) + Def(d.name.toString, flags(d), path(d)) /** val */ case v: ValDef if !v.symbol.is(Flags.ModuleVal) => - Val(v.name.toString, comment, flags(v), path(v, v.name.toString)) + Val(v.name.toString, flags(v), path(v)) case x => { - dottydoc.println(s"Found unwanted entity: $x (${x.pos}, ${comment})\n${x.show}") + //dottydoc.println(s"Found unwanted entity: $x (${x.pos}, ${comment})\n${x.show}") NonEntity } } @@ -82,8 +93,7 @@ object Phases { val path = p.path.mkString(".") packages = packages + (path -> packages.get(path).map { ex => val children = (ex.children ::: p.children).distinct.sortBy(_.name) - val comment = ex.comment.orElse(p.comment) - Package(p.name, children, comment, p.path) + Package(p.name, children, p.path, None) }.getOrElse(p)) } @@ -94,8 +104,58 @@ object Phases { } override def runOn(units: List[CompilationUnit])(implicit ctx: Context): List[CompilationUnit] = { + // (1) Create package structure for all `units`, this will give us a complete structure val compUnits = super.runOn(units) + + // (2) Set parent of all package children + def setParent(ent: Entity, to: Entity): Unit = + ent match { + case e: Class => + e.parent = Some(to) + e.members.foreach(setParent(_, e)) + case e: CaseClass => + e.parent = Some(to) + e.members.foreach(setParent(_, e)) + case e: Object => + e.parent = Some(to) + e.members.foreach(setParent(_, e)) + case e: Trait => + e.parent = Some(to) + e.members.foreach(setParent(_, e)) + case e: Val => + e.parent = Some(to) + case e: Def => + e.parent = Some(to) + case _ => () + } + + for { + parent <- packages.values + child <- parent.children + } setParent(child, to = parent) + + // (3) Create documentation template from docstrings, with internal links + packages.values.foreach { p => + mutateEntities(p) { + case e: Package => e.comment = commentCache(e)(e, packages) + case e: Class => e.comment = commentCache(e)(e, packages) + case e: CaseClass => e.comment = commentCache(e)(e, packages) + case e: Object => e.comment = commentCache(e)(e, packages) + case e: Trait => e.comment = commentCache(e)(e, packages) + case e: Val => e.comment = commentCache(e)(e, packages) + case e: Def => e.comment = commentCache(e)(e, packages) + case _ => () + } + } + + // (4) Write the finished model to JSON util.IndexWriters.writeJs(packages, "../js/out") + + + // (5) Clear caches + commentCache = Map.empty + + // Return super's result compUnits } } diff --git a/dottydoc/jvm/src/dotty/tools/dottydoc/model/CommentParsers.scala b/dottydoc/jvm/src/dotty/tools/dottydoc/model/CommentParsers.scala index cc7bf6949..08d9d4a5a 100644 --- a/dottydoc/jvm/src/dotty/tools/dottydoc/model/CommentParsers.scala +++ b/dottydoc/jvm/src/dotty/tools/dottydoc/model/CommentParsers.scala @@ -8,21 +8,19 @@ import dotc.core.Contexts.Context object CommentParsers { import comment._ import BodyParsers._ + import Entities.{Entity, Package} sealed class WikiParser 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)) - } + def parseHtml(sym: Symbol, entity: Entity, packages: Map[String, Package])(implicit ctx: Context): Option[Comment] = + ctx.base.docstring(sym).map { d => + val expanded = expand(sym) + parse(entity, packages, clean(expanded), expanded, d.pos).toHtml(entity) match { + case "" => None + case x => Some(Comment(x)) + } + }.flatten } - } val wikiParser = new WikiParser } diff --git a/dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/BodyEntities.scala b/dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/BodyEntities.scala index e06789e98..683fc6b19 100644 --- a/dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/BodyEntities.scala +++ b/dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/BodyEntities.scala @@ -3,6 +3,7 @@ package model package comment import scala.collection._ +import Entities.Entity /** A body of text. A comment has a single body, which is composed of * at least one block. Inside every body is exactly one summary (see @@ -61,12 +62,11 @@ final case class Subscript(text: Inline) extends Inline final case class Link(target: String, title: Inline) extends Inline final case class Monospace(text: Inline) extends Inline final case class Text(text: String) extends Inline -//TODO: this should be used -//abstract class EntityLink(val title: Inline) extends Inline { def link: LinkTo } -//object EntityLink { -// def apply(title: Inline, linkTo: LinkTo) = new EntityLink(title) { def link: LinkTo = linkTo } -// def unapply(el: EntityLink): Option[(Inline, LinkTo)] = Some((el.title, el.link)) -//} +abstract class EntityLink(val title: Inline) extends Inline { def link: LinkTo } +object EntityLink { + def apply(title: Inline, linkTo: LinkTo) = new EntityLink(title) { def link: LinkTo = linkTo } + def unapply(el: EntityLink): Option[(Inline, LinkTo)] = Some((el.title, el.link)) +} final case class HtmlTag(data: String) extends Inline { private val Pattern = """(?ms)\A<(/?)(.*?)[\s>].*\z""".r private val (isEnd, tagName) = data match { @@ -86,3 +86,9 @@ final case class HtmlTag(data: String) extends Inline { /** The summary of a comment, usually its first sentence. There must be exactly one summary per body. */ final case class Summary(text: Inline) extends Inline + +sealed trait LinkTo +final case class LinkToMember(mbr: Entity, parent: Entity) extends LinkTo +final case class LinkToEntity(entity: Entity) extends LinkTo +final case class LinkToExternal(name: String, url: String) extends LinkTo +final case class Tooltip(name: String) extends LinkTo diff --git a/dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/BodyParsers.scala b/dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/BodyParsers.scala index 9b6ec600d..6e7988f43 100644 --- a/dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/BodyParsers.scala +++ b/dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/BodyParsers.scala @@ -3,55 +3,72 @@ package model package comment object BodyParsers { + import model.Entities.{Entity, Members} + implicit class BodyToHtml(val body: Body) extends AnyVal { - def toHtml: String = bodyToHtml(body) - - private def inlineToHtml(inl: Inline): String = inl match { - case Chain(items) => (items map inlineToHtml).mkString - case Italic(in) => s"<i>${inlineToHtml(in)}</i>" - case Bold(in) => s"<b>${inlineToHtml(in)}</b>" - case Underline(in) => s"<u>${inlineToHtml(in)}</u>" - case Superscript(in) => s"<sup>${inlineToHtml(in)}</sup>" - case Subscript(in) => s"<sub>${inlineToHtml(in) }</sub>" - case Link(raw, title) => s"""<a href=$raw target="_blank">${inlineToHtml(title)}</a>""" - case Monospace(in) => s"<code>${inlineToHtml(in)}</code>" - case Text(text) => text - case Summary(in) => inlineToHtml(in) - case HtmlTag(tag) => tag - //TODO: when we have EntityLinks, they should be enabled here too - //case EntityLink(target, link) => linkToHtml(target, link, hasLinks = true) - } + def toHtml(origin: Entity): String = { + def inlineToHtml(inl: Inline): String = inl match { + case Chain(items) => (items map inlineToHtml).mkString + case Italic(in) => s"<i>${inlineToHtml(in)}</i>" + case Bold(in) => s"<b>${inlineToHtml(in)}</b>" + case Underline(in) => s"<u>${inlineToHtml(in)}</u>" + case Superscript(in) => s"<sup>${inlineToHtml(in)}</sup>" + case Subscript(in) => s"<sub>${inlineToHtml(in) }</sub>" + case Link(raw, title) => s"""<a href=$raw target="_blank">${inlineToHtml(title)}</a>""" + case Monospace(in) => s"<code>${inlineToHtml(in)}</code>" + case Text(text) => text + case Summary(in) => inlineToHtml(in) + case HtmlTag(tag) => tag + case EntityLink(target, link) => enityLinkToHtml(target, link) + } - private def bodyToHtml(body: Body): String = - (body.blocks map blockToHtml).mkString - - private def blockToHtml(block: Block): String = block match { - case Title(in, 1) => s"<h1>${inlineToHtml(in)}</h1>" - case Title(in, 2) => s"<h2>${inlineToHtml(in)}</h2>" - case Title(in, 3) => s"<h3>${inlineToHtml(in)}</h3>" - case Title(in, _) => s"<h4>${inlineToHtml(in)}</h4>" - case Paragraph(in) => s"<p>${inlineToHtml(in)}</p>" - case Code(data) => s"""<pre><code class="scala">$data</code></pre>""" - case UnorderedList(items) => - s"<ul>${listItemsToHtml(items)}</ul>" - case OrderedList(items, listStyle) => - s"<ol class=${listStyle}>${listItemsToHtml(items)}</ol>" - case DefinitionList(items) => - s"<dl>${items map { case (t, d) => s"<dt>${inlineToHtml(t)}</dt><dd>${blockToHtml(d)}</dd>" } }</dl>" - case HorizontalRule() => - "<hr/>" - } + def enityLinkToHtml(target: Inline, link: LinkTo) = link match { + case Tooltip(_) => inlineToHtml(target) + case LinkToExternal(n, url) => s"""<a href="$url">$n</a>""" + case LinkToMember(mbr, parent) => + s"""<a href="${relativePath(parent)}#${mbr.name}">${mbr.name}</a>""" + case LinkToEntity(target: Entity) => + s"""<a href="${relativePath(target)}">${target.name}</a>""" + } + + def relativePath(target: Entity) = + "../" * (origin.path.length - 1) + + target.path.mkString("/") + + ".html" + + def bodyToHtml(body: Body): String = + (body.blocks map blockToHtml).mkString + + def blockToHtml(block: Block): String = block match { + case Title(in, 1) => s"<h1>${inlineToHtml(in)}</h1>" + case Title(in, 2) => s"<h2>${inlineToHtml(in)}</h2>" + case Title(in, 3) => s"<h3>${inlineToHtml(in)}</h3>" + case Title(in, _) => s"<h4>${inlineToHtml(in)}</h4>" + case Paragraph(in) => s"<p>${inlineToHtml(in)}</p>" + case Code(data) => s"""<pre><code class="scala">$data</code></pre>""" + case UnorderedList(items) => + s"<ul>${listItemsToHtml(items)}</ul>" + case OrderedList(items, listStyle) => + s"<ol class=${listStyle}>${listItemsToHtml(items)}</ol>" + case DefinitionList(items) => + s"<dl>${items map { case (t, d) => s"<dt>${inlineToHtml(t)}</dt><dd>${blockToHtml(d)}</dd>" } }</dl>" + case HorizontalRule() => + "<hr/>" + } + + def listItemsToHtml(items: Seq[Block]) = + items.foldLeft(""){ (list, item) => + item match { + case OrderedList(_, _) | UnorderedList(_) => // html requires sub ULs to be put into the last LI + list + s"<li>${blockToHtml(item)}</li>" + case Paragraph(inline) => + list + s"<li>${inlineToHtml(inline)}</li>" // LIs are blocks, no need to use Ps + case block => + list + s"<li>${blockToHtml(block)}</li>" + } + } - private def listItemsToHtml(items: Seq[Block]) = - items.foldLeft(""){ (list, item) => - item match { - case OrderedList(_, _) | UnorderedList(_) => // html requires sub ULs to be put into the last LI - list + s"<li>${blockToHtml(item)}</li>" - case Paragraph(inline) => - list + s"<li>${inlineToHtml(inline)}</li>" // LIs are blocks, no need to use Ps - case block => - list + s"<li>${blockToHtml(block)}</li>" - } + bodyToHtml(body) } } } 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 b0ea0773a..3c5411c5c 100644 --- a/dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/CommentParser.scala +++ b/dottydoc/jvm/src/dotty/tools/dottydoc/model/comment/CommentParser.scala @@ -4,20 +4,29 @@ package comment import dotty.tools.dotc.util.Positions._ import dotty.tools.dotc.core.Symbols._ +import dotty.tools.dotc.core.Contexts.Context import scala.collection.mutable import dotty.tools.dotc.config.Printers.dottydoc import scala.util.matching.Regex +import Entities.{Entity, Package} -//TODO: re-enable pos? -trait CommentParser { +trait CommentParser extends util.MemberLookup { import Regexes._ /** Parses a raw comment string into a `Comment` object. + * @param packages all packages parsed by Scaladoc tool, used for lookup * @param cleanComment a cleaned comment to be parsed * @param src the raw comment source string. * @param pos the position of the comment in source. */ - def parse(comment: List[String], src: String, /*pos: Position,*/ site: Symbol = NoSymbol): Body = { + def parse( + entity: Entity, + packages: Map[String, Package], + comment: List[String], + src: String, + pos: Position, + site: Symbol = NoSymbol + )(implicit ctx: Context): Body = { /** Parses a comment (in the form of a list of lines) to a `Comment` * instance, recursively on lines. To do so, it splits the whole comment @@ -136,12 +145,12 @@ trait CommentParser { val tagsWithoutDiagram = tags.filterNot(pair => stripTags.contains(pair._1)) val bodyTags: mutable.Map[TagKey, List[Body]] = - mutable.Map((tagsWithoutDiagram mapValues {tag => tag map (parseWikiAtSymbol(_, /*pos,*/ site))}).toSeq: _*) + mutable.Map((tagsWithoutDiagram mapValues {tag => tag map (parseWikiAtSymbol(entity, packages, _, pos, site))}).toSeq: _*) def oneTag(key: SimpleTagKey, filterEmpty: Boolean = true): Option[Body] = ((bodyTags remove key): @unchecked) match { case Some(r :: rs) if !(filterEmpty && r.blocks.isEmpty) => - //if (!rs.isEmpty) dottydoc.println(s"$pos: only one '@${key.name}' tag is allowed") + if (!rs.isEmpty) dottydoc.println(s"$pos: only one '@${key.name}' tag is allowed") Some(r) case _ => None } @@ -154,15 +163,15 @@ trait CommentParser { bodyTags.keys.toSeq flatMap { case stk: SymbolTagKey if (stk.name == key.name) => Some(stk) case stk: SimpleTagKey if (stk.name == key.name) => - //dottydoc.println(s"$pos: tag '@${stk.name}' must be followed by a symbol name") + dottydoc.println(s"$pos: tag '@${stk.name}' must be followed by a symbol name") None case _ => None } val pairs: Seq[(String, Body)] = for (key <- keys) yield { val bs = (bodyTags remove key).get - //if (bs.length > 1) - //dottydoc.println(s"$pos: only one '@${key.name}' tag for symbol ${key.symbol} is allowed") + if (bs.length > 1) + dottydoc.println(s"$pos: only one '@${key.name}' tag for symbol ${key.symbol} is allowed") (key.symbol, bs.head) } Map.empty[String, Body] ++ (if (filterEmpty) pairs.filterNot(_._2.blocks.isEmpty) else pairs) @@ -171,16 +180,16 @@ trait CommentParser { def linkedExceptions: Map[String, Body] = { val m = allSymsOneTag(SimpleTagKey("throws"), filterEmpty = false) - m.map { case (name,body) => - //val link = memberLookup(pos, name, site) + m.map { case (targetStr,body) => + val link = lookup(entity, packages, targetStr, pos) val newBody = body match { case Body(List(Paragraph(Chain(content)))) => val descr = Text(" ") +: content - //val entityLink = EntityLink(Monospace(Text(name)), link) - Body(List(Paragraph(Chain(/*entityLink +: */descr)))) + val entityLink = EntityLink(Monospace(Text(targetStr)), link) + Body(List(Paragraph(Chain(entityLink +: descr)))) case _ => body } - (name, newBody) + (targetStr, newBody) } } @@ -214,7 +223,7 @@ trait CommentParser { //for ((key, _) <- bodyTags) // dottydoc.println(s"$pos: Tag '@${key.name}' is not recognised") - parseWikiAtSymbol(docBody.toString, /*pos,*/ site) + parseWikiAtSymbol(entity, packages, docBody.toString, pos, site) } } @@ -241,14 +250,26 @@ trait CommentParser { * - 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 parseWikiAtSymbol(string: String, /*pos: Position,*/ site: Symbol): Body = new WikiParser(string, /*pos,*/ site).document() - - /** TODO - * + def parseWikiAtSymbol( + entity: Entity, + packages: Map[String, Package], + string: String, + pos: Position, + site: Symbol + )(implicit ctx: Context): Body = new WikiParser(entity, packages, string, pos, site).document() + + /** Original wikiparser from NSC * @author Ingo Maier * @author Manohar Jonnalagedda - * @author Gilles Dubochet */ - protected final class WikiParser(val buffer: String, /*pos: Position,*/ site: Symbol) extends CharReader(buffer) { wiki => + * @author Gilles Dubochet + */ + protected final class WikiParser( + entity: Entity, + packages: Map[String, Package], + val buffer: String, + pos: Position, + site: Symbol + )(implicit ctx: Context) extends CharReader(buffer) { wiki => var summaryParsed = false def document(): Body = { @@ -335,7 +356,7 @@ trait CommentParser { jump("{{{") val str = readUntil("}}}") if (char == endOfText) - reportError(/*pos,*/ "unclosed code block") + reportError(pos, "unclosed code block") else jump("}}}") blockEnded("code block") @@ -349,7 +370,7 @@ trait CommentParser { val text = inline(check("=" * inLevel)) val outLevel = repeatJump('=', inLevel) if (inLevel != outLevel) - reportError(/*pos,*/ "unbalanced or unclosed heading") + reportError(pos, "unbalanced or unclosed heading") blockEnded("heading") Title(text, inLevel) } @@ -560,9 +581,7 @@ trait CommentParser { case (SchemeUri(uri), optTitle) => Link(uri, optTitle getOrElse Text(uri)) case (qualName, optTitle) => - //TODO: this should be enabled - //makeEntityLink(optTitle getOrElse Text(target), pos, target, site) - title.getOrElse(Text(qualName)) + makeEntityLink(entity, packages, optTitle getOrElse Text(target), pos, target) } } @@ -571,7 +590,7 @@ trait CommentParser { /** {{{ eol ::= { whitespace } '\n' }}} */ def blockEnded(blockType: String): Unit = { if (char != endOfLine && char != endOfText) { - reportError(/*pos,*/ "no additional content on same line after " + blockType) + reportError(pos, "no additional content on same line after " + blockType) jumpUntil(endOfLine) } while (char == endOfLine) @@ -629,9 +648,8 @@ trait CommentParser { } } - def reportError(/*pos: Position,*/ message: String) = - //dottydoc.println(s"$pos: $message") - dottydoc.println(s"$message") + def reportError(pos: Position, message: String) = + dottydoc.println(s"$pos: $message") } protected sealed class CharReader(buffer: String) { reader => diff --git a/dottydoc/jvm/src/dotty/tools/dottydoc/util/MemberLookup.scala b/dottydoc/jvm/src/dotty/tools/dottydoc/util/MemberLookup.scala new file mode 100644 index 000000000..ad8d99341 --- /dev/null +++ b/dottydoc/jvm/src/dotty/tools/dottydoc/util/MemberLookup.scala @@ -0,0 +1,74 @@ +package dotty.tools +package dottydoc +package util + +import dotc.config.Printers.dottydoc +import dotc.core.Contexts.Context +import dotc.core.Flags +import dotc.core.Names._ +import dotc.core.Symbols._ +import dotc.core.Types._ +import dotc.core.Names._ +import dotc.util.Positions._ +import model.comment._ +import model.Entities._ + +trait MemberLookup { + def lookup( + entity: Entity, + packages: Map[String, Package], + query: String, + pos: Position + ): LinkTo = { + val notFound: LinkTo = Tooltip(query) + val querys = query.split("\\.").toList + + def localLookup(ent: Entity with Members, searchStr: String): LinkTo = + ent.members.find(_.name == searchStr).fold(notFound)(e => LinkToEntity(e)) + + def downwardLookup(ent: Entity with Members, search: List[String]): LinkTo = + search match { + case Nil => notFound + case x :: Nil => + localLookup(ent, x) + case x :: xs => + ent + .members + .collect { case e: Entity with Members => e } + .find(_.name == x) + .fold(notFound)(e => downwardLookup(e, xs)) + } + + def globalLookup: LinkTo = { + def longestMatch(list: List[String]): List[String] = + if (list == Nil) Nil + else + packages + .get(list.mkString(".")) + .map(_ => list) + .getOrElse(longestMatch(list.dropRight(1))) + + longestMatch(querys) match { + case Nil => notFound + case xs => downwardLookup(packages(xs.mkString(".")), querys diff xs) + } + } + + (querys, entity) match { + case (x :: Nil, e: Entity with Members) => + localLookup(e, x) + case (x :: _, e: Entity with Members) if x == entity.name => + downwardLookup(e, querys) + case _ => + globalLookup + } + } + + def makeEntityLink( + entity: Entity, + packages: Map[String, Package], + title: Inline, + pos: Position, + query: String + ): Inline = EntityLink(title, lookup(entity, packages, query, pos)) +} diff --git a/dottydoc/shared/src/main/scala/dotty/tools/dottydoc/model/Entity.scala b/dottydoc/shared/src/main/scala/dotty/tools/dottydoc/model/Entity.scala index 9bd175fde..675bc8319 100644 --- a/dottydoc/shared/src/main/scala/dotty/tools/dottydoc/model/Entity.scala +++ b/dottydoc/shared/src/main/scala/dotty/tools/dottydoc/model/Entity.scala @@ -11,13 +11,20 @@ object Entities { sealed trait Entity { def name: String + /** Path from root, i.e. `scala.Option$` */ def path: List[String] def comment: Option[Comment] - val sourceUrl: String = "#" + def sourceUrl: String = "#" - val kind: String + def kind: String + + def parent: Option[Entity] + + /** All parents from package level i.e. Package to Object to Member etc */ + def parents: List[Entity] = + parent.map(p => p :: p.parents).getOrElse(Nil) } sealed trait Members { @@ -34,11 +41,12 @@ object Entities { final case class Package( name: String, members: List[Entity], - comment: Option[Comment], - path: List[String] - ) extends Entity { + path: List[String], + var comment: Option[Comment] = None + ) extends Entity with Members { override val kind = "package" + var parent: Option[Entity] = None val children: List[PackageMember] = members.collect { case x: PackageMember => x } } @@ -46,59 +54,65 @@ object Entities { final case class Class( name: String, members: List[Entity], - comment: Option[Comment], modifiers: List[String], - path: List[String] + path: List[String], + var comment: Option[Comment] = None ) extends Entity with Members with Modifiers { override val kind = "class" + var parent: Option[Entity] = None } final case class CaseClass( name: String, members: List[Entity], - comment: Option[Comment], modifiers: List[String], - path: List[String] + path: List[String], + var comment: Option[Comment] = None ) extends Entity with Members with Modifiers { override val kind = "case class" + var parent: Option[Entity] = None } final case class Trait( name: String, members: List[Entity], - comment: Option[Comment], modifiers: List[String], - path: List[String] + path: List[String], + var comment: Option[Comment] = None ) extends Entity with Members with Modifiers { override val kind = "trait" + var parent: Option[Entity] = None } final case class Object( name: String, members: List[Entity], - comment: Option[Comment], modifiers: List[String], - path: List[String] + path: List[String], + var comment: Option[Comment] = None ) extends Entity with Members with Modifiers { override val kind = "object" + var parent: Option[Entity] = None } final case class Def( name: String, - comment: Option[Comment], modifiers: List[String], - path: List[String] + path: List[String], + var comment: Option[Comment] = None ) extends Entity with Modifiers { override val kind = "def" + var parent: Option[Entity] = None } final case class Val( name: String, - comment: Option[Comment], modifiers: List[String], - path: List[String] + path: List[String], + var comment: Option[Comment] = None ) extends Entity with Modifiers { override val kind = "val" + var parent: Option[Entity] = None } /** This object is used to represent entities that are to be filtered out */ @@ -107,6 +121,15 @@ object Entities { override val comment = None override val path = Nil override val kind = "" + override val parent = None + } + + final case object RootEntity extends Entity { + override val name = "root" + override val comment = None + override val path = Nil + override val kind = "" + override val parent = None } //implicit val pMPickler: PicklerPair[PackageMember] = CompositePickler[PackageMember] diff --git a/dottydoc/shared/src/main/scala/dotty/tools/dottydoc/model/EntityFactories.scala b/dottydoc/shared/src/main/scala/dotty/tools/dottydoc/model/EntityFactories.scala index 5e459a766..09510a1f8 100644 --- a/dottydoc/shared/src/main/scala/dotty/tools/dottydoc/model/EntityFactories.scala +++ b/dottydoc/shared/src/main/scala/dotty/tools/dottydoc/model/EntityFactories.scala @@ -16,7 +16,7 @@ object EntityFactories { def flags(t: Tree)(implicit ctx: Context): List[String] = (t.symbol.flags & SourceModifierFlags).flagStrings.toList - def path(t: Tree, name: String)(implicit ctx: Context): List[String] = { + def path(t: Tree)(implicit ctx: Context): List[String] = { def pathList(tpe: Type): List[String] = tpe match { case t: ThisType => pathList(t.tref) diff --git a/dottydoc/shared/src/main/scala/dotty/tools/dottydoc/util/Traversing.scala b/dottydoc/shared/src/main/scala/dotty/tools/dottydoc/util/Traversing.scala new file mode 100644 index 000000000..1aa07dec1 --- /dev/null +++ b/dottydoc/shared/src/main/scala/dotty/tools/dottydoc/util/Traversing.scala @@ -0,0 +1,13 @@ +package dotty.tools.dottydoc +package util + +object Traversing { + import model.Entities._ + + def mutateEntities(e: Entity)(trans: Entity => Unit): Unit = e match { + case e: Entity with Members => + trans(e) + e.members.map(mutateEntities(_)(trans)) + case e: Entity => trans(e) + } +} diff --git a/src/dotty/tools/dotc/config/Printers.scala b/src/dotty/tools/dotc/config/Printers.scala index 31c9d06cd..fa36ad12c 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 = new Printer + val dottydoc: Printer = noPrinter val core: Printer = noPrinter val typr: Printer = noPrinter val constr: Printer = noPrinter diff --git a/test/test/DottyDocParsingTests.scala b/test/test/DottyDocParsingTests.scala index b09d048da..ed89c6114 100644 --- a/test/test/DottyDocParsingTests.scala +++ b/test/test/DottyDocParsingTests.scala @@ -14,7 +14,7 @@ class DottyDocParsingTests extends DottyDocTest { checkFrontend(source) { case PackageDef(_, Seq(c: TypeDef)) => - assert(c.rawComment == None, "Should not have a comment, mainly used for exhaustive tests") + assert(c.rawComment.map(_.chrs) == None, "Should not have a comment, mainly used for exhaustive tests") } } @@ -29,7 +29,7 @@ class DottyDocParsingTests extends DottyDocTest { checkFrontend(source) { case PackageDef(_, Seq(t @ TypeDef(name, _))) if name.toString == "Class" => - checkDocString(t.rawComment, "/** Hello world! */") + checkDocString(t.rawComment.map(_.chrs), "/** Hello world! */") } } @@ -44,7 +44,7 @@ class DottyDocParsingTests extends DottyDocTest { checkFrontend(source) { case PackageDef(_, Seq(t @ TypeDef(name, _))) if name.toString == "Class" => - checkDocString(t.rawComment, "/** Hello /* multiple open */ world! */") + checkDocString(t.rawComment.map(_.chrs), "/** Hello /* multiple open */ world! */") } } @Test def multipleClassesInPackage = { @@ -62,8 +62,8 @@ class DottyDocParsingTests extends DottyDocTest { checkCompile("frontend", source) { (_, ctx) => ctx.compilationUnit.untpdTree match { case PackageDef(_, Seq(c1 @ TypeDef(_,_), c2 @ TypeDef(_,_))) => { - checkDocString(c1.rawComment, "/** Class1 docstring */") - checkDocString(c2.rawComment, "/** Class2 docstring */") + checkDocString(c1.rawComment.map(_.chrs), "/** Class1 docstring */") + checkDocString(c2.rawComment.map(_.chrs), "/** Class2 docstring */") } } } @@ -77,7 +77,7 @@ class DottyDocParsingTests extends DottyDocTest { """.stripMargin checkFrontend(source) { - case PackageDef(_, Seq(t @ TypeDef(_,_))) => checkDocString(t.rawComment, "/** Class without package */") + case PackageDef(_, Seq(t @ TypeDef(_,_))) => checkDocString(t.rawComment.map(_.chrs), "/** Class without package */") } } @@ -85,7 +85,7 @@ class DottyDocParsingTests extends DottyDocTest { val source = "/** Trait docstring */\ntrait Trait" checkFrontend(source) { - case PackageDef(_, Seq(t @ TypeDef(_,_))) => checkDocString(t.rawComment, "/** Trait docstring */") + case PackageDef(_, Seq(t @ TypeDef(_,_))) => checkDocString(t.rawComment.map(_.chrs), "/** Trait docstring */") } } @@ -101,8 +101,8 @@ class DottyDocParsingTests extends DottyDocTest { checkFrontend(source) { case PackageDef(_, Seq(t1 @ TypeDef(_,_), t2 @ TypeDef(_,_))) => { - checkDocString(t1.rawComment, "/** Trait1 docstring */") - checkDocString(t2.rawComment, "/** Trait2 docstring */") + checkDocString(t1.rawComment.map(_.chrs), "/** Trait1 docstring */") + checkDocString(t2.rawComment.map(_.chrs), "/** Trait2 docstring */") } } } @@ -127,10 +127,10 @@ class DottyDocParsingTests extends DottyDocTest { checkFrontend(source) { case PackageDef(_, Seq(t1 @ TypeDef(_,_), c2 @ TypeDef(_,_), cc3 @ TypeDef(_,_), _, ac4 @ TypeDef(_,_))) => { - checkDocString(t1.rawComment, "/** Trait1 docstring */") - checkDocString(c2.rawComment, "/** Class2 docstring */") - checkDocString(cc3.rawComment, "/** CaseClass3 docstring */") - checkDocString(ac4.rawComment, "/** AbstractClass4 docstring */") + checkDocString(t1.rawComment.map(_.chrs), "/** Trait1 docstring */") + checkDocString(c2.rawComment.map(_.chrs), "/** Class2 docstring */") + checkDocString(cc3.rawComment.map(_.chrs), "/** CaseClass3 docstring */") + checkDocString(ac4.rawComment.map(_.chrs), "/** AbstractClass4 docstring */") } } } @@ -147,9 +147,9 @@ class DottyDocParsingTests extends DottyDocTest { checkFrontend(source) { case PackageDef(_, Seq(outer @ TypeDef(_, tpl @ Template(_,_,_,_)))) => { - checkDocString(outer.rawComment, "/** Outer docstring */") + checkDocString(outer.rawComment.map(_.chrs), "/** Outer docstring */") tpl.body match { - case (inner @ TypeDef(_,_)) :: _ => checkDocString(inner.rawComment, "/** Inner docstring */") + case (inner @ TypeDef(_,_)) :: _ => checkDocString(inner.rawComment.map(_.chrs), "/** Inner docstring */") case _ => assert(false, "Couldn't find inner class") } } @@ -171,10 +171,10 @@ class DottyDocParsingTests extends DottyDocTest { checkFrontend(source) { case PackageDef(_, Seq(o1 @ TypeDef(_, tpl @ Template(_,_,_,_)), o2 @ TypeDef(_,_))) => { - checkDocString(o1.rawComment, "/** Outer1 docstring */") - checkDocString(o2.rawComment, "/** Outer2 docstring */") + checkDocString(o1.rawComment.map(_.chrs), "/** Outer1 docstring */") + checkDocString(o2.rawComment.map(_.chrs), "/** Outer2 docstring */") tpl.body match { - case (inner @ TypeDef(_,_)) :: _ => checkDocString(inner.rawComment, "/** Inner docstring */") + case (inner @ TypeDef(_,_)) :: _ => checkDocString(inner.rawComment.map(_.chrs), "/** Inner docstring */") case _ => assert(false, "Couldn't find inner class") } } @@ -196,9 +196,9 @@ class DottyDocParsingTests extends DottyDocTest { checkFrontend(source) { case p @ PackageDef(_, Seq(o1: MemberDef[Untyped], o2: MemberDef[Untyped])) => { assertEquals(o1.name.toString, "Object1") - checkDocString(o1.rawComment, "/** Object1 docstring */") + checkDocString(o1.rawComment.map(_.chrs), "/** Object1 docstring */") assertEquals(o2.name.toString, "Object2") - checkDocString(o2.rawComment, "/** Object2 docstring */") + checkDocString(o2.rawComment.map(_.chrs), "/** Object2 docstring */") } } } @@ -223,12 +223,12 @@ class DottyDocParsingTests extends DottyDocTest { checkFrontend(source) { case p @ PackageDef(_, Seq(o1: ModuleDef, o2: ModuleDef)) => { assert(o1.name.toString == "Object1") - checkDocString(o1.rawComment, "/** Object1 docstring */") + checkDocString(o1.rawComment.map(_.chrs), "/** Object1 docstring */") assert(o2.name.toString == "Object2") - checkDocString(o2.rawComment, "/** Object2 docstring */") + checkDocString(o2.rawComment.map(_.chrs), "/** Object2 docstring */") o2.impl.body match { - case _ :: (inner @ TypeDef(_,_)) :: _ => checkDocString(inner.rawComment, "/** Inner docstring */") + case _ :: (inner @ TypeDef(_,_)) :: _ => checkDocString(inner.rawComment.map(_.chrs), "/** Inner docstring */") case _ => assert(false, "Couldn't find inner class") } } @@ -257,14 +257,14 @@ class DottyDocParsingTests extends DottyDocTest { import dotty.tools.dotc.ast.untpd._ checkFrontend(source) { case PackageDef(_, Seq(p: ModuleDef)) => { - checkDocString(p.rawComment, "/** Package object docstring */") + checkDocString(p.rawComment.map(_.chrs), "/** Package object docstring */") p.impl.body match { case (b: TypeDef) :: (t: TypeDef) :: (o: ModuleDef) :: Nil => { - checkDocString(b.rawComment, "/** Boo docstring */") - checkDocString(t.rawComment, "/** Trait docstring */") - checkDocString(o.rawComment, "/** InnerObject docstring */") - checkDocString(o.impl.body.head.asInstanceOf[TypeDef].rawComment, "/** InnerClass docstring */") + checkDocString(b.rawComment.map(_.chrs), "/** Boo docstring */") + checkDocString(t.rawComment.map(_.chrs), "/** Trait docstring */") + checkDocString(o.rawComment.map(_.chrs), "/** InnerObject docstring */") + checkDocString(o.impl.body.head.asInstanceOf[TypeDef].rawComment.map(_.chrs), "/** InnerClass docstring */") } case _ => assert(false, "Incorrect structure inside package object") } @@ -284,7 +284,7 @@ class DottyDocParsingTests extends DottyDocTest { import dotty.tools.dotc.ast.untpd._ checkFrontend(source) { case PackageDef(_, Seq(c: TypeDef)) => - checkDocString(c.rawComment, "/** Real comment */") + checkDocString(c.rawComment.map(_.chrs), "/** Real comment */") } } @@ -303,7 +303,7 @@ class DottyDocParsingTests extends DottyDocTest { import dotty.tools.dotc.ast.untpd._ checkFrontend(source) { case PackageDef(_, Seq(c: TypeDef)) => - checkDocString(c.rawComment, "/** Real comment */") + checkDocString(c.rawComment.map(_.chrs), "/** Real comment */") } } @@ -329,9 +329,9 @@ class DottyDocParsingTests extends DottyDocTest { case PackageDef(_, Seq(o: ModuleDef)) => { o.impl.body match { case (v1: MemberDef) :: (v2: MemberDef) :: (v3: MemberDef) :: Nil => { - checkDocString(v1.rawComment, "/** val1 */") - checkDocString(v2.rawComment, "/** val2 */") - checkDocString(v3.rawComment, "/** val3 */") + checkDocString(v1.rawComment.map(_.chrs), "/** val1 */") + checkDocString(v2.rawComment.map(_.chrs), "/** val2 */") + checkDocString(v3.rawComment.map(_.chrs), "/** val3 */") } case _ => assert(false, "Incorrect structure inside object") } @@ -361,9 +361,9 @@ class DottyDocParsingTests extends DottyDocTest { case PackageDef(_, Seq(o: ModuleDef)) => { o.impl.body match { case (v1: MemberDef) :: (v2: MemberDef) :: (v3: MemberDef) :: Nil => { - checkDocString(v1.rawComment, "/** var1 */") - checkDocString(v2.rawComment, "/** var2 */") - checkDocString(v3.rawComment, "/** var3 */") + checkDocString(v1.rawComment.map(_.chrs), "/** var1 */") + checkDocString(v2.rawComment.map(_.chrs), "/** var2 */") + checkDocString(v3.rawComment.map(_.chrs), "/** var3 */") } case _ => assert(false, "Incorrect structure inside object") } @@ -393,9 +393,9 @@ class DottyDocParsingTests extends DottyDocTest { case PackageDef(_, Seq(o: ModuleDef)) => { o.impl.body match { case (v1: MemberDef) :: (v2: MemberDef) :: (v3: MemberDef) :: Nil => { - checkDocString(v1.rawComment, "/** def1 */") - checkDocString(v2.rawComment, "/** def2 */") - checkDocString(v3.rawComment, "/** def3 */") + checkDocString(v1.rawComment.map(_.chrs), "/** def1 */") + checkDocString(v2.rawComment.map(_.chrs), "/** def2 */") + checkDocString(v3.rawComment.map(_.chrs), "/** def3 */") } case _ => assert(false, "Incorrect structure inside object") } @@ -425,9 +425,9 @@ class DottyDocParsingTests extends DottyDocTest { case PackageDef(_, Seq(o: ModuleDef)) => { o.impl.body match { case (v1: MemberDef) :: (v2: MemberDef) :: (v3: MemberDef) :: Nil => { - checkDocString(v1.rawComment, "/** type1 */") - checkDocString(v2.rawComment, "/** type2 */") - checkDocString(v3.rawComment, "/** type3 */") + checkDocString(v1.rawComment.map(_.chrs), "/** type1 */") + checkDocString(v2.rawComment.map(_.chrs), "/** type2 */") + checkDocString(v3.rawComment.map(_.chrs), "/** type3 */") } case _ => assert(false, "Incorrect structure inside object") } @@ -451,7 +451,7 @@ class DottyDocParsingTests extends DottyDocTest { case PackageDef(_, Seq(o: ModuleDef)) => o.impl.body match { case (foo: MemberDef) :: Nil => - expectNoDocString(foo.rawComment) + expectNoDocString(foo.rawComment.map(_.chrs)) case _ => assert(false, "Incorrect structure inside object") } } @@ -468,7 +468,7 @@ class DottyDocParsingTests extends DottyDocTest { import dotty.tools.dotc.ast.untpd._ checkFrontend(source) { case p @ PackageDef(_, Seq(_, c: TypeDef)) => - checkDocString(c.rawComment, "/** Class1 */") + checkDocString(c.rawComment.map(_.chrs), "/** Class1 */") } } @@ -483,7 +483,7 @@ class DottyDocParsingTests extends DottyDocTest { import dotty.tools.dotc.ast.untpd._ checkFrontend(source) { case p @ PackageDef(_, Seq(c: TypeDef)) => - checkDocString(c.rawComment, "/** Class1 */") + checkDocString(c.rawComment.map(_.chrs), "/** Class1 */") } } } /* End class */ |