diff options
Diffstat (limited to 'dottydoc')
18 files changed, 435 insertions, 739 deletions
diff --git a/dottydoc/src/dotty/tools/dottydoc/DottyDoc.scala b/dottydoc/src/dotty/tools/dottydoc/DottyDoc.scala index 2d4c7abcf..0dea96134 100644 --- a/dottydoc/src/dotty/tools/dottydoc/DottyDoc.scala +++ b/dottydoc/src/dotty/tools/dottydoc/DottyDoc.scala @@ -1,11 +1,13 @@ package dotty.tools package dottydoc +import dotty.tools.dottydoc.util.syntax._ import core._ import core.transform._ import dotc.config.CompilerCommand import dotc.config.Printers.dottydoc import dotc.core.Contexts._ +import dotc.core.Comments.ContextDoc import dotc.core.Phases.Phase import dotc.typer.FrontEnd import dotc.{ CompilationUnit, Compiler, Driver, Run } @@ -31,7 +33,9 @@ class DocCompiler extends Compiler { List(new DocFrontEnd), List(new DocImplicitsPhase), List(new DocASTPhase), - List(DocMiniTransformations(new LinkReturnTypes, + List(DocMiniTransformations(new UsecasePhase, + new DocstringPhase, + new LinkReturnTypes, new LinkParamListTypes, new LinkImplicitlyAddedTypes, new LinkSuperTypes, @@ -54,6 +58,7 @@ abstract class DocDriver extends Driver { ctx.setSettings(summary.sstate) ctx.setSetting(ctx.settings.YkeepComments, true) + ctx.setProperty(ContextDoc, new ContextDottydoc) val fileNames = CompilerCommand.checkUsage(summary, sourcesRequired)(ctx) (fileNames, ctx) @@ -65,7 +70,7 @@ abstract class DocDriver extends Driver { val (fileNames, ctx) = setup(args, initCtx.fresh) doCompile(newCompiler(ctx), fileNames)(ctx) - ctx.docbase.packages[Package] + ctx.docbase.packages } def compiledDocsJava(args: Array[String]): JMap[String, Package] = diff --git a/dottydoc/src/dotty/tools/dottydoc/core/ContextDottydoc.scala b/dottydoc/src/dotty/tools/dottydoc/core/ContextDottydoc.scala new file mode 100644 index 000000000..c60038836 --- /dev/null +++ b/dottydoc/src/dotty/tools/dottydoc/core/ContextDottydoc.scala @@ -0,0 +1,23 @@ +package dotty.tools +package dottydoc +package core + +import dotc.core.Symbols.Symbol +import dotc.core.Comments.ContextDocstrings +import model.Package + +class ContextDottydoc extends ContextDocstrings { + import scala.collection.mutable + + private[this] val _packages: mutable.Map[String, Package] = mutable.Map.empty + def packages: Map[String, Package] = _packages.toMap + def packagesMutable: mutable.Map[String, Package] = _packages + + /** Should perhaps factorize this into caches that get flushed */ + private var _defs: Map[Symbol, Set[Symbol]] = Map.empty + def defs(sym: Symbol): Set[Symbol] = _defs.get(sym).getOrElse(Set.empty) + + def addDef(s: Symbol, d: Symbol): Unit = _defs = (_defs + { + s -> _defs.get(s).map(xs => xs + d).getOrElse(Set(d)) + }) +} diff --git a/dottydoc/src/dotty/tools/dottydoc/core/DocASTPhase.scala b/dottydoc/src/dotty/tools/dottydoc/core/DocASTPhase.scala index 7744752ce..806d9d0ae 100644 --- a/dottydoc/src/dotty/tools/dottydoc/core/DocASTPhase.scala +++ b/dottydoc/src/dotty/tools/dottydoc/core/DocASTPhase.scala @@ -7,6 +7,7 @@ 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.Phases.Phase import dotc.core.Symbols.{ Symbol, NoSymbol } @@ -14,29 +15,17 @@ class DocASTPhase extends Phase { import model._ import model.factories._ import model.internal._ - import model.parsers.WikiParser 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" - private[this] val commentParser = new WikiParser - - /** Saves the commentParser function for later evaluation, for when the AST has been filled */ - def track(symbol: Symbol, ctx: Context, parent: Symbol = NoSymbol)(op: => Entity) = { - val entity = op - - if (entity != NonEntity) - commentParser += (entity, symbol, parent, ctx) - - entity - } - /** Build documentation hierarchy from existing tree */ - def collect(tree: Tree, prev: List[String] = Nil)(implicit ctx: Context): Entity = track(tree.symbol, ctx) { + 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] = @@ -58,28 +47,26 @@ class DocASTPhase extends Phase { val defs = sym.info.bounds.hi.membersBasedOnFlags(Flags.Method, Flags.Synthetic | Flags.Private) .filterNot(_.symbol.owner.name.show == "Any") .map { meth => - track(meth.symbol, ctx, tree.symbol) { - DefImpl( - meth.symbol.name.show, - Nil, - path(meth.symbol), - returnType(meth.info), - typeParams(meth.symbol), - paramLists(meth.info), - implicitlyAddedFrom = Some(returnType(meth.symbol.owner.info)) - ) - } + DefImpl( + 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 val vals = sym.info.fields.filterNot(_.symbol.is(Flags.Private | Flags.Synthetic)).map { value => - track(value.symbol, ctx, tree.symbol) { - ValImpl( - value.symbol.name.show, - Nil, path(value.symbol), - returnType(value.info), - implicitlyAddedFrom = Some(returnType(value.symbol.owner.info)) - ) - } + ValImpl( + value.symbol, + value.symbol.name.show, + Nil, path(value.symbol), + returnType(value.info), + implicitlyAddedFrom = Some(returnType(value.symbol.owner.info)) + ) } defs ++ vals @@ -90,38 +77,38 @@ class DocASTPhase extends Phase { /** package */ case pd @ PackageDef(pid, st) => val newPath = prev :+ pid.name.toString - addEntity(PackageImpl(newPath.mkString("."), collectEntityMembers(st, newPath), newPath)) + addEntity(PackageImpl(pd.symbol, newPath.mkString("."), collectEntityMembers(st, newPath), newPath)) /** trait */ case t @ TypeDef(n, rhs) if t.symbol.is(Flags.Trait) => val name = n.decode.toString val newPath = prev :+ name //TODO: should not `collectMember` from `rhs` - instead: get from symbol, will get inherited members as well - TraitImpl(name, collectMembers(rhs), flags(t), newPath, typeParams(t.symbol), traitParameters(t.symbol), superTypes(t)) + TraitImpl(t.symbol, name, collectMembers(rhs), flags(t), newPath, 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 = n.decode.toString.dropRight(1) //TODO: should not `collectMember` from `rhs` - instead: get from symbol, will get inherited members as well - ObjectImpl(name, collectMembers(rhs, prev :+ name), flags(o), prev :+ (name + "$"), superTypes(o)) + ObjectImpl(o.symbol, name, collectMembers(rhs, prev :+ name), flags(o), prev :+ (name + "$"), superTypes(o)) /** class / case class */ case c @ TypeDef(n, rhs) if c.symbol.isClass => val name = n.decode.toString val newPath = prev :+ name //TODO: should not `collectMember` from `rhs` - instead: get from symbol, will get inherited members as well - (name, collectMembers(rhs), flags(c), newPath, typeParams(c.symbol), constructors(c.symbol), superTypes(c), None) match { + (c.symbol, name, collectMembers(rhs), flags(c), newPath, 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.name.decode.toString, flags(d), path(d.symbol), returnType(d.tpt.tpe), typeParams(d.symbol), paramLists(d.symbol.info)) + DefImpl(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) => - ValImpl(v.name.decode.toString, flags(v), path(v.symbol), returnType(v.tpt.tpe)) + ValImpl(v.symbol, v.name.decode.toString, flags(v), path(v.symbol), returnType(v.tpt.tpe)) case x => { //dottydoc.println(s"Found unwanted entity: $x (${x.pos},\n${x.show}") @@ -175,15 +162,8 @@ class DocASTPhase extends Phase { child <- parent.children } setParent(child, to = parent) - // (3) Create documentation template from docstrings, with internal links - println("Generating documentation, this might take a while...") - commentParser.parse(packages) - - // (4) Clear caches - commentParser.clear() - - // (5) Update Doc AST in ctx.base - for (kv <- packages) ctx.docbase.packages += kv + // (3) Update Doc AST in ctx.base + for (kv <- packages) ctx.docbase.packagesMutable += kv // Return super's result compUnits diff --git a/dottydoc/src/dotty/tools/dottydoc/core/DocImplicitsPhase.scala b/dottydoc/src/dotty/tools/dottydoc/core/DocImplicitsPhase.scala index f322d7a5a..6577f0cb7 100644 --- a/dottydoc/src/dotty/tools/dottydoc/core/DocImplicitsPhase.scala +++ b/dottydoc/src/dotty/tools/dottydoc/core/DocImplicitsPhase.scala @@ -5,6 +5,7 @@ package core import dotty.tools.dotc.transform.TreeTransforms.{ MiniPhaseTransform, TransformerInfo } import dotty.tools.dotc.core.Flags import dotc.core.Contexts.Context +import util.syntax._ class DocImplicitsPhase extends MiniPhaseTransform { thisTransformer => import dotty.tools.dotc.ast.tpd._ diff --git a/dottydoc/src/dotty/tools/dottydoc/core/DocstringPhase.scala b/dottydoc/src/dotty/tools/dottydoc/core/DocstringPhase.scala new file mode 100644 index 000000000..cff614528 --- /dev/null +++ b/dottydoc/src/dotty/tools/dottydoc/core/DocstringPhase.scala @@ -0,0 +1,47 @@ +package dotty.tools +package dottydoc +package core + +import dotc.core.Contexts.Context +import transform.DocMiniPhase +import model._ +import model.internal._ +import model.comment._ +import BodyParsers._ +import util.syntax._ + +class DocstringPhase extends DocMiniPhase with CommentParser with CommentCleaner { + private def parsedComment[E <: Entity](ent: E)(implicit ctx: Context): Option[Comment] = + ctx.docbase.docstring(ent.symbol).map { cmt => + parse(ent, ctx.docbase.packages, clean(cmt.raw), cmt.raw, cmt.pos) + .toComment(_.toHtml(ent)) + } + + override def transformPackage(implicit ctx: Context) = { case ent: PackageImpl => + ent.copy(comment = parsedComment(ent)) + } + + override def transformClass(implicit ctx: Context) = { case ent: ClassImpl => + ent.copy(comment = parsedComment(ent)) + } + + override def transformCaseClass(implicit ctx: Context) = { case ent: CaseClassImpl => + ent.copy(comment = parsedComment(ent)) + } + + override def transformTrait(implicit ctx: Context) = { case ent: TraitImpl => + ent.copy(comment = parsedComment(ent)) + } + + override def transformObject(implicit ctx: Context) = { case ent: ObjectImpl => + ent.copy(comment = parsedComment(ent)) + } + + override def transformDef(implicit ctx: Context) = { case ent: DefImpl => + ent.copy(comment = parsedComment(ent)) + } + + override def transformVal(implicit ctx: Context) = { case ent: ValImpl => + ent.copy(comment = parsedComment(ent)) + } +} diff --git a/dottydoc/src/dotty/tools/dottydoc/core/MiniPhaseTransform.scala b/dottydoc/src/dotty/tools/dottydoc/core/MiniPhaseTransform.scala index 2690ac7b7..150a4e08f 100644 --- a/dottydoc/src/dotty/tools/dottydoc/core/MiniPhaseTransform.scala +++ b/dottydoc/src/dotty/tools/dottydoc/core/MiniPhaseTransform.scala @@ -4,9 +4,11 @@ package core import dotc.CompilationUnit import dotc.core.Contexts.Context +import dotc.core.Comments.ContextDocstrings import dotc.core.Phases.Phase import model._ import model.internal._ +import util.syntax._ object transform { /** @@ -43,9 +45,9 @@ object transform { override def runOn(units: List[CompilationUnit])(implicit ctx: Context): List[CompilationUnit] = { for { rootName <- rootPackages - pack = ctx.docbase.packages[Package](rootName) + pack = ctx.docbase.packages(rootName) transformed = performPackageTransform(pack) - } yield ctx.docbase.packages(rootName) = transformed + } yield ctx.docbase.packagesMutable(rootName) = transformed super.runOn(units) } @@ -77,6 +79,7 @@ object transform { def traverse(ent: Entity): Entity = ent match { case p: Package => transformEntity(p, _.packageTransformation) { p => val newPackage = PackageImpl( + p.symbol, p.name, p.members.map(traverse), p.path, @@ -84,12 +87,13 @@ object transform { ) // Update reference in context to newPackage - ctx.docbase.packages[Package] += (newPackage.path.mkString(".") -> newPackage) + ctx.docbase.packagesMutable += (newPackage.path.mkString(".") -> newPackage) newPackage } case c: Class => transformEntity(c, _.classTransformation) { cls => ClassImpl( + cls.symbol, cls.name, cls.members.map(traverse), cls.modifiers, @@ -102,6 +106,7 @@ object transform { } case cc: CaseClass => transformEntity(cc, _.caseClassTransformation) { cc => CaseClassImpl( + cc.symbol, cc.name, cc.members.map(traverse), cc.modifiers, @@ -114,6 +119,7 @@ object transform { } case trt: Trait => transformEntity(trt, _.traitTransformation) { trt => TraitImpl( + trt.symbol, trt.name, trt.members.map(traverse), trt.modifiers, @@ -126,6 +132,7 @@ object transform { } case obj: Object => transformEntity(obj, _.objectTransformation) { obj => ObjectImpl( + obj.symbol, obj.name, obj.members.map(traverse), obj.modifiers, @@ -136,6 +143,7 @@ object transform { } case df: Def => transformEntity(df, _.defTransformation) { df => DefImpl( + df.symbol, df.name, df.modifiers, df.path, @@ -148,6 +156,7 @@ object transform { } case vl: Val => transformEntity(vl, _.valTransformation) { vl => ValImpl( + vl.symbol, vl.name, vl.modifiers, vl.path, diff --git a/dottydoc/src/dotty/tools/dottydoc/core/TypeLinkingPhases.scala b/dottydoc/src/dotty/tools/dottydoc/core/TypeLinkingPhases.scala index ae07effa9..1aecca9e1 100644 --- a/dottydoc/src/dotty/tools/dottydoc/core/TypeLinkingPhases.scala +++ b/dottydoc/src/dotty/tools/dottydoc/core/TypeLinkingPhases.scala @@ -14,15 +14,16 @@ import BodyParsers._ import util.MemberLookup import util.traversing._ import util.internal.setters._ +import util.syntax._ class LinkReturnTypes extends DocMiniPhase with TypeLinker { override def transformDef(implicit ctx: Context) = { case df: DefImpl => - val returnValue = linkReference(df, df.returnValue, ctx.docbase.packages[Package].toMap) + val returnValue = linkReference(df, df.returnValue, ctx.docbase.packages) df.copy(returnValue = returnValue) } override def transformVal(implicit ctx: Context) = { case vl: ValImpl => - val returnValue = linkReference(vl, vl.returnValue, ctx.docbase.packages[Package].toMap) + val returnValue = linkReference(vl, vl.returnValue, ctx.docbase.packages) vl.copy(returnValue = returnValue) } } @@ -31,7 +32,7 @@ class LinkParamListTypes extends DocMiniPhase with TypeLinker { override def transformDef(implicit ctx: Context) = { case df: DefImpl => val newParamLists = for { ParamListImpl(list, isImplicit) <- df.paramLists - newList = list.map(linkReference(df, _, ctx.docbase.packages[Package].toMap)) + newList = list.map(linkReference(df, _, ctx.docbase.packages)) } yield ParamListImpl(newList.asInstanceOf[List[NamedReference]], isImplicit) df.copy(paramLists = newParamLists) @@ -42,7 +43,7 @@ class LinkSuperTypes extends DocMiniPhase with TypeLinker { def linkSuperTypes(ent: Entity with SuperTypes)(implicit ctx: Context): List[MaterializableLink] = ent.superTypes.collect { case UnsetLink(title, query) => - val packages = ctx.docbase.packages[Package].toMap + val packages = ctx.docbase.packages val entityLink = makeEntityLink(ent, packages, Text(title), NoPosition, query).link handleEntityLink(title, entityLink, ent) } @@ -67,13 +68,13 @@ class LinkSuperTypes extends DocMiniPhase with TypeLinker { class LinkImplicitlyAddedTypes extends DocMiniPhase with TypeLinker { override def transformDef(implicit ctx: Context) = { case df: DefImpl if df.implicitlyAddedFrom.isDefined => - val implicitlyAddedFrom = linkReference(df, df.implicitlyAddedFrom.get, ctx.docbase.packages[Package].toMap) + val implicitlyAddedFrom = linkReference(df, df.implicitlyAddedFrom.get, ctx.docbase.packages) df.copy(implicitlyAddedFrom = Some(implicitlyAddedFrom)) } override def transformVal(implicit ctx: Context) = { case vl: ValImpl if vl.implicitlyAddedFrom.isDefined => - val implicitlyAddedFrom = linkReference(vl, vl.implicitlyAddedFrom.get, ctx.docbase.packages[Package].toMap) + val implicitlyAddedFrom = linkReference(vl, vl.implicitlyAddedFrom.get, ctx.docbase.packages) vl.copy(implicitlyAddedFrom = Some(implicitlyAddedFrom)) } } diff --git a/dottydoc/src/dotty/tools/dottydoc/core/UsecasePhase.scala b/dottydoc/src/dotty/tools/dottydoc/core/UsecasePhase.scala new file mode 100644 index 000000000..47376eb26 --- /dev/null +++ b/dottydoc/src/dotty/tools/dottydoc/core/UsecasePhase.scala @@ -0,0 +1,31 @@ +package dotty.tools +package dottydoc +package core + +import dotc.core.Contexts.Context +import dotc.ast.tpd + +import transform.DocMiniPhase +import model.internal._ +import model.factories._ +import dotty.tools.dotc.core.Symbols.Symbol +import util.syntax._ + +class UsecasePhase extends DocMiniPhase { + private def defdefToDef(d: tpd.DefDef, sym: Symbol)(implicit ctx: Context) = { + val name = d.name.show.split("\\$").head // UseCase defs get $pos appended to their names + DefImpl( + sym, + name, + flags(d), + path(d.symbol).init :+ name, + returnType(d.tpt.tpe), + typeParams(d.symbol), + paramLists(d.symbol.info) + ) + } + + override def transformDef(implicit ctx: Context) = { case df: DefImpl => + ctx.docbase.docstring(df.symbol).flatMap(_.usecases.headOption.map(_.tpdCode)).map(defdefToDef(_, df.symbol)).getOrElse(df) + } +} diff --git a/dottydoc/src/dotty/tools/dottydoc/model/comment/CommentExpander.scala b/dottydoc/src/dotty/tools/dottydoc/model/comment/CommentExpander.scala deleted file mode 100644 index 32a0d8128..000000000 --- a/dottydoc/src/dotty/tools/dottydoc/model/comment/CommentExpander.scala +++ /dev/null @@ -1,344 +0,0 @@ -/* - * Port of DocComment.scala from nsc - * @author Martin Odersky - * @author Felix Mulder - */ - -package dotty.tools -package dottydoc -package model -package comment - -import dotc.config.Printers.dottydoc -import dotc.core.Contexts.Context -import dotc.core.Symbols._ -import dotc.core.Flags -import dotc.util.Positions._ - -import scala.collection.mutable - -trait CommentExpander { - import CommentUtils._ - - def expand(sym: Symbol, site: Symbol)(implicit ctx: Context): String = { - val parent = if (site != NoSymbol) site else sym - defineVariables(parent) - expandedDocComment(sym, parent) - } - - /** The cooked doc comment of symbol `sym` after variable expansion, or "" if missing. - * - * @param sym The symbol for which doc comment is returned - * @param site The class for which doc comments are generated - * @throws ExpansionLimitExceeded when more than 10 successive expansions - * of the same string are done, which is - * interpreted as a recursive variable definition. - */ - def expandedDocComment(sym: Symbol, site: Symbol, docStr: String = "")(implicit ctx: Context): String = { - // when parsing a top level class or module, use the (module-)class itself to look up variable definitions - val parent = if ((sym.is(Flags.Module) || sym.isClass) && site.is(Flags.Package)) sym - else site - expandVariables(cookedDocComment(sym, docStr), sym, parent) - } - - private def template(raw: String): String = { - val sections = tagIndex(raw) - - val defines = sections filter { startsWithTag(raw, _, "@define") } - val usecases = sections filter { startsWithTag(raw, _, "@usecase") } - - val end = startTag(raw, (defines ::: usecases).sortBy(_._1)) - - if (end == raw.length - 2) raw else raw.substring(0, end) + "*/" - } - - def defines(raw: String): List[String] = { - val sections = tagIndex(raw) - val defines = sections filter { startsWithTag(raw, _, "@define") } - val usecases = sections filter { startsWithTag(raw, _, "@usecase") } - val end = startTag(raw, (defines ::: usecases).sortBy(_._1)) - - defines map { case (start, end) => raw.substring(start, end) } - } - - private def replaceInheritDocToInheritdoc(docStr: String): String = - docStr.replaceAll("""\{@inheritDoc\p{Zs}*\}""", "@inheritdoc") - - /** The cooked doc comment of an overridden symbol */ - protected def superComment(sym: Symbol)(implicit ctx: Context): Option[String] = - allInheritedOverriddenSymbols(sym).iterator map (x => cookedDocComment(x)) find (_ != "") - - private val cookedDocComments = mutable.HashMap[Symbol, String]() - - /** The raw doc comment of symbol `sym`, minus usecase and define sections, augmented by - * missing sections of an inherited doc comment. - * If a symbol does not have a doc comment but some overridden version of it does, - * the doc comment of the overridden version is copied instead. - */ - def cookedDocComment(sym: Symbol, docStr: String = "")(implicit ctx: Context): String = cookedDocComments.getOrElseUpdate(sym, { - var ownComment = - if (docStr.length == 0) ctx.docbase.docstring(sym).map(c => template(c.chrs)).getOrElse("") - else template(docStr) - ownComment = replaceInheritDocToInheritdoc(ownComment) - - superComment(sym) match { - case None => - // SI-8210 - The warning would be false negative when this symbol is a setter - if (ownComment.indexOf("@inheritdoc") != -1 && ! sym.isSetter) - dottydoc.println(s"${sym.pos}: the comment for ${sym} contains @inheritdoc, but no parent comment is available to inherit from.") - ownComment.replaceAllLiterally("@inheritdoc", "<invalid inheritdoc annotation>") - case Some(sc) => - if (ownComment == "") sc - else expandInheritdoc(sc, merge(sc, ownComment, sym), sym) - } - }) - - private def isMovable(str: String, sec: (Int, Int)): Boolean = - startsWithTag(str, sec, "@param") || - startsWithTag(str, sec, "@tparam") || - startsWithTag(str, sec, "@return") - - def merge(src: String, dst: String, sym: Symbol, copyFirstPara: Boolean = false): String = { - val srcSections = tagIndex(src) - val dstSections = tagIndex(dst) - val srcParams = paramDocs(src, "@param", srcSections) - val dstParams = paramDocs(dst, "@param", dstSections) - val srcTParams = paramDocs(src, "@tparam", srcSections) - val dstTParams = paramDocs(dst, "@tparam", dstSections) - val out = new StringBuilder - var copied = 0 - var tocopy = startTag(dst, dstSections dropWhile (!isMovable(dst, _))) - - if (copyFirstPara) { - val eop = // end of comment body (first para), which is delimited by blank line, or tag, or end of comment - (findNext(src, 0)(src.charAt(_) == '\n')) min startTag(src, srcSections) - out append src.substring(0, eop).trim - copied = 3 - tocopy = 3 - } - - def mergeSection(srcSec: Option[(Int, Int)], dstSec: Option[(Int, Int)]) = dstSec match { - case Some((start, end)) => - if (end > tocopy) tocopy = end - case None => - srcSec match { - case Some((start1, end1)) => { - out append dst.substring(copied, tocopy).trim - out append "\n" - copied = tocopy - out append src.substring(start1, end1).trim - } - case None => - } - } - - //TODO: enable this once you know how to get `sym.paramss` - /* - for (params <- sym.paramss; param <- params) - mergeSection(srcParams get param.name.toString, dstParams get param.name.toString) - for (tparam <- sym.typeParams) - mergeSection(srcTParams get tparam.name.toString, dstTParams get tparam.name.toString) - - mergeSection(returnDoc(src, srcSections), returnDoc(dst, dstSections)) - mergeSection(groupDoc(src, srcSections), groupDoc(dst, dstSections)) - */ - - if (out.length == 0) dst - else { - out append dst.substring(copied) - out.toString - } - } - - /** - * Expand inheritdoc tags - * - for the main comment we transform the inheritdoc into the super variable, - * and the variable expansion can expand it further - * - for the param, tparam and throws sections we must replace comments on the spot - * - * This is done separately, for two reasons: - * 1. It takes longer to run compared to merge - * 2. The inheritdoc annotation should not be used very often, as building the comment from pieces severely - * impacts performance - * - * @param parent The source (or parent) comment - * @param child The child (overriding member or usecase) comment - * @param sym The child symbol - * @return The child comment with the inheritdoc sections expanded - */ - def expandInheritdoc(parent: String, child: String, sym: Symbol): String = - if (child.indexOf("@inheritdoc") == -1) - child - else { - val parentSections = tagIndex(parent) - val childSections = tagIndex(child) - val parentTagMap = sectionTagMap(parent, parentSections) - val parentNamedParams = Map() + - ("@param" -> paramDocs(parent, "@param", parentSections)) + - ("@tparam" -> paramDocs(parent, "@tparam", parentSections)) + - ("@throws" -> paramDocs(parent, "@throws", parentSections)) - - val out = new StringBuilder - - def replaceInheritdoc(childSection: String, parentSection: => String) = - if (childSection.indexOf("@inheritdoc") == -1) - childSection - else - childSection.replaceAllLiterally("@inheritdoc", parentSection) - - def getParentSection(section: (Int, Int)): String = { - - def getSectionHeader = extractSectionTag(child, section) match { - case param@("@param"|"@tparam"|"@throws") => param + " " + extractSectionParam(child, section) - case other => other - } - - def sectionString(param: String, paramMap: Map[String, (Int, Int)]): String = - paramMap.get(param) match { - case Some(section) => - // Cleanup the section tag and parameter - val sectionTextBounds = extractSectionText(parent, section) - cleanupSectionText(parent.substring(sectionTextBounds._1, sectionTextBounds._2)) - case None => - dottydoc.println(s"""${sym.pos}: the """" + getSectionHeader + "\" annotation of the " + sym + - " comment contains @inheritdoc, but the corresponding section in the parent is not defined.") - "<invalid inheritdoc annotation>" - } - - child.substring(section._1, section._1 + 7) match { - case param@("@param "|"@tparam"|"@throws") => - sectionString(extractSectionParam(child, section), parentNamedParams(param.trim)) - case _ => - sectionString(extractSectionTag(child, section), parentTagMap) - } - } - - def mainComment(str: String, sections: List[(Int, Int)]): String = - if (str.trim.length > 3) - str.trim.substring(3, startTag(str, sections)) - else - "" - - // Append main comment - out.append("/**") - out.append(replaceInheritdoc(mainComment(child, childSections), mainComment(parent, parentSections))) - - // Append sections - for (section <- childSections) - out.append(replaceInheritdoc(child.substring(section._1, section._2), getParentSection(section))) - - out.append("*/") - out.toString - } - - protected def expandVariables(initialStr: String, sym: Symbol, site: Symbol)(implicit ctx: Context): String = { - val expandLimit = 10 - - def expandInternal(str: String, depth: Int): String = { - if (depth >= expandLimit) - throw new ExpansionLimitExceeded(str) - - val out = new StringBuilder - var copied, idx = 0 - // excluding variables written as \$foo so we can use them when - // necessary to document things like Symbol#decode - def isEscaped = idx > 0 && str.charAt(idx - 1) == '\\' - while (idx < str.length) { - if ((str charAt idx) != '$' || isEscaped) - idx += 1 - else { - val vstart = idx - idx = skipVariable(str, idx + 1) - def replaceWith(repl: String) { - out append str.substring(copied, vstart) - out append repl - copied = idx - } - variableName(str.substring(vstart + 1, idx)) match { - case "super" => - superComment(sym) foreach { sc => - val superSections = tagIndex(sc) - replaceWith(sc.substring(3, startTag(sc, superSections))) - for (sec @ (start, end) <- superSections) - if (!isMovable(sc, sec)) out append sc.substring(start, end) - } - case "" => idx += 1 - case vname => - lookupVariable(vname, site) match { - case Some(replacement) => replaceWith(replacement) - case None => - dottydoc.println(s"Variable $vname undefined in comment for $sym in $site") - } - } - } - } - if (out.length == 0) str - else { - out append str.substring(copied) - expandInternal(out.toString, depth + 1) - } - } - - // We suppressed expanding \$ throughout the recursion, and now we - // need to replace \$ with $ so it looks as intended. - expandInternal(initialStr, 0).replaceAllLiterally("""\$""", "$") - } - - def defineVariables(sym: Symbol)(implicit ctx: Context) = { - val Trim = "(?s)^[\\s&&[^\n\r]]*(.*?)\\s*$".r - - val raw = ctx.docbase.docstring(sym).map(_.chrs).getOrElse("") - defs(sym) ++= defines(raw).map { - str => { - val start = skipWhitespace(str, "@define".length) - val (key, value) = str.splitAt(skipVariable(str, start)) - key.drop(start) -> value - } - } map { - case (key, Trim(value)) => - variableName(key) -> value.replaceAll("\\s+\\*+$", "") - } - } - - /** Maps symbols to the variable -> replacement maps that are defined - * in their doc comments - */ - private val defs = mutable.HashMap[Symbol, Map[String, String]]() withDefaultValue Map() - - /** Lookup definition of variable. - * - * @param vble The variable for which a definition is searched - * @param site The class for which doc comments are generated - */ - def lookupVariable(vble: String, site: Symbol)(implicit ctx: Context): Option[String] = site match { - case NoSymbol => None - case _ => - val searchList = - if (site.flags.is(Flags.Module)) site :: site.info.baseClasses - else site.info.baseClasses - - searchList collectFirst { case x if defs(x) contains vble => defs(x)(vble) } match { - case Some(str) if str startsWith "$" => lookupVariable(str.tail, site) - case res => res orElse lookupVariable(vble, site.owner) - } - } - - /** The position of the raw doc comment of symbol `sym`, or NoPosition if missing - * If a symbol does not have a doc comment but some overridden version of it does, - * the position of the doc comment of the overridden version is returned instead. - */ - def docCommentPos(sym: Symbol)(implicit ctx: Context): Position = - ctx.docbase.docstring(sym).map(_.pos).getOrElse(NoPosition) - - /** A version which doesn't consider self types, as a temporary measure: - * an infinite loop has broken out between superComment and cookedDocComment - * since r23926. - */ - private def allInheritedOverriddenSymbols(sym: Symbol)(implicit ctx: Context): List[Symbol] = { - if (!sym.owner.isClass) Nil - else sym.allOverriddenSymbols.toList.filter(_ != NoSymbol) //TODO: could also be `sym.owner.allOverrid..` - //else sym.owner.ancestors map (sym overriddenSymbol _) filter (_ != NoSymbol) - } - - class ExpansionLimitExceeded(str: String) extends Exception -} diff --git a/dottydoc/src/dotty/tools/dottydoc/model/comment/CommentUtils.scala b/dottydoc/src/dotty/tools/dottydoc/model/comment/CommentUtils.scala deleted file mode 100644 index e5307bd3c..000000000 --- a/dottydoc/src/dotty/tools/dottydoc/model/comment/CommentUtils.scala +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Port of DocStrings.scala from nsc - * @author Martin Odersky - * @author Felix Mulder - */ - -package dotty.tools -package dottydoc -package model -package comment - -import scala.reflect.internal.Chars._ - -object CommentUtils { - - /** Returns index of string `str` following `start` skipping longest - * sequence of whitespace characters characters (but no newlines) - */ - def skipWhitespace(str: String, start: Int): Int = - if (start < str.length && isWhitespace(str charAt start)) skipWhitespace(str, start + 1) - else start - - /** Returns index of string `str` following `start` skipping - * sequence of identifier characters. - */ - def skipIdent(str: String, start: Int): Int = - if (start < str.length && isIdentifierPart(str charAt start)) skipIdent(str, start + 1) - else start - - /** Returns index of string `str` following `start` skipping - * sequence of identifier characters. - */ - def skipTag(str: String, start: Int): Int = - if (start < str.length && (str charAt start) == '@') skipIdent(str, start + 1) - else start - - - /** Returns index of string `str` after `start` skipping longest - * sequence of space and tab characters, possibly also containing - * a single `*` character or the `/``**` sequence. - * @pre start == str.length || str(start) == `\n` - */ - def skipLineLead(str: String, start: Int): Int = - if (start == str.length) start - else { - val idx = skipWhitespace(str, start + 1) - if (idx < str.length && (str charAt idx) == '*') skipWhitespace(str, idx + 1) - else if (idx + 2 < str.length && (str charAt idx) == '/' && (str charAt (idx + 1)) == '*' && (str charAt (idx + 2)) == '*') - skipWhitespace(str, idx + 3) - else idx - } - - /** Skips to next occurrence of `\n` or to the position after the `/``**` sequence following index `start`. - */ - def skipToEol(str: String, start: Int): Int = - if (start + 2 < str.length && (str charAt start) == '/' && (str charAt (start + 1)) == '*' && (str charAt (start + 2)) == '*') start + 3 - else if (start < str.length && (str charAt start) != '\n') skipToEol(str, start + 1) - else start - - /** Returns first index following `start` and starting a line (i.e. after skipLineLead) or starting the comment - * which satisfies predicate `p`. - */ - def findNext(str: String, start: Int)(p: Int => Boolean): Int = { - val idx = skipLineLead(str, skipToEol(str, start)) - if (idx < str.length && !p(idx)) findNext(str, idx)(p) - else idx - } - - /** Return first index following `start` and starting a line (i.e. after skipLineLead) - * which satisfies predicate `p`. - */ - def findAll(str: String, start: Int)(p: Int => Boolean): List[Int] = { - val idx = findNext(str, start)(p) - if (idx == str.length) List() - else idx :: findAll(str, idx)(p) - } - - /** Produces a string index, which is a list of `sections`, i.e - * pairs of start/end positions of all tagged sections in the string. - * Every section starts with an at sign and extends to the next at sign, - * or to the end of the comment string, but excluding the final two - * characters which terminate the comment. - * - * Also take usecases into account - they need to expand until the next - * usecase or the end of the string, as they might include other sections - * of their own - */ - def tagIndex(str: String, p: Int => Boolean = (idx => true)): List[(Int, Int)] = { - var indices = findAll(str, 0) (idx => str(idx) == '@' && p(idx)) - indices = mergeUsecaseSections(str, indices) - indices = mergeInheritdocSections(str, indices) - - indices match { - case List() => List() - case idxs => idxs zip (idxs.tail ::: List(str.length - 2)) - } - } - - /** - * Merge sections following an usecase into the usecase comment, so they - * can override the parent symbol's sections - */ - def mergeUsecaseSections(str: String, idxs: List[Int]): List[Int] = { - idxs.indexWhere(str.startsWith("@usecase", _)) match { - case firstUCIndex if firstUCIndex != -1 => - val commentSections = idxs.take(firstUCIndex) - val usecaseSections = idxs.drop(firstUCIndex).filter(str.startsWith("@usecase", _)) - commentSections ::: usecaseSections - case _ => - idxs - } - } - - /** - * Merge the inheritdoc sections, as they never make sense on their own - */ - def mergeInheritdocSections(str: String, idxs: List[Int]): List[Int] = - idxs.filterNot(str.startsWith("@inheritdoc", _)) - - /** Does interval `iv` start with given `tag`? - */ - def startsWithTag(str: String, section: (Int, Int), tag: String): Boolean = - startsWithTag(str, section._1, tag) - - def startsWithTag(str: String, start: Int, tag: String): Boolean = - str.startsWith(tag, start) && !isIdentifierPart(str charAt (start + tag.length)) - - /** The first start tag of a list of tag intervals, - * or the end of the whole comment string - 2 if list is empty - */ - def startTag(str: String, sections: List[(Int, Int)]) = sections match { - case Nil => str.length - 2 - case (start, _) :: _ => start - } - - /** A map from parameter names to start/end indices describing all parameter - * sections in `str` tagged with `tag`, where `sections` is the index of `str`. - */ - def paramDocs(str: String, tag: String, sections: List[(Int, Int)]): Map[String, (Int, Int)] = - Map() ++ { - for (section <- sections if startsWithTag(str, section, tag)) yield { - val start = skipWhitespace(str, section._1 + tag.length) - str.substring(start, skipIdent(str, start)) -> section - } - } - - /** Optionally start and end index of return section in `str`, or `None` - * if `str` does not have a @group. */ - def groupDoc(str: String, sections: List[(Int, Int)]): Option[(Int, Int)] = - sections find (startsWithTag(str, _, "@group")) - - - /** Optionally start and end index of return section in `str`, or `None` - * if `str` does not have a @return. - */ - def returnDoc(str: String, sections: List[(Int, Int)]): Option[(Int, Int)] = - sections find (startsWithTag(str, _, "@return")) - - /** Extracts variable name from a string, stripping any pair of surrounding braces */ - def variableName(str: String): String = - if (str.length >= 2 && (str charAt 0) == '{' && (str charAt (str.length - 1)) == '}') - str.substring(1, str.length - 1) - else - str - - /** Returns index following variable, or start index if no variable was recognized - */ - def skipVariable(str: String, start: Int): Int = { - var idx = start - if (idx < str.length && (str charAt idx) == '{') { - do idx += 1 - while (idx < str.length && (str charAt idx) != '}') - if (idx < str.length) idx + 1 else start - } else { - while (idx < str.length && isVarPart(str charAt idx)) - idx += 1 - idx - } - } - - /** A map from the section tag to section parameters */ - def sectionTagMap(str: String, sections: List[(Int, Int)]): Map[String, (Int, Int)] = - Map() ++ { - for (section <- sections) yield - extractSectionTag(str, section) -> section - } - - /** Extract the section tag, treating the section tag as an identifier */ - def extractSectionTag(str: String, section: (Int, Int)): String = - str.substring(section._1, skipTag(str, section._1)) - - /** Extract the section parameter */ - def extractSectionParam(str: String, section: (Int, Int)): String = { - val (beg, _) = section - assert(str.startsWith("@param", beg) || - str.startsWith("@tparam", beg) || - str.startsWith("@throws", beg)) - - val start = skipWhitespace(str, skipTag(str, beg)) - val finish = skipIdent(str, start) - - str.substring(start, finish) - } - - /** Extract the section text, except for the tag and comment newlines */ - def extractSectionText(str: String, section: (Int, Int)): (Int, Int) = { - val (beg, end) = section - if (str.startsWith("@param", beg) || - str.startsWith("@tparam", beg) || - str.startsWith("@throws", beg)) - (skipWhitespace(str, skipIdent(str, skipWhitespace(str, skipTag(str, beg)))), end) - else - (skipWhitespace(str, skipTag(str, beg)), end) - } - - /** Cleanup section text */ - def cleanupSectionText(str: String) = { - var result = str.trim.replaceAll("\n\\s+\\*\\s+", " \n") - while (result.endsWith("\n")) - result = result.substring(0, str.length - 1) - result - } - -} diff --git a/dottydoc/src/dotty/tools/dottydoc/model/entities.scala b/dottydoc/src/dotty/tools/dottydoc/model/entities.scala index 76792070c..52379f9ad 100644 --- a/dottydoc/src/dotty/tools/dottydoc/model/entities.scala +++ b/dottydoc/src/dotty/tools/dottydoc/model/entities.scala @@ -3,8 +3,11 @@ package model import comment._ import references._ +import dotty.tools.dotc.core.Symbols.{ Symbol, NoSymbol } trait Entity { + def symbol: Symbol + def name: String /** Path from root, i.e. `scala.Option$` */ @@ -103,6 +106,7 @@ trait Var extends Entity with Modifiers with ReturnValue { trait NonEntity extends Entity { val name = "" + val symbol = NoSymbol val comment = None val path = Nil val kind = "" diff --git a/dottydoc/src/dotty/tools/dottydoc/model/internal.scala b/dottydoc/src/dotty/tools/dottydoc/model/internal.scala index 6afb1ec9b..09f642d0b 100644 --- a/dottydoc/src/dotty/tools/dottydoc/model/internal.scala +++ b/dottydoc/src/dotty/tools/dottydoc/model/internal.scala @@ -3,6 +3,7 @@ package model import comment.Comment import references._ +import dotty.tools.dotc.core.Symbols.Symbol object internal { @@ -11,6 +12,7 @@ object internal { } final case class PackageImpl( + symbol: Symbol, name: String, var members: List[Entity], path: List[String], @@ -21,6 +23,7 @@ object internal { } final case class ClassImpl( + symbol: Symbol, name: String, members: List[Entity], modifiers: List[String], @@ -32,6 +35,7 @@ object internal { ) extends Class with Impl final case class CaseClassImpl( + symbol: Symbol, name: String, members: List[Entity], modifiers: List[String], @@ -43,6 +47,7 @@ object internal { ) extends CaseClass with Impl final case class TraitImpl( + symbol: Symbol, name: String, members: List[Entity], modifiers: List[String], @@ -54,6 +59,7 @@ object internal { ) extends Trait with Impl final case class ObjectImpl( + symbol: Symbol, name: String, members: List[Entity], modifiers: List[String], @@ -63,6 +69,7 @@ object internal { ) extends Object with Impl final case class DefImpl( + symbol: Symbol, name: String, modifiers: List[String], path: List[String], @@ -74,6 +81,7 @@ object internal { ) extends Def with Impl final case class ValImpl( + symbol: Symbol, name: String, modifiers: List[String], path: List[String], diff --git a/dottydoc/src/dotty/tools/dottydoc/model/parsers.scala b/dottydoc/src/dotty/tools/dottydoc/model/parsers.scala deleted file mode 100644 index fa54163e5..000000000 --- a/dottydoc/src/dotty/tools/dottydoc/model/parsers.scala +++ /dev/null @@ -1,98 +0,0 @@ -package dotty.tools -package dottydoc -package model - -import dotc.core.Symbols.Symbol -import dotc.core.Contexts.Context -import dotc.util.Positions.NoPosition - -object parsers { - import comment._ - import BodyParsers._ - import model.internal._ - import util.MemberLookup - import util.traversing._ - import util.internal.setters._ - - class WikiParser extends CommentCleaner with CommentParser with CommentExpander { - private[this] var commentCache: Map[String, (Entity, Map[String, Package]) => Option[Comment]] = Map.empty - - /** Parses comment and returns the path to the entity with an optional comment - * - * The idea here is to use this fact to create `Future[Seq[(String, Option[Comment]]]` - * which can then be awaited near the end of the run - before the pickling. - */ - def parseHtml(sym: Symbol, parent: Symbol, entity: Entity, packages: Map[String, Package])(implicit ctx: Context): (String, Option[Comment]) = { - val cmt = ctx.docbase.docstring(sym).map { d => - val expanded = expand(sym, parent) - parse(entity, packages, clean(expanded), expanded, d.pos).toComment(_.toHtml(entity)) - } - - (entity.path.mkString("."), cmt) - } - - - def add(entity: Entity, symbol: Symbol, parent: Symbol, ctx: Context): Unit = { - val commentParser = { (entity: Entity, packs: Map[String, Package]) => - parseHtml(symbol, parent, entity, packs)(ctx)._2 - } - - /** TODO: this if statement searches for doc comments in parent - * definitions if one is not defined for the current symbol. - * - * It might be a good idea to factor this out of the WikiParser - since - * it mutates the state of docbase sort of silently. - */ - implicit val implCtx = ctx - if (!ctx.docbase.docstring(symbol).isDefined) { - val parentCmt = - symbol.extendedOverriddenSymbols - .find(ctx.docbase.docstring(_).isDefined) - .flatMap(p => ctx.docbase.docstring(p)) - - ctx.docbase.addDocstring(symbol, parentCmt) - } - - - val path = entity.path.mkString(".") - if (!commentCache.contains(path) || ctx.docbase.docstring(symbol).isDefined) - commentCache = commentCache + (path -> commentParser) - } - - def +=(entity: Entity, symbol: Symbol, parent: Symbol, ctx: Context) = add(entity, symbol, parent, ctx) - - def size: Int = commentCache.size - - private def parse(entity: Entity, packs: Map[String, Package]): Option[Comment] = - commentCache(entity.path.mkString("."))(entity, packs) - - def parse(packs: Map[String, Package]): Unit = { - def rootPackages: List[String] = { - var currentDepth = Int.MaxValue - var packages: List[String] = Nil - - for (key <- packs.keys) { - val keyDepth = key.split("\\.").length - packages = - if (keyDepth < currentDepth) { - currentDepth = keyDepth - key :: Nil - } else if (keyDepth == currentDepth) { - key :: packages - } else packages - } - - packages - } - - for (pack <- rootPackages) { - mutateEntities(packs(pack)) { e => - val comment = parse(e, packs) - setComment(e, to = comment) - } - } - } - - def clear(): Unit = commentCache = Map.empty - } -} diff --git a/dottydoc/src/dotty/tools/dottydoc/util/syntax.scala b/dottydoc/src/dotty/tools/dottydoc/util/syntax.scala new file mode 100644 index 000000000..dd3d21f8d --- /dev/null +++ b/dottydoc/src/dotty/tools/dottydoc/util/syntax.scala @@ -0,0 +1,16 @@ +package dotty.tools +package dottydoc +package util + +import dotc.core.Contexts.Context +import dotc.core.Comments._ +import model.Package +import core.ContextDottydoc + +object syntax { + implicit class ContextWithContextDottydoc(val ctx: Context) extends AnyVal { + def docbase: ContextDottydoc = ctx.docCtx.getOrElse { + throw new IllegalStateException("DocBase must be set before running dottydoc phases") + }.asInstanceOf[ContextDottydoc] + } +} diff --git a/dottydoc/test/BaseTest.scala b/dottydoc/test/BaseTest.scala index 46a24c579..303c476a5 100644 --- a/dottydoc/test/BaseTest.scala +++ b/dottydoc/test/BaseTest.scala @@ -1,13 +1,14 @@ package dotty.tools package dottydoc -import dotc.core.Contexts -import Contexts.{ Context, ContextBase, FreshContext } +import dotc.core.Contexts.{ Context, ContextBase, FreshContext } +import dotc.core.Comments.{ ContextDoc, ContextDocstrings } import dotc.util.SourceFile import dotc.core.Phases.Phase import dotc.typer.FrontEnd -import dottydoc.core.DocASTPhase +import dottydoc.core.{ DocASTPhase, ContextDottydoc } import model.Package +import dotty.tools.dottydoc.util.syntax._ trait DottyTest { dotty.tools.dotc.parsing.Scanners // initialize keywords @@ -19,6 +20,7 @@ trait DottyTest { ctx.setSetting(ctx.settings.language, List("Scala2")) ctx.setSetting(ctx.settings.YkeepComments, true) ctx.setSetting(ctx.settings.YnoInline, true) + ctx.setProperty(ContextDoc, new ContextDottydoc) base.initialize()(ctx) ctx } @@ -28,7 +30,7 @@ trait DottyTest { List(new Phase { def phaseName = "assertionPhase" override def run(implicit ctx: Context): Unit = - assertion(ctx.docbase.packages[Package].toMap) + assertion(ctx.docbase.packages) }) :: Nil override def phases = diff --git a/dottydoc/test/ConstructorTest.scala b/dottydoc/test/ConstructorTest.scala index 8aa883022..44a05acad 100644 --- a/dottydoc/test/ConstructorTest.scala +++ b/dottydoc/test/ConstructorTest.scala @@ -22,7 +22,7 @@ class Constructors extends DottyTest { checkSources(source :: Nil) { packages => packages("scala") match { - case PackageImpl(_, List(cls: Class), _, _) => + case PackageImpl(_, _, List(cls: Class), _, _) => cls.constructors.headOption match { case Some(ParamListImpl(NamedReference("str", _, false, false) :: Nil, false) :: Nil) => // success! @@ -44,7 +44,7 @@ class Constructors extends DottyTest { checkSources(source :: Nil) { packages => packages("scala") match { - case PackageImpl(_, List(cls: Class), _, _) => + case PackageImpl(_, _, List(cls: Class), _, _) => cls.constructors match { case ( ParamListImpl(NamedReference("str1", _, false, false) :: Nil, false) :: @@ -69,7 +69,7 @@ class Constructors extends DottyTest { checkSources(source :: Nil) { packages => packages("scala") match { - case PackageImpl(_, List(cls: Class), _, _) => + case PackageImpl(_, _, List(cls: Class), _, _) => cls.constructors match { case ( ParamListImpl(NamedReference("str1", _, false, false) :: Nil, false) :: @@ -101,7 +101,7 @@ class Constructors extends DottyTest { checkSources(source :: Nil) { packages => packages("scala") match { - case PackageImpl(_, List(cls: Class), _, _) => + case PackageImpl(_, _, List(cls: Class), _, _) => cls.constructors match { case ( ParamListImpl(NamedReference("main", _, false, false) :: Nil, false) :: Nil @@ -139,7 +139,7 @@ class Constructors extends DottyTest { checkSources(source :: Nil) { packages => packages("scala") match { - case PackageImpl(_, List(cls: CaseClass, obj: Object), _, _) => + case PackageImpl(_, _, List(cls: CaseClass, obj: Object), _, _) => cls.constructors match { case ( ParamListImpl(NamedReference("main", _, false, false) :: Nil, false) :: Nil @@ -172,7 +172,7 @@ class Constructors extends DottyTest { checkSources(source :: Nil) { packages => packages("scala") match { - case PackageImpl(_, List(trt: Trait), _, _) => + case PackageImpl(_, _, List(trt: Trait), _, _) => trt.traitParams match { case ParamListImpl(NamedReference("main", _, false, false) :: Nil, false) :: Nil => case _ => @@ -199,7 +199,7 @@ class Constructors extends DottyTest { checkSources(source :: Nil) { packages => packages("scala") match { - case PackageImpl(_, List(cc: CaseClass, _, cls: Class, trt: Trait), _, _) => + case PackageImpl(_, _, List(cc: CaseClass, _, cls: Class, trt: Trait), _, _) => import model.json._ lazy val incorrectJson = s"The json generated for:\n$actualSource\n\nIs not correct" assert(cc.json.contains(s""""constructors":[[{"list":[{"title":"main""""), incorrectJson) diff --git a/dottydoc/test/PackageStructure.scala b/dottydoc/test/PackageStructure.scala index 00caaa2c0..4e7006bfe 100644 --- a/dottydoc/test/PackageStructure.scala +++ b/dottydoc/test/PackageStructure.scala @@ -29,7 +29,7 @@ class PackageStructure extends DottyTest { checkSources(source1 :: source2 :: Nil) { packages => packages("scala") match { - case PackageImpl(_, List(tA, tB), _, _) => + case PackageImpl(_, _, List(tA, tB), _, _) => assert( tA.name == "A" && tB.name == "B", s"trait A had name '${tA.name}' and trait B had name '${tB.name}'" @@ -62,8 +62,9 @@ class PackageStructure extends DottyTest { checkSources(source1 :: source2 :: Nil) { packages => packages("scala") match { case PackageImpl( + _, "scala", - List(PackageImpl("scala.collection", List(tA, tB), _, _)), + List(PackageImpl(_, "scala.collection", List(tA, tB), _, _)), _, _ ) => assert( @@ -76,7 +77,7 @@ class PackageStructure extends DottyTest { } packages("scala.collection") match { - case PackageImpl("scala.collection", List(tA, tB), _, _) => + case PackageImpl(_, "scala.collection", List(tA, tB), _, _) => assert( tA.name == "A" && tB.name == "B", s"trait A had name '${tA.name}' and trait B had name '${tB.name}'" diff --git a/dottydoc/test/UsecaseTest.scala b/dottydoc/test/UsecaseTest.scala new file mode 100644 index 000000000..b5d47e4b6 --- /dev/null +++ b/dottydoc/test/UsecaseTest.scala @@ -0,0 +1,234 @@ +package dotty.tools +package dottydoc + +import org.junit.Test +import org.junit.Assert._ + +import dotc.util.SourceFile +import model._ +import model.internal._ +import model.references._ +import util.syntax._ + +class UsecaseTest extends DottyTest { + @Test def simpleUsecase = { + val source = new SourceFile( + "DefWithUseCase.scala", + """ + |package scala + | + |trait Test[A] { + | /** Definition with a "disturbing" signature + | * + | * @usecase def foo: A + | */ + | def foo[B]: A => B + |} + """.stripMargin + ) + + checkSources(source :: Nil) { packages => + packages("scala") match { + case PackageImpl(_, _, List(trt: Trait), _, _) => + val List(foo: Def) = trt.members + + assert(foo.comment.isDefined, "Lost comment in transformations") + + val returnValue = foo.returnValue match { + case ref: TypeReference => ref.title + case _ => + assert( + false, + "Incorrect return value after usecase transformation" + ) + "" + } + + assert( + foo.typeParams.isEmpty, + "Type parameters were not stripped by usecase" + ) + assert(returnValue == "A", "Incorrect return type after usecase") + + assert(foo.name == "foo", s"Incorrect name after transform: ${foo.name}") + } + } + } + + @Test def simpleUsecaseAddedArg = { + val source = new SourceFile( + "DefWithUseCase.scala", + """ + |package scala + | + |trait Test[A] { + | /** Definition with a "disturbing" signature + | * + | * @usecase def foo(a: A): A + | */ + | def foo[B]: A => B + |} + """.stripMargin + ) + + checkSources(source :: Nil) { packages => + packages("scala") match { + case PackageImpl(_, _, List(trt: Trait), _, _) => + val List(foo: Def) = trt.members + + val returnValue = foo.returnValue match { + case ref: TypeReference => ref.title + case _ => + assert( + false, + "Incorrect return value after usecase transformation" + ) + "" + } + + assert( + foo.typeParams.isEmpty, + "Type parameters were not stripped by usecase" + ) + assert(returnValue == "A", "Incorrect return type after usecase") + assert( + foo.paramLists.head.list.head.title == "a", + "Incorrect parameter to function after usecase transformation" + ) + assert(foo.name == "foo", s"Incorrect name after transform: ${foo.name}") + } + } + } + + @Test def simpleTparamUsecase = { + val source = new SourceFile( + "DefWithUseCase.scala", + """ + |package scala + | + |trait Test[A] { + | /** Definition with a "disturbing" signature + | * + | * @usecase def foo[C]: A + | */ + | def foo[B]: A => B + |} + """.stripMargin + ) + + checkSources(source :: Nil) { packages => + packages("scala") match { + case PackageImpl(_, _, List(trt: Trait), _, _) => + val List(foo: Def) = trt.members + + val returnValue = foo.returnValue match { + case ref: TypeReference => ref.title + case _ => + assert( + false, + "Incorrect return value after usecase transformation" + ) + "" + } + + assert( + foo.typeParams.nonEmpty, + "Type parameters were incorrectly stripped by usecase" + ) + + assert(foo.typeParams.head == "C", "Incorrectly switched tparam") + assert(returnValue == "A", "Incorrect return type after usecase") + + assert(foo.name == "foo", s"Incorrect name after transform: ${foo.name}") + } + } + } + + @Test def expandColl = { + val source = new SourceFile( + "ExpandColl.scala", + """ + |package scala + | + |/** The trait $Coll + | * + | * @define Coll Iterable + | */ + |trait Iterable[A] { + | /** Definition with a "disturbing" signature + | * + | * @usecase def map[B](f: A => B): $Coll[B] + | */ + | def map[B, M[B]](f: A => B): M[B] = ??? + |} + """.stripMargin + ) + + checkSources(source :: Nil) { packages => + packages("scala") match { + case PackageImpl(_, _, List(trt: Trait), _, _) => + val List(map: Def) = trt.members + + val returnValue = map.returnValue match { + case ref: TypeReference => ref.title + case _ => + assert( + false, + "Incorrect return value after usecase transformation" + ) + "" + } + + assert( + returnValue == "Iterable", + "Incorrect return type after usecase transformation" + ) + } + } + } + + @Test def checkStripping = { + val source = new SourceFile( + "CheckStripping.scala", + """ + |package scala + | + |/** The trait $Coll + | * + | * @define Coll Iterable + | */ + |trait Iterable[A] { + | /** Definition with a "disturbing" signature + | * + | * @usecase def map[B](f: A => B): $Coll[B] + | */ + | def map[B, M[B]](f: A => B): M[B] = ??? + |} + """.stripMargin + ) + + checkSources(source :: Nil) { packages => + packages("scala") match { + case PackageImpl(_, _, List(trt: Trait), _, _) => + val List(map: Def) = trt.members + assert(map.comment.isDefined, "Lost comment in transformations") + + val docstr = ctx.docbase.docstring(map.symbol).get.raw + assert( + !docstr.contains("@usecase"), + s"Comment should not contain usecase after stripping, but was:\n$docstr" + ) + } + } + } + + @Test def checkIterator = + checkFiles("./scala-scala/src/library/scala/collection/Iterator.scala" :: Nil) { _ => + // success if typer throws no errors! :) + } + + @Test def checkIterableLike = + checkFiles("./scala-scala/src/library/scala/collection/IterableLike.scala" :: Nil) { _ => + // success if typer throws no errors! :) + } +} |