Skip to content

Commit c012ad1

Browse files
committed
New format of source linksNow we use liquid templates to generate link to source file.We use our custom-build support for handling this instead of one from dokka.This commit also adds predefined templates for github and gitlab as well ass 'revision setting' that allows users to specify revision of documented code (and source link)
1 parent 306e4fc commit c012ad1

16 files changed

+301
-151
lines changed

project/Build.scala

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

14601460
def asScala3doc: Project = {
14611461
def generateDocumentation(targets: String, name: String, outDir: String, params: String = "") = Def.taskDyn {
1462-
val sourceMapping = "=https://github.com/lampepfl/dotty/tree/master#L"
1463-
run.in(Compile).toTask(s""" -d output/$outDir -t $targets -n "$name" -s $sourceMapping $params""")
1462+
val sourcesAndRevision = "-s github://lampepfl/dotty --revision master"
1463+
run.in(Compile).toTask(
1464+
s""" -d output/$outDir -t $targets -n "$name" $sourcesAndRevision $params"""
1465+
)
14641466
}
14651467

14661468
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: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
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+
15+
def githubTemplate(organization: String, repo: String)(revision: String) =
16+
s"""https://github.com/$organization/$repo/{{ operation | replace: "view", "blob" }}/$revision/{{ path }}#L{{ line }}""".stripMargin
17+
18+
def gitlabTemplate(organization: String, repo: String)(revision: String) =
19+
s"""https://gitlab.com/$organization/$repo/-/{{ operation | replace: "view", "blob" }}/$revision/{{ path }}#L{{ line }}"""
20+
21+
22+
private def parseLinkDefinition(s: String): Option[SourceLink] = ???
23+
24+
def parse(string: String, revision: Option[String]): Either[String, SourceLink] =
25+
def asRawTemplate =
26+
try Right(SourceLink(None,Template.parse(string))) catch
27+
case e: RuntimeException =>
28+
Left(s"Failed to parse template: ${e.getMessage}")
29+
30+
string match
31+
case KnownProvider(name, organization, repo) =>
32+
def withRevision(template: String => String) =
33+
revision.fold(Left(s"No revision provided"))(rev => Right(SourceLink(None, Template.parse(template(rev)))))
34+
35+
name match
36+
case "github" =>
37+
withRevision(githubTemplate(organization, repo))
38+
case "gitlab" =>
39+
withRevision(gitlabTemplate(organization, repo))
40+
case other =>
41+
Left(s"'$other' is not a known provider, please provide full source path template.")
42+
43+
case SubPath(prefix, config) =>
44+
parse(config, revision) match
45+
case l: Left[String, _] => l
46+
case Right(SourceLink(Some(prefix), _)) =>
47+
Left(s"Source path $string has duplicated subpath setting (scm template can not contains '=')")
48+
case Right(SourceLink(None, template)) =>
49+
Right(SourceLink(Some(Paths.get(prefix)), template))
50+
case BrokenKnownProvider("gitlab" | "github") =>
51+
Left(s"Does not match known provider syntax: `<name>://organization/repository`")
52+
case template => asRawTemplate
53+
54+
55+
type Operation = "view" | "edit"
56+
57+
case class SourceLinks(links: Seq[SourceLink], projectRoot: Path):
58+
def pathTo(rawPath: Path, line: Option[Int] = None, operation: Operation = "view"): Option[String] =
59+
def resolveRelativePath(path: Path) =
60+
links.find(_.path.forall(p => path.startsWith(p))).map { link =>
61+
val config = java.util.HashMap[String, Object]()
62+
config.put("path", path.toString)
63+
line.foreach(l => config.put("line", l.toString))
64+
config.put("operation", operation)
65+
66+
link.urlTemplate.render(config)
67+
}
68+
69+
if rawPath.isAbsolute then
70+
if rawPath.startsWith(projectRoot) then resolveRelativePath(projectRoot.relativize(rawPath))
71+
else None
72+
else resolveRelativePath(rawPath)
73+
74+
def pathTo(member: Member): Option[String] =
75+
member.sources.flatMap(s => pathTo(Paths.get(s.path), Option(s.lineNumber)))
76+
77+
object SourceLinks:
78+
79+
val usage =
80+
"""Source links provide a mapping between file in documentation and code repositry (usual)." +
81+
|Accepted formats:
82+
|<sub-path>=<source-link>
83+
|<source-link>
84+
|
85+
|where <source-link> is one of following:
86+
| - `github://<organization>/<repository>` (requires revision to be specified as argument for scala3doc)
87+
| - `gitlab://<organization>/<repository>` (requires revision to be specified as argument for scala3doc)
88+
| - <template>
89+
|
90+
|<template> is a liqid template string that can accepts follwoing arguments:
91+
| - `operation`: either "view" or "edit"
92+
| - `path`: relative path of file to provide link to
93+
| - `line`: optional parameter that specify line number within a file
94+
|
95+
|
96+
|Template can defined only by subset of sources defined by path prefix represented by `<sub-path>`""".stripMargin
97+
98+
def load(configs: Seq[String], revision: Option[String], projectRoot: Path): SourceLinks =
99+
// TODO ...
100+
val mappings = configs.map(str => str -> SourceLink.parse(str, revision))
101+
102+
val errors = mappings.collect {
103+
case (template, Left(message)) =>
104+
s"'$template': $message"
105+
}.mkString("\n")
106+
107+
if errors.nonEmpty then println(
108+
s"""Following templates has invalid format:
109+
|$errors
110+
|
111+
|$usage
112+
|""".stripMargin
113+
)
114+
115+
SourceLinks(mappings.collect {case (_, Right(link)) => link}, projectRoot)
116+
117+
def load(config: DocConfiguration): SourceLinks =
118+
load(
119+
config.args.sourceLinks,
120+
config.args.revision,
121+
Paths.get("").toAbsolutePath
122+
)

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)