Skip to content

Commit 89c012f

Browse files
authored
Merge pull request scala#13705 from pikinier20/scaladoc/cats-feedback
Scaladoc: Fix bugs found while setting up cats
2 parents b217b3d + d496ca5 commit 89c012f

File tree

11 files changed

+136
-35
lines changed

11 files changed

+136
-35
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package tests
2+
3+
/** Some documentation that should be present in Scaladoc
4+
*
5+
* It's a test
6+
*
7+
*/
8+
package object packageobjdocs {
9+
def placeholder: Int = 42
10+
}

scaladoc/src/dotty/tools/scaladoc/ExternalDocLink.scala

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,31 +18,44 @@ enum DocumentationKind:
1818
case Scaladoc3 extends DocumentationKind
1919

2020
object ExternalDocLink:
21-
def parse(mapping: String): Either[String, ExternalDocLink] =
22-
def fail(msg: String) = Left(s"Unable to process external mapping $mapping. $msg")
21+
private def fail(mapping: String, msg: String) = Left(s"Unable to process external mapping $mapping. $msg")
22+
23+
private def tryParse[T](mapping: String, descr: String)(op: => T): Either[String, T] = Try(op) match {
24+
case Success(v) => Right(v)
25+
case Failure(e) => fail(mapping, s"Unable to parse $descr. Exception $e occured")
26+
}
2327

24-
def tryParse[T](descr: String)(op: => T): Either[String, T] = Try(op) match {
25-
case Success(v) => Right(v)
26-
case Failure(e) => fail(s"Unable to parse $descr. Exception $e occured")
27-
}
28+
def parseLegacy(mapping: String): Either[String, ExternalDocLink] =
29+
mapping.split("#").toList match
30+
case path :: apiUrl :: Nil => for {
31+
url <- tryParse(mapping, "url")(URL(apiUrl))
32+
} yield ExternalDocLink(
33+
List(s"$path.*".r),
34+
url,
35+
DocumentationKind.Scaladoc2,
36+
None
37+
)
38+
case _ => fail(mapping, "Wrong format of legacy external mapping. path#apiUrl format is accepted.")
39+
40+
def parse(mapping: String): Either[String, ExternalDocLink] =
2841

2942
def parsePackageList(elements: List[String]) = elements match
30-
case List(urlStr) => tryParse("packageList")(Some(URL(urlStr)))
43+
case List(urlStr) => tryParse(mapping, "packageList")(Some(URL(urlStr)))
3144
case Nil => Right(None)
32-
case other => fail(s"Provided multiple package lists: $other")
45+
case other => fail(mapping, s"Provided multiple package lists: $other")
3346

3447
def doctoolByName(name: String) = name match
3548
case "javadoc" => Right(DocumentationKind.Javadoc)
3649
case "scaladoc2" => Right(DocumentationKind.Scaladoc2)
3750
case "scaladoc3" => Right(DocumentationKind.Scaladoc3)
38-
case other => fail(s"Unknown doctool: $other")
51+
case other => fail(mapping, s"Unknown doctool: $other")
3952

4053

4154
mapping.split("::").toList match
4255
case regexStr :: docToolStr :: urlStr :: rest =>
4356
for {
44-
regex <- tryParse("regex")(regexStr.r)
45-
url <- tryParse("url")(URL(urlStr))
57+
regex <- tryParse(mapping, "regex")(regexStr.r)
58+
url <- tryParse(mapping, "url")(URL(urlStr))
4659
doctool <- doctoolByName(docToolStr)
4760
packageList <- parsePackageList(rest)
4861
} yield ExternalDocLink(
@@ -52,4 +65,4 @@ object ExternalDocLink:
5265
packageList
5366
)
5467
case _ =>
55-
fail("Accepted format: `regexStr::docToolStr::urlStr[::rest]`")
68+
fail(mapping, "Accepted format: `regexStr::docToolStr::urlStr[::rest]`")

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

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,8 @@ object Scaladoc:
169169
CommentSyntax.default
170170
}
171171
}
172+
val legacySourceLinkList = if legacySourceLink.get.nonEmpty then List(legacySourceLink.get) else Nil
173+
172174
val externalMappings =
173175
externalDocumentationMappings.get.flatMap( s =>
174176
ExternalDocLink.parse(s).fold(left => {
@@ -178,6 +180,15 @@ object Scaladoc:
178180
)
179181
)
180182

183+
val legacyExternalMappings =
184+
legacyExternalDocumentationMappings.get.flatMap { s =>
185+
ExternalDocLink.parseLegacy(s).fold(left => {
186+
report.warning(left)
187+
None
188+
}, right => Some(right)
189+
)
190+
}
191+
181192
val socialLinksParsed =
182193
socialLinks.get.flatMap { s =>
183194
SocialLinks.parse(s).fold(left => {
@@ -208,9 +219,9 @@ object Scaladoc:
208219
projectLogo.nonDefault,
209220
projectFooter.nonDefault,
210221
parseSyntax,
211-
sourceLinks.get,
222+
sourceLinks.get ++ legacySourceLinkList,
212223
revision.nonDefault,
213-
externalMappings,
224+
externalMappings ++ legacyExternalMappings,
214225
socialLinksParsed,
215226
skipById.get ++ deprecatedSkipPackages.get,
216227
skipByRegex.get,

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,16 @@ class ScaladocSettings extends SettingGroup with AllScalaSettings:
3434
StringSetting("-project-version", "project version", "The current version of your project.", "", aliases = List("-doc-version"))
3535

3636
val projectLogo: Setting[String] =
37-
StringSetting("-project-logo", "project logo filename", "The file that contains the project's logo (in /images).", "", aliases = List("-doc-logo"))
37+
StringSetting("-project-logo", "project logo filename", "Path to the file that contains the project's logo. Provided path can be absolute or relative to the project root directory.", "", aliases = List("-doc-logo"))
3838

3939
val projectFooter: Setting[String] = StringSetting("-project-footer", "project footer", "A footer on every Scaladoc page.", "", aliases = List("-doc-footer"))
4040

4141
val sourceLinks: Setting[List[String]] =
4242
MultiStringSetting("-source-links", "sources", SourceLinks.usage)
4343

44+
val legacySourceLink: Setting[String] =
45+
StringSetting("-doc-source-url", "sources", "Legacy option from Scala 2. Use -source-links instead.", "")
46+
4447
val syntax: Setting[String] =
4548
StringSetting("-comment-syntax", "syntax", "Syntax of the comment used", "")
4649

@@ -52,6 +55,9 @@ class ScaladocSettings extends SettingGroup with AllScalaSettings:
5255
"Mapping between regexes matching classpath entries and external documentation. " +
5356
"'regex::[scaladoc|scaladoc|javadoc]::path' syntax is used")
5457

58+
val legacyExternalDocumentationMappings: Setting[List[String]] =
59+
MultiStringSetting("-doc-external-doc", "legacy-external-mappings", "Legacy option from Scala 2. Mapping betweeen path and external documentation. Use -external-mappings instead.")
60+
5561
val socialLinks: Setting[List[String]] =
5662
MultiStringSetting("-social-links", "social-links",
5763
"Links to social sites. '[github|twitter|gitter|discord]::link' syntax is used. " +
@@ -124,4 +130,4 @@ class ScaladocSettings extends SettingGroup with AllScalaSettings:
124130
StringSetting("-scastie-configuration", "Scastie configuration", "Additional configuration passed to Scastie in code snippets", "")
125131

126132
def scaladocSpecificSettings: Set[Setting[_]] =
127-
Set(sourceLinks, syntax, revision, externalDocumentationMappings, socialLinks, skipById, skipByRegex, deprecatedSkipPackages, docRootContent, snippetCompiler, generateInkuire, scastieConfiguration)
133+
Set(sourceLinks, legacySourceLink, syntax, revision, externalDocumentationMappings, socialLinks, skipById, skipByRegex, deprecatedSkipPackages, docRootContent, snippetCompiler, generateInkuire, scastieConfiguration)

scaladoc/src/dotty/tools/scaladoc/SourceLinks.scala

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ case class TemplateSourceLink(val urlTemplate: String) extends SourceLink:
2222
"\\{\\{ path \\}\\}".r -> pathString,
2323
"\\{\\{ line \\}\\}".r -> line.fold("")(_.toString),
2424
"\\{\\{ ext \\}\\}".r -> Some(
25-
pathString).filter(_.lastIndexOf(".") == -1).fold("")(p => p.substring(p.lastIndexOf("."))
25+
pathString).filter(_.lastIndexOf(".") != -1).fold("")(p => p.substring(p.lastIndexOf("."))
2626
),
2727
"\\{\\{ path_no_ext \\}\\}".r -> Some(
28-
pathString).filter(_.lastIndexOf(".") == -1).fold(pathString)(p => p.substring(0, p.lastIndexOf("."))
28+
pathString).filter(_.lastIndexOf(".") != -1).fold(pathString)(p => p.substring(0, p.lastIndexOf("."))
2929
),
3030
"\\{\\{ name \\}\\}".r -> memberName
3131
)
@@ -43,7 +43,7 @@ case class WebBasedSourceLink(prefix: String, revision: String, subPath: String)
4343
s"$prefix/$action/$finalRevision$subPath/${pathToString(path)}$linePart"
4444

4545
class SourceLinkParser(revision: Option[String]) extends ArgParser[SourceLink]:
46-
val KnownProvider = raw"(\w+):\/\/([^\/#]+)\/([^\/#]+)(\/[^\/#]+)?(#.+)?".r
46+
val KnownProvider = raw"(\w+):\/\/([^\/#]+)\/([^\/#]+)(\/[^#]+)?(#.+)?".r
4747
val BrokenKnownProvider = raw"(\w+):\/\/.+".r
4848
val ScalaDocPatten = raw"€\{(TPL_NAME|TPL_OWNER|FILE_PATH|FILE_EXT|FILE_LINE|FILE_PATH_EXT)\}".r
4949
val SupportedScalaDocPatternReplacements = Map(
@@ -63,6 +63,12 @@ class SourceLinkParser(revision: Option[String]) extends ArgParser[SourceLink]:
6363

6464
def parse(string: String): Either[String, SourceLink] =
6565
val res = string match
66+
case scaladocSetting if ScalaDocPatten.findFirstIn(scaladocSetting).nonEmpty =>
67+
val all = ScalaDocPatten.findAllIn(scaladocSetting)
68+
val (supported, unsupported) = all.partition(SupportedScalaDocPatternReplacements.contains)
69+
if unsupported.nonEmpty then Left(s"Unsupported patterns from scaladoc format are used: ${unsupported.mkString(" ")}")
70+
else Right(TemplateSourceLink(supported.foldLeft(string)((template, pattern) =>
71+
template.replace(pattern, SupportedScalaDocPatternReplacements(pattern)))))
6672
case KnownProvider(name, organization, repo, rawRevision, rawSubPath) =>
6773
val subPath = Option(rawSubPath).fold("")("/" + _.drop(1))
6874
val pathRev = Option(rawRevision).map(_.drop(1)).orElse(revision)
@@ -81,12 +87,6 @@ class SourceLinkParser(revision: Option[String]) extends ArgParser[SourceLink]:
8187
Left(s"'$other' is not a known provider, please provide full source path template.")
8288
case BrokenKnownProvider("gitlab" | "github") =>
8389
Left(s"Does not match known provider syntax: `<name>://organization/repository`")
84-
case scaladocSetting if ScalaDocPatten.findFirstIn(scaladocSetting).nonEmpty =>
85-
val all = ScalaDocPatten.findAllIn(scaladocSetting)
86-
val (supported, unsupported) = all.partition(SupportedScalaDocPatternReplacements.contains)
87-
if unsupported.nonEmpty then Left(s"Unsupported patterns from scaladoc format are used: ${unsupported.mkString(" ")}")
88-
else Right(TemplateSourceLink(supported.foldLeft(string)((template, pattern) =>
89-
template.replace(pattern, SupportedScalaDocPatternReplacements(pattern)))))
9090
case other =>
9191
Left("Does not match any implemented source link syntax")
9292
res match {

scaladoc/src/dotty/tools/scaladoc/api.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,8 @@ extension[T] (member: Member)
195195

196196
def withKind(kind: Kind): Member = member.copy(kind = kind)
197197

198+
def withDocs(docs: Option[Comment]) = member.copy(docs = docs)
199+
198200
def withNewMembers(newMembers: Seq[Member]): Member =
199201
member.copy(members = member.members ++ newMembers)
200202

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,8 @@ class SymOpsWithLinkCache:
271271
val extLink = if externalLinkCache.contains(csym.associatedFile)
272272
then externalLinkCache(csym.associatedFile)
273273
else {
274-
val calculatedLink = Option(csym.associatedFile).map(_.path).flatMap { path =>
274+
def calculatePath(file: AbstractFile): String = file.underlyingSource.filter(_ != file).fold("")(f => calculatePath(f) + "/") + file.path
275+
val calculatedLink = Option(csym.associatedFile).map(f => calculatePath(f)).flatMap { path =>
275276
dctx.externalDocumentationLinks.find(_.originRegexes.exists(r => r.matches(path)))
276277
}
277278
externalLinkCache += (csym.associatedFile -> calculatedLink)

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,8 @@ case class ScaladocTastyInspector()(using ctx: DocContext) extends DocTastyInspe
160160
all.groupBy(_._1).map { case (pckName, members) =>
161161
val (pcks, rest) = members.map(_._2).partition(_.kind == Kind.Package)
162162
val basePck = pcks.reduce( (p1, p2) =>
163-
p1.withNewMembers(p2.members) // TODO add doc
163+
val withNewMembers = p1.withNewMembers(p2.members)
164+
if withNewMembers.docs.isEmpty then withNewMembers.withDocs(p2.docs) else withNewMembers
164165
)
165166
basePck.withMembers((basePck.members ++ rest).sortBy(_.name))
166167
}.toList -> rootDoc

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,18 @@ class Scaladoc3ExternalLocationProviderIntegrationTest extends ExternalLocationP
4545
)
4646
)
4747

48+
class Scaladoc2LegacyExternalLocationProviderIntegrationTest extends LegacyExternalLocationProviderIntegrationTest(
49+
"externalScaladoc2",
50+
List(".*scala.*#https://www.scala-lang.org/api/current/"),
51+
List(
52+
"https://www.scala-lang.org/api/current/scala/util/matching/Regex$$Match.html",
53+
"https://www.scala-lang.org/api/current/scala/Predef$.html#String",
54+
"https://www.scala-lang.org/api/current/scala/collection/immutable/Map.html",
55+
"https://www.scala-lang.org/api/current/scala/collection/IterableOnceOps.html#addString(b:StringBuilder,start:String,sep:String,end:String):StringBuilder",
56+
"https://www.scala-lang.org/api/current/scala/collection/IterableOnceOps.html#mkString(start:String,sep:String,end:String):String"
57+
)
58+
)
59+
4860

4961
abstract class ExternalLocationProviderIntegrationTest(
5062
name: String,
@@ -85,3 +97,16 @@ abstract class ExternalLocationProviderIntegrationTest(
8597
}
8698
} :: Nil
8799

100+
abstract class LegacyExternalLocationProviderIntegrationTest(
101+
name: String,
102+
mappings: Seq[String],
103+
expectedLinks: Seq[String]
104+
) extends ExternalLocationProviderIntegrationTest(name, mappings, expectedLinks):
105+
106+
override def args = super.args.copy(
107+
externalMappings = mappings.flatMap( s =>
108+
ExternalDocLink.parseLegacy(s).fold(left => None, right => Some(right)
109+
)
110+
).toList
111+
)
112+
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package dotty.tools.scaladoc
2+
3+
import org.junit.Assert._
4+
import com.vladsch.flexmark.util.{ast => mdu, sequence}
5+
import com.vladsch.flexmark.{ast => mda}
6+
import collection.JavaConverters._
7+
8+
9+
class PackageDocumentationTest extends ScaladocTest("packageobjdocs"):
10+
override def runTest: Unit = withModule { module =>
11+
module.members.values.find {
12+
case member if member.kind == Kind.Package => true
13+
case _ => false
14+
}.flatMap(_.docs).map(_.body).fold(throw AssertionError("No package found or documentation is not present")) {
15+
case node: mdu.ContentNode =>
16+
val text = node.getDescendants().asScala.toList.map {
17+
case node: mdu.ContentNode => node.getContentChars().toString()
18+
case _ => ""
19+
}.mkString
20+
assertTrue("Documentation for package is incorrect", text.contains("It's a test"))
21+
case _ => throw AssertionError("No documentation node found in package docs")
22+
}
23+
}

scaladoc/test/dotty/tools/scaladoc/source-links/SourceLinksTest.scala

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class SourceLinkTest:
3333
Seq(
3434
"github://lampepfl/dotty",
3535
"gitlab://lampepfl/dotty",
36+
"github://lampepfl/dotty/branch/withslash",
3637
"https://github.com/scala/scala/blob/2.13.x€{FILE_PATH_EXT}#€{FILE_LINE}"
3738
).foreach{ template =>
3839
test(template)
@@ -45,12 +46,6 @@ class SourceLinkTest:
4546
val template = s"$provider://ala/ma"
4647
val res = SourceLinkParser(None).parse(template)
4748
assertTrue(s"Expected failure containing missing revision: $res", res.left.exists(_.contains("revision")))
48-
49-
Seq(s"$provider://ala/ma/", s"$provider://ala", s"$provider://ala/ma/develop/on/master").foreach { template =>
50-
val res = SourceLinkParser(Some("develop")).parse(template)
51-
assertTrue(s"Expected failure syntax info: $res", res.left.exists(_.contains("syntax")))
52-
}
53-
5449
}
5550

5651
class SourceLinksTest:
@@ -121,7 +116,7 @@ class SourceLinksTest:
121116
("project/Build.scala", 54, edit) -> "https://gitlab.com/lampepfl/dotty/-/edit/develop/project/Build.scala#L54",
122117
)
123118

124-
testLink(Seq("€{FILE_PATH}#€{FILE_LINE}"), Some("develop"))(
119+
testLink(Seq("€{FILE_PATH}.scala#€{FILE_LINE}"), Some("develop"))(
125120
"project/Build.scala" -> "/project/Build.scala#",
126121
("project/Build.scala", 54) -> "/project/Build.scala#54",
127122
("project/Build.scala", edit) -> "/project/Build.scala#",
@@ -135,6 +130,20 @@ class SourceLinksTest:
135130
("project/Build.scala", 54, edit) -> "https://github.com/scala/scala/blob/2.13.x/project/Build.scala#L54",
136131
)
137132

133+
testLink(Seq("https://github.com/scala/scala/blob/2.13.x€{FILE_PATH}.scala#L€{FILE_LINE}"), Some("develop"))(
134+
"project/Build.scala" -> "https://github.com/scala/scala/blob/2.13.x/project/Build.scala#L",
135+
("project/Build.scala", 54) -> "https://github.com/scala/scala/blob/2.13.x/project/Build.scala#L54",
136+
("project/Build.scala", edit) -> "https://github.com/scala/scala/blob/2.13.x/project/Build.scala#L",
137+
("project/Build.scala", 54, edit) -> "https://github.com/scala/scala/blob/2.13.x/project/Build.scala#L54",
138+
)
139+
140+
testLink(Seq("github://lampepfl/dotty/branch/withslash#src/lib"), None)(
141+
"project/Build.scala" -> "https://github.com/lampepfl/dotty/blob/branch/withslash/src/lib/project/Build.scala",
142+
("project/Build.scala", 54) -> "https://github.com/lampepfl/dotty/blob/branch/withslash/src/lib/project/Build.scala#L54",
143+
("project/Build.scala", edit) -> "https://github.com/lampepfl/dotty/edit/branch/withslash/src/lib/project/Build.scala",
144+
("project/Build.scala", 54, edit) -> "https://github.com/lampepfl/dotty/edit/branch/withslash/src/lib/project/Build.scala#L54",
145+
)
146+
138147
@Test
139148
def testBasicPrefixedPaths =
140149
testLink(Seq("src=gitlab://lampepfl/dotty"), Some("develop"))(
@@ -148,7 +157,7 @@ class SourceLinksTest:
148157
@Test
149158
def prefixedPaths =
150159
testLink(Seq(
151-
"src/generated=€{FILE_PATH}#€{FILE_LINE}",
160+
"src/generated=€{FILE_PATH_EXT}#€{FILE_LINE}",
152161
"src=gitlab://lampepfl/dotty",
153162
"github://lampepfl/dotty"
154163
), Some("develop"))(

0 commit comments

Comments
 (0)