diff --git a/project/Build.scala b/project/Build.scala index daba197295b5..ab6671ddc0c6 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1466,21 +1466,23 @@ object Build { def joinProducts(products: Seq[java.io.File]): String = products.iterator.map(_.getAbsolutePath.toString).mkString(java.io.File.pathSeparator) + val dokkaVersion = "1.4.10.2" + project.settings(commonBootstrappedSettings). dependsOn(`scala3-compiler-bootstrapped`). dependsOn(`scala3-tasty-inspector`). settings( // Needed to download dokka and its dependencies resolvers += Resolver.jcenterRepo, - // Needed to download dokka-site - resolvers += Resolver.bintrayRepo("virtuslab", "dokka"), libraryDependencies ++= Seq( - "com.virtuslab.dokka" % "dokka-site" % "0.1.9", + "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-all" % "0.42.12", "nl.big-o" % "liqp" % "0.6.7", "args4j" % "args4j" % "2.33", - "org.jetbrains.dokka" % "dokka-test-api" % "1.4.10.2" % "test", + "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, diff --git a/sbt-dotty/src/dotty/tools/sbtplugin/DottyPlugin.scala b/sbt-dotty/src/dotty/tools/sbtplugin/DottyPlugin.scala index 09b1356dd023..368560276a26 100644 --- a/sbt-dotty/src/dotty/tools/sbtplugin/DottyPlugin.scala +++ b/sbt-dotty/src/dotty/tools/sbtplugin/DottyPlugin.scala @@ -358,10 +358,7 @@ object DottyPlugin extends AutoPlugin { }.value, // Configuration for the doctool - resolvers ++= (if(!useScala3doc.value) Nil else Seq( - Resolver.jcenterRepo, - Resolver.bintrayRepo("virtuslab", "dokka"), - )), + resolvers ++= (if(!useScala3doc.value) Nil else Seq(Resolver.jcenterRepo)), useScala3doc := false, scala3docOptions := Nil, Compile / doc / scalacOptions := { diff --git a/scala3doc/dotty-docs/docs/index.html b/scala3doc/dotty-docs/docs/index.html index f31017f8e264..b0065cf5d2c8 100644 --- a/scala3doc/dotty-docs/docs/index.html +++ b/scala3doc/dotty-docs/docs/index.html @@ -49,158 +49,31 @@

Dotty

- -
-
+
-

Try Dotty

-

If you are a Mac user, you can install Dotty with brew:

-
brew install lampepfl/brew/dotty
+

Try Dotty

+

If you are a Mac user, you can install Dotty with brew:

+
brew install lampepfl/brew/dotty
-

If you are a Linux or Windows user, download the latest release. Optionally add path of the folder bin/ to the system environment variable PATH.

+

If you are a Linux or Windows user, download the latest release. Optionally add path of the folder bin/ to the system environment variable PATH.

-

Now you can compile Scala source code:

-
dotc hello.scala
+

Now you can compile Scala source code:

+
scalac hello.scala
-

To start the REPL, run: dotr.

+

To start the REPL, run: scala.

-

Or, you can try Dotty in your browser with Scastie.

+

Or, you can try Dotty in your browser with Scastie.

-

Create a Dotty Project

-

The fastest way to create a new project in Dotty is using sbt (1.1.4+).

+

Create a Dotty Project

+

The fastest way to create a new project in Dotty is using sbt (1.1.4+).

-

Create a Dotty project:

-
sbt new lampepfl/dotty.g8
+

Create a Dotty project:

+
sbt new lampepfl/dotty.g8
-

Or a Dotty project that cross compiles with Scala 2:

-
sbt new lampepfl/dotty-cross.g8
+

Or a Dotty project that cross compiles with Scala 2:

+
sbt new lampepfl/dotty-cross.g8
-

For documentation see the Dotty Example Project.

-
+

For documentation see the Dotty Example Project.

+
- -
-
-

So, features?

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Intersection TypesImplemented
Union TypesImplemented
Type lambdasImplemented
Context queryImplemented
Trait parametersImplemented
Implied InstancesImplemented
Inferable parametersImplemented
Extension MethodsImplemented
Opaque Type AliasesImplemented
Toplevel definitionsImplemented
Export clausesImplemented
Vararg patternsImplemented
Creator applicationsImplemented
@static methods and fieldsImplemented
SBT incremental buildImplemented
Option-less pattern matchingImplemented
Multiversal equalityImplemented
Erased TermsImplemented
Auto-SpecializationIn progress
Whole program optimizerIn progress
HList & HMaps/Record typesIn progress
EffectsConsidered
…and many more, check the overview page for a comprehensive list
-
-

Talks on Dotty?

- -

I have more questions!

-
-

That’s great! We have more details on the docs and please join our Gitter channel!

-
-
-
-
- diff --git a/scala3doc/src/dotty/dokka/DottyDokkaConfig.scala b/scala3doc/src/dotty/dokka/DottyDokkaConfig.scala index 6e5b7287f087..4c69b057cfa8 100644 --- a/scala3doc/src/dotty/dokka/DottyDokkaConfig.scala +++ b/scala3doc/src/dotty/dokka/DottyDokkaConfig.scala @@ -3,51 +3,47 @@ package dotty.dokka import org.jetbrains.dokka._ import org.jetbrains.dokka.DokkaSourceSetImpl import java.io.File -import java.util.{ List => JList, Map => JMap} import collection.JavaConverters._ +import dotty.dokka.site.StaticSiteContext case class DottyDokkaConfig(docConfiguration: DocConfiguration) extends DokkaConfiguration: override def getOutputDir: File = docConfiguration.args.output override def getCacheRoot: File = null override def getOfflineMode: Boolean = false override def getFailOnWarning: Boolean = false - override def getSourceSets: JList[DokkaConfiguration.DokkaSourceSet] = List(mkSourceSet).asJava - override def getModules: JList[DokkaConfiguration.DokkaModuleDescription] = List().asJava - override def getPluginsClasspath: JList[File] = Nil.asJava + override def getSourceSets: JList[DokkaSourceSet] = JList(mkSourceSet) + override def getModules: JList[DokkaConfiguration.DokkaModuleDescription] = JList() + override def getPluginsClasspath: JList[File] = JList() override def getModuleName(): String = "ModuleName" override def getModuleVersion(): String = "" - private object OurConfig extends DokkaConfiguration.PluginConfiguration: - override def getFqPluginName = "ExternalDocsTooKey" - override def getSerializationFormat: DokkaConfiguration$SerializationFormat = - DokkaConfiguration$SerializationFormat.JSON.asInstanceOf[DokkaConfiguration$SerializationFormat] - override def getValues: String = docConfiguration.args.docsRoot.getOrElse("") + lazy val staticSiteContext = docConfiguration.args.docsRoot.map(path => StaticSiteContext(File(path).getAbsoluteFile(), Set(mkSourceSet.asInstanceOf[SourceSetWrapper]))) - override def getPluginsConfiguration: JList[DokkaConfiguration.PluginConfiguration] = List(OurConfig).asJava + override def getPluginsConfiguration: JList[DokkaConfiguration.PluginConfiguration] = JList() - def mkSourceSet: DokkaConfiguration.DokkaSourceSet = + lazy val mkSourceSet: DokkaSourceSet = val sourceLinks:Set[SourceLinkDefinitionImpl] = docConfiguration.args.sourceLinks.map(SourceLinkDefinitionImpl.Companion.parseSourceLinkDefinition(_)).toSet new DokkaSourceSetImpl( /*displayName=*/ docConfiguration.args.name, /*sourceSetID=*/ new DokkaSourceSetID(docConfiguration.args.name, "main"), - /*classpath=*/ Nil.asJava, - /*sourceRoots=*/ Set().asJava, - /*dependentSourceSets=*/ Set().asJava, - /*samples=*/ Set().asJava, - /*includes=*/ Set().asJava, + /*classpath=*/ JList(), + /*sourceRoots=*/ JSet(), + /*dependentSourceSets=*/ JSet(), + /*samples=*/ JSet(), + /*includes=*/ JSet(), /*includeNonPublic=*/ true, /*reportUndocumented=*/ false, /* changed because of exception in reportUndocumentedTransformer - there's 'when' which doesnt match because it contains only KotlinVisbility cases */ /*skipEmptyPackages=*/ false, // Now all our packages are empty from dokka perspective /*skipDeprecated=*/ true, /*jdkVersion=*/ 8, /*sourceLinks=*/ sourceLinks.asJava, - /*perPackageOptions=*/ Nil.asJava, - /*externalDocumentationLinks=*/ Set().asJava, + /*perPackageOptions=*/ JList(), + /*externalDocumentationLinks=*/ JSet(), /*languageVersion=*/ null, /*apiVersion=*/ null, /*noStdlibLink=*/ true, /*noJdkLink=*/ true, - /*suppressedFiles=*/ Set().asJava, + /*suppressedFiles=*/ JSet(), /*suppressedFiles=*/ Platform.jvm - ).asInstanceOf[DokkaConfiguration.DokkaSourceSet] // Why I do need to cast here? Kotlin magic? + ).asInstanceOf[DokkaSourceSet] // Why I do need to cast here? Kotlin magic? diff --git a/scala3doc/src/dotty/dokka/DottyDokkaPlugin.scala b/scala3doc/src/dotty/dokka/DottyDokkaPlugin.scala index 212d7a59b69f..f435ebbe26ec 100644 --- a/scala3doc/src/dotty/dokka/DottyDokkaPlugin.scala +++ b/scala3doc/src/dotty/dokka/DottyDokkaPlugin.scala @@ -12,8 +12,6 @@ import org.jetbrains.dokka.links._ import org.jetbrains.dokka.model.doc._ import org.jetbrains.dokka.base.parsers._ import org.jetbrains.dokka.plugability.DokkaContext -import com.virtuslab.dokka.site.SourceSetWrapper -import com.virtuslab.dokka.site.JavaSourceToDocumentableTranslator import collection.JavaConverters._ import org.jetbrains.dokka.model.properties.PropertyContainer import dotty.dokka.tasty.{DokkaTastyInspector, SbtDokkaTastyInspector} @@ -23,10 +21,13 @@ import org.jetbrains.dokka.base.signatures.SignatureProvider import org.jetbrains.dokka.pages._ import dotty.dokka.model.api._ import org.jetbrains.dokka.CoreExtensions -import com.virtuslab.dokka.site.StaticSitePlugin import org.jetbrains.dokka.base.DokkaBase -import com.virtuslab.dokka.site.ExtensionBuilderEx -import java.util.{List => JList} + +import dotty.dokka.site.SitePagesCreator +import dotty.dokka.site.StaticSiteContext +import dotty.dokka.site.RootIndexPageCreator +import dotty.dokka.site.SiteResourceManager +import dotty.dokka.site.StaticSiteLocationProviderFactory /** Main Dokka plugin for the doctool. * @@ -38,7 +39,6 @@ import java.util.{List => JList} class DottyDokkaPlugin extends DokkaJavaPlugin: lazy val dokkaBase = plugin(classOf[DokkaBase]) - lazy val dokkaSitePlugin = plugin(classOf[StaticSitePlugin]) val provideMembers = extend( _.extensionPoint(CoreExtensions.INSTANCE.getSourceToDocumentableTranslator) @@ -116,7 +116,7 @@ class DottyDokkaPlugin extends DokkaJavaPlugin: val ourRenderer = extend( _.extensionPoint(CoreExtensions.INSTANCE.getRenderer) .fromRecipe(ScalaHtmlRenderer(_)) - .overrideExtension(dokkaSitePlugin.getCustomRenderer) + .overrideExtension(dokkaBase.getHtmlRenderer) ) val commentsToContentConverter = extend( @@ -126,7 +126,7 @@ class DottyDokkaPlugin extends DokkaJavaPlugin: ) val implicitMembersExtensionTransformer = extend( - _.extensionPoint(CoreExtensions.INSTANCE.getDocumentableTransformer ) + _.extensionPoint(CoreExtensions.INSTANCE.getDocumentableTransformer) .fromRecipe(ImplicitMembersExtensionTransformer(_)) .name("implicitMembersExtensionTransformer") ) @@ -140,10 +140,71 @@ class DottyDokkaPlugin extends DokkaJavaPlugin: .name("muteDefaultSourceLinksTransformer") ) -// TODO remove once problem is fixed in Dokka + val customDocumentationProvider = extend( + _.extensionPoint(dokkaBase.getHtmlPreprocessors) + .fromRecipe(c => SitePagesCreator(c.siteContext)) + .name("customDocumentationProvider") + .ordered( + before = Seq( + dokkaBase.getNavigationPageInstaller, + dokkaBase.getScriptsInstaller, + dokkaBase.getStylesInstaller, + dokkaBase.getPackageListCreator, + ), + after = Seq(dokkaBase.getRootCreator) + ) + ) + + val customIndexRootProvider = extend( + _.extensionPoint(dokkaBase.getHtmlPreprocessors) + .fromRecipe(c => RootIndexPageCreator(c.siteContext)) + .name("customIndexRootProvider") + .ordered( + before = Seq( + dokkaBase.getScriptsInstaller, + dokkaBase.getStylesInstaller, + ), + after = Seq(dokkaBase.getNavigationPageInstaller) + ) + ) + + val customDocumentationResources = extend( + _.extensionPoint(dokkaBase.getHtmlPreprocessors) + .fromRecipe(c => SiteResourceManager(c.siteContext)) + .name("customDocumentationResources") + .after( + scalaEmbeddedResourceAppender.getValue + ) + ) + + val locationProvider = extend( + _.extensionPoint(dokkaBase.getLocationProviderFactory) + .fromRecipe(StaticSiteLocationProviderFactory(_)) + .overrideExtension(dokkaBase.getLocationProvider) + ) + + extension (ctx: DokkaContext): + def siteContext: Option[StaticSiteContext] = ctx.getConfiguration match + case d: DottyDokkaConfig => d.staticSiteContext + case _ => None + +// TODO (https://github.com/lampepfl/scala3doc/issues/232): remove once problem is fixed in Dokka extension [T] (builder: ExtensionBuilder[T]): - def before(exts: Extension[_, _, _]*): ExtensionBuilder[T] = - (new ExtensionBuilderEx).newOrdering(builder, exts.toArray, Array.empty) + 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] = - (new ExtensionBuilderEx).newOrdering(builder, Array.empty, exts.toArray) + 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 581c2c111ecf..98207a368288 100644 --- a/scala3doc/src/dotty/dokka/Main.scala +++ b/scala3doc/src/dotty/dokka/Main.scala @@ -8,7 +8,6 @@ import java.io.File import java.util.jar._ import collection.JavaConverters._ import collection.immutable.ArraySeq -import java.util.{List => JList} import scala.tasty.Reflection import scala.tasty.inspector.TastyInspector diff --git a/scala3doc/src/dotty/dokka/ScalaModuleCreator.scala b/scala3doc/src/dotty/dokka/ScalaModuleCreator.scala index 86ccd58739a0..e8ba83931d7a 100644 --- a/scala3doc/src/dotty/dokka/ScalaModuleCreator.scala +++ b/scala3doc/src/dotty/dokka/ScalaModuleCreator.scala @@ -1,23 +1,22 @@ package dotty.dokka import org.jetbrains.dokka.{ DokkaConfiguration$DokkaSourceSet => DokkaSourceSet } -import com.virtuslab.dokka.site.JavaSourceToDocumentableTranslator -import com.virtuslab.dokka.site.SourceSetWrapper 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, SbtDokkaTastyInspector} import org.jetbrains.dokka.pages._ import dotty.dokka.model.api._ import org.jetbrains.dokka.model._ import org.jetbrains.dokka.links.DRI -import java.util.{List => JList} import org.jetbrains.dokka.base.parsers.MarkdownParser import collection.JavaConverters._ +import kotlin.coroutines.Continuation -object ScalaModuleProvider extends JavaSourceToDocumentableTranslator: - override def process(rawSourceSet: DokkaSourceSet, cxt: DokkaContext) = - val sourceSet = SourceSetWrapper(rawSourceSet) +object ScalaModuleProvider extends SourceToDocumentableTranslator: + override def invoke(sourceSet: DokkaSourceSet, cxt: DokkaContext, unused: Continuation[? >: DModule]) = cxt.getConfiguration match case dottyConfig: DottyDokkaConfig => val result = dottyConfig.docConfiguration match { @@ -41,9 +40,9 @@ object ScalaModuleProvider extends JavaSourceToDocumentableTranslator: def flattenMember(m: Member): Seq[(DRI, Member)] = (m.dri -> m) +: m.allMembers.flatMap(flattenMember) new DModule( - sourceSet.getSourceSet.getDisplayName, + sourceSet.getDisplayName, result.asJava, - Map().asJava, + JMap(), null, sourceSet.toSet, PropertyContainer.Companion.empty() plus ModuleExtension(result.flatMap(flattenMember).toMap) @@ -51,6 +50,6 @@ object ScalaModuleProvider extends JavaSourceToDocumentableTranslator: case _ => ??? -object EmptyModuleProvider extends JavaSourceToDocumentableTranslator: - override def process(sourceSet: DokkaSourceSet, context: DokkaContext) = - DModule("", Nil.asJava, Map.empty.asJava, null, Set(sourceSet).asJava, PropertyContainer.Companion.empty()) +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()) diff --git a/scala3doc/src/dotty/dokka/compat.scala b/scala3doc/src/dotty/dokka/compat.scala new file mode 100644 index 000000000000..cf9440bd6913 --- /dev/null +++ b/scala3doc/src/dotty/dokka/compat.scala @@ -0,0 +1,59 @@ +package dotty.dokka + +import org.jetbrains.dokka.links.{DRI, PointingToDeclaration} +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 + +def mkDRI(packageName: String = null, extra: String = null) = new DRI(packageName, null, null, PointingToDeclaration.INSTANCE, extra) + +val U: kotlin.Unit = kotlin.Unit.INSTANCE + +def JList[T](e: T*): JList[T] = e.asJava +def JSet[T](e: T*): JSet[T] = e.toSet.asJava +def JMap[K, V](e: (K, V)*): JMap[K, V] = e.toMap.asJava +def JMap2[K, V](): JMap[K, V] = ??? // e.toMap.asJava + +type JList[T] = java.util.List[T] +type JSet[T] = java.util.Set[T] +type JMap[K, V] = java.util.Map[K, V] + +type SourceSetWrapper = DokkaConfiguration$DokkaSourceSet +type DokkaSourceSet = DokkaConfiguration.DokkaSourceSet + +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 + +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 + +extension [V](jlist: JList[V]): + def ++ (other: JList[V]): JList[V] = + Stream.of(jlist, other).flatMap(_.stream).collect(Collectors.toList()) diff --git a/scala3doc/src/dotty/dokka/model/api/api.scala b/scala3doc/src/dotty/dokka/model/api/api.scala index 4e2d86a27606..9a956f57c481 100644 --- a/scala3doc/src/dotty/dokka/model/api/api.scala +++ b/scala3doc/src/dotty/dokka/model/api/api.scala @@ -10,8 +10,6 @@ import org.jetbrains.dokka.links._ import org.jetbrains.dokka.model.doc._ import org.jetbrains.dokka.model.properties._ import org.jetbrains.dokka.pages._ -import java.util.{List => JList, Set => JSet} - enum Visibility(val name: String): case Unrestricted extends Visibility("") @@ -89,7 +87,7 @@ object Annotation: // TODO (longterm) properly represent signatures case class Link(name: String, dri: DRI) -type Signature = Seq[String | Link]// TODO migrate tupes to Links +type Signature = Seq[String | Link] object Signature: def apply(names: (String | Link)*): Signature = names // TO batter dotty shortcommings in union types diff --git a/scala3doc/src/dotty/dokka/model/api/internalExtensions.scala b/scala3doc/src/dotty/dokka/model/api/internalExtensions.scala index cf53fc2a1a27..ea3e64445ac5 100644 --- a/scala3doc/src/dotty/dokka/model/api/internalExtensions.scala +++ b/scala3doc/src/dotty/dokka/model/api/internalExtensions.scala @@ -21,9 +21,6 @@ import collection.JavaConverters._ import org.jetbrains.dokka.links._ import org.jetbrains.dokka.model.doc.DocumentationNode import org.jetbrains.dokka.model.properties._ -import java.util.{List => JList, Set => JSet} - -import com.virtuslab.dokka.site.SourceSetWrapper private [model] case class MemberExtension( visibility: Visibility, diff --git a/scala3doc/src/dotty/dokka/model/extras.scala b/scala3doc/src/dotty/dokka/model/extras.scala index 9e5a86d7a1a8..179f5a5d51ff 100644 --- a/scala3doc/src/dotty/dokka/model/extras.scala +++ b/scala3doc/src/dotty/dokka/model/extras.scala @@ -9,7 +9,6 @@ import collection.JavaConverters._ import org.jetbrains.dokka.links._ import org.jetbrains.dokka.model.doc._ import org.jetbrains.dokka.model.properties._ -import java.util.{List => JList, Set => JSet} import dotty.dokka.model.api._ case class ModuleExtension(driMap: Map[DRI, Member]) extends ExtraProperty[DModule]: diff --git a/scala3doc/src/dotty/dokka/model/scalaModel.scala b/scala3doc/src/dotty/dokka/model/scalaModel.scala index 329768236bc1..ad4cb5b3b3e2 100644 --- a/scala3doc/src/dotty/dokka/model/scalaModel.scala +++ b/scala3doc/src/dotty/dokka/model/scalaModel.scala @@ -8,7 +8,6 @@ import org.jetbrains.dokka.links._ import org.jetbrains.dokka.model.doc._ import org.jetbrains.dokka.model.properties._ import org.jetbrains.dokka.pages._ -import java.util.{List => JList, Set => JSet} import dotty.dokka.model.api.Signature import dotty.dokka.model.api.HierarchyGraph @@ -33,7 +32,7 @@ case class HtmlContentNode( 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] = Nil.asJava + override def getChildren: JList[ContentNode] = JList() override def getExtra = extra override def withNewExtras(p: PropertyContainer[ContentNode]) = copy(extra = p) @@ -70,7 +69,7 @@ case class HierarchyGraphContentNode( override def getStyle = style.asJava override def hasAnyContent = !diagram.edges.isEmpty def withSourceSets(sourceSets: JSet[DisplaySourceSet]) = copy(sourceSets = sourceSets.asScala.toSet) - override def getChildren: JList[ContentNode] = Nil.asJava + override def getChildren: JList[ContentNode] = JList() override def getExtra = extra override def withNewExtras(p: PropertyContainer[ContentNode]) = copy(extra = p) @@ -91,7 +90,7 @@ abstract class ScalaContentNode(params: ContentNodeParams) extends ContentNode: override def hasAnyContent = true def withSourceSets(sourceSets: JSet[DisplaySourceSet]) = newInstance(params.copy(sourceSets = sourceSets)) - override def getChildren: JList[ContentNode] = Nil.asJava + override def getChildren: JList[ContentNode] = JList() override def getExtra = params.extra override def withNewExtras(p: PropertyContainer[ContentNode]) = newInstance(params.copy(extra = p)) diff --git a/scala3doc/src/dotty/dokka/site/PartiallyRenderedContent.scala b/scala3doc/src/dotty/dokka/site/PartiallyRenderedContent.scala new file mode 100644 index 000000000000..6b02f20086f1 --- /dev/null +++ b/scala3doc/src/dotty/dokka/site/PartiallyRenderedContent.scala @@ -0,0 +1,25 @@ +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} + +case class PartiallyRenderedContent( + template: TemplateFile, + 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) diff --git a/scala3doc/src/dotty/dokka/site/StaticPageNode.scala b/scala3doc/src/dotty/dokka/site/StaticPageNode.scala new file mode 100644 index 000000000000..5d00be49a6bd --- /dev/null +++ b/scala3doc/src/dotty/dokka/site/StaticPageNode.scala @@ -0,0 +1,44 @@ +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.links.DRI +import org.jetbrains.dokka.model.Documentable +import org.jetbrains.dokka.pages._ +import org.jetbrains.dokka.transformers.pages.PageTransformer + +case class LoadedTemplate(templateFile: TemplateFile, children: List[LoadedTemplate], file: File) { + def relativePath(root: File): String = + root.toPath().relativize(file.toPath()).toString().replace(File.separatorChar, '.') +} + +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() + + 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 \ No newline at end of file diff --git a/scala3doc/src/dotty/dokka/site/StaticSiteContext.scala b/scala3doc/src/dotty/dokka/site/StaticSiteContext.scala new file mode 100644 index 000000000000..a177f6352927 --- /dev/null +++ b/scala3doc/src/dotty/dokka/site/StaticSiteContext.scala @@ -0,0 +1,98 @@ +package dotty.dokka +package site + +import java.io.File + +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.links.DRI +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 scala.collection.JavaConverters._ + +class StaticSiteContext(val root: File, sourceSets: Set[SourceSetWrapper]): + val docsFile = new File(root, "docs") + + def indexPage():Option[StaticPageNode] = + val files = List(new File(root, "index.html"), new File(root, "index.md")).filter { _.exists() } + if (files.size > 1) println(s"ERROR: Multiple root index pages found: ${files.map(_.getAbsolutePath)}") // TODO (#14): provide proper error handling + loadFiles(files).headOption + + lazy val layouts: Map[String, TemplateFile] = + val layoutRoot = new File(root, "_layouts") + val dirs: Array[File] = Option(layoutRoot.listFiles()).getOrElse(Array()) + dirs.map { it => loadTemplateFile(it) }.map { it => it.name() -> it }.toMap + + private def isValidTemplate(file: File): Boolean = + (file.isDirectory && !file.getName.startsWith("_")) || + file.getName.endsWith(".md") || + file.getName.endsWith(".html") + + + private def loadTemplate(from: File): Option[LoadedTemplate] = + if (!isValidTemplate(from)) None else + try + val (indexes, children) = Option(from.listFiles()).toSeq.flatten.flatMap(loadTemplate).partition(_.templateFile.isIndexPage()) + if (indexes.size > 1) + println(s"ERROR: Multiple index pages for $from found in ${indexes.map(_.file)}") // TODO (#14): provide proper error handling + + def loadIndexPage(): TemplateFile = + val indexFiles = from.listFiles { file =>file.getName == "index.md" || file.getName == "index.html" } + indexFiles.size match + case 0 => emptyTemplate(from) + case 1 => loadTemplateFile(indexFiles.head).copy(file = from) + case _ => + val msg = s"ERROR: Multiple index pages found under ${from.toPath}" + throw new java.lang.RuntimeException(msg) + + val templateFile = if (from.isDirectory) loadIndexPage() else loadTemplateFile(from) + + Some(LoadedTemplate(templateFile, children.toList, from)) + catch + case e: RuntimeException => + e.printStackTrace() // TODO (#14): provide proper error handling + None + + def asContent(doctag: DocTag, dri: DRI) = new DocTagToContentConverter().buildContent( + doctag, + new DCI(Set(dri).asJava, ContentKind.Empty), + sourceSets.asJava, + JSet(), + new PropertyContainer(JMap()) + ) + + def loadFiles(files: List[File], customChildren: List[PageNode] = Nil): List[StaticPageNode] = + val all = files.flatMap(loadTemplate) + def flatten(it: LoadedTemplate): List[String] = + List(it.relativePath(root)) ++ it.children.flatMap(flatten) + + def pathToDRI(path: String) = mkDRI(s"_.$path") + + val driMap = all.flatMap(flatten).map(it => it -> pathToDRI(it)).toMap + + def templateToPage(myTemplate: LoadedTemplate): StaticPageNode = + val dri = pathToDRI(myTemplate.relativePath(root)) + val content = new PartiallyRenderedContent( + myTemplate.templateFile, + 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) ++ customChildren).asJava + ) + + all.map(templateToPage) diff --git a/scala3doc/src/dotty/dokka/site/StaticSiteLocationProvider.scala b/scala3doc/src/dotty/dokka/site/StaticSiteLocationProvider.scala new file mode 100644 index 000000000000..3092446d6b5d --- /dev/null +++ b/scala3doc/src/dotty/dokka/site/StaticSiteLocationProvider.scala @@ -0,0 +1,42 @@ +package dotty.dokka +package site + +import org.jetbrains.dokka.base.resolvers.local.DokkaLocationProvider +import org.jetbrains.dokka.base.resolvers.local.LocationProvider +import org.jetbrains.dokka.base.resolvers.local.LocationProviderFactory +import org.jetbrains.dokka.pages.ContentPage +import org.jetbrains.dokka.pages.PageNode +import org.jetbrains.dokka.pages.RootPageNode +import org.jetbrains.dokka.plugability.DokkaContext + +import scala.collection.JavaConverters._ + +class StaticSiteLocationProviderFactory(private val ctx: DokkaContext) extends LocationProviderFactory: + override def getLocationProvider(pageNode: RootPageNode): LocationProvider = + new StaticSiteLocationProvider(ctx, pageNode) + +class StaticSiteLocationProvider(ctx: DokkaContext, pageNode: RootPageNode) + extends DokkaLocationProvider(pageNode, ctx, ".html"): + private def updatePageEntry(page: PageNode, jpath: JList[String]): JList[String] = + page match + case page: StaticPageNode => + if (page.getDri.contains(docsRootDRI)) JList("index") + else { + val path = jpath.asScala.toList + val start = if (path.head == "--root--") List("docs") else path.take(1) + val pageName = page.template.file.getName + val dotIndex = pageName.lastIndexOf('.') + val newName = if (dotIndex < 0) pageName else pageName.substring(0, dotIndex) + (start ++ path.drop(1).dropRight(1) ++ List(newName)).asJava + } + case page: ContentPage if page.getDri.contains(docsDRI) => + JList("docs") + 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 _ => + jpath + + override val getPathsIndex: JMap[PageNode, JList[String]] = + super.getPathsIndex.asScala.mapValuesInPlace(updatePageEntry).asJava diff --git a/scala3doc/src/dotty/dokka/site/common.scala b/scala3doc/src/dotty/dokka/site/common.scala new file mode 100644 index 000000000000..50d2eb6090b7 --- /dev/null +++ b/scala3doc/src/dotty/dokka/site/common.scala @@ -0,0 +1,72 @@ +package dotty.dokka +package site + +import java.io.File +import java.nio.file.Files + +import com.vladsch.flexmark.ext.anchorlink.AnchorLinkExtension +import com.vladsch.flexmark.ext.autolink.AutolinkExtension +import com.vladsch.flexmark.ext.emoji.EmojiExtension +import com.vladsch.flexmark.ext.gfm.strikethrough.StrikethroughExtension +import com.vladsch.flexmark.ext.gfm.tasklist.TaskListExtension +import com.vladsch.flexmark.ext.tables.TablesExtension +import com.vladsch.flexmark.ext.yaml.front.matter.{AbstractYamlFrontMatterVisitor, YamlFrontMatterExtension} +import com.vladsch.flexmark.parser.{Parser, ParserEmulationProfile} +import com.vladsch.flexmark.util.options.{DataHolder, MutableDataSet} +import org.jetbrains.dokka.links.{DRI, PointingToDeclaration} +import org.jetbrains.dokka.model.doc.Text + +import scala.collection.JavaConverters._ + +val docsRootDRI: DRI = mkDRI(extra = "_top_level_index") +val docsDRI: DRI = mkDRI(extra = "_docs_level_index") +val apiPageDRI: DRI = mkDRI(packageName = "api", extra = "__api__") + +val defaultMarkdownOptions: DataHolder = + new MutableDataSet() + .setFrom(ParserEmulationProfile.KRAMDOWN.getOptions) + .set( + Parser.EXTENSIONS, List( + TablesExtension.create(), + TaskListExtension.create(), + AutolinkExtension.create(), + AnchorLinkExtension.create(), + EmojiExtension.create(), + YamlFrontMatterExtension.create(), + StrikethroughExtension.create() + ).asJava) + .set( + EmojiExtension.ROOT_IMAGE_PATH, + "https://github.global.ssl.fastly.net/images/icons/emoji/" + ) + +def emptyTemplate(file: File): TemplateFile = TemplateFile(file, isHtml = true, "", Map()) + +final val ConfigSeparator = "---" +final val LineSeparator = "\n" + +val yamlParser: Parser = Parser.builder(defaultMarkdownOptions).build() + +def loadTemplateFile(file: File): TemplateFile = { + val lines = Files.readAllLines(file.toPath).asScala.toList + + val (config, content) = if (lines.head == ConfigSeparator) { + // Taking the second occurrence of ConfigSeparator. + // The rest may appear within the content. + val index = lines.drop(1).indexOf(ConfigSeparator) + 2 + (lines.take(index), lines.drop(index)) + } else (Nil, lines) + + val configParsed = yamlParser.parse(config.mkString(LineSeparator)) + val yamlCollector = new AbstractYamlFrontMatterVisitor() + yamlCollector.visit(configParsed) + + TemplateFile( + file = file, + file.getName.endsWith(".html"), + rawCode = content.mkString(LineSeparator), + settings = yamlCollector.getData.asScala.toMap.transform((_, v) => v.asScala.toList) + ) +} + +def Text(msg: String = "") = new Text(msg, JList(), JMap()) diff --git a/scala3doc/src/dotty/dokka/site/processors.scala b/scala3doc/src/dotty/dokka/site/processors.scala new file mode 100644 index 000000000000..c6a6db695be7 --- /dev/null +++ b/scala3doc/src/dotty/dokka/site/processors.scala @@ -0,0 +1,133 @@ +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.links.DRI +import org.jetbrains.dokka.model.Documentable +import org.jetbrains.dokka.pages._ +import org.jetbrains.dokka.transformers.pages.PageTransformer + +import scala.collection.JavaConverters._ + +abstract class BaseStaticSiteProcessor(staticSiteContext: Option[StaticSiteContext]) extends PageTransformer: + final override def invoke(input: RootPageNode): RootPageNode = staticSiteContext.fold(input)(transform(input, _)) + + protected def transform(input: RootPageNode, ctx: StaticSiteContext): RootPageNode + +class SiteResourceManager(ctx: Option[StaticSiteContext]) extends BaseStaticSiteProcessor(ctx): + private def listResources(nodes: Seq[PageNode]): Set[String] = + nodes.flatMap { + case it: StaticPageNode => listResources(it.getChildren.asScala.toList) ++ it.resources() + case _ => Seq.empty + }.toSet + + override def transform(input: RootPageNode, ctx: StaticSiteContext): RootPageNode = + val imgPath = ctx.root.toPath().resolve("images") + val images = + if !Files.exists(imgPath) then Nil + else + val stream = Files.walk(imgPath)filter(p => Files.isRegularFile(p) && p.getFileName().toString().endsWith(".svg")) + stream.iterator().asScala.toList.map(_.toString) + + val resources = listResources(input.getChildren.asScala.toList) ++ images + val resourcePages = resources.map { path => + val content = Files.readAllLines(ctx.root.toPath.resolve(path)).asScala.mkString("\n") + new RendererSpecificResourcePage(path, JList(), new RenderingStrategy.Write(content)) + }.toList + + 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 + } + modified.modified(modified.getName, (resourcePages ++ modified.getChildren.asScala).asJava) + +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]]) + +class SitePagesCreator(ctx: Option[StaticSiteContext]) extends BaseStaticSiteProcessor(ctx): + private def processRootPage(input: RootPageNode, children: List[PageNode] = Nil): 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) => + processRootPage(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 modifiedModuleRoot = processRootPage(input, contentPage) + val allFiles = Option(ctx.docsFile.listFiles()).toList.flatten + val (indexes, children) = ctx.loadFiles(allFiles).partition(_.template.isIndexPage()) + // TODO (#14): provide proper error handling + if (indexes.size > 1) println("ERROR: Multiple index pages found $indexes}") + + val rootContent = indexes.headOption.fold(ctx.asContent(Text(), mkDRI(extra = "root_content")).get(0))(_.getContent) + + val root = AContentPage( + input.getName, + (List(modifiedModuleRoot.modified("API", modifiedModuleRoot.getChildren)) ++ children).asJava, + rootContent, + JSet(docsDRI), + JList() + ) + + new RendererSpecificRootPage( + modifiedModuleRoot.getName, + (List(root) ++ others).asJava, + RenderingStrategy.DoNothing.INSTANCE + ) + +class RootIndexPageCreator(ctx: Option[StaticSiteContext]) extends BaseStaticSiteProcessor(ctx): + override def transform(input: RootPageNode, ctx: StaticSiteContext): RootPageNode = + ctx.indexPage().fold(input){ it => + val (contentNodes, nonContent) = input.getChildren.asScala.partition { _.isInstanceOf[ContentNode] } + val (navigations, rest) = nonContent.partition { _.isInstanceOf[NavigationPage] } + val modifiedNavigation = navigations.map { it => + val root = it.asInstanceOf[NavigationPage].getRoot + val (api, rest) = root.getChildren.asScala.partition { _.getDri == apiPageDRI } + new NavigationPage( + new NavigationNode( + input.getName, + docsRootDRI, + root.getSourceSets, + (rest ++ api).asJava + ) + ) + } + val newRoot = it.copy(getDri = JSet(docsRootDRI), getChildren = contentNodes.asJava) + input.modified(input.getName, (List(newRoot) ++ rest ++ modifiedNavigation).asJava) + } diff --git a/scala3doc/src/dotty/dokka/site/templates.scala b/scala3doc/src/dotty/dokka/site/templates.scala new file mode 100644 index 000000000000..65dff5441714 --- /dev/null +++ b/scala3doc/src/dotty/dokka/site/templates.scala @@ -0,0 +1,93 @@ +package dotty.dokka.site + +import java.io.File +import java.nio.file.Files + +import com.vladsch.flexmark.ext.anchorlink.AnchorLinkExtension +import com.vladsch.flexmark.ext.autolink.AutolinkExtension +import com.vladsch.flexmark.ext.emoji.EmojiExtension +import com.vladsch.flexmark.ext.gfm.strikethrough.StrikethroughExtension +import com.vladsch.flexmark.ext.gfm.tasklist.TaskListExtension +import com.vladsch.flexmark.ext.tables.TablesExtension +import com.vladsch.flexmark.ext.yaml.front.matter.{AbstractYamlFrontMatterVisitor, YamlFrontMatterExtension} +import com.vladsch.flexmark.parser.{Parser, ParserEmulationProfile} +import com.vladsch.flexmark.util.options.{DataHolder, MutableDataSet} +import com.vladsch.flexmark.html.HtmlRenderer +import liqp.Template + +import scala.collection.JavaConverters._ +import scala.io.Source + +case class RenderingContext( + properties: Map[String, Object], + layouts: Map[String, TemplateFile] = Map(), + resolving: Set[String] = Set(), + markdownOptions: DataHolder = defaultMarkdownOptions, + resources: List[String] = Nil + ): + + def nest(code: String, file: File, resources: List[String]) = + copy( + resolving = resolving + file.getAbsolutePath, + properties = properties + ("content" -> code), + resources = this.resources ++ resources + ) + +case class ResolvedPage(val code: String, val resources: List[String] = Nil) + +/** + * case class for the template files. + * Template file is a file `.md` or `.html` handling settings. + * + * @param file The Actual file defining the template. + * @param rawCode The content, what is to be shown, everything but settings. + * @param settings The config defined in the begging of the file, between the pair of `---` (e.g. layout: basic). + */ +case class TemplateFile( + val file: File, + val isHtml: Boolean, + val rawCode: String, + private val settings: Map[String, List[String]] + ): + + private def stringSetting(name: String): Option[String] = + settings.get(name) map { + case List(single) => single.stripPrefix("\"").stripSuffix("\"") + case nonSingle => + throw new RuntimeException(s"Setting $name is a not a singlel-ement list but $nonSingle") + } + + + private def listSetting(name: String): List[String] = settings.getOrElse(name, Nil) + + def name(): String = stringSetting("name").getOrElse(file.getName.stripSuffix(if (isHtml) ".html" else ".md")) + + def title(): String = stringSetting("title").getOrElse(name()) + + def layout(): Option[String] = stringSetting("layout") + + def hasFrame(): Boolean = !stringSetting("hasFrame").contains("false") + + def isIndexPage() = file.isFile && (file.getName == "index.md" || file.getName == "index.html") + + def resolveToHtml(ctx: StaticSiteContext): ResolvedPage = resolveInner(RenderingContext(Map(), ctx.layouts)) + + private[site] def resolveInner(ctx: RenderingContext): ResolvedPage = + if (ctx.resolving.contains(file.getAbsolutePath)) + throw new RuntimeException(s"Cycle in templates involving $file: ${ctx.resolving}") + + val layoutTemplate = layout().map(name => + ctx.layouts.getOrElse(name, throw new RuntimeException(s"No layouts named $name in ${ctx.layouts}"))) + + // Library requires mutable maps.. + val mutableProperties = new java.util.HashMap[String, Object](ctx.properties.asJava) + val rendered = Template.parse(this.rawCode).render(mutableProperties) + val code = if (!isHtml) rendered else + val parser: Parser = Parser.builder().build() + HtmlRenderer.builder(ctx.markdownOptions).build().render(parser.parse(rendered)) + + val resources = listSetting("extraCSS") ++ listSetting("extraJS") + layoutTemplate match + case None => ResolvedPage(code, resources ++ ctx.resources) + case Some(layoutTemplate) => + layoutTemplate.resolveInner(ctx.nest(code, file, resources)) diff --git a/scala3doc/src/dotty/dokka/tasty/BasicSupport.scala b/scala3doc/src/dotty/dokka/tasty/BasicSupport.scala index 9b80d12efd94..0d2dcac86833 100644 --- a/scala3doc/src/dotty/dokka/tasty/BasicSupport.scala +++ b/scala3doc/src/dotty/dokka/tasty/BasicSupport.scala @@ -35,14 +35,14 @@ trait BasicSupport: extension (sym: Symbol): def documentation(using cxt: Context) = sym.documentation match case Some(comment) => - Map(sourceSet.getSourceSet -> parseComment(comment, sym.tree)) + Map(sourceSet -> parseComment(comment, sym.tree)) case None => Map.empty def source(using ctx: Context) = val path = Some(sym.pos.sourceFile.jpath).filter(_ != null).map(_.toAbsolutePath).map(_.toString) path match{ - case Some(p) => Map(sourceSet.getSourceSet -> TastyDocumentableSource(p, sym.pos.startLine)) + case Some(p) => Map(sourceSet -> TastyDocumentableSource(p, sym.pos.startLine)) case None => Map.empty } diff --git a/scala3doc/src/dotty/dokka/tasty/ClassLikeSupport.scala b/scala3doc/src/dotty/dokka/tasty/ClassLikeSupport.scala index fbad85e22c80..d478569fdf7e 100644 --- a/scala3doc/src/dotty/dokka/tasty/ClassLikeSupport.scala +++ b/scala3doc/src/dotty/dokka/tasty/ClassLikeSupport.scala @@ -18,8 +18,8 @@ trait ClassLikeSupport: self: TastyParser => import qctx.reflect._ - private val placeholderVisibility = Map(sourceSet.getSourceSet -> KotlinVisibility.Public.INSTANCE).asJava - private val placeholderModifier = Map(sourceSet.getSourceSet -> KotlinModifier.Empty.INSTANCE).asJava + private val placeholderVisibility = JMap(sourceSet -> KotlinVisibility.Public.INSTANCE) + private val placeholderModifier = JMap(sourceSet -> KotlinModifier.Empty.INSTANCE) private def kindForClasslike(sym: Symbol): Kind = if sym.flags.is(Flags.Object) then Kind.Object @@ -79,9 +79,9 @@ trait ClassLikeSupport: dri, name, (if(signatureOnly) Nil else classDef.getConstructors.map(parseMethod(_))).asJava, - Nil.asJava, - Nil.asJava, - Nil.asJava, + JList(), + JList(), + JList(), classDef.symbol.source.asJava, placeholderVisibility, null, @@ -250,7 +250,7 @@ trait ClassLikeSupport: /*generics =*/ genericTypes.map(parseTypeArgument).asJava, /*receiver =*/ null, // Not used /*modifier =*/ placeholderModifier, - sourceSet.toSet(), + sourceSet.toSet, /*isExpectActual =*/ false, PropertyContainer.Companion.empty() plus MethodExtension(paramLists.map(_.size)) @@ -270,7 +270,7 @@ trait ClassLikeSupport: argument.symbol.documentation.asJava, null, argument.tpt.dokkaType, - sourceSet.toSet(), + sourceSet.toSet, PropertyContainer.Companion.empty() .plus(ParameterExtension(isExtendedSymbol, isGrouped)) .plus(MemberExtension.empty.copy(annotations = argument.symbol.getAnnotations())) @@ -287,8 +287,8 @@ trait ClassLikeSupport: Invariance(TypeParameter(argument.symbol.dri, variancePrefix + argument.symbol.name, null)), argument.symbol.documentation.asJava, null, - List(argument.rhs.dokkaType).asJava, - sourceSet.toSet(), + JList(argument.rhs.dokkaType), + sourceSet.toSet, PropertyContainer.Companion.empty() ) @@ -317,7 +317,7 @@ trait ClassLikeSupport: /*setter =*/ null, /*getter =*/ null, /*modifier =*/ placeholderModifier, - sourceSet.toSet(), + sourceSet.toSet, /*generics =*/ generics.asJava, // TODO /*isExpectActual =*/ false, PropertyContainer.Companion.empty() plus MemberExtension( @@ -355,8 +355,8 @@ trait ClassLikeSupport: /*setter =*/ null, /*getter =*/ null, /*modifier =*/ placeholderModifier, - sourceSet.toSet(), - /*generics =*/ Nil.asJava, + sourceSet.toSet, + /*generics =*/ JList(), /*isExpectActual =*/ false, PropertyContainer.Companion.empty().plus(MemberExtension( valDef.symbol.getVisibility(), diff --git a/scala3doc/src/dotty/dokka/tasty/PackageSupport.scala b/scala3doc/src/dotty/dokka/tasty/PackageSupport.scala index 01abbb289059..6dd08843364f 100644 --- a/scala3doc/src/dotty/dokka/tasty/PackageSupport.scala +++ b/scala3doc/src/dotty/dokka/tasty/PackageSupport.scala @@ -1,4 +1,5 @@ -package dotty.dokka.tasty +package dotty.dokka +package tasty import org.jetbrains.dokka.model._ import org.jetbrains.dokka.links._ @@ -18,10 +19,10 @@ trait PackageSupport: val documentation = pck.symbol.documentation DPackage( new DRI(name, null, null, PointingToDeclaration.INSTANCE, null), - Nil.asJava, - Nil.asJava, - Nil.asJava, - Nil.asJava, + JList(), + JList(), + JList(), + JList(), documentation.asJava, null, sourceSet.toSet, @@ -36,8 +37,8 @@ trait PackageSupport: new DRI(pckObj.symbol.dri.getPackageName, null, null, PointingToDeclaration.INSTANCE, null), clazz.getFunctions, clazz.getProperties, - Nil.asJava, - Nil.asJava, + JList(), + JList(), pckObj.symbol.documentation.asJava, null, sourceSet.toSet, diff --git a/scala3doc/src/dotty/dokka/tasty/SymOps.scala b/scala3doc/src/dotty/dokka/tasty/SymOps.scala index b4b5663fcbb9..bd7d589704c6 100644 --- a/scala3doc/src/dotty/dokka/tasty/SymOps.scala +++ b/scala3doc/src/dotty/dokka/tasty/SymOps.scala @@ -113,7 +113,7 @@ class SymOps[R <: Reflection](val r: R): new DRI( sym.packageName, sym.topLevelEntryName.orNull, // TODO do we need any of this fields? - method.map(s => new org.jetbrains.dokka.links.Callable(s.name, null, Nil.asJava)).orNull, + method.map(s => new org.jetbrains.dokka.links.Callable(s.name, null, JList())).orNull, pointsTo, // TODO different targets? s"${sym.show}/${sym.signature.resultSig}/[${sym.signature.paramSigs.mkString("/")}]" ) diff --git a/scala3doc/src/dotty/dokka/tasty/TastyParser.scala b/scala3doc/src/dotty/dokka/tasty/TastyParser.scala index 0a7f2621fbf1..33861eeb741a 100644 --- a/scala3doc/src/dotty/dokka/tasty/TastyParser.scala +++ b/scala3doc/src/dotty/dokka/tasty/TastyParser.scala @@ -14,12 +14,10 @@ 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.{List => JList} import quoted.QuoteContext import scala.tasty.inspector.TastyInspector import dotty.dokka.model.api.withNewMembers -import com.virtuslab.dokka.site.SourceSetWrapper /** Responsible for collectively inspecting all the Tasty files we're interested in. * @@ -124,8 +122,8 @@ trait DokkaBaseTastyInspector: f.getDri, f.getFunctions, f.getProperties, - Nil.asJava, // TODO add support for other things like type or package object entries - Nil.asJava, + JList(), // TODO add support for other things like type or package object entries + JList(), f.getDocumentation, null, sourceSet.toSet, @@ -137,22 +135,18 @@ trait DokkaBaseTastyInspector: }.toList extension (self: DPackage) def mergeWith(other: DPackage): DPackage = - val doc1 = self.getDocumentation.asScala.get(sourceSet.getSourceSet).map(_.getChildren).getOrElse(Nil.asJava) - val doc2 = other.getDocumentation.asScala.get(sourceSet.getSourceSet).map(_.getChildren).getOrElse(Nil.asJava) + def nodes(p: DPackage): JList[TagWrapper] = p.getDocumentation.get(sourceSet) match + case null => JList[TagWrapper]() + case node => node.getChildren + mergeExtras( DPackage( self.getDri, (self.getFunctions.asScala ++ other.getFunctions.asScala).asJava, (self.getProperties.asScala ++ other.getProperties.asScala).asJava, - Nil.asJava, // WARNING Merging is done before collecting classlikes, if it changes it needs to be refactored - Nil.asJava, - sourceSet.asMap( - DocumentationNode( - ( - doc1.asScala ++ doc2.asScala - ).asJava - ) - ), + JList(), // WARNING Merging is done before collecting classlikes, if it changes it needs to be refactored + JList(), + sourceSet.toMap(DocumentationNode(nodes(self) ++ nodes(other))), null, sourceSet.toSet, PropertyContainer.Companion.empty() diff --git a/scala3doc/src/dotty/dokka/tasty/comments/MarkdownConverter.scala b/scala3doc/src/dotty/dokka/tasty/comments/MarkdownConverter.scala index fda6e78f2f7b..f2e22e6896ea 100644 --- a/scala3doc/src/dotty/dokka/tasty/comments/MarkdownConverter.scala +++ b/scala3doc/src/dotty/dokka/tasty/comments/MarkdownConverter.scala @@ -1,4 +1,5 @@ -package dotty.dokka.tasty.comments +package dotty.dokka +package tasty.comments import scala.jdk.CollectionConverters._ import scala.tasty.Reflection @@ -40,7 +41,8 @@ class MarkdownConverter(val repr: Repr) extends BaseConverter { 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, kt.emptyMap) + 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) diff --git a/scala3doc/src/dotty/dokka/tasty/comments/package.scala b/scala3doc/src/dotty/dokka/tasty/comments/package.scala index 55cc90954528..aba26cb94d98 100644 --- a/scala3doc/src/dotty/dokka/tasty/comments/package.scala +++ b/scala3doc/src/dotty/dokka/tasty/comments/package.scala @@ -1,4 +1,5 @@ -package dotty.dokka.tasty.comments +package dotty.dokka +package tasty.comments import scala.jdk.CollectionConverters._ @@ -17,7 +18,7 @@ object dkk: def p(params: (String, String)*)(children: dkkd.DocTag*) = dkkd.P(children.asJava, params.toMap.asJava) - def text(str: String) = dkkd.Text(str, Nil.asJava, Map.empty.asJava) + def text(str: String) = dkkd.Text(str, JList(), Map.empty.asJava) def a(children: dkkd.DocTag*) = dkkd.A(children.asJava, Map.empty.asJava) diff --git a/scala3doc/src/dotty/dokka/transformers/ScalaSourceLinksTransformer.scala b/scala3doc/src/dotty/dokka/transformers/ScalaSourceLinksTransformer.scala index 0815ab5b6011..dec0bcb6aa14 100644 --- a/scala3doc/src/dotty/dokka/transformers/ScalaSourceLinksTransformer.scala +++ b/scala3doc/src/dotty/dokka/transformers/ScalaSourceLinksTransformer.scala @@ -24,10 +24,10 @@ class ScalaSourceLinksTransformer( val sourceLinks = ctx.getConfiguration.getSourceSets.asScala.flatMap(s => s.getSourceLinks.asScala.map(l => SourceLink(l, s))) val pageBuilder = ScalaPageContentBuilder(commentsToContentConverter, signatureProvider, logger) - case class SourceLink(val path: String, val url: String, val lineSuffix: Option[String], val sourceSetData: DokkaConfiguration.DokkaSourceSet) + case class SourceLink(val path: String, val url: String, val lineSuffix: Option[String], val sourceSetData: DokkaSourceSet) object SourceLink { - def apply(sourceLinkDef: DokkaConfiguration$SourceLinkDefinition, sourceSetData: DokkaConfiguration.DokkaSourceSet): SourceLink = + def apply(sourceLinkDef: DokkaConfiguration$SourceLinkDefinition, sourceSetData: DokkaSourceSet): SourceLink = SourceLink(sourceLinkDef.getLocalDirectory, sourceLinkDef.getRemoteUrl.toString, Option(sourceLinkDef.getRemoteLineSuffix), sourceSetData) } diff --git a/scala3doc/src/dotty/dokka/translators/ScalaContentBuilder.scala b/scala3doc/src/dotty/dokka/translators/ScalaContentBuilder.scala index af5bc9a83405..e15296aa88a8 100644 --- a/scala3doc/src/dotty/dokka/translators/ScalaContentBuilder.scala +++ b/scala3doc/src/dotty/dokka/translators/ScalaContentBuilder.scala @@ -370,7 +370,7 @@ class ScalaPageContentBuilder( address, DCI(mainDRI.asJava, kind), sourceSets.toDisplay, - Set().asJava, + JSet(), PropertyContainer.Companion.empty() ) ) @@ -388,7 +388,7 @@ class ScalaPageContentBuilder( address, DCI(mainDRI.asJava, kind), sourceSets.toDisplay, - Set().asJava, + JSet(), PropertyContainer.Companion.empty() ) ) @@ -407,7 +407,7 @@ class ScalaPageContentBuilder( address, DCI(mainDRI.asJava, kind), sourceSets.toDisplay, - Set().asJava, + JSet(), PropertyContainer.Companion.empty() ) ) @@ -434,7 +434,7 @@ class ScalaPageContentBuilder( docTag, DCI(mainDRI.asJava, kind), sourceSets.asJava, - Set().asJava, + JSet(), PropertyContainer.Companion.empty() ).asScala.toSeq diff --git a/scala3doc/src/dotty/dokka/utils.scala b/scala3doc/src/dotty/dokka/utils.scala index bf6d272aae43..67986989ad29 100644 --- a/scala3doc/src/dotty/dokka/utils.scala +++ b/scala3doc/src/dotty/dokka/utils.scala @@ -13,24 +13,10 @@ 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 java.util.{List => JList, Set => JSet, Map => JMap} import org.jetbrains.dokka.DokkaConfiguration$DokkaSourceSet import org.jetbrains.dokka.plugability._ import kotlin.jvm.JvmClassMappingKt.getKotlinClass -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 = // TODO remove some of the InstanceOf - a.withNewExtras(a.getExtra plus value).asInstanceOf[E] - -extension [V] (map: JMap[DokkaConfiguration$DokkaSourceSet, V]): - def defaultValue: V = map.values.asScala.toSeq(0) - -extension (sourceSets: Set[DokkaConfiguration$DokkaSourceSet]): - def toDisplay = sourceSets.map(DisplaySourceSet(_)).asJava - class BaseKey[T, V] extends ExtraProperty.Key[T, V]: override def mergeStrategyFor(left: V, right: V): MergeStrategy[T] = MergeStrategy.Remove.INSTANCE.asInstanceOf[MergeStrategy[T]] @@ -51,14 +37,6 @@ def getFromExtra[V](e: WithExtraProperties[_], k: ExtraProperty.Key[_, V]): Opti extension (f: DFunction): def isRightAssociative(): Boolean = f.getName.endsWith(":") -object JList: - def apply[T](elem: T): JList[T] = List(elem).asJava - def apply[T]() = List[T]().asJava - -object JSet: - def apply[T](elem: T): JSet[T] = Set(elem).asJava - def apply[T]() = Set[T]().asJava - def modifyContentGroup(originalContentNodeWithParents: Seq[ContentGroup], modifiedContentNode: ContentGroup): ContentGroup = originalContentNodeWithParents match { case head :: tail => tail match { diff --git a/scala3doc/src/dotty/renderers/ScalaHtmlRenderer.scala b/scala3doc/src/dotty/renderers/ScalaHtmlRenderer.scala index c145a9de75b1..32dbd744f530 100644 --- a/scala3doc/src/dotty/renderers/ScalaHtmlRenderer.scala +++ b/scala3doc/src/dotty/renderers/ScalaHtmlRenderer.scala @@ -3,21 +3,24 @@ 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._ import HTML._ import collection.JavaConverters._ -import com.virtuslab.dokka.site.SiteRenderer -import com.virtuslab.dokka.site.BaseStaticSiteProcessor import java.net.URI 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 org.jetbrains.dokka.links.DRI import dotty.dokka.model.api.Link import dotty.dokka.model.api.HierarchyGraph import org.jetbrains.dokka.base.resolvers.local.LocationProvider - +import dotty.dokka.site.StaticPageNode +import dotty.dokka.site.PartiallyRenderedContent class SignatureRenderer(pageContext: ContentPage, sourceSetRestriciton: JSet[DisplaySourceSet], locationProvider: LocationProvider): def link(dri: DRI): Option[String] = Option(locationProvider.resolve(dri, sourceSetRestriciton, pageContext)) @@ -35,7 +38,27 @@ class SignatureRenderer(pageContext: ContentPage, sourceSetRestriciton: JSet[Dis def renderElement(e: String | (String, DRI) | Link) = renderElementWith(e) -class ScalaHtmlRenderer(ctx: DokkaContext) extends SiteRenderer(ctx) { +class ScalaHtmlRenderer(ctx: DokkaContext) extends HtmlRenderer(ctx) { + + // 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 @@ -166,7 +189,7 @@ class ScalaHtmlRenderer(ctx: DokkaContext) extends SiteRenderer(ctx) { ): Unit = { import kotlinx.html.{Gen_consumer_tagsKt => dsl} val c = f.getConsumer - val U = kotlin.Unit.INSTANCE + dsl.a(c, node.getAddress, /*target*/ null, /*classes*/ null, { e => import ScalaCommentToContentConverter._ // node.getExtra.getMap.asScala.get(LinkAttributesKey) @@ -184,9 +207,9 @@ class ScalaHtmlRenderer(ctx: DokkaContext) extends SiteRenderer(ctx) { 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 - val U = kotlin.Unit.INSTANCE dsl.div(c, "sample-container", { e => dsl.pre(c, null, { e => @@ -210,26 +233,35 @@ class ScalaHtmlRenderer(ctx: DokkaContext) extends SiteRenderer(ctx) { ).toString() ) + 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 => + withHtml(context, prc.resolved.code) + case content => + build(content, context, page, /*sourceSetRestriction=*/null) + + override def buildHtml(page: PageNode, resources: JList[String], kotlinxContent: FlowContentConsumer): String = - val (pageTitle, pageResources, fromTemplate) = page match - case static: BaseStaticSiteProcessor.StaticPageNode => - val res = if static.hasFrame then resources else static.resources - val title = static.getLoadedTemplate.getTemplateFile.title - (title, res, !static.hasFrame) + val (pageTitle, noFrame) = page match + case static: StaticPageNode => + (static.template.title(), !static.hasFrame()) case _ => - (page.getName, resources, false) + (page.getName, false) + html( head( meta(charset := "utf-8"), meta(name := "viewport", content := "width=device-width, initial-scale=1"), title(pageTitle), - linkResources(page, pageResources.asScala).toSeq, + linkResources(page, resources.asScala).toSeq, script(raw(s"""var pathToRoot = "${getLocationProvider.pathToRoot(page)}";""")) ), body( - if fromTemplate then - raw(buildWithKotlinx(kotlinxContent)) - else + if noFrame then raw(buildWithKotlinx(kotlinxContent)) else div(id := "container")( div(id := "leftColumn")( div(id := "logo"), @@ -295,5 +327,4 @@ class ScalaHtmlRenderer(ctx: DokkaContext) extends SiteRenderer(ctx) { null, func ).toString.stripPrefix("
").stripSuffix("
\n") - } diff --git a/scala3doc/test/dotty/dokka/DottyTestRunner.scala b/scala3doc/test/dotty/dokka/DottyTestRunner.scala index 1345ac644288..a9ad7e92e4e6 100644 --- a/scala3doc/test/dotty/dokka/DottyTestRunner.scala +++ b/scala3doc/test/dotty/dokka/DottyTestRunner.scala @@ -66,7 +66,7 @@ abstract class DottyAbstractCoreTest extends AbstractCoreTest: config, new TestLogger(DokkaConsoleLogger.INSTANCE), tests.build(), - Nil.asJava + JList() ).generate() signatures diff --git a/scala3doc/test/dotty/dokka/site/TemplateFileTests.scala b/scala3doc/test/dotty/dokka/site/TemplateFileTests.scala new file mode 100644 index 000000000000..ecdb58f4432a --- /dev/null +++ b/scala3doc/test/dotty/dokka/site/TemplateFileTests.scala @@ -0,0 +1,218 @@ +package dotty.dokka.site + +import com.vladsch.flexmark.html.HtmlRenderer +import com.vladsch.flexmark.parser.Parser +import org.junit.Assert.assertEquals +import org.junit.Test +import java.nio.file.Files + +class TemplateFileTests: + private def testTemplate(code: String, ext: String = "html")(op: TemplateFile => Unit): Unit = + val tmpFile = Files.createTempFile("headerTests", s".${ext}").toFile() + try + Files.write(tmpFile.toPath, code.getBytes) + op(loadTemplateFile(tmpFile)) + finally tmpFile.delete() + + + private def testTemplates( + props: Map[String, String], + template: List[(String, String)])( + op: RenderingContext => Unit + ) = + def rec(cxt: RenderingContext, remaining: List[(String, String)]): Unit = + if (remaining.isEmpty) op(cxt) + else + val (code, ext) = remaining.head + testTemplate(code, ext) { template => + val newCtx = cxt.copy(layouts = cxt.layouts + (template.name() -> template)) + rec(newCtx, remaining.drop(1)) + } + + rec(RenderingContext(props), template) + + private def fullRender(template: TemplateFile, ctx: RenderingContext): String = template.resolveInner(ctx).code.trim() + + @Test + def testParsingHeaders(): Unit = + testTemplate( + """--- + |title: myTitle + |--- + |code""".stripMargin + ) { t => + assertEquals(t.rawCode, "code") + assertEquals(t.title(), "myTitle") + } + + + @Test + def testLinks(): Unit = + val base = + """--- + |title: myTitle + |name: base + |--- + |Ala {{ content }}. {{p2}} with [link](link/target.md)! + |""".stripMargin + + val content = + """--- + |layout: base + |name: content + |--- + |ma kota w **{{ p1 }}** from [here](link/here.md) + |""".stripMargin + + + val expected = """

Ala ma kota w paski from here. Hej with link!

""" + + testTemplates( + Map("p1" -> "paski", "p2" -> "Hej"), + List(base -> "html", content -> "md") + ) { it => + assertEquals( + expected, + fullRender(it.layouts("content"), it) + ) + } + + @Test + def layout(): Unit = + val base = + """--- + |title: myTitle + |name: base + |--- + |Ala {{ content }}. {{p2}}! + |""".stripMargin + + val content = + """--- + |layout: base + |name: content + |--- + |ma kota w **{{ p1 }}** + |""".stripMargin + + + val expected = """

Ala ma kota w paski. Hej!

""".stripMargin + + testTemplates( + Map("p1" -> "paski", "p2" -> "Hej"), + List(base -> "html", content -> "md") + ) { it => + assertEquals( + expected, + fullRender(it.layouts("content"), it) + ) + } + + @Test + def nestedLayout_htmlMdHtml(): Unit = + val toplevel = + """--- + |name: toplevel + |--- + |[div id="root"]{{ content }}[/div] + |""".stripMargin + + val basePage = + """--- + |layout: toplevel + |name: basePage + |--- + |# {{ pageName }} + | + |{{content}} + | + |## {{ pageName }} end + |""".stripMargin + + val content = + """--- + |layout: basePage + |name: content + |--- + |Hello {{ name }}! + |""".stripMargin + + + val expected = + """[div id="root"][h1]Test page[/h1] + |[p]Hello world!![/p] + |[h2]Test page end[/h2] + |[/div]""".stripMargin + + testTemplates( + Map("pageName" -> "Test page", "name" -> "world!"), + List( + toplevel -> "html", + basePage -> "md", + content -> "md" + ) + ) (it => fullRender(it.layouts("content"), it)) + + @Test + def nestedLayout_mdHtmlMd(): Unit = + val toplevel = + """--- + |name: toplevel + |--- + |

The Page

+ |{{ content }} + |""".stripMargin + + val basePage = + """--- + |layout: toplevel + |name: basePage + |--- + |

{{ pageName }}

+ | + |{{content}} + | + |

{{ pageName }} end

+ |""".stripMargin + + val content = + """--- + |layout: basePage + |name: content + |--- + |Hello {{ name }}! + |""".stripMargin + + + val expected = + """

The Page

+ |

Test page

+ |

Hello world!!

+ |

Test page end

""".stripMargin + + testTemplates( + Map("pageName" -> "Test page", "name" -> "world!"), + List( + toplevel -> "html", + basePage -> "html", + content -> "md" + ) + ) { ctx => assertEquals(expected, fullRender(ctx.layouts("content"), ctx)) } + + @Test + def markdown(): Unit = + testTemplate( + """# Hello {{ msg }}!""", + ext = "md" + ) { t => + assertEquals("# Hello there!", t.resolveInner(RenderingContext(Map("msg" -> "there"))).code.trim()) + } + + @Test + def mixedTemplates() : Unit = + testTemplate( + """# Hello {{ msg }}!""", + ext = "md" + ) { t => + assertEquals("# Hello there!", t.resolveInner(RenderingContext(Map("msg" -> "there"))).code.trim()) + } \ No newline at end of file