From 8071e83420a8b867ad0a8132b3a36dbe28962779 Mon Sep 17 00:00:00 2001 From: Krzysztof Romanowski Date: Tue, 19 Jan 2021 10:57:12 +0100 Subject: [PATCH 01/12] Migrate renderer away from dokka --- .../images/dotty-logo-white.svg | 0 .../resources/dotty_res/images/scala_logo.svg | 32 --- .../src/dotty/dokka/DottyDokkaPlugin.scala | 27 +-- .../src/dotty/dokka/ScalaModuleCreator.scala | 2 +- scala3doc/src/dotty/dokka/compat.scala | 7 + .../ScalaEmbeddedResourceApppender.scala | 46 ---- .../ScalaResourceInstaller.scala | 38 ---- .../dokka/site/SiteResourceManager.scala | 49 ---- .../src/dotty/dokka/site/StaticPageNode.scala | 2 + .../dotty/dokka/site/StaticSiteContext.scala | 47 +++- scala3doc/src/dotty/dokka/site/common.scala | 18 ++ ...rer.scala => DokkaScalaHtmlRenderer.scala} | 61 +++-- .../src/dotty/renderers/HtmlRenderer.scala | 214 ++++++++++++++++++ scala3doc/src/dotty/renderers/Locations.scala | 109 +++++++++ scala3doc/src/dotty/renderers/Resources.scala | 113 +++++++++ .../src/dotty/renderers/SiteRenderer.scala | 55 +++++ scala3doc/src/dotty/renderers/Writter.scala | 35 +++ .../noDocsIndex/docs/Adoc.md | 6 + .../noDocsIndex/docs/dir/index.md | 6 + .../noDocsIndex/docs/dir/nested.md | 6 + .../noDocsIndex/docs/index.md | 3 + .../noDocsIndex/images/basic.svg | 30 +++ .../test-documentations/noDocsIndex/index.md | 8 + .../noGlobalIndex/docs/Adoc.md | 6 + .../noGlobalIndex/docs/dir/index.md | 6 + .../noGlobalIndex/docs/dir/nested.md | 6 + .../noGlobalIndex/docs/index.md | 3 + .../noGlobalIndex/images/basic.svg | 30 +++ .../noIndexes/docs/Adoc.md | 6 + .../noIndexes/docs/dir/index.md | 6 + .../noIndexes/docs/dir/nested.md | 6 + .../noIndexes/images/basic.svg | 30 +++ .../dotty/dokka/renderers/LocationTests.scala | 39 ++++ .../dokka/site/SiteGeneratationTest.scala | 97 ++++++-- 34 files changed, 916 insertions(+), 233 deletions(-) rename {scala3doc/resources/dotty_res => docs}/images/dotty-logo-white.svg (100%) delete mode 100644 scala3doc/resources/dotty_res/images/scala_logo.svg delete mode 100644 scala3doc/src/dotty/dokka/preprocessors/ScalaEmbeddedResourceApppender.scala delete mode 100644 scala3doc/src/dotty/dokka/preprocessors/ScalaResourceInstaller.scala delete mode 100644 scala3doc/src/dotty/dokka/site/SiteResourceManager.scala rename scala3doc/src/dotty/renderers/{ScalaHtmlRenderer.scala => DokkaScalaHtmlRenderer.scala} (86%) create mode 100644 scala3doc/src/dotty/renderers/HtmlRenderer.scala create mode 100644 scala3doc/src/dotty/renderers/Locations.scala create mode 100644 scala3doc/src/dotty/renderers/Resources.scala create mode 100644 scala3doc/src/dotty/renderers/SiteRenderer.scala create mode 100644 scala3doc/src/dotty/renderers/Writter.scala create mode 100644 scala3doc/test-documentations/noDocsIndex/docs/Adoc.md create mode 100644 scala3doc/test-documentations/noDocsIndex/docs/dir/index.md create mode 100644 scala3doc/test-documentations/noDocsIndex/docs/dir/nested.md create mode 100644 scala3doc/test-documentations/noDocsIndex/docs/index.md create mode 100644 scala3doc/test-documentations/noDocsIndex/images/basic.svg create mode 100644 scala3doc/test-documentations/noDocsIndex/index.md create mode 100644 scala3doc/test-documentations/noGlobalIndex/docs/Adoc.md create mode 100644 scala3doc/test-documentations/noGlobalIndex/docs/dir/index.md create mode 100644 scala3doc/test-documentations/noGlobalIndex/docs/dir/nested.md create mode 100644 scala3doc/test-documentations/noGlobalIndex/docs/index.md create mode 100644 scala3doc/test-documentations/noGlobalIndex/images/basic.svg create mode 100644 scala3doc/test-documentations/noIndexes/docs/Adoc.md create mode 100644 scala3doc/test-documentations/noIndexes/docs/dir/index.md create mode 100644 scala3doc/test-documentations/noIndexes/docs/dir/nested.md create mode 100644 scala3doc/test-documentations/noIndexes/images/basic.svg create mode 100644 scala3doc/test/dotty/dokka/renderers/LocationTests.scala diff --git a/scala3doc/resources/dotty_res/images/dotty-logo-white.svg b/docs/images/dotty-logo-white.svg similarity index 100% rename from scala3doc/resources/dotty_res/images/dotty-logo-white.svg rename to docs/images/dotty-logo-white.svg diff --git a/scala3doc/resources/dotty_res/images/scala_logo.svg b/scala3doc/resources/dotty_res/images/scala_logo.svg deleted file mode 100644 index 6196f9200f00..000000000000 --- a/scala3doc/resources/dotty_res/images/scala_logo.svg +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - - - - - - - background - - - - Layer 1 - - - - - - - - - - \ No newline at end of file diff --git a/scala3doc/src/dotty/dokka/DottyDokkaPlugin.scala b/scala3doc/src/dotty/dokka/DottyDokkaPlugin.scala index 33d61d477f02..f8dd57cf3048 100644 --- a/scala3doc/src/dotty/dokka/DottyDokkaPlugin.scala +++ b/scala3doc/src/dotty/dokka/DottyDokkaPlugin.scala @@ -27,7 +27,6 @@ import org.jetbrains.dokka.base.resolvers.shared._ import dotty.dokka.site.NavigationCreator import dotty.dokka.site.SitePagesCreator import dotty.dokka.site.StaticSiteContext -import dotty.dokka.site.SiteResourceManager import dotty.dokka.site.StaticSiteLocationProviderFactory /** Main Dokka plugin for the doctool. @@ -62,20 +61,6 @@ class DottyDokkaPlugin extends DokkaJavaPlugin: .overrideExtension(dokkaBase.getModulesAndPackagesDocumentation) ) - val scalaResourceInstaller = extend( - _.extensionPoint(dokkaBase.getHtmlPreprocessors) - .fromRecipe{ case ctx @ given DokkaContext => new ScalaResourceInstaller } - .name("scalaResourceInstaller") - .after(dokkaBase.getCustomResourceInstaller) - ) - - val scalaEmbeddedResourceAppender = extend( - _.extensionPoint(dokkaBase.getHtmlPreprocessors) - .fromInstance(new ScalaEmbeddedResourceAppender()) - .after(dokkaBase.getCustomResourceInstaller) - .name("scalaEmbeddedResourceAppender") - ) - val scalaDocumentableToPageTranslator = extend( _.extensionPoint(CoreExtensions.INSTANCE.getDocumentableToPageTranslator) .fromRecipe { case ctx @ given DokkaContext => @@ -90,7 +75,7 @@ class DottyDokkaPlugin extends DokkaJavaPlugin: val ourRenderer = extend( _.extensionPoint(CoreExtensions.INSTANCE.getRenderer) - .fromRecipe { case ctx @ given DokkaContext => new ScalaHtmlRenderer } + .fromRecipe { case ctx @ given DokkaContext => new DokkaScalaHtmlRenderer } .overrideExtension(dokkaBase.getHtmlRenderer) ) @@ -129,16 +114,6 @@ class DottyDokkaPlugin extends DokkaJavaPlugin: .overrideExtension(dokkaBase.getNavigationPageInstaller) ) - val customDocumentationResources = extend( - _.extensionPoint(dokkaBase.getHtmlPreprocessors) - .fromRecipe{ case c @ given DokkaContext => new SiteResourceManager } - .name("customDocumentationResources") - .after( - scalaEmbeddedResourceAppender.getValue, - customDocumentationProvider.getValue - ) - ) - val locationProvider = extend( _.extensionPoint(dokkaBase.getLocationProviderFactory) .fromRecipe { case c @ given DokkaContext => new StaticSiteLocationProviderFactory } diff --git a/scala3doc/src/dotty/dokka/ScalaModuleCreator.scala b/scala3doc/src/dotty/dokka/ScalaModuleCreator.scala index 1b4b277f51f3..0e6d3c6925ac 100644 --- a/scala3doc/src/dotty/dokka/ScalaModuleCreator.scala +++ b/scala3doc/src/dotty/dokka/ScalaModuleCreator.scala @@ -45,7 +45,7 @@ class ScalaModuleProvider(using ctx: DocContext) extends SourceToDocumentableTra JMap(), null, sourceSet.toSet, - PropertyContainer.Companion.empty() plus ModuleExtension(result.flatMap(flattenMember).toMap) + PropertyContainer.Companion.empty() plus ModuleExtension(flattenMember(topLevelPackage).toMap) ) transformers.foldLeft(module)( (module, transformer) => transformer(module) ) diff --git a/scala3doc/src/dotty/dokka/compat.scala b/scala3doc/src/dotty/dokka/compat.scala index 0e721da414e7..a2bc0953899c 100644 --- a/scala3doc/src/dotty/dokka/compat.scala +++ b/scala3doc/src/dotty/dokka/compat.scala @@ -15,6 +15,7 @@ import java.util.Collections import org.jetbrains.dokka.plugability._ import kotlin.jvm.JvmClassMappingKt.getKotlinClass import org.jetbrains.dokka.links._ +import java.nio.file.Path val U: kotlin.Unit = kotlin.Unit.INSTANCE @@ -42,6 +43,8 @@ extension (dri: DRI) extra = Option(dri.getExtra).fold(null)(e => raw"\[origin:(.*)\]".r.replaceAllIn(e, "")) ) + def isStaticFile = dri.getExtra == staticFileExtra + def location: String = dri.getPackageName def anchor: Option[String] = Option(dri.getClassNames).filterNot(_.isEmpty) @@ -57,7 +60,11 @@ extension (dri: DRI) extra: String = dri.extra ) = new DRI(location, anchor.getOrElse(""), null, target, extra) +val staticFileExtra = "___staticFile___" + object DRI: + def forPath(path: Path) = apply(location = path.toString, extra = staticFileExtra) + def apply( location: String = "", anchor: Option[String] = None, diff --git a/scala3doc/src/dotty/dokka/preprocessors/ScalaEmbeddedResourceApppender.scala b/scala3doc/src/dotty/dokka/preprocessors/ScalaEmbeddedResourceApppender.scala deleted file mode 100644 index eb5463a8b675..000000000000 --- a/scala3doc/src/dotty/dokka/preprocessors/ScalaEmbeddedResourceApppender.scala +++ /dev/null @@ -1,46 +0,0 @@ -package dotty.dokka - -import org.jetbrains.dokka.transformers.pages.{PageTransformer} -import org.jetbrains.dokka.pages.{RootPageNode, PageNode} -import scala.jdk.CollectionConverters._ - -class ScalaEmbeddedResourceAppender extends PageTransformer { - override def invoke(input: RootPageNode): RootPageNode = - input.transformContentPagesTree(page => - page.modified( - page.getName, - page.getContent, - page.getDri, - // Remove default CSS and navigation loader and add our own versions - (page.getEmbeddedResources.asScala - .filterNot(_ == "scripts/navigation-loader.js") - .filterNot(_.endsWith(".css")) ++ Seq( - "styles/nord-light.css", - "styles/scalastyle.css", - "styles/dotty-icons.css", - "styles/diagram.css", - "styles/filter-bar.css", - "styles/search-bar.css", - "styles/scala3doc-searchbar.css", - "https://code.jquery.com/jquery-3.5.1.min.js", - "https://d3js.org/d3.v6.min.js", - "https://cdn.jsdelivr.net/npm/graphlib-dot@0.6.2/dist/graphlib-dot.min.js", - "https://cdnjs.cloudflare.com/ajax/libs/dagre-d3/0.6.1/dagre-d3.min.js", - "styles/filter-bar.css", - "hljs/highlight.pack.js", - "scripts/hljs-scala3.js", - "scripts/ux.js", - "scripts/common/component.js", - "scripts/common/utils.js", - "scripts/components/FilterBar.js", - "scripts/components/DocumentableList.js", - "scripts/components/Input.js", - "scripts/components/FilterGroup.js", - "scripts/components/Filter.js", - "scripts/data.js", - "scripts/searchbar.js" - )).asJava, - page.getChildren - ) - ) -} diff --git a/scala3doc/src/dotty/dokka/preprocessors/ScalaResourceInstaller.scala b/scala3doc/src/dotty/dokka/preprocessors/ScalaResourceInstaller.scala deleted file mode 100644 index ed9db8933c10..000000000000 --- a/scala3doc/src/dotty/dokka/preprocessors/ScalaResourceInstaller.scala +++ /dev/null @@ -1,38 +0,0 @@ -package dotty.dokka - -import org.jetbrains.dokka.transformers.pages.{PageTransformer} -import org.jetbrains.dokka.pages.{RootPageNode, RendererSpecificResourcePage, RenderingStrategy$Copy, PageNode, RenderingStrategy$Write} -import scala.jdk.CollectionConverters._ -import com.fasterxml.jackson.databind.ObjectMapper -import dotty.dokka.translators.FilterAttributes -import java.nio.file.Paths - -class ScalaResourceInstaller(using ctx: DocContext) extends PageTransformer: - private def dottyRes(resourceName: String) = - new RendererSpecificResourcePage(resourceName, java.util.ArrayList(), RenderingStrategy$Copy(s"/dotty_res/$resourceName")) - - override def invoke(input: RootPageNode): RootPageNode = - val dirs = Seq("fonts", "images", "styles", "scripts", "hljs", "favicon.ico") - val defaultResources = input.getChildren.asScala ++ dirs.map(dottyRes) - val newResources = - projectLogo ++ defaultResources ++ Seq(dynamicJsData, scala3docVersionFile) - input.modified(input.getName, newResources.asJava) - - private def textFile(path: String, content: String) = - val strategy = RenderingStrategy$Write(content) - new RendererSpecificResourcePage(path, java.util.ArrayList(), strategy) - - private def dynamicJsData = - // If data at any point will become more complex we should use a proper mapping - val data: Map[String, Map[String, String]] = - Map("filterDefaults" -> FilterAttributes.defaultValues) - val str = new ObjectMapper().writeValueAsString(data.transform((_, v) => v.asJava).asJava) - textFile("scripts/data.js", s"var scala3DocData = $str") - - private def scala3docVersionFile = textFile("scala3doc.version", BuildInfo.version) - - private def projectLogo = ctx.args.projectLogo.toSeq.map { path => - val fileName = Paths.get(path).getFileName() - val strategy = new RenderingStrategy$Copy(path) - new RendererSpecificResourcePage(s"project-logo/$fileName", JList(), strategy) - } diff --git a/scala3doc/src/dotty/dokka/site/SiteResourceManager.scala b/scala3doc/src/dotty/dokka/site/SiteResourceManager.scala deleted file mode 100644 index 489de9b3769a..000000000000 --- a/scala3doc/src/dotty/dokka/site/SiteResourceManager.scala +++ /dev/null @@ -1,49 +0,0 @@ -package dotty.dokka -package site - -import java.nio.file.Files -import java.nio.file.FileVisitOption - -import org.jetbrains.dokka.model.Documentable -import org.jetbrains.dokka.pages._ - -import scala.collection.JavaConverters._ -import dotty.dokka.model.api._ - -class SiteResourceManager(using ctx: DocContext) extends BaseStaticSiteProcessor: - private def listResources(nodes: Seq[PageNode]): Set[String] = - nodes.flatMap { - case it: AContentPage => - listResources(it.getChildren.asScala.toList) - case it: StaticPageNode => - listResources(it.getChildren.asScala.toList) ++ it.resources() - case _ => Seq.empty - }.toSet - - override def transform(input: RootPageNode, ctx: StaticSiteContext): RootPageNode = - val rootPath = ctx.root.toPath - val imgPath = rootPath.resolve("images") - val images = - if !Files.exists(imgPath) then Nil - else - val allPaths = Files.walk(imgPath, FileVisitOption.FOLLOW_LINKS) - val files = allPaths.filter(Files.isRegularFile(_)).iterator().asScala - files.map(p => rootPath.relativize(p).toString).toList - - val resources = images ++ listResources(input.getChildren.asScala.toList) - - val resourcePages = resources.map { path => - val strategy = new RenderingStrategy.Copy(rootPath.resolve(path).toString) - new RendererSpecificResourcePage(path, JList(), strategy) - } - - val modified = input.transformContentPagesTree { - case it: StaticPageNode => - it.copy(getEmbeddedResources = - if it.template.hasFrame then it.getEmbeddedResources ++ it.resources().asJava - else it.resources().asJava - ) - case it => it - } - val newChildren = (resourcePages ++ modified.getChildren.asScala).asJava - modified.modified(modified.getName, newChildren) \ No newline at end of file diff --git a/scala3doc/src/dotty/dokka/site/StaticPageNode.scala b/scala3doc/src/dotty/dokka/site/StaticPageNode.scala index 58f35f1961f4..dc3f837a3b11 100644 --- a/scala3doc/src/dotty/dokka/site/StaticPageNode.scala +++ b/scala3doc/src/dotty/dokka/site/StaticPageNode.scala @@ -22,6 +22,8 @@ case class StaticPageNode( def title(): String = template.title def hasFrame(): Boolean = template.hasFrame + def dri = getDri.iterator.next() + override def modified( name: String, content: ContentNode, diff --git a/scala3doc/src/dotty/dokka/site/StaticSiteContext.scala b/scala3doc/src/dotty/dokka/site/StaticSiteContext.scala index bde05f5a06e8..cc44646b8b68 100644 --- a/scala3doc/src/dotty/dokka/site/StaticSiteContext.scala +++ b/scala3doc/src/dotty/dokka/site/StaticSiteContext.scala @@ -28,14 +28,16 @@ class StaticSiteContext( var memberLinkResolver: String => Option[DRI] = _ => None - def indexPage(): Option[StaticPageNode] = + def indexTemplate(): Seq[LoadedTemplate] = val files = List(new File(root, "index.html"), new File(root, "index.md")).filter { _.exists() } if files.size > 1 then val msg = s"ERROR: Multiple root index pages found: ${files.map(_.getAbsolutePath)}" report.error(msg) - files.flatMap(loadTemplate(_, isBlog = false)).headOption.map(templateToPage) + files.flatMap(loadTemplate(_, isBlog = false)).take(1) + + def indexPage(): Option[StaticPageNode] = indexTemplate().headOption.map(templateToPage) lazy val layouts: Map[String, TemplateFile] = val layoutRoot = new File(root, "_layouts") @@ -49,6 +51,27 @@ class StaticSiteContext( lazy val templates: Seq[LoadedTemplate] = sideBarConfig.fold(loadAllFiles())(_.map(loadSidebarContent)) + lazy val orphanedTemplates: Seq[LoadedTemplate] = { + def doFlatten(t: LoadedTemplate): Seq[Path] = + t.file.toPath +: t.children.flatMap(doFlatten) + val mainFiles = templates.flatMap(doFlatten) + + val allPaths = + if !Files.exists(docsPath) then Nil + else Files.walk(docsPath, FileVisitOption.FOLLOW_LINKS).iterator().asScala.toList + + val orphanedFiles = allPaths.filterNot { p => + def name = p.getFileName.toString + def isMain = name == "index.html" || name == "index.md" + mainFiles.contains(p) || (isMain && mainFiles.contains(p.getParent)) + }.filter { p => + val name = p.getFileName.toString + name.endsWith(".md") || name.endsWith(".html") + } + + orphanedFiles.flatMap(p => loadTemplate(p.toFile, isBlog = false)) + } + lazy val mainPages: Seq[StaticPageNode] = templates.map(templateToPage) val docsPath = root.toPath.resolve("docs") @@ -168,7 +191,25 @@ class StaticSiteContext( pathsDri.getOrElse(memberLinkResolver(link).toList) def driFor(dest: Path): DRI = - DRI(location = root.toPath.relativize(dest).iterator.asScala.mkString(".")) + val rawFilePath = root.toPath.relativize(dest) + val pageName = dest.getFileName.toString + val dotIndex = pageName.lastIndexOf('.') + + val relativePath = + if rawFilePath.startsWith(Paths.get("blog","_posts")) then + val regex = raw"(\d*)-(\d*)-(\d*)-(.*)\..*".r + pageName.toString match + case regex(year, month, day, name) => + rawFilePath.getParent.resolveSibling(Paths.get(year, month, day, name)) + case _ => + val msg = s"Relative path for blog: $rawFilePath doesn't match `yyy-mm-dd-name.md` format." + report.warn(msg, dest.toFile) + rawFilePath.resolveSibling(pageName.substring(0, dotIndex)) + else + if (dotIndex < 0) rawFilePath.resolve("index") + else rawFilePath.resolveSibling(pageName.substring(0, dotIndex)) + + DRI.forPath(relativePath) def relativePath(myTemplate: LoadedTemplate) = root.toPath.relativize(myTemplate.file.toPath) diff --git a/scala3doc/src/dotty/dokka/site/common.scala b/scala3doc/src/dotty/dokka/site/common.scala index 06d77a19c5e5..3611e55aec1e 100644 --- a/scala3doc/src/dotty/dokka/site/common.scala +++ b/scala3doc/src/dotty/dokka/site/common.scala @@ -125,6 +125,24 @@ abstract class BaseStaticSiteProcessor(using ctx: DocContext) protected def transform(input: RootPageNode, ctx: StaticSiteContext): RootPageNode +// Needed until we will migrate away from dokka +case class FakeContentPage( + dri: DRI, + override val getContent: ContentNode) extends ContentPage: + override val getName: String = "" + override val getChildren: JList[PageNode] = JList() + override val getEmbeddedResources: JList[String] = JList() + override def getDocumentable: Documentable = null + override def modified( + name: String, + content: ContentNode, + dri: JSet[DRI], + embeddedResources: JList[String], + children: JList[_ <: PageNode] + ): ContentPage = this + override def modified(name: String, children: JList[_ <: PageNode]): PageNode = this + override val getDri: JSet[DRI] = JSet(dri) + case class AContentPage( override val getName: String, override val getChildren: JList[PageNode], diff --git a/scala3doc/src/dotty/renderers/ScalaHtmlRenderer.scala b/scala3doc/src/dotty/renderers/DokkaScalaHtmlRenderer.scala similarity index 86% rename from scala3doc/src/dotty/renderers/ScalaHtmlRenderer.scala rename to scala3doc/src/dotty/renderers/DokkaScalaHtmlRenderer.scala index 15b4da14d5c3..7dbeaca3ccc7 100644 --- a/scala3doc/src/dotty/renderers/ScalaHtmlRenderer.scala +++ b/scala3doc/src/dotty/renderers/DokkaScalaHtmlRenderer.scala @@ -4,6 +4,7 @@ import org.jetbrains.dokka.plugability.DokkaContext import org.jetbrains.dokka.pages._ import org.jetbrains.dokka.model._ import org.jetbrains.dokka.base.renderers.html.HtmlRenderer +import org.jetbrains.dokka.base.renderers.DefaultRenderer import org.jetbrains.dokka._ import HTML._ import collection.JavaConverters._ @@ -28,10 +29,11 @@ import org.jetbrains.dokka.base.renderers.html.SearchbarDataInstaller import org.jsoup.Jsoup import java.nio.file.Paths -class SignatureRenderer(pageContext: ContentPage, sourceSetRestriciton: JSet[DisplaySourceSet], locationProvider: LocationProvider): - val currentDri = pageContext.getDri.asScala.head +trait SignatureRenderer: + def currentDri: DRI + def link(dri: DRI): Option[String] - def link(dri: DRI): Option[String] = Option(locationProvider.resolve(dri, sourceSetRestriciton, pageContext)) + def renderElement(e: String | (String, DRI) | Link) = renderElementWith(e) def renderLink(name: String, dri: DRI, modifiers: AppliedAttr*) = link(dri) match @@ -43,19 +45,51 @@ class SignatureRenderer(pageContext: ContentPage, sourceSetRestriciton: JSet[Dis case name: String => raw(name) case Link(name, dri) => renderLink(name, dri, modifiers:_*) +class SignatureRendererImpl(pageContext: ContentPage, sourceSetRestriciton: JSet[DisplaySourceSet], locationProvider: LocationProvider) extends SignatureRenderer: + val currentDri = pageContext.getDri.asScala.head - def renderElement(e: String | (String, DRI) | Link) = renderElementWith(e) + def link(dri: DRI): Option[String] = Option(locationProvider.resolve(dri, sourceSetRestriciton, pageContext)) -class ScalaHtmlRenderer(using ctx: DokkaContext) extends HtmlRenderer(ctx) { +class DokkaScalaHtmlRenderer(using ctx: DokkaContext) extends HtmlRenderer(ctx) { val args = summon[DocContext].args - // TODO #239 - val hackScalaSearchbarDataInstaller: SearchbarDataInstaller = { - val f = classOf[HtmlRenderer].getDeclaredField("searchbarDataInstaller") + override def render(root: RootPageNode): Unit = + def getModule(node: PageNode): Seq[DModule] = node match + case m: ModulePageNode => + m.getDocumentable match + case dmodule: DModule => Seq(dmodule) + case _ => Nil + case other => Nil + + val module = getModule(root).head + val pck = module.getPackages.get(0) + import dotty.dokka.model.api.driMap + val ourRenderer = new dotty.dokka.renderers.HtmlRenderer( + pck, + module.driMap, + dri => c => buildWithKotlinx(c, site.FakeContentPage(dri, c), null) + ) + + val f = classOf[DefaultRenderer[_]].getDeclaredField("locationProvider") f.setAccessible(true) - f.set(this, ScalaSearchbarDataInstaller(ctx)) - f.get(this).asInstanceOf[ScalaSearchbarDataInstaller] - } + f.set(this, new LocationProvider { + type DSS = JSet[org.jetbrains.dokka.model.DisplaySourceSet] + def resolve(to: DRI, sourceSets: DSS, context: PageNode): String = + context match + case null => ??? + case fromPage: ContentPage => + val from = fromPage.getDri.asScala.head + val path = ourRenderer.pathToPage(to, from) + path + + // None other operation is needed + // This will goes away once we migrate away from dokka + def resolve(node: PageNode, context: PageNode, skipExtension: Boolean): String = ??? + def pathToRoot(from: PageNode): String = ??? + def ancestors(node: PageNode): JList[PageNode] = ??? + def expectedLocationForDri(dri: DRI): String = ??? + }) + ourRenderer.render() // Implementation below is based on Kotlin bytecode and we will try to migrate it to dokka // TODO (https://github.com/lampepfl/scala3doc/issues/232): Move this method to dokka @@ -111,7 +145,7 @@ class ScalaHtmlRenderer(using ctx: DokkaContext) extends HtmlRenderer(ctx) { } private def renderers(pageContext: ContentPage): (SignatureRenderer, MemberRenderer) = - val renderer = SignatureRenderer(pageContext, sourceSets, getLocationProvider) + val renderer = SignatureRendererImpl(pageContext, sourceSets, getLocationProvider) (renderer, new MemberRenderer(renderer, buildWithKotlinx(_, pageContext, null))) private def buildNavigation(r: SignatureRenderer)(rootNav: NavigationNode): AppliedTag = @@ -232,7 +266,6 @@ class ScalaHtmlRenderer(using ctx: DokkaContext) extends HtmlRenderer(ctx) { case content => build(content, context, page, /*sourceSetRestriction=*/null) - override def buildHtml(page: PageNode, resources: JList[String], kotlinxContent: FlowContentConsumer): String = val (pageTitle, noFrame) = page match case static: StaticPageNode => @@ -246,7 +279,7 @@ class ScalaHtmlRenderer(using ctx: DokkaContext) extends HtmlRenderer(ctx) { span(img(src := resolveRoot(page, s"project-logo/$fileName"))) }.toSeq - val renderer = SignatureRenderer(page.asInstanceOf[ContentPage], sourceSets, getLocationProvider) + val renderer = SignatureRendererImpl(page.asInstanceOf[ContentPage], sourceSets, getLocationProvider) html( head( diff --git a/scala3doc/src/dotty/renderers/HtmlRenderer.scala b/scala3doc/src/dotty/renderers/HtmlRenderer.scala new file mode 100644 index 000000000000..9d88d271a535 --- /dev/null +++ b/scala3doc/src/dotty/renderers/HtmlRenderer.scala @@ -0,0 +1,214 @@ +package dotty.dokka +package renderers + +import HTML._ +import collection.JavaConverters._ +import java.net.URI +import java.net.URL +import dotty.dokka.model.api._ +import dotty.dokka.site._ +import scala.util.Try +import org.jsoup.Jsoup +import java.nio.file.Paths +import java.nio.file.Path +import java.nio.file.Files +import java.nio.file.FileVisitOption +import java.io.File +import org.jetbrains.dokka.pages.ContentNode + +case class Page(link: Link, content: Member | ResolvedTemplate | String, children: Seq[Page]): + def withNewChildren(newChildren: Seq[Page]) = copy(children = children ++ newChildren) + + def withTitle(newTitle: String) = copy(link = link.copy(name = newTitle)) + + def hasFrame = content match + case t: ResolvedTemplate => t.hasFrame + case _ => true + +class HtmlRenderer(rootPackage: Member, val members: Map[DRI, Member], buildNode: DRI => ContentNode => String)(using ctx: DocContext) + extends SiteRenderer, Resources, Locations, Writter: + private val args = summon[DocContext].args + val staticSite = summon[DocContext].staticSiteContext + + private def memberPage(member: Member): Page = + val childrenPages = member + .membersBy(m => m.kind == Kind.Package || m.kind.isInstanceOf[Classlike]) + .filter(m => m.origin == Origin.RegularlyDefined && m.inheritedFrom.isEmpty) + Page(Link(member.name, member.dri), member, childrenPages.map(memberPage)) + + val navigablePage: Page = + val rootPckPage = memberPage(rootPackage) + staticSite match + case None => rootPckPage.withTitle(args.name) + case Some(siteContext) => + val (indexes, templates) = siteContext.templates.partition(f => + f.templateFile.isIndexPage() && f.file.toPath.getParent() == siteContext.docsPath) + if (indexes.size > 1) + val msg = s"ERROR: Multiple index pages for doc found ${indexes.map(_.file)}" + report.error(msg) + + val templatePages = + (templates ++ siteContext.indexTemplate()).map(templateToPage(_, siteContext)) + indexes.headOption match + case None if templatePages.isEmpty=> + rootPckPage.withTitle(args.name) + case None => + Page(Link(args.name, docsRootDRI),"", templatePages :+ rootPckPage.withTitle("API")) + case Some(indexPage) => + val newChildren = templatePages :+ rootPckPage.withTitle("API") + templateToPage(indexPage, siteContext).withNewChildren(newChildren) + + val hiddenPages: Seq[Page] = + staticSite.toSeq.flatMap(c => c.orphanedTemplates.map(templateToPage(_, c))) + + def renderContent(page: Page) = page.content match + case m: Member => + val signatureRenderer = new SignatureRenderer: + def currentDri: DRI = page.link.dri + def link(dri: DRI): Option[String] = Some(pathToPage(page.link.dri, dri)) + + MemberRenderer(signatureRenderer, buildNode(page.link.dri)).fullMember(m) + case t: ResolvedTemplate => siteContent(page.link.dri, t) + case a: String => raw(a) + + + def renderPage(page: Page, parents: Vector[Link]): Seq[String] = + val newParents = parents :+ page.link + val content = html( + mkHead(page), + body( + if !page.hasFrame then renderContent(page) + else mkFrame(page.link, newParents, renderContent(page)) + ) + ) + write(page.link.dri, content) +: page.children.flatMap(renderPage(_, newParents)) + + private def specificResources(page: Page): Set[String] = + page.children.toSet.flatMap(specificResources) ++ (page.content match + case r: ResolvedTemplate => + r.resolved.resources.toSet + case _ => Set.empty + ) + + private def renderResources(): Seq[String] = + def siteRoot = staticSite.get.root.toPath + def pathToResource(p: String) = Resource.File(p, siteRoot.resolve(p)) + + val siteImages = staticSite.toSeq.flatMap { _ => + val siteImgPath = siteRoot.resolve("images") + if !Files.exists(siteImgPath) then Nil + else + val allPaths = Files.walk(siteImgPath, FileVisitOption.FOLLOW_LINKS) + val files = allPaths.filter(Files.isRegularFile(_)).iterator().asScala + files.map(p => siteRoot.relativize(p).toString).toList + } + + val siteResourcesPaths = + (navigablePage +: hiddenPages).toSet.flatMap(specificResources) ++ siteImages + + val resources = siteResourcesPaths.toSeq.map(pathToResource) ++ allResources(rootPackage) + resources.flatMap(renderResource) + + def render(): Unit = + val renderedResources = renderResources() + val sites = (navigablePage +: hiddenPages).map(renderPage(_, Vector.empty)) + + def mkHead(page: Page): AppliedTag = + val resources = page.content match + case t: ResolvedTemplate => + t.resolved.resources ++ (if t.hasFrame then memberResourcesPaths else Nil) + case _ => + memberResourcesPaths + + head( + meta(charset := "utf-8"), + meta(HTML.name := "viewport", content := "width=device-width, initial-scale=1"), + title(page.link.name), + link( + rel := "shortcut icon", + `type` := "image/x-icon", + href := resolveLink(page.link.dri, "favicon.ico") + ), + linkResources(page.link.dri, resources).toList, + script(raw(s"""var pathToRoot = "${pathToRoot(page.link.dri)}";""")) + ) + + private def buildNavigation(pageLink: Link): AppliedTag = + def renderNested(nav: Page): (Boolean, AppliedTag) = + val isSelected = nav.link.dri == pageLink.dri + def linkHtml(exapnded: Boolean = false) = + val attrs = if (isSelected) Seq(cls := "selected expanded") else Nil + a(href := pathToPage(nav.link.dri, pageLink.dri), attrs)(nav.link.name) + + nav.children match + case Nil => isSelected -> div(linkHtml()) + case children => + val nested = children.map(renderNested) + val expanded = nested.exists(_._1) || nav.link == pageLink + val attr = if expanded || isSelected then Seq(cls := "expanded") else Nil + (isSelected || expanded) -> div(attr)( + linkHtml(expanded), + span(), + nested.map(_._2) + ) + renderNested(navigablePage)._2 + + private def mkFrame(link: Link, parents: Vector[Link], content: => AppliedTag): AppliedTag = + val projectLogo = + args.projectLogo.map { path => + val fileName = Paths.get(path).getFileName() + span(img(src := resolveRoot(link.dri, s"project-logo/$fileName"))) + }.toSeq + + val parentsHtml = + val innerTags = parents.flatMap[TagArg](b => Seq( + a(href := pathToPage(b.dri, link.dri))(b.name), + "/" + )).dropRight(1) + div(cls := "breadcrumbs")(innerTags:_*) + + div(id := "container")( + div(id := "leftColumn")( + div(id := "logo")( + projectLogo, + span( + div(cls:="projectName")(args.name) + ), + span( + args.projectVersion.map(v => div(cls:="projectVersion")(v)).toList + ) + ), + div(id := "paneSearch"), + nav(id := "sideMenu2")( + buildNavigation(link) + ), + ), + div(id := "main")( + div (id := "leftToggler")( + span(cls := "icon-toggler") + ), + div(id := "searchBar"), + main( + div(id := "content")( + parentsHtml, + div(content), + ) + ), + footer( + span(cls := "go-to-top-icon")( + a(href := "#container")( + span(cls:="icon-vertical_align_top"), + raw(" Back to top") + ) + ), + raw("Generated by "), + a(href := "https://github.com/lampepfl/dotty/tree/master/scala3doc")( + img( + src := resolveRoot(link.dri, "images/scala3doc_logo.svg"), + alt := "Scala3doc", + cls := "scala3doc_logo" + ) + ) + ) + ) + ) diff --git a/scala3doc/src/dotty/renderers/Locations.scala b/scala3doc/src/dotty/renderers/Locations.scala new file mode 100644 index 000000000000..c910c5e5861a --- /dev/null +++ b/scala3doc/src/dotty/renderers/Locations.scala @@ -0,0 +1,109 @@ +package dotty.dokka +package renderers + +import HTML._ +import collection.JavaConverters._ +import java.net.URI +import java.net.URL +import dotty.dokka.model.api._ +import dotty.dokka.site._ +import scala.util.Try +import org.jsoup.Jsoup +import java.nio.file.Paths +import java.nio.file.Path +import java.nio.file.Files +import java.io.File +import scala.util.matching._ + +trait Locations(using ctx: DocContext): + def members: Map[DRI, Member] + + // TODO verify if location exisits + def rawLocation(dri: DRI): Seq[String] = dri match + case `docsDRI` => List("docs", "index") + case `docsRootDRI` => List("index") + case `apiPageDRI` => List("api", "index") + case dri if dri.isStaticFile => + Paths.get(dri.location).iterator.asScala.map(_.toString).toList + case dri => + val loc = dri.location + if loc == null then List("index") // Dokka leftovers + else + val fqn = loc.split(Array('.')).toList ++ dri.anchor.toList match + case List("") => List("index") + case other => other + + Seq("api") ++ fqn + + private def unknownPage(dri: DRI): String = + // TODO we should switch that to warning probably or has dedicated setting + report.inform(s"Unrecognized page for ${dri.location} ($dri)") + "#" + + def pathToPage(from: DRI, to: DRI): String = + if to.isStaticFile || members.contains(to) then + pathTo(rawLocation(from), rawLocation(to)) +".html" + else + val regex = raw"\[origin:(.*)\]".r + val origin = regex.findFirstIn(Option(to.extra).getOrElse("")) + origin match + case Some(path) => + val external = + ctx.externalDocumentationLinks.find(_.originRegexes.exists(r => r.matches(path))) + external.fold(unknownPage(to))(constructPath(to)) + case None => + unknownPage(to) + + + def pathTo(to: Seq[String], fullFrom: Seq[String]): String = + val from = fullFrom.dropRight(1) + val commonPaths = to.zip(from).takeWhile{ case (a, b) => a == b }.size + + val contextPath = from.drop(commonPaths).map(_ => "..") + val nodePath = to.drop(commonPaths) match + case Nil if contextPath.isEmpty && to.nonEmpty=> Seq("..", to.last) + case Nil => to.lastOption.fold(Seq("index"))(".." :: _ :: Nil) + case l => l + + (contextPath ++ nodePath).mkString("/") + + def resolveRoot(from: Seq[String], to: String): String = + pathTo(to.split("/").toList, from) + + def resolveRoot(dri: DRI, path: String): String = resolveRoot(rawLocation(dri), path) + + def resolveLink(dri: DRI, url: String): String = + if URI(url).isAbsolute then url else resolveRoot(dri, url) + + def pathToRoot(dri: DRI): String = + pathTo(rawLocation(dri), Nil).dropRight(1) + + def driExisits(dri: DRI) = true // TODO implement checks! + + def constructPath(dri: DRI)(link: ExternalDocLink): String = + val extension = ".html" + val docURL = link.documentationUrl.toString + def constructPathForJavadoc(dri: DRI): String = { + val location = "\\$+".r.replaceAllIn(dri.location.replace(".","/"), _ => ".") + val anchor = dri.anchor + docURL + location + extension + } + + //TODO #263: Add anchor support + def constructPathForScaladoc(dri: DRI): String = { + val location = dri.location.replace(".","/") + val anchor = dri.anchor + docURL + location + extension + } + + def constructPathForScala3doc(dri: DRI): String = { + val location = dri.location.replace(".","/") + val anchor = dri.anchor + docURL + location + extension + } + + link.kind match { + case DocumentationKind.Javadoc => constructPathForJavadoc(dri) + case DocumentationKind.Scaladoc => constructPathForScaladoc(dri) + case DocumentationKind.Scala3doc => constructPathForScala3doc(dri) + } diff --git a/scala3doc/src/dotty/renderers/Resources.scala b/scala3doc/src/dotty/renderers/Resources.scala new file mode 100644 index 000000000000..1184e64e0b67 --- /dev/null +++ b/scala3doc/src/dotty/renderers/Resources.scala @@ -0,0 +1,113 @@ +package dotty.dokka +package renderers + +import HTML._ +import collection.JavaConverters._ +import java.net.URI +import java.net.URL +import dotty.dokka.model.api._ +import dotty.dokka.site._ +import scala.util.Try +import org.jsoup.Jsoup +import java.nio.file.Paths +import java.nio.file.Path +import java.nio.file.Files +import java.io.File +import dotty.dokka.translators.FilterAttributes +import com.fasterxml.jackson.databind.ObjectMapper + +enum Resource(val path: String): + case Text(override val path: String, content: String) extends Resource(path) + case Classpath(override val path: String, name: String) extends Resource(path) + case File(override val path: String, file: Path) extends Resource(path) + case URL(url: String) extends Resource(url) + +trait Resources(using ctx: DocContext) extends Locations, Writter: + private def dynamicJsData = + // If data at any point will become more complex we should use a proper mapping + val data: Map[String, Map[String, String]] = + Map("filterDefaults" -> FilterAttributes.defaultValues) + val str = new ObjectMapper().writeValueAsString(data.transform((_, v) => v.asJava).asJava) + Resource.Text("scripts/data.js", s"var scala3DocData = $str") + + private def scala3docVersionFile = Resource.Text("scala3doc.version", BuildInfo.version) + + private def projectLogo = ctx.args.projectLogo.toSeq.map { p => + val path = Paths.get(p) + Resource.File(s"project-logo/${path.getFileName()}", path) + } + + private def dottyRes(path: String) = Resource.Classpath(path, s"dotty_res/$path") + + def linkResources(dri: DRI, resources: Iterable[String]): Iterable[AppliedTag] = + def fileExtension(url: String): String = + val param = url.indexOf('?') + val end = if param < 0 then url.length else param + val point = url.lastIndexOf('.', end) + url.substring(point+1, end) + + for res <- resources yield + fileExtension(res) match + case "css" => link(rel := "stylesheet", href := resolveLink(dri, res)) + case "js" => script(`type` := "text/javascript", src := resolveLink(dri, res), defer := "true") + case _ => raw("") + + val memberResources: Seq[Resource] = + val fromResources = List( + "styles/nord-light.css", + "styles/scalastyle.css", + "styles/dotty-icons.css", + "styles/diagram.css", + "styles/filter-bar.css", + "styles/search-bar.css", + "hljs/highlight.pack.js", + "hljs/LICENSE", + "scripts/hljs-scala3.js", + "scripts/ux.js", + "scripts/common/component.js", + "scripts/common/utils.js", + "scripts/components/FilterBar.js", + "scripts/components/DocumentableList.js", + "scripts/components/Input.js", + "scripts/components/FilterGroup.js", + "scripts/components/Filter.js", + ).map(dottyRes) + + val urls = List( + "https://code.jquery.com/jquery-3.5.1.min.js", + "https://d3js.org/d3.v6.min.js", + "https://cdn.jsdelivr.net/npm/graphlib-dot@0.6.2/dist/graphlib-dot.min.js", + "https://cdnjs.cloudflare.com/ajax/libs/dagre-d3/0.6.1/dagre-d3.min.js", + ).map(Resource.URL.apply) + + fromResources ++ urls ++ projectLogo ++ Seq(scala3docVersionFile, dynamicJsData) + + val memberResourcesPaths = memberResources.map(_.path) + + val scala3docLogo = dottyRes("images/scala3doc_logo.svg") + + def packageList(topLevelPackage: Member) = Resource.Text("scala3doc/package-list", "TODO") + + def allResources(topLevelPackage: Member): Seq[Resource] = memberResources ++ Seq( + dottyRes("favicon.ico"), + dottyRes("fonts/dotty-icons.woff"), + dottyRes("fonts/dotty-icons.ttf"), + scala3docLogo, + packageList(topLevelPackage) + ) + + def renderResource(resource: Resource): Seq[String] = + resource match + case Resource.Text(path, content) => + Seq(write(path, content)) + case Resource.Classpath(path, name) => + getClass.getClassLoader.getResourceAsStream(name) match + case null => + report.error(s"Unable to find $name on classpath") + Nil + case is => + try Seq(copy(is, path)) finally is.close() + case Resource.File(path, file) => + Seq(copy(file, path)) + case Resource.URL(url) => + Nil diff --git a/scala3doc/src/dotty/renderers/SiteRenderer.scala b/scala3doc/src/dotty/renderers/SiteRenderer.scala new file mode 100644 index 000000000000..a6e9ea1cf948 --- /dev/null +++ b/scala3doc/src/dotty/renderers/SiteRenderer.scala @@ -0,0 +1,55 @@ +package dotty.dokka +package renderers + +import HTML._ +import collection.JavaConverters._ +import java.net.URI +import java.net.URL +import dotty.dokka.model.api._ +import dotty.dokka.site._ +import scala.util.Try +import org.jsoup.Jsoup +import java.nio.file.Paths +import java.nio.file.Path +import java.nio.file.Files +import java.io.File + +case class ResolvedTemplate(template: LoadedTemplate, ctx: StaticSiteContext): + val resolved = template.resolveToHtml(ctx) + def hasFrame = template.templateFile.hasFrame + +trait SiteRenderer(using DocContext) extends Locations: + + def templateToPage(t: LoadedTemplate, staticSiteCtx: StaticSiteContext): Page = + val dri = staticSiteCtx.driFor(t.file.toPath) + val content = ResolvedTemplate(t, staticSiteCtx) + Page(Link(t.templateFile.title, dri), content, t.children.map(templateToPage(_, staticSiteCtx))) + + private val HashRegex = "([^#]+)(#.+)".r + + def siteContent(pageDri: DRI, content: ResolvedTemplate): AppliedTag = + import content.ctx + def tryAsDri(str: String) = // TODO Does not seem to work with links to API :( + val (path, prefix) = str match + case HashRegex(path, prefix) => (path, prefix) + case _ => (str, "") + + val res = ctx.driForLink(content.template.templateFile, path).filter(driExisits) + if res.isEmpty then report.warn(s"Unable to resolve link '$str'", content.template.file) + res.headOption.fold(str)(pathToPage(_, pageDri) + prefix) + + def processLocalLink(str: String): String = + if str.startsWith("#") || str.isEmpty then str + else Try(URL(str)).map(_ => str).getOrElse(tryAsDri(str)) + + val document = Jsoup.parse(content.resolved.code) + document.select("a").forEach(element => + element.attr("href", processLocalLink(element.attr("href"))) + ) + document.select("img").forEach { element => + val link = element.attr("src") + Try(new URL(link)).getOrElse { + if(link.startsWith("/")) element.attr("src", resolveLink(pageDri, link.drop(1))) + } + }// forrach does not work here + raw(document.outerHtml()) diff --git a/scala3doc/src/dotty/renderers/Writter.scala b/scala3doc/src/dotty/renderers/Writter.scala new file mode 100644 index 000000000000..e3da480b75ee --- /dev/null +++ b/scala3doc/src/dotty/renderers/Writter.scala @@ -0,0 +1,35 @@ +package dotty.dokka +package renderers +import java.io.InputStream +import java.nio.file.Paths +import java.nio.file.Path +import java.nio.file.Files +import java.io.File + +import HTML._ + +// TODO be more clever about writting - make it much faster! +trait Writter(using ctx: DocContext) extends Locations: + private val args = summon[DocContext].args + + private def dest(path: String) = + val absPath = args.output.toPath.resolve(path) + if !Files.exists(absPath.getParent) then Files.createDirectories(absPath.getParent) + absPath + + def write(dri: DRI, content: AppliedTag, ext: String = ".html"): String = + val path = rawLocation(dri).mkString("", "/", ext) + Files.write(dest(path), content.toString.getBytes) + path + + def write(path: String, content: String): String = + Files.write(dest(path), content.toString.getBytes) + path + + def copy(from: Path, to: String): String = + Files.copy(from, dest(to)) + to + + def copy(from: InputStream, to: String): String = + Files.copy(from, dest(to)) + to \ No newline at end of file diff --git a/scala3doc/test-documentations/noDocsIndex/docs/Adoc.md b/scala3doc/test-documentations/noDocsIndex/docs/Adoc.md new file mode 100644 index 000000000000..f9e75f26fb07 --- /dev/null +++ b/scala3doc/test-documentations/noDocsIndex/docs/Adoc.md @@ -0,0 +1,6 @@ +--- +title: Adoc +--- +# Header in Adoc + +And a text! \ No newline at end of file diff --git a/scala3doc/test-documentations/noDocsIndex/docs/dir/index.md b/scala3doc/test-documentations/noDocsIndex/docs/dir/index.md new file mode 100644 index 000000000000..40309d387819 --- /dev/null +++ b/scala3doc/test-documentations/noDocsIndex/docs/dir/index.md @@ -0,0 +1,6 @@ +--- +title: A directory +--- +# {{ page.title }} + +And a text! \ No newline at end of file diff --git a/scala3doc/test-documentations/noDocsIndex/docs/dir/nested.md b/scala3doc/test-documentations/noDocsIndex/docs/dir/nested.md new file mode 100644 index 000000000000..3e0ab5922afe --- /dev/null +++ b/scala3doc/test-documentations/noDocsIndex/docs/dir/nested.md @@ -0,0 +1,6 @@ +--- +title: Nested in a directory +--- +# {{ page.title }} + +And a text! \ No newline at end of file diff --git a/scala3doc/test-documentations/noDocsIndex/docs/index.md b/scala3doc/test-documentations/noDocsIndex/docs/index.md new file mode 100644 index 000000000000..5cdd253040db --- /dev/null +++ b/scala3doc/test-documentations/noDocsIndex/docs/index.md @@ -0,0 +1,3 @@ +# {{ page.title }} in header + +And a text! \ No newline at end of file diff --git a/scala3doc/test-documentations/noDocsIndex/images/basic.svg b/scala3doc/test-documentations/noDocsIndex/images/basic.svg new file mode 100644 index 000000000000..1fb642c8bfa0 --- /dev/null +++ b/scala3doc/test-documentations/noDocsIndex/images/basic.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scala3doc/test-documentations/noDocsIndex/index.md b/scala3doc/test-documentations/noDocsIndex/index.md new file mode 100644 index 000000000000..2205e0c12d3e --- /dev/null +++ b/scala3doc/test-documentations/noDocsIndex/index.md @@ -0,0 +1,8 @@ +--- +title: Basic test +--- +# Header + +[Link to docs](docs/index.html) + +And a text! \ No newline at end of file diff --git a/scala3doc/test-documentations/noGlobalIndex/docs/Adoc.md b/scala3doc/test-documentations/noGlobalIndex/docs/Adoc.md new file mode 100644 index 000000000000..f9e75f26fb07 --- /dev/null +++ b/scala3doc/test-documentations/noGlobalIndex/docs/Adoc.md @@ -0,0 +1,6 @@ +--- +title: Adoc +--- +# Header in Adoc + +And a text! \ No newline at end of file diff --git a/scala3doc/test-documentations/noGlobalIndex/docs/dir/index.md b/scala3doc/test-documentations/noGlobalIndex/docs/dir/index.md new file mode 100644 index 000000000000..40309d387819 --- /dev/null +++ b/scala3doc/test-documentations/noGlobalIndex/docs/dir/index.md @@ -0,0 +1,6 @@ +--- +title: A directory +--- +# {{ page.title }} + +And a text! \ No newline at end of file diff --git a/scala3doc/test-documentations/noGlobalIndex/docs/dir/nested.md b/scala3doc/test-documentations/noGlobalIndex/docs/dir/nested.md new file mode 100644 index 000000000000..3e0ab5922afe --- /dev/null +++ b/scala3doc/test-documentations/noGlobalIndex/docs/dir/nested.md @@ -0,0 +1,6 @@ +--- +title: Nested in a directory +--- +# {{ page.title }} + +And a text! \ No newline at end of file diff --git a/scala3doc/test-documentations/noGlobalIndex/docs/index.md b/scala3doc/test-documentations/noGlobalIndex/docs/index.md new file mode 100644 index 000000000000..5cdd253040db --- /dev/null +++ b/scala3doc/test-documentations/noGlobalIndex/docs/index.md @@ -0,0 +1,3 @@ +# {{ page.title }} in header + +And a text! \ No newline at end of file diff --git a/scala3doc/test-documentations/noGlobalIndex/images/basic.svg b/scala3doc/test-documentations/noGlobalIndex/images/basic.svg new file mode 100644 index 000000000000..1fb642c8bfa0 --- /dev/null +++ b/scala3doc/test-documentations/noGlobalIndex/images/basic.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scala3doc/test-documentations/noIndexes/docs/Adoc.md b/scala3doc/test-documentations/noIndexes/docs/Adoc.md new file mode 100644 index 000000000000..f9e75f26fb07 --- /dev/null +++ b/scala3doc/test-documentations/noIndexes/docs/Adoc.md @@ -0,0 +1,6 @@ +--- +title: Adoc +--- +# Header in Adoc + +And a text! \ No newline at end of file diff --git a/scala3doc/test-documentations/noIndexes/docs/dir/index.md b/scala3doc/test-documentations/noIndexes/docs/dir/index.md new file mode 100644 index 000000000000..40309d387819 --- /dev/null +++ b/scala3doc/test-documentations/noIndexes/docs/dir/index.md @@ -0,0 +1,6 @@ +--- +title: A directory +--- +# {{ page.title }} + +And a text! \ No newline at end of file diff --git a/scala3doc/test-documentations/noIndexes/docs/dir/nested.md b/scala3doc/test-documentations/noIndexes/docs/dir/nested.md new file mode 100644 index 000000000000..3e0ab5922afe --- /dev/null +++ b/scala3doc/test-documentations/noIndexes/docs/dir/nested.md @@ -0,0 +1,6 @@ +--- +title: Nested in a directory +--- +# {{ page.title }} + +And a text! \ No newline at end of file diff --git a/scala3doc/test-documentations/noIndexes/images/basic.svg b/scala3doc/test-documentations/noIndexes/images/basic.svg new file mode 100644 index 000000000000..1fb642c8bfa0 --- /dev/null +++ b/scala3doc/test-documentations/noIndexes/images/basic.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scala3doc/test/dotty/dokka/renderers/LocationTests.scala b/scala3doc/test/dotty/dokka/renderers/LocationTests.scala new file mode 100644 index 000000000000..c69175dfa495 --- /dev/null +++ b/scala3doc/test/dotty/dokka/renderers/LocationTests.scala @@ -0,0 +1,39 @@ +package dotty.dokka +package renderers + +import org.junit.{Test, Rule} +import org.junit.Assert.{assertSame, assertTrue, assertEquals} +import dotty.dokka.HTML._ + +class LocationTests: + given DocContext = testDocContext + object locations extends Locations: + val members = Map.empty + + @Test + def testPathToRoot() = + assertEquals("../root.png", locations.resolveRoot(Seq("a", "b"), "root.png")) + assertEquals("root.png", locations.resolveRoot(Seq("ala.html"), "root.png")) + assertEquals("c/root.png", locations.resolveRoot(Seq("a", "b", "index"), "a/b/c/root.png")) + + @Test + def testLinks() = + def path(from: String, to: String) = + locations.pathTo(to.split('/').toList, from.split('/').toList) + + assertEquals("a/b", locations.pathTo(Seq("a", "b"), Nil)) + + assertEquals( + "../comments", + path("api/dotty/dokka/tasty/comments/wiki", "api/dotty/dokka/tasty/comments"), + ) + + assertEquals( + "../../../../index", + path("api/dotty/dokka/tasty/comments/wiki", "api/index"), + ) + + assertEquals( + "../../annotation", + path("api/scala/annotation/meta/beanGetter", "api/scala/annotation"), + ) \ No newline at end of file diff --git a/scala3doc/test/dotty/dokka/site/SiteGeneratationTest.scala b/scala3doc/test/dotty/dokka/site/SiteGeneratationTest.scala index e099315556a3..ce0b5f2bc260 100644 --- a/scala3doc/test/dotty/dokka/site/SiteGeneratationTest.scala +++ b/scala3doc/test/dotty/dokka/site/SiteGeneratationTest.scala @@ -15,17 +15,20 @@ class SiteGeneratationTest: val projectName = "Test Project Name" val projectVersion = "1.0.1-M1" - private def withGeneratedSite(base: Path)(op: Path => Unit) = + case class ProjectContext(path: Path) + + private def withGeneratedSite(base: Path)(op: ProjectContext ?=> Unit) = val dest = Files.createTempDirectory("test-doc") try val args = Scala3doc.Args( name = projectName, + tastyFiles = tastyFiles("links"), output = dest.toFile, docsRoot = Some(base.toAbsolutePath.toString), projectVersion = Some(projectVersion), ) Scala3doc.run(args)(using testContext) - op(dest) + op(using ProjectContext(dest)) finally IO.delete(dest.toFile) @@ -46,39 +49,85 @@ class SiteGeneratationTest: val found = d.select(selector).eachAttr(attr).asScala assertEquals(niceMsg(s"Attribute $attr does not match for '$selector'"), expected.toList, found.toList) - def withHtmlFile(path: Path)(op: DocumentContext => Unit) = { assertTrue(s"File at $path does not exisits!", Files.exists(path)) val document = Jsoup.parse(IO.read(path)) op(DocumentContext(document, path)) } - @Test - def basicTest() = withGeneratedSite(testDocPath.resolve("basic")){ dest => - - def checkFile(path: String)( - title: String, - header: String, - parents: Seq[String] = Nil, - checks: DocumentContext => Unit = _ => ()) = - withHtmlFile(dest.resolve(path)){ content => - content.assertTextsIn(".projectName", projectName) - content.assertTextsIn(".projectVersion", projectVersion) - content.assertTextsIn("h1", header) - content.assertTextsIn("title", title) - content.assertTextsIn(".breadcrumbs a", (parents :+ title):_*) - checks(content) - } - - def indexLinks(content: DocumentContext) = - content.assertAttr("p a","href", "docs/index.html") - - checkFile("index.html")(title = "Basic test", header = "Header", parents = Seq(projectName), indexLinks) + def indexLinks(content: DocumentContext) = + content.assertAttr("p a","href", "docs/index.html") + + def checkFile( + path: String)( + title: String, + header: String, + parents: Seq[String] = Nil, + checks: DocumentContext => Unit = _ => ())(using ProjectContext) = + withHtmlFile(summon[ProjectContext].path.resolve(path)){ content => + content.assertTextsIn(".projectName", projectName) + content.assertTextsIn(".projectVersion", projectVersion) + content.assertTextsIn("h1", header) + content.assertTextsIn("title", title) + content.assertTextsIn(".breadcrumbs a", (parents :+ title):_*) + checks(content) + } + + def testDocPages()(using ProjectContext) = checkFile("docs/Adoc.html")(title = "Adoc", header = "Header in Adoc", parents = Seq(projectName)) checkFile("docs/Adoc.html")(title = "Adoc", header = "Header in Adoc", parents = Seq(projectName)) checkFile("docs/dir/index.html")(title = "A directory", header = "A directory", parents = Seq(projectName)) checkFile("docs/dir/nested.html")( title = "Nested in a directory", header = "Nested in a directory", parents = Seq(projectName, "A directory")) + def testDocIndexPage()(using ProjectContext) = checkFile("docs/index.html")(title = projectName, header = s"$projectName in header") + + def testMainIndexPage()(using ProjectContext) = + checkFile("index.html")(title = "Basic test", header = "Header", parents = Seq(projectName), indexLinks) + + def testApiPages( + mainTitle: String = "API", + parents: Seq[String] = Seq(projectName))(using ProjectContext) = + checkFile("api/index.html")( + title = mainTitle, + header = projectName, + parents = parents + ) + checkFile("api/tests/links.html")( + title = "tests.links", + header = "tests.links", + parents = parents :+ mainTitle + ) + checkFile("api/tests/links/LinksTest.html")( + title = "LinksTest", + header = "LinksTest", + parents = parents ++ Seq(mainTitle, "tests.links") + ) + + + @Test + def basicTest() = withGeneratedSite(testDocPath.resolve("basic")){ + testDocPages() + testDocIndexPage() + testMainIndexPage() + testApiPages() } + + @Test + def noGlobalIndexTest() = withGeneratedSite(testDocPath.resolve("noGlobalIndex")){ + testDocPages() + testDocIndexPage() + testApiPages() + } + + @Test + def noIndexesTest() = withGeneratedSite(testDocPath.resolve("noIndexes")){ + testDocPages() + testApiPages() + } + + @Test + def noExistingDocs() = withGeneratedSite(testDocPath.resolve("noExisting")){ + testApiPages(mainTitle = projectName, parents = Nil) + } \ No newline at end of file From cd01740fa7bbfb6d8ef8c9da6057fa77fb233be6 Mon Sep 17 00:00:00 2001 From: Krzysztof Romanowski Date: Tue, 19 Jan 2021 15:50:57 +0100 Subject: [PATCH 02/12] Adapt search to new renderer --- scala3doc-js/src/searchbar/PageEntry.scala | 18 +++--- .../searchbar/engine/SearchbarEngine.scala | 6 +- .../src/dotty/renderers/HtmlRenderer.scala | 11 ++-- scala3doc/src/dotty/renderers/Locations.scala | 6 +- scala3doc/src/dotty/renderers/Resources.scala | 60 ++++++++++++++++--- scala3doc/src/dotty/renderers/Writter.scala | 4 +- 6 files changed, 74 insertions(+), 31 deletions(-) diff --git a/scala3doc-js/src/searchbar/PageEntry.scala b/scala3doc-js/src/searchbar/PageEntry.scala index 046cf428ebf9..e67b61109efa 100644 --- a/scala3doc-js/src/searchbar/PageEntry.scala +++ b/scala3doc-js/src/searchbar/PageEntry.scala @@ -4,10 +4,10 @@ import scala.scalajs.js @js.native trait PageEntryJS extends js.Object { - val name: String = js.native - val description: String = js.native - val location: String = js.native - val searchKeys: js.Array[String] = js.native + val n: String = js.native + val t: String = js.native + val d: String = js.native + val l: String = js.native } case class PageEntry( @@ -23,10 +23,10 @@ object PageEntry { s.headOption.map(firstLetter => firstLetter.toString ++ s.tail.filter(_.isUpper)) def apply(jsObj: PageEntryJS): PageEntry = PageEntry( - jsObj.name, - jsObj.description, - jsObj.location, - jsObj.searchKeys.head.toLowerCase, - createAcronym(jsObj.searchKeys.head) + jsObj.t, + jsObj.d, + jsObj.l, + jsObj.n.toLowerCase, + createAcronym(jsObj.n) ) } diff --git a/scala3doc-js/src/searchbar/engine/SearchbarEngine.scala b/scala3doc-js/src/searchbar/engine/SearchbarEngine.scala index d0398f233ae9..4ee3d0f943a4 100644 --- a/scala3doc-js/src/searchbar/engine/SearchbarEngine.scala +++ b/scala3doc-js/src/searchbar/engine/SearchbarEngine.scala @@ -2,8 +2,8 @@ package dotty.dokka import math.Ordering.Implicits.seqOrdering -class SearchbarEngine(pages: List[PageEntry]) { - def query(query: List[Matchers]): List[PageEntry] = { +class SearchbarEngine(pages: List[PageEntry]): + def query(query: List[Matchers]): List[PageEntry] = pages .map( page => page -> query.map(matcher => matcher(page)) @@ -17,5 +17,3 @@ class SearchbarEngine(pages: List[PageEntry]) { .map { case (page, matchResults) => page } - } -} \ No newline at end of file diff --git a/scala3doc/src/dotty/renderers/HtmlRenderer.scala b/scala3doc/src/dotty/renderers/HtmlRenderer.scala index 9d88d271a535..07ecad169595 100644 --- a/scala3doc/src/dotty/renderers/HtmlRenderer.scala +++ b/scala3doc/src/dotty/renderers/HtmlRenderer.scala @@ -61,6 +61,8 @@ class HtmlRenderer(rootPackage: Member, val members: Map[DRI, Member], buildNode val hiddenPages: Seq[Page] = staticSite.toSeq.flatMap(c => c.orphanedTemplates.map(templateToPage(_, c))) + val allPages = navigablePage +: hiddenPages + def renderContent(page: Page) = page.content match case m: Member => val signatureRenderer = new SignatureRenderer: @@ -103,15 +105,14 @@ class HtmlRenderer(rootPackage: Member, val members: Map[DRI, Member], buildNode files.map(p => siteRoot.relativize(p).toString).toList } - val siteResourcesPaths = - (navigablePage +: hiddenPages).toSet.flatMap(specificResources) ++ siteImages + val siteResourcesPaths = allPages.toSet.flatMap(specificResources) ++ siteImages - val resources = siteResourcesPaths.toSeq.map(pathToResource) ++ allResources(rootPackage) + val resources = siteResourcesPaths.toSeq.map(pathToResource) ++ allResources(allPages) resources.flatMap(renderResource) def render(): Unit = val renderedResources = renderResources() - val sites = (navigablePage +: hiddenPages).map(renderPage(_, Vector.empty)) + val sites = allPages.map(renderPage(_, Vector.empty)) def mkHead(page: Page): AppliedTag = val resources = page.content match @@ -187,7 +188,7 @@ class HtmlRenderer(rootPackage: Member, val members: Map[DRI, Member], buildNode div (id := "leftToggler")( span(cls := "icon-toggler") ), - div(id := "searchBar"), + div(id := "scala3doc-searchBar"), main( div(id := "content")( parentsHtml, diff --git a/scala3doc/src/dotty/renderers/Locations.scala b/scala3doc/src/dotty/renderers/Locations.scala index c910c5e5861a..d7d9bbfd5529 100644 --- a/scala3doc/src/dotty/renderers/Locations.scala +++ b/scala3doc/src/dotty/renderers/Locations.scala @@ -71,12 +71,14 @@ trait Locations(using ctx: DocContext): pathTo(to.split("/").toList, from) def resolveRoot(dri: DRI, path: String): String = resolveRoot(rawLocation(dri), path) + def absolutePath(dri: DRI): String = rawLocation(dri).mkString("", "/", ".html") def resolveLink(dri: DRI, url: String): String = if URI(url).isAbsolute then url else resolveRoot(dri, url) - def pathToRoot(dri: DRI): String = - pathTo(rawLocation(dri), Nil).dropRight(1) + def pathToRoot(dri: DRI): String = rawLocation(dri).drop(1).map(_ => "..") match + case Nil => "" + case seq => seq.mkString("", "/", "/") def driExisits(dri: DRI) = true // TODO implement checks! diff --git a/scala3doc/src/dotty/renderers/Resources.scala b/scala3doc/src/dotty/renderers/Resources.scala index 1184e64e0b67..a3cba6518384 100644 --- a/scala3doc/src/dotty/renderers/Resources.scala +++ b/scala3doc/src/dotty/renderers/Resources.scala @@ -60,6 +60,7 @@ trait Resources(using ctx: DocContext) extends Locations, Writter: "styles/diagram.css", "styles/filter-bar.css", "styles/search-bar.css", + "styles/scala3doc-searchbar.css", "hljs/highlight.pack.js", "hljs/LICENSE", "scripts/hljs-scala3.js", @@ -71,6 +72,7 @@ trait Resources(using ctx: DocContext) extends Locations, Writter: "scripts/components/Input.js", "scripts/components/FilterGroup.js", "scripts/components/Filter.js", + "scripts/searchbar.js" ).map(dottyRes) val urls = List( @@ -82,18 +84,58 @@ trait Resources(using ctx: DocContext) extends Locations, Writter: fromResources ++ urls ++ projectLogo ++ Seq(scala3docVersionFile, dynamicJsData) - val memberResourcesPaths = memberResources.map(_.path) - - val scala3docLogo = dottyRes("images/scala3doc_logo.svg") - - def packageList(topLevelPackage: Member) = Resource.Text("scala3doc/package-list", "TODO") - - def allResources(topLevelPackage: Member): Seq[Resource] = memberResources ++ Seq( + val searchDataPath = "scripts/searchData.js" + val memberResourcesPaths = Seq(searchDataPath) ++ memberResources.map(_.path) + + case class PageEntry( + dri: DRI, + name: String, + text: String, + descr: String, + ): + // for jackson + def getL: String = absolutePath(dri) + def getN: String = name + def getT: String = text + def getD: String = descr + + def searchData(pages: Seq[Page]) = + def flattenToText(signature: Signature): String = + signature.map { + case Link(name, dri) => name + case s: String => s + }.mkString + + def processPage(page: Page): Seq[PageEntry] = + val res = page.content match + case m: Member => + val descr = m.dri.location.replace("/", ".") + def processMember(member: Member): Seq[PageEntry] = + val signatureBuilder = ScalaSignatureProvider.rawSignature(member, InlineSignatureBuilder()).asInstanceOf[InlineSignatureBuilder] + val sig = Signature(member.kind.name, " ") ++ Seq(Link(member.name, member.dri)) ++ signatureBuilder.names.reverse + val entry = PageEntry(member.dri, member.name, flattenToText(sig), descr) + val children = member + .membersBy(m => m.kind != dotty.dokka.model.api.Kind.Package && !m.kind.isInstanceOf[Classlike]) + .filter(m => m.origin == Origin.RegularlyDefined && m.inheritedFrom.isEmpty) + Seq(entry) ++ children.flatMap(processMember) + + processMember(m) + case _ => + Seq(PageEntry(page.link.dri, page.link.name, page.link.name, "")) + + res ++ page.children.flatMap(processPage) + + val entries = pages.flatMap(processPage).toArray + val entriesText = new ObjectMapper().writeValueAsString(entries) + Resource.Text(searchDataPath, s"pages = $entriesText;") + + + def allResources(pages: Seq[Page]): Seq[Resource] = memberResources ++ Seq( dottyRes("favicon.ico"), dottyRes("fonts/dotty-icons.woff"), dottyRes("fonts/dotty-icons.ttf"), - scala3docLogo, - packageList(topLevelPackage) + dottyRes("images/scala3doc_logo.svg"), + searchData(pages) ) def renderResource(resource: Resource): Seq[String] = diff --git a/scala3doc/src/dotty/renderers/Writter.scala b/scala3doc/src/dotty/renderers/Writter.scala index e3da480b75ee..2a96263afa18 100644 --- a/scala3doc/src/dotty/renderers/Writter.scala +++ b/scala3doc/src/dotty/renderers/Writter.scala @@ -17,8 +17,8 @@ trait Writter(using ctx: DocContext) extends Locations: if !Files.exists(absPath.getParent) then Files.createDirectories(absPath.getParent) absPath - def write(dri: DRI, content: AppliedTag, ext: String = ".html"): String = - val path = rawLocation(dri).mkString("", "/", ext) + def write(dri: DRI, content: AppliedTag): String = + val path = absolutePath(dri) Files.write(dest(path), content.toString.getBytes) path From 6f39e75fddc298be369bd4c1fc31631d0b7d66a3 Mon Sep 17 00:00:00 2001 From: Krzysztof Romanowski Date: Thu, 21 Jan 2021 00:10:18 +0100 Subject: [PATCH 03/12] Migrate away from dokka outside doc comments This is huge and scarry commit --- ...rg.jetbrains.dokka.plugability.DokkaPlugin | 1 - scala3doc/src/dotty/dokka/DRI.scala | 44 ++++ scala3doc/src/dotty/dokka/DocContext.scala | 35 +--- .../src/dotty/dokka/DottyDokkaPlugin.scala | 157 -------------- scala3doc/src/dotty/dokka/Main.scala | 3 - scala3doc/src/dotty/dokka/Scala3doc.scala | 22 +- scala3doc/src/dotty/dokka/Scala3docArgs.scala | 3 - .../src/dotty/dokka/ScalaModuleCreator.scala | 51 ++--- scala3doc/src/dotty/dokka/compat.scala | 72 +++---- .../ScalaExternalLocationProvider.scala | 57 ------ ...ScalaExternalLocationProviderFactory.scala | 17 -- .../location/ScalaPackageListService.scala | 46 ----- scala3doc/src/dotty/dokka/model/api/api.scala | 67 +++--- .../dokka/model/api/internalExtensions.scala | 133 ++---------- .../dotty/dokka/model/api/membersUtils.scala | 11 - scala3doc/src/dotty/dokka/model/extras.scala | 15 -- .../src/dotty/dokka/model/scalaModel.scala | 3 +- .../ScalaPackageListCreator.scala | 38 ---- .../src/dotty/dokka/site/LoadedTemplate.scala | 4 - .../dotty/dokka/site/NavigationCreator.scala | 66 ------ .../dokka/site/PartiallyRenderedContent.scala | 42 ---- .../dotty/dokka/site/SitePagesCreator.scala | 64 ------ .../src/dotty/dokka/site/StaticPageNode.scala | 40 ---- .../dotty/dokka/site/StaticSiteContext.scala | 67 +----- .../site/StaticSiteLocationProvider.scala | 164 --------------- scala3doc/src/dotty/dokka/site/common.scala | 57 +----- .../src/dotty/dokka/tasty/BasicSupport.scala | 1 - .../dotty/dokka/tasty/ClassLikeSupport.scala | 153 +++++--------- .../dotty/dokka/tasty/PackageSupport.scala | 7 +- scala3doc/src/dotty/dokka/tasty/SymOps.scala | 14 +- .../dotty/dokka/tasty/SyntheticSupport.scala | 4 +- .../src/dotty/dokka/tasty/TastyParser.scala | 14 +- .../src/dotty/dokka/tasty/TypesSupport.scala | 52 +++-- .../tasty/comments/MarkdownConverter.scala | 2 +- .../dokka/tasty/comments/wiki/Converter.scala | 2 +- .../ImplicitMembersExtensionTransformer.scala | 22 +- .../InheritanceInformationTransformer.scala | 15 +- .../transformers/ModuleTransformer.scala | 6 - .../dokka/translators/FilterAttributes.scala | 14 -- .../dokka/translators/ScalaPageCreator.scala | 74 ------- .../translators/ScalaSignatureProvider.scala | 26 +-- .../translators/ScalaSignatureUtils.scala | 10 +- scala3doc/src/dotty/dokka/utils.scala | 75 ------- .../renderers/DokkaScalaHtmlRenderer.scala | 135 ++---------- .../dotty/renderers/DotDiagramBuilder.scala | 3 - scala3doc/src/dotty/renderers/Locations.scala | 16 +- .../src/dotty/renderers/MemberRenderer.scala | 10 +- .../ScalaSearchbarDataInstaller.scala | 67 ------ ...ernalLocationProviderIntegrationTest.scala | 12 +- .../dokka/ExternalLocationProviderTest.scala | 104 +++++----- .../test/dotty/dokka/RaportingTest.scala | 2 +- scala3doc/test/dotty/dokka/ScaladocTest.scala | 64 ++---- .../test/dotty/dokka/SignatureTest.scala | 16 +- .../dotty/dokka/diagram/HierarchyTest.scala | 192 +++++++++--------- .../test/dotty/dokka/linking/DriTest.scala | 10 +- .../dotty/dokka/linking/DriTestCases.scala | 3 +- .../dotty/dokka/site/RelativeLinksTests.scala | 24 --- 57 files changed, 452 insertions(+), 1976 deletions(-) delete mode 100644 scala3doc/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin create mode 100644 scala3doc/src/dotty/dokka/DRI.scala delete mode 100644 scala3doc/src/dotty/dokka/DottyDokkaPlugin.scala delete mode 100644 scala3doc/src/dotty/dokka/location/ScalaExternalLocationProvider.scala delete mode 100644 scala3doc/src/dotty/dokka/location/ScalaExternalLocationProviderFactory.scala delete mode 100644 scala3doc/src/dotty/dokka/location/ScalaPackageListService.scala delete mode 100644 scala3doc/src/dotty/dokka/model/extras.scala delete mode 100644 scala3doc/src/dotty/dokka/preprocessors/ScalaPackageListCreator.scala delete mode 100644 scala3doc/src/dotty/dokka/site/NavigationCreator.scala delete mode 100644 scala3doc/src/dotty/dokka/site/PartiallyRenderedContent.scala delete mode 100644 scala3doc/src/dotty/dokka/site/SitePagesCreator.scala delete mode 100644 scala3doc/src/dotty/dokka/site/StaticPageNode.scala delete mode 100644 scala3doc/src/dotty/dokka/site/StaticSiteLocationProvider.scala delete mode 100644 scala3doc/src/dotty/dokka/transformers/ModuleTransformer.scala delete mode 100644 scala3doc/src/dotty/dokka/translators/ScalaPageCreator.scala delete mode 100644 scala3doc/src/dotty/dokka/utils.scala delete mode 100644 scala3doc/src/dotty/renderers/ScalaSearchbarDataInstaller.scala delete mode 100644 scala3doc/test/dotty/dokka/site/RelativeLinksTests.scala diff --git a/scala3doc/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin b/scala3doc/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin deleted file mode 100644 index 1c1de05b36b3..000000000000 --- a/scala3doc/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin +++ /dev/null @@ -1 +0,0 @@ -dotty.dokka.DottyDokkaPlugin \ No newline at end of file diff --git a/scala3doc/src/dotty/dokka/DRI.scala b/scala3doc/src/dotty/dokka/DRI.scala new file mode 100644 index 000000000000..2733a2920d77 --- /dev/null +++ b/scala3doc/src/dotty/dokka/DRI.scala @@ -0,0 +1,44 @@ +package dotty.dokka + +import java.nio.file.Path +import org.jetbrains.dokka.links.PointingToDeclaration + +val staticFileSymbolUUID = "___staticFile___" + +val topLevelDri = DRI("/") + +type DDRI = org.jetbrains.dokka.links.DRI + +// we may need target... +case class DRI( + location: String, + anchor: String = "", + origin: String = "", + symbolUUID: String = "" +): + def withNoOrigin = copy(origin = "") + + def isStaticFile = symbolUUID == staticFileSymbolUUID + + def asDokka: DDRI = new DDRI( + location, + anchor, + null, + PointingToDeclaration.INSTANCE, + origin + ":" + symbolUUID + ) + +object DRI: + def forPath(path: Path) = DRI(location = path.toString, symbolUUID = staticFileSymbolUUID) + +extension (dokkaDri: DDRI) + def asScala: DRI = + val elements = dokkaDri.getExtra.split(":") + val origin = elements.headOption.getOrElse("") + val symbolUUID = elements.drop(1).mkString(":") + DRI( + dokkaDri.getPackageName, + dokkaDri.getClassNames, + origin, + symbolUUID + ) \ No newline at end of file diff --git a/scala3doc/src/dotty/dokka/DocContext.scala b/scala3doc/src/dotty/dokka/DocContext.scala index 2031a0f6b987..9c1c78bbf76a 100644 --- a/scala3doc/src/dotty/dokka/DocContext.scala +++ b/scala3doc/src/dotty/dokka/DocContext.scala @@ -1,7 +1,6 @@ package dotty.dokka import org.jetbrains.dokka._ -import org.jetbrains.dokka.DokkaSourceSetImpl import org.jetbrains.dokka.plugability.DokkaContext import java.io.File import java.nio.file.Files @@ -80,7 +79,7 @@ case class DocContext(args: Scala3doc.Args, compilerContext: CompilerContext) override def getCacheRoot: File = null override def getOfflineMode: Boolean = false override def getFailOnWarning: Boolean = false - override def getSourceSets: JList[DokkaSourceSet] = JList(mkSourceSet) + override def getSourceSets: JList[DokkaSourceSet] = JNil override def getModules: JList[DokkaConfiguration.DokkaModuleDescription] = JNil override def getPluginsClasspath: JList[File] = JNil override def getModuleName(): String = "ModuleName" @@ -97,7 +96,6 @@ case class DocContext(args: Scala3doc.Args, compilerContext: CompilerContext) lazy val staticSiteContext = args.docsRoot.map(path => StaticSiteContext( File(path).getAbsoluteFile(), - Set(mkSourceSet.asInstanceOf[SourceSetWrapper]), args, sourceLinks )(using compilerContext)) @@ -105,33 +103,4 @@ case class DocContext(args: Scala3doc.Args, compilerContext: CompilerContext) val externalDocumentationLinks = args.externalMappings override def getPluginsConfiguration: JList[DokkaConfiguration.PluginConfiguration] = - JNil - - val mkSourceSet: DokkaSourceSet = - new DokkaSourceSetImpl( - /*displayName=*/ args.name, - /*sourceSetID=*/ new DokkaSourceSetID(args.name, "main"), - /*classpath=*/ JNil, - /*sourceRoots=*/ JSet(), - /*dependentSourceSets=*/ JSet(), - /*samples=*/ JSet(), - /*includes=*/ JSet(), - /*includeNonPublic=*/ true, - /* changed because of exception in reportUndocumentedTransformer - there's 'when' which doesnt match because it contains only KotlinVisbility cases */ - /*reportUndocumented=*/ false, - // Now all our packages are empty from dokka perspective - /*skipEmptyPackages=*/ false, - /*skipDeprecated=*/ true, - /*jdkVersion=*/ 8, - /*sourceLinks=*/ JSet(), - /*perPackageOptions=*/ JList(), - /*externalDocumentationLinks=*/ JSet(), - /*languageVersion=*/ null, - /*apiVersion=*/ null, - /*noStdlibLink=*/ true, - /*noJdkLink=*/ true, - /*suppressedFiles=*/ JSet(), - /*suppressedFiles=*/ Platform.jvm - ).asInstanceOf[DokkaSourceSet] // Why I do need to cast here? Kotlin magic? - - val sourceSet = mkSourceSet.asInstanceOf[SourceSetWrapper] + JNil \ No newline at end of file diff --git a/scala3doc/src/dotty/dokka/DottyDokkaPlugin.scala b/scala3doc/src/dotty/dokka/DottyDokkaPlugin.scala deleted file mode 100644 index f8dd57cf3048..000000000000 --- a/scala3doc/src/dotty/dokka/DottyDokkaPlugin.scala +++ /dev/null @@ -1,157 +0,0 @@ -package dotty.dokka - -import org.jetbrains.dokka.plugability._ -import org.jetbrains.dokka.transformers.sources._ -import org.jetbrains.dokka.transformers.documentation.PreMergeDocumentableTransformer -import org.jetbrains.dokka.transformers.pages.PageTransformer -import org.jetbrains.dokka.transformers.documentation.DocumentableToPageTranslator - -import org.jetbrains.dokka.DokkaConfiguration -import org.jetbrains.dokka.{ DokkaConfiguration$DokkaSourceSet => DokkaSourceSet } -import org.jetbrains.dokka.model._ -import org.jetbrains.dokka.model.doc._ -import org.jetbrains.dokka.base.parsers._ -import org.jetbrains.dokka.plugability.DokkaContext -import collection.JavaConverters._ -import org.jetbrains.dokka.model.properties.PropertyContainer -import dotty.dokka.tasty.DokkaTastyInspector -import org.jetbrains.dokka.base.transformers.pages.comments.CommentsToContentConverter -import org.jetbrains.dokka.utilities.DokkaLogger -import org.jetbrains.dokka.base.signatures.SignatureProvider -import org.jetbrains.dokka.pages._ -import dotty.dokka.model.api._ -import org.jetbrains.dokka.CoreExtensions -import org.jetbrains.dokka.base.DokkaBase -import org.jetbrains.dokka.base.resolvers.shared._ - -import dotty.dokka.site.NavigationCreator -import dotty.dokka.site.SitePagesCreator -import dotty.dokka.site.StaticSiteContext -import dotty.dokka.site.StaticSiteLocationProviderFactory - -/** Main Dokka plugin for the doctool. - * - * Wires together classes responsible for consuming Tasty and generating - * documentation. - * - * Most of the work of parsing Tasty is done by [[DokkaTastyInspector]]. - */ -class DottyDokkaPlugin extends DokkaJavaPlugin: - - lazy val dokkaBase = plugin(classOf[DokkaBase]) - - val provideMembers = extend( - _.extensionPoint(CoreExtensions.INSTANCE.getSourceToDocumentableTranslator) - .fromInstance(EmptyModuleProvider) - .overrideExtension(dokkaBase.getPsiToDocumentableTranslator) - ) - - // Just turn off another translator since multiple overrides does not work - val disableDescriptorTranslator = extend( - _.extensionPoint(CoreExtensions.INSTANCE.getSourceToDocumentableTranslator) - .fromRecipe { case ctx @ given DokkaContext => new ScalaModuleProvider } - .overrideExtension(dokkaBase.getDescriptorToDocumentableTranslator) - .name("disableDescriptorTranslator") - ) - - // Clean up empty module provided in disableDescriptorTranslator - val cleanUpEmptyModules = extend( - _.extensionPoint(CoreExtensions.INSTANCE.getPreMergeDocumentableTransformer) - .fromInstance(_.asScala.filterNot(_.getName.isEmpty).asJava) - .overrideExtension(dokkaBase.getModulesAndPackagesDocumentation) - ) - - val scalaDocumentableToPageTranslator = extend( - _.extensionPoint(CoreExtensions.INSTANCE.getDocumentableToPageTranslator) - .fromRecipe { case ctx @ given DokkaContext => - new DocumentableToPageTranslator { - override def invoke(module: DModule): ModulePageNode = ScalaPageCreator( - ctx.single(dokkaBase.getCommentsToContentConverter), - ctx.single(dokkaBase.getSignatureProvider) - ).pageForModule(module) - } - }.overrideExtension(dokkaBase.getDocumentableToPageTranslator) - ) - - val ourRenderer = extend( - _.extensionPoint(CoreExtensions.INSTANCE.getRenderer) - .fromRecipe { case ctx @ given DokkaContext => new DokkaScalaHtmlRenderer } - .overrideExtension(dokkaBase.getHtmlRenderer) - ) - - val commentsToContentConverter = extend( - _.extensionPoint(dokkaBase.getCommentsToContentConverter) - .fromInstance(ScalaCommentToContentConverter) - .overrideExtension(dokkaBase.getDocTagToContentConverter) - ) - - val customDocumentationProvider = extend( - _.extensionPoint(dokkaBase.getHtmlPreprocessors) - .fromRecipe{ case c @ given DokkaContext => new SitePagesCreator } - .name("customDocumentationProvider") - .ordered( - before = Seq( - dokkaBase.getNavigationPageInstaller, - dokkaBase.getScriptsInstaller, - dokkaBase.getStylesInstaller, - dokkaBase.getPackageListCreator, - ), - after = Seq(dokkaBase.getRootCreator) - ) - ) - - val customNavigation = extend( - _.extensionPoint(dokkaBase.getHtmlPreprocessors) - .fromRecipe{ case c @ given DokkaContext => new NavigationCreator } - .name("customNavigation") - .ordered( - before = Seq( - dokkaBase.getScriptsInstaller, - dokkaBase.getStylesInstaller, - ), - after = Seq(customDocumentationProvider.getValue) - ) - .overrideExtension(dokkaBase.getNavigationPageInstaller) - ) - - val locationProvider = extend( - _.extensionPoint(dokkaBase.getLocationProviderFactory) - .fromRecipe { case c @ given DokkaContext => new StaticSiteLocationProviderFactory } - .overrideExtension(dokkaBase.getLocationProvider) - ) - - val scalaPackageListCreator = extend( - _.extensionPoint(dokkaBase.getHtmlPreprocessors) - .fromRecipe(c => ScalaPackageListCreator(c, RecognizedLinkFormat.DokkaHtml)) - .overrideExtension(dokkaBase.getPackageListCreator) - .after( - customDocumentationProvider.getValue - ) - ) - - val scalaExternalLocationProviderFactory = extend( - _.extensionPoint(dokkaBase.getExternalLocationProviderFactory) - .fromRecipe{ case c @ given DokkaContext => new ScalaExternalLocationProviderFactory } - .overrideExtension(dokkaBase.getDokkaLocationProvider) - ) - -// TODO (https://github.com/lampepfl/scala3doc/issues/232): remove once problem is fixed in Dokka -extension [T] (builder: ExtensionBuilder[T]) - def ordered(before: Seq[Extension[_, _, _]], after: Seq[Extension[_, _, _]]): ExtensionBuilder[T] = - val byDsl = new OrderingKind.ByDsl(dsl => { - dsl.after(after:_*) - dsl.before(before:_*) - kotlin.Unit.INSTANCE // TODO why U does not work here? - }) - // Does not compile but compiles in scala 2 - // ExtensionBuilder.copy$default(builder, null, null, null, byDsl, null, null, 55, null) - val m = classOf[ExtensionBuilder[_]].getDeclaredMethods().find(_.getName == "copy$default").get - m.setAccessible(true) - // All nulls and 55 is taken from Kotlin bytecode and represent how defaut parameter are represented in Kotlin - // Defaut arguments are encoded by null and mapping that 55 represent whic arguments are actually provided - m.invoke(null, builder, null, null, null, byDsl, null, null, 55, null).asInstanceOf[ExtensionBuilder[T]] - - - def before(exts: Extension[_, _, _]*): ExtensionBuilder[T] = ordered(exts, Nil) - - def after(exts: Extension[_, _, _]*): ExtensionBuilder[T] = ordered(Nil, exts) diff --git a/scala3doc/src/dotty/dokka/Main.scala b/scala3doc/src/dotty/dokka/Main.scala index b8fba83599a0..8a79345b942c 100644 --- a/scala3doc/src/dotty/dokka/Main.scala +++ b/scala3doc/src/dotty/dokka/Main.scala @@ -1,8 +1,5 @@ package dotty.dokka -import org.jetbrains.dokka._ -import org.jetbrains.dokka.utilities._ -import org.jetbrains.dokka.plugability._ import java.util.ServiceLoader import java.io.File import java.util.jar._ diff --git a/scala3doc/src/dotty/dokka/Scala3doc.scala b/scala3doc/src/dotty/dokka/Scala3doc.scala index 55d548d38ef6..9098da6ce6dc 100644 --- a/scala3doc/src/dotty/dokka/Scala3doc.scala +++ b/scala3doc/src/dotty/dokka/Scala3doc.scala @@ -93,8 +93,22 @@ object Scala3doc: ctx.reporter - private [dokka] def run(args: Args)(using ctx: CompilerContext) = - val docContext = new DocContext(args, ctx) - new DokkaGenerator(docContext, docContext.logger).generate() - + private [dokka] def run(args: Args)(using ctx: CompilerContext): DocContext = + + given docContext: DocContext = new DocContext(args, ctx) + + val module = ScalaModuleProvider.mkModule() + given dokkaContext: DokkaContext = + DokkaContext.Companion.create(docContext, docContext.logger, JList()) + val dokkaRenderer = new DokkaScalaHtmlRenderer + + val renderer = new dotty.dokka.renderers.HtmlRenderer( + module.rootPackage, + module.members, + dri => c => dokkaRenderer.buildWithKotlinx(c, FakeContentPage(dri.asDokka, c), null) + ) + dokkaRenderer.init(renderer) + renderer.render() + report.inform("generation completed successfully") + docContext diff --git a/scala3doc/src/dotty/dokka/Scala3docArgs.scala b/scala3doc/src/dotty/dokka/Scala3docArgs.scala index 0012b886e2ec..e0c820a9f580 100644 --- a/scala3doc/src/dotty/dokka/Scala3docArgs.scala +++ b/scala3doc/src/dotty/dokka/Scala3docArgs.scala @@ -1,8 +1,5 @@ package dotty.dokka -import org.jetbrains.dokka._ -import org.jetbrains.dokka.utilities._ -import org.jetbrains.dokka.plugability._ import java.util.ServiceLoader import java.io.File import java.util.jar._ diff --git a/scala3doc/src/dotty/dokka/ScalaModuleCreator.scala b/scala3doc/src/dotty/dokka/ScalaModuleCreator.scala index 0e6d3c6925ac..601edb0d7c99 100644 --- a/scala3doc/src/dotty/dokka/ScalaModuleCreator.scala +++ b/scala3doc/src/dotty/dokka/ScalaModuleCreator.scala @@ -1,55 +1,28 @@ package dotty.dokka -import org.jetbrains.dokka.{ DokkaConfiguration$DokkaSourceSet => DokkaSourceSet } -import org.jetbrains.dokka.plugability.DokkaContext -import org.jetbrains.dokka.model.properties.PropertyContainer -import org.jetbrains.dokka.model.DModule -import org.jetbrains.dokka.transformers.sources.SourceToDocumentableTranslator - import dotty.dokka.tasty.DokkaTastyInspector -import org.jetbrains.dokka.pages.{Kind => _, _} import dotty.dokka.model.api._ -import org.jetbrains.dokka.model._ import org.jetbrains.dokka.base.parsers.MarkdownParser import collection.JavaConverters._ import kotlin.coroutines.Continuation -class ScalaModuleProvider(using ctx: DocContext) extends SourceToDocumentableTranslator: - override def invoke(sourceSet: DokkaSourceSet, cxt: DokkaContext, unused: Continuation[? >: DModule]) = +case class Module(rootPackage: Member, members: Map[DRI, Member]) + +object ScalaModuleProvider: + def mkModule()(using ctx: DocContext): Module = val (result, rootDoc) = DokkaTastyInspector(new MarkdownParser(_ => null)).result() - val (rootPck, rest) = result.partition(_.name == "") - val packageMembers = (rest ++ rootPck.flatMap(_.allMembers)).sortBy(_.name) + val (rootPck, rest) = result.partition(_.name == "API") + val packageMembers = (rest ++ rootPck.flatMap(_.members)).sortBy(_.name) - def flattenMember(m: Member): Seq[(DRI, Member)] = (m.dri -> m) +: m.allMembers.flatMap(flattenMember) + def flattenMember(m: Member): Seq[(DRI, Member)] = (m.dri -> m) +: m.members.flatMap(flattenMember) - val topLevelPackage = new DPackage( - DRI(location = ""), - JNil, - JNil, - JNil, - JNil, - JMap(), - null, - JSet(ctx.sourceSet), - PropertyContainer.Companion.empty() - ).withNewMembers(packageMembers).withKind(Kind.RootPackage).withDocs(rootDoc).asInstanceOf[DPackage] + val topLevelPackage = + Member("API", site.apiPageDRI, Kind.RootPackage, members = packageMembers, docs = rootDoc) + + val original = Module(topLevelPackage, flattenMember(topLevelPackage).toMap) val transformers = List( ImplicitMembersExtensionTransformer(), InheritanceInformationTransformer() ) - - val module = new DModule( - sourceSet.getDisplayName, - JList(topLevelPackage), - JMap(), - null, - sourceSet.toSet, - PropertyContainer.Companion.empty() plus ModuleExtension(flattenMember(topLevelPackage).toMap) - ) - - transformers.foldLeft(module)( (module, transformer) => transformer(module) ) - -object EmptyModuleProvider extends SourceToDocumentableTranslator: - override def invoke(sourceSet: DokkaSourceSet, cxt: DokkaContext, unused: Continuation[? >: DModule]) = - DModule("", JList(), Map.empty.asJava, null, Set(sourceSet).asJava, PropertyContainer.Companion.empty()) + transformers.foldLeft(original)((module, transformer) => transformer.apply(module)) diff --git a/scala3doc/src/dotty/dokka/compat.scala b/scala3doc/src/dotty/dokka/compat.scala index a2bc0953899c..9e981446d98a 100644 --- a/scala3doc/src/dotty/dokka/compat.scala +++ b/scala3doc/src/dotty/dokka/compat.scala @@ -1,6 +1,5 @@ package dotty.dokka -import org.jetbrains.dokka.links.PointingToDeclaration import org.jetbrains.dokka.DokkaConfiguration import org.jetbrains.dokka.DokkaConfiguration$DokkaSourceSet import collection.JavaConverters._ @@ -16,6 +15,8 @@ import org.jetbrains.dokka.plugability._ import kotlin.jvm.JvmClassMappingKt.getKotlinClass import org.jetbrains.dokka.links._ import java.nio.file.Path +import org.jetbrains.dokka.pages._ +import org.jetbrains.dokka.model.Documentable val U: kotlin.Unit = kotlin.Unit.INSTANCE @@ -35,46 +36,11 @@ def JNil[A] = emptyListInst.asInstanceOf[JList[A]] private val emptyMapInst = Collections.emptyMap def emptyJMap[A, B] = emptyMapInst.asInstanceOf[JMap[A, B]] -type DRI = org.jetbrains.dokka.links.DRI -val topLevelDri = org.jetbrains.dokka.links.DRI.Companion.getTopLevel - -extension (dri: DRI) - def withNoOrigin = dri._copy( - extra = Option(dri.getExtra).fold(null)(e => raw"\[origin:(.*)\]".r.replaceAllIn(e, "")) - ) - - def isStaticFile = dri.getExtra == staticFileExtra - - def location: String = dri.getPackageName - - def anchor: Option[String] = Option(dri.getClassNames).filterNot(_.isEmpty) - - def extra: String = dri.getExtra - - def target: DriTarget = dri.getTarget - - def _copy( - location: String = dri.location, - anchor: Option[String] = dri.anchor, - target: DriTarget = dri.target, - extra: String = dri.extra - ) = new DRI(location, anchor.getOrElse(""), null, target, extra) - -val staticFileExtra = "___staticFile___" - -object DRI: - def forPath(path: Path) = apply(location = path.toString, extra = staticFileExtra) - - def apply( - location: String = "", - anchor: Option[String] = None, - target: DriTarget = PointingToDeclaration.INSTANCE, - extra: String = "" - ) = new DRI(location, anchor.getOrElse(""), null, target, extra) - type SourceSetWrapper = DokkaConfiguration$DokkaSourceSet type DokkaSourceSet = DokkaConfiguration.DokkaSourceSet +type DocPart = org.jetbrains.dokka.model.doc.DocTag + extension [T] (wrapper: SourceSetWrapper) def toSet: JSet[DokkaConfiguration$DokkaSourceSet] = JSet(wrapper) def toMap(value: T): JMap[DokkaConfiguration$DokkaSourceSet, T] = JMap(wrapper -> value) @@ -109,14 +75,22 @@ extension [V](jset: JSet[V]) def ++ (other: JSet[V]): JSet[V] = Stream.of(jset, other).flatMap(_.stream).collect(Collectors.toSet()) -object PluginUtils: - import scala.reflect.ClassTag - import scala.reflect._ - def plugin[T <: DokkaPlugin: ClassTag](ctx: DokkaContext) = - ctx.plugin[T](getKotlinClass(implicitly[ClassTag[T]].runtimeClass.asInstanceOf[Class[T]])) - - def query[T <: DokkaPlugin: ClassTag, E](ctx: DokkaContext, queryFunction: (T) => ExtensionPoint[E]): List[E] = - ctx.get(queryFunction(plugin[T](ctx))).asScala.toList - - def querySingle[T <: DokkaPlugin: ClassTag, E](ctx: DokkaContext, queryFunction: (T) => ExtensionPoint[E]): E = - ctx.single(queryFunction(plugin[T](ctx))) +// Needed until we will migrate away from dokka + + +case class FakeContentPage( + dri: DRI, + override val getContent: ContentNode) extends ContentPage: + override val getName: String = "" + override val getChildren: JList[PageNode] = JList() + override val getEmbeddedResources: JList[String] = JList() + override def getDocumentable: Documentable = null + override def modified( + name: String, + content: ContentNode, + dri: JSet[DRI], + embeddedResources: JList[String], + children: JList[_ <: PageNode] + ): ContentPage = this + override def modified(name: String, children: JList[_ <: PageNode]): PageNode = this + override val getDri: JSet[DRI] = JSet(dri) diff --git a/scala3doc/src/dotty/dokka/location/ScalaExternalLocationProvider.scala b/scala3doc/src/dotty/dokka/location/ScalaExternalLocationProvider.scala deleted file mode 100644 index 01f19d1d6a8b..000000000000 --- a/scala3doc/src/dotty/dokka/location/ScalaExternalLocationProvider.scala +++ /dev/null @@ -1,57 +0,0 @@ -package dotty.dokka - -import org.jetbrains.dokka.base.resolvers.local._ -import org.jetbrains.dokka.base.DokkaBase -import org.jetbrains.dokka.base.resolvers.external._ -import org.jetbrains.dokka.base.resolvers.shared._ -import org.jetbrains.dokka.base.resolvers.anchors._ -import org.jetbrains.dokka.links.DRI -import org.jetbrains.dokka.model.DisplaySourceSet -import org.jetbrains.dokka.pages.RootPageNode -import dotty.dokka.model.api._ -import org.jetbrains.dokka.plugability._ -import collection.JavaConverters._ -import java.util.{Set => JSet} - - -class ScalaExternalLocationProvider( - externalDocumentation: ExternalDocumentation, - extension: String, - kind: DocumentationKind -) extends ExternalLocationProvider: - def docURL = externalDocumentation.getDocumentationURL.toString.stripSuffix("/") + "/" - override def resolve(dri: DRI): String = - Option(externalDocumentation.getPackageList).map(_.getLocations.asScala.toMap).flatMap(_.get(dri.toString)) - .fold(constructPath(dri))( l => { - this.docURL + l - } - ) - - private val originRegex = raw"\[origin:(.*)\]".r - - def constructPath(dri: DRI): String = kind match { - case DocumentationKind.Javadoc => constructPathForJavadoc(dri) - case DocumentationKind.Scaladoc => constructPathForScaladoc(dri) - case DocumentationKind.Scala3doc => constructPathForScala3doc(dri) - } - - //TODO #263: Add anchor support - - private def constructPathForJavadoc(dri: DRI): String = { - val location = "\\$+".r.replaceAllIn(dri.location.replace(".","/"), _ => ".") - val origin = originRegex.findFirstIn(dri.extra) - val anchor = dri.anchor - docURL + location + extension - } - - private def constructPathForScaladoc(dri: DRI): String = { - val location = dri.location.replace(".","/") - val anchor = dri.anchor - docURL + location + extension - } - - private def constructPathForScala3doc(dri: DRI): String = { - val location = dri.location.replace(".","/") - val anchor = dri.anchor - docURL + location + extension - } diff --git a/scala3doc/src/dotty/dokka/location/ScalaExternalLocationProviderFactory.scala b/scala3doc/src/dotty/dokka/location/ScalaExternalLocationProviderFactory.scala deleted file mode 100644 index d448e07089b2..000000000000 --- a/scala3doc/src/dotty/dokka/location/ScalaExternalLocationProviderFactory.scala +++ /dev/null @@ -1,17 +0,0 @@ -package dotty.dokka - -import org.jetbrains.dokka.base.resolvers.local._ -import org.jetbrains.dokka.base.DokkaBase -import org.jetbrains.dokka.base.resolvers.external._ -import org.jetbrains.dokka.base.resolvers.shared._ -import org.jetbrains.dokka.base.resolvers.anchors._ -import org.jetbrains.dokka.links.DRI -import org.jetbrains.dokka.model.DisplaySourceSet -import org.jetbrains.dokka.pages.RootPageNode -import org.jetbrains.dokka.plugability._ -import collection.JavaConverters._ -import java.util.{Set => JSet} - -class ScalaExternalLocationProviderFactory(using ctx: DokkaContext) extends ExternalLocationProviderFactory: - override def getExternalLocationProvider(doc: ExternalDocumentation): ExternalLocationProvider = - ScalaExternalLocationProvider(doc, ".html", DocumentationKind.Scala3doc) \ No newline at end of file diff --git a/scala3doc/src/dotty/dokka/location/ScalaPackageListService.scala b/scala3doc/src/dotty/dokka/location/ScalaPackageListService.scala deleted file mode 100644 index 7c73efbbd0ef..000000000000 --- a/scala3doc/src/dotty/dokka/location/ScalaPackageListService.scala +++ /dev/null @@ -1,46 +0,0 @@ -package dotty.dokka - -import org.jetbrains.dokka.base.renderers._ -import org.jetbrains.dokka.base.resolvers.local._ -import org.jetbrains.dokka.base._ -import org.jetbrains.dokka.links._ -import org.jetbrains.dokka.pages._ -import org.jetbrains.dokka.plugability._ -import collection.JavaConverters._ -import dotty.dokka.withNoOrigin - -object ScalaPackageListService: - val DOKKA_PARAM_PREFIX = "$dokka" - -class ScalaPackageListService(context: DokkaContext, rootPage: RootPageNode): - import ScalaPackageListService._ //Why I need to do this? - - val locationProvider = PluginUtils.querySingle[DokkaBase, LocationProviderFactory](context, _.getLocationProviderFactory) - .getLocationProvider(rootPage) - - def createPackageList(format: String, linkExtension: String): String = { - val packages = retrievePackageInfo(rootPage) - val relocations = getRelocations(rootPage) - s"$DOKKA_PARAM_PREFIX.format:$format\n" ++ - s"$DOKKA_PARAM_PREFIX.linkExtenstion:$linkExtension\n" ++ - relocations.map( (dri, link) => - s"$DOKKA_PARAM_PREFIX.location:${dri.withNoOrigin.toString}\u001f$link.$linkExtension" - ).mkString("","\n","\n") ++ - packages.mkString("","\n","\n") - } - - private def retrievePackageInfo(current: PageNode): Set[String] = current match { - case p: PackagePageNode => p.getChildren.asScala.toSet.flatMap(retrievePackageInfo) ++ Option(p.getDocumentable.getDri.getPackageName) - case other => other.getChildren.asScala.toSet.flatMap(retrievePackageInfo) - } - - private def getRelocations(current: PageNode): List[(DRI, String)] = current match { - case c: ContentPage => getRelocation(c.getDri.asScala.toList, c) ++ c.getChildren.asScala.toList.flatMap(getRelocations) - case other => other.getChildren.asScala.toList.flatMap(getRelocations) - } - - private def getRelocation(dris: List[DRI], node: ContentPage): List[(DRI, String)] = - val link = locationProvider.resolve(node, rootPage, true) - dris.map( dri => - if locationProvider.expectedLocationForDri(dri) != link then Some(dri, link) else None - ).flatten \ No newline at end of file diff --git a/scala3doc/src/dotty/dokka/model/api/api.scala b/scala3doc/src/dotty/dokka/model/api/api.scala index a1af1152a7e2..b5a1bc45e38f 100644 --- a/scala3doc/src/dotty/dokka/model/api/api.scala +++ b/scala3doc/src/dotty/dokka/model/api/api.scala @@ -2,12 +2,6 @@ package dotty.dokka package model package api -import org.jetbrains.dokka.DokkaConfiguration$DokkaSourceSet -import org.jetbrains.dokka.model._ -import collection.JavaConverters._ -import org.jetbrains.dokka.model.doc._ -import org.jetbrains.dokka.model.properties._ -import org.jetbrains.dokka.pages._ import dotty.dokka.tasty.comments.Comment enum Visibility(val name: String): @@ -146,49 +140,38 @@ object HierarchyGraph: def withEdges(edges: Seq[(LinkToType, LinkToType)]) = HierarchyGraph.empty ++ edges -type Member = Documentable +case class Member( + name: String, + dri: DRI, + kind: Kind, + visibility: Visibility = Visibility.Unrestricted, + modifiers: Seq[dotty.dokka.model.api.Modifier] = Nil, + annotations: List[Annotation] = Nil, + signature: Signature = Signature(), + sources: Option[TastyDocumentableSource] = None, + origin: Origin = Origin.RegularlyDefined, + inheritedFrom: Option[InheritedFrom] = None, + graph: HierarchyGraph = HierarchyGraph.empty, + docs: Option[Comment] = None, + members : Seq[Member] = Nil, + directParents: Seq[LinkToType] = Nil, + parents: Seq[LinkToType] = Nil, + knownChildren: Seq[LinkToType] = Nil, + companion: Option[DRI] = None, +) object Member: - def unapply(d: Documentable): Option[(String, DRI, Visibility, Kind, Origin)] = - d.memberExt.map(v => (d.getName, d.getDri, v.visibility, v.kind, v.origin)) + def unapply(v: Member): Option[(String, DRI, Visibility, Kind, Origin)] = + Some((v.name, v.dri, v.visibility, v.kind, v.origin)) extension[T] (member: Member) + def asLink: LinkToType = LinkToType(member.signature, member.dri, member.kind) + def deprecated: Option[Annotation] = + member.annotations.find(_.dri.location == "scala.deprecated") - private[api] def memberExt = MemberExtension.getFrom(member) - - private[api] def compositeMemberExt = CompositeMemberExtension.getFrom(member) - - def visibility: Visibility = memberExt.fold(Visibility.Unrestricted)(_.visibility) - - def signature: Signature = memberExt.fold(Signature(name))(_.signature) - def asLink: LinkToType = LinkToType(signature, dri, kind) - def deprecated: Option[Annotation] = memberExt.flatMap(_.annotations.find(a => a.dri.getPackageName == "scala" && a.dri.getClassNames == "deprecated")) - - def modifiers: Seq[dotty.dokka.model.api.Modifier] = memberExt.fold(Nil)(_.modifiers) - def kind: Kind = memberExt.fold(Kind.Unknown)(_.kind) - def origin: Origin = memberExt.fold(Origin.RegularlyDefined)(_.origin) - def inheritedFrom: Option[InheritedFrom] = memberExt.fold(None)(_.inheritedFrom) - def annotations: List[Annotation] = memberExt.fold(Nil)(_.annotations) - def sources: Option[TastyDocumentableSource] = memberExt.fold(None)(_.sources) - def docs: Option[Comment] = memberExt.fold(None)(_.rawDoc) - def name = member.getName - def dri = member.getDri - - // TODO rename parent and knownChildren - def allMembers: Seq[Member] = compositeMemberExt.fold(Nil)(_.members) - def parents: Seq[LinkToType] = compositeMemberExt.fold(Nil)(_.parents) - def directParents: Seq[LinkToType] = compositeMemberExt.fold(Nil)(_.directParents) - def knownChildren: Seq[LinkToType] = compositeMemberExt.fold(Nil)(_.knownChildren) - def companion: Option[DRI] = compositeMemberExt.fold(None)(_.companion) - - def membersBy(op: Member => Boolean): Seq[Member] = allMembers.filter(op) + def membersBy(op: Member => Boolean): Seq[Member] = member.members.filter(op) extension (members: Seq[Member]) def byInheritance = members.partition(_.inheritedFrom.isEmpty) -extension (module: DModule) - def driMap: Map[DRI, Member] = ModuleExtension.getFrom(module).fold(Map.empty)(_.driMap) - case class TastyDocumentableSource(val path: String, val lineNumber: Int) - -type DocPart = DocTag diff --git a/scala3doc/src/dotty/dokka/model/api/internalExtensions.scala b/scala3doc/src/dotty/dokka/model/api/internalExtensions.scala index b667eb142490..df56e9f5a8af 100644 --- a/scala3doc/src/dotty/dokka/model/api/internalExtensions.scala +++ b/scala3doc/src/dotty/dokka/model/api/internalExtensions.scala @@ -2,133 +2,40 @@ package dotty.dokka package model package api -import org.jetbrains.dokka.DokkaConfiguration$DokkaSourceSet -import org.jetbrains.dokka.model.{Projection => JProjection} -import org.jetbrains.dokka.model.Documentable -import org.jetbrains.dokka.model.DFunction -import org.jetbrains.dokka.model.DClass -import org.jetbrains.dokka.model.DocumentableSource -import org.jetbrains.dokka.model.Dynamic -import org.jetbrains.dokka.model.Bound -import org.jetbrains.dokka.model.TypeConstructor -import org.jetbrains.dokka.model.TypeParameter -import org.jetbrains.dokka.model.UnresolvedBound -import org.jetbrains.dokka.model.DPackage -import org.jetbrains.dokka.model.DModule - -import collection.JavaConverters._ -import org.jetbrains.dokka.model.doc.DocumentationNode -import org.jetbrains.dokka.model.properties._ -import dotty.dokka.tasty.comments.Comment - -case class MemberExtension( - visibility: Visibility, - modifiers: Seq[dotty.dokka.model.api.Modifier], - kind: Kind, - annotations: List[Annotation], - signature: Signature, - sources: Option[TastyDocumentableSource] = None, - origin: Origin = Origin.RegularlyDefined, - inheritedFrom: Option[InheritedFrom] = None, - graph: HierarchyGraph = HierarchyGraph.empty, - rawDoc: Option[Comment] = None -) extends ExtraProperty[Documentable]: - override def getKey = MemberExtension - -object MemberExtension extends BaseKey[Documentable, MemberExtension]: - val empty = MemberExtension(Visibility.Unrestricted, Nil, Kind.Unknown, Nil, Signature(), None) - -case class CompositeMemberExtension( - members : Seq[Member] = Nil, - directParents: Seq[LinkToType] = Nil, - parents: Seq[LinkToType] = Nil, - knownChildren: Seq[LinkToType] = Nil, - companion: Option[DRI] = None, -) extends ExtraProperty[Documentable]: - override def getKey = CompositeMemberExtension - -object CompositeMemberExtension extends BaseKey[Documentable, CompositeMemberExtension]: - val empty = CompositeMemberExtension() - - override def mergeStrategyFor(left: CompositeMemberExtension, right: CompositeMemberExtension) = - new MergeStrategy$Replace(left.copy(members = left.members ++ right.members)) - .asInstanceOf[MergeStrategy[Documentable]] - extension (member: Member) - private def putInMember(ext: MemberExtension) = - val memberWithExtra = member.asInstanceOf[WithExtraProperties[Member]] - memberWithExtra.withNewExtras(memberWithExtra.getExtra plus ext).asInstanceOf[Member] + def withMembers(newMembers: Seq[Member]): Member = member.copy(members = newMembers) - private def putInCompositeMember(ext: CompositeMemberExtension) = - val memberWithExtra = member.asInstanceOf[WithExtraProperties[Member]] - memberWithExtra.withNewExtras(memberWithExtra.getExtra plus ext).asInstanceOf[Member] - - def copy(modifiers: Seq[Modifier]): Member = - val ext = MemberExtension.getFrom(member).getOrElse(MemberExtension.empty).copy(modifiers = modifiers) - putInMember(ext) + def updateRecusivly(op: Member => Member): Member = + val newMembers = member.members.map(_.updateRecusivly(op)) + op(member).withMembers(newMembers) - def withOrigin(origin: Origin): Member = - val ext = MemberExtension.getFrom(member).getOrElse(MemberExtension.empty).copy(origin = origin) - putInMember(ext) - def withInheritedFrom(inheritedFrom: InheritedFrom): Member = - val ext = MemberExtension.getFrom(member).getOrElse(MemberExtension.empty).copy(inheritedFrom = Some(inheritedFrom)) - putInMember(ext) + def withOrigin(origin: Origin): Member = member.copy(origin = origin) - def withKind(kind: Kind): Member = - val ext = MemberExtension.getFrom(member).getOrElse(MemberExtension.empty).copy(kind = kind) - putInMember(ext) - def withDocs(docs: Option[Comment]): Member = - val ext = MemberExtension.getFrom(member).getOrElse(MemberExtension.empty).copy(rawDoc = docs) - putInMember(ext) + def withKind(kind: Kind): Member = member.copy(kind = kind) - def withMembers(newMembers: Seq[Member]): Member = - val original = member.compositeMemberExt.getOrElse(CompositeMemberExtension()) - val newExt = original.copy(members = newMembers) - putInCompositeMember(newExt) def withNewMembers(newMembers: Seq[Member]): Member = - val original = member.compositeMemberExt.getOrElse(CompositeMemberExtension()) - val newExt = original.copy(members = original.members ++ newMembers) - putInCompositeMember(newExt) + member.copy(members = member.members ++ newMembers) def withKnownChildren(knownChildren: Seq[LinkToType]): Member = - val original = member.compositeMemberExt.getOrElse(CompositeMemberExtension()) - val newExt = original.copy(knownChildren = knownChildren) - putInCompositeMember(newExt) + member.copy(knownChildren = knownChildren) def withNewGraphEdges(edges: Seq[(LinkToType, LinkToType)]): Member = - val oldExt = MemberExtension.getFrom(member).getOrElse(MemberExtension.empty) - val newExt = oldExt.copy(graph = oldExt.graph ++ edges) - putInMember(newExt) - - def updateRecusivly(op: Member => Member): Member = - val newMembers = member.allMembers.map(_.updateRecusivly(op)) - op(member).withMembers(newMembers) + member.copy(graph = member.graph ++ edges) -extension (bound: Bound) - def asSignature: Signature = bound match - case tc: TypeConstructor => - tc.getProjections.asScala.toSeq.map { - case txt: UnresolvedBound => txt.getName - case link: TypeParameter => - Link(link.getName, link.getDri) - } +extension (m: Module) + def updatePackages(op: Seq[Member] => Seq[Member]): Module = + val newRoot = m.rootPackage.withMembers(op(m.rootPackage.members)) + m.copy(rootPackage = newRoot) -extension (m: DModule) - def updatePackages(op: Seq[Member] => Seq[Member]): DModule = - val topLevelPck = m.getPackages.get(0) - val newRoot = topLevelPck.withMembers(op(topLevelPck.allMembers)) + def updateMembers(op: Member => Member): Module = + updatePackages(_.map(p => p.updateRecusivly(op))) - m.copy( - m.getName, - JList(newRoot.asInstanceOf[DPackage]), - m.getDocumentation, - m.getExpectPresentInSet, - m.getSourceSets, - m.getExtra - ) + def visitMembers(callback: Member => Unit): Unit = + def visitClasslike(c: Member): Unit = + callback(c) + c.members.foreach(visitClasslike(_)) - def updateMembers(op: Member => Member): DModule = - updatePackages(_.map(p => p.updateRecusivly(op))) + visitClasslike(m.rootPackage) diff --git a/scala3doc/src/dotty/dokka/model/api/membersUtils.scala b/scala3doc/src/dotty/dokka/model/api/membersUtils.scala index e13048bb1c95..7a7d7882173f 100644 --- a/scala3doc/src/dotty/dokka/model/api/membersUtils.scala +++ b/scala3doc/src/dotty/dokka/model/api/membersUtils.scala @@ -1,16 +1,5 @@ package dotty.dokka.model.api -import org.jetbrains.dokka.model.DModule -import scala.jdk.CollectionConverters.{ListHasAsScala, SeqHasAsJava} -import dotty.dokka.model.api._ - -extension (m: DModule) - def visitMembers(callback: Member => Unit): Unit = - def visitClasslike(c: Member): Unit = - callback(c) - c.allMembers.foreach(visitClasslike(_)) - m.getPackages.asScala.foreach(_.allMembers.foreach(visitClasslike(_))) - extension (s: Signature) def getName: String = s.map { diff --git a/scala3doc/src/dotty/dokka/model/extras.scala b/scala3doc/src/dotty/dokka/model/extras.scala deleted file mode 100644 index 1a25833253da..000000000000 --- a/scala3doc/src/dotty/dokka/model/extras.scala +++ /dev/null @@ -1,15 +0,0 @@ -package dotty.dokka - -import org.jetbrains.dokka.DokkaConfiguration$DokkaSourceSet -import org.jetbrains.dokka.model.{Projection => JProjection} -import org.jetbrains.dokka.model._ -import org.jetbrains.dokka.pages._ -import collection.JavaConverters._ -import org.jetbrains.dokka.model.doc._ -import org.jetbrains.dokka.model.properties._ -import dotty.dokka.model.api._ - -case class ModuleExtension(driMap: Map[DRI, Member]) extends ExtraProperty[DModule]: - override def getKey = ModuleExtension - -object ModuleExtension extends BaseKey[DModule, ModuleExtension] diff --git a/scala3doc/src/dotty/dokka/model/scalaModel.scala b/scala3doc/src/dotty/dokka/model/scalaModel.scala index 343896407207..f4a5a38d250e 100644 --- a/scala3doc/src/dotty/dokka/model/scalaModel.scala +++ b/scala3doc/src/dotty/dokka/model/scalaModel.scala @@ -51,8 +51,7 @@ object ScalaTagWrapper { } case class ImplicitConversion(conversion: Documentable, from: DRI, to: DRI) - - +// TODO clear it out case class ContentNodeParams( val dci: DCI, val sourceSets: java.util.Set[DisplaySourceSet], diff --git a/scala3doc/src/dotty/dokka/preprocessors/ScalaPackageListCreator.scala b/scala3doc/src/dotty/dokka/preprocessors/ScalaPackageListCreator.scala deleted file mode 100644 index 7823be2d4be5..000000000000 --- a/scala3doc/src/dotty/dokka/preprocessors/ScalaPackageListCreator.scala +++ /dev/null @@ -1,38 +0,0 @@ -package dotty.dokka - -import org.jetbrains.dokka.transformers.pages.{PageTransformer} -import org.jetbrains.dokka.base.renderers._ -import org.jetbrains.dokka.base.resolvers.local._ -import org.jetbrains.dokka.base.resolvers.shared._ -import org.jetbrains.dokka.base._ -import org.jetbrains.dokka.links._ -import org.jetbrains.dokka.pages._ -import org.jetbrains.dokka.plugability._ -import collection.JavaConverters._ - -class ScalaPackageListCreator( - context: DokkaContext, - format: LinkFormat, - outputFileNames: List[String] = List("package-list"), - removeModulePrefix: Boolean = true -) extends PageTransformer: - override def invoke(input: RootPageNode) = { - input.modified(input.getName, (input.getChildren.asScala ++ packageList(input)).asJava) - } - - private def processPage(page: PageNode, input: RootPageNode): PageNode = page - - private def packageList(rootPageNode: RootPageNode): List[RendererSpecificPage] = { - val content = ScalaPackageListService(context, rootPageNode).createPackageList( - format.getFormatName, - format.getLinkExtension - ) - outputFileNames.map( name => { - RendererSpecificResourcePage( - s"${rootPageNode.getName}/${name}", - JList(), - RenderingStrategy.Write(content) - ) - } - ) - } \ No newline at end of file diff --git a/scala3doc/src/dotty/dokka/site/LoadedTemplate.scala b/scala3doc/src/dotty/dokka/site/LoadedTemplate.scala index c9f7fa289047..a82ba5eeb7ea 100644 --- a/scala3doc/src/dotty/dokka/site/LoadedTemplate.scala +++ b/scala3doc/src/dotty/dokka/site/LoadedTemplate.scala @@ -5,10 +5,6 @@ import java.io.File import java.nio.file.Files import java.nio.file.Paths -import org.jetbrains.dokka.base.renderers.html.{NavigationNode, NavigationPage} -import org.jetbrains.dokka.model.Documentable -import org.jetbrains.dokka.pages._ -import org.jetbrains.dokka.transformers.pages.PageTransformer import org.jsoup.Jsoup import scala.collection.JavaConverters._ diff --git a/scala3doc/src/dotty/dokka/site/NavigationCreator.scala b/scala3doc/src/dotty/dokka/site/NavigationCreator.scala deleted file mode 100644 index 6aa7fd5795eb..000000000000 --- a/scala3doc/src/dotty/dokka/site/NavigationCreator.scala +++ /dev/null @@ -1,66 +0,0 @@ -package dotty.dokka -package site - -import org.jetbrains.dokka.base.renderers.html.NavigationPage -import org.jetbrains.dokka.model.DModule -import org.jetbrains.dokka.pages.{Kind => _, _} -import org.jetbrains.dokka.transformers.pages.PageTransformer -import dotty.dokka.model.api._ - -import scala.collection.JavaConverters._ - -class NavigationCreator(using ctx: DocContext) extends PageTransformer: - - private def processApiPages(pages: List[PageNode]): List[NavigationNode] = - def flatMapPackages(pn: PageNode): List[NavigationNode] = - def processChildren = pn.getChildren.asScala.flatMap(flatMapPackages).toList - pn match - case cp: ContentPage => cp.getDocumentable match - case null => - processChildren - case m: Member if m.kind == Kind.Package => - val ss = m.getSourceSets.asScala.toSet.toDisplay - List(new NavigationNode(m.name, m.dri, Nil)) ++ processChildren - case _: DModule => - processChildren - case _ => - Nil - case _ => - Nil - - pages.flatMap(flatMapPackages).sortBy(_.name) - - private def processStaticPages(input: PageNode)(staticSiteContext: StaticSiteContext) = - def toNavigationNode(page: StaticPageNode): NavigationNode = NavigationNode( - page.title(), - page.getDri.asScala.head, - page.getChildren.asScala.collect { case p: StaticPageNode => toNavigationNode(p) }.toList - ) - - def singleContentPage(p: PageNode) = - p.getChildren.asScala.collectFirst { case c: ContentPage => c }.get - val pageRoot = singleContentPage(input) - val docsRoot = - if !pageRoot.getDri.contains(topLevelDri) then pageRoot - else singleContentPage(pageRoot) - val apiPages = docsRoot.getChildren.asScala.filterNot(_.isInstanceOf[StaticPageNode]) - val staticPages = staticSiteContext.mainPages.map(toNavigationNode).toList - val apiNodes = processApiPages(apiPages.toList) - staticPages ++ List(NavigationNode("API", apiPageDRI, apiNodes)) - - private def emptyNavigationJson = - val strategy = new RenderingStrategy.Write("[]") - new RendererSpecificResourcePage("scripts/navigation-pane.json", JList(), strategy) - - final override def invoke(input: RootPageNode): RootPageNode = - def defaultApiPages = processApiPages(input.getChildren.asScala.toList) - val nodes = ctx.staticSiteContext.fold(defaultApiPages)(processStaticPages(input)) - - summon[DocContext].navigationNode = Some(NavigationNode( - ctx.args.name, - ctx.staticSiteContext.fold(topLevelDri)(_ => docsRootDRI), - nodes - )) - - val newChildren = input.getChildren ++ JList(emptyNavigationJson) - input.modified(input.getName, newChildren) \ No newline at end of file diff --git a/scala3doc/src/dotty/dokka/site/PartiallyRenderedContent.scala b/scala3doc/src/dotty/dokka/site/PartiallyRenderedContent.scala deleted file mode 100644 index a0b6a3e45349..000000000000 --- a/scala3doc/src/dotty/dokka/site/PartiallyRenderedContent.scala +++ /dev/null @@ -1,42 +0,0 @@ -package dotty.dokka -package site - -import org.jetbrains.dokka.model.DisplaySourceSet -import org.jetbrains.dokka.model.properties.PropertyContainer -import org.jetbrains.dokka.pages.{ContentNode, DCI, Style} -import org.jetbrains.dokka.base.resolvers.local.LocationProvider -import com.vladsch.flexmark.convert.html.FlexmarkHtmlParser -import org.jsoup.Jsoup -import scala.collection.JavaConverters._ -import scala.util.Try -import java.net.URL - -case class PartiallyRenderedContent( - template: LoadedTemplate, - context: StaticSiteContext, - override val getChildren: JList[ContentNode], - override val getDci: DCI, - override val getSourceSets: JSet[DisplaySourceSet], - override val getStyle: JSet[Style] = JSet(), - override val getExtra: PropertyContainer[ContentNode] = new PropertyContainer(JMap()) -) extends ContentNode: - override def hasAnyContent(): Boolean = true - - override def withNewExtras(newExtras: PropertyContainer[ContentNode]): ContentNode = - copy(getExtra = newExtras) - - override def withSourceSets(sourceSets: JSet[DisplaySourceSet]): ContentNode = - copy(getSourceSets = sourceSets) - - lazy val resolved = template.resolveToHtml(context) - - def procsesHtml(linkTo: String => String, absoluteResource: String => String): String = - val document = Jsoup.parse(resolved.code) - document.select("a").forEach(element => element.attr("href", linkTo(element.attr("href")))) - document.select("img").forEach { element => - val link = element.attr("src") - Try(new URL(link)).getOrElse { - if(link.startsWith("/")) element.attr("src", absoluteResource(link.drop(1))) - } - }// forrach does not work here - document.outerHtml() diff --git a/scala3doc/src/dotty/dokka/site/SitePagesCreator.scala b/scala3doc/src/dotty/dokka/site/SitePagesCreator.scala deleted file mode 100644 index 282406ec3054..000000000000 --- a/scala3doc/src/dotty/dokka/site/SitePagesCreator.scala +++ /dev/null @@ -1,64 +0,0 @@ -package dotty.dokka -package site - -import java.io.File -import java.nio.file.Files -import java.nio.file.FileVisitOption - -import org.jetbrains.dokka.model.Documentable -import org.jetbrains.dokka.pages._ - -import scala.collection.JavaConverters._ - -import dotty.dokka.model.api._ - -class SitePagesCreator(using ctx: DocContext) extends BaseStaticSiteProcessor: - private def mkRootPage(input: RootPageNode, children: List[PageNode]): AContentPage = - input match - case input: ContentPage => - AContentPage( - input.getName, - children.asJava, - input.getContent, - JSet(apiPageDRI), - input.getEmbeddedResources - ) - case _: RendererSpecificRootPage => - children.filter(_.isInstanceOf[RootPageNode]) match - case List(nestedRoot: RootPageNode) => - mkRootPage(nestedRoot, children.filter { _ != nestedRoot } ++ - nestedRoot.getChildren.asScala) - case other => - throw new RuntimeException(s"Expected single nested roor but get: $other") - - case _ => throw new RuntimeException(s"UNSUPPORTED! ${input.getClass.getName}") - - - override def transform(input: RootPageNode, ctx: StaticSiteContext): RootPageNode = - val (contentPage, others) = - input.getChildren.asScala.toList.partition { _.isInstanceOf[ContentPage] } - - val apiRoot = mkRootPage(input, contentPage) - val (indexes, children) = ctx.allPages.partition(f => - f.template.isIndexPage() && f.template.file.toPath.getParent() == ctx.docsPath ) - - if (indexes.size > 1) - val msg = s"ERROR: Multiple index pages for doc found ${indexes.map(_.template.file)}" - report.error(msg) - - def emptyContent = ctx.asContent(Text(), DRI(extra = "root_content")).get(0) - - val root = ctx.indexPage().toList.map(_.copy(getDri = JSet(docsRootDRI))) - val docsRoot = AContentPage( - ctx.args.name, - (List(apiRoot.modified("API", apiRoot.getChildren)) ++ children ++ root).asJava, - indexes.headOption.fold(emptyContent)(_.getContent), - JSet(docsDRI), - JList() - ) - - new RendererSpecificRootPage( - apiRoot.getName, - (List(docsRoot) ++ others).asJava, - RenderingStrategy.DoNothing.INSTANCE - ) diff --git a/scala3doc/src/dotty/dokka/site/StaticPageNode.scala b/scala3doc/src/dotty/dokka/site/StaticPageNode.scala deleted file mode 100644 index dc3f837a3b11..000000000000 --- a/scala3doc/src/dotty/dokka/site/StaticPageNode.scala +++ /dev/null @@ -1,40 +0,0 @@ -package dotty.dokka -package site - -import java.io.File -import java.nio.file.Files - -import org.jetbrains.dokka.base.renderers.html.{NavigationNode, NavigationPage} -import org.jetbrains.dokka.model.Documentable -import org.jetbrains.dokka.pages._ -import org.jetbrains.dokka.transformers.pages.PageTransformer - -case class StaticPageNode( - template: TemplateFile, - override val getName: String, - override val getContent: ContentNode, - override val getDri: JSet[DRI], - override val getEmbeddedResources: JList[String], - override val getChildren: JList[PageNode], - ) extends ContentPage: - override def getDocumentable: Documentable = null - - def title(): String = template.title - def hasFrame(): Boolean = template.hasFrame - - def dri = getDri.iterator.next() - - override def modified( - name: String, - content: ContentNode, - dri: JSet[DRI], - embeddedResources: JList[String], - children: JList[_ <: PageNode]): ContentPage = - copy(template, name, content, dri, embeddedResources, children.asInstanceOf[JList[PageNode]]) - - override def modified(name: String, children: JList[_ <: PageNode]): PageNode = - copy(getName = name, getChildren = children.asInstanceOf[JList[PageNode]]) - - def resources(): List[String] = getContent match - case p: PartiallyRenderedContent => p.resolved.resources - case _ => Nil diff --git a/scala3doc/src/dotty/dokka/site/StaticSiteContext.scala b/scala3doc/src/dotty/dokka/site/StaticSiteContext.scala index cc44646b8b68..246d5c33fe31 100644 --- a/scala3doc/src/dotty/dokka/site/StaticSiteContext.scala +++ b/scala3doc/src/dotty/dokka/site/StaticSiteContext.scala @@ -7,15 +7,6 @@ import java.nio.file.FileVisitOption import java.nio.file.Path import java.nio.file.Paths -import org.jetbrains.dokka.base.parsers.MarkdownParser -import org.jetbrains.dokka.base.transformers.pages.comments.DocTagToContentConverter -import org.jetbrains.dokka.DokkaConfiguration -import org.jetbrains.dokka.model.doc.{DocTag, Text} -import org.jetbrains.dokka.model.properties.PropertyContainer -import org.jetbrains.dokka.pages.{ContentKind, ContentNode, DCI, PageNode} -import org.jetbrains.dokka.plugability.DokkaContext -import org.jetbrains.dokka.pages.Style -import org.jetbrains.dokka.model.DisplaySourceSet import util.Try import collection.JavaConverters._ @@ -23,8 +14,8 @@ import dotty.dokka.model.api._ class StaticSiteContext( val root: File, - sourceSets: Set[SourceSetWrapper], - val args: Scala3doc.Args, val sourceLinks: SourceLinks)(using val outerCtx: CompilerContext): + val args: Scala3doc.Args, + val sourceLinks: SourceLinks)(using val outerCtx: CompilerContext): var memberLinkResolver: String => Option[DRI] = _ => None @@ -37,8 +28,6 @@ class StaticSiteContext( files.flatMap(loadTemplate(_, isBlog = false)).take(1) - def indexPage(): Option[StaticPageNode] = indexTemplate().headOption.map(templateToPage) - lazy val layouts: Map[String, TemplateFile] = val layoutRoot = new File(root, "_layouts") val dirs: Array[File] = Option(layoutRoot.listFiles()).getOrElse(Array()) @@ -72,33 +61,8 @@ class StaticSiteContext( orphanedFiles.flatMap(p => loadTemplate(p.toFile, isBlog = false)) } - lazy val mainPages: Seq[StaticPageNode] = templates.map(templateToPage) - val docsPath = root.toPath.resolve("docs") - lazy val allPages: Seq[StaticPageNode] = sideBarConfig.fold(mainPages){ sidebar => - def flattenPages(p: StaticPageNode): Set[Path] = - Set(p.template.file.toPath) ++ p.getChildren.asScala.collect { case p: StaticPageNode => flattenPages(p) }.flatten - - val mainFiles = mainPages.toSet.flatMap(flattenPages) - - val allPaths = - if !Files.exists(docsPath) then Nil - else Files.walk(docsPath, FileVisitOption.FOLLOW_LINKS).iterator().asScala.toList - - val orphanedFiles = allPaths.filterNot { p => - def name = p.getFileName.toString - def isMain = name == "index.html" || name == "index.md" - mainFiles.contains(p) || (isMain && mainFiles.contains(p.getParent)) - }.filter { p => - val name = p.getFileName.toString - name.endsWith(".md") || name.endsWith(".html") - } - - val orphanedTemplates = orphanedFiles.flatMap(p => loadTemplate(p.toFile, isBlog = false)) - mainPages ++ orphanedTemplates.map(templateToPage) - } - private def isValidTemplate(file: File): Boolean = (file.isDirectory && !file.getName.startsWith("_")) || file.getName.endsWith(".md") || @@ -145,14 +109,6 @@ class StaticSiteContext( e.printStackTrace() None - def asContent(doctag: DocTag, dri: DRI) = new DocTagToContentConverter().buildContent( - doctag, - new DCI(Set(dri).asJava, ContentKind.Empty), - sourceSets.asJava, - JSet(), - new PropertyContainer(JMap()) - ) - private def loadSidebarContent(entry: Sidebar): LoadedTemplate = entry match case Sidebar.Page(title, url) => val isBlog = title == "Blog" @@ -213,25 +169,6 @@ class StaticSiteContext( def relativePath(myTemplate: LoadedTemplate) = root.toPath.relativize(myTemplate.file.toPath) - def templateToPage(myTemplate: LoadedTemplate): StaticPageNode = - val dri = driFor(myTemplate.file.toPath) - val content = new PartiallyRenderedContent( - myTemplate, - this, - JList(), - new DCI(Set(dri).asJava, ContentKind.Empty), - sourceSets.toDisplay, - JSet() - ) - StaticPageNode( - myTemplate.templateFile, - myTemplate.templateFile.title, - content, - JSet(dri), - JList(), - (myTemplate.children.map(templateToPage)).asJava - ) - val projectWideProperties = Seq("projectName" -> args.name) ++ args.projectVersion.map("projectVersion" -> _) diff --git a/scala3doc/src/dotty/dokka/site/StaticSiteLocationProvider.scala b/scala3doc/src/dotty/dokka/site/StaticSiteLocationProvider.scala deleted file mode 100644 index bea0d573e9af..000000000000 --- a/scala3doc/src/dotty/dokka/site/StaticSiteLocationProvider.scala +++ /dev/null @@ -1,164 +0,0 @@ -package dotty.dokka -package site - -import org.jetbrains.dokka.pages.ContentPage -import org.jetbrains.dokka.pages.PageNode -import org.jetbrains.dokka.pages.RootPageNode -import org.jetbrains.dokka.pages.ModulePage -import org.jetbrains.dokka.pages.ClasslikePageNode -import org.jetbrains.dokka.model.DPackage -import org.jetbrains.dokka.plugability.DokkaContext -import org.jetbrains.dokka.base.resolvers.external._ -import org.jetbrains.dokka.base.resolvers.shared._ -import org.jetbrains.dokka.base.resolvers.local._ -import org.jetbrains.dokka.model.DisplaySourceSet -import dotty.dokka.withNoOrigin - -import scala.collection.JavaConverters._ -import java.nio.file.Paths -import java.nio.file.Path -import scala.util.matching._ -import dotty.dokka.model.api._ - -class StaticSiteLocationProviderFactory(using ctx: DokkaContext) extends LocationProviderFactory: - override def getLocationProvider(pageNode: RootPageNode): LocationProvider = - try new StaticSiteLocationProvider(pageNode) - catch - case e: Error => - // TODO (https://github.com/lampepfl/scala3doc/issues/238) error handling - e.printStackTrace() - // We encounter bug in Kotlin coroutines (race) when this method throws exception - // In such case we want to return null to trigger NPE in other piece of code to fail properly coroutine context - // Making generated DRIs not-unique will reproduce this behavior - null - -class StaticSiteLocationProvider(pageNode: RootPageNode)(using ctx: DokkaContext) - extends DokkaLocationProvider(pageNode, ctx, ".html"): - private def updatePageEntry(page: PageNode, jpath: JList[String]): JList[String] = - page match - case page: ContentPage if page.getDri.contains(docsDRI) => - JList("docs", "index") - case page: ContentPage if page.getDri.contains(docsRootDRI) => - JList("index") - case page: StaticPageNode => - summon[DocContext].staticSiteContext.fold(jpath) { context => - val rawFilePath = context.root.toPath.relativize(page.template.file.toPath) - val pageName = page.template.file.getName - val dotIndex = pageName.lastIndexOf('.') - - if (isBlogPostPath(rawFilePath)) { - val regex = raw"(\d*)-(\d*)-(\d*)-(.*)\..*".r - val blogPostPath = pageName.toString match { - case regex(year, month, day, name) => - rawFilePath.getParent.resolveSibling(Paths.get(year, month, day, name)) - case _ => - val msg = s"Relative path for blog: $rawFilePath doesn't match `yyy-mm-dd-name.md` format." - report.warn(msg, page.template.file) - rawFilePath.resolveSibling(pageName.substring(0, dotIndex)) - } - blogPostPath.iterator.asScala.map(_.toString).toList.asJava - } else { - val newPath = - if (dotIndex < 0) rawFilePath.resolve("index") - else rawFilePath.resolveSibling(pageName.substring(0, dotIndex)) - newPath.iterator.asScala.map(_.toString).toList.asJava - } - } - - case page: ContentPage if page.getDri.contains(apiPageDRI) => - JList("api", "index") - case _ if jpath.size() > 1 && jpath.get(0) == "--root--" && jpath.get(1) == "-a-p-i" => - (List("api") ++ jpath.asScala.drop(2)).asJava - - case _: ModulePage if summon[DocContext].staticSiteContext.isEmpty => - JList("index") - case page: ContentPage if page.getDocumentable != null => - ( - List("api") ++ - page.getDocumentable.getDri.location.split(Array('.')).toList ++ - (if(page.getDocumentable.isInstanceOf[DPackage]) then List("index") else List.empty) ++ - page.getDocumentable.getDri.anchor - ).asJava - case _ => - jpath - - private def isBlogPostPath(path: Path): Boolean = path.startsWith(Paths.get("blog","_posts")) - - override val getPathsIndex: JMap[PageNode, JList[String]] = - super.getPathsIndex.asScala.mapValuesInPlace(updatePageEntry).asJava - - // We should build our own provider at some point - val ourPages: Map[String, ClasslikePageNode] = getPathsIndex.asScala.collect { - case (node: ClasslikePageNode, path) => node.getDri.asScala.head.location -> node - }.toMap - - - override def resolve( - dri: DRI, - sourceSets: JSet[DisplaySourceSet], - context: PageNode): String = - ourPages.get(dri.location).fold(super.resolve(dri, sourceSets, context)){ page => - val path = pathTo(page,context) match - case "" => "" - case path => s"$path.html" - dri.anchor.fold(path)(hash => s"$path#$hash") - } - - override def resolve(node: PageNode, from: PageNode, skipExtension: Boolean): String = - pathTo(node, from) match - case "" => "" - case path => if skipExtension then path else s"$path.html" - - override def pathTo(node: PageNode, context: PageNode): String = - if node == context then "" - else - val nodePaths = getPathsIndex.get(node).asScala.toList - val contextPaths = Option(context).fold(Nil)(getPathsIndex.get(_).asScala).toList - relativePath(contextPaths, nodePaths) - - - val externalLocationProviders: List[(List[Regex], ExternalLocationProvider)] = - val sourceSet = ctx.getConfiguration.getSourceSets.asScala(0) - ctx.getConfiguration - .asInstanceOf[DocContext] - .externalDocumentationLinks - .map { link => - val emptyExtDoc = ExternalDocumentation( - link.documentationUrl, - PackageList( - RecognizedLinkFormat.Javadoc1, JSet(), JMap(), link.documentationUrl - ) - ) - val extDoc = link.packageListUrl.fold(emptyExtDoc)( pl => ExternalDocumentation( - link.documentationUrl, - PackageList.Companion.load(pl, sourceSet.getJdkVersion, ctx.getConfiguration.getOfflineMode) - ) - ) - (extDoc, link) - } - .map { (extDoc, link) => - - val externalLocationProvider = ScalaExternalLocationProvider(extDoc, ".html", link.kind) - link.originRegexes -> externalLocationProvider - }.toList - - override def getExternalLocation(dri: DRI, sourceSets: JSet[DisplaySourceSet]): String = - val regex = raw"\[origin:(.*)\]".r - val origin = regex.findFirstIn(Option(dri.extra).getOrElse("")) - origin match { - case Some(path) => externalLocationProviders.find { (regexes, provider) => - regexes.exists(r => r.matches(path)) - }.fold(null)(_(1).resolve(dri.withNoOrigin)) - case None => null - } - -def relativePath(fullFrom: Seq[String], to: Seq[String]): String = - val from = fullFrom.dropRight(1) - val commonPaths = to.zip(from).takeWhile{ case (a, b) => a == b }.size - - val contextPath = from.drop(commonPaths).map(_ => "..") - val nodePath = to.drop(commonPaths) match - case Nil if contextPath.isEmpty && to.nonEmpty=> Seq("..", to.last) - case Nil => Seq("index") - case l => l - (contextPath ++ nodePath).mkString("/") \ No newline at end of file diff --git a/scala3doc/src/dotty/dokka/site/common.scala b/scala3doc/src/dotty/dokka/site/common.scala index 3611e55aec1e..778217886334 100644 --- a/scala3doc/src/dotty/dokka/site/common.scala +++ b/scala3doc/src/dotty/dokka/site/common.scala @@ -16,16 +16,11 @@ import com.vladsch.flexmark.parser.{Parser, ParserEmulationProfile} import com.vladsch.flexmark.util.options.{DataHolder, MutableDataSet} import com.vladsch.flexmark.ext.wikilink.WikiLinkExtension -import org.jetbrains.dokka.model.doc.Text -import org.jetbrains.dokka.model.Documentable -import org.jetbrains.dokka.transformers.pages.PageTransformer -import org.jetbrains.dokka.pages._ - import scala.collection.JavaConverters._ val docsRootDRI: DRI = DRI(location = "index.md") -val docsDRI: DRI = DRI(location = "docs.index.md") -val apiPageDRI: DRI = DRI(location = "api", extra = "__api__") +val docsDRI: DRI = DRI(location = "docs/index.md") +val apiPageDRI: DRI = DRI(location = "api") val defaultMarkdownOptions: DataHolder = new MutableDataSet() @@ -115,51 +110,3 @@ def loadTemplateFile(file: File): TemplateFile = { layout = stringSetting(allSettings, "layout") ) } - -def Text(msg: String = "") = new Text(msg, JList(), JMap()) - -abstract class BaseStaticSiteProcessor(using ctx: DocContext) - extends PageTransformer: - final override def invoke(input: RootPageNode): RootPageNode = - ctx.staticSiteContext.fold(input)(transform(input, _)) - - protected def transform(input: RootPageNode, ctx: StaticSiteContext): RootPageNode - -// Needed until we will migrate away from dokka -case class FakeContentPage( - dri: DRI, - override val getContent: ContentNode) extends ContentPage: - override val getName: String = "" - override val getChildren: JList[PageNode] = JList() - override val getEmbeddedResources: JList[String] = JList() - override def getDocumentable: Documentable = null - override def modified( - name: String, - content: ContentNode, - dri: JSet[DRI], - embeddedResources: JList[String], - children: JList[_ <: PageNode] - ): ContentPage = this - override def modified(name: String, children: JList[_ <: PageNode]): PageNode = this - override val getDri: JSet[DRI] = JSet(dri) - -case class AContentPage( - override val getName: String, - override val getChildren: JList[PageNode], - override val getContent: ContentNode, - override val getDri: JSet[DRI], - override val getEmbeddedResources: JList[String] = JList(), -) extends ContentPage: - override def getDocumentable: Documentable = null - - override def modified( - name: String, - content: ContentNode, - dri: JSet[DRI], - embeddedResources: JList[String], - children: JList[_ <: PageNode] - ): ContentPage = - copy(name, children.asInstanceOf[JList[PageNode]], content, dri, embeddedResources) - - override def modified(name: String, children: JList[_ <: PageNode]): PageNode = - copy(name, getChildren = children.asInstanceOf[JList[PageNode]]) diff --git a/scala3doc/src/dotty/dokka/tasty/BasicSupport.scala b/scala3doc/src/dotty/dokka/tasty/BasicSupport.scala index da25037236cd..dced432a9da8 100644 --- a/scala3doc/src/dotty/dokka/tasty/BasicSupport.scala +++ b/scala3doc/src/dotty/dokka/tasty/BasicSupport.scala @@ -1,7 +1,6 @@ package dotty.dokka package tasty -import org.jetbrains.dokka.model._ import collection.JavaConverters._ import dotty.dokka._ import dotty.dokka.model.api.Annotation diff --git a/scala3doc/src/dotty/dokka/tasty/ClassLikeSupport.scala b/scala3doc/src/dotty/dokka/tasty/ClassLikeSupport.scala index f8f239c71ff1..ba32af6f2d89 100644 --- a/scala3doc/src/dotty/dokka/tasty/ClassLikeSupport.scala +++ b/scala3doc/src/dotty/dokka/tasty/ClassLikeSupport.scala @@ -1,12 +1,7 @@ package dotty.dokka.tasty -import org.jetbrains.dokka.model.{TypeConstructor => DTypeConstructor, TypeParameter => _, _} -import org.jetbrains.dokka.model.doc._ -import org.jetbrains.dokka.DokkaConfiguration$DokkaSourceSet import collection.JavaConverters._ -import org.jetbrains.dokka.model.properties._ import dotty.dokka._ -import org.jetbrains.dokka.base.transformers.documentables.CallableExtensions import dotty.dokka.model.api._ import dotty.dokka.model.api.Modifier import dotty.dokka.model.api.Kind @@ -17,9 +12,6 @@ trait ClassLikeSupport: self: TastyParser => import qctx.reflect._ - private val placeholderVisibility = JMap(ctx.sourceSet -> KotlinVisibility.Public.INSTANCE) - private val placeholderModifier = JMap(ctx.sourceSet -> KotlinModifier.Empty.INSTANCE) - private def bareClasslikeKind(symbol: Symbol): Kind = if symbol.flags.is(Flags.Module) then Kind.Object else if symbol.flags.is(Flags.Trait) then Kind.Trait(Nil, Nil) @@ -54,7 +46,7 @@ trait ClassLikeSupport: else if classDef.symbol.flags.is(Flags.Enum) then Kind.Enum else Kind.Class(typeArgs, args) - def mkClass[T >: DClass](classDef: ClassDef)( + def mkClass(classDef: ClassDef)( dri: DRI = classDef.symbol.dri, name: String = classDef.symbol.normalizedName, signatureOnly: Boolean = false, @@ -75,41 +67,29 @@ trait ClassLikeSupport: val parents = unpackTreeToClassDef(classDef).parents parents.flatMap { case tree => val symbol = if tree.symbol.isClassConstructor then tree.symbol.owner else tree.symbol - val superLink = LinkToType(tree.dokkaType.asSignature, symbol.dri, bareClasslikeKind(symbol)) + val superLink = LinkToType(tree.asSignature, symbol.dri, bareClasslikeKind(symbol)) Seq(link -> superLink) ++ getSupertypesGraph(tree, superLink) } val supertypes = getSupertypes(using qctx)(classDef).map { case (symbol, tpe) => - LinkToType(tpe.dokkaType.asSignature, symbol.dri, bareClasslikeKind(symbol)) + LinkToType(tpe.asSignature, symbol.dri, bareClasslikeKind(symbol)) } - val selfSiangture: DSignature = typeForClass(classDef).dokkaType.asSignature + val selfSiangture: DSignature = typeForClass(classDef).asSignature val graph = HierarchyGraph.withEdges(getSupertypesGraph(classDef, LinkToType(selfSiangture, classDef.symbol.dri, bareClasslikeKind(classDef.symbol)))) + val baseMember = mkMember(classDef.symbol, kindForClasslike(classDef), selfSiangture)( + modifiers = modifiers, + graph = graph + ) - val compositeExt = - if signatureOnly then CompositeMemberExtension.empty - else CompositeMemberExtension( - classDef.extractPatchedMembers, - classDef.getParentsAsLinkToTypes, - supertypes, - Nil, - classDef.getCompanion - ) - mkMember( - classDef.symbol, - MemberExtension( - classDef.symbol.getVisibility(), - modifiers, - kindForClasslike(classDef), - classDef.symbol.getAnnotations(), - selfSiangture, - classDef.symbol.source(using qctx), - graph = graph - ), - compositeExt + if signatureOnly then baseMember else baseMember.copy( + members = classDef.extractPatchedMembers, + directParents = classDef.getParentsAsLinkToTypes, + parents = supertypes, + companion = classDef.getCompanion ) private val conversionSymbol = Symbol.requiredClass("scala.Conversion") @@ -130,7 +110,7 @@ trait ClassLikeSupport: dd.symbol.extendedSymbol.map { extSym => val target = ExtensionTarget( extSym.symbol.normalizedName, - extSym.tpt.dokkaType.asSignature, + extSym.tpt.asSignature, extSym.tpt.symbol.dri, extSym.symbol.pos.get.start ) @@ -142,7 +122,7 @@ trait ClassLikeSupport: .filterNot(_.exists) .map { _ => parseMethod(c, dd.symbol, specificKind = - Kind.Given(_, getGivenInstance(dd).map(_.asSignature), None) + Kind.Given(_, getGivenInstance(dd), None) ) } @@ -210,16 +190,18 @@ trait ClassLikeSupport: ) } - private def parseInheritedMember(c: ClassDef)(s: Tree): Option[Member] = processTreeOpt(s)(s match - case c: ClassDef if c.symbol.shouldDocumentClasslike && !c.symbol.isGiven => Some(parseClasslike(c, signatureOnly = true)) - case other => parseMember(c)(other) - ).map(_.withInheritedFrom(InheritedFrom(s.symbol.owner.normalizedName, s.symbol.owner.dri))) + private def parseInheritedMember(c: ClassDef)(s: Tree): Option[Member] = + def inheritance = Some(InheritedFrom(s.symbol.owner.normalizedName, s.symbol.owner.dri)) + processTreeOpt(s)(s match + case c: ClassDef if c.symbol.shouldDocumentClasslike && !c.symbol.isGiven => Some(parseClasslike(c, signatureOnly = true)) + case other => parseMember(c)(other) + ).map(_.copy(inheritedFrom = inheritance)) extension (c: ClassDef) def membersToDocument = c.body.filterNot(_.symbol.isHiddenByVisibility) def getNonTrivialInheritedMemberTrees = - c.symbol.getAllMembers.filterNot(s => s.isHiddenByVisibility || s.maybeOwner == c.symbol) + c.symbol.getmembers.filterNot(s => s.isHiddenByVisibility || s.maybeOwner == c.symbol) .filter(s => s.maybeOwner != defn.ObjectClass && s.maybeOwner != defn.AnyClass) .map(_.tree) @@ -325,7 +307,7 @@ trait ClassLikeSupport: enumTypes.map(et => et.withKind(Kind.EnumCase(et.kind.asInstanceOf[Kind.Type]))) ++ enumVals.map(_.withKind(Kind.EnumCase(Kind.Val))) - classlikie.withNewMembers(cases).asInstanceOf[DClass] + classlikie.withNewMembers(cases) def parseMethod( c: ClassDef, @@ -373,20 +355,10 @@ trait ClassLikeSupport: val overridenSyms = methodSymbol.allOverriddenSymbols.map(_.owner) Origin.Overrides(overridenSyms.map(s => Overriden(s.name, s.dri)).toSeq) - mkMember( - method.symbol, - MemberExtension( - methodSymbol.getVisibility(), - methodSymbol.getExtraModifiers(), - methodKind, - methodSymbol.getAnnotations(), - memberInfo.res.dokkaType.asSignature, - methodSymbol.source(using qctx), - origin - ) - ) + mkMember(method.symbol, methodKind, memberInfo.res.asSignature)(origin = origin) - def mkParameter(argument: ValDef, + def mkParameter( + argument: ValDef, prefix: Symbol => String = _ => "", isExtendedSymbol: Boolean = false, isGrouped: Boolean = false, @@ -399,7 +371,7 @@ trait ClassLikeSupport: inlinePrefix + prefix(argument.symbol), nameIfNotSynthetic, argument.symbol.dri, - memberInfo.get(name).fold(argument.tpt.dokkaType.asSignature)(_.dokkaType.asSignature), + memberInfo.get(name).fold(argument.tpt.asSignature)(_.asSignature), isExtendedSymbol, isGrouped ) @@ -415,7 +387,7 @@ trait ClassLikeSupport: variancePrefix, name, argument.symbol.dri, - memberInfo.get(name).fold(argument.rhs.dokkaType.asSignature)(_.dokkaType.asSignature) + memberInfo.get(name).fold(argument.rhs.asSignature)(_.asSignature) ) def parseTypeDef(typeDef: TypeDef): Member = @@ -429,17 +401,8 @@ trait ClassLikeSupport: case LambdaTypeTree(params, body) => (params.map(mkTypeArgument(_)), body) case tpe => (Nil, tpe) - mkMember( - typeDef.symbol, - MemberExtension( - typeDef.symbol.getVisibility(), - typeDef.symbol.getExtraModifiers(), - Kind.Type(!isTreeAbstract(typeDef.rhs), typeDef.symbol.isOpaque, generics), - typeDef.symbol.getAnnotations(), - tpeTree.dokkaType.asSignature, - typeDef.symbol.source(using qctx) - ) - ) + val kind = Kind.Type(!isTreeAbstract(typeDef.rhs), typeDef.symbol.isOpaque, generics) + mkMember(typeDef.symbol, kind, tpeTree.asSignature)() def parseValDef(c: ClassDef, valDef: ValDef): Member = def defaultKind = if valDef.symbol.flags.is(Flags.Mutable) then Kind.Var else Kind.Val @@ -448,42 +411,26 @@ trait ClassLikeSupport: Kind.Implicit(Kind.Val, extractImplicitConversion(valDef.tpt.tpe)) else defaultKind - mkMember( - valDef.symbol, - MemberExtension( - valDef.symbol.getVisibility(), - valDef.symbol.getExtraModifiers(), - kind, - valDef.symbol.getAnnotations(), - memberInfo.res.dokkaType.asSignature, - valDef.symbol.source(using qctx) - ) - ) - - def mkMember[T <: Kind]( - symbol: Symbol, - member: MemberExtension, - compositeExt: CompositeMemberExtension = CompositeMemberExtension.empty, - nameOverride: Symbol => String = _.normalizedName - ): Member = - new DClass( - symbol.dri, - nameOverride(symbol), - JNil, - JNil, - JNil, - JNil, - emptyJMap, - placeholderVisibility, - null, - JNil, - emptyJMap, - emptyJMap, - null, - placeholderModifier, - ctx.sourceSet.toSet, - /*isExpectActual =*/ false, - PropertyContainer.Companion.empty().plus(member.copy(rawDoc = symbol.documentation)).plus(compositeExt) + mkMember(valDef.symbol, kind, memberInfo.res.asSignature)() + + def mkMember(symbol: Symbol, kind: Kind, signature: DSignature)( + modifiers: Seq[dotty.dokka.model.api.Modifier] = symbol.getExtraModifiers(), + origin: Origin = Origin.RegularlyDefined, + inheritedFrom: Option[InheritedFrom] = None, + graph: HierarchyGraph = HierarchyGraph.empty, + ) = Member( + name = symbol.normalizedName, + dri = symbol.dri, + kind = kind, + visibility = symbol.getVisibility(), + modifiers = modifiers, + annotations = symbol.getAnnotations(), + signature = signature, + sources = symbol.source(using qctx), + origin = origin, + inheritedFrom = inheritedFrom, + graph = graph, + docs = symbol.documentation ) case class MemberInfo(genericTypes: Map[String, TypeBounds], paramLists: List[Map[String, TypeRepr]], res: TypeRepr) diff --git a/scala3doc/src/dotty/dokka/tasty/PackageSupport.scala b/scala3doc/src/dotty/dokka/tasty/PackageSupport.scala index c35a49e1d6e4..2dd95dafc2be 100644 --- a/scala3doc/src/dotty/dokka/tasty/PackageSupport.scala +++ b/scala3doc/src/dotty/dokka/tasty/PackageSupport.scala @@ -1,10 +1,6 @@ package dotty.dokka package tasty -import org.jetbrains.dokka.model._ -import org.jetbrains.dokka.links.PointingToDeclaration -import org.jetbrains.dokka.model.properties._ -import org.jetbrains.dokka.model.doc.DocumentationNode import dotty.dokka._ import dotty.dokka.model.api._ @@ -16,8 +12,7 @@ trait PackageSupport: def parsePackage(pck: PackageClause): (String, Member) = val name = extractPackageName(pck.pid.show) - val mExt = MemberExtension.empty.copy(kind = Kind.Package) - name -> mkMember(pck.symbol, mExt, nameOverride = _ => name) + (name, Member(name, pck.symbol.dri, Kind.Package)) def parsePackageObject(pckObj: ClassDef): (String, Member) = pckObj.symbol.packageName -> parseClasslike(pckObj).withKind(Kind.Package) diff --git a/scala3doc/src/dotty/dokka/tasty/SymOps.scala b/scala3doc/src/dotty/dokka/tasty/SymOps.scala index 97664f510cbb..5add3cb71a96 100644 --- a/scala3doc/src/dotty/dokka/tasty/SymOps.scala +++ b/scala3doc/src/dotty/dokka/tasty/SymOps.scala @@ -1,6 +1,5 @@ package dotty.dokka.tasty -import org.jetbrains.dokka.links._ import org.jetbrains.dokka.model._ import collection.JavaConverters._ import dotty.dokka._ @@ -112,10 +111,6 @@ class SymOps[Q <: Quotes](val q: Q): if sym == Symbol.noSymbol then topLevelDri else if sym.isValDef && sym.moduleClass.exists then sym.moduleClass.dri else - val pointsTo = - if (!sym.isTypeDef) PointingToDeclaration.INSTANCE - else PointingToGenericParameters(sym.owner.memberTypes.indexOf(sym)) - val method = if (sym.isDefDef) Some(sym) else if (sym.maybeOwner.isDefDef) Some(sym.owner) @@ -126,16 +121,15 @@ class SymOps[Q <: Quotes](val q: Q): import dotty.tools.dotc given ctx: dotc.core.Contexts.Context = q.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx val csym = sym.asInstanceOf[dotc.core.Symbols.Symbol] - Option(csym.associatedFile).map(_.path).fold("")(p => s"[origin:$p]") + Option(csym.associatedFile).fold("")(_.path) } // We want package object to point to package val className = sym.className.filter(_ != "package$") - new DRI( + DRI( className.fold(sym.packageName)(cn => s"${sym.packageName}.${cn}"), - sym.anchor.getOrElse(""), // TODO do we need any of this fields? - null, - pointsTo, + anchor = sym.anchor.getOrElse(""), + origin = originPath, // sym.show returns the same signature for def << = 1 and def >> = 2. // For some reason it contains `$$$` instrad of symbol name s"${sym.name}${sym.fullName}/${sym.signature.resultSig}/[${sym.signature.paramSigs.mkString("/")}]$originPath" diff --git a/scala3doc/src/dotty/dokka/tasty/SyntheticSupport.scala b/scala3doc/src/dotty/dokka/tasty/SyntheticSupport.scala index ea58a3f9df22..89e04587987f 100644 --- a/scala3doc/src/dotty/dokka/tasty/SyntheticSupport.scala +++ b/scala3doc/src/dotty/dokka/tasty/SyntheticSupport.scala @@ -35,7 +35,7 @@ trait SyntheticsSupport: def isInfix: Boolean = hackIsInfix(using qctx)(s) - def getAllMembers: List[Symbol] = hackGetAllMembers(using qctx)(s) + def getmembers: List[Symbol] = hackGetmembers(using qctx)(s) def isValidPos(pos: Position) = if hackExists(using qctx)(pos) then pos.start != pos.end else false @@ -63,7 +63,7 @@ trait SyntheticsSupport: They are valdefs that describe case companion objects and cases from enum. TASTY crashed when calling _.tree on them. */ - def hackGetAllMembers(using Quotes)(rsym: qctx.reflect.Symbol): List[qctx.reflect.Symbol] = { + def hackGetmembers(using Quotes)(rsym: qctx.reflect.Symbol): List[qctx.reflect.Symbol] = { import qctx.reflect._ import dotty.tools.dotc given ctx: dotc.core.Contexts.Context = qctx.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx diff --git a/scala3doc/src/dotty/dokka/tasty/TastyParser.scala b/scala3doc/src/dotty/dokka/tasty/TastyParser.scala index f78bfaed6d64..13683bb921d0 100644 --- a/scala3doc/src/dotty/dokka/tasty/TastyParser.scala +++ b/scala3doc/src/dotty/dokka/tasty/TastyParser.scala @@ -1,18 +1,8 @@ package dotty.dokka package tasty -import org.jetbrains.dokka.plugability._ -import org.jetbrains.dokka.transformers.sources._ - -import org.jetbrains.dokka.DokkaConfiguration -import org.jetbrains.dokka.model._ import org.jetbrains.dokka.model.doc._ import org.jetbrains.dokka.base.parsers._ -import org.jetbrains.dokka.plugability.DokkaContext -import collection.JavaConverters._ -import org.jetbrains.dokka.model.properties.PropertyContainer -import org.jetbrains.dokka.model.properties.PropertyContainerKt._ -import org.jetbrains.dokka.model.properties.{WithExtraProperties} import java.util.regex.Pattern @@ -141,9 +131,9 @@ case class DokkaTastyInspector(parser: Parser)(using ctx: DocContext) extends Do all.groupBy(_._1).map { case (pckName, members) => val (pcks, rest) = members.map(_._2).partition(_.kind == Kind.Package) val basePck = pcks.reduce( (p1, p2) => - p1.withNewMembers(p2.allMembers) // TODO add doc + p1.withNewMembers(p2.members) // TODO add doc ) - basePck.withMembers((basePck.allMembers ++ rest).sortBy(_.name)) + basePck.withMembers((basePck.members ++ rest).sortBy(_.name)) }.toList -> rootDoc /** Parses a single Tasty compilation unit. */ diff --git a/scala3doc/src/dotty/dokka/tasty/TypesSupport.scala b/scala3doc/src/dotty/dokka/tasty/TypesSupport.scala index 2bfbf61d13c4..dee425eaabde 100644 --- a/scala3doc/src/dotty/dokka/tasty/TypesSupport.scala +++ b/scala3doc/src/dotty/dokka/tasty/TypesSupport.scala @@ -1,14 +1,17 @@ package dotty.dokka.tasty -import org.jetbrains.dokka.model._ -import org.jetbrains.dokka.model.{Projection => JProjection} import collection.JavaConverters._ +import dotty.dokka.model.api.Link + trait TypesSupport: self: TastyParser => import qctx.reflect._ - def getGivenInstance(method: DefDef): Option[Bound] = + type DocSignaturePart = String | Link + type DocSignature = List[DocSignaturePart] + + def getGivenInstance(method: DefDef): Option[DocSignature] = def extractTypeSymbol(t: Tree): Option[Symbol] = t match case tpeTree: TypeTree => inner(tpeTree.tpe) @@ -32,39 +35,30 @@ trait TypesSupport: typeSymbol.map(_.tree).collect { case c: ClassDef => c.getTreeOfFirstParent case _ => Some(method.returnTpt) - }.flatten.map(_.dokkaType) - + }.flatten.map(_.asSignature) given TreeSyntax: AnyRef with extension (tpeTree: Tree) - def dokkaType: Bound = - val data = tpeTree match + def asSignature: DocSignature = + tpeTree match case TypeBoundsTree(low, high) => typeBound(low.tpe, low = true) ++ typeBound(high.tpe, low = false) case tpeTree: TypeTree => inner(tpeTree.tpe) case term: Term => inner(term.tpe) - new GenericTypeConstructor(tpeTree.symbol.dri, data.asJava, null) - given TypeSyntax: AnyRef with extension (tpe: TypeRepr) - def dokkaType: Bound = - val data = inner(tpe) - val dri = data.collect{ - case o: TypeParameter => o - }.headOption.map(_.getDri).getOrElse(defn.AnyClass.dri) - new GenericTypeConstructor(dri, data.asJava, null) + def asSignature: DocSignature = inner(tpe) - private def text(str: String): JProjection = new UnresolvedBound(str) - private def texts(str: String): List[JProjection] = List(text(str)) + private def text(str: String): DocSignaturePart = str + private def texts(str: String): DocSignature = List(text(str)) - private def link(symbol: Symbol): List[JProjection] = { + private def link(symbol: Symbol): DocSignature = val suffix = if symbol.isValDef then texts(".type") else Nil - (new TypeParameter(symbol.dri, symbol.normalizedName, null)) :: suffix - } + Link(symbol.normalizedName, symbol.dri) :: suffix - private def commas(lists: List[List[JProjection]]) = lists match + private def commas(lists: List[DocSignature]) = lists match case List(single) => single case other => other.reduce((r, e) => r ++ texts(", ") ++ e) @@ -83,8 +77,8 @@ trait TypesSupport: case _ => false // TODO #23 add support for all types signatures that makes sense - private def inner(tp: TypeRepr): List[JProjection] = - def noSupported(name: String): List[JProjection] = + private def inner(tp: TypeRepr): DocSignature = + def noSupported(name: String): DocSignature = println(s"WARN: Unsupported type: $name: ${tp.show}") List(text(s"Unsupported[$name]")) @@ -115,18 +109,18 @@ trait TypesSupport: case t => List(t) } - def getParamBounds(t: PolyType): List[JProjection] = commas( + def getParamBounds(t: PolyType): DocSignature = commas( t.paramNames.zip(t.paramBounds.map(inner(_))) .map(b => texts(b(0)) ++ b(1)) ) - def getParamList(m: MethodType): List[JProjection] = + def getParamList(m: MethodType): DocSignature = texts("(") ++ m.paramNames.zip(m.paramTypes).map{ case (name, tp) => texts(s"$name: ") ++ inner(tp)} - .reduceLeftOption((acc: List[JProjection], elem: List[JProjection]) => acc ++ texts(", ") ++ elem).getOrElse(List()) + .reduceLeftOption((acc: DocSignature, elem: DocSignature) => acc ++ texts(", ") ++ elem).getOrElse(List()) ++ texts(")") - def parseRefinedElem(name: String, info: TypeRepr, polyTyped: List[JProjection] = Nil): List[JProjection] = ( info match { + def parseRefinedElem(name: String, info: TypeRepr, polyTyped: DocSignature = Nil): DocSignature = ( info match { case m: MethodType => { val paramList = getParamList(m) texts(s"def $name") ++ polyTyped ++ paramList ++ texts(": ") ++ inner(m.resType) @@ -145,7 +139,7 @@ trait TypesSupport: case other => noSupported(s"Not supported type in refinement $info") } ) ++ texts("; ") - def parsePolyFunction(info: TypeRepr): List[JProjection] = info match { + def parsePolyFunction(info: TypeRepr): DocSignature = info match { case t: PolyType => val paramBounds = getParamBounds(t) val method = t.resType.asInstanceOf[MethodType] @@ -157,7 +151,7 @@ trait TypesSupport: val refinementInfo = getRefinementInformation(r) val refinedType = refinementInfo.head val refinedElems = refinementInfo.tail.collect{ case r: Refinement => r }.toList - val prefix = if refinedType.typeSymbol != defn.ObjectClass then inner(refinedType) ++ texts(" ") else List.empty[JProjection] + val prefix = if refinedType.typeSymbol != defn.ObjectClass then inner(refinedType) ++ texts(" ") else Nil if (refinedType.typeSymbol.fullName == "scala.PolyFunction" && refinedElems.size == 1) { parsePolyFunction(refinedElems.head.info) } else { diff --git a/scala3doc/src/dotty/dokka/tasty/comments/MarkdownConverter.scala b/scala3doc/src/dotty/dokka/tasty/comments/MarkdownConverter.scala index a705a60eae51..a6ae4c8435fb 100644 --- a/scala3doc/src/dotty/dokka/tasty/comments/MarkdownConverter.scala +++ b/scala3doc/src/dotty/dokka/tasty/comments/MarkdownConverter.scala @@ -192,7 +192,7 @@ class MarkdownConverter(val repr: Repr) extends BaseConverter { withParsedQuery(queryStr) { query => MemberLookup.lookup(using qctx)(query, owner) match { case Some((sym, targetText)) => - dkkd.DocumentationLink(sym.dri, resolveBody(default = targetText), kt.emptyMap) + dkkd.DocumentationLink(sym.dri.asDokka, resolveBody(default = targetText), kt.emptyMap) case None => // println(s"WARN: Definition lookup for following query failed: $queryStr") dkkd.A(resolveBody(default = query.join), Map("title" -> s"Definition was not found: $queryStr", "href" -> "#").asJava) diff --git a/scala3doc/src/dotty/dokka/tasty/comments/wiki/Converter.scala b/scala3doc/src/dotty/dokka/tasty/comments/wiki/Converter.scala index 7e846e1d0218..bec172c96c99 100644 --- a/scala3doc/src/dotty/dokka/tasty/comments/wiki/Converter.scala +++ b/scala3doc/src/dotty/dokka/tasty/comments/wiki/Converter.scala @@ -150,7 +150,7 @@ class Converter(val repr: Repr) extends BaseConverter { withParsedQuery(queryStr) { query => MemberLookup.lookup(using qctx)(query, owner) match { case Some((sym, targetText)) => - dkkd.DocumentationLink(sym.dri, resolveBody(default = targetText), kt.emptyMap) + dkkd.DocumentationLink(sym.dri.asDokka, resolveBody(default = targetText), kt.emptyMap) case None => dkkd.A(resolveBody(default = query.join), Map("href" -> "#").asJava) } diff --git a/scala3doc/src/dotty/dokka/transformers/ImplicitMembersExtensionTransformer.scala b/scala3doc/src/dotty/dokka/transformers/ImplicitMembersExtensionTransformer.scala index 3d787873edf4..dfe14ebb0321 100644 --- a/scala3doc/src/dotty/dokka/transformers/ImplicitMembersExtensionTransformer.scala +++ b/scala3doc/src/dotty/dokka/transformers/ImplicitMembersExtensionTransformer.scala @@ -1,19 +1,11 @@ package dotty.dokka -import org.jetbrains.dokka.transformers.documentation.DocumentableTransformer -import org.jetbrains.dokka.model._ -import collection.JavaConverters -import collection.JavaConverters._ -import org.jetbrains.dokka.plugability.DokkaContext -import org.jetbrains.dokka.model.properties._ - import dotty.dokka.model._ import dotty.dokka.model.api._ -class ImplicitMembersExtensionTransformer(using context: DocContext) extends ModuleTransformer: - override def apply(original: DModule): DModule = - val classlikeMap = original.driMap - val logger = context.logger +class ImplicitMembersExtensionTransformer(using DocContext) extends(Module => Module): + override def apply(original: Module): Module = + val classlikeMap = original.members def retrieveCompanion(m: Member) = m.companion.flatMap { dri => val res = classlikeMap.get(dri) @@ -34,8 +26,8 @@ class ImplicitMembersExtensionTransformer(using context: DocContext) extends Mod val applicableDRIs = c.parents.map(_.dri).toSet + c.dri - val MyDri = c.getDri - def collectApplicableMembers(source: Member): Seq[Member] = source.allMembers.flatMap { + val MyDri = c.dri + def collectApplicableMembers(source: Member): Seq[Member] = source.members.flatMap { case m @ Member(_, _, _, Kind.Extension(ExtensionTarget(_, _, MyDri, _), _), Origin.RegularlyDefined) => val kind = m.kind match case d: Kind.Def => d @@ -46,7 +38,7 @@ class ImplicitMembersExtensionTransformer(using context: DocContext) extends Mod conversionProvider.conversion match case Some(ImplicitConversion(MyDri, to)) => classlikeMap.get(to).toSeq.flatMap { owner => - val newMembers = owner.allMembers.filter(_.origin match + val newMembers = owner.members.filter(_.origin match case Origin.RegularlyDefined => true case _ => false ) @@ -59,7 +51,7 @@ class ImplicitMembersExtensionTransformer(using context: DocContext) extends Mod } val newImplicitMembers = implictSources.flatMap(collectApplicableMembers).distinct - val expandedMembers = c.allMembers.map(expandMember(newImplicitMembers ++ Seq(c))) + val expandedMembers = c.members.map(expandMember(newImplicitMembers ++ Seq(c))) c.withMembers(newImplicitMembers ++ expandedMembers) original.updatePackages(_.map(expandMember(Nil)(_))) diff --git a/scala3doc/src/dotty/dokka/transformers/InheritanceInformationTransformer.scala b/scala3doc/src/dotty/dokka/transformers/InheritanceInformationTransformer.scala index c298550608de..04cd6c228760 100644 --- a/scala3doc/src/dotty/dokka/transformers/InheritanceInformationTransformer.scala +++ b/scala3doc/src/dotty/dokka/transformers/InheritanceInformationTransformer.scala @@ -1,18 +1,11 @@ package dotty.dokka -import org.jetbrains.dokka.transformers.documentation.DocumentableTransformer -import org.jetbrains.dokka.model._ -import collection.JavaConverters._ -import org.jetbrains.dokka.plugability.DokkaContext -import org.jetbrains.dokka.model.properties._ - import dotty.dokka.model._ import dotty.dokka.model.api._ - -class InheritanceInformationTransformer(using context: DocContext) extends ModuleTransformer: - override def apply(original: DModule): DModule = - val subtypes = getSupertypes(original.getPackages.get(0)).groupBy(_._1).transform((k, v) => v.map(_._2)) +class InheritanceInformationTransformer(using DocContext) extends (Module => Module): + override def apply(original: Module): Module = + val subtypes = getSupertypes(original.rootPackage).groupBy(_._1).transform((k, v) => v.map(_._2)) original.updateMembers { m => val edges = getEdges(m.asLink.copy(kind = bareClasslikeKind(m.kind)), subtypes) val st: Seq[LinkToType] = edges.map(_._1).distinct @@ -32,4 +25,4 @@ class InheritanceInformationTransformer(using context: DocContext) extends Modul val selfMapping = if !c.kind.isInstanceOf[Classlike] then Nil else c.directParents.map(_._2 -> c.asLink) - c.allMembers.flatMap(getSupertypes) ++ selfMapping + c.members.flatMap(getSupertypes) ++ selfMapping diff --git a/scala3doc/src/dotty/dokka/transformers/ModuleTransformer.scala b/scala3doc/src/dotty/dokka/transformers/ModuleTransformer.scala deleted file mode 100644 index a550ff047aad..000000000000 --- a/scala3doc/src/dotty/dokka/transformers/ModuleTransformer.scala +++ /dev/null @@ -1,6 +0,0 @@ -package dotty.dokka - -import org.jetbrains.dokka.model._ - -abstract trait ModuleTransformer: - def apply(m: DModule): DModule \ No newline at end of file diff --git a/scala3doc/src/dotty/dokka/translators/FilterAttributes.scala b/scala3doc/src/dotty/dokka/translators/FilterAttributes.scala index 77544f108d5c..62cb89d053d7 100644 --- a/scala3doc/src/dotty/dokka/translators/FilterAttributes.scala +++ b/scala3doc/src/dotty/dokka/translators/FilterAttributes.scala @@ -1,19 +1,5 @@ package dotty.dokka.translators -import org.jetbrains.dokka.base.translators.documentables.DefaultPageCreator -import org.jetbrains.dokka.base.signatures.SignatureProvider -import org.jetbrains.dokka.base.transformers.pages.comments.CommentsToContentConverter -import org.jetbrains.dokka.transformers.documentation.DocumentableToPageTranslator -import org.jetbrains.dokka.utilities.DokkaLogger -import org.jetbrains.dokka.model._ -import org.jetbrains.dokka.pages._ -import collection.JavaConverters._ -import org.jetbrains.dokka.model.properties._ -import org.jetbrains.dokka.base.transformers.documentables.CallableExtensions -import org.jetbrains.dokka.DokkaConfiguration$DokkaSourceSet -import org.jetbrains.dokka.base.resolvers.anchors._ -import org.jetbrains.dokka.model.properties.PropertyContainer -import org.jetbrains.dokka.model.doc._ import dotty.dokka.model.api._ import dotty.dokka._ diff --git a/scala3doc/src/dotty/dokka/translators/ScalaPageCreator.scala b/scala3doc/src/dotty/dokka/translators/ScalaPageCreator.scala deleted file mode 100644 index 5a4408b354b7..000000000000 --- a/scala3doc/src/dotty/dokka/translators/ScalaPageCreator.scala +++ /dev/null @@ -1,74 +0,0 @@ -package dotty.dokka - -import scala.collection.mutable -import scala.collection.mutable.ListBuffer -import scala.util.chaining._ -import org.jetbrains.dokka.base.translators.documentables.DefaultPageCreator -import org.jetbrains.dokka.base.signatures.SignatureProvider -import org.jetbrains.dokka.base.transformers.pages.comments.CommentsToContentConverter -import org.jetbrains.dokka.transformers.documentation.DocumentableToPageTranslator -import org.jetbrains.dokka.utilities.DokkaLogger -import org.jetbrains.dokka.model._ -import org.jetbrains.dokka.pages._ -import collection.JavaConverters._ -import org.jetbrains.dokka.model.properties._ -import org.jetbrains.dokka.base.transformers.documentables.CallableExtensions -import org.jetbrains.dokka.DokkaConfiguration$DokkaSourceSet -import org.jetbrains.dokka.base.resolvers.anchors._ -import org.jetbrains.dokka.model.doc._ -import dotty.dokka.model.api._ -import dotty.dokka.model.api.Kind -import dotty.dokka.model.api.Link - -class ScalaPageCreator( - commentsToContentConverter: CommentsToContentConverter, - signatureProvider: SignatureProvider, -)(using ctx: DocContext) - extends DefaultPageCreator(commentsToContentConverter, signatureProvider, ctx.logger): - - def mkMemberInfo(m: Member) = MemberInfo(m, ContentNodeParams( - new DCI(JSet(m.dri), ContentKind.Main), - m.getSourceSets.asScala.toSet.toDisplay, - Set(), - PropertyContainer.Companion.empty() - )) - - override def pageForModule(m: DModule): ModulePageNode = - val rootPackage = m.getPackages.get(0) - new ModulePageNode( - m.getName, - mkMemberInfo(rootPackage), - m, - pagesForMembers(rootPackage), - JNil - ) - - private def pagesForMembers(member: Member): JList[PageNode] = - def filterFunc(kind: Kind): Boolean = kind match { - case Kind.Package => true - case _ if kind.isInstanceOf[Classlike] => true - case Kind.Given(inner, _, _) => filterFunc(inner) - case Kind.EnumCase(inner) => filterFunc(inner) - case _ => false - } - val all = member - .membersBy(m => filterFunc(m.kind)) - .filter(m => m.origin == Origin.RegularlyDefined && m.inheritedFrom.isEmpty) - all.map(pageForMember(_)).asJava - - def pageForMember(c: Member): ClasslikePageNode = { - val name = - if c.kind == Kind.Object && c.companion.isDefined then - c.getName + "$" - else c.getName - - // Hack, need our own page! - ClasslikePageNode( - name, - mkMemberInfo(c), - JSet(c.getDri), - c.asInstanceOf[DClass], - JNil, - JNil, - ).modified(name, pagesForMembers(c)) // We need override default page - } diff --git a/scala3doc/src/dotty/dokka/translators/ScalaSignatureProvider.scala b/scala3doc/src/dotty/dokka/translators/ScalaSignatureProvider.scala index 04b6d7db74da..343c3a6cafc6 100644 --- a/scala3doc/src/dotty/dokka/translators/ScalaSignatureProvider.scala +++ b/scala3doc/src/dotty/dokka/translators/ScalaSignatureProvider.scala @@ -1,18 +1,6 @@ package dotty.dokka -import org.jetbrains.dokka.base.signatures._ -import org.jetbrains.dokka.model._ -import org.jetbrains.dokka.model.properties.{WithExtraProperties} -import org.jetbrains.dokka.pages._ -import org.jetbrains.dokka.base.signatures.KotlinSignatureProvider -import org.jetbrains.dokka.base.transformers.pages.comments.CommentsToContentConverter -import org.jetbrains.dokka.utilities.DokkaLogger -import collection.JavaConverters._ -import org.jetbrains.dokka.base.translators.documentables._ -import org.jetbrains.dokka.model.properties.PropertyContainer -import java.util.function.Consumer -import kotlin.jvm.functions.Function2 -import dotty.dokka.model.api.{Kind, _} +import dotty.dokka.model.api._ object ScalaSignatureProvider: def rawSignature(documentable: Member, builder: SignatureBuilder): SignatureBuilder = @@ -98,7 +86,7 @@ object ScalaSignatureProvider: private def classSignature(clazz: Member, cls: Kind.Class, builder: SignatureBuilder): SignatureBuilder = val selfSignature = builder .modifiersAndVisibility(clazz, clazz.kind.name) - .name(clazz.getName, clazz.getDri) + .name(clazz.name, clazz.dri) .generics(cls.typeParams) .functionParameters(cls.argsLists) @@ -107,14 +95,14 @@ object ScalaSignatureProvider: private def objectSignature(clazz: Member, builder: SignatureBuilder): SignatureBuilder = val selfSignature = builder .modifiersAndVisibility(clazz, clazz.kind.name) - .name(clazz.getName, clazz.getDri) + .name(clazz.name, clazz.dri) parentsSignature(clazz, selfSignature) private def traitSignature(clazz: Member, cls: Kind.Trait, builder: SignatureBuilder): SignatureBuilder = val selfSignature = builder .modifiersAndVisibility(clazz, clazz.kind.name) - .name(clazz.getName, clazz.getDri) + .name(clazz.name, clazz.dri) .generics(cls.typeParams) .functionParameters(cls.argsLists) @@ -123,7 +111,7 @@ object ScalaSignatureProvider: private def extensionSignature(extension: Member, fun: Kind.Def, builder: SignatureBuilder): SignatureBuilder = val withSignature = builder .modifiersAndVisibility(extension, "def") - .name(extension.getName, extension.getDri) + .name(extension.name, extension.dri) .generics(fun.typeParams) .functionParameters(fun.argsLists) @@ -141,7 +129,7 @@ object ScalaSignatureProvider: private def methodSignature(method: Member, cls: Kind.Def, builder: SignatureBuilder): SignatureBuilder = val bdr = builder .modifiersAndVisibility(method, "def") - .name(method.getName, method.getDri) + .name(method.name, method.dri) .generics(cls.typeParams) .functionParameters(cls.argsLists) if !method.kind.isInstanceOf[Kind.Constructor] then @@ -151,7 +139,7 @@ object ScalaSignatureProvider: private def typeSignature(tpe: Kind.Type, typeDef: Member, builder: SignatureBuilder): SignatureBuilder = val bdr = builder .modifiersAndVisibility(typeDef, if tpe.opaque then "opaque type" else "type") - .name(typeDef.getName, typeDef.getDri) + .name(typeDef.name, typeDef.dri) .generics(tpe.typeParams) if(!tpe.opaque){ (if tpe.concreate then bdr.text(" = ") else bdr) diff --git a/scala3doc/src/dotty/dokka/translators/ScalaSignatureUtils.scala b/scala3doc/src/dotty/dokka/translators/ScalaSignatureUtils.scala index ad48910b0c32..d1363b2da48a 100644 --- a/scala3doc/src/dotty/dokka/translators/ScalaSignatureUtils.scala +++ b/scala3doc/src/dotty/dokka/translators/ScalaSignatureUtils.scala @@ -1,11 +1,7 @@ package dotty.dokka -import org.jetbrains.dokka.base.signatures._ -import org.jetbrains.dokka.model.{ TypeParameter => _, _ } -import org.jetbrains.dokka.model.properties.WithExtraProperties -import org.jetbrains.dokka.pages._ -import collection.JavaConverters._ -import dotty.dokka.model.api.{Kind, _} + +import dotty.dokka.model.api._ case class InlineSignatureBuilder(names: Signature = Nil, preName: Signature = Nil) extends SignatureBuilder: override def text(str: String): SignatureBuilder = copy(names = str +: names) @@ -14,7 +10,7 @@ case class InlineSignatureBuilder(names: Signature = Nil, preName: Signature = N override def signature(s: Signature): SignatureBuilder = copy(names = s.reverse ++ names) object InlineSignatureBuilder: - def typeSignatureFor(d: Documentable): Signature = + def typeSignatureFor(d: Member): Signature = ScalaSignatureProvider.rawSignature(d, InlineSignatureBuilder()).asInstanceOf[InlineSignatureBuilder].names.reverse trait SignatureBuilder extends ScalaSignatureUtils { diff --git a/scala3doc/src/dotty/dokka/utils.scala b/scala3doc/src/dotty/dokka/utils.scala deleted file mode 100644 index 4d499c03c793..000000000000 --- a/scala3doc/src/dotty/dokka/utils.scala +++ /dev/null @@ -1,75 +0,0 @@ -package dotty.dokka - -import org.jetbrains.dokka.model.properties._ -import org.jetbrains.dokka.base.signatures._ -import org.jetbrains.dokka.model._ -import org.jetbrains.dokka.pages._ -import org.jetbrains.dokka.base.signatures.KotlinSignatureProvider -import org.jetbrains.dokka.base.transformers.pages.comments.CommentsToContentConverter -import org.jetbrains.dokka.utilities.DokkaLogger -import collection.JavaConverters._ -import org.jetbrains.dokka.base.translators.documentables._ -import org.jetbrains.dokka.model.properties.PropertyContainer -import java.util.function.Consumer -import kotlin.jvm.functions.Function2 -import org.jetbrains.dokka.DokkaConfiguration$DokkaSourceSet - -class BaseKey[T, V] extends ExtraProperty.Key[T, V]: - override def mergeStrategyFor(left: V, right: V): MergeStrategy[T] = - MergeStrategy.Remove.INSTANCE.asInstanceOf[MergeStrategy[T]] - - def definedIn(e: T): Boolean = e match - case e: WithExtraProperties[_] => e.getExtra.getMap.containsKey(this) - case _ => false - - - def getFrom(e: T): Option[V] = e match - case e: WithExtraProperties[_] => getFromExtra(e, this) - case _ => None - -def getFromExtra[V](e: WithExtraProperties[_], k: ExtraProperty.Key[_, V]): Option[V] = - Option(e.getExtra.getMap.get(k)).asInstanceOf[Option[V]] - - -extension (f: DFunction) - def isRightAssociative(): Boolean = f.getName.endsWith(":") - -def modifyContentGroup(originalContentNodeWithParents: Seq[ContentGroup], modifiedContentNode: ContentGroup): ContentGroup = - originalContentNodeWithParents match { - case head :: tail => tail match { - case tailHead :: tailTail => - val newChildren = tailHead.getChildren.asScala.map(c => if c != head then c else modifiedContentNode) - modifyContentGroup( - tailTail, - tailHead.copy( - newChildren.asJava, - tailHead.getDci, - tailHead.getSourceSets, - tailHead.getStyle, - tailHead.getExtra - ) - ) - case _ => head - } - case _ => modifiedContentNode - } - -def getContentGroupWithParents(root: ContentGroup, condition: ContentGroup => Boolean): Seq[ContentGroup] = { - def getFirstMatch(list: List[ContentNode]): Seq[ContentGroup] = list match { - case head :: tail => head match { - case g: ContentGroup => - val res = getContentGroupWithParents(g, condition) - if(!res.isEmpty) res - else getFirstMatch(tail) - case _ => getFirstMatch(tail) - } - - case _ => Seq() - } - if(condition(root)) Seq(root) - else { - val res = getFirstMatch(root.getChildren.asScala.toList) - if(!res.isEmpty) res ++ Seq(root) - else Seq() - } -} diff --git a/scala3doc/src/dotty/renderers/DokkaScalaHtmlRenderer.scala b/scala3doc/src/dotty/renderers/DokkaScalaHtmlRenderer.scala index 7dbeaca3ccc7..04d81bbb4bd8 100644 --- a/scala3doc/src/dotty/renderers/DokkaScalaHtmlRenderer.scala +++ b/scala3doc/src/dotty/renderers/DokkaScalaHtmlRenderer.scala @@ -19,11 +19,8 @@ import kotlinx.html.HTMLTag import kotlinx.html.DIV import dotty.dokka.model.api.Link import dotty.dokka.model.api.HierarchyGraph -import dotty.dokka.model.api.DocPart import org.jetbrains.dokka.base.resolvers.local.LocationProvider import org.jetbrains.dokka.base.transformers.pages.comments.DocTagToContentConverter -import dotty.dokka.site.StaticPageNode -import dotty.dokka.site.PartiallyRenderedContent import scala.util.Try import org.jetbrains.dokka.base.renderers.html.SearchbarDataInstaller import org.jsoup.Jsoup @@ -46,40 +43,25 @@ trait SignatureRenderer: case Link(name, dri) => renderLink(name, dri, modifiers:_*) class SignatureRendererImpl(pageContext: ContentPage, sourceSetRestriciton: JSet[DisplaySourceSet], locationProvider: LocationProvider) extends SignatureRenderer: - val currentDri = pageContext.getDri.asScala.head + val currentDri = pageContext.getDri.asScala.head.asScala - def link(dri: DRI): Option[String] = Option(locationProvider.resolve(dri, sourceSetRestriciton, pageContext)) + def link(dri: DRI): Option[String] = + Option(locationProvider.resolve(dri.asDokka, sourceSetRestriciton, pageContext)) class DokkaScalaHtmlRenderer(using ctx: DokkaContext) extends HtmlRenderer(ctx) { val args = summon[DocContext].args - override def render(root: RootPageNode): Unit = - def getModule(node: PageNode): Seq[DModule] = node match - case m: ModulePageNode => - m.getDocumentable match - case dmodule: DModule => Seq(dmodule) - case _ => Nil - case other => Nil - - val module = getModule(root).head - val pck = module.getPackages.get(0) - import dotty.dokka.model.api.driMap - val ourRenderer = new dotty.dokka.renderers.HtmlRenderer( - pck, - module.driMap, - dri => c => buildWithKotlinx(c, site.FakeContentPage(dri, c), null) - ) - + def init(ourRenderer: dotty.dokka.renderers.HtmlRenderer): Unit = val f = classOf[DefaultRenderer[_]].getDeclaredField("locationProvider") f.setAccessible(true) f.set(this, new LocationProvider { type DSS = JSet[org.jetbrains.dokka.model.DisplaySourceSet] - def resolve(to: DRI, sourceSets: DSS, context: PageNode): String = + def resolve(to: DDRI, sourceSets: DSS, context: PageNode): String = context match case null => ??? case fromPage: ContentPage => val from = fromPage.getDri.asScala.head - val path = ourRenderer.pathToPage(to, from) + val path = ourRenderer.pathToPage(to.asScala, from.asScala) path // None other operation is needed @@ -87,9 +69,11 @@ class DokkaScalaHtmlRenderer(using ctx: DokkaContext) extends HtmlRenderer(ctx) def resolve(node: PageNode, context: PageNode, skipExtension: Boolean): String = ??? def pathToRoot(from: PageNode): String = ??? def ancestors(node: PageNode): JList[PageNode] = ??? - def expectedLocationForDri(dri: DRI): String = ??? + def expectedLocationForDri(dri: DDRI): String = ??? }) - ourRenderer.render() + + override def render(root: RootPageNode): Unit = ??? + // We are using dokka renderer to render doc strings // Implementation below is based on Kotlin bytecode and we will try to migrate it to dokka // TODO (https://github.com/lampepfl/scala3doc/issues/232): Move this method to dokka @@ -239,101 +223,6 @@ class DokkaScalaHtmlRenderer(using ctx: DokkaContext) extends HtmlRenderer(ctx) private val HashRegex = "([^#]+)(#.+)".r - override def buildPageContent(context: FlowContent, page: ContentPage): Unit = - page match - case s: StaticPageNode if !s.hasFrame() => - case _ => buildNavigation(context, page) - - page.getContent match - case prc: PartiallyRenderedContent => - def tryAsDri(str: String) = - val (path, prefix) = str match - case HashRegex(path, prefix) => (path, prefix) - case _ => (str, "") - - val dri = prc.context.driForLink(prc.template.templateFile, path) - val res = dri.flatMap(dri => Option(getLocationProvider.resolve(dri, sourceSets, page))) - if res.isEmpty then - report.warn(s"Unable to resolve link '$str'", prc.template.file) - res.headOption.fold(str)(_ + prefix) - - def processLocalLink(str: String): String = - if str.startsWith("#") || str.isEmpty then str - else Try(URL(str)).map(_ => str).getOrElse(tryAsDri(str)) - - val html = prc.procsesHtml(processLocalLink, resolveLink(page)) - withHtml(context, html) - case content => - build(content, context, page, /*sourceSetRestriction=*/null) - - override def buildHtml(page: PageNode, resources: JList[String], kotlinxContent: FlowContentConsumer): String = - val (pageTitle, noFrame) = page match - case static: StaticPageNode => - (static.template.title, !static.hasFrame()) - case _ => - (page.getName, false) - - val projectLogo = - args.projectLogo.map { path => - val fileName = Paths.get(path).getFileName() - span(img(src := resolveRoot(page, s"project-logo/$fileName"))) - }.toSeq - - val renderer = SignatureRendererImpl(page.asInstanceOf[ContentPage], sourceSets, getLocationProvider) - - html( - head( - meta(charset := "utf-8"), - meta(name := "viewport", content := "width=device-width, initial-scale=1"), - title(pageTitle), - link(rel := "shortcut icon", `type` := "image/x-icon", href := resolveLink(page)("favicon.ico")), - linkResources(page, resources.asScala).toSeq, - script(raw(s"""var pathToRoot = "${getLocationProvider.pathToRoot(page)}";""")) - ), - body( - if noFrame then raw(buildWithKotlinx(kotlinxContent)) else - div(id := "container")( - div(id := "leftColumn")( - div(id := "logo")( - projectLogo, - span( - div(cls:="projectName")(args.name) - ), - span( - args.projectVersion.map(v => div(cls:="projectVersion")(v)).toList - ) - ), - div(id := "paneSearch"), - nav(id := "sideMenu2")( - summon[DocContext ].navigationNode.fold("No Navigation")(buildNavigation(renderer)) - ), - ), - div(id := "main")( - div (id := "leftToggler")( - span(cls := "icon-toggler") - ), - div(id := "scala3doc-searchBar"), - main( - raw(buildWithKotlinx(kotlinxContent)) - ), - footer( - span(cls := "go-to-top-icon")( - a(href := "#container")( - span(cls:="icon-vertical_align_top"), - raw(" Back to top") - ) - ), - raw("Generated by "), - a(href := "https://github.com/lampepfl/dotty/tree/master/scala3doc")( - img(src := resolveRoot(page, "images/scala3doc_logo.svg"), alt := "Scala3doc", cls := "scala3doc_logo") - ) - ) - ) - ), - script(`type` := "text/javascript", src := resolveRoot(page, "scripts/pages.js")) - ) - ).toString - private def resolveRoot(page: PageNode, path: String) = getLocationProvider.pathToRoot(page) + path @@ -353,14 +242,14 @@ class DokkaScalaHtmlRenderer(using ctx: DokkaContext) extends HtmlRenderer(ctx) case "js" => script(`type` := "text/javascript", src := resolveLink(page)(res), defer := "true") case _ => raw(res) - private def buildWithKotlinx(node: ContentNode, pageContext: ContentPage, sourceSetRestriction: JSet[DisplaySourceSet]): String = + def buildWithKotlinx(node: ContentNode, pageContext: ContentPage, sourceSetRestriction: JSet[DisplaySourceSet]): String = Gen_consumer_tagsKt.div( StreamKt.createHTML(true, false), null, (div) => {build(node, div, pageContext, sourceSetRestriction); kotlin.Unit.INSTANCE} ).toString.stripPrefix("
").stripSuffix("
\n") - private def buildWithKotlinx(func: FlowContentConsumer): String = + def buildWithKotlinx(func: FlowContentConsumer): String = Gen_consumer_tagsKt.div( StreamKt.createHTML(true, false), null, diff --git a/scala3doc/src/dotty/renderers/DotDiagramBuilder.scala b/scala3doc/src/dotty/renderers/DotDiagramBuilder.scala index 2171f21e0a2a..162eb7b23d4a 100644 --- a/scala3doc/src/dotty/renderers/DotDiagramBuilder.scala +++ b/scala3doc/src/dotty/renderers/DotDiagramBuilder.scala @@ -1,9 +1,6 @@ package dotty.dokka import dotty.dokka.model._ -import org.jetbrains.dokka.model._ -import org.jetbrains.dokka.pages._ -import dotty.dokka.model.api.Kind import HTML._ import dotty.dokka.model.api._ diff --git a/scala3doc/src/dotty/renderers/Locations.scala b/scala3doc/src/dotty/renderers/Locations.scala index d7d9bbfd5529..93df58a630f9 100644 --- a/scala3doc/src/dotty/renderers/Locations.scala +++ b/scala3doc/src/dotty/renderers/Locations.scala @@ -29,7 +29,7 @@ trait Locations(using ctx: DocContext): val loc = dri.location if loc == null then List("index") // Dokka leftovers else - val fqn = loc.split(Array('.')).toList ++ dri.anchor.toList match + val fqn = loc.split(Array('.')).toList match case List("") => List("index") case other => other @@ -42,17 +42,17 @@ trait Locations(using ctx: DocContext): def pathToPage(from: DRI, to: DRI): String = if to.isStaticFile || members.contains(to) then - pathTo(rawLocation(from), rawLocation(to)) +".html" + val anchor = if to.anchor.isEmpty then "" else "#" + to.anchor + pathTo(rawLocation(from), rawLocation(to)) +".html" + anchor else - val regex = raw"\[origin:(.*)\]".r - val origin = regex.findFirstIn(Option(to.extra).getOrElse("")) - origin match - case Some(path) => + to.origin match + case "" => + unknownPage(to) + case path => val external = ctx.externalDocumentationLinks.find(_.originRegexes.exists(r => r.matches(path))) external.fold(unknownPage(to))(constructPath(to)) - case None => - unknownPage(to) + def pathTo(to: Seq[String], fullFrom: Seq[String]): String = diff --git a/scala3doc/src/dotty/renderers/MemberRenderer.scala b/scala3doc/src/dotty/renderers/MemberRenderer.scala index 26e42d01d398..88ece96f64f9 100644 --- a/scala3doc/src/dotty/renderers/MemberRenderer.scala +++ b/scala3doc/src/dotty/renderers/MemberRenderer.scala @@ -3,7 +3,6 @@ package dotty.dokka import dotty.dokka.model.api._ import scala.collection.immutable.SortedMap import dotty.dokka.HTML._ -import org.jetbrains.dokka.base.transformers.pages.comments.DocTagToContentConverter import org.jetbrains.dokka.pages.{DCI, ContentKind, ContentNode} import org.jetbrains.dokka.model.properties.PropertyContainer import collection.JavaConverters._ @@ -162,8 +161,9 @@ class MemberRenderer(signatureRenderer: SignatureRenderer, buildNode: ContentNod def member(member: Member) = val filterAttributes = FilterAttributes.attributesFor(member) + val anchor = if member.dri.anchor.isEmpty then Nil else Seq(id := member.dri.anchor) def topLevelAttr = Seq(cls := "documentableElement") - ++ member.dri.anchor.map(id := _) + ++ anchor ++ filterAttributes.map{ case (n, v) => Attr(s"data-f-$n") := v } div(topLevelAttr:_*)( @@ -252,7 +252,7 @@ class MemberRenderer(signatureRenderer: SignatureRenderer, buildNode: ContentNod Tab("Grouped members", "custom_groups", content, "selected") def buildMembers(s: Member): AppliedTag = - val (membersInGroups, rest) = s.allMembers.partition(_.docs.exists(_.group.nonEmpty)) + val (membersInGroups, rest) = s.members.partition(_.docs.exists(_.group.nonEmpty)) val extensions = rest.groupBy{ _.kind match @@ -312,8 +312,8 @@ class MemberRenderer(signatureRenderer: SignatureRenderer, buildNode: ContentNod def classLikeParts(m: Member): Seq[AppliedTag] = if !m.kind.isInstanceOf[Classlike] then Nil else - val graphHtml = MemberExtension.getFrom(m).map(_.graph) match - case Some(graph) if graph.edges.nonEmpty => + val graphHtml = m.graph match + case graph if graph.edges.nonEmpty => Seq(div( id := "inheritance-diagram", cls := "diagram-class showGraph")( input(value := "Reset zoom", `type` := "button", cls := "btn", onclick := "zoomOut()"), svg(id := "graph"), diff --git a/scala3doc/src/dotty/renderers/ScalaSearchbarDataInstaller.scala b/scala3doc/src/dotty/renderers/ScalaSearchbarDataInstaller.scala deleted file mode 100644 index 3d0b1103b0d7..000000000000 --- a/scala3doc/src/dotty/renderers/ScalaSearchbarDataInstaller.scala +++ /dev/null @@ -1,67 +0,0 @@ -package dotty.dokka - -import com.fasterxml.jackson.module.kotlin.ExtensionsKt._ -import org.jetbrains.dokka.base.renderers.html.{SearchbarDataInstaller, SearchRecord} -import java.util.{List => JList} -import java.util.concurrent.ConcurrentHashMap -import collection.JavaConverters._ -import org.jetbrains.dokka.pages._ -import dotty.dokka.model.api._ -import org.jetbrains.dokka.plugability._ -import org.jetbrains.dokka.plugability.DokkaContext -import org.jetbrains.dokka.plugability.DokkaPluginKt._ -import org.jetbrains.dokka.base.DokkaBase -import dotty.dokka.PluginUtils._ -import org.jetbrains.dokka.base.signatures.SignatureProvider -import org.jetbrains.dokka._ -import org.jetbrains.dokka.model._ -import scala.collection.concurrent.TrieMap -import dotty.dokka.site.StaticPageNode - -class ScalaSearchbarDataInstaller(val ctx: DokkaContext) extends SearchbarDataInstaller: - - case class PageEntry(val name: String, val signature: String, val link: String, val pkg: String) - - // We need to use there mutable, concurrent collections because Dokka renders content concurrently - // and adds entry to searchbar on start of processing page - val pages = TrieMap[String, PageEntry]() - - - override def processPage(page: ContentPage, link: String) = - Option(page.getDocumentable) match { - case Some(member) => { - // All members that don't have their own page - val all = member - .membersBy(m => m.kind != dotty.dokka.model.api.Kind.Package && !m.kind.isInstanceOf[Classlike]) - .filter(m => m.origin == Origin.RegularlyDefined && m.inheritedFrom.isEmpty) - all.foreach(processMember(_, link)) - processMember(member, link) - } - case None => page match { - case p: StaticPageNode => processStaticSite(p, link) - case _ => () - } - } - - def flattenToText(signature: Signature): String = - signature.map { - case Link(name, dri) => name - case s: String => s - }.mkString - - def processMember(member: Member, link: String) = { - val signatureBuilder = ScalaSignatureProvider.rawSignature(member, InlineSignatureBuilder()).asInstanceOf[InlineSignatureBuilder] - val memberSignature = flattenToText(Seq(signatureBuilder.preName.head) ++ Seq(Link(member.name, member.dri)) ++ signatureBuilder.names.reverse) - val memberPackage = Option(member.dri.getPackageName).mkString - pages.addOne(memberSignature + link, PageEntry(member.name, memberSignature, link, memberPackage)) - } - - def processStaticSite(p: StaticPageNode, link: String) = { - pages.addOne(p.getName + link, PageEntry(p.getName, p.getName, link, "")) - } - - override def generatePagesList(): String = { - val mapper = jacksonObjectMapper() - val pagesList = pages.values.map(p => createSearchRecord(p.signature, p.pkg, p.link, (List(p.name)).asJava)).toList.asJava - mapper.writeValueAsString(pagesList) - } diff --git a/scala3doc/test/dotty/dokka/ExternalLocationProviderIntegrationTest.scala b/scala3doc/test/dotty/dokka/ExternalLocationProviderIntegrationTest.scala index bde7d1a406a7..a784286cb2ed 100644 --- a/scala3doc/test/dotty/dokka/ExternalLocationProviderIntegrationTest.scala +++ b/scala3doc/test/dotty/dokka/ExternalLocationProviderIntegrationTest.scala @@ -5,8 +5,6 @@ import scala.jdk.CollectionConverters._ import scala.util.matching.Regex import dotty.dokka.test.BuildInfo import java.nio.file.Path; -import org.jetbrains.dokka.plugability.DokkaContext -import org.jetbrains.dokka.pages.{RootPageNode, PageNode, ContentPage, ContentText, ContentNode, ContentComposite} import org.jsoup.Jsoup class JavadocExternalLocationProviderIntegrationTest extends ExternalLocationProviderIntegrationTest( @@ -40,7 +38,12 @@ class Scala3docExternalLocationProviderIntegrationTest extends ExternalLocationP ) -abstract class ExternalLocationProviderIntegrationTest(name: String, mappings: Seq[String], expectedLinks: Seq[String]) extends ScaladocTest(name): +abstract class ExternalLocationProviderIntegrationTest( + name: String, + mappings: Seq[String], + expectedLinks: Seq[String] + ) extends ScaladocTest(name): + override def args = super.args.copy( externalMappings = mappings.flatMap( s => ExternalDocLink.parse(s).fold(left => None, right => Some(right) @@ -48,8 +51,7 @@ abstract class ExternalLocationProviderIntegrationTest(name: String, mappings: S ).toList ) - def assertions = Assertion.AfterRendering { (root, ctx) => - given DokkaContext = ctx + override def runTest = afterRendering { val output = summon[DocContext].args.output.toPath.resolve("api") val linksBuilder = List.newBuilder[String] diff --git a/scala3doc/test/dotty/dokka/ExternalLocationProviderTest.scala b/scala3doc/test/dotty/dokka/ExternalLocationProviderTest.scala index fb71e0ff02f2..8b97b39b8e8d 100644 --- a/scala3doc/test/dotty/dokka/ExternalLocationProviderTest.scala +++ b/scala3doc/test/dotty/dokka/ExternalLocationProviderTest.scala @@ -1,17 +1,5 @@ package dotty.dokka -import org.jetbrains.dokka.pages.ContentPage -import org.jetbrains.dokka.pages.PageNode -import org.jetbrains.dokka.pages.RootPageNode -import org.jetbrains.dokka.pages.ModulePage -import org.jetbrains.dokka.pages.ClasslikePageNode -import org.jetbrains.dokka.model.DPackage -import org.jetbrains.dokka.plugability.DokkaContext -import org.jetbrains.dokka.base.resolvers.external._ -import org.jetbrains.dokka.base.resolvers.shared.{ExternalDocumentation => ED, _} -import org.jetbrains.dokka.base.resolvers.local._ -import org.jetbrains.dokka.model.DisplaySourceSet -import dotty.dokka.withNoOrigin import dotty.dokka.tasty._ import scala.collection.JavaConverters._ @@ -25,52 +13,54 @@ import org.junit.Assert._ import scala.quoted._ -class ExternalLocationProviderTest: - def createExternalLocationProvider(docURL: String, ext: String, kind: DocumentationKind) = { - val emptyExtDoc = ED( - URL(docURL), - PackageList( - RecognizedLinkFormat.Javadoc1, JSet(), JMap(), URL(docURL) - ) - ) - ScalaExternalLocationProvider(emptyExtDoc, ext, kind) - } +class ExternalLocationProviderTest + // TODO rewrite for locations! - def testResolvedLinks(provider: ScalaExternalLocationProvider, testcases: List[(DRI, String)]) = testcases.foreach { - case (dri, expect) => assertEquals(provider.resolve(dri), expect) - } +// def createExternalLocationProvider(docURL: String, ext: String, kind: DocumentationKind) = { +// val emptyExtDoc = ED( +// URL(docURL), +// PackageList( +// RecognizedLinkFormat.Javadoc1, JSet(), JMap(), URL(docURL) +// ) +// ) +// ScalaExternalLocationProvider(emptyExtDoc, ext, kind) +// } - @Test - def javadocExternalLocationProviderTest(): Unit = { - val provider = createExternalLocationProvider("https://docs.oracle.com/javase/8/docs/api/", ".html", DocumentationKind.Javadoc) - val testcases = List( - (DRI("java.util.Map$$Entry"), "https://docs.oracle.com/javase/8/docs/api/java/util/Map.Entry.html"), - (DRI("javax.swing.plaf.nimbus.AbstractRegionPainter$$PaintContext$$CacheMode"), "https://docs.oracle.com/javase/8/docs/api/javax/swing/plaf/nimbus/AbstractRegionPainter.PaintContext.CacheMode.html"), - (DRI("java.lang.CharSequence"), "https://docs.oracle.com/javase/8/docs/api/java/lang/CharSequence.html") - ) - testResolvedLinks(provider, testcases) - } +// def testResolvedLinks(provider: ScalaExternalLocationProvider, testcases: List[(DRI, String)]) = testcases.foreach { +// case (dri, expect) => assertEquals(provider.resolve(dri), expect) +// } - @Test - def scaladocExternalLocationProviderTest(): Unit = { - val provider = createExternalLocationProvider("https://www.scala-lang.org/api/current/", ".html", DocumentationKind.Scaladoc) - val testcases = List( - (DRI("scala.Predef$"),"https://www.scala-lang.org/api/current/scala/Predef$.html"), - (DRI("scala.util.package$$chaining$"), "https://www.scala-lang.org/api/current/scala/util/package$$chaining$.html"), - (DRI("scala.util.Using$"), "https://www.scala-lang.org/api/current/scala/util/Using$.html"), - (DRI("scala.util.matching.Regex$$Match"), "https://www.scala-lang.org/api/current/scala/util/matching/Regex$$Match.html") - ) - testResolvedLinks(provider, testcases) - } +// @Test +// def javadocExternalLocationProviderTest(): Unit = { +// val provider = createExternalLocationProvider("https://docs.oracle.com/javase/8/docs/api/", ".html", DocumentationKind.Javadoc) +// val testcases = List( +// (DRI("java.util.Map$$Entry"), "https://docs.oracle.com/javase/8/docs/api/java/util/Map.Entry.html"), +// (DRI("javax.swing.plaf.nimbus.AbstractRegionPainter$$PaintContext$$CacheMode"), "https://docs.oracle.com/javase/8/docs/api/javax/swing/plaf/nimbus/AbstractRegionPainter.PaintContext.CacheMode.html"), +// (DRI("java.lang.CharSequence"), "https://docs.oracle.com/javase/8/docs/api/java/lang/CharSequence.html") +// ) +// testResolvedLinks(provider, testcases) +// } - @Test - def scala3docExternalLocationProviderTest(): Unit = { - val provider = createExternalLocationProvider("https://dotty.epfl.ch/api/", ".html", DocumentationKind.Scala3doc) - val testcases = List( - (DRI("scala.Predef$"),"https://dotty.epfl.ch/api/scala/Predef$.html"), - (DRI("scala.util.package$$chaining$"), "https://dotty.epfl.ch/api/scala/util/package$$chaining$.html"), - (DRI("scala.util.Using$"), "https://dotty.epfl.ch/api/scala/util/Using$.html"), - (DRI("scala.util.matching.Regex$$Match"), "https://dotty.epfl.ch/api/scala/util/matching/Regex$$Match.html") - ) - testResolvedLinks(provider, testcases) - } \ No newline at end of file +// @Test +// def scaladocExternalLocationProviderTest(): Unit = { +// val provider = createExternalLocationProvider("https://www.scala-lang.org/api/current/", ".html", DocumentationKind.Scaladoc) +// val testcases = List( +// (DRI("scala.Predef$"),"https://www.scala-lang.org/api/current/scala/Predef$.html"), +// (DRI("scala.util.package$$chaining$"), "https://www.scala-lang.org/api/current/scala/util/package$$chaining$.html"), +// (DRI("scala.util.Using$"), "https://www.scala-lang.org/api/current/scala/util/Using$.html"), +// (DRI("scala.util.matching.Regex$$Match"), "https://www.scala-lang.org/api/current/scala/util/matching/Regex$$Match.html") +// ) +// testResolvedLinks(provider, testcases) +// } + +// @Test +// def scala3docExternalLocationProviderTest(): Unit = { +// val provider = createExternalLocationProvider("https://dotty.epfl.ch/api/", ".html", DocumentationKind.Scala3doc) +// val testcases = List( +// (DRI("scala.Predef$"),"https://dotty.epfl.ch/api/scala/Predef$.html"), +// (DRI("scala.util.package$$chaining$"), "https://dotty.epfl.ch/api/scala/util/package$$chaining$.html"), +// (DRI("scala.util.Using$"), "https://dotty.epfl.ch/api/scala/util/Using$.html"), +// (DRI("scala.util.matching.Regex$$Match"), "https://dotty.epfl.ch/api/scala/util/matching/Regex$$Match.html") +// ) +// testResolvedLinks(provider, testcases) +// } \ No newline at end of file diff --git a/scala3doc/test/dotty/dokka/RaportingTest.scala b/scala3doc/test/dotty/dokka/RaportingTest.scala index df9bad455696..ca6633f406fa 100644 --- a/scala3doc/test/dotty/dokka/RaportingTest.scala +++ b/scala3doc/test/dotty/dokka/RaportingTest.scala @@ -55,5 +55,5 @@ class ReportingTest: assertNoWarning(diag) assertNoErrors(diag) - assertMessagesAbout(diag.infoMsgs)("dotty.dokka.DottyDokkaPlugin", "generation completed successfully") + assertMessagesAbout(diag.infoMsgs)("generation completed successfully") } \ No newline at end of file diff --git a/scala3doc/test/dotty/dokka/ScaladocTest.scala b/scala3doc/test/dotty/dokka/ScaladocTest.scala index c9644035989d..5b1afc1cc6dd 100644 --- a/scala3doc/test/dotty/dokka/ScaladocTest.scala +++ b/scala3doc/test/dotty/dokka/ScaladocTest.scala @@ -1,19 +1,20 @@ package dotty.dokka -import org.jetbrains.dokka.plugability.DokkaContext -import org.jetbrains.dokka.model.{DModule, WithChildren} -import org.jetbrains.dokka.pages.RootPageNode -import org.jetbrains.dokka.testApi.testRunner.{DokkaTestGenerator, TestMethods} -import org.jetbrains.dokka.testApi.logger.TestLogger -import org.jetbrains.dokka.utilities.DokkaConsoleLogger -import org.jetbrains.dokka.DokkaConfiguration import scala.jdk.CollectionConverters.{ListHasAsScala, SeqHasAsJava} import org.junit.{Test, Rule} import org.junit.rules.{TemporaryFolder, ErrorCollector} import java.io.File abstract class ScaladocTest(val name: String): - def assertions: Seq[Assertion] + + def afterRendering(op: DocContext ?=> Unit) = + val ctx = Scala3doc.run(args)(using testContext) + op(using ctx) + + + def withModule(op: DocContext ?=> Module => Unit) = + given DocContext = testDocContext + op(ScalaModuleProvider.mkModule()) private def getTempDir() : TemporaryFolder = val folder = new TemporaryFolder() @@ -27,52 +28,13 @@ abstract class ScaladocTest(val name: String): projectVersion = Some("1.0") ) + @Test + def runTest: Unit + + @Rule def collector = _collector private val _collector = new ErrorCollector(); def reportError(msg: String) = collector.addError(new AssertionError(msg)) - @Test - def executeTest = - DokkaTestGenerator( - DocContext(args, testContext), - TestLogger(new Scala3DocDokkaLogger(using testContext)), - assertions.asTestMethods, - Nil.asJava - ).generate() - -end ScaladocTest - -type Validator = () => Unit - -/** - * Those assertions map 1-1 to their dokka counterparts. Some of them may be irrelevant in scala3doc. - */ -enum Assertion: - case AfterPluginSetup(fn: DokkaContext => Unit) - case DuringValidation(fn: Validator => Unit) - case AfterDocumentablesCreation(fn: Seq[DModule] => Unit) - case AfterPreMergeDocumentablesTransformation(fn: Seq[DModule] => Unit) - case AfterDocumentablesMerge(fn: DModule => Unit) - case AfterDocumentablesTransformation(fn: DModule => Unit) - case AfterPagesGeneration(fn: RootPageNode => Unit) - case AfterPagesTransformation(fn: RootPageNode => Unit) - case AfterRendering(fn: (RootPageNode, DokkaContext) => Unit) - -extension (s: Seq[Assertion]) - def asTestMethods: TestMethods = - import Assertion._ - TestMethods( - (context => s.collect { case AfterPluginSetup(fn) => fn(context) }.kUnit), - (validator => s.collect { case DuringValidation(fn) => fn(() => validator.invoke()) }.kUnit), - (modules => s.collect { case AfterDocumentablesCreation(fn) => fn(modules.asScala.toSeq) }.kUnit), - (modules => s.collect { case AfterPreMergeDocumentablesTransformation(fn) => fn(modules.asScala.toSeq) }.kUnit), - (module => s.collect { case AfterDocumentablesMerge(fn) => fn(module)}.kUnit), - (module => s.collect { case AfterDocumentablesTransformation(fn) => fn(module) }.kUnit), - (root => s.collect { case AfterPagesGeneration(fn) => fn(root) }.kUnit), - (root => s.collect { case AfterPagesTransformation(fn) => fn(root) }.kUnit), - ((root, context) => s.collect { case AfterRendering(fn) => fn(root, context)}.kUnit) - ) -extension [T] (s: T) - private def kUnit = kotlin.Unit.INSTANCE diff --git a/scala3doc/test/dotty/dokka/SignatureTest.scala b/scala3doc/test/dotty/dokka/SignatureTest.scala index 3a82ccfc53e2..9995d6f9c5d9 100644 --- a/scala3doc/test/dotty/dokka/SignatureTest.scala +++ b/scala3doc/test/dotty/dokka/SignatureTest.scala @@ -5,8 +5,6 @@ import scala.jdk.CollectionConverters._ import scala.util.matching.Regex import dotty.dokka.test.BuildInfo import java.nio.file.Path; -import org.jetbrains.dokka.plugability.DokkaContext -import org.jetbrains.dokka.pages.{RootPageNode, PageNode, ContentPage, ContentText, ContentNode, ContentComposite} import org.jsoup.Jsoup import dotty.dokka.model.api._ @@ -23,7 +21,8 @@ abstract class SignatureTest( ignoreMissingSignatures: Boolean = false, filterFunc: (Path) => Boolean = _ => true ) extends ScaladocTest(testName): - override def assertions = Assertion.AfterRendering { (root, ctx) => + + def runTest = afterRendering { val sources = sourceFiles match case Nil => testName :: Nil case s => s @@ -37,10 +36,10 @@ abstract class SignatureTest( .groupMap(_.name)(_.signature) val unexpectedFromSources: Set[String] = allSignaturesFromSources.collect { case Unexpected(name) => name }.toSet - given DokkaContext = ctx - val actualSignatures: Map[String, Seq[String]] = signaturesFromDocumentation(root).flatMap { signature => - findName(signature, signatureKinds).map(_ -> signature) - }.groupMap(_._1)(_._2) + val actualSignatures: Map[String, Seq[String]] = + signaturesFromDocumentation().flatMap { signature => + findName(signature, signatureKinds).map(_ -> signature) + }.groupMap(_._1)(_._2) val unexpected = unexpectedFromSources.flatMap(actualSignatures.get).flatten val expectedButNotFound = expectedFromSources.flatMap { @@ -104,9 +103,8 @@ abstract class SignatureTest( ) } - private def signaturesFromDocumentation(root: PageNode)(using DocContext): Seq[String] = + private def signaturesFromDocumentation()(using DocContext): Seq[String] = val output = summon[DocContext].args.output.toPath.resolve("api") - println(output) val signatures = List.newBuilder[String] def processFile(path: Path): Unit = if filterFunc(path) then diff --git a/scala3doc/test/dotty/dokka/diagram/HierarchyTest.scala b/scala3doc/test/dotty/dokka/diagram/HierarchyTest.scala index f197bea3aa97..0908be5c7af2 100644 --- a/scala3doc/test/dotty/dokka/diagram/HierarchyTest.scala +++ b/scala3doc/test/dotty/dokka/diagram/HierarchyTest.scala @@ -1,107 +1,101 @@ package dotty.dokka.diagram import dotty.dokka.ScaladocTest -import dotty.dokka.Assertion.AfterDocumentablesTransformation -import dotty.dokka.kUnit import dotty.dokka.model.api._ import scala.jdk.CollectionConverters.{ListHasAsScala, SeqHasAsJava} import org.junit.Assert.{assertSame, assertTrue, assertEquals} class HierarchyTest extends ScaladocTest("hierarchy"): - override def assertions = Seq( - AfterDocumentablesTransformation { m => - m.visitMembers { x => - if (x.getName == "C1") { - assertEquals(List("A1", "A2[Int]", "A3[Int, String]", "Any", "B1", "B2", "B3", "Matchable", "Object"), x.getParentsAsStrings) - assertEquals(List("B1", "B2", "B3"), x.getDirectParentsAsStrings) - assertEquals(List("E1", "E2"), x.getKnownChildrenAsStrings) - val graph = MemberExtension.getFrom(x).map(_.graph) - assertTrue("Graph is empty!", graph.isDefined) - assertEquals( - Set( - "Object" -> "Matchable", - "Matchable" -> "Any", - "Object" -> "Any", - "A1" -> "Object", - "A2[Int]" -> "Object", - "A3[Int, String]" -> "Object", - "B1" -> "Object", - "B1" -> "A1", - "B2" -> "Object", - "B2" -> "A1", - "B2" -> "A2[Int]", - "B3" -> "Object", - "B3" -> "A2[Int]", - "B3" -> "A3[Int, String]", - "C1[A, B, C]" -> "Object", - "C1[A, B, C]" -> "B1", - "C1[A, B, C]" -> "B2", - "C1[A, B, C]" -> "B3", - "E1" -> "C1[A, B, C]", - "E2" -> "C1[A, B, C]" - ), - graph.get.edges.map((a, b) => (a.signature.getName, b.signature.getName)).toSet - ) - } - if (x.getName == "E2") { - assertEquals(List("A1", "A2[Int]", "A3[Int, String]", "A4", "Any", "B1", "B2", "B3", "C1[Int, Boolean, Any]", "D2[Int, Boolean]", "D3", "Matchable", "Object"), x.getParentsAsStrings) - assertEquals(List("C1[Int, Boolean, Any]", "D2[Int, Boolean]", "D3"), x.getDirectParentsAsStrings) - assertEquals(List.empty, x.getKnownChildrenAsStrings) - val graph = MemberExtension.getFrom(x).map(_.graph) - assertTrue("Graph is empty!", graph.isDefined) - assertEquals( - Set( - "Object" -> "Any", - "A1" -> "Object", - "A2[Int]" -> "Object", - "A3[Int, String]" -> "Object", - "A4" -> "Object", - "B1" -> "Object", - "B1" -> "A1", - "B2" -> "Object", - "B2" -> "A1", - "B2" -> "A2[Int]", - "B3" -> "Object", - "B3" -> "A2[Int]", - "B3" -> "A3[Int, String]", - "C1[Int, Boolean, Any]" -> "Object", - "C1[Int, Boolean, Any]" -> "B1", - "C1[Int, Boolean, Any]" -> "B2", - "C1[Int, Boolean, Any]" -> "B3", - "Object" -> "Matchable", - "Matchable" -> "Any", - "E2" -> "D2[Int, Boolean]", - "E2" -> "D3", - "D2[Int, Boolean]" -> "Object", - "D3" -> "A4", - "D3" -> "Object", - "E2" -> "C1[Int, Boolean, Any]" - ), - graph.get.edges.map((a, b) => (a.signature.getName, b.signature.getName)).toSet - ) - } - if (x.getName == "A2") { - assertEquals(List("Any", "Matchable", "Object"), x.getParentsAsStrings) - assertEquals(List.empty, x.getDirectParentsAsStrings) - assertEquals(List("B2", "B3", "C1[A, B, C]", "E1", "E2"), x.getKnownChildrenAsStrings) - val graph = MemberExtension.getFrom(x).map(_.graph) - assertTrue("Graph is empty!", graph.isDefined) - assertEquals( - Set( - "Object" -> "Matchable", - "Matchable" -> "Any", - "Object" -> "Any", - "A2[T]" -> "Object", - "B2" -> "A2[T]", - "B3" -> "A2[T]", - "C1[A, B, C]" -> "B2", - "C1[A, B, C]" -> "B3", - "E1" -> "C1[A, B, C]", - "E2" -> "C1[A, B, C]" - ), - graph.get.edges.map((a, b) => (a.signature.getName, b.signature.getName)).toSet - ) - } - } - } - ) + override def runTest = withModule(_.visitMembers(checkMember)) + + def checkMember(x: Member) = x.name match + case "C1" => + assertEquals(List("A1", "A2[Int]", "A3[Int, String]", "Any", "B1", "B2", "B3", "Matchable", "Object"), x.getParentsAsStrings) + assertEquals(List("B1", "B2", "B3"), x.getDirectParentsAsStrings) + assertEquals(List("E1", "E2"), x.getKnownChildrenAsStrings) + assertTrue("Graph is empty!", x.graph != HierarchyGraph.empty) + assertEquals( + Set( + "Object" -> "Matchable", + "Matchable" -> "Any", + "Object" -> "Any", + "A1" -> "Object", + "A2[Int]" -> "Object", + "A3[Int, String]" -> "Object", + "B1" -> "Object", + "B1" -> "A1", + "B2" -> "Object", + "B2" -> "A1", + "B2" -> "A2[Int]", + "B3" -> "Object", + "B3" -> "A2[Int]", + "B3" -> "A3[Int, String]", + "C1[A, B, C]" -> "Object", + "C1[A, B, C]" -> "B1", + "C1[A, B, C]" -> "B2", + "C1[A, B, C]" -> "B3", + "E1" -> "C1[A, B, C]", + "E2" -> "C1[A, B, C]" + ), + x.graph.edges.map((a, b) => (a.signature.getName, b.signature.getName)).toSet + ) + case "E2" => + assertEquals(List("A1", "A2[Int]", "A3[Int, String]","A4", "Any", "B1", "B2", "B3", "C1[Int, Boolean, Any]", "D2[Int, Boolean]", "D3", "Matchable", "Object"), x.getParentsAsStrings) + assertEquals(List("C1[Int, Boolean, Any]", "D2[Int, Boolean]", "D3"), x.getDirectParentsAsStrings) + assertEquals(List.empty, x.getKnownChildrenAsStrings) + assertTrue("Graph is empty!", x.graph != HierarchyGraph.empty) + assertEquals( + Set( + "Object" -> "Any", + "A1" -> "Object", + "A2[Int]" -> "Object", + "A3[Int, String]" -> "Object", + "A4" -> "Object", + "B1" -> "Object", + "B1" -> "A1", + "B2" -> "Object", + "B2" -> "A1", + "B2" -> "A2[Int]", + "B3" -> "Object", + "B3" -> "A2[Int]", + "B3" -> "A3[Int, String]", + "C1[Int, Boolean, Any]" -> "Object", + "C1[Int, Boolean, Any]" -> "B1", + "C1[Int, Boolean, Any]" -> "B2", + "C1[Int, Boolean, Any]" -> "B3", + "Object" -> "Matchable", + "Matchable" -> "Any", + "E2" -> "D2[Int, Boolean]", + "E2" -> "D3", + "D2[Int, Boolean]" -> "Object", + "D3" -> "A4", + "D3" -> "Object", + "E2" -> "C1[Int, Boolean, Any]", + ), + x.graph.edges.map((a, b) => (a.signature.getName, b.signature.getName)).toSet + ) + case "A2" => + assertEquals(List("Any", "Matchable", "Object"), x.getParentsAsStrings) + assertEquals(List.empty, x.getDirectParentsAsStrings) + assertEquals(List("B2", "B3", "C1[A, B, C]", "E1", "E2"), x.getKnownChildrenAsStrings) + assertTrue("Graph is empty!", x.graph != HierarchyGraph.empty) + assertEquals( + Set( + "Object" -> "Matchable", + "Matchable" -> "Any", + "Object" -> "Any", + "A2[T]" -> "Object", + "B2" -> "A2[T]", + "B3" -> "A2[T]", + "C1[A, B, C]" -> "B2", + "C1[A, B, C]" -> "B3", + "E1" -> "C1[A, B, C]", + "E2" -> "C1[A, B, C]", + ), + x.graph.edges.map((a, b) => (a.signature.getName, b.signature.getName)).toSet + ) + case _ => + + + + diff --git a/scala3doc/test/dotty/dokka/linking/DriTest.scala b/scala3doc/test/dotty/dokka/linking/DriTest.scala index 92ba7ca64d74..4f43ee2dad40 100644 --- a/scala3doc/test/dotty/dokka/linking/DriTest.scala +++ b/scala3doc/test/dotty/dokka/linking/DriTest.scala @@ -3,16 +3,15 @@ package linking import scala.jdk.CollectionConverters._ import scala.Function.const -import org.jetbrains.dokka.model.DModule import dotty.dokka.model.api._ -import dotty.dokka.{ScaladocTest, Assertion} +import dotty.dokka.ScaladocTest abstract class DriTest(testName: String) extends ScaladocTest(testName): // override for additional assertions def assertOnDRIs(dris: Seq[DRI]): Unit = () - override def assertions = Assertion.AfterDocumentablesTransformation { root => - val dris = root.collectMembers.map(_.dri) + override def runTest = withModule { module => + val dris = module.members.keys.toSeq val grouping = dris.groupMapReduce(identity)(const(1))(_+_) val duplicates = grouping.filter { (_, v )=> v > 1 } @@ -26,7 +25,6 @@ abstract class DriTest(testName: String) extends ScaladocTest(testName): assertOnDRIs(dris) } :: Nil -extension (m: DModule) private def collectMembers = m.getPackages.asScala.toList.flatMap(collectFrom) private def collectFrom(m: Member): Seq[Member] = - m +: m.allMembers.filter(_.origin == Origin.RegularlyDefined).flatMap(collectFrom) + m +: m.members.filter(_.origin == Origin.RegularlyDefined).flatMap(collectFrom) diff --git a/scala3doc/test/dotty/dokka/linking/DriTestCases.scala b/scala3doc/test/dotty/dokka/linking/DriTestCases.scala index 9dba3f3d752a..90da9e2cfeb0 100644 --- a/scala3doc/test/dotty/dokka/linking/DriTestCases.scala +++ b/scala3doc/test/dotty/dokka/linking/DriTestCases.scala @@ -14,12 +14,11 @@ class FunctionTest extends DriTest("functionDRI") class NestingTest extends DriTest("nestingDRI"): override def assertOnDRIs(dris: Seq[DRI]) = - println(dris.groupBy(_.location).map(_._1)) dris.groupBy(_.location).foreach{ case (location, dris) => assertTrue(s"Location $location has multiple dris assigned: $dris", dris.size == 1) } @Ignore class ShadowingTest extends DriTest("shadowingDRI"): override def assertOnDRIs(dris: Seq[DRI]) = - if (!dris.flatMap(d => Option(d.getExtra)).exists(_.contains("findThisDeclaration"))) then + if (!dris.exists(_.symbolUUID.contains("findThisDeclaration"))) then reportError("\n\nSymbol with name `findThisDeclaration` was expected but not found\n\n") \ No newline at end of file diff --git a/scala3doc/test/dotty/dokka/site/RelativeLinksTests.scala b/scala3doc/test/dotty/dokka/site/RelativeLinksTests.scala deleted file mode 100644 index 5e4459e027bb..000000000000 --- a/scala3doc/test/dotty/dokka/site/RelativeLinksTests.scala +++ /dev/null @@ -1,24 +0,0 @@ -package dotty.dokka.site - -import org.junit.Test -import org.junit.Assert._ - -class RelativePathsLink: - - @Test - def testLinks() = - def path(from: String, to: String) = - relativePath(from.split('/').toList, to.split('/').toList) - - assertEquals(relativePath(Nil, Seq("a", "b")), "a/b") - - assertEquals( - path("api/dotty/dokka/tasty/comments/wiki", "api/dotty/dokka/tasty/comments"), - "../comments" - ) - - assertEquals( - path("api/dotty/dokka/tasty/comments/wiki", "api/index"), - "../../../../index" - ) - From 4e337f22c237ef2e752edb72b605d756ac872ff1 Mon Sep 17 00:00:00 2001 From: Krzysztof Romanowski Date: Mon, 25 Jan 2021 23:15:49 +0100 Subject: [PATCH 04/12] Extend Flexmark with doc strings Remove rest of dokka usages --- scala3doc-testcases/src/tests/docString.scala | 82 ++++ scala3doc-testcases/src/tests/site.scala | 52 +++ scala3doc/src/dotty/dokka/DRI.scala | 25 +- scala3doc/src/dotty/dokka/DocContext.scala | 43 +- scala3doc/src/dotty/dokka/Scala3doc.scala | 43 +- .../src/dotty/dokka/ScalaModuleCreator.scala | 3 +- scala3doc/src/dotty/dokka/compat.scala | 73 +--- .../src/dotty/dokka/model/scalaModel.scala | 117 ------ .../dotty/dokka/tasty/ScalaDocSupport.scala | 3 - scala3doc/src/dotty/dokka/tasty/SymOps.scala | 2 - .../src/dotty/dokka/tasty/TastyParser.scala | 4 +- .../dokka/tasty/comments/BaseConverter.scala | 23 -- .../dotty/dokka/tasty/comments/Comments.scala | 148 ++++--- .../dotty/dokka/tasty/comments/Emitter.scala | 18 - .../tasty/comments/MarkdownConverter.scala | 211 ---------- .../dokka/tasty/comments/MarkdownParser.scala | 38 +- .../markdown/DocFlexmarkExtension.scala | 69 ++++ .../dotty/dokka/tasty/comments/package.scala | 6 - .../dokka/tasty/comments/wiki/Converter.scala | 159 -------- .../dokka/tasty/comments/wiki/Entities.scala | 19 +- .../dokka/tasty/comments/wiki/Parser.scala | 5 +- .../ScalaCommentToContentConverter.scala | 60 --- .../renderers/DokkaScalaHtmlRenderer.scala | 258 ------------ .../src/dotty/renderers/HtmlRenderer.scala | 7 +- .../src/dotty/renderers/MemberRenderer.scala | 26 +- .../dotty/renderers/SignatureRenderer.scala | 41 ++ .../src/dotty/renderers/WikiDocRenderer.scala | 69 ++++ scala3doc/src/dotty/renderers/html.scala | 8 + scala3doc/test/dotty/dokka/BaseHtmlTest.scala | 68 ++++ .../dokka/site/SiteGeneratationTest.scala | 61 +-- .../tasty/comments/CommentParserTest.scala | 380 ------------------ .../comments/DocFlexmarkParserTests.scala | 13 + .../tasty/comments/IntegrationTest.scala | 32 ++ .../comments/MarkdownConverterTests.scala | 13 - 34 files changed, 595 insertions(+), 1584 deletions(-) create mode 100644 scala3doc-testcases/src/tests/docString.scala create mode 100644 scala3doc-testcases/src/tests/site.scala delete mode 100644 scala3doc/src/dotty/dokka/model/scalaModel.scala delete mode 100644 scala3doc/src/dotty/dokka/tasty/comments/BaseConverter.scala delete mode 100644 scala3doc/src/dotty/dokka/tasty/comments/Emitter.scala delete mode 100644 scala3doc/src/dotty/dokka/tasty/comments/MarkdownConverter.scala create mode 100644 scala3doc/src/dotty/dokka/tasty/comments/markdown/DocFlexmarkExtension.scala delete mode 100644 scala3doc/src/dotty/dokka/tasty/comments/wiki/Converter.scala delete mode 100644 scala3doc/src/dotty/dokka/transformers/ScalaCommentToContentConverter.scala delete mode 100644 scala3doc/src/dotty/renderers/DokkaScalaHtmlRenderer.scala create mode 100644 scala3doc/src/dotty/renderers/SignatureRenderer.scala create mode 100644 scala3doc/src/dotty/renderers/WikiDocRenderer.scala create mode 100644 scala3doc/test/dotty/dokka/BaseHtmlTest.scala delete mode 100644 scala3doc/test/dotty/dokka/tasty/comments/CommentParserTest.scala create mode 100644 scala3doc/test/dotty/dokka/tasty/comments/DocFlexmarkParserTests.scala create mode 100644 scala3doc/test/dotty/dokka/tasty/comments/IntegrationTest.scala delete mode 100644 scala3doc/test/dotty/dokka/tasty/comments/MarkdownConverterTests.scala diff --git a/scala3doc-testcases/src/tests/docString.scala b/scala3doc-testcases/src/tests/docString.scala new file mode 100644 index 000000000000..3ceb0c8ee82e --- /dev/null +++ b/scala3doc-testcases/src/tests/docString.scala @@ -0,0 +1,82 @@ +package tests + +package commonlinks: + class SomeOtherPackage: + def method = 123 + + object SomeOtherPackage + + enum SomeOtherEnum: + case A + case B + + + +package wikilinks: + class SomeClass: + def method = 123 + + /** + * [[AnNonExisitingObject]] + * [[SomeClass!.ala]] + * @syntax wiki + */ + class BrokenLinks + + /** + * [[tests.commonlinks.SomeOtherPackage]] + * @syntax wiki + */ + class OtherPackageLink + + /** + * [[tests.commonlinks.SomeOtherPackage!method]] + * [[tests.commonlinks.SomeOtherPackage#]] + * [[tests.commonlinks.SomeOtherEnum!A]] + * @syntax wiki + */ + class OtherPackageMembers + /** + * [[SomeClass]] + * @syntax wiki + */ + class SamePackageLink + + /** + * [[SomeClass.method]] + * @syntax wiki + */ + class SamePackageMembers + +// It should be exact copy of wikilinks +package mdlinks: + class SomeClass: + def method = 123 + + /** + * [[AnNonExisitingObject]] + * [[SomeClass!.ala]] + */ + class BrokenLinks + + /** + * [[tests.commonlinks.SomeOtherPackage]] + */ + class OtherPackageLink + + /** + * [[tests.commonlinks.SomeOtherPackage!method]] + * [[tests.commonlinks.SomeOtherPackage#]] + * [[tests.commonlinks.SomeOtherEnum!A]] + * @syntax wiki + */ + class OtherPackageMembers + /** + * [[SomeClass]] + */ + class SamePackageLink + + /** + * [[SomeClass.method]] + */ + class SamePackageMembers \ No newline at end of file diff --git a/scala3doc-testcases/src/tests/site.scala b/scala3doc-testcases/src/tests/site.scala new file mode 100644 index 000000000000..5750f6bcd37f --- /dev/null +++ b/scala3doc-testcases/src/tests/site.scala @@ -0,0 +1,52 @@ +package tests.site + + +package some.other: + class SomeOtherPackage + +class SomeClass: + def method(a: Int): Int + = 123 + a + val field = + 123 + +/** + * Broken link, that should result a warning not break compilation + * [[tests.links.AnObject]] + + */ +class BrokenLink: + def verifyIfLinksTestIsGenerated(b: Int): Int + = 123 + +/** + * [[tests.links.some.other.SomeOtherPackage]] + */ +class OtherPackageLink + +/** + * [[tests.links.SomeClass]] + */ +class SamePackageLink + + +/** + * Broken link, that should result a warning not break compilation + * [[tests.links.AnObject]] + + */ +class BrokenLinkWiki: + def verifyIfLinksTestIsGenerated(b: Int): Int + = 123 + +/** + * [[tests.links.some.other.SomeOtherPackage]] + * @syntax wiki + */ +class OtherPackageLinkWiki + +/** + * [[tests.links.SomeClass]] + * @syntax wiki + */ +class SamePackageLinkWiki \ No newline at end of file diff --git a/scala3doc/src/dotty/dokka/DRI.scala b/scala3doc/src/dotty/dokka/DRI.scala index 2733a2920d77..bf82058c98ee 100644 --- a/scala3doc/src/dotty/dokka/DRI.scala +++ b/scala3doc/src/dotty/dokka/DRI.scala @@ -1,16 +1,13 @@ package dotty.dokka import java.nio.file.Path -import org.jetbrains.dokka.links.PointingToDeclaration val staticFileSymbolUUID = "___staticFile___" val topLevelDri = DRI("/") -type DDRI = org.jetbrains.dokka.links.DRI - // we may need target... -case class DRI( +final case class DRI( location: String, anchor: String = "", origin: String = "", @@ -20,25 +17,5 @@ case class DRI( def isStaticFile = symbolUUID == staticFileSymbolUUID - def asDokka: DDRI = new DDRI( - location, - anchor, - null, - PointingToDeclaration.INSTANCE, - origin + ":" + symbolUUID - ) - object DRI: def forPath(path: Path) = DRI(location = path.toString, symbolUUID = staticFileSymbolUUID) - -extension (dokkaDri: DDRI) - def asScala: DRI = - val elements = dokkaDri.getExtra.split(":") - val origin = elements.headOption.getOrElse("") - val symbolUUID = elements.drop(1).mkString(":") - DRI( - dokkaDri.getPackageName, - dokkaDri.getClassNames, - origin, - symbolUUID - ) \ No newline at end of file diff --git a/scala3doc/src/dotty/dokka/DocContext.scala b/scala3doc/src/dotty/dokka/DocContext.scala index 9c1c78bbf76a..9306a827190c 100644 --- a/scala3doc/src/dotty/dokka/DocContext.scala +++ b/scala3doc/src/dotty/dokka/DocContext.scala @@ -1,7 +1,5 @@ package dotty.dokka -import org.jetbrains.dokka._ -import org.jetbrains.dokka.plugability.DokkaContext import java.io.File import java.nio.file.Files import java.nio.file.Path @@ -25,9 +23,6 @@ type CompilerContext = dotty.tools.dotc.core.Contexts.Context given compilerContext(using docContext: DocContext): CompilerContext = docContext.compilerContext -given docContextFromDokka(using dokkaContext: DokkaContext): DocContext = - dokkaContext.getConfiguration.asInstanceOf[DocContext] - val report = dotty.tools.dotc.report def relativePath(p: Path)(using Context): Path = @@ -73,34 +68,16 @@ extension (r: report.type) case class NavigationNode(name: String, dri: DRI, nested: Seq[NavigationNode]) -case class DocContext(args: Scala3doc.Args, compilerContext: CompilerContext) - extends DokkaConfiguration: - override def getOutputDir: File = args.output - override def getCacheRoot: File = null - override def getOfflineMode: Boolean = false - override def getFailOnWarning: Boolean = false - override def getSourceSets: JList[DokkaSourceSet] = JNil - override def getModules: JList[DokkaConfiguration.DokkaModuleDescription] = JNil - override def getPluginsClasspath: JList[File] = JNil - override def getModuleName(): String = "ModuleName" - override def getModuleVersion(): String = "" - - lazy val sourceLinks: SourceLinks = SourceLinks.load(using this) - - lazy val displaySourceSets = getSourceSets.toDisplaySourceSet - - // Nasty hack but will get rid of it once we migrate away from dokka renderer - var navigationNode: Option[NavigationNode] = None - - val logger = new Scala3DocDokkaLogger(using compilerContext) +case class DocContext(args: Scala3doc.Args, compilerContext: CompilerContext): + lazy val sourceLinks: SourceLinks = SourceLinks.load(using this) - lazy val staticSiteContext = args.docsRoot.map(path => StaticSiteContext( - File(path).getAbsoluteFile(), - args, - sourceLinks - )(using compilerContext)) + // Nasty hack but will get rid of it once we migrate away from dokka renderer + var navigationNode: Option[NavigationNode] = None - val externalDocumentationLinks = args.externalMappings + lazy val staticSiteContext = args.docsRoot.map(path => StaticSiteContext( + File(path).getAbsoluteFile(), + args, + sourceLinks + )(using compilerContext)) - override def getPluginsConfiguration: JList[DokkaConfiguration.PluginConfiguration] = - JNil \ No newline at end of file + val externalDocumentationLinks = args.externalMappings diff --git a/scala3doc/src/dotty/dokka/Scala3doc.scala b/scala3doc/src/dotty/dokka/Scala3doc.scala index 9098da6ce6dc..b08f2f75dbc5 100644 --- a/scala3doc/src/dotty/dokka/Scala3doc.scala +++ b/scala3doc/src/dotty/dokka/Scala3doc.scala @@ -1,8 +1,5 @@ package dotty.dokka -import org.jetbrains.dokka._ -import org.jetbrains.dokka.utilities._ -import org.jetbrains.dokka.plugability._ import java.util.ServiceLoader import java.io.File import java.util.jar._ @@ -15,31 +12,6 @@ import dotty.tools.dotc.config.Settings._ import dotty.tools.dotc.config.CommonScalaSettings import dotty.tools.dotc.reporting.Reporter - -class Scala3DocDokkaLogger(using CompilerContext) extends DokkaLogger: - def debug(msg: String): Unit = report.debuglog(msg) - - // We do not want errors from dokka (that are) not critical to fail our runs - def error(msg: String): Unit = - errors += 1 - report.warning(msg) - - def info(msg: String): Unit = report.inform(msg) - def progress(msg: String): Unit = report.informProgress(msg) - - private val dokkaAlphaWarning = "Dokka 1.4.* is an alpha project" - def warn(msg: String): Unit = - if msg != dokkaAlphaWarning then - warnings += 1 - report.warning(msg) - - private var errors = 0 - private var warnings = 0 - def getErrorsCount(): Int = errors - def getWarningsCount(): Int = warnings - def setErrorsCount(count: Int): Unit = errors = count - def setWarningsCount(count: Int): Unit = warnings = count - object Scala3doc: enum CommentSyntax: case Wiki @@ -94,21 +66,10 @@ object Scala3doc: private [dokka] def run(args: Args)(using ctx: CompilerContext): DocContext = - given docContext: DocContext = new DocContext(args, ctx) - val module = ScalaModuleProvider.mkModule() - given dokkaContext: DokkaContext = - DokkaContext.Companion.create(docContext, docContext.logger, JList()) - val dokkaRenderer = new DokkaScalaHtmlRenderer - - val renderer = new dotty.dokka.renderers.HtmlRenderer( - module.rootPackage, - module.members, - dri => c => dokkaRenderer.buildWithKotlinx(c, FakeContentPage(dri.asDokka, c), null) - ) - dokkaRenderer.init(renderer) - renderer.render() + + new dotty.dokka.renderers.HtmlRenderer(module.rootPackage, module.members).render() report.inform("generation completed successfully") docContext diff --git a/scala3doc/src/dotty/dokka/ScalaModuleCreator.scala b/scala3doc/src/dotty/dokka/ScalaModuleCreator.scala index 601edb0d7c99..7ed162f50248 100644 --- a/scala3doc/src/dotty/dokka/ScalaModuleCreator.scala +++ b/scala3doc/src/dotty/dokka/ScalaModuleCreator.scala @@ -2,7 +2,6 @@ package dotty.dokka import dotty.dokka.tasty.DokkaTastyInspector import dotty.dokka.model.api._ -import org.jetbrains.dokka.base.parsers.MarkdownParser import collection.JavaConverters._ import kotlin.coroutines.Continuation @@ -10,7 +9,7 @@ case class Module(rootPackage: Member, members: Map[DRI, Member]) object ScalaModuleProvider: def mkModule()(using ctx: DocContext): Module = - val (result, rootDoc) = DokkaTastyInspector(new MarkdownParser(_ => null)).result() + val (result, rootDoc) = DokkaTastyInspector().result() val (rootPck, rest) = result.partition(_.name == "API") val packageMembers = (rest ++ rootPck.flatMap(_.members)).sortBy(_.name) diff --git a/scala3doc/src/dotty/dokka/compat.scala b/scala3doc/src/dotty/dokka/compat.scala index 9e981446d98a..372cb77e47a1 100644 --- a/scala3doc/src/dotty/dokka/compat.scala +++ b/scala3doc/src/dotty/dokka/compat.scala @@ -1,24 +1,13 @@ package dotty.dokka -import org.jetbrains.dokka.DokkaConfiguration -import org.jetbrains.dokka.DokkaConfiguration$DokkaSourceSet -import collection.JavaConverters._ -import org.jetbrains.dokka.model.DisplaySourceSet -import org.jetbrains.dokka.model.properties.WithExtraProperties -// TODO reproduction! - comment line below to broke compiler! -import org.jetbrains.dokka.model.properties.ExtraProperty -// import java.util.Stream // TODO reproduction uncomment import java.util.stream.Stream // comment out - wrong error! import java.util.stream.Collectors import java.util.Collections -import org.jetbrains.dokka.plugability._ import kotlin.jvm.JvmClassMappingKt.getKotlinClass -import org.jetbrains.dokka.links._ import java.nio.file.Path -import org.jetbrains.dokka.pages._ -import org.jetbrains.dokka.model.Documentable - -val U: kotlin.Unit = kotlin.Unit.INSTANCE +import com.vladsch.flexmark.util.ast.{Node => MdNode} +import dotty.dokka.tasty.comments.wiki.WikiDocElement +import collection.JavaConverters._ def JList[T](e: T*): JList[T] = e.asJava def JSet[T](e: T*): JSet[T] = e.toSet.asJava @@ -36,36 +25,12 @@ def JNil[A] = emptyListInst.asInstanceOf[JList[A]] private val emptyMapInst = Collections.emptyMap def emptyJMap[A, B] = emptyMapInst.asInstanceOf[JMap[A, B]] -type SourceSetWrapper = DokkaConfiguration$DokkaSourceSet -type DokkaSourceSet = DokkaConfiguration.DokkaSourceSet - -type DocPart = org.jetbrains.dokka.model.doc.DocTag - -extension [T] (wrapper: SourceSetWrapper) - def toSet: JSet[DokkaConfiguration$DokkaSourceSet] = JSet(wrapper) - def toMap(value: T): JMap[DokkaConfiguration$DokkaSourceSet, T] = JMap(wrapper -> value) - -extension [T] (wrapper: DokkaSourceSet) - // when named `toSet` fails in runtime -- TODO: create a minimal! - // def toSet: JSet[DokkaConfiguration$DokkaSourceSet] = JSet(wrapper.asInstanceOf[SourceSetWrapper]) - def asSet: JSet[DokkaConfiguration$DokkaSourceSet] = JSet(wrapper.asInstanceOf[SourceSetWrapper]) - def asMap(value: T): JMap[DokkaConfiguration$DokkaSourceSet, T] = JMap(wrapper.asInstanceOf[SourceSetWrapper] -> value) - -extension (sourceSets: JList[DokkaSourceSet]) - def asDokka: JSet[SourceSetWrapper] = sourceSets.asScala.toSet.asJava.asInstanceOf[JSet[SourceSetWrapper]] - def toDisplaySourceSet = sourceSets.asScala.map(ss => DisplaySourceSet(ss.asInstanceOf[SourceSetWrapper])).toSet.asJava +enum DocLink: + case ToURL(url: String) + case ToDRI(dri: DRI, name: String) + case UnresolvedDRI(query: String, msg: String) -extension (sourceSets: Set[SourceSetWrapper]) - def toDisplay = sourceSets.map(DisplaySourceSet(_)).asJava - -extension [V] (a: WithExtraProperties[_]) - def get(key: ExtraProperty.Key[_, V]): V = a.getExtra().getMap().get(key).asInstanceOf[V] - -extension [E <: WithExtraProperties[E]] (a: E) - def put(value: ExtraProperty[_ >: E]): E = a.withNewExtras(a.getExtra plus value) - -extension [V] (map: JMap[SourceSetWrapper, V]) - def defaultValue: V = map.values.asScala.head +type DocPart = Seq[WikiDocElement] | MdNode extension [V](jlist: JList[V]) def ++ (other: JList[V]): JList[V] = @@ -73,24 +38,4 @@ extension [V](jlist: JList[V]) extension [V](jset: JSet[V]) def ++ (other: JSet[V]): JSet[V] = - Stream.of(jset, other).flatMap(_.stream).collect(Collectors.toSet()) - -// Needed until we will migrate away from dokka - - -case class FakeContentPage( - dri: DRI, - override val getContent: ContentNode) extends ContentPage: - override val getName: String = "" - override val getChildren: JList[PageNode] = JList() - override val getEmbeddedResources: JList[String] = JList() - override def getDocumentable: Documentable = null - override def modified( - name: String, - content: ContentNode, - dri: JSet[DRI], - embeddedResources: JList[String], - children: JList[_ <: PageNode] - ): ContentPage = this - override def modified(name: String, children: JList[_ <: PageNode]): PageNode = this - override val getDri: JSet[DRI] = JSet(dri) + Stream.of(jset, other).flatMap(_.stream).collect(Collectors.toSet()) \ No newline at end of file diff --git a/scala3doc/src/dotty/dokka/model/scalaModel.scala b/scala3doc/src/dotty/dokka/model/scalaModel.scala deleted file mode 100644 index f4a5a38d250e..000000000000 --- a/scala3doc/src/dotty/dokka/model/scalaModel.scala +++ /dev/null @@ -1,117 +0,0 @@ -package dotty.dokka - -import org.jetbrains.dokka.DokkaConfiguration$DokkaSourceSet -import org.jetbrains.dokka.model._ -import collection.JavaConverters._ -import org.jetbrains.dokka.model.doc._ -import org.jetbrains.dokka.model.properties._ -import org.jetbrains.dokka.pages._ -import dotty.dokka.model.api.Signature -import dotty.dokka.model.api.HierarchyGraph -import dotty.dokka.model.api.Member - -enum TableStyle extends org.jetbrains.dokka.pages.Style: - case Borderless - case DescriptionList - case NestedDescriptionList - -case class HtmlContentNode( - val body: String, - val dci: DCI, - val sourceSets: Set[DisplaySourceSet], - val style: Set[Style], - val extra: PropertyContainer[ContentNode] = PropertyContainer.Companion.empty -) extends ContentNode: - override def getDci = dci - override def getSourceSets = sourceSets.asJava - override def getStyle = style.asJava - override def hasAnyContent = !body.isEmpty - def withSourceSets(sourceSets: JSet[DisplaySourceSet]) = copy(sourceSets = sourceSets.asScala.toSet) - override def getChildren: JList[ContentNode] = JList() - override def getExtra = extra - override def withNewExtras(p: PropertyContainer[ContentNode]) = copy(extra = p) - -class ScalaTagWrapper(root: DocTag, val name: String) extends TagWrapper(null): - override def getRoot = root - -object ScalaTagWrapper { - - case class See(root: DocTag) extends ScalaTagWrapper(root, "See") - case class Todo(root: DocTag) extends ScalaTagWrapper(root, "Todo") - case class Note(root: DocTag) extends ScalaTagWrapper(root, "Note") - case class Example(root: DocTag) extends ScalaTagWrapper(root, "Example") - case class NestedNamedTag( - name: String, - subname: String, - identTag: DocTag, - descTag: DocTag - ) extends NamedTagWrapper(null): - override def getName = name - override def getRoot = descTag -} - -case class ImplicitConversion(conversion: Documentable, from: DRI, to: DRI) -// TODO clear it out -case class ContentNodeParams( - val dci: DCI, - val sourceSets: java.util.Set[DisplaySourceSet], - val style: Set[Style], - val extra: PropertyContainer[ContentNode] = PropertyContainer.Companion.empty -): - def dri = dci.getDri.asScala.head - -abstract class ScalaContentNode(params: ContentNodeParams) extends ContentNode: - def newInstance(params: ContentNodeParams): ScalaContentNode - - override def getDci = params.dci - override def getSourceSets = params.sourceSets - override def getStyle = params.style.asJava - override def hasAnyContent = true - def withSourceSets(sourceSets: JSet[DisplaySourceSet]) = - newInstance(params.copy(sourceSets = sourceSets)) - override def getChildren: JList[ContentNode] = JList() - override def getExtra = params.extra - override def withNewExtras(p: PropertyContainer[ContentNode]) = newInstance(params.copy(extra = p)) - -case class DocumentableNameWithStyles( - name: String, - styles: Set[Style] = Set.empty, -) - -case class DocumentableElement( - annotations: Signature, - modifiers: Signature, - nameWithStyles: DocumentableNameWithStyles, - signature: Signature, - brief: Seq[ContentNode], - originInfo: Signature, - attributes: Map[String, String], - params: ContentNodeParams, - member: Member -) extends ScalaContentNode(params): - override def newInstance(params: ContentNodeParams) = copy(params = params) - -case class DocumentableElementGroup( - header: Signature, - elements: Seq[DocumentableElement], - params: ContentNodeParams -) extends ScalaContentNode(params): - override def newInstance(params: ContentNodeParams) = copy(params = params) - override def hasAnyContent = elements.nonEmpty - override def getChildren: JList[ContentNode] = elements.asJava - -case class DocumentableList( - groupName: Signature, - elements: Seq[DocumentableElement | DocumentableElementGroup], - params: ContentNodeParams -) extends ScalaContentNode(params): - override def newInstance(params: ContentNodeParams) = copy(params = params) - override def hasAnyContent = elements.nonEmpty - override def getChildren: JList[ContentNode] = elements.asJava - -case class DocumentableFilter(params: ContentNodeParams) extends ScalaContentNode(params): - override def newInstance(params: ContentNodeParams) = copy(params = params) - -case class MemberInfo(member: Member, params: ContentNodeParams) - extends ScalaContentNode(params): - override def newInstance(params: ContentNodeParams) = copy(params = params) \ No newline at end of file diff --git a/scala3doc/src/dotty/dokka/tasty/ScalaDocSupport.scala b/scala3doc/src/dotty/dokka/tasty/ScalaDocSupport.scala index 29b953bb3452..e71430bf2b0d 100644 --- a/scala3doc/src/dotty/dokka/tasty/ScalaDocSupport.scala +++ b/scala3doc/src/dotty/dokka/tasty/ScalaDocSupport.scala @@ -2,10 +2,7 @@ package dotty.dokka.tasty import scala.jdk.CollectionConverters._ -import org.jetbrains.dokka.model.{doc => dkkd} - import dotty.dokka.Scala3doc.CommentSyntax -import dotty.dokka.ScalaTagWrapper import comments.{kt, dkk} import dotty.dokka.tasty.comments.Comment diff --git a/scala3doc/src/dotty/dokka/tasty/SymOps.scala b/scala3doc/src/dotty/dokka/tasty/SymOps.scala index 5add3cb71a96..fe137ff0e8b2 100644 --- a/scala3doc/src/dotty/dokka/tasty/SymOps.scala +++ b/scala3doc/src/dotty/dokka/tasty/SymOps.scala @@ -1,7 +1,5 @@ package dotty.dokka.tasty -import org.jetbrains.dokka.model._ -import collection.JavaConverters._ import dotty.dokka._ import dotty.dokka.model.api.Visibility import dotty.dokka.model.api.VisibilityScope diff --git a/scala3doc/src/dotty/dokka/tasty/TastyParser.scala b/scala3doc/src/dotty/dokka/tasty/TastyParser.scala index 13683bb921d0..73bd4522f0ee 100644 --- a/scala3doc/src/dotty/dokka/tasty/TastyParser.scala +++ b/scala3doc/src/dotty/dokka/tasty/TastyParser.scala @@ -1,8 +1,6 @@ package dotty.dokka package tasty -import org.jetbrains.dokka.model.doc._ -import org.jetbrains.dokka.base.parsers._ import java.util.regex.Pattern @@ -24,7 +22,7 @@ import java.nio.file.Files * * Delegates most of the work to [[TastyParser]] [[dotty.dokka.tasty.TastyParser]]. */ -case class DokkaTastyInspector(parser: Parser)(using ctx: DocContext) extends DocTastyInspector: +case class DokkaTastyInspector()(using ctx: DocContext) extends DocTastyInspector: private val topLevels = Seq.newBuilder[(String, Member)] private var rootDoc: Option[Comment] = None diff --git a/scala3doc/src/dotty/dokka/tasty/comments/BaseConverter.scala b/scala3doc/src/dotty/dokka/tasty/comments/BaseConverter.scala deleted file mode 100644 index 7c93ea889ad0..000000000000 --- a/scala3doc/src/dotty/dokka/tasty/comments/BaseConverter.scala +++ /dev/null @@ -1,23 +0,0 @@ -package dotty.dokka.tasty.comments - -import scala.jdk.CollectionConverters._ - -import org.jetbrains.dokka.model.{doc => dkkd} - -/** Quick'n'dirty class to remove some code duplication */ -trait BaseConverter { - - protected def withParsedQuery(queryStr: String)(thunk: Query => dkkd.DocTag): dkkd.DocTag = { - QueryParser(queryStr).tryReadQuery() match { - case Left(err) => - val msg = err.getMessage - // TODO: for better experience we should show source location here - println("WARN: " + msg) - dkkd.A(List(dkk.text(queryStr)).asJava, Map("title" -> msg, "href" -> "#").asJava) - case Right(query) => - thunk(query) - } - } - - protected val SchemeUri = """[a-z]+:.*""".r -} diff --git a/scala3doc/src/dotty/dokka/tasty/comments/Comments.scala b/scala3doc/src/dotty/dokka/tasty/comments/Comments.scala index bb6326c1ac74..27faca5d19d9 100644 --- a/scala3doc/src/dotty/dokka/tasty/comments/Comments.scala +++ b/scala3doc/src/dotty/dokka/tasty/comments/Comments.scala @@ -1,44 +1,48 @@ -package dotty.dokka.tasty.comments +package dotty.dokka +package tasty.comments import scala.collection.immutable.SortedMap - -import org.jetbrains.dokka.model.{doc => dkkd} +import scala.util.Try import com.vladsch.flexmark.util.{ast => mdu} +import com.vladsch.flexmark.{ast => mda} import com.vladsch.flexmark.formatter.Formatter import com.vladsch.flexmark.util.options.MutableDataSet import scala.quoted._ import dotty.dokka.tasty.comments.wiki.Paragraph +import dotty.dokka.DocPart +import dotty.dokka.tasty.SymOps +import collection.JavaConverters._ class Repr(val qctx: Quotes)(val sym: qctx.reflect.Symbol) case class Comment ( - body: dkkd.DocTag, - short: Option[dkkd.DocTag], - authors: List[dkkd.DocTag], - see: List[dkkd.DocTag], - result: Option[dkkd.DocTag], - throws: SortedMap[String, (dkkd.DocTag, dkkd.DocTag)], - valueParams: SortedMap[String, dkkd.DocTag], - typeParams: SortedMap[String, dkkd.DocTag], - version: Option[dkkd.DocTag], - since: Option[dkkd.DocTag], - todo: List[dkkd.DocTag], - deprecated: Option[dkkd.DocTag], - note: List[dkkd.DocTag], - example: List[dkkd.DocTag], - constructor: Option[dkkd.DocTag], + body: DocPart, + short: Option[DocPart], + authors: List[DocPart], + see: List[DocPart], + result: Option[DocPart], + throws: SortedMap[String, DocPart], + valueParams: SortedMap[String, DocPart], + typeParams: SortedMap[String, DocPart], + version: Option[DocPart], + since: Option[DocPart], + todo: List[DocPart], + deprecated: Option[DocPart], + note: List[DocPart], + example: List[DocPart], + constructor: Option[DocPart], group: Option[String], // see comment in PreparsedComment below regarding these - groupDesc: SortedMap[String, dkkd.DocTag], - groupNames: SortedMap[String, dkkd.DocTag], + groupDesc: SortedMap[String, DocPart], + groupNames: SortedMap[String, DocPart], groupPrio: SortedMap[String, Int], /** List of conversions to hide - containing e.g: `scala.Predef.FloatArrayOps` */ - hideImplicitConversions: List[dkkd.DocTag] + hideImplicitConversions: List[DocPart] ) -case class PreparsedComment ( +case class PreparsedComment( body: String, authors: List[String], see: List[String], @@ -63,17 +67,43 @@ case class PreparsedComment ( syntax: List[String], ) -case class DokkaCommentBody(summary: Option[dkkd.DocTag], body: dkkd.DocTag) +case class DokkaCommentBody(summary: Option[DocPart], body: DocPart) -trait MarkupConversion[T] { - protected def linkedExceptions(m: SortedMap[String, String]): SortedMap[String, (dkkd.DocTag, dkkd.DocTag)] +abstract class MarkupConversion[T](val repr: Repr)(using DocContext) { protected def stringToMarkup(str: String): T - protected def markupToDokka(t: T): dkkd.DocTag + protected def markupToDokka(t: T): DocPart protected def markupToString(t: T): String protected def markupToDokkaCommentBody(t: T): DokkaCommentBody protected def filterEmpty(xs: List[String]): List[T] protected def filterEmpty(xs: SortedMap[String, String]): SortedMap[String, T] + val qctx: repr.qctx.type = if repr == null then null else repr.qctx // TODO why we do need null? + val owner: qctx.reflect.Symbol = + if repr == null then null.asInstanceOf[qctx.reflect.Symbol] else repr.sym + + object SymOps extends SymOps[qctx.type](qctx) + export SymOps.dri + + def resolveLink(queryStr: String): DocLink = + if SchemeUri.matches(queryStr) then DocLink.ToURL(queryStr) + else QueryParser(queryStr).tryReadQuery() match + case Left(err) => + // TODO convert owner.pos to get to the comment, add stack trace + report.warning(s"Unable to parse query `$queryStr`: ${err.getMessage}") + val msg = s"Unable to parse query: ${err.getMessage}" + DocLink.UnresolvedDRI(queryStr, msg) + case Right(query) => + MemberLookup.lookup(using qctx)(query, owner) match + case Some((sym, targetText)) => + DocLink.ToDRI(sym.dri, targetText) + case None => + val msg = s"Not found any dri for query" + // TODO convert owner.pos to get to the comment, change to warning + report.inform(s"$msg: $queryStr") + DocLink.UnresolvedDRI(queryStr, msg) + + private val SchemeUri = """[a-z]+:.*""".r + private def single(annot: String, xs: List[String], filter: Boolean = true): Option[T] = (if (filter) filterEmpty(xs) else xs.map(stringToMarkup)) match { case x :: xs => @@ -89,7 +119,7 @@ trait MarkupConversion[T] { authors = filterEmpty(preparsed.authors).map(markupToDokka), see = filterEmpty(preparsed.see).map(markupToDokka), result = single("@result", preparsed.result).map(markupToDokka), - throws = linkedExceptions(preparsed.throws), + throws = filterEmpty(preparsed.throws).view.mapValues(markupToDokka).to(SortedMap), valueParams = filterEmpty(preparsed.valueParams).view.mapValues(markupToDokka).to(SortedMap), typeParams = filterEmpty(preparsed.typeParams).view.mapValues(markupToDokka).to(SortedMap), version = single("@version", preparsed.version).map(markupToDokka), @@ -107,30 +137,21 @@ trait MarkupConversion[T] { ) } -class MarkdownCommentParser(repr: Repr) - extends MarkupConversion[mdu.Document] { +class MarkdownCommentParser(repr: Repr)(using DocContext) + extends MarkupConversion[mdu.Node](repr) { def stringToMarkup(str: String) = - MarkdownParser.parseToMarkdown(str) + MarkdownParser.parseToMarkdown(str, markdown.DocFlexmarkParser(resolveLink)) - def markupToString(t: mdu.Document): String = t.toString() // ?? + def markupToString(t: mdu.Node): String = t.toString() - def markupToDokka(md: mdu.Document) = - MarkdownConverter(repr).convertDocument(md) + def markupToDokka(md: mdu.Node): DocPart = md - def markupToDokkaCommentBody(md: mdu.Document) = - val converter = MarkdownConverter(repr) - DokkaCommentBody( - summary = converter.extractAndConvertSummary(md), - body = converter.convertDocument(md), - ) + def markupToDokkaCommentBody(md: mdu.Node) = + val summary = + md.getChildIterator.asScala.collectFirst { case p: mda.Paragraph => p } - def linkedExceptions(m: SortedMap[String, String]) = { - val c = MarkdownConverter(repr) - m.map { case (targetStr, body) => - targetStr -> (c.resolveLinkQuery(targetStr, ""), dkk.text(body)) - } - } + DokkaCommentBody(summary, md) def filterEmpty(xs: List[String]) = { xs.map(_.trim) @@ -144,12 +165,16 @@ class MarkdownCommentParser(repr: Repr) .mapValues(stringToMarkup).to(SortedMap) } -case class WikiCommentParser(repr: Repr) - extends MarkupConversion[wiki.Body] { +class WikiCommentParser(repr: Repr)(using DocContext) + extends MarkupConversion[wiki.Body](repr): - def stringToMarkup(str: String) = - wiki.Parser(str).document() + def stringToMarkup(str: String) = wiki.Parser(str, resolverLink).document() + + def resolverLink(queryStr: String, bodyOpt: Option[wiki.Inline]): wiki.Inline = + val link = resolveLink(queryStr) + wiki.Link(link, bodyOpt) + // Do we need those? private def flatten(b: wiki.Inline): String = b match case wiki.Text(t) => t case wiki.Italic(t) => flatten(t) @@ -157,7 +182,7 @@ case class WikiCommentParser(repr: Repr) case wiki.Underline(t) => flatten(t) case wiki.Superscript(t) => flatten(t) case wiki.Subscript(t) => flatten(t) - case wiki.Link(_, t) => flatten(t) + case wiki.Link(_, t) => t.fold("")(flatten) case wiki.Monospace(t) => flatten(t) case wiki.RepresentationLink(t, _) => flatten(t) case wiki.Chain(elems) => elems.headOption.fold("")(flatten) @@ -171,32 +196,23 @@ case class WikiCommentParser(repr: Repr) case wiki.UnorderedList(elems) => elems.headOption.fold("")(flatten) case wiki.OrderedList(elems, _) => elems.headOption.fold("")(flatten) case wiki.DefinitionList(items) => items.headOption.fold("")(e => flatten(e._1)) - case wiki.HorizontalRule() => "" + case wiki.HorizontalRule => "" def markupToString(str: wiki.Body) = str.blocks.headOption.fold("")(flatten) - def markupToDokka(body: wiki.Body) = - wiki.Converter(repr).convertBody(body) + def markupToDokka(body: wiki.Body) = parseBlocks(body.blocks) // TODO + + def parseBlocks(blocks: Seq[wiki.WikiDocElement]) = blocks def markupToDokkaCommentBody(body: wiki.Body) = - val converter = wiki.Converter(repr) - DokkaCommentBody( - summary = body.summary.map(converter.convertBody), - body = converter.convertBody(body), + DokkaCommentBody( + summary = body.summary.map(s => parseBlocks(s.blocks)), + body = parseBlocks(body.blocks), ) - def linkedExceptions(m: SortedMap[String, String]) = { - m.map { case (targetStr, body) => - val c = wiki.Converter(repr) - targetStr -> (c.resolveLinkQuery(targetStr, None), c.convertBody(stringToMarkup(body))) - } - } - def filterEmpty(xs: List[String]) = xs.map(stringToMarkup) def filterEmpty(xs: SortedMap[String,String]) = xs.view.mapValues(stringToMarkup).to(SortedMap) .filterNot { case (_, v) => v.blocks.isEmpty } - -} diff --git a/scala3doc/src/dotty/dokka/tasty/comments/Emitter.scala b/scala3doc/src/dotty/dokka/tasty/comments/Emitter.scala deleted file mode 100644 index 2b7ea003f0b2..000000000000 --- a/scala3doc/src/dotty/dokka/tasty/comments/Emitter.scala +++ /dev/null @@ -1,18 +0,0 @@ -package dotty.dokka.tasty.comments - -import scala.collection.mutable.ArrayBuffer - -object Emitter { - opaque type Emitter[T] = ArrayBuffer[T] - - def collect[T](thunk: Emitter[T] ?=> Unit): Seq[T] = { - val bld = new ArrayBuffer[T] - thunk(using bld) - bld.toSeq - } - - def emit[T](using e: Emitter[T])(t: T) = e.addOne(t) - - def lastEmittedItem[T](using e: Emitter[T]) = - if e.isEmpty then None else Some(e.last) -} diff --git a/scala3doc/src/dotty/dokka/tasty/comments/MarkdownConverter.scala b/scala3doc/src/dotty/dokka/tasty/comments/MarkdownConverter.scala deleted file mode 100644 index a6ae4c8435fb..000000000000 --- a/scala3doc/src/dotty/dokka/tasty/comments/MarkdownConverter.scala +++ /dev/null @@ -1,211 +0,0 @@ -package dotty.dokka -package tasty.comments - -import scala.jdk.CollectionConverters._ - -import org.jetbrains.dokka.model.{doc => dkkd} -import com.vladsch.flexmark.{ast => mda} -import com.vladsch.flexmark.util.{ast => mdu} -import com.vladsch.flexmark.ext.gfm.{tables => mdt} -import com.vladsch.flexmark.ext.{wikilink => mdw} - -import dotty.dokka.tasty.SymOps - -class MarkdownConverter(val repr: Repr) extends BaseConverter { - import Emitter._ - - // makeshift support for not passing an owner - // see same in wiki.Converter - val qctx: repr.qctx.type = if repr == null then null else repr.qctx - val owner: qctx.reflect.Symbol = if repr == null then null.asInstanceOf[qctx.reflect.Symbol] else repr.sym - - object SymOps extends SymOps[qctx.type](qctx) - import SymOps._ - - def convertDocument(doc: mdu.Document): dkkd.DocTag = { - val res = collect { - doc.getChildIterator.asScala.foreach(emitConvertedNode) - } - - dkkd.P(res.asJava, kt.emptyMap) - } - - def convertChildren(n: mdu.Node): Seq[dkkd.DocTag] = - collect { - n.getChildIterator.asScala.foreach(emitConvertedNode) - } - - def emitConvertedNode(n: mdu.Node)(using Emitter[dkkd.DocTag]): Unit = n match { - case n: mda.Paragraph => - emit(dkkd.P(convertChildren(n).asJava, kt.emptyMap)) - - case n: mda.Heading => emit(n.getLevel match { - case 1 => dkkd.H1(List(dkk.text(n.getText().toString)).asJava, JMap()) - // case -1 => dkkd.H1(List(dkk.text(n.getText().toString)).asJava, JMap()) // This does not compile but should! - case 2 => dkkd.H2(List(dkk.text(n.getText().toString)).asJava, kt.emptyMap) - case 3 => dkkd.H3(List(dkk.text(n.getText().toString)).asJava, kt.emptyMap) - case 4 => dkkd.H4(List(dkk.text(n.getText().toString)).asJava, kt.emptyMap) - case 5 => dkkd.H5(List(dkk.text(n.getText().toString)).asJava, kt.emptyMap) - case 6 => dkkd.H6(List(dkk.text(n.getText().toString)).asJava, kt.emptyMap) - }) - - case n: mda.Text => emit(dkk.text(n.getChars.toString)) - case n: mda.TextBase => - // TextBase is a wrapper for other nodes that for unclear reasons - // sometimes gets emitted (`AutoLink`s seem to be involved) - n.getChildren.asScala.foreach(n => emitConvertedNode(n)) - - // case n: mda.HtmlInline => dkkd.Br.INSTANCE - case n: mda.Emphasis => - // TODO doesn't actually show up in output, why? - emit(n.getOpeningMarker.toString match { - case "*" => dkkd.B(convertChildren(n).asJava, kt.emptyMap) - case "_" => dkkd.I(convertChildren(n).asJava, kt.emptyMap) - }) - - case n: mda.StrongEmphasis => - // TODO doesn't actually show up in output, why? - // TODO distinguish between strong and regular emphasis? - emit(n.getOpeningMarker.toString match { - case "**" => dkkd.B(convertChildren(n).asJava, kt.emptyMap) - case "__" => dkkd.I(convertChildren(n).asJava, kt.emptyMap) - }) - - case n: mda.AutoLink => - val url = n.getUrl.toString - emit(dkkd.A(List(dkk.text(url)).asJava, Map("href" -> url).asJava)) - - case n: mda.Link => - val body: String = n.getText.toString - val target: String = n.getUrl.toString - def resolveBody(default: String) = - val resolved = if !body.isEmpty then body else default - List(dkk.text(resolved)).asJava - - emit(dkkd.A(resolveBody(default = target), Map("href" -> target).asJava)) - - case n: mdw.WikiLink => - val (target, body) = - val chars = n.getChars.toString.substring(2, n.getChars.length - 2) - MarkdownConverter.splitWikiLink(chars) - - def resolveBody(default: String) = - val resolved = if !body.isEmpty then body else default - List(dkk.text(resolved)).asJava - - emit(target match { - case SchemeUri() => - dkkd.A(resolveBody(default = target), Map("href" -> target).asJava) - case _ => - resolveLinkQuery(target, body) - }) - - case n: mda.Code => - emit(dkkd.CodeInline(convertChildren(n).asJava, kt.emptyMap)) - case n: mda.IndentedCodeBlock => - val bld = new StringBuilder - n.getContentLines.asScala.foreach(bld append _) - emit(dkkd.CodeBlock(List(dkk.text(bld.toString)).asJava, kt.emptyMap)) - case n: mda.FencedCodeBlock => - // n.getInfo - where to stick this? - emit(dkkd.CodeBlock(convertChildren(n).asJava, kt.emptyMap)) - - case n: mda.ListBlock => - val c = convertChildren(n).asJava - emit(n match { - case _: mda.OrderedList => dkkd.Ol(c, kt.emptyMap) - case _ => dkkd.Ul(c, kt.emptyMap) - }) - case n: mda.ListItem => - emit(dkkd.Li(convertChildren(n).asJava, kt.emptyMap)) - - case n: mda.BlockQuote => - emit(dkkd.BlockQuote(convertChildren(n).asJava, kt.emptyMap)) - - case n: mdt.TableBlock => - // the structure is: - // TableBlock { - // TableHeader { - // TableRow { - // TableCell { ... } - // TableCell { ... } - // } - // } - // TableSeparator { TableRow { ... } } - // TableBody { TableRow { ... } ... } - // } - val header = - n.getFirstChild.getChildIterator.asScala.map { nn => - dkkd.Tr( - nn.getChildIterator.asScala.map { nnn => - dkkd.Th(convertChildren(nnn).asJava, kt.emptyMap) - }.toSeq.asJava, - kt.emptyMap - ) - } - - val body = - n.getChildIterator.asScala.drop(2).next.getChildIterator.asScala.map { nn => - dkkd.Tr( - nn.getChildIterator.asScala.map { nnn => - dkkd.Td(convertChildren(nnn).asJava, kt.emptyMap) - }.toSeq.asJava, - kt.emptyMap - ) - } - - emit(dkkd.Table( - (header ++ body).toSeq.asJava, - kt.emptyMap - )) - - case _: mda.SoftLineBreak => emit(dkkd.Br.INSTANCE) - - case inline: mda.HtmlInline => - emit(dkkd.Html(List(dkk.text(inline.getSegments.mkString)).asJava, kt.emptyMap)) - - case entity: mda.HtmlEntity => - emit(dkkd.Html(List(dkk.text(entity.getSegments.mkString)).asJava, kt.emptyMap)) - - case block: mda.HtmlBlock => - emit(dkkd.Html(List(dkk.text(block.getContentChars.toString)).asJava, kt.emptyMap)) - - // TODO (https://github.com/lampepfl/scala3doc/issues/205): for now just silent the warnigs - case _: mda.LinkRef | _: com.vladsch.flexmark.ext.emoji.Emoji => - emit(dkk.text(MarkdownParser.renderToText(n))) - - case _ => - println(s"WARN: Encountered unrecognised Markdown node `${n.getNodeName}`, please open an issue.") - emit(dkk.text(MarkdownParser.renderToText(n))) - } - - def extractAndConvertSummary(doc: mdu.Document): Option[dkkd.DocTag] = - doc.getChildIterator.asScala.collectFirst { case p: mda.Paragraph => - dkkd.P(convertChildren(p).asJava, kt.emptyMap) - } - - def resolveLinkQuery(queryStr: String, body: String): dkkd.DocTag = { - def resolveBody(default: String) = - val resolved = if !body.isEmpty then body else default - List(dkk.text(resolved)).asJava - - withParsedQuery(queryStr) { query => - MemberLookup.lookup(using qctx)(query, owner) match { - case Some((sym, targetText)) => - dkkd.DocumentationLink(sym.dri.asDokka, resolveBody(default = targetText), kt.emptyMap) - case None => - // println(s"WARN: Definition lookup for following query failed: $queryStr") - dkkd.A(resolveBody(default = query.join), Map("title" -> s"Definition was not found: $queryStr", "href" -> "#").asJava) - } - } - } -} - -object MarkdownConverter { - def splitWikiLink(chars: String): (String, String) = - // split on a space which is not backslash escaped (regex uses "zero-width negative lookbehind") - chars.split("(? (target, "") - case Array(target, userText) => (target, userText) - } -} diff --git a/scala3doc/src/dotty/dokka/tasty/comments/MarkdownParser.scala b/scala3doc/src/dotty/dokka/tasty/comments/MarkdownParser.scala index 024c3c3f5f80..13e86b344119 100644 --- a/scala3doc/src/dotty/dokka/tasty/comments/MarkdownParser.scala +++ b/scala3doc/src/dotty/dokka/tasty/comments/MarkdownParser.scala @@ -17,33 +17,43 @@ import com.vladsch.flexmark.ext.anchorlink.AnchorLinkExtension import com.vladsch.flexmark.ext.yaml.front.matter.YamlFrontMatterExtension import com.vladsch.flexmark.ext.wikilink.WikiLinkExtension import com.vladsch.flexmark.util.options.{ DataHolder, MutableDataSet } +import com.vladsch.flexmark.util.builder.Extension + +import collection.JavaConverters._ object MarkdownParser { - val markdownOptions: DataHolder = + def mkMarkdownOptions(additionalExtensions: Seq[Extension]) = + val extArray = Seq( + TablesExtension.create(), + TaskListExtension.create(), + AutolinkExtension.create(), + AnchorLinkExtension.create(), + EmojiExtension.create(), + YamlFrontMatterExtension.create(), + StrikethroughExtension.create() + ) ++ additionalExtensions + new MutableDataSet() .setFrom(ParserEmulationProfile.KRAMDOWN.getOptions) - .set(Parser.EXTENSIONS, Arrays.asList( - TablesExtension.create(), - TaskListExtension.create(), - AutolinkExtension.create(), - AnchorLinkExtension.create(), - EmojiExtension.create(), - YamlFrontMatterExtension.create(), - StrikethroughExtension.create(), - WikiLinkExtension.create(), - )) + .set(Parser.EXTENSIONS, Arrays.asList(extArray:_*)) .set(EmojiExtension.ROOT_IMAGE_PATH, "https://github.global.ssl.fastly.net/images/icons/emoji/") .set(WikiLinkExtension.LINK_ESCAPE_CHARS, "") - val parser = Parser.builder(markdownOptions).build() + val markdownOptions: DataHolder = mkMarkdownOptions(Seq(WikiLinkExtension.create())) + + private val parser = Parser.builder(markdownOptions).build() val RENDERER = Formatter.builder(markdownOptions).build() - def parseToMarkdown(text: String): mdu.Document = + def parseToMarkdown(text: String, extensions: Extension*): mdu.Document = + val thisParser = + if(extensions.isEmpty) parser + else Parser.builder(mkMarkdownOptions(extensions)).build() + // We need to remove safe tag markers as they break flexmark parsing - parser.parse(text.replace(safeTagMarker.toString, "")).asInstanceOf[mdu.Document] + thisParser.parse(text.replace(safeTagMarker.toString, "")).asInstanceOf[mdu.Document] def renderToText(node: mdu.Node): String = diff --git a/scala3doc/src/dotty/dokka/tasty/comments/markdown/DocFlexmarkExtension.scala b/scala3doc/src/dotty/dokka/tasty/comments/markdown/DocFlexmarkExtension.scala new file mode 100644 index 000000000000..19a0ab42961a --- /dev/null +++ b/scala3doc/src/dotty/dokka/tasty/comments/markdown/DocFlexmarkExtension.scala @@ -0,0 +1,69 @@ +package dotty.dokka +package tasty.comments +package markdown + +import com.vladsch.flexmark.html._ +import com.vladsch.flexmark.html.renderer._ +import com.vladsch.flexmark.parser._ +import com.vladsch.flexmark.ext.wikilink._ +import com.vladsch.flexmark.ext.wikilink.internal.WikiLinkLinkRefProcessor +import com.vladsch.flexmark.util.ast._ +import com.vladsch.flexmark.util.options._ +import com.vladsch.flexmark.util.sequence.BasedSequence + + +class DocLinkNode( + val target: DocLink, + val body: String, + seq: BasedSequence + ) extends WikiNode(seq, false, false, false, false) + +class DocFlexmarkParser(resolveLink: String => DocLink) extends Parser.ParserExtension: + + def parserOptions(opt: MutableDataHolder): Unit = () // noop + + class Factory extends LinkRefProcessorFactory: + override def getBracketNestingLevel(options: DataHolder) = 1 + override def getWantExclamationPrefix(options: DataHolder) = false + override def create(doc: Document): LinkRefProcessor = + new WikiLinkLinkRefProcessor(doc): + override def createNode(nodeChars: BasedSequence): Node = + val chars = nodeChars.toString.substring(2, nodeChars.length - 2) + val (target, body) = DocFlexmarkParser.splitWikiLink(chars) + new DocLinkNode(resolveLink(target), body, nodeChars) + + + override def extend(parserBuilder: Parser.Builder) = + parserBuilder.linkRefProcessorFactory(new Factory) + +object DocFlexmarkParser { + def splitWikiLink(chars: String): (String, String) = + // split on a space which is not backslash escaped (regex uses "zero-width negative lookbehind") + chars.split("(? (target, "") + case Array(target, userText) => (target, userText) + } +} + +case class DocFlexmarkRenderer(renderLink: (DocLink, String) => String) + extends HtmlRenderer.HtmlRendererExtension: + def rendererOptions(opt: MutableDataHolder): Unit = () // noop + + object Handler extends CustomNodeRenderer[DocLinkNode]: + override def render(node: DocLinkNode, c: NodeRendererContext, html: HtmlWriter): Unit = + html.raw(renderLink(node.target, node.body)) + + object Render extends NodeRenderer: + override def getNodeRenderingHandlers: JSet[NodeRenderingHandler[_]] = + JSet(new NodeRenderingHandler(classOf[DocLinkNode], Handler)) + + object Factory extends NodeRendererFactory: + override def create(options: DataHolder): NodeRenderer = Render + + def extend(htmlRendererBuilder: HtmlRenderer.Builder, tpe: String): Unit = + htmlRendererBuilder.nodeRendererFactory(Factory) + +object DocFlexmarkRenderer: + def render(node: Node)(renderLink: (DocLink, String) => String) = + val opts = MarkdownParser.mkMarkdownOptions(Seq(DocFlexmarkRenderer(renderLink))) + HtmlRenderer.builder(opts).build().render(node) \ No newline at end of file diff --git a/scala3doc/src/dotty/dokka/tasty/comments/package.scala b/scala3doc/src/dotty/dokka/tasty/comments/package.scala index aba26cb94d98..35efdb587d24 100644 --- a/scala3doc/src/dotty/dokka/tasty/comments/package.scala +++ b/scala3doc/src/dotty/dokka/tasty/comments/package.scala @@ -68,9 +68,3 @@ object dbg: def see(n: mdu.Node): See = See(n, n.getChildIterator.asScala.map(see).toList) - - def parseRaw(str: String) = - MarkdownCommentParser(null).stringToMarkup(str) - - def parse(str: String) = - parseRaw( Preparser.preparse( Cleaner.clean(str) ).body ) diff --git a/scala3doc/src/dotty/dokka/tasty/comments/wiki/Converter.scala b/scala3doc/src/dotty/dokka/tasty/comments/wiki/Converter.scala deleted file mode 100644 index bec172c96c99..000000000000 --- a/scala3doc/src/dotty/dokka/tasty/comments/wiki/Converter.scala +++ /dev/null @@ -1,159 +0,0 @@ -package dotty.dokka.tasty.comments -package wiki - -import scala.jdk.CollectionConverters._ - -import org.jetbrains.dokka.model.{doc => dkkd} - -import dotty.dokka.tasty.SymOps - -class Converter(val repr: Repr) extends BaseConverter { - import Emitter._ - - // makeshift support for not passing an owner - // see same in MarkdownConverter - val qctx: repr.qctx.type = if repr == null then null else repr.qctx - val owner: qctx.reflect.Symbol = if repr == null then null.asInstanceOf[qctx.reflect.Symbol] else repr.sym - - object SymOps extends SymOps[qctx.type](qctx) - import SymOps._ - - def convertBody(body: Body): dkkd.DocTag = { - dkkd.P( - collect { - body.blocks.foreach(emitBlock) - }.asJava, - kt.emptyMap, - ) - } - - def emitBlock(block: Block)(using Emitter[dkkd.DocTag]): Unit = - block match { - case Title(text, level) => - val content = convertInline(text) - // NOTE: these aren't strictly necessary, but if you inline them, incremental compilation will break - val jContent = content.asJava : java.util.List[_ <: dkkd.DocTag] - val jAtt = kt.emptyMap[String, String] - emit(level match { - case 1 => dkkd.H1(jContent, jAtt) - case 2 => dkkd.H2(jContent, jAtt) - case 3 => dkkd.H3(jContent, jAtt) - case 4 => dkkd.H4(jContent, jAtt) - case 5 => dkkd.H5(jContent, jAtt) - case 6 => dkkd.H6(jContent, jAtt) - }) - - case Paragraph(text) => - emit(dkkd.P( - convertInline(text).asJava, - kt.emptyMap, - )) - case Code(data: String) => emit(dkkd.CodeBlock(List(dkk.text(data)).asJava, kt.emptyMap)) - case HorizontalRule() => emit(dkkd.HorizontalRule.INSTANCE) - case DefinitionList(items) => - sys.error("not supported yet: definition list") - - case UnorderedList(items) => - emit(dkkd.Ul( - convertListItems(items).asJava, - kt.emptyMap, - )) - - case OrderedList(items, style) => - // TODO use style - emit(dkkd.Ol( - convertListItems(items).asJava, - kt.emptyMap, - )) - } - - def convertListItems(items: Seq[Block]): Seq[dkkd.DocTag] = { - import scala.collection.mutable.ListBuffer - val listBld = ListBuffer.empty[dkkd.DocTag] - var elemBld = ListBuffer.empty[dkkd.DocTag] - - items.foreach { i => - val c = convertBlock(i) - c match { - case Seq(list: (dkkd.Ul | dkkd.Ol)) => - elemBld.append(list) - case c => - if !elemBld.isEmpty then { - listBld.append(dkkd.Li(elemBld.result.asJava, kt.emptyMap)) - elemBld = ListBuffer.empty - } - elemBld.appendAll(c) - } - } - - if elemBld.nonEmpty then - listBld.append(dkkd.Li(elemBld.result.asJava, kt.emptyMap)) - - listBld.result - } - - def convertBlock(block: Block): Seq[dkkd.DocTag] = - collect { emitBlock(block) } - - def emitInline(inl: Inline)(using Emitter[dkkd.DocTag]): Unit = inl match { - case Chain(items: Seq[Inline]) => items.foreach(emitInline) - case Summary(text) => emitInline(text) - case Text(text) => emit(dkk.text(text)) - case Italic(text) => emit(dkkd.I(convertInline(text).asJava, kt.emptyMap)) - case Bold(text) => emit(dkkd.B(convertInline(text).asJava, kt.emptyMap)) - case Underline(text) => emit(dkkd.U(convertInline(text).asJava, kt.emptyMap)) - case Monospace(text) => emit(dkkd.CodeInline(convertInline(text).asJava, kt.emptyMap)) - case Link(target, body) => - def resolveBody(default: String) = - if !body.isEmpty - then convertInline(body).asJava - else Seq(dkk.text(default)).asJava - - emit(target match { - case SchemeUri() => - dkkd.A(resolveBody(default = target), Map("href" -> target).asJava) - case _ => - resolveLinkQuery(target, Some(body).filter(!_.isEmpty)) - }) - - case Superscript(i) => - def name = inl.getClass.getSimpleName - // println(s"WARN: Wiki syntax tag not yet fully supported: $name") - emitInline(i) - - case Subscript(i) => - def name = inl.getClass.getSimpleName - // println(s"WARN: Wiki syntax tag not yet fully supported: $name") - emitInline(i) - - case HtmlTag(content) => - emit(dkkd.Html(List(dkk.text(content)).asJava, kt.emptyMap)) - - case _: RepresentationLink => - val name = inl.getClass.getSimpleName - // println(s"WARN: Wiki syntax tag not yet supported: $name") - emit(dkk.text(name)) - } - - def convertInline(inl: Inline): Seq[dkkd.DocTag] = - collect { emitInline(inl) } - - def resolveLinkQuery(queryStr: String, bodyOpt: Option[Inline]): dkkd.DocTag = { - def resolveBody(default: String) = - bodyOpt match { - case Some(body) => - convertInline(body).asJava - case None => - Seq(dkk.text(default)).asJava - } - - withParsedQuery(queryStr) { query => - MemberLookup.lookup(using qctx)(query, owner) match { - case Some((sym, targetText)) => - dkkd.DocumentationLink(sym.dri.asDokka, resolveBody(default = targetText), kt.emptyMap) - case None => - dkkd.A(resolveBody(default = query.join), Map("href" -> "#").asJava) - } - } - } -} diff --git a/scala3doc/src/dotty/dokka/tasty/comments/wiki/Entities.scala b/scala3doc/src/dotty/dokka/tasty/comments/wiki/Entities.scala index 4f573cb0390c..f253738b9662 100644 --- a/scala3doc/src/dotty/dokka/tasty/comments/wiki/Entities.scala +++ b/scala3doc/src/dotty/dokka/tasty/comments/wiki/Entities.scala @@ -1,4 +1,5 @@ -package dotty.dokka.tasty.comments.wiki +package dotty.dokka +package tasty.comments.wiki import scala.collection.{Seq => _, _} // import representations._ @@ -26,7 +27,7 @@ final case class Body(blocks: Seq[Block]) { case Underline(text) => summaryInInline(text) case Superscript(text) => summaryInInline(text) case Subscript(text) => summaryInInline(text) - case Link(_, title) => summaryInInline(title) + case Link(_, title) => title.fold(Nil)(summaryInInline) case _ => Nil } (blocks flatMap summaryInBlock).toList match { @@ -37,8 +38,10 @@ final case class Body(blocks: Seq[Block]) { } } +sealed abstract class WikiDocElement + /** A block-level element of text, such as a paragraph or code block. */ -sealed abstract class Block +sealed abstract class Block extends WikiDocElement final case class Title(text: Inline, level: Int) extends Block final case class Paragraph(text: Inline) extends Block @@ -46,15 +49,13 @@ final case class Code(data: String) extends Block final case class UnorderedList(items: Seq[Block]) extends Block final case class OrderedList(items: Seq[Block], style: String) extends Block final case class DefinitionList(items: SortedMap[Inline, Block]) extends Block -final case class HorizontalRule() extends Block +object HorizontalRule extends Block /** An section of text inside a block, possibly with formatting. */ -sealed abstract class Inline { - def isEmpty = this match { +sealed abstract class Inline extends WikiDocElement: + def isEmpty = this match case Chain(items) if items.isEmpty => true case _ => false - } -} final case class Chain(items: Seq[Inline]) extends Inline object Chain { @@ -65,7 +66,7 @@ final case class Bold(text: Inline) extends Inline final case class Underline(text: Inline) extends Inline final case class Superscript(text: Inline) extends Inline final case class Subscript(text: Inline) extends Inline -final case class Link(target: String, title: Inline) extends Inline +final case class Link(link: DocLink, title: Option[Inline]) extends Inline final case class Monospace(text: Inline) extends Inline final case class Text(text: String) extends Inline abstract class RepresentationLink(val title: Inline) extends Inline { def link: LinkTo } diff --git a/scala3doc/src/dotty/dokka/tasty/comments/wiki/Parser.scala b/scala3doc/src/dotty/dokka/tasty/comments/wiki/Parser.scala index 94cda29c05a7..234f3ef6480b 100644 --- a/scala3doc/src/dotty/dokka/tasty/comments/wiki/Parser.scala +++ b/scala3doc/src/dotty/dokka/tasty/comments/wiki/Parser.scala @@ -11,6 +11,7 @@ import dotty.dokka.tasty.comments.Regexes._ */ final class Parser( val buffer: String, + linkResolver: (String, Option[Inline]) => Inline ) extends CharReader(buffer) { wiki => var summaryParsed = false @@ -122,7 +123,7 @@ final class Parser( jumpWhitespace() repeatJump('-') blockEnded("horizontal rule") - HorizontalRule() + HorizontalRule } /** {{{ para ::= inline '\n' }}} */ @@ -320,7 +321,7 @@ final class Parser( else None jump(stop) - Link(target, title getOrElse Chain.Empty) + linkResolver(target, title) } /* UTILITY */ diff --git a/scala3doc/src/dotty/dokka/transformers/ScalaCommentToContentConverter.scala b/scala3doc/src/dotty/dokka/transformers/ScalaCommentToContentConverter.scala deleted file mode 100644 index 33b873fa8732..000000000000 --- a/scala3doc/src/dotty/dokka/transformers/ScalaCommentToContentConverter.scala +++ /dev/null @@ -1,60 +0,0 @@ -package dotty.dokka - -import org.jetbrains.dokka._ -import org.jetbrains.dokka.model.doc._ -import org.jetbrains.dokka.model.properties.PropertyContainer -import org.jetbrains.dokka.pages._ -import collection.JavaConverters._ -import org.jetbrains.dokka.base.transformers.pages.comments.{DocTagToContentConverter, CommentsToContentConverter} -import org.jetbrains.dokka.model.properties.{PropertyContainer, ExtraProperty, ExtraProperty$Key, MergeStrategy} -import java.util.{Set => JSet, List => JList} - -object ScalaCommentToContentConverter extends DocTagToContentConverter { - override def buildContent( - docTag: DocTag, - dci: DCI, - sourceSets: JSet[? <: DokkaConfiguration$DokkaSourceSet], - styles: JSet[? <: Style], - extra: PropertyContainer[ContentNode] - ): JList[ContentNode] = docTag match { - case p: P => - val group = super.buildContent(p, dci, sourceSets, styles, extra).get(0).asInstanceOf[ContentGroup] - List(group.copy( - group.getChildren, - group.getDci, - group.getSourceSets, - Set(TextStyle.Block).asJava, - group.getExtra - )).asJava - case docTag: A => - val superRes = super.buildContent(docTag, dci, sourceSets, styles, extra).get(0) - val res = superRes.withNewExtras(superRes.getExtra plus ExtraLinkAttributes( - title = docTag.getParams.asScala.get("title") - )) - List(res).asJava - - case h: Html => - val children = h.getChildren - require(children.size() == 1) - require(children.get(0).isInstanceOf[Text]) - List( - HtmlContentNode( - children.get(0).asInstanceOf[Text].getBody, - dci, - sourceSets.asScala.toSet.toDisplay.asScala.toSet, - styles.asScala.toSet - ) - ).asJava - case other => super.buildContent(other, dci, sourceSets, styles, extra) - } - - case class ExtraLinkAttributes(title: Option[String]) extends ExtraProperty[ContentNode] { - def getKey() = LinkAttributesKey - } - - case object LinkAttributesKey extends ExtraProperty.Key[ContentNode, Null] { - def mergeStrategyFor(left: Null, right: Null) = MergeStrategy.Fail( - () => throw NotImplementedError(s"Property merging for $this is not implemented") - ).asInstanceOf[MergeStrategy[ContentNode]] - } -} diff --git a/scala3doc/src/dotty/renderers/DokkaScalaHtmlRenderer.scala b/scala3doc/src/dotty/renderers/DokkaScalaHtmlRenderer.scala deleted file mode 100644 index 04d81bbb4bd8..000000000000 --- a/scala3doc/src/dotty/renderers/DokkaScalaHtmlRenderer.scala +++ /dev/null @@ -1,258 +0,0 @@ -package dotty.dokka - -import org.jetbrains.dokka.plugability.DokkaContext -import org.jetbrains.dokka.pages._ -import org.jetbrains.dokka.model._ -import org.jetbrains.dokka.base.renderers.html.HtmlRenderer -import org.jetbrains.dokka.base.renderers.DefaultRenderer -import org.jetbrains.dokka._ -import HTML._ -import collection.JavaConverters._ -import java.net.URI -import java.net.URL -import java.util.{List => JList, Set => JSet} -import kotlinx.html.FlowContent -import kotlinx.html.stream.StreamKt -import kotlinx.html.Gen_consumer_tagsKt -import kotlinx.html.ApiKt -import kotlinx.html.HTMLTag -import kotlinx.html.DIV -import dotty.dokka.model.api.Link -import dotty.dokka.model.api.HierarchyGraph -import org.jetbrains.dokka.base.resolvers.local.LocationProvider -import org.jetbrains.dokka.base.transformers.pages.comments.DocTagToContentConverter -import scala.util.Try -import org.jetbrains.dokka.base.renderers.html.SearchbarDataInstaller -import org.jsoup.Jsoup -import java.nio.file.Paths - -trait SignatureRenderer: - def currentDri: DRI - def link(dri: DRI): Option[String] - - def renderElement(e: String | (String, DRI) | Link) = renderElementWith(e) - - def renderLink(name: String, dri: DRI, modifiers: AppliedAttr*) = - link(dri) match - case Some(link) => a(href := link, modifiers)(name) - case _ => span(Attr("data-unresolved-link") := "", modifiers)(name) - - def renderElementWith(e: String | (String, DRI) | Link, modifiers: AppliedAttr*) = e match - case (name, dri) => renderLink(name, dri, modifiers:_*) - case name: String => raw(name) - case Link(name, dri) => renderLink(name, dri, modifiers:_*) - -class SignatureRendererImpl(pageContext: ContentPage, sourceSetRestriciton: JSet[DisplaySourceSet], locationProvider: LocationProvider) extends SignatureRenderer: - val currentDri = pageContext.getDri.asScala.head.asScala - - def link(dri: DRI): Option[String] = - Option(locationProvider.resolve(dri.asDokka, sourceSetRestriciton, pageContext)) - -class DokkaScalaHtmlRenderer(using ctx: DokkaContext) extends HtmlRenderer(ctx) { - val args = summon[DocContext].args - - def init(ourRenderer: dotty.dokka.renderers.HtmlRenderer): Unit = - val f = classOf[DefaultRenderer[_]].getDeclaredField("locationProvider") - f.setAccessible(true) - f.set(this, new LocationProvider { - type DSS = JSet[org.jetbrains.dokka.model.DisplaySourceSet] - def resolve(to: DDRI, sourceSets: DSS, context: PageNode): String = - context match - case null => ??? - case fromPage: ContentPage => - val from = fromPage.getDri.asScala.head - val path = ourRenderer.pathToPage(to.asScala, from.asScala) - path - - // None other operation is needed - // This will goes away once we migrate away from dokka - def resolve(node: PageNode, context: PageNode, skipExtension: Boolean): String = ??? - def pathToRoot(from: PageNode): String = ??? - def ancestors(node: PageNode): JList[PageNode] = ??? - def expectedLocationForDri(dri: DDRI): String = ??? - }) - - override def render(root: RootPageNode): Unit = ??? - // We are using dokka renderer to render doc strings - - // Implementation below is based on Kotlin bytecode and we will try to migrate it to dokka - // TODO (https://github.com/lampepfl/scala3doc/issues/232): Move this method to dokka - def withHtml(context: FlowContent, content: String): Unit = context match { - case tag: HTMLTag => - ApiKt.unsafe(tag, { x => - x.unaryPlus(content) - U - }) - case _ => - val div = new DIV(JMap(), context.getConsumer()) - try - div.getConsumer().onTagStart(div) - withHtml(div, content) - catch - case e: Throwable => - div.getConsumer.onTagError(div, e) - finally - div.getConsumer.onTagEnd(div) - } - - lazy val sourceSets = ctx.getConfiguration.getSourceSets.asScala - .map(s => DisplaySourceSetKt.toDisplaySourceSet(s.asInstanceOf[DokkaConfiguration$DokkaSourceSet])).toSet.asJava - - type FlowContentConsumer = kotlin.jvm.functions.Function1[? >: kotlinx.html.FlowContent, kotlin.Unit] - - override def buildTable(f: FlowContent, node: ContentTable, pageContext: ContentPage, sourceSetRestriciton: JSet[DisplaySourceSet]) = { - val nodeStyles = node.getStyle.asScala.toSet - if nodeStyles.contains(TableStyle.DescriptionList) || nodeStyles.contains(TableStyle.NestedDescriptionList) then - withHtml(f, buildDescriptionList(node, pageContext, sourceSetRestriciton)) - else super.buildTable(f, node, pageContext, sourceSetRestriciton) - } - - override def wrapGroup(f: FlowContent, node: ContentGroup, pageContext: ContentPage, childrenCallback: FlowContentConsumer) = { - val additionalClasses = node.getStyle.asScala.map(_.toString.toLowerCase).mkString("", " ", "") - def buildSymbol: String = div(cls := s"symbol $additionalClasses")( - raw( - buildWithKotlinx(childrenCallback).toString - ) - ).toString - if node.getDci.getKind == ContentKind.Symbol && node.getStyle.asScala.toSet.contains(TextStyle.Monospace) then withHtml(f, buildSymbol) else super.wrapGroup(f, node, pageContext, childrenCallback) - } - - override def buildContentNode(f: FlowContent, node: ContentNode, pageContext: ContentPage, sourceSetRestriciton: JSet[DisplaySourceSet]) = { - node match { - case n: HtmlContentNode => - withHtml(f, raw(n.body).toString) - case mi: MemberInfo => - val memberHtml = div(renderers(pageContext)._2.fullMember(mi.member)) - withHtml(f, memberHtml.toString) - case other => super.buildContentNode(f, node, pageContext, sourceSetRestriciton) - } - } - - private def renderers(pageContext: ContentPage): (SignatureRenderer, MemberRenderer) = - val renderer = SignatureRendererImpl(pageContext, sourceSets, getLocationProvider) - (renderer, new MemberRenderer(renderer, buildWithKotlinx(_, pageContext, null))) - - private def buildNavigation(r: SignatureRenderer)(rootNav: NavigationNode): AppliedTag = - val currentPageDri = r.currentDri - - def renderNested(nav: NavigationNode): (Boolean, AppliedTag) = - val isSelected = nav.dri == currentPageDri - def linkHtml(exapnded: Boolean = false) = - val attrs = if (isSelected) Seq(cls := "selected expanded") else Nil - a(href := r.link(nav.dri).getOrElse("#"), attrs)(nav.name) - - nav.nested match - case Nil => isSelected -> div(linkHtml()) - case children => - val nested = children.map(renderNested) - val expanded = nested.exists(_._1) | nav == rootNav - val attr = if expanded || isSelected then Seq(cls := "expanded") else Nil - (isSelected || expanded) -> div(attr)( - linkHtml(expanded), - span(), - nested.map(_._2) - ) - - renderNested(rootNav)._2 - - def buildDescriptionList(node: ContentTable, pageContext: ContentPage, sourceSetRestriciton: JSet[DisplaySourceSet]) = { - val children = node.getChildren.asScala.toList.zipWithIndex - val nodeStyles = node.getStyle.asScala.toSet - val classes = if nodeStyles.contains(TableStyle.NestedDescriptionList) then "paramsdesc" else "attributes" - dl(cls := classes)( - children.map((e, i) => - if(i % 2 == 0) - dt( - raw( - buildWithKotlinx(e, pageContext, sourceSetRestriciton) - ) - ) - else - dd( - raw( - buildWithKotlinx(e, pageContext, sourceSetRestriciton) - ) - ) - ) - ).toString - } - - override def buildResolvedLink( - f: FlowContent, - node: ContentResolvedLink, - pageContext: ContentPage, - sourceSetRestriction: JSet[DisplaySourceSet], - ): Unit = { - import kotlinx.html.{Gen_consumer_tagsKt => dsl} - val c = f.getConsumer - - dsl.a(c, node.getAddress, /*target*/ null, /*classes*/ null, { e => - import ScalaCommentToContentConverter._ - // node.getExtra.getMap.asScala.get(LinkAttributesKey) - Option(node.get(LinkAttributesKey).asInstanceOf[ExtraLinkAttributes]) - .flatMap(_.title) - .foreach(e.getAttributes.put("title", _)) - buildText(f, node.getChildren, pageContext, sourceSetRestriction) - U - }) - } - - override def buildCodeBlock( - f: FlowContent, - code: ContentCodeBlock, - pageContext: ContentPage, - ): Unit = { - // we cannot use Scalatags, because we need to call buildContentNode - // TODO rewrite it to using HTML - import kotlinx.html.{Gen_consumer_tagsKt => dsl} - val c = f.getConsumer - - dsl.div(c, "sample-container", { e => - dsl.pre(c, null, { e => - val codeClass = code.getStyle.asScala.iterator.map(_.toString.toLowerCase).mkString("", " ", " language-scala") - dsl.code(c, codeClass, { e => - e.getAttributes.put("theme", "idea") - code.getChildren.asScala.foreach(buildContentNode(f, _, pageContext, /*sourceSetRestriction*/ null)) - U - }) - U - }) - U - }) - } - - private val HashRegex = "([^#]+)(#.+)".r - - private def resolveRoot(page: PageNode, path: String) = - getLocationProvider.pathToRoot(page) + path - - private def resolveLink(page: PageNode)(url: String): String = - if URI(url).isAbsolute then url else resolveRoot(page, url) - - private def linkResources(page: PageNode, resources: Iterable[String]): Iterable[AppliedTag] = - def fileExtension(url: String): String = - val param = url.indexOf('?') - val end = if param < 0 then url.length else param - val point = url.lastIndexOf('.', end) - url.substring(point+1, end) - - for res <- resources yield - fileExtension(res) match - case "css" => link(rel := "stylesheet", href := resolveLink(page)(res)) - case "js" => script(`type` := "text/javascript", src := resolveLink(page)(res), defer := "true") - case _ => raw(res) - - def buildWithKotlinx(node: ContentNode, pageContext: ContentPage, sourceSetRestriction: JSet[DisplaySourceSet]): String = - Gen_consumer_tagsKt.div( - StreamKt.createHTML(true, false), - null, - (div) => {build(node, div, pageContext, sourceSetRestriction); kotlin.Unit.INSTANCE} - ).toString.stripPrefix("
").stripSuffix("
\n") - - def buildWithKotlinx(func: FlowContentConsumer): String = - Gen_consumer_tagsKt.div( - StreamKt.createHTML(true, false), - null, - func - ).toString.stripPrefix("
").stripSuffix("
\n") -} diff --git a/scala3doc/src/dotty/renderers/HtmlRenderer.scala b/scala3doc/src/dotty/renderers/HtmlRenderer.scala index 07ecad169595..c5a63db61e04 100644 --- a/scala3doc/src/dotty/renderers/HtmlRenderer.scala +++ b/scala3doc/src/dotty/renderers/HtmlRenderer.scala @@ -14,7 +14,6 @@ import java.nio.file.Path import java.nio.file.Files import java.nio.file.FileVisitOption import java.io.File -import org.jetbrains.dokka.pages.ContentNode case class Page(link: Link, content: Member | ResolvedTemplate | String, children: Seq[Page]): def withNewChildren(newChildren: Seq[Page]) = copy(children = children ++ newChildren) @@ -25,7 +24,7 @@ case class Page(link: Link, content: Member | ResolvedTemplate | String, childre case t: ResolvedTemplate => t.hasFrame case _ => true -class HtmlRenderer(rootPackage: Member, val members: Map[DRI, Member], buildNode: DRI => ContentNode => String)(using ctx: DocContext) +class HtmlRenderer(rootPackage: Member, val members: Map[DRI, Member])(using ctx: DocContext) extends SiteRenderer, Resources, Locations, Writter: private val args = summon[DocContext].args val staticSite = summon[DocContext].staticSiteContext @@ -67,9 +66,9 @@ class HtmlRenderer(rootPackage: Member, val members: Map[DRI, Member], buildNode case m: Member => val signatureRenderer = new SignatureRenderer: def currentDri: DRI = page.link.dri - def link(dri: DRI): Option[String] = Some(pathToPage(page.link.dri, dri)) + def link(dri: DRI): Option[String] = Some(pathToPage(dri, page.link.dri)) - MemberRenderer(signatureRenderer, buildNode(page.link.dri)).fullMember(m) + MemberRenderer(signatureRenderer).fullMember(m) case t: ResolvedTemplate => siteContent(page.link.dri, t) case a: String => raw(a) diff --git a/scala3doc/src/dotty/renderers/MemberRenderer.scala b/scala3doc/src/dotty/renderers/MemberRenderer.scala index 88ece96f64f9..e33eab4a6742 100644 --- a/scala3doc/src/dotty/renderers/MemberRenderer.scala +++ b/scala3doc/src/dotty/renderers/MemberRenderer.scala @@ -3,26 +3,14 @@ package dotty.dokka import dotty.dokka.model.api._ import scala.collection.immutable.SortedMap import dotty.dokka.HTML._ -import org.jetbrains.dokka.pages.{DCI, ContentKind, ContentNode} -import org.jetbrains.dokka.model.properties.PropertyContainer import collection.JavaConverters._ import dotty.dokka.translators.FilterAttributes +import dotty.dokka.tasty.comments.markdown.DocFlexmarkRenderer +import com.vladsch.flexmark.util.ast.{Node => MdNode} +import dotty.dokka.tasty.comments.wiki.WikiDocElement -class MemberRenderer(signatureRenderer: SignatureRenderer, buildNode: ContentNode => String)(using DocContext): - +class MemberRenderer(signatureRenderer: SignatureRenderer)(using DocContext) extends DocRender(signatureRenderer): import signatureRenderer._ - private val converter = ScalaCommentToContentConverter - - def renderDocPart(d: DocPart): AppliedTag = - val sb = StringBuilder() - converter.buildContent( - d, - DCI(JSet(), ContentKind.Comment), - JSet(), - JSet(), - PropertyContainer.Companion.empty() - ).forEach(c => sb.append(buildNode(c))) - raw(sb) def doc(m: Member): Seq[AppliedTag] = m.docs.fold(Nil)(d => Seq(renderDocPart(d.body))) @@ -61,7 +49,7 @@ class MemberRenderer(signatureRenderer: SignatureRenderer, buildNode: ContentNod nested("Type Params", d.typeParams) ++ nested("Value Params", d.valueParams) ++ opt("Returns", d.result) ++ - nested("Throws", d.throws.transform((_, v) => v._1)) ++ + nested("Throws", d.throws) ++ opt("Constructor", d.constructor) ++ list("Authors", d.authors) ++ list("See also", d.see) ++ @@ -104,9 +92,7 @@ class MemberRenderer(signatureRenderer: SignatureRenderer, buildNode: ContentNod } def memberInfo(m: Member): Seq[AppliedTag] = - val bodyContents = m.docs.fold(Nil)(d => - if d.body.getChildren.isEmpty then Seq(d.body) else d.body.getChildren.asScala.toList - ).map(renderDocPart) + val bodyContents = m.docs.fold(Nil)(e => renderDocPart(e.body) :: Nil) Seq( div(cls := "documentableBrief doc")(bodyContents.take(1)), diff --git a/scala3doc/src/dotty/renderers/SignatureRenderer.scala b/scala3doc/src/dotty/renderers/SignatureRenderer.scala new file mode 100644 index 000000000000..4b627585087e --- /dev/null +++ b/scala3doc/src/dotty/renderers/SignatureRenderer.scala @@ -0,0 +1,41 @@ +package dotty.dokka + +import HTML._ +import collection.JavaConverters._ +import java.net.URI +import java.net.URL +import java.util.{List => JList, Set => JSet} +import kotlinx.html.FlowContent +import kotlinx.html.stream.StreamKt +import kotlinx.html.Gen_consumer_tagsKt +import kotlinx.html.ApiKt +import kotlinx.html.HTMLTag +import kotlinx.html.DIV +import dotty.dokka.model.api.Link +import dotty.dokka.model.api.HierarchyGraph +import scala.util.Try +import org.jsoup.Jsoup +import java.nio.file.Paths + +// TODO merge it with new renderer +trait SignatureRenderer: + def currentDri: DRI + def link(dri: DRI): Option[String] + + def renderElement(e: String | (String, DRI) | Link) = renderElementWith(e) + + def renderLink(name: String, dri: DRI, modifiers: AppliedAttr*) = + renderLinkContent(name, dri, modifiers:_*) + + def unresolvedLink(content: TagArg, modifiers: AppliedAttr*) = + span(Attr("data-unresolved-link") := "", modifiers)(content) + + def renderLinkContent(content: TagArg, dri: DRI, modifiers: AppliedAttr*) = + link(dri) match + case Some(link) => a(href := link, modifiers)(content) + case _ => unresolvedLink(content, modifiers:_*) + + def renderElementWith(e: String | (String, DRI) | Link, modifiers: AppliedAttr*) = e match + case (name, dri) => renderLink(name, dri, modifiers:_*) + case name: String => raw(name) + case Link(name, dri) => renderLink(name, dri, modifiers:_*) diff --git a/scala3doc/src/dotty/renderers/WikiDocRenderer.scala b/scala3doc/src/dotty/renderers/WikiDocRenderer.scala new file mode 100644 index 000000000000..6357fe9dc8f3 --- /dev/null +++ b/scala3doc/src/dotty/renderers/WikiDocRenderer.scala @@ -0,0 +1,69 @@ +package dotty.dokka + +import dotty.dokka.tasty.comments.wiki._ +import dotty.dokka.HTML._ +import com.vladsch.flexmark.util.ast.{Node => MdNode} +import dotty.dokka.tasty.comments.wiki.WikiDocElement +import dotty.dokka.tasty.comments.markdown.DocFlexmarkRenderer + +class DocRender(signatureRenderer: SignatureRenderer)(using DocContext): + + def renderDocPart(doc: DocPart): AppliedTag = doc match + case md: MdNode => renderMarkdown(md) + case Nil => raw("") + case Seq(elem: WikiDocElement) => renderElement(elem) + case list: Seq[WikiDocElement] => div(list.map(renderElement)) + + private def renderMarkdown(el: MdNode): AppliedTag = + raw(DocFlexmarkRenderer.render(el)( (link,name) => + renderLink(link, default => text(if name.isEmpty then default else name)).toString + )) + + private def listItems(items: Seq[WikiDocElement]) = + items.map(i => li(renderElement(i))) + private def notSupported(name: String, content: AppliedTag): AppliedTag = + report.warning(s"Wiki syntax does not support $name in ${signatureRenderer.currentDri.location}") + content + + private def renderLink(target: DocLink, linkBody: String => TagArg): AppliedTag = + target match + case DocLink.ToDRI(dri: DRI, name: String) => + signatureRenderer.renderLinkContent(linkBody(name), dri) + case DocLink.ToURL(url) => a(href := url)(linkBody(url)) + case DocLink.UnresolvedDRI(query, msg) => + val tooltip = s"Problem linking $query: $msg" + signatureRenderer.unresolvedLink(linkBody(query), titleAttr := tooltip) + + private def renderElement(e: WikiDocElement): AppliedTag = e match + case Title(text, level) => + val content = renderElement(text) + level match + case 1 => h1(content) + case 2 => h2(content) + case 3 => h3(content) + case 4 => h4(content) + case 5 => h5(content) + case 6 => h6(content) + case Paragraph(text) => p(renderElement(text)) + case Code(data: String) => code(raw(data)) // TODO add classes + case HorizontalRule => hr + + case UnorderedList(items) => ul(listItems(items)) + case OrderedList(items, style) => ol(listItems(items)) // TODO use style + case Chain(items: Seq[Inline]) => span(items.map(renderElement)) + case Italic(text) => span(cls:="italic")(renderElement(text)) + case Underline(text) => span(cls:="underline")(renderElement(text)) + case Bold(text) => span(cls:="bold")(renderElement(text)) + case Monospace(text) => code(renderElement(text)) + case Superscript(text) => span(cls:="superscript")(renderElement(text)) // TODO implement style + case Subscript(text) => span(cls:="subscript")(renderElement(text)) // TODO implement style + case Link(target, body) => + renderLink(target, default => body.fold[TagArg](text(default))(renderElement)) + case Text(text) => raw(text) + case Summary(text) => renderElement(text) + case HtmlTag(content) => raw(content) + + case DefinitionList(items) => notSupported("DefinitionList", raw("")) + + case link: RepresentationLink => + notSupported("Subscript", renderElement(link.title)) diff --git a/scala3doc/src/dotty/renderers/html.scala b/scala3doc/src/dotty/renderers/html.scala index a3e29b056161..05a95f232e20 100644 --- a/scala3doc/src/dotty/renderers/html.scala +++ b/scala3doc/src/dotty/renderers/html.scala @@ -60,6 +60,8 @@ object HTML: val h2 = Tag("h2") val h3 = Tag("h3") val h4 = Tag("h4") + val h5 = Tag("h4") + val h6 = Tag("h4") val dl = Tag("dl") val dd = Tag("dd") val dt = Tag("dt") @@ -78,6 +80,7 @@ object HTML: val nav = Tag("nav") val img = Tag("img") val ul = Tag("ul") + val ol = Tag("ol") val li = Tag("li") val code = Tag("code") @@ -98,6 +101,11 @@ object HTML: val alt = Attr("alt") val value = Attr("value") val onclick=Attr("onclick") + val titleAttr =Attr("title") def raw(content: String): AppliedTag = new AppliedTag(content) def raw(content: StringBuilder): AppliedTag = content + + def text(content: String) = content.escapeReservedTokens + + val hr = raw("
") diff --git a/scala3doc/test/dotty/dokka/BaseHtmlTest.scala b/scala3doc/test/dotty/dokka/BaseHtmlTest.scala new file mode 100644 index 000000000000..921fa19056ad --- /dev/null +++ b/scala3doc/test/dotty/dokka/BaseHtmlTest.scala @@ -0,0 +1,68 @@ +package dotty.dokka + +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths +import org.junit.Test +import org.junit.Assert._ +import org.jsoup.Jsoup +import org.jsoup.nodes.Document +import java.nio.charset.Charset +import dotty.dokka.test.BuildInfo + +class BaseHtmlTest: + val unresolvedLinkSelector = ".documentableBrief span[data-unresolved-link]" + + def projectName = "Test Project Name" + def projectVersion = "1.0.1-M1" + + case class ProjectContext(path: Path) + + def withGeneratedSite(base: Path)(op: ProjectContext ?=> Unit): Unit = + withGeneratedDoc(Seq("site"), docsRoot = Some(base.toAbsolutePath.toString))(op) + + def withGeneratedDoc( + pcks: Seq[String], + docsRoot: Option[String] = None)( + op: ProjectContext ?=> Unit, + ): Unit = + val dest = Files.createTempDirectory("test-doc") + try + val args = Scala3doc.Args( + name = projectName, + tastyFiles = pcks.flatMap(tastyFiles), + output = dest.toFile, + docsRoot = docsRoot, + projectVersion = Some(projectVersion), + ) + Scala3doc.run(args)(using testContext) + op(using ProjectContext(dest)) + + finally IO.delete(dest.toFile) + + val testDocPath = Paths.get(BuildInfo.testDocumentationRoot) + + class DocumentContext(d: Document, path: Path): + import collection.JavaConverters._ + + def niceMsg(msg: String) = s"$msg in $path (body):\n ${d.html()}:\n" + + def assertTextsIn(selector: String, expected: String*) = + assertFalse(niceMsg("Selector not found"), d.select(selector).isEmpty) + val found = d.select(selector).eachText.asScala + assertEquals(niceMsg(s"Context does not match for '$selector'"), expected.toList, found.toList) + + def assertAttr(selector: String, attr: String, expected: String*) = + assertFalse(niceMsg(s"Selector '$selector' not found"), d.select(selector).isEmpty) + val found = d.select(selector).eachAttr(attr).asScala + assertEquals(niceMsg(s"Attribute $attr does not match for '$selector'"), expected.toList, found.toList) + + def assertNotExists(selector: String) = + val msg = niceMsg(s"Selector '$selector' exisits in document") + assertTrue(msg, d.select(selector).isEmpty) + + def withHtmlFile(pathStr: String)(op: DocumentContext => Unit)(using ProjectContext) = + val path = summon[ProjectContext].path.resolve(pathStr) + assertTrue(s"File at $path does not exisits!", Files.exists(path)) + val document = Jsoup.parse(IO.read(path)) + op(DocumentContext(document, path)) \ No newline at end of file diff --git a/scala3doc/test/dotty/dokka/site/SiteGeneratationTest.scala b/scala3doc/test/dotty/dokka/site/SiteGeneratationTest.scala index ce0b5f2bc260..3ff9b0d3977f 100644 --- a/scala3doc/test/dotty/dokka/site/SiteGeneratationTest.scala +++ b/scala3doc/test/dotty/dokka/site/SiteGeneratationTest.scala @@ -11,49 +11,7 @@ import org.jsoup.nodes.Document import java.nio.charset.Charset import dotty.dokka.test.BuildInfo -class SiteGeneratationTest: - val projectName = "Test Project Name" - val projectVersion = "1.0.1-M1" - - case class ProjectContext(path: Path) - - private def withGeneratedSite(base: Path)(op: ProjectContext ?=> Unit) = - val dest = Files.createTempDirectory("test-doc") - try - val args = Scala3doc.Args( - name = projectName, - tastyFiles = tastyFiles("links"), - output = dest.toFile, - docsRoot = Some(base.toAbsolutePath.toString), - projectVersion = Some(projectVersion), - ) - Scala3doc.run(args)(using testContext) - op(using ProjectContext(dest)) - - finally IO.delete(dest.toFile) - - val testDocPath = Paths.get(BuildInfo.testDocumentationRoot) - - class DocumentContext(d: Document, path: Path): - import collection.JavaConverters._ - - def niceMsg(msg: String) = s"$msg in $path (body):\n ${d.html()}:\n" - - def assertTextsIn(selector: String, expected: String*) = - assertFalse(niceMsg("Selector not found"), d.select(selector).isEmpty) - val found = d.select(selector).eachText.asScala - assertEquals(niceMsg(s"Context does not match for '$selector'"), expected.toList, found.toList) - - def assertAttr(selector: String, attr: String, expected: String*) = - assertFalse(niceMsg("Selector not found"), d.select(selector).isEmpty) - val found = d.select(selector).eachAttr(attr).asScala - assertEquals(niceMsg(s"Attribute $attr does not match for '$selector'"), expected.toList, found.toList) - - def withHtmlFile(path: Path)(op: DocumentContext => Unit) = { - assertTrue(s"File at $path does not exisits!", Files.exists(path)) - val document = Jsoup.parse(IO.read(path)) - op(DocumentContext(document, path)) - } +class SiteGeneratationTest extends BaseHtmlTest: def indexLinks(content: DocumentContext) = content.assertAttr("p a","href", "docs/index.html") @@ -64,7 +22,7 @@ class SiteGeneratationTest: header: String, parents: Seq[String] = Nil, checks: DocumentContext => Unit = _ => ())(using ProjectContext) = - withHtmlFile(summon[ProjectContext].path.resolve(path)){ content => + withHtmlFile(path){ content => content.assertTextsIn(".projectName", projectName) content.assertTextsIn(".projectVersion", projectVersion) content.assertTextsIn("h1", header) @@ -94,18 +52,17 @@ class SiteGeneratationTest: header = projectName, parents = parents ) - checkFile("api/tests/links.html")( - title = "tests.links", - header = "tests.links", + checkFile("api/tests/site.html")( + title = "tests.site", + header = "tests.site", parents = parents :+ mainTitle ) - checkFile("api/tests/links/LinksTest.html")( - title = "LinksTest", - header = "LinksTest", - parents = parents ++ Seq(mainTitle, "tests.links") + checkFile("api/tests/site/SomeClass.html")( + title = "SomeClass", + header = "SomeClass", + parents = parents ++ Seq(mainTitle, "tests.site") ) - @Test def basicTest() = withGeneratedSite(testDocPath.resolve("basic")){ testDocPages() diff --git a/scala3doc/test/dotty/dokka/tasty/comments/CommentParserTest.scala b/scala3doc/test/dotty/dokka/tasty/comments/CommentParserTest.scala deleted file mode 100644 index 1612c2e0ca5f..000000000000 --- a/scala3doc/test/dotty/dokka/tasty/comments/CommentParserTest.scala +++ /dev/null @@ -1,380 +0,0 @@ -package dotty.dokka.tasty.comments - -import scala.jdk.CollectionConverters._ -import scala.collection.mutable -import scala.collection.mutable.ListBuffer - -import org.jetbrains.dokka.model.{doc => dkkd} - -import org.junit.Test - -class CommentParserTest { - import CommentParserTest._ - - @Test def testMdBlockCode(): Unit = { - val mdp = MarkdownCommentParser(null) - val str = """```scala - |is.an("actual code block") - |with.multiple("lines") - |```""".stripMargin - - val res = mdp.markupToDokka(mdp.stringToMarkup(str)) - assertSame(res, - dkk.p()(dkk.codeBlock()( - dkk.text("""is.an("actual code block") - |with.multiple("lines") - |""".stripMargin) - )), - ) - } - - @Test def testMdIndentedCode(): Unit = { - val mdp = MarkdownCommentParser(null) - val str = """ is.an("actual code block") - | with.multiple("lines")""".stripMargin - - val res = mdp.markupToDokka(mdp.stringToMarkup(str)) - assertSame(res, - dkk.p()(dkk.codeBlock()( - dkk.text("""is.an("actual code block") - |with.multiple("lines")""".stripMargin) - ))) - } - - @Test def testMdAutolinks(): Unit = { - val mdp = MarkdownCommentParser(null) - val link = "http://www.google.com" - val str = s"This is an autolink: $link" - val res = mdp.markupToDokka(mdp.stringToMarkup(str)) - - assertSame(res, - { import dkk._ - p(p( - text("This is an autolink: "), - a("href" -> link)(text(link)), - )) - }) - } - - @Test def testMdWrappedAutolinks(): Unit = { - val mdp = MarkdownCommentParser(null) - val link = "http://www.google.com" - val str = s"This is an autolink: <$link>" - val res = mdp.markupToDokka(mdp.stringToMarkup(str)) - - assertSame(res, - { import dkk._ - p(p( - text("This is an autolink: "), - a("href" -> link)(text(link)), - )) - }) - } - - @Test def testMdList(): Unit = { - val mdp = MarkdownCommentParser(null) - val str = - """* a - | - a.a - | - a.b - | - a.c - |* b - | 1. b.1 - | 1. b.2 - | 1. b.3 - | * b.3.a - | * b.3.b - | * b.3.c""".stripMargin - - val res = mdp.markupToDokka(mdp.stringToMarkup(str)) - assertSame(res, - { import dkk._ - p( - ul( - li( - p(text("a")), - ul( - li(p(text("a.a"))), - li(p(text("a.b"))), - li(p(text("a.c"))), - )), - li( - p(text("b")), - ol( - li(p(text("b.1"))), - li(p(text("b.2"))), - li( - p(text("b.3")), - ul( - li(p(text("b.3.a"))), - li(p(text("b.3.b"))), - li(p(text("b.3.c"))), - )))))) - }, - ) - } - - @Test def testWikiList(): Unit = { - val mdp = WikiCommentParser(null) - val str = - """ - a - | - a.a - | - a.b - | - a.c - | - b - | 1. b.1 - | 1. b.2 - | 1. b.3 - | a. b.3.a - | a. b.3.b - | a. b.3.c""".stripMargin - - val res = mdp.markupToDokka(mdp.stringToMarkup(str)) - assertSame(res, - { import dkk._ - p( - ul( - li( - p(text("a")), - ul( - li(p(text("a.a"))), - li(p(text("a.b"))), - li(p(text("a.c"))), - )), - li( - p(text("b")), - ol( - li(p(text("b.1"))), - li(p(text("b.2"))), - li( - p(text("b.3")), - ol( - li(p(text("b.3.a"))), - li(p(text("b.3.b"))), - li(p(text("b.3.c"))), - )))))) - }, - ) - } -} - -object CommentParserTest { - - enum TagComparison { - case OK(self: Class[_], children: List[TagComparison], params: List[ParamComparison]) - case Mismatch(expCls: Class[_], gotCls: Class[_], gotTag: dkkd.DocTag) - case TextOK(text: String, children: List[TagComparison], params: List[ParamComparison]) - case TextMismatch(expStr: String, gotStr: String) - case Missing(tag: dkkd.DocTag) - case Extra(tag: dkkd.DocTag) - } - - enum ParamComparison { - case OK(name: String, value: String) - case Mismatch(name: String, exp: String, got: String) - case Missing(name: String, value: String) - case Extra(name: String, value: String) - } - - class TagComparisonBuilder( - val parent: Option[TagComparisonBuilder] - ) { - private var failed: Boolean = false - private var abortedWith: Option[TagComparison] = None - private val childrenBld: ListBuffer[TagComparison] = ListBuffer.empty - private val paramsBld: ListBuffer[ParamComparison] = ListBuffer.empty - - def emit(cmp: ParamComparison): Unit = { - cmp match { - case _: ParamComparison.OK => ; - case _ => - fail() - } - paramsBld.append(cmp) - } - - def emit(cmp: TagComparison): Unit = { - cmp match { - case _: (TagComparison.OK | TagComparison.TextOK) => ; - case _ => fail() - } - childrenBld.append(cmp) - } - - def child: TagComparisonBuilder = - TagComparisonBuilder(parent = Some(this)) - - def abort(res: TagComparison): Unit = { - failed = true - abortedWith = Some(res) - parent.foreach(_.fail()) - } - - def fail(): Unit = { - failed = true - parent.foreach(_.fail()) - } - - def hasFailed = failed - - def result(exp: dkkd.DocTag) = abortedWith.getOrElse(exp match { - case exp: dkkd.Text => - TagComparison.TextOK(exp.getBody, childrenBld.result, paramsBld.result) - case exp => - TagComparison.OK(exp.getClass, childrenBld.result, paramsBld.result) - }) - } - - def compareTags(exp: dkkd.DocTag, got: dkkd.DocTag): (TagComparison, Boolean) = { - val bld = TagComparisonBuilder(parent = None) - doCompareTags(bld)(exp, got) - (bld.result(exp), bld.hasFailed) - } - - def doCompareTags(bld: TagComparisonBuilder)(exp: dkkd.DocTag, got: dkkd.DocTag): Unit = - (exp, got) match { - case (_, _) if exp.getClass != got.getClass => - bld.abort(TagComparison.Mismatch(expCls = exp.getClass, gotCls = got.getClass, gotTag = got)) - case (exp: dkkd.Text, got: dkkd.Text) if exp.getBody != got.getBody => - bld.abort(TagComparison.TextMismatch(expStr = exp.getBody, gotStr = got.getBody)) - case _ => - val propmap = mutable.Map.empty[String, ParamComparison] - got.getParams.asScala.foreach { (k, v) => - propmap(k) = ParamComparison.Extra(k, v) - } - exp.getParams.asScala.foreach { (k, v) => - propmap.get(k) match { - case None => - propmap(k) = ParamComparison.Missing(k, v) - case Some(ParamComparison.Extra(_, gotV)) => - if gotV == v then - propmap(k) = ParamComparison.OK(k, v) - else - propmap(k) = ParamComparison.Mismatch(k, exp = v, got = gotV) - case other => - sys.error(s"unexpected param comparison: $other") - } - } - propmap.values.foreach(bld.emit) - val expIter = exp.getChildren.asScala.iterator - val gotIter = got.getChildren.asScala.iterator - while expIter.hasNext || gotIter.hasNext do - if !expIter.hasNext then - bld.emit(TagComparison.Extra(gotIter.next)) - else if !gotIter.hasNext then - bld.emit(TagComparison.Missing(expIter.next)) - else { - val exp = expIter.next - val got = gotIter.next - val childBld = bld.child - doCompareTags(childBld)(exp, got) - bld.emit(childBld.result(exp)) - } - } - - - def doRender(bld: StringBuilder, indent: Int)(cmp: TagComparison): Unit = { - def doIndent(ind: Int = indent): Unit = - bld ++= " " * ind - - def doLn(ln: String, ind: Int = indent): Unit = - doIndent(ind) - bld ++= ln - bld += '\n' - - def doText(text: String, ind: Int = indent): Unit = - var firstLine = true - text.linesIterator.foreach { ln => - if !firstLine then bld ++= "\n" else firstLine = false - doIndent(ind) - bld ++= ln - bld - } - - def renderTag(indent: Int)(tag: dkkd.DocTag): Unit = { - tag match { - case tag: dkkd.Text => - doLn("Text:", indent) - doText(tag.getBody, indent + 2) - case tag => - doIndent(indent) - bld ++= tag.getClass.getSimpleName - bld ++= "(\n" - var firstChild = true - tag.getChildren.asScala.foreach { c => - if !firstChild then bld += '\n' else firstChild = false - renderTag(indent + 2)(c) - } - bld ++= "\n" - doIndent(indent) - bld ++= ")" - } - } - - cmp match { - case TagComparison.TextOK(text, children, props) => - doLn("Text:") - doText(text, indent + 2) - case TagComparison.TextMismatch(expStr, gotStr) => - doLn("!!! MISMATCH: Text:") - doLn("Expected:", indent + 2) - doText(expStr, indent + 4) - bld += '\n' - doLn("Got:", indent + 2) - doText(gotStr, indent + 4) - case TagComparison.OK(cls, children, props) => - doIndent() - bld ++= cls.getSimpleName - bld ++= "(\n" - var firstChild = true - children.foreach { c => - if !firstChild then bld += '\n' else firstChild = false - doRender(bld, indent + 2)(c) - } - bld ++= "\n" - doIndent() - bld ++= ")" - case TagComparison.Mismatch(expCls, gotCls, gotTag) => - doLn(s"!!! MISMATCH: expected=${expCls.getSimpleName}, got=${gotCls.getSimpleName}, tag:") - renderTag(indent + 2)(gotTag) - case TagComparison.Extra(tag) => - doLn(s"!!! EXTRA:") - renderTag(indent + 2)(tag) - case TagComparison.Missing(tag) => - doLn(s"!!! MISSING:") - renderTag(indent + 2)(tag) - } - } - - def assertSame(got: dkkd.DocTag, exp: dkkd.DocTag, explode: Boolean = false) = { - val (comparison, failure) = compareTags(exp, got) - if explode || failure then { - val bld = new StringBuilder - bld += '\n' - doRender(bld, indent = 0)(comparison) - bld += '\n' - throw new java.lang.AssertionError(bld.toString) - } - } - - - object DkkDbg { - case class See(n: dkkd.DocTag, c: Seq[See]) { - def show(sb: StringBuilder, indent: Int): Unit = { - sb ++= " " * indent - sb ++= n.toString - sb ++= "\n" - c.foreach { s => s.show(sb, indent + 2) } - } - - override def toString = { - val sb = new StringBuilder - show(sb, 0) - sb.toString - } - } - - def see(n: dkkd.DocTag): See = - See(n, n.getChildren.asScala.map(see).toList) - } -} diff --git a/scala3doc/test/dotty/dokka/tasty/comments/DocFlexmarkParserTests.scala b/scala3doc/test/dotty/dokka/tasty/comments/DocFlexmarkParserTests.scala new file mode 100644 index 000000000000..6df1cbb9c536 --- /dev/null +++ b/scala3doc/test/dotty/dokka/tasty/comments/DocFlexmarkParserTests.scala @@ -0,0 +1,13 @@ +package dotty.dokka.tasty.comments + +import org.junit.{Test, Rule} +import org.junit.Assert.{assertSame, assertTrue, assertEquals} + +class DocFlexmarkParserTests { + @Test def test(): Unit = { + assertEquals(("a", "b c d"), DocFlexmarkParser.splitWikiLink("a b c d")) + assertEquals(("a", "b\\ c d"), DocFlexmarkParser.splitWikiLink("a b\\ c d")) + assertEquals(("a\\ b", "c d"), DocFlexmarkParser.splitWikiLink("a\\ b c d")) + assertEquals(("a\\\\", "b c d"), DocFlexmarkParser.splitWikiLink("a\\\\ b c d")) + } +} diff --git a/scala3doc/test/dotty/dokka/tasty/comments/IntegrationTest.scala b/scala3doc/test/dotty/dokka/tasty/comments/IntegrationTest.scala new file mode 100644 index 000000000000..376daf216bd9 --- /dev/null +++ b/scala3doc/test/dotty/dokka/tasty/comments/IntegrationTest.scala @@ -0,0 +1,32 @@ +package dotty.dokka +package tasty.comments + +import org.junit.Test + +abstract class BaseIntegrationTest(pck: String) extends BaseHtmlTest: + + @Test + def testLinks: Unit = withGeneratedDoc(pcks = Seq(pck, "commonlinks")) { + def checkDocLinks(links: String*)(ctx: DocumentContext): Unit = + ctx.assertAttr(".documentableBrief a", "href", links:_*) + ctx.assertNotExists("unresolvedLinkSelector") + + def checkUnresolved(ctx: DocumentContext): Unit = + ctx.assertAttr( + unresolvedLinkSelector, + "data-unresolved-link", + "", "" // each represent a link + ) + + withHtmlFile(s"api/tests/$pck/BrokenLinks.html")(checkUnresolved) + val otherPackagePath = "../commonlinks/SomeOtherPackage.html" + withHtmlFile(s"api/tests/$pck/OtherPackageLink.html")(checkDocLinks(otherPackagePath)) + // OtherPackageMembers - does not work, TODO? + withHtmlFile(s"api/tests/$pck/SamePackageLink.html")(checkDocLinks("SomeClass.html")) + // SamePackageMembers - does not work, TODO? + } + + +class MarkdownIntegrationTest extends BaseIntegrationTest("mdlinks") + +class WikiIntegrationTest extends BaseIntegrationTest("wikilinks") diff --git a/scala3doc/test/dotty/dokka/tasty/comments/MarkdownConverterTests.scala b/scala3doc/test/dotty/dokka/tasty/comments/MarkdownConverterTests.scala deleted file mode 100644 index 9f3b6d78b8c0..000000000000 --- a/scala3doc/test/dotty/dokka/tasty/comments/MarkdownConverterTests.scala +++ /dev/null @@ -1,13 +0,0 @@ -package dotty.dokka.tasty.comments - -import org.junit.{Test, Rule} -import org.junit.Assert.{assertSame, assertTrue, assertEquals} - -class MarkdownConverterTests { - @Test def test(): Unit = { - assertEquals(("a", "b c d"), MarkdownConverter.splitWikiLink("a b c d")) - assertEquals(("a", "b\\ c d"), MarkdownConverter.splitWikiLink("a b\\ c d")) - assertEquals(("a\\ b", "c d"), MarkdownConverter.splitWikiLink("a\\ b c d")) - assertEquals(("a\\\\", "b c d"), MarkdownConverter.splitWikiLink("a\\\\ b c d")) - } -} From a5c206f4afa1e4b0bc8b4edfcc4bb534a8937bd4 Mon Sep 17 00:00:00 2001 From: Krzysztof Romanowski Date: Tue, 26 Jan 2021 11:53:27 +0100 Subject: [PATCH 05/12] Do not depend on dokka anymore FIx some test failures --- project/Build.scala | 11 ----- scala3doc/README.md | 17 +------ scala3doc/src/dotty/dokka/Main.scala | 12 +---- .../src/dotty/dokka/ScalaModuleCreator.scala | 1 - scala3doc/src/dotty/dokka/compat.scala | 1 - .../src/dotty/dokka/site/SidebarParser.scala | 5 ++- .../dotty/dokka/tasty/ScalaDocSupport.scala | 1 - .../dotty/dokka/tasty/comments/package.scala | 45 ------------------- .../src/dotty/renderers/HtmlRenderer.scala | 3 +- scala3doc/src/dotty/renderers/Locations.scala | 35 ++++++++------- .../dotty/renderers/SignatureRenderer.scala | 6 --- .../src/dotty/renderers/SiteRenderer.scala | 2 +- ...ernalLocationProviderIntegrationTest.scala | 2 +- .../dotty/dokka/renderers/LocationTests.scala | 4 +- .../comments/DocFlexmarkParserTests.scala | 1 + 15 files changed, 31 insertions(+), 115 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index 73383c11d3a7..a668bfbd805b 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1544,19 +1544,13 @@ object Build { def joinProducts(products: Seq[java.io.File]): String = products.iterator.map(_.getAbsolutePath.toString).mkString(" ") - val dokkaVersion = "1.4.10.2" val flexmarkVersion = "0.42.12" project.settings(commonBootstrappedSettings). dependsOn(`scala3-compiler-bootstrapped`). dependsOn(`scala3-tasty-inspector`). settings( - // Needed to download dokka and its dependencies - resolvers += Resolver.jcenterRepo, libraryDependencies ++= Seq( - "org.jetbrains.dokka" % "dokka-core" % dokkaVersion, - "org.jetbrains.dokka" % "dokka-base" % dokkaVersion, - "org.jetbrains.kotlinx" % "kotlinx-html-jvm" % "0.7.2", // Needs update when dokka version changes "com.vladsch.flexmark" % "flexmark" % flexmarkVersion, "com.vladsch.flexmark" % "flexmark-html-parser" % flexmarkVersion, "com.vladsch.flexmark" % "flexmark-ext-anchorlink" % flexmarkVersion, @@ -1572,15 +1566,12 @@ object Build { "args4j" % "args4j" % "2.33", Dependencies.`jackson-dataformat-yaml`, - "org.jetbrains.dokka" % "dokka-test-api" % dokkaVersion % "test", "com.novocode" % "junit-interface" % "0.11" % "test", ), Test / test := (Test / test).dependsOn(compile.in(Compile).in(`scala3doc-testcases`)).value, testcasesOutputDir.in(Test) := joinProducts((`scala3doc-testcases`/Compile/products).value), testcasesSourceRoot.in(Test) := (baseDirectory.in(`scala3doc-testcases`).value / "src").getAbsolutePath.toString, Compile / mainClass := Some("dotty.dokka.Main"), - // There is a bug in dokka that prevents parallel tests withing the same jvm - fork.in(test) := true, baseDirectory.in(run) := baseDirectory.in(ThisBuild).value, generateSelfDocumentation := Def.taskDyn { generateDocumentation( @@ -1665,8 +1656,6 @@ object Build { BuildInfoPlugin.buildInfoScopedSettings(Test), BuildInfoPlugin.buildInfoScopedSettings(Compile), BuildInfoPlugin.buildInfoDefaultSettings, - // Uncomment to debug dokka processing (require to run debug in listen mode on 5005 port) - // javaOptions.in(run) += "-agentlib:jdwp=transport=dt_socket,server=n,address=localhost:5005,suspend=y" ) } diff --git a/scala3doc/README.md b/scala3doc/README.md index 4819a9021698..68cfc3dd7f9f 100644 --- a/scala3doc/README.md +++ b/scala3doc/README.md @@ -2,8 +2,7 @@ Scala3doc (name subject to change) is the documentation tool for [Dotty](https://github.com/lampepfl/dotty), which is scheduled to become -Scala 3. It's based on [Dokka](https://github.com/Kotlin/dokka), the -documentation tool for Kotlin. It uses the TastyInspector to access definitions, +Scala 3. It uses the TastyInspector to access definitions, which is an officially supported way to access Dotty's perspective of a codebase. @@ -48,9 +47,7 @@ CLI command for running our tool is in form: `sbt main -n -o -t - ``: classpath that was used to generate tasty files - ``: links to source files of module that are used to link symbols on pages to their source file. They need to be supplied in form: `local_dir=remote_dir#line_suffix` e.g. `src/main/scala=https://github.com/lampepfl/scala3doc/tree/master/src/main/scala#L` -- ``: directory of static documentation that you would like to render with API documentation. This feature is provided by dokka-site plugin: - - [GitHub](https://github.com/VirtusLab/dokka-site) - - [Documentation](https://virtuslab.github.io/dokka-site/index.html) +- ``: directory of static documentation that you would like to render with API documentation. ## Developing @@ -164,16 +161,6 @@ Make sure all the tests pass (simply run `sbt test` to verify that). ## FAQ -### Why depend on Dokka? - -We have two primary reasons for depending on Dokka. One of them is division of -labour - Dokka already has a team of maintainers, and it supports an excellent -API which already allowed us to quite easily generate documentation with it. By -depending on Dokka, we will be able to share a large portion of the maintenance -burden. The second reason is very pragmatic - on our own, it'd be difficult for -us to reach even feature parity with Scaladoc, simply because of workforce -constraints. Meanwhile, Dokka maintainers from VirtusLab reached out to us with -an offer of help, which we were happy to take. ### Why use TASTy? diff --git a/scala3doc/src/dotty/dokka/Main.scala b/scala3doc/src/dotty/dokka/Main.scala index 8a79345b942c..007238188a63 100644 --- a/scala3doc/src/dotty/dokka/Main.scala +++ b/scala3doc/src/dotty/dokka/Main.scala @@ -12,17 +12,7 @@ import dotty.tools.dotc.config.Settings._ import dotty.tools.dotc.config.CommonScalaSettings import dotty.tools.dotc.core.Contexts._ -/** Main class for the doctool. - * - * The `main` method is mostly responsible just for parsing arguments and - * configuring Dokka. After that, we hand control to Dokka. - * - * Other important classes: - * - * - [](package.DottyDokkaPlugin) is our class that Dokka calls back and which - * actually generates the documentation. - * - [](package.DocContext) is our config for Dokka - */ +/** Main class for the doctool when used from cli. */ object Main: def main(args: Array[String]): Unit = try diff --git a/scala3doc/src/dotty/dokka/ScalaModuleCreator.scala b/scala3doc/src/dotty/dokka/ScalaModuleCreator.scala index 7ed162f50248..22348611dc5d 100644 --- a/scala3doc/src/dotty/dokka/ScalaModuleCreator.scala +++ b/scala3doc/src/dotty/dokka/ScalaModuleCreator.scala @@ -3,7 +3,6 @@ package dotty.dokka import dotty.dokka.tasty.DokkaTastyInspector import dotty.dokka.model.api._ import collection.JavaConverters._ -import kotlin.coroutines.Continuation case class Module(rootPackage: Member, members: Map[DRI, Member]) diff --git a/scala3doc/src/dotty/dokka/compat.scala b/scala3doc/src/dotty/dokka/compat.scala index 372cb77e47a1..9a15f938926a 100644 --- a/scala3doc/src/dotty/dokka/compat.scala +++ b/scala3doc/src/dotty/dokka/compat.scala @@ -3,7 +3,6 @@ package dotty.dokka import java.util.stream.Stream // comment out - wrong error! import java.util.stream.Collectors import java.util.Collections -import kotlin.jvm.JvmClassMappingKt.getKotlinClass import java.nio.file.Path import com.vladsch.flexmark.util.ast.{Node => MdNode} import dotty.dokka.tasty.comments.wiki.WikiDocElement diff --git a/scala3doc/src/dotty/dokka/site/SidebarParser.scala b/scala3doc/src/dotty/dokka/site/SidebarParser.scala index f7457468a771..ed5e4b081935 100644 --- a/scala3doc/src/dotty/dokka/site/SidebarParser.scala +++ b/scala3doc/src/dotty/dokka/site/SidebarParser.scala @@ -20,7 +20,8 @@ object Sidebar: def setUrl(u: String) = this.url = u def setSubsection(l: JList[RawInput]) = this.subsection = l - private object RawTypeRef extends TypeReference[JMap[String, JList[RawInput]]] + type RawInnerTpe = JMap[String, JList[RawInput]] + private object RawTypeRef extends TypeReference[RawInnerTpe] private def toSidebar(r: RawInput): Sidebar = r match case RawInput(title, url, list) if title.nonEmpty && url.nonEmpty && list.isEmpty() => @@ -30,6 +31,6 @@ object Sidebar: def load(content: String): Seq[Sidebar] = val mapper = ObjectMapper(YAMLFactory()) - val raw = mapper.readValue(content, RawTypeRef) + val raw: RawInnerTpe = mapper.readValue(content, RawTypeRef) raw.get("sidebar").asScala.toList.map(toSidebar) diff --git a/scala3doc/src/dotty/dokka/tasty/ScalaDocSupport.scala b/scala3doc/src/dotty/dokka/tasty/ScalaDocSupport.scala index e71430bf2b0d..8888552cd28a 100644 --- a/scala3doc/src/dotty/dokka/tasty/ScalaDocSupport.scala +++ b/scala3doc/src/dotty/dokka/tasty/ScalaDocSupport.scala @@ -3,7 +3,6 @@ package dotty.dokka.tasty import scala.jdk.CollectionConverters._ import dotty.dokka.Scala3doc.CommentSyntax -import comments.{kt, dkk} import dotty.dokka.tasty.comments.Comment trait ScaladocSupport { self: TastyParser => diff --git a/scala3doc/src/dotty/dokka/tasty/comments/package.scala b/scala3doc/src/dotty/dokka/tasty/comments/package.scala index 35efdb587d24..24e5b1dd0fc3 100644 --- a/scala3doc/src/dotty/dokka/tasty/comments/package.scala +++ b/scala3doc/src/dotty/dokka/tasty/comments/package.scala @@ -3,53 +3,8 @@ package tasty.comments import scala.jdk.CollectionConverters._ -import org.jetbrains.dokka.model.{doc => dkkd} import com.vladsch.flexmark.util.{ast => mdu} -object kt: - import kotlin.collections.builders.{ListBuilder => KtListBuilder, MapBuilder => KtMapBuilder} - - def emptyList[T] = new KtListBuilder[T]().build() - def emptyMap[A, B] = new KtMapBuilder[A, B]().build() - -object dkk: - def p(children: dkkd.DocTag*) = - dkkd.P(children.asJava, Map.empty.asJava) - def p(params: (String, String)*)(children: dkkd.DocTag*) = - dkkd.P(children.asJava, params.toMap.asJava) - - def text(str: String) = dkkd.Text(str, JList(), Map.empty.asJava) - - def a(children: dkkd.DocTag*) = - dkkd.A(children.asJava, Map.empty.asJava) - def a(params: (String, String)*)(children: dkkd.DocTag*) = - dkkd.A(children.asJava, params.toMap.asJava) - - def pre(params: (String, String)*)(children: dkkd.DocTag*) = - dkkd.Pre(children.asJava, params.toMap.asJava) - - def codeInline(children: dkkd.DocTag*) = - dkkd.CodeInline(children.asJava, Map.empty.asJava) - def codeInline(params: (String, String)*)(children: dkkd.DocTag*) = - dkkd.CodeInline(children.asJava, params.toMap.asJava) - def codeBlock(children: dkkd.DocTag*) = - dkkd.CodeBlock(children.asJava, Map.empty.asJava) - def codeBlock(params: (String, String)*)(children: dkkd.DocTag*) = - dkkd.CodeBlock(children.asJava, params.toMap.asJava) - - def ul(children: dkkd.DocTag*) = - dkkd.Ul(children.asJava, Map.empty.asJava) - def ul(params: (String, String)*)(children: dkkd.DocTag*) = - dkkd.Ul(children.asJava, params.toMap.asJava) - def ol(children: dkkd.DocTag*) = - dkkd.Ol(children.asJava, Map.empty.asJava) - def ol(params: (String, String)*)(children: dkkd.DocTag*) = - dkkd.Ol(children.asJava, params.toMap.asJava) - def li(children: dkkd.DocTag*) = - dkkd.Li(children.asJava, Map.empty.asJava) - def li(params: (String, String)*)(children: dkkd.DocTag*) = - dkkd.Li(children.asJava, params.toMap.asJava) - object dbg: case class See(n: mdu.Node, c: Seq[See]) { def show(sb: StringBuilder, indent: Int): Unit = { diff --git a/scala3doc/src/dotty/renderers/HtmlRenderer.scala b/scala3doc/src/dotty/renderers/HtmlRenderer.scala index c5a63db61e04..544511230ce7 100644 --- a/scala3doc/src/dotty/renderers/HtmlRenderer.scala +++ b/scala3doc/src/dotty/renderers/HtmlRenderer.scala @@ -66,7 +66,8 @@ class HtmlRenderer(rootPackage: Member, val members: Map[DRI, Member])(using ctx case m: Member => val signatureRenderer = new SignatureRenderer: def currentDri: DRI = page.link.dri - def link(dri: DRI): Option[String] = Some(pathToPage(dri, page.link.dri)) + def link(dri: DRI): Option[String] = + Some(pathToPage(currentDri, dri)).filter(_ != UnresolvedLocationLink) MemberRenderer(signatureRenderer).fullMember(m) case t: ResolvedTemplate => siteContent(page.link.dri, t) diff --git a/scala3doc/src/dotty/renderers/Locations.scala b/scala3doc/src/dotty/renderers/Locations.scala index 93df58a630f9..20f0cffa1709 100644 --- a/scala3doc/src/dotty/renderers/Locations.scala +++ b/scala3doc/src/dotty/renderers/Locations.scala @@ -15,20 +15,21 @@ import java.nio.file.Files import java.io.File import scala.util.matching._ +val UnresolvedLocationLink = "#" + trait Locations(using ctx: DocContext): def members: Map[DRI, Member] // TODO verify if location exisits - def rawLocation(dri: DRI): Seq[String] = dri match - case `docsDRI` => List("docs", "index") - case `docsRootDRI` => List("index") - case `apiPageDRI` => List("api", "index") - case dri if dri.isStaticFile => - Paths.get(dri.location).iterator.asScala.map(_.toString).toList - case dri => - val loc = dri.location - if loc == null then List("index") // Dokka leftovers - else + def rawLocation(dri: DRI): Seq[String] = + dri match + case `docsDRI` => List("docs", "index") + case `docsRootDRI` => List("index") + case `apiPageDRI` => List("api", "index") + case dri if dri.isStaticFile => + Paths.get(dri.location).iterator.asScala.map(_.toString).toList + case dri => + val loc = dri.location val fqn = loc.split(Array('.')).toList match case List("") => List("index") case other => other @@ -38,12 +39,12 @@ trait Locations(using ctx: DocContext): private def unknownPage(dri: DRI): String = // TODO we should switch that to warning probably or has dedicated setting report.inform(s"Unrecognized page for ${dri.location} ($dri)") - "#" + UnresolvedLocationLink def pathToPage(from: DRI, to: DRI): String = if to.isStaticFile || members.contains(to) then val anchor = if to.anchor.isEmpty then "" else "#" + to.anchor - pathTo(rawLocation(from), rawLocation(to)) +".html" + anchor + pathToRaw(rawLocation(from), rawLocation(to)) +".html" + anchor else to.origin match case "" => @@ -55,11 +56,11 @@ trait Locations(using ctx: DocContext): - def pathTo(to: Seq[String], fullFrom: Seq[String]): String = - val from = fullFrom.dropRight(1) - val commonPaths = to.zip(from).takeWhile{ case (a, b) => a == b }.size + def pathToRaw(from: Seq[String], to: Seq[String]): String = + val fromDir = from.dropRight(1) + val commonPaths = to.zip(fromDir).takeWhile{ case (a, b) => a == b }.size - val contextPath = from.drop(commonPaths).map(_ => "..") + val contextPath = fromDir.drop(commonPaths).map(_ => "..") val nodePath = to.drop(commonPaths) match case Nil if contextPath.isEmpty && to.nonEmpty=> Seq("..", to.last) case Nil => to.lastOption.fold(Seq("index"))(".." :: _ :: Nil) @@ -68,7 +69,7 @@ trait Locations(using ctx: DocContext): (contextPath ++ nodePath).mkString("/") def resolveRoot(from: Seq[String], to: String): String = - pathTo(to.split("/").toList, from) + pathToRaw(from, to.split("/").toList) def resolveRoot(dri: DRI, path: String): String = resolveRoot(rawLocation(dri), path) def absolutePath(dri: DRI): String = rawLocation(dri).mkString("", "/", ".html") diff --git a/scala3doc/src/dotty/renderers/SignatureRenderer.scala b/scala3doc/src/dotty/renderers/SignatureRenderer.scala index 4b627585087e..698a617ea5a5 100644 --- a/scala3doc/src/dotty/renderers/SignatureRenderer.scala +++ b/scala3doc/src/dotty/renderers/SignatureRenderer.scala @@ -5,12 +5,6 @@ import collection.JavaConverters._ import java.net.URI import java.net.URL import java.util.{List => JList, Set => JSet} -import kotlinx.html.FlowContent -import kotlinx.html.stream.StreamKt -import kotlinx.html.Gen_consumer_tagsKt -import kotlinx.html.ApiKt -import kotlinx.html.HTMLTag -import kotlinx.html.DIV import dotty.dokka.model.api.Link import dotty.dokka.model.api.HierarchyGraph import scala.util.Try diff --git a/scala3doc/src/dotty/renderers/SiteRenderer.scala b/scala3doc/src/dotty/renderers/SiteRenderer.scala index a6e9ea1cf948..0afabe9d7094 100644 --- a/scala3doc/src/dotty/renderers/SiteRenderer.scala +++ b/scala3doc/src/dotty/renderers/SiteRenderer.scala @@ -36,7 +36,7 @@ trait SiteRenderer(using DocContext) extends Locations: val res = ctx.driForLink(content.template.templateFile, path).filter(driExisits) if res.isEmpty then report.warn(s"Unable to resolve link '$str'", content.template.file) - res.headOption.fold(str)(pathToPage(_, pageDri) + prefix) + res.headOption.fold(str)(pathToPage(pageDri, _) + prefix) def processLocalLink(str: String): String = if str.startsWith("#") || str.isEmpty then str diff --git a/scala3doc/test/dotty/dokka/ExternalLocationProviderIntegrationTest.scala b/scala3doc/test/dotty/dokka/ExternalLocationProviderIntegrationTest.scala index a784286cb2ed..7ec824b2a0b0 100644 --- a/scala3doc/test/dotty/dokka/ExternalLocationProviderIntegrationTest.scala +++ b/scala3doc/test/dotty/dokka/ExternalLocationProviderIntegrationTest.scala @@ -64,7 +64,7 @@ abstract class ExternalLocationProviderIntegrationTest( linksBuilder ++= hrefValues } - + println(output) IO.foreachFileIn(output, processFile) val links = linksBuilder.result val errors = expectedLinks.flatMap(expect => Option.when(!links.contains(expect))(expect)) diff --git a/scala3doc/test/dotty/dokka/renderers/LocationTests.scala b/scala3doc/test/dotty/dokka/renderers/LocationTests.scala index c69175dfa495..cdd3e745f409 100644 --- a/scala3doc/test/dotty/dokka/renderers/LocationTests.scala +++ b/scala3doc/test/dotty/dokka/renderers/LocationTests.scala @@ -19,9 +19,9 @@ class LocationTests: @Test def testLinks() = def path(from: String, to: String) = - locations.pathTo(to.split('/').toList, from.split('/').toList) + locations.pathToRaw(from.split('/').toList, to.split('/').toList) - assertEquals("a/b", locations.pathTo(Seq("a", "b"), Nil)) + assertEquals("a/b", locations.pathToRaw(Nil, Seq("a", "b"))) assertEquals( "../comments", diff --git a/scala3doc/test/dotty/dokka/tasty/comments/DocFlexmarkParserTests.scala b/scala3doc/test/dotty/dokka/tasty/comments/DocFlexmarkParserTests.scala index 6df1cbb9c536..3ade70609fa6 100644 --- a/scala3doc/test/dotty/dokka/tasty/comments/DocFlexmarkParserTests.scala +++ b/scala3doc/test/dotty/dokka/tasty/comments/DocFlexmarkParserTests.scala @@ -2,6 +2,7 @@ package dotty.dokka.tasty.comments import org.junit.{Test, Rule} import org.junit.Assert.{assertSame, assertTrue, assertEquals} +import dotty.dokka.tasty.comments.markdown.DocFlexmarkParser class DocFlexmarkParserTests { @Test def test(): Unit = { From 7d8a9dd4f25dc15877e4551a389a97593477ed68 Mon Sep 17 00:00:00 2001 From: Krzysztof Romanowski Date: Tue, 26 Jan 2021 15:03:43 +0100 Subject: [PATCH 06/12] Fixes and optimization - Cache dri -> path transformations - compute graphs on demand not on initialization - change writter to writer --- scala3doc/src/dotty/dokka/DRI.scala | 5 +- scala3doc/src/dotty/dokka/SourceLinks.scala | 1 - scala3doc/src/dotty/dokka/model/api/api.scala | 4 +- .../dotty/dokka/site/StaticSiteContext.scala | 2 +- .../src/dotty/dokka/tasty/TastyParser.scala | 3 +- .../src/dotty/renderers/HtmlRenderer.scala | 2 +- scala3doc/src/dotty/renderers/Locations.scala | 48 ++++++++++--------- scala3doc/src/dotty/renderers/Resources.scala | 4 +- .../src/dotty/renderers/SiteRenderer.scala | 4 +- scala3doc/src/dotty/renderers/Writter.scala | 2 +- .../test-documentations/basic/docs/Adoc.md | 2 + .../dokka/site/SiteGeneratationTest.scala | 5 +- 12 files changed, 46 insertions(+), 36 deletions(-) diff --git a/scala3doc/src/dotty/dokka/DRI.scala b/scala3doc/src/dotty/dokka/DRI.scala index bf82058c98ee..141bfd598cfc 100644 --- a/scala3doc/src/dotty/dokka/DRI.scala +++ b/scala3doc/src/dotty/dokka/DRI.scala @@ -17,5 +17,8 @@ final case class DRI( def isStaticFile = symbolUUID == staticFileSymbolUUID + def asFileLocation: String = location.replace(".","/") + object DRI: - def forPath(path: Path) = DRI(location = path.toString, symbolUUID = staticFileSymbolUUID) + def forPath(path: Path) = + DRI(location = path.toString, symbolUUID = staticFileSymbolUUID) diff --git a/scala3doc/src/dotty/dokka/SourceLinks.scala b/scala3doc/src/dotty/dokka/SourceLinks.scala index 6fda20465508..0c2112c06f2e 100644 --- a/scala3doc/src/dotty/dokka/SourceLinks.scala +++ b/scala3doc/src/dotty/dokka/SourceLinks.scala @@ -156,7 +156,6 @@ object SourceLinks: revision: Option[String], projectRoot: Path)( using Context): SourceLinks = - // TODO ... val mappings = configs.map(str => str -> SourceLink.parse(str, revision)) val errors = mappings.collect { diff --git a/scala3doc/src/dotty/dokka/model/api/api.scala b/scala3doc/src/dotty/dokka/model/api/api.scala index b5a1bc45e38f..2dd31846dbcd 100644 --- a/scala3doc/src/dotty/dokka/model/api/api.scala +++ b/scala3doc/src/dotty/dokka/model/api/api.scala @@ -129,8 +129,8 @@ extension (s: Signature) case class LinkToType(signature: Signature, dri: DRI, kind: Kind) case class HierarchyGraph(edges: Seq[(LinkToType, LinkToType)]): - def vertecies: Seq[LinkToType] = edges.flatten((a, b) => Seq(a, b)).distinct - val verteciesWithId: Map[LinkToType, Int] = vertecies.zipWithIndex.toMap + private def vertecies: Seq[LinkToType] = edges.flatten((a, b) => Seq(a, b)).distinct + def verteciesWithId: Map[LinkToType, Int] = vertecies.zipWithIndex.toMap def +(edge: (LinkToType, LinkToType)): HierarchyGraph = HierarchyGraph((edges :+ edge).distinct) def ++(edges: Seq[(LinkToType, LinkToType)]): HierarchyGraph = edges.foldLeft(this) { case (acc, edge) => acc + edge diff --git a/scala3doc/src/dotty/dokka/site/StaticSiteContext.scala b/scala3doc/src/dotty/dokka/site/StaticSiteContext.scala index 246d5c33fe31..45704ecc867c 100644 --- a/scala3doc/src/dotty/dokka/site/StaticSiteContext.scala +++ b/scala3doc/src/dotty/dokka/site/StaticSiteContext.scala @@ -143,7 +143,7 @@ class StaticSiteContext( val strippedIndexes = trySuffix("index.html") ++ trySuffix("index.md") (Seq(baseFile, mdFile) ++ strippedIndexes).filter(Files.exists(_)).map(driFor) - }.toOption + }.toOption.filter(_.nonEmpty) pathsDri.getOrElse(memberLinkResolver(link).toList) def driFor(dest: Path): DRI = diff --git a/scala3doc/src/dotty/dokka/tasty/TastyParser.scala b/scala3doc/src/dotty/dokka/tasty/TastyParser.scala index 73bd4522f0ee..8689338b7d62 100644 --- a/scala3doc/src/dotty/dokka/tasty/TastyParser.scala +++ b/scala3doc/src/dotty/dokka/tasty/TastyParser.scala @@ -83,7 +83,8 @@ case class DokkaTastyInspector()(using ctx: DocContext) extends DocTastyInspecto import symOps._ Try(QueryParser(link).readQuery()).toOption.flatMap(q => MemberLookup.lookupOpt(q, None).map{ case (sym, _) => sym.dri} - ) + ) + ctx.staticSiteContext.foreach(_.memberLinkResolver = driFor) var alreadyProcessed = false diff --git a/scala3doc/src/dotty/renderers/HtmlRenderer.scala b/scala3doc/src/dotty/renderers/HtmlRenderer.scala index 544511230ce7..ac732d791e3f 100644 --- a/scala3doc/src/dotty/renderers/HtmlRenderer.scala +++ b/scala3doc/src/dotty/renderers/HtmlRenderer.scala @@ -25,7 +25,7 @@ case class Page(link: Link, content: Member | ResolvedTemplate | String, childre case _ => true class HtmlRenderer(rootPackage: Member, val members: Map[DRI, Member])(using ctx: DocContext) - extends SiteRenderer, Resources, Locations, Writter: + extends SiteRenderer, Resources, Locations, Writer: private val args = summon[DocContext].args val staticSite = summon[DocContext].staticSiteContext diff --git a/scala3doc/src/dotty/renderers/Locations.scala b/scala3doc/src/dotty/renderers/Locations.scala index 20f0cffa1709..d1c88ad336db 100644 --- a/scala3doc/src/dotty/renderers/Locations.scala +++ b/scala3doc/src/dotty/renderers/Locations.scala @@ -20,21 +20,28 @@ val UnresolvedLocationLink = "#" trait Locations(using ctx: DocContext): def members: Map[DRI, Member] + var cache = new JHashMap[DRI, Seq[String]]() + // TODO verify if location exisits def rawLocation(dri: DRI): Seq[String] = - dri match - case `docsDRI` => List("docs", "index") - case `docsRootDRI` => List("index") - case `apiPageDRI` => List("api", "index") - case dri if dri.isStaticFile => - Paths.get(dri.location).iterator.asScala.map(_.toString).toList - case dri => - val loc = dri.location - val fqn = loc.split(Array('.')).toList match - case List("") => List("index") - case other => other - - Seq("api") ++ fqn + cache.get(dri) match + case null => + val path = dri match + case `docsDRI` => List("docs", "index") + case `docsRootDRI` => List("index") + case `apiPageDRI` => List("api", "index") + case dri if dri.isStaticFile => + Paths.get(dri.location).iterator.asScala.map(_.toString).toList + case dri => + val loc = dri.location + val fqn = loc.split(Array('.')).toList match + case List("") => List("index") + case other => other + + Seq("api") ++ fqn + cache.put(dri, path) + path + case cached => cached private def unknownPage(dri: DRI): String = // TODO we should switch that to warning probably or has dedicated setting @@ -93,17 +100,12 @@ trait Locations(using ctx: DocContext): } //TODO #263: Add anchor support - def constructPathForScaladoc(dri: DRI): String = { - val location = dri.location.replace(".","/") - val anchor = dri.anchor - docURL + location + extension - } + def constructPathForScaladoc(dri: DRI): String = + docURL + dri.asFileLocation + extension - def constructPathForScala3doc(dri: DRI): String = { - val location = dri.location.replace(".","/") - val anchor = dri.anchor - docURL + location + extension - } + // TODO Add tests for it! + def constructPathForScala3doc(dri: DRI): String = + docURL + dri.asFileLocation + extension + "#" + dri.anchor link.kind match { case DocumentationKind.Javadoc => constructPathForJavadoc(dri) diff --git a/scala3doc/src/dotty/renderers/Resources.scala b/scala3doc/src/dotty/renderers/Resources.scala index a3cba6518384..a73ce22c7c72 100644 --- a/scala3doc/src/dotty/renderers/Resources.scala +++ b/scala3doc/src/dotty/renderers/Resources.scala @@ -22,7 +22,7 @@ enum Resource(val path: String): case File(override val path: String, file: Path) extends Resource(path) case URL(url: String) extends Resource(url) -trait Resources(using ctx: DocContext) extends Locations, Writter: +trait Resources(using ctx: DocContext) extends Locations, Writer: private def dynamicJsData = // If data at any point will become more complex we should use a proper mapping val data: Map[String, Map[String, String]] = @@ -109,7 +109,7 @@ trait Resources(using ctx: DocContext) extends Locations, Writter: def processPage(page: Page): Seq[PageEntry] = val res = page.content match case m: Member => - val descr = m.dri.location.replace("/", ".") + val descr = m.dri.asFileLocation def processMember(member: Member): Seq[PageEntry] = val signatureBuilder = ScalaSignatureProvider.rawSignature(member, InlineSignatureBuilder()).asInstanceOf[InlineSignatureBuilder] val sig = Signature(member.kind.name, " ") ++ Seq(Link(member.name, member.dri)) ++ signatureBuilder.names.reverse diff --git a/scala3doc/src/dotty/renderers/SiteRenderer.scala b/scala3doc/src/dotty/renderers/SiteRenderer.scala index 0afabe9d7094..54bc83359496 100644 --- a/scala3doc/src/dotty/renderers/SiteRenderer.scala +++ b/scala3doc/src/dotty/renderers/SiteRenderer.scala @@ -29,7 +29,7 @@ trait SiteRenderer(using DocContext) extends Locations: def siteContent(pageDri: DRI, content: ResolvedTemplate): AppliedTag = import content.ctx - def tryAsDri(str: String) = // TODO Does not seem to work with links to API :( + def tryAsDri(str: String) = val (path, prefix) = str match case HashRegex(path, prefix) => (path, prefix) case _ => (str, "") @@ -51,5 +51,5 @@ trait SiteRenderer(using DocContext) extends Locations: Try(new URL(link)).getOrElse { if(link.startsWith("/")) element.attr("src", resolveLink(pageDri, link.drop(1))) } - }// forrach does not work here + }// foreach does not work here raw(document.outerHtml()) diff --git a/scala3doc/src/dotty/renderers/Writter.scala b/scala3doc/src/dotty/renderers/Writter.scala index 2a96263afa18..887f8e5fd3af 100644 --- a/scala3doc/src/dotty/renderers/Writter.scala +++ b/scala3doc/src/dotty/renderers/Writter.scala @@ -9,7 +9,7 @@ import java.io.File import HTML._ // TODO be more clever about writting - make it much faster! -trait Writter(using ctx: DocContext) extends Locations: +trait Writer(using ctx: DocContext) extends Locations: private val args = summon[DocContext].args private def dest(path: String) = diff --git a/scala3doc/test-documentations/basic/docs/Adoc.md b/scala3doc/test-documentations/basic/docs/Adoc.md index f9e75f26fb07..153a9efc966a 100644 --- a/scala3doc/test-documentations/basic/docs/Adoc.md +++ b/scala3doc/test-documentations/basic/docs/Adoc.md @@ -3,4 +3,6 @@ title: Adoc --- # Header in Adoc +[[tests.site.SomeClass]] + And a text! \ No newline at end of file diff --git a/scala3doc/test/dotty/dokka/site/SiteGeneratationTest.scala b/scala3doc/test/dotty/dokka/site/SiteGeneratationTest.scala index 3ff9b0d3977f..586f2bfe452c 100644 --- a/scala3doc/test/dotty/dokka/site/SiteGeneratationTest.scala +++ b/scala3doc/test/dotty/dokka/site/SiteGeneratationTest.scala @@ -32,7 +32,6 @@ class SiteGeneratationTest extends BaseHtmlTest: } def testDocPages()(using ProjectContext) = - checkFile("docs/Adoc.html")(title = "Adoc", header = "Header in Adoc", parents = Seq(projectName)) checkFile("docs/Adoc.html")(title = "Adoc", header = "Header in Adoc", parents = Seq(projectName)) checkFile("docs/dir/index.html")(title = "A directory", header = "A directory", parents = Seq(projectName)) checkFile("docs/dir/nested.html")( @@ -69,6 +68,10 @@ class SiteGeneratationTest extends BaseHtmlTest: testDocIndexPage() testMainIndexPage() testApiPages() + + withHtmlFile("docs/Adoc.html"){ content => + content.assertAttr("p a","href", "../api/tests/site/SomeClass.html") + } } @Test From 67249debccceacb44bb4f3637b8a940527698ba2 Mon Sep 17 00:00:00 2001 From: Krzysztof Romanowski Date: Tue, 26 Jan 2021 15:25:26 +0100 Subject: [PATCH 07/12] Fix external location tests --- .../dotty/dokka/tasty/ClassLikeSupport.scala | 2 +- scala3doc/src/dotty/renderers/Locations.scala | 3 +- ...ernalLocationProviderIntegrationTest.scala | 2 +- .../dokka/ExternalLocationProviderTest.scala | 66 ------------------- 4 files changed, 4 insertions(+), 69 deletions(-) delete mode 100644 scala3doc/test/dotty/dokka/ExternalLocationProviderTest.scala diff --git a/scala3doc/src/dotty/dokka/tasty/ClassLikeSupport.scala b/scala3doc/src/dotty/dokka/tasty/ClassLikeSupport.scala index ba32af6f2d89..056f50d182fa 100644 --- a/scala3doc/src/dotty/dokka/tasty/ClassLikeSupport.scala +++ b/scala3doc/src/dotty/dokka/tasty/ClassLikeSupport.scala @@ -244,7 +244,7 @@ trait ClassLikeSupport: def getParentsAsLinkToTypes: List[LinkToType] = c.getParentsAsTreeSymbolTuples.map { - (tree, symbol) => LinkToType(tree.dokkaType.asSignature, symbol.dri, bareClasslikeKind(symbol)) + (tree, symbol) => LinkToType(tree.asSignature, symbol.dri, bareClasslikeKind(symbol)) } def getParentsAsTreeSymbolTuples: List[(Tree, Symbol)] = diff --git a/scala3doc/src/dotty/renderers/Locations.scala b/scala3doc/src/dotty/renderers/Locations.scala index d1c88ad336db..dc0b6a194c71 100644 --- a/scala3doc/src/dotty/renderers/Locations.scala +++ b/scala3doc/src/dotty/renderers/Locations.scala @@ -105,7 +105,8 @@ trait Locations(using ctx: DocContext): // TODO Add tests for it! def constructPathForScala3doc(dri: DRI): String = - docURL + dri.asFileLocation + extension + "#" + dri.anchor + val base = docURL + dri.asFileLocation + extension + if dri.anchor.isEmpty then base else base + "#" + dri.anchor link.kind match { case DocumentationKind.Javadoc => constructPathForJavadoc(dri) diff --git a/scala3doc/test/dotty/dokka/ExternalLocationProviderIntegrationTest.scala b/scala3doc/test/dotty/dokka/ExternalLocationProviderIntegrationTest.scala index 7ec824b2a0b0..c1d465e4899d 100644 --- a/scala3doc/test/dotty/dokka/ExternalLocationProviderIntegrationTest.scala +++ b/scala3doc/test/dotty/dokka/ExternalLocationProviderIntegrationTest.scala @@ -32,7 +32,7 @@ class Scala3docExternalLocationProviderIntegrationTest extends ExternalLocationP List(".*scala.*::scala3doc::https://dotty.epfl.ch/api/"), List( "https://dotty.epfl.ch/api/scala/collection/immutable/Map.html", - "https://dotty.epfl.ch/api/scala/Predef$.html", + "https://dotty.epfl.ch/api/scala/Predef$.html#String", "https://dotty.epfl.ch/api/scala/util/matching/Regex$$Match.html" ) ) diff --git a/scala3doc/test/dotty/dokka/ExternalLocationProviderTest.scala b/scala3doc/test/dotty/dokka/ExternalLocationProviderTest.scala deleted file mode 100644 index 8b97b39b8e8d..000000000000 --- a/scala3doc/test/dotty/dokka/ExternalLocationProviderTest.scala +++ /dev/null @@ -1,66 +0,0 @@ -package dotty.dokka - -import dotty.dokka.tasty._ - -import scala.collection.JavaConverters._ -import java.nio.file.Paths -import java.nio.file.Path -import scala.util.matching._ -import dotty.dokka.model.api._ -import java.net.URL -import org.junit.{Test} -import org.junit.Assert._ - -import scala.quoted._ - -class ExternalLocationProviderTest - // TODO rewrite for locations! - -// def createExternalLocationProvider(docURL: String, ext: String, kind: DocumentationKind) = { -// val emptyExtDoc = ED( -// URL(docURL), -// PackageList( -// RecognizedLinkFormat.Javadoc1, JSet(), JMap(), URL(docURL) -// ) -// ) -// ScalaExternalLocationProvider(emptyExtDoc, ext, kind) -// } - -// def testResolvedLinks(provider: ScalaExternalLocationProvider, testcases: List[(DRI, String)]) = testcases.foreach { -// case (dri, expect) => assertEquals(provider.resolve(dri), expect) -// } - -// @Test -// def javadocExternalLocationProviderTest(): Unit = { -// val provider = createExternalLocationProvider("https://docs.oracle.com/javase/8/docs/api/", ".html", DocumentationKind.Javadoc) -// val testcases = List( -// (DRI("java.util.Map$$Entry"), "https://docs.oracle.com/javase/8/docs/api/java/util/Map.Entry.html"), -// (DRI("javax.swing.plaf.nimbus.AbstractRegionPainter$$PaintContext$$CacheMode"), "https://docs.oracle.com/javase/8/docs/api/javax/swing/plaf/nimbus/AbstractRegionPainter.PaintContext.CacheMode.html"), -// (DRI("java.lang.CharSequence"), "https://docs.oracle.com/javase/8/docs/api/java/lang/CharSequence.html") -// ) -// testResolvedLinks(provider, testcases) -// } - -// @Test -// def scaladocExternalLocationProviderTest(): Unit = { -// val provider = createExternalLocationProvider("https://www.scala-lang.org/api/current/", ".html", DocumentationKind.Scaladoc) -// val testcases = List( -// (DRI("scala.Predef$"),"https://www.scala-lang.org/api/current/scala/Predef$.html"), -// (DRI("scala.util.package$$chaining$"), "https://www.scala-lang.org/api/current/scala/util/package$$chaining$.html"), -// (DRI("scala.util.Using$"), "https://www.scala-lang.org/api/current/scala/util/Using$.html"), -// (DRI("scala.util.matching.Regex$$Match"), "https://www.scala-lang.org/api/current/scala/util/matching/Regex$$Match.html") -// ) -// testResolvedLinks(provider, testcases) -// } - -// @Test -// def scala3docExternalLocationProviderTest(): Unit = { -// val provider = createExternalLocationProvider("https://dotty.epfl.ch/api/", ".html", DocumentationKind.Scala3doc) -// val testcases = List( -// (DRI("scala.Predef$"),"https://dotty.epfl.ch/api/scala/Predef$.html"), -// (DRI("scala.util.package$$chaining$"), "https://dotty.epfl.ch/api/scala/util/package$$chaining$.html"), -// (DRI("scala.util.Using$"), "https://dotty.epfl.ch/api/scala/util/Using$.html"), -// (DRI("scala.util.matching.Regex$$Match"), "https://dotty.epfl.ch/api/scala/util/matching/Regex$$Match.html") -// ) -// testResolvedLinks(provider, testcases) -// } \ No newline at end of file From fac392bc5c66535820bb1f7ae01d4da82d32c520 Mon Sep 17 00:00:00 2001 From: Krzysztof Romanowski Date: Tue, 26 Jan 2021 16:41:57 +0100 Subject: [PATCH 08/12] Fix navigation links and add tests --- .../src/dotty/renderers/HtmlRenderer.scala | 2 +- .../dotty/dokka/site/NavitationTest.scala | 50 +++++++++++++++++++ .../dokka/site/SiteGeneratationTest.scala | 2 +- 3 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 scala3doc/test/dotty/dokka/site/NavitationTest.scala diff --git a/scala3doc/src/dotty/renderers/HtmlRenderer.scala b/scala3doc/src/dotty/renderers/HtmlRenderer.scala index ac732d791e3f..aae293baf4eb 100644 --- a/scala3doc/src/dotty/renderers/HtmlRenderer.scala +++ b/scala3doc/src/dotty/renderers/HtmlRenderer.scala @@ -139,7 +139,7 @@ class HtmlRenderer(rootPackage: Member, val members: Map[DRI, Member])(using ctx val isSelected = nav.link.dri == pageLink.dri def linkHtml(exapnded: Boolean = false) = val attrs = if (isSelected) Seq(cls := "selected expanded") else Nil - a(href := pathToPage(nav.link.dri, pageLink.dri), attrs)(nav.link.name) + a(href := pathToPage(pageLink.dri, nav.link.dri), attrs)(nav.link.name) nav.children match case Nil => isSelected -> div(linkHtml()) diff --git a/scala3doc/test/dotty/dokka/site/NavitationTest.scala b/scala3doc/test/dotty/dokka/site/NavitationTest.scala new file mode 100644 index 000000000000..9e4901229d69 --- /dev/null +++ b/scala3doc/test/dotty/dokka/site/NavitationTest.scala @@ -0,0 +1,50 @@ +package dotty.dokka +package site + +import org.junit.Test + +class NavitationTest extends BaseHtmlTest: + + case class NavMenuTestEntry( name: String, link: String, nested: Seq[NavMenuTestEntry]) + + def testNavMenu(page: String, topLevel: NavMenuTestEntry)(using ProjectContext): Unit = + withHtmlFile(page){ content => + def flatten(l: NavMenuTestEntry): Seq[NavMenuTestEntry] = l +: l.nested.flatMap(flatten) + + def test(query: String, el: Seq[NavMenuTestEntry]) = + content.assertTextsIn(query, el.map(_.name):_*) + content.assertAttr(query,"href", el.map(_.link):_*) + + test("#sideMenu2 a", flatten(topLevel)) + test("#sideMenu2>div>div>a", topLevel.nested) + test("#sideMenu2>div>div>div>a", topLevel.nested.flatMap(_.nested)) + test("#sideMenu2>div>div>div>div>a", topLevel.nested.flatMap(_.nested.flatMap(_.nested))) + } + + + @Test + def testBasicNavigation() = withGeneratedSite(testDocPath.resolve("basic")){ + val topLevelNav = NavMenuTestEntry(projectName, "index.html", Seq( + NavMenuTestEntry("Adoc", "Adoc.html", Seq()), + NavMenuTestEntry("A directory", "dir/index.html", Seq( + NavMenuTestEntry("Nested in a directory", "dir/nested.html", Nil) + )), + NavMenuTestEntry("Basic test", "../index.html", Seq()), + NavMenuTestEntry("API", "../api/index.html", Seq( + NavMenuTestEntry("tests.site", "../api/tests/site.html", Seq( + NavMenuTestEntry("BrokenLink", "../api/tests/site/BrokenLink.html", Nil), + NavMenuTestEntry("BrokenLinkWiki", "../api/tests/site/BrokenLinkWiki.html", Nil), + NavMenuTestEntry("OtherPackageLink", "../api/tests/site/OtherPackageLink.html", Nil), + NavMenuTestEntry("OtherPackageLinkWiki", "../api/tests/site/OtherPackageLinkWiki.html", Nil), + NavMenuTestEntry("SamePackageLink", "../api/tests/site/SamePackageLink.html", Nil), + NavMenuTestEntry("SamePackageLinkWiki", "../api/tests/site/SamePackageLinkWiki.html", Nil), + NavMenuTestEntry("SomeClass", "../api/tests/site/SomeClass.html", Nil) + )), + NavMenuTestEntry("tests.site.some.other", "../api/tests/site/some/other.html", Seq( + NavMenuTestEntry("SomeOtherPackage", "../api/tests/site/some/other/SomeOtherPackage.html", Nil), + )) + )), + )) + + testNavMenu("docs/Adoc.html", topLevelNav) + } \ No newline at end of file diff --git a/scala3doc/test/dotty/dokka/site/SiteGeneratationTest.scala b/scala3doc/test/dotty/dokka/site/SiteGeneratationTest.scala index 586f2bfe452c..c78b003c8f9e 100644 --- a/scala3doc/test/dotty/dokka/site/SiteGeneratationTest.scala +++ b/scala3doc/test/dotty/dokka/site/SiteGeneratationTest.scala @@ -71,7 +71,7 @@ class SiteGeneratationTest extends BaseHtmlTest: withHtmlFile("docs/Adoc.html"){ content => content.assertAttr("p a","href", "../api/tests/site/SomeClass.html") - } + } } @Test From cf732e626cb1dc168acbab9413a7e3eadb1f9245 Mon Sep 17 00:00:00 2001 From: Krzysztof Romanowski Date: Tue, 26 Jan 2021 16:53:12 +0100 Subject: [PATCH 09/12] Do not display empty packages --- scala3doc/src/dotty/dokka/ScalaModuleCreator.scala | 3 ++- scala3doc/src/dotty/dokka/site/StaticSiteContext.scala | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/scala3doc/src/dotty/dokka/ScalaModuleCreator.scala b/scala3doc/src/dotty/dokka/ScalaModuleCreator.scala index 22348611dc5d..11696df9efd2 100644 --- a/scala3doc/src/dotty/dokka/ScalaModuleCreator.scala +++ b/scala3doc/src/dotty/dokka/ScalaModuleCreator.scala @@ -10,7 +10,8 @@ object ScalaModuleProvider: def mkModule()(using ctx: DocContext): Module = val (result, rootDoc) = DokkaTastyInspector().result() val (rootPck, rest) = result.partition(_.name == "API") - val packageMembers = (rest ++ rootPck.flatMap(_.members)).sortBy(_.name) + val packageMembers = (rest ++ rootPck.flatMap(_.members)) + .filter(p => p.members.nonEmpty || p.docs.nonEmpty).sortBy(_.name) def flattenMember(m: Member): Seq[(DRI, Member)] = (m.dri -> m) +: m.members.flatMap(flattenMember) diff --git a/scala3doc/src/dotty/dokka/site/StaticSiteContext.scala b/scala3doc/src/dotty/dokka/site/StaticSiteContext.scala index 45704ecc867c..8af92b0fe8c0 100644 --- a/scala3doc/src/dotty/dokka/site/StaticSiteContext.scala +++ b/scala3doc/src/dotty/dokka/site/StaticSiteContext.scala @@ -128,7 +128,7 @@ class StaticSiteContext( private def loadAllFiles() = def dir(name: String)= List(new File(root, name)).filter(_.isDirectory) dir("docs").flatMap(_.listFiles()).flatMap(loadTemplate(_, isBlog = false)) - ++ dir("blog").flatMap(loadTemplate(_, isBlog = true)) + ++ dir("blog").flatMap(loadTemplate(_, isBlog = true)).sortBy(_.templateFile.title) def driForLink(template: TemplateFile, link: String): Seq[DRI] = val pathsDri: Option[Seq[DRI]] = Try { From fd07df4dfde70cf559b887d7dfb13f72f90316fe Mon Sep 17 00:00:00 2001 From: Krzysztof Romanowski Date: Tue, 26 Jan 2021 18:15:09 +0100 Subject: [PATCH 10/12] Fix breadcrumbs and add tests --- scala3doc-testcases/src/tests/docString.scala | 2 -- scala3doc/src/dotty/dokka/site/StaticSiteContext.scala | 7 ++++--- scala3doc/src/dotty/renderers/HtmlRenderer.scala | 2 +- .../site/{NavitationTest.scala => NavigationTest.scala} | 4 ++-- scala3doc/test/dotty/dokka/site/SiteGeneratationTest.scala | 6 ++++++ 5 files changed, 13 insertions(+), 8 deletions(-) rename scala3doc/test/dotty/dokka/site/{NavitationTest.scala => NavigationTest.scala} (98%) diff --git a/scala3doc-testcases/src/tests/docString.scala b/scala3doc-testcases/src/tests/docString.scala index 3ceb0c8ee82e..6451b7158916 100644 --- a/scala3doc-testcases/src/tests/docString.scala +++ b/scala3doc-testcases/src/tests/docString.scala @@ -31,7 +31,6 @@ package wikilinks: /** * [[tests.commonlinks.SomeOtherPackage!method]] - * [[tests.commonlinks.SomeOtherPackage#]] * [[tests.commonlinks.SomeOtherEnum!A]] * @syntax wiki */ @@ -66,7 +65,6 @@ package mdlinks: /** * [[tests.commonlinks.SomeOtherPackage!method]] - * [[tests.commonlinks.SomeOtherPackage#]] * [[tests.commonlinks.SomeOtherEnum!A]] * @syntax wiki */ diff --git a/scala3doc/src/dotty/dokka/site/StaticSiteContext.scala b/scala3doc/src/dotty/dokka/site/StaticSiteContext.scala index 8af92b0fe8c0..7f5776090558 100644 --- a/scala3doc/src/dotty/dokka/site/StaticSiteContext.scala +++ b/scala3doc/src/dotty/dokka/site/StaticSiteContext.scala @@ -38,7 +38,8 @@ class StaticSiteContext( if (!Files.exists(sidebarFile)) None else Some(Sidebar.load(Files.readAllLines(sidebarFile).asScala.mkString("\n"))) - lazy val templates: Seq[LoadedTemplate] = sideBarConfig.fold(loadAllFiles())(_.map(loadSidebarContent)) + lazy val templates: Seq[LoadedTemplate] = + sideBarConfig.fold(loadAllFiles().sortBy(_.templateFile.title))(_.map(loadSidebarContent)) lazy val orphanedTemplates: Seq[LoadedTemplate] = { def doFlatten(t: LoadedTemplate): Seq[Path] = @@ -88,7 +89,7 @@ class StaticSiteContext( val templateFile = if (from.isDirectory) loadIndexPage() else loadTemplateFile(from) - val processedChildren = if !isBlog then children else + val processedChildren = if !isBlog then children.sortBy(_.templateFile.title) else def dateFrom(p: LoadedTemplate): String = val pageSettings = p.templateFile.settings.get("page").collect{ case m: Map[String @unchecked, _] => m } pageSettings.flatMap(_.get("date").collect{ case s: String => s}).getOrElse("1900-01-01") // blogs without date are last @@ -128,7 +129,7 @@ class StaticSiteContext( private def loadAllFiles() = def dir(name: String)= List(new File(root, name)).filter(_.isDirectory) dir("docs").flatMap(_.listFiles()).flatMap(loadTemplate(_, isBlog = false)) - ++ dir("blog").flatMap(loadTemplate(_, isBlog = true)).sortBy(_.templateFile.title) + ++ dir("blog").flatMap(loadTemplate(_, isBlog = true)) def driForLink(template: TemplateFile, link: String): Seq[DRI] = val pathsDri: Option[Seq[DRI]] = Try { diff --git a/scala3doc/src/dotty/renderers/HtmlRenderer.scala b/scala3doc/src/dotty/renderers/HtmlRenderer.scala index aae293baf4eb..22c49a51227c 100644 --- a/scala3doc/src/dotty/renderers/HtmlRenderer.scala +++ b/scala3doc/src/dotty/renderers/HtmlRenderer.scala @@ -163,7 +163,7 @@ class HtmlRenderer(rootPackage: Member, val members: Map[DRI, Member])(using ctx val parentsHtml = val innerTags = parents.flatMap[TagArg](b => Seq( - a(href := pathToPage(b.dri, link.dri))(b.name), + a(href := pathToPage(link.dri, b.dri))(b.name), "/" )).dropRight(1) div(cls := "breadcrumbs")(innerTags:_*) diff --git a/scala3doc/test/dotty/dokka/site/NavitationTest.scala b/scala3doc/test/dotty/dokka/site/NavigationTest.scala similarity index 98% rename from scala3doc/test/dotty/dokka/site/NavitationTest.scala rename to scala3doc/test/dotty/dokka/site/NavigationTest.scala index 9e4901229d69..565f716625b7 100644 --- a/scala3doc/test/dotty/dokka/site/NavitationTest.scala +++ b/scala3doc/test/dotty/dokka/site/NavigationTest.scala @@ -3,7 +3,7 @@ package site import org.junit.Test -class NavitationTest extends BaseHtmlTest: +class NavigationTest extends BaseHtmlTest: case class NavMenuTestEntry( name: String, link: String, nested: Seq[NavMenuTestEntry]) @@ -25,10 +25,10 @@ class NavitationTest extends BaseHtmlTest: @Test def testBasicNavigation() = withGeneratedSite(testDocPath.resolve("basic")){ val topLevelNav = NavMenuTestEntry(projectName, "index.html", Seq( - NavMenuTestEntry("Adoc", "Adoc.html", Seq()), NavMenuTestEntry("A directory", "dir/index.html", Seq( NavMenuTestEntry("Nested in a directory", "dir/nested.html", Nil) )), + NavMenuTestEntry("Adoc", "Adoc.html", Seq()), NavMenuTestEntry("Basic test", "../index.html", Seq()), NavMenuTestEntry("API", "../api/index.html", Seq( NavMenuTestEntry("tests.site", "../api/tests/site.html", Seq( diff --git a/scala3doc/test/dotty/dokka/site/SiteGeneratationTest.scala b/scala3doc/test/dotty/dokka/site/SiteGeneratationTest.scala index c78b003c8f9e..d406d8f214ed 100644 --- a/scala3doc/test/dotty/dokka/site/SiteGeneratationTest.scala +++ b/scala3doc/test/dotty/dokka/site/SiteGeneratationTest.scala @@ -72,6 +72,12 @@ class SiteGeneratationTest extends BaseHtmlTest: withHtmlFile("docs/Adoc.html"){ content => content.assertAttr("p a","href", "../api/tests/site/SomeClass.html") } + + withHtmlFile("api/tests/site/SomeClass.html"){ content => + content.assertAttr(".breadcrumbs a","href", + "../../../docs/index.html", "../../index.html", "../site.html", "SomeClass.html" + ) + } } @Test From 99baf23bdcede03c511ca87f82b4ff7d504bcb04 Mon Sep 17 00:00:00 2001 From: Krzysztof Romanowski Date: Thu, 28 Jan 2021 10:41:54 +0100 Subject: [PATCH 11/12] Review fixes --- scala3doc/src/dotty/renderers/{Writter.scala => Writer.scala} | 0 scala3doc/src/dotty/renderers/html.scala | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename scala3doc/src/dotty/renderers/{Writter.scala => Writer.scala} (100%) diff --git a/scala3doc/src/dotty/renderers/Writter.scala b/scala3doc/src/dotty/renderers/Writer.scala similarity index 100% rename from scala3doc/src/dotty/renderers/Writter.scala rename to scala3doc/src/dotty/renderers/Writer.scala diff --git a/scala3doc/src/dotty/renderers/html.scala b/scala3doc/src/dotty/renderers/html.scala index 05a95f232e20..318323e130bb 100644 --- a/scala3doc/src/dotty/renderers/html.scala +++ b/scala3doc/src/dotty/renderers/html.scala @@ -60,8 +60,8 @@ object HTML: val h2 = Tag("h2") val h3 = Tag("h3") val h4 = Tag("h4") - val h5 = Tag("h4") - val h6 = Tag("h4") + val h5 = Tag("h5") + val h6 = Tag("h6") val dl = Tag("dl") val dd = Tag("dd") val dt = Tag("dt") From 81f6a24326a27574c4fc6fd5a91346dd175e30c6 Mon Sep 17 00:00:00 2001 From: Krzysztof Romanowski Date: Fri, 29 Jan 2021 15:29:46 +0100 Subject: [PATCH 12/12] Use in-house json printer rather then jackson --- project/Build.scala | 1 - scala3doc/src/dotty/dokka/JSON.scala | 55 +++++++++++++++++++ scala3doc/src/dotty/renderers/Resources.scala | 39 ++++++------- scala3doc/test/dotty/dokka/JSONTest.scala | 10 ++++ 4 files changed, 81 insertions(+), 24 deletions(-) create mode 100644 scala3doc/src/dotty/dokka/JSON.scala create mode 100644 scala3doc/test/dotty/dokka/JSONTest.scala diff --git a/project/Build.scala b/project/Build.scala index a668bfbd805b..507884573765 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1563,7 +1563,6 @@ object Build { "com.vladsch.flexmark" % "flexmark-ext-yaml-front-matter" % flexmarkVersion, "nl.big-o" % "liqp" % "0.6.7", "org.jsoup" % "jsoup" % "1.13.1", // Needed to process .html files for static site - "args4j" % "args4j" % "2.33", Dependencies.`jackson-dataformat-yaml`, "com.novocode" % "junit-interface" % "0.11" % "test", diff --git a/scala3doc/src/dotty/dokka/JSON.scala b/scala3doc/src/dotty/dokka/JSON.scala new file mode 100644 index 000000000000..721489dad4ed --- /dev/null +++ b/scala3doc/src/dotty/dokka/JSON.scala @@ -0,0 +1,55 @@ +package dotty.dokka + +import scala.annotation.tailrec + +opaque type JSON = String + +def jsonList(elems: Seq[JSON]): JSON = elems.mkString("[", ",\n", "]") + +def jsonObject(fields: (String, JSON)*): JSON = + fields.map{ case (k, v) => quoteStr(k)+":"+v}.mkString("{", ",", "}") + + +def quoteStr(str: String) = s""""$str"""" + +// based on Spray Json +def jsonString(s: String): JSON = + def requiresEncoding(c: Char): Boolean = + // from RFC 4627 + // unescaped = %x20-21 / %x23-5B / %x5D-10FFFF + c match + case '"' => true + case '\\' => true + case c => c < 0x20 + + val sb = new StringBuilder + @tailrec def firstToBeEncoded(ix: Int = 0): Int = + if (ix == s.length) -1 else if (requiresEncoding(s.charAt(ix))) ix else firstToBeEncoded(ix + 1) + + sb.append('"') + firstToBeEncoded() match + case -1 ⇒ sb.append(s) + case first => + // sb.append(s, 0, first) for "abc", 0, 2 produce "(abc,0,2)" rather then "ab" as in Java + sb.append(s.substring(0, first)) + @tailrec def append(ix: Int): Unit = + if (ix < s.length) { + s.charAt(ix) match + case c if !requiresEncoding(c) => sb.append(c) + case '"' => sb.append("\\\"") + case '\\' => sb.append("\\\\") + case '\b' => sb.append("\\b") + case '\f' => sb.append("\\f") + case '\n' => sb.append("\\n") + case '\r' => sb.append("\\r") + case '\t' => sb.append("\\t") + case x if x <= 0xF => sb.append("\\u000").append(Integer.toHexString(x)) + case x if x <= 0xFF => sb.append("\\u00").append(Integer.toHexString(x)) + case x if x <= 0xFFF => sb.append("\\u0").append(Integer.toHexString(x)) + case x => sb.append("\\u").append(Integer.toHexString(x)) + + append(ix + 1) + } + append(first) + + sb.append('"').toString diff --git a/scala3doc/src/dotty/renderers/Resources.scala b/scala3doc/src/dotty/renderers/Resources.scala index a73ce22c7c72..379f4612c125 100644 --- a/scala3doc/src/dotty/renderers/Resources.scala +++ b/scala3doc/src/dotty/renderers/Resources.scala @@ -14,7 +14,6 @@ import java.nio.file.Path import java.nio.file.Files import java.io.File import dotty.dokka.translators.FilterAttributes -import com.fasterxml.jackson.databind.ObjectMapper enum Resource(val path: String): case Text(override val path: String, content: String) extends Resource(path) @@ -24,10 +23,9 @@ enum Resource(val path: String): trait Resources(using ctx: DocContext) extends Locations, Writer: private def dynamicJsData = - // If data at any point will become more complex we should use a proper mapping - val data: Map[String, Map[String, String]] = - Map("filterDefaults" -> FilterAttributes.defaultValues) - val str = new ObjectMapper().writeValueAsString(data.transform((_, v) => v.asJava).asJava) + val str = jsonObject("filterDefaults" -> jsonObject( + FilterAttributes.defaultValues.toSeq.map { case (n, v) => n -> jsonString(v) }:_* + )) Resource.Text("scripts/data.js", s"var scala3DocData = $str") private def scala3docVersionFile = Resource.Text("scala3doc.version", BuildInfo.version) @@ -87,17 +85,6 @@ trait Resources(using ctx: DocContext) extends Locations, Writer: val searchDataPath = "scripts/searchData.js" val memberResourcesPaths = Seq(searchDataPath) ++ memberResources.map(_.path) - case class PageEntry( - dri: DRI, - name: String, - text: String, - descr: String, - ): - // for jackson - def getL: String = absolutePath(dri) - def getN: String = name - def getT: String = text - def getD: String = descr def searchData(pages: Seq[Page]) = def flattenToText(signature: Signature): String = @@ -106,14 +93,21 @@ trait Resources(using ctx: DocContext) extends Locations, Writer: case s: String => s }.mkString - def processPage(page: Page): Seq[PageEntry] = + def mkEntry(dri: DRI, name: String, text: String, descr: String) = jsonObject( + "l" -> jsonString(absolutePath(dri)), + "n" -> jsonString(name), + "t" -> jsonString(text), + "d" -> jsonString(descr) + ) + + def processPage(page: Page): Seq[JSON] = val res = page.content match case m: Member => val descr = m.dri.asFileLocation - def processMember(member: Member): Seq[PageEntry] = + def processMember(member: Member): Seq[JSON] = val signatureBuilder = ScalaSignatureProvider.rawSignature(member, InlineSignatureBuilder()).asInstanceOf[InlineSignatureBuilder] val sig = Signature(member.kind.name, " ") ++ Seq(Link(member.name, member.dri)) ++ signatureBuilder.names.reverse - val entry = PageEntry(member.dri, member.name, flattenToText(sig), descr) + val entry = mkEntry(member.dri, member.name, flattenToText(sig), descr) val children = member .membersBy(m => m.kind != dotty.dokka.model.api.Kind.Package && !m.kind.isInstanceOf[Classlike]) .filter(m => m.origin == Origin.RegularlyDefined && m.inheritedFrom.isEmpty) @@ -121,13 +115,12 @@ trait Resources(using ctx: DocContext) extends Locations, Writer: processMember(m) case _ => - Seq(PageEntry(page.link.dri, page.link.name, page.link.name, "")) + Seq(mkEntry(page.link.dri, page.link.name, page.link.name, "")) res ++ page.children.flatMap(processPage) - val entries = pages.flatMap(processPage).toArray - val entriesText = new ObjectMapper().writeValueAsString(entries) - Resource.Text(searchDataPath, s"pages = $entriesText;") + val entries = pages.flatMap(processPage) + Resource.Text(searchDataPath, s"pages = ${jsonList(entries)};") def allResources(pages: Seq[Page]): Seq[Resource] = memberResources ++ Seq( diff --git a/scala3doc/test/dotty/dokka/JSONTest.scala b/scala3doc/test/dotty/dokka/JSONTest.scala new file mode 100644 index 000000000000..cfb424201e22 --- /dev/null +++ b/scala3doc/test/dotty/dokka/JSONTest.scala @@ -0,0 +1,10 @@ +package dotty.dokka + +import org.junit.Test +import org.junit.Assert._ + +class JSONTest: + @Test + def testStrings = + assertEquals(quoteStr("""ala"""), jsonString("""ala""")) + assertEquals(quoteStr("""\""""), jsonString(""""""")) \ No newline at end of file