From ac6acf71ebf7dc45863aeed280a581b389d5c07f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Zyba=C5=82a?= Date: Mon, 1 Mar 2021 10:09:34 +0100 Subject: [PATCH 1/3] Add support for anchors in javadoc --- .../src/tests/externalLocations/javadoc.scala | 6 ++ scaladoc/src/dotty/tools/scaladoc/DRI.scala | 4 +- .../tools/scaladoc/renderers/Locations.scala | 34 +----------- .../scaladoc/tasty/ClassLikeSupport.scala | 2 +- .../scaladoc/tasty/JavadocAnchorCreator.scala | 34 ++++++++++++ .../dotty/tools/scaladoc/tasty/SymOps.scala | 55 +++++++++++++++---- ...ernalLocationProviderIntegrationTest.scala | 7 ++- 7 files changed, 95 insertions(+), 47 deletions(-) create mode 100644 scaladoc/src/dotty/tools/scaladoc/tasty/JavadocAnchorCreator.scala diff --git a/scaladoc-testcases/src/tests/externalLocations/javadoc.scala b/scaladoc-testcases/src/tests/externalLocations/javadoc.scala index edeac135eb86..c5286af418af 100644 --- a/scaladoc-testcases/src/tests/externalLocations/javadoc.scala +++ b/scaladoc-testcases/src/tests/externalLocations/javadoc.scala @@ -10,3 +10,9 @@ class Test { def c: java.util.stream.Stream.Builder[String] = ??? } +class MyException extends java.lang.Exception + +class MyArrayList[T] extends java.util.ArrayList[T] + +trait MyPrintStream extends java.io.PrintStream + diff --git a/scaladoc/src/dotty/tools/scaladoc/DRI.scala b/scaladoc/src/dotty/tools/scaladoc/DRI.scala index 8d5317a39f45..926b75836b83 100644 --- a/scaladoc/src/dotty/tools/scaladoc/DRI.scala +++ b/scaladoc/src/dotty/tools/scaladoc/DRI.scala @@ -11,10 +11,10 @@ val topLevelDri = DRI("/") final case class DRI( location: String, anchor: String = "", - origin: String = "", + externalLink: Option[String] = None, symbolUUID: String = "" ): - def withNoOrigin = copy(origin = "") + def withNoExternalLink = copy(externalLink = None) def isStaticFile = symbolUUID == staticFileSymbolUUID diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/Locations.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/Locations.scala index f3d67e2f8466..1d31617f9f8c 100644 --- a/scaladoc/src/dotty/tools/scaladoc/renderers/Locations.scala +++ b/scaladoc/src/dotty/tools/scaladoc/renderers/Locations.scala @@ -53,15 +53,7 @@ trait Locations(using ctx: DocContext): val anchor = if to.anchor.isEmpty then "" else "#" + to.anchor pathToRaw(rawLocation(from), rawLocation(to)) +".html" + anchor else - to.origin match - case "" => - unknownPage(to) - case path => - val external = - ctx.externalDocumentationLinks.find(_.originRegexes.exists(r => r.matches(path))) - external.fold(unknownPage(to))(constructPath(to)) - - + to.externalLink.fold(unknownPage(to))(l => l) def pathToRaw(from: Seq[String], to: Seq[String]): String = import dotty.tools.scaladoc.util.Escape._ @@ -90,27 +82,3 @@ trait Locations(using ctx: DocContext): case seq => seq.mkString("", "/", "/") def driExisits(dri: DRI) = true // TODO implement checks! - - def constructPath(dri: DRI)(link: ExternalDocLink): String = - val extension = ".html" - val docURL = link.documentationUrl.toString - def constructPathForJavadoc(dri: DRI): String = { - val location = "\\$+".r.replaceAllIn(dri.location.replace(".","/"), _ => ".") - val anchor = dri.anchor - docURL + location + extension - } - - //TODO #263: Add anchor support - def constructPathForScaladoc2(dri: DRI): String = - docURL + dri.asFileLocation + extension - - // TODO Add tests for it! - def constructPathForScaladoc3(dri: DRI): String = - val base = docURL + dri.asFileLocation + extension - if dri.anchor.isEmpty then base else base + "#" + dri.anchor - - link.kind match { - case DocumentationKind.Javadoc => constructPathForJavadoc(dri) - case DocumentationKind.Scaladoc2 => constructPathForScaladoc2(dri) - case DocumentationKind.Scaladoc3 => constructPathForScaladoc3(dri) - } diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala index b819c63ed646..1f28b561b3da 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala @@ -201,7 +201,7 @@ trait ClassLikeSupport: p.copy( dri = p.dri.copy( location = parentDRI.location, - origin = parentDRI.origin + externalLink = None ) ) ) diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/JavadocAnchorCreator.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/JavadocAnchorCreator.scala new file mode 100644 index 000000000000..c58fd618acc9 --- /dev/null +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/JavadocAnchorCreator.scala @@ -0,0 +1,34 @@ +package dotty.tools.scaladoc +package tasty + +import scala.quoted._ +import dotty.tools.scaladoc.util.Escape._ +import scala.util.matching.Regex + +trait JavadocAnchorCreator: + self: SymOps[_] => + + import self.q.reflect._ + + private val javadocPrimitivesMap = Map( + "scala.Int" -> "int", + "scala.Float" -> "float", + "scala.Double" -> "double", + "scala.Long" -> "long", + "scala.Byte" -> "byte", + "scala.Boolean" -> "boolean", + "scala.Char" -> "char", + "scala.Short" -> "short", + "." -> "java.lang.Object" + ) + + private def transformPrimitiveType(s: String): String = javadocPrimitivesMap.getOrElse(s, s) + + private def transformType(tpe: TypeRepr): String = tpe.simplified match { + case AppliedType(tpe, typeList) if tpe.show == "scala.Array" => transformType(typeList.head) + ":A" + case AppliedType(tpe, typeList) if tpe.show == "scala." => transformType(typeList.head) + "..." + case AppliedType(tpe, typeList) => transformPrimitiveType(tpe.show) + case other => transformPrimitiveType(other.show) + } + + def getJavadocType(s: TypeRepr) = transformType(s) diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala index 178fd9656d5e..34f0f10fbd50 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala @@ -2,8 +2,9 @@ package dotty.tools.scaladoc package tasty import scala.quoted._ +import dotty.tools.scaladoc.util.Escape._ -class SymOps[Q <: Quotes](val q: Q): +class SymOps[Q <: Quotes](val q: Q) extends JavadocAnchorCreator: import q.reflect._ given Q = q @@ -109,8 +110,36 @@ class SymOps[Q <: Quotes](val q: Q): else termParamss(1).params(0) } + private def constructPath(location: String, anchor: Option[String], link: ExternalDocLink): String = + val extension = ".html" + val docURL = link.documentationUrl.toString + def constructPathForJavadoc: String = + val l = "\\$+".r.replaceAllIn(location.replace(".","/"), _ => ".") + val javadocAnchor = { + val paramSigs = sym.paramSymss.flatten.map(_.tree).collect { + case v: ValDef => v.tpt.tpe + }.map(getJavadocType) + paramSigs.mkString("-","-","-") + } + docURL + l + extension + anchor.fold("")(a => "#" + sym.name + javadocAnchor) + + //TODO #263: Add anchor support + def constructPathForScaladoc2: String = + docURL + escapeUrl(location).replace(".", "/") + extension + + // TODO Add tests for it! + def constructPathForScaladoc3: String = + val base = docURL + escapeUrl(location).replace(".", "/") + extension + anchor.fold(base)(a => base + "#" + a) + + link.kind match { + case DocumentationKind.Javadoc => constructPathForJavadoc + case DocumentationKind.Scaladoc2 => constructPathForScaladoc2 + case DocumentationKind.Scaladoc3 => constructPathForScaladoc3 + } + // TODO #22 make sure that DRIs are unique plus probably reuse semantic db code? - def dri: DRI = + def dri(using dctx: DocContext): DRI = if sym == Symbol.noSymbol then topLevelDri else if sym.isValDef && sym.moduleClass.exists then sym.moduleClass.dri else @@ -119,21 +148,27 @@ class SymOps[Q <: Quotes](val q: Q): else if (sym.maybeOwner.isDefDef) Some(sym.owner) else None - val originPath = { + val className = sym.className + + val location = className.fold(sym.packageName)(cn => s"${sym.packageName}.${cn}") + + val anchor = sym.anchor + + val externalLink = { 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).fold("")(_.path) + Option(csym.associatedFile).map(_.path).flatMap( path => + dctx.externalDocumentationLinks.find(_.originRegexes.exists(r => r.matches(path))) + ).map(link => constructPath(location, anchor, link)) } - val className = sym.className - DRI( - className.fold(sym.packageName)(cn => s"${sym.packageName}.${cn}"), - anchor = sym.anchor.getOrElse(""), - origin = originPath, + location, + anchor.getOrElse(""), + externalLink = externalLink, // 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("/")}]$originPath" + s"${sym.name}${sym.fullName}/${sym.signature.resultSig}/[${sym.signature.paramSigs.mkString("/")}]" ) diff --git a/scaladoc/test/dotty/tools/scaladoc/ExternalLocationProviderIntegrationTest.scala b/scaladoc/test/dotty/tools/scaladoc/ExternalLocationProviderIntegrationTest.scala index f92c3b4ca843..f590cceafe2f 100644 --- a/scaladoc/test/dotty/tools/scaladoc/ExternalLocationProviderIntegrationTest.scala +++ b/scaladoc/test/dotty/tools/scaladoc/ExternalLocationProviderIntegrationTest.scala @@ -14,7 +14,12 @@ class JavadocExternalLocationProviderIntegrationTest extends ExternalLocationPro 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" + "https://docs.oracle.com/javase/8/docs/api/java/util/Map.html", + "https://docs.oracle.com/javase/8/docs/api/java/util/ArrayList.html#forEach-java.util.function.Consumer-", + "https://docs.oracle.com/javase/8/docs/api/java/util/ArrayList.html#toArray-T:A-", + "https://docs.oracle.com/javase/8/docs/api/java/util/ArrayList.html#subList-int-int-", + "https://docs.oracle.com/javase/8/docs/api/java/io/PrintStream.html#printf-java.lang.String-java.lang.Object...-", + "https://docs.oracle.com/javase/8/docs/api/java/io/PrintStream.html#write-byte:A-int-int-" ) ) From 3c2b323385221e40c1ebe5bb57c4790563eeef55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Zyba=C5=82a?= Date: Mon, 1 Mar 2021 13:14:05 +0100 Subject: [PATCH 2/3] Add support for scaladoc2 anchors --- .../tests/externalLocations/scaladoc2.scala | 2 ++ .../tasty/Scaladoc2AnchorCreator.scala | 21 +++++++++++++++++++ .../dotty/tools/scaladoc/tasty/SymOps.scala | 16 ++++++++------ ...ernalLocationProviderIntegrationTest.scala | 6 ++++-- 4 files changed, 37 insertions(+), 8 deletions(-) create mode 100644 scaladoc/src/dotty/tools/scaladoc/tasty/Scaladoc2AnchorCreator.scala diff --git a/scaladoc-testcases/src/tests/externalLocations/scaladoc2.scala b/scaladoc-testcases/src/tests/externalLocations/scaladoc2.scala index 3ed5d5f55751..b7d024ff5eb4 100644 --- a/scaladoc-testcases/src/tests/externalLocations/scaladoc2.scala +++ b/scaladoc-testcases/src/tests/externalLocations/scaladoc2.scala @@ -10,3 +10,5 @@ class Test { def c: Regex.Match = ??? } +abstract class MySeq[T] extends scala.collection.immutable.Seq[T] + diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/Scaladoc2AnchorCreator.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/Scaladoc2AnchorCreator.scala new file mode 100644 index 000000000000..438fd9ed0153 --- /dev/null +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/Scaladoc2AnchorCreator.scala @@ -0,0 +1,21 @@ +package dotty.tools.scaladoc +package tasty + +import scala.quoted._ +import dotty.tools.scaladoc.util.Escape._ +import scala.util.matching.Regex + +trait Scaladoc2AnchorCreator: + self: SymOps[_] => + + import self.q.reflect._ + + implicit private val printer: Printer[Tree] = Printer.TreeShortCode + + def getScaladoc2Type(t: Tree) = { + (t match { + case d: DefDef => d.show.split("def", 2)(1) + case t: TypeDef => t.show.split("type", 2)(1) + case v: ValDef => v.show.split("val|var", 2)(1) + }).replace(" ","") +} \ No newline at end of file diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala index 34f0f10fbd50..d1ef072cc779 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala @@ -4,7 +4,7 @@ package tasty import scala.quoted._ import dotty.tools.scaladoc.util.Escape._ -class SymOps[Q <: Quotes](val q: Q) extends JavadocAnchorCreator: +class SymOps[Q <: Quotes](val q: Q) extends JavadocAnchorCreator with Scaladoc2AnchorCreator: import q.reflect._ given Q = q @@ -115,17 +115,21 @@ class SymOps[Q <: Quotes](val q: Q) extends JavadocAnchorCreator: val docURL = link.documentationUrl.toString def constructPathForJavadoc: String = val l = "\\$+".r.replaceAllIn(location.replace(".","/"), _ => ".") - val javadocAnchor = { + val javadocAnchor = if anchor.isDefined then { val paramSigs = sym.paramSymss.flatten.map(_.tree).collect { case v: ValDef => v.tpt.tpe }.map(getJavadocType) - paramSigs.mkString("-","-","-") - } - docURL + l + extension + anchor.fold("")(a => "#" + sym.name + javadocAnchor) + "#" + sym.name + paramSigs.mkString("-","-","-") + } else "" + docURL + l + extension + javadocAnchor //TODO #263: Add anchor support def constructPathForScaladoc2: String = - docURL + escapeUrl(location).replace(".", "/") + extension + val l = escapeUrl(location).replace(".", "/") + val scaladoc2Anchor = if anchor.isDefined then { + "#" + getScaladoc2Type(sym.tree) + } else "" + docURL + l + extension + scaladoc2Anchor // TODO Add tests for it! def constructPathForScaladoc3: String = diff --git a/scaladoc/test/dotty/tools/scaladoc/ExternalLocationProviderIntegrationTest.scala b/scaladoc/test/dotty/tools/scaladoc/ExternalLocationProviderIntegrationTest.scala index f590cceafe2f..9c10a557b9de 100644 --- a/scaladoc/test/dotty/tools/scaladoc/ExternalLocationProviderIntegrationTest.scala +++ b/scaladoc/test/dotty/tools/scaladoc/ExternalLocationProviderIntegrationTest.scala @@ -28,8 +28,10 @@ class Scaladoc2ExternalLocationProviderIntegrationTest extends ExternalLocationP List(".*scala.*::scaladoc2::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" + "https://www.scala-lang.org/api/current/scala/Predef$.html#String", + "https://www.scala-lang.org/api/current/scala/collection/immutable/Map.html", + "https://www.scala-lang.org/api/current/scala/collection/IterableOnceOps.html#addString(b:StringBuilder,start:String,sep:String,end:String):StringBuilder", + "https://www.scala-lang.org/api/current/scala/collection/IterableOnceOps.html#mkString(start:String,sep:String,end:String):String" ) ) From d60bf45bc34bc8d2dae9ff04016c158865602b8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Zyba=C5=82a?= Date: Mon, 1 Mar 2021 23:21:55 +0100 Subject: [PATCH 3/3] CRs. Change logic to use definitions instead of show --- .../scaladoc/tasty/JavadocAnchorCreator.scala | 31 ++++++++++--------- .../tools/scaladoc/tasty/PackageSupport.scala | 7 +---- .../dotty/tools/scaladoc/tasty/SymOps.scala | 31 +++++++++++++------ 3 files changed, 40 insertions(+), 29 deletions(-) diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/JavadocAnchorCreator.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/JavadocAnchorCreator.scala index c58fd618acc9..5f9751d0d8e3 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/JavadocAnchorCreator.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/JavadocAnchorCreator.scala @@ -11,24 +11,27 @@ trait JavadocAnchorCreator: import self.q.reflect._ private val javadocPrimitivesMap = Map( - "scala.Int" -> "int", - "scala.Float" -> "float", - "scala.Double" -> "double", - "scala.Long" -> "long", - "scala.Byte" -> "byte", - "scala.Boolean" -> "boolean", - "scala.Char" -> "char", - "scala.Short" -> "short", - "." -> "java.lang.Object" + defn.IntClass -> "int", + defn.FloatClass -> "float", + defn.DoubleClass -> "double", + defn.LongClass -> "long", + defn.ByteClass -> "byte", + defn.BooleanClass -> "boolean", + defn.CharClass -> "char", + defn.ShortClass -> "short", + defn.ObjectClass -> "java.lang.Object" ) - private def transformPrimitiveType(s: String): String = javadocPrimitivesMap.getOrElse(s, s) + private def transformPrimitiveType(tpe: TypeRepr): String = tpe.classSymbol + .flatMap(javadocPrimitivesMap.get) + .filter(_ => !tpe.typeSymbol.isTypeParam) + .getOrElse(tpe.show) private def transformType(tpe: TypeRepr): String = tpe.simplified match { - case AppliedType(tpe, typeList) if tpe.show == "scala.Array" => transformType(typeList.head) + ":A" - case AppliedType(tpe, typeList) if tpe.show == "scala." => transformType(typeList.head) + "..." - case AppliedType(tpe, typeList) => transformPrimitiveType(tpe.show) - case other => transformPrimitiveType(other.show) + case AppliedType(tpe, typeList) if tpe.classSymbol.fold(false)(_ == defn.ArrayClass) => transformType(typeList.head) + ":A" + case AppliedType(tpe, typeList) if tpe.classSymbol.fold(false)(_ == defn.RepeatedParamClass) => transformType(typeList.head) + "..." + case AppliedType(tpe, typeList) => transformPrimitiveType(tpe) + case other => transformPrimitiveType(other) } def getJavadocType(s: TypeRepr) = transformType(s) diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/PackageSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/PackageSupport.scala index 3cde59320b23..04d4a66ff6cc 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/PackageSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/PackageSupport.scala @@ -8,13 +8,8 @@ trait PackageSupport: import qctx.reflect._ def parsePackage(pck: PackageClause): (String, Member) = - val name = extractPackageName(pck.pid.show) + val name = pck.symbol.fullName (name, Member(name, pck.symbol.dri, Kind.Package)) def parsePackageObject(pckObj: ClassDef): (String, Member) = pckObj.symbol.packageName -> parseClasslike(pckObj).withKind(Kind.Package) - - private def extractPackageName(pidShowNoColor: String): String = { - val pidSplit = pidShowNoColor.split("\\.") - pidSplit.mkString("",".","") - } diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala index d1ef072cc779..c3fa15cbb25c 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala @@ -3,17 +3,25 @@ package tasty import scala.quoted._ import dotty.tools.scaladoc.util.Escape._ +import scala.collection.mutable.{ Map => MMap } +import dotty.tools.io.AbstractFile class SymOps[Q <: Quotes](val q: Q) extends JavadocAnchorCreator with Scaladoc2AnchorCreator: import q.reflect._ given Q = q + + private val externalLinkCache: scala.collection.mutable.Map[AbstractFile, Option[ExternalDocLink]] = MMap() + extension (sym: Symbol) def packageName: String = ( if (sym.isPackageDef) sym.fullName else sym.maybeOwner.packageName ) + def packageNameSplitted: Seq[String] = + sym.packageName.split('.').toList + 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 @@ -110,11 +118,11 @@ class SymOps[Q <: Quotes](val q: Q) extends JavadocAnchorCreator with Scaladoc2A else termParamss(1).params(0) } - private def constructPath(location: String, anchor: Option[String], link: ExternalDocLink): String = + private def constructPath(location: Seq[String], anchor: Option[String], link: ExternalDocLink): String = val extension = ".html" val docURL = link.documentationUrl.toString def constructPathForJavadoc: String = - val l = "\\$+".r.replaceAllIn(location.replace(".","/"), _ => ".") + val l = "\\$+".r.replaceAllIn(location.mkString("/"), _ => ".") val javadocAnchor = if anchor.isDefined then { val paramSigs = sym.paramSymss.flatten.map(_.tree).collect { case v: ValDef => v.tpt.tpe @@ -125,7 +133,7 @@ class SymOps[Q <: Quotes](val q: Q) extends JavadocAnchorCreator with Scaladoc2A //TODO #263: Add anchor support def constructPathForScaladoc2: String = - val l = escapeUrl(location).replace(".", "/") + val l = escapeUrl(location.mkString("/")) val scaladoc2Anchor = if anchor.isDefined then { "#" + getScaladoc2Type(sym.tree) } else "" @@ -133,7 +141,7 @@ class SymOps[Q <: Quotes](val q: Q) extends JavadocAnchorCreator with Scaladoc2A // TODO Add tests for it! def constructPathForScaladoc3: String = - val base = docURL + escapeUrl(location).replace(".", "/") + extension + val base = docURL + escapeUrl(location.mkString("/")) + extension anchor.fold(base)(a => base + "#" + a) link.kind match { @@ -154,7 +162,7 @@ class SymOps[Q <: Quotes](val q: Q) extends JavadocAnchorCreator with Scaladoc2A val className = sym.className - val location = className.fold(sym.packageName)(cn => s"${sym.packageName}.${cn}") + val location = sym.packageNameSplitted ++ className val anchor = sym.anchor @@ -163,13 +171,18 @@ class SymOps[Q <: Quotes](val q: Q) extends JavadocAnchorCreator with Scaladoc2A 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).flatMap( path => - dctx.externalDocumentationLinks.find(_.originRegexes.exists(r => r.matches(path))) - ).map(link => constructPath(location, anchor, link)) + val extLink = if externalLinkCache.contains(csym.associatedFile) then externalLinkCache(csym.associatedFile) + else { + val calculatedLink = Option(csym.associatedFile).map(_.path).flatMap( path => + dctx.externalDocumentationLinks.find(_.originRegexes.exists(r => r.matches(path)))) + externalLinkCache += (csym.associatedFile -> calculatedLink) + calculatedLink + } + extLink.map(link => constructPath(location, anchor, link)) } DRI( - location, + location.mkString("."), anchor.getOrElse(""), externalLink = externalLink, // sym.show returns the same signature for def << = 1 and def >> = 2.