Skip to content

Commit f4260b5

Browse files
authored
Merge pull request #13130 from romanowski/scaladoc/api-layout
Scaladoc generate flatten structure for API rather then put everything in `api` directory
2 parents 372930e + 49f2d03 commit f4260b5

File tree

21 files changed

+161
-74
lines changed

21 files changed

+161
-74
lines changed

project/Build.scala

+13-9
Original file line numberDiff line numberDiff line change
@@ -1325,6 +1325,7 @@ object Build {
13251325
generateScalaDocumentation := Def.inputTaskDyn {
13261326
val extraArgs = spaceDelimited("[output]").parsed
13271327
val dest = file(extraArgs.headOption.getOrElse("scaladoc/output/scala3")).getAbsoluteFile
1328+
val justAPI = extraArgs.drop(1).headOption == Some("--justAPI")
13281329
val majorVersion = (LocalProject("scala3-library-bootstrapped") / scalaBinaryVersion).value
13291330

13301331
val dottyJars: Seq[java.io.File] = Seq(
@@ -1346,21 +1347,24 @@ object Build {
13461347

13471348
val dottyLibRoot = projectRoot.relativize(dottyManagesSources.toPath.normalize())
13481349

1350+
def generateDocTask =
1351+
generateDocumentation(
1352+
roots, "Scala 3", dest.getAbsolutePath, "master",
1353+
Seq(
1354+
"-comment-syntax", "wiki",
1355+
s"-source-links:docs=github://lampepfl/dotty/master#docs",
1356+
"-doc-root-content", docRootFile.toString,
1357+
"-Ydocument-synthetic-types"
1358+
) ++ (if (justAPI) Nil else Seq("-siteroot", "docs", "-Yapi-subdirectory")))
1359+
13491360
if (dottyJars.isEmpty) Def.task { streams.value.log.error("Dotty lib wasn't found") }
1361+
else if (justAPI) generateDocTask
13501362
else Def.task{
13511363
IO.write(dest / "versions" / "latest-nightly-base", majorVersion)
13521364

13531365
// This file is used by GitHub Pages when the page is available in a custom domain
13541366
IO.write(dest / "CNAME", "dotty.epfl.ch")
1355-
}.dependsOn(generateDocumentation(
1356-
roots, "Scala 3", dest.getAbsolutePath, "master",
1357-
Seq(
1358-
"-comment-syntax", "wiki",
1359-
"-siteroot", "docs",
1360-
s"-source-links:docs=github://lampepfl/dotty/master#docs",
1361-
"-doc-root-content", docRootFile.toString,
1362-
"-Ydocument-synthetic-types"
1363-
)))
1367+
}.dependsOn(generateDocTask)
13641368
}.evaluated,
13651369

13661370
generateTestcasesDocumentation := Def.taskDyn {

sbt-test/sbt-dotty/scaladoc/build.sbt

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ TaskKey[Unit]("checkScaladocOptions") := {
66
}
77

88
TaskKey[Unit]("checkHtmlFiles") := {
9-
val helloHtml = (Compile / doc / target).value / "api" / "hello.html"
9+
val helloHtml = (Compile / doc / target).value / "hello.html"
1010
assert(helloHtml.exists)
1111
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package docs.tests
2+
3+
class Adoc:
4+
def foo = 123
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package resources.tests
2+
3+
class Adoc:
4+
def foo = 123

scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala

+4-2
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ object Scaladoc:
5858
snippetCompilerDebug: Boolean = false,
5959
noLinkWarnings: Boolean = false,
6060
versionsDictionaryUrl: Option[String] = None,
61-
generateInkuire : Boolean = false
61+
generateInkuire : Boolean = false,
62+
apiSubdirectory : Boolean = false
6263
)
6364

6465
def run(args: Array[String], rootContext: CompilerContext): Reporter =
@@ -223,7 +224,8 @@ object Scaladoc:
223224
noLinkWarnings.get,
224225
snippetCompilerDebug.get,
225226
versionsDictionaryUrl.nonDefault,
226-
generateInkuire.get
227+
generateInkuire.get,
228+
apiSubdirectory.get,
227229
)
228230
(Some(docArgs), newContext)
229231
}

scaladoc/src/dotty/tools/scaladoc/ScaladocSettings.scala

+3
Original file line numberDiff line numberDiff line change
@@ -120,5 +120,8 @@ class ScaladocSettings extends SettingGroup with AllScalaSettings:
120120
val generateInkuire: Setting[Boolean] =
121121
BooleanSetting("-Ygenerate-inkuire", "Generates InkuireDB and enables Hoogle-like searches", false)
122122

123+
val apiSubdirectory: Setting[Boolean] =
124+
BooleanSetting("-Yapi-subdirectory", "Keep all api member inside `api` directory", false)
125+
123126
def scaladocSpecificSettings: Set[Setting[_]] =
124127
Set(sourceLinks, syntax, revision, externalDocumentationMappings, socialLinks, skipById, skipByRegex, deprecatedSkipPackages, docRootContent, snippetCompiler, snippetCompilerDebug, generateInkuire)

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

+23-5
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,18 @@ class HtmlRenderer(rootPackage: Member, val members: Map[DRI, Member])(using ctx
6464
children = Nil
6565
))
6666
case Some(siteContext) =>
67-
(siteContext.orphanedTemplates :+ siteContext.indexTemplate()).map(templateToPage(_, siteContext))
67+
// In case that we do not have an index page and we do not have any API entries
68+
// we want to create empty index page, so there is one
69+
val actualIndexTemplate = siteContext.indexTemplates() match
70+
case Nil if effectiveMembers.isEmpty => Seq(siteContext.emptyIndexTemplate)
71+
case templates => templates
72+
73+
(siteContext.orphanedTemplates ++ actualIndexTemplate).map(templateToPage(_, siteContext))
6874

6975
/**
7076
* Here we have to retrive index pages from hidden pages and replace fake index pages in navigable page tree.
7177
*/
72-
private def getAllPages: Seq[Page] =
73-
78+
val allPages: Seq[Page] =
7479
def traversePages(page: Page): (Page, Seq[Page]) =
7580
val (newChildren, newPagesToRemove): (Seq[Page], Seq[Page]) = page.children.map(traversePages(_)).foldLeft((Seq[Page](), Seq[Page]())) {
7681
case ((pAcc, ptrAcc), (p, ptr)) => (pAcc :+ p, ptrAcc ++ ptr)
@@ -83,9 +88,22 @@ class HtmlRenderer(rootPackage: Member, val members: Map[DRI, Member])(using ctx
8388

8489
val (newNavigablePage, pagesToRemove) = traversePages(navigablePage)
8590

86-
newNavigablePage +: hiddenPages.filterNot(pagesToRemove.contains)
91+
val all = newNavigablePage +: hiddenPages.filterNot(pagesToRemove.contains)
92+
// We need to check for conflicts only if we have top-level member called blog or docs
93+
val hasPotentialConflict =
94+
rootPackage.members.exists(m => m.name.startsWith("docs") || m.name.startsWith("blog"))
95+
96+
if hasPotentialConflict then
97+
def walk(page: Page): Unit =
98+
if page.link.dri.isStaticFile then
99+
val dest = absolutePath(page.link.dri)
100+
if apiPaths.contains(dest) then
101+
report.error(s"Conflict between static page and API member for $dest. $pathsConflictResoultionMsg")
102+
page.children.foreach(walk)
103+
104+
all.foreach (walk)
87105

88-
val allPages = getAllPages
106+
all
89107

90108
def renderContent(page: Page) = page.content match
91109
case m: Member =>

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

+12-3
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,25 @@ val UnresolvedLocationLink = "#"
1919
trait Locations(using ctx: DocContext):
2020
def effectiveMembers: Map[DRI, Member]
2121

22+
// We generate this collection only if there may be a conflict with resources.
23+
// Potentially can be quite big.
24+
lazy val apiPaths = effectiveMembers.keySet.filterNot(_.isStaticFile).map(absolutePath)
25+
2226
var cache = new JHashMap[DRI, Seq[String]]()
2327

28+
private[renderers] def pathsConflictResoultionMsg =
29+
"Using `-Yapi-subdirectory` flag will move all API documentation into `api` subdirectory and will fix this conflict."
30+
2431
// TODO verify if location exisits
2532
def rawLocation(dri: DRI): Seq[String] =
2633
cache.get(dri) match
2734
case null =>
2835
val path = dri match
2936
case `docsRootDRI` => List("docs", "index")
30-
case `apiPageDRI` => List("api", "index")
37+
case `apiPageDRI` =>
38+
if ctx.args.apiSubdirectory || ctx.staticSiteContext.fold(false)(_.hasIndexFile)
39+
then List("api", "index")
40+
else List("index")
3141
case dri if dri.isStaticFile =>
3242
Paths.get(dri.location).iterator.asScala.map(_.toString).toList
3343
case dri =>
@@ -36,8 +46,7 @@ trait Locations(using ctx: DocContext):
3646
case "<empty>" :: Nil => "_empty_" :: Nil
3747
case "<empty>" :: tail => "_empty_" :: tail
3848
case other => other
39-
40-
Seq("api") ++ fqn
49+
if ctx.args.apiSubdirectory then "api" :: fqn else fqn
4150
cache.put(dri, path)
4251
path
4352
case cached => cached

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

+20-16
Original file line numberDiff line numberDiff line change
@@ -171,19 +171,23 @@ trait Resources(using ctx: DocContext) extends Locations, Writer:
171171
)
172172

173173
def renderResource(resource: Resource): Seq[String] =
174-
resource match
175-
case Resource.Text(path, content) =>
176-
Seq(write(path, content))
177-
case Resource.Classpath(path, name) =>
178-
getClass.getClassLoader.getResourceAsStream(name) match
179-
case null =>
180-
report.error(s"Unable to find $name on classpath")
181-
Nil
182-
case is =>
183-
try Seq(copy(is, path)) finally is.close()
184-
case Resource.File(path, file) =>
185-
Seq(copy(file, path))
186-
case Resource.URL(url) =>
187-
Nil
188-
case Resource.URLToCopy(url, dest) =>
189-
Seq(copy(new URL(url).openStream(), dest))
174+
if resource.path.endsWith(".html") && apiPaths.contains(resource.path) then
175+
report.error(s"Conflict between resource and API member for ${resource.path}. $pathsConflictResoultionMsg")
176+
Nil
177+
else
178+
resource match
179+
case Resource.Text(path, content) =>
180+
Seq(write(path, content))
181+
case Resource.Classpath(path, name) =>
182+
getClass.getClassLoader.getResourceAsStream(name) match
183+
case null =>
184+
report.error(s"Unable to find $name on classpath")
185+
Nil
186+
case is =>
187+
try Seq(copy(is, path)) finally is.close()
188+
case Resource.File(path, file) =>
189+
Seq(copy(file, path))
190+
case Resource.URL(url) =>
191+
Nil
192+
case Resource.URLToCopy(url, dest) =>
193+
Seq(copy(new URL(url).openStream(), dest))

scaladoc/src/dotty/tools/scaladoc/site/StaticSiteContext.scala

+9-5
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,21 @@ class StaticSiteContext(
1919

2020
var memberLinkResolver: String => Option[DRI] = _ => None
2121

22-
def indexTemplate(): LoadedTemplate =
22+
private def indexFiles =
2323
val files = List(new File(root, "index.html"), new File(root, "index.md")).filter { _.exists() }
2424

2525
if files.size > 1 then
2626
val msg = s"ERROR: Multiple root index pages found: ${files.map(_.getAbsolutePath)}"
2727
report.error(msg)
28+
files
2829

29-
files.flatMap(loadTemplate(_, isBlog = false)).headOption.getOrElse {
30-
val fakeFile = new File(root, "index.html")
31-
LoadedTemplate(emptyTemplate(fakeFile, "index"), List.empty, fakeFile)
32-
}
30+
def hasIndexFile = indexFiles.nonEmpty
31+
32+
def emptyIndexTemplate =
33+
val fakeFile = new File(root, "index.html")
34+
LoadedTemplate(emptyTemplate(fakeFile, "index"), List.empty, fakeFile)
35+
36+
def indexTemplates(): Seq[LoadedTemplate] = indexFiles.flatMap(loadTemplate(_, isBlog = false))
3337

3438
lazy val layouts: Map[String, TemplateFile] =
3539
val layoutRoot = new File(root, "_layouts")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Trying to override a api page!
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<html><body>I am causing conflicts!</body></html>

scaladoc/test/dotty/tools/scaladoc/BaseHtmlTest.scala

-2
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,6 @@ class BaseHtmlTest:
4141

4242
finally IO.delete(dest.toFile)
4343

44-
val testDocPath = Paths.get(BuildInfo.testDocumentationRoot)
45-
4644
class DocumentContext(d: Document, path: Path):
4745
import collection.JavaConverters._
4846

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

+1-2
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ abstract class ExternalLocationProviderIntegrationTest(
6060
)
6161

6262
override def runTest = afterRendering {
63-
val output = summon[DocContext].args.output.toPath.resolve("api")
63+
val output = summon[DocContext].args.output.toPath
6464
val linksBuilder = List.newBuilder[String]
6565

6666
def processFile(path: Path): Unit =
@@ -72,7 +72,6 @@ abstract class ExternalLocationProviderIntegrationTest(
7272
linksBuilder ++= hrefValues
7373
}
7474

75-
println(output)
7675
IO.foreachFileIn(output, processFile)
7776
val links = linksBuilder.result
7877
val errors = expectedLinks.flatMap(expect => Option.when(!links.contains(expect))(expect))

scaladoc/test/dotty/tools/scaladoc/RaportingTest.scala

+33-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ class ReportingTest:
4444
finally Files.delete(notTasty)
4545

4646
@Test
47-
def verbosePrintsDokkaMessage =
47+
def testSuccessfulDocsGeneration =
4848
val ctx = testContext
4949
ctx.setSetting(ctx.settings.verbose, true)
5050
checkReportedDiagnostics(ctx = ctx){ diag =>
@@ -53,3 +53,35 @@ class ReportingTest:
5353

5454
assertMessagesAbout(diag.infoMsgs)("generation completed successfully")
5555
}
56+
57+
@Test
58+
def testErrorInCaseOfAssetShadowing =
59+
val ctx = testContext
60+
ctx.setSetting(ctx.settings.verbose, true)
61+
val docsRoot = testDocPath.resolve("conflicts-resources").toString
62+
checkReportedDiagnostics(_.copy(
63+
docsRoot = Some(docsRoot),
64+
tastyFiles = tastyFiles("tests", rootPck = "resources")
65+
)){ diag =>
66+
assertNoWarning(diag)
67+
val Seq(msg) = diag.errorMsgs.map(_.toLowerCase)
68+
Seq("conflict","api", "resource", "resources/tests/adoc.html").foreach(word =>
69+
Assert.assertTrue(s"Error message: $msg should contains $word", msg.contains(word)))
70+
}
71+
72+
@Test
73+
def testErrorInCaseOfDocsShadowing =
74+
val ctx = testContext
75+
ctx.setSetting(ctx.settings.verbose, true)
76+
val docsRoot = testDocPath.resolve("conflicts-pages").toString
77+
checkReportedDiagnostics(_.copy(
78+
docsRoot = Some(docsRoot),
79+
tastyFiles = tastyFiles("tests", rootPck = "docs")
80+
)){ diag =>
81+
assertNoWarning(diag)
82+
val Seq(msg) = diag.errorMsgs.map(_.toLowerCase)
83+
Seq("conflict","api", "static", "page", "docs/tests/adoc.html")
84+
.foreach( word =>
85+
Assert.assertTrue(s"Error message: $msg should contains $word", msg.contains(word))
86+
)
87+
}

scaladoc/test/dotty/tools/scaladoc/signatures/AbstractMemberSignaturesTest.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class AbstractMembers extends ScaladocTest("abstractmembersignatures"):
2626
}
2727

2828
private def signaturesFromDocumentation()(using DocContext): Map[String, List[(String, String)]] =
29-
val output = summon[DocContext].args.output.toPath.resolve("api")
29+
val output = summon[DocContext].args.output.toPath
3030
val signatures = List.newBuilder[(String, (String, String))]
3131
def processFile(path: Path): Unit =
3232
val document = Jsoup.parse(IO.read(path))

scaladoc/test/dotty/tools/scaladoc/signatures/SignatureTest.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ abstract class SignatureTest(
104104
}
105105

106106
private def signaturesFromDocumentation()(using DocContext): Seq[String] =
107-
val output = summon[DocContext].args.output.toPath.resolve("api")
107+
val output = summon[DocContext].args.output.toPath
108108
val signatures = List.newBuilder[String]
109109

110110
def processFile(path: Path): Unit = if filterFunc(path) then

scaladoc/test/dotty/tools/scaladoc/site/NavigationTest.scala

+10-10
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,17 @@ class NavigationTest extends BaseHtmlTest:
3030
)),
3131
NavMenuTestEntry("Adoc", "Adoc.html", Seq()),
3232
NavMenuTestEntry("API", "../api/index.html", Seq(
33-
NavMenuTestEntry("tests.site", "../api/tests/site.html", Seq(
34-
NavMenuTestEntry("BrokenLink", "../api/tests/site/BrokenLink.html", Nil),
35-
NavMenuTestEntry("BrokenLinkWiki", "../api/tests/site/BrokenLinkWiki.html", Nil),
36-
NavMenuTestEntry("OtherPackageLink", "../api/tests/site/OtherPackageLink.html", Nil),
37-
NavMenuTestEntry("OtherPackageLinkWiki", "../api/tests/site/OtherPackageLinkWiki.html", Nil),
38-
NavMenuTestEntry("SamePackageLink", "../api/tests/site/SamePackageLink.html", Nil),
39-
NavMenuTestEntry("SamePackageLinkWiki", "../api/tests/site/SamePackageLinkWiki.html", Nil),
40-
NavMenuTestEntry("SomeClass", "../api/tests/site/SomeClass.html", Nil)
33+
NavMenuTestEntry("tests.site", "../tests/site.html", Seq(
34+
NavMenuTestEntry("BrokenLink", "../tests/site/BrokenLink.html", Nil),
35+
NavMenuTestEntry("BrokenLinkWiki", "../tests/site/BrokenLinkWiki.html", Nil),
36+
NavMenuTestEntry("OtherPackageLink", "../tests/site/OtherPackageLink.html", Nil),
37+
NavMenuTestEntry("OtherPackageLinkWiki", "../tests/site/OtherPackageLinkWiki.html", Nil),
38+
NavMenuTestEntry("SamePackageLink", "../tests/site/SamePackageLink.html", Nil),
39+
NavMenuTestEntry("SamePackageLinkWiki", "../tests/site/SamePackageLinkWiki.html", Nil),
40+
NavMenuTestEntry("SomeClass", "../tests/site/SomeClass.html", Nil)
4141
)),
42-
NavMenuTestEntry("tests.site.some.other", "../api/tests/site/some/other.html", Seq(
43-
NavMenuTestEntry("SomeOtherPackage", "../api/tests/site/some/other/SomeOtherPackage.html", Nil),
42+
NavMenuTestEntry("tests.site.some.other", "../tests/site/some/other.html", Seq(
43+
NavMenuTestEntry("SomeOtherPackage", "../tests/site/some/other/SomeOtherPackage.html", Nil),
4444
))
4545
)),
4646
))

0 commit comments

Comments
 (0)