Skip to content

Commit c20471b

Browse files
committed
Fix broken Scaladoc external Java references
Fixes scala/bug#11839
1 parent 50a8ded commit c20471b

File tree

7 files changed

+91
-22
lines changed

7 files changed

+91
-22
lines changed

project/ScalaOptionParser.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ object ScalaOptionParser {
124124
private def scalaDocBooleanSettingNames = List("-implicits", "-implicits-debug", "-implicits-show-all", "-implicits-sound-shadowing", "-implicits-hide", "-author", "-diagrams", "-diagrams-debug", "-raw-output", "-no-prefixes", "-no-link-warnings", "-expand-all-types", "-groups")
125125
private def scalaDocIntSettingNames = List("-diagrams-max-classes", "-diagrams-max-implicits", "-diagrams-dot-timeout", "-diagrams-dot-restart")
126126
private def scalaDocChoiceSettingNames = Map("-doc-format" -> List("html"))
127-
private def scaladocStringSettingNames = List("-doc-title", "-doc-version", "-doc-footer", "-doc-no-compile", "-doc-source-url", "-doc-generator", "-skip-packages")
127+
private def scaladocStringSettingNames = List("-doc-title", "-doc-version", "-doc-footer", "-doc-no-compile", "-doc-source-url", "-doc-generator", "-skip-packages", "jdk-api-doc-base")
128128
private def scaladocPathSettingNames = List("-doc-root-content", "-diagrams-dot-path")
129129
private def scaladocMultiStringSettingNames = List("-doc-external-doc")
130130

src/manual/scala/man1/scaladoc.scala

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,11 @@ object scaladoc extends Command {
7777
"Define a URL to be concatenated with source locations for link to source files."),
7878
Definition(
7979
CmdOption("doc-external-doc", Argument("external-doc")),
80-
"Define a comma-separated list of classpath_entry_path#doc_URL pairs describing external dependencies."))),
80+
"Define a comma-separated list of classpath_entry_path#doc_URL pairs describing external dependencies."),
81+
Definition(
82+
CmdOption("jdk-api-doc-base", Argument("url")),
83+
"Define a URL to be concatenated with source locations for link to Java API."))
84+
),
8185

8286
Section("Compiler Options",
8387
DefinitionList(

src/reflect/scala/reflect/io/AbstractFile.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import java.io.{BufferedOutputStream, ByteArrayOutputStream, IOException, InputS
1818
import java.io.{File => JFile}
1919
import java.net.URL
2020
import java.nio.ByteBuffer
21+
import java.nio.file.Paths
2122

2223
import scala.collection.AbstractIterable
2324

@@ -265,6 +266,15 @@ abstract class AbstractFile extends AbstractIterable[AbstractFile] {
265266
fileOrSubdirectoryNamed(name, isDir = true)
266267
}
267268

269+
/**
270+
* Check if this file is a child of the given directory string. Can only be used
271+
* on directories that actually exist in the file system.
272+
*/
273+
def isChildOf(dir: String): Boolean = {
274+
val parent = Paths.get(dir).toAbsolutePath().toString
275+
canonicalPath.startsWith(parent)
276+
}
277+
268278
protected def unsupported(): Nothing = unsupported(null)
269279
protected def unsupported(msg: String): Nothing = throw new UnsupportedOperationException(msg)
270280

src/reflect/scala/reflect/io/PlainFile.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ final class PlainNioFile(val nioPath: java.nio.file.Path) extends AbstractFile {
151151
if (nioPath.getNameCount > 2 && nioPath.startsWith("/modules")) {
152152
// TODO limit this to OpenJDK based JVMs?
153153
val moduleName = nioPath.getName(1)
154-
Some(new PlainNioFile(Paths.get("/modules", moduleName.toString + ".jmod")))
154+
Some(new PlainNioFile(Paths.get(System.getProperty("java.home"), "jmods", moduleName.toString + ".jmod")))
155155
} else None
156156
case _ => None
157157
}

src/scaladoc/scala/tools/nsc/doc/Settings.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,13 @@ class Settings(error: String => Unit, val printMsg: String => Unit = println(_))
8585
"comma-separated list of classpath_entry_path#doc_URL pairs describing external dependencies."
8686
)
8787

88+
val jdkApiDocBase = StringSetting (
89+
"-jdk-api-doc-base",
90+
"url",
91+
"URL used to link Java API references.",
92+
""
93+
)
94+
8895
val docgenerator = StringSetting (
8996
"-doc-generator",
9097
"class-name",

src/scaladoc/scala/tools/nsc/doc/model/MemberLookup.scala

Lines changed: 63 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -57,28 +57,74 @@ trait MemberLookup extends base.MemberLookupBase {
5757
/* Get package object which has associatedFile ne null */
5858
sym.info.member(newTermName("package"))
5959
else sym
60-
def classpathEntryFor(s: Symbol): Option[String] = {
61-
Option(s.associatedFile).flatMap(_.underlyingSource).map { src =>
62-
val path = src.canonicalPath
63-
if(path.endsWith(".class")) { // Individual class file -> Classpath entry is root dir
64-
val nesting = s.ownerChain.count(_.hasPackageFlag)
65-
if(nesting > 0) {
66-
val p = 0.until(nesting).foldLeft(src) {
67-
case (null, _) => null
68-
case (f, _) => f.container
69-
}
70-
if(p eq null) path else p.canonicalPath
71-
} else path
72-
} else path // JAR file (and fallback option)
73-
}
74-
}
7560
classpathEntryFor(sym1) flatMap { path =>
76-
settings.extUrlMapping get path map { url => {
77-
LinkToExternalTpl(name, url, makeTemplate(sym))
61+
if (isJDK(sym1)) {
62+
Some(LinkToExternalTpl(name, jdkUrl(path), makeTemplate(sym)))
63+
}
64+
else {
65+
settings.extUrlMapping get path map { url =>
66+
LinkToExternalTpl(name, url, makeTemplate(sym))
7867
}
7968
}
8069
}
8170
}
8271

72+
private def classpathEntryFor(s: Symbol): Option[String] = {
73+
Option(s.associatedFile).flatMap(_.underlyingSource).map { src =>
74+
val path = src.canonicalPath
75+
if(path.endsWith(".class")) { // Individual class file -> Classpath entry is root dir
76+
val nesting = s.ownerChain.count(_.hasPackageFlag)
77+
if(nesting > 0) {
78+
val p = 0.until(nesting).foldLeft(src) {
79+
case (null, _) => null
80+
case (f, _) => f.container
81+
}
82+
if(p eq null) path else p.canonicalPath
83+
} else path
84+
} else path // JAR file (and fallback option)
85+
}
86+
}
87+
88+
private def isJDK(sym: Symbol) =
89+
sym.associatedFile.underlyingSource.map(_.isChildOf(sys.props("java.home"))).getOrElse(false)
90+
91+
def jdkUrl(path: String): String = {
92+
if (path.endsWith(".jmod")) {
93+
val tokens = path.split("/")
94+
val module = tokens.last.stripSuffix(".jmod")
95+
s"$jdkUrl/$module"
96+
}
97+
else {
98+
jdkUrl
99+
}
100+
}
101+
102+
def jdkUrl: String = {
103+
if (settings.jdkApiDocBase.isDefault)
104+
defaultJdkUrl
105+
else
106+
settings.jdkApiDocBase.value
107+
}
108+
109+
lazy val defaultJdkUrl = {
110+
if (javaVersion < 11) {
111+
s"https://docs.oracle.com/javase/$javaVersion/docs/api"
112+
}
113+
else {
114+
s"https://docs.oracle.com/en/java/javase/$javaVersion/docs/api"
115+
}
116+
}
117+
118+
lazy val javaVersion: Int = {
119+
var version = System.getProperty("java.version")
120+
(if (version.startsWith("1.")) {
121+
version.substring(2, 3)
122+
}
123+
else {
124+
val dot = version.indexOf(".")
125+
version.substring(0, dot)
126+
}).toInt
127+
}
128+
83129
override def warnNoLink = !settings.docNoLinkWarnings.value
84130
}

test/scaladoc/run/t191.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ object Test extends ScaladocModelTest {
1919
* - [[scala]] Linking to a package
2020
* - [[scala.AbstractMethodError]] Linking to a member in the package object
2121
* - [[scala.Predef.String]] Linking to a member in an object
22+
* - [[java.lang.Throwable]] Linking to a class in the JDK
2223
*
2324
* Don't look at:
2425
* - [[scala.NoLink]] Not linking :)
@@ -32,6 +33,7 @@ object Test extends ScaladocModelTest {
3233
"""
3334

3435
def scalaURL = "http://bog.us"
36+
val jdkUrl = "http://java.us"
3537

3638
override def scaladocSettings = {
3739
val samplePath = getClass.getClassLoader.getResource("scala/Function1.class").getPath
@@ -41,7 +43,7 @@ object Test extends ScaladocModelTest {
4143
} else { // individual class files on disk
4244
samplePath.replace('\\', '/').dropRight("scala/Function1.class".length)
4345
}
44-
s"-no-link-warnings -doc-external-doc $scalaLibPath#$scalaURL"
46+
s"-no-link-warnings -doc-external-doc $scalaLibPath#$scalaURL jdk-api-doc-base $jdkUrl"
4547
}
4648

4749
def testModel(rootPackage: Package): Unit = {
@@ -72,7 +74,7 @@ object Test extends ScaladocModelTest {
7274
).map( _.split("#").toSeq ).map({
7375
case Seq(one) => scalaURL + "/" + one + ".html"
7476
case Seq(one, two) => scalaURL + "/" + one + ".html#" + two
75-
})
77+
}) ++ Set(s"$jdkUrl/java/lang.Throwable")
7678

7779
def isExpectedExternalLink(l: EntityLink) = l.link match {
7880
case LinkToExternalTpl(name, baseUrlString, tpl: TemplateEntity) =>

0 commit comments

Comments
 (0)