Skip to content

Scala3doc: Remove liquid templates from source links generation. #10807

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Dec 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion project/Build.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1532,7 +1532,7 @@ object Build {
val projectRoot = (ThisBuild/baseDirectory).value.toPath
val stdLibRoot = projectRoot.relativize(managedSources.toPath.normalize())
val scalaSourceLink =
s"$stdLibRoot=github://scala/scala/v${stdlibVersion(Bootstrapped)}#src/library"
s"$stdLibRoot=github://scala/scala/v${stdlibVersion(Bootstrapped)}#src/library"
val sourcesAndRevision = s"-source-links $scalaSourceLink,github://lampepfl/dotty -revision $ref -project-version $projectVersion"
val cmd = s""" -d $outDir -project "$name" $sourcesAndRevision $params $targets"""
run.in(Compile).toTask(cmd)
Expand Down
66 changes: 35 additions & 31 deletions scala3doc/src/dotty/dokka/SourceLinks.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,48 +2,61 @@ package dotty.dokka

import java.nio.file.Path
import java.nio.file.Paths
import liqp.Template
import dotty.dokka.model.api._
import dotty.tools.dotc.core.Contexts.Context
import scala.util.matching.Regex

def pathToString(p: Path) = p.toString.replace('\\', '/')

trait SourceLink:
val path: Option[Path] = None
def render(path: Path, operation: String, line: Option[Int]): String
def render(memberName: String, path: Path, operation: String, line: Option[Int]): String

case class PrefixedSourceLink(val myPath: Path, nested: SourceLink) extends SourceLink:
val myPrefix = pathToString(myPath)
override val path = Some(myPath)
override def render(path: Path, operation: String, line: Option[Int]): String =
nested.render(myPath.relativize(path), operation, line)
override def render(memberName: String, path: Path, operation: String, line: Option[Int]): String =
nested.render(memberName, myPath.relativize(path), operation, line)


case class TemplateSourceLink(val urlTemplate: Template) extends SourceLink:
case class TemplateSourceLink(val urlTemplate: String) extends SourceLink:
override val path: Option[Path] = None
override def render(path: Path, operation: String, line: Option[Int]): String =
val config = java.util.HashMap[String, Object]()
config.put("path", pathToString(path))
line.foreach(l => config.put("line", l.toString))
config.put("operation", operation)
override def render(memberName: String, path: Path, operation: String, line: Option[Int]): String =
val pathString = "/" + pathToString(path)
val mapping = Map(
"\\{\\{ path \\}\\}".r -> pathString,
"\\{\\{ line \\}\\}".r -> line.fold("")(_.toString),
"\\{\\{ ext \\}\\}".r -> Some(
pathString).filter(_.lastIndexOf(".") == -1).fold("")(p => p.substring(p.lastIndexOf("."))
),
"\\{\\{ path_no_ext \\}\\}".r -> Some(
pathString).filter(_.lastIndexOf(".") == -1).fold(pathString)(p => p.substring(0, p.lastIndexOf("."))
),
"\\{\\{ name \\}\\}".r -> memberName
)
mapping.foldLeft(urlTemplate) {
case (sourceLink, (regex, value)) => regex.replaceAllIn(sourceLink, Regex.quoteReplacement(value))
}

urlTemplate.render(config)

case class WebBasedSourceLink(prefix: String, revision: String, subPath: String) extends SourceLink:
override val path: Option[Path] = None
override def render(path: Path, operation: String, line: Option[Int]): String =
override def render(memberName: String, path: Path, operation: String, line: Option[Int]): String =
val action = if operation == "view" then "blob" else operation
val linePart = line.fold("")(l => s"#L$l")
s"$prefix/$action/$revision$subPath/$path$linePart"
s"$prefix/$action/$revision$subPath/${pathToString(path)}$linePart"

object SourceLink:
val SubPath = "([^=]+)=(.+)".r
val KnownProvider = raw"(\w+):\/\/([^\/#]+)\/([^\/#]+)(\/[^\/#]+)?(#.+)?".r
val BrokenKnownProvider = raw"(\w+):\/\/.+".r
val ScalaDocPatten = raw"€\{(TPL_NAME|TPL_NAME|FILE_PATH|FILE_EXT|FILE_LINE|FILE_PATH_EXT)\}".r
val ScalaDocPatten = raw"€\{(TPL_NAME|TPL_OWNER|FILE_PATH|FILE_EXT|FILE_LINE|FILE_PATH_EXT)\}".r
val SupportedScalaDocPatternReplacements = Map(
"€{FILE_PATH_EXT}" -> "{{ path }}",
"€{FILE_LINE}" -> "{{ line }}"
"€{FILE_LINE}" -> "{{ line }}",
"€{TPL_NAME}" -> "{{ name }}",
"€{FILE_EXT}" -> "{{ ext }}",
"€{FILE_PATH}" -> "{{ path_no_ext }}"
)

def githubPrefix(org: String, repo: String) = s"https://github.com/$org/$repo"
Expand All @@ -54,10 +67,6 @@ object SourceLink:
private def parseLinkDefinition(s: String): Option[SourceLink] = ???

def parse(string: String, revision: Option[String]): Either[String, SourceLink] =
def asTemplate(template: String) =
try Right(TemplateSourceLink(Template.parse(template))) catch
case e: RuntimeException =>
Left(s"Failed to parse template: ${e.getMessage}")

string match
case KnownProvider(name, organization, repo, rawRevision, rawSubPath) =>
Expand Down Expand Up @@ -90,28 +99,28 @@ object SourceLink:
val all = ScalaDocPatten.findAllIn(scaladocSetting)
val (supported, unsupported) = all.partition(SupportedScalaDocPatternReplacements.contains)
if unsupported.nonEmpty then Left(s"Unsupported patterns from scaladoc format are used: ${unsupported.mkString(" ")}")
else asTemplate(supported.foldLeft(string)((template, pattern) =>
template.replace(pattern, SupportedScalaDocPatternReplacements(pattern))))

case template => asTemplate(template)
else Right(TemplateSourceLink(supported.foldLeft(string)((template, pattern) =>
template.replace(pattern, SupportedScalaDocPatternReplacements(pattern)))))
case other =>
Right(TemplateSourceLink(""))


type Operation = "view" | "edit"

case class SourceLinks(links: Seq[SourceLink], projectRoot: Path):
def pathTo(rawPath: Path, line: Option[Int] = None, operation: Operation = "view"): Option[String] =
def pathTo(rawPath: Path, memberName: String = "", line: Option[Int] = None, operation: Operation = "view"): Option[String] =
def resolveRelativePath(path: Path) =
links
.find(_.path.forall(p => path.startsWith(p)))
.map(_.render(path, operation, line))
.map(_.render(memberName, path, operation, line))

if rawPath.isAbsolute then
if rawPath.startsWith(projectRoot) then resolveRelativePath(projectRoot.relativize(rawPath))
else None
else resolveRelativePath(rawPath)

def pathTo(member: Member): Option[String] =
member.sources.flatMap(s => pathTo(Paths.get(s.path), Option(s.lineNumber).map(_ + 1)))
member.sources.flatMap(s => pathTo(Paths.get(s.path), member.name, Option(s.lineNumber).map(_ + 1)))

object SourceLinks:

Expand All @@ -130,15 +139,10 @@ object SourceLinks:
| will match https://gitlab.com/$organization/$repository/-/[blob|edit]/$revision[/$subpath]/$filePath[$lineNumber]
| when revision is not provided then requires revision to be specified as argument for scala3doc
| - <scaladoc-template>
| - <template>
|
|<scaladoc-template> is a format for `doc-source-url` parameter scaladoc.
|NOTE: We only supports `€{FILE_PATH_EXT}` and €{FILE_LINE} patterns
|
|<template> is a liqid template string that can accepts follwoing arguments:
| - `operation`: either "view" or "edit"
| - `path`: relative path of file to provide link to
| - `line`: optional parameter that specify line number within a file
|
|
|Template can defined only by subset of sources defined by path prefix represented by `<sub-path>`.
Expand Down
20 changes: 9 additions & 11 deletions scala3doc/test/dotty/dokka/SourceLinksTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ class SourceLinkTest:
testFailure("ala://ma/kota", "known provider")
testFailure("ala=ala=ala://ma/kota", "known provider")
testFailure("ala=ala=ala", "subpath")
testFailure("""{{ ala "ala"}}""", "parse")
testFailure("""€{TPL_NAME}""", "scaladoc")
testFailure("""€{TPL_OWNER}""", "scaladoc")


@Test
Expand All @@ -31,7 +30,6 @@ class SourceLinkTest:


Seq(
"""https://github.com/organization/repo/{{ operation | replace: "view", "blob" }}/$revision/{{ path }}{{ line | prepend: "L#"}}""",
"github://lampepfl/dotty",
"gitlab://lampepfl/dotty",
"https://github.com/scala/scala/blob/2.13.x€{FILE_PATH_EXT}#€{FILE_LINE}"
Expand Down Expand Up @@ -124,14 +122,14 @@ class SourceLinksTest:
("project/Build.scala", 54, edit) -> "https://gitlab.com/lampepfl/dotty/-/edit/develop/project/Build.scala#L54",
)

testLink(Seq("/{{operation}}/{{path}}#{{line}}"), Some("develop"))(
"project/Build.scala" -> "/view/project/Build.scala#",
("project/Build.scala", 54) -> "/view/project/Build.scala#54",
("project/Build.scala", edit) -> "/edit/project/Build.scala#",
("project/Build.scala", 54, edit) -> "/edit/project/Build.scala#54",
testLink(Seq("€{FILE_PATH}#€{FILE_LINE}"), Some("develop"))(
"project/Build.scala" -> "/project/Build.scala#",
("project/Build.scala", 54) -> "/project/Build.scala#54",
("project/Build.scala", edit) -> "/project/Build.scala#",
("project/Build.scala", 54, edit) -> "/project/Build.scala#54",
)

testLink(Seq("https://github.com/scala/scala/blob/2.13.x/€{FILE_PATH_EXT}#L€{FILE_LINE}"), Some("develop"))(
testLink(Seq("https://github.com/scala/scala/blob/2.13.x€{FILE_PATH_EXT}#L€{FILE_LINE}"), Some("develop"))(
"project/Build.scala" -> "https://github.com/scala/scala/blob/2.13.x/project/Build.scala#L",
("project/Build.scala", 54) -> "https://github.com/scala/scala/blob/2.13.x/project/Build.scala#L54",
("project/Build.scala", edit) -> "https://github.com/scala/scala/blob/2.13.x/project/Build.scala#L",
Expand All @@ -151,12 +149,12 @@ class SourceLinksTest:
@Test
def prefixedPaths =
testLink(Seq(
"src/generated=/{{operation}}/{{path}}#{{line}}",
"src/generated=€{FILE_PATH}#€{FILE_LINE}",
"src=gitlab://lampepfl/dotty",
"github://lampepfl/dotty"
), Some("develop"))(
("project/Build.scala", 54, edit) -> "https://github.com/lampepfl/dotty/edit/develop/project/Build.scala#L54",
("src/lib/core.scala", 33, edit) -> "https://gitlab.com/lampepfl/dotty/-/edit/develop/lib/core.scala#L33",
("src/generated.scala", 33, edit) -> "https://gitlab.com/lampepfl/dotty/-/edit/develop/generated.scala#L33",
("src/generated/template.scala", 1, edit) -> "/edit/template.scala#1"
("src/generated/template.scala", 1, edit) -> "/template.scala#1"
)