diff options
-rw-r--r-- | doc-tool/resources/_layouts/api-page.html | 2 | ||||
-rw-r--r-- | doc-tool/resources/css/api-page.css | 5 | ||||
-rw-r--r-- | doc-tool/src/dotty/tools/dottydoc/DocCompiler.scala | 3 | ||||
-rw-r--r-- | doc-tool/src/dotty/tools/dottydoc/DocDriver.scala | 5 | ||||
-rw-r--r-- | doc-tool/src/dotty/tools/dottydoc/core/ContextDottydoc.scala | 86 | ||||
-rw-r--r-- | doc-tool/src/dotty/tools/dottydoc/core/DocASTPhase.scala | 5 | ||||
-rw-r--r-- | doc-tool/src/dotty/tools/dottydoc/core/DocstringPhase.scala | 1 | ||||
-rw-r--r-- | doc-tool/src/dotty/tools/dottydoc/core/StatisticsPhase.scala | 156 | ||||
-rw-r--r-- | doc-tool/src/dotty/tools/dottydoc/core/transform.scala (renamed from doc-tool/src/dotty/tools/dottydoc/core/MiniPhaseTransform.scala) | 15 | ||||
-rw-r--r-- | doc-tool/src/dotty/tools/dottydoc/model/JavaConverters.scala | 8 | ||||
-rw-r--r-- | doc-tool/src/dotty/tools/dottydoc/model/entities.scala | 3 | ||||
-rw-r--r-- | doc-tool/src/dotty/tools/dottydoc/util/traversing.scala | 4 | ||||
-rw-r--r-- | project/Build.scala | 12 |
13 files changed, 288 insertions, 17 deletions
diff --git a/doc-tool/resources/_layouts/api-page.html b/doc-tool/resources/_layouts/api-page.html index 26a4290da..878ec8d8f 100644 --- a/doc-tool/resources/_layouts/api-page.html +++ b/doc-tool/resources/_layouts/api-page.html @@ -95,7 +95,7 @@ extraCSS: {% for member in entity.members %} <div id="{{ member.signature }}" class="member {% if member.isPrivate %}private{% elsif member.isProtected %}protected{% endif %}"> <div class="member-title"> - <span class="expand-button" onclick="toggleMemberBody(this, '{{ member.signature }}');">[+]</span> + <span class="expand-button {% if member.hasShortenedDocstring == false %}invisible{% endif %}" onclick="toggleMemberBody(this, '{{ member.signature }}');">[+]</span> <span class="member-annotations"> {% for annot in member.annotations %}@{{ annot | split: '.' | last }} {% endfor %} </span> diff --git a/doc-tool/resources/css/api-page.css b/doc-tool/resources/css/api-page.css index 380efb834..1dd4f559c 100644 --- a/doc-tool/resources/css/api-page.css +++ b/doc-tool/resources/css/api-page.css @@ -122,6 +122,11 @@ div.entity-section > div.member > div.member-title > span.expand-button:hover { user-select: none; } +div.entity-section > div.member > div.member-title > span.expand-button.invisible, +div.entity-section > div.member > div.member-title > span.expand-button.invisible:hover { + color: transparent; +} + div.entity-section > div.member > div.member-body { margin: 5px 0 0 39px; } diff --git a/doc-tool/src/dotty/tools/dottydoc/DocCompiler.scala b/doc-tool/src/dotty/tools/dottydoc/DocCompiler.scala index 708c26cc0..c5d20d30b 100644 --- a/doc-tool/src/dotty/tools/dottydoc/DocCompiler.scala +++ b/doc-tool/src/dotty/tools/dottydoc/DocCompiler.scala @@ -32,6 +32,7 @@ class DocCompiler extends Compiler { new LinkSuperTypes, new LinkCompanions, new AlternateConstructors, - new SortMembers)) + new SortMembers)), + List(new StatisticsPhase) ) } diff --git a/doc-tool/src/dotty/tools/dottydoc/DocDriver.scala b/doc-tool/src/dotty/tools/dottydoc/DocDriver.scala index 515de9ae9..1434d5a49 100644 --- a/doc-tool/src/dotty/tools/dottydoc/DocDriver.scala +++ b/doc-tool/src/dotty/tools/dottydoc/DocDriver.scala @@ -9,6 +9,7 @@ import model.Package import dotc.config._ import dotc.core.Comments.ContextDoc import staticsite.Site +import dotc.printing.Highlighting._ /** `DocDriver` implements the main entry point to the Dotty documentation * tool. It's methods are used by the external scala and java APIs. @@ -43,16 +44,18 @@ class DocDriver extends Driver { implicit val (filesToDocument, ctx) = setup(args, initCtx.fresh) val reporter = doCompile(newCompiler(ctx), filesToDocument)(ctx) val siteRoot = new java.io.File(ctx.settings.siteRoot.value) + val projectName = ctx.settings.projectName.value if (!siteRoot.exists || !siteRoot.isDirectory) ctx.error(s"Site root does not exist: $siteRoot") else { - Site(siteRoot, ctx.settings.projectName.value, ctx.docbase.packages) + Site(siteRoot, projectName, ctx.docbase.packages) .generateApiDocs() .copyStaticFiles() .generateHtmlFiles() .generateBlog() + ctx.docbase.printSummary() System.exit(if (reporter.hasErrors) 1 else 0) } } diff --git a/doc-tool/src/dotty/tools/dottydoc/core/ContextDottydoc.scala b/doc-tool/src/dotty/tools/dottydoc/core/ContextDottydoc.scala index 16f0776fa..8f463833d 100644 --- a/doc-tool/src/dotty/tools/dottydoc/core/ContextDottydoc.scala +++ b/doc-tool/src/dotty/tools/dottydoc/core/ContextDottydoc.scala @@ -2,9 +2,12 @@ package dotty.tools package dottydoc package core -import dotc.core.Symbols.Symbol +import dotc.core.Symbols._ +import dotc.core.Flags._ +import dotc.core.Decorators._ import dotc.core.Comments.ContextDocstrings -import model.Package +import model.{ Package, Entity } +import model.comment.Comment import dotc.core.Contexts.Context import dotc.printing.Highlighting._ @@ -17,6 +20,12 @@ class ContextDottydoc extends ContextDocstrings { def packages: Map[String, Package] = _packages.toMap def packagesMutable: mutable.Map[String, Package] = _packages + private[this] var _statistics: Map[String, Statistics] = Map.empty + def registerStatistics(pkgName: String, stat: Statistics): Unit = + _statistics = _statistics + (pkgName -> stat) + + def statistics: Map[String, Statistics] = _statistics + /** Should perhaps factorize this into caches that get flushed */ private var _defs: Map[Symbol, Set[Symbol]] = Map.empty def defs(sym: Symbol): Set[Symbol] = _defs.get(sym).getOrElse(Set.empty) @@ -49,4 +58,77 @@ class ContextDottydoc extends ContextDocstrings { }.toString, pos) def debug(msg: String)(implicit ctx: Context): Unit = debug(msg, NoSourcePosition) + + def printSummary()(implicit ctx: Context): Unit = { + def colored(part: Int, total: Int) = + if (total == 0) "0" + else { + val percentage = (part * 100.0 / total).toInt + val str = s"$part/$total ($percentage%)" + + if (percentage > 75) Green(str) + else if (percentage > 50) Yellow(str) + else Red(str) + } + + val totalEntities = statistics.totalEntities + + val projectName = ctx.settings.projectName.value + val warningsText = + if (ctx.reporter.hasWarnings) + s"total warnings with regards to compilation and documentation: ${ctx.reporter.warningCount}" + else "" + + val api = statistics.values.iterator.map(_.api).foldLeft(Counters(0,0,0,0,0,0))(_ merge _) + val internalApi = statistics.values.iterator.map(_.internalApi).foldLeft(Counters(0,0,0,0,0,0))(_ merge _) + + val apiSummary = (for { + (pkgName, stat) <- statistics.toList.sortBy(_._1) + } yield { + val pub = colored(stat.api.publicDocstrings, stat.api.publicEntities) + val pro = colored(stat.api.protectedDocstrings, stat.api.protectedEntities) + s"""|package $pkgName + |${Blue("-" * ctx.settings.pageWidth.value)} + |public: $pub \t protected: $pro + |""".stripMargin + }).mkString("\n") + + val internalSummary = (for { + (pkgName, stat) <- statistics.toList.sortBy(_._1) + } yield { + val pub = colored(stat.internalApi.publicDocstrings, stat.internalApi.publicEntities) + val pro = colored(stat.internalApi.protectedDocstrings, stat.internalApi.protectedEntities) + val pri = colored(stat.internalApi.privateDocstrings, stat.internalApi.privateEntities) + s"""|package $pkgName + |${Blue("-" * ctx.settings.pageWidth.value)} + |public: $pub \t protected: $pro \t private: $pri + |""".stripMargin + }).mkString("\n") + + ctx.echo { + s"""|${Blue("=" * ctx.settings.pageWidth.value)} + |Dottydoc summary report for project `$projectName` + |${Blue("=" * ctx.settings.pageWidth.value)} + |Documented members in public API: + | + |$apiSummary + | + |Summary: + | + |public members with docstrings: ${colored(api.publicDocstrings, api.publicEntities)} + |${hl"${"protected"}"} members with docstrings: ${colored(api.protectedDocstrings, api.protectedEntities)} + |${Blue("=" * ctx.settings.pageWidth.value)} + | + |Documented members in internal API: + | + |$internalSummary + | + |Summary internal API: + | + |public members with docstrings: ${colored(internalApi.publicDocstrings, internalApi.publicEntities)} + |${hl"${"protected"}"} members with docstrings: ${colored(internalApi.protectedDocstrings, internalApi.protectedEntities)} + |${hl"${"private"}"} members with docstrings: ${colored(internalApi.privateDocstrings, internalApi.privateEntities)} + |$warningsText""".stripMargin + } + } } diff --git a/doc-tool/src/dotty/tools/dottydoc/core/DocASTPhase.scala b/doc-tool/src/dotty/tools/dottydoc/core/DocASTPhase.scala index 460566838..7f44c5656 100644 --- a/doc-tool/src/dotty/tools/dottydoc/core/DocASTPhase.scala +++ b/doc-tool/src/dotty/tools/dottydoc/core/DocASTPhase.scala @@ -226,7 +226,7 @@ class DocASTPhase extends Phase { override def run(implicit ctx: Context): Unit = { currentRun += 1 - ctx.docbase.echo(s"Compiling ($currentRun/$totalRuns): ${ctx.compilationUnit.source.file.name}") + ctx.echo(s"Compiling ($currentRun/$totalRuns): ${ctx.compilationUnit.source.file.name}") collect(ctx.compilationUnit.tpdTree) // Will put packages in `packages` var } @@ -237,8 +237,7 @@ class DocASTPhase extends Phase { // (2) Set parents of entities, needed for linking for { - parentName <- rootPackages(packages) - parent = packages(parentName) + parent <- rootPackages(packages) child <- parent.members } setParent(child, to = parent) diff --git a/doc-tool/src/dotty/tools/dottydoc/core/DocstringPhase.scala b/doc-tool/src/dotty/tools/dottydoc/core/DocstringPhase.scala index 3de8f68f7..2471e9220 100644 --- a/doc-tool/src/dotty/tools/dottydoc/core/DocstringPhase.scala +++ b/doc-tool/src/dotty/tools/dottydoc/core/DocstringPhase.scala @@ -14,6 +14,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] = ctx.docbase.docstring(sym) .orElse { diff --git a/doc-tool/src/dotty/tools/dottydoc/core/StatisticsPhase.scala b/doc-tool/src/dotty/tools/dottydoc/core/StatisticsPhase.scala new file mode 100644 index 000000000..e7f9a9ec7 --- /dev/null +++ b/doc-tool/src/dotty/tools/dottydoc/core/StatisticsPhase.scala @@ -0,0 +1,156 @@ +package dotty.tools +package dottydoc +package core + +import dotc.core.Phases.Phase +import dotc.core.Contexts.Context +import dotc.core.Symbols.Symbol +import dotc.core.Decorators._ +import dotc.core.Flags._ +import dotc.CompilationUnit +import dottydoc.util.syntax._ +import dottydoc.util.traversing._ + +import model._ + +object Statistics { + implicit class MapTotals(val map: Map[String, Statistics]) extends AnyVal { + def totalEntities = + map.values.foldLeft(0)(_ + _.totalEntities) + } +} + +case class Statistics(pkgName: String, api: Counters, internalApi: Counters) { + def totalEntities = + api.totalEntities + internalApi.totalEntities + + def totalDocstrings = + api.totalDocstrings + internalApi.totalDocstrings +} + +case class Counters( + publicEntities: Int, + privateEntities: Int, + protectedEntities: Int, + + publicDocstrings: Int, + privateDocstrings: Int, + protectedDocstrings: Int +) { + def totalEntities = + publicEntities + privateEntities + protectedEntities + + def totalDocstrings = + publicDocstrings + privateDocstrings + protectedDocstrings + + def merge(o: Counters): Counters = Counters( + publicEntities + o.publicEntities, + privateEntities + o.privateEntities, + protectedEntities + o.protectedEntities, + publicDocstrings + o.publicDocstrings, + privateDocstrings + o.privateDocstrings, + protectedDocstrings + o.protectedDocstrings + ) +} + +class StatisticsPhase extends Phase { + + def phaseName = "StatisticsPhase" + + override def run(implicit ctx: Context): Unit = () + + override def runOn(units: List[CompilationUnit])(implicit ctx: Context): List[CompilationUnit] = { + for { + (pkgName, pack) <- ctx.docbase.packages + externalApi = collectPublicStats(pack) + internalApi = collectInternalStats(pack) + stats = Statistics(pkgName, externalApi, internalApi) + } ctx.docbase.registerStatistics(pkgName, stats) + + units + } + + def collectPublicStats(pack: Package)(implicit ctx: Context): Counters = { + var publicEntities: Int = 0 + var protectedEntities: Int = 0 + var publicDocstrings: Int = 0 + var protectedDocstrings: Int = 0 + + if (pack.comment.isDefined) { + publicEntities += 1 + publicDocstrings += 1 + } + + def doCount(sym: Symbol, comment: Int): Unit = + if (!sym.is(Protected)) { + publicEntities += 1 + publicDocstrings += comment + } + else { + protectedEntities += 1 + protectedDocstrings += comment + } + + + def recur(e: Entity, reachable: Boolean): Unit = { + val isVisible = !e.symbol.is(Private) && !e.symbol.privateWithin.exists + val shouldCount = isVisible && reachable + e match { + case e: Package => () + case e: Entity with Members => if (shouldCount) { + doCount(e.symbol, if (e.comment.isDefined) 1 else 0) + e.members.foreach { c => + if (!(e.symbol.is(Final) && c.symbol.is(Protected))) recur(c, true) + } + } + case e => + if (shouldCount) doCount(e.symbol, if (e.comment.isDefined) 1 else 0) + } + } + + pack.members.foreach(recur(_, true)) + Counters(publicEntities, 0, protectedEntities, publicDocstrings, 0, protectedDocstrings) + } + + def collectInternalStats(pack: Package)(implicit ctx: Context): Counters = { + var publicEntities: Int = 0 + var privateEntities: Int = 0 + var protectedEntities: Int = 0 + var publicDocstrings: Int = 0 + var privateDocstrings: Int = 0 + var protectedDocstrings: Int = 0 + + def doCount(sym: Symbol, comment: Int): Unit = + if (sym.is(Private)) { + privateEntities += 1 + privateDocstrings += comment + } + else if (!sym.is(Protected)) { + publicEntities += 1 + publicDocstrings += comment + } + else { + protectedEntities += 1 + protectedDocstrings += comment + } + + + def recur(e: Entity, reachable: Boolean): Unit = { + val internal = !reachable || e.symbol.is(Private) || e.symbol.privateWithin.exists + e match { + case _: Package => () + case e: Entity with Members => + e.members.foreach { c => + val childIsInternal = !internal || (e.symbol.is(Final) && c.symbol.is(Protected)) + recur(c, childIsInternal) + } + if (internal) doCount(e.symbol, if (e.comment.isDefined) 1 else 0) + case _ => + if (internal) doCount(e.symbol, if (e.comment.isDefined) 1 else 0) + } + } + + pack.members.foreach(recur(_, true)) + Counters(publicEntities, privateEntities, protectedEntities, publicDocstrings, privateDocstrings, protectedDocstrings) + } +} diff --git a/doc-tool/src/dotty/tools/dottydoc/core/MiniPhaseTransform.scala b/doc-tool/src/dotty/tools/dottydoc/core/transform.scala index 1f30e089d..e4b9ae6b6 100644 --- a/doc-tool/src/dotty/tools/dottydoc/core/MiniPhaseTransform.scala +++ b/doc-tool/src/dotty/tools/dottydoc/core/transform.scala @@ -45,15 +45,15 @@ object transform { * ------------------------- * To delete a node in the AST, simply return `NonEntity` from transforming method */ - abstract class DocMiniTransformations(transformations: List[DocMiniPhase]) extends Phase { + trait DocMiniTransformations extends Phase { + def transformations: List[DocMiniPhase] override def runOn(units: List[CompilationUnit])(implicit ctx: Context): List[CompilationUnit] = { for { - rootName <- rootPackages(ctx.docbase.packages) - pack = ctx.docbase.packages(rootName) + pack <- rootPackages(ctx.docbase.packages) transformed = performPackageTransform(pack) - } yield ctx.docbase.packagesMutable(rootName) = transformed - super.runOn(units) + } yield ctx.docbase.packagesMutable(pack.name) = transformed + units } private def performPackageTransform(pack: Package)(implicit ctx: Context): Package = { @@ -197,8 +197,9 @@ object transform { object DocMiniTransformations { private var previousPhase = 0 - def apply(transformations: DocMiniPhase*) = - new DocMiniTransformations(transformations.toList) { + def apply(miniPhases: DocMiniPhase*) = + new DocMiniTransformations { + val transformations = miniPhases.toList val packages = Map.empty[String, Package] def phaseName = s"MiniTransformation${ previousPhase += 1 }" diff --git a/doc-tool/src/dotty/tools/dottydoc/model/JavaConverters.scala b/doc-tool/src/dotty/tools/dottydoc/model/JavaConverters.scala index e7cf54a03..239656141 100644 --- a/doc-tool/src/dotty/tools/dottydoc/model/JavaConverters.scala +++ b/doc-tool/src/dotty/tools/dottydoc/model/JavaConverters.scala @@ -55,6 +55,7 @@ object JavaConverters { "comment" -> ent.comment.map(_.asJava).asJava, "superTypes" -> ent.superTypes, "hasVisibleMembers" -> ent.hasVisibleMembers, + "hasShortenedDocstring" -> ent.hasShortenedDocstring, "signature" -> ent.signature ) ++ extras).asJava } @@ -74,6 +75,7 @@ object JavaConverters { "isPrivate" -> ent.isPrivate, "isProtected" -> ent.isProtected, "hasVisibleMembers" -> ent.hasVisibleMembers, + "hasShortenedDocstring" -> ent.hasShortenedDocstring, "hasCompanion" -> ent.hasCompanion, "companionPath" -> ent.companionPath.asJava, "signature" -> ent.signature @@ -95,6 +97,7 @@ object JavaConverters { "isPrivate" -> ent.isPrivate, "isProtected" -> ent.isProtected, "hasVisibleMembers" -> ent.hasVisibleMembers, + "hasShortenedDocstring" -> ent.hasShortenedDocstring, "hasCompanion" -> ent.hasCompanion, "companionPath" -> ent.companionPath.asJava, "signature" -> ent.signature @@ -115,6 +118,7 @@ object JavaConverters { "isPrivate" -> ent.isPrivate, "isProtected" -> ent.isProtected, "hasVisibleMembers" -> ent.hasVisibleMembers, + "hasShortenedDocstring" -> ent.hasShortenedDocstring, "hasCompanion" -> ent.hasCompanion, "companionPath" -> ent.companionPath.asJava, "signature" -> ent.signature @@ -134,6 +138,7 @@ object JavaConverters { "isPrivate" -> ent.isPrivate, "isProtected" -> ent.isProtected, "hasVisibleMembers" -> ent.hasVisibleMembers, + "hasShortenedDocstring" -> ent.hasShortenedDocstring, "hasCompanion" -> ent.hasCompanion, "companionPath" -> ent.companionPath.asJava, "signature" -> ent.signature @@ -152,6 +157,7 @@ object JavaConverters { "paramLists" -> ent.paramLists.map(_.asJava).asJava, "comment" -> ent.comment.map(_.asJava).asJava, "implicitlyAddedFrom" -> ent.implicitlyAddedFrom.map(_.asJava).asJava, + "hasShortenedDocstring" -> ent.hasShortenedDocstring, "isPrivate" -> ent.isPrivate, "isProtected" -> ent.isProtected, "signature" -> ent.signature @@ -168,6 +174,7 @@ object JavaConverters { "returnValue" -> ent.returnValue.asJava, "comment" -> ent.comment.map(_.asJava).asJava, "implicitlyAddedFrom" -> ent.implicitlyAddedFrom.map(_.asJava).asJava, + "hasShortenedDocstring" -> ent.hasShortenedDocstring, "isPrivate" -> ent.isPrivate, "isProtected" -> ent.isProtected, "signature" -> ent.signature @@ -183,6 +190,7 @@ object JavaConverters { "path" -> ent.path.asJava, "alias" -> ent.alias.map(_.asJava).asJava, "comment" -> ent.comment.map(_.asJava).asJava, + "hasShortenedDocstring" -> ent.hasShortenedDocstring, "isPrivate" -> ent.isPrivate, "isProtected" -> ent.isProtected, "signature" -> ent.signature diff --git a/doc-tool/src/dotty/tools/dottydoc/model/entities.scala b/doc-tool/src/dotty/tools/dottydoc/model/entities.scala index 9cbd1f6c8..d35077816 100644 --- a/doc-tool/src/dotty/tools/dottydoc/model/entities.scala +++ b/doc-tool/src/dotty/tools/dottydoc/model/entities.scala @@ -21,6 +21,9 @@ trait Entity { entity => def annotations: List[String] + def hasShortenedDocstring: Boolean = + comment.map(d => d.body.length > d.short.length).getOrElse(false) + def signature: String = entity.name + (entity match { case o: Object => "$" diff --git a/doc-tool/src/dotty/tools/dottydoc/util/traversing.scala b/doc-tool/src/dotty/tools/dottydoc/util/traversing.scala index 9c2e3bf54..956cb9291 100644 --- a/doc-tool/src/dotty/tools/dottydoc/util/traversing.scala +++ b/doc-tool/src/dotty/tools/dottydoc/util/traversing.scala @@ -24,7 +24,7 @@ object traversing { } - def rootPackages(pkgs: Map[String, Package]): List[String] = { + def rootPackages(pkgs: Map[String, Package]): List[Package] = { var currentDepth = Int.MaxValue var packs = List.empty[String] @@ -38,6 +38,6 @@ object traversing { key :: packs } else packs } - packs + packs.map(pkgs.apply) } } diff --git a/project/Build.scala b/project/Build.scala index f03111f76..08b1b4f4e 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -56,6 +56,9 @@ object DottyBuild extends Build { // Compiles the documentation and static site lazy val genDocs = inputKey[Unit]("run dottydoc to generate static documentation site") + // Shorthand for compiling a docs site + lazy val dottydoc = inputKey[Unit]("run dottydoc") + /** Dottydoc deps */ lazy val dottydocDeps = SettingKey[Seq[ModuleID]]( "dottydocDeps", @@ -273,6 +276,15 @@ object DottyBuild extends Build { ) }.evaluated, + dottydoc := Def.inputTaskDyn { + val args: Seq[String] = spaceDelimited("<arg>").parsed + val dottyLib = packageAll.value("dotty-library") + val dottyInterfaces = packageAll.value("dotty-interfaces") + val otherDeps = (dependencyClasspath in Compile).value.map(_.data).mkString(":") + val cp: Seq[String] = Seq("-classpath", s"$dottyLib:$dottyInterfaces:$otherDeps") + (runMain in Compile).toTask(s""" dotty.tools.dottydoc.Main ${cp.mkString(" ")} """ + args.mkString(" ")) + }.evaluated, + // Override run to be able to run compiled classfiles dotr := { val args: Seq[String] = spaceDelimited("<arg>").parsed |