Skip to content

Commit 7131651

Browse files
authored
Merge pull request #10274 from romanowski/scala3doc/source-links1
New format of source links
2 parents 1180aba + c1ef7d0 commit 7131651

16 files changed

+326
-151
lines changed

project/Build.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1455,8 +1455,10 @@ object Build {
14551455

14561456
def asScala3doc: Project = {
14571457
def generateDocumentation(targets: String, name: String, outDir: String, params: String = "") = Def.taskDyn {
1458-
val sourceMapping = "=https://github.com/lampepfl/dotty/tree/master#L"
1459-
run.in(Compile).toTask(s""" -d output/$outDir -t $targets -n "$name" -s $sourceMapping $params""")
1458+
val sourcesAndRevision = "-s github://lampepfl/dotty --revision master"
1459+
run.in(Compile).toTask(
1460+
s""" -d output/$outDir -t $targets -n "$name" $sourcesAndRevision $params"""
1461+
)
14601462
}
14611463

14621464
def joinProducts(products: Seq[java.io.File]): String =

scala3doc/src/dotty/dokka/DottyDokkaConfig.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,11 @@ case class DottyDokkaConfig(docConfiguration: DocConfiguration) extends DokkaCon
1919

2020
lazy val staticSiteContext = docConfiguration.args.docsRoot.map(path => StaticSiteContext(File(path).getAbsoluteFile(), Set(mkSourceSet.asInstanceOf[SourceSetWrapper])))
2121

22+
lazy val sourceLinks: SourceLinks = SourceLinks.load(docConfiguration)
23+
2224
override def getPluginsConfiguration: JList[DokkaConfiguration.PluginConfiguration] = JList()
2325

2426
lazy val mkSourceSet: DokkaSourceSet =
25-
val sourceLinks:Set[SourceLinkDefinitionImpl] = docConfiguration.args.sourceLinks.map(SourceLinkDefinitionImpl.Companion.parseSourceLinkDefinition(_)).toSet
2627
new DokkaSourceSetImpl(
2728
/*displayName=*/ docConfiguration.args.name,
2829
/*sourceSetID=*/ new DokkaSourceSetID(docConfiguration.args.name, "main"),
@@ -36,7 +37,7 @@ case class DottyDokkaConfig(docConfiguration: DocConfiguration) extends DokkaCon
3637
/*skipEmptyPackages=*/ false, // Now all our packages are empty from dokka perspective
3738
/*skipDeprecated=*/ true,
3839
/*jdkVersion=*/ 8,
39-
/*sourceLinks=*/ sourceLinks.asJava,
40+
/*sourceLinks=*/ JSet(),
4041
/*perPackageOptions=*/ JList(),
4142
/*externalDocumentationLinks=*/ JSet(),
4243
/*languageVersion=*/ null,

scala3doc/src/dotty/dokka/DottyDokkaPlugin.scala

Lines changed: 11 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import org.jetbrains.dokka.plugability._
44
import org.jetbrains.dokka.transformers.sources._
55
import org.jetbrains.dokka.transformers.documentation.PreMergeDocumentableTransformer
66
import org.jetbrains.dokka.transformers.pages.PageTransformer
7+
import org.jetbrains.dokka.transformers.documentation.DocumentableToPageTranslator
78

89
import org.jetbrains.dokka.DokkaConfiguration
910
import org.jetbrains.dokka.{ DokkaConfiguration$DokkaSourceSet => DokkaSourceSet }
@@ -82,11 +83,16 @@ class DottyDokkaPlugin extends DokkaJavaPlugin:
8283

8384
val scalaDocumentableToPageTranslator = extend(
8485
_.extensionPoint(CoreExtensions.INSTANCE.getDocumentableToPageTranslator)
85-
.fromRecipe(ctx => ScalaDocumentableToPageTranslator(
86-
ctx.single(dokkaBase.getCommentsToContentConverter),
87-
ctx.single(dokkaBase.getSignatureProvider),
88-
ctx.getLogger
89-
))
86+
.fromRecipe(ctx =>
87+
new DocumentableToPageTranslator {
88+
override def invoke(module: DModule): ModulePageNode = ScalaPageCreator(
89+
ctx.single(dokkaBase.getCommentsToContentConverter),
90+
ctx.single(dokkaBase.getSignatureProvider),
91+
ctx.getConfiguration.asInstanceOf[DottyDokkaConfig].sourceLinks,
92+
ctx.getLogger
93+
).pageForModule(module)
94+
}
95+
)
9096
.overrideExtension(dokkaBase.getDocumentableToPageTranslator)
9197
)
9298

@@ -102,17 +108,6 @@ class DottyDokkaPlugin extends DokkaJavaPlugin:
102108
.name("inheritanceTransformer")
103109
)
104110

105-
val ourSourceLinksTransformer = extend(
106-
_.extensionPoint(CoreExtensions.INSTANCE.getDocumentableTransformer)
107-
.fromRecipe(ctx => ScalaSourceLinksTransformer(
108-
ctx,
109-
ctx.single(dokkaBase.getCommentsToContentConverter),
110-
ctx.single(dokkaBase.getSignatureProvider),
111-
ctx.getLogger
112-
)
113-
)
114-
)
115-
116111
val ourRenderer = extend(
117112
_.extensionPoint(CoreExtensions.INSTANCE.getRenderer)
118113
.fromRecipe(ScalaHtmlRenderer(_))
@@ -131,15 +126,6 @@ class DottyDokkaPlugin extends DokkaJavaPlugin:
131126
.name("implicitMembersExtensionTransformer")
132127
)
133128

134-
val muteDefaultSourceLinksTransformer = extend(
135-
_.extensionPoint(CoreExtensions.INSTANCE.getPageTransformer)
136-
.fromInstance(new PageTransformer {
137-
override def invoke(root: RootPageNode) = root
138-
})
139-
.overrideExtension(dokkaBase.getSourceLinksTransformer)
140-
.name("muteDefaultSourceLinksTransformer")
141-
)
142-
143129
val customDocumentationProvider = extend(
144130
_.extensionPoint(dokkaBase.getHtmlPreprocessors)
145131
.fromRecipe(c => SitePagesCreator(c.siteContext))

scala3doc/src/dotty/dokka/Main.scala

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ class RawArgs:
4747
@COption(name="--syntax")
4848
protected var syntax: String = null
4949

50+
@COption(name="--revision")
51+
protected var revision: String = null
52+
5053
def toArgs =
5154
val parsedSyntax = syntax match
5255
case null => None
@@ -66,7 +69,8 @@ class RawArgs:
6669
Option(projectTitle),
6770
Option(projectLogo),
6871
parsedSyntax,
69-
Option(sourceLinks).map(_.asScala.toList).getOrElse(List.empty)
72+
Option(sourceLinks).map(_.asScala.toList).getOrElse(List.empty),
73+
Option(revision)
7074
)
7175

7276

@@ -80,7 +84,8 @@ case class Args(
8084
projectTitle: Option[String],
8185
projectLogo: Option[String],
8286
defaultSyntax: Option[Args.CommentSyntax],
83-
sourceLinks: List[String]
87+
sourceLinks: List[String],
88+
revision: Option[String]
8489
)
8590

8691
object Args:
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package dotty.dokka
2+
3+
import java.nio.file.Path
4+
import java.nio.file.Paths
5+
import liqp.Template
6+
import dotty.dokka.model.api._
7+
8+
case class SourceLink(val path: Option[Path], val urlTemplate: Template)
9+
10+
object SourceLink:
11+
val SubPath = "([^=]+)=(.+)".r
12+
val KnownProvider = raw"(\w+):\/\/([^\/]+)\/([^\/]+)".r
13+
val BrokenKnownProvider = raw"(\w+):\/\/.+".r
14+
val ScalaDocPatten = raw"€\{(TPL_NAME|TPL_NAME|FILE_PATH|FILE_EXT|FILE_LINE|FILE_PATH_EXT)\}".r
15+
val SupportedScalaDocPatternReplacements = Map(
16+
"€{FILE_PATH_EXT}" -> "{{ path }}",
17+
"€{FILE_LINE}" -> "{{ line }}"
18+
)
19+
20+
def githubTemplate(organization: String, repo: String)(revision: String) =
21+
s"""https://github.com/$organization/$repo/{{ operation | replace: "view", "blob" }}/$revision/{{ path }}#L{{ line }}""".stripMargin
22+
23+
def gitlabTemplate(organization: String, repo: String)(revision: String) =
24+
s"""https://gitlab.com/$organization/$repo/-/{{ operation | replace: "view", "blob" }}/$revision/{{ path }}#L{{ line }}"""
25+
26+
27+
private def parseLinkDefinition(s: String): Option[SourceLink] = ???
28+
29+
def parse(string: String, revision: Option[String]): Either[String, SourceLink] =
30+
def asTemplate(template: String) =
31+
try Right(SourceLink(None,Template.parse(template))) catch
32+
case e: RuntimeException =>
33+
Left(s"Failed to parse template: ${e.getMessage}")
34+
35+
string match
36+
case KnownProvider(name, organization, repo) =>
37+
def withRevision(template: String => String) =
38+
revision.fold(Left(s"No revision provided"))(rev => Right(SourceLink(None, Template.parse(template(rev)))))
39+
40+
name match
41+
case "github" =>
42+
withRevision(githubTemplate(organization, repo))
43+
case "gitlab" =>
44+
withRevision(gitlabTemplate(organization, repo))
45+
case other =>
46+
Left(s"'$other' is not a known provider, please provide full source path template.")
47+
48+
case SubPath(prefix, config) =>
49+
parse(config, revision) match
50+
case l: Left[String, _] => l
51+
case Right(SourceLink(Some(prefix), _)) =>
52+
Left(s"Source path $string has duplicated subpath setting (scm template can not contains '=')")
53+
case Right(SourceLink(None, template)) =>
54+
Right(SourceLink(Some(Paths.get(prefix)), template))
55+
case BrokenKnownProvider("gitlab" | "github") =>
56+
Left(s"Does not match known provider syntax: `<name>://organization/repository`")
57+
case scaladocSetting if ScalaDocPatten.findFirstIn(scaladocSetting).nonEmpty =>
58+
val all = ScalaDocPatten.findAllIn(scaladocSetting)
59+
val (supported, unsupported) = all.partition(SupportedScalaDocPatternReplacements.contains)
60+
if unsupported.nonEmpty then Left(s"Unsupported patterns from scaladoc format are used: ${unsupported.mkString(" ")}")
61+
else asTemplate(supported.foldLeft(string)((template, pattern) =>
62+
template.replace(pattern, SupportedScalaDocPatternReplacements(pattern))))
63+
64+
case template => asTemplate(template)
65+
66+
67+
type Operation = "view" | "edit"
68+
69+
case class SourceLinks(links: Seq[SourceLink], projectRoot: Path):
70+
def pathTo(rawPath: Path, line: Option[Int] = None, operation: Operation = "view"): Option[String] =
71+
def resolveRelativePath(path: Path) =
72+
links.find(_.path.forall(p => path.startsWith(p))).map { link =>
73+
val config = java.util.HashMap[String, Object]()
74+
val pathString = path.toString.replace('\\', '/')
75+
config.put("path", pathString)
76+
line.foreach(l => config.put("line", l.toString))
77+
config.put("operation", operation)
78+
79+
link.urlTemplate.render(config)
80+
}
81+
82+
if rawPath.isAbsolute then
83+
if rawPath.startsWith(projectRoot) then resolveRelativePath(projectRoot.relativize(rawPath))
84+
else None
85+
else resolveRelativePath(rawPath)
86+
87+
def pathTo(member: Member): Option[String] =
88+
member.sources.flatMap(s => pathTo(Paths.get(s.path), Option(s.lineNumber)))
89+
90+
object SourceLinks:
91+
92+
val usage =
93+
"""Source links provide a mapping between file in documentation and code repositry (usual)." +
94+
|Accepted formats:
95+
|<sub-path>=<source-link>
96+
|<source-link>
97+
|
98+
|where <source-link> is one of following:
99+
| - `github://<organization>/<repository>` (requires revision to be specified as argument for scala3doc)
100+
| - `gitlab://<organization>/<repository>` (requires revision to be specified as argument for scala3doc)
101+
| - <scaladoc-template>
102+
| - <template>
103+
|
104+
|<scaladoc-template> is a format for `doc-source-url` parameter scaladoc.
105+
|NOTE: We only supports `€{FILE_PATH_EXT}` and €{FILE_LINE} patterns
106+
|
107+
|<template> is a liqid template string that can accepts follwoing arguments:
108+
| - `operation`: either "view" or "edit"
109+
| - `path`: relative path of file to provide link to
110+
| - `line`: optional parameter that specify line number within a file
111+
|
112+
|
113+
|Template can defined only by subset of sources defined by path prefix represented by `<sub-path>`""".stripMargin
114+
115+
def load(configs: Seq[String], revision: Option[String], projectRoot: Path): SourceLinks =
116+
// TODO ...
117+
val mappings = configs.map(str => str -> SourceLink.parse(str, revision))
118+
119+
val errors = mappings.collect {
120+
case (template, Left(message)) =>
121+
s"'$template': $message"
122+
}.mkString("\n")
123+
124+
if errors.nonEmpty then println(
125+
s"""Following templates has invalid format:
126+
|$errors
127+
|
128+
|$usage
129+
|""".stripMargin
130+
)
131+
132+
SourceLinks(mappings.collect {case (_, Right(link)) => link}, projectRoot)
133+
134+
def load(config: DocConfiguration): SourceLinks =
135+
load(
136+
config.args.sourceLinks,
137+
config.args.revision,
138+
Paths.get("").toAbsolutePath
139+
)

scala3doc/src/dotty/dokka/model/api/api.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ extension[T] (member: Member):
129129
def kind: Kind = memberExt.fold(Kind.Unknown)(_.kind)
130130
def origin: Origin = memberExt.fold(Origin.DefinedWithin)(_.origin)
131131
def annotations: List[Annotation] = memberExt.fold(Nil)(_.annotations)
132+
def sources: Option[TastyDocumentableSource] = memberExt.fold(None)(_.sources)
132133
def name = member.getName
133134
def dri = member.getDri
134135

@@ -143,3 +144,5 @@ extension[T] (member: Member):
143144

144145
extension (module: DModule):
145146
def driMap: Map[DRI, Member] = ModuleExtension.getFrom(module).fold(Map.empty)(_.driMap)
147+
148+
case class TastyDocumentableSource(val path: String, val lineNumber: Int)

scala3doc/src/dotty/dokka/model/api/internalExtensions.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,14 @@ private [model] case class MemberExtension(
2828
kind: Kind,
2929
val annotations: List[Annotation],
3030
signature: Signature,
31+
sources: Option[TastyDocumentableSource] = None,
3132
origin: Origin = Origin.DefinedWithin,
3233
graph: HierarchyGraph = HierarchyGraph.empty,
3334
) extends ExtraProperty[Documentable]:
3435
override def getKey = MemberExtension
3536

3637
object MemberExtension extends BaseKey[Documentable, MemberExtension]:
37-
val empty = MemberExtension(Visibility.Unrestricted, Nil, Kind.Unknown, Nil, Nil)
38+
val empty = MemberExtension(Visibility.Unrestricted, Nil, Kind.Unknown, Nil, Signature(), None)
3839

3940
case class CompositeMemberExtension(
4041
members : Seq[Member] = Nil,

scala3doc/src/dotty/dokka/model/extras.scala

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,20 +34,6 @@ case class ClasslikeExtension(
3434

3535
object ClasslikeExtension extends BaseKey[DClasslike, ClasslikeExtension]
3636

37-
38-
case class SourceLinks(
39-
links: Map[DokkaConfiguration$DokkaSourceSet, String]
40-
) extends ExtraProperty[Documentable]:
41-
override def getKey = SourceLinks
42-
43-
object SourceLinks extends BaseKey[Documentable, SourceLinks]
44-
45-
// case class ImplicitConversions(val conversions: List[ImplicitConversion]) extends ExtraProperty[WithScope]:
46-
// override def getKey = ImplicitConversions
47-
48-
// object ImplicitConversions extends BaseKey[WithScope, ImplicitConversions]
49-
50-
5137
case class IsInherited(flag: Boolean) extends ExtraProperty[Documentable]:
5238
override def getKey = IsInherited
5339

scala3doc/src/dotty/dokka/model/scalaModel.scala

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,6 @@ import org.jetbrains.dokka.pages._
1111
import dotty.dokka.model.api.Signature
1212
import dotty.dokka.model.api.HierarchyGraph
1313

14-
case class TastyDocumentableSource(val path: String, val lineNumber: Int) extends DocumentableSource {
15-
override def getPath = path
16-
}
17-
1814
enum TableStyle extends org.jetbrains.dokka.pages.Style:
1915
case Borderless
2016
case DescriptionList

scala3doc/src/dotty/dokka/tasty/BasicSupport.scala

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import org.jetbrains.dokka.model._
55
import collection.JavaConverters._
66
import dotty.dokka._
77
import dotty.dokka.model.api.Annotation
8+
import dotty.dokka.model.api.TastyDocumentableSource
89

910
trait BasicSupport:
1011
self: TastyParser =>
@@ -41,10 +42,7 @@ trait BasicSupport:
4142

4243
def source(using ctx: Context) =
4344
val path = Some(sym.pos.sourceFile.jpath).filter(_ != null).map(_.toAbsolutePath).map(_.toString)
44-
path match{
45-
case Some(p) => Map(sourceSet -> TastyDocumentableSource(p, sym.pos.startLine))
46-
case None => Map.empty
47-
}
45+
path.map(TastyDocumentableSource(_, sym.pos.startLine))
4846

4947
def getAnnotations(): List[Annotation] =
5048
sym.annots.filterNot(_.symbol.packageName.startsWith("scala.annotation.internal")).map(parseAnnotation).reverse

0 commit comments

Comments
 (0)