From 28023b440de7481a10548d4153fc4e843129b274 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Thu, 28 Jun 2018 09:40:40 +0200 Subject: [PATCH 01/16] Support for `-from-tasty` in Dottydoc Dottydoc can now be used in one of two ways: - By receiving a list of source files that will be parsed, typed, and for which the documentation will be generated - When `-from-tasty` is set, by receiving a list of fully qualified class names. The trees will be unpickled and the documentation will be generated. --- .../fromtasty/ReadTastyTreesFromClasses.scala | 2 +- .../src/dotty/tools/dottydoc/DocCompiler.scala | 15 ++++++++++++++- .../src/dotty/tools/dottydoc/DocFrontEnd.scala | 11 +++++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/fromtasty/ReadTastyTreesFromClasses.scala b/compiler/src/dotty/tools/dotc/fromtasty/ReadTastyTreesFromClasses.scala index 0153ba932044..f49c742dedca 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 } diff --git a/doc-tool/src/dotty/tools/dottydoc/DocCompiler.scala b/doc-tool/src/dotty/tools/dottydoc/DocCompiler.scala index b1c15740c9f2..7a2374dae41c 100644 --- a/doc-tool/src/dotty/tools/dottydoc/DocCompiler.scala +++ b/doc-tool/src/dotty/tools/dottydoc/DocCompiler.scala @@ -3,8 +3,12 @@ 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} /** Custom Compiler with phases for the documentation tool * @@ -20,6 +24,15 @@ 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 diff --git a/doc-tool/src/dotty/tools/dottydoc/DocFrontEnd.scala b/doc-tool/src/dotty/tools/dottydoc/DocFrontEnd.scala index 606fd0fc6f55..82ee81fabc4f 100644 --- a/doc-tool/src/dotty/tools/dottydoc/DocFrontEnd.scala +++ b/doc-tool/src/dotty/tools/dottydoc/DocFrontEnd.scala @@ -1,6 +1,7 @@ package dotty.tools package dottydoc +import dotc.fromtasty.ReadTastyTreesFromClasses import dotc.typer.FrontEnd import dotc.core.Contexts.Context import dotc.CompilationUnit @@ -12,6 +13,16 @@ import dotc.CompilationUnit * `discardAfterTyper`. */ class DocFrontEnd extends FrontEnd { + + override def runOn(units: List[CompilationUnit])(implicit ctx: Context): List[CompilationUnit] = { + if (ctx.settings.fromTasty.value) { + val fromTastyFrontend = new ReadTastyTreesFromClasses + fromTastyFrontend.runOn(units) + } else { + super.runOn(units) + } + } + override protected def discardAfterTyper(unit: CompilationUnit)(implicit ctx: Context) = unit.isJava } From 0c555cc9bf03ef5527528eda144fea94532042d6 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Thu, 28 Jun 2018 16:45:33 +0200 Subject: [PATCH 02/16] Add tests for Dottydoc from tasty --- compiler/test/dotty/tools/IOUtils.scala | 27 ++++++++++ .../dotc/core/tasty/CommentPicklingTest.scala | 31 ++--------- doc-tool/test/ConstructorTest.scala | 29 ++++++++--- doc-tool/test/DottyDocTest.scala | 51 +++++++++++++++++-- doc-tool/test/MarkdownTests.scala | 2 +- doc-tool/test/PackageStructure.scala | 13 +++-- doc-tool/test/SimpleComments.scala | 14 +++-- doc-tool/test/TemplateErrorTests.scala | 2 +- doc-tool/test/UsecaseTest.scala | 27 +++++++--- doc-tool/test/WhitelistedStdLib.scala | 2 +- .../tools/dottydoc/staticsite/PageTests.scala | 2 +- .../tools/dottydoc/staticsite/SiteTests.scala | 2 +- 12 files changed, 149 insertions(+), 53 deletions(-) create mode 100644 compiler/test/dotty/tools/IOUtils.scala diff --git a/compiler/test/dotty/tools/IOUtils.scala b/compiler/test/dotty/tools/IOUtils.scala new file mode 100644 index 000000000000..5af18d96a818 --- /dev/null +++ b/compiler/test/dotty/tools/IOUtils.scala @@ -0,0 +1,27 @@ +package dotty.tools + +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 + +object IOUtils { + + 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(_)) + } + } + + 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/dotc/core/tasty/CommentPicklingTest.scala b/compiler/test/dotty/tools/dotc/core/tasty/CommentPicklingTest.scala index 8b2a7be3daed..88258938a2d2 100644 --- a/compiler/test/dotty/tools/dotc/core/tasty/CommentPicklingTest.scala +++ b/compiler/test/dotty/tools/dotc/core/tasty/CommentPicklingTest.scala @@ -10,16 +10,11 @@ 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.IOUtils -import dotty.tools.vulpix.{TestConfiguration, ParallelTesting} +import dotty.tools.vulpix.TestConfiguration -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 java.nio.file.{Files, Path} import org.junit.Test import org.junit.Assert.{assertEquals, assertFalse, fail} @@ -86,7 +81,7 @@ class CommentPicklingTest { } private def compileAndUnpickle[T](sources: List[String])(fn: (List[tpd.Tree], Context) => T) = { - inTempDirectory { tmp => + IOUtils.inTempDirectory { tmp => val sourceFiles = sources.zipWithIndex.map { case (src, id) => val path = tmp.resolve(s"Src$id.scala").toAbsolutePath @@ -102,7 +97,7 @@ class CommentPicklingTest { Main.process(options.all, reporter) assertFalse("Compilation failed.", reporter.hasErrors) - val tastyFiles = getAll(tmp, "glob:**.tasty") + val tastyFiles = IOUtils.getAll(tmp, "glob:**.tasty") val unpicklingOptions = unpickleOptions .withClasspath(out.toAbsolutePath.toString) .and("dummy") // Need to pass a dummy source file name @@ -126,20 +121,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/doc-tool/test/ConstructorTest.scala b/doc-tool/test/ConstructorTest.scala index b61c5911df3f..9818d6f099bd 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) { 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) { 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) { 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) { 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) { 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) { 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..5aa201627138 100644 --- a/doc-tool/test/DottyDocTest.scala +++ b/doc-tool/test/DottyDocTest.scala @@ -3,10 +3,12 @@ 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 @@ -16,11 +18,12 @@ import dotc.interfaces.Diagnostic.ERROR import org.junit.Assert.fail import java.io.{ BufferedWriter, OutputStreamWriter } +import java.nio.file.Files 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,12 +35,13 @@ 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[this] val assertionPhase: List[List[Phase]] = @@ -93,9 +97,50 @@ trait DottyDocTest extends MessageRendering { run.compile(sources) } - def checkSources(sourceFiles: List[SourceFile])(assertion: Map[String, Package] => Unit): Unit = { + def checkFromSource(sourceFiles: List[SourceFile])(assertion: 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: Map[String, Package] => Unit): Unit = { + IOUtils.inTempDirectory { tmp => + val ctx = "shadow ctx" + val out = tmp.resolve("out") + Files.createDirectories(out) + + 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: Map[String, Package] => Unit): Unit + +} + +trait CheckFromSource extends DottyDocTest { + override def check(classNames: List[String], sources: List[SourceFile])(assertion: Map[String, Package] => Unit): Unit = { + checkFromSource(sources)(assertion) + } +} + +trait CheckFromTasty extends DottyDocTest { + override def check(classNames: List[String], sources: List[SourceFile])(assertion: Map[String, Package] => Unit): Unit = { + checkFromTasty(classNames, sources)(assertion) + } } diff --git a/doc-tool/test/MarkdownTests.scala b/doc-tool/test/MarkdownTests.scala index 98ed3085a0a7..15adf1abacb8 100644 --- a/doc-tool/test/MarkdownTests.scala +++ b/doc-tool/test/MarkdownTests.scala @@ -10,7 +10,7 @@ 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 diff --git a/doc-tool/test/PackageStructure.scala b/doc-tool/test/PackageStructure.scala index e53de5de71e6..2e59c29150e1 100644 --- a/doc-tool/test/PackageStructure.scala +++ b/doc-tool/test/PackageStructure.scala @@ -7,7 +7,10 @@ import org.junit.Assert._ import dotc.util.SourceFile 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 multipleCompilationUnits = { val source1 = new SourceFile( "TraitA.scala", @@ -27,7 +30,9 @@ class PackageStructure extends DottyDocTest { """.stripMargin ) - checkSources(source1 :: source2 :: Nil) { packages => + val classNames = "scala.A" :: "scala.B" :: Nil + + check(classNames, source1 :: source2 :: Nil) { packages => packages("scala") match { case PackageImpl(_, _, _, List(tA, tB), _, _, _, _) => assert( @@ -59,7 +64,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) { 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..42dc98cc8281 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 = @@ -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 + ) - checkSource(source) { packages => + val className = "scala.HelloWorld" + + check(className :: Nil, source :: Nil) { packages => val traitCmt = packages("scala") .children.find(_.path.mkString(".") == "scala.HelloWorld") 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..2e667a49ec89 100644 --- a/doc-tool/test/UsecaseTest.scala +++ b/doc-tool/test/UsecaseTest.scala @@ -10,7 +10,12 @@ import model.internal._ import model.references._ import util.syntax._ -class UsecaseTest extends DottyDocTest { +class UsecaseFromSourceTest extends UsecaseBase with CheckFromSource + +// Use case from TASTY do not work at the moment +// class UsecaseFromTastyTest extends UsecaseBase with CheckFromTasty + +abstract class UsecaseBase extends DottyDocTest { @Test def simpleUsecase = { val source = new SourceFile( "DefWithUseCase.scala", @@ -27,7 +32,9 @@ class UsecaseTest extends DottyDocTest { """.stripMargin ) - checkSources(source :: Nil) { packages => + val className = "scala.Test" + + check(className :: Nil, source :: Nil) { packages => packages("scala") match { case PackageImpl(_, _, _, List(trt: Trait), _, _, _, _) => val List(foo: Def) = trt.members @@ -71,7 +78,9 @@ class UsecaseTest extends DottyDocTest { """.stripMargin ) - checkSources(source :: Nil) { packages => + val className = "scala.Test" + + check(className :: Nil, source :: Nil) { packages => packages("scala") match { case PackageImpl(_, _, _, List(trt: Trait), _, _, _, _) => val List(foo: Def) = trt.members @@ -116,7 +125,9 @@ class UsecaseTest extends DottyDocTest { """.stripMargin ) - checkSources(source :: Nil) { packages => + val className = "scala.Test" + + check(className :: Nil, source :: Nil) { packages => packages("scala") match { case PackageImpl(_, _, _, List(trt: Trait), _, _, _, _) => val List(foo: Def) = trt.members @@ -164,7 +175,9 @@ class UsecaseTest extends DottyDocTest { """.stripMargin ) - checkSources(source :: Nil) { packages => + val className = "scala.Iterable" + + check(className :: Nil, source :: Nil) { packages => packages("scala") match { case PackageImpl(_, _, _, List(trt: Trait), _, _, _, _) => val List(map: Def) = trt.members @@ -207,7 +220,9 @@ class UsecaseTest extends DottyDocTest { """.stripMargin ) - checkSources(source :: Nil) { packages => + val className = "scala.Iterable" + + check(className :: Nil, source :: Nil) { packages => packages("scala") match { case PackageImpl(_, _, _, List(trt: Trait), _, _, _, _) => val List(map: Def) = trt.members diff --git a/doc-tool/test/WhitelistedStdLib.scala b/doc-tool/test/WhitelistedStdLib.scala index 36a967997215..4aecc65eef2b 100644 --- a/doc-tool/test/WhitelistedStdLib.scala +++ b/doc-tool/test/WhitelistedStdLib.scala @@ -4,7 +4,7 @@ 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 => 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") From 79ffffdd53e7ecfff7606f7685b62d61ff0f2f7d Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Thu, 28 Jun 2018 17:12:43 +0200 Subject: [PATCH 03/16] Remove unused `comment` property of `UseCase`s --- compiler/src/dotty/tools/dotc/core/Comments.scala | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Comments.scala b/compiler/src/dotty/tools/dotc/core/Comments.scala index 0d38791efacc..33fe2a8addfa 100644 --- a/compiler/src/dotty/tools/dotc/core/Comments.scala +++ b/compiler/src/dotty/tools/dotc/core/Comments.scala @@ -90,11 +90,8 @@ object Comments { 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) - UseCase(Comment(commentPos, commentStr), code, codePos) + UseCase(code, codePos) } } @@ -106,7 +103,7 @@ object Comments { } } - abstract case class UseCase(comment: Comment, code: String, codePos: Position) { + abstract case class UseCase(code: String, codePos: Position) { /** Set by typer */ var tpdCode: tpd.DefDef = _ @@ -114,8 +111,8 @@ object Comments { } object UseCase { - def apply(comment: Comment, code: String, codePos: Position)(implicit ctx: Context) = - new UseCase(comment, code, codePos) { + def apply(code: String, codePos: Position)(implicit ctx: Context) = + new UseCase(code, codePos) { val untpdCode = { val tree = new Parser(new SourceFile("", code)).localDef(codePos.start) From a02f8ad2b9e310a6a7c641e66e19dc407892f37a Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Thu, 28 Jun 2018 17:41:31 +0200 Subject: [PATCH 04/16] Small refactorings in `Comments` --- .../src/dotty/tools/dotc/core/Comments.scala | 25 ++++++++++++------- .../tools/dottydoc/core/DocstringPhase.scala | 3 ++- doc-tool/test/UsecaseTest.scala | 2 +- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Comments.scala b/compiler/src/dotty/tools/dotc/core/Comments.scala index 33fe2a8addfa..7c50f77a7c90 100644 --- a/compiler/src/dotty/tools/dotc/core/Comments.scala +++ b/compiler/src/dotty/tools/dotc/core/Comments.scala @@ -42,8 +42,17 @@ object Comments { * `@usecase` sections as well as functionality to map the `raw` docstring */ abstract case class Comment(pos: Position, raw: String) { self => + /** Has this comment been cooked or expanded? */ def isExpanded: Boolean + /** The body of this comment, without the `@usecase` and `@define` sections. */ + lazy val body: String = + removeSections(raw, "@usecase", "@define") + + /** + * The `@usecase` sections of this comment. + * This is populated by calling `withUsecases` on this object. + */ def usecases: List[UseCase] val isDocComment = raw.startsWith("/**") @@ -53,21 +62,19 @@ object Comments { val usecases = self.usecases } - def withUsecases(implicit ctx: Context): Comment = new Comment(pos, stripUsecases) { + def withUsecases(implicit ctx: Context): Comment = new Comment(pos, raw) { val isExpanded = self.isExpanded val usecases = parseUsecases } - private[this] lazy val stripUsecases: String = - removeSections(raw, "@usecase", "@define") - private[this] def parseUsecases(implicit ctx: Context): List[UseCase] = - if (!raw.startsWith("/**")) - List.empty[UseCase] - else + if (!isDocComment) { + Nil + } else { tagIndex(raw) - .filter { startsWithTag(raw, _, "@usecase") } - .map { case (start, end) => decomposeUseCase(start, end) } + .filter { startsWithTag(raw, _, "@usecase") } + .map { case (start, end) => decomposeUseCase(start, end) } + } /** Turns a usecase section into a UseCase, with code changed to: * {{{ diff --git a/doc-tool/src/dotty/tools/dottydoc/core/DocstringPhase.scala b/doc-tool/src/dotty/tools/dottydoc/core/DocstringPhase.scala index 2471e9220c05..8682f99405a3 100644 --- a/doc-tool/src/dotty/tools/dottydoc/core/DocstringPhase.scala +++ b/doc-tool/src/dotty/tools/dottydoc/core/DocstringPhase.scala @@ -29,7 +29,8 @@ class DocstringPhase extends DocMiniPhase with CommentParser with CommentCleaner 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) + val text = cmt.body + val parsed = parse(ent, ctx.docbase.packages, clean(text), text, cmt.pos) if (ctx.settings.wikiSyntax.value) WikiComment(ent, parsed, cmt.pos).comment diff --git a/doc-tool/test/UsecaseTest.scala b/doc-tool/test/UsecaseTest.scala index 2e667a49ec89..5e0c7bc6bfdf 100644 --- a/doc-tool/test/UsecaseTest.scala +++ b/doc-tool/test/UsecaseTest.scala @@ -228,7 +228,7 @@ abstract class UsecaseBase extends DottyDocTest { 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.body assert( !docstr.contains("@usecase"), s"Comment should not contain usecase after stripping, but was:\n$docstr" From 12e4605791dccf6578823ab2dcb148f39309491b Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Sat, 30 Jun 2018 11:08:15 +0200 Subject: [PATCH 05/16] Pickle comments positions We're not reusing the PositionPickler for this task, because it is very tied to pickling the positions of `Tree`s, which `Comment`s are not. We simply pickle the coordinates of the comment, and reconstruct the position from the coordinates when unpickling. --- .../src/dotty/tools/dotc/core/tasty/CommentPickler.scala | 1 + .../src/dotty/tools/dotc/core/tasty/CommentUnpickler.scala | 5 +++-- compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/CommentPickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/CommentPickler.scala index b32efeb7b4f2..807a10a68cea 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/CommentPickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/CommentPickler.scala @@ -23,6 +23,7 @@ class CommentPickler(pickler: TastyPickler, addrOfTree: tpd.Tree => Option[Addr] buf.writeAddr(addr) buf.writeNat(length) buf.writeBytes(bytes, length) + buf.writeLongInt(cmt.pos.coords) buf.writeByte(if (cmt.isExpanded) 1 else 0) 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..9fd896bc5748 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,10 @@ class CommentUnpickler(reader: TastyReader) { val length = readNat() if (length > 0) { val bytes = readBytes(length) + val position = new Position(readLongInt()) val expanded = readByte() == 1 val rawComment = new String(bytes, Charset.forName("UTF-8")) - comments(addr) = Comment(Positions.NoPosition, rawComment, expanded = expanded) + comments(addr) = Comment(position, rawComment, expanded = expanded) } } 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..25c95236b2b4 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala @@ -235,8 +235,8 @@ 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 Byte // Raw comment's bytes encoded as UTF-8, followed by the comment's coordinates, + // plus a byte indicating whether the comment is expanded (1) or not expanded (0) **************************************************************************************/ From a48ee6d4ac15ba1f13e7b9ed23e8af27498fbb7e Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Mon, 2 Jul 2018 10:43:30 +0200 Subject: [PATCH 06/16] Always pickle uncooked comments The comments are now always pickled as raw comments, as seen in the source file. It's the job of Dotty doc to trigger expansion of the comments after they are unpickled. This approach simplifies incremental generation of the documentation, as we don't need to teach Zinc how to extract dependencies in the documentation. --- .../src/dotty/tools/dotc/core/Comments.scala | 44 ++++++++++++------- .../dotc/core/tasty/CommentPickler.scala | 1 - .../dotc/core/tasty/CommentUnpickler.scala | 3 +- .../tools/dotc/core/tasty/TastyFormat.scala | 3 +- .../dotty/tools/dotc/typer/Docstrings.scala | 44 ++++++++++--------- .../src/dotty/tools/dotc/typer/Typer.scala | 6 ++- .../dotty/tools/dottydoc/DocFrontEnd.scala | 20 ++++++++- .../tools/dottydoc/core/DocstringPhase.scala | 19 ++++---- .../tools/dottydoc/core/UsecasePhase.scala | 8 +++- doc-tool/test/ConstructorTest.scala | 12 ++--- doc-tool/test/DottyDocTest.scala | 19 ++++---- doc-tool/test/MarkdownTests.scala | 21 ++++----- doc-tool/test/PackageStructure.scala | 4 +- doc-tool/test/SimpleComments.scala | 6 +-- doc-tool/test/UsecaseTest.scala | 20 ++++----- doc-tool/test/WhitelistedStdLib.scala | 2 +- 16 files changed, 135 insertions(+), 97 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Comments.scala b/compiler/src/dotty/tools/dotc/core/Comments.scala index 7c50f77a7c90..0d541b41d888 100644 --- a/compiler/src/dotty/tools/dotc/core/Comments.scala +++ b/compiler/src/dotty/tools/dotc/core/Comments.scala @@ -40,14 +40,21 @@ object Comments { * * The `Comment` contains functionality to create versions of itself without * `@usecase` sections as well as functionality to map the `raw` docstring + * + * @param pos The position of this `Comment`. + * @param raw The raw comment, as seen in the source code, without any expansion. */ abstract case class Comment(pos: Position, raw: String) { self => + + /** The expansion of this comment */ + def expanded: Option[String] + /** Has this comment been cooked or expanded? */ - def isExpanded: Boolean + final def isExpanded: Boolean = expanded.isDefined - /** The body of this comment, without the `@usecase` and `@define` sections. */ - lazy val body: String = - removeSections(raw, "@usecase", "@define") + /** The body of this comment, without the `@usecase` and `@define` sections, after expansion. */ + lazy val expandedBody: Option[String] = + expanded.map(removeSections(_, "@usecase", "@define")) /** * The `@usecase` sections of this comment. @@ -57,13 +64,14 @@ object Comments { val isDocComment = raw.startsWith("/**") - def expand(f: String => String): Comment = new Comment(pos, f(raw)) { - val isExpanded = true + def expand(f: String => String): Comment = new Comment(pos, raw) { + val expanded = Some(f(raw)) val usecases = self.usecases } def withUsecases(implicit ctx: Context): Comment = new Comment(pos, raw) { - val isExpanded = self.isExpanded + assert(self.isExpanded) + val expanded = self.expanded val usecases = parseUsecases } @@ -71,9 +79,11 @@ object Comments { if (!isDocComment) { Nil } else { - tagIndex(raw) - .filter { startsWithTag(raw, _, "@usecase") } - .map { case (start, end) => decomposeUseCase(start, end) } + expanded.map { body => + tagIndex(body) + .filter { startsWithTag(body, _, "@usecase") } + .map { case (start, end) => decomposeUseCase(body, start, end) } + }.getOrElse(Nil) } /** Turns a usecase section into a UseCase, with code changed to: @@ -84,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, start: Int, end: Int)(implicit ctx: Context): UseCase = { def subPos(start: Int, end: Int) = if (pos == NoPosition) NoPosition else { @@ -93,19 +103,19 @@ 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 codeStart = skipWhitespace(body, start + "@usecase".length) + val codeEnd = skipToEol(body, codeStart) + val code = body.substring(codeStart, codeEnd) + " = ???" + val codePos = subPos(codeStart, codeEnd) UseCase(code, codePos) } } object Comment { - def apply(pos: Position, raw: String, expanded: Boolean = false, usc: List[UseCase] = Nil): Comment = + def apply(pos: Position, raw: String, expandedComment: Option[String] = None, usc: List[UseCase] = Nil): Comment = new Comment(pos, raw) { - val isExpanded = expanded + val expanded = expandedComment val usecases = usc } } diff --git a/compiler/src/dotty/tools/dotc/core/tasty/CommentPickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/CommentPickler.scala index 807a10a68cea..5fa2272d39e7 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/CommentPickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/CommentPickler.scala @@ -24,7 +24,6 @@ class CommentPickler(pickler: TastyPickler, addrOfTree: tpd.Tree => Option[Addr] buf.writeNat(length) buf.writeBytes(bytes, length) buf.writeLongInt(cmt.pos.coords) - buf.writeByte(if (cmt.isExpanded) 1 else 0) 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 9fd896bc5748..7d4e48cbcd18 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/CommentUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/CommentUnpickler.scala @@ -20,9 +20,8 @@ class CommentUnpickler(reader: TastyReader) { if (length > 0) { val bytes = readBytes(length) val position = new Position(readLongInt()) - val expanded = readByte() == 1 val rawComment = new String(bytes, Charset.forName("UTF-8")) - comments(addr) = Comment(position, 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 25c95236b2b4..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 LongInt Byte // Raw comment's bytes encoded as UTF-8, followed by the comment's coordinates, - // 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/typer/Docstrings.scala b/compiler/src/dotty/tools/dotc/typer/Docstrings.scala index fdbcbb3e4d6e..97526a71d52a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Docstrings.scala +++ b/compiler/src/dotty/tools/dotc/typer/Docstrings.scala @@ -9,29 +9,33 @@ import ast.tpd trait Docstrings { self: Typer => - /** 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)) - - 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) - } + /** + * 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): Unit = { + for { + docbase <- ctx.docCtx + comment <- docbase.docstring(sym).filter(!_.isExpanded) + } { + expandParentDocs(sym) + docbase.docstring(sym).get.usecases.foreach { usecase => + enterSymbol(createSymbol(usecase.untpdCode)) + 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 expandParentDocs(sym: Symbol)(implicit ctx: Context): Unit = ctx.docCtx.foreach { docCtx => diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 67b669742e81..ccb0a876d6b2 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1570,7 +1570,11 @@ class Typer extends Namer // 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) + val cookingCtx = ctx.localContext(cdef, cls).setNewScope + body1.foreach { stat => + cookComment(stat.symbol, self1.symbol)(cookingCtx) + } + cookComment(cls, cls)(cookingCtx) } checkNoDoubleDeclaration(cls) diff --git a/doc-tool/src/dotty/tools/dottydoc/DocFrontEnd.scala b/doc-tool/src/dotty/tools/dottydoc/DocFrontEnd.scala index 82ee81fabc4f..3cbb359d526e 100644 --- a/doc-tool/src/dotty/tools/dottydoc/DocFrontEnd.scala +++ b/doc-tool/src/dotty/tools/dottydoc/DocFrontEnd.scala @@ -2,13 +2,18 @@ package dotty.tools package dottydoc import dotc.fromtasty.ReadTastyTreesFromClasses -import dotc.typer.FrontEnd +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`. */ @@ -17,7 +22,18 @@ class DocFrontEnd extends FrontEnd { override def runOn(units: List[CompilationUnit])(implicit ctx: Context): List[CompilationUnit] = { if (ctx.settings.fromTasty.value) { val fromTastyFrontend = new ReadTastyTreesFromClasses - fromTastyFrontend.runOn(units) + val unpickledUnits = fromTastyFrontend.runOn(units) + + val typer = new Typer() + if (ctx.settings.YcookComments.value) { + ctx.docbase.docstrings.keys.foreach { sym => + val owner = sym.owner + val cookingCtx = ctx.withOwner(owner) + typer.cookComment(sym, owner)(cookingCtx) + } + } + + unpickledUnits } else { super.runOn(units) } diff --git a/doc-tool/src/dotty/tools/dottydoc/core/DocstringPhase.scala b/doc-tool/src/dotty/tools/dottydoc/core/DocstringPhase.scala index 8682f99405a3..4dd18da367bc 100644 --- a/doc-tool/src/dotty/tools/dottydoc/core/DocstringPhase.scala +++ b/doc-tool/src/dotty/tools/dottydoc/core/DocstringPhase.scala @@ -15,7 +15,7 @@ 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,17 +26,20 @@ 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 text = cmt.body - val parsed = parse(ent, ctx.docbase.packages, clean(text), text, 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)) diff --git a/doc-tool/src/dotty/tools/dottydoc/core/UsecasePhase.scala b/doc-tool/src/dotty/tools/dottydoc/core/UsecasePhase.scala index 5940df0bc9e0..d056334329e4 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,10 @@ 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) + ctx.docbase + .docstring(df.symbol) + .flatMap(_.usecases.headOption.map(_.tpdCode)) + .map(defdefToDef(_, df.symbol)) + .getOrElse(df) } } diff --git a/doc-tool/test/ConstructorTest.scala b/doc-tool/test/ConstructorTest.scala index 9818d6f099bd..086abd99c9fd 100644 --- a/doc-tool/test/ConstructorTest.scala +++ b/doc-tool/test/ConstructorTest.scala @@ -25,7 +25,7 @@ abstract class ConstructorsBase extends DottyDocTest { val className = "scala.Class" - check(className :: Nil, source :: Nil) { packages => + check(className :: Nil, source :: Nil) { (ctx, packages) => packages("scala") match { case PackageImpl(_, _, _, List(cls: Class), _, _, _, _) => cls.constructors.headOption match { @@ -49,7 +49,7 @@ abstract class ConstructorsBase extends DottyDocTest { val className = "scala.Class" - check(className :: Nil, source :: Nil) { packages => + check(className :: Nil, source :: Nil) { (ctx, packages) => packages("scala") match { case PackageImpl(_, _, _, List(cls: Class), _, _, _, _) => cls.constructors match { @@ -76,7 +76,7 @@ abstract class ConstructorsBase extends DottyDocTest { val className = "scala.Class" - check(className :: Nil, source :: Nil) { packages => + check(className :: Nil, source :: Nil) { (ctx, packages) => packages("scala") match { case PackageImpl(_, _, _, List(cls: Class), _, _, _, _) => cls.constructors match { @@ -110,7 +110,7 @@ abstract class ConstructorsBase extends DottyDocTest { val className = "scala.Class" - check(className :: Nil, source :: Nil) { packages => + check(className :: Nil, source :: Nil) { (ctx, packages) => packages("scala") match { case PackageImpl(_, _, _, List(cls: Class), _, _, _, _) => cls.constructors match { @@ -150,7 +150,7 @@ abstract class ConstructorsBase extends DottyDocTest { val className = "scala.Class" - check(className :: Nil, source :: Nil) { packages => + check(className :: Nil, source :: Nil) { (ctx, packages) => packages("scala") match { case PackageImpl(_, _, _, List(cls: CaseClass, obj: Object), _, _, _, _) => cls.constructors match { @@ -185,7 +185,7 @@ abstract class ConstructorsBase extends DottyDocTest { val className = "scala.Trait" - check(className :: Nil, source :: Nil) { packages => + 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 5aa201627138..6999e71a59d3 100644 --- a/doc-tool/test/DottyDocTest.scala +++ b/doc-tool/test/DottyDocTest.scala @@ -13,6 +13,7 @@ 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 org.junit.Assert.fail @@ -43,12 +44,12 @@ trait DottyDocTest extends MessageRendering { } 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 => @@ -85,25 +86,25 @@ 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 checkFromSource(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: Map[String, Package] => Unit): Unit = { + def checkFromTasty(classNames: List[String], sources: List[SourceFile])(assertion: (Context, Map[String, Package]) => Unit): Unit = { IOUtils.inTempDirectory { tmp => val ctx = "shadow ctx" val out = tmp.resolve("out") @@ -129,18 +130,18 @@ trait DottyDocTest extends MessageRendering { } } - def check(classNames: List[String], sources: List[SourceFile])(assertion: Map[String, Package] => Unit): Unit + 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: Map[String, Package] => Unit): Unit = { + 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: Map[String, Package] => Unit): Unit = { + 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/MarkdownTests.scala b/doc-tool/test/MarkdownTests.scala index 15adf1abacb8..64255081e1f8 100644 --- a/doc-tool/test/MarkdownTests.scala +++ b/doc-tool/test/MarkdownTests.scala @@ -17,6 +17,7 @@ class MarkdownTests extends DottyDocTest with CheckFromSource { 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 with CheckFromSource { |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 with CheckFromSource { |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 with CheckFromSource { |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 with CheckFromSource { |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 with CheckFromSource { |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 with CheckFromSource { |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 with CheckFromSource { |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 with CheckFromSource { |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 with CheckFromSource { |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 with CheckFromSource { |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 2e59c29150e1..d98e51202e01 100644 --- a/doc-tool/test/PackageStructure.scala +++ b/doc-tool/test/PackageStructure.scala @@ -32,7 +32,7 @@ abstract class PackageStructureBase extends DottyDocTest { val classNames = "scala.A" :: "scala.B" :: Nil - check(classNames, source1 :: source2 :: Nil) { packages => + check(classNames, source1 :: source2 :: Nil) { (ctx, packages) => packages("scala") match { case PackageImpl(_, _, _, List(tA, tB), _, _, _, _) => assert( @@ -66,7 +66,7 @@ abstract class PackageStructureBase extends DottyDocTest { val classNames = "scala.collection.A" :: "scala.collection.B" :: Nil - check(classNames, source1 :: source2 :: Nil) { packages => + 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 42dc98cc8281..f9d8ae043ba9 100644 --- a/doc-tool/test/SimpleComments.scala +++ b/doc-tool/test/SimpleComments.scala @@ -24,7 +24,7 @@ abstract class SimpleCommentsBase 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") @@ -47,7 +47,7 @@ abstract class SimpleCommentsBase extends DottyDocTest { val className = "scala.HelloWorld" - check(className :: Nil, source :: Nil) { packages => + check(className :: Nil, source :: Nil) { (ctx, packages) => val traitCmt = packages("scala") .children.find(_.path.mkString(".") == "scala.HelloWorld") @@ -65,7 +65,7 @@ abstract class SimpleCommentsBase 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/UsecaseTest.scala b/doc-tool/test/UsecaseTest.scala index 5e0c7bc6bfdf..98a451cc36b8 100644 --- a/doc-tool/test/UsecaseTest.scala +++ b/doc-tool/test/UsecaseTest.scala @@ -11,9 +11,7 @@ import model.references._ import util.syntax._ class UsecaseFromSourceTest extends UsecaseBase with CheckFromSource - -// Use case from TASTY do not work at the moment -// class UsecaseFromTastyTest extends UsecaseBase with CheckFromTasty +class UsecaseFromTastyTest extends UsecaseBase with CheckFromTasty abstract class UsecaseBase extends DottyDocTest { @Test def simpleUsecase = { @@ -34,7 +32,7 @@ abstract class UsecaseBase extends DottyDocTest { val className = "scala.Test" - check(className :: Nil, source :: Nil) { packages => + check(className :: Nil, source :: Nil) { (ctx, packages) => packages("scala") match { case PackageImpl(_, _, _, List(trt: Trait), _, _, _, _) => val List(foo: Def) = trt.members @@ -80,7 +78,7 @@ abstract class UsecaseBase extends DottyDocTest { val className = "scala.Test" - check(className :: Nil, source :: Nil) { packages => + check(className :: Nil, source :: Nil) { (ctx, packages) => packages("scala") match { case PackageImpl(_, _, _, List(trt: Trait), _, _, _, _) => val List(foo: Def) = trt.members @@ -127,7 +125,7 @@ abstract class UsecaseBase extends DottyDocTest { val className = "scala.Test" - check(className :: Nil, source :: Nil) { packages => + check(className :: Nil, source :: Nil) { (ctx, packages) => packages("scala") match { case PackageImpl(_, _, _, List(trt: Trait), _, _, _, _) => val List(foo: Def) = trt.members @@ -177,7 +175,7 @@ abstract class UsecaseBase extends DottyDocTest { val className = "scala.Iterable" - check(className :: Nil, source :: Nil) { packages => + check(className :: Nil, source :: Nil) { (ctx, packages) => packages("scala") match { case PackageImpl(_, _, _, List(trt: Trait), _, _, _, _) => val List(map: Def) = trt.members @@ -222,13 +220,13 @@ abstract class UsecaseBase extends DottyDocTest { val className = "scala.Iterable" - check(className :: Nil, source :: Nil) { packages => + 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.body + 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" @@ -238,12 +236,12 @@ abstract class UsecaseBase extends DottyDocTest { } @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 4aecc65eef2b..dc94b404c94e 100644 --- a/doc-tool/test/WhitelistedStdLib.scala +++ b/doc-tool/test/WhitelistedStdLib.scala @@ -7,7 +7,7 @@ import org.junit.Assert._ 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") From 8bacff5216aae0867df3cc8e8046eedddf609f0d Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Mon, 2 Jul 2018 11:55:18 +0200 Subject: [PATCH 07/16] Doc-tool: Remove `@sourcefile` annotation The `@sourcefile` annotation is added to symbols when they're unpickled from TASTY, and we need to remove them so that they do not appear when generating the documentation. --- .../tools/dottydoc/model/factories.scala | 10 +++----- doc-tool/test/PackageStructure.scala | 23 ++++++++++++++++++- 2 files changed, 25 insertions(+), 8 deletions(-) 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/test/PackageStructure.scala b/doc-tool/test/PackageStructure.scala index d98e51202e01..2ef1352f2d07 100644 --- a/doc-tool/test/PackageStructure.scala +++ b/doc-tool/test/PackageStructure.scala @@ -3,14 +3,35 @@ package dottydoc import org.junit.Test import org.junit.Assert._ - import dotc.util.SourceFile +import model.Trait import model.internal._ 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", From 48d7a5dd73dd8d7d368b51cfa80d916daa45758f Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Tue, 3 Jul 2018 00:57:01 +0200 Subject: [PATCH 08/16] Remove `NonEntity` in Dottydoc This special entity was used to mean that an entity needs to be removed from the doc AST after the transformation. Instead, we refactor the DocMiniPhases to have them return a `List[Entity]` (rather than a single `Entity`, as before). It follows that removing an entity from the AST means returning an empty list after transformation. This change will be necessary to be able to support multiple `@usecase` sections in the documentation, because it means that the transformation has to introduce multiple `Entity`-es. --- .../core/AlternateConstructorsPhase.scala | 4 +- .../tools/dottydoc/core/DocASTPhase.scala | 49 +++++++-------- .../tools/dottydoc/core/DocstringPhase.scala | 17 +++-- .../dottydoc/core/LinkCompanionsPhase.scala | 18 +++--- .../dottydoc/core/PackageObjectsPhase.scala | 13 ++-- .../core/RemoveEmptyPackagesPhase.scala | 4 +- .../dottydoc/core/SortMembersPhase.scala | 10 +-- .../dottydoc/core/TypeLinkingPhases.scala | 29 ++++----- .../tools/dottydoc/core/UsecasePhase.scala | 2 +- .../dotty/tools/dottydoc/core/transform.scala | 62 +++++++++---------- .../dottydoc/model/comment/Comment.scala | 5 +- .../dotty/tools/dottydoc/model/entities.scala | 43 +------------ .../dotty/tools/dottydoc/model/internal.scala | 16 ++--- .../tools/dottydoc/model/references.scala | 4 +- .../dottydoc/staticsite/DefaultParams.scala | 21 +++---- .../staticsite/MarkdownLinkVisitor.scala | 4 +- .../tools/dottydoc/staticsite/Site.scala | 16 +++-- .../tools/dottydoc/util/MemberLookup.scala | 21 ++----- .../tools/dottydoc/util/internal/mutate.scala | 17 +++-- doc-tool/test/JavaConverterTest.scala | 22 +++---- 20 files changed, 152 insertions(+), 225 deletions(-) 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 4dd18da367bc..fa6c06cd4759 100644 --- a/doc-tool/src/dotty/tools/dottydoc/core/DocstringPhase.scala +++ b/doc-tool/src/dotty/tools/dottydoc/core/DocstringPhase.scala @@ -9,7 +9,6 @@ import transform.DocMiniPhase import model._ import model.internal._ import model.comment._ -import HtmlParsers._ import util.syntax._ /** Phase to add docstrings to the Dottydoc AST */ @@ -42,34 +41,34 @@ class DocstringPhase extends DocMiniPhase with CommentParser with CommentCleaner } 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 d056334329e4..eea8922786a0 100644 --- a/doc-tool/src/dotty/tools/dottydoc/core/UsecasePhase.scala +++ b/doc-tool/src/dotty/tools/dottydoc/core/UsecasePhase.scala @@ -31,6 +31,6 @@ class UsecasePhase extends DocMiniPhase { .docstring(df.symbol) .flatMap(_.usecases.headOption.map(_.tpdCode)) .map(defdefToDef(_, df.symbol)) - .getOrElse(df) + .getOrElse(df) :: Nil } } 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/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/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 } From 10de931ab2bc43db8b2c5185bdc11d493f83e708 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Tue, 3 Jul 2018 01:01:46 +0200 Subject: [PATCH 09/16] Fix #4752: Support multiple `@usecase` sections In the documentation, it is possible to have several `@usecase` sections, which means that each of these section should be displayed as a separate member in the documentation. So far, Dottydoc was only considering the first `@usecase` section and was disregarding the others. This commit fixes that, and generates a new member for each of the usecases. Fixes #4752 --- .../tools/dottydoc/core/UsecasePhase.scala | 12 ++++---- doc-tool/test/UsecaseTest.scala | 28 +++++++++++++++++++ 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/doc-tool/src/dotty/tools/dottydoc/core/UsecasePhase.scala b/doc-tool/src/dotty/tools/dottydoc/core/UsecasePhase.scala index eea8922786a0..4e574fc39816 100644 --- a/doc-tool/src/dotty/tools/dottydoc/core/UsecasePhase.scala +++ b/doc-tool/src/dotty/tools/dottydoc/core/UsecasePhase.scala @@ -27,10 +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) :: Nil + val defdefs = + ctx.docbase.docstring(df.symbol) + .map(_.usecases.map(_.tpdCode)) + .getOrElse(Nil) + + if (defdefs.isEmpty) df :: Nil + else defdefs.map(defdefToDef(_, df.symbol)) } } diff --git a/doc-tool/test/UsecaseTest.scala b/doc-tool/test/UsecaseTest.scala index 98a451cc36b8..f3772a882782 100644 --- a/doc-tool/test/UsecaseTest.scala +++ b/doc-tool/test/UsecaseTest.scala @@ -235,6 +235,34 @@ abstract class UsecaseBase 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) { case _ => // success if typer throws no errors! :) From b2fe7cb3472fbf99a117cb7450ccd74824e49d87 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Tue, 3 Jul 2018 09:16:36 +0200 Subject: [PATCH 10/16] Cook comments before displaying in the IDE --- .../tools/languageserver/DottyLanguageServer.scala | 14 ++++++++++++-- .../dotty/tools/languageserver/HoverTest.scala | 12 ++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala b/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala index 2050e2a1c3a9..6d1423e1bc89 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 @@ -367,7 +368,16 @@ class DottyLanguageServer extends LanguageServer if (tpw == NoType) null // null here indicates that no response should be sent else { val symbol = Interactive.enclosingSourceSymbol(trees, pos) - val docComment = ctx.docCtx.flatMap(_.docstring(symbol)) + val docComment = ctx.docCtx.flatMap(_.docstring(symbol)).map { + case comment if !comment.isExpanded => + val typer = new Typer() + val owner = symbol.owner + val cookingCtx = ctx.withOwner(owner) + typer.cookComment(symbol, owner)(cookingCtx) + ctx.docCtx.get.docstring(symbol).get + case comment => + comment + } val content = hoverContent(tpw.show, docComment) new Hover(content, null) } @@ -490,7 +500,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 */")) + } + } From 1a458badfb12d6e25df9eece0f3141d82d72ceb7 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Thu, 12 Jul 2018 15:49:35 +0200 Subject: [PATCH 11/16] Further refactorings to `Comments` --- .../src/dotty/tools/dotc/core/Comments.scala | 76 +++++++++---------- .../dotty/tools/dotc/typer/Docstrings.scala | 1 - 2 files changed, 34 insertions(+), 43 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Comments.scala b/compiler/src/dotty/tools/dotc/core/Comments.scala index 0d541b41d888..1357d060e872 100644 --- a/compiler/src/dotty/tools/dotc/core/Comments.scala +++ b/compiler/src/dotty/tools/dotc/core/Comments.scala @@ -36,54 +36,54 @@ object Comments { doc.map(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 - * - * @param pos The position of this `Comment`. - * @param raw The raw comment, as seen in the source code, without any expansion. - */ - abstract case class Comment(pos: Position, raw: String) { self => - - /** The expansion of this comment */ - def expanded: Option[String] + /** + * 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]) { /** Has this comment been cooked or expanded? */ - final def isExpanded: Boolean = expanded.isDefined + def isExpanded: Boolean = expanded.isDefined /** The body of this comment, without the `@usecase` and `@define` sections, after expansion. */ lazy val expandedBody: Option[String] = expanded.map(removeSections(_, "@usecase", "@define")) + val isDocComment = Comment.isDocComment(raw) + /** - * The `@usecase` sections of this comment. - * This is populated by calling `withUsecases` on this object. + * 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 usecases: List[UseCase] + 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) + } + } - val isDocComment = raw.startsWith("/**") + object Comment { - def expand(f: String => String): Comment = new Comment(pos, raw) { - val expanded = Some(f(raw)) - val usecases = self.usecases - } + def isDocComment(comment: String): Boolean = comment.startsWith("/**") - def withUsecases(implicit ctx: Context): Comment = new Comment(pos, raw) { - assert(self.isExpanded) - val expanded = self.expanded - val usecases = parseUsecases - } + def apply(pos: Position, raw: String): Comment = + Comment(pos, raw, None, Nil) - private[this] def parseUsecases(implicit ctx: Context): List[UseCase] = - if (!isDocComment) { + private def parseUsecases(expandedComment: String, pos: Position)(implicit ctx: Context): List[UseCase] = + if (!isDocComment(expandedComment)) { Nil } else { - expanded.map { body => - tagIndex(body) - .filter { startsWithTag(body, _, "@usecase") } - .map { case (start, end) => decomposeUseCase(body, start, end) } - }.getOrElse(Nil) + 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: @@ -94,7 +94,7 @@ object Comments { * def foo: A = ??? * }}} */ - private[this] def decomposeUseCase(body: String, 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 { @@ -112,14 +112,6 @@ object Comments { } } - object Comment { - def apply(pos: Position, raw: String, expandedComment: Option[String] = None, usc: List[UseCase] = Nil): Comment = - new Comment(pos, raw) { - val expanded = expandedComment - val usecases = usc - } - } - abstract case class UseCase(code: String, codePos: Position) { /** Set by typer */ var tpdCode: tpd.DefDef = _ diff --git a/compiler/src/dotty/tools/dotc/typer/Docstrings.scala b/compiler/src/dotty/tools/dotc/typer/Docstrings.scala index 97526a71d52a..12b3394383e7 100644 --- a/compiler/src/dotty/tools/dotc/typer/Docstrings.scala +++ b/compiler/src/dotty/tools/dotc/typer/Docstrings.scala @@ -46,7 +46,6 @@ trait Docstrings { self: Typer => val newCmt = cmt .expand(tplExp.expandedDocComment(sym, owner, _)) - .withUsecases docCtx.addDocstring(sym, Some(newCmt)) } From 66d44a2bef4a6ac437fdbe2704caa06d17f364b8 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Thu, 12 Jul 2018 16:38:44 +0200 Subject: [PATCH 12/16] More refactorings to comment cooking --- .../src/dotty/tools/dotc/core/Comments.scala | 36 +++++----- .../dotty/tools/dotc/typer/Docstrings.scala | 69 +++++++++++-------- .../tools/dottydoc/core/UsecasePhase.scala | 2 +- 3 files changed, 57 insertions(+), 50 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Comments.scala b/compiler/src/dotty/tools/dotc/core/Comments.scala index 1357d060e872..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,7 +33,7 @@ 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)) } /** @@ -112,29 +112,25 @@ object Comments { } } - abstract case class UseCase(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(code: String, codePos: Position)(implicit ctx: Context) = - new UseCase(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/typer/Docstrings.scala b/compiler/src/dotty/tools/dotc/typer/Docstrings.scala index 12b3394383e7..a1ee58563279 100644 --- a/compiler/src/dotty/tools/dotc/typer/Docstrings.scala +++ b/compiler/src/dotty/tools/dotc/typer/Docstrings.scala @@ -4,7 +4,6 @@ package typer import core._ import Contexts._, Symbols._, Decorators._, Comments._ -import util.Positions._ import ast.tpd trait Docstrings { self: Typer => @@ -21,39 +20,51 @@ trait Docstrings { self: Typer => * @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): Unit = { - for { - docbase <- ctx.docCtx - comment <- docbase.docstring(sym).filter(!_.isExpanded) - } { - expandParentDocs(sym) - docbase.docstring(sym).get.usecases.foreach { usecase => - enterSymbol(createSymbol(usecase.untpdCode)) - 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) - } - } + def cookComment(sym: Symbol, owner: Symbol)(implicit ctx: Context): Option[Comment] = { + ctx.docCtx.flatMap { docCtx => + expand(sym, owner)(ctx, docCtx) } } - 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, _)) + 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 => + enterSymbol(createSymbol(usecase.untpdCode)) + 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 + } + } - docCtx.addDocstring(sym, Some(newCmt)) + val commentWithUsecases = expanded.copy(usecases = typedUsecases) + docCtx.addDocstring(sym, Some(commentWithUsecases)) + commentWithUsecases } + } + } - if (sym ne NoSymbol) { - expandParentDocs(sym.owner) - expandDoc(sym.owner) - } - } + 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 + } + + 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/doc-tool/src/dotty/tools/dottydoc/core/UsecasePhase.scala b/doc-tool/src/dotty/tools/dottydoc/core/UsecasePhase.scala index 4e574fc39816..f9c118055c1e 100644 --- a/doc-tool/src/dotty/tools/dottydoc/core/UsecasePhase.scala +++ b/doc-tool/src/dotty/tools/dottydoc/core/UsecasePhase.scala @@ -29,7 +29,7 @@ class UsecasePhase extends DocMiniPhase { override def transformDef(implicit ctx: Context) = { case df: DefImpl => val defdefs = ctx.docbase.docstring(df.symbol) - .map(_.usecases.map(_.tpdCode)) + .map(_.usecases.flatMap(_.tpdCode)) .getOrElse(Nil) if (defdefs.isEmpty) df :: Nil From c58a4fd3af84b44fbbfb2f18b9d58fe06a94b283 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Fri, 13 Jul 2018 09:50:11 +0200 Subject: [PATCH 13/16] Cook documentation when displaying in REPL --- .../src/dotty/tools/repl/ReplCompiler.scala | 21 +++++++++++-------- compiler/test/dotty/tools/repl/DocTests.scala | 14 +++++++++++++ 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/compiler/src/dotty/tools/repl/ReplCompiler.scala b/compiler/src/dotty/tools/repl/ReplCompiler.scala index 6a3fb7ebd4a2..d7cb5d1ad96f 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.{FrontEnd, ImportInfo, Typer} 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,18 @@ 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 typer = new Typer() + val doc = for { + sym <- symbols + owner = sym.owner + cookingCtx = ctx.withOwner(owner) + cooked <- typer.cookComment(sym, owner)(cookingCtx) + body <- cooked.expandedBody + } yield body + + if (doc.hasNext) doc.next() + else s"// No doc for `$expr`" } case _ => 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) } From 0f5b5ed2ab7ea8b5d8bc772d05516f94b20152f2 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Thu, 2 Aug 2018 08:55:29 +0200 Subject: [PATCH 14/16] Remove IOUtils --- compiler/src/dotty/tools/io/Directory.scala | 6 +++++ compiler/test/dotty/tools/IOUtils.scala | 27 ------------------- .../dotc/core/tasty/CommentPicklingTest.scala | 27 +++++++++---------- doc-tool/test/DottyDocTest.scala | 8 +++--- 4 files changed, 22 insertions(+), 46 deletions(-) delete mode 100644 compiler/test/dotty/tools/IOUtils.scala 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/test/dotty/tools/IOUtils.scala b/compiler/test/dotty/tools/IOUtils.scala deleted file mode 100644 index 5af18d96a818..000000000000 --- a/compiler/test/dotty/tools/IOUtils.scala +++ /dev/null @@ -1,27 +0,0 @@ -package dotty.tools - -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 - -object IOUtils { - - 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(_)) - } - } - - 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/dotc/core/tasty/CommentPicklingTest.scala b/compiler/test/dotty/tools/dotc/core/tasty/CommentPicklingTest.scala index 88258938a2d2..4cafc1673027 100644 --- a/compiler/test/dotty/tools/dotc/core/tasty/CommentPicklingTest.scala +++ b/compiler/test/dotty/tools/dotc/core/tasty/CommentPicklingTest.scala @@ -10,12 +10,10 @@ 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.IOUtils +import dotty.tools.io.{Directory, File, Path} import dotty.tools.vulpix.TestConfiguration -import java.nio.file.{Files, Path} - import org.junit.Test import org.junit.Assert.{assertEquals, assertFalse, fail} @@ -81,25 +79,25 @@ class CommentPicklingTest { } private def compileAndUnpickle[T](sources: List[String])(fn: (List[tpd.Tree], Context) => T) = { - IOUtils.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 = IOUtils.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) @@ -108,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) } diff --git a/doc-tool/test/DottyDocTest.scala b/doc-tool/test/DottyDocTest.scala index 6999e71a59d3..46cb123e35e6 100644 --- a/doc-tool/test/DottyDocTest.scala +++ b/doc-tool/test/DottyDocTest.scala @@ -16,10 +16,10 @@ 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 } -import java.nio.file.Files trait DottyDocTest extends MessageRendering { dotty.tools.dotc.parsing.Scanners // initialize keywords @@ -105,10 +105,10 @@ trait DottyDocTest extends MessageRendering { } def checkFromTasty(classNames: List[String], sources: List[SourceFile])(assertion: (Context, Map[String, Package]) => Unit): Unit = { - IOUtils.inTempDirectory { tmp => + Directory.inTempDirectory { tmp => val ctx = "shadow ctx" - val out = tmp.resolve("out") - Files.createDirectories(out) + val out = tmp./(Directory("out")) + out.createDirectory() val dotcCtx = { val ctx = freshCtx(out.toString :: Nil) From a082b5a072b0205b264c59f41955670272203d31 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Thu, 2 Aug 2018 16:23:05 +0200 Subject: [PATCH 15/16] Decouple source and TASTY frontend in dottydoc --- .../fromtasty/ReadTastyTreesFromClasses.scala | 5 +++++ .../dotty/tools/dotc/typer/Docstrings.scala | 14 ++++++++++++++ .../src/dotty/tools/dotc/typer/Typer.scala | 15 ++++++--------- .../dotty/tools/dottydoc/DocCompiler.scala | 3 ++- .../dotty/tools/dottydoc/DocFrontEnd.scala | 19 ++----------------- 5 files changed, 29 insertions(+), 27 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/fromtasty/ReadTastyTreesFromClasses.scala b/compiler/src/dotty/tools/dotc/fromtasty/ReadTastyTreesFromClasses.scala index f49c742dedca..9d988b05ba03 100644 --- a/compiler/src/dotty/tools/dotc/fromtasty/ReadTastyTreesFromClasses.scala +++ b/compiler/src/dotty/tools/dotc/fromtasty/ReadTastyTreesFromClasses.scala @@ -45,6 +45,9 @@ class ReadTastyTreesFromClasses extends FrontEnd { else { val unit = mkCompilationUnit(cls, cls.tree, forceTrees = true) unit.pickled += (cls -> unpickler.unpickler.bytes) + if (ctx.settings.YcookComments.value) { + ctx.typer.cookComments(cls.tree, cls) + } Some(unit) } case tree: Tree[_] => @@ -72,5 +75,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/typer/Docstrings.scala b/compiler/src/dotty/tools/dotc/typer/Docstrings.scala index a1ee58563279..d8febafe6937 100644 --- a/compiler/src/dotty/tools/dotc/typer/Docstrings.scala +++ b/compiler/src/dotty/tools/dotc/typer/Docstrings.scala @@ -8,6 +8,20 @@ import ast.tpd trait Docstrings { self: Typer => + /** + * Expands or cooks the documentation for all members of `cdef`. + * + * @see Docstrings#cookComment + */ + def cookComments(cdef: tpd.Tree, cls: ClassSymbol)(implicit ctx: Context): Unit = { + // val cls = cdef.symbol + val cookingCtx = ctx.localContext(cdef, cls).setNewScope + cls.info.allMembers.foreach { member => + cookComment(member.symbol, cls)(cookingCtx) + } + cookComment(cls, cls)(cookingCtx) + } + /** * Expands or cooks the documentation for `sym` in class `owner`. * The expanded comment will directly replace the original comment in the doc context. diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index ccb0a876d6b2..88f55526345b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1568,15 +1568,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) { - val cookingCtx = ctx.localContext(cdef, cls).setNewScope - body1.foreach { stat => - cookComment(stat.symbol, self1.symbol)(cookingCtx) - } - cookComment(cls, cls)(cookingCtx) - } - checkNoDoubleDeclaration(cls) val impl1 = cpy.Template(impl)(constr1, parents1, self1, body1) .withType(dummy.termRef) @@ -1601,6 +1592,12 @@ class Typer extends Namer if (ctx.settings.YretainTrees.value) cls.treeOrProvider = cdef1 + // Expand comments and type usecases if `-Ycook-comments` is set. + if (ctx.settings.YcookComments.value) { + cookComments(cdef1, cls) + } + + cdef1 // todo later: check that diff --git a/doc-tool/src/dotty/tools/dottydoc/DocCompiler.scala b/doc-tool/src/dotty/tools/dottydoc/DocCompiler.scala index 7a2374dae41c..ef592f27e468 100644 --- a/doc-tool/src/dotty/tools/dottydoc/DocCompiler.scala +++ b/doc-tool/src/dotty/tools/dottydoc/DocCompiler.scala @@ -34,7 +34,8 @@ class DocCompiler extends Compiler { } 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 diff --git a/doc-tool/src/dotty/tools/dottydoc/DocFrontEnd.scala b/doc-tool/src/dotty/tools/dottydoc/DocFrontEnd.scala index 3cbb359d526e..25cd8d95b42f 100644 --- a/doc-tool/src/dotty/tools/dottydoc/DocFrontEnd.scala +++ b/doc-tool/src/dotty/tools/dottydoc/DocFrontEnd.scala @@ -20,23 +20,8 @@ import util.syntax.ContextWithContextDottydoc class DocFrontEnd extends FrontEnd { override def runOn(units: List[CompilationUnit])(implicit ctx: Context): List[CompilationUnit] = { - if (ctx.settings.fromTasty.value) { - val fromTastyFrontend = new ReadTastyTreesFromClasses - val unpickledUnits = fromTastyFrontend.runOn(units) - - val typer = new Typer() - if (ctx.settings.YcookComments.value) { - ctx.docbase.docstrings.keys.foreach { sym => - val owner = sym.owner - val cookingCtx = ctx.withOwner(owner) - typer.cookComment(sym, owner)(cookingCtx) - } - } - - unpickledUnits - } else { - super.runOn(units) - } + if (ctx.settings.fromTasty.value) units + else super.runOn(units) } override protected def discardAfterTyper(unit: CompilationUnit)(implicit ctx: Context) = From 0cd20b4e05b3b46bc498fef7f2ffdbb52aae8ae6 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Fri, 3 Aug 2018 11:48:02 +0200 Subject: [PATCH 16/16] Add `CookComments` miniphase This miniphase is used to cook the comments when `-Ycook-comments` is set. This phase is used by the compiler, the REPL, Dotty IDE and Dottydoc. --- compiler/src/dotty/tools/dotc/Compiler.scala | 3 +- .../fromtasty/ReadTastyTreesFromClasses.scala | 3 -- .../interactive/InteractiveCompiler.scala | 3 +- .../dotc/interactive/InteractiveDriver.scala | 1 + .../tools/dotc/transform/CookComments.scala | 28 +++++++++++++++++++ .../dotty/tools/dotc/typer/Docstrings.scala | 20 ++----------- .../src/dotty/tools/dotc/typer/Typer.scala | 9 +----- .../src/dotty/tools/repl/ReplCompiler.scala | 10 +++---- .../src/dotty/tools/repl/ReplDriver.scala | 1 + .../dotty/tools/dottydoc/DocCompiler.scala | 2 ++ .../languageserver/DottyLanguageServer.scala | 11 +------- 11 files changed, 45 insertions(+), 46 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/transform/CookComments.scala 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/fromtasty/ReadTastyTreesFromClasses.scala b/compiler/src/dotty/tools/dotc/fromtasty/ReadTastyTreesFromClasses.scala index 9d988b05ba03..d22383d2f9b7 100644 --- a/compiler/src/dotty/tools/dotc/fromtasty/ReadTastyTreesFromClasses.scala +++ b/compiler/src/dotty/tools/dotc/fromtasty/ReadTastyTreesFromClasses.scala @@ -45,9 +45,6 @@ class ReadTastyTreesFromClasses extends FrontEnd { else { val unit = mkCompilationUnit(cls, cls.tree, forceTrees = true) unit.pickled += (cls -> unpickler.unpickler.bytes) - if (ctx.settings.YcookComments.value) { - ctx.typer.cookComments(cls.tree, cls) - } Some(unit) } case tree: Tree[_] => 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 d8febafe6937..65e68a1df4e4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Docstrings.scala +++ b/compiler/src/dotty/tools/dotc/typer/Docstrings.scala @@ -6,21 +6,7 @@ import core._ import Contexts._, Symbols._, Decorators._, Comments._ import ast.tpd -trait Docstrings { self: Typer => - - /** - * Expands or cooks the documentation for all members of `cdef`. - * - * @see Docstrings#cookComment - */ - def cookComments(cdef: tpd.Tree, cls: ClassSymbol)(implicit ctx: Context): Unit = { - // val cls = cdef.symbol - val cookingCtx = ctx.localContext(cdef, cls).setNewScope - cls.info.allMembers.foreach { member => - cookComment(member.symbol, cls)(cookingCtx) - } - cookComment(cls, cls)(cookingCtx) - } +object Docstrings { /** * Expands or cooks the documentation for `sym` in class `owner`. @@ -47,8 +33,8 @@ trait Docstrings { self: Typer => case _ => expandComment(sym).map { expanded => val typedUsecases = expanded.usecases.map { usecase => - enterSymbol(createSymbol(usecase.untpdCode)) - typedStats(usecase.untpdCode :: Nil, owner) match { + 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 _ => diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 88f55526345b..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 => _, _} @@ -1592,12 +1591,6 @@ class Typer extends Namer if (ctx.settings.YretainTrees.value) cls.treeOrProvider = cdef1 - // Expand comments and type usecases if `-Ycook-comments` is set. - if (ctx.settings.YcookComments.value) { - cookComments(cdef1, cls) - } - - cdef1 // todo later: check that diff --git a/compiler/src/dotty/tools/repl/ReplCompiler.scala b/compiler/src/dotty/tools/repl/ReplCompiler.scala index d7cb5d1ad96f..cf81040d655d 100644 --- a/compiler/src/dotty/tools/repl/ReplCompiler.scala +++ b/compiler/src/dotty/tools/repl/ReplCompiler.scala @@ -14,7 +14,7 @@ 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, Typer} +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} @@ -195,13 +195,11 @@ class ReplCompiler extends Compiler { if (stat.tpe.isError) stat.tpe.show else { val symbols = extractSymbols(stat) - val typer = new Typer() val doc = for { sym <- symbols - owner = sym.owner - cookingCtx = ctx.withOwner(owner) - cooked <- typer.cookComment(sym, owner)(cookingCtx) - body <- cooked.expandedBody + docCtx <- ctx.docCtx + comment <- docCtx.docstring(sym) + body <- comment.expandedBody } yield body if (doc.hasNext) doc.next() 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/doc-tool/src/dotty/tools/dottydoc/DocCompiler.scala b/doc-tool/src/dotty/tools/dottydoc/DocCompiler.scala index ef592f27e468..eb1d0dc873d5 100644 --- a/doc-tool/src/dotty/tools/dottydoc/DocCompiler.scala +++ b/doc-tool/src/dotty/tools/dottydoc/DocCompiler.scala @@ -9,6 +9,7 @@ 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 * @@ -41,6 +42,7 @@ class DocCompiler extends Compiler { Nil override protected def transformPhases: List[List[Phase]] = + List(new CookComments) :: List(new DocImplicitsPhase) :: List(new DocASTPhase) :: List(DocMiniTransformations(new UsecasePhase, diff --git a/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala b/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala index 6d1423e1bc89..e1a9f41d6e58 100644 --- a/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala +++ b/language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala @@ -368,16 +368,7 @@ class DottyLanguageServer extends LanguageServer if (tpw == NoType) null // null here indicates that no response should be sent else { val symbol = Interactive.enclosingSourceSymbol(trees, pos) - val docComment = ctx.docCtx.flatMap(_.docstring(symbol)).map { - case comment if !comment.isExpanded => - val typer = new Typer() - val owner = symbol.owner - val cookingCtx = ctx.withOwner(owner) - typer.cookComment(symbol, owner)(cookingCtx) - ctx.docCtx.get.docstring(symbol).get - case comment => - comment - } + val docComment = ctx.docCtx.flatMap(_.docstring(symbol)) val content = hoverContent(tpw.show, docComment) new Hover(content, null) }