Skip to content

Commit 0d51319

Browse files
authored
Merge pull request #11564 from lampepfl/external-location-anchors
Add support for anchors to old scaladoc and javadoc
2 parents 1a71095 + d60bf45 commit 0d51319

File tree

10 files changed

+143
-55
lines changed

10 files changed

+143
-55
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-testcases/src/tests/externalLocations/scaladoc2.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,5 @@ class Test {
1010
def c: Regex.Match = ???
1111
}
1212

13+
abstract class MySeq[T] extends scala.collection.immutable.Seq[T]
14+

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: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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+
defn.IntClass -> "int",
15+
defn.FloatClass -> "float",
16+
defn.DoubleClass -> "double",
17+
defn.LongClass -> "long",
18+
defn.ByteClass -> "byte",
19+
defn.BooleanClass -> "boolean",
20+
defn.CharClass -> "char",
21+
defn.ShortClass -> "short",
22+
defn.ObjectClass -> "java.lang.Object"
23+
)
24+
25+
private def transformPrimitiveType(tpe: TypeRepr): String = tpe.classSymbol
26+
.flatMap(javadocPrimitivesMap.get)
27+
.filter(_ => !tpe.typeSymbol.isTypeParam)
28+
.getOrElse(tpe.show)
29+
30+
private def transformType(tpe: TypeRepr): String = tpe.simplified match {
31+
case AppliedType(tpe, typeList) if tpe.classSymbol.fold(false)(_ == defn.ArrayClass) => transformType(typeList.head) + ":A"
32+
case AppliedType(tpe, typeList) if tpe.classSymbol.fold(false)(_ == defn.RepeatedParamClass) => transformType(typeList.head) + "..."
33+
case AppliedType(tpe, typeList) => transformPrimitiveType(tpe)
34+
case other => transformPrimitiveType(other)
35+
}
36+
37+
def getJavadocType(s: TypeRepr) = transformType(s)

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

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,8 @@ trait PackageSupport:
88
import qctx.reflect._
99

1010
def parsePackage(pck: PackageClause): (String, Member) =
11-
val name = extractPackageName(pck.pid.show)
11+
val name = pck.symbol.fullName
1212
(name, Member(name, pck.symbol.dri, Kind.Package))
1313

1414
def parsePackageObject(pckObj: ClassDef): (String, Member) =
1515
pckObj.symbol.packageName -> parseClasslike(pckObj).withKind(Kind.Package)
16-
17-
private def extractPackageName(pidShowNoColor: String): String = {
18-
val pidSplit = pidShowNoColor.split("\\.")
19-
pidSplit.mkString("",".","")
20-
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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 Scaladoc2AnchorCreator:
9+
self: SymOps[_] =>
10+
11+
import self.q.reflect._
12+
13+
implicit private val printer: Printer[Tree] = Printer.TreeShortCode
14+
15+
def getScaladoc2Type(t: Tree) = {
16+
(t match {
17+
case d: DefDef => d.show.split("def", 2)(1)
18+
case t: TypeDef => t.show.split("type", 2)(1)
19+
case v: ValDef => v.show.split("val|var", 2)(1)
20+
}).replace(" ","")
21+
}

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

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

44
import scala.quoted._
5+
import dotty.tools.scaladoc.util.Escape._
6+
import scala.collection.mutable.{ Map => MMap }
7+
import dotty.tools.io.AbstractFile
58

6-
class SymOps[Q <: Quotes](val q: Q):
9+
class SymOps[Q <: Quotes](val q: Q) extends JavadocAnchorCreator with Scaladoc2AnchorCreator:
710
import q.reflect._
811

912
given Q = q
13+
14+
private val externalLinkCache: scala.collection.mutable.Map[AbstractFile, Option[ExternalDocLink]] = MMap()
15+
1016
extension (sym: Symbol)
1117
def packageName: String = (
1218
if (sym.isPackageDef) sym.fullName
1319
else sym.maybeOwner.packageName
1420
)
1521

22+
def packageNameSplitted: Seq[String] =
23+
sym.packageName.split('.').toList
24+
1625
def className: Option[String] =
1726
if (sym.isClassDef && !sym.flags.is(Flags.Package)) Some(
1827
Some(sym.maybeOwner).filter(s => s.exists).flatMap(_.className).fold("")(cn => cn + "$") + sym.name
@@ -109,8 +118,40 @@ class SymOps[Q <: Quotes](val q: Q):
109118
else termParamss(1).params(0)
110119
}
111120

121+
private def constructPath(location: Seq[String], anchor: Option[String], link: ExternalDocLink): String =
122+
val extension = ".html"
123+
val docURL = link.documentationUrl.toString
124+
def constructPathForJavadoc: String =
125+
val l = "\\$+".r.replaceAllIn(location.mkString("/"), _ => ".")
126+
val javadocAnchor = if anchor.isDefined then {
127+
val paramSigs = sym.paramSymss.flatten.map(_.tree).collect {
128+
case v: ValDef => v.tpt.tpe
129+
}.map(getJavadocType)
130+
"#" + sym.name + paramSigs.mkString("-","-","-")
131+
} else ""
132+
docURL + l + extension + javadocAnchor
133+
134+
//TODO #263: Add anchor support
135+
def constructPathForScaladoc2: String =
136+
val l = escapeUrl(location.mkString("/"))
137+
val scaladoc2Anchor = if anchor.isDefined then {
138+
"#" + getScaladoc2Type(sym.tree)
139+
} else ""
140+
docURL + l + extension + scaladoc2Anchor
141+
142+
// TODO Add tests for it!
143+
def constructPathForScaladoc3: String =
144+
val base = docURL + escapeUrl(location.mkString("/")) + extension
145+
anchor.fold(base)(a => base + "#" + a)
146+
147+
link.kind match {
148+
case DocumentationKind.Javadoc => constructPathForJavadoc
149+
case DocumentationKind.Scaladoc2 => constructPathForScaladoc2
150+
case DocumentationKind.Scaladoc3 => constructPathForScaladoc3
151+
}
152+
112153
// TODO #22 make sure that DRIs are unique plus probably reuse semantic db code?
113-
def dri: DRI =
154+
def dri(using dctx: DocContext): DRI =
114155
if sym == Symbol.noSymbol then topLevelDri
115156
else if sym.isValDef && sym.moduleClass.exists then sym.moduleClass.dri
116157
else
@@ -119,21 +160,32 @@ class SymOps[Q <: Quotes](val q: Q):
119160
else if (sym.maybeOwner.isDefDef) Some(sym.owner)
120161
else None
121162

122-
val originPath = {
163+
val className = sym.className
164+
165+
val location = sym.packageNameSplitted ++ className
166+
167+
val anchor = sym.anchor
168+
169+
val externalLink = {
123170
import q.reflect._
124171
import dotty.tools.dotc
125172
given ctx: dotc.core.Contexts.Context = q.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx
126173
val csym = sym.asInstanceOf[dotc.core.Symbols.Symbol]
127-
Option(csym.associatedFile).fold("")(_.path)
174+
val extLink = if externalLinkCache.contains(csym.associatedFile) then externalLinkCache(csym.associatedFile)
175+
else {
176+
val calculatedLink = Option(csym.associatedFile).map(_.path).flatMap( path =>
177+
dctx.externalDocumentationLinks.find(_.originRegexes.exists(r => r.matches(path))))
178+
externalLinkCache += (csym.associatedFile -> calculatedLink)
179+
calculatedLink
180+
}
181+
extLink.map(link => constructPath(location, anchor, link))
128182
}
129183

130-
val className = sym.className
131-
132184
DRI(
133-
className.fold(sym.packageName)(cn => s"${sym.packageName}.${cn}"),
134-
anchor = sym.anchor.getOrElse(""),
135-
origin = originPath,
185+
location.mkString("."),
186+
anchor.getOrElse(""),
187+
externalLink = externalLink,
136188
// sym.show returns the same signature for def << = 1 and def >> = 2.
137189
// For some reason it contains `$$$` instrad of symbol name
138-
s"${sym.name}${sym.fullName}/${sym.signature.resultSig}/[${sym.signature.paramSigs.mkString("/")}]$originPath"
190+
s"${sym.name}${sym.fullName}/${sym.signature.resultSig}/[${sym.signature.paramSigs.mkString("/")}]"
139191
)

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

Lines changed: 10 additions & 3 deletions
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

@@ -23,8 +28,10 @@ class Scaladoc2ExternalLocationProviderIntegrationTest extends ExternalLocationP
2328
List(".*scala.*::scaladoc2::https://www.scala-lang.org/api/current/"),
2429
List(
2530
"https://www.scala-lang.org/api/current/scala/util/matching/Regex$$Match.html",
26-
"https://www.scala-lang.org/api/current/scala/Predef$.html",
27-
"https://www.scala-lang.org/api/current/scala/collection/immutable/Map.html"
31+
"https://www.scala-lang.org/api/current/scala/Predef$.html#String",
32+
"https://www.scala-lang.org/api/current/scala/collection/immutable/Map.html",
33+
"https://www.scala-lang.org/api/current/scala/collection/IterableOnceOps.html#addString(b:StringBuilder,start:String,sep:String,end:String):StringBuilder",
34+
"https://www.scala-lang.org/api/current/scala/collection/IterableOnceOps.html#mkString(start:String,sep:String,end:String):String"
2835
)
2936
)
3037

0 commit comments

Comments
 (0)