Skip to content

Commit ac6acf7

Browse files
committed
Add support for anchors in javadoc
1 parent ce30a1c commit ac6acf7

File tree

7 files changed

+95
-47
lines changed

7 files changed

+95
-47
lines changed

scaladoc-testcases/src/tests/externalLocations/javadoc.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,9 @@ class Test {
1010
def c: java.util.stream.Stream.Builder[String] = ???
1111
}
1212

13+
class MyException extends java.lang.Exception
14+
15+
class MyArrayList[T] extends java.util.ArrayList[T]
16+
17+
trait MyPrintStream extends java.io.PrintStream
18+

scaladoc/src/dotty/tools/scaladoc/DRI.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ val topLevelDri = DRI("/")
1111
final case class DRI(
1212
location: String,
1313
anchor: String = "",
14-
origin: String = "",
14+
externalLink: Option[String] = None,
1515
symbolUUID: String = ""
1616
):
17-
def withNoOrigin = copy(origin = "")
17+
def withNoExternalLink = copy(externalLink = None)
1818

1919
def isStaticFile = symbolUUID == staticFileSymbolUUID
2020

scaladoc/src/dotty/tools/scaladoc/renderers/Locations.scala

Lines changed: 1 addition & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -53,15 +53,7 @@ trait Locations(using ctx: DocContext):
5353
val anchor = if to.anchor.isEmpty then "" else "#" + to.anchor
5454
pathToRaw(rawLocation(from), rawLocation(to)) +".html" + anchor
5555
else
56-
to.origin match
57-
case "" =>
58-
unknownPage(to)
59-
case path =>
60-
val external =
61-
ctx.externalDocumentationLinks.find(_.originRegexes.exists(r => r.matches(path)))
62-
external.fold(unknownPage(to))(constructPath(to))
63-
64-
56+
to.externalLink.fold(unknownPage(to))(l => l)
6557

6658
def pathToRaw(from: Seq[String], to: Seq[String]): String =
6759
import dotty.tools.scaladoc.util.Escape._
@@ -90,27 +82,3 @@ trait Locations(using ctx: DocContext):
9082
case seq => seq.mkString("", "/", "/")
9183

9284
def driExisits(dri: DRI) = true // TODO implement checks!
93-
94-
def constructPath(dri: DRI)(link: ExternalDocLink): String =
95-
val extension = ".html"
96-
val docURL = link.documentationUrl.toString
97-
def constructPathForJavadoc(dri: DRI): String = {
98-
val location = "\\$+".r.replaceAllIn(dri.location.replace(".","/"), _ => ".")
99-
val anchor = dri.anchor
100-
docURL + location + extension
101-
}
102-
103-
//TODO #263: Add anchor support
104-
def constructPathForScaladoc2(dri: DRI): String =
105-
docURL + dri.asFileLocation + extension
106-
107-
// TODO Add tests for it!
108-
def constructPathForScaladoc3(dri: DRI): String =
109-
val base = docURL + dri.asFileLocation + extension
110-
if dri.anchor.isEmpty then base else base + "#" + dri.anchor
111-
112-
link.kind match {
113-
case DocumentationKind.Javadoc => constructPathForJavadoc(dri)
114-
case DocumentationKind.Scaladoc2 => constructPathForScaladoc2(dri)
115-
case DocumentationKind.Scaladoc3 => constructPathForScaladoc3(dri)
116-
}

scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ trait ClassLikeSupport:
201201
p.copy(
202202
dri = p.dri.copy(
203203
location = parentDRI.location,
204-
origin = parentDRI.origin
204+
externalLink = None
205205
)
206206
)
207207
)
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package dotty.tools.scaladoc
2+
package tasty
3+
4+
import scala.quoted._
5+
import dotty.tools.scaladoc.util.Escape._
6+
import scala.util.matching.Regex
7+
8+
trait JavadocAnchorCreator:
9+
self: SymOps[_] =>
10+
11+
import self.q.reflect._
12+
13+
private val javadocPrimitivesMap = Map(
14+
"scala.Int" -> "int",
15+
"scala.Float" -> "float",
16+
"scala.Double" -> "double",
17+
"scala.Long" -> "long",
18+
"scala.Byte" -> "byte",
19+
"scala.Boolean" -> "boolean",
20+
"scala.Char" -> "char",
21+
"scala.Short" -> "short",
22+
"<special-ops>.<FromJavaObject>" -> "java.lang.Object"
23+
)
24+
25+
private def transformPrimitiveType(s: String): String = javadocPrimitivesMap.getOrElse(s, s)
26+
27+
private def transformType(tpe: TypeRepr): String = tpe.simplified match {
28+
case AppliedType(tpe, typeList) if tpe.show == "scala.Array" => transformType(typeList.head) + ":A"
29+
case AppliedType(tpe, typeList) if tpe.show == "scala.<repeated>" => transformType(typeList.head) + "..."
30+
case AppliedType(tpe, typeList) => transformPrimitiveType(tpe.show)
31+
case other => transformPrimitiveType(other.show)
32+
}
33+
34+
def getJavadocType(s: TypeRepr) = transformType(s)

scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ package dotty.tools.scaladoc
22
package tasty
33

44
import scala.quoted._
5+
import dotty.tools.scaladoc.util.Escape._
56

6-
class SymOps[Q <: Quotes](val q: Q):
7+
class SymOps[Q <: Quotes](val q: Q) extends JavadocAnchorCreator:
78
import q.reflect._
89

910
given Q = q
@@ -109,8 +110,36 @@ class SymOps[Q <: Quotes](val q: Q):
109110
else termParamss(1).params(0)
110111
}
111112

113+
private def constructPath(location: String, anchor: Option[String], link: ExternalDocLink): String =
114+
val extension = ".html"
115+
val docURL = link.documentationUrl.toString
116+
def constructPathForJavadoc: String =
117+
val l = "\\$+".r.replaceAllIn(location.replace(".","/"), _ => ".")
118+
val javadocAnchor = {
119+
val paramSigs = sym.paramSymss.flatten.map(_.tree).collect {
120+
case v: ValDef => v.tpt.tpe
121+
}.map(getJavadocType)
122+
paramSigs.mkString("-","-","-")
123+
}
124+
docURL + l + extension + anchor.fold("")(a => "#" + sym.name + javadocAnchor)
125+
126+
//TODO #263: Add anchor support
127+
def constructPathForScaladoc2: String =
128+
docURL + escapeUrl(location).replace(".", "/") + extension
129+
130+
// TODO Add tests for it!
131+
def constructPathForScaladoc3: String =
132+
val base = docURL + escapeUrl(location).replace(".", "/") + extension
133+
anchor.fold(base)(a => base + "#" + a)
134+
135+
link.kind match {
136+
case DocumentationKind.Javadoc => constructPathForJavadoc
137+
case DocumentationKind.Scaladoc2 => constructPathForScaladoc2
138+
case DocumentationKind.Scaladoc3 => constructPathForScaladoc3
139+
}
140+
112141
// TODO #22 make sure that DRIs are unique plus probably reuse semantic db code?
113-
def dri: DRI =
142+
def dri(using dctx: DocContext): DRI =
114143
if sym == Symbol.noSymbol then topLevelDri
115144
else if sym.isValDef && sym.moduleClass.exists then sym.moduleClass.dri
116145
else
@@ -119,21 +148,27 @@ class SymOps[Q <: Quotes](val q: Q):
119148
else if (sym.maybeOwner.isDefDef) Some(sym.owner)
120149
else None
121150

122-
val originPath = {
151+
val className = sym.className
152+
153+
val location = className.fold(sym.packageName)(cn => s"${sym.packageName}.${cn}")
154+
155+
val anchor = sym.anchor
156+
157+
val externalLink = {
123158
import q.reflect._
124159
import dotty.tools.dotc
125160
given ctx: dotc.core.Contexts.Context = q.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx
126161
val csym = sym.asInstanceOf[dotc.core.Symbols.Symbol]
127-
Option(csym.associatedFile).fold("")(_.path)
162+
Option(csym.associatedFile).map(_.path).flatMap( path =>
163+
dctx.externalDocumentationLinks.find(_.originRegexes.exists(r => r.matches(path)))
164+
).map(link => constructPath(location, anchor, link))
128165
}
129166

130-
val className = sym.className
131-
132167
DRI(
133-
className.fold(sym.packageName)(cn => s"${sym.packageName}.${cn}"),
134-
anchor = sym.anchor.getOrElse(""),
135-
origin = originPath,
168+
location,
169+
anchor.getOrElse(""),
170+
externalLink = externalLink,
136171
// sym.show returns the same signature for def << = 1 and def >> = 2.
137172
// For some reason it contains `$$$` instrad of symbol name
138-
s"${sym.name}${sym.fullName}/${sym.signature.resultSig}/[${sym.signature.paramSigs.mkString("/")}]$originPath"
173+
s"${sym.name}${sym.fullName}/${sym.signature.resultSig}/[${sym.signature.paramSigs.mkString("/")}]"
139174
)

scaladoc/test/dotty/tools/scaladoc/ExternalLocationProviderIntegrationTest.scala

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@ class JavadocExternalLocationProviderIntegrationTest extends ExternalLocationPro
1414
List(
1515
"https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.Builder.html",
1616
"https://docs.oracle.com/javase/8/docs/api/java/util/Map.Entry.html",
17-
"https://docs.oracle.com/javase/8/docs/api/java/util/Map.html"
17+
"https://docs.oracle.com/javase/8/docs/api/java/util/Map.html",
18+
"https://docs.oracle.com/javase/8/docs/api/java/util/ArrayList.html#forEach-java.util.function.Consumer-",
19+
"https://docs.oracle.com/javase/8/docs/api/java/util/ArrayList.html#toArray-T:A-",
20+
"https://docs.oracle.com/javase/8/docs/api/java/util/ArrayList.html#subList-int-int-",
21+
"https://docs.oracle.com/javase/8/docs/api/java/io/PrintStream.html#printf-java.lang.String-java.lang.Object...-",
22+
"https://docs.oracle.com/javase/8/docs/api/java/io/PrintStream.html#write-byte:A-int-int-"
1823
)
1924
)
2025

0 commit comments

Comments
 (0)