diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index dafbe2584064..042e6609927c 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -59,7 +59,8 @@ class Compiler { protected def transformPhases: List[List[Phase]] = List(new FirstTransform, // Some transformations to put trees into a canonical form new CheckReentrant, // Internal use only: Check that compiled program has no data races involving global vars - new ElimPackagePrefixes) :: // Eliminate references to package prefixes in Select nodes + new ElimPackagePrefixes, // Eliminate references to package prefixes in Select nodes + new CookComments) :: // Cook the comments: expand variables, doc, etc. List(new CheckStatic, // Check restrictions that apply to @static members new ElimRepeated, // Rewrite vararg parameters and arguments new ExpandSAMs, // Expand single abstract method closures to anonymous classes diff --git a/compiler/src/dotty/tools/dotc/core/Comments.scala b/compiler/src/dotty/tools/dotc/core/Comments.scala index 0d38791efacc..d372423e9e66 100644 --- a/compiler/src/dotty/tools/dotc/core/Comments.scala +++ b/compiler/src/dotty/tools/dotc/core/Comments.scala @@ -3,7 +3,7 @@ package dotc package core import ast.{ untpd, tpd } -import Decorators._, Symbols._, Contexts._, Flags.EmptyFlags +import Decorators._, Symbols._, Contexts._ import util.SourceFile import util.Positions._ import util.CommentParsing._ @@ -33,41 +33,58 @@ object Comments { def docstring(sym: Symbol): Option[Comment] = _docstrings.get(sym) def addDocstring(sym: Symbol, doc: Option[Comment]): Unit = - doc.map(d => _docstrings.update(sym, d)) + doc.foreach(d => _docstrings.update(sym, d)) } - /** A `Comment` contains the unformatted docstring as well as a position - * - * The `Comment` contains functionality to create versions of itself without - * `@usecase` sections as well as functionality to map the `raw` docstring - */ - abstract case class Comment(pos: Position, raw: String) { self => - def isExpanded: Boolean + /** + * A `Comment` contains the unformatted docstring, it's position and potentially more + * information that is populated when the comment is "cooked". + * + * @param pos The position of this `Comment`. + * @param raw The raw comment, as seen in the source code, without any expansion. + * @param expanded If this comment has been expanded, it's expansion, otherwise `None`. + * @param usecases The usecases for this comment. + */ + final case class Comment(pos: Position, raw: String, expanded: Option[String], usecases: List[UseCase]) { - def usecases: List[UseCase] + /** Has this comment been cooked or expanded? */ + def isExpanded: Boolean = expanded.isDefined - val isDocComment = raw.startsWith("/**") + /** The body of this comment, without the `@usecase` and `@define` sections, after expansion. */ + lazy val expandedBody: Option[String] = + expanded.map(removeSections(_, "@usecase", "@define")) - def expand(f: String => String): Comment = new Comment(pos, f(raw)) { - val isExpanded = true - val usecases = self.usecases - } + val isDocComment = Comment.isDocComment(raw) - def withUsecases(implicit ctx: Context): Comment = new Comment(pos, stripUsecases) { - val isExpanded = self.isExpanded - val usecases = parseUsecases + /** + * Expands this comment by giving its content to `f`, and then parsing the `@usecase` sections. + * Typically, `f` will take care of expanding the variables. + * + * @param f The expansion function. + * @return The expanded comment, with the `usecases` populated. + */ + def expand(f: String => String)(implicit ctx: Context): Comment = { + val expandedComment = f(raw) + val useCases = Comment.parseUsecases(expandedComment, pos) + Comment(pos, raw, Some(expandedComment), useCases) } + } + + object Comment { - private[this] lazy val stripUsecases: String = - removeSections(raw, "@usecase", "@define") + def isDocComment(comment: String): Boolean = comment.startsWith("/**") - private[this] def parseUsecases(implicit ctx: Context): List[UseCase] = - if (!raw.startsWith("/**")) - List.empty[UseCase] - else - tagIndex(raw) - .filter { startsWithTag(raw, _, "@usecase") } - .map { case (start, end) => decomposeUseCase(start, end) } + def apply(pos: Position, raw: String): Comment = + Comment(pos, raw, None, Nil) + + private def parseUsecases(expandedComment: String, pos: Position)(implicit ctx: Context): List[UseCase] = + if (!isDocComment(expandedComment)) { + Nil + } else { + tagIndex(expandedComment) + .filter { startsWithTag(expandedComment, _, "@usecase") } + .map { case (start, end) => decomposeUseCase(expandedComment, pos, start, end) } + } /** Turns a usecase section into a UseCase, with code changed to: * {{{ @@ -77,7 +94,7 @@ object Comments { * def foo: A = ??? * }}} */ - private[this] def decomposeUseCase(start: Int, end: Int)(implicit ctx: Context): UseCase = { + private[this] def decomposeUseCase(body: String, pos: Position, start: Int, end: Int)(implicit ctx: Context): UseCase = { def subPos(start: Int, end: Int) = if (pos == NoPosition) NoPosition else { @@ -86,49 +103,34 @@ object Comments { pos withStart start1 withPoint start1 withEnd end1 } - val codeStart = skipWhitespace(raw, start + "@usecase".length) - val codeEnd = skipToEol(raw, codeStart) - val code = raw.substring(codeStart, codeEnd) + " = ???" - val codePos = subPos(codeStart, codeEnd) - val commentStart = skipLineLead(raw, codeEnd + 1) min end - val commentStr = "/** " + raw.substring(commentStart, end) + "*/" - val commentPos = subPos(commentStart, end) + val codeStart = skipWhitespace(body, start + "@usecase".length) + val codeEnd = skipToEol(body, codeStart) + val code = body.substring(codeStart, codeEnd) + " = ???" + val codePos = subPos(codeStart, codeEnd) - UseCase(Comment(commentPos, commentStr), code, codePos) + UseCase(code, codePos) } } - object Comment { - def apply(pos: Position, raw: String, expanded: Boolean = false, usc: List[UseCase] = Nil): Comment = - new Comment(pos, raw) { - val isExpanded = expanded - val usecases = usc - } - } - - abstract case class UseCase(comment: Comment, code: String, codePos: Position) { - /** Set by typer */ - var tpdCode: tpd.DefDef = _ - - def untpdCode: untpd.Tree + final case class UseCase(code: String, codePos: Position, untpdCode: untpd.Tree, tpdCode: Option[tpd.DefDef]) { + def typed(tpdCode: tpd.DefDef): UseCase = copy(tpdCode = Some(tpdCode)) } object UseCase { - def apply(comment: Comment, code: String, codePos: Position)(implicit ctx: Context) = - new UseCase(comment, code, codePos) { - val untpdCode = { - val tree = new Parser(new SourceFile("", code)).localDef(codePos.start) - - tree match { - case tree: untpd.DefDef => - val newName = ctx.freshNames.newName(tree.name, NameKinds.DocArtifactName) - untpd.DefDef(newName, tree.tparams, tree.vparamss, tree.tpt, tree.rhs) - case _ => - ctx.error(ProperDefinitionNotFound(), codePos) - tree - } + def apply(code: String, codePos: Position)(implicit ctx: Context): UseCase = { + val tree = { + val tree = new Parser(new SourceFile("", code)).localDef(codePos.start) + tree match { + case tree: untpd.DefDef => + val newName = ctx.freshNames.newName(tree.name, NameKinds.DocArtifactName) + tree.copy(name = newName) + case _ => + ctx.error(ProperDefinitionNotFound(), codePos) + tree } } + UseCase(code, codePos, tree, None) + } } /** diff --git a/compiler/src/dotty/tools/dotc/core/tasty/CommentPickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/CommentPickler.scala index b32efeb7b4f2..5fa2272d39e7 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/CommentPickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/CommentPickler.scala @@ -23,7 +23,7 @@ class CommentPickler(pickler: TastyPickler, addrOfTree: tpd.Tree => Option[Addr] buf.writeAddr(addr) buf.writeNat(length) buf.writeBytes(bytes, length) - buf.writeByte(if (cmt.isExpanded) 1 else 0) + buf.writeLongInt(cmt.pos.coords) case other => () } diff --git a/compiler/src/dotty/tools/dotc/core/tasty/CommentUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/CommentUnpickler.scala index 158794dff72c..7d4e48cbcd18 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/CommentUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/CommentUnpickler.scala @@ -3,7 +3,7 @@ package dotty.tools.dotc.core.tasty import dotty.tools.dotc.core.Comments.Comment import dotty.tools.dotc.core.Symbols.Symbol import dotty.tools.dotc.core.tasty.TastyBuffer.Addr -import dotty.tools.dotc.util.Positions +import dotty.tools.dotc.util.Positions.Position import scala.collection.mutable.HashMap @@ -19,9 +19,9 @@ class CommentUnpickler(reader: TastyReader) { val length = readNat() if (length > 0) { val bytes = readBytes(length) - val expanded = readByte() == 1 + val position = new Position(readLongInt()) val rawComment = new String(bytes, Charset.forName("UTF-8")) - comments(addr) = Comment(Positions.NoPosition, rawComment, expanded = expanded) + comments(addr) = Comment(position, rawComment) } } comments.toMap diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala index 724f32463069..b79228b4224f 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala @@ -235,8 +235,7 @@ Standard Section: "Positions" Assoc* Standard Section: "Comments" Comment* - Comment = Length Bytes Byte // Raw comment's bytes encoded as UTF-8, plus a byte indicating - // whether the comment is expanded (1) or not expanded (0) + Comment = Length Bytes LongInt // Raw comment's bytes encoded as UTF-8, followed by the comment's coordinates. **************************************************************************************/ diff --git a/compiler/src/dotty/tools/dotc/fromtasty/ReadTastyTreesFromClasses.scala b/compiler/src/dotty/tools/dotc/fromtasty/ReadTastyTreesFromClasses.scala index 0153ba932044..d22383d2f9b7 100644 --- a/compiler/src/dotty/tools/dotc/fromtasty/ReadTastyTreesFromClasses.scala +++ b/compiler/src/dotty/tools/dotc/fromtasty/ReadTastyTreesFromClasses.scala @@ -33,7 +33,7 @@ class ReadTastyTreesFromClasses extends FrontEnd { } def alreadyLoaded(): None.type = { - ctx.warning(s"sclass $className cannot be unpickled because it is already loaded") + ctx.warning(s"class $className cannot be unpickled because it is already loaded") None } @@ -72,5 +72,7 @@ class ReadTastyTreesFromClasses extends FrontEnd { case _ => cannotUnpickle(s"no class file was found") } + case unit => + Some(unit) } } diff --git a/compiler/src/dotty/tools/dotc/interactive/InteractiveCompiler.scala b/compiler/src/dotty/tools/dotc/interactive/InteractiveCompiler.scala index 6822699a4f9c..661d0bc686aa 100644 --- a/compiler/src/dotty/tools/dotc/interactive/InteractiveCompiler.scala +++ b/compiler/src/dotty/tools/dotc/interactive/InteractiveCompiler.scala @@ -13,6 +13,7 @@ class InteractiveCompiler extends Compiler { // This could be improved by reporting errors back to the IDE // after each phase group instead of waiting for the pipeline to finish. override def phases: List[List[Phase]] = List( - List(new FrontEnd) + List(new FrontEnd), + List(new transform.CookComments) ) } diff --git a/compiler/src/dotty/tools/dotc/interactive/InteractiveDriver.scala b/compiler/src/dotty/tools/dotc/interactive/InteractiveDriver.scala index 91104acda262..895b8c4f9318 100644 --- a/compiler/src/dotty/tools/dotc/interactive/InteractiveDriver.scala +++ b/compiler/src/dotty/tools/dotc/interactive/InteractiveDriver.scala @@ -33,6 +33,7 @@ class InteractiveDriver(val settings: List[String]) extends Driver { private val myInitCtx: Context = { val rootCtx = initCtx.fresh.addMode(Mode.ReadPositions).addMode(Mode.Interactive).addMode(Mode.ReadComments) rootCtx.setSetting(rootCtx.settings.YretainTrees, true) + rootCtx.setSetting(rootCtx.settings.YcookComments, true) val ctx = setup(settings.toArray, rootCtx)._2 ctx.initialize()(ctx) ctx diff --git a/compiler/src/dotty/tools/dotc/transform/CookComments.scala b/compiler/src/dotty/tools/dotc/transform/CookComments.scala new file mode 100644 index 000000000000..adaf8e36976f --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/CookComments.scala @@ -0,0 +1,28 @@ +package dotty.tools.dotc.transform + +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.typer.Docstrings + +class CookComments extends MegaPhase.MiniPhase { + override def phaseName = "cookComments" + + override def transformTypeDef(tree: tpd.TypeDef)(implicit ctx: Context): tpd.Tree = { + if (ctx.settings.YcookComments.value && tree.isClassDef) { + val cls = tree.symbol + val cookingCtx = ctx.localContext(tree, cls).setNewScope + val template = tree.rhs.asInstanceOf[tpd.Template] + val owner = template.self.symbol.orElse(cls) + + template.body.foreach { stat => + Docstrings.cookComment(stat.symbol, owner)(cookingCtx) + } + + Docstrings.cookComment(cls, cls)(cookingCtx) + } + + tree + + } + +} diff --git a/compiler/src/dotty/tools/dotc/typer/Docstrings.scala b/compiler/src/dotty/tools/dotc/typer/Docstrings.scala index fdbcbb3e4d6e..65e68a1df4e4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Docstrings.scala +++ b/compiler/src/dotty/tools/dotc/typer/Docstrings.scala @@ -4,53 +4,67 @@ package typer import core._ import Contexts._, Symbols._, Decorators._, Comments._ -import util.Positions._ import ast.tpd -trait Docstrings { self: Typer => +object Docstrings { - /** The Docstrings typer will handle the expansion of `@define` and - * `@inheritdoc` if there is a `DocContext` present as a property in the - * supplied `ctx`. - * - * It will also type any `@usecase` available in function definitions. - */ - def cookComments(syms: List[Symbol], owner: Symbol)(implicit ctx: Context): Unit = - ctx.docCtx.foreach { docbase => - val relevantSyms = syms.filter(docbase.docstring(_).exists(!_.isExpanded)) - relevantSyms.foreach { sym => - expandParentDocs(sym) - val usecases = docbase.docstring(sym).map(_.usecases).getOrElse(Nil) - - usecases.foreach { usecase => - enterSymbol(createSymbol(usecase.untpdCode)) + /** + * Expands or cooks the documentation for `sym` in class `owner`. + * The expanded comment will directly replace the original comment in the doc context. + * + * The expansion registers `@define` sections, and will replace `@inheritdoc` and variable + * occurrences in the comments. + * + * If the doc comments contain `@usecase` sections, they will be typed. + * + * @param sym The symbol for which the comment is being cooked. + * @param owner The class for which comments are being cooked. + */ + def cookComment(sym: Symbol, owner: Symbol)(implicit ctx: Context): Option[Comment] = { + ctx.docCtx.flatMap { docCtx => + expand(sym, owner)(ctx, docCtx) + } + } - typedStats(usecase.untpdCode :: Nil, owner) match { - case List(df: tpd.DefDef) => usecase.tpdCode = df - case _ => ctx.error("`@usecase` was not a valid definition", usecase.codePos) + private def expand(sym: Symbol, owner: Symbol)(implicit ctx: Context, docCtx: ContextDocstrings): Option[Comment] = { + docCtx.docstring(sym).flatMap { + case cmt if cmt.isExpanded => + Some(cmt) + case _ => + expandComment(sym).map { expanded => + val typedUsecases = expanded.usecases.map { usecase => + ctx.typer.enterSymbol(ctx.typer.createSymbol(usecase.untpdCode)) + ctx.typer.typedStats(usecase.untpdCode :: Nil, owner) match { + case List(df: tpd.DefDef) => + usecase.typed(df) + case _ => + ctx.error("`@usecase` was not a valid definition", usecase.codePos) + usecase + } } + + val commentWithUsecases = expanded.copy(usecases = typedUsecases) + docCtx.addDocstring(sym, Some(commentWithUsecases)) + commentWithUsecases } - } } + } - private def expandParentDocs(sym: Symbol)(implicit ctx: Context): Unit = - ctx.docCtx.foreach { docCtx => - docCtx.docstring(sym).foreach { cmt => - def expandDoc(owner: Symbol): Unit = if (!cmt.isExpanded) { - val tplExp = docCtx.templateExpander - tplExp.defineVariables(sym) - - val newCmt = cmt - .expand(tplExp.expandedDocComment(sym, owner, _)) - .withUsecases + private def expandComment(sym: Symbol, owner: Symbol, comment: Comment)(implicit ctx: Context, docCtx: ContextDocstrings): Comment = { + val tplExp = docCtx.templateExpander + tplExp.defineVariables(sym) + val newComment = comment.expand(tplExp.expandedDocComment(sym, owner, _)) + docCtx.addDocstring(sym, Some(newComment)) + newComment + } - docCtx.addDocstring(sym, Some(newCmt)) - } - - if (sym ne NoSymbol) { - expandParentDocs(sym.owner) - expandDoc(sym.owner) - } - } + private def expandComment(sym: Symbol)(implicit ctx: Context, docCtx: ContextDocstrings): Option[Comment] = { + if (sym eq NoSymbol) None + else { + for { + cmt <- docCtx.docstring(sym) if !cmt.isExpanded + _ = expandComment(sym.owner) + } yield expandComment(sym, sym.owner, cmt) } + } } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 67b669742e81..6be9008ca0dc 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -85,8 +85,7 @@ class Typer extends Namer with Implicits with Inferencing with Dynamic - with Checking - with Docstrings { + with Checking { import Typer._ import tpd.{cpy => _, _} @@ -1568,11 +1567,6 @@ class Typer extends Namer val body1 = addAccessorDefs(cls, typedStats(impl.body, dummy)(ctx.inClassContext(self1.symbol))) - // Expand comments and type usecases if `-Ycook-comments` is set. - if (ctx.settings.YcookComments.value) { - cookComments(cls :: body1.map(_.symbol), self1.symbol)(ctx.localContext(cdef, cls).setNewScope) - } - checkNoDoubleDeclaration(cls) val impl1 = cpy.Template(impl)(constr1, parents1, self1, body1) .withType(dummy.termRef) diff --git a/compiler/src/dotty/tools/io/Directory.scala b/compiler/src/dotty/tools/io/Directory.scala index b961e913bd44..28d936e92666 100644 --- a/compiler/src/dotty/tools/io/Directory.scala +++ b/compiler/src/dotty/tools/io/Directory.scala @@ -23,6 +23,12 @@ object Directory { if (userDir == "") None else Some(apply(userDir).normalize) + def inTempDirectory[T](fn: Directory => T): T = { + val temp = Directory(Files.createTempDirectory("temp")) + try fn(temp) + finally temp.deleteRecursively() + } + def apply(path: String): Directory = apply(Paths.get(path)) def apply(path: JPath): Directory = new Directory(path) } diff --git a/compiler/src/dotty/tools/repl/ReplCompiler.scala b/compiler/src/dotty/tools/repl/ReplCompiler.scala index 6a3fb7ebd4a2..cf81040d655d 100644 --- a/compiler/src/dotty/tools/repl/ReplCompiler.scala +++ b/compiler/src/dotty/tools/repl/ReplCompiler.scala @@ -1,6 +1,5 @@ package dotty.tools.repl -import dotty.tools.backend.jvm.GenBCode import dotty.tools.dotc.ast.Trees._ import dotty.tools.dotc.ast.{tpd, untpd} import dotty.tools.dotc.ast.tpd.TreeOps @@ -15,11 +14,10 @@ import dotty.tools.dotc.core.StdNames._ import dotty.tools.dotc.core.Symbols._ import dotty.tools.dotc.reporting.diagnostic.messages import dotty.tools.dotc.transform.PostTyper -import dotty.tools.dotc.typer.{FrontEnd, ImportInfo} +import dotty.tools.dotc.typer.ImportInfo import dotty.tools.dotc.util.Positions._ import dotty.tools.dotc.util.SourceFile import dotty.tools.dotc.{CompilationUnit, Compiler, Run} -import dotty.tools.io._ import dotty.tools.repl.results._ import scala.collection.mutable @@ -196,13 +194,16 @@ class ReplCompiler extends Compiler { val stat = stats.last.asInstanceOf[tpd.Tree] if (stat.tpe.isError) stat.tpe.show else { - val docCtx = ctx.docCtx.get val symbols = extractSymbols(stat) - val doc = symbols.collectFirst { - case sym if docCtx.docstrings.contains(sym) => - docCtx.docstrings(sym).raw - } - doc.getOrElse(s"// No doc for `${expr}`") + val doc = for { + sym <- symbols + docCtx <- ctx.docCtx + comment <- docCtx.docstring(sym) + body <- comment.expandedBody + } yield body + + if (doc.hasNext) doc.next() + else s"// No doc for `$expr`" } case _ => diff --git a/compiler/src/dotty/tools/repl/ReplDriver.scala b/compiler/src/dotty/tools/repl/ReplDriver.scala index 47e77062bda6..514287a7ad52 100644 --- a/compiler/src/dotty/tools/repl/ReplDriver.scala +++ b/compiler/src/dotty/tools/repl/ReplDriver.scala @@ -65,6 +65,7 @@ class ReplDriver(settings: Array[String], /** Create a fresh and initialized context with IDE mode enabled */ private[this] def initialCtx = { val rootCtx = initCtx.fresh.addMode(Mode.ReadPositions | Mode.Interactive | Mode.ReadComments) + rootCtx.setSetting(rootCtx.settings.YcookComments, true) val ictx = setup(settings, rootCtx)._2 ictx.base.initialize()(ictx) ictx diff --git a/compiler/test/dotty/tools/dotc/core/tasty/CommentPicklingTest.scala b/compiler/test/dotty/tools/dotc/core/tasty/CommentPicklingTest.scala index 8b2a7be3daed..4cafc1673027 100644 --- a/compiler/test/dotty/tools/dotc/core/tasty/CommentPicklingTest.scala +++ b/compiler/test/dotty/tools/dotc/core/tasty/CommentPicklingTest.scala @@ -10,16 +10,9 @@ import dotty.tools.dotc.core.Mode import dotty.tools.dotc.core.Names.Name import dotty.tools.dotc.interfaces.Diagnostic.ERROR import dotty.tools.dotc.reporting.TestReporter +import dotty.tools.io.{Directory, File, Path} -import dotty.tools.vulpix.{TestConfiguration, ParallelTesting} - -import java.io.IOException -import java.nio.file.{FileSystems, FileVisitOption, FileVisitResult, FileVisitor, Files, Path} -import java.nio.file.attribute.BasicFileAttributes -import java.util.EnumSet - -import scala.collection.JavaConverters.asScalaIteratorConverter -import scala.concurrent.duration.{Duration, DurationInt} +import dotty.tools.vulpix.TestConfiguration import org.junit.Test import org.junit.Assert.{assertEquals, assertFalse, fail} @@ -86,25 +79,25 @@ class CommentPicklingTest { } private def compileAndUnpickle[T](sources: List[String])(fn: (List[tpd.Tree], Context) => T) = { - inTempDirectory { tmp => + Directory.inTempDirectory { tmp => val sourceFiles = sources.zipWithIndex.map { case (src, id) => - val path = tmp.resolve(s"Src$id.scala").toAbsolutePath - Files.write(path, src.getBytes("UTF-8")) + val path = tmp./(File("Src$id.scala")).toAbsolute + path.writeAll(src) path.toString } - val out = tmp.resolve("out") - Files.createDirectories(out) + val out = tmp./("out") + out.createDirectory() - val options = compileOptions.and("-d", out.toAbsolutePath.toString).and(sourceFiles: _*) + val options = compileOptions.and("-d", out.toAbsolute.toString).and(sourceFiles: _*) val reporter = TestReporter.reporter(System.out, logLevel = ERROR) Main.process(options.all, reporter) assertFalse("Compilation failed.", reporter.hasErrors) - val tastyFiles = getAll(tmp, "glob:**.tasty") + val tastyFiles = Path.onlyFiles(out.walkFilter(_.extension == "tasty")).toList val unpicklingOptions = unpickleOptions - .withClasspath(out.toAbsolutePath.toString) + .withClasspath(out.toAbsolute.toString) .and("dummy") // Need to pass a dummy source file name val unpicklingDriver = new UnpicklingDriver unpicklingDriver.unpickle(unpicklingOptions.all, tastyFiles)(fn) @@ -113,12 +106,11 @@ class CommentPicklingTest { private class UnpicklingDriver extends Driver { override def initCtx = super.initCtx.addMode(Mode.ReadComments) - def unpickle[T](args: Array[String], paths: List[Path])(fn: (List[tpd.Tree], Context) => T): T = { + def unpickle[T](args: Array[String], files: List[File])(fn: (List[tpd.Tree], Context) => T): T = { implicit val (_, ctx: Context) = setup(args, initCtx) ctx.initialize() - val trees = paths.flatMap { p => - val bytes = Files.readAllBytes(p) - val unpickler = new DottyUnpickler(bytes) + val trees = files.flatMap { f => + val unpickler = new DottyUnpickler(f.bytes().toArray) unpickler.enter(roots = Set.empty) unpickler.trees(ctx) } @@ -126,20 +118,4 @@ class CommentPicklingTest { } } - private def inTempDirectory[T](fn: Path => T): T = { - val temp = Files.createTempDirectory("temp") - try fn(temp) - finally { - val allFiles = getAll(temp, "glob:**").sortBy(_.toAbsolutePath.toString).reverse - allFiles.foreach(Files.delete(_)) - } - } - - private def getAll(base: Path, pattern: String): List[Path] = { - val paths = Files.walk(base) - val matcher = FileSystems.getDefault.getPathMatcher(pattern) - try paths.filter(matcher.matches).iterator().asScala.toList - finally paths.close() - } - } diff --git a/compiler/test/dotty/tools/repl/DocTests.scala b/compiler/test/dotty/tools/repl/DocTests.scala index 4cf8e03bb31e..b3157966f208 100644 --- a/compiler/test/dotty/tools/repl/DocTests.scala +++ b/compiler/test/dotty/tools/repl/DocTests.scala @@ -146,6 +146,20 @@ class DocTests extends ReplTest { assertEquals("/** doc0 */", doc("O.foo.bar")) } + @Test def docIsCooked = + eval( + """/** + | * An object + | * @define Variable some-value + | */ + |object Foo { + | /** Expansion: $Variable */ + | def hello = "world" + |} + """.stripMargin).andThen { implicit s => + assertEquals("/** Expansion: some-value */", doc("Foo.hello")) + } + private def eval(code: String): State = fromInitialState { implicit s => run(code) } diff --git a/doc-tool/src/dotty/tools/dottydoc/DocCompiler.scala b/doc-tool/src/dotty/tools/dottydoc/DocCompiler.scala index b1c15740c9f2..eb1d0dc873d5 100644 --- a/doc-tool/src/dotty/tools/dottydoc/DocCompiler.scala +++ b/doc-tool/src/dotty/tools/dottydoc/DocCompiler.scala @@ -3,8 +3,13 @@ package dottydoc import core._ import core.transform._ +import dotc.core.Contexts.Context import dotc.core.Phases.Phase -import dotc.Compiler +import dotc.core.Mode +import dotc.{Compiler, Run} + +import dotty.tools.dotc.fromtasty.{ReadTastyTreesFromClasses, TASTYRun} +import dotty.tools.dotc.transform.CookComments /** Custom Compiler with phases for the documentation tool * @@ -20,13 +25,24 @@ import dotc.Compiler */ class DocCompiler extends Compiler { + override def newRun(implicit ctx: Context): Run = { + if (ctx.settings.fromTasty.value) { + reset() + new TASTYRun(this, ctx.addMode(Mode.ReadPositions).addMode(Mode.ReadComments)) + } else { + super.newRun + } + } + override protected def frontendPhases: List[List[Phase]] = - List(new DocFrontEnd) :: Nil + List(new ReadTastyTreesFromClasses) :: + List(new DocFrontEnd) :: Nil override protected def picklerPhases: List[List[Phase]] = Nil override protected def transformPhases: List[List[Phase]] = + List(new CookComments) :: List(new DocImplicitsPhase) :: List(new DocASTPhase) :: List(DocMiniTransformations(new UsecasePhase, diff --git a/doc-tool/src/dotty/tools/dottydoc/DocFrontEnd.scala b/doc-tool/src/dotty/tools/dottydoc/DocFrontEnd.scala index 606fd0fc6f55..25cd8d95b42f 100644 --- a/doc-tool/src/dotty/tools/dottydoc/DocFrontEnd.scala +++ b/doc-tool/src/dotty/tools/dottydoc/DocFrontEnd.scala @@ -1,17 +1,29 @@ package dotty.tools package dottydoc -import dotc.typer.FrontEnd +import dotc.fromtasty.ReadTastyTreesFromClasses +import dotc.typer.{FrontEnd, Typer} import dotc.core.Contexts.Context import dotc.CompilationUnit +import util.syntax.ContextWithContextDottydoc + /** `DocFrontEnd` uses the Dotty `FrontEnd` without discarding the AnyVal * interfaces for Boolean, Int, Char, Long, Byte etc. * + * If `-from-tasty` is set, then the trees and documentation will be loaded + * from TASTY. The comments will be cooked after being unpickled. + * * It currently still throws away Java sources by overriding * `discardAfterTyper`. */ class DocFrontEnd extends FrontEnd { + + override def runOn(units: List[CompilationUnit])(implicit ctx: Context): List[CompilationUnit] = { + if (ctx.settings.fromTasty.value) units + else super.runOn(units) + } + override protected def discardAfterTyper(unit: CompilationUnit)(implicit ctx: Context) = unit.isJava } diff --git a/doc-tool/src/dotty/tools/dottydoc/core/AlternateConstructorsPhase.scala b/doc-tool/src/dotty/tools/dottydoc/core/AlternateConstructorsPhase.scala index 53c96fc87487..345fac86db34 100644 --- a/doc-tool/src/dotty/tools/dottydoc/core/AlternateConstructorsPhase.scala +++ b/doc-tool/src/dotty/tools/dottydoc/core/AlternateConstructorsPhase.scala @@ -24,11 +24,11 @@ class AlternateConstructors extends DocMiniPhase { override def transformClass(implicit ctx: Context) = { case cls: ClassImpl => val (constructors, members) = partitionMembers(cls) - cls.copy(members = members, constructors = constructors) + cls.copy(members = members, constructors = constructors) :: Nil } override def transformCaseClass(implicit ctx: Context) = { case cc: CaseClassImpl => val (constructors, members) = partitionMembers(cc) - cc.copy(members = members, constructors = constructors) + cc.copy(members = members, constructors = constructors) :: Nil } } diff --git a/doc-tool/src/dotty/tools/dottydoc/core/DocASTPhase.scala b/doc-tool/src/dotty/tools/dottydoc/core/DocASTPhase.scala index 507d4ce77bda..587a80b86bad 100644 --- a/doc-tool/src/dotty/tools/dottydoc/core/DocASTPhase.scala +++ b/doc-tool/src/dotty/tools/dottydoc/core/DocASTPhase.scala @@ -5,10 +5,8 @@ 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.{PolyType, NoType} +import dotc.core.Types.PolyType import dotc.core.Phases.Phase import dotc.core.Symbols.{ Symbol, NoSymbol } import dotc.core.NameOps._ @@ -17,7 +15,6 @@ 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._ @@ -27,20 +24,20 @@ class DocASTPhase extends Phase { def phaseName = "docASTPhase" /** Build documentation hierarchy from existing tree */ - def collect(tree: Tree)(implicit ctx: Context): Entity = { + def collect(tree: Tree)(implicit ctx: Context): List[Entity] = { val implicitConversions = ctx.docbase.defs(tree.symbol) def collectList(xs: List[Tree]): List[Entity] = - xs.map(collect).filter(_ != NonEntity) + xs.flatMap(collect) def collectEntityMembers(xs: List[Tree]) = collectList(xs).asInstanceOf[List[Entity with Members]] def collectMembers(tree: Tree)(implicit ctx: Context): List[Entity] = { - val defs = (tree match { + val defs = tree match { case t: Template => collectList(t.body) case _ => Nil - }) + } defs ++ implicitConversions.flatMap(membersFromSymbol) } @@ -83,55 +80,57 @@ class DocASTPhase extends Phase { } - if (tree.symbol.is(Flags.Synthetic) && !tree.symbol.is(Flags.Module)) NonEntity + if (tree.symbol.is(Flags.Synthetic) && !tree.symbol.is(Flags.Module)) Nil else tree match { /** package */ case pd @ PackageDef(pid, st) => - addPackage(PackageImpl(pd.symbol, annotations(pd.symbol), pd.symbol.showFullName, collectEntityMembers(st), path(pd.symbol))) + addPackage(PackageImpl(pd.symbol, annotations(pd.symbol), pd.symbol.showFullName, collectEntityMembers(st), path(pd.symbol))) :: Nil /** type alias */ case t: TypeDef if !t.isClassDef => val sym = t.symbol if (sym.is(Flags.Synthetic | Flags.Param)) - NonEntity + Nil else { val tparams = t.rhs.tpe match { case tp: PolyType => tp.paramNames.map(_.show) case _ => Nil } - TypeAliasImpl(sym, annotations(sym), flags(t), t.name.show.split("\\$\\$").last, path(sym), alias(t.rhs.tpe), tparams) + TypeAliasImpl(sym, annotations(sym), flags(t), t.name.show.split("\\$\\$").last, path(sym), alias(t.rhs.tpe), tparams) :: Nil } /** 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)) + TraitImpl(t.symbol, annotations(t.symbol), n.show, collectMembers(rhs), flags(t), path(t.symbol), typeParams(t.symbol), traitParameters(t.symbol), superTypes(t)) :: Nil /** objects, on the format "Object$" so drop the last letter */ case o @ TypeDef(n, rhs) if o.symbol.is(Flags.Module) => //TODO: should not `collectMember` from `rhs` - instead: get from symbol, will get inherited members as well - ObjectImpl(o.symbol, annotations(o.symbol), o.name.stripModuleClassSuffix.show, collectMembers(rhs), flags(o), path(o.symbol), superTypes(o)) + ObjectImpl(o.symbol, annotations(o.symbol), o.name.stripModuleClassSuffix.show, collectMembers(rhs), flags(o), path(o.symbol), superTypes(o)) :: Nil /** 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, Nil, NonEntity) match { - case x if c.symbol.is(Flags.CaseClass) => CaseClassImpl.tupled(x) - case x => ClassImpl.tupled(x) + val parameters = (c.symbol, annotations(c.symbol), n.show, collectMembers(rhs), flags(c), path(c.symbol), typeParams(c.symbol), constructors(c.symbol), superTypes(c), None, Nil, None) + if (c.symbol.is(Flags.CaseClass)) { + CaseClassImpl.tupled(parameters) :: Nil + } else { + ClassImpl.tupled(parameters) :: Nil } /** 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)) + 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)) :: Nil /** 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) + ValImpl(v.symbol, annotations(v.symbol), v.name.decode.toString, flags(v), path(v.symbol), returnType(v.tpt.tpe), kind) :: Nil case x => { ctx.docbase.debug(s"Found unwanted entity: $x (${x.pos},\n${x.show}") - NonEntity + Nil } } } @@ -158,7 +157,7 @@ class DocASTPhase extends Phase { if (old.annotations.isEmpty) old.annotations = newPkg.annotations mergeMembers(newPkg, old) if (old.superTypes.isEmpty) old.superTypes = newPkg.superTypes - if (!old.comment.isDefined) old.comment = newPkg.comment + if (old.comment.isEmpty) old.comment = newPkg.comment old } @@ -178,9 +177,9 @@ class DocASTPhase extends Phase { def createAndInsert(currentPkg: PackageImpl, path: List[String]): PackageImpl = { (path: @unchecked) match { case x :: Nil => { - val existingPkg = currentPkg.members.collect { + val existingPkg = currentPkg.members.collectFirst { case p: PackageImpl if p.name == newPkg.name => p - }.headOption + } if (existingPkg.isDefined) mergedPackages(existingPkg.get, newPkg) else { @@ -190,9 +189,9 @@ class DocASTPhase extends Phase { } case x :: xs => { val subPkg = s"${currentPkg.name}.$x" - val existingPkg = currentPkg.members.collect { + val existingPkg = currentPkg.members.collectFirst { case p: PackageImpl if p.name == subPkg => p - }.headOption + } if (existingPkg.isDefined) createAndInsert(existingPkg.get, xs) else { diff --git a/doc-tool/src/dotty/tools/dottydoc/core/DocstringPhase.scala b/doc-tool/src/dotty/tools/dottydoc/core/DocstringPhase.scala index 2471e9220c05..fa6c06cd4759 100644 --- a/doc-tool/src/dotty/tools/dottydoc/core/DocstringPhase.scala +++ b/doc-tool/src/dotty/tools/dottydoc/core/DocstringPhase.scala @@ -9,13 +9,12 @@ import transform.DocMiniPhase import model._ import model.internal._ import model.comment._ -import HtmlParsers._ import util.syntax._ /** Phase to add docstrings to the Dottydoc AST */ class DocstringPhase extends DocMiniPhase with CommentParser with CommentCleaner { - private def getComment(sym: Symbol)(implicit ctx: Context): Option[CompilerComment] = + private def getComment(sym: Symbol)(implicit ctx: Context): Option[CompilerComment] = { ctx.docbase.docstring(sym) .orElse { // If the symbol doesn't have a docstring, look for an overridden @@ -26,46 +25,50 @@ class DocstringPhase extends DocMiniPhase with CommentParser with CommentCleaner } .flatMap(ctx.docbase.docstring) } + } - private def parsedComment(ent: Entity)(implicit ctx: Context): Option[Comment] = - getComment(ent.symbol).map { cmt => - val parsed = parse(ent, ctx.docbase.packages, clean(cmt.raw), cmt.raw, cmt.pos) - + private def parsedComment(ent: Entity)(implicit ctx: Context): Option[Comment] = { + for { + comment <- getComment(ent.symbol) + text <- comment.expandedBody + } yield { + val parsed = parse(ent, ctx.docbase.packages, clean(text), text, comment.pos) if (ctx.settings.wikiSyntax.value) - WikiComment(ent, parsed, cmt.pos).comment + WikiComment(ent, parsed, comment.pos).comment else - MarkdownComment(ent, parsed, cmt.pos).comment + MarkdownComment(ent, parsed, comment.pos).comment } + } override def transformPackage(implicit ctx: Context) = { case ent: PackageImpl => - ent.copy(comment = parsedComment(ent)) + ent.copy(comment = parsedComment(ent)) :: Nil } override def transformClass(implicit ctx: Context) = { case ent: ClassImpl => - ent.copy(comment = parsedComment(ent)) + ent.copy(comment = parsedComment(ent)) :: Nil } override def transformCaseClass(implicit ctx: Context) = { case ent: CaseClassImpl => - ent.copy(comment = parsedComment(ent)) + ent.copy(comment = parsedComment(ent)) :: Nil } override def transformTrait(implicit ctx: Context) = { case ent: TraitImpl => - ent.copy(comment = parsedComment(ent)) + ent.copy(comment = parsedComment(ent)) :: Nil } override def transformObject(implicit ctx: Context) = { case ent: ObjectImpl => - ent.copy(comment = parsedComment(ent)) + ent.copy(comment = parsedComment(ent)) :: Nil } override def transformDef(implicit ctx: Context) = { case ent: DefImpl => - ent.copy(comment = parsedComment(ent)) + ent.copy(comment = parsedComment(ent)) :: Nil } override def transformVal(implicit ctx: Context) = { case ent: ValImpl => - ent.copy(comment = parsedComment(ent)) + ent.copy(comment = parsedComment(ent)) :: Nil } override def transformTypeAlias(implicit ctx: Context) = { case ent: TypeAliasImpl => - ent.copy(comment = parsedComment(ent)) + ent.copy(comment = parsedComment(ent)) :: Nil } } diff --git a/doc-tool/src/dotty/tools/dottydoc/core/LinkCompanionsPhase.scala b/doc-tool/src/dotty/tools/dottydoc/core/LinkCompanionsPhase.scala index 82bd90da0561..07e0c0f8ffcd 100644 --- a/doc-tool/src/dotty/tools/dottydoc/core/LinkCompanionsPhase.scala +++ b/doc-tool/src/dotty/tools/dottydoc/core/LinkCompanionsPhase.scala @@ -3,44 +3,40 @@ package dottydoc package core import dotc.core.Contexts.Context -import dotc.ast.tpd import transform.DocMiniPhase import model.internal._ import model._ -import model.factories._ -import dotty.tools.dotc.core.Symbols.Symbol -import util.syntax._ class LinkCompanions extends DocMiniPhase { private def linkCompanions(ent: Entity)(implicit ctx: Context): ent.type = { ent.children.groupBy(_.name).foreach { - case (_, List(x1: Companion, x2: Companion)) => { + case (_, List(x1: Companion, x2: Companion)) => x1.companionPath = x2.path x2.companionPath = x1.path - } + case _ => () } ent } override def transformPackage(implicit ctx: Context) = { case ent: PackageImpl => - linkCompanions(ent) + linkCompanions(ent) :: Nil } override def transformClass(implicit ctx: Context) = { case ent: ClassImpl => - linkCompanions(ent) + linkCompanions(ent) :: Nil } override def transformCaseClass(implicit ctx: Context) = { case ent: CaseClassImpl => - linkCompanions(ent) + linkCompanions(ent) :: Nil } override def transformObject(implicit ctx: Context) = { case ent: ObjectImpl => - linkCompanions(ent) + linkCompanions(ent) :: Nil } override def transformTrait(implicit ctx: Context) = { case ent: TraitImpl => - linkCompanions(ent) + linkCompanions(ent) :: Nil } } diff --git a/doc-tool/src/dotty/tools/dottydoc/core/PackageObjectsPhase.scala b/doc-tool/src/dotty/tools/dottydoc/core/PackageObjectsPhase.scala index adcadb0baa49..ccb8d2f102cf 100644 --- a/doc-tool/src/dotty/tools/dottydoc/core/PackageObjectsPhase.scala +++ b/doc-tool/src/dotty/tools/dottydoc/core/PackageObjectsPhase.scala @@ -2,13 +2,9 @@ package dotty.tools package dottydoc package core -import dotty.tools.dotc.core.Symbols.Symbol import dotc.core.Contexts.Context -import dotc.ast.tpd - import model._ import model.internal._ -import util.syntax._ import transform.DocMiniPhase class PackageObjectsPhase extends DocMiniPhase { @@ -16,8 +12,7 @@ class PackageObjectsPhase extends DocMiniPhase { override def transformPackage(implicit ctx: Context) = { case pkg: PackageImpl => pkg .members - .collect { case o: Object if o.symbol.isPackageObject => o } - .headOption + .collectFirst { case o: Object if o.symbol.isPackageObject => o } .map { obj => pkg.copy( members = obj.members ++ pkg.members, @@ -25,11 +20,11 @@ class PackageObjectsPhase extends DocMiniPhase { comment = obj.comment ) } - .getOrElse(pkg) + .getOrElse(pkg) :: Nil } override def transformObject(implicit ctx: Context) = { case obj: Object => - if (obj.symbol.isPackageObject) NonEntity - else obj + if (obj.symbol.isPackageObject) Nil + else obj :: Nil } } diff --git a/doc-tool/src/dotty/tools/dottydoc/core/RemoveEmptyPackagesPhase.scala b/doc-tool/src/dotty/tools/dottydoc/core/RemoveEmptyPackagesPhase.scala index c7917b9d0e1c..33fb7d0030d5 100644 --- a/doc-tool/src/dotty/tools/dottydoc/core/RemoveEmptyPackagesPhase.scala +++ b/doc-tool/src/dotty/tools/dottydoc/core/RemoveEmptyPackagesPhase.scala @@ -9,7 +9,7 @@ import model._ class RemoveEmptyPackages extends DocMiniPhase { override def transformPackage(implicit ctx: Context) = { case p: Package => - if (p.members.exists(_.kind != "package")) p - else NonEntity + if (p.members.exists(_.kind != "package")) p :: Nil + else Nil } } diff --git a/doc-tool/src/dotty/tools/dottydoc/core/SortMembersPhase.scala b/doc-tool/src/dotty/tools/dottydoc/core/SortMembersPhase.scala index a281558d4af5..3f554fd7f236 100644 --- a/doc-tool/src/dotty/tools/dottydoc/core/SortMembersPhase.scala +++ b/doc-tool/src/dotty/tools/dottydoc/core/SortMembersPhase.scala @@ -34,22 +34,22 @@ class SortMembers extends DocMiniPhase { } override def transformPackage(implicit ctx: Context) = { case p: PackageImpl => - p.copy(members = sort(p.members)) + p.copy(members = sort(p.members)) :: Nil } override def transformClass(implicit ctx: Context) = { case c: ClassImpl => - c.copy(members = sort(c.members)) + c.copy(members = sort(c.members)) :: Nil } override def transformCaseClass(implicit ctx: Context) = { case cc: CaseClassImpl => - cc.copy(members = sort(cc.members)) + cc.copy(members = sort(cc.members)) :: Nil } override def transformTrait(implicit ctx: Context) = { case t: TraitImpl => - t.copy(members = sort(t.members)) + t.copy(members = sort(t.members)) :: Nil } override def transformObject(implicit ctx: Context) = { case o: ObjectImpl => - o.copy(members = sort(o.members)) + o.copy(members = sort(o.members)) :: Nil } } diff --git a/doc-tool/src/dotty/tools/dottydoc/core/TypeLinkingPhases.scala b/doc-tool/src/dotty/tools/dottydoc/core/TypeLinkingPhases.scala index 5e0099f4bf74..5f79ba84e23b 100644 --- a/doc-tool/src/dotty/tools/dottydoc/core/TypeLinkingPhases.scala +++ b/doc-tool/src/dotty/tools/dottydoc/core/TypeLinkingPhases.scala @@ -3,7 +3,6 @@ package dottydoc package core import dotc.core.Contexts.Context -import dotc.util.Positions.NoPosition import transform.DocMiniPhase import model._ @@ -12,27 +11,25 @@ import model.comment._ import model.references._ import HtmlParsers._ 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) - df.copy(returnValue = returnValue) + df.copy(returnValue = returnValue) :: Nil } override def transformVal(implicit ctx: Context) = { case vl: ValImpl => val returnValue = linkReference(vl, vl.returnValue, ctx.docbase.packages) - vl.copy(returnValue = returnValue) + vl.copy(returnValue = returnValue) :: Nil } override def transformTypeAlias(implicit ctx: Context) = { case ta: TypeAliasImpl => ta.alias.map { alias => val linkedAlias = linkReference(ta, alias, ctx.docbase.packages) - ta.copy(alias = Some(linkedAlias)) + ta.copy(alias = Some(linkedAlias)) :: Nil } - .getOrElse(ta) + .getOrElse(ta :: Nil) } } @@ -43,7 +40,7 @@ class LinkParamListTypes extends DocMiniPhase with TypeLinker { newList = list.map(linkReference(df, _, ctx.docbase.packages)) } yield ParamListImpl(newList.asInstanceOf[List[NamedReference]], isImplicit) - df.copy(paramLists = newParamLists) + df.copy(paramLists = newParamLists) :: Nil } } @@ -51,23 +48,23 @@ class LinkSuperTypes extends DocMiniPhase with TypeLinker { def linkSuperTypes(ent: Entity with SuperTypes)(implicit ctx: Context): List[MaterializableLink] = ent.superTypes.collect { case UnsetLink(title, query) => - handleEntityLink(title, lookup(ent, ctx.docbase.packages, query), ent) + handleEntityLink(title, lookup(Some(ent), ctx.docbase.packages, query), ent) } override def transformClass(implicit ctx: Context) = { case cls: ClassImpl => - cls.copy(superTypes = linkSuperTypes(cls)) + cls.copy(superTypes = linkSuperTypes(cls)) :: Nil } override def transformCaseClass(implicit ctx: Context) = { case cc: CaseClassImpl => - cc.copy(superTypes = linkSuperTypes(cc)) + cc.copy(superTypes = linkSuperTypes(cc)) :: Nil } override def transformTrait(implicit ctx: Context) = { case trt: TraitImpl => - trt.copy(superTypes = linkSuperTypes(trt)) + trt.copy(superTypes = linkSuperTypes(trt)) :: Nil } override def transformObject(implicit ctx: Context) = { case obj: ObjectImpl => - obj.copy(superTypes = linkSuperTypes(obj)) + obj.copy(superTypes = linkSuperTypes(obj)) :: Nil } } @@ -75,13 +72,13 @@ 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) - df.copy(implicitlyAddedFrom = Some(implicitlyAddedFrom)) + df.copy(implicitlyAddedFrom = Some(implicitlyAddedFrom)) :: Nil } override def transformVal(implicit ctx: Context) = { case vl: ValImpl if vl.implicitlyAddedFrom.isDefined => val implicitlyAddedFrom = linkReference(vl, vl.implicitlyAddedFrom.get, ctx.docbase.packages) - vl.copy(implicitlyAddedFrom = Some(implicitlyAddedFrom)) + vl.copy(implicitlyAddedFrom = Some(implicitlyAddedFrom)) :: Nil } } @@ -100,7 +97,7 @@ trait TypeLinker extends MemberLookup { val inlineToHtml = InlineToHtml(ent) val title = t - val target = handleEntityLink(title, lookup(ent, packs, query), ent, query) + val target = handleEntityLink(title, lookup(Some(ent), packs, query), ent, query) val tpTargets = tps.map(linkReference(ent, _, packs)) ref.copy(tpeLink = target, paramLinks = tpTargets) case ref @ OrTypeReference(left, right) => diff --git a/doc-tool/src/dotty/tools/dottydoc/core/UsecasePhase.scala b/doc-tool/src/dotty/tools/dottydoc/core/UsecasePhase.scala index 5940df0bc9e0..f9c118055c1e 100644 --- a/doc-tool/src/dotty/tools/dottydoc/core/UsecasePhase.scala +++ b/doc-tool/src/dotty/tools/dottydoc/core/UsecasePhase.scala @@ -13,7 +13,7 @@ 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 + val name = d.name.show.split("\\$").head DefImpl( sym, annotations(sym), @@ -27,6 +27,12 @@ class UsecasePhase extends DocMiniPhase { } 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) + val defdefs = + ctx.docbase.docstring(df.symbol) + .map(_.usecases.flatMap(_.tpdCode)) + .getOrElse(Nil) + + if (defdefs.isEmpty) df :: Nil + else defdefs.map(defdefToDef(_, df.symbol)) } } diff --git a/doc-tool/src/dotty/tools/dottydoc/core/transform.scala b/doc-tool/src/dotty/tools/dottydoc/core/transform.scala index 5eba48b289f6..2be09b76c6d8 100644 --- a/doc-tool/src/dotty/tools/dottydoc/core/transform.scala +++ b/doc-tool/src/dotty/tools/dottydoc/core/transform.scala @@ -4,7 +4,6 @@ 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._ @@ -14,8 +13,8 @@ import util.traversing._ object transform { /** * The idea behind DocMiniTransformations is to fuse transformations to the - * doc AST, much like `MiniPhase` in dotty core - but in a much more - * simple implementation + * doc AST, much like `MiniPhase` in dotty core - but in a much simpler + * implementation * * Usage * ----- @@ -24,7 +23,7 @@ object transform { * * ``` * override def transformDef(implicit ctx: Context) = { - * case x if shouldTransform(x) => x.copy(newValue = ...) + * case x if shouldTransform(x) => x.copy(newValue = ...) :: Nil * } * ``` * @@ -43,7 +42,7 @@ object transform { * * Deleting nodes in the AST * ------------------------- - * To delete a node in the AST, simply return `NonEntity` from transforming method + * To delete a node in the AST, simply return an empty list from transforming method */ trait DocMiniTransformations extends Phase { def transformations: List[DocMiniPhase] @@ -52,32 +51,29 @@ object transform { for { pack <- rootPackages(ctx.docbase.packages) transformed = performPackageTransform(pack) - } yield ctx.docbase.packagesMutable(pack.name) = transformed - - ctx.docbase.packagesMutable.foreach { case (key, value) => - if (value eq NonEntity) ctx.docbase.packagesMutable -= key + } { + ctx.docbase.packagesMutable -= pack.name + transformed.foreach(p => ctx.docbase.packagesMutable += p.name -> p) } units } - private def performPackageTransform(pack: Package)(implicit ctx: Context): Package = { - def transformEntity[E <: Entity](e: E, f: DocMiniPhase => E => E)(createNew: E => E): Entity = { - val transformedEntity = transformations.foldLeft(e) { case (oldE, transf) => - f(transf)(oldE) + private def performPackageTransform(pack: Package)(implicit ctx: Context): List[Package] = { + def transformEntity[E <: Entity](e: E, f: DocMiniPhase => E => List[E])(createNew: E => E): List[Entity] = { + val transformEntities = transformations.foldLeft(e :: Nil) { case (oldEs, transf) => + oldEs.flatMap(f(transf)) } - - if (transformedEntity eq NonEntity) NonEntity - else createNew(transformedEntity) + transformEntities.map(createNew) } - def traverse(ent: Entity): Entity = ent match { + def traverse(ent: Entity): List[Entity] = ent match { case p: Package => transformEntity(p, _.packageTransformation) { p => val newPackage = PackageImpl( p.symbol, p.annotations, p.name, - p.members.map(traverse).filterNot(_ eq NonEntity), + p.members.flatMap(traverse), p.path, p.superTypes, p.comment, @@ -107,7 +103,7 @@ object transform { cls.symbol, cls.annotations, cls.name, - cls.members.map(traverse).filterNot(_ eq NonEntity), + cls.members.flatMap(traverse), cls.modifiers, cls.path, cls.typeParams, @@ -123,7 +119,7 @@ object transform { cc.symbol, cc.annotations, cc.name, - cc.members.map(traverse).filterNot(_ eq NonEntity), + cc.members.flatMap(traverse), cc.modifiers, cc.path, cc.typeParams, @@ -139,7 +135,7 @@ object transform { trt.symbol, trt.annotations, trt.name, - trt.members.map(traverse).filterNot(_ eq NonEntity), + trt.members.flatMap(traverse), trt.modifiers, trt.path, trt.typeParams, @@ -155,7 +151,7 @@ object transform { obj.symbol, obj.annotations, obj.name, - obj.members.map(traverse).filterNot(_ eq NonEntity), + obj.members.flatMap(traverse), obj.modifiers, obj.path, obj.superTypes, @@ -195,7 +191,7 @@ object transform { } } - traverse(pack).asInstanceOf[Package] + traverse(pack).asInstanceOf[List[Package]] } override def run(implicit ctx: Context): Unit = () @@ -213,18 +209,18 @@ object transform { } trait DocMiniPhase { phase => - private def identity[E]: PartialFunction[E, E] = { - case id: E @unchecked => id + private def identity[E]: PartialFunction[E, List[E]] = { + case id: E @unchecked => id :: Nil } - def transformPackage(implicit ctx: Context): PartialFunction[Package, Package] = identity - def transformTypeAlias(implicit ctx: Context): PartialFunction[TypeAlias, TypeAlias] = identity - def transformClass(implicit ctx: Context): PartialFunction[Class, Class] = identity - def transformCaseClass(implicit ctx: Context): PartialFunction[CaseClass, CaseClass] = identity - def transformTrait(implicit ctx: Context): PartialFunction[Trait, Trait] = identity - def transformObject(implicit ctx: Context): PartialFunction[Object, Object] = identity - def transformDef(implicit ctx: Context): PartialFunction[Def, Def] = identity - def transformVal(implicit ctx: Context): PartialFunction[Val, Val] = identity + def transformPackage(implicit ctx: Context): PartialFunction[Package, List[Package]] = identity + def transformTypeAlias(implicit ctx: Context): PartialFunction[TypeAlias, List[TypeAlias]] = identity + def transformClass(implicit ctx: Context): PartialFunction[Class, List[Class]] = identity + def transformCaseClass(implicit ctx: Context): PartialFunction[CaseClass, List[CaseClass]] = identity + def transformTrait(implicit ctx: Context): PartialFunction[Trait, List[Trait]] = identity + def transformObject(implicit ctx: Context): PartialFunction[Object, List[Object]] = identity + def transformDef(implicit ctx: Context): PartialFunction[Def, List[Def]] = identity + def transformVal(implicit ctx: Context): PartialFunction[Val, List[Val]] = identity private[transform] def packageTransformation(p: Package)(implicit ctx: Context) = (transformPackage orElse identity)(p) private[transform] def typeAliasTransformation(alias: TypeAlias)(implicit ctx: Context) = (transformTypeAlias orElse identity)(alias) diff --git a/doc-tool/src/dotty/tools/dottydoc/model/comment/Comment.scala b/doc-tool/src/dotty/tools/dottydoc/model/comment/Comment.scala index 5b6c34de8afc..dbc76d2fd618 100644 --- a/doc-tool/src/dotty/tools/dottydoc/model/comment/Comment.scala +++ b/doc-tool/src/dotty/tools/dottydoc/model/comment/Comment.scala @@ -6,13 +6,10 @@ package comment import dotty.tools.dottydoc.util.syntax._ import dotty.tools.dotc.core.Contexts.Context import dotty.tools.dotc.util.Positions._ -import dotty.tools.dotc.config.Printers.dottydoc import com.vladsch.flexmark.ast.{ Node => MarkdownNode } import HtmlParsers._ import util.MemberLookup -import dotc.util.SourceFile - case class Comment ( body: String, short: String, @@ -161,7 +158,7 @@ extends MarkupConversion[Body] { def linkedExceptions(m: Map[String, String])(implicit ctx: Context) = { m.mapValues(_.toWiki(ent, ctx.docbase.packages, pos)).map { case (targetStr, body) => - val link = lookup(ent, ctx.docbase.packages, targetStr) + val link = lookup(Some(ent), ctx.docbase.packages, targetStr) val newBody = body match { case Body(List(Paragraph(Chain(content)))) => val descr = Text(" ") +: content diff --git a/doc-tool/src/dotty/tools/dottydoc/model/entities.scala b/doc-tool/src/dotty/tools/dottydoc/model/entities.scala index cede5f48f8cf..71e96ca165c3 100644 --- a/doc-tool/src/dotty/tools/dottydoc/model/entities.scala +++ b/doc-tool/src/dotty/tools/dottydoc/model/entities.scala @@ -3,7 +3,7 @@ package model import comment._ import references._ -import dotty.tools.dotc.core.Symbols.{ Symbol, NoSymbol } +import dotty.tools.dotc.core.Symbols.Symbol trait Entity { entity => def symbol: Symbol @@ -17,7 +17,7 @@ trait Entity { entity => def kind: String - def parent: Entity + def parent: Option[Entity] def annotations: List[String] @@ -35,16 +35,7 @@ trait Entity { entity => } /** All parents from package level i.e. Package to Object to Member etc */ - def parents: List[Entity] = parent match { - case NonEntity => Nil - case e => e :: e.parents - } - - /** Applies `f` to entity if != `NonEntity` */ - def fold[A](nonEntity: A)(f: Entity => A) = this match { - case NonEntity => nonEntity - case x => f(x) - } + def parents: List[Entity] = this :: this.parents } trait SuperTypes { @@ -134,31 +125,3 @@ trait Def extends Entity with Modifiers with TypeParams with ReturnValue with Im } trait Val extends Entity with Modifiers with ReturnValue with ImplicitlyAddedEntity - -sealed trait NonEntity extends Package with TypeAlias with Class with CaseClass with Trait with Object with Def with Val { - override val kind = "" - val annotations = Nil - val name = "" - val symbol = NoSymbol - val comment = None - val path = Nil - val parent = NonEntity - val constructors = Nil - val paramLists = Nil - val implicitlyAddedFrom = None - val members = Nil - val modifiers = Nil - val reference = EmptyReference - val returnValue = EmptyReference - val superTypes = Nil - val typeParams = Nil - val traitParams = Nil - val alias = None - val companionPath = Nil - def companionPath_=(xs: List[String]) = () -} - -final case object NonEntity extends NonEntity -final case object RootEntity extends NonEntity { - override val name = "root" -} diff --git a/doc-tool/src/dotty/tools/dottydoc/model/factories.scala b/doc-tool/src/dotty/tools/dottydoc/model/factories.scala index 968428e30056..0b02cf75555f 100644 --- a/doc-tool/src/dotty/tools/dottydoc/model/factories.scala +++ b/doc-tool/src/dotty/tools/dottydoc/model/factories.scala @@ -1,19 +1,13 @@ package dotty.tools.dottydoc package model -import comment._ import references._ import dotty.tools.dotc import dotc.core.Types import Types._ -import dotc.core.TypeApplications._ import dotc.core.Contexts.Context import dotc.core.Symbols.{ Symbol, ClassSymbol } import dotty.tools.dotc.core.SymDenotations._ -import dotty.tools.dotc.config.Printers.dottydoc -import dotty.tools.dotc.core.Names.TypeName -import dotc.ast.Trees._ -import dotc.core.StdNames._ import scala.annotation.tailrec @@ -42,7 +36,9 @@ object factories { } def annotations(sym: Symbol)(implicit ctx: Context): List[String] = - sym.annotations.map(_.symbol.showFullName) + sym.annotations.collect { + case ann if ann.symbol != ctx.definitions.SourceFileAnnot => ann.symbol.showFullName + } private val product = """Product[1-9][0-9]*""".r diff --git a/doc-tool/src/dotty/tools/dottydoc/model/internal.scala b/doc-tool/src/dotty/tools/dottydoc/model/internal.scala index 61c26936a742..ebf9ce057b18 100644 --- a/doc-tool/src/dotty/tools/dottydoc/model/internal.scala +++ b/doc-tool/src/dotty/tools/dottydoc/model/internal.scala @@ -15,7 +15,7 @@ object internal { var path: List[String], var superTypes: List[MaterializableLink] = Nil, var comment: Option[Comment] = None, - var parent: Entity = NonEntity + var parent: Option[Entity] = None ) extends Package object EmptyPackage { @@ -33,7 +33,7 @@ object internal { alias: Option[Reference], typeParams: List[String] = Nil, var comment: Option[Comment] = None, - var parent: Entity = NonEntity + var parent: Option[Entity] = None ) extends TypeAlias final case class ClassImpl( @@ -48,7 +48,7 @@ object internal { superTypes: List[MaterializableLink] = Nil, var comment: Option[Comment] = None, var companionPath: List[String] = Nil, - var parent: Entity = NonEntity + var parent: Option[Entity] = None ) extends Class final case class CaseClassImpl( @@ -63,7 +63,7 @@ object internal { superTypes: List[MaterializableLink] = Nil, var comment: Option[Comment] = None, var companionPath: List[String] = Nil, - var parent: Entity = NonEntity + var parent: Option[Entity] = None ) extends CaseClass final case class TraitImpl( @@ -78,7 +78,7 @@ object internal { superTypes: List[MaterializableLink] = Nil, var comment: Option[Comment] = None, var companionPath: List[String] = Nil, - var parent: Entity = NonEntity + var parent: Option[Entity] = None ) extends Trait final case class ObjectImpl( @@ -91,7 +91,7 @@ object internal { superTypes: List[MaterializableLink] = Nil, var comment: Option[Comment] = None, var companionPath: List[String] = Nil, - var parent: Entity = NonEntity + var parent: Option[Entity] = None ) extends Object { def modifiers: List[String] = mods.filterNot(_ == "final") } @@ -107,7 +107,7 @@ object internal { paramLists: List[ParamList] = Nil, var comment: Option[Comment] = None, implicitlyAddedFrom: Option[Reference] = None, - var parent: Entity = NonEntity + var parent: Option[Entity] = None ) extends Def final case class ValImpl( @@ -120,7 +120,7 @@ object internal { kind: String, var comment: Option[Comment] = None, implicitlyAddedFrom: Option[Reference] = None, - var parent: Entity = NonEntity + var parent: Option[Entity] = None ) extends Val final case class ParamListImpl( diff --git a/doc-tool/src/dotty/tools/dottydoc/model/references.scala b/doc-tool/src/dotty/tools/dottydoc/model/references.scala index 46ae79e6fe58..a5b77024885d 100644 --- a/doc-tool/src/dotty/tools/dottydoc/model/references.scala +++ b/doc-tool/src/dotty/tools/dottydoc/model/references.scala @@ -21,7 +21,7 @@ object references { case target: Package => target.path.mkString("/") + "/index.html" case _: TypeAlias | _: Def | _: Val => - target.parent.path.mkString("/") + ".html#" + target.signature + target.parent.map(_.path.mkString("/")).getOrElse("") + ".html#" + target.signature case _ => target.path.mkString("/") + ".html" }) @@ -61,7 +61,7 @@ object references { s"$title: $byName${ref.showReference}$repeated" case ConstantReference(title) => title - case EmptyReference => + case EmptyReference => assert(false, "unexpected empty reference") "" } diff --git a/doc-tool/src/dotty/tools/dottydoc/staticsite/DefaultParams.scala b/doc-tool/src/dotty/tools/dottydoc/staticsite/DefaultParams.scala index 0fcc6d22591b..9dff4bcfa32a 100644 --- a/doc-tool/src/dotty/tools/dottydoc/staticsite/DefaultParams.scala +++ b/doc-tool/src/dotty/tools/dottydoc/staticsite/DefaultParams.scala @@ -2,9 +2,9 @@ package dotty.tools package dottydoc package staticsite -import model.{ Entity, Package, NonEntity } +import model.{Entity, Package} -import java.util.{ HashMap, List => JList, Map => JMap } +import java.util.{HashMap, List => JList, Map => JMap} import java.time.LocalDateTime import java.time.format.DateTimeFormatter import scala.collection.JavaConverters._ @@ -16,7 +16,7 @@ case class DefaultParams( page: PageInfo, site: SiteInfo, sidebar: Sidebar, - entity: Entity = NonEntity + entity: Option[Entity] = None ) { import model.JavaConverters._ @@ -46,8 +46,8 @@ case class DefaultParams( "sidebar" -> sidebar.toMap ) val entityMap = entity match { - case NonEntity => Map.empty - case _ => Map( + case None => Map.empty + case Some(entity) => Map( "entity" -> entity.asJava ) } @@ -61,7 +61,7 @@ case class DefaultParams( def withUrl(url: String): DefaultParams = copy(page = PageInfo(url)) - def withEntity(e: model.Entity) = copy(entity = e) + def withEntity(e: Option[model.Entity]) = copy(entity = e) def withDate(d: String) = copy(page = PageInfo(page.url, d)) } @@ -88,7 +88,7 @@ case class Sidebar(titles: List[Title]) { object Sidebar { def apply(map: HashMap[String, AnyRef]): Option[Sidebar] = Option(map.get("sidebar")).map { case list: JList[JMap[String, AnyRef]] @unchecked if !list.isEmpty => - new Sidebar(list.asScala.map(Title.apply).flatMap(x => x).toList) + new Sidebar(list.asScala.map(Title.apply).flatten.toList) case _ => Sidebar.empty } @@ -96,12 +96,11 @@ object Sidebar { } case class Title(title: String, url: Option[String], subsection: List[Title], description: Option[String]) { - import model.JavaConverters._ def toMap: JMap[String, _] = Map( "title" -> title, - "url" -> url.getOrElse(null), // ugh, Java + "url" -> url.orNull, // ugh, Java "subsection" -> subsection.map(_.toMap).asJava, - "description" -> description.getOrElse(null) + "description" -> description.orNull ).asJava } @@ -120,7 +119,7 @@ object Title { val subsection = Option(map.get("subsection")).collect { case xs: JList[JMap[String, AnyRef]] @unchecked => - xs.asScala.map(Title.apply).toList.flatMap(x => x) + xs.asScala.map(Title.apply).toList.flatten }.getOrElse(Nil) title.map { diff --git a/doc-tool/src/dotty/tools/dottydoc/staticsite/MarkdownLinkVisitor.scala b/doc-tool/src/dotty/tools/dottydoc/staticsite/MarkdownLinkVisitor.scala index 7f0a7b93d55b..7687065921a1 100644 --- a/doc-tool/src/dotty/tools/dottydoc/staticsite/MarkdownLinkVisitor.scala +++ b/doc-tool/src/dotty/tools/dottydoc/staticsite/MarkdownLinkVisitor.scala @@ -4,7 +4,7 @@ package staticsite import com.vladsch.flexmark.ast._ import com.vladsch.flexmark.util.sequence.{BasedSequence, CharSubSequence} -import model.{Def, NonEntity, Package, TypeAlias, Val} +import model.{Def, Package, TypeAlias, Val} import dottydoc.util.MemberLookup object MarkdownLinkVisitor { @@ -21,7 +21,7 @@ object MarkdownLinkVisitor { url.subSequence(0, url.lastIndexOf('.')).append(".html") } else if (EntityLink.unapplySeq(url.toString).isDefined) { - lookup(NonEntity, docs, url.toString).foreach { ent => + lookup(None, docs, url.toString).foreach { ent => val (path, suffix) = ent match { case ent: Val => (ent.path.dropRight(1), ".html#" + ent.signature) case ent: Def => (ent.path.dropRight(1), ".html#" + ent.signature) diff --git a/doc-tool/src/dotty/tools/dottydoc/staticsite/Site.scala b/doc-tool/src/dotty/tools/dottydoc/staticsite/Site.scala index b9169f138f50..826445ea1636 100644 --- a/doc-tool/src/dotty/tools/dottydoc/staticsite/Site.scala +++ b/doc-tool/src/dotty/tools/dottydoc/staticsite/Site.scala @@ -5,7 +5,7 @@ package staticsite import java.nio.file.{ Files, FileSystems } import java.nio.file.StandardCopyOption.REPLACE_EXISTING import java.io.{ File => JFile, OutputStreamWriter, BufferedWriter, ByteArrayInputStream } -import java.util.{ List => JList, Map => JMap, Arrays } +import java.util.{ List => JList, Arrays } import java.nio.file.Path import java.nio.charset.StandardCharsets @@ -29,11 +29,11 @@ import scala.collection.mutable.ArrayBuffer import util.syntax._ case class Site( - val root: JFile, - val projectTitle: String, - val projectVersion: String, - val projectUrl: String, - val documentation: Map[String, Package] + root: JFile, + projectTitle: String, + projectVersion: String, + projectUrl: String, + documentation: Map[String, Package] ) extends ResourceFinder { /** Documentation serialized to java maps */ private val docs: JList[_] = { @@ -162,7 +162,6 @@ case class Site( /** Generate default params included in each page */ private def defaultParams(pageLocation: JFile, additionalDepth: Int = 0): DefaultParams = { - import scala.collection.JavaConverters._ val pathFromRoot = stripRoot(pageLocation) val baseUrl: String = { val rootLen = root.getAbsolutePath.split('/').length @@ -200,7 +199,7 @@ case class Site( else (".html", 0) val target = mkdirs(fs.getPath(outDir.getAbsolutePath + "/api/" + e.path.mkString("/") + suffix)) - val params = defaultParams(target.toFile, -1).withPosts(blogInfo).withEntity(e).toMap + val params = defaultParams(target.toFile, -1).withPosts(blogInfo).withEntity(Some(e)).toMap val page = new HtmlPage("_layouts/api-page.html", layouts("api-page").content, params, includes) render(page).foreach { rendered => @@ -422,7 +421,6 @@ case class Site( def render(page: Page, params: Map[String, AnyRef] = Map.empty)(implicit ctx: Context): Option[String] = page.yaml.get("layout").flatMap(xs => layouts.get(xs.toString)) match { case Some(layout) if page.html.isDefined => - import scala.collection.JavaConverters._ val newParams = page.params ++ params ++ Map("page" -> page.yaml) ++ Map("content" -> page.html.get) val expandedTemplate = new HtmlPage(layout.path, layout.content, newParams, includes) render(expandedTemplate, params) diff --git a/doc-tool/src/dotty/tools/dottydoc/util/MemberLookup.scala b/doc-tool/src/dotty/tools/dottydoc/util/MemberLookup.scala index 5d2d3230d40d..d478557df27f 100644 --- a/doc-tool/src/dotty/tools/dottydoc/util/MemberLookup.scala +++ b/doc-tool/src/dotty/tools/dottydoc/util/MemberLookup.scala @@ -2,14 +2,6 @@ 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.Names._ -import dotc.util.Positions._ -import model.internal._ import model.comment._ import model._ @@ -19,7 +11,7 @@ trait MemberLookup { * Will return a `Tooltip` if unsuccessful, otherwise a LinkToEntity or * LinkToExternal */ - def lookup(entity: Entity, packages: Map[String, Package], query: String): Option[Entity] = { + def lookup(entity: Option[Entity], packages: Map[String, Package], query: String): Option[Entity] = { val notFound: Option[Entity] = None val querys = query.split("\\.").toList @@ -42,11 +34,10 @@ trait MemberLookup { case x :: xs => ent .members - .collect { + .collectFirst { case e: Entity with Members if e.name == x => e case e: Entity with Members if e.name == x.init && x.last == '$' => e } - .headOption .fold(notFound)(e => downwardLookup(e, xs)) } @@ -69,10 +60,10 @@ trait MemberLookup { } (querys, entity) match { - case (xs, NonEntity) => globalLookup - case (x :: Nil, e: Entity with Members) => + case (xs, None) => globalLookup + case (x :: Nil, Some(e: Entity with Members)) => localLookup(e, x) - case (x :: _, e: Entity with Members) if x == entity.name => + case (x :: _, Some(e: Entity with Members)) if x == e.name => downwardLookup(e, querys) case (x :: xs, _) => if (xs.nonEmpty) globalLookup @@ -89,7 +80,7 @@ trait MemberLookup { query: String ): EntityLink = { val link = - lookup(entity, packages, query) + lookup(Some(entity), packages, query) .map(LinkToEntity) .getOrElse(Tooltip(query)) diff --git a/doc-tool/src/dotty/tools/dottydoc/util/internal/mutate.scala b/doc-tool/src/dotty/tools/dottydoc/util/internal/mutate.scala index 4633bf257881..2f4a255dccf8 100644 --- a/doc-tool/src/dotty/tools/dottydoc/util/internal/mutate.scala +++ b/doc-tool/src/dotty/tools/dottydoc/util/internal/mutate.scala @@ -5,7 +5,6 @@ package internal object setters { import model._ import comment.Comment - import model.references._ import internal._ def setComment(ent: Entity, to: Option[Comment]) = ent match { @@ -21,26 +20,26 @@ object setters { def setParent(ent: Entity, to: Entity): Unit = ent match { case e: PackageImpl => - e.parent = to + e.parent = Some(to) e.members.foreach(setParent(_, e)) case e: ClassImpl => - e.parent = to + e.parent = Some(to) e.members.foreach(setParent(_, e)) case e: CaseClassImpl => - e.parent = to + e.parent = Some(to) e.members.foreach(setParent(_, e)) case e: ObjectImpl => - e.parent = to + e.parent = Some(to) e.members.foreach(setParent(_, e)) case e: TraitImpl => - e.parent = to + e.parent = Some(to) e.members.foreach(setParent(_, e)) case e: ValImpl => - e.parent = to + e.parent = Some(to) case e: DefImpl => - e.parent = to + e.parent = Some(to) case e: TypeAliasImpl => - e.parent = to + e.parent = Some(to) case _ => () } diff --git a/doc-tool/test/ConstructorTest.scala b/doc-tool/test/ConstructorTest.scala index b61c5911df3f..086abd99c9fd 100644 --- a/doc-tool/test/ConstructorTest.scala +++ b/doc-tool/test/ConstructorTest.scala @@ -9,7 +9,10 @@ import model._ import model.internal._ import model.references._ -class Constructors extends DottyDocTest { +class ConstructorsFromSourceTest extends ConstructorsBase with CheckFromSource +class ConstructorsFromTastyTest extends ConstructorsBase with CheckFromTasty + +abstract class ConstructorsBase extends DottyDocTest { @Test def singleClassConstructor = { val source = new SourceFile ( "Class.scala", @@ -20,7 +23,9 @@ class Constructors extends DottyDocTest { """.stripMargin ) - checkSources(source :: Nil) { packages => + val className = "scala.Class" + + check(className :: Nil, source :: Nil) { (ctx, packages) => packages("scala") match { case PackageImpl(_, _, _, List(cls: Class), _, _, _, _) => cls.constructors.headOption match { @@ -42,7 +47,9 @@ class Constructors extends DottyDocTest { """.stripMargin ) - checkSources(source :: Nil) { packages => + val className = "scala.Class" + + check(className :: Nil, source :: Nil) { (ctx, packages) => packages("scala") match { case PackageImpl(_, _, _, List(cls: Class), _, _, _, _) => cls.constructors match { @@ -67,7 +74,9 @@ class Constructors extends DottyDocTest { """.stripMargin ) - checkSources(source :: Nil) { packages => + val className = "scala.Class" + + check(className :: Nil, source :: Nil) { (ctx, packages) => packages("scala") match { case PackageImpl(_, _, _, List(cls: Class), _, _, _, _) => cls.constructors match { @@ -99,7 +108,9 @@ class Constructors extends DottyDocTest { """.stripMargin ) - checkSources(source :: Nil) { packages => + val className = "scala.Class" + + check(className :: Nil, source :: Nil) { (ctx, packages) => packages("scala") match { case PackageImpl(_, _, _, List(cls: Class), _, _, _, _) => cls.constructors match { @@ -137,7 +148,9 @@ class Constructors extends DottyDocTest { """.stripMargin ) - checkSources(source :: Nil) { packages => + val className = "scala.Class" + + check(className :: Nil, source :: Nil) { (ctx, packages) => packages("scala") match { case PackageImpl(_, _, _, List(cls: CaseClass, obj: Object), _, _, _, _) => cls.constructors match { @@ -170,7 +183,9 @@ class Constructors extends DottyDocTest { """.stripMargin ) - checkSources(source :: Nil) { packages => + val className = "scala.Trait" + + check(className :: Nil, source :: Nil) { (ctx, packages) => packages("scala") match { case PackageImpl(_, _, _, List(trt: Trait), _, _, _, _) => trt.traitParams match { diff --git a/doc-tool/test/DottyDocTest.scala b/doc-tool/test/DottyDocTest.scala index c950af38d143..46cb123e35e6 100644 --- a/doc-tool/test/DottyDocTest.scala +++ b/doc-tool/test/DottyDocTest.scala @@ -3,16 +3,20 @@ package dottydoc import vulpix.TestConfiguration +import dotc.Compiler import dotc.core.Contexts.{ Context, ContextBase, FreshContext } import dotc.core.Comments.{ ContextDoc, ContextDocstrings } import dotc.util.SourceFile import dotc.core.Phases.Phase +import dotty.tools.io.AbstractFile import dotc.typer.FrontEnd import dottydoc.core.{ DocASTPhase, ContextDottydoc } import model.Package import dotty.tools.dottydoc.util.syntax._ +import dotty.tools.io.AbstractFile import dotc.reporting.{ StoreReporter, MessageRendering } import dotc.interfaces.Diagnostic.ERROR +import io.Directory import org.junit.Assert.fail import java.io.{ BufferedWriter, OutputStreamWriter } @@ -20,7 +24,7 @@ import java.io.{ BufferedWriter, OutputStreamWriter } trait DottyDocTest extends MessageRendering { dotty.tools.dotc.parsing.Scanners // initialize keywords - implicit val ctx: FreshContext = { + private def freshCtx(extraClasspath: List[String]): FreshContext = { val base = new ContextBase import base.settings._ val ctx = base.initialCtx.fresh @@ -32,19 +36,20 @@ trait DottyDocTest extends MessageRendering { ctx.setProperty(ContextDoc, new ContextDottydoc) ctx.setSetting( ctx.settings.classpath, - TestConfiguration.basicClasspath + (TestConfiguration.basicClasspath :: extraClasspath).mkString(java.io.File.pathSeparator) ) ctx.setReporter(new StoreReporter(ctx.reporter)) base.initialize()(ctx) ctx } + implicit val ctx: FreshContext = freshCtx(Nil) - private def compilerWithChecker(assertion: Map[String, Package] => Unit) = new DocCompiler { + private def compilerWithChecker(assertion: (Context, Map[String, Package]) => Unit) = new DocCompiler { private[this] val assertionPhase: List[List[Phase]] = List(new Phase { def phaseName = "assertionPhase" override def run(implicit ctx: Context): Unit = - assertion(ctx.docbase.packages) + assertion(ctx, ctx.docbase.packages) if (ctx.reporter.hasErrors) { System.err.println("reporter had errors:") ctx.reporter.removeBufferedMessages.foreach { msg => @@ -81,21 +86,62 @@ trait DottyDocTest extends MessageRendering { new SourceFile(virtualFile, scala.io.Codec.UTF8) } - def checkSource(source: String)(assertion: Map[String, Package] => Unit): Unit = { + def checkSource(source: String)(assertion: (Context, Map[String, Package]) => Unit): Unit = { val c = compilerWithChecker(assertion) val run = c.newRun run.compileSources(sourceFileFromString(callingMethod, source) :: Nil) } - def checkFiles(sources: List[String])(assertion: Map[String, Package] => Unit): Unit = { + def checkFiles(sources: List[String])(assertion: (Context, Map[String, Package]) => Unit): Unit = { val c = compilerWithChecker(assertion) val run = c.newRun run.compile(sources) } - def checkSources(sourceFiles: List[SourceFile])(assertion: Map[String, Package] => Unit): Unit = { + def checkFromSource(sourceFiles: List[SourceFile])(assertion: (Context, Map[String, Package]) => Unit): Unit = { val c = compilerWithChecker(assertion) val run = c.newRun run.compileSources(sourceFiles) } + + def checkFromTasty(classNames: List[String], sources: List[SourceFile])(assertion: (Context, Map[String, Package]) => Unit): Unit = { + Directory.inTempDirectory { tmp => + val ctx = "shadow ctx" + val out = tmp./(Directory("out")) + out.createDirectory() + + val dotcCtx = { + val ctx = freshCtx(out.toString :: Nil) + ctx.setSetting(ctx.settings.outputDir, AbstractFile.getDirectory(out)) + } + val dotc = new Compiler + val run = dotc.newRun(dotcCtx) + run.compileSources(sources) + assert(!dotcCtx.reporter.hasErrors) + + val fromTastyCtx = { + val ctx = freshCtx(out.toString :: Nil) + ctx.setSetting(ctx.settings.fromTasty, true) + } + val fromTastyCompiler = compilerWithChecker(assertion) + val fromTastyRun = fromTastyCompiler.newRun(fromTastyCtx) + fromTastyRun.compile(classNames) + assert(!fromTastyCtx.reporter.hasErrors) + } + } + + def check(classNames: List[String], sources: List[SourceFile])(assertion: (Context, Map[String, Package]) => Unit): Unit + +} + +trait CheckFromSource extends DottyDocTest { + override def check(classNames: List[String], sources: List[SourceFile])(assertion: (Context, Map[String, Package]) => Unit): Unit = { + checkFromSource(sources)(assertion) + } +} + +trait CheckFromTasty extends DottyDocTest { + override def check(classNames: List[String], sources: List[SourceFile])(assertion: (Context, Map[String, Package]) => Unit): Unit = { + checkFromTasty(classNames, sources)(assertion) + } } diff --git a/doc-tool/test/JavaConverterTest.scala b/doc-tool/test/JavaConverterTest.scala index 5daf9c3d705e..defced772707 100644 --- a/doc-tool/test/JavaConverterTest.scala +++ b/doc-tool/test/JavaConverterTest.scala @@ -22,14 +22,12 @@ import model.{ Trait, Package, Def, - NonEntity, ParamList } import model.references._ -import model.internal.{ParamListImpl} -import model.comment.Comment +import model.internal.ParamListImpl import dotty.tools.dotc.core.Symbols.NoSymbol -import java.util.{Optional => JOptional, Map => JMap, List => JList} +import java.util.{Map => JMap, List => JList} class JavaConverterTest { import model.JavaConverters._ @@ -43,7 +41,7 @@ class JavaConverterTest { def path = "path" :: "to" :: "def" :: Nil def comment = None def annotations = List("test") - def parent = NonEntity + def parent = None def modifiers = "private" :: Nil def typeParams = "String" :: "String" :: Nil def implicitlyAddedFrom = Some( @@ -58,7 +56,7 @@ class JavaConverterTest { def path = "path" :: "to" :: "trait" :: Nil def comment = None def annotations = List("test") - def parent = NonEntity + def parent = None def modifiers = "protected" :: Nil def typeParams = "String" :: "String" :: Nil def superTypes = new NoLink("title", "query") :: Nil @@ -74,7 +72,7 @@ class JavaConverterTest { def path = "path" :: "to" :: "test" :: Nil def comment = None def annotations = List("test") - def parent = NonEntity + def parent = None def modifiers = "private" :: Nil def typeParams = "String" :: "String" :: Nil def superTypes = new NoLink("title", "query") :: Nil @@ -90,7 +88,7 @@ class JavaConverterTest { def path = "path" :: "to" :: "test" :: Nil def comment = None def annotations = List("test") - def parent = NonEntity + def parent = None def modifiers = "private" :: Nil def typeParams = "String" :: "String" :: Nil def constructors = List(List(paramList)) @@ -106,7 +104,7 @@ class JavaConverterTest { def path = "path" :: "to" :: "object" :: Nil def comment = None def annotations = List("test") - def parent = NonEntity + def parent = None def modifiers = "protected" :: Nil def typeParams = "String" :: "String" :: Nil def superTypes = new NoLink("title", "query") :: Nil @@ -121,7 +119,7 @@ class JavaConverterTest { def path = "path" :: "to" :: "typeAlias" :: Nil def comment = None def annotations = List("test") - def parent = NonEntity + def parent = None def modifiers = "private" :: Nil def typeParams = "String" :: "String" :: Nil def alias = Some(new TypeReference("String", new NoLink("title", "target"), List())) @@ -134,7 +132,7 @@ class JavaConverterTest { def path = "path" :: "to" :: "val" :: Nil def comment = None def annotations = List("test") - def parent = NonEntity + def parent = None def modifiers = "private" :: Nil def returnValue = new TypeReference("String", new NoLink("title", "target"), List()) def implicitlyAddedFrom = Some( @@ -147,7 +145,7 @@ class JavaConverterTest { def path = "path" :: "to" :: "test" :: Nil def comment = None def annotations = List("test") - def parent = NonEntity + def parent = None def members = trt :: typeAlias :: Nil def superTypes = new NoLink("title", "query") :: Nil } diff --git a/doc-tool/test/MarkdownTests.scala b/doc-tool/test/MarkdownTests.scala index 98ed3085a0a7..64255081e1f8 100644 --- a/doc-tool/test/MarkdownTests.scala +++ b/doc-tool/test/MarkdownTests.scala @@ -10,13 +10,14 @@ import dotc.core.Contexts.{ Context, ContextBase, FreshContext } import dotc.core.Comments.{ ContextDoc, ContextDocstrings } import dottydoc.core.ContextDottydoc -class MarkdownTests extends DottyDocTest { +class MarkdownTests extends DottyDocTest with CheckFromSource { override implicit val ctx: FreshContext = { // TODO: check if can reuse parent instead of copy-paste val base = new ContextBase import base.settings._ val ctx = base.initialCtx.fresh ctx.setSetting(ctx.settings.language, List("Scala2")) + ctx.setSetting(ctx.settings.YcookComments, true) ctx.setSetting(ctx.settings.YnoInline, true) ctx.setSetting(ctx.settings.Ycheck, "all" :: Nil) // No wiki syntax! @@ -39,7 +40,7 @@ class MarkdownTests extends DottyDocTest { |trait HelloWorld """.stripMargin - checkSource(source) { packages => + checkSource(source) { (ctx, packages) => val traitCmt = packages("scala") .children.find(_.path.mkString(".") == "scala.HelloWorld") @@ -60,7 +61,7 @@ class MarkdownTests extends DottyDocTest { |trait HelloWorld """.stripMargin - checkSource(source) { packages => + checkSource(source) { (ctx, packages) => val traitCmt = packages("scala") .children.find(_.path.mkString(".") == "scala.HelloWorld") @@ -83,7 +84,7 @@ class MarkdownTests extends DottyDocTest { |trait None """.stripMargin - checkSource(source) { packages => + checkSource(source) { (ctx, packages) => val traitCmt = packages("scala") .children.find(_.path.mkString(".") == "scala.HelloWorld") @@ -106,7 +107,7 @@ class MarkdownTests extends DottyDocTest { |trait None """.stripMargin - checkSource(source) { packages => + checkSource(source) { (ctx, packages) => val traitCmt = packages("scala") .children.find(_.path.mkString(".") == "scala.HelloWorld") @@ -132,7 +133,7 @@ class MarkdownTests extends DottyDocTest { |trait None """.stripMargin - checkSource(source) { packages => + checkSource(source) { (ctx, packages) => val traitCmt = packages("scala") .children.find(_.path.mkString(".") == "scala.HelloWorld") @@ -165,7 +166,7 @@ class MarkdownTests extends DottyDocTest { |trait None """.stripMargin - checkSource(source) { packages => + checkSource(source) { (ctx, packages) => val traitCmt = packages("scala") .children.find(_.path.mkString(".") == "scala.HelloWorld") @@ -201,7 +202,7 @@ class MarkdownTests extends DottyDocTest { |trait None """.stripMargin - checkSource(source) { packages => + checkSource(source) { (ctx, packages) => val traitCmt = packages("scala") .children.find(_.path.mkString(".") == "scala.HelloWorld") @@ -232,7 +233,7 @@ class MarkdownTests extends DottyDocTest { |trait None """.stripMargin - checkSource(source) { packages => + checkSource(source) { (ctx, packages) => val traitCmt = packages("scala") .children.find(_.path.mkString(".") == "scala.HelloWorld") @@ -261,7 +262,7 @@ class MarkdownTests extends DottyDocTest { |trait HelloWorld """.stripMargin - checkSource(source) { packages => + checkSource(source) { (ctx, packages) => val traitCmt = packages("scala") .children.find(_.path.mkString(".") == "scala.HelloWorld") @@ -291,7 +292,7 @@ class MarkdownTests extends DottyDocTest { |trait HelloWorld """.stripMargin - checkSource(source) { packages => + checkSource(source) { (ctx, packages) => val traitCmt = packages("scala") .children.find(_.path.mkString(".") == "scala.HelloWorld") diff --git a/doc-tool/test/PackageStructure.scala b/doc-tool/test/PackageStructure.scala index e53de5de71e6..2ef1352f2d07 100644 --- a/doc-tool/test/PackageStructure.scala +++ b/doc-tool/test/PackageStructure.scala @@ -3,11 +3,35 @@ package dottydoc import org.junit.Test import org.junit.Assert._ - import dotc.util.SourceFile +import model.Trait import model.internal._ -class PackageStructure extends DottyDocTest { +class PackageStructureFromSourceTest extends PackageStructureBase with CheckFromSource +class PackageStructureFromTastyTest extends PackageStructureBase with CheckFromTasty + +abstract class PackageStructureBase extends DottyDocTest { + + @Test def sourceFileAnnotIsStripped = { + val source = new SourceFile( + "A.scala", + """package scala + | + |/** Some doc */ + |trait A + """.stripMargin + ) + + val className = "scala.A" + + check(className :: Nil, source :: Nil) { (ctx, packages) => + packages("scala") match { + case PackageImpl(_, _, _, List(trt: Trait), _, _, _, _) => + assert(trt.annotations.isEmpty) + } + } + } + @Test def multipleCompilationUnits = { val source1 = new SourceFile( "TraitA.scala", @@ -27,7 +51,9 @@ class PackageStructure extends DottyDocTest { """.stripMargin ) - checkSources(source1 :: source2 :: Nil) { packages => + val classNames = "scala.A" :: "scala.B" :: Nil + + check(classNames, source1 :: source2 :: Nil) { (ctx, packages) => packages("scala") match { case PackageImpl(_, _, _, List(tA, tB), _, _, _, _) => assert( @@ -59,7 +85,9 @@ class PackageStructure extends DottyDocTest { |trait B """.stripMargin) - checkSources(source1 :: source2 :: Nil) { packages => + val classNames = "scala.collection.A" :: "scala.collection.B" :: Nil + + check(classNames, source1 :: source2 :: Nil) { (ctx, packages) => packages("scala.collection") match { case PackageImpl(_, _, "scala.collection", List(tA, tB), _, _, _, _) => assert( diff --git a/doc-tool/test/SimpleComments.scala b/doc-tool/test/SimpleComments.scala index ac04fe645d02..f9d8ae043ba9 100644 --- a/doc-tool/test/SimpleComments.scala +++ b/doc-tool/test/SimpleComments.scala @@ -2,11 +2,15 @@ package dotty.tools package dottydoc import model.internal._ +import dotc.util.SourceFile import org.junit.Test import org.junit.Assert._ -class TestSimpleComments extends DottyDocTest { +class SimpleCommentsFromSourceTest extends SimpleCommentsBase with CheckFromSource +class SimpleCommentsFromTastyTest extends SimpleCommentsBase with CheckFromTasty + +abstract class SimpleCommentsBase extends DottyDocTest { @Test def cookCommentEmptyClass = { val source = @@ -20,7 +24,7 @@ class TestSimpleComments extends DottyDocTest { | */ |trait Test""".stripMargin - checkSource(source) { packages => + checkSource(source) { (_, packages) => packages("scala") match { case PackageImpl(_, _, _, List(trt), _, _, _, _) => assert(trt.comment.isDefined, "Lost comment in transformations") @@ -31,15 +35,19 @@ class TestSimpleComments extends DottyDocTest { } @Test def simpleComment = { - val source = + val source = new SourceFile( + "HelloWorld.scala", """ |package scala | |/** Hello, world! */ |trait HelloWorld """.stripMargin + ) + + val className = "scala.HelloWorld" - checkSource(source) { packages => + check(className :: Nil, source :: Nil) { (ctx, packages) => val traitCmt = packages("scala") .children.find(_.path.mkString(".") == "scala.HelloWorld") @@ -57,7 +65,7 @@ class TestSimpleComments extends DottyDocTest { |package object foobar { class A } """.stripMargin - checkSource(source) { packages => + checkSource(source) { (_, packages) => val packageCmt = packages("foobar").comment.get.body assertEquals("

Hello, world!

", packageCmt) } diff --git a/doc-tool/test/TemplateErrorTests.scala b/doc-tool/test/TemplateErrorTests.scala index 3359c7791fc8..42518cae99ad 100644 --- a/doc-tool/test/TemplateErrorTests.scala +++ b/doc-tool/test/TemplateErrorTests.scala @@ -5,7 +5,7 @@ package staticsite import org.junit.Test import org.junit.Assert._ -class TemplateErrorTests extends DottyDocTest with SourceFileOps { +class TemplateErrorTests extends DottyDocTest with SourceFileOps with CheckFromSource { @Test def unclosedTag: Unit = { htmlPage( """|Yo dawg: diff --git a/doc-tool/test/UsecaseTest.scala b/doc-tool/test/UsecaseTest.scala index f0f38d09f79d..f3772a882782 100644 --- a/doc-tool/test/UsecaseTest.scala +++ b/doc-tool/test/UsecaseTest.scala @@ -10,7 +10,10 @@ import model.internal._ import model.references._ import util.syntax._ -class UsecaseTest extends DottyDocTest { +class UsecaseFromSourceTest extends UsecaseBase with CheckFromSource +class UsecaseFromTastyTest extends UsecaseBase with CheckFromTasty + +abstract class UsecaseBase extends DottyDocTest { @Test def simpleUsecase = { val source = new SourceFile( "DefWithUseCase.scala", @@ -27,7 +30,9 @@ class UsecaseTest extends DottyDocTest { """.stripMargin ) - checkSources(source :: Nil) { packages => + val className = "scala.Test" + + check(className :: Nil, source :: Nil) { (ctx, packages) => packages("scala") match { case PackageImpl(_, _, _, List(trt: Trait), _, _, _, _) => val List(foo: Def) = trt.members @@ -71,7 +76,9 @@ class UsecaseTest extends DottyDocTest { """.stripMargin ) - checkSources(source :: Nil) { packages => + val className = "scala.Test" + + check(className :: Nil, source :: Nil) { (ctx, packages) => packages("scala") match { case PackageImpl(_, _, _, List(trt: Trait), _, _, _, _) => val List(foo: Def) = trt.members @@ -116,7 +123,9 @@ class UsecaseTest extends DottyDocTest { """.stripMargin ) - checkSources(source :: Nil) { packages => + val className = "scala.Test" + + check(className :: Nil, source :: Nil) { (ctx, packages) => packages("scala") match { case PackageImpl(_, _, _, List(trt: Trait), _, _, _, _) => val List(foo: Def) = trt.members @@ -164,7 +173,9 @@ class UsecaseTest extends DottyDocTest { """.stripMargin ) - checkSources(source :: Nil) { packages => + val className = "scala.Iterable" + + check(className :: Nil, source :: Nil) { (ctx, packages) => packages("scala") match { case PackageImpl(_, _, _, List(trt: Trait), _, _, _, _) => val List(map: Def) = trt.members @@ -207,13 +218,15 @@ class UsecaseTest extends DottyDocTest { """.stripMargin ) - checkSources(source :: Nil) { packages => + val className = "scala.Iterable" + + check(className :: Nil, source :: Nil) { (ctx, 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 + val docstr = ctx.docbase.docstring(map.symbol).get.expandedBody.get assert( !docstr.contains("@usecase"), s"Comment should not contain usecase after stripping, but was:\n$docstr" @@ -222,13 +235,41 @@ class UsecaseTest extends DottyDocTest { } } + @Test def multipleUseCases: Unit = { + val source = new SourceFile( + name = "MultipleUseCases.scala", + """ + |package scala + | + |trait Test { + | /** A first method + | * @usecase def foo(x: Int): Int + | * @usecase def foo(x: Double): Double + | */ + | def foo(x: String): Unit + |} + """.stripMargin + ) + + val className = "scala.Test" + + check(className :: Nil, source :: Nil) { (ctx, packages) => + packages("scala") match { + case PackageImpl(_, _, _, List(trt: Trait), _, _, _, _) => + val List(foo0: Def, foo1: Def) = trt.members + assertEquals(TypeReference("Int", NoLink("Int", "scala.Int"), Nil), foo0.returnValue) + assertEquals(TypeReference("Double", NoLink("Double", "scala.Double"), Nil), foo1.returnValue) + } + } + } + @Test def checkIterator = - checkFiles("../scala2-library/src/library/scala/collection/Iterator.scala" :: Nil) { _ => + checkFiles("../scala2-library/src/library/scala/collection/Iterator.scala" :: Nil) { case _ => // success if typer throws no errors! :) } @Test def checkIterableLike = - checkFiles("../scala2-library/src/library/scala/collection/IterableLike.scala" :: Nil) { _ => + checkFiles("../scala2-library/src/library/scala/collection/IterableLike.scala" :: Nil) { case _ => // success if typer throws no errors! :) } } diff --git a/doc-tool/test/WhitelistedStdLib.scala b/doc-tool/test/WhitelistedStdLib.scala index 36a967997215..dc94b404c94e 100644 --- a/doc-tool/test/WhitelistedStdLib.scala +++ b/doc-tool/test/WhitelistedStdLib.scala @@ -4,10 +4,10 @@ package dottydoc import org.junit.Test import org.junit.Assert._ -class TestWhitelistedCollections extends DottyDocTest { +class TestWhitelistedCollections extends DottyDocTest with CheckFromSource { @Test def arrayAndImmutableHasDocumentation = - checkFiles(TestWhitelistedCollections.files) { packages => + checkFiles(TestWhitelistedCollections.files) { (ctx, packages) => val array = packages("scala") .children.find(_.path.mkString(".") == "scala.Array") diff --git a/doc-tool/test/dotty/tools/dottydoc/staticsite/PageTests.scala b/doc-tool/test/dotty/tools/dottydoc/staticsite/PageTests.scala index 7febe7fe515b..7ef28ba7ad7c 100644 --- a/doc-tool/test/dotty/tools/dottydoc/staticsite/PageTests.scala +++ b/doc-tool/test/dotty/tools/dottydoc/staticsite/PageTests.scala @@ -5,7 +5,7 @@ package staticsite import org.junit.Test import org.junit.Assert._ -class PageTests extends DottyDocTest with SourceFileOps { +class PageTests extends DottyDocTest with SourceFileOps with CheckFromSource { import scala.collection.JavaConverters._ @Test def mdHas1Key = { diff --git a/doc-tool/test/dotty/tools/dottydoc/staticsite/SiteTests.scala b/doc-tool/test/dotty/tools/dottydoc/staticsite/SiteTests.scala index 7c08c98ccfbe..168ba57bd2c6 100644 --- a/doc-tool/test/dotty/tools/dottydoc/staticsite/SiteTests.scala +++ b/doc-tool/test/dotty/tools/dottydoc/staticsite/SiteTests.scala @@ -5,7 +5,7 @@ package staticsite import org.junit.Test import org.junit.Assert._ -class SiteTests extends DottyDocTest with SourceFileOps { +class SiteTests extends DottyDocTest with SourceFileOps with CheckFromSource { @Test def hasCorrectLayoutFiles = { assert(site.root.exists && site.root.isDirectory, s"'${site.root.getName}' is not a directory") diff --git a/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala b/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala index 2050e2a1c3a9..e1a9f41d6e58 100644 --- a/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala +++ b/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala @@ -22,6 +22,7 @@ import core._, core.Decorators.{sourcePos => _, _} import Comments._, Contexts._, Flags._, Names._, NameOps._, Symbols._, SymDenotations._, Trees._, Types._ import classpath.ClassPathEntries import reporting._, reporting.diagnostic.MessageContainer +import typer.Typer import util._ import interactive._, interactive.InteractiveDriver._ import Interactive.Include @@ -490,7 +491,7 @@ object DottyLanguageServer { val markup = new lsp4j.MarkupContent markup.setKind("markdown") markup.setValue(( - comment.map(_.raw) match { + comment.flatMap(_.expandedBody) match { case Some(comment) => s"""```scala |$typeInfo diff --git a/language-server/test/dotty/tools/languageserver/HoverTest.scala b/language-server/test/dotty/tools/languageserver/HoverTest.scala index 8c018d6a5364..b15e05aa94d7 100644 --- a/language-server/test/dotty/tools/languageserver/HoverTest.scala +++ b/language-server/test/dotty/tools/languageserver/HoverTest.scala @@ -89,4 +89,16 @@ class HoverTest { .hover(m6 to m7, hoverContent("Int")) } + @Test def documentationIsCooked: Unit = { + code"""/** A class: $$Variable + | * @define Variable Test + | */ + |class ${m1}Foo${m2} + |/** $$Variable */ + |class ${m3}Bar${m4} extends Foo + """.withSource + .hover(m1 to m2, hoverContent("Foo", "/** A class: Test\n * */")) + .hover(m3 to m4, hoverContent("Bar", "/** Test */")) + } + }