Skip to content

Fix scala3doc ignoring literal identifiers #10199

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

Closed
wants to merge 4 commits into from
Closed
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
38 changes: 30 additions & 8 deletions scala3doc-testcases/src/tests/complexNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,47 @@ package tests

package complexNames

import scala.annotation.StaticAnnotation

class `*** Annotation` extends StaticAnnotation
class `OtherAnnotation` extends StaticAnnotation

class `*** Type`
class `OtherType`

abstract class A:
def ++(other: A): A
def +:(other: Int): A
def :+(other: Int): A

// scala3doc has problems with names in backticks
// def `multi word name`: Int
// def `*** name with arbitrary chars ^%`: Int
// def `mischievous(param:Int)`(otherParam: Int): String
// def withMischievousParams(`param: String, param2`: String): String
def `multi word name`: Int
def `*** name with arbitrary chars ^%`: Int
def `mischievous(param:Int)`(otherParam: Int): String
def withMischievousParams(`param: String, param2`: String): String

def complexName_^*(param: String): A

def `completelyUnnecessaryBackticks`: Int //expected: def completelyUnnecessaryBackticks: Int
def `+++:`(other: Int): A //expected: def +++:(other: Int): A
def `:+++`(other: Int): A //expected: def :+++(other: Int): A

def `abc_^^_&&`: A //expected: def abc_^^_&&: A
def `abc_^^_&&`: A
def `abc_def`: A //expected: def abc_def: A
def `abc_def_++`: A //expected: def abc_def_++: A
// def `++_abc`: A
// def `abc_++_--`: A
def `++_abc`: A
def `abc_++_--`: A

@`*** Annotation` def withStrangeAnnotation: A
@`OtherAnnotation` def withOtherAnnotation: A //expected: @OtherAnnotation def withOtherAnnotation: A
@OtherAnnotation def withOtherAnnotation2: A

def withStrangeType: `*** Type`
def withOtherType: `OtherType` //expected: def withOtherType: OtherType
def withOtherType2: OtherType

def `class`: A
def `case`: A
def `=>`: A
def `=>:`(other: A): A //expected: def =>:(other: A): A
def `caseclass`: A //expected: def caseclass: A
def `Class`: A //expected: def Class: A
2 changes: 1 addition & 1 deletion scala3doc/src/dotty/dokka/model/extras.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ case class MethodExtension(parametersListSizes: Seq[Int]) extends ExtraProperty[

object MethodExtension extends BaseKey[DFunction, MethodExtension]

case class ParameterExtension(isExtendedSymbol: Boolean, isGrouped: Boolean) extends ExtraProperty[DParameter]:
case class ParameterExtension(isExtendedSymbol: Boolean, isGrouped: Boolean, prefix: String) extends ExtraProperty[DParameter]:
override def getKey = ParameterExtension

object ParameterExtension extends BaseKey[DParameter, ParameterExtension]
Expand Down
10 changes: 5 additions & 5 deletions scala3doc/src/dotty/dokka/tasty/ClassLikeSupport.scala
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,9 @@ trait ClassLikeSupport:
.map { _ =>
parseMethod(dd.symbol, kind = Kind.Given(getGivenInstance(dd).map(_.asSignature), None))
}

case dd: DefDef if !dd.symbol.isHiddenByVisibility && dd.symbol.isExported =>
val exportedTarget = dd.rhs.collect {
val exportedTarget = dd.rhs.collect {
case a: Apply => a.fun.asInstanceOf[Select]
case s: Select => s
}
Expand All @@ -137,7 +137,7 @@ trait ClassLikeSupport:
case Select(qualifier: Ident, _) => qualifier.tpe.typeSymbol.normalizedName
}.getOrElse("instance")
val dri = dd.rhs.collect {
case s: Select if s.symbol.isDefDef => s.symbol.dri
case s: Select if s.symbol.isDefDef => s.symbol.dri
}.orElse(exportedTarget.map(_.qualifier.tpe.typeSymbol.dri))
Some(parseMethod(dd.symbol, kind = Kind.Exported).withOrigin(Origin.ExportedFrom(s"$instanceName.$functionName", dri)))

Expand Down Expand Up @@ -350,13 +350,13 @@ trait ClassLikeSupport:
def parseArgument(argument: ValDef, prefix: Symbol => String, isExtendedSymbol: Boolean = false, isGrouped: Boolean = false): DParameter =
new DParameter(
argument.symbol.dri,
prefix(argument.symbol) + argument.symbol.normalizedName,
argument.symbol.normalizedName,
argument.symbol.documentation.asJava,
null,
argument.tpt.dokkaType,
ctx.sourceSet.toSet,
PropertyContainer.Companion.empty()
.plus(ParameterExtension(isExtendedSymbol, isGrouped))
.plus(ParameterExtension(isExtendedSymbol, isGrouped, prefix(argument.symbol)))
.plus(MemberExtension.empty.copy(annotations = argument.symbol.getAnnotations()))
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -498,7 +498,7 @@ class ScalaPageContentBuilder(
DocumentableElement(
buildAnnotations(documentable),
signatureBuilder.preName.reverse,
documentable.getName,
hackEscapedName(documentable.getName),
signatureBuilder.names.reverse,
docs.fold(Nil)(d => reset().rawComment(d.getRoot)),
originInfo,
Expand Down
24 changes: 20 additions & 4 deletions scala3doc/src/dotty/dokka/translators/ScalaSignatureUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import org.jetbrains.dokka.model.properties.WithExtraProperties
import org.jetbrains.dokka.pages._
import collection.JavaConverters._
import dotty.dokka.model.api.{Kind, _}
import dotty.tools.dotc.core.StdNames.nme.keywords
import dotty.tools.dotc.core.Names.termName

case class InlineSignatureBuilder(names: Signature = Nil, preName: Signature = Nil) extends SignatureBuilder:
override def text(str: String): SignatureBuilder = copy(names = str +: names)
Expand All @@ -19,7 +21,7 @@ object InlineSignatureBuilder:

trait SignatureBuilder extends ScalaSignatureUtils {
def text(str: String): SignatureBuilder
def name(str: String, dri: DRI) = driLink(str, dri)
def name(str: String, dri: DRI) = driLink(hackEscapedName(str), dri)
def driLink(text: String, dri: DRI): SignatureBuilder

def signature(s: Signature) = s.foldLeft(this){ (b, e) => e match
Expand Down Expand Up @@ -47,7 +49,8 @@ trait SignatureBuilder extends ScalaSignatureUtils {
d.annotations.foldLeft(this){ (bdr, annotation) => bdr.buildAnnotation(annotation) }

private def buildAnnotation(a: Annotation): SignatureBuilder =
text("@").driLink(a.dri.getClassNames, a.dri).buildAnnotationParams(a).text(" ")
val name = hackEscapedName(a.dri.getClassNames)
text("@").driLink(name, a.dri).buildAnnotationParams(a).text(" ")

private def buildAnnotationParams(a: Annotation): SignatureBuilder =
if !a.params.isEmpty then
Expand Down Expand Up @@ -81,7 +84,7 @@ trait SignatureBuilder extends ScalaSignatureUtils {
tc.getProjections.asScala.foldLeft(this) { (bdr, elem) => elem match {
case text: UnresolvedBound => bdr.text(text.getName)
case link: TypeParameter =>
bdr.driLink(link.getName, link.getDri)
bdr.driLink(hackEscapedName(link.getName), link.getDri)
case other =>
bdr.text(s"TODO($other)")
}
Expand All @@ -105,16 +108,29 @@ trait SignatureBuilder extends ScalaSignatureUtils {
else if !method.kind.isInstanceOf[Kind.Extension] || from != receiverPos then
val b = builder.list(method.getParameters.subList(from, toIndex).asScala.toList, "(", ")"){ (bdr, param) => bdr
.annotationsInline(param)
.text(param.getName)
.text(prefixFor(param) + hackEscapedName(param.getName))
.text(": ")
.typeSignature(param.getType)
}
(b, toIndex)
else (builder, toIndex)
}
bldr

private def prefixFor(param: DParameter): String = param.get(ParameterExtension).prefix
}

trait ScalaSignatureUtils:
extension (tokens: Seq[String]) def toSignatureString(): String =
tokens.filter(_.trim.nonEmpty).mkString(""," "," ")

private[dokka] val ignoredKeywords: Set[String] = Set("this")

// TODO: remove after adding name abstraction to reflection api
private[dokka] def hackEscapedName(name: String) =
val simpleIdentifierRegex = raw"(?:\w+_[^\[\(\s_]+)|\w+|[^\[\(\s\w_]+".r
name match
case n if ignoredKeywords(n) => n
case n if keywords(termName(n)) => s"`$n`"
case simpleIdentifierRegex() => name
case n => s"`$n`"
13 changes: 12 additions & 1 deletion scala3doc/test/dotty/dokka/ScaladocTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,22 @@ abstract class ScaladocTest(val name: String):

private def args = Scala3doc.Args(
name = "test",
tastyFiles = tastyFiles(name),
tastyFiles = tastyFiles,
output = getTempDir().getRoot,
projectVersion = Some("1.0")
)

private def tastyFiles =
def listFilesSafe(dir: File) = Option(dir.listFiles).getOrElse {
throw AssertionError(s"$dir not found. The test name is incorrect or scala3doc-testcases were not recompiled.")
}
def collectFiles(dir: File): List[File] = listFilesSafe(dir).toList.flatMap {
case f if f.isDirectory => collectFiles(f)
case f if f.getName endsWith ".tasty" => f :: Nil
case _ => Nil
}
collectFiles(File(s"${BuildInfo.test_testcasesOutputDir}/tests/$name"))

@Rule
def collector = _collector
private val _collector = new ErrorCollector();
Expand Down