From ccc77d4666a7445e266be18106167624768e5bb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Zyba=C5=82a?= Date: Wed, 10 Feb 2021 12:19:57 +0100 Subject: [PATCH] Fix links to members inside package objects, links containing hashes. Fix names that should be put inside backticks" --- scaladoc-testcases/src/tests/complexNames.scala | 17 +++++++++-------- scaladoc-testcases/src/tests/functionDRI.scala | 6 ++++++ scaladoc/src/dotty/tools/scaladoc/DRI.scala | 3 ++- .../tools/scaladoc/renderers/HtmlRenderer.scala | 2 ++ .../tools/scaladoc/renderers/Locations.scala | 7 ++++--- .../tools/scaladoc/tasty/NameNormalizer.scala | 15 ++++++++++++++- .../src/dotty/tools/scaladoc/tasty/SymOps.scala | 6 +++--- .../src/dotty/tools/scaladoc/util/escape.scala | 4 ++++ .../tools/scaladoc/SignatureTestCases.scala | 2 +- .../scaladoc/renderers/LocationTests.scala | 2 +- 10 files changed, 46 insertions(+), 18 deletions(-) create mode 100644 scaladoc/src/dotty/tools/scaladoc/util/escape.scala diff --git a/scaladoc-testcases/src/tests/complexNames.scala b/scaladoc-testcases/src/tests/complexNames.scala index 92c0abc319af..ea05056d0f61 100644 --- a/scaladoc-testcases/src/tests/complexNames.scala +++ b/scaladoc-testcases/src/tests/complexNames.scala @@ -7,11 +7,10 @@ abstract class A: def +:(other: Int): A def :+(other: Int): A - // scaladoc 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, param23`: String): String def complexName_^*(param: String): A @@ -19,8 +18,10 @@ abstract class A: 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 + +class `class with backticks to check links` diff --git a/scaladoc-testcases/src/tests/functionDRI.scala b/scaladoc-testcases/src/tests/functionDRI.scala index 2407498e8b86..32a00df1a2c3 100644 --- a/scaladoc-testcases/src/tests/functionDRI.scala +++ b/scaladoc-testcases/src/tests/functionDRI.scala @@ -31,3 +31,9 @@ class E: def b = 10 object F: def b = 11 + +object #:: { + def #:: = 10 +} + + diff --git a/scaladoc/src/dotty/tools/scaladoc/DRI.scala b/scaladoc/src/dotty/tools/scaladoc/DRI.scala index 45eb4745b4bb..8d5317a39f45 100644 --- a/scaladoc/src/dotty/tools/scaladoc/DRI.scala +++ b/scaladoc/src/dotty/tools/scaladoc/DRI.scala @@ -1,6 +1,7 @@ package dotty.tools.scaladoc import java.nio.file.Path +import dotty.tools.scaladoc.util.Escape._ val staticFileSymbolUUID = "___staticFile___" @@ -17,7 +18,7 @@ final case class DRI( def isStaticFile = symbolUUID == staticFileSymbolUUID - def asFileLocation: String = location.replace(".","/") + def asFileLocation: String = escapeUrl(location).replace(".", "/") object DRI: def forPath(path: Path) = diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/HtmlRenderer.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/HtmlRenderer.scala index 9b2c87bc0cb8..34a792685b92 100644 --- a/scaladoc/src/dotty/tools/scaladoc/renderers/HtmlRenderer.scala +++ b/scaladoc/src/dotty/tools/scaladoc/renderers/HtmlRenderer.scala @@ -28,6 +28,8 @@ class HtmlRenderer(rootPackage: Member, val members: Map[DRI, Member])(using ctx private val args = summon[DocContext].args val staticSite = summon[DocContext].staticSiteContext + val effectiveMembers = members.filter( (dri, member) => member.origin == Origin.RegularlyDefined && member.inheritedFrom.isEmpty) + private def needsOwnPage(member: Member): Boolean = def properKind(kind: Kind): Boolean = kind match case Kind.Package => true diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/Locations.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/Locations.scala index aae61ab30507..05ba195f8e02 100644 --- a/scaladoc/src/dotty/tools/scaladoc/renderers/Locations.scala +++ b/scaladoc/src/dotty/tools/scaladoc/renderers/Locations.scala @@ -17,7 +17,7 @@ import scala.util.matching._ val UnresolvedLocationLink = "#" trait Locations(using ctx: DocContext): - def members: Map[DRI, Member] + def effectiveMembers: Map[DRI, Member] var cache = new JHashMap[DRI, Seq[String]]() @@ -48,7 +48,7 @@ trait Locations(using ctx: DocContext): UnresolvedLocationLink def pathToPage(from: DRI, to: DRI): String = - if to.isStaticFile || members.contains(to) then + if to.isStaticFile || effectiveMembers.contains(to) then val anchor = if to.anchor.isEmpty then "" else "#" + to.anchor pathToRaw(rawLocation(from), rawLocation(to)) +".html" + anchor else @@ -63,6 +63,7 @@ trait Locations(using ctx: DocContext): def pathToRaw(from: Seq[String], to: Seq[String]): String = + import dotty.tools.scaladoc.util.Escape._ val fromDir = from.dropRight(1) val commonPaths = to.zip(fromDir).takeWhile{ case (a, b) => a == b }.size @@ -72,7 +73,7 @@ trait Locations(using ctx: DocContext): case Nil => to.lastOption.fold(Seq("index"))(".." :: _ :: Nil) case l => l - (contextPath ++ nodePath).mkString("/") + escapeUrl((contextPath ++ nodePath).mkString("/")) def resolveRoot(from: Seq[String], to: String): String = pathToRaw(from, to.split("/").toList) diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/NameNormalizer.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/NameNormalizer.scala index 8ddf9ed5fec6..24366288557c 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/NameNormalizer.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/NameNormalizer.scala @@ -1,6 +1,8 @@ package dotty.tools.scaladoc.tasty import dotty.tools.scaladoc._ +import dotty.tools.dotc.core.StdNames.nme.keywords +import dotty.tools.dotc.core.Names.termName trait NameNormalizer { self: TastyParser => import qctx.reflect._ @@ -8,6 +10,17 @@ trait NameNormalizer { self: TastyParser => val withoutGivenPrefix = if s.isGiven then s.name.stripPrefix("given_") else s.name val withoutObjectSuffix = if s.flags.is(Flags.Module) then withoutGivenPrefix.stripSuffix("$") else withoutGivenPrefix val constructorNormalizedName = if s.isClassConstructor then "this" else withoutObjectSuffix - constructorNormalizedName + val escaped = escapedName(constructorNormalizedName) + escaped } + + private val ignoredKeywords: Set[String] = Set("this") + + private def escapedName(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`" } diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala index 3842aae82509..64121556c670 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/SymOps.scala @@ -16,7 +16,7 @@ class SymOps[Q <: Quotes](val q: Q): def className: Option[String] = if (sym.isClassDef && !sym.flags.is(Flags.Package)) Some( Some(sym.maybeOwner).filter(s => s.exists).flatMap(_.className).fold("")(cn => cn + "$") + sym.name - ) + ).filterNot(_.contains("package$")) else if (sym.isPackageDef) None else sym.maybeOwner.className @@ -117,8 +117,8 @@ class SymOps[Q <: Quotes](val q: Q): val csym = sym.asInstanceOf[dotc.core.Symbols.Symbol] Option(csym.associatedFile).fold("")(_.path) } - // We want package object to point to package - val className = sym.className.filter(_ != "package$") + + val className = sym.className DRI( className.fold(sym.packageName)(cn => s"${sym.packageName}.${cn}"), diff --git a/scaladoc/src/dotty/tools/scaladoc/util/escape.scala b/scaladoc/src/dotty/tools/scaladoc/util/escape.scala new file mode 100644 index 000000000000..66035e76ca98 --- /dev/null +++ b/scaladoc/src/dotty/tools/scaladoc/util/escape.scala @@ -0,0 +1,4 @@ +package dotty.tools.scaladoc.util + +object Escape: + def escapeUrl(url: String) = url.replace("#","%23") \ No newline at end of file diff --git a/scaladoc/test/dotty/tools/scaladoc/SignatureTestCases.scala b/scaladoc/test/dotty/tools/scaladoc/SignatureTestCases.scala index 0644f89f6fa1..4a85b191dc92 100644 --- a/scaladoc/test/dotty/tools/scaladoc/SignatureTestCases.scala +++ b/scaladoc/test/dotty/tools/scaladoc/SignatureTestCases.scala @@ -58,7 +58,7 @@ class InheritanceLoop extends SignatureTest("inheritanceLoop", SignatureTest.all class InheritedMembers extends SignatureTest("inheritedMembers2", SignatureTest.all.filter(_ != "class"), sourceFiles = List("inheritedMembers1", "inheritedMembers2")) -class ComplexNames extends SignatureTest("complexNames", Seq("def")) +class ComplexNames extends SignatureTest("complexNames", Seq("def", "class")) class WrongDocumentationLinks extends SignatureTest("links", Seq("def")) diff --git a/scaladoc/test/dotty/tools/scaladoc/renderers/LocationTests.scala b/scaladoc/test/dotty/tools/scaladoc/renderers/LocationTests.scala index 6317cc7bfd08..ffcf1a69106e 100644 --- a/scaladoc/test/dotty/tools/scaladoc/renderers/LocationTests.scala +++ b/scaladoc/test/dotty/tools/scaladoc/renderers/LocationTests.scala @@ -8,7 +8,7 @@ import dotty.tools.scaladoc.util.HTML._ class LocationTests: given DocContext = testDocContext() object locations extends Locations: - val members = Map.empty + val effectiveMembers = Map.empty @Test def testPathToRoot() =