Skip to content

Scala3doc: Add support for annotations in type parameters, add tests for external location providing, minor fixes #10833

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Dec 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions scala3doc-testcases/src/tests/externalLocations/javadoc.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package tests.externalJavadoc

import java.util._

class Test {
def a: Map.Entry[String, String] = ???

def b: java.util.Map[String, Int] = ???

def c: java.util.stream.Stream.Builder[String] = ???
}

12 changes: 12 additions & 0 deletions scala3doc-testcases/src/tests/externalLocations/scala3doc.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package tests.externalScala3doc

import scala.util.matching._

class Test {
def a: String = ???

def b: Map[String, Int] = ???

def c: Regex.Match = ???
}

12 changes: 12 additions & 0 deletions scala3doc-testcases/src/tests/externalLocations/scaladoc.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package tests.externalScaladoc

import scala.util.matching._

class Test {
def a: String = ???

def b: Map[String, Int] = ???

def c: Regex.Match = ???
}

4 changes: 4 additions & 0 deletions scala3doc-testcases/src/tests/objectSignatures.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,9 @@ object Base

object A2 extends A[String] with C

object <

object >

// We are not going to add final below
// final object B
14 changes: 14 additions & 0 deletions scala3doc-testcases/src/tests/specializedSignature.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package tests

package specializedSignature

import scala.{specialized}

trait AdditiveMonoid[@specialized(Int, Long, Float, Double) A]
{
def a: A
= ???

def b[@specialized(Int, Float) B]: B
= ???
}
36 changes: 17 additions & 19 deletions scala3doc/src/dotty/dokka/ExternalDocLink.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package dotty.dokka

import java.net.URL
import scala.util.matching._
import scala.util.Try
import scala.util.{ Try, Success, Failure }

case class ExternalDocLink(
originRegexes: List[Regex],
Expand All @@ -18,25 +18,23 @@ enum DocumentationKind:
case Scala3doc extends DocumentationKind

object ExternalDocLink:
def parse(mapping: String)(using CompilerContext): Option[ExternalDocLink] =
def fail(msg: String) =
report.warning(s"Unable to parocess external mapping $mapping. $msg")
None
def parse(mapping: String): Either[String, ExternalDocLink] =
def fail(msg: String) = Left(s"Unable to process external mapping $mapping. $msg")

def tryParse[T](descr: String)(op: => T): Option[T] = try Some(op) catch
case e: RuntimeException =>
report.warn(s"Unable to parse $descr", e)
None
def tryParse[T](descr: String)(op: => T): Either[String, T] = Try(op) match {
case Success(v) => Right(v)
case Failure(e) => fail(s"Unable to parse $descr. Exception $e occured")
}

def parsePackageList(elements: List[String]) = elements match
case List(urlStr) => tryParse("packageList")(Option(URL(urlStr)))
case Nil => Some(None)
case List(urlStr) => tryParse("packageList")(Some(URL(urlStr)))
case Nil => Right(None)
case other => fail(s"Provided multiple package lists: $other")

def doctoolByName(name: String) = name match
case "javadoc" => Some(DocumentationKind.Javadoc)
case "scaladoc" => Some(DocumentationKind.Scaladoc)
case "scala3doc" => Some(DocumentationKind.Scala3doc)
case "javadoc" => Right(DocumentationKind.Javadoc)
case "scaladoc" => Right(DocumentationKind.Scaladoc)
case "scala3doc" => Right(DocumentationKind.Scala3doc)
case other => fail(s"Unknown doctool: $other")


Expand All @@ -48,10 +46,10 @@ object ExternalDocLink:
doctool <- doctoolByName(docToolStr)
packageList <- parsePackageList(rest)
} yield ExternalDocLink(
List(regex),
url,
doctool,
packageList
)
List(regex),
url,
doctool,
packageList
)
case _ =>
fail("Accepted format: `regexStr::docToolStr::urlStr[::rest]`")
8 changes: 7 additions & 1 deletion scala3doc/src/dotty/dokka/Scala3docArgs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,13 @@ object Scala3docArgs:
}
}
val externalMappings =
externalDocumentationMappings.get.flatMap(ExternalDocLink.parse)
externalDocumentationMappings.get.flatMap( s =>
ExternalDocLink.parse(s).fold(left => {
report.warning(left)
None
}, right => Some(right)
)
)

unsupportedSettings.filter(s => s.get != s.default).foreach { s =>
report.warning(s"Setting ${s.name} is currently not supported.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,37 +18,40 @@ class ScalaExternalLocationProvider(
externalDocumentation: ExternalDocumentation,
extension: String,
kind: DocumentationKind
)(using ctx: DokkaContext) extends DefaultExternalLocationProvider(externalDocumentation, extension, ctx):
) extends ExternalLocationProvider:
def docURL = externalDocumentation.getDocumentationURL.toString.stripSuffix("/") + "/"
override def resolve(dri: DRI): String =
Option(externalDocumentation.getPackageList).map(_.getLocations.asScala.toMap).flatMap(_.get(dri.toString))
.fold(constructPath(dri))( l => {
this.getDocURL + l
this.docURL + l
}
)

private val originRegex = raw"\[origin:(.*)\]".r

override def constructPath(dri: DRI): String = kind match {
def constructPath(dri: DRI): String = kind match {
case DocumentationKind.Javadoc => constructPathForJavadoc(dri)
case DocumentationKind.Scaladoc => constructPathForScaladoc(dri)
case DocumentationKind.Scala3doc => constructPathForScala3doc(dri)
}

//TODO #263: Add anchor support

private def constructPathForJavadoc(dri: DRI): String = {
val location = "\\$+".r.replaceAllIn(dri.location.replace(".","/"), _ => ".")
val origin = originRegex.findFirstIn(dri.extra)
val anchor = dri.anchor
getDocURL + location + extension + anchor.fold("")(a => s"#$a")
docURL + location + extension
}

private def constructPathForScaladoc(dri: DRI): String = {
val location = dri.location.replace(".","/")
val anchor = dri.anchor
getDocURL + location + extension + anchor.fold("")(a => s"#$a")
docURL + location + extension
}

private def constructPathForScala3doc(dri: DRI): String = {
val location = dri.location.replace(".","/")
val anchor = dri.anchor
getDocURL + location + anchor.fold(extension)(a => s"/$a$extension")
docURL + location + extension
}
1 change: 1 addition & 0 deletions scala3doc/src/dotty/dokka/model/api/api.scala
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ case class Parameter(
)

case class TypeParameter(
annotations: Seq[Annotation],
variance: "" | "+" | "-",
name: String,
dri: DRI,
Expand Down
19 changes: 13 additions & 6 deletions scala3doc/src/dotty/dokka/tasty/BasicSupport.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,22 @@ trait BasicSupport:
export SymOps._

def parseAnnotation(annotTerm: Term): Annotation =
import dotty.tools.dotc.ast.Trees.{SeqLiteral}
val dri = annotTerm.tpe.typeSymbol.dri
def inner(t: Term): List[Annotation.AnnotationParameter] = t match {
case i: Ident => List(Annotation.LinkParameter(None, i.tpe.typeSymbol.dri, i.name))
case Typed(term, tpeTree) => inner(term)
case SeqLiteral(args, tpeTree) => args.map(_.asInstanceOf[Term]).flatMap(inner)
case Literal(constant) => List(Annotation.PrimitiveParameter(None, constant.show))
case NamedArg(name, Literal(constant)) => List(Annotation.PrimitiveParameter(Some(name), constant.show))
case x @ Select(qual, name) => List.empty
case other => List(Annotation.UnresolvedParameter(None, other.show))
}


val params = annotTerm match
case Apply(target, appliedWith) => {
appliedWith.flatMap {
case Literal(constant) => Some(Annotation.PrimitiveParameter(None, constant.show))
case NamedArg(name, Literal(constant)) => Some(Annotation.PrimitiveParameter(Some(name), constant.show))
case x @ Select(qual, name) => None
case other => Some(Annotation.UnresolvedParameter(None, other.show))
}
appliedWith.flatMap(inner)
}

Annotation(dri, params)
Expand Down
1 change: 1 addition & 0 deletions scala3doc/src/dotty/dokka/tasty/ClassLikeSupport.scala
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,7 @@ trait ClassLikeSupport:
else ""

TypeParameter(
argument.symbol.getAnnotations(),
variancePrefix,
argument.symbol.normalizedName,
argument.symbol.dri,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ trait SignatureBuilder extends ScalaSignatureUtils {
def annotationsInline(d: Parameter): SignatureBuilder =
d.annotations.foldLeft(this){ (bdr, annotation) => bdr.buildAnnotation(annotation) }

def annotationsInline(t: TypeParameter): SignatureBuilder =
t.annotations.foldLeft(this){ (bdr, annotation) => bdr.buildAnnotation(annotation) }

private def buildAnnotation(a: Annotation): SignatureBuilder =
text("@").driLink(a.dri.location.split('.').last, a.dri).buildAnnotationParams(a).text(" ")

Expand Down Expand Up @@ -77,7 +80,7 @@ trait SignatureBuilder extends ScalaSignatureUtils {
text(all.toSignatureString()).text(kind + " ")

def generics(on: Seq[TypeParameter]) = list(on.toList, "[", "]"){ (bdr, e) =>
bdr.text(e.variance).memberName(e.name, e.dri).signature(e.signature)
bdr.annotationsInline(e).text(e.variance).memberName(e.name, e.dri).signature(e.signature)
}

def functionParameters(params: Seq[ParametersList]) =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package dotty.dokka

import scala.io.Source
import scala.jdk.CollectionConverters._
import scala.util.matching.Regex
import dotty.dokka.test.BuildInfo
import java.nio.file.Path;
import org.jetbrains.dokka.plugability.DokkaContext
import org.jetbrains.dokka.pages.{RootPageNode, PageNode, ContentPage, ContentText, ContentNode, ContentComposite}
import org.jsoup.Jsoup

class JavadocExternalLocationProviderIntegrationTest extends ExternalLocationProviderIntegrationTest(
"externalJavadoc",
List(".*java.*::javadoc::https://docs.oracle.com/javase/8/docs/api/"),
List(
"https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.Builder.html",
"https://docs.oracle.com/javase/8/docs/api/java/util/Map.Entry.html",
"https://docs.oracle.com/javase/8/docs/api/java/util/Map.html"
)
)

class ScaladocExternalLocationProviderIntegrationTest extends ExternalLocationProviderIntegrationTest(
"externalScaladoc",
List(".*scala.*::scaladoc::https://www.scala-lang.org/api/current/"),
List(
"https://www.scala-lang.org/api/current/scala/util/matching/Regex$$Match.html",
"https://www.scala-lang.org/api/current/scala/Predef$.html",
"https://www.scala-lang.org/api/current/scala/collection/immutable/Map.html"
)
)

class Scala3docExternalLocationProviderIntegrationTest extends ExternalLocationProviderIntegrationTest(
"externalScala3doc",
List(".*scala.*::scala3doc::https://dotty.epfl.ch/api/"),
List(
"https://dotty.epfl.ch/api/scala/collection/immutable/Map.html",
"https://dotty.epfl.ch/api/scala/Predef$.html",
"https://dotty.epfl.ch/api/scala/util/matching/Regex$$Match.html"
)
)


abstract class ExternalLocationProviderIntegrationTest(name: String, mappings: Seq[String], expectedLinks: Seq[String]) extends ScaladocTest(name):
override def args = super.args.copy(
externalMappings = mappings.flatMap( s =>
ExternalDocLink.parse(s).fold(left => None, right => Some(right)
)
).toList
)

def assertions = Assertion.AfterRendering { (root, ctx) =>
given DokkaContext = ctx
val output = summon[DocContext].args.output.toPath.resolve("api")
val linksBuilder = List.newBuilder[String]

def processFile(path: Path): Unit =
val document = Jsoup.parse(IO.read(path))
val content = document.select(".documentableElement").forEach { elem =>
val hrefValues = elem.select("a").asScala.map { a =>
a.attr("href")
}
linksBuilder ++= hrefValues
}


IO.foreachFileIn(output, processFile)
val links = linksBuilder.result
val errors = expectedLinks.flatMap(expect => Option.when(!links.contains(expect))(expect))
if !errors.isEmpty then {
val reportMessage =
"External location provider integration test failed.\n" +
"Missing links:\n"
+ errors.mkString("\n","\n","\n")
+ "Found links:" + links.mkString("\n","\n","\n")
reportError(reportMessage)
}
} :: Nil

Loading