[2.x] fix: Use -external-mappings for Scala 3 doc task #6652 (#8602)

**Problem**

When `autoAPIMappings := true` is set on a Scala 3 project, running `sbt doc` emits warnings:

```
[warn] bad option '-doc-external-doc:/modules/java.base#https://docs.oracle.com/...
```

This happens because Scala 3's scaladoc doesn't recognize Scala 2's `-doc-external-doc` option.

Fixes #6652

**Solution**

- Added `Opts.doc.externalAPIScala3` that generates the Scala 3 format: `-external-mappings:regex::[scaladoc3|javadoc]::url`
- Modified `Defaults.scala` to use the appropriate method based on Scala version
- Added heuristics to detect javadoc vs scaladoc based on file/URL patterns
This commit is contained in:
calm 2026-01-21 23:13:10 -08:00 committed by GitHub
parent c099be5f18
commit f4ab500d19
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 76 additions and 1 deletions

View File

@ -2030,7 +2030,10 @@ object Defaults extends BuildCommon {
val xapisFiles = xapis.map { (k, v) =>
converter.toPath(k).toFile() -> v
}
val options = sOpts ++ Opts.doc.externalAPI(xapisFiles)
val externalApiOpts =
if (ScalaArtifacts.isScala3(sv)) Opts.doc.externalAPIScala3(xapisFiles)
else Opts.doc.externalAPI(xapisFiles)
val options = sOpts ++ externalApiOpts
val scalac = cs.scalac match
case ac: AnalyzingCompiler => ac.onArgs(exported(s, "scaladoc"))
val docSrcFiles = if ScalaArtifacts.isScala3(sv) then tFiles else srcs

View File

@ -38,6 +38,37 @@ object Opts {
mappings
.map { (f, u) => s"${f.getAbsolutePath}#${u.toURL().toExternalForm}" }
.mkString("-doc-external-doc:", ",", "") :: Nil
/**
* Generates Scala 3 scaladoc external mappings option.
* Format: -external-mappings:regex::[scaladoc3|scaladoc|javadoc]::url,...
*/
def externalAPIScala3(mappings: Iterable[(File, URI)]): Seq[String] =
if (mappings.isEmpty) Nil
else
mappings
.map { (f, u) =>
val fileName = f.getName
// Escape regex special characters in the filename
val escapedName = fileName.replaceAll("([\\[\\]{}()*+?.\\\\^$|])", "\\\\$1")
// Use a regex pattern that matches the file anywhere in the classpath
val regex = s".*$escapedName"
// Determine if this is javadoc or scaladoc based on the file/URL
val docType = if (isJavaDoc(f, u)) "javadoc" else "scaladoc3"
s"$regex::$docType::${u.toURL().toExternalForm}"
}
.mkString("-external-mappings:", ",", "") :: Nil
private def isJavaDoc(file: File, uri: URI): Boolean = {
val name = file.getName.toLowerCase
val url = uri.toString.toLowerCase
// Heuristics to detect Java documentation
name.startsWith("rt.jar") ||
name.contains("java") && !name.contains("scala") ||
url.contains("docs.oracle.com") ||
url.contains("javadoc") ||
url.contains("/java/") && !url.contains("scala")
}
}
object resolver {
import sbt.io.syntax.*

View File

@ -0,0 +1,22 @@
// Test for issue #6652: Doc task fails with `bad option '-doc-external-doc'` for scala3 project
// This test verifies that autoAPIMappings works correctly with Scala 3's -external-mappings option
lazy val root = (project in file("."))
.settings(
scalaVersion := "3.3.4",
autoAPIMappings := true,
libraryDependencies += "org.typelevel" %% "cats-core" % "2.12.0",
TaskKey[Unit]("checkDocGenerated") := {
val docDir = (Compile / doc / target).value
val indexFile = docDir / "index.html"
assert(indexFile.exists(), s"Expected $indexFile to exist after doc generation")
println(s"Documentation generated successfully at $docDir")
},
TaskKey[Unit]("checkApiMappings") := {
val mappings = (Compile / doc / apiMappings).value
println(s"API Mappings count: ${mappings.size}")
// With autoAPIMappings and cats-core dependency, we should have some mappings
// (cats-core publishes with apiURL)
println(s"API Mappings: ${mappings.take(3).mkString("\n ", "\n ", "")}")
}
)

View File

@ -0,0 +1,7 @@
package example
/** An example class to generate documentation for. */
class Example {
/** Returns a greeting */
def greet: String = "Hello"
}

View File

@ -0,0 +1,12 @@
# Test for issue #6652: Doc task should use -external-mappings for Scala 3
# instead of -doc-external-doc which causes "bad option" warnings
# Show the API mappings that will be used
> checkApiMappings
# Run the doc task - this should succeed without "bad option" warnings
# Before the fix, this would emit a warning about bad option -doc-external-doc
> doc
# Verify documentation was actually generated
> checkDocGenerated