diff --git a/docs/docs/reference/new-types/polymorphic-function-types.md b/docs/docs/reference/new-types/polymorphic-function-types.md index c555a83df89d..e983cd37aa95 100644 --- a/docs/docs/reference/new-types/polymorphic-function-types.md +++ b/docs/docs/reference/new-types/polymorphic-function-types.md @@ -80,7 +80,7 @@ println(e1) // Apply(Apply(Var(wrap),Var(f)),Apply(Var(wrap),Var(a))) ### Relationship With Type Lambdas Polymorphic function types are not to be confused with -[_type lambdas_](new-types/type-lambdas.md). +[_type lambdas_](type-lambdas.md). While the former describes the _type_ of a polymorphic _value_, the latter is an actual function value _at the type level_. diff --git a/project/Build.scala b/project/Build.scala index bd4ed3fb966a..8aee10f4fc20 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1565,7 +1565,14 @@ object Build { generateDocumentation( classDirectory.in(Compile).value.getAbsolutePath, "scala3doc", "scala3doc/output/self", VersionUtil.gitHash, - "-siteroot scala3doc/documentation -project-logo scala3doc/documentation/logo.svg") + "-siteroot scala3doc/documentation -project-logo scala3doc/documentation/logo.svg " + + "-external-mappings " + raw".*scala.*" + "::" + + "scala3doc" + "::" + + "http://dotty.epfl.ch/api/" + ":::" + + raw".*java.*" + "::" + + "javadoc" + "::" + + "https://docs.oracle.com/javase/8/docs/api/" + ) }.value, generateScala3Documentation := Def.inputTaskDyn { @@ -1589,7 +1596,11 @@ object Build { IO.write(dest / "CNAME", "dotty.epfl.ch") }.dependsOn(generateDocumentation( roots, "Scala 3", dest.getAbsolutePath, "master", - "-comment-syntax wiki -siteroot scala3doc/scala3-docs -project-logo scala3doc/scala3-docs/logo.svg")) + "-comment-syntax wiki -siteroot scala3doc/scala3-docs -project-logo scala3doc/scala3-docs/logo.svg " + + "-external-mappings " + raw".*java.*" + "::" + + "javadoc" + "::" + + "https://docs.oracle.com/javase/8/docs/api/" + )) }.evaluated, generateTestcasesDocumentation := Def.taskDyn { diff --git a/scala3doc-testcases/src/tests/extensionMethodSignatures.scala b/scala3doc-testcases/src/tests/extensionMethodSignatures.scala index e31c57bbca18..50152cc2ef1d 100644 --- a/scala3doc-testcases/src/tests/extensionMethodSignatures.scala +++ b/scala3doc-testcases/src/tests/extensionMethodSignatures.scala @@ -17,6 +17,8 @@ class ClassOne extension (c: ClassTwo) def |||:(a: Int, b: Int, d: Int)(e: String): Int = 56 + def ++:(a: Int): Int + = 45 extension (b: Int) def secondGroup(): String diff --git a/scala3doc-testcases/src/tests/givenSignatures.scala b/scala3doc-testcases/src/tests/givenSignatures.scala index 4b0d815cc86b..a1143e908da9 100644 --- a/scala3doc-testcases/src/tests/givenSignatures.scala +++ b/scala3doc-testcases/src/tests/givenSignatures.scala @@ -2,43 +2,15 @@ package tests package givenSignatures +object Obj +given Seq[String] = Nil -class GivenClass { - trait B - trait C[T] - val r: Int = 5 - type R = Int - given R = r - trait Ord[T] { - def compare(x: T, y: T): Int - extension (x: T) def < (y: T) = compare(x, y) < 0 - extension (x: T) def > (y: T) = compare(x, y) > 0 - } - given intOrd: Ord[Int] with { - def compare(x: Int, y: Int) = - if (x < y) -1 else if (x > y) +1 else 0 - } +given GivenType = GivenType() - given asd(using int: Int): B with {} +class GivenType - given asd2[T]: C[T] with {} - - given listOrd[T](using ord: Ord[T]): Ord[List[T]] with { - - def compare(xs: List[T], ys: List[T]): Int = (xs, ys) match - case (Nil, Nil) => 0 - case (Nil, _) => -1 - case (_, Nil) => +1 - case (x :: xs1, y :: ys1) => - val fst = ord.compare(x, y) - if (fst != 0) fst else compare(xs1, ys1) - } - - given IntOps: Int.type = Int - - given GivenType = GivenType() - - class GivenType -} +trait Ord[T] +given listOrd[T](using ord: Ord[T]): Ord[List[T]] + = ??? diff --git a/scala3doc-testcases/src/tests/givenSignaturesPg.scala b/scala3doc-testcases/src/tests/givenSignaturesPg.scala new file mode 100644 index 000000000000..0ad12ecf6715 --- /dev/null +++ b/scala3doc-testcases/src/tests/givenSignaturesPg.scala @@ -0,0 +1,44 @@ +package tests + +package givenSignaturesPg + + + +class GivenClass { + trait B + trait C[T] + val r: Int = 5 + type R = Int + given R = r + trait Ord[T] { + def compare(x: T, y: T): Int + extension (x: T) def < (y: T) = compare(x, y) < 0 + extension (x: T) def > (y: T) = compare(x, y) > 0 + } + given intOrd: Ord[Int] with { + def compare(x: Int, y: Int) = + if (x < y) -1 else if (x > y) +1 else 0 + } + + given asd(using int: Int): B with {} + + given asd2[T]: C[T] with {} + + given listOrd[T](using ord: Ord[T]): Ord[List[T]] with { + + def compare(xs: List[T], ys: List[T]): Int = (xs, ys) match + case (Nil, Nil) => 0 + case (Nil, _) => -1 + case (_, Nil) => +1 + case (x :: xs1, y :: ys1) => + val fst = ord.compare(x, y) + if (fst != 0) fst else compare(xs1, ys1) + } + + given IntOps: Int.type = Int + + given GivenType = GivenType() + + class GivenType +} + diff --git a/scala3doc-testcases/src/tests/methodsAndConstructors.scala b/scala3doc-testcases/src/tests/methodsAndConstructors.scala index f9d48b88e77a..d0f3386730bc 100644 --- a/scala3doc-testcases/src/tests/methodsAndConstructors.scala +++ b/scala3doc-testcases/src/tests/methodsAndConstructors.scala @@ -47,3 +47,10 @@ class Methods: def arrays(a: Array[String], b: Array[Int]): Array[Double] = ??? + + def rightA1(a: Int): Int + = ??? + + def ++:(a: Int)(b: Double): Int + = ??? + diff --git a/scala3doc-testcases/src/tests/modifiersSignatureTestSource.scala b/scala3doc-testcases/src/tests/modifiersSignatureTestSource.scala index ef6faecf9232..3cb250ae172b 100644 --- a/scala3doc-testcases/src/tests/modifiersSignatureTestSource.scala +++ b/scala3doc-testcases/src/tests/modifiersSignatureTestSource.scala @@ -24,6 +24,9 @@ abstract class Methods() implicit def toImplicitString(): String = "asd" + + inline def method2(inline name: String): String + = "ala" } class ImplementedMethods() extends Methods/*<-*/()/*->*/ diff --git a/scala3doc-testcases/src/tests/nestingDRI.scala b/scala3doc-testcases/src/tests/nestingDRI.scala new file mode 100644 index 000000000000..4954c23aa656 --- /dev/null +++ b/scala3doc-testcases/src/tests/nestingDRI.scala @@ -0,0 +1,16 @@ +package tests.nestingDRI + +trait TestClass + +class A: + class B + object B: + object C + class C: + object D + + +class AA: + object B: + class C: + object D \ No newline at end of file diff --git a/scala3doc-testcases/src/toplevel.scala b/scala3doc-testcases/src/toplevel.scala new file mode 100644 index 000000000000..764e9cbaf9cf --- /dev/null +++ b/scala3doc-testcases/src/toplevel.scala @@ -0,0 +1,3 @@ +def toplevelDef = 123 + +class ToplevelClass \ No newline at end of file diff --git a/scala3doc/resources/dotty_res/scripts/ux.js b/scala3doc/resources/dotty_res/scripts/ux.js index 3bb71dd03d89..185b2a718d44 100644 --- a/scala3doc/resources/dotty_res/scripts/ux.js +++ b/scala3doc/resources/dotty_res/scripts/ux.js @@ -5,6 +5,24 @@ window.addEventListener("DOMContentLoaded", () => { document.getElementById("leftColumn").classList.toggle("open"); }; } + + var elements = document.getElementsByClassName("documentableElement") + if (elements) { + for (i = 0; i < elements.length; i++) { + elements[i].onclick = function(){ + this.classList.toggle("expand") + } + } + } + + + if (location.hash) { + var selected = document.getElementById(location.hash.substring(1)); + if (selected){ + selected.classList.toggle("expand"); + } + } + var logo = document.getElementById("logo"); if (logo) { logo.onclick = function() { diff --git a/scala3doc/resources/dotty_res/styles/scalastyle.css b/scala3doc/resources/dotty_res/styles/scalastyle.css index ee46f28d78c0..6e17a18f45d2 100644 --- a/scala3doc/resources/dotty_res/styles/scalastyle.css +++ b/scala3doc/resources/dotty_res/styles/scalastyle.css @@ -12,6 +12,9 @@ --inactive-fg: #777; --title-fg: #00485E; + --link-sig-fd: #7c99a5; + --link-sig-hover-fd: #7c99a5; + --leftbar-bg: #003048; --leftbar-fg: #fff; --leftbar-current-bg: #0090BB; @@ -364,7 +367,7 @@ dl.attributes > dt.implicit { } dl.attributes > dd { display: block; - padding-left: 10em; + padding-left: 6em; margin-bottom: 5px; min-height: 15px; } @@ -437,11 +440,43 @@ footer .pull-right { margin-left: auto; } + .modifiers { - width: 12em; display: table-cell; - text-align: right; padding-right: 0.5em; + min-width: 10em; + max-width: 10em; + overflow: hidden; + direction: rtl; + white-space: nowrap; + text-indent: 0em; +} + +.modifiers .other-modifiers { + color: gray; +} + +.other-modifiers a, .other-modifiers a:visited, .other-modifiers span[data-unresolved-link] { + color: var(--link-sig-fd); +} + +.expand .modifiers { + display: inline-table; + min-width: 7em; +} + +.signature { + color: gray; + display: table-cell; + padding-left: 0.5em; +} + +.signature a, .signature a:visited, .signature span[data-unresolved-link] { + color: var(--link-sig-fd); +} + +.expand .signature { + display: inline; } .documentableElement { @@ -454,15 +489,57 @@ footer .pull-right { font-weight: 500; font-size: 12px; background: var(--code-bg); + border: 0.25em solid white; } .documentableElement>div { display: table; } +.expand.documentableElement>div { + display: block; + padding-left: 3em; +} + +.expand.documentableElement>div.header { + display: block; + padding-left: 7.5em; + text-indent: -4.5em; +} + +.documentableElement>div .cover { + display: none; +} + +.documentableElement.expand>div .cover { + display: block; +} + +.expand .doc { + margin-left: 6.5em; +} + +.doc code { + padding: 0; +} + +.documentableElement:hover { + cursor: pointer; + border-left: 0.25em solid var(--leftbar-bg); +} + +.expand.documentableElement { + border-left: 0.25em solid var(--leftbar-bg); +} .annotations { - margin-left: 9em; + color: gray; + display: none; } + +.expand .annotations { + display: inline-block; +} + .documentableAnchor { position: absolute; width: 24px; diff --git a/scala3doc/src/dotty/dokka/DocContext.scala b/scala3doc/src/dotty/dokka/DocContext.scala index 2922d1aa3a7f..d6752cb4e889 100644 --- a/scala3doc/src/dotty/dokka/DocContext.scala +++ b/scala3doc/src/dotty/dokka/DocContext.scala @@ -18,6 +18,8 @@ import dotty.tools.dotc.util.Spans import java.io.ByteArrayOutputStream import java.io.PrintStream import scala.io.Codec +import java.net.URL +import scala.util.Try type CompilerContext = dotty.tools.dotc.core.Contexts.Context @@ -75,8 +77,8 @@ case class DocContext(args: Scala3doc.Args, compilerContext: CompilerContext) override def getOfflineMode: Boolean = false override def getFailOnWarning: Boolean = false override def getSourceSets: JList[DokkaSourceSet] = JList(mkSourceSet) - override def getModules: JList[DokkaConfiguration.DokkaModuleDescription] = JList() - override def getPluginsClasspath: JList[File] = JList() + override def getModules: JList[DokkaConfiguration.DokkaModuleDescription] = JNil + override def getPluginsClasspath: JList[File] = JNil override def getModuleName(): String = "ModuleName" override def getModuleVersion(): String = "" @@ -93,14 +95,60 @@ case class DocContext(args: Scala3doc.Args, compilerContext: CompilerContext) sourceLinks )(using compilerContext)) + def parseDocTool(docTool: String) = docTool match { + case "scaladoc" => Some(DocumentationKind.Scaladoc) + case "scala3doc" => Some(DocumentationKind.Scala3doc) + case "javadoc" => Some(DocumentationKind.Javadoc) + case other => None + } + val externalDocumentationLinks: List[Scala3docExternalDocumentationLink] = args.externalMappings.filter(_.size >= 3).flatMap { mapping => + val regexStr = mapping(0) + val docTool = mapping(1) + val urlStr = mapping(2) + val packageListUrlStr = if mapping.size > 3 then Some(mapping(3)) else None + val regex = Try(regexStr.r).toOption + val url = Try(URL(urlStr)).toOption + val packageListUrl = Try(packageListUrlStr.map(URL(_))) + .fold( + e => { + logger.warn(s"Wrong packageListUrl parameter in external mapping. Found '$packageListUrlStr'. " + + s"Package list url will be omitted") + None}, + res => res + ) + + val parsedDocTool = parseDocTool(docTool) + val res = if regexStr.isEmpty then + logger.warn(s"Wrong regex parameter in external mapping. Found '$regexStr'. Mapping will be omitted") + None + else if url.isEmpty then + logger.warn(s"Wrong url parameter in external mapping. Found '$urlStr'. Mapping will be omitted") + None + else if parsedDocTool.isEmpty then + logger.warn(s"Wrong doc-tool parameter in external mapping. " + + s"Expected one of: 'scaladoc', 'scala3doc', 'javadoc'. Found:'$docTool'. Mapping will be omitted " + ) + None + else + Some( + Scala3docExternalDocumentationLink( + List(regexStr.r), + URL(urlStr), + parsedDocTool.get, + packageListUrlStr.map(URL(_)) + ) + ) + res + } + override def getPluginsConfiguration: JList[DokkaConfiguration.PluginConfiguration] = - JList() + JNil val mkSourceSet: DokkaSourceSet = new DokkaSourceSetImpl( /*displayName=*/ args.name, /*sourceSetID=*/ new DokkaSourceSetID(args.name, "main"), - /*classpath=*/ JList(), + /*classpath=*/ JNil, /*sourceRoots=*/ JSet(), /*dependentSourceSets=*/ JSet(), /*samples=*/ JSet(), diff --git a/scala3doc/src/dotty/dokka/DottyDokkaPlugin.scala b/scala3doc/src/dotty/dokka/DottyDokkaPlugin.scala index 4584c16e7962..302dff9cb896 100644 --- a/scala3doc/src/dotty/dokka/DottyDokkaPlugin.scala +++ b/scala3doc/src/dotty/dokka/DottyDokkaPlugin.scala @@ -22,6 +22,7 @@ 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 @@ -71,6 +72,7 @@ class DottyDokkaPlugin extends DokkaJavaPlugin: val scalaResourceInstaller = extend( _.extensionPoint(dokkaBase.getHtmlPreprocessors) .fromRecipe{ case ctx @ given DokkaContext => new ScalaResourceInstaller } + .name("scalaResourceInstaller") .after(dokkaBase.getCustomResourceInstaller) ) @@ -120,8 +122,9 @@ class DottyDokkaPlugin extends DokkaJavaPlugin: val implicitMembersExtensionTransformer = extend( _.extensionPoint(CoreExtensions.INSTANCE.getDocumentableTransformer) - .fromRecipe(ImplicitMembersExtensionTransformer(_)) - .name("implicitMembersExtensionTransformer") + .fromRecipe { case ctx @ given DokkaContext => + new ImplicitMembersExtensionTransformer + }.name("implicitMembersExtensionTransformer") ) val customDocumentationProvider = extend( @@ -169,6 +172,21 @@ class DottyDokkaPlugin extends DokkaJavaPlugin: .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] = diff --git a/scala3doc/src/dotty/dokka/Scala3doc.scala b/scala3doc/src/dotty/dokka/Scala3doc.scala index df978a316a5a..b3e100f1660f 100644 --- a/scala3doc/src/dotty/dokka/Scala3doc.scala +++ b/scala3doc/src/dotty/dokka/Scala3doc.scala @@ -64,7 +64,8 @@ object Scala3doc: projectLogo: Option[String] = None, defaultSyntax: CommentSyntax = CommentSyntax.Markdown, sourceLinks: List[String] = Nil, - revision: Option[String] = None + revision: Option[String] = None, + externalMappings: List[List[String]] = List.empty ) def run(args: Array[String], rootContext: CompilerContext): Reporter = diff --git a/scala3doc/src/dotty/dokka/Scala3docArgs.scala b/scala3doc/src/dotty/dokka/Scala3docArgs.scala index b71594cf4480..f14c2efd1e79 100644 --- a/scala3doc/src/dotty/dokka/Scala3docArgs.scala +++ b/scala3doc/src/dotty/dokka/Scala3docArgs.scala @@ -34,7 +34,10 @@ class Scala3docArgs extends SettingGroup with CommonScalaSettings: val revision: Setting[String] = StringSetting("-revision", "revision", "Revision (branch or ref) used to build project project", "") - def scala3docSpecificSettings: Set[Setting[_]] = Set(sourceLinks, syntax, revision) + val externalDocumentationMappings: Setting[String] = + StringSetting("-external-mappings", "external-mappings", "Mapping between regex matching class file and external documentation", "") + + def scala3docSpecificSettings: Set[Setting[_]] = Set(sourceLinks, syntax, revision, externalDocumentationMappings) object Scala3docArgs: def extract(args: List[String], rootCtx: CompilerContext):(Scala3doc.Args, CompilerContext) = @@ -95,6 +98,7 @@ object Scala3docArgs: CommentSyntax.default } } + val externalMappings = externalDocumentationMappings.get.split(":::").map(_.split("::").toList).toList unsupportedSettings.filter(s => s.get != s.default).foreach { s => report.warning(s"Setting ${s.name} is currently not supported.") @@ -115,6 +119,7 @@ object Scala3docArgs: projectLogo.nonDefault, parseSyntax, sourceLinks.nonDefault.fold(Nil)(_.split(",").toList), - revision.nonDefault + revision.nonDefault, + externalMappings ) (docArgs, newContext) \ No newline at end of file diff --git a/scala3doc/src/dotty/dokka/SourceLinks.scala b/scala3doc/src/dotty/dokka/SourceLinks.scala index 7dd8cadc9bac..19e127d699ec 100644 --- a/scala3doc/src/dotty/dokka/SourceLinks.scala +++ b/scala3doc/src/dotty/dokka/SourceLinks.scala @@ -6,7 +6,30 @@ import liqp.Template import dotty.dokka.model.api._ import dotty.tools.dotc.core.Contexts.Context -case class SourceLink(val path: Option[Path], val urlTemplate: Template) +trait SourceLink: + val path: Option[Path] = None + def render(path: String, operation: String, line: Option[Int]): String + +case class PrefixedSourceLink(val myPath: Path, nested: SourceLink) extends SourceLink: + export nested.render + override val path = Some(myPath) + +case class TemplateSourceLink(val urlTemplate: Template) extends SourceLink: + override val path: Option[Path] = None + override def render(path: String, operation: String, line: Option[Int]): String = + val config = java.util.HashMap[String, Object]() + config.put("path", path) + line.foreach(l => config.put("line", l.toString)) + config.put("operation", operation) + + urlTemplate.render(config) + +case class WebBasedSourceLink(prefix: String, revision: String) extends SourceLink: + override val path: Option[Path] = None + override def render(path: String, operation: String, line: Option[Int]): String = + val action = if operation == "view" then "blob" else operation + val linePart = line.fold("")(l => s"#L$l") + s"$prefix/$action/$revision/$path$linePart" object SourceLink: val SubPath = "([^=]+)=(.+)".r @@ -18,41 +41,39 @@ object SourceLink: "€{FILE_LINE}" -> "{{ line }}" ) - def githubTemplate(organization: String, repo: String)(revision: String) = - s"""https://github.com/$organization/$repo/{{ operation | replace: "view", "blob" }}/$revision/{{ path }}#L{{ line }}""".stripMargin + def githubPrefix(org: String, repo: String) = s"https://github.com/$org/$repo" - def gitlabTemplate(organization: String, repo: String)(revision: String) = - s"""https://gitlab.com/$organization/$repo/-/{{ operation | replace: "view", "blob" }}/$revision/{{ path }}#L{{ line }}""" + def gitlabPrefix(org: String, repo: String) = s"https://gitlab.com/$org/$repo/-" private def parseLinkDefinition(s: String): Option[SourceLink] = ??? def parse(string: String, revision: Option[String]): Either[String, SourceLink] = def asTemplate(template: String) = - try Right(SourceLink(None,Template.parse(template))) catch + try Right(TemplateSourceLink(Template.parse(template))) catch case e: RuntimeException => Left(s"Failed to parse template: ${e.getMessage}") string match case KnownProvider(name, organization, repo) => - def withRevision(template: String => String) = - revision.fold(Left(s"No revision provided"))(rev => Right(SourceLink(None, Template.parse(template(rev))))) + def withRevision(template: String => SourceLink) = + revision.fold(Left(s"No revision provided"))(r => Right(template(r))) name match case "github" => - withRevision(githubTemplate(organization, repo)) + withRevision(rev => WebBasedSourceLink(githubPrefix(organization, repo), rev)) case "gitlab" => - withRevision(gitlabTemplate(organization, repo)) + withRevision(rev => WebBasedSourceLink(gitlabPrefix(organization, repo), rev)) case other => Left(s"'$other' is not a known provider, please provide full source path template.") case SubPath(prefix, config) => parse(config, revision) match case l: Left[String, _] => l - case Right(SourceLink(Some(prefix), _)) => + case Right(_:PrefixedSourceLink) => Left(s"Source path $string has duplicated subpath setting (scm template can not contains '=')") - case Right(SourceLink(None, template)) => - Right(SourceLink(Some(Paths.get(prefix)), template)) + case Right(nested) => + Right(PrefixedSourceLink(Paths.get(prefix), nested)) case BrokenKnownProvider("gitlab" | "github") => Left(s"Does not match known provider syntax: `://organization/repository`") case scaladocSetting if ScalaDocPatten.findFirstIn(scaladocSetting).nonEmpty => @@ -70,15 +91,9 @@ type Operation = "view" | "edit" case class SourceLinks(links: Seq[SourceLink], projectRoot: Path): def pathTo(rawPath: Path, line: Option[Int] = None, operation: Operation = "view"): Option[String] = def resolveRelativePath(path: Path) = - links.find(_.path.forall(p => path.startsWith(p))).map { link => - val config = java.util.HashMap[String, Object]() - val pathString = path.toString.replace('\\', '/') - config.put("path", pathString) - line.foreach(l => config.put("line", l.toString)) - config.put("operation", operation) - - link.urlTemplate.render(config) - } + links + .find(_.path.forall(p => path.startsWith(p))) + .map(_.render(path.toString.replace('\\', '/'), operation, line)) if rawPath.isAbsolute then if rawPath.startsWith(projectRoot) then resolveRelativePath(projectRoot.relativize(rawPath)) diff --git a/scala3doc/src/dotty/dokka/compat.scala b/scala3doc/src/dotty/dokka/compat.scala index 944cffae9fb8..0e721da414e7 100644 --- a/scala3doc/src/dotty/dokka/compat.scala +++ b/scala3doc/src/dotty/dokka/compat.scala @@ -11,10 +11,10 @@ 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 - -def mkDRI(packageName: String = null, extra: String = null) = new DRI(packageName, null, null, PointingToDeclaration.INSTANCE, extra) +import org.jetbrains.dokka.links._ val U: kotlin.Unit = kotlin.Unit.INSTANCE @@ -28,9 +28,43 @@ type JMap[K, V] = java.util.Map[K, V] type JHashMap[K, V] = java.util.HashMap[K, V] type JMapEntry[K, V] = java.util.Map.Entry[K, V] +private val emptyListInst = Collections.emptyList +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 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) + +object DRI: + 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 diff --git a/scala3doc/src/dotty/dokka/externalDocumentationLinks.scala b/scala3doc/src/dotty/dokka/externalDocumentationLinks.scala new file mode 100644 index 000000000000..9d8ddfbe7d5a --- /dev/null +++ b/scala3doc/src/dotty/dokka/externalDocumentationLinks.scala @@ -0,0 +1,18 @@ +package dotty.dokka + +import org.jetbrains.dokka._ +import java.net.URL +import scala.util.matching._ + +case class Scala3docExternalDocumentationLink( + originRegexes: List[Regex], + documentationUrl: URL, + kind: DocumentationKind, + packageListUrl: Option[URL] = None +): + def withPackageList(url: URL): Scala3docExternalDocumentationLink = copy(packageListUrl = Some(url)) + +enum DocumentationKind: + case Javadoc extends DocumentationKind + case Scaladoc extends DocumentationKind + case Scala3doc extends DocumentationKind \ No newline at end of file diff --git a/scala3doc/src/dotty/dokka/location/ScalaExternalLocationProvider.scala b/scala3doc/src/dotty/dokka/location/ScalaExternalLocationProvider.scala new file mode 100644 index 000000000000..1db247b39a43 --- /dev/null +++ b/scala3doc/src/dotty/dokka/location/ScalaExternalLocationProvider.scala @@ -0,0 +1,54 @@ +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 +)(using ctx: DokkaContext) extends DefaultExternalLocationProvider(externalDocumentation, extension, ctx): + override def resolve(dri: DRI): String = + Option(externalDocumentation.getPackageList).map(_.getLocations.asScala.toMap).flatMap(_.get(dri.toString)) + .fold(constructPath(dri))( l => { + this.getDocURL + l + } + ) + + private val originRegex = raw"\[origin:(.*)\]".r + + override def constructPath(dri: DRI): String = kind match { + case DocumentationKind.Javadoc => constructPathForJavadoc(dri) + case DocumentationKind.Scaladoc => constructPathForScaladoc(dri) + case DocumentationKind.Scala3doc => constructPathForScala3doc(dri) + } + + private def constructPathForJavadoc(dri: DRI): String = { + val location = "\\$+".r.replaceAllIn(dri.location.replace(".","/"), _ => ".") + val origin = originRegex.findFirstIn(dri.extra) + val anchor = dri.anchor + getDocURL + location + extension + anchor.fold("")(a => s"#$a") + } + + private def constructPathForScaladoc(dri: DRI): String = { + val location = dri.location.replace(".","/") + val anchor = dri.anchor + getDocURL + location + extension + anchor.fold("")(a => s"#$a") + } + + private def constructPathForScala3doc(dri: DRI): String = { + val location = dri.location.replace(".","/") + val anchor = dri.anchor + getDocURL + location + anchor.fold(extension)(a => s"/$a$extension") + } diff --git a/scala3doc/src/dotty/dokka/location/ScalaExternalLocationProviderFactory.scala b/scala3doc/src/dotty/dokka/location/ScalaExternalLocationProviderFactory.scala new file mode 100644 index 000000000000..d448e07089b2 --- /dev/null +++ b/scala3doc/src/dotty/dokka/location/ScalaExternalLocationProviderFactory.scala @@ -0,0 +1,17 @@ +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 new file mode 100644 index 000000000000..7c73efbbd0ef --- /dev/null +++ b/scala3doc/src/dotty/dokka/location/ScalaPackageListService.scala @@ -0,0 +1,46 @@ +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 285a19fa4db4..299d3a48a0c7 100644 --- a/scala3doc/src/dotty/dokka/model/api/api.scala +++ b/scala3doc/src/dotty/dokka/model/api/api.scala @@ -8,6 +8,7 @@ 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): case Unrestricted extends Visibility("") @@ -53,20 +54,26 @@ trait ImplicitConversionProvider { def conversion: Option[ImplicitConversion] } trait Classlike enum Kind(val name: String){ - case Class extends Kind("class") with Classlike + case Class(typeParams: Seq[TypeParameter], argsLists: Seq[Seq[Parameter]]) + extends Kind("class") with Classlike case Object extends Kind("object") with Classlike - case Trait extends Kind("trait") with Classlike + case Trait(typeParams: Seq[TypeParameter], argsLists: Seq[Seq[Parameter]]) + extends Kind("trait") with Classlike case Enum extends Kind("enum") with Classlike - case EnumCase extends Kind("case") - case Def extends Kind("def") - case Extension(on: ExtensionTarget) extends Kind("def") - case Constructor extends Kind("def") + case EnumCase(kind: Object.type | Type | Val.type) extends Kind("case") + case Def(typeParams: Seq[TypeParameter], argsLists: Seq[Seq[Parameter]]) + extends Kind("def") + case Extension(on: ExtensionTarget, m: Kind.Def) extends Kind("def") + case Constructor(base: Kind.Def) extends Kind("def") case Var extends Kind("var") case Val extends Kind("val") - case Exported extends Kind("export") - case Type(concreate: Boolean, opaque: Boolean) extends Kind("Type") // should we handle opaque as modifier? - case Given(as: Option[Signature], conversion: Option[ImplicitConversion]) extends Kind("Given") with ImplicitConversionProvider - case Implicit(kind: Kind, conversion: Option[ImplicitConversion]) extends Kind(kind.name) with ImplicitConversionProvider + case Exported(m: Kind.Def) extends Kind("export") + case Type(concreate: Boolean, opaque: Boolean, typeParams: Seq[TypeParameter]) + extends Kind("Type") // should we handle opaque as modifier? + case Given(kind: Def | Class, as: Option[Signature], conversion: Option[ImplicitConversion]) + extends Kind("Given") with ImplicitConversionProvider + case Implicit(kind: Kind.Def | Kind.Val.type, conversion: Option[ImplicitConversion]) + extends Kind(kind.name) with ImplicitConversionProvider case Unknown extends Kind("Unknown") } @@ -91,6 +98,23 @@ object Annotation: case class LinkParameter(name: Option[String] = None, dri: DRI, value: String) extends AnnotationParameter case class UnresolvedParameter(name: Option[String] = None, unresolvedText: String) extends AnnotationParameter +case class Parameter( + annotations: Seq[Annotation], + modifiers: String, + name: String, + dri: DRI, + signature: Signature, + isExtendedSymbol: Boolean = false, + isGrouped: Boolean = false +) + +case class TypeParameter( + variance: "" | "+" | "-", + name: String, + dri: DRI, + signature: Signature +) + // TODO (longterm) properly represent signatures case class Link(name: String, dri: DRI) type Signature = Seq[String | Link] @@ -114,7 +138,7 @@ object HierarchyGraph: def withEdges(edges: Seq[(LinkToType, LinkToType)]) = HierarchyGraph.empty ++ edges -type Member = Documentable // with WithExtraProperty[_] // Kotlin does not add generics to ExtraProperty implemented by e.g. DFunction +type Member = Documentable object Member: def unapply(d: Documentable): Option[(String, DRI, Visibility, Kind, Origin)] = @@ -138,6 +162,7 @@ extension[T] (member: Member) 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 @@ -146,12 +171,16 @@ extension[T] (member: Member) def parents: Seq[LinkToType] = compositeMemberExt.fold(Nil)(_.parents) def directParents: Seq[Signature] = 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 membersByWithInheritancePartition(op: Member => Boolean): (Seq[Member], Seq[Member]) = membersBy(op).partition(_.inheritedFrom.isEmpty) +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 ca1e7f85417b..be4803dcad62 100644 --- a/scala3doc/src/dotty/dokka/model/api/internalExtensions.scala +++ b/scala3doc/src/dotty/dokka/model/api/internalExtensions.scala @@ -19,8 +19,9 @@ 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 -private [model] case class MemberExtension( +case class MemberExtension( visibility: Visibility, modifiers: Seq[dotty.dokka.model.api.Modifier], kind: Kind, @@ -30,6 +31,7 @@ private [model] case class MemberExtension( origin: Origin = Origin.RegularlyDefined, inheritedFrom: Option[InheritedFrom] = None, graph: HierarchyGraph = HierarchyGraph.empty, + rawDoc: Option[Comment] = None ) extends ExtraProperty[Documentable]: override def getKey = MemberExtension @@ -40,7 +42,8 @@ case class CompositeMemberExtension( members : Seq[Member] = Nil, directParents: Seq[Signature] = Nil, parents: Seq[LinkToType] = Nil, - knownChildren: Seq[LinkToType] = Nil + knownChildren: Seq[LinkToType] = Nil, + companion: Option[DRI] = None, ) extends ExtraProperty[Documentable]: override def getKey = CompositeMemberExtension diff --git a/scala3doc/src/dotty/dokka/model/extras.scala b/scala3doc/src/dotty/dokka/model/extras.scala index 5e49f617c929..1a25833253da 100644 --- a/scala3doc/src/dotty/dokka/model/extras.scala +++ b/scala3doc/src/dotty/dokka/model/extras.scala @@ -13,26 +13,3 @@ case class ModuleExtension(driMap: Map[DRI, Member]) extends ExtraProperty[DModu override def getKey = ModuleExtension object ModuleExtension extends BaseKey[DModule, ModuleExtension] - -case class MethodExtension(parametersListSizes: Seq[Int]) extends ExtraProperty[DFunction]: - override def getKey = MethodExtension - -object MethodExtension extends BaseKey[DFunction, MethodExtension] - -case class ParameterExtension(isExtendedSymbol: Boolean, isGrouped: Boolean) extends ExtraProperty[DParameter]: - override def getKey = ParameterExtension - -object ParameterExtension extends BaseKey[DParameter, ParameterExtension] - -case class ClasslikeExtension( - constructor: Option[DFunction], // will be replaced by signature - companion: Option[DRI], // moved to kind? -) extends ExtraProperty[DClasslike]: - override def getKey = ClasslikeExtension - -object ClasslikeExtension extends BaseKey[DClasslike, ClasslikeExtension] - -case class IsInherited(flag: Boolean) extends ExtraProperty[Documentable]: - override def getKey = IsInherited - -object IsInherited extends BaseKey[Documentable, IsInherited] diff --git a/scala3doc/src/dotty/dokka/model/scalaModel.scala b/scala3doc/src/dotty/dokka/model/scalaModel.scala index b01f615e24c2..3611f69b0ea7 100644 --- a/scala3doc/src/dotty/dokka/model/scalaModel.scala +++ b/scala3doc/src/dotty/dokka/model/scalaModel.scala @@ -8,6 +8,7 @@ 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 @@ -30,15 +31,15 @@ case class HtmlContentNode( override def getExtra = extra override def withNewExtras(p: PropertyContainer[ContentNode]) = copy(extra = p) -class ScalaTagWrapper(root: DocTag) extends TagWrapper(null): +class ScalaTagWrapper(root: DocTag, val name: String) extends TagWrapper(null): override def getRoot = root object ScalaTagWrapper { - case class See(root: DocTag) extends ScalaTagWrapper(root) - case class Todo(root: DocTag) extends ScalaTagWrapper(root) - case class Note(root: DocTag) extends ScalaTagWrapper(root) - case class Example(root: DocTag) extends ScalaTagWrapper(root) + 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, @@ -101,7 +102,8 @@ case class DocumentableElement( brief: Seq[ContentNode], originInfo: Signature, attributes: Map[String, String], - params: ContentNodeParams + params: ContentNodeParams, + member: Member ) extends ScalaContentNode(params): override def newInstance(params: ContentNodeParams) = copy(params = params) @@ -125,3 +127,7 @@ case class DocumentableList( 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/preprocessors/ScalaPackageListCreator.scala b/scala3doc/src/dotty/dokka/preprocessors/ScalaPackageListCreator.scala new file mode 100644 index 000000000000..7823be2d4be5 --- /dev/null +++ b/scala3doc/src/dotty/dokka/preprocessors/ScalaPackageListCreator.scala @@ -0,0 +1,38 @@ +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/SitePagesCreator.scala b/scala3doc/src/dotty/dokka/site/SitePagesCreator.scala index 132c73730398..282406ec3054 100644 --- a/scala3doc/src/dotty/dokka/site/SitePagesCreator.scala +++ b/scala3doc/src/dotty/dokka/site/SitePagesCreator.scala @@ -10,6 +10,8 @@ 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 @@ -44,7 +46,7 @@ class SitePagesCreator(using ctx: DocContext) extends BaseStaticSiteProcessor: val msg = s"ERROR: Multiple index pages for doc found ${indexes.map(_.template.file)}" report.error(msg) - def emptyContent = ctx.asContent(Text(), mkDRI(extra = "root_content")).get(0) + def emptyContent = ctx.asContent(Text(), DRI(extra = "root_content")).get(0) val root = ctx.indexPage().toList.map(_.copy(getDri = JSet(docsRootDRI))) val docsRoot = AContentPage( diff --git a/scala3doc/src/dotty/dokka/site/StaticSiteContext.scala b/scala3doc/src/dotty/dokka/site/StaticSiteContext.scala index 786d6d577260..bde05f5a06e8 100644 --- a/scala3doc/src/dotty/dokka/site/StaticSiteContext.scala +++ b/scala3doc/src/dotty/dokka/site/StaticSiteContext.scala @@ -17,8 +17,9 @@ import org.jetbrains.dokka.plugability.DokkaContext import org.jetbrains.dokka.pages.Style import org.jetbrains.dokka.model.DisplaySourceSet import util.Try +import collection.JavaConverters._ -import scala.collection.JavaConverters._ +import dotty.dokka.model.api._ class StaticSiteContext( val root: File, @@ -166,7 +167,8 @@ class StaticSiteContext( }.toOption pathsDri.getOrElse(memberLinkResolver(link).toList) - def driFor(dest: Path): DRI = mkDRI(s"_.${root.toPath.relativize(dest)}") + def driFor(dest: Path): DRI = + DRI(location = root.toPath.relativize(dest).iterator.asScala.mkString(".")) def relativePath(myTemplate: LoadedTemplate) = root.toPath.relativize(myTemplate.file.toPath) diff --git a/scala3doc/src/dotty/dokka/site/StaticSiteLocationProvider.scala b/scala3doc/src/dotty/dokka/site/StaticSiteLocationProvider.scala index f392c3f0eeb0..7b8cf77476fb 100644 --- a/scala3doc/src/dotty/dokka/site/StaticSiteLocationProvider.scala +++ b/scala3doc/src/dotty/dokka/site/StaticSiteLocationProvider.scala @@ -1,18 +1,24 @@ 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.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 = @@ -66,6 +72,13 @@ class StaticSiteLocationProvider(pageNode: RootPageNode)(using ctx: DokkaContext 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 @@ -74,14 +87,67 @@ class StaticSiteLocationProvider(pageNode: RootPageNode)(using ctx: DokkaContext 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 pathTo(node: PageNode, context: PageNode): String = - val nodePaths = getPathsIndex.get(node).asScala - val contextPaths = Option(context).fold(Nil)(getPathsIndex.get(_).asScala.dropRight(1)) - val commonPaths = nodePaths.zip(contextPaths).takeWhile{ case (a, b) => a == b }.size - - val contextPath = contextPaths.drop(commonPaths).map(_ => "..") - val nodePath = nodePaths.drop(commonPaths) match - case l if l.isEmpty => Seq("index") - case l => l - (contextPath ++ nodePath).mkString("/") \ No newline at end of file + if node == context then "" + else + val nodePaths = getPathsIndex.get(node).asScala + val contextPaths = Option(context).fold(Nil)(getPathsIndex.get(_).asScala.dropRight(1)) + val commonPaths = nodePaths.zip(contextPaths).takeWhile{ case (a, b) => a == b }.size + + val contextPath = contextPaths.drop(commonPaths).map(_ => "..") + val nodePath = nodePaths.drop(commonPaths) match + case l if l.isEmpty => Seq("index") + case l => l + (contextPath ++ nodePath).mkString("/") + + 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 + } \ 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 b2e0354d2f5d..06d77a19c5e5 100644 --- a/scala3doc/src/dotty/dokka/site/common.scala +++ b/scala3doc/src/dotty/dokka/site/common.scala @@ -3,6 +3,7 @@ package site import java.io.File import java.nio.file.Files +import dotty.dokka.model.api._ import com.vladsch.flexmark.ext.anchorlink.AnchorLinkExtension import com.vladsch.flexmark.ext.autolink.AutolinkExtension @@ -22,9 +23,9 @@ import org.jetbrains.dokka.pages._ import scala.collection.JavaConverters._ -val docsRootDRI: DRI = mkDRI("_.index.md") -val docsDRI: DRI = mkDRI("_.docs/index.md") -val apiPageDRI: DRI = mkDRI(packageName = "api", extra = "__api__") +val docsRootDRI: DRI = DRI(location = "index.md") +val docsDRI: DRI = DRI(location = "docs.index.md") +val apiPageDRI: DRI = DRI(location = "api", extra = "__api__") val defaultMarkdownOptions: DataHolder = new MutableDataSet() diff --git a/scala3doc/src/dotty/dokka/tasty/BasicSupport.scala b/scala3doc/src/dotty/dokka/tasty/BasicSupport.scala index 91c8f8529765..3dda29313235 100644 --- a/scala3doc/src/dotty/dokka/tasty/BasicSupport.scala +++ b/scala3doc/src/dotty/dokka/tasty/BasicSupport.scala @@ -41,6 +41,8 @@ trait BasicSupport: case None => Map.empty + def documentation2 = sym.docstring.map(preparseComment(_, sym.tree)) + def source = val path = Some(sym.pos.get.sourceFile.jpath).filter(_ != null).map(_.toAbsolutePath).map(_.toString) path.map(TastyDocumentableSource(_, sym.pos.get.startLine)) @@ -48,4 +50,6 @@ trait BasicSupport: def getAnnotations(): List[Annotation] = sym.annotations.filterNot(_.symbol.packageName.startsWith("scala.annotation.internal")).map(parseAnnotation).reverse + def isLeftAssoc: Boolean = !sym.name.endsWith(":") + diff --git a/scala3doc/src/dotty/dokka/tasty/ClassLikeSupport.scala b/scala3doc/src/dotty/dokka/tasty/ClassLikeSupport.scala index 31ee86c2b4df..0ec479876a2d 100644 --- a/scala3doc/src/dotty/dokka/tasty/ClassLikeSupport.scala +++ b/scala3doc/src/dotty/dokka/tasty/ClassLikeSupport.scala @@ -1,6 +1,6 @@ package dotty.dokka.tasty -import org.jetbrains.dokka.model.{TypeConstructor => DTypeConstructor, _} +import org.jetbrains.dokka.model.{TypeConstructor => DTypeConstructor, TypeParameter => _, _} import org.jetbrains.dokka.model.doc._ import org.jetbrains.dokka.DokkaConfiguration$DokkaSourceSet import collection.JavaConverters._ @@ -20,83 +20,95 @@ trait ClassLikeSupport: private val placeholderVisibility = JMap(ctx.sourceSet -> KotlinVisibility.Public.INSTANCE) private val placeholderModifier = JMap(ctx.sourceSet -> KotlinModifier.Empty.INSTANCE) - private def kindForClasslike(sym: Symbol): Kind = - if sym.flags.is(Flags.Module) then Kind.Object - else if sym.flags.is(Flags.Trait) then Kind.Trait - else if sym.flags.is(Flags.Enum) then Kind.Enum - else Kind.Class - - object DClass: - def apply[T >: DClass](classDef: ClassDef)( - dri: DRI = classDef.symbol.dri, - name: String = classDef.symbol.normalizedName, - signatureOnly: Boolean = false, - modifiers: Seq[Modifier] = classDef.symbol.getExtraModifiers(), - ): DClass = - - // This Try is here because of problem that code compiles, but at runtime fails claiming - // java.lang.ClassCastException: class dotty.tools.dotc.ast.Trees$DefDef cannot be cast to class dotty.tools.dotc.ast.Trees$TypeDef (dotty.tools.dotc.ast.Trees$DefDef and dotty.tools.dotc.ast.Trees$TypeDef are in unnamed module of loader 'app') - // It is probably bug in Tasty - def hackGetParents(classDef: ClassDef): Option[List[Tree]] = scala.util.Try(classDef.parents).toOption - - def getSupertypesGraph(classDef: ClassDef, link: LinkToType): Seq[(LinkToType, LinkToType)] = - val smbl = classDef.symbol - val parents = if smbl.exists then hackGetParents(smbl.tree.asInstanceOf[ClassDef]) else None - parents.fold(Seq())(_.flatMap { case tree => - val symbol = if tree.symbol.isClassConstructor then tree.symbol.owner else tree.symbol - val superLink = LinkToType(tree.dokkaType.asSignature, symbol.dri, kindForClasslike(symbol)) - Seq(link -> superLink) ++ getSupertypesGraph(tree.asInstanceOf[ClassDef], superLink) - } - ) + 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) + else if symbol.flags.is(Flags.Enum) then Kind.Enum + else Kind.Class(Nil, Nil) - val supertypes = getSupertypes(using qctx)(classDef).map { - case (symbol, tpe) => LinkToType(tpe.dokkaType.asSignature, symbol.dri, kindForClasslike(symbol)) - } - val selfSiangture: DSignature = typeForClass(classDef).dokkaType.asSignature - - val graph = HierarchyGraph.withEdges(getSupertypesGraph(classDef, LinkToType(selfSiangture, classDef.symbol.dri, kindForClasslike(classDef.symbol)))) - val baseExtra = PropertyContainer.Companion.empty() - .plus(ClasslikeExtension(classDef.getConstructorMethod(), classDef.getCompanion)) - .plus(MemberExtension( - classDef.symbol.getVisibility(), - modifiers, - kindForClasslike( classDef.symbol), - classDef.symbol.getAnnotations(), - selfSiangture, - classDef.symbol.source, - graph = graph - )) - - val fullExtra = - if (signatureOnly) baseExtra - else - baseExtra.plus(CompositeMemberExtension( - classDef.extractPatchedMembers, - classDef.getParents.map(_.dokkaType.asSignature), - supertypes, - Nil)) - end if + private def kindForClasslike(classDef: ClassDef): Kind = + def typeArgs = classDef.getTypeParams.map(mkTypeArgument) - new DClass( - dri, - name, - (if(signatureOnly) Nil else classDef.getConstructors.map(parseMethod(_))).asJava, - JList(), - JList(), - JList(), - JMap(), - placeholderVisibility, - null, - /*generics =*/classDef.getTypeParams.map(parseTypeArgument).asJava, - Map.empty.asJava, - classDef.symbol.documentation.asJava, - null, - placeholderModifier, - ctx.sourceSet.toSet, - /*isExpectActual =*/ false, - fullExtra.asInstanceOf[PropertyContainer[DClass]] + def parameterModifier(parameter: Symbol): String = + val fieldSymbol = classDef.symbol.declaredField(parameter.normalizedName) + def isVal = fieldSymbol.flags.is(Flags.ParamAccessor) && + !classDef.symbol.flags.is(Flags.Case) && + !fieldSymbol.flags.is(Flags.Private) + + if fieldSymbol.flags.is(Flags.Mutable) then "var " + else if isVal then "val " + else "" + + def args = if constructorWithoutParamLists(classDef) then Nil else + val constr = + Some(classDef.constructor.symbol) + .filter(s => s.exists && !s.isHiddenByVisibility) + .map( _.tree.asInstanceOf[DefDef]) + constr.fold(Nil)( + _.paramss.map(_.map(mkParameter(_, parameterModifier))) + ) + + if classDef.symbol.flags.is(Flags.Module) then Kind.Object + else if classDef.symbol.flags.is(Flags.Trait) then + Kind.Trait(typeArgs, args) + else if classDef.symbol.flags.is(Flags.Enum) then Kind.Enum + else Kind.Class(typeArgs, args) + + def mkClass[T >: DClass](classDef: ClassDef)( + dri: DRI = classDef.symbol.dri, + name: String = classDef.symbol.normalizedName, + signatureOnly: Boolean = false, + modifiers: Seq[Modifier] = classDef.symbol.getExtraModifiers(), + ): Member = + + // This Try is here because of problem that code compiles, but at runtime fails claiming + // java.lang.ClassCastException: class dotty.tools.dotc.ast.Trees$DefDef cannot be cast to class dotty.tools.dotc.ast.Trees$TypeDef (dotty.tools.dotc.ast.Trees$DefDef and dotty.tools.dotc.ast.Trees$TypeDef are in unnamed module of loader 'app') + // It is probably bug in Tasty + def hackGetParents(classDef: ClassDef): Option[List[Tree]] = scala.util.Try(classDef.parents).toOption + + def getSupertypesGraph(classDef: ClassDef, link: LinkToType): Seq[(LinkToType, LinkToType)] = + val smbl = classDef.symbol + val parents = if smbl.exists then hackGetParents(smbl.tree.asInstanceOf[ClassDef]) else None + parents.fold(Seq())(_.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)) + Seq(link -> superLink) ++ getSupertypesGraph(tree.asInstanceOf[ClassDef], superLink) + } ) + val supertypes = getSupertypes(using qctx)(classDef).map { + case (symbol, tpe) => + LinkToType(tpe.dokkaType.asSignature, symbol.dri, bareClasslikeKind(symbol)) + } + val selfSiangture: DSignature = typeForClass(classDef).dokkaType.asSignature + + val graph = HierarchyGraph.withEdges(getSupertypesGraph(classDef, + LinkToType(selfSiangture, classDef.symbol.dri, bareClasslikeKind(classDef.symbol)))) + + val compositeExt = + if signatureOnly then CompositeMemberExtension.empty + else CompositeMemberExtension( + classDef.extractPatchedMembers, + classDef.getParents.map(_.dokkaType.asSignature), + supertypes, + Nil, + classDef.getCompanion + ) + + mkMember( + classDef.symbol, + MemberExtension( + classDef.symbol.getVisibility(), + modifiers, + kindForClasslike(classDef), + classDef.symbol.getAnnotations(), + selfSiangture, + classDef.symbol.source, + graph = graph + ), + compositeExt + ) + private val conversionSymbol = Symbol.requiredClass("scala.Conversion") def extractImplicitConversion(tpe: TypeRepr): Option[ImplicitConversion] = @@ -107,8 +119,11 @@ trait ClassLikeSupport: None else None + private def isDocumentableExtension(s: Symbol) = + !s.isHiddenByVisibility && !s.isSyntheticFunc && s.isExtensionMethod + private def parseMember(s: Tree): Option[Member] = processTreeOpt(s)(s match - case dd: DefDef if !dd.symbol.isHiddenByVisibility && !dd.symbol.isSyntheticFunc && dd.symbol.isExtensionMethod => + case dd: DefDef if isDocumentableExtension(dd.symbol) => dd.symbol.extendedSymbol.map { extSym => val target = ExtensionTarget( extSym.symbol.normalizedName, @@ -116,14 +131,16 @@ trait ClassLikeSupport: extSym.tpt.symbol.dri, extSym.symbol.pos.get.start ) - parseMethod(dd.symbol, kind = Kind.Extension(target)) + parseMethod(dd.symbol,specificKind = Kind.Extension(target, _)) } // TODO check given methods? case dd: DefDef if !dd.symbol.isHiddenByVisibility && dd.symbol.isGiven => Some(dd.symbol.owner.memberType(dd.name)) .filterNot(_.exists) .map { _ => - parseMethod(dd.symbol, kind = Kind.Given(getGivenInstance(dd).map(_.asSignature), None)) + parseMethod(dd.symbol, specificKind = + Kind.Given(_, getGivenInstance(dd).map(_.asSignature), None) + ) } case dd: DefDef if !dd.symbol.isHiddenByVisibility && dd.symbol.isExported => @@ -139,7 +156,9 @@ trait ClassLikeSupport: val dri = dd.rhs.collect { case s: Select if s.symbol.isDefDef => s.symbol.dri }.orElse(exportedTarget.map(_.qualifier.tpe.typeSymbol.dri)) - Some(parseMethod(dd.symbol, kind = Kind.Exported).withOrigin(Origin.ExportedFrom(s"$instanceName.$functionName", dri))) + + Some(parseMethod(dd.symbol, specificKind = Kind.Exported(_)) + .withOrigin(Origin.ExportedFrom(s"$instanceName.$functionName", dri))) case dd: DefDef if !dd.symbol.isHiddenByVisibility && !dd.symbol.isGiven && !dd.symbol.isSyntheticFunc && !dd.symbol.isExtensionMethod => Some(parseMethod(dd.symbol)) @@ -167,19 +186,24 @@ trait ClassLikeSupport: private def parseGivenClasslike(c: ClassDef): Member = { val parsedClasslike = parseClasslike(c) + val parentTpe = c.parents(0) match { case t: TypeTree => Some(t.tpe) case t: Term => Some(t.tpe) case _ => None } - val modifiedClasslikeExtension = ClasslikeExtension.getFrom(parsedClasslike).map(_.copy( - constructor = c.getConstructorMethod(Some(_ => "using ")) - ) - ).get - parsedClasslike.withNewExtras( - parsedClasslike.getExtra.plus(modifiedClasslikeExtension) - ).withKind( - Kind.Given(parsedClasslike.directParents.headOption, parentTpe.flatMap(extractImplicitConversion)) + + val givenParents = parsedClasslike.directParents.headOption + val cls: Kind.Class = parsedClasslike.kind match + case Kind.Object => Kind.Class(Nil, Nil) + case Kind.Trait(tps, args) => Kind.Class(tps, args) + case cls: Kind.Class => cls + case other => + report.warning("Unrecoginzed kind for given: $other", c.pos) + Kind.Class(Nil, Nil) + + parsedClasslike.withKind( + Kind.Given(cls, givenParents, parentTpe.flatMap(extractImplicitConversion)) ) } @@ -248,32 +272,28 @@ trait ClassLikeSupport: else if fieldSymbol.flags.is(Flags.ParamAccessor) && !c.symbol.flags.is(Flags.Case) && !fieldSymbol.flags.is(Flags.Private) then "val " else "" - def getTypeParams: List[TypeDef] = c.body.collect { case targ: TypeDef => targ }.filter(_.symbol.isTypeParam) + def getTypeParams: List[TypeDef] = + c.body.collect { case targ: TypeDef => targ }.filter(_.symbol.isTypeParam) def getCompanion: Option[DRI] = c.symbol.getCompanionSymbol .filter(!_.flags.is(Flags.Synthetic)) .filterNot(_.isHiddenByVisibility) .map(_.dri) - def getConstructorMethod(paramModifierFunc: Option[Symbol => String] = None): Option[DFunction] = - Some(c.constructor.symbol).filter(_.exists).filterNot(_.isHiddenByVisibility).map( d => - parseMethod(d, constructorWithoutParamLists(c), paramModifierFunc.getOrElse(s => c.getParameterModifier(s))) - ) - def parseClasslike(classDef: ClassDef, signatureOnly: Boolean = false): DClass = classDef match + def parseClasslike(classDef: ClassDef, signatureOnly: Boolean = false): Member = classDef match case c: ClassDef if classDef.symbol.flags.is(Flags.Module) => parseObject(c, signatureOnly) case c: ClassDef if classDef.symbol.flags.is(Flags.Enum) => parseEnum(c, signatureOnly) - case clazz => DClass(classDef)(signatureOnly = signatureOnly) + case clazz => mkClass(classDef)(signatureOnly = signatureOnly) - def parseObject(classDef: ClassDef, signatureOnly: Boolean = false): DClass = - DClass(classDef)( - // All objects are final so we do not need final modifer! + def parseObject(classDef: ClassDef, signatureOnly: Boolean = false): Member = + mkClass(classDef)( + // All objects are final so we do not need final modifier! modifiers = classDef.symbol.getExtraModifiers().filter(_ != Modifier.Final), signatureOnly = signatureOnly ) - // TODO check withNewExtras? - def parseEnum(classDef: ClassDef, signatureOnly: Boolean = false): DClass = + def parseEnum(classDef: ClassDef, signatureOnly: Boolean = false): Member = val extraModifiers = classDef.symbol.getExtraModifiers().filter(_ != Modifier.Sealed).filter(_ != Modifier.Abstract) val companion = classDef.symbol.getCompanionSymbol.map(_.tree.asInstanceOf[ClassDef]).get @@ -289,165 +309,159 @@ trait ClassLikeSupport: case c: ClassDef if c.symbol.flags.is(Flags.Case) && c.symbol.flags.is(Flags.Enum) => processTree(c)(parseClasslike(c)) }.flatten - val classlikie = DClass(classDef)(modifiers = extraModifiers, signatureOnly = signatureOnly) - classlikie.withNewMembers((enumVals ++ enumTypes ++ enumNested).map(_.withKind(Kind.EnumCase))).asInstanceOf[DClass] + val classlikie = mkClass(classDef)(modifiers = extraModifiers, signatureOnly = signatureOnly) + val cases = + enumNested.map(_.withKind(Kind.EnumCase(Kind.Object))) ++ + enumTypes.map(et => et.withKind(Kind.EnumCase(et.kind.asInstanceOf[Kind.Type]))) ++ + enumVals.map(_.withKind(Kind.EnumCase(Kind.Val))) + + classlikie.withNewMembers(cases).asInstanceOf[DClass] def parseMethod( methodSymbol: Symbol, emptyParamsList: Boolean = false, paramPrefix: Symbol => String = _ => "", - kind: Kind = Kind.Def - ): DFunction = + specificKind: (Kind.Def => Kind) = identity + ): Member = val method = methodSymbol.tree.asInstanceOf[DefDef] - val paramLists = if emptyParamsList then Nil else method.paramss + val paramLists = + if emptyParamsList then Nil + else if methodSymbol.isExtensionMethod then + val params = method.paramss + if methodSymbol.isLeftAssoc || params.size == 1 then params.tail + else params.head :: params.tail.drop(1) + else method.paramss val genericTypes = if (methodSymbol.isClassConstructor) Nil else method.typeParams + val basicKind: Kind.Def = Kind.Def( + genericTypes.map(mkTypeArgument), + paramLists.map(_.map(mkParameter(_, paramPrefix))) + ) + val methodKind = - if methodSymbol.isClassConstructor then Kind.Constructor + if methodSymbol.isClassConstructor then Kind.Constructor(basicKind) else if methodSymbol.flags.is(Flags.Implicit) then extractImplicitConversion(method.returnTpt.tpe) match case Some(conversion) if paramLists.size == 0 || (paramLists.size == 1 && paramLists.head.size == 0) => - Kind.Implicit(Kind.Def, Some(conversion)) + Kind.Implicit(basicKind, Some(conversion)) case None if paramLists.size == 1 && paramLists(0).size == 1 => - Kind.Implicit(Kind.Def, Some( + Kind.Implicit(basicKind, Some( ImplicitConversion( paramLists(0)(0).tpt.tpe.typeSymbol.dri, method.returnTpt.tpe.typeSymbol.dri ) )) case _ => - Kind.Implicit(Kind.Def, None) - else kind - - val name = method.symbol.normalizedName - - val memberExtension = MemberExtension( - methodSymbol.getVisibility(), - methodSymbol.getExtraModifiers(), - methodKind, - methodSymbol.getAnnotations(), - method.returnTpt.dokkaType.asSignature, - methodSymbol.source - ) - - val memberExtensionWithOrigin = - if methodSymbol.isOverriden then memberExtension.copy( - origin = Origin.Overrides(methodSymbol.allOverriddenSymbols.map(_.owner).map(s => Overriden(s.name, s.dri)).toSeq) - ) else memberExtension - - new DFunction( - methodSymbol.dri, - name, - /*isConstructor =*/ methodSymbol.isClassConstructor, - /*parameters =*/ paramLists.flatten.map(parseArgument(_, paramPrefix)).asJava, // TODO add support for parameters - /*documentation =*/ methodSymbol.documentation.asJava, - /*expectPresentInSet =*/ null, // unused - /*sources =*/ JMap(), - /*visibility =*/ placeholderVisibility, - /*type =*/ method.returnTpt.dokkaType, - /*generics =*/ genericTypes.map(parseTypeArgument).asJava, - /*receiver =*/ null, // Not used - /*modifier =*/ placeholderModifier, - ctx.sourceSet.toSet, - /*isExpectActual =*/ false, - PropertyContainer.Companion.empty() - plus MethodExtension(paramLists.map(_.size)) - plus(memberExtensionWithOrigin) + Kind.Implicit(basicKind, None) + else specificKind(basicKind) + + val origin = if !methodSymbol.isOverriden then Origin.RegularlyDefined else + 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(), + method.returnTpt.dokkaType.asSignature, + methodSymbol.source, + origin + ) ) - def parseArgument(argument: ValDef, prefix: Symbol => String, isExtendedSymbol: Boolean = false, isGrouped: Boolean = false): DParameter = - new DParameter( - argument.symbol.dri, - prefix(argument.symbol) + argument.symbol.normalizedName, - argument.symbol.documentation.asJava, - null, - argument.tpt.dokkaType, - ctx.sourceSet.toSet, - PropertyContainer.Companion.empty() - .plus(ParameterExtension(isExtendedSymbol, isGrouped)) - .plus(MemberExtension.empty.copy(annotations = argument.symbol.getAnnotations())) - ) + def mkParameter(argument: ValDef, + prefix: Symbol => String = _ => "", + isExtendedSymbol: Boolean = false, + isGrouped: Boolean = false) = + val inlinePrefix = if argument.symbol.flags.is(Flags.Inline) then "inline " else "" + // TODO (https://github.com/lampepfl/dotty/issues/10525): Add using flag + + Parameter( + argument.symbol.getAnnotations(), + inlinePrefix + prefix(argument.symbol), + argument.symbol.normalizedName, + argument.symbol.dri, + argument.tpt.dokkaType.asSignature, + isExtendedSymbol, + isGrouped + ) - def parseTypeArgument(argument: TypeDef): DTypeParameter = - // Not sure if we should have such hacks... - val variancePrefix = + def mkTypeArgument(argument: TypeDef): TypeParameter = + val variancePrefix: "+" | "-" | "" = if argument.symbol.flags.is(Flags.Covariant) then "+" else if argument.symbol.flags.is(Flags.Contravariant) then "-" else "" - new DTypeParameter( - Invariance(TypeParameter(argument.symbol.dri, variancePrefix + argument.symbol.normalizedName, null)), - argument.symbol.documentation.asJava, - null, - JList(argument.rhs.dokkaType), - ctx.sourceSet.toSet, - PropertyContainer.Companion.empty() + TypeParameter( + variancePrefix, + argument.symbol.normalizedName, + argument.symbol.dri, + argument.rhs.dokkaType.asSignature ) - def parseTypeDef(typeDef: TypeDef): DProperty = - + def parseTypeDef(typeDef: TypeDef): Member = def isTreeAbstract(typ: Tree): Boolean = typ match { case TypeBoundsTree(_, _) => true case LambdaTypeTree(params, body) => isTreeAbstract(body) case _ => false } - val (generics, tpeTree) = typeDef.rhs match - case LambdaTypeTree(params, body) => (params.map(parseTypeArgument), body) + case LambdaTypeTree(params, body) => (params.map(mkTypeArgument), body) case tpe => (Nil, tpe) - new DProperty( - typeDef.symbol.dri, - typeDef.symbol.normalizedName, - /*documentation =*/ typeDef.symbol.documentation.asJava, - /*expectPresentInSet =*/ null, // unused - /*sources =*/ JMap(), - /*visibility =*/ placeholderVisibility, - /*type =*/ tpeTree.dokkaType, // TODO this may be hard... - /*receiver =*/ null, // Not used - /*setter =*/ null, - /*getter =*/ null, - /*modifier =*/ placeholderModifier, - ctx.sourceSet.toSet, - /*generics =*/ generics.asJava, // TODO - /*isExpectActual =*/ false, - PropertyContainer.Companion.empty() plus MemberExtension( + mkMember( + typeDef.symbol, + MemberExtension( typeDef.symbol.getVisibility(), typeDef.symbol.getExtraModifiers(), - Kind.Type(!isTreeAbstract(typeDef.rhs), typeDef.symbol.isOpaque), + Kind.Type(!isTreeAbstract(typeDef.rhs), typeDef.symbol.isOpaque, generics), typeDef.symbol.getAnnotations(), tpeTree.dokkaType.asSignature, typeDef.symbol.source ) ) - def parseValDef(valDef: ValDef): DProperty = + def parseValDef(valDef: ValDef): Member = def defaultKind = if valDef.symbol.flags.is(Flags.Mutable) then Kind.Var else Kind.Val val kind = if valDef.symbol.flags.is(Flags.Implicit) then Kind.Implicit(Kind.Val, extractImplicitConversion(valDef.tpt.tpe)) else defaultKind - new DProperty( - valDef.symbol.dri, - valDef.symbol.normalizedName, - /*documentation =*/ valDef.symbol.documentation.asJava, - /*expectPresentInSet =*/ null, // unused - /*sources =*/ JMap(), - /*visibility =*/ placeholderVisibility, - /*type =*/ valDef.tpt.dokkaType, - /*receiver =*/ null, // Not used - /*setter =*/ null, - /*getter =*/ null, - /*modifier =*/ placeholderModifier, - ctx.sourceSet.toSet, - /*generics =*/ JList(), - /*isExpectActual =*/ false, - PropertyContainer.Companion.empty().plus(MemberExtension( + mkMember( + valDef.symbol, + MemberExtension( valDef.symbol.getVisibility(), valDef.symbol.getExtraModifiers(), kind, valDef.symbol.getAnnotations(), valDef.tpt.tpe.dokkaType.asSignature, valDef.symbol.source - )) + ) ) + + def mkMember[T <: Kind]( + symbol: Symbol, + member: MemberExtension, + compositeExt: CompositeMemberExtension = CompositeMemberExtension.empty): Member = + new DClass( + symbol.dri, + symbol.normalizedName, + 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.documentation2)).plus(compositeExt) + ) \ No newline at end of file diff --git a/scala3doc/src/dotty/dokka/tasty/PackageSupport.scala b/scala3doc/src/dotty/dokka/tasty/PackageSupport.scala index 8ad677911de9..fe6e99c05d04 100644 --- a/scala3doc/src/dotty/dokka/tasty/PackageSupport.scala +++ b/scala3doc/src/dotty/dokka/tasty/PackageSupport.scala @@ -34,7 +34,7 @@ trait PackageSupport: parseClasslike(pckObj) match { case clazz: DClass => DPackage( - new DRI(pckObj.symbol.dri.getPackageName, null, null, PointingToDeclaration.INSTANCE, null), + new DRI(pckObj.symbol.packageName, null, null, PointingToDeclaration.INSTANCE, null), clazz.getFunctions, clazz.getProperties, JList(), diff --git a/scala3doc/src/dotty/dokka/tasty/ScalaDocSupport.scala b/scala3doc/src/dotty/dokka/tasty/ScalaDocSupport.scala index 4d650e40eab0..8bf914ff4040 100644 --- a/scala3doc/src/dotty/dokka/tasty/ScalaDocSupport.scala +++ b/scala3doc/src/dotty/dokka/tasty/ScalaDocSupport.scala @@ -7,10 +7,50 @@ 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 trait ScaladocSupport { self: TastyParser => import qctx.reflect._ + def preparseComment( + docstring: String, + tree: Tree + ): Comment = + val commentString: String = + if tree.symbol.isClassDef || tree.symbol.owner.isClassDef then + import dotty.tools.dotc + given ctx: dotc.core.Contexts.Context = qctx.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx + + val sym = tree.symbol.asInstanceOf[dotc.core.Symbols.Symbol] + + comments.CommentExpander.cookComment(sym)(using ctx) + .get.expanded.get + else + docstring + + val preparsed = + comments.Preparser.preparse(comments.Cleaner.clean(commentString)) + + val commentSyntax = + preparsed.syntax.headOption match { + case Some(commentSetting) => + CommentSyntax.parse(commentSetting).getOrElse { + val msg = s"not a valid comment syntax: $commentSetting, defaulting to Markdown syntax." + // we should update pos with span from documentation + report.warning(msg, tree.pos) + CommentSyntax.default + } + case None => ctx.args.defaultSyntax + } + + val parser = commentSyntax match { + case CommentSyntax.Wiki => + comments.WikiCommentParser(comments.Repr(qctx)(tree.symbol)) + case CommentSyntax.Markdown => + comments.MarkdownCommentParser(comments.Repr(qctx)(tree.symbol)) + } + parser.parse(preparsed) + def parseComment( docstring: String, tree: Tree diff --git a/scala3doc/src/dotty/dokka/tasty/SymOps.scala b/scala3doc/src/dotty/dokka/tasty/SymOps.scala index f3ea472d5962..c11be73c1628 100644 --- a/scala3doc/src/dotty/dokka/tasty/SymOps.scala +++ b/scala3doc/src/dotty/dokka/tasty/SymOps.scala @@ -15,12 +15,22 @@ class SymOps[Q <: Quotes](val q: Q): given Q = q extension (sym: Symbol) - def packageName: String = + def packageName: String = ( if (sym.isPackageDef) sym.fullName else sym.maybeOwner.packageName + ) - def topLevelEntryName: Option[String] = if (sym.isPackageDef) None else - if (sym.owner.isPackageDef) Some(sym.name) else sym.owner.topLevelEntryName + def className: Option[String] = + if (sym.isClassDef && !sym.flags.is(Flags.Package)) Some( + Some(sym.maybeOwner).filter(s => s.exists).flatMap(_.className).fold("")(cn => cn + "$") + sym.name + ) + else if (sym.isPackageDef) None + else sym.maybeOwner.className + + def anchor: Option[String] = + if (!sym.isClassDef && !sym.isPackageDef) Some(sym.name) + else None + //TODO: Retrieve string that will match scaladoc anchors def getVisibility(): Visibility = import VisibilityScope._ @@ -91,10 +101,11 @@ class SymOps[Q <: Quotes](val q: Q): def isLeftAssoc(d: Symbol): Boolean = !d.name.endsWith(":") def extendedSymbol: Option[ValDef] = - Option.when(sym.isExtensionMethod)( - if(isLeftAssoc(sym)) sym.tree.asInstanceOf[DefDef].paramss(0)(0) - else sym.tree.asInstanceOf[DefDef].paramss(1)(0) - ) + Option.when(sym.isExtensionMethod){ + val params = sym.tree.asInstanceOf[DefDef].paramss + if isLeftAssoc(sym) || params.size == 1 then params(0)(0) + else params(1)(0) + } // TODO #22 make sure that DRIs are unique plus probably reuse semantic db code? def dri: DRI = @@ -110,12 +121,22 @@ class SymOps[Q <: Quotes](val q: Q): else if (sym.maybeOwner.isDefDef) Some(sym.owner) else None - 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, JList())).orNull, + val originPath = { + import q.reflect._ + 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]") + } + // We want package object to point to package + val className = sym.className.filter(_ != "package$") + + DRI( + className.fold(sym.packageName)(cn => s"${sym.packageName}.${cn}"), + sym.anchor.getOrElse(""), // TODO do we need any of this fields? + null, pointsTo, // 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("/")}]" + s"${sym.name}${sym.fullName}/${sym.signature.resultSig}/[${sym.signature.paramSigs.mkString("/")}]$originPath" ) diff --git a/scala3doc/src/dotty/dokka/tasty/TastyParser.scala b/scala3doc/src/dotty/dokka/tasty/TastyParser.scala index 054587a4fcc8..e707cb586471 100644 --- a/scala3doc/src/dotty/dokka/tasty/TastyParser.scala +++ b/scala3doc/src/dotty/dokka/tasty/TastyParser.scala @@ -20,6 +20,7 @@ import dotty.dokka.model.api.withNewMembers import dotty.dokka.tasty.comments.MemberLookup import dotty.dokka.tasty.comments.QueryParser import scala.util.Try +import dotty.dokka.model.api._ /** Responsible for collectively inspecting all the Tasty files we're interested in. * @@ -62,26 +63,30 @@ case class DokkaTastyInspector(parser: Parser)(using ctx: DocContext) extends Do .map((dri, pckgs) => pckgs.reduce(_.mergeWith(_)) ) + .toList + .sortBy( pckg => pckg.dri.location.size ) + .reverse - val byPackage = all.filter(_.getDri != null).groupBy(_.getDri().getPackageName()) - byPackage.map { - case (pck, entries) => { - val found = packages.find(d => d.getName == pck) - .map( f => - new DPackage( - f.getDri, - f.getFunctions, - f.getProperties, - JList(), // TODO add support for other things like type or package object entries - JList(), - f.getDocumentation, - null, - JSet(ctx.sourceSet), - f.getExtra - ).withNewMembers(entries.filterNot(_.isInstanceOf[DPackage]).toList).asInstanceOf[DPackage] - ) - found.getOrElse(throw IllegalStateException("No package for entries found")) - } + + val byPackage = all.filter(_.dri != null).groupBy( p => + packages.find(d => p.dri.location.contains(d.dri.location)).getOrElse(throw IllegalStateException("No package for entries found")) + ) + + byPackage + .map { + case (f, entries) => { + new DPackage( + f.getDri, + f.getFunctions, + f.getProperties, + JList(), // TODO add support for other things like type or package object entries + JList(), + f.getDocumentation, + null, + JSet(ctx.sourceSet), + f.getExtra + ).withNewMembers(entries.filterNot(_.isInstanceOf[DPackage]).toList).asInstanceOf[DPackage] + } }.toList extension (self: DPackage) def mergeWith(other: DPackage): DPackage = diff --git a/scala3doc/src/dotty/dokka/transformers/ImplicitMembersExtensionTransformer.scala b/scala3doc/src/dotty/dokka/transformers/ImplicitMembersExtensionTransformer.scala index 037f235cb9dd..f782a28e3b33 100644 --- a/scala3doc/src/dotty/dokka/transformers/ImplicitMembersExtensionTransformer.scala +++ b/scala3doc/src/dotty/dokka/transformers/ImplicitMembersExtensionTransformer.scala @@ -10,20 +10,16 @@ import org.jetbrains.dokka.model.properties._ import dotty.dokka.model._ import dotty.dokka.model.api._ -class ImplicitMembersExtensionTransformer(ctx: DokkaContext) extends DocumentableTransformer: +class ImplicitMembersExtensionTransformer(using DocContext) extends DocumentableTransformer: override def invoke(original: DModule, context: DokkaContext): DModule = val classlikeMap = original.driMap val logger = context.getLogger - def retrieveCompanion(m: Member) = m match { - case classlike: DClass => - val comp = ClasslikeExtension.getFrom(classlike).flatMap(_.companion) - comp.flatMap { dri => - val res = classlikeMap.get(dri) - if res.isEmpty then logger.warn(s"Companion for class ${classlike.name} exists but is missing in classlike map") - res - } - case _ => None + def retrieveCompanion(m: Member) = m.companion.flatMap { dri => + val res = classlikeMap.get(dri) + if res.isEmpty then + report.warning(s"Companion for class ${m.name} exists but is missing in classlike map") + res } def expandMember(outerMembers: Seq[Member])(c: Member): Member = @@ -40,8 +36,12 @@ class ImplicitMembersExtensionTransformer(ctx: DokkaContext) extends Documentabl val MyDri = c.getDri def collectApplicableMembers(source: Member): Seq[Member] = source.allMembers.flatMap { - case m @ Member(_, _, _, Kind.Extension(ExtensionTarget(_, _, MyDri, _)), Origin.RegularlyDefined) => - Seq(m.withOrigin(Origin.ExtensionFrom(source.name, source.dri)).withKind(Kind.Def)) + case m @ Member(_, _, _, Kind.Extension(ExtensionTarget(_, _, MyDri, _), _), Origin.RegularlyDefined) => + val kind = m.kind match + case d: Kind.Def => d + case _ => Kind.Def(Nil, Nil) + + Seq(m.withOrigin(Origin.ExtensionFrom(source.name, source.dri)).withKind(kind)) case m @ Member(_, _, _, conversionProvider: ImplicitConversionProvider, Origin.RegularlyDefined) => conversionProvider.conversion match case Some(ImplicitConversion(MyDri, to)) => diff --git a/scala3doc/src/dotty/dokka/translators/ScalaContentBuilder.scala b/scala3doc/src/dotty/dokka/translators/ScalaContentBuilder.scala index 5411b569d59c..b3215a265d4b 100644 --- a/scala3doc/src/dotty/dokka/translators/ScalaContentBuilder.scala +++ b/scala3doc/src/dotty/dokka/translators/ScalaContentBuilder.scala @@ -346,7 +346,7 @@ class ScalaPageContentBuilder( } else this def list[T]( - elements: List[T], + elements: Seq[T], prefix: String = "", suffix: String = "", separator: String = ", ", @@ -474,6 +474,8 @@ class ScalaPageContentBuilder( type Self = ScalaPageContentBuilder#ScalaDocumentableContentBuilder + def memberInfo(m: Member) = addChild(MemberInfo(m, asParams(m.dri))) + def documentableTab(name: String)(children: DocumentableGroup*): Self = def buildSignature(d: Documentable) = ScalaSignatureProvider.rawSignature(d, InlineSignatureBuilder()).asInstanceOf[InlineSignatureBuilder] @@ -487,15 +489,11 @@ class ScalaPageContentBuilder( val originInfo = documentable.origin match { case Origin.ImplicitlyAddedBy(name, dri) => Signature("Implicitly added by ", SLink(name, dri)) case Origin.ExtensionFrom(name, dri) => Signature("Extension method from ", SLink(name, dri)) - case Origin.ExportedFrom(name, dri) => + case Origin.ExportedFrom(name, dri) => val signatureName: String | dotty.dokka.model.api.Link = dri match case Some(dri: DRI) => SLink(name, dri) case None => name Signature("Exported from ", signatureName) - case Origin.Overrides(overridenMembers) => - def intersperse(xs: Seq[SLink]): Seq[(String | SLink)] = - xs.flatMap(Seq(_, " -> ")).dropRight(1).asInstanceOf[Seq[(String | SLink)]] // dropRight returns `Seq[Object]` - Signature("Definition classes: ").join(Signature(intersperse(overridenMembers.map(SLink(_, _))):_*)) case _ => Nil } val styles: Set[Style] = if documentable.deprecated.isDefined then Set(TextStyle.Strikethrough) else Set.empty @@ -507,7 +505,8 @@ class ScalaPageContentBuilder( docs.fold(Nil)(d => reset().rawComment(d.getRoot)), originInfo, FilterAttributes.attributesFor(documentable), - asParams(documentable.getDri) + asParams(documentable.getDri), + documentable ) def element(e: Documentable | DocumentableSubGroup): DocumentableElement | DocumentableElementGroup = e match diff --git a/scala3doc/src/dotty/dokka/translators/ScalaPageCreator.scala b/scala3doc/src/dotty/dokka/translators/ScalaPageCreator.scala index 94feda84e51d..080bc8631411 100644 --- a/scala3doc/src/dotty/dokka/translators/ScalaPageCreator.scala +++ b/scala3doc/src/dotty/dokka/translators/ScalaPageCreator.scala @@ -34,47 +34,37 @@ class ScalaPageCreator( override def pageForModule(m: DModule): ModulePageNode = super.pageForModule(m) - private def updatePageNameForMember(page: PageNode, p: Member) = - val name = page.getName - - page.modified(name, page.getChildren) - - private def pagesForMembers(p: Member): Seq[PageNode] = - p.allMembers.filter(m => m.origin == Origin.RegularlyDefined && m.inheritedFrom.isEmpty).collect { - case f: DFunction => updatePageNameForMember(pageForFunction(f), f) - case c: DClass => updatePageNameForMember(pageForDClass(c), c) - } + private def pagesForMembers(member: Member): JList[PageNode] = + val all = member + .membersBy(_.kind.isInstanceOf[Classlike]) + .filter(m => m.origin == Origin.RegularlyDefined && m.inheritedFrom.isEmpty) + all.map(pageForMember(_)).asJava override def pageForPackage(p: DPackage): PackagePageNode = - val originalPage = super.pageForPackage(p) - val originalPages: Seq[PageNode] = originalPage.getChildren.asScala.toList - val allPage: Seq[PageNode] = originalPages ++ pagesForMembers(p) - originalPage.modified(p.getName, allPage.asJava) - - override def pageForClasslike(c: DClasslike): ClasslikePageNode = c match { - case clazz: DClass => pageForDClass(clazz) - case other => throw UnsupportedOperationException("Only DClass classlike is supported.") - } - - def pageForDClass(c: DClass): ClasslikePageNode = { - val constructors = c.getConstructors - - val ext = c.get(ClasslikeExtension) + PackagePageNode( + p.name, + contentForPackage(p), + JSet(p.dri), + p, + pagesForMembers(p), + JNil + ) - val name = if c.kind == Kind.Object && ext.companion.isDefined then c.getName + "$" else c.getName + 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, - contentForClasslike(c), + contentForClass(c.asInstanceOf[DClass]), JSet(c.getDri), - c, - (constructors.asScala.map(pageForFunction) ++ - c.getClasslikes.asScala.map(pageForClasslike) ++ - c.getFunctions.asScala.map(pageForFunction) ++ - pagesForMembers(c)).asJava, - List.empty.asJava - ) - + c.asInstanceOf[DClass], + JNil, + JNil, + ).modified(name, pagesForMembers(c)) // We need override default page } override def pageForFunction(f: DFunction) = super.pageForFunction(f) @@ -114,10 +104,8 @@ class ScalaPageCreator( contentBuilder.contentForDocumentable(p, buildBlock = buildBlock) } - override def contentForClasslike(c: DClasslike) = c match { - case d: DClass => contentForClass(d) - case other => throw UnsupportedOperationException("Only DClass classlike is supported.") - } + override def contentForClasslike(c: DClasslike) = throw UnsupportedOperationException( + s"Unable to generate DClasslike using default dokka method for $c!") def contentForClass(c: DClass) = { def buildBlock = (builder: DocBuilder) => builder @@ -138,26 +126,6 @@ class ScalaPageCreator( contentBuilder.contentForDocumentable(c, buildBlock = buildBlock) } - override def contentForMember(d: Documentable) = { - def buildBlock = (builder: DocBuilder) => builder - .group(kind = ContentKind.Cover){ bd => bd.cover(d.getName)() } - .divergentGroup( - ContentDivergentGroup.GroupID("member") - ) { divbdr => divbdr - .instance(Set(d.getDri), sourceSets = d.getSourceSets.asScala.toSet) { insbdr => insbdr - .before(){ bbdr => bbdr - .contentForDescription(d) - .contentForComments(d) - } - .divergent(kind = ContentKind.Symbol) { dbdr => dbdr - .signature(d) - } - } - } - contentBuilder.contentForDocumentable(d, buildBlock = buildBlock) - } - - override def contentForFunction(f: DFunction) = contentForMember(f) extension (b: DocBuilder) def descriptionIfNotEmpty(d: Documentable): DocBuilder = { @@ -174,208 +142,27 @@ class ScalaPageCreator( res } - def contentForComments(d: Documentable) = b - - def contentForDescription(d: Documentable) = { - val specialTags = Set[Class[_]](classOf[Description]) - - type SourceSet = DokkaConfiguration$DokkaSourceSet - - val tags: List[(SourceSet, TagWrapper)] = - d.getDocumentation.asScala.toList.flatMap( (pd, doc) => doc.getChildren.asScala.map(pd -> _).toList ) - - val platforms = d.getSourceSets.asScala.toSet - - val description = tags.collect{ case (pd, d: Description) => (pd, d) }.drop(1).groupBy(_(0)).map( (key, value) => key -> value.map(_(1))) - - /** Collect the key-value pairs from `iter` into a `Map` with a `cleanup` step, - * keeping the original order of the pairs. - */ - def collectInMap[K, E, V]( - iter: Iterator[(K, E)] - )( - cleanup: List[E] => V - ): collection.Map[K, V] = { - val lhm = mutable.LinkedHashMap.empty[K, ListBuffer[E]] - iter.foreach { case (k, e) => - lhm.updateWith(k) { - case None => Some(ListBuffer.empty.append(e)) - case Some(buf) => - buf.append(e) - Some(buf) - } - } - lhm.iterator.map { case (key, buf) => key -> cleanup(buf.result)}.to(mutable.LinkedHashMap) - } - - val unnamedTags: collection.Map[(SourceSet, Class[_]), List[TagWrapper]] = - collectInMap { - tags.iterator - .filterNot { t => - t(1).isInstanceOf[NamedTagWrapper] || specialTags.contains(t(1).getClass) - }.map { t => - (t(0), t(1).getClass) -> t(1) - } - }(cleanup = identity) - - val namedTags: collection.Map[ - String, - Either[ - collection.Map[SourceSet, NamedTagWrapper], - collection.Map[(SourceSet, String), ScalaTagWrapper.NestedNamedTag], - ], - ] = { - val grouped = collectInMap { - tags.iterator.collect { - case (sourcesets, n: NamedTagWrapper) => - (n.getName, n.isInstanceOf[ScalaTagWrapper.NestedNamedTag]) -> (sourcesets, n) - } - }(cleanup = identity) - - grouped.iterator.map { - case ((name, true), values) => - val groupedValues = - values.iterator.map { - case (sourcesets, t) => - val tag = t.asInstanceOf[ScalaTagWrapper.NestedNamedTag] - (sourcesets, tag.subname) -> tag - }.to(mutable.LinkedHashMap) - name -> Right(groupedValues) - case ((name, false), values) => - name -> Left(values.to(mutable.LinkedHashMap)) - }.to(mutable.LinkedHashMap) - } - - b.group(Set(d.getDri), styles = Set(TextStyle.Block, TableStyle.Borderless)) { bdr => - val b1 = description.foldLeft(bdr){ - case (bdr, (key, value)) => bdr - .group(sourceSets = Set(key)){ gbdr => - value.foldLeft(gbdr) { (gbdr, tag) => gbdr - .comment(tag.getRoot) - } - } - } - - b1.table(kind = ContentKind.Comment, styles = Set(TableStyle.DescriptionList)){ tbdr => - val withUnnamedTags = unnamedTags.foldLeft(tbdr){ case (bdr, (key, value) ) => bdr - .cell(sourceSets = Set(key(0))){ b => b - .text(key(1).getSimpleName, styles = Set(TextStyle.Bold)) - } - .cell(sourceSets = Set(key(0))) { b => b - .list(value, separator = ""){ (bdr, elem) => bdr - .comment(elem.getRoot) - } - } - } - - val withNamedTags = namedTags.foldLeft(withUnnamedTags){ - case (bdr, (key, Left(value))) => - value.foldLeft(bdr){ case (bdr, (sourceSets, v)) => bdr - .cell(sourceSets = Set(sourceSets)){ b => b - .text(key) - } - .cell(sourceSets = Set(sourceSets)){ b => b - .comment(v.getRoot) - } - } - case (bdr, (key, Right(groupedValues))) => bdr - .cell(sourceSets = d.getSourceSets.asScala.toSet){ b => b - .text(key) - } - .cell(sourceSets = d.getSourceSets.asScala.toSet)(_.table(kind = ContentKind.Comment, styles = Set(TableStyle.NestedDescriptionList)){ tbdr => - groupedValues.foldLeft(tbdr){ case (bdr, ((sourceSets, _), v)) => bdr - .cell(sourceSets = Set(sourceSets)){ b => b - .comment(v.identTag) - } - .cell(sourceSets = Set(sourceSets)){ b => b - .comment(v.descTag) - } - } - }) - } - - val withCompanion = d match { - case d: DClass => - val ext = d.get(ClasslikeExtension) - val co = ext.companion - co.fold(withNamedTags) { co => withNamedTags - .cell(sourceSets = d.getSourceSets.asScala.toSet){ b => b - .text("Companion") - } - .cell(sourceSets = d.getSourceSets.asScala.toSet){ b => b - .driLink( - d.kind match { - case Kind.Object => "class" - case _ => "object" - }, - co - ) - } - } - case _ => withNamedTags - } - - val withExtensionInformation = d.kind match { - case Kind.Extension(on) => - val sourceSets = d.getSourceSets.asScala.toSet - withCompanion.cell(sourceSets = sourceSets)(_.text("Extension")) - .cell(sourceSets = sourceSets)(_.text(s"This function is an extension on (${on.name}: ").inlineSignature(d, on.signature).text(")")) - case _ => withCompanion - } - - val withSource = d match - case null => withExtensionInformation - case m: Member => - ctx.sourceLinks.pathTo(m).fold(withCompanion){ link => - val sourceSets = m.getSourceSets.asScala.toSet - withExtensionInformation.cell(sourceSets = sourceSets)(_.text("Source")) - .cell(sourceSets = sourceSets)(_.resolvedLink("(source)", link)) - - } - - d.deprecated match - case None => withSource - case Some(a) => - extension (b: ScalaPageContentBuilder#ScalaDocumentableContentBuilder) - def annotationParameter(p: Option[Annotation.AnnotationParameter]): ScalaPageContentBuilder#ScalaDocumentableContentBuilder = - p match - case Some(Annotation.PrimitiveParameter(_, value)) => b.text(value.stripPrefix("\"").stripSuffix("\"")) - case Some(Annotation.LinkParameter(_, dri, text)) => b.driLink(text.stripPrefix("\"").stripSuffix("\""), dri) - case Some(Annotation.UnresolvedParameter(_, value)) => b.text(value.stripPrefix("\"").stripSuffix("\"")) - case _ => b - val since = a.params.find(_.name.contains("since")) - val message = a.params.find(_.name.contains("message")) - val sourceSets = d.getSourceSets.asScala.toSet - withSource.cell(sourceSets = sourceSets)(_.text("Deprecated")) - .cell(sourceSets = sourceSets) { b => - val withPossibleSince = if (since.isDefined) b.text("(Since version ").annotationParameter(since).text(") ") else b - withPossibleSince.annotationParameter(message) - } - } - } - } + def contentForDescription(m: Member) = b.memberInfo(m) def contentForScope(s: Documentable & WithScope & WithExtraProperties[_]) = def groupExtensions(extensions: Seq[Member]): Seq[DocumentableSubGroup] = extensions.groupBy(_.kind).map { - case (Kind.Extension(on), members) => + case (Kind.Extension(on, _), members) => val signature = Signature(s"extension (${on.name}: ") join on.signature join Signature(")") DocumentableSubGroup(signature, members.toSeq) case other => sys.error(s"unexpected value: $other") }.toSeq - - val (definedMethods, inheritedMethods) = s.membersByWithInheritancePartition(_.kind == Kind.Def) - val (definedFields, inheritedFiles) = s.membersByWithInheritancePartition(m => m.kind == Kind.Val || m.kind == Kind.Var) - val (definedClasslikes, inheritedClasslikes) = s.membersByWithInheritancePartition(m => m.kind.isInstanceOf[Classlike]) - val (definedTypes, inheritedTypes) = s.membersByWithInheritancePartition(_.kind.isInstanceOf[Kind.Type]) - val (definedGivens, inheritedGives) = s.membersByWithInheritancePartition(_.kind.isInstanceOf[Kind.Given]) - val (definedExtensions, inheritedExtensions) = s.membersByWithInheritancePartition(_.kind.isInstanceOf[Kind.Extension]) - val (definedExports, inheritedExports) = s.membersByWithInheritancePartition(_.kind == Kind.Exported) - val (definedImplicits, inheritedImplicits) = s.membersByWithInheritancePartition(_.kind.isInstanceOf[Kind.Implicit]) + val (definedMethods, inheritedMethods) = s.membersBy(_.kind.isInstanceOf[Kind.Def]).byInheritance + val (definedFields, inheritedFiles) = s.membersBy(m => m.kind == Kind.Val || m.kind == Kind.Var).byInheritance + val (definedClasslikes, inheritedClasslikes) = s.membersBy(m => m.kind.isInstanceOf[Classlike]).byInheritance + val (definedTypes, inheritedTypes) = s.membersBy(_.kind.isInstanceOf[Kind.Type]).byInheritance + val (definedGivens, inheritedGives) = s.membersBy(_.kind.isInstanceOf[Kind.Given]).byInheritance + val (definedExtensions, inheritedExtensions) = s.membersBy(_.kind.isInstanceOf[Kind.Extension]).byInheritance + val (definedExports, inheritedExports) = s.membersBy(_.kind.isInstanceOf[Kind.Exported]).byInheritance + val (definedImplicits, inheritedImplicits) = s.membersBy(_.kind.isInstanceOf[Kind.Implicit]).byInheritance b - .contentForComments(s) .documentableTab("Type members")( DocumentableGroup(Some("Types"), definedTypes), DocumentableGroup(Some("Classlikes"), definedClasslikes), @@ -416,7 +203,7 @@ class ScalaPageCreator( def contentForConstructors(c: DClass) = b.documentableTab("Constructors")( - DocumentableGroup(None, c.getConstructors.asScala.toList) + DocumentableGroup(None, c.membersBy(_.kind.isInstanceOf[Kind.Constructor])) ) diff --git a/scala3doc/src/dotty/dokka/translators/ScalaSignatureProvider.scala b/scala3doc/src/dotty/dokka/translators/ScalaSignatureProvider.scala index 242e35f4d22a..dc71188a726a 100644 --- a/scala3doc/src/dotty/dokka/translators/ScalaSignatureProvider.scala +++ b/scala3doc/src/dotty/dokka/translators/ScalaSignatureProvider.scala @@ -22,7 +22,7 @@ class ScalaSignatureProvider(contentConverter: CommentsToContentConverter)(using private def signatureContent(d: Documentable)( func: ScalaPageContentBuilder#ScalaDocumentableContentBuilder => ScalaPageContentBuilder#ScalaDocumentableContentBuilder - ) = + ) = val styles = stylesIfDeprecated(d) contentBuilder.contentForDocumentable(d, kind = ContentKind.Symbol, styles = styles, buildBlock = func) @@ -46,182 +46,163 @@ class ScalaSignatureProvider(contentConverter: CommentsToContentConverter)(using private def stylesIfDeprecated(m: Member): Set[Style] = if m.deprecated.isDefined then styles ++ Set(TextStyle.Strikethrough) else styles - + object ScalaSignatureProvider: def rawSignature(documentable: Documentable, builder: SignatureBuilder): SignatureBuilder = - documentable match - case extension: DFunction if extension.kind.isInstanceOf[Kind.Extension] => - extensionSignature(extension, builder) - case method: DFunction if method.kind.isInstanceOf[Kind.Given] => - givenMethodSignature(method, builder) - case exprt: DFunction if exprt.kind == Kind.Exported => - exportedMethodSignature(exprt, builder) - case method: DFunction => - methodSignature(method, builder) - case enumEntry: DClass if enumEntry.kind == Kind.EnumCase => - enumEntrySignature(enumEntry, builder) - case givenClazz: DClass if givenClazz.kind.isInstanceOf[Kind.Given] => - givenClassSignature(givenClazz, builder) - case clazz: DClass => - classSignature(clazz, builder) - case enumProperty: DProperty if enumProperty.kind == Kind.EnumCase => - enumPropertySignature(enumProperty, builder) - case property: DProperty => - propertySignature(property, builder) - case parameter: DParameter => - parameterSignature(parameter, builder) - case _ => + documentable.kind match + case Kind.Extension(_, m) => + extensionSignature(documentable, m, builder) + case Kind.Exported(d) => + methodSignature(documentable, d, builder) + case d: Kind.Def => + methodSignature(documentable, d, builder) + case Kind.Constructor(d) => + methodSignature(documentable, d, builder) + case Kind.Implicit(d: Kind.Def, _) => + methodSignature(documentable, d, builder) + case Kind.EnumCase(cls: Kind.Class) => + enumEntrySignature(documentable, cls, builder) + case Kind.EnumCase(_) => + enumPropertySignature(documentable, builder) + case Kind.Given(cls: Kind.Class, _, _) => + givenClassSignature(documentable, cls, builder) + case Kind.Given(d: Kind.Def, _, _) => + givenMethodSignature(documentable, d, builder) + case Kind.Given(Kind.Val, _, _) => + givenPropertySignature(documentable, builder) + case cls: Kind.Class => + classSignature(documentable, cls, builder) + case Kind.Object | Kind.Enum => + objectSignature(documentable, builder) + case trt: Kind.Trait => + traitSignature(documentable, trt, builder) + case Kind.Val | Kind.Var | Kind.Implicit(Kind.Val, _) => + fieldSignature(documentable, documentable.kind.name, builder) + case tpe: Kind.Type => + typeSignature(tpe, documentable, builder) + case Kind.Unknown => ??? - - private def enumEntrySignature(entry: DClass, bdr: SignatureBuilder): SignatureBuilder = - val ext = entry.get(ClasslikeExtension) + private def enumEntrySignature(member: Member, cls: Kind.Class, bdr: SignatureBuilder): SignatureBuilder = val withPrefixes: SignatureBuilder = bdr .text("case ") - .name(entry.getName, entry.getDri) - .generics(entry) + .memberName(member.name, member.dri) + .generics(cls.typeParams) + + val withParameters = withPrefixes.functionParameters(cls.argsLists) + parentsSignature(member, withParameters) - val withParameters = ext.constructor.toSeq.foldLeft(withPrefixes){ (bdr, elem) => - bdr.functionParameters(elem) + private def enumPropertySignature(entry: Member, builder: SignatureBuilder): SignatureBuilder = + val modifiedType = entry.signature.map { + case " & " => " with " + case o => o } - parentsSignature(entry, withParameters) - - private def enumPropertySignature(entry: DProperty, builder: SignatureBuilder): SignatureBuilder = - val modifiedType = entry.getType match - case t: TypeConstructor => GenericTypeConstructor( - t.getDri, - t.getProjections.asScala.map{ - case t: UnresolvedBound if t.getName == " & " => UnresolvedBound(" with "); - case other => other - }.asJava, - null - ) - case other => other builder .text("case ") - .name(entry.getName, entry.getDri) + .name(entry.name, entry.dri) .text(" extends ") - .typeSignature(modifiedType) + .signature(modifiedType) - private def parentsSignature(d: DClass, builder: SignatureBuilder): SignatureBuilder = - d.directParents match + private def parentsSignature(member: Member, builder: SignatureBuilder): SignatureBuilder = + member.directParents match case Nil => builder case extendType :: withTypes => val extendPart = builder.text(" extends ").signature(extendType) withTypes.foldLeft(extendPart)((bdr, tpe) => bdr.text(" with ").signature(tpe)) - private def givenClassSignature(clazz: DClass, builder: SignatureBuilder): SignatureBuilder = - val ext = clazz.get(ClasslikeExtension) + private def givenClassSignature(member: Member, cls: Kind.Class, builder: SignatureBuilder): SignatureBuilder = val prefixes = builder - .modifiersAndVisibility(clazz, "given") + .modifiersAndVisibility(member, "given") + .name(member.name, member.dri) + .generics(cls.typeParams) + .functionParameters(cls.argsLists) + + member.kind match + case Kind.Given(_, Some(instance), _) => prefixes + .text(": ") + .signature(instance) + case _ => prefixes + + private def classSignature(clazz: Member, cls: Kind.Class, builder: SignatureBuilder): SignatureBuilder = + val selfSignature = builder + .modifiersAndVisibility(clazz, clazz.kind.name) .name(clazz.getName, clazz.getDri) - .generics(clazz) + .generics(cls.typeParams) + .functionParameters(cls.argsLists) - val withGenerics = ext.constructor.toSeq.foldLeft(prefixes){ (bdr, elem) => - bdr.functionParameters(elem) - } - clazz.kind match - case Kind.Given(Some(instance), _) => withGenerics - .text(" as ") - .signature(instance) - case _ => withGenerics + parentsSignature(clazz, selfSignature) - private def classSignature(clazz: DClass, builder: SignatureBuilder): SignatureBuilder = - val ext = clazz.get(ClasslikeExtension) - val prefixes = builder + private def objectSignature(clazz: Member, builder: SignatureBuilder): SignatureBuilder = + val selfSignature = builder .modifiersAndVisibility(clazz, clazz.kind.name) .name(clazz.getName, clazz.getDri) - .generics(clazz) - val withGenerics = ext.constructor.toSeq.foldLeft(prefixes){ (bdr, elem) => - bdr.functionParameters(elem) - } - parentsSignature(clazz, withGenerics) + parentsSignature(clazz, selfSignature) - private def extensionSignature(extension: DFunction, builder: SignatureBuilder): SignatureBuilder = - val extendedSymbol = if (extension.isRightAssociative()) { - extension.getParameters.asScala(extension.get(MethodExtension).parametersListSizes(0)) - } else { - extension.getParameters.asScala(0) - } + private def traitSignature(clazz: Member, cls: Kind.Trait, builder: SignatureBuilder): SignatureBuilder = + val selfSignature = builder + .modifiersAndVisibility(clazz, clazz.kind.name) + .name(clazz.getName, clazz.getDri) + .generics(cls.typeParams) + .functionParameters(cls.argsLists) + + parentsSignature(clazz, selfSignature) + + private def extensionSignature(extension: Member, fun: Kind.Def, builder: SignatureBuilder): SignatureBuilder = val withSignature = builder .modifiersAndVisibility(extension, "def") .name(extension.getName, extension.getDri) - .generics(extension) - .functionParameters(extension) + .generics(fun.typeParams) + .functionParameters(fun.argsLists) - if extension.isConstructor then withSignature - else withSignature.text(":").text(" ").typeSignature(extension.getType) + withSignature.text(":").text(" ").signature(extension.signature) - private def givenMethodSignature(method: DFunction, builder: SignatureBuilder): SignatureBuilder = method.kind match - case Kind.Given(Some(instance), _) => + private def givenMethodSignature(method: Member, body: Kind.Def, builder: SignatureBuilder): SignatureBuilder = method.kind match + case Kind.Given(_, Some(instance), _) => builder.text("given ") - .name(method.getName, method.getDri) - .text(" as ") + .name(method.name, method.dri) + .text(": ") .signature(instance) case _ => - builder.text("given ").name(method.getName, method.getDri) - - private def exportedMethodSignature(method: DFunction, builder: SignatureBuilder): SignatureBuilder = - methodSignature(method, builder) + builder.text("given ").name(method.name, method.dri) - private def methodSignature(method: DFunction, builder: SignatureBuilder): SignatureBuilder = + private def methodSignature(method: Member, cls: Kind.Def, builder: SignatureBuilder): SignatureBuilder = val bdr = builder .modifiersAndVisibility(method, "def") .name(method.getName, method.getDri) - .generics(method) - .functionParameters(method) - if !method.isConstructor then - bdr - .text(":") - .text(" ") - .typeSignature(method.getType) + .generics(cls.typeParams) + .functionParameters(cls.argsLists) + if !method.kind.isInstanceOf[Kind.Constructor] then + bdr.text(": ").signature(method.signature) else bdr - - private def propertySignature(property: DProperty, builder: SignatureBuilder): SignatureBuilder = - property.kind match - case _: Kind.Given => givenPropertySignature(property, builder) - case tpe: Kind.Type => typeSignature(tpe, property, builder) - case other => fieldSignature(property, other.name, builder) - - - private def typeSignature(tpe: Kind.Type, typeDef: DProperty, builder: SignatureBuilder): SignatureBuilder = + 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) - .generics(typeDef) + .generics(tpe.typeParams) if(!tpe.opaque){ (if tpe.concreate then bdr.text(" = ") else bdr) - .typeSignature(typeDef.getType) + .signature(typeDef.signature) } else bdr - private def givenPropertySignature(property: DProperty, builder: SignatureBuilder): SignatureBuilder = + private def givenPropertySignature(property: Member, builder: SignatureBuilder): SignatureBuilder = val bdr = builder .text("given ") - .name(property.getName, property.getDri) + .name(property.name, property.dri) property.kind match - case Kind.Given(Some(instance), _) => + case Kind.Given(_, Some(instance), _) => bdr.text(" as ").signature(instance) case _ => bdr - private def fieldSignature(property: DProperty, kind: String, builder: SignatureBuilder): SignatureBuilder = + private def fieldSignature(member: Member, kind: String, builder: SignatureBuilder): SignatureBuilder = builder - .modifiersAndVisibility(property, kind) - .name(property.getName, property.getDri) + .modifiersAndVisibility(member, kind) + .name(member.name, member.dri) .text(":") .text(" ") - .typeSignature(property.getType) - - private def parameterSignature(parameter: DParameter, builder: SignatureBuilder): SignatureBuilder = - val ext = parameter.get(ParameterExtension) - builder - .text(if ext.isGrouped then "extension (" else "(") - .text(parameter.getName) - .text(": ") - .typeSignature(parameter.getType) - .text(")") + .signature(member.signature) diff --git a/scala3doc/src/dotty/dokka/translators/ScalaSignatureUtils.scala b/scala3doc/src/dotty/dokka/translators/ScalaSignatureUtils.scala index 30384ee39d1f..e0bf9808a4e0 100644 --- a/scala3doc/src/dotty/dokka/translators/ScalaSignatureUtils.scala +++ b/scala3doc/src/dotty/dokka/translators/ScalaSignatureUtils.scala @@ -2,7 +2,7 @@ package dotty.dokka import org.jetbrains.dokka.base.signatures._ import org.jetbrains.dokka.base.translators.documentables.PageContentBuilder -import org.jetbrains.dokka.model._ +import org.jetbrains.dokka.model.{ TypeParameter => _, _ } import org.jetbrains.dokka.model.properties.WithExtraProperties import org.jetbrains.dokka.pages._ import collection.JavaConverters._ @@ -12,6 +12,7 @@ case class InlineSignatureBuilder(names: Signature = Nil, preName: Signature = N override def text(str: String): SignatureBuilder = copy(names = str +: names) override def name(str: String, dri: DRI): SignatureBuilder = copy(names = Nil, preName = names) override def driLink(text: String, dri: DRI): SignatureBuilder = copy(names = Link(text, dri) +: names) + override def signature(s: Signature): SignatureBuilder = copy(names = s.reverse ++ names) object InlineSignatureBuilder: def typeSignatureFor(d: Documentable): Signature = @@ -21,14 +22,16 @@ trait SignatureBuilder extends ScalaSignatureUtils { def text(str: String): SignatureBuilder def name(str: String, dri: DRI) = driLink(str, dri) def driLink(text: String, dri: DRI): SignatureBuilder - - def signature(s: Signature) = s.foldLeft(this){ (b, e) => e match - case Link(name, dri) => b.driLink(name, dri) - case txt: String => b.text(txt) + def signature(s: Signature): SignatureBuilder = s.foldLeft(this){ + case (bld, s: String) => bld.text(s) + case (bld, Link(text: String, dri: DRI)) => bld.driLink(text, dri) } + // Support properly once we rewrite signature builder + def memberName(name: String, dri: DRI) = text(name) + def list[E]( - elements: List[E], + elements: Seq[E], prefix: String = "", suffix: String = "", separator: String = ", ", @@ -43,11 +46,11 @@ trait SignatureBuilder extends ScalaSignatureUtils { def annotationsBlock(d: Member): SignatureBuilder = d.annotations.foldLeft(this){ (bdr, annotation) => bdr.buildAnnotation(annotation)} - def annotationsInline(d: Documentable with WithExtraProperties[_]): SignatureBuilder = + def annotationsInline(d: Parameter): SignatureBuilder = d.annotations.foldLeft(this){ (bdr, annotation) => bdr.buildAnnotation(annotation) } private def buildAnnotation(a: Annotation): SignatureBuilder = - text("@").driLink(a.dri.getClassNames, a.dri).buildAnnotationParams(a).text(" ") + text("@").driLink(a.dri.location.split('.').last, a.dri).buildAnnotationParams(a).text(" ") private def buildAnnotationParams(a: Annotation): SignatureBuilder = if !a.params.isEmpty then @@ -68,51 +71,28 @@ trait SignatureBuilder extends ScalaSignatureUtils { addParameterName(name).text(value) } - def modifiersAndVisibility(t: Documentable with WithAbstraction with WithVisibility with WithExtraProperties[_], kind: String) = - import org.jetbrains.dokka.model.properties._ - val extras = t.getExtra.getMap() + def modifiersAndVisibility(t: Member, kind: String) = val (prefixMods, suffixMods) = t.modifiers.partition(_.prefix) val all = prefixMods.map(_.name) ++ Seq(t.visibility.asSignature) ++ suffixMods.map(_.name) text(all.toSignatureString()).text(kind + " ") - def typeSignature(b: Projection): SignatureBuilder = b match { - case tc: TypeConstructor => - tc.getProjections.asScala.foldLeft(this) { (bdr, elem) => elem match { - case text: UnresolvedBound => bdr.text(text.getName) - case link: TypeParameter => - bdr.driLink(link.getName, link.getDri) - case other => - bdr.text(s"TODO($other)") - } - } - case other => - text(s"TODO: $other") + def generics(on: Seq[TypeParameter]) = list(on.toList, "[", "]"){ (bdr, e) => + bdr.text(e.variance).memberName(e.name, e.dri).signature(e.signature) } - def generics(on: WithGenerics) = list(on.getGenerics.asScala.toList, "[", "]"){ (bdr, e) => - val bldr = bdr.text(e.getName) - e.getBounds.asScala.foldLeft(bldr)( (b, bound) => b.typeSignature(bound)) - } - - def functionParameters(method: DFunction) = - val methodExtension = method.get(MethodExtension) - val receiverPos = if method.isRightAssociative() then methodExtension.parametersListSizes(0) else 0 - val (bldr, index) = methodExtension.parametersListSizes.foldLeft(this, 0){ - case ((builder, from), size) => - val toIndex = from + size - if from == toIndex then (builder.text("()"), toIndex) - else if !method.kind.isInstanceOf[Kind.Extension] || from != receiverPos then - val b = builder.list(method.getParameters.subList(from, toIndex).asScala.toList, "(", ")"){ (bdr, param) => bdr - .annotationsInline(param) - .text(param.getName) + def functionParameters(params: Seq[Seq[Parameter]]) = + if params.isEmpty then this.text("") + else if params == List(Nil) then this.text("()") + else this.list(params, separator = ""){ (bld, pList) => + bld.list(pList, "(", ")"){ (bld, p) => + bld.annotationsInline(p) + .text(p.modifiers) + .memberName(p.name, p.dri) .text(": ") - .typeSignature(param.getType) - } - (b, toIndex) - else (builder, toIndex) + .signature(p.signature) + } } - bldr } trait ScalaSignatureUtils: diff --git a/scala3doc/src/dotty/renderers/DotDiagramBuilder.scala b/scala3doc/src/dotty/renderers/DotDiagramBuilder.scala index 226d4e6ae394..bebd8f3bb82b 100644 --- a/scala3doc/src/dotty/renderers/DotDiagramBuilder.scala +++ b/scala3doc/src/dotty/renderers/DotDiagramBuilder.scala @@ -8,7 +8,15 @@ import HTML._ import dotty.dokka.model.api._ object DotDiagramBuilder: - def build(diagram: HierarchyGraph, renderer: SignatureRenderer): String = + def build(diagram: HierarchyGraph, renderer: SignatureRenderer)(using DocContext): String = + def getStyle(vertex: LinkToType) = vertex.kind match + case _ : Kind.Class => "fill: #45AD7D;" + case Kind.Object => "fill: #285577;" + case _ : Kind.Trait => "fill: #1CAACF;" + case Kind.Enum => "fill: #B66722;" + case _ : Kind.EnumCase => "fill: #B66722;" + case other => report.error(s"unexpected value: $other") + val vWithId = diagram.verteciesWithId val vertecies = vWithId.map { (vertex, id) => s"""node${id} [label="${getHtmlLabel(vertex, renderer)}", style="${getStyle(vertex)}"];\n""" @@ -25,16 +33,6 @@ object DotDiagramBuilder: |} |""".stripMargin - - private def getStyle(vertex: LinkToType) = vertex.kind match - case Kind.Class => "fill: #45AD7D;" - case Kind.Object => "fill: #285577;" - case Kind.Trait => "fill: #1CAACF;" - case Kind.Enum => "fill: #B66722;" - case Kind.EnumCase => "fill: #B66722;" - case other => sys.error(s"unexpected value: $other") - - private def getHtmlLabel(vertex: LinkToType, renderer: SignatureRenderer): String = span(style := "color: #FFFFFF;")( vertex.kind.name, diff --git a/scala3doc/src/dotty/renderers/MemberRenderer.scala b/scala3doc/src/dotty/renderers/MemberRenderer.scala new file mode 100644 index 000000000000..0c2205356dd2 --- /dev/null +++ b/scala3doc/src/dotty/renderers/MemberRenderer.scala @@ -0,0 +1,118 @@ +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._ + +class MemberRenderer(signatureRenderer: SignatureRenderer, buildNode: ContentNode => String)(using DocContext): + + private val converter = new DocTagToContentConverter() + + 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 fullSignature(m: Member): AppliedTag = raw("signature: TODO!") + + def doc(m: Member): Seq[AppliedTag] = m.docs.fold(Nil)(d => Seq(renderDocPart(d.body))) + + def tableRow(name: String, content: AppliedTag) = Seq(dt(name), dd(content)) + + def defintionClasses(m: Member) = m.origin match + case Origin.Overrides(defs) => + def renderDef(d: Overriden): Seq[TagArg] = + Seq(signatureRenderer.renderLink(d.name, d.dri), " -> ") + + val nodes: Seq[TagArg] = defs.flatMap(renderDef).dropRight(1) // drop trailing arrow + tableRow("Definition Classes", div(nodes:_*)) + + case _ => Nil + + def docAttributes(m: Member): Seq[AppliedTag] = + + def nested(name: String, on: SortedMap[String, DocPart]): Seq[AppliedTag] = if on.isEmpty then Nil else + tableRow(name, dl(cls := "attributes")( + on.map { case (name, value) => tableRow(name, renderDocPart(value))}.toList:_* + )) + + def list(name: String, on: List[DocPart]): Seq[AppliedTag] = if on.isEmpty then Nil else + tableRow(name, div(on.map(e => div(renderDocPart(e))))) + + def opt(name: String, on: Option[DocPart]): Seq[AppliedTag] = if on.isEmpty then Nil else + tableRow(name, renderDocPart(on.get)) + + m.docs.fold(Nil)(d => + nested("Type Params", d.typeParams) ++ + nested("Value Params", d.valueParams) ++ + opt("Returns", d.result) ++ + nested("Throws", d.throws.transform((_, v) => v._1)) ++ + opt("Constructor", d.constructor) ++ + list("Authors", d.authors) ++ + list("See also", d.see) ++ + opt("Version", d.version) ++ + opt("Since", d.since) ++ + list("Todo", d.todo) ++ + list("Note", d.note) ++ + list("Example", d.example) + ) + + def companion(m: Member): Seq[AppliedTag] = m.companion.fold(Nil){dri => + val kindName = if m.kind == Kind.Object then "class" else "object" + tableRow("Companion", signatureRenderer.renderLink(kindName, dri)) + } + + def source(m: Member): Seq[AppliedTag] = + summon[DocContext].sourceLinks.pathTo(m).fold(Nil){ link => + tableRow("Source", a(href := link)("(source)")) + } + + def deprecation(m: Member): Seq[AppliedTag] = m.deprecated.fold(Nil){ a => + def stripQuotes(s: String) = s.stripPrefix("\"").stripSuffix("\"") + def parameter(p: Annotation.AnnotationParameter): TagArg = p match + case Annotation.PrimitiveParameter(_, value) => stripQuotes(value) + case Annotation.LinkParameter(_, dri, text) => + signatureRenderer.renderLink(stripQuotes(text), dri) + case Annotation.UnresolvedParameter(_, value) => stripQuotes(value) + + val (named, unnamed) = a.params.partition(_.name.nonEmpty) + val message = named.find(_.name.get == "message").orElse(unnamed.headOption) + val since = named.find(_.name.get == "since").orElse(unnamed.drop(1).headOption) + + val content = + since.fold(Nil)(since => + Seq(code("[Since version ", parameter(since), "] ")) ++ + message.fold(Nil)(m => Seq(parameter(m))) ++ + m.docs.fold(Nil)(_.deprecated.toSeq.map(renderDocPart)) + ) + Seq(dt("Deprecated"), dd(content:_*)) + } + + 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) + + Seq( + div(cls := "documentableBrief doc")(bodyContents.take(1)), + div(cls := "cover")( + div(cls := "doc")(bodyContents.drop(1)), + dl(cls := "attributes")( + docAttributes(m), + companion(m), + deprecation(m), + defintionClasses(m), + source(m), + ) + ) + ) \ No newline at end of file diff --git a/scala3doc/src/dotty/renderers/ScalaHtmlRenderer.scala b/scala3doc/src/dotty/renderers/ScalaHtmlRenderer.scala index acac28b06a4f..5f7180a38af7 100644 --- a/scala3doc/src/dotty/renderers/ScalaHtmlRenderer.scala +++ b/scala3doc/src/dotty/renderers/ScalaHtmlRenderer.scala @@ -18,7 +18,9 @@ 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 @@ -103,39 +105,51 @@ class ScalaHtmlRenderer(using ctx: DokkaContext) extends HtmlRenderer(ctx) { val ss = if sourceSetRestriciton == null then Set.empty.asJava else sourceSetRestriciton withHtml(f, buildDocumentableList(n, pageContext, ss).toString()) case n: DocumentableFilter => withHtml(f, buildDocumentableFilter.toString) + case mi: MemberInfo => + val memberHtml = div(renderers(pageContext)._2.memberInfo(mi.member)) + withHtml(f, memberHtml.toString) case other => super.buildContentNode(f, node, pageContext, sourceSetRestriciton) } } - + private def renderers(pageContext: ContentPage): (SignatureRenderer, MemberRenderer) = + val renderer = SignatureRenderer(pageContext, sourceSets, getLocationProvider) + (renderer, new MemberRenderer(renderer, buildWithKotlinx(_, pageContext, null))) private def buildDocumentableList(n: DocumentableList, pageContext: ContentPage, sourceSetRestriciton: JSet[DisplaySourceSet]) = def render(n: ContentNode) = raw(buildWithKotlinx(n, pageContext, null)) - val renderer = SignatureRenderer(pageContext, sourceSets, getLocationProvider) + val (renderer, memberRenderer) = renderers(pageContext) import renderer._ def buildDocumentable(element: DocumentableElement) = - def topLevelAttr = Seq(cls := "documentableElement") ++ element.attributes.map{ case (n, v) => Attr(s"data-f-$n") := v } + def topLevelAttr = Seq(cls := "documentableElement") + ++ element.params.dri.anchor.map(id := _) + ++ element.attributes.map{ case (n, v) => Attr(s"data-f-$n") := v } val kind = element.modifiers.takeRight(1) val otherModifiers = element.modifiers.dropRight(1) + val nameStyles = element.nameWithStyles.styles.map(_.toString.toLowerCase).mkString(" ") + val nameClasses = cls := s"documentableName monospace ${nameStyles.mkString(" ")}" + div(topLevelAttr:_*)( - div(cls := "annotations monospace")(element.annotations.map(renderElement)), - div( - a(href:=link(element.params.dri).getOrElse("#"), cls := "documentableAnchor"), + a(href:=link(element.params.dri).getOrElse("#"), cls := "documentableAnchor"), + div(span(cls := "annotations monospace")(element.annotations.map(renderElement))), + div(cls := "header")( span(cls := "modifiers monospace")( span(cls := "other-modifiers")(otherModifiers.map(renderElement)), span(cls := "kind")(kind.map(renderElement)), ), - renderLink(element.nameWithStyles.name, element.params.dri, cls := s"documentableName monospace ${element.nameWithStyles.styles.map(_.toString.toLowerCase).mkString(" ")}"), + renderLink(element.nameWithStyles.name, element.params.dri, nameClasses), span(cls := "signature monospace")(element.signature.map(renderElement)), + ), + div(cls := "docs")( + span(cls := "modifiers monospace"), div( div(cls := "originInfo")(element.originInfo.map(renderElement)), - div(cls := "documentableBrief")(element.brief.map(render)), + div(cls := "documentableBrief")(memberRenderer.memberInfo(element.member)), ) - ), - + ) ) div(cls := "documentableList", testId := "definitionList")( diff --git a/scala3doc/src/dotty/renderers/html.scala b/scala3doc/src/dotty/renderers/html.scala index b5cd9668fa5f..a5a4297cdc3e 100644 --- a/scala3doc/src/dotty/renderers/html.scala +++ b/scala3doc/src/dotty/renderers/html.scala @@ -31,7 +31,7 @@ object HTML: case a: AppliedTag => sb.append(a) case s: String => - sb.append(s.escapeReservedTokens) + sb.append(s.escapeReservedTokens) } } sb.append(s"") @@ -77,6 +77,10 @@ object HTML: val body = Tag("body") val nav = Tag("nav") val img = Tag("img") + val ul = Tag("ul") + val li = Tag("li") + val code = Tag("code") + val cls = Attr("class") val href = Attr("href") @@ -94,4 +98,4 @@ object HTML: val alt = Attr("alt") def raw(content: String): AppliedTag = AppliedTag(content) - + def raw(content: StringBuilder): AppliedTag = content diff --git a/scala3doc/test/dotty/dokka/SignatureTestCases.scala b/scala3doc/test/dotty/dokka/SignatureTestCases.scala index 57c8a5f7b105..d41d80d10f8f 100644 --- a/scala3doc/test/dotty/dokka/SignatureTestCases.scala +++ b/scala3doc/test/dotty/dokka/SignatureTestCases.scala @@ -50,7 +50,7 @@ class StructuralTypes extends SignatureTest("structuralTypes", SignatureTest.mem class OpaqueTypes extends SignatureTest("opaqueTypes", SignatureTest.all) -// class GivenSignatures extends SignatureTest("givenSignatures", SignatureTest.all) +class GivenSignatures extends SignatureTest("givenSignatures", SignatureTest.all) class Annotations extends SignatureTest("annotations", SignatureTest.all) diff --git a/scala3doc/test/dotty/dokka/SourceLinksTests.scala b/scala3doc/test/dotty/dokka/SourceLinksTests.scala index 5d99c765ed5b..5a624f09a175 100644 --- a/scala3doc/test/dotty/dokka/SourceLinksTests.scala +++ b/scala3doc/test/dotty/dokka/SourceLinksTests.scala @@ -83,16 +83,16 @@ class SourceLinksTest: @Test def testBasicPaths = testLink(Seq("github://lampepfl/dotty"), Some("develop"))( - "project/Build.scala" -> "https://github.com/lampepfl/dotty/blob/develop/project/Build.scala#L", + "project/Build.scala" -> "https://github.com/lampepfl/dotty/blob/develop/project/Build.scala", ("project/Build.scala", 54) -> "https://github.com/lampepfl/dotty/blob/develop/project/Build.scala#L54", - ("project/Build.scala", edit) -> "https://github.com/lampepfl/dotty/edit/develop/project/Build.scala#L", + ("project/Build.scala", edit) -> "https://github.com/lampepfl/dotty/edit/develop/project/Build.scala", ("project/Build.scala", 54, edit) -> "https://github.com/lampepfl/dotty/edit/develop/project/Build.scala#L54", ) testLink(Seq("gitlab://lampepfl/dotty"), Some("develop"))( - "project/Build.scala" -> "https://gitlab.com/lampepfl/dotty/-/blob/develop/project/Build.scala#L", + "project/Build.scala" -> "https://gitlab.com/lampepfl/dotty/-/blob/develop/project/Build.scala", ("project/Build.scala", 54) -> "https://gitlab.com/lampepfl/dotty/-/blob/develop/project/Build.scala#L54", - ("project/Build.scala", edit) -> "https://gitlab.com/lampepfl/dotty/-/edit/develop/project/Build.scala#L", + ("project/Build.scala", edit) -> "https://gitlab.com/lampepfl/dotty/-/edit/develop/project/Build.scala", ("project/Build.scala", 54, edit) -> "https://gitlab.com/lampepfl/dotty/-/edit/develop/project/Build.scala#L54", ) @@ -113,7 +113,7 @@ class SourceLinksTest: @Test def testBasicPrefixedPaths = testLink(Seq("src=gitlab://lampepfl/dotty"), Some("develop"))( - "src/lib/core.scala" -> "https://gitlab.com/lampepfl/dotty/-/blob/develop/src/lib/core.scala#L", + "src/lib/core.scala" -> "https://gitlab.com/lampepfl/dotty/-/blob/develop/src/lib/core.scala", ("src/lib/core.scala", 33, edit) -> "https://gitlab.com/lampepfl/dotty/-/edit/develop/src/lib/core.scala#L33", ("src/lib/core.scala", 33, edit) -> "https://gitlab.com/lampepfl/dotty/-/edit/develop/src/lib/core.scala#L33", "build.sbt" -> None diff --git a/scala3doc/test/dotty/dokka/linking/DriTestCases.scala b/scala3doc/test/dotty/dokka/linking/DriTestCases.scala index 527415997c4d..9dba3f3d752a 100644 --- a/scala3doc/test/dotty/dokka/linking/DriTestCases.scala +++ b/scala3doc/test/dotty/dokka/linking/DriTestCases.scala @@ -1,5 +1,6 @@ package dotty.dokka package linking +import org.junit.Assert.assertTrue import org.junit.Ignore @@ -11,6 +12,13 @@ class GenericTest extends DriTest("genericDRI") 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 diff --git a/scala3doc/test/dotty/dokka/testUtils.scala b/scala3doc/test/dotty/dokka/testUtils.scala index 54dcb1d6de4b..8d3db74371a3 100644 --- a/scala3doc/test/dotty/dokka/testUtils.scala +++ b/scala3doc/test/dotty/dokka/testUtils.scala @@ -53,7 +53,7 @@ class TestReporter extends ConsoleReporter: def testArgs(files: Seq[File] = Nil, dest: File = new File("notUsed")) = Scala3doc.Args( name = "Test Project Name", output = dest, - tastyFiles = files + tastyFiles = files, ) def testContext = (new ContextBase).initialCtx.fresh.setReporter(new TestReporter)