Skip to content

Commit 33b710a

Browse files
authored
Merge pull request #10833 from lampepfl/scala3doc-fixes
Scala3doc: Add support for annotations in type parameters, add tests for external location providing, minor fixes
2 parents 8a6e24f + a5d21ba commit 33b710a

16 files changed

+264
-35
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package tests.externalJavadoc
2+
3+
import java.util._
4+
5+
class Test {
6+
def a: Map.Entry[String, String] = ???
7+
8+
def b: java.util.Map[String, Int] = ???
9+
10+
def c: java.util.stream.Stream.Builder[String] = ???
11+
}
12+
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package tests.externalScala3doc
2+
3+
import scala.util.matching._
4+
5+
class Test {
6+
def a: String = ???
7+
8+
def b: Map[String, Int] = ???
9+
10+
def c: Regex.Match = ???
11+
}
12+
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package tests.externalScaladoc
2+
3+
import scala.util.matching._
4+
5+
class Test {
6+
def a: String = ???
7+
8+
def b: Map[String, Int] = ???
9+
10+
def c: Regex.Match = ???
11+
}
12+

scala3doc-testcases/src/tests/objectSignatures.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,9 @@ object Base
1515

1616
object A2 extends A[String] with C
1717

18+
object <
19+
20+
object >
21+
1822
// We are not going to add final below
1923
// final object B
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package tests
2+
3+
package specializedSignature
4+
5+
import scala.{specialized}
6+
7+
trait AdditiveMonoid[@specialized(Int, Long, Float, Double) A]
8+
{
9+
def a: A
10+
= ???
11+
12+
def b[@specialized(Int, Float) B]: B
13+
= ???
14+
}

scala3doc/src/dotty/dokka/ExternalDocLink.scala

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package dotty.dokka
22

33
import java.net.URL
44
import scala.util.matching._
5-
import scala.util.Try
5+
import scala.util.{ Try, Success, Failure }
66

77
case class ExternalDocLink(
88
originRegexes: List[Regex],
@@ -18,25 +18,23 @@ enum DocumentationKind:
1818
case Scala3doc extends DocumentationKind
1919

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

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

3129
def parsePackageList(elements: List[String]) = elements match
32-
case List(urlStr) => tryParse("packageList")(Option(URL(urlStr)))
33-
case Nil => Some(None)
30+
case List(urlStr) => tryParse("packageList")(Some(URL(urlStr)))
31+
case Nil => Right(None)
3432
case other => fail(s"Provided multiple package lists: $other")
3533

3634
def doctoolByName(name: String) = name match
37-
case "javadoc" => Some(DocumentationKind.Javadoc)
38-
case "scaladoc" => Some(DocumentationKind.Scaladoc)
39-
case "scala3doc" => Some(DocumentationKind.Scala3doc)
35+
case "javadoc" => Right(DocumentationKind.Javadoc)
36+
case "scaladoc" => Right(DocumentationKind.Scaladoc)
37+
case "scala3doc" => Right(DocumentationKind.Scala3doc)
4038
case other => fail(s"Unknown doctool: $other")
4139

4240

@@ -48,10 +46,10 @@ object ExternalDocLink:
4846
doctool <- doctoolByName(docToolStr)
4947
packageList <- parsePackageList(rest)
5048
} yield ExternalDocLink(
51-
List(regex),
52-
url,
53-
doctool,
54-
packageList
55-
)
49+
List(regex),
50+
url,
51+
doctool,
52+
packageList
53+
)
5654
case _ =>
5755
fail("Accepted format: `regexStr::docToolStr::urlStr[::rest]`")

scala3doc/src/dotty/dokka/Scala3docArgs.scala

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,13 @@ object Scala3docArgs:
114114
}
115115
}
116116
val externalMappings =
117-
externalDocumentationMappings.get.flatMap(ExternalDocLink.parse)
117+
externalDocumentationMappings.get.flatMap( s =>
118+
ExternalDocLink.parse(s).fold(left => {
119+
report.warning(left)
120+
None
121+
}, right => Some(right)
122+
)
123+
)
118124

119125
unsupportedSettings.filter(s => s.get != s.default).foreach { s =>
120126
report.warning(s"Setting ${s.name} is currently not supported.")

scala3doc/src/dotty/dokka/location/ScalaExternalLocationProvider.scala

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,37 +18,40 @@ class ScalaExternalLocationProvider(
1818
externalDocumentation: ExternalDocumentation,
1919
extension: String,
2020
kind: DocumentationKind
21-
)(using ctx: DokkaContext) extends DefaultExternalLocationProvider(externalDocumentation, extension, ctx):
21+
) extends ExternalLocationProvider:
22+
def docURL = externalDocumentation.getDocumentationURL.toString.stripSuffix("/") + "/"
2223
override def resolve(dri: DRI): String =
2324
Option(externalDocumentation.getPackageList).map(_.getLocations.asScala.toMap).flatMap(_.get(dri.toString))
2425
.fold(constructPath(dri))( l => {
25-
this.getDocURL + l
26+
this.docURL + l
2627
}
2728
)
2829

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

31-
override def constructPath(dri: DRI): String = kind match {
32+
def constructPath(dri: DRI): String = kind match {
3233
case DocumentationKind.Javadoc => constructPathForJavadoc(dri)
3334
case DocumentationKind.Scaladoc => constructPathForScaladoc(dri)
3435
case DocumentationKind.Scala3doc => constructPathForScala3doc(dri)
3536
}
3637

38+
//TODO #263: Add anchor support
39+
3740
private def constructPathForJavadoc(dri: DRI): String = {
3841
val location = "\\$+".r.replaceAllIn(dri.location.replace(".","/"), _ => ".")
3942
val origin = originRegex.findFirstIn(dri.extra)
4043
val anchor = dri.anchor
41-
getDocURL + location + extension + anchor.fold("")(a => s"#$a")
44+
docURL + location + extension
4245
}
4346

4447
private def constructPathForScaladoc(dri: DRI): String = {
4548
val location = dri.location.replace(".","/")
4649
val anchor = dri.anchor
47-
getDocURL + location + extension + anchor.fold("")(a => s"#$a")
50+
docURL + location + extension
4851
}
4952

5053
private def constructPathForScala3doc(dri: DRI): String = {
5154
val location = dri.location.replace(".","/")
5255
val anchor = dri.anchor
53-
getDocURL + location + anchor.fold(extension)(a => s"/$a$extension")
56+
docURL + location + extension
5457
}

scala3doc/src/dotty/dokka/model/api/api.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ case class Parameter(
116116
)
117117

118118
case class TypeParameter(
119+
annotations: Seq[Annotation],
119120
variance: "" | "+" | "-",
120121
name: String,
121122
dri: DRI,

scala3doc/src/dotty/dokka/tasty/BasicSupport.scala

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,22 @@ trait BasicSupport:
1515
export SymOps._
1616

1717
def parseAnnotation(annotTerm: Term): Annotation =
18+
import dotty.tools.dotc.ast.Trees.{SeqLiteral}
1819
val dri = annotTerm.tpe.typeSymbol.dri
20+
def inner(t: Term): List[Annotation.AnnotationParameter] = t match {
21+
case i: Ident => List(Annotation.LinkParameter(None, i.tpe.typeSymbol.dri, i.name))
22+
case Typed(term, tpeTree) => inner(term)
23+
case SeqLiteral(args, tpeTree) => args.map(_.asInstanceOf[Term]).flatMap(inner)
24+
case Literal(constant) => List(Annotation.PrimitiveParameter(None, constant.show))
25+
case NamedArg(name, Literal(constant)) => List(Annotation.PrimitiveParameter(Some(name), constant.show))
26+
case x @ Select(qual, name) => List.empty
27+
case other => List(Annotation.UnresolvedParameter(None, other.show))
28+
}
29+
30+
1931
val params = annotTerm match
2032
case Apply(target, appliedWith) => {
21-
appliedWith.flatMap {
22-
case Literal(constant) => Some(Annotation.PrimitiveParameter(None, constant.show))
23-
case NamedArg(name, Literal(constant)) => Some(Annotation.PrimitiveParameter(Some(name), constant.show))
24-
case x @ Select(qual, name) => None
25-
case other => Some(Annotation.UnresolvedParameter(None, other.show))
26-
}
33+
appliedWith.flatMap(inner)
2734
}
2835

2936
Annotation(dri, params)

scala3doc/src/dotty/dokka/tasty/ClassLikeSupport.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,7 @@ trait ClassLikeSupport:
395395
else ""
396396

397397
TypeParameter(
398+
argument.symbol.getAnnotations(),
398399
variancePrefix,
399400
argument.symbol.normalizedName,
400401
argument.symbol.dri,

scala3doc/src/dotty/dokka/translators/ScalaSignatureUtils.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ trait SignatureBuilder extends ScalaSignatureUtils {
4848
def annotationsInline(d: Parameter): SignatureBuilder =
4949
d.annotations.foldLeft(this){ (bdr, annotation) => bdr.buildAnnotation(annotation) }
5050

51+
def annotationsInline(t: TypeParameter): SignatureBuilder =
52+
t.annotations.foldLeft(this){ (bdr, annotation) => bdr.buildAnnotation(annotation) }
53+
5154
private def buildAnnotation(a: Annotation): SignatureBuilder =
5255
text("@").driLink(a.dri.location.split('.').last, a.dri).buildAnnotationParams(a).text(" ")
5356

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

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

8386
def functionParameters(params: Seq[ParametersList]) =
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package dotty.dokka
2+
3+
import scala.io.Source
4+
import scala.jdk.CollectionConverters._
5+
import scala.util.matching.Regex
6+
import dotty.dokka.test.BuildInfo
7+
import java.nio.file.Path;
8+
import org.jetbrains.dokka.plugability.DokkaContext
9+
import org.jetbrains.dokka.pages.{RootPageNode, PageNode, ContentPage, ContentText, ContentNode, ContentComposite}
10+
import org.jsoup.Jsoup
11+
12+
class JavadocExternalLocationProviderIntegrationTest extends ExternalLocationProviderIntegrationTest(
13+
"externalJavadoc",
14+
List(".*java.*::javadoc::https://docs.oracle.com/javase/8/docs/api/"),
15+
List(
16+
"https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.Builder.html",
17+
"https://docs.oracle.com/javase/8/docs/api/java/util/Map.Entry.html",
18+
"https://docs.oracle.com/javase/8/docs/api/java/util/Map.html"
19+
)
20+
)
21+
22+
class ScaladocExternalLocationProviderIntegrationTest extends ExternalLocationProviderIntegrationTest(
23+
"externalScaladoc",
24+
List(".*scala.*::scaladoc::https://www.scala-lang.org/api/current/"),
25+
List(
26+
"https://www.scala-lang.org/api/current/scala/util/matching/Regex$$Match.html",
27+
"https://www.scala-lang.org/api/current/scala/Predef$.html",
28+
"https://www.scala-lang.org/api/current/scala/collection/immutable/Map.html"
29+
)
30+
)
31+
32+
class Scala3docExternalLocationProviderIntegrationTest extends ExternalLocationProviderIntegrationTest(
33+
"externalScala3doc",
34+
List(".*scala.*::scala3doc::https://dotty.epfl.ch/api/"),
35+
List(
36+
"https://dotty.epfl.ch/api/scala/collection/immutable/Map.html",
37+
"https://dotty.epfl.ch/api/scala/Predef$.html",
38+
"https://dotty.epfl.ch/api/scala/util/matching/Regex$$Match.html"
39+
)
40+
)
41+
42+
43+
abstract class ExternalLocationProviderIntegrationTest(name: String, mappings: Seq[String], expectedLinks: Seq[String]) extends ScaladocTest(name):
44+
override def args = super.args.copy(
45+
externalMappings = mappings.flatMap( s =>
46+
ExternalDocLink.parse(s).fold(left => None, right => Some(right)
47+
)
48+
).toList
49+
)
50+
51+
def assertions = Assertion.AfterRendering { (root, ctx) =>
52+
given DokkaContext = ctx
53+
val output = summon[DocContext].args.output.toPath.resolve("api")
54+
val linksBuilder = List.newBuilder[String]
55+
56+
def processFile(path: Path): Unit =
57+
val document = Jsoup.parse(IO.read(path))
58+
val content = document.select(".documentableElement").forEach { elem =>
59+
val hrefValues = elem.select("a").asScala.map { a =>
60+
a.attr("href")
61+
}
62+
linksBuilder ++= hrefValues
63+
}
64+
65+
66+
IO.foreachFileIn(output, processFile)
67+
val links = linksBuilder.result
68+
val errors = expectedLinks.flatMap(expect => Option.when(!links.contains(expect))(expect))
69+
if !errors.isEmpty then {
70+
val reportMessage =
71+
"External location provider integration test failed.\n" +
72+
"Missing links:\n"
73+
+ errors.mkString("\n","\n","\n")
74+
+ "Found links:" + links.mkString("\n","\n","\n")
75+
reportError(reportMessage)
76+
}
77+
} :: Nil
78+

0 commit comments

Comments
 (0)