package dotty.tools
package dottydoc
package core
/** Dotty and Dottydoc imports */
import dotc.ast.Trees._
import dotc.CompilationUnit
import dotc.config.Printers.dottydoc
import dotc.core.Contexts.Context
import dotc.core.Comments.ContextDocstrings
import dotc.core.Types.NoType
import dotc.core.Phases.Phase
import dotc.core.Symbols.{ Symbol, NoSymbol }
class DocASTPhase extends Phase {
import model._
import model.factories._
import model.internal._
import model.comment.Comment
import dotty.tools.dotc.core.Flags
import dotty.tools.dotc.ast.tpd._
import dotty.tools.dottydoc.util.syntax._
import util.traversing._
import util.internal.setters._
def phaseName = "docphase"
/** Build documentation hierarchy from existing tree */
def collect(tree: Tree, prev: List[String] = Nil)(implicit ctx: Context): Entity = {
val implicitConversions = ctx.docbase.defs(tree.symbol)
def collectList(xs: List[Tree], ps: List[String]): List[Entity] =
xs.map(collect(_, ps)).filter(_ != NonEntity)
def collectEntityMembers(xs: List[Tree], ps: List[String]) =
collectList(xs, ps).asInstanceOf[List[Entity with Members]]
def collectMembers(tree: Tree, ps: List[String] = prev)(implicit ctx: Context): List[Entity] = {
val defs = (tree match {
case t: Template => collectList(t.body, ps)
case _ => Nil
})
defs ++ implicitConversions.flatMap(membersFromSymbol)
}
def membersFromSymbol(sym: Symbol): List[Entity] = {
if (sym.info.exists) {
val defs = sym.info.bounds.hi.finalResultType.membersBasedOnFlags(Flags.Method, Flags.Synthetic | Flags.Private)
.filterNot(_.symbol.owner.name.show == "Any")
.map { meth =>
DefImpl(
meth.symbol,
annotations(meth.symbol),
meth.symbol.name.show,
Nil,
path(meth.symbol),
returnType(meth.info),
typeParams(meth.symbol),
paramLists(meth.info),
implicitlyAddedFrom = Some(returnType(meth.symbol.owner.info))
)
}.toList
// don't add privates, synthetics or class parameters (i.e. value class constructor val)
val vals = sym.info.fields.filterNot(_.symbol.is(Flags.ParamAccessor | Flags.Private | Flags.Synthetic)).map { value =>
println(value + " " + value.symbol.flags)
val kind = if (value.symbol.is(Flags.Mutable)) "var" else "val"
ValImpl(
value.symbol,
annotations(value.symbol),
value.symbol.name.show,
Nil, path(value.symbol),
returnType(value.info),
kind,
implicitlyAddedFrom = Some(returnType(value.symbol.owner.info))
)
}
defs ++ vals
}
else Nil
}
tree match {
/** package */
case pd @ PackageDef(pid, st) =>
val pkgPath = path(pd.symbol)
addEntity(PackageImpl(pd.symbol, annotations(pd.symbol), pd.symbol.showFullName, collectEntityMembers(st, pkgPath), pkgPath))
/** type alias */
case t: TypeDef if !t.isClassDef =>
val sym = t.symbol
TypeAliasImpl(sym, annotations(sym), flags(t), t.name.show.split("\\$\\$").last, path(sym), None)
/** trait */
case t @ TypeDef(n, rhs) if t.symbol.is(Flags.Trait) =>
//TODO: should not `collectMember` from `rhs` - instead: get from symbol, will get inherited members as well
TraitImpl(t.symbol, annotations(t.symbol), n.show, collectMembers(rhs), flags(t), path(t.symbol), typeParams(t.symbol), traitParameters(t.symbol), superTypes(t))
/** objects, on the format "Object$" so drop the last letter */
case o @ TypeDef(n, rhs) if o.symbol.is(Flags.Module) =>
val name = o.name.show
//TODO: should not `collectMember` from `rhs` - instead: get from symbol, will get inherited members as well
ObjectImpl(o.symbol, annotations(o.symbol), name.dropRight(1), collectMembers(rhs, prev :+ name), flags(o), path(o.symbol).init :+ name, superTypes(o))
/** class / case class */
case c @ TypeDef(n, rhs) if c.symbol.isClass =>
//TODO: should not `collectMember` from `rhs` - instead: get from symbol, will get inherited members as well
(c.symbol, annotations(c.symbol), n.show, collectMembers(rhs), flags(c), path(c.symbol), typeParams(c.symbol), constructors(c.symbol), superTypes(c), None) match {
case x if c.symbol.is(Flags.CaseClass) => CaseClassImpl.tupled(x)
case x => ClassImpl.tupled(x)
}
/** def */
case d: DefDef =>
DefImpl(d.symbol, annotations(d.symbol), d.name.decode.toString, flags(d), path(d.symbol), returnType(d.tpt.tpe), typeParams(d.symbol), paramLists(d.symbol.info))
/** val */
case v: ValDef if !v.symbol.is(Flags.ModuleVal) =>
val kind = if (v.symbol.is(Flags.Mutable)) "var" else "val"
ValImpl(v.symbol, annotations(v.symbol), v.name.decode.toString, flags(v), path(v.symbol), returnType(v.tpt.tpe), kind)
case x => {
//dottydoc.println(s"Found unwanted entity: $x (${x.pos},\n${x.show}")
NonEntity
}
}
}
var packages: Map[String, Package] = Map.empty
def addEntity(p: Package): Package = {
def mergedChildren(x1s: List[Entity], x2s: List[Entity]): List[Entity] = {
val (packs1, others1) = x1s.partition(_.kind == "package")
val (packs2, others2) = x2s.partition(_.kind == "package")
val others = others1 ::: others2
val packs = (packs1 ::: packs2).groupBy(_.path).map(_._2.head)
(others ++ packs).sortBy(_.name)
}
val path = p.path.mkString(".")
val newPack = packages.get(path).map {
case ex: PackageImpl =>
if (!ex.comment.isDefined) ex.comment = p.comment
ex.members = mergedChildren(ex.members, p.members)
ex
}.getOrElse(p)
packages = packages + (path -> newPack)
newPack
}
private[this] var totalRuns = 0
private[this] var currentRun = 0
override def run(implicit ctx: Context): Unit = {
currentRun += 1
println(s"Compiling ($currentRun/$totalRuns): ${ctx.compilationUnit.source.file.name}")
collect(ctx.compilationUnit.tpdTree) // Will put packages in `packages` var
}
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
totalRuns = units.length
val compUnits = super.runOn(units)
// (2) Set parents of entities, needed for linking
for {
parent <- packages.values
child <- parent.children
} setParent(child, to = parent)
// (3) Update Doc AST in ctx.base
for (kv <- packages) ctx.docbase.packagesMutable += kv
// Return super's result
compUnits
}
}