From 558322468e1c61a08b71ea1461275b68191452bd Mon Sep 17 00:00:00 2001 From: Krzysztof Romanowski Date: Sat, 5 Dec 2020 00:35:52 +0100 Subject: [PATCH 01/12] MIgrate away from Kotlin Documentable Scala3doc was only loosly using dokka model and it has started to blocking us. This PR standarize over Member and expand semantic of Kind. This allow us to have more typesafe way to reason about code. It will also make migration away from dokka easier if needed. --- .../src/tests/extensionMethodSignatures.scala | 2 + .../src/tests/givenSignatures.scala | 42 +- .../src/tests/givenSignaturesPg.scala | 44 ++ .../src/tests/methodsAndConstructors.scala | 7 + .../tests/modifiersSignatureTestSource.scala | 3 + scala3doc/src/dotty/dokka/DocContext.scala | 8 +- .../src/dotty/dokka/DottyDokkaPlugin.scala | 5 +- scala3doc/src/dotty/dokka/compat.scala | 6 + scala3doc/src/dotty/dokka/model/api/api.scala | 49 +- .../dokka/model/api/internalExtensions.scala | 5 +- scala3doc/src/dotty/dokka/model/extras.scala | 23 - .../src/dotty/dokka/tasty/BasicSupport.scala | 2 + .../dotty/dokka/tasty/ClassLikeSupport.scala | 424 +++++++++--------- scala3doc/src/dotty/dokka/tasty/SymOps.scala | 9 +- .../ImplicitMembersExtensionTransformer.scala | 24 +- .../translators/ScalaContentBuilder.scala | 4 +- .../dokka/translators/ScalaPageCreator.scala | 117 ++--- .../translators/ScalaSignatureProvider.scala | 219 +++++---- .../translators/ScalaSignatureUtils.scala | 66 +-- .../dotty/renderers/DotDiagramBuilder.scala | 20 +- .../test/dotty/dokka/SignatureTestCases.scala | 2 +- 21 files changed, 528 insertions(+), 553 deletions(-) create mode 100644 scala3doc-testcases/src/tests/givenSignaturesPg.scala diff --git a/scala3doc-testcases/src/tests/extensionMethodSignatures.scala b/scala3doc-testcases/src/tests/extensionMethodSignatures.scala index e31c57bbca18..50152cc2ef1d 100644 --- a/scala3doc-testcases/src/tests/extensionMethodSignatures.scala +++ b/scala3doc-testcases/src/tests/extensionMethodSignatures.scala @@ -17,6 +17,8 @@ class ClassOne extension (c: ClassTwo) def |||:(a: Int, b: Int, d: Int)(e: String): Int = 56 + def ++:(a: Int): Int + = 45 extension (b: Int) def secondGroup(): String diff --git a/scala3doc-testcases/src/tests/givenSignatures.scala b/scala3doc-testcases/src/tests/givenSignatures.scala index 4b0d815cc86b..a1143e908da9 100644 --- a/scala3doc-testcases/src/tests/givenSignatures.scala +++ b/scala3doc-testcases/src/tests/givenSignatures.scala @@ -2,43 +2,15 @@ package tests package givenSignatures +object Obj +given Seq[String] = Nil -class GivenClass { - trait B - trait C[T] - val r: Int = 5 - type R = Int - given R = r - trait Ord[T] { - def compare(x: T, y: T): Int - extension (x: T) def < (y: T) = compare(x, y) < 0 - extension (x: T) def > (y: T) = compare(x, y) > 0 - } - given intOrd: Ord[Int] with { - def compare(x: Int, y: Int) = - if (x < y) -1 else if (x > y) +1 else 0 - } +given GivenType = GivenType() - given asd(using int: Int): B with {} +class GivenType - given asd2[T]: C[T] with {} - - given listOrd[T](using ord: Ord[T]): Ord[List[T]] with { - - def compare(xs: List[T], ys: List[T]): Int = (xs, ys) match - case (Nil, Nil) => 0 - case (Nil, _) => -1 - case (_, Nil) => +1 - case (x :: xs1, y :: ys1) => - val fst = ord.compare(x, y) - if (fst != 0) fst else compare(xs1, ys1) - } - - given IntOps: Int.type = Int - - given GivenType = GivenType() - - class GivenType -} +trait Ord[T] +given listOrd[T](using ord: Ord[T]): Ord[List[T]] + = ??? diff --git a/scala3doc-testcases/src/tests/givenSignaturesPg.scala b/scala3doc-testcases/src/tests/givenSignaturesPg.scala new file mode 100644 index 000000000000..0ad12ecf6715 --- /dev/null +++ b/scala3doc-testcases/src/tests/givenSignaturesPg.scala @@ -0,0 +1,44 @@ +package tests + +package givenSignaturesPg + + + +class GivenClass { + trait B + trait C[T] + val r: Int = 5 + type R = Int + given R = r + trait Ord[T] { + def compare(x: T, y: T): Int + extension (x: T) def < (y: T) = compare(x, y) < 0 + extension (x: T) def > (y: T) = compare(x, y) > 0 + } + given intOrd: Ord[Int] with { + def compare(x: Int, y: Int) = + if (x < y) -1 else if (x > y) +1 else 0 + } + + given asd(using int: Int): B with {} + + given asd2[T]: C[T] with {} + + given listOrd[T](using ord: Ord[T]): Ord[List[T]] with { + + def compare(xs: List[T], ys: List[T]): Int = (xs, ys) match + case (Nil, Nil) => 0 + case (Nil, _) => -1 + case (_, Nil) => +1 + case (x :: xs1, y :: ys1) => + val fst = ord.compare(x, y) + if (fst != 0) fst else compare(xs1, ys1) + } + + given IntOps: Int.type = Int + + given GivenType = GivenType() + + class GivenType +} + diff --git a/scala3doc-testcases/src/tests/methodsAndConstructors.scala b/scala3doc-testcases/src/tests/methodsAndConstructors.scala index f9d48b88e77a..d0f3386730bc 100644 --- a/scala3doc-testcases/src/tests/methodsAndConstructors.scala +++ b/scala3doc-testcases/src/tests/methodsAndConstructors.scala @@ -47,3 +47,10 @@ class Methods: def arrays(a: Array[String], b: Array[Int]): Array[Double] = ??? + + def rightA1(a: Int): Int + = ??? + + def ++:(a: Int)(b: Double): Int + = ??? + diff --git a/scala3doc-testcases/src/tests/modifiersSignatureTestSource.scala b/scala3doc-testcases/src/tests/modifiersSignatureTestSource.scala index ef6faecf9232..3cb250ae172b 100644 --- a/scala3doc-testcases/src/tests/modifiersSignatureTestSource.scala +++ b/scala3doc-testcases/src/tests/modifiersSignatureTestSource.scala @@ -24,6 +24,9 @@ abstract class Methods() implicit def toImplicitString(): String = "asd" + + inline def method2(inline name: String): String + = "ala" } class ImplementedMethods() extends Methods/*<-*/()/*->*/ diff --git a/scala3doc/src/dotty/dokka/DocContext.scala b/scala3doc/src/dotty/dokka/DocContext.scala index 2922d1aa3a7f..702a2c55f906 100644 --- a/scala3doc/src/dotty/dokka/DocContext.scala +++ b/scala3doc/src/dotty/dokka/DocContext.scala @@ -75,8 +75,8 @@ case class DocContext(args: Scala3doc.Args, compilerContext: CompilerContext) override def getOfflineMode: Boolean = false override def getFailOnWarning: Boolean = false override def getSourceSets: JList[DokkaSourceSet] = JList(mkSourceSet) - override def getModules: JList[DokkaConfiguration.DokkaModuleDescription] = JList() - override def getPluginsClasspath: JList[File] = JList() + override def getModules: JList[DokkaConfiguration.DokkaModuleDescription] = JNil + override def getPluginsClasspath: JList[File] = JNil override def getModuleName(): String = "ModuleName" override def getModuleVersion(): String = "" @@ -94,13 +94,13 @@ case class DocContext(args: Scala3doc.Args, compilerContext: CompilerContext) )(using compilerContext)) override def getPluginsConfiguration: JList[DokkaConfiguration.PluginConfiguration] = - JList() + JNil val mkSourceSet: DokkaSourceSet = new DokkaSourceSetImpl( /*displayName=*/ args.name, /*sourceSetID=*/ new DokkaSourceSetID(args.name, "main"), - /*classpath=*/ JList(), + /*classpath=*/ JNil, /*sourceRoots=*/ JSet(), /*dependentSourceSets=*/ JSet(), /*samples=*/ JSet(), diff --git a/scala3doc/src/dotty/dokka/DottyDokkaPlugin.scala b/scala3doc/src/dotty/dokka/DottyDokkaPlugin.scala index 4584c16e7962..4d12d6ed4b05 100644 --- a/scala3doc/src/dotty/dokka/DottyDokkaPlugin.scala +++ b/scala3doc/src/dotty/dokka/DottyDokkaPlugin.scala @@ -120,8 +120,9 @@ class DottyDokkaPlugin extends DokkaJavaPlugin: val implicitMembersExtensionTransformer = extend( _.extensionPoint(CoreExtensions.INSTANCE.getDocumentableTransformer) - .fromRecipe(ImplicitMembersExtensionTransformer(_)) - .name("implicitMembersExtensionTransformer") + .fromRecipe { case ctx @ given DokkaContext => + new ImplicitMembersExtensionTransformer + }.name("implicitMembersExtensionTransformer") ) val customDocumentationProvider = extend( diff --git a/scala3doc/src/dotty/dokka/compat.scala b/scala3doc/src/dotty/dokka/compat.scala index 944cffae9fb8..e0a0d7bd4ccb 100644 --- a/scala3doc/src/dotty/dokka/compat.scala +++ b/scala3doc/src/dotty/dokka/compat.scala @@ -28,6 +28,12 @@ type JMap[K, V] = java.util.Map[K, V] type JHashMap[K, V] = java.util.HashMap[K, V] type JMapEntry[K, V] = java.util.Map.Entry[K, V] +private val emptyListInst = JList() +def JNil[A] = emptyListInst.asInstanceOf[JList[A]] + +private val emptyMapInst = JMap() +def emptyJMap[A, B] = emptyMapInst.asInstanceOf[JMap[A, B]] + type DRI = org.jetbrains.dokka.links.DRI val topLevelDri = org.jetbrains.dokka.links.DRI.Companion.getTopLevel diff --git a/scala3doc/src/dotty/dokka/model/api/api.scala b/scala3doc/src/dotty/dokka/model/api/api.scala index 285a19fa4db4..f12c2fab7874 100644 --- a/scala3doc/src/dotty/dokka/model/api/api.scala +++ b/scala3doc/src/dotty/dokka/model/api/api.scala @@ -53,20 +53,26 @@ trait ImplicitConversionProvider { def conversion: Option[ImplicitConversion] } trait Classlike enum Kind(val name: String){ - case Class extends Kind("class") with Classlike + case Class(typeParams: Seq[TypeParameter], argsLists: Seq[Seq[Parameter]]) + extends Kind("class") with Classlike case Object extends Kind("object") with Classlike - case Trait extends Kind("trait") with Classlike + case Trait(typeParams: Seq[TypeParameter], argsLists: Seq[Seq[Parameter]]) + extends Kind("trait") with Classlike case Enum extends Kind("enum") with Classlike - case EnumCase extends Kind("case") - case Def extends Kind("def") - case Extension(on: ExtensionTarget) extends Kind("def") - case Constructor extends Kind("def") + case EnumCase(kind: Object.type | Type | Val.type) extends Kind("case") + case Def(typeParams: Seq[TypeParameter], argsLists: Seq[Seq[Parameter]]) + extends Kind("def") + case Extension(on: ExtensionTarget, m: Kind.Def) extends Kind("def") + case Constructor(base: Kind.Def) extends Kind("def") case Var extends Kind("var") case Val extends Kind("val") - case Exported extends Kind("export") - case Type(concreate: Boolean, opaque: Boolean) extends Kind("Type") // should we handle opaque as modifier? - case Given(as: Option[Signature], conversion: Option[ImplicitConversion]) extends Kind("Given") with ImplicitConversionProvider - case Implicit(kind: Kind, conversion: Option[ImplicitConversion]) extends Kind(kind.name) with ImplicitConversionProvider + case Exported(m: Kind.Def) extends Kind("export") + case Type(concreate: Boolean, opaque: Boolean, typeParams: Seq[TypeParameter]) + extends Kind("Type") // should we handle opaque as modifier? + case Given(kind: Def | Class, as: Option[Signature], conversion: Option[ImplicitConversion]) + extends Kind("Given") with ImplicitConversionProvider + case Implicit(kind: Kind.Def | Kind.Val.type, conversion: Option[ImplicitConversion]) + extends Kind(kind.name) with ImplicitConversionProvider case Unknown extends Kind("Unknown") } @@ -91,6 +97,23 @@ object Annotation: case class LinkParameter(name: Option[String] = None, dri: DRI, value: String) extends AnnotationParameter case class UnresolvedParameter(name: Option[String] = None, unresolvedText: String) extends AnnotationParameter +case class Parameter( + annotations: Seq[Annotation], + modifiers: String, + name: String, + dri: DRI, + signature: Signature, + isExtendedSymbol: Boolean = false, + isGrouped: Boolean = false +) + +case class TypeParameter( + variance: "" | "+" | "-", + name: String, + dri: DRI, + signature: Signature +) + // TODO (longterm) properly represent signatures case class Link(name: String, dri: DRI) type Signature = Seq[String | Link] @@ -114,7 +137,7 @@ object HierarchyGraph: def withEdges(edges: Seq[(LinkToType, LinkToType)]) = HierarchyGraph.empty ++ edges -type Member = Documentable // with WithExtraProperty[_] // Kotlin does not add generics to ExtraProperty implemented by e.g. DFunction +type Member = Documentable object Member: def unapply(d: Documentable): Option[(String, DRI, Visibility, Kind, Origin)] = @@ -146,10 +169,12 @@ extension[T] (member: Member) def parents: Seq[LinkToType] = compositeMemberExt.fold(Nil)(_.parents) def directParents: Seq[Signature] = compositeMemberExt.fold(Nil)(_.directParents) def knownChildren: Seq[LinkToType] = compositeMemberExt.fold(Nil)(_.knownChildren) + def companion: Option[DRI] = compositeMemberExt.fold(None)(_.companion) def membersBy(op: Member => Boolean): Seq[Member] = allMembers.filter(op) - def membersByWithInheritancePartition(op: Member => Boolean): (Seq[Member], Seq[Member]) = membersBy(op).partition(_.inheritedFrom.isEmpty) +extension (members: Seq[Member]) def byInheritance = + members.partition(_.inheritedFrom.isEmpty) extension (module: DModule) def driMap: Map[DRI, Member] = ModuleExtension.getFrom(module).fold(Map.empty)(_.driMap) diff --git a/scala3doc/src/dotty/dokka/model/api/internalExtensions.scala b/scala3doc/src/dotty/dokka/model/api/internalExtensions.scala index ca1e7f85417b..068b60424612 100644 --- a/scala3doc/src/dotty/dokka/model/api/internalExtensions.scala +++ b/scala3doc/src/dotty/dokka/model/api/internalExtensions.scala @@ -20,7 +20,7 @@ import collection.JavaConverters._ import org.jetbrains.dokka.model.doc.DocumentationNode import org.jetbrains.dokka.model.properties._ -private [model] case class MemberExtension( +case class MemberExtension( visibility: Visibility, modifiers: Seq[dotty.dokka.model.api.Modifier], kind: Kind, @@ -40,7 +40,8 @@ case class CompositeMemberExtension( members : Seq[Member] = Nil, directParents: Seq[Signature] = Nil, parents: Seq[LinkToType] = Nil, - knownChildren: Seq[LinkToType] = Nil + knownChildren: Seq[LinkToType] = Nil, + companion: Option[DRI] = None, ) extends ExtraProperty[Documentable]: override def getKey = CompositeMemberExtension diff --git a/scala3doc/src/dotty/dokka/model/extras.scala b/scala3doc/src/dotty/dokka/model/extras.scala index 5e49f617c929..1a25833253da 100644 --- a/scala3doc/src/dotty/dokka/model/extras.scala +++ b/scala3doc/src/dotty/dokka/model/extras.scala @@ -13,26 +13,3 @@ case class ModuleExtension(driMap: Map[DRI, Member]) extends ExtraProperty[DModu override def getKey = ModuleExtension object ModuleExtension extends BaseKey[DModule, ModuleExtension] - -case class MethodExtension(parametersListSizes: Seq[Int]) extends ExtraProperty[DFunction]: - override def getKey = MethodExtension - -object MethodExtension extends BaseKey[DFunction, MethodExtension] - -case class ParameterExtension(isExtendedSymbol: Boolean, isGrouped: Boolean) extends ExtraProperty[DParameter]: - override def getKey = ParameterExtension - -object ParameterExtension extends BaseKey[DParameter, ParameterExtension] - -case class ClasslikeExtension( - constructor: Option[DFunction], // will be replaced by signature - companion: Option[DRI], // moved to kind? -) extends ExtraProperty[DClasslike]: - override def getKey = ClasslikeExtension - -object ClasslikeExtension extends BaseKey[DClasslike, ClasslikeExtension] - -case class IsInherited(flag: Boolean) extends ExtraProperty[Documentable]: - override def getKey = IsInherited - -object IsInherited extends BaseKey[Documentable, IsInherited] diff --git a/scala3doc/src/dotty/dokka/tasty/BasicSupport.scala b/scala3doc/src/dotty/dokka/tasty/BasicSupport.scala index 91c8f8529765..9eb1f573ea96 100644 --- a/scala3doc/src/dotty/dokka/tasty/BasicSupport.scala +++ b/scala3doc/src/dotty/dokka/tasty/BasicSupport.scala @@ -48,4 +48,6 @@ trait BasicSupport: def getAnnotations(): List[Annotation] = sym.annotations.filterNot(_.symbol.packageName.startsWith("scala.annotation.internal")).map(parseAnnotation).reverse + def isLeftAssoc: Boolean = !sym.name.endsWith(":") + diff --git a/scala3doc/src/dotty/dokka/tasty/ClassLikeSupport.scala b/scala3doc/src/dotty/dokka/tasty/ClassLikeSupport.scala index 31ee86c2b4df..d2f9ac212efd 100644 --- a/scala3doc/src/dotty/dokka/tasty/ClassLikeSupport.scala +++ b/scala3doc/src/dotty/dokka/tasty/ClassLikeSupport.scala @@ -1,6 +1,6 @@ package dotty.dokka.tasty -import org.jetbrains.dokka.model.{TypeConstructor => DTypeConstructor, _} +import org.jetbrains.dokka.model.{TypeConstructor => DTypeConstructor, TypeParameter => _, _} import org.jetbrains.dokka.model.doc._ import org.jetbrains.dokka.DokkaConfiguration$DokkaSourceSet import collection.JavaConverters._ @@ -20,83 +20,95 @@ trait ClassLikeSupport: private val placeholderVisibility = JMap(ctx.sourceSet -> KotlinVisibility.Public.INSTANCE) private val placeholderModifier = JMap(ctx.sourceSet -> KotlinModifier.Empty.INSTANCE) - private def kindForClasslike(sym: Symbol): Kind = - if sym.flags.is(Flags.Module) then Kind.Object - else if sym.flags.is(Flags.Trait) then Kind.Trait - else if sym.flags.is(Flags.Enum) then Kind.Enum - else Kind.Class - - object DClass: - def apply[T >: DClass](classDef: ClassDef)( - dri: DRI = classDef.symbol.dri, - name: String = classDef.symbol.normalizedName, - signatureOnly: Boolean = false, - modifiers: Seq[Modifier] = classDef.symbol.getExtraModifiers(), - ): DClass = - - // This Try is here because of problem that code compiles, but at runtime fails claiming - // java.lang.ClassCastException: class dotty.tools.dotc.ast.Trees$DefDef cannot be cast to class dotty.tools.dotc.ast.Trees$TypeDef (dotty.tools.dotc.ast.Trees$DefDef and dotty.tools.dotc.ast.Trees$TypeDef are in unnamed module of loader 'app') - // It is probably bug in Tasty - def hackGetParents(classDef: ClassDef): Option[List[Tree]] = scala.util.Try(classDef.parents).toOption - - def getSupertypesGraph(classDef: ClassDef, link: LinkToType): Seq[(LinkToType, LinkToType)] = - val smbl = classDef.symbol - val parents = if smbl.exists then hackGetParents(smbl.tree.asInstanceOf[ClassDef]) else None - parents.fold(Seq())(_.flatMap { case tree => - val symbol = if tree.symbol.isClassConstructor then tree.symbol.owner else tree.symbol - val superLink = LinkToType(tree.dokkaType.asSignature, symbol.dri, kindForClasslike(symbol)) - Seq(link -> superLink) ++ getSupertypesGraph(tree.asInstanceOf[ClassDef], superLink) - } - ) - - val supertypes = getSupertypes(using qctx)(classDef).map { - case (symbol, tpe) => LinkToType(tpe.dokkaType.asSignature, symbol.dri, kindForClasslike(symbol)) - } - val selfSiangture: DSignature = typeForClass(classDef).dokkaType.asSignature - - val graph = HierarchyGraph.withEdges(getSupertypesGraph(classDef, LinkToType(selfSiangture, classDef.symbol.dri, kindForClasslike(classDef.symbol)))) - val baseExtra = PropertyContainer.Companion.empty() - .plus(ClasslikeExtension(classDef.getConstructorMethod(), classDef.getCompanion)) - .plus(MemberExtension( - classDef.symbol.getVisibility(), - modifiers, - kindForClasslike( classDef.symbol), - classDef.symbol.getAnnotations(), - selfSiangture, - classDef.symbol.source, - graph = graph - )) - - val fullExtra = - if (signatureOnly) baseExtra - else - baseExtra.plus(CompositeMemberExtension( - classDef.extractPatchedMembers, - classDef.getParents.map(_.dokkaType.asSignature), - supertypes, - Nil)) - end if + private def bareClasslikeKind(symbol: Symbol): Kind = + if symbol.flags.is(Flags.Module) then Kind.Object + else if symbol.flags.is(Flags.Trait) then Kind.Trait(Nil, Nil) + else if symbol.flags.is(Flags.Enum) then Kind.Enum + else Kind.Class(Nil, Nil) + + private def kindForClasslike(classDef: ClassDef): Kind = + def typeArgs = classDef.getTypeParams.map(mkTypeArgument) + + def parameterModifier(parameter: Symbol): String = + val fieldSymbol = classDef.symbol.field(parameter.normalizedName) + def isVal = fieldSymbol.flags.is(Flags.ParamAccessor) && + !classDef.symbol.flags.is(Flags.Case) && + !fieldSymbol.flags.is(Flags.Private) + + if fieldSymbol.flags.is(Flags.Mutable) then "var " + else if isVal then "val " + else "" + + def args = if constructorWithoutParamLists(classDef) then Nil else + val constr = + Some(classDef.constructor.symbol) + .filter(s => s.exists && !s.isHiddenByVisibility) + .map( _.tree.asInstanceOf[DefDef]) + constr.fold(Nil)( + _.paramss.map(_.map(mkParameter(_, parameterModifier))) + ) - new DClass( - dri, - name, - (if(signatureOnly) Nil else classDef.getConstructors.map(parseMethod(_))).asJava, - JList(), - JList(), - JList(), - JMap(), - placeholderVisibility, - null, - /*generics =*/classDef.getTypeParams.map(parseTypeArgument).asJava, - Map.empty.asJava, - classDef.symbol.documentation.asJava, - null, - placeholderModifier, - ctx.sourceSet.toSet, - /*isExpectActual =*/ false, - fullExtra.asInstanceOf[PropertyContainer[DClass]] + if classDef.symbol.flags.is(Flags.Module) then Kind.Object + else if classDef.symbol.flags.is(Flags.Trait) then + Kind.Trait(typeArgs, args) + else if classDef.symbol.flags.is(Flags.Enum) then Kind.Enum + else Kind.Class(typeArgs, args) + + def mkClass[T >: DClass](classDef: ClassDef)( + dri: DRI = classDef.symbol.dri, + name: String = classDef.symbol.normalizedName, + signatureOnly: Boolean = false, + modifiers: Seq[Modifier] = classDef.symbol.getExtraModifiers(), + ): Member = + + // This Try is here because of problem that code compiles, but at runtime fails claiming + // java.lang.ClassCastException: class dotty.tools.dotc.ast.Trees$DefDef cannot be cast to class dotty.tools.dotc.ast.Trees$TypeDef (dotty.tools.dotc.ast.Trees$DefDef and dotty.tools.dotc.ast.Trees$TypeDef are in unnamed module of loader 'app') + // It is probably bug in Tasty + def hackGetParents(classDef: ClassDef): Option[List[Tree]] = scala.util.Try(classDef.parents).toOption + + def getSupertypesGraph(classDef: ClassDef, link: LinkToType): Seq[(LinkToType, LinkToType)] = + val smbl = classDef.symbol + val parents = if smbl.exists then hackGetParents(smbl.tree.asInstanceOf[ClassDef]) else None + parents.fold(Seq())(_.flatMap { case tree => + val symbol = if tree.symbol.isClassConstructor then tree.symbol.owner else tree.symbol + val superLink = LinkToType(tree.dokkaType.asSignature, symbol.dri, bareClasslikeKind(symbol)) + Seq(link -> superLink) ++ getSupertypesGraph(tree.asInstanceOf[ClassDef], superLink) + } ) + val supertypes = getSupertypes(using qctx)(classDef).map { + case (symbol, tpe) => + LinkToType(tpe.dokkaType.asSignature, symbol.dri, bareClasslikeKind(symbol)) + } + val selfSiangture: DSignature = typeForClass(classDef).dokkaType.asSignature + + val graph = HierarchyGraph.withEdges(getSupertypesGraph(classDef, + LinkToType(selfSiangture, classDef.symbol.dri, bareClasslikeKind(classDef.symbol)))) + + val compositeExt = + if signatureOnly then CompositeMemberExtension.empty + else CompositeMemberExtension( + classDef.extractPatchedMembers, + classDef.getParents.map(_.dokkaType.asSignature), + supertypes, + Nil, + classDef.getCompanion + ) + + mkMember( + classDef.symbol, + MemberExtension( + classDef.symbol.getVisibility(), + modifiers, + kindForClasslike(classDef), + classDef.symbol.getAnnotations(), + selfSiangture, + classDef.symbol.source, + graph = graph + ), + compositeExt + ) + private val conversionSymbol = Symbol.requiredClass("scala.Conversion") def extractImplicitConversion(tpe: TypeRepr): Option[ImplicitConversion] = @@ -107,8 +119,11 @@ trait ClassLikeSupport: None else None + private def isDocumentableExtension(s: Symbol) = + !s.isHiddenByVisibility && !s.isSyntheticFunc && s.isExtensionMethod + private def parseMember(s: Tree): Option[Member] = processTreeOpt(s)(s match - case dd: DefDef if !dd.symbol.isHiddenByVisibility && !dd.symbol.isSyntheticFunc && dd.symbol.isExtensionMethod => + case dd: DefDef if isDocumentableExtension(dd.symbol) => dd.symbol.extendedSymbol.map { extSym => val target = ExtensionTarget( extSym.symbol.normalizedName, @@ -116,14 +131,16 @@ trait ClassLikeSupport: extSym.tpt.symbol.dri, extSym.symbol.pos.get.start ) - parseMethod(dd.symbol, kind = Kind.Extension(target)) + parseMethod(dd.symbol,specificKind = Kind.Extension(target, _)) } // TODO check given methods? case dd: DefDef if !dd.symbol.isHiddenByVisibility && dd.symbol.isGiven => Some(dd.symbol.owner.memberType(dd.name)) .filterNot(_.exists) .map { _ => - parseMethod(dd.symbol, kind = Kind.Given(getGivenInstance(dd).map(_.asSignature), None)) + parseMethod(dd.symbol, specificKind = + Kind.Given(_, getGivenInstance(dd).map(_.asSignature), None) + ) } case dd: DefDef if !dd.symbol.isHiddenByVisibility && dd.symbol.isExported => @@ -139,7 +156,9 @@ trait ClassLikeSupport: val dri = dd.rhs.collect { 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))) + + Some(parseMethod(dd.symbol, specificKind = Kind.Exported(_)) + .withOrigin(Origin.ExportedFrom(s"$instanceName.$functionName", dri))) case dd: DefDef if !dd.symbol.isHiddenByVisibility && !dd.symbol.isGiven && !dd.symbol.isSyntheticFunc && !dd.symbol.isExtensionMethod => Some(parseMethod(dd.symbol)) @@ -167,19 +186,24 @@ trait ClassLikeSupport: private def parseGivenClasslike(c: ClassDef): Member = { val parsedClasslike = parseClasslike(c) + val parentTpe = c.parents(0) match { case t: TypeTree => Some(t.tpe) case t: Term => Some(t.tpe) case _ => None } - val modifiedClasslikeExtension = ClasslikeExtension.getFrom(parsedClasslike).map(_.copy( - constructor = c.getConstructorMethod(Some(_ => "using ")) - ) - ).get - parsedClasslike.withNewExtras( - parsedClasslike.getExtra.plus(modifiedClasslikeExtension) - ).withKind( - Kind.Given(parsedClasslike.directParents.headOption, parentTpe.flatMap(extractImplicitConversion)) + + val givenParents = parsedClasslike.directParents.headOption + val cls: Kind.Class = parsedClasslike.kind match + case Kind.Object => Kind.Class(Nil, Nil) + case Kind.Trait(tps, args) => Kind.Class(tps, args) + case cls: Kind.Class => cls + case other => + report.warning("Unrecoginzed kind for given: $other", c.pos) + Kind.Class(Nil, Nil) + + parsedClasslike.withKind( + Kind.Given(cls, givenParents, parentTpe.flatMap(extractImplicitConversion)) ) } @@ -248,32 +272,28 @@ trait ClassLikeSupport: else if fieldSymbol.flags.is(Flags.ParamAccessor) && !c.symbol.flags.is(Flags.Case) && !fieldSymbol.flags.is(Flags.Private) then "val " else "" - def getTypeParams: List[TypeDef] = c.body.collect { case targ: TypeDef => targ }.filter(_.symbol.isTypeParam) + def getTypeParams: List[TypeDef] = + c.body.collect { case targ: TypeDef => targ }.filter(_.symbol.isTypeParam) def getCompanion: Option[DRI] = c.symbol.getCompanionSymbol .filter(!_.flags.is(Flags.Synthetic)) .filterNot(_.isHiddenByVisibility) .map(_.dri) - def getConstructorMethod(paramModifierFunc: Option[Symbol => String] = None): Option[DFunction] = - Some(c.constructor.symbol).filter(_.exists).filterNot(_.isHiddenByVisibility).map( d => - parseMethod(d, constructorWithoutParamLists(c), paramModifierFunc.getOrElse(s => c.getParameterModifier(s))) - ) - def parseClasslike(classDef: ClassDef, signatureOnly: Boolean = false): DClass = classDef match + def parseClasslike(classDef: ClassDef, signatureOnly: Boolean = false): Member = classDef match case c: ClassDef if classDef.symbol.flags.is(Flags.Module) => parseObject(c, signatureOnly) case c: ClassDef if classDef.symbol.flags.is(Flags.Enum) => parseEnum(c, signatureOnly) - case clazz => DClass(classDef)(signatureOnly = signatureOnly) + case clazz => mkClass(classDef)(signatureOnly = signatureOnly) - def parseObject(classDef: ClassDef, signatureOnly: Boolean = false): DClass = - DClass(classDef)( - // All objects are final so we do not need final modifer! + def parseObject(classDef: ClassDef, signatureOnly: Boolean = false): Member = + mkClass(classDef)( + // All objects are final so we do not need final modifier! modifiers = classDef.symbol.getExtraModifiers().filter(_ != Modifier.Final), signatureOnly = signatureOnly ) - // TODO check withNewExtras? - def parseEnum(classDef: ClassDef, signatureOnly: Boolean = false): DClass = + def parseEnum(classDef: ClassDef, signatureOnly: Boolean = false): Member = val extraModifiers = classDef.symbol.getExtraModifiers().filter(_ != Modifier.Sealed).filter(_ != Modifier.Abstract) val companion = classDef.symbol.getCompanionSymbol.map(_.tree.asInstanceOf[ClassDef]).get @@ -289,165 +309,159 @@ trait ClassLikeSupport: case c: ClassDef if c.symbol.flags.is(Flags.Case) && c.symbol.flags.is(Flags.Enum) => processTree(c)(parseClasslike(c)) }.flatten - val classlikie = DClass(classDef)(modifiers = extraModifiers, signatureOnly = signatureOnly) - classlikie.withNewMembers((enumVals ++ enumTypes ++ enumNested).map(_.withKind(Kind.EnumCase))).asInstanceOf[DClass] + val classlikie = mkClass(classDef)(modifiers = extraModifiers, signatureOnly = signatureOnly) + val cases = + enumNested.map(_.withKind(Kind.EnumCase(Kind.Object))) ++ + enumTypes.map(et => et.withKind(Kind.EnumCase(et.kind.asInstanceOf[Kind.Type]))) ++ + enumVals.map(_.withKind(Kind.EnumCase(Kind.Val))) + + classlikie.withNewMembers(cases).asInstanceOf[DClass] def parseMethod( methodSymbol: Symbol, emptyParamsList: Boolean = false, paramPrefix: Symbol => String = _ => "", - kind: Kind = Kind.Def - ): DFunction = + specificKind: (Kind.Def => Kind) = identity + ): Member = val method = methodSymbol.tree.asInstanceOf[DefDef] - val paramLists = if emptyParamsList then Nil else method.paramss + val paramLists = + if emptyParamsList then Nil + else if methodSymbol.isExtensionMethod then + val params = method.paramss + if methodSymbol.isLeftAssoc || params.size == 1 then params.tail + else params.head :: params.tail.drop(1) + else method.paramss val genericTypes = if (methodSymbol.isClassConstructor) Nil else method.typeParams + val basicKind: Kind.Def = Kind.Def( + genericTypes.map(mkTypeArgument), + paramLists.map(_.map(mkParameter(_, paramPrefix))) + ) + val methodKind = - if methodSymbol.isClassConstructor then Kind.Constructor + if methodSymbol.isClassConstructor then Kind.Constructor(basicKind) else if methodSymbol.flags.is(Flags.Implicit) then extractImplicitConversion(method.returnTpt.tpe) match case Some(conversion) if paramLists.size == 0 || (paramLists.size == 1 && paramLists.head.size == 0) => - Kind.Implicit(Kind.Def, Some(conversion)) + Kind.Implicit(basicKind, Some(conversion)) case None if paramLists.size == 1 && paramLists(0).size == 1 => - Kind.Implicit(Kind.Def, Some( + Kind.Implicit(basicKind, Some( ImplicitConversion( paramLists(0)(0).tpt.tpe.typeSymbol.dri, method.returnTpt.tpe.typeSymbol.dri ) )) case _ => - Kind.Implicit(Kind.Def, None) - else kind - - val name = method.symbol.normalizedName - - val memberExtension = MemberExtension( - methodSymbol.getVisibility(), - methodSymbol.getExtraModifiers(), - methodKind, - methodSymbol.getAnnotations(), - method.returnTpt.dokkaType.asSignature, - methodSymbol.source - ) - - val memberExtensionWithOrigin = - if methodSymbol.isOverriden then memberExtension.copy( - origin = Origin.Overrides(methodSymbol.allOverriddenSymbols.map(_.owner).map(s => Overriden(s.name, s.dri)).toSeq) - ) else memberExtension - - new DFunction( - methodSymbol.dri, - name, - /*isConstructor =*/ methodSymbol.isClassConstructor, - /*parameters =*/ paramLists.flatten.map(parseArgument(_, paramPrefix)).asJava, // TODO add support for parameters - /*documentation =*/ methodSymbol.documentation.asJava, - /*expectPresentInSet =*/ null, // unused - /*sources =*/ JMap(), - /*visibility =*/ placeholderVisibility, - /*type =*/ method.returnTpt.dokkaType, - /*generics =*/ genericTypes.map(parseTypeArgument).asJava, - /*receiver =*/ null, // Not used - /*modifier =*/ placeholderModifier, - ctx.sourceSet.toSet, - /*isExpectActual =*/ false, - PropertyContainer.Companion.empty() - plus MethodExtension(paramLists.map(_.size)) - plus(memberExtensionWithOrigin) + Kind.Implicit(basicKind, None) + else specificKind(basicKind) + + val origin = if !methodSymbol.isOverriden then Origin.RegularlyDefined else + val overridenSyms = methodSymbol.allOverriddenSymbols.map(_.owner) + Origin.Overrides(overridenSyms.map(s => Overriden(s.name, s.dri)).toSeq) + + mkMember( + method.symbol, + MemberExtension( + methodSymbol.getVisibility(), + methodSymbol.getExtraModifiers(), + methodKind, + methodSymbol.getAnnotations(), + method.returnTpt.dokkaType.asSignature, + methodSymbol.source, + origin + ) ) - 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.documentation.asJava, - null, - argument.tpt.dokkaType, - ctx.sourceSet.toSet, - PropertyContainer.Companion.empty() - .plus(ParameterExtension(isExtendedSymbol, isGrouped)) - .plus(MemberExtension.empty.copy(annotations = argument.symbol.getAnnotations())) - ) + def mkParameter(argument: ValDef, + prefix: Symbol => String = _ => "", + isExtendedSymbol: Boolean = false, + isGrouped: Boolean = false) = + val inlinePrefix = if argument.symbol.flags.is(Flags.Inline) then "inline " else "" + // TODO (https://github.com/lampepfl/dotty/issues/10525): Add using flag + + Parameter( + argument.symbol.getAnnotations(), + inlinePrefix + prefix(argument.symbol), + argument.symbol.normalizedName, + argument.symbol.dri, + argument.tpt.dokkaType.asSignature, + isExtendedSymbol, + isGrouped + ) - def parseTypeArgument(argument: TypeDef): DTypeParameter = - // Not sure if we should have such hacks... - val variancePrefix = + def mkTypeArgument(argument: TypeDef): TypeParameter = + val variancePrefix: "+" | "-" | "" = if argument.symbol.flags.is(Flags.Covariant) then "+" else if argument.symbol.flags.is(Flags.Contravariant) then "-" else "" - new DTypeParameter( - Invariance(TypeParameter(argument.symbol.dri, variancePrefix + argument.symbol.normalizedName, null)), - argument.symbol.documentation.asJava, - null, - JList(argument.rhs.dokkaType), - ctx.sourceSet.toSet, - PropertyContainer.Companion.empty() + TypeParameter( + variancePrefix, + argument.symbol.normalizedName, + argument.symbol.dri, + argument.rhs.dokkaType.asSignature ) - def parseTypeDef(typeDef: TypeDef): DProperty = - + def parseTypeDef(typeDef: TypeDef): Member = def isTreeAbstract(typ: Tree): Boolean = typ match { case TypeBoundsTree(_, _) => true case LambdaTypeTree(params, body) => isTreeAbstract(body) case _ => false } - val (generics, tpeTree) = typeDef.rhs match - case LambdaTypeTree(params, body) => (params.map(parseTypeArgument), body) + case LambdaTypeTree(params, body) => (params.map(mkTypeArgument), body) case tpe => (Nil, tpe) - new DProperty( - typeDef.symbol.dri, - typeDef.symbol.normalizedName, - /*documentation =*/ typeDef.symbol.documentation.asJava, - /*expectPresentInSet =*/ null, // unused - /*sources =*/ JMap(), - /*visibility =*/ placeholderVisibility, - /*type =*/ tpeTree.dokkaType, // TODO this may be hard... - /*receiver =*/ null, // Not used - /*setter =*/ null, - /*getter =*/ null, - /*modifier =*/ placeholderModifier, - ctx.sourceSet.toSet, - /*generics =*/ generics.asJava, // TODO - /*isExpectActual =*/ false, - PropertyContainer.Companion.empty() plus MemberExtension( + mkMember( + typeDef.symbol, + MemberExtension( typeDef.symbol.getVisibility(), typeDef.symbol.getExtraModifiers(), - Kind.Type(!isTreeAbstract(typeDef.rhs), typeDef.symbol.isOpaque), + Kind.Type(!isTreeAbstract(typeDef.rhs), typeDef.symbol.isOpaque, generics), typeDef.symbol.getAnnotations(), tpeTree.dokkaType.asSignature, typeDef.symbol.source ) ) - def parseValDef(valDef: ValDef): DProperty = + def parseValDef(valDef: ValDef): Member = def defaultKind = if valDef.symbol.flags.is(Flags.Mutable) then Kind.Var else Kind.Val val kind = if valDef.symbol.flags.is(Flags.Implicit) then Kind.Implicit(Kind.Val, extractImplicitConversion(valDef.tpt.tpe)) else defaultKind - new DProperty( - valDef.symbol.dri, - valDef.symbol.normalizedName, - /*documentation =*/ valDef.symbol.documentation.asJava, - /*expectPresentInSet =*/ null, // unused - /*sources =*/ JMap(), - /*visibility =*/ placeholderVisibility, - /*type =*/ valDef.tpt.dokkaType, - /*receiver =*/ null, // Not used - /*setter =*/ null, - /*getter =*/ null, - /*modifier =*/ placeholderModifier, - ctx.sourceSet.toSet, - /*generics =*/ JList(), - /*isExpectActual =*/ false, - PropertyContainer.Companion.empty().plus(MemberExtension( + mkMember( + valDef.symbol, + MemberExtension( valDef.symbol.getVisibility(), valDef.symbol.getExtraModifiers(), kind, valDef.symbol.getAnnotations(), valDef.tpt.tpe.dokkaType.asSignature, valDef.symbol.source - )) + ) ) + + def mkMember[T <: Kind]( + symbol: Symbol, + member: MemberExtension, + compositeExt: CompositeMemberExtension = CompositeMemberExtension.empty): Member = + new DClass( + symbol.dri, + symbol.normalizedName, + JNil, + JNil, + JNil, + JNil, + emptyJMap, + placeholderVisibility, + null, + JNil, + emptyJMap, + symbol.documentation.asJava, + null, + placeholderModifier, + ctx.sourceSet.toSet, + /*isExpectActual =*/ false, + PropertyContainer.Companion.empty().plus(member).plus(compositeExt) + ) \ No newline at end of file diff --git a/scala3doc/src/dotty/dokka/tasty/SymOps.scala b/scala3doc/src/dotty/dokka/tasty/SymOps.scala index f3ea472d5962..7832cbb1e5bd 100644 --- a/scala3doc/src/dotty/dokka/tasty/SymOps.scala +++ b/scala3doc/src/dotty/dokka/tasty/SymOps.scala @@ -91,10 +91,11 @@ class SymOps[Q <: Quotes](val q: Q): def isLeftAssoc(d: Symbol): Boolean = !d.name.endsWith(":") def extendedSymbol: Option[ValDef] = - Option.when(sym.isExtensionMethod)( - if(isLeftAssoc(sym)) sym.tree.asInstanceOf[DefDef].paramss(0)(0) - else sym.tree.asInstanceOf[DefDef].paramss(1)(0) - ) + Option.when(sym.isExtensionMethod){ + val params = sym.tree.asInstanceOf[DefDef].paramss + if isLeftAssoc(sym) || params.size == 1 then params(0)(0) + else params(1)(0) + } // TODO #22 make sure that DRIs are unique plus probably reuse semantic db code? def dri: DRI = diff --git a/scala3doc/src/dotty/dokka/transformers/ImplicitMembersExtensionTransformer.scala b/scala3doc/src/dotty/dokka/transformers/ImplicitMembersExtensionTransformer.scala index 037f235cb9dd..f782a28e3b33 100644 --- a/scala3doc/src/dotty/dokka/transformers/ImplicitMembersExtensionTransformer.scala +++ b/scala3doc/src/dotty/dokka/transformers/ImplicitMembersExtensionTransformer.scala @@ -10,20 +10,16 @@ import org.jetbrains.dokka.model.properties._ import dotty.dokka.model._ import dotty.dokka.model.api._ -class ImplicitMembersExtensionTransformer(ctx: DokkaContext) extends DocumentableTransformer: +class ImplicitMembersExtensionTransformer(using DocContext) extends DocumentableTransformer: override def invoke(original: DModule, context: DokkaContext): DModule = val classlikeMap = original.driMap val logger = context.getLogger - def retrieveCompanion(m: Member) = m match { - case classlike: DClass => - val comp = ClasslikeExtension.getFrom(classlike).flatMap(_.companion) - comp.flatMap { dri => - val res = classlikeMap.get(dri) - if res.isEmpty then logger.warn(s"Companion for class ${classlike.name} exists but is missing in classlike map") - res - } - case _ => None + def retrieveCompanion(m: Member) = m.companion.flatMap { dri => + val res = classlikeMap.get(dri) + if res.isEmpty then + report.warning(s"Companion for class ${m.name} exists but is missing in classlike map") + res } def expandMember(outerMembers: Seq[Member])(c: Member): Member = @@ -40,8 +36,12 @@ class ImplicitMembersExtensionTransformer(ctx: DokkaContext) extends Documentabl val MyDri = c.getDri def collectApplicableMembers(source: Member): Seq[Member] = source.allMembers.flatMap { - case m @ Member(_, _, _, Kind.Extension(ExtensionTarget(_, _, MyDri, _)), Origin.RegularlyDefined) => - Seq(m.withOrigin(Origin.ExtensionFrom(source.name, source.dri)).withKind(Kind.Def)) + case m @ Member(_, _, _, Kind.Extension(ExtensionTarget(_, _, MyDri, _), _), Origin.RegularlyDefined) => + val kind = m.kind match + case d: Kind.Def => d + case _ => Kind.Def(Nil, Nil) + + Seq(m.withOrigin(Origin.ExtensionFrom(source.name, source.dri)).withKind(kind)) case m @ Member(_, _, _, conversionProvider: ImplicitConversionProvider, Origin.RegularlyDefined) => conversionProvider.conversion match case Some(ImplicitConversion(MyDri, to)) => diff --git a/scala3doc/src/dotty/dokka/translators/ScalaContentBuilder.scala b/scala3doc/src/dotty/dokka/translators/ScalaContentBuilder.scala index 5411b569d59c..5f6279220c09 100644 --- a/scala3doc/src/dotty/dokka/translators/ScalaContentBuilder.scala +++ b/scala3doc/src/dotty/dokka/translators/ScalaContentBuilder.scala @@ -346,7 +346,7 @@ class ScalaPageContentBuilder( } else this def list[T]( - elements: List[T], + elements: Seq[T], prefix: String = "", suffix: String = "", separator: String = ", ", @@ -487,7 +487,7 @@ class ScalaPageContentBuilder( val originInfo = documentable.origin match { case Origin.ImplicitlyAddedBy(name, dri) => Signature("Implicitly added by ", SLink(name, dri)) case Origin.ExtensionFrom(name, dri) => Signature("Extension method from ", SLink(name, dri)) - case Origin.ExportedFrom(name, dri) => + case Origin.ExportedFrom(name, dri) => val signatureName: String | dotty.dokka.model.api.Link = dri match case Some(dri: DRI) => SLink(name, dri) case None => name diff --git a/scala3doc/src/dotty/dokka/translators/ScalaPageCreator.scala b/scala3doc/src/dotty/dokka/translators/ScalaPageCreator.scala index 94feda84e51d..4e9605e79749 100644 --- a/scala3doc/src/dotty/dokka/translators/ScalaPageCreator.scala +++ b/scala3doc/src/dotty/dokka/translators/ScalaPageCreator.scala @@ -34,47 +34,37 @@ class ScalaPageCreator( override def pageForModule(m: DModule): ModulePageNode = super.pageForModule(m) - private def updatePageNameForMember(page: PageNode, p: Member) = - val name = page.getName - - page.modified(name, page.getChildren) - - private def pagesForMembers(p: Member): Seq[PageNode] = - p.allMembers.filter(m => m.origin == Origin.RegularlyDefined && m.inheritedFrom.isEmpty).collect { - case f: DFunction => updatePageNameForMember(pageForFunction(f), f) - case c: DClass => updatePageNameForMember(pageForDClass(c), c) - } + private def pagesForMembers(member: Member): JList[PageNode] = + val (all, _) = member.membersBy(_.kind.isInstanceOf[Classlike]) + all.map(pageForMember(_)).asJava override def pageForPackage(p: DPackage): PackagePageNode = - val originalPage = super.pageForPackage(p) - val originalPages: Seq[PageNode] = originalPage.getChildren.asScala.toList - val allPage: Seq[PageNode] = originalPages ++ pagesForMembers(p) - originalPage.modified(p.getName, allPage.asJava) - - override def pageForClasslike(c: DClasslike): ClasslikePageNode = c match { - case clazz: DClass => pageForDClass(clazz) - case other => throw UnsupportedOperationException("Only DClass classlike is supported.") - } - - def pageForDClass(c: DClass): ClasslikePageNode = { - val constructors = c.getConstructors + PackagePageNode( + p.name, + contentForPackage(p), + JSet(p.dri), + p, + pagesForMembers(p), + JNil + ) - val ext = c.get(ClasslikeExtension) + override def pageForClasslike(c: DClasslike): ClasslikePageNode = ??? - val name = if c.kind == Kind.Object && ext.companion.isDefined then c.getName + "$" else c.getName + def pageForMember(c: Member): ClasslikePageNode = { + val name = + if c.kind == Kind.Object && c.companion.isDefined then + c.getName + "$" + else c.getName + // Hack, need our own page! ClasslikePageNode( name, - contentForClasslike(c), + contentForClass(c.asInstanceOf[DClass]), JSet(c.getDri), - c, - (constructors.asScala.map(pageForFunction) ++ - c.getClasslikes.asScala.map(pageForClasslike) ++ - c.getFunctions.asScala.map(pageForFunction) ++ - pagesForMembers(c)).asJava, - List.empty.asJava - ) - + c.asInstanceOf[DClass], + JNil, + JNil, + ).modified(name, pagesForMembers(c)) // We need override default page } override def pageForFunction(f: DFunction) = super.pageForFunction(f) @@ -114,10 +104,8 @@ class ScalaPageCreator( contentBuilder.contentForDocumentable(p, buildBlock = buildBlock) } - override def contentForClasslike(c: DClasslike) = c match { - case d: DClass => contentForClass(d) - case other => throw UnsupportedOperationException("Only DClass classlike is supported.") - } + override def contentForClasslike(c: DClasslike) = throw UnsupportedOperationException( + s"Unable to generate DClasslike using default dokka method for $c!") def contentForClass(c: DClass) = { def buildBlock = (builder: DocBuilder) => builder @@ -138,26 +126,6 @@ class ScalaPageCreator( contentBuilder.contentForDocumentable(c, buildBlock = buildBlock) } - override def contentForMember(d: Documentable) = { - def buildBlock = (builder: DocBuilder) => builder - .group(kind = ContentKind.Cover){ bd => bd.cover(d.getName)() } - .divergentGroup( - ContentDivergentGroup.GroupID("member") - ) { divbdr => divbdr - .instance(Set(d.getDri), sourceSets = d.getSourceSets.asScala.toSet) { insbdr => insbdr - .before(){ bbdr => bbdr - .contentForDescription(d) - .contentForComments(d) - } - .divergent(kind = ContentKind.Symbol) { dbdr => dbdr - .signature(d) - } - } - } - contentBuilder.contentForDocumentable(d, buildBlock = buildBlock) - } - - override def contentForFunction(f: DFunction) = contentForMember(f) extension (b: DocBuilder) def descriptionIfNotEmpty(d: Documentable): DocBuilder = { @@ -294,11 +262,7 @@ class ScalaPageCreator( }) } - val withCompanion = d match { - case d: DClass => - val ext = d.get(ClasslikeExtension) - val co = ext.companion - co.fold(withNamedTags) { co => withNamedTags + val withCompanion = d.companion.fold(withNamedTags){ co => withNamedTags .cell(sourceSets = d.getSourceSets.asScala.toSet){ b => b .text("Companion") } @@ -312,11 +276,9 @@ class ScalaPageCreator( ) } } - case _ => withNamedTags - } val withExtensionInformation = d.kind match { - case Kind.Extension(on) => + case Kind.Extension(on, _) => val sourceSets = d.getSourceSets.asScala.toSet withCompanion.cell(sourceSets = sourceSets)(_.text("Extension")) .cell(sourceSets = sourceSets)(_.text(s"This function is an extension on (${on.name}: ").inlineSignature(d, on.signature).text(")")) @@ -335,8 +297,8 @@ class ScalaPageCreator( d.deprecated match case None => withSource - case Some(a) => - extension (b: ScalaPageContentBuilder#ScalaDocumentableContentBuilder) + case Some(a) => + extension (b: ScalaPageContentBuilder#ScalaDocumentableContentBuilder) def annotationParameter(p: Option[Annotation.AnnotationParameter]): ScalaPageContentBuilder#ScalaDocumentableContentBuilder = p match case Some(Annotation.PrimitiveParameter(_, value)) => b.text(value.stripPrefix("\"").stripSuffix("\"")) @@ -358,21 +320,20 @@ class ScalaPageCreator( def contentForScope(s: Documentable & WithScope & WithExtraProperties[_]) = def groupExtensions(extensions: Seq[Member]): Seq[DocumentableSubGroup] = extensions.groupBy(_.kind).map { - case (Kind.Extension(on), members) => + case (Kind.Extension(on, _), members) => val signature = Signature(s"extension (${on.name}: ") join on.signature join Signature(")") DocumentableSubGroup(signature, members.toSeq) case other => sys.error(s"unexpected value: $other") }.toSeq - - val (definedMethods, inheritedMethods) = s.membersByWithInheritancePartition(_.kind == Kind.Def) - val (definedFields, inheritedFiles) = s.membersByWithInheritancePartition(m => m.kind == Kind.Val || m.kind == Kind.Var) - val (definedClasslikes, inheritedClasslikes) = s.membersByWithInheritancePartition(m => m.kind.isInstanceOf[Classlike]) - val (definedTypes, inheritedTypes) = s.membersByWithInheritancePartition(_.kind.isInstanceOf[Kind.Type]) - val (definedGivens, inheritedGives) = s.membersByWithInheritancePartition(_.kind.isInstanceOf[Kind.Given]) - val (definedExtensions, inheritedExtensions) = s.membersByWithInheritancePartition(_.kind.isInstanceOf[Kind.Extension]) - val (definedExports, inheritedExports) = s.membersByWithInheritancePartition(_.kind == Kind.Exported) - val (definedImplicits, inheritedImplicits) = s.membersByWithInheritancePartition(_.kind.isInstanceOf[Kind.Implicit]) + val (definedMethods, inheritedMethods) = s.membersBy(_.kind.isInstanceOf[Kind.Def]).byInheritance + val (definedFields, inheritedFiles) = s.membersBy(m => m.kind == Kind.Val || m.kind == Kind.Var).byInheritance + val (definedClasslikes, inheritedClasslikes) = s.membersBy(m => m.kind.isInstanceOf[Classlike]).byInheritance + val (definedTypes, inheritedTypes) = s.membersBy(_.kind.isInstanceOf[Kind.Type]).byInheritance + val (definedGivens, inheritedGives) = s.membersBy(_.kind.isInstanceOf[Kind.Given]).byInheritance + val (definedExtensions, inheritedExtensions) = s.membersBy(_.kind.isInstanceOf[Kind.Extension]).byInheritance + val (definedExports, inheritedExports) = s.membersBy(_.kind.isInstanceOf[Kind.Exported]).byInheritance + val (definedImplicits, inheritedImplicits) = s.membersBy(_.kind.isInstanceOf[Kind.Implicit]).byInheritance b .contentForComments(s) @@ -416,7 +377,7 @@ class ScalaPageCreator( def contentForConstructors(c: DClass) = b.documentableTab("Constructors")( - DocumentableGroup(None, c.getConstructors.asScala.toList) + DocumentableGroup(None, c.membersBy(_.kind.isInstanceOf[Kind.Constructor])._1) ) diff --git a/scala3doc/src/dotty/dokka/translators/ScalaSignatureProvider.scala b/scala3doc/src/dotty/dokka/translators/ScalaSignatureProvider.scala index 242e35f4d22a..b25928dfe114 100644 --- a/scala3doc/src/dotty/dokka/translators/ScalaSignatureProvider.scala +++ b/scala3doc/src/dotty/dokka/translators/ScalaSignatureProvider.scala @@ -50,178 +50,159 @@ class ScalaSignatureProvider(contentConverter: CommentsToContentConverter)(using object ScalaSignatureProvider: def rawSignature(documentable: Documentable, builder: SignatureBuilder): SignatureBuilder = - documentable match - case extension: DFunction if extension.kind.isInstanceOf[Kind.Extension] => - extensionSignature(extension, builder) - case method: DFunction if method.kind.isInstanceOf[Kind.Given] => - givenMethodSignature(method, builder) - case exprt: DFunction if exprt.kind == Kind.Exported => - exportedMethodSignature(exprt, builder) - case method: DFunction => - methodSignature(method, builder) - case enumEntry: DClass if enumEntry.kind == Kind.EnumCase => - enumEntrySignature(enumEntry, builder) - case givenClazz: DClass if givenClazz.kind.isInstanceOf[Kind.Given] => - givenClassSignature(givenClazz, builder) - case clazz: DClass => - classSignature(clazz, builder) - case enumProperty: DProperty if enumProperty.kind == Kind.EnumCase => - enumPropertySignature(enumProperty, builder) - case property: DProperty => - propertySignature(property, builder) - case parameter: DParameter => - parameterSignature(parameter, builder) - case _ => + documentable.kind match + case Kind.Extension(_, m) => + extensionSignature(documentable, m, builder) + case Kind.Exported(d) => + methodSignature(documentable, d, builder) + case d: Kind.Def => + methodSignature(documentable, d, builder) + case Kind.Constructor(d) => + methodSignature(documentable, d, builder) + case Kind.Implicit(d: Kind.Def, _) => + methodSignature(documentable, d, builder) + case Kind.EnumCase(cls: Kind.Class) => + enumEntrySignature(documentable, cls, builder) + case Kind.EnumCase(_) => + enumPropertySignature(documentable, builder) + case Kind.Given(cls: Kind.Class, _, _) => + givenClassSignature(documentable, cls, builder) + case Kind.Given(d: Kind.Def, _, _) => + givenMethodSignature(documentable, d, builder) + case Kind.Given(Kind.Val, _, _) => + givenPropertySignature(documentable, builder) + case cls: Kind.Class => + classSignature(documentable, cls, builder) + case Kind.Object | Kind.Enum => + objectSignature(documentable, builder) + case trt: Kind.Trait => + traitSignature(documentable, trt, builder) + case Kind.Val | Kind.Var | Kind.Implicit(Kind.Val, _) => + fieldSignature(documentable, documentable.kind.name, builder) + case tpe: Kind.Type => + typeSignature(tpe, documentable, builder) + case Kind.Unknown => ??? - - private def enumEntrySignature(entry: DClass, bdr: SignatureBuilder): SignatureBuilder = - val ext = entry.get(ClasslikeExtension) + private def enumEntrySignature(member: Member, cls: Kind.Class, bdr: SignatureBuilder): SignatureBuilder = val withPrefixes: SignatureBuilder = bdr .text("case ") - .name(entry.getName, entry.getDri) - .generics(entry) + .memberName(member.name, member.dri) + .generics(cls.typeParams) + + val withParameters = withPrefixes.functionParameters(cls.argsLists) + parentsSignature(member, withParameters) - val withParameters = ext.constructor.toSeq.foldLeft(withPrefixes){ (bdr, elem) => - bdr.functionParameters(elem) + private def enumPropertySignature(entry: Member, builder: SignatureBuilder): SignatureBuilder = + val modifiedType = entry.signature.map { + case " & " => " with " + case o => o } - parentsSignature(entry, withParameters) - - private def enumPropertySignature(entry: DProperty, builder: SignatureBuilder): SignatureBuilder = - val modifiedType = entry.getType match - case t: TypeConstructor => GenericTypeConstructor( - t.getDri, - t.getProjections.asScala.map{ - case t: UnresolvedBound if t.getName == " & " => UnresolvedBound(" with "); - case other => other - }.asJava, - null - ) - case other => other builder .text("case ") - .name(entry.getName, entry.getDri) + .name(entry.name, entry.dri) .text(" extends ") - .typeSignature(modifiedType) + .signature(modifiedType) - private def parentsSignature(d: DClass, builder: SignatureBuilder): SignatureBuilder = - d.directParents match + private def parentsSignature(member: Member, builder: SignatureBuilder): SignatureBuilder = + member.directParents match case Nil => builder case extendType :: withTypes => val extendPart = builder.text(" extends ").signature(extendType) withTypes.foldLeft(extendPart)((bdr, tpe) => bdr.text(" with ").signature(tpe)) - private def givenClassSignature(clazz: DClass, builder: SignatureBuilder): SignatureBuilder = - val ext = clazz.get(ClasslikeExtension) + private def givenClassSignature(member: Member, cls: Kind.Class, builder: SignatureBuilder): SignatureBuilder = val prefixes = builder - .modifiersAndVisibility(clazz, "given") + .modifiersAndVisibility(member, "given") + .name(member.name, member.dri) + .generics(cls.typeParams) + .functionParameters(cls.argsLists) + + member.kind match + case Kind.Given(_, Some(instance), _) => prefixes + .text(": ") + .signature(instance) + case _ => prefixes + + private def classSignature(clazz: Member, cls: Kind.Class, builder: SignatureBuilder): SignatureBuilder = + val selfSignature = builder + .modifiersAndVisibility(clazz, clazz.kind.name) .name(clazz.getName, clazz.getDri) - .generics(clazz) + .generics(cls.typeParams) + .functionParameters(cls.argsLists) - val withGenerics = ext.constructor.toSeq.foldLeft(prefixes){ (bdr, elem) => - bdr.functionParameters(elem) - } - clazz.kind match - case Kind.Given(Some(instance), _) => withGenerics - .text(" as ") - .signature(instance) - case _ => withGenerics + parentsSignature(clazz, selfSignature) - private def classSignature(clazz: DClass, builder: SignatureBuilder): SignatureBuilder = - val ext = clazz.get(ClasslikeExtension) - val prefixes = builder + private def objectSignature(clazz: Member, builder: SignatureBuilder): SignatureBuilder = + val selfSignature = builder .modifiersAndVisibility(clazz, clazz.kind.name) .name(clazz.getName, clazz.getDri) - .generics(clazz) - val withGenerics = ext.constructor.toSeq.foldLeft(prefixes){ (bdr, elem) => - bdr.functionParameters(elem) - } - parentsSignature(clazz, withGenerics) + parentsSignature(clazz, selfSignature) - private def extensionSignature(extension: DFunction, builder: SignatureBuilder): SignatureBuilder = - val extendedSymbol = if (extension.isRightAssociative()) { - extension.getParameters.asScala(extension.get(MethodExtension).parametersListSizes(0)) - } else { - extension.getParameters.asScala(0) - } + private def traitSignature(clazz: Member, cls: Kind.Trait, builder: SignatureBuilder): SignatureBuilder = + val selfSignature = builder + .modifiersAndVisibility(clazz, clazz.kind.name) + .name(clazz.getName, clazz.getDri) + .generics(cls.typeParams) + .functionParameters(cls.argsLists) + + parentsSignature(clazz, selfSignature) + + private def extensionSignature(extension: Member, fun: Kind.Def, builder: SignatureBuilder): SignatureBuilder = val withSignature = builder .modifiersAndVisibility(extension, "def") .name(extension.getName, extension.getDri) - .generics(extension) - .functionParameters(extension) + .generics(fun.typeParams) + .functionParameters(fun.argsLists) - if extension.isConstructor then withSignature - else withSignature.text(":").text(" ").typeSignature(extension.getType) + withSignature.text(":").text(" ").signature(extension.signature) - private def givenMethodSignature(method: DFunction, builder: SignatureBuilder): SignatureBuilder = method.kind match - case Kind.Given(Some(instance), _) => + private def givenMethodSignature(method: Member, body: Kind.Def, builder: SignatureBuilder): SignatureBuilder = method.kind match + case Kind.Given(_, Some(instance), _) => builder.text("given ") - .name(method.getName, method.getDri) - .text(" as ") + .name(method.name, method.dri) + .text(": ") .signature(instance) case _ => - builder.text("given ").name(method.getName, method.getDri) + builder.text("given ").name(method.name, method.dri) - private def exportedMethodSignature(method: DFunction, builder: SignatureBuilder): SignatureBuilder = - methodSignature(method, builder) - - private def methodSignature(method: DFunction, builder: SignatureBuilder): SignatureBuilder = + private def methodSignature(method: Member, cls: Kind.Def, builder: SignatureBuilder): SignatureBuilder = val bdr = builder .modifiersAndVisibility(method, "def") .name(method.getName, method.getDri) - .generics(method) - .functionParameters(method) - if !method.isConstructor then - bdr - .text(":") - .text(" ") - .typeSignature(method.getType) + .generics(cls.typeParams) + .functionParameters(cls.argsLists) + if !method.kind.isInstanceOf[Kind.Constructor] then + bdr.text(": ").signature(method.signature) else bdr - - private def propertySignature(property: DProperty, builder: SignatureBuilder): SignatureBuilder = - property.kind match - case _: Kind.Given => givenPropertySignature(property, builder) - case tpe: Kind.Type => typeSignature(tpe, property, builder) - case other => fieldSignature(property, other.name, builder) - - - private def typeSignature(tpe: Kind.Type, typeDef: DProperty, builder: SignatureBuilder): SignatureBuilder = + private def typeSignature(tpe: Kind.Type, typeDef: Member, builder: SignatureBuilder): SignatureBuilder = val bdr = builder .modifiersAndVisibility(typeDef, if tpe.opaque then "opaque type" else "type") .name(typeDef.getName, typeDef.getDri) - .generics(typeDef) + .generics(tpe.typeParams) if(!tpe.opaque){ (if tpe.concreate then bdr.text(" = ") else bdr) - .typeSignature(typeDef.getType) + .signature(typeDef.signature) } else bdr - private def givenPropertySignature(property: DProperty, builder: SignatureBuilder): SignatureBuilder = + private def givenPropertySignature(property: Member, builder: SignatureBuilder): SignatureBuilder = val bdr = builder .text("given ") - .name(property.getName, property.getDri) + .name(property.name, property.dri) property.kind match - case Kind.Given(Some(instance), _) => + case Kind.Given(_, Some(instance), _) => bdr.text(" as ").signature(instance) case _ => bdr - private def fieldSignature(property: DProperty, kind: String, builder: SignatureBuilder): SignatureBuilder = + private def fieldSignature(member: Member, kind: String, builder: SignatureBuilder): SignatureBuilder = builder - .modifiersAndVisibility(property, kind) - .name(property.getName, property.getDri) + .modifiersAndVisibility(member, kind) + .name(member.name, member.dri) .text(":") .text(" ") - .typeSignature(property.getType) - - private def parameterSignature(parameter: DParameter, builder: SignatureBuilder): SignatureBuilder = - val ext = parameter.get(ParameterExtension) - builder - .text(if ext.isGrouped then "extension (" else "(") - .text(parameter.getName) - .text(": ") - .typeSignature(parameter.getType) - .text(")") + .signature(member.signature) diff --git a/scala3doc/src/dotty/dokka/translators/ScalaSignatureUtils.scala b/scala3doc/src/dotty/dokka/translators/ScalaSignatureUtils.scala index 30384ee39d1f..22fc9d5c953e 100644 --- a/scala3doc/src/dotty/dokka/translators/ScalaSignatureUtils.scala +++ b/scala3doc/src/dotty/dokka/translators/ScalaSignatureUtils.scala @@ -2,7 +2,7 @@ package dotty.dokka import org.jetbrains.dokka.base.signatures._ import org.jetbrains.dokka.base.translators.documentables.PageContentBuilder -import org.jetbrains.dokka.model._ +import org.jetbrains.dokka.model.{ TypeParameter => _, _ } import org.jetbrains.dokka.model.properties.WithExtraProperties import org.jetbrains.dokka.pages._ import collection.JavaConverters._ @@ -12,6 +12,7 @@ case class InlineSignatureBuilder(names: Signature = Nil, preName: Signature = N override def text(str: String): SignatureBuilder = copy(names = str +: names) override def name(str: String, dri: DRI): SignatureBuilder = copy(names = Nil, preName = names) override def driLink(text: String, dri: DRI): SignatureBuilder = copy(names = Link(text, dri) +: names) + override def signature(s: Signature): SignatureBuilder = copy(names = s.reverse ++ names) object InlineSignatureBuilder: def typeSignatureFor(d: Documentable): Signature = @@ -21,14 +22,16 @@ trait SignatureBuilder extends ScalaSignatureUtils { def text(str: String): SignatureBuilder def name(str: String, dri: DRI) = driLink(str, dri) def driLink(text: String, dri: DRI): SignatureBuilder - - def signature(s: Signature) = s.foldLeft(this){ (b, e) => e match - case Link(name, dri) => b.driLink(name, dri) - case txt: String => b.text(txt) + def signature(s: Signature): SignatureBuilder = s.foldLeft(this){ + case (bld, s: String) => bld.text(s) + case (bld, Link(text: String, dri: DRI)) => bld.driLink(text, dri) } + // Support properly once we rewrite signature builder + def memberName(name: String, dri: DRI) = text(name) + def list[E]( - elements: List[E], + elements: Seq[E], prefix: String = "", suffix: String = "", separator: String = ", ", @@ -43,7 +46,7 @@ trait SignatureBuilder extends ScalaSignatureUtils { def annotationsBlock(d: Member): SignatureBuilder = d.annotations.foldLeft(this){ (bdr, annotation) => bdr.buildAnnotation(annotation)} - def annotationsInline(d: Documentable with WithExtraProperties[_]): SignatureBuilder = + def annotationsInline(d: Parameter): SignatureBuilder = d.annotations.foldLeft(this){ (bdr, annotation) => bdr.buildAnnotation(annotation) } private def buildAnnotation(a: Annotation): SignatureBuilder = @@ -68,51 +71,28 @@ trait SignatureBuilder extends ScalaSignatureUtils { addParameterName(name).text(value) } - def modifiersAndVisibility(t: Documentable with WithAbstraction with WithVisibility with WithExtraProperties[_], kind: String) = - import org.jetbrains.dokka.model.properties._ - val extras = t.getExtra.getMap() + def modifiersAndVisibility(t: Member, kind: String) = val (prefixMods, suffixMods) = t.modifiers.partition(_.prefix) val all = prefixMods.map(_.name) ++ Seq(t.visibility.asSignature) ++ suffixMods.map(_.name) text(all.toSignatureString()).text(kind + " ") - def typeSignature(b: Projection): SignatureBuilder = b match { - case tc: TypeConstructor => - 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) - case other => - bdr.text(s"TODO($other)") - } - } - case other => - text(s"TODO: $other") + def generics(on: Seq[TypeParameter]) = list(on.toList, "[", "]"){ (bdr, e) => + bdr.text(e.variance).memberName(e.name, e.dri).signature(e.signature) } - def generics(on: WithGenerics) = list(on.getGenerics.asScala.toList, "[", "]"){ (bdr, e) => - val bldr = bdr.text(e.getName) - e.getBounds.asScala.foldLeft(bldr)( (b, bound) => b.typeSignature(bound)) - } - - def functionParameters(method: DFunction) = - val methodExtension = method.get(MethodExtension) - val receiverPos = if method.isRightAssociative() then methodExtension.parametersListSizes(0) else 0 - val (bldr, index) = methodExtension.parametersListSizes.foldLeft(this, 0){ - case ((builder, from), size) => - val toIndex = from + size - if from == toIndex then (builder.text("()"), toIndex) - 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) + def functionParameters(params: Seq[Seq[Parameter]]) = + if params.isEmpty then this.text("") + else if params == List(Nil) then this.text("()") + else this.list(params, separator = ""){ (bld, pList) => + bld.list(pList, "(", ")"){ (bld, p) => + bld.annotationsInline(p) + .text(p.modifiers) + .memberName(p.name, p.dri) .text(": ") - .typeSignature(param.getType) - } - (b, toIndex) - else (builder, toIndex) + .signature(p.signature) + } } - bldr } trait ScalaSignatureUtils: diff --git a/scala3doc/src/dotty/renderers/DotDiagramBuilder.scala b/scala3doc/src/dotty/renderers/DotDiagramBuilder.scala index 226d4e6ae394..bebd8f3bb82b 100644 --- a/scala3doc/src/dotty/renderers/DotDiagramBuilder.scala +++ b/scala3doc/src/dotty/renderers/DotDiagramBuilder.scala @@ -8,7 +8,15 @@ import HTML._ import dotty.dokka.model.api._ object DotDiagramBuilder: - def build(diagram: HierarchyGraph, renderer: SignatureRenderer): String = + def build(diagram: HierarchyGraph, renderer: SignatureRenderer)(using DocContext): String = + def getStyle(vertex: LinkToType) = vertex.kind match + case _ : Kind.Class => "fill: #45AD7D;" + case Kind.Object => "fill: #285577;" + case _ : Kind.Trait => "fill: #1CAACF;" + case Kind.Enum => "fill: #B66722;" + case _ : Kind.EnumCase => "fill: #B66722;" + case other => report.error(s"unexpected value: $other") + val vWithId = diagram.verteciesWithId val vertecies = vWithId.map { (vertex, id) => s"""node${id} [label="${getHtmlLabel(vertex, renderer)}", style="${getStyle(vertex)}"];\n""" @@ -25,16 +33,6 @@ object DotDiagramBuilder: |} |""".stripMargin - - private def getStyle(vertex: LinkToType) = vertex.kind match - case Kind.Class => "fill: #45AD7D;" - case Kind.Object => "fill: #285577;" - case Kind.Trait => "fill: #1CAACF;" - case Kind.Enum => "fill: #B66722;" - case Kind.EnumCase => "fill: #B66722;" - case other => sys.error(s"unexpected value: $other") - - private def getHtmlLabel(vertex: LinkToType, renderer: SignatureRenderer): String = span(style := "color: #FFFFFF;")( vertex.kind.name, diff --git a/scala3doc/test/dotty/dokka/SignatureTestCases.scala b/scala3doc/test/dotty/dokka/SignatureTestCases.scala index 57c8a5f7b105..d41d80d10f8f 100644 --- a/scala3doc/test/dotty/dokka/SignatureTestCases.scala +++ b/scala3doc/test/dotty/dokka/SignatureTestCases.scala @@ -50,7 +50,7 @@ class StructuralTypes extends SignatureTest("structuralTypes", SignatureTest.mem class OpaqueTypes extends SignatureTest("opaqueTypes", SignatureTest.all) -// class GivenSignatures extends SignatureTest("givenSignatures", SignatureTest.all) +class GivenSignatures extends SignatureTest("givenSignatures", SignatureTest.all) class Annotations extends SignatureTest("annotations", SignatureTest.all) From d742110f011a6595b14e3d23436a0270678b6aaa Mon Sep 17 00:00:00 2001 From: Krzysztof Romanowski Date: Thu, 10 Dec 2020 19:48:12 +0100 Subject: [PATCH 02/12] Render full documentation on summary page --- scala3doc/resources/dotty_res/scripts/ux.js | 10 + .../resources/dotty_res/styles/scalastyle.css | 78 +++++++- scala3doc/src/dotty/dokka/SourceLinks.scala | 59 +++--- scala3doc/src/dotty/dokka/model/api/api.scala | 4 + .../dokka/model/api/internalExtensions.scala | 2 + .../src/dotty/dokka/model/scalaModel.scala | 18 +- .../src/dotty/dokka/tasty/BasicSupport.scala | 2 + .../dotty/dokka/tasty/ClassLikeSupport.scala | 6 +- .../dotty/dokka/tasty/ScalaDocSupport.scala | 40 ++++ .../translators/ScalaContentBuilder.scala | 5 +- .../dokka/translators/ScalaPageCreator.scala | 180 +----------------- .../translators/ScalaSignatureProvider.scala | 4 +- .../src/dotty/renderers/MemberRenderer.scala | 108 +++++++++++ .../dotty/renderers/ScalaHtmlRenderer.scala | 25 ++- scala3doc/src/dotty/renderers/html.scala | 6 +- .../test/dotty/dokka/SourceLinksTests.scala | 10 +- 16 files changed, 329 insertions(+), 228 deletions(-) create mode 100644 scala3doc/src/dotty/renderers/MemberRenderer.scala diff --git a/scala3doc/resources/dotty_res/scripts/ux.js b/scala3doc/resources/dotty_res/scripts/ux.js index 3bb71dd03d89..e49cd8676a11 100644 --- a/scala3doc/resources/dotty_res/scripts/ux.js +++ b/scala3doc/resources/dotty_res/scripts/ux.js @@ -5,6 +5,16 @@ window.addEventListener("DOMContentLoaded", () => { document.getElementById("leftColumn").classList.toggle("open"); }; } + + var elements = document.getElementsByClassName("documentableElement") + if (elements) { + for (i = 0; i < elements.length; i++) { + elements[i].onclick = function(){ + this.classList.toggle("expand") + } + } + } + var logo = document.getElementById("logo"); if (logo) { logo.onclick = function() { diff --git a/scala3doc/resources/dotty_res/styles/scalastyle.css b/scala3doc/resources/dotty_res/styles/scalastyle.css index ee46f28d78c0..16ccb8ece48f 100644 --- a/scala3doc/resources/dotty_res/styles/scalastyle.css +++ b/scala3doc/resources/dotty_res/styles/scalastyle.css @@ -12,6 +12,9 @@ --inactive-fg: #777; --title-fg: #00485E; + --link-sig-fd: #7c99a5; + --link-sig-hover-fd: #7c99a5; + --leftbar-bg: #003048; --leftbar-fg: #fff; --leftbar-current-bg: #0090BB; @@ -364,7 +367,7 @@ dl.attributes > dt.implicit { } dl.attributes > dd { display: block; - padding-left: 10em; + padding-left: 5em; margin-bottom: 5px; min-height: 15px; } @@ -437,11 +440,43 @@ footer .pull-right { margin-left: auto; } + .modifiers { - width: 12em; display: table-cell; - text-align: right; padding-right: 0.5em; + min-width: 10em; + max-width: 10em; + overflow: hidden; + direction: rtl; + white-space: nowrap; + text-indent: 0em; +} + +.modifiers .other-modifiers { + color: gray; +} + +.other-modifiers a, .other-modifiers a:visited, .other-modifiers span[data-unresolved-link] { + color: var(--link-sig-fd); +} + +.expand .modifiers { + display: inline-table; + min-width: 4em; +} + +.signature { + color: gray; + display: table-cell; + padding-left: 0.5em; +} + +.signature a, .signature a:visited, .signature span[data-unresolved-link] { + color: var(--link-sig-fd); +} + +.expand .signature { + display: inline; } .documentableElement { @@ -454,15 +489,50 @@ footer .pull-right { font-weight: 500; font-size: 12px; background: var(--code-bg); + border: 0.25em solid white; } .documentableElement>div { display: table; } +.expand.documentableElement>div { + display: block; + padding-left: 3em; +} + +.expand.documentableElement>div.header { + display: block; + padding-left: 7.5em; + text-indent: -4.5em; +} + +.documentableElement>div .cover { + display: none; +} + +.documentableElement.expand>div .cover { + display: block; +} + +.expand .doc { + margin-left: 6.5em; +} + +.documentableElement:hover { + cursor: pointer; + border-left: 0.25em solid var(--leftbar-bg); +} + .annotations { - margin-left: 9em; + color: gray; + display: none; } + +.expand .annotations { + display: inline-block; +} + .documentableAnchor { position: absolute; width: 24px; diff --git a/scala3doc/src/dotty/dokka/SourceLinks.scala b/scala3doc/src/dotty/dokka/SourceLinks.scala index 7dd8cadc9bac..19e127d699ec 100644 --- a/scala3doc/src/dotty/dokka/SourceLinks.scala +++ b/scala3doc/src/dotty/dokka/SourceLinks.scala @@ -6,7 +6,30 @@ import liqp.Template import dotty.dokka.model.api._ import dotty.tools.dotc.core.Contexts.Context -case class SourceLink(val path: Option[Path], val urlTemplate: Template) +trait SourceLink: + val path: Option[Path] = None + def render(path: String, operation: String, line: Option[Int]): String + +case class PrefixedSourceLink(val myPath: Path, nested: SourceLink) extends SourceLink: + export nested.render + override val path = Some(myPath) + +case class TemplateSourceLink(val urlTemplate: Template) extends SourceLink: + override val path: Option[Path] = None + override def render(path: String, operation: String, line: Option[Int]): String = + val config = java.util.HashMap[String, Object]() + config.put("path", path) + line.foreach(l => config.put("line", l.toString)) + config.put("operation", operation) + + urlTemplate.render(config) + +case class WebBasedSourceLink(prefix: String, revision: String) extends SourceLink: + override val path: Option[Path] = None + override def render(path: String, 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/$path$linePart" object SourceLink: val SubPath = "([^=]+)=(.+)".r @@ -18,41 +41,39 @@ object SourceLink: "€{FILE_LINE}" -> "{{ line }}" ) - def githubTemplate(organization: String, repo: String)(revision: String) = - s"""https://github.com/$organization/$repo/{{ operation | replace: "view", "blob" }}/$revision/{{ path }}#L{{ line }}""".stripMargin + def githubPrefix(org: String, repo: String) = s"https://github.com/$org/$repo" - def gitlabTemplate(organization: String, repo: String)(revision: String) = - s"""https://gitlab.com/$organization/$repo/-/{{ operation | replace: "view", "blob" }}/$revision/{{ path }}#L{{ line }}""" + def gitlabPrefix(org: String, repo: String) = s"https://gitlab.com/$org/$repo/-" private def parseLinkDefinition(s: String): Option[SourceLink] = ??? def parse(string: String, revision: Option[String]): Either[String, SourceLink] = def asTemplate(template: String) = - try Right(SourceLink(None,Template.parse(template))) catch + 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) => - def withRevision(template: String => String) = - revision.fold(Left(s"No revision provided"))(rev => Right(SourceLink(None, Template.parse(template(rev))))) + def withRevision(template: String => SourceLink) = + revision.fold(Left(s"No revision provided"))(r => Right(template(r))) name match case "github" => - withRevision(githubTemplate(organization, repo)) + withRevision(rev => WebBasedSourceLink(githubPrefix(organization, repo), rev)) case "gitlab" => - withRevision(gitlabTemplate(organization, repo)) + withRevision(rev => WebBasedSourceLink(gitlabPrefix(organization, repo), rev)) case other => Left(s"'$other' is not a known provider, please provide full source path template.") case SubPath(prefix, config) => parse(config, revision) match case l: Left[String, _] => l - case Right(SourceLink(Some(prefix), _)) => + case Right(_:PrefixedSourceLink) => Left(s"Source path $string has duplicated subpath setting (scm template can not contains '=')") - case Right(SourceLink(None, template)) => - Right(SourceLink(Some(Paths.get(prefix)), template)) + case Right(nested) => + Right(PrefixedSourceLink(Paths.get(prefix), nested)) case BrokenKnownProvider("gitlab" | "github") => Left(s"Does not match known provider syntax: `://organization/repository`") case scaladocSetting if ScalaDocPatten.findFirstIn(scaladocSetting).nonEmpty => @@ -70,15 +91,9 @@ 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 resolveRelativePath(path: Path) = - links.find(_.path.forall(p => path.startsWith(p))).map { link => - val config = java.util.HashMap[String, Object]() - val pathString = path.toString.replace('\\', '/') - config.put("path", pathString) - line.foreach(l => config.put("line", l.toString)) - config.put("operation", operation) - - link.urlTemplate.render(config) - } + links + .find(_.path.forall(p => path.startsWith(p))) + .map(_.render(path.toString.replace('\\', '/'), operation, line)) if rawPath.isAbsolute then if rawPath.startsWith(projectRoot) then resolveRelativePath(projectRoot.relativize(rawPath)) diff --git a/scala3doc/src/dotty/dokka/model/api/api.scala b/scala3doc/src/dotty/dokka/model/api/api.scala index f12c2fab7874..299d3a48a0c7 100644 --- a/scala3doc/src/dotty/dokka/model/api/api.scala +++ b/scala3doc/src/dotty/dokka/model/api/api.scala @@ -8,6 +8,7 @@ import collection.JavaConverters._ import org.jetbrains.dokka.model.doc._ import org.jetbrains.dokka.model.properties._ import org.jetbrains.dokka.pages._ +import dotty.dokka.tasty.comments.Comment enum Visibility(val name: String): case Unrestricted extends Visibility("") @@ -161,6 +162,7 @@ extension[T] (member: Member) def inheritedFrom: Option[InheritedFrom] = memberExt.fold(None)(_.inheritedFrom) def annotations: List[Annotation] = memberExt.fold(Nil)(_.annotations) def sources: Option[TastyDocumentableSource] = memberExt.fold(None)(_.sources) + def docs: Option[Comment] = memberExt.fold(None)(_.rawDoc) def name = member.getName def dri = member.getDri @@ -180,3 +182,5 @@ extension (module: DModule) def driMap: Map[DRI, Member] = ModuleExtension.getFrom(module).fold(Map.empty)(_.driMap) case class TastyDocumentableSource(val path: String, val lineNumber: Int) + +type DocPart = DocTag diff --git a/scala3doc/src/dotty/dokka/model/api/internalExtensions.scala b/scala3doc/src/dotty/dokka/model/api/internalExtensions.scala index 068b60424612..be4803dcad62 100644 --- a/scala3doc/src/dotty/dokka/model/api/internalExtensions.scala +++ b/scala3doc/src/dotty/dokka/model/api/internalExtensions.scala @@ -19,6 +19,7 @@ import org.jetbrains.dokka.model.DModule import collection.JavaConverters._ import org.jetbrains.dokka.model.doc.DocumentationNode import org.jetbrains.dokka.model.properties._ +import dotty.dokka.tasty.comments.Comment case class MemberExtension( visibility: Visibility, @@ -30,6 +31,7 @@ case class MemberExtension( origin: Origin = Origin.RegularlyDefined, inheritedFrom: Option[InheritedFrom] = None, graph: HierarchyGraph = HierarchyGraph.empty, + rawDoc: Option[Comment] = None ) extends ExtraProperty[Documentable]: override def getKey = MemberExtension diff --git a/scala3doc/src/dotty/dokka/model/scalaModel.scala b/scala3doc/src/dotty/dokka/model/scalaModel.scala index b01f615e24c2..3611f69b0ea7 100644 --- a/scala3doc/src/dotty/dokka/model/scalaModel.scala +++ b/scala3doc/src/dotty/dokka/model/scalaModel.scala @@ -8,6 +8,7 @@ import org.jetbrains.dokka.model.properties._ import org.jetbrains.dokka.pages._ import dotty.dokka.model.api.Signature import dotty.dokka.model.api.HierarchyGraph +import dotty.dokka.model.api.Member enum TableStyle extends org.jetbrains.dokka.pages.Style: case Borderless @@ -30,15 +31,15 @@ case class HtmlContentNode( override def getExtra = extra override def withNewExtras(p: PropertyContainer[ContentNode]) = copy(extra = p) -class ScalaTagWrapper(root: DocTag) extends TagWrapper(null): +class ScalaTagWrapper(root: DocTag, val name: String) extends TagWrapper(null): override def getRoot = root object ScalaTagWrapper { - case class See(root: DocTag) extends ScalaTagWrapper(root) - case class Todo(root: DocTag) extends ScalaTagWrapper(root) - case class Note(root: DocTag) extends ScalaTagWrapper(root) - case class Example(root: DocTag) extends ScalaTagWrapper(root) + case class See(root: DocTag) extends ScalaTagWrapper(root, "See") + case class Todo(root: DocTag) extends ScalaTagWrapper(root, "Todo") + case class Note(root: DocTag) extends ScalaTagWrapper(root, "Note") + case class Example(root: DocTag) extends ScalaTagWrapper(root, "Example") case class NestedNamedTag( name: String, subname: String, @@ -101,7 +102,8 @@ case class DocumentableElement( brief: Seq[ContentNode], originInfo: Signature, attributes: Map[String, String], - params: ContentNodeParams + params: ContentNodeParams, + member: Member ) extends ScalaContentNode(params): override def newInstance(params: ContentNodeParams) = copy(params = params) @@ -125,3 +127,7 @@ case class DocumentableList( case class DocumentableFilter(params: ContentNodeParams) extends ScalaContentNode(params): override def newInstance(params: ContentNodeParams) = copy(params = params) + +case class MemberInfo(member: Member, params: ContentNodeParams) + extends ScalaContentNode(params): + override def newInstance(params: ContentNodeParams) = copy(params = params) \ No newline at end of file diff --git a/scala3doc/src/dotty/dokka/tasty/BasicSupport.scala b/scala3doc/src/dotty/dokka/tasty/BasicSupport.scala index 9eb1f573ea96..3dda29313235 100644 --- a/scala3doc/src/dotty/dokka/tasty/BasicSupport.scala +++ b/scala3doc/src/dotty/dokka/tasty/BasicSupport.scala @@ -41,6 +41,8 @@ trait BasicSupport: case None => Map.empty + def documentation2 = sym.docstring.map(preparseComment(_, sym.tree)) + def source = val path = Some(sym.pos.get.sourceFile.jpath).filter(_ != null).map(_.toAbsolutePath).map(_.toString) path.map(TastyDocumentableSource(_, sym.pos.get.startLine)) diff --git a/scala3doc/src/dotty/dokka/tasty/ClassLikeSupport.scala b/scala3doc/src/dotty/dokka/tasty/ClassLikeSupport.scala index d2f9ac212efd..4f3fe3945859 100644 --- a/scala3doc/src/dotty/dokka/tasty/ClassLikeSupport.scala +++ b/scala3doc/src/dotty/dokka/tasty/ClassLikeSupport.scala @@ -30,7 +30,7 @@ trait ClassLikeSupport: def typeArgs = classDef.getTypeParams.map(mkTypeArgument) def parameterModifier(parameter: Symbol): String = - val fieldSymbol = classDef.symbol.field(parameter.normalizedName) + val fieldSymbol = classDef.symbol.declaredField(parameter.normalizedName) def isVal = fieldSymbol.flags.is(Flags.ParamAccessor) && !classDef.symbol.flags.is(Flags.Case) && !fieldSymbol.flags.is(Flags.Private) @@ -458,10 +458,10 @@ trait ClassLikeSupport: null, JNil, emptyJMap, - symbol.documentation.asJava, + emptyJMap, null, placeholderModifier, ctx.sourceSet.toSet, /*isExpectActual =*/ false, - PropertyContainer.Companion.empty().plus(member).plus(compositeExt) + PropertyContainer.Companion.empty().plus(member.copy(rawDoc = symbol.documentation2)).plus(compositeExt) ) \ No newline at end of file diff --git a/scala3doc/src/dotty/dokka/tasty/ScalaDocSupport.scala b/scala3doc/src/dotty/dokka/tasty/ScalaDocSupport.scala index 4d650e40eab0..8bf914ff4040 100644 --- a/scala3doc/src/dotty/dokka/tasty/ScalaDocSupport.scala +++ b/scala3doc/src/dotty/dokka/tasty/ScalaDocSupport.scala @@ -7,10 +7,50 @@ import org.jetbrains.dokka.model.{doc => dkkd} import dotty.dokka.Scala3doc.CommentSyntax import dotty.dokka.ScalaTagWrapper import comments.{kt, dkk} +import dotty.dokka.tasty.comments.Comment trait ScaladocSupport { self: TastyParser => import qctx.reflect._ + def preparseComment( + docstring: String, + tree: Tree + ): Comment = + val commentString: String = + if tree.symbol.isClassDef || tree.symbol.owner.isClassDef then + import dotty.tools.dotc + given ctx: dotc.core.Contexts.Context = qctx.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx + + val sym = tree.symbol.asInstanceOf[dotc.core.Symbols.Symbol] + + comments.CommentExpander.cookComment(sym)(using ctx) + .get.expanded.get + else + docstring + + val preparsed = + comments.Preparser.preparse(comments.Cleaner.clean(commentString)) + + val commentSyntax = + preparsed.syntax.headOption match { + case Some(commentSetting) => + CommentSyntax.parse(commentSetting).getOrElse { + val msg = s"not a valid comment syntax: $commentSetting, defaulting to Markdown syntax." + // we should update pos with span from documentation + report.warning(msg, tree.pos) + CommentSyntax.default + } + case None => ctx.args.defaultSyntax + } + + val parser = commentSyntax match { + case CommentSyntax.Wiki => + comments.WikiCommentParser(comments.Repr(qctx)(tree.symbol)) + case CommentSyntax.Markdown => + comments.MarkdownCommentParser(comments.Repr(qctx)(tree.symbol)) + } + parser.parse(preparsed) + def parseComment( docstring: String, tree: Tree diff --git a/scala3doc/src/dotty/dokka/translators/ScalaContentBuilder.scala b/scala3doc/src/dotty/dokka/translators/ScalaContentBuilder.scala index 5f6279220c09..0fed4a38bdf3 100644 --- a/scala3doc/src/dotty/dokka/translators/ScalaContentBuilder.scala +++ b/scala3doc/src/dotty/dokka/translators/ScalaContentBuilder.scala @@ -474,6 +474,8 @@ class ScalaPageContentBuilder( type Self = ScalaPageContentBuilder#ScalaDocumentableContentBuilder + def memberInfo(m: Member) = addChild(MemberInfo(m, asParams(m.dri))) + def documentableTab(name: String)(children: DocumentableGroup*): Self = def buildSignature(d: Documentable) = ScalaSignatureProvider.rawSignature(d, InlineSignatureBuilder()).asInstanceOf[InlineSignatureBuilder] @@ -507,7 +509,8 @@ class ScalaPageContentBuilder( docs.fold(Nil)(d => reset().rawComment(d.getRoot)), originInfo, FilterAttributes.attributesFor(documentable), - asParams(documentable.getDri) + asParams(documentable.getDri), + documentable ) def element(e: Documentable | DocumentableSubGroup): DocumentableElement | DocumentableElementGroup = e match diff --git a/scala3doc/src/dotty/dokka/translators/ScalaPageCreator.scala b/scala3doc/src/dotty/dokka/translators/ScalaPageCreator.scala index 4e9605e79749..a48c296b94d8 100644 --- a/scala3doc/src/dotty/dokka/translators/ScalaPageCreator.scala +++ b/scala3doc/src/dotty/dokka/translators/ScalaPageCreator.scala @@ -35,7 +35,9 @@ class ScalaPageCreator( override def pageForModule(m: DModule): ModulePageNode = super.pageForModule(m) private def pagesForMembers(member: Member): JList[PageNode] = - val (all, _) = member.membersBy(_.kind.isInstanceOf[Classlike]) + val all = member + .membersBy(_.kind.isInstanceOf[Classlike]) + .filter(m => m.origin == Origin.RegularlyDefined && m.inheritedFrom.isEmpty) all.map(pageForMember(_)).asJava override def pageForPackage(p: DPackage): PackagePageNode = @@ -142,180 +144,7 @@ class ScalaPageCreator( res } - def contentForComments(d: Documentable) = b - - def contentForDescription(d: Documentable) = { - val specialTags = Set[Class[_]](classOf[Description]) - - type SourceSet = DokkaConfiguration$DokkaSourceSet - - val tags: List[(SourceSet, TagWrapper)] = - d.getDocumentation.asScala.toList.flatMap( (pd, doc) => doc.getChildren.asScala.map(pd -> _).toList ) - - val platforms = d.getSourceSets.asScala.toSet - - val description = tags.collect{ case (pd, d: Description) => (pd, d) }.drop(1).groupBy(_(0)).map( (key, value) => key -> value.map(_(1))) - - /** Collect the key-value pairs from `iter` into a `Map` with a `cleanup` step, - * keeping the original order of the pairs. - */ - def collectInMap[K, E, V]( - iter: Iterator[(K, E)] - )( - cleanup: List[E] => V - ): collection.Map[K, V] = { - val lhm = mutable.LinkedHashMap.empty[K, ListBuffer[E]] - iter.foreach { case (k, e) => - lhm.updateWith(k) { - case None => Some(ListBuffer.empty.append(e)) - case Some(buf) => - buf.append(e) - Some(buf) - } - } - lhm.iterator.map { case (key, buf) => key -> cleanup(buf.result)}.to(mutable.LinkedHashMap) - } - - val unnamedTags: collection.Map[(SourceSet, Class[_]), List[TagWrapper]] = - collectInMap { - tags.iterator - .filterNot { t => - t(1).isInstanceOf[NamedTagWrapper] || specialTags.contains(t(1).getClass) - }.map { t => - (t(0), t(1).getClass) -> t(1) - } - }(cleanup = identity) - - val namedTags: collection.Map[ - String, - Either[ - collection.Map[SourceSet, NamedTagWrapper], - collection.Map[(SourceSet, String), ScalaTagWrapper.NestedNamedTag], - ], - ] = { - val grouped = collectInMap { - tags.iterator.collect { - case (sourcesets, n: NamedTagWrapper) => - (n.getName, n.isInstanceOf[ScalaTagWrapper.NestedNamedTag]) -> (sourcesets, n) - } - }(cleanup = identity) - - grouped.iterator.map { - case ((name, true), values) => - val groupedValues = - values.iterator.map { - case (sourcesets, t) => - val tag = t.asInstanceOf[ScalaTagWrapper.NestedNamedTag] - (sourcesets, tag.subname) -> tag - }.to(mutable.LinkedHashMap) - name -> Right(groupedValues) - case ((name, false), values) => - name -> Left(values.to(mutable.LinkedHashMap)) - }.to(mutable.LinkedHashMap) - } - - b.group(Set(d.getDri), styles = Set(TextStyle.Block, TableStyle.Borderless)) { bdr => - val b1 = description.foldLeft(bdr){ - case (bdr, (key, value)) => bdr - .group(sourceSets = Set(key)){ gbdr => - value.foldLeft(gbdr) { (gbdr, tag) => gbdr - .comment(tag.getRoot) - } - } - } - - b1.table(kind = ContentKind.Comment, styles = Set(TableStyle.DescriptionList)){ tbdr => - val withUnnamedTags = unnamedTags.foldLeft(tbdr){ case (bdr, (key, value) ) => bdr - .cell(sourceSets = Set(key(0))){ b => b - .text(key(1).getSimpleName, styles = Set(TextStyle.Bold)) - } - .cell(sourceSets = Set(key(0))) { b => b - .list(value, separator = ""){ (bdr, elem) => bdr - .comment(elem.getRoot) - } - } - } - - val withNamedTags = namedTags.foldLeft(withUnnamedTags){ - case (bdr, (key, Left(value))) => - value.foldLeft(bdr){ case (bdr, (sourceSets, v)) => bdr - .cell(sourceSets = Set(sourceSets)){ b => b - .text(key) - } - .cell(sourceSets = Set(sourceSets)){ b => b - .comment(v.getRoot) - } - } - case (bdr, (key, Right(groupedValues))) => bdr - .cell(sourceSets = d.getSourceSets.asScala.toSet){ b => b - .text(key) - } - .cell(sourceSets = d.getSourceSets.asScala.toSet)(_.table(kind = ContentKind.Comment, styles = Set(TableStyle.NestedDescriptionList)){ tbdr => - groupedValues.foldLeft(tbdr){ case (bdr, ((sourceSets, _), v)) => bdr - .cell(sourceSets = Set(sourceSets)){ b => b - .comment(v.identTag) - } - .cell(sourceSets = Set(sourceSets)){ b => b - .comment(v.descTag) - } - } - }) - } - - val withCompanion = d.companion.fold(withNamedTags){ co => withNamedTags - .cell(sourceSets = d.getSourceSets.asScala.toSet){ b => b - .text("Companion") - } - .cell(sourceSets = d.getSourceSets.asScala.toSet){ b => b - .driLink( - d.kind match { - case Kind.Object => "class" - case _ => "object" - }, - co - ) - } - } - - val withExtensionInformation = d.kind match { - case Kind.Extension(on, _) => - val sourceSets = d.getSourceSets.asScala.toSet - withCompanion.cell(sourceSets = sourceSets)(_.text("Extension")) - .cell(sourceSets = sourceSets)(_.text(s"This function is an extension on (${on.name}: ").inlineSignature(d, on.signature).text(")")) - case _ => withCompanion - } - - val withSource = d match - case null => withExtensionInformation - case m: Member => - ctx.sourceLinks.pathTo(m).fold(withCompanion){ link => - val sourceSets = m.getSourceSets.asScala.toSet - withExtensionInformation.cell(sourceSets = sourceSets)(_.text("Source")) - .cell(sourceSets = sourceSets)(_.resolvedLink("(source)", link)) - - } - - d.deprecated match - case None => withSource - case Some(a) => - extension (b: ScalaPageContentBuilder#ScalaDocumentableContentBuilder) - def annotationParameter(p: Option[Annotation.AnnotationParameter]): ScalaPageContentBuilder#ScalaDocumentableContentBuilder = - p match - case Some(Annotation.PrimitiveParameter(_, value)) => b.text(value.stripPrefix("\"").stripSuffix("\"")) - case Some(Annotation.LinkParameter(_, dri, text)) => b.driLink(text.stripPrefix("\"").stripSuffix("\""), dri) - case Some(Annotation.UnresolvedParameter(_, value)) => b.text(value.stripPrefix("\"").stripSuffix("\"")) - case _ => b - val since = a.params.find(_.name.contains("since")) - val message = a.params.find(_.name.contains("message")) - val sourceSets = d.getSourceSets.asScala.toSet - withSource.cell(sourceSets = sourceSets)(_.text("Deprecated")) - .cell(sourceSets = sourceSets) { b => - val withPossibleSince = if (since.isDefined) b.text("(Since version ").annotationParameter(since).text(") ") else b - withPossibleSince.annotationParameter(message) - } - } - } - } + def contentForDescription(m: Member) = b.memberInfo(m) def contentForScope(s: Documentable & WithScope & WithExtraProperties[_]) = def groupExtensions(extensions: Seq[Member]): Seq[DocumentableSubGroup] = @@ -336,7 +165,6 @@ class ScalaPageCreator( val (definedImplicits, inheritedImplicits) = s.membersBy(_.kind.isInstanceOf[Kind.Implicit]).byInheritance b - .contentForComments(s) .documentableTab("Type members")( DocumentableGroup(Some("Types"), definedTypes), DocumentableGroup(Some("Classlikes"), definedClasslikes), diff --git a/scala3doc/src/dotty/dokka/translators/ScalaSignatureProvider.scala b/scala3doc/src/dotty/dokka/translators/ScalaSignatureProvider.scala index b25928dfe114..dc71188a726a 100644 --- a/scala3doc/src/dotty/dokka/translators/ScalaSignatureProvider.scala +++ b/scala3doc/src/dotty/dokka/translators/ScalaSignatureProvider.scala @@ -22,7 +22,7 @@ class ScalaSignatureProvider(contentConverter: CommentsToContentConverter)(using private def signatureContent(d: Documentable)( func: ScalaPageContentBuilder#ScalaDocumentableContentBuilder => ScalaPageContentBuilder#ScalaDocumentableContentBuilder - ) = + ) = val styles = stylesIfDeprecated(d) contentBuilder.contentForDocumentable(d, kind = ContentKind.Symbol, styles = styles, buildBlock = func) @@ -46,7 +46,7 @@ class ScalaSignatureProvider(contentConverter: CommentsToContentConverter)(using private def stylesIfDeprecated(m: Member): Set[Style] = if m.deprecated.isDefined then styles ++ Set(TextStyle.Strikethrough) else styles - + object ScalaSignatureProvider: def rawSignature(documentable: Documentable, builder: SignatureBuilder): SignatureBuilder = diff --git a/scala3doc/src/dotty/renderers/MemberRenderer.scala b/scala3doc/src/dotty/renderers/MemberRenderer.scala new file mode 100644 index 000000000000..e42cf75afc86 --- /dev/null +++ b/scala3doc/src/dotty/renderers/MemberRenderer.scala @@ -0,0 +1,108 @@ +package dotty.dokka + +import dotty.dokka.model.api._ +import scala.collection.immutable.SortedMap +import dotty.dokka.HTML._ +import org.jetbrains.dokka.base.transformers.pages.comments.DocTagToContentConverter +import org.jetbrains.dokka.pages.{DCI, ContentKind, ContentNode} +import org.jetbrains.dokka.model.properties.PropertyContainer +import collection.JavaConverters._ + +class MemberRenderer(signatureRenderer: SignatureRenderer, buildNode: ContentNode => String)(using DocContext): + + private val converter = new DocTagToContentConverter() + + def renderDocPart(d: DocPart): AppliedTag = + val sb = StringBuilder() + converter.buildContent( + d, + DCI(JSet(), ContentKind.Comment), + JSet(), + JSet(), + PropertyContainer.Companion.empty() + ).forEach(c => sb.append(buildNode(c))) + raw(sb) + + def fullSignature(m: Member): AppliedTag = raw("signature: TODO!") + + def doc(m: Member): Seq[AppliedTag] = m.docs.fold(Nil)(d => Seq(renderDocPart(d.body))) + + def tableRow(name: String, content: AppliedTag) = Seq(dt(name), dd(content)) + + + def docAttributes(m: Member): Seq[AppliedTag] = + + def nested(name: String, on: SortedMap[String, DocPart]): Seq[AppliedTag] = if on.isEmpty then Nil else + tableRow(name, dl(cls := "attributes")( + on.map { case (name, value) => tableRow(name, renderDocPart(value))}.toList:_* + )) + + def list(name: String, on: List[DocPart]): Seq[AppliedTag] = if on.isEmpty then Nil else + tableRow(name, div(on.map(e => div(renderDocPart(e))))) + + def opt(name: String, on: Option[DocPart]): Seq[AppliedTag] = if on.isEmpty then Nil else + tableRow(name, renderDocPart(on.get)) + + m.docs.fold(Nil)(d => + nested("Type Params", d.typeParams) ++ + nested("Value Params", d.valueParams) ++ + opt("Returns", d.result) ++ + nested("Throws", d.throws.transform((_, v) => v._1)) ++ + opt("Constructor", d.constructor) ++ + list("Authors", d.authors) ++ + list("See also", d.see) ++ + opt("Version", d.version) ++ + opt("Since", d.since) ++ + list("Todo", d.todo) ++ + list("Note", d.note) ++ + list("Example", d.example) + ) + + def companion(m: Member): Seq[AppliedTag] = m.companion.fold(Nil){dri => + val kindName = if m.kind == Kind.Object then "class" else "object" + tableRow("Companion", signatureRenderer.renderLink(kindName, dri)) + } + + def source(m: Member): Seq[AppliedTag] = + summon[DocContext].sourceLinks.pathTo(m).fold(Nil){ link => + tableRow("Source", a(href := link)("(source)")) + } + + def deprecation(m: Member): Seq[AppliedTag] = m.deprecated.fold(Nil){ a => + def stripQuotes(s: String) = s.stripPrefix("\"").stripSuffix("\"") + def parameter(p: Annotation.AnnotationParameter): TagArg = p match + case Annotation.PrimitiveParameter(_, value) => stripQuotes(value) + case Annotation.LinkParameter(_, dri, text) => + signatureRenderer.renderLink(stripQuotes(text), dri) + case Annotation.UnresolvedParameter(_, value) => stripQuotes(value) + + val (named, unnamed) = a.params.partition(_.name.nonEmpty) + val message = named.find(_.name.get == "message").orElse(unnamed.headOption) + val since = named.find(_.name.get == "since").orElse(unnamed.drop(1).headOption) + + val content = + since.fold(Nil)(since => + Seq(code("[Since version ", parameter(since), "] ")) ++ + message.fold(Nil)(m => Seq(parameter(m))) ++ + m.docs.fold(Nil)(_.deprecated.toSeq.map(renderDocPart)) + ) + Seq(dt("Deprecated"), dd(content:_*)) + } + + def memberInfo(m: Member): Seq[AppliedTag] = + val bodyContents = m.docs.fold(Nil)(d => + if d.body.getChildren.isEmpty then Seq(d.body) else d.body.getChildren.asScala.toList + ).map(renderDocPart) + + Seq( + div(cls := "documentableBrief doc")(bodyContents.take(1)), + div(cls := "cover")( + div(cls := "doc")(bodyContents.drop(1)), + dl(cls := "attributes")( + docAttributes(m), + companion(m), + deprecation(m), + source(m), + ) + ) + ) \ No newline at end of file diff --git a/scala3doc/src/dotty/renderers/ScalaHtmlRenderer.scala b/scala3doc/src/dotty/renderers/ScalaHtmlRenderer.scala index acac28b06a4f..35edddbe6d7e 100644 --- a/scala3doc/src/dotty/renderers/ScalaHtmlRenderer.scala +++ b/scala3doc/src/dotty/renderers/ScalaHtmlRenderer.scala @@ -18,7 +18,9 @@ import kotlinx.html.HTMLTag import kotlinx.html.DIV import dotty.dokka.model.api.Link import dotty.dokka.model.api.HierarchyGraph +import dotty.dokka.model.api.DocPart import org.jetbrains.dokka.base.resolvers.local.LocationProvider +import org.jetbrains.dokka.base.transformers.pages.comments.DocTagToContentConverter import dotty.dokka.site.StaticPageNode import dotty.dokka.site.PartiallyRenderedContent import scala.util.Try @@ -103,16 +105,21 @@ class ScalaHtmlRenderer(using ctx: DokkaContext) extends HtmlRenderer(ctx) { val ss = if sourceSetRestriciton == null then Set.empty.asJava else sourceSetRestriciton withHtml(f, buildDocumentableList(n, pageContext, ss).toString()) case n: DocumentableFilter => withHtml(f, buildDocumentableFilter.toString) + case mi: MemberInfo => + val memberHtml = div(renderers(pageContext)._2.memberInfo(mi.member)) + withHtml(f, memberHtml.toString) case other => super.buildContentNode(f, node, pageContext, sourceSetRestriciton) } } - + private def renderers(pageContext: ContentPage): (SignatureRenderer, MemberRenderer) = + val renderer = SignatureRenderer(pageContext, sourceSets, getLocationProvider) + (renderer, new MemberRenderer(renderer, buildWithKotlinx(_, pageContext, null))) private def buildDocumentableList(n: DocumentableList, pageContext: ContentPage, sourceSetRestriciton: JSet[DisplaySourceSet]) = def render(n: ContentNode) = raw(buildWithKotlinx(n, pageContext, null)) - val renderer = SignatureRenderer(pageContext, sourceSets, getLocationProvider) + val (renderer, memberRenderer) = renderers(pageContext) import renderer._ def buildDocumentable(element: DocumentableElement) = @@ -121,21 +128,23 @@ class ScalaHtmlRenderer(using ctx: DokkaContext) extends HtmlRenderer(ctx) { val otherModifiers = element.modifiers.dropRight(1) div(topLevelAttr:_*)( - div(cls := "annotations monospace")(element.annotations.map(renderElement)), - div( - a(href:=link(element.params.dri).getOrElse("#"), cls := "documentableAnchor"), + a(href:=link(element.params.dri).getOrElse("#"), cls := "documentableAnchor"), + div(span(cls := "annotations monospace")(element.annotations.map(renderElement))), + div(cls := "header")( span(cls := "modifiers monospace")( span(cls := "other-modifiers")(otherModifiers.map(renderElement)), span(cls := "kind")(kind.map(renderElement)), ), renderLink(element.nameWithStyles.name, element.params.dri, cls := s"documentableName monospace ${element.nameWithStyles.styles.map(_.toString.toLowerCase).mkString(" ")}"), span(cls := "signature monospace")(element.signature.map(renderElement)), + ), + div(cls := "docs")( + span(cls := "modifiers monospace"), div( div(cls := "originInfo")(element.originInfo.map(renderElement)), - div(cls := "documentableBrief")(element.brief.map(render)), + div(cls := "documentableBrief")(memberRenderer.memberInfo(element.member)), ) - ), - + ) ) div(cls := "documentableList", testId := "definitionList")( diff --git a/scala3doc/src/dotty/renderers/html.scala b/scala3doc/src/dotty/renderers/html.scala index b5cd9668fa5f..af2b0b42c13f 100644 --- a/scala3doc/src/dotty/renderers/html.scala +++ b/scala3doc/src/dotty/renderers/html.scala @@ -77,6 +77,10 @@ object HTML: val body = Tag("body") val nav = Tag("nav") val img = Tag("img") + val ul = Tag("ul") + val li = Tag("li") + val code = Tag("code") + val cls = Attr("class") val href = Attr("href") @@ -94,4 +98,4 @@ object HTML: val alt = Attr("alt") def raw(content: String): AppliedTag = AppliedTag(content) - + def raw(content: StringBuilder): AppliedTag = content diff --git a/scala3doc/test/dotty/dokka/SourceLinksTests.scala b/scala3doc/test/dotty/dokka/SourceLinksTests.scala index 5d99c765ed5b..5a624f09a175 100644 --- a/scala3doc/test/dotty/dokka/SourceLinksTests.scala +++ b/scala3doc/test/dotty/dokka/SourceLinksTests.scala @@ -83,16 +83,16 @@ class SourceLinksTest: @Test def testBasicPaths = testLink(Seq("github://lampepfl/dotty"), Some("develop"))( - "project/Build.scala" -> "https://github.com/lampepfl/dotty/blob/develop/project/Build.scala#L", + "project/Build.scala" -> "https://github.com/lampepfl/dotty/blob/develop/project/Build.scala", ("project/Build.scala", 54) -> "https://github.com/lampepfl/dotty/blob/develop/project/Build.scala#L54", - ("project/Build.scala", edit) -> "https://github.com/lampepfl/dotty/edit/develop/project/Build.scala#L", + ("project/Build.scala", edit) -> "https://github.com/lampepfl/dotty/edit/develop/project/Build.scala", ("project/Build.scala", 54, edit) -> "https://github.com/lampepfl/dotty/edit/develop/project/Build.scala#L54", ) testLink(Seq("gitlab://lampepfl/dotty"), Some("develop"))( - "project/Build.scala" -> "https://gitlab.com/lampepfl/dotty/-/blob/develop/project/Build.scala#L", + "project/Build.scala" -> "https://gitlab.com/lampepfl/dotty/-/blob/develop/project/Build.scala", ("project/Build.scala", 54) -> "https://gitlab.com/lampepfl/dotty/-/blob/develop/project/Build.scala#L54", - ("project/Build.scala", edit) -> "https://gitlab.com/lampepfl/dotty/-/edit/develop/project/Build.scala#L", + ("project/Build.scala", edit) -> "https://gitlab.com/lampepfl/dotty/-/edit/develop/project/Build.scala", ("project/Build.scala", 54, edit) -> "https://gitlab.com/lampepfl/dotty/-/edit/develop/project/Build.scala#L54", ) @@ -113,7 +113,7 @@ class SourceLinksTest: @Test def testBasicPrefixedPaths = testLink(Seq("src=gitlab://lampepfl/dotty"), Some("develop"))( - "src/lib/core.scala" -> "https://gitlab.com/lampepfl/dotty/-/blob/develop/src/lib/core.scala#L", + "src/lib/core.scala" -> "https://gitlab.com/lampepfl/dotty/-/blob/develop/src/lib/core.scala", ("src/lib/core.scala", 33, edit) -> "https://gitlab.com/lampepfl/dotty/-/edit/develop/src/lib/core.scala#L33", ("src/lib/core.scala", 33, edit) -> "https://gitlab.com/lampepfl/dotty/-/edit/develop/src/lib/core.scala#L33", "build.sbt" -> None From 9effbe49a266114c6672cd5788b8c4c89f0c4efc Mon Sep 17 00:00:00 2001 From: Krzysztof Romanowski Date: Fri, 11 Dec 2020 00:35:59 +0100 Subject: [PATCH 03/12] Move definition classes to documentation --- scala3doc/resources/dotty_res/styles/scalastyle.css | 8 ++++++-- .../dotty/dokka/translators/ScalaContentBuilder.scala | 4 ---- .../src/dotty/dokka/translators/ScalaPageCreator.scala | 2 +- scala3doc/src/dotty/renderers/MemberRenderer.scala | 10 ++++++++++ 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/scala3doc/resources/dotty_res/styles/scalastyle.css b/scala3doc/resources/dotty_res/styles/scalastyle.css index 16ccb8ece48f..0cc83dbf6eed 100644 --- a/scala3doc/resources/dotty_res/styles/scalastyle.css +++ b/scala3doc/resources/dotty_res/styles/scalastyle.css @@ -367,7 +367,7 @@ dl.attributes > dt.implicit { } dl.attributes > dd { display: block; - padding-left: 5em; + padding-left: 6em; margin-bottom: 5px; min-height: 15px; } @@ -462,7 +462,7 @@ footer .pull-right { .expand .modifiers { display: inline-table; - min-width: 4em; + min-width: 7em; } .signature { @@ -519,6 +519,10 @@ footer .pull-right { margin-left: 6.5em; } +.doc code { + padding: 0; +} + .documentableElement:hover { cursor: pointer; border-left: 0.25em solid var(--leftbar-bg); diff --git a/scala3doc/src/dotty/dokka/translators/ScalaContentBuilder.scala b/scala3doc/src/dotty/dokka/translators/ScalaContentBuilder.scala index 0fed4a38bdf3..b3215a265d4b 100644 --- a/scala3doc/src/dotty/dokka/translators/ScalaContentBuilder.scala +++ b/scala3doc/src/dotty/dokka/translators/ScalaContentBuilder.scala @@ -494,10 +494,6 @@ class ScalaPageContentBuilder( case Some(dri: DRI) => SLink(name, dri) case None => name Signature("Exported from ", signatureName) - case Origin.Overrides(overridenMembers) => - def intersperse(xs: Seq[SLink]): Seq[(String | SLink)] = - xs.flatMap(Seq(_, " -> ")).dropRight(1).asInstanceOf[Seq[(String | SLink)]] // dropRight returns `Seq[Object]` - Signature("Definition classes: ").join(Signature(intersperse(overridenMembers.map(SLink(_, _))):_*)) case _ => Nil } val styles: Set[Style] = if documentable.deprecated.isDefined then Set(TextStyle.Strikethrough) else Set.empty diff --git a/scala3doc/src/dotty/dokka/translators/ScalaPageCreator.scala b/scala3doc/src/dotty/dokka/translators/ScalaPageCreator.scala index a48c296b94d8..b30db55585e0 100644 --- a/scala3doc/src/dotty/dokka/translators/ScalaPageCreator.scala +++ b/scala3doc/src/dotty/dokka/translators/ScalaPageCreator.scala @@ -205,7 +205,7 @@ class ScalaPageCreator( def contentForConstructors(c: DClass) = b.documentableTab("Constructors")( - DocumentableGroup(None, c.membersBy(_.kind.isInstanceOf[Kind.Constructor])._1) + DocumentableGroup(None, c.membersBy(_.kind.isInstanceOf[Kind.Constructor])) ) diff --git a/scala3doc/src/dotty/renderers/MemberRenderer.scala b/scala3doc/src/dotty/renderers/MemberRenderer.scala index e42cf75afc86..0c2205356dd2 100644 --- a/scala3doc/src/dotty/renderers/MemberRenderer.scala +++ b/scala3doc/src/dotty/renderers/MemberRenderer.scala @@ -29,6 +29,15 @@ class MemberRenderer(signatureRenderer: SignatureRenderer, buildNode: ContentNod def tableRow(name: String, content: AppliedTag) = Seq(dt(name), dd(content)) + def defintionClasses(m: Member) = m.origin match + case Origin.Overrides(defs) => + def renderDef(d: Overriden): Seq[TagArg] = + Seq(signatureRenderer.renderLink(d.name, d.dri), " -> ") + + val nodes: Seq[TagArg] = defs.flatMap(renderDef).dropRight(1) // drop trailing arrow + tableRow("Definition Classes", div(nodes:_*)) + + case _ => Nil def docAttributes(m: Member): Seq[AppliedTag] = @@ -102,6 +111,7 @@ class MemberRenderer(signatureRenderer: SignatureRenderer, buildNode: ContentNod docAttributes(m), companion(m), deprecation(m), + defintionClasses(m), source(m), ) ) From 83b3bf5cf668e88fe3bad9d52f9f352833e1c730 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Zyba=C5=82a?= Date: Mon, 30 Nov 2020 08:27:26 +0100 Subject: [PATCH 04/12] Add external location providing between scala3doc instances --- scala3doc/src/dotty/dokka/DocContext.scala | 19 +++++++ .../src/dotty/dokka/DottyDokkaPlugin.scala | 21 ++++++++ .../dokka/externalDocumentationLinks.scala | 18 +++++++ .../ScalaExternalLocationProvider.scala | 53 +++++++++++++++++++ ...ScalaExternalLocationProviderFactory.scala | 17 ++++++ .../location/ScalaPackageListService.scala | 46 ++++++++++++++++ scala3doc/src/dotty/dokka/model/api/api.scala | 9 ++++ .../ScalaPackageListCreator.scala | 38 +++++++++++++ .../site/StaticSiteLocationProvider.scala | 46 ++++++++++++++-- scala3doc/src/dotty/dokka/tasty/SymOps.scala | 10 +++- 10 files changed, 272 insertions(+), 5 deletions(-) create mode 100644 scala3doc/src/dotty/dokka/externalDocumentationLinks.scala create mode 100644 scala3doc/src/dotty/dokka/location/ScalaExternalLocationProvider.scala create mode 100644 scala3doc/src/dotty/dokka/location/ScalaExternalLocationProviderFactory.scala create mode 100644 scala3doc/src/dotty/dokka/location/ScalaPackageListService.scala create mode 100644 scala3doc/src/dotty/dokka/preprocessors/ScalaPackageListCreator.scala diff --git a/scala3doc/src/dotty/dokka/DocContext.scala b/scala3doc/src/dotty/dokka/DocContext.scala index 702a2c55f906..0669a406491b 100644 --- a/scala3doc/src/dotty/dokka/DocContext.scala +++ b/scala3doc/src/dotty/dokka/DocContext.scala @@ -18,6 +18,7 @@ import dotty.tools.dotc.util.Spans import java.io.ByteArrayOutputStream import java.io.PrintStream import scala.io.Codec +import java.net.URL type CompilerContext = dotty.tools.dotc.core.Contexts.Context @@ -93,6 +94,24 @@ case class DocContext(args: Scala3doc.Args, compilerContext: CompilerContext) sourceLinks )(using compilerContext)) + val externalDocumentationLinks: List[Scala3docExternalDocumentationLink] = List( + Scala3docExternalDocumentationLink( + List(raw".*scala\/quoted.*".r), + new URL("http://127.0.0.1:5500/scala3doc/output/scala3/"), + DocumentationKind.Scala3doc + ).withPackageList(new URL("http://127.0.0.1:5500/scala3doc/output/scala3/-scala%203/package-list")), + Scala3docExternalDocumentationLink( + List(raw".*java.*".r), + new URL("https://docs.oracle.com/javase/8/docs/api/"), + DocumentationKind.Javadoc + ).withPackageList(new URL("https://docs.oracle.com/javase/8/docs/api/package-list")), + Scala3docExternalDocumentationLink( + List(raw".*scala.*".r), + new URL("https://www.scala-lang.org/api/current/"), + DocumentationKind.Scaladoc + ) + ) + override def getPluginsConfiguration: JList[DokkaConfiguration.PluginConfiguration] = JNil diff --git a/scala3doc/src/dotty/dokka/DottyDokkaPlugin.scala b/scala3doc/src/dotty/dokka/DottyDokkaPlugin.scala index 4d12d6ed4b05..72ffe45f844b 100644 --- a/scala3doc/src/dotty/dokka/DottyDokkaPlugin.scala +++ b/scala3doc/src/dotty/dokka/DottyDokkaPlugin.scala @@ -22,6 +22,7 @@ import org.jetbrains.dokka.pages._ import dotty.dokka.model.api._ import org.jetbrains.dokka.CoreExtensions import org.jetbrains.dokka.base.DokkaBase +import org.jetbrains.dokka.base.resolvers.shared._ import dotty.dokka.site.NavigationCreator import dotty.dokka.site.SitePagesCreator @@ -71,6 +72,7 @@ class DottyDokkaPlugin extends DokkaJavaPlugin: val scalaResourceInstaller = extend( _.extensionPoint(dokkaBase.getHtmlPreprocessors) .fromRecipe{ case ctx @ given DokkaContext => new ScalaResourceInstaller } + .name("scalaResourceInstaller") .after(dokkaBase.getCustomResourceInstaller) ) @@ -170,6 +172,25 @@ class DottyDokkaPlugin extends DokkaJavaPlugin: .overrideExtension(dokkaBase.getLocationProvider) ) + val scalaPackageListCreator = extend( + _.extensionPoint(dokkaBase.getHtmlPreprocessors) + .fromRecipe(c => ScalaPackageListCreator(c, RecognizedLinkFormat.DokkaHtml)) + .overrideExtension(dokkaBase.getPackageListCreator) + .after( + customDocumentationProvider.getValue + ) + ) + + val scalaExternalLocationProviderFactory = extend( + _.extensionPoint(dokkaBase.getExternalLocationProviderFactory) + .fromRecipe{ case c as given DokkaContext => new ScalaExternalLocationProviderFactory } + .overrideExtension(dokkaBase.getDokkaLocationProvider) + ) + +extension (ctx: DokkaContext): + def siteContext: Option[StaticSiteContext] = ctx.getConfiguration.asInstanceOf[DocContext].staticSiteContext + def args: Scala3doc.Args = ctx.getConfiguration.asInstanceOf[DocContext].args + // TODO (https://github.com/lampepfl/scala3doc/issues/232): remove once problem is fixed in Dokka extension [T] (builder: ExtensionBuilder[T]) def ordered(before: Seq[Extension[_, _, _]], after: Seq[Extension[_, _, _]]): ExtensionBuilder[T] = diff --git a/scala3doc/src/dotty/dokka/externalDocumentationLinks.scala b/scala3doc/src/dotty/dokka/externalDocumentationLinks.scala new file mode 100644 index 000000000000..9d8ddfbe7d5a --- /dev/null +++ b/scala3doc/src/dotty/dokka/externalDocumentationLinks.scala @@ -0,0 +1,18 @@ +package dotty.dokka + +import org.jetbrains.dokka._ +import java.net.URL +import scala.util.matching._ + +case class Scala3docExternalDocumentationLink( + originRegexes: List[Regex], + documentationUrl: URL, + kind: DocumentationKind, + packageListUrl: Option[URL] = None +): + def withPackageList(url: URL): Scala3docExternalDocumentationLink = copy(packageListUrl = Some(url)) + +enum DocumentationKind: + case Javadoc extends DocumentationKind + case Scaladoc extends DocumentationKind + case Scala3doc extends DocumentationKind \ No newline at end of file diff --git a/scala3doc/src/dotty/dokka/location/ScalaExternalLocationProvider.scala b/scala3doc/src/dotty/dokka/location/ScalaExternalLocationProvider.scala new file mode 100644 index 000000000000..a02bba95c9c5 --- /dev/null +++ b/scala3doc/src/dotty/dokka/location/ScalaExternalLocationProvider.scala @@ -0,0 +1,53 @@ +package dotty.dokka + +import org.jetbrains.dokka.base.resolvers.local._ +import org.jetbrains.dokka.base.DokkaBase +import org.jetbrains.dokka.base.resolvers.external._ +import org.jetbrains.dokka.base.resolvers.shared._ +import org.jetbrains.dokka.base.resolvers.anchors._ +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.model.DisplaySourceSet +import org.jetbrains.dokka.pages.RootPageNode +import org.jetbrains.dokka.plugability._ +import collection.JavaConverters._ +import java.util.{Set => JSet} + + +class ScalaExternalLocationProvider( + externalDocumentation: ExternalDocumentation, + extension: String, + kind: DocumentationKind +)(using ctx: DokkaContext) extends DefaultExternalLocationProvider(externalDocumentation, extension, ctx): + override def resolve(dri: DRI): String = + Option(externalDocumentation.getPackageList).map(_.getLocations.asScala.toMap).flatMap(_.get(dri.toString)) + .fold(constructPath(dri))( l => { + this.getDocURL + l + } + ) + + private val originRegex = raw"\[origin:(.*)\]".r + + override def constructPath(dri: DRI): String = kind match { + case DocumentationKind.Javadoc => constructPathForJavadoc(dri) + case DocumentationKind.Scaladoc => constructPathForScaladoc(dri) + case DocumentationKind.Scala3doc => constructPathForScala3doc(dri) + } + + private def constructPathForJavadoc(dri: DRI): String = { + val packagePrefix = dri.getPackageName.replace(".","/") + val origin = originRegex.findFirstIn(dri.getExtra) + val className = origin match { + case Some(path) => + path.split("/").last.stripSuffix(".class") + case None => dri.getClassNames + } + getDocURL + packagePrefix + "/" + className + extension + } + + private def constructPathForScaladoc(dri: DRI): String = { + val packagePrefix = dri.getPackageName.replace(".","/") + val className = dri.getClassNames + getDocURL + packagePrefix + "/" + className + extension + } + + private def constructPathForScala3doc(dri: DRI): String = super.constructPath(dri) diff --git a/scala3doc/src/dotty/dokka/location/ScalaExternalLocationProviderFactory.scala b/scala3doc/src/dotty/dokka/location/ScalaExternalLocationProviderFactory.scala new file mode 100644 index 000000000000..d448e07089b2 --- /dev/null +++ b/scala3doc/src/dotty/dokka/location/ScalaExternalLocationProviderFactory.scala @@ -0,0 +1,17 @@ +package dotty.dokka + +import org.jetbrains.dokka.base.resolvers.local._ +import org.jetbrains.dokka.base.DokkaBase +import org.jetbrains.dokka.base.resolvers.external._ +import org.jetbrains.dokka.base.resolvers.shared._ +import org.jetbrains.dokka.base.resolvers.anchors._ +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.model.DisplaySourceSet +import org.jetbrains.dokka.pages.RootPageNode +import org.jetbrains.dokka.plugability._ +import collection.JavaConverters._ +import java.util.{Set => JSet} + +class ScalaExternalLocationProviderFactory(using ctx: DokkaContext) extends ExternalLocationProviderFactory: + override def getExternalLocationProvider(doc: ExternalDocumentation): ExternalLocationProvider = + ScalaExternalLocationProvider(doc, ".html", DocumentationKind.Scala3doc) \ No newline at end of file diff --git a/scala3doc/src/dotty/dokka/location/ScalaPackageListService.scala b/scala3doc/src/dotty/dokka/location/ScalaPackageListService.scala new file mode 100644 index 000000000000..c506f1c0d18b --- /dev/null +++ b/scala3doc/src/dotty/dokka/location/ScalaPackageListService.scala @@ -0,0 +1,46 @@ +package dotty.dokka + +import org.jetbrains.dokka.base.renderers._ +import org.jetbrains.dokka.base.resolvers.local._ +import org.jetbrains.dokka.base._ +import org.jetbrains.dokka.links._ +import org.jetbrains.dokka.pages._ +import org.jetbrains.dokka.plugability._ +import collection.JavaConverters._ +import dotty.dokka.model.api.withNoOrigin + +object ScalaPackageListService: + val DOKKA_PARAM_PREFIX = "$dokka" + +class ScalaPackageListService(context: DokkaContext, rootPage: RootPageNode): + import ScalaPackageListService._ //Why I need to do this? + + val locationProvider = PluginUtils.querySingle[DokkaBase, LocationProviderFactory](context, _.getLocationProviderFactory) + .getLocationProvider(rootPage) + + def createPackageList(format: String, linkExtension: String): String = { + val packages = retrievePackageInfo(rootPage) + val relocations = getRelocations(rootPage) + s"$DOKKA_PARAM_PREFIX.format:$format\n" ++ + s"$DOKKA_PARAM_PREFIX.linkExtenstion:$linkExtension\n" ++ + relocations.map( (dri, link) => + s"$DOKKA_PARAM_PREFIX.location:${dri.withNoOrigin.toString}\u001f$link.$linkExtension" + ).mkString("","\n","\n") ++ + packages.mkString("","\n","\n") + } + + private def retrievePackageInfo(current: PageNode): Set[String] = current match { + case p: PackagePageNode => p.getChildren.asScala.toSet.flatMap(retrievePackageInfo) ++ Option(p.getDocumentable.getDri.getPackageName) + case other => other.getChildren.asScala.toSet.flatMap(retrievePackageInfo) + } + + private def getRelocations(current: PageNode): List[(DRI, String)] = current match { + case c: ContentPage => getRelocation(c.getDri.asScala.toList, c) ++ c.getChildren.asScala.toList.flatMap(getRelocations) + case other => other.getChildren.asScala.toList.flatMap(getRelocations) + } + + private def getRelocation(dris: List[DRI], node: ContentPage): List[(DRI, String)] = + val link = locationProvider.resolve(node, rootPage, true) + dris.map( dri => + if locationProvider.expectedLocationForDri(dri) != link then Some(dri, link) else None + ).flatten \ No newline at end of file diff --git a/scala3doc/src/dotty/dokka/model/api/api.scala b/scala3doc/src/dotty/dokka/model/api/api.scala index 299d3a48a0c7..bf48cc6dea41 100644 --- a/scala3doc/src/dotty/dokka/model/api/api.scala +++ b/scala3doc/src/dotty/dokka/model/api/api.scala @@ -181,6 +181,15 @@ extension (members: Seq[Member]) def byInheritance = extension (module: DModule) def driMap: Map[DRI, Member] = ModuleExtension.getFrom(module).fold(Map.empty)(_.driMap) +extension (dri: DRI): + def withNoOrigin = DRI( + dri.getPackageName, + dri.getClassNames, + dri.getCallable, + dri.getTarget, + Option(dri.getExtra).fold(null)(e => raw"\[origin:(.*)\]".r.replaceAllIn(e, "")) + ) + case class TastyDocumentableSource(val path: String, val lineNumber: Int) type DocPart = DocTag diff --git a/scala3doc/src/dotty/dokka/preprocessors/ScalaPackageListCreator.scala b/scala3doc/src/dotty/dokka/preprocessors/ScalaPackageListCreator.scala new file mode 100644 index 000000000000..7823be2d4be5 --- /dev/null +++ b/scala3doc/src/dotty/dokka/preprocessors/ScalaPackageListCreator.scala @@ -0,0 +1,38 @@ +package dotty.dokka + +import org.jetbrains.dokka.transformers.pages.{PageTransformer} +import org.jetbrains.dokka.base.renderers._ +import org.jetbrains.dokka.base.resolvers.local._ +import org.jetbrains.dokka.base.resolvers.shared._ +import org.jetbrains.dokka.base._ +import org.jetbrains.dokka.links._ +import org.jetbrains.dokka.pages._ +import org.jetbrains.dokka.plugability._ +import collection.JavaConverters._ + +class ScalaPackageListCreator( + context: DokkaContext, + format: LinkFormat, + outputFileNames: List[String] = List("package-list"), + removeModulePrefix: Boolean = true +) extends PageTransformer: + override def invoke(input: RootPageNode) = { + input.modified(input.getName, (input.getChildren.asScala ++ packageList(input)).asJava) + } + + private def processPage(page: PageNode, input: RootPageNode): PageNode = page + + private def packageList(rootPageNode: RootPageNode): List[RendererSpecificPage] = { + val content = ScalaPackageListService(context, rootPageNode).createPackageList( + format.getFormatName, + format.getLinkExtension + ) + outputFileNames.map( name => { + RendererSpecificResourcePage( + s"${rootPageNode.getName}/${name}", + JList(), + RenderingStrategy.Write(content) + ) + } + ) + } \ No newline at end of file diff --git a/scala3doc/src/dotty/dokka/site/StaticSiteLocationProvider.scala b/scala3doc/src/dotty/dokka/site/StaticSiteLocationProvider.scala index f392c3f0eeb0..046e5b13990f 100644 --- a/scala3doc/src/dotty/dokka/site/StaticSiteLocationProvider.scala +++ b/scala3doc/src/dotty/dokka/site/StaticSiteLocationProvider.scala @@ -1,18 +1,21 @@ package dotty.dokka package site -import org.jetbrains.dokka.base.resolvers.local.DokkaLocationProvider -import org.jetbrains.dokka.base.resolvers.local.LocationProvider -import org.jetbrains.dokka.base.resolvers.local.LocationProviderFactory import org.jetbrains.dokka.pages.ContentPage import org.jetbrains.dokka.pages.PageNode import org.jetbrains.dokka.pages.RootPageNode import org.jetbrains.dokka.pages.ModulePage import org.jetbrains.dokka.plugability.DokkaContext +import org.jetbrains.dokka.base.resolvers.external._ +import org.jetbrains.dokka.base.resolvers.shared._ +import org.jetbrains.dokka.base.resolvers.local._ +import org.jetbrains.dokka.model.DisplaySourceSet +import dotty.dokka.model.api.withNoOrigin import scala.collection.JavaConverters._ import java.nio.file.Paths import java.nio.file.Path +import scala.util.matching._ class StaticSiteLocationProviderFactory(using ctx: DokkaContext) extends LocationProviderFactory: override def getLocationProvider(pageNode: RootPageNode): LocationProvider = @@ -84,4 +87,39 @@ class StaticSiteLocationProvider(pageNode: RootPageNode)(using ctx: DokkaContext val nodePath = nodePaths.drop(commonPaths) match case l if l.isEmpty => Seq("index") case l => l - (contextPath ++ nodePath).mkString("/") \ No newline at end of file + (contextPath ++ nodePath).mkString("/") + + val externalLocationProviders: List[(List[Regex], ExternalLocationProvider)] = + val sourceSet = ctx.getConfiguration.getSourceSets.asScala(0) + ctx.getConfiguration + .asInstanceOf[DocContext] + .externalDocumentationLinks + .map { link => + val emptyExtDoc = ExternalDocumentation( + link.documentationUrl, + PackageList( + RecognizedLinkFormat.Javadoc1, JSet(), JMap(), link.documentationUrl + ) + ) + val extDoc = link.packageListUrl.fold(emptyExtDoc)( pl => ExternalDocumentation( + link.documentationUrl, + PackageList.Companion.load(pl, sourceSet.getJdkVersion, ctx.getConfiguration.getOfflineMode) + ) + ) + (extDoc, link) + } + .map { (extDoc, link) => + + val externalLocationProvider = ScalaExternalLocationProvider(extDoc, ".html", link.kind) + link.originRegexes -> externalLocationProvider + }.toList + + override def getExternalLocation(dri: DRI, sourceSets: JSet[DisplaySourceSet]): String = + val regex = raw"\[origin:(.*)\]".r + val origin = regex.findFirstIn(Option(dri.getExtra).getOrElse("")) + origin match { + case Some(path) => externalLocationProviders.find { (regexes, provider) => + regexes.exists(r => r.matches(path)) + }.fold(null)(_(1).resolve(dri.withNoOrigin)) + case None => null + } \ No newline at end of file diff --git a/scala3doc/src/dotty/dokka/tasty/SymOps.scala b/scala3doc/src/dotty/dokka/tasty/SymOps.scala index 7832cbb1e5bd..c5507e1827a9 100644 --- a/scala3doc/src/dotty/dokka/tasty/SymOps.scala +++ b/scala3doc/src/dotty/dokka/tasty/SymOps.scala @@ -111,6 +111,14 @@ class SymOps[Q <: Quotes](val q: Q): else if (sym.maybeOwner.isDefDef) Some(sym.owner) else None + val originPath = { + import q.reflect._ + import dotty.tools.dotc + given ctx as dotc.core.Contexts.Context = q.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx + val csym = sym.asInstanceOf[dotc.core.Symbols.Symbol] + Option(csym.associatedFile).map(_.path).fold("")(p => s"[origin:$p]") + } + new DRI( sym.packageName, sym.topLevelEntryName.orNull, // TODO do we need any of this fields? @@ -118,5 +126,5 @@ class SymOps[Q <: Quotes](val q: Q): pointsTo, // sym.show returns the same signature for def << = 1 and def >> = 2. // For some reason it contains `$$$` instrad of symbol name - s"${sym.name}${sym.fullName}/${sym.signature.resultSig}/[${sym.signature.paramSigs.mkString("/")}]" + s"${sym.name}${sym.fullName}/${sym.signature.resultSig}/[${sym.signature.paramSigs.mkString("/")}]$originPath" ) From a8096918ba4ba91594fade488e3e9724891b5ef6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Zyba=C5=82a?= Date: Tue, 8 Dec 2020 08:55:48 +0100 Subject: [PATCH 05/12] Add cli parameter to handle external documentation mappings --- scala3doc/src/dotty/dokka/DocContext.scala | 81 +++++++++++++++---- .../src/dotty/dokka/DottyDokkaPlugin.scala | 2 +- scala3doc/src/dotty/dokka/Scala3doc.scala | 3 +- scala3doc/src/dotty/dokka/Scala3docArgs.scala | 9 ++- scala3doc/src/dotty/dokka/tasty/SymOps.scala | 2 +- 5 files changed, 75 insertions(+), 22 deletions(-) diff --git a/scala3doc/src/dotty/dokka/DocContext.scala b/scala3doc/src/dotty/dokka/DocContext.scala index 0669a406491b..9a435fd16459 100644 --- a/scala3doc/src/dotty/dokka/DocContext.scala +++ b/scala3doc/src/dotty/dokka/DocContext.scala @@ -19,6 +19,7 @@ import java.io.ByteArrayOutputStream import java.io.PrintStream import scala.io.Codec import java.net.URL +import scala.util.Try type CompilerContext = dotty.tools.dotc.core.Contexts.Context @@ -94,23 +95,69 @@ case class DocContext(args: Scala3doc.Args, compilerContext: CompilerContext) sourceLinks )(using compilerContext)) - val externalDocumentationLinks: List[Scala3docExternalDocumentationLink] = List( - Scala3docExternalDocumentationLink( - List(raw".*scala\/quoted.*".r), - new URL("http://127.0.0.1:5500/scala3doc/output/scala3/"), - DocumentationKind.Scala3doc - ).withPackageList(new URL("http://127.0.0.1:5500/scala3doc/output/scala3/-scala%203/package-list")), - Scala3docExternalDocumentationLink( - List(raw".*java.*".r), - new URL("https://docs.oracle.com/javase/8/docs/api/"), - DocumentationKind.Javadoc - ).withPackageList(new URL("https://docs.oracle.com/javase/8/docs/api/package-list")), - Scala3docExternalDocumentationLink( - List(raw".*scala.*".r), - new URL("https://www.scala-lang.org/api/current/"), - DocumentationKind.Scaladoc - ) - ) + def parseDocTool(docTool: String) = docTool match { + case "scaladoc" => Some(DocumentationKind.Scaladoc) + case "scala3doc" => Some(DocumentationKind.Scala3doc) + case "javadoc" => Some(DocumentationKind.Javadoc) + case other => None + } + val externalDocumentationLinks: List[Scala3docExternalDocumentationLink] = args.externalMappings.filter(_.size >= 3).flatMap { mapping => + val regexStr = mapping(0) + val docTool = mapping(1) + val urlStr = mapping(2) + val packageListUrlStr = if mapping.size > 3 then Some(mapping(3)) else None + val regex = Try(regexStr.r).toOption + val url = Try(URL(urlStr)).toOption + val packageListUrl = Try(packageListUrlStr.map(URL(_))) + .fold( + e => { + logger.warn(s"Wrong packageListUrl parameter in external mapping. Found '$packageListUrlStr'. " + + s"Package list url will be omitted") + None}, + res => res + ) + + val parsedDocTool = parseDocTool(docTool) + val res = if regexStr.isEmpty then + logger.warn(s"Wrong regex parameter in external mapping. Found '$regexStr'. Mapping will be omitted") + None + else if url.isEmpty then + logger.warn(s"Wrong url parameter in external mapping. Found '$urlStr'. Mapping will be omitted") + None + else if parsedDocTool.isEmpty then + logger.warn(s"Wrong doc-tool parameter in external mapping. " + + s"Expected one of: 'scaladoc', 'scala3doc', 'javadoc'. Found:'$docTool'. Mapping will be omitted " + ) + None + else + Some( + Scala3docExternalDocumentationLink( + List(regexStr.r), + URL(urlStr), + parsedDocTool.get, + packageListUrlStr.map(URL(_)) + ) + ) + res + } + + //val externalDocumentationLinks: List[Scala3docExternalDocumentationLink] = List( + // Scala3docExternalDocumentationLink( + // List(raw".*scala\/quoted.*".r), + // new URL("http://127.0.0.1:5500/scala3doc/output/scala3/"), + // DocumentationKind.Scala3doc + // ).withPackageList(new URL("http://127.0.0.1:5500/scala3doc/output/scala3/-scala%203/package-list")), + // Scala3docExternalDocumentationLink( + // List(raw".*java.*".r), + // new URL("https://docs.oracle.com/javase/8/docs/api/"), + // DocumentationKind.Javadoc + // ).withPackageList(new URL("https://docs.oracle.com/javase/8/docs/api/package-list")), + // Scala3docExternalDocumentationLink( + // List(raw".*scala.*".r), + // new URL("https://www.scala-lang.org/api/current/"), + // DocumentationKind.Scaladoc + // ) + //) override def getPluginsConfiguration: JList[DokkaConfiguration.PluginConfiguration] = JNil diff --git a/scala3doc/src/dotty/dokka/DottyDokkaPlugin.scala b/scala3doc/src/dotty/dokka/DottyDokkaPlugin.scala index 72ffe45f844b..76cd1baab186 100644 --- a/scala3doc/src/dotty/dokka/DottyDokkaPlugin.scala +++ b/scala3doc/src/dotty/dokka/DottyDokkaPlugin.scala @@ -183,7 +183,7 @@ class DottyDokkaPlugin extends DokkaJavaPlugin: val scalaExternalLocationProviderFactory = extend( _.extensionPoint(dokkaBase.getExternalLocationProviderFactory) - .fromRecipe{ case c as given DokkaContext => new ScalaExternalLocationProviderFactory } + .fromRecipe{ case c @ given DokkaContext => new ScalaExternalLocationProviderFactory } .overrideExtension(dokkaBase.getDokkaLocationProvider) ) diff --git a/scala3doc/src/dotty/dokka/Scala3doc.scala b/scala3doc/src/dotty/dokka/Scala3doc.scala index df978a316a5a..049662a9b339 100644 --- a/scala3doc/src/dotty/dokka/Scala3doc.scala +++ b/scala3doc/src/dotty/dokka/Scala3doc.scala @@ -64,7 +64,8 @@ object Scala3doc: projectLogo: Option[String] = None, defaultSyntax: CommentSyntax = CommentSyntax.Markdown, sourceLinks: List[String] = Nil, - revision: Option[String] = None + revision: Option[String] = None, + externalMappings: List[List[String]] ) def run(args: Array[String], rootContext: CompilerContext): Reporter = diff --git a/scala3doc/src/dotty/dokka/Scala3docArgs.scala b/scala3doc/src/dotty/dokka/Scala3docArgs.scala index b71594cf4480..f14c2efd1e79 100644 --- a/scala3doc/src/dotty/dokka/Scala3docArgs.scala +++ b/scala3doc/src/dotty/dokka/Scala3docArgs.scala @@ -34,7 +34,10 @@ class Scala3docArgs extends SettingGroup with CommonScalaSettings: val revision: Setting[String] = StringSetting("-revision", "revision", "Revision (branch or ref) used to build project project", "") - def scala3docSpecificSettings: Set[Setting[_]] = Set(sourceLinks, syntax, revision) + val externalDocumentationMappings: Setting[String] = + StringSetting("-external-mappings", "external-mappings", "Mapping between regex matching class file and external documentation", "") + + def scala3docSpecificSettings: Set[Setting[_]] = Set(sourceLinks, syntax, revision, externalDocumentationMappings) object Scala3docArgs: def extract(args: List[String], rootCtx: CompilerContext):(Scala3doc.Args, CompilerContext) = @@ -95,6 +98,7 @@ object Scala3docArgs: CommentSyntax.default } } + val externalMappings = externalDocumentationMappings.get.split(":::").map(_.split("::").toList).toList unsupportedSettings.filter(s => s.get != s.default).foreach { s => report.warning(s"Setting ${s.name} is currently not supported.") @@ -115,6 +119,7 @@ object Scala3docArgs: projectLogo.nonDefault, parseSyntax, sourceLinks.nonDefault.fold(Nil)(_.split(",").toList), - revision.nonDefault + revision.nonDefault, + externalMappings ) (docArgs, newContext) \ No newline at end of file diff --git a/scala3doc/src/dotty/dokka/tasty/SymOps.scala b/scala3doc/src/dotty/dokka/tasty/SymOps.scala index c5507e1827a9..441bc580d4f0 100644 --- a/scala3doc/src/dotty/dokka/tasty/SymOps.scala +++ b/scala3doc/src/dotty/dokka/tasty/SymOps.scala @@ -114,7 +114,7 @@ class SymOps[Q <: Quotes](val q: Q): val originPath = { import q.reflect._ import dotty.tools.dotc - given ctx as dotc.core.Contexts.Context = q.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx + given ctx: dotc.core.Contexts.Context = q.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx val csym = sym.asInstanceOf[dotc.core.Symbols.Symbol] Option(csym.associatedFile).map(_.path).fold("")(p => s"[origin:$p]") } From e8014ef4d637ee2d64d80cf2a353f95e6d696585 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Zyba=C5=82a?= Date: Tue, 8 Dec 2020 15:37:57 +0100 Subject: [PATCH 06/12] Change usage of DRI. Change pages structure to more standarized. Minor fixes to external location mechanism. --- project/Build.scala | 21 ++++++++- scala3doc/src/dotty/dokka/DocContext.scala | 18 -------- scala3doc/src/dotty/dokka/Scala3doc.scala | 2 +- scala3doc/src/dotty/dokka/compat.scala | 2 - .../ScalaExternalLocationProvider.scala | 25 +++++------ scala3doc/src/dotty/dokka/model/api/api.scala | 32 +++++++++++--- .../dotty/dokka/site/SitePagesCreator.scala | 4 +- .../dotty/dokka/site/StaticSiteContext.scala | 4 +- .../site/StaticSiteLocationProvider.scala | 11 ++++- scala3doc/src/dotty/dokka/site/common.scala | 7 +-- .../dotty/dokka/tasty/PackageSupport.scala | 2 +- scala3doc/src/dotty/dokka/tasty/SymOps.scala | 27 ++++++++---- .../src/dotty/dokka/tasty/TastyParser.scala | 43 +++++++++++-------- .../translators/ScalaSignatureUtils.scala | 2 +- scala3doc/src/dotty/renderers/html.scala | 2 +- scala3doc/test/dotty/dokka/testUtils.scala | 2 +- 16 files changed, 125 insertions(+), 79 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index bd4ed3fb966a..de624668d7d6 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1565,7 +1565,17 @@ object Build { generateDocumentation( classDirectory.in(Compile).value.getAbsolutePath, "scala3doc", "scala3doc/output/self", VersionUtil.gitHash, - "-siteroot scala3doc/documentation -project-logo scala3doc/documentation/logo.svg") + "-siteroot scala3doc/documentation -project-logo scala3doc/documentation/logo.svg " + + "-external-mappings " + raw".*scala\/quoted.*" + "::" + + "scala3doc" + "::" + + "http://dotty.epfl.ch/api/" + ":::" + + raw".*java.*" + "::" + + "javadoc" + "::" + + "https://docs.oracle.com/javase/8/docs/api/" + ":::" + + raw".*scala.*" + "::" + + "scaladoc" + "::" + + "https://www.scala-lang.org/api/current/" + ) }.value, generateScala3Documentation := Def.inputTaskDyn { @@ -1589,7 +1599,14 @@ object Build { IO.write(dest / "CNAME", "dotty.epfl.ch") }.dependsOn(generateDocumentation( roots, "Scala 3", dest.getAbsolutePath, "master", - "-comment-syntax wiki -siteroot scala3doc/scala3-docs -project-logo scala3doc/scala3-docs/logo.svg")) + "-comment-syntax wiki -siteroot scala3doc/scala3-docs -project-logo scala3doc/scala3-docs/logo.svg " + + "-external-mappings " + raw".*java.*" + "::" + + "javadoc" + "::" + + "https://docs.oracle.com/javase/8/docs/api/" + ":::" + + raw".*scala.*" + "::" + + "scaladoc" + "::" + + "https://www.scala-lang.org/api/current/" + )) }.evaluated, generateTestcasesDocumentation := Def.taskDyn { diff --git a/scala3doc/src/dotty/dokka/DocContext.scala b/scala3doc/src/dotty/dokka/DocContext.scala index 9a435fd16459..d6752cb4e889 100644 --- a/scala3doc/src/dotty/dokka/DocContext.scala +++ b/scala3doc/src/dotty/dokka/DocContext.scala @@ -141,24 +141,6 @@ case class DocContext(args: Scala3doc.Args, compilerContext: CompilerContext) res } - //val externalDocumentationLinks: List[Scala3docExternalDocumentationLink] = List( - // Scala3docExternalDocumentationLink( - // List(raw".*scala\/quoted.*".r), - // new URL("http://127.0.0.1:5500/scala3doc/output/scala3/"), - // DocumentationKind.Scala3doc - // ).withPackageList(new URL("http://127.0.0.1:5500/scala3doc/output/scala3/-scala%203/package-list")), - // Scala3docExternalDocumentationLink( - // List(raw".*java.*".r), - // new URL("https://docs.oracle.com/javase/8/docs/api/"), - // DocumentationKind.Javadoc - // ).withPackageList(new URL("https://docs.oracle.com/javase/8/docs/api/package-list")), - // Scala3docExternalDocumentationLink( - // List(raw".*scala.*".r), - // new URL("https://www.scala-lang.org/api/current/"), - // DocumentationKind.Scaladoc - // ) - //) - override def getPluginsConfiguration: JList[DokkaConfiguration.PluginConfiguration] = JNil diff --git a/scala3doc/src/dotty/dokka/Scala3doc.scala b/scala3doc/src/dotty/dokka/Scala3doc.scala index 049662a9b339..b3e100f1660f 100644 --- a/scala3doc/src/dotty/dokka/Scala3doc.scala +++ b/scala3doc/src/dotty/dokka/Scala3doc.scala @@ -65,7 +65,7 @@ object Scala3doc: defaultSyntax: CommentSyntax = CommentSyntax.Markdown, sourceLinks: List[String] = Nil, revision: Option[String] = None, - externalMappings: List[List[String]] + externalMappings: List[List[String]] = List.empty ) def run(args: Array[String], rootContext: CompilerContext): Reporter = diff --git a/scala3doc/src/dotty/dokka/compat.scala b/scala3doc/src/dotty/dokka/compat.scala index e0a0d7bd4ccb..f0864d222e28 100644 --- a/scala3doc/src/dotty/dokka/compat.scala +++ b/scala3doc/src/dotty/dokka/compat.scala @@ -14,8 +14,6 @@ import java.util.stream.Collectors import org.jetbrains.dokka.plugability._ import kotlin.jvm.JvmClassMappingKt.getKotlinClass -def mkDRI(packageName: String = null, extra: String = null) = new DRI(packageName, null, null, PointingToDeclaration.INSTANCE, extra) - val U: kotlin.Unit = kotlin.Unit.INSTANCE def JList[T](e: T*): JList[T] = e.asJava diff --git a/scala3doc/src/dotty/dokka/location/ScalaExternalLocationProvider.scala b/scala3doc/src/dotty/dokka/location/ScalaExternalLocationProvider.scala index a02bba95c9c5..1db247b39a43 100644 --- a/scala3doc/src/dotty/dokka/location/ScalaExternalLocationProvider.scala +++ b/scala3doc/src/dotty/dokka/location/ScalaExternalLocationProvider.scala @@ -8,6 +8,7 @@ import org.jetbrains.dokka.base.resolvers.anchors._ import org.jetbrains.dokka.links.DRI import org.jetbrains.dokka.model.DisplaySourceSet import org.jetbrains.dokka.pages.RootPageNode +import dotty.dokka.model.api._ import org.jetbrains.dokka.plugability._ import collection.JavaConverters._ import java.util.{Set => JSet} @@ -34,20 +35,20 @@ class ScalaExternalLocationProvider( } private def constructPathForJavadoc(dri: DRI): String = { - val packagePrefix = dri.getPackageName.replace(".","/") - val origin = originRegex.findFirstIn(dri.getExtra) - val className = origin match { - case Some(path) => - path.split("/").last.stripSuffix(".class") - case None => dri.getClassNames - } - getDocURL + packagePrefix + "/" + className + extension + val location = "\\$+".r.replaceAllIn(dri.location.replace(".","/"), _ => ".") + val origin = originRegex.findFirstIn(dri.extra) + val anchor = dri.anchor + getDocURL + location + extension + anchor.fold("")(a => s"#$a") } private def constructPathForScaladoc(dri: DRI): String = { - val packagePrefix = dri.getPackageName.replace(".","/") - val className = dri.getClassNames - getDocURL + packagePrefix + "/" + className + extension + val location = dri.location.replace(".","/") + val anchor = dri.anchor + getDocURL + location + extension + anchor.fold("")(a => s"#$a") } - private def constructPathForScala3doc(dri: DRI): String = super.constructPath(dri) + private def constructPathForScala3doc(dri: DRI): String = { + val location = dri.location.replace(".","/") + val anchor = dri.anchor + getDocURL + location + anchor.fold(extension)(a => s"/$a$extension") + } diff --git a/scala3doc/src/dotty/dokka/model/api/api.scala b/scala3doc/src/dotty/dokka/model/api/api.scala index bf48cc6dea41..40ad1c7f13d8 100644 --- a/scala3doc/src/dotty/dokka/model/api/api.scala +++ b/scala3doc/src/dotty/dokka/model/api/api.scala @@ -9,6 +9,7 @@ import org.jetbrains.dokka.model.doc._ import org.jetbrains.dokka.model.properties._ import org.jetbrains.dokka.pages._ import dotty.dokka.tasty.comments.Comment +import org.jetbrains.dokka.links._ enum Visibility(val name: String): case Unrestricted extends Visibility("") @@ -182,14 +183,33 @@ extension (module: DModule) def driMap: Map[DRI, Member] = ModuleExtension.getFrom(module).fold(Map.empty)(_.driMap) extension (dri: DRI): - def withNoOrigin = DRI( - dri.getPackageName, - dri.getClassNames, - dri.getCallable, - dri.getTarget, - Option(dri.getExtra).fold(null)(e => raw"\[origin:(.*)\]".r.replaceAllIn(e, "")) + def withNoOrigin = dri._copy( + extra = Option(dri.getExtra).fold(null)(e => raw"\[origin:(.*)\]".r.replaceAllIn(e, "")) ) + def location: String = dri.getPackageName + + def anchor: Option[String] = Option(dri.getClassNames).filterNot(_.isEmpty) + + def extra: String = dri.getExtra + + def target: DriTarget = dri.getTarget + + def _copy( + location: String = dri.location, + anchor: Option[String] = dri.anchor, + target: DriTarget = dri.target, + extra: String = dri.extra + ) = new DRI(location, anchor.getOrElse(""), null, target, extra) + +object DRI: + def apply( + location: String = "", + anchor: Option[String] = None, + target: DriTarget = PointingToDeclaration.INSTANCE, + extra: String = "" + ) = new DRI(location, anchor.getOrElse(""), null, target, extra) + case class TastyDocumentableSource(val path: String, val lineNumber: Int) type DocPart = DocTag diff --git a/scala3doc/src/dotty/dokka/site/SitePagesCreator.scala b/scala3doc/src/dotty/dokka/site/SitePagesCreator.scala index 132c73730398..282406ec3054 100644 --- a/scala3doc/src/dotty/dokka/site/SitePagesCreator.scala +++ b/scala3doc/src/dotty/dokka/site/SitePagesCreator.scala @@ -10,6 +10,8 @@ import org.jetbrains.dokka.pages._ import scala.collection.JavaConverters._ +import dotty.dokka.model.api._ + class SitePagesCreator(using ctx: DocContext) extends BaseStaticSiteProcessor: private def mkRootPage(input: RootPageNode, children: List[PageNode]): AContentPage = input match @@ -44,7 +46,7 @@ class SitePagesCreator(using ctx: DocContext) extends BaseStaticSiteProcessor: val msg = s"ERROR: Multiple index pages for doc found ${indexes.map(_.template.file)}" report.error(msg) - def emptyContent = ctx.asContent(Text(), mkDRI(extra = "root_content")).get(0) + def emptyContent = ctx.asContent(Text(), DRI(extra = "root_content")).get(0) val root = ctx.indexPage().toList.map(_.copy(getDri = JSet(docsRootDRI))) val docsRoot = AContentPage( diff --git a/scala3doc/src/dotty/dokka/site/StaticSiteContext.scala b/scala3doc/src/dotty/dokka/site/StaticSiteContext.scala index 786d6d577260..3c4fb997a50f 100644 --- a/scala3doc/src/dotty/dokka/site/StaticSiteContext.scala +++ b/scala3doc/src/dotty/dokka/site/StaticSiteContext.scala @@ -20,6 +20,8 @@ import util.Try import scala.collection.JavaConverters._ +import dotty.dokka.model.api._ + class StaticSiteContext( val root: File, sourceSets: Set[SourceSetWrapper], @@ -166,7 +168,7 @@ class StaticSiteContext( }.toOption pathsDri.getOrElse(memberLinkResolver(link).toList) - def driFor(dest: Path): DRI = mkDRI(s"_.${root.toPath.relativize(dest)}") + def driFor(dest: Path): DRI = DRI(location = s"_.${root.toPath.relativize(dest)}") def relativePath(myTemplate: LoadedTemplate) = root.toPath.relativize(myTemplate.file.toPath) diff --git a/scala3doc/src/dotty/dokka/site/StaticSiteLocationProvider.scala b/scala3doc/src/dotty/dokka/site/StaticSiteLocationProvider.scala index 046e5b13990f..15f8cf3b174d 100644 --- a/scala3doc/src/dotty/dokka/site/StaticSiteLocationProvider.scala +++ b/scala3doc/src/dotty/dokka/site/StaticSiteLocationProvider.scala @@ -5,6 +5,7 @@ import org.jetbrains.dokka.pages.ContentPage import org.jetbrains.dokka.pages.PageNode import org.jetbrains.dokka.pages.RootPageNode import org.jetbrains.dokka.pages.ModulePage +import org.jetbrains.dokka.model.DPackage import org.jetbrains.dokka.plugability.DokkaContext import org.jetbrains.dokka.base.resolvers.external._ import org.jetbrains.dokka.base.resolvers.shared._ @@ -16,6 +17,7 @@ import scala.collection.JavaConverters._ import java.nio.file.Paths import java.nio.file.Path import scala.util.matching._ +import dotty.dokka.model.api._ class StaticSiteLocationProviderFactory(using ctx: DokkaContext) extends LocationProviderFactory: override def getLocationProvider(pageNode: RootPageNode): LocationProvider = @@ -69,6 +71,13 @@ class StaticSiteLocationProvider(pageNode: RootPageNode)(using ctx: DokkaContext case _: ModulePage if summon[DocContext].staticSiteContext.isEmpty => JList("index") + case page: ContentPage if page.getDocumentable != null => + ( + List("api") ++ + page.getDocumentable.getDri.location.split(Array('.')).toList ++ + (if(page.getDocumentable.isInstanceOf[DPackage]) then List("index") else List.empty) ++ + page.getDocumentable.getDri.anchor + ).asJava case _ => jpath @@ -116,7 +125,7 @@ class StaticSiteLocationProvider(pageNode: RootPageNode)(using ctx: DokkaContext override def getExternalLocation(dri: DRI, sourceSets: JSet[DisplaySourceSet]): String = val regex = raw"\[origin:(.*)\]".r - val origin = regex.findFirstIn(Option(dri.getExtra).getOrElse("")) + val origin = regex.findFirstIn(Option(dri.extra).getOrElse("")) origin match { case Some(path) => externalLocationProviders.find { (regexes, provider) => regexes.exists(r => r.matches(path)) diff --git a/scala3doc/src/dotty/dokka/site/common.scala b/scala3doc/src/dotty/dokka/site/common.scala index b2e0354d2f5d..fba59dce83d6 100644 --- a/scala3doc/src/dotty/dokka/site/common.scala +++ b/scala3doc/src/dotty/dokka/site/common.scala @@ -3,6 +3,7 @@ package site import java.io.File import java.nio.file.Files +import dotty.dokka.model.api._ import com.vladsch.flexmark.ext.anchorlink.AnchorLinkExtension import com.vladsch.flexmark.ext.autolink.AutolinkExtension @@ -22,9 +23,9 @@ import org.jetbrains.dokka.pages._ import scala.collection.JavaConverters._ -val docsRootDRI: DRI = mkDRI("_.index.md") -val docsDRI: DRI = mkDRI("_.docs/index.md") -val apiPageDRI: DRI = mkDRI(packageName = "api", extra = "__api__") +val docsRootDRI: DRI = DRI(location = "_.index.md") +val docsDRI: DRI = DRI(location = "_.docs/index.md") +val apiPageDRI: DRI = DRI(location = "api", extra = "__api__") val defaultMarkdownOptions: DataHolder = new MutableDataSet() diff --git a/scala3doc/src/dotty/dokka/tasty/PackageSupport.scala b/scala3doc/src/dotty/dokka/tasty/PackageSupport.scala index 8ad677911de9..fe6e99c05d04 100644 --- a/scala3doc/src/dotty/dokka/tasty/PackageSupport.scala +++ b/scala3doc/src/dotty/dokka/tasty/PackageSupport.scala @@ -34,7 +34,7 @@ trait PackageSupport: parseClasslike(pckObj) match { case clazz: DClass => DPackage( - new DRI(pckObj.symbol.dri.getPackageName, null, null, PointingToDeclaration.INSTANCE, null), + new DRI(pckObj.symbol.packageName, null, null, PointingToDeclaration.INSTANCE, null), clazz.getFunctions, clazz.getProperties, JList(), diff --git a/scala3doc/src/dotty/dokka/tasty/SymOps.scala b/scala3doc/src/dotty/dokka/tasty/SymOps.scala index 441bc580d4f0..c6e115c91cd7 100644 --- a/scala3doc/src/dotty/dokka/tasty/SymOps.scala +++ b/scala3doc/src/dotty/dokka/tasty/SymOps.scala @@ -14,13 +14,23 @@ class SymOps[Q <: Quotes](val q: Q): import q.reflect._ given Q = q - extension (sym: Symbol) - def packageName: String = + extension (sym: Symbol): + def packageName: String = ( if (sym.isPackageDef) sym.fullName else sym.maybeOwner.packageName + ) - def topLevelEntryName: Option[String] = if (sym.isPackageDef) None else - if (sym.owner.isPackageDef) Some(sym.name) else sym.owner.topLevelEntryName + 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 + ) + else if (sym.isPackageDef) None + else sym.maybeOwner.className + + def anchor: Option[String] = + if (!sym.isClassDef && !sym.isPackageDef) Some(sym.name) + else None + //TODO: Retrieve string that will match scaladoc anchors def getVisibility(): Visibility = import VisibilityScope._ @@ -118,11 +128,10 @@ class SymOps[Q <: Quotes](val q: Q): val csym = sym.asInstanceOf[dotc.core.Symbols.Symbol] Option(csym.associatedFile).map(_.path).fold("")(p => s"[origin:$p]") } - - new DRI( - sym.packageName, - sym.topLevelEntryName.orNull, // TODO do we need any of this fields? - method.map(s => new org.jetbrains.dokka.links.Callable(s.name, null, JList())).orNull, + DRI( + sym.className.fold(sym.packageName)(cn => s"${sym.packageName}.${cn}"), + sym.anchor.getOrElse(""), // TODO do we need any of this fields? + null, pointsTo, // sym.show returns the same signature for def << = 1 and def >> = 2. // For some reason it contains `$$$` instrad of symbol name diff --git a/scala3doc/src/dotty/dokka/tasty/TastyParser.scala b/scala3doc/src/dotty/dokka/tasty/TastyParser.scala index 054587a4fcc8..e707cb586471 100644 --- a/scala3doc/src/dotty/dokka/tasty/TastyParser.scala +++ b/scala3doc/src/dotty/dokka/tasty/TastyParser.scala @@ -20,6 +20,7 @@ import dotty.dokka.model.api.withNewMembers import dotty.dokka.tasty.comments.MemberLookup import dotty.dokka.tasty.comments.QueryParser import scala.util.Try +import dotty.dokka.model.api._ /** Responsible for collectively inspecting all the Tasty files we're interested in. * @@ -62,26 +63,30 @@ case class DokkaTastyInspector(parser: Parser)(using ctx: DocContext) extends Do .map((dri, pckgs) => pckgs.reduce(_.mergeWith(_)) ) + .toList + .sortBy( pckg => pckg.dri.location.size ) + .reverse - val byPackage = all.filter(_.getDri != null).groupBy(_.getDri().getPackageName()) - byPackage.map { - case (pck, entries) => { - val found = packages.find(d => d.getName == pck) - .map( f => - new DPackage( - f.getDri, - f.getFunctions, - f.getProperties, - JList(), // TODO add support for other things like type or package object entries - JList(), - f.getDocumentation, - null, - JSet(ctx.sourceSet), - f.getExtra - ).withNewMembers(entries.filterNot(_.isInstanceOf[DPackage]).toList).asInstanceOf[DPackage] - ) - found.getOrElse(throw IllegalStateException("No package for entries found")) - } + + val byPackage = all.filter(_.dri != null).groupBy( p => + packages.find(d => p.dri.location.contains(d.dri.location)).getOrElse(throw IllegalStateException("No package for entries found")) + ) + + byPackage + .map { + case (f, entries) => { + new DPackage( + f.getDri, + f.getFunctions, + f.getProperties, + JList(), // TODO add support for other things like type or package object entries + JList(), + f.getDocumentation, + null, + JSet(ctx.sourceSet), + f.getExtra + ).withNewMembers(entries.filterNot(_.isInstanceOf[DPackage]).toList).asInstanceOf[DPackage] + } }.toList extension (self: DPackage) def mergeWith(other: DPackage): DPackage = diff --git a/scala3doc/src/dotty/dokka/translators/ScalaSignatureUtils.scala b/scala3doc/src/dotty/dokka/translators/ScalaSignatureUtils.scala index 22fc9d5c953e..e0bf9808a4e0 100644 --- a/scala3doc/src/dotty/dokka/translators/ScalaSignatureUtils.scala +++ b/scala3doc/src/dotty/dokka/translators/ScalaSignatureUtils.scala @@ -50,7 +50,7 @@ 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(" ") + text("@").driLink(a.dri.location.split('.').last, a.dri).buildAnnotationParams(a).text(" ") private def buildAnnotationParams(a: Annotation): SignatureBuilder = if !a.params.isEmpty then diff --git a/scala3doc/src/dotty/renderers/html.scala b/scala3doc/src/dotty/renderers/html.scala index af2b0b42c13f..a5a4297cdc3e 100644 --- a/scala3doc/src/dotty/renderers/html.scala +++ b/scala3doc/src/dotty/renderers/html.scala @@ -31,7 +31,7 @@ object HTML: case a: AppliedTag => sb.append(a) case s: String => - sb.append(s.escapeReservedTokens) + sb.append(s.escapeReservedTokens) } } sb.append(s"") diff --git a/scala3doc/test/dotty/dokka/testUtils.scala b/scala3doc/test/dotty/dokka/testUtils.scala index 54dcb1d6de4b..8d3db74371a3 100644 --- a/scala3doc/test/dotty/dokka/testUtils.scala +++ b/scala3doc/test/dotty/dokka/testUtils.scala @@ -53,7 +53,7 @@ class TestReporter extends ConsoleReporter: def testArgs(files: Seq[File] = Nil, dest: File = new File("notUsed")) = Scala3doc.Args( name = "Test Project Name", output = dest, - tastyFiles = files + tastyFiles = files, ) def testContext = (new ContextBase).initialCtx.fresh.setReporter(new TestReporter) From bcaa1a7d02765f8d7aa20cc0c611b21c1921691e Mon Sep 17 00:00:00 2001 From: Krzysztof Romanowski Date: Fri, 11 Dec 2020 00:56:02 +0100 Subject: [PATCH 07/12] Fix extension syntax --- scala3doc/src/dotty/dokka/DottyDokkaPlugin.scala | 2 +- scala3doc/src/dotty/dokka/model/api/api.scala | 2 +- scala3doc/src/dotty/dokka/tasty/SymOps.scala | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scala3doc/src/dotty/dokka/DottyDokkaPlugin.scala b/scala3doc/src/dotty/dokka/DottyDokkaPlugin.scala index 76cd1baab186..015661bfe2a3 100644 --- a/scala3doc/src/dotty/dokka/DottyDokkaPlugin.scala +++ b/scala3doc/src/dotty/dokka/DottyDokkaPlugin.scala @@ -187,7 +187,7 @@ class DottyDokkaPlugin extends DokkaJavaPlugin: .overrideExtension(dokkaBase.getDokkaLocationProvider) ) -extension (ctx: DokkaContext): +extension (ctx: DokkaContext) def siteContext: Option[StaticSiteContext] = ctx.getConfiguration.asInstanceOf[DocContext].staticSiteContext def args: Scala3doc.Args = ctx.getConfiguration.asInstanceOf[DocContext].args diff --git a/scala3doc/src/dotty/dokka/model/api/api.scala b/scala3doc/src/dotty/dokka/model/api/api.scala index 40ad1c7f13d8..e021108ec68d 100644 --- a/scala3doc/src/dotty/dokka/model/api/api.scala +++ b/scala3doc/src/dotty/dokka/model/api/api.scala @@ -182,7 +182,7 @@ extension (members: Seq[Member]) def byInheritance = extension (module: DModule) def driMap: Map[DRI, Member] = ModuleExtension.getFrom(module).fold(Map.empty)(_.driMap) -extension (dri: DRI): +extension (dri: DRI) def withNoOrigin = dri._copy( extra = Option(dri.getExtra).fold(null)(e => raw"\[origin:(.*)\]".r.replaceAllIn(e, "")) ) diff --git a/scala3doc/src/dotty/dokka/tasty/SymOps.scala b/scala3doc/src/dotty/dokka/tasty/SymOps.scala index c6e115c91cd7..46e3bb53b656 100644 --- a/scala3doc/src/dotty/dokka/tasty/SymOps.scala +++ b/scala3doc/src/dotty/dokka/tasty/SymOps.scala @@ -14,7 +14,7 @@ class SymOps[Q <: Quotes](val q: Q): import q.reflect._ given Q = q - extension (sym: Symbol): + extension (sym: Symbol) def packageName: String = ( if (sym.isPackageDef) sym.fullName else sym.maybeOwner.packageName From 7132cec6fb194d81ac5cde79001a02dc3eb26e15 Mon Sep 17 00:00:00 2001 From: Krzysztof Romanowski Date: Fri, 11 Dec 2020 03:25:10 +0100 Subject: [PATCH 08/12] Do not link to scala 2 docs --- project/Build.scala | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index de624668d7d6..8aee10f4fc20 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1566,15 +1566,12 @@ object Build { classDirectory.in(Compile).value.getAbsolutePath, "scala3doc", "scala3doc/output/self", VersionUtil.gitHash, "-siteroot scala3doc/documentation -project-logo scala3doc/documentation/logo.svg " + - "-external-mappings " + raw".*scala\/quoted.*" + "::" + + "-external-mappings " + raw".*scala.*" + "::" + "scala3doc" + "::" + "http://dotty.epfl.ch/api/" + ":::" + raw".*java.*" + "::" + "javadoc" + "::" + - "https://docs.oracle.com/javase/8/docs/api/" + ":::" + - raw".*scala.*" + "::" + - "scaladoc" + "::" + - "https://www.scala-lang.org/api/current/" + "https://docs.oracle.com/javase/8/docs/api/" ) }.value, @@ -1602,10 +1599,7 @@ object Build { "-comment-syntax wiki -siteroot scala3doc/scala3-docs -project-logo scala3doc/scala3-docs/logo.svg " + "-external-mappings " + raw".*java.*" + "::" + "javadoc" + "::" + - "https://docs.oracle.com/javase/8/docs/api/" + ":::" + - raw".*scala.*" + "::" + - "scaladoc" + "::" + - "https://www.scala-lang.org/api/current/" + "https://docs.oracle.com/javase/8/docs/api/" )) }.evaluated, From 74277b9ff973ca90dacec016a35cc019e68543d9 Mon Sep 17 00:00:00 2001 From: Krzysztof Romanowski Date: Fri, 11 Dec 2020 03:27:03 +0100 Subject: [PATCH 09/12] Add testcases for nested DRIs --- scala3doc-testcases/src/tests/nestingDRI.scala | 16 ++++++++++++++++ scala3doc-testcases/src/toplevel.scala | 3 +++ .../test/dotty/dokka/linking/DriTestCases.scala | 8 ++++++++ 3 files changed, 27 insertions(+) create mode 100644 scala3doc-testcases/src/tests/nestingDRI.scala create mode 100644 scala3doc-testcases/src/toplevel.scala diff --git a/scala3doc-testcases/src/tests/nestingDRI.scala b/scala3doc-testcases/src/tests/nestingDRI.scala new file mode 100644 index 000000000000..4954c23aa656 --- /dev/null +++ b/scala3doc-testcases/src/tests/nestingDRI.scala @@ -0,0 +1,16 @@ +package tests.nestingDRI + +trait TestClass + +class A: + class B + object B: + object C + class C: + object D + + +class AA: + object B: + class C: + object D \ No newline at end of file diff --git a/scala3doc-testcases/src/toplevel.scala b/scala3doc-testcases/src/toplevel.scala new file mode 100644 index 000000000000..764e9cbaf9cf --- /dev/null +++ b/scala3doc-testcases/src/toplevel.scala @@ -0,0 +1,3 @@ +def toplevelDef = 123 + +class ToplevelClass \ No newline at end of file diff --git a/scala3doc/test/dotty/dokka/linking/DriTestCases.scala b/scala3doc/test/dotty/dokka/linking/DriTestCases.scala index 527415997c4d..9dba3f3d752a 100644 --- a/scala3doc/test/dotty/dokka/linking/DriTestCases.scala +++ b/scala3doc/test/dotty/dokka/linking/DriTestCases.scala @@ -1,5 +1,6 @@ package dotty.dokka package linking +import org.junit.Assert.assertTrue import org.junit.Ignore @@ -11,6 +12,13 @@ class GenericTest extends DriTest("genericDRI") class FunctionTest extends DriTest("functionDRI") +class NestingTest extends DriTest("nestingDRI"): + override def assertOnDRIs(dris: Seq[DRI]) = + println(dris.groupBy(_.location).map(_._1)) + dris.groupBy(_.location).foreach{ case (location, dris) => + assertTrue(s"Location $location has multiple dris assigned: $dris", dris.size == 1) + } + @Ignore class ShadowingTest extends DriTest("shadowingDRI"): override def assertOnDRIs(dris: Seq[DRI]) = if (!dris.flatMap(d => Option(d.getExtra)).exists(_.contains("findThisDeclaration"))) then From cdab39fd9ef9c308a7d68e7cefefc6337d4a2ccb Mon Sep 17 00:00:00 2001 From: Krzysztof Romanowski Date: Fri, 11 Dec 2020 03:28:32 +0100 Subject: [PATCH 10/12] Scroll to selected member --- scala3doc/resources/dotty_res/scripts/ux.js | 8 ++++++++ scala3doc/resources/dotty_res/styles/scalastyle.css | 3 +++ scala3doc/src/dotty/renderers/ScalaHtmlRenderer.scala | 9 +++++++-- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/scala3doc/resources/dotty_res/scripts/ux.js b/scala3doc/resources/dotty_res/scripts/ux.js index e49cd8676a11..185b2a718d44 100644 --- a/scala3doc/resources/dotty_res/scripts/ux.js +++ b/scala3doc/resources/dotty_res/scripts/ux.js @@ -15,6 +15,14 @@ window.addEventListener("DOMContentLoaded", () => { } } + + if (location.hash) { + var selected = document.getElementById(location.hash.substring(1)); + if (selected){ + selected.classList.toggle("expand"); + } + } + var logo = document.getElementById("logo"); if (logo) { logo.onclick = function() { diff --git a/scala3doc/resources/dotty_res/styles/scalastyle.css b/scala3doc/resources/dotty_res/styles/scalastyle.css index 0cc83dbf6eed..6e17a18f45d2 100644 --- a/scala3doc/resources/dotty_res/styles/scalastyle.css +++ b/scala3doc/resources/dotty_res/styles/scalastyle.css @@ -528,6 +528,9 @@ footer .pull-right { border-left: 0.25em solid var(--leftbar-bg); } +.expand.documentableElement { + border-left: 0.25em solid var(--leftbar-bg); +} .annotations { color: gray; display: none; diff --git a/scala3doc/src/dotty/renderers/ScalaHtmlRenderer.scala b/scala3doc/src/dotty/renderers/ScalaHtmlRenderer.scala index 35edddbe6d7e..5f7180a38af7 100644 --- a/scala3doc/src/dotty/renderers/ScalaHtmlRenderer.scala +++ b/scala3doc/src/dotty/renderers/ScalaHtmlRenderer.scala @@ -123,10 +123,15 @@ class ScalaHtmlRenderer(using ctx: DokkaContext) extends HtmlRenderer(ctx) { import renderer._ def buildDocumentable(element: DocumentableElement) = - def topLevelAttr = Seq(cls := "documentableElement") ++ element.attributes.map{ case (n, v) => Attr(s"data-f-$n") := v } + def topLevelAttr = Seq(cls := "documentableElement") + ++ element.params.dri.anchor.map(id := _) + ++ element.attributes.map{ case (n, v) => Attr(s"data-f-$n") := v } val kind = element.modifiers.takeRight(1) val otherModifiers = element.modifiers.dropRight(1) + val nameStyles = element.nameWithStyles.styles.map(_.toString.toLowerCase).mkString(" ") + val nameClasses = cls := s"documentableName monospace ${nameStyles.mkString(" ")}" + div(topLevelAttr:_*)( a(href:=link(element.params.dri).getOrElse("#"), cls := "documentableAnchor"), div(span(cls := "annotations monospace")(element.annotations.map(renderElement))), @@ -135,7 +140,7 @@ class ScalaHtmlRenderer(using ctx: DokkaContext) extends HtmlRenderer(ctx) { span(cls := "other-modifiers")(otherModifiers.map(renderElement)), span(cls := "kind")(kind.map(renderElement)), ), - renderLink(element.nameWithStyles.name, element.params.dri, cls := s"documentableName monospace ${element.nameWithStyles.styles.map(_.toString.toLowerCase).mkString(" ")}"), + renderLink(element.nameWithStyles.name, element.params.dri, nameClasses), span(cls := "signature monospace")(element.signature.map(renderElement)), ), div(cls := "docs")( From 750b30a8bb7aa983aaa0de3a0acf9f9609d8e3cc Mon Sep 17 00:00:00 2001 From: Krzysztof Romanowski Date: Fri, 11 Dec 2020 03:29:53 +0100 Subject: [PATCH 11/12] Address review feedback --- .../new-types/polymorphic-function-types.md | 2 +- .../src/dotty/dokka/DottyDokkaPlugin.scala | 4 -- scala3doc/src/dotty/dokka/compat.scala | 34 ++++++++++++- .../location/ScalaPackageListService.scala | 2 +- scala3doc/src/dotty/dokka/model/api/api.scala | 29 ----------- .../dotty/dokka/site/StaticSiteContext.scala | 6 +-- scala3doc/src/dotty/dokka/site/common.scala | 4 +- .../dotty/dokka/tasty/ClassLikeSupport.scala | 50 +++++++++---------- .../dokka/translators/ScalaPageCreator.scala | 2 - 9 files changed, 64 insertions(+), 69 deletions(-) diff --git a/docs/docs/reference/new-types/polymorphic-function-types.md b/docs/docs/reference/new-types/polymorphic-function-types.md index c555a83df89d..e983cd37aa95 100644 --- a/docs/docs/reference/new-types/polymorphic-function-types.md +++ b/docs/docs/reference/new-types/polymorphic-function-types.md @@ -80,7 +80,7 @@ println(e1) // Apply(Apply(Var(wrap),Var(f)),Apply(Var(wrap),Var(a))) ### Relationship With Type Lambdas Polymorphic function types are not to be confused with -[_type lambdas_](new-types/type-lambdas.md). +[_type lambdas_](type-lambdas.md). While the former describes the _type_ of a polymorphic _value_, the latter is an actual function value _at the type level_. diff --git a/scala3doc/src/dotty/dokka/DottyDokkaPlugin.scala b/scala3doc/src/dotty/dokka/DottyDokkaPlugin.scala index 015661bfe2a3..302dff9cb896 100644 --- a/scala3doc/src/dotty/dokka/DottyDokkaPlugin.scala +++ b/scala3doc/src/dotty/dokka/DottyDokkaPlugin.scala @@ -187,10 +187,6 @@ class DottyDokkaPlugin extends DokkaJavaPlugin: .overrideExtension(dokkaBase.getDokkaLocationProvider) ) -extension (ctx: DokkaContext) - def siteContext: Option[StaticSiteContext] = ctx.getConfiguration.asInstanceOf[DocContext].staticSiteContext - def args: Scala3doc.Args = ctx.getConfiguration.asInstanceOf[DocContext].args - // TODO (https://github.com/lampepfl/scala3doc/issues/232): remove once problem is fixed in Dokka extension [T] (builder: ExtensionBuilder[T]) def ordered(before: Seq[Extension[_, _, _]], after: Seq[Extension[_, _, _]]): ExtensionBuilder[T] = diff --git a/scala3doc/src/dotty/dokka/compat.scala b/scala3doc/src/dotty/dokka/compat.scala index f0864d222e28..0e721da414e7 100644 --- a/scala3doc/src/dotty/dokka/compat.scala +++ b/scala3doc/src/dotty/dokka/compat.scala @@ -11,8 +11,10 @@ import org.jetbrains.dokka.model.properties.ExtraProperty // import java.util.Stream // TODO reproduction uncomment import java.util.stream.Stream // comment out - wrong error! import java.util.stream.Collectors +import java.util.Collections import org.jetbrains.dokka.plugability._ import kotlin.jvm.JvmClassMappingKt.getKotlinClass +import org.jetbrains.dokka.links._ val U: kotlin.Unit = kotlin.Unit.INSTANCE @@ -26,15 +28,43 @@ type JMap[K, V] = java.util.Map[K, V] type JHashMap[K, V] = java.util.HashMap[K, V] type JMapEntry[K, V] = java.util.Map.Entry[K, V] -private val emptyListInst = JList() +private val emptyListInst = Collections.emptyList def JNil[A] = emptyListInst.asInstanceOf[JList[A]] -private val emptyMapInst = JMap() +private val emptyMapInst = Collections.emptyMap def emptyJMap[A, B] = emptyMapInst.asInstanceOf[JMap[A, B]] type DRI = org.jetbrains.dokka.links.DRI val topLevelDri = org.jetbrains.dokka.links.DRI.Companion.getTopLevel +extension (dri: DRI) + def withNoOrigin = dri._copy( + extra = Option(dri.getExtra).fold(null)(e => raw"\[origin:(.*)\]".r.replaceAllIn(e, "")) + ) + + def location: String = dri.getPackageName + + def anchor: Option[String] = Option(dri.getClassNames).filterNot(_.isEmpty) + + def extra: String = dri.getExtra + + def target: DriTarget = dri.getTarget + + def _copy( + location: String = dri.location, + anchor: Option[String] = dri.anchor, + target: DriTarget = dri.target, + extra: String = dri.extra + ) = new DRI(location, anchor.getOrElse(""), null, target, extra) + +object DRI: + def apply( + location: String = "", + anchor: Option[String] = None, + target: DriTarget = PointingToDeclaration.INSTANCE, + extra: String = "" + ) = new DRI(location, anchor.getOrElse(""), null, target, extra) + type SourceSetWrapper = DokkaConfiguration$DokkaSourceSet type DokkaSourceSet = DokkaConfiguration.DokkaSourceSet diff --git a/scala3doc/src/dotty/dokka/location/ScalaPackageListService.scala b/scala3doc/src/dotty/dokka/location/ScalaPackageListService.scala index c506f1c0d18b..7c73efbbd0ef 100644 --- a/scala3doc/src/dotty/dokka/location/ScalaPackageListService.scala +++ b/scala3doc/src/dotty/dokka/location/ScalaPackageListService.scala @@ -7,7 +7,7 @@ import org.jetbrains.dokka.links._ import org.jetbrains.dokka.pages._ import org.jetbrains.dokka.plugability._ import collection.JavaConverters._ -import dotty.dokka.model.api.withNoOrigin +import dotty.dokka.withNoOrigin object ScalaPackageListService: val DOKKA_PARAM_PREFIX = "$dokka" diff --git a/scala3doc/src/dotty/dokka/model/api/api.scala b/scala3doc/src/dotty/dokka/model/api/api.scala index e021108ec68d..299d3a48a0c7 100644 --- a/scala3doc/src/dotty/dokka/model/api/api.scala +++ b/scala3doc/src/dotty/dokka/model/api/api.scala @@ -9,7 +9,6 @@ import org.jetbrains.dokka.model.doc._ import org.jetbrains.dokka.model.properties._ import org.jetbrains.dokka.pages._ import dotty.dokka.tasty.comments.Comment -import org.jetbrains.dokka.links._ enum Visibility(val name: String): case Unrestricted extends Visibility("") @@ -182,34 +181,6 @@ extension (members: Seq[Member]) def byInheritance = extension (module: DModule) def driMap: Map[DRI, Member] = ModuleExtension.getFrom(module).fold(Map.empty)(_.driMap) -extension (dri: DRI) - def withNoOrigin = dri._copy( - extra = Option(dri.getExtra).fold(null)(e => raw"\[origin:(.*)\]".r.replaceAllIn(e, "")) - ) - - def location: String = dri.getPackageName - - def anchor: Option[String] = Option(dri.getClassNames).filterNot(_.isEmpty) - - def extra: String = dri.getExtra - - def target: DriTarget = dri.getTarget - - def _copy( - location: String = dri.location, - anchor: Option[String] = dri.anchor, - target: DriTarget = dri.target, - extra: String = dri.extra - ) = new DRI(location, anchor.getOrElse(""), null, target, extra) - -object DRI: - def apply( - location: String = "", - anchor: Option[String] = None, - target: DriTarget = PointingToDeclaration.INSTANCE, - extra: String = "" - ) = new DRI(location, anchor.getOrElse(""), null, target, extra) - case class TastyDocumentableSource(val path: String, val lineNumber: Int) type DocPart = DocTag diff --git a/scala3doc/src/dotty/dokka/site/StaticSiteContext.scala b/scala3doc/src/dotty/dokka/site/StaticSiteContext.scala index 3c4fb997a50f..bde05f5a06e8 100644 --- a/scala3doc/src/dotty/dokka/site/StaticSiteContext.scala +++ b/scala3doc/src/dotty/dokka/site/StaticSiteContext.scala @@ -17,8 +17,7 @@ import org.jetbrains.dokka.plugability.DokkaContext import org.jetbrains.dokka.pages.Style import org.jetbrains.dokka.model.DisplaySourceSet import util.Try - -import scala.collection.JavaConverters._ +import collection.JavaConverters._ import dotty.dokka.model.api._ @@ -168,7 +167,8 @@ class StaticSiteContext( }.toOption pathsDri.getOrElse(memberLinkResolver(link).toList) - def driFor(dest: Path): DRI = DRI(location = s"_.${root.toPath.relativize(dest)}") + def driFor(dest: Path): DRI = + DRI(location = root.toPath.relativize(dest).iterator.asScala.mkString(".")) def relativePath(myTemplate: LoadedTemplate) = root.toPath.relativize(myTemplate.file.toPath) diff --git a/scala3doc/src/dotty/dokka/site/common.scala b/scala3doc/src/dotty/dokka/site/common.scala index fba59dce83d6..06d77a19c5e5 100644 --- a/scala3doc/src/dotty/dokka/site/common.scala +++ b/scala3doc/src/dotty/dokka/site/common.scala @@ -23,8 +23,8 @@ import org.jetbrains.dokka.pages._ import scala.collection.JavaConverters._ -val docsRootDRI: DRI = DRI(location = "_.index.md") -val docsDRI: DRI = DRI(location = "_.docs/index.md") +val docsRootDRI: DRI = DRI(location = "index.md") +val docsDRI: DRI = DRI(location = "docs.index.md") val apiPageDRI: DRI = DRI(location = "api", extra = "__api__") val defaultMarkdownOptions: DataHolder = diff --git a/scala3doc/src/dotty/dokka/tasty/ClassLikeSupport.scala b/scala3doc/src/dotty/dokka/tasty/ClassLikeSupport.scala index 4f3fe3945859..0ec479876a2d 100644 --- a/scala3doc/src/dotty/dokka/tasty/ClassLikeSupport.scala +++ b/scala3doc/src/dotty/dokka/tasty/ClassLikeSupport.scala @@ -27,32 +27,32 @@ trait ClassLikeSupport: else Kind.Class(Nil, Nil) private def kindForClasslike(classDef: ClassDef): Kind = - def typeArgs = classDef.getTypeParams.map(mkTypeArgument) - - def parameterModifier(parameter: Symbol): String = - val fieldSymbol = classDef.symbol.declaredField(parameter.normalizedName) - def isVal = fieldSymbol.flags.is(Flags.ParamAccessor) && - !classDef.symbol.flags.is(Flags.Case) && - !fieldSymbol.flags.is(Flags.Private) - - if fieldSymbol.flags.is(Flags.Mutable) then "var " - else if isVal then "val " - else "" - - def args = if constructorWithoutParamLists(classDef) then Nil else - val constr = - Some(classDef.constructor.symbol) - .filter(s => s.exists && !s.isHiddenByVisibility) - .map( _.tree.asInstanceOf[DefDef]) - constr.fold(Nil)( - _.paramss.map(_.map(mkParameter(_, parameterModifier))) - ) + def typeArgs = classDef.getTypeParams.map(mkTypeArgument) + + def parameterModifier(parameter: Symbol): String = + val fieldSymbol = classDef.symbol.declaredField(parameter.normalizedName) + def isVal = fieldSymbol.flags.is(Flags.ParamAccessor) && + !classDef.symbol.flags.is(Flags.Case) && + !fieldSymbol.flags.is(Flags.Private) + + if fieldSymbol.flags.is(Flags.Mutable) then "var " + else if isVal then "val " + else "" + + def args = if constructorWithoutParamLists(classDef) then Nil else + val constr = + Some(classDef.constructor.symbol) + .filter(s => s.exists && !s.isHiddenByVisibility) + .map( _.tree.asInstanceOf[DefDef]) + constr.fold(Nil)( + _.paramss.map(_.map(mkParameter(_, parameterModifier))) + ) - if classDef.symbol.flags.is(Flags.Module) then Kind.Object - else if classDef.symbol.flags.is(Flags.Trait) then - Kind.Trait(typeArgs, args) - else if classDef.symbol.flags.is(Flags.Enum) then Kind.Enum - else Kind.Class(typeArgs, args) + if classDef.symbol.flags.is(Flags.Module) then Kind.Object + else if classDef.symbol.flags.is(Flags.Trait) then + Kind.Trait(typeArgs, args) + else if classDef.symbol.flags.is(Flags.Enum) then Kind.Enum + else Kind.Class(typeArgs, args) def mkClass[T >: DClass](classDef: ClassDef)( dri: DRI = classDef.symbol.dri, diff --git a/scala3doc/src/dotty/dokka/translators/ScalaPageCreator.scala b/scala3doc/src/dotty/dokka/translators/ScalaPageCreator.scala index b30db55585e0..080bc8631411 100644 --- a/scala3doc/src/dotty/dokka/translators/ScalaPageCreator.scala +++ b/scala3doc/src/dotty/dokka/translators/ScalaPageCreator.scala @@ -50,8 +50,6 @@ class ScalaPageCreator( JNil ) - override def pageForClasslike(c: DClasslike): ClasslikePageNode = ??? - def pageForMember(c: Member): ClasslikePageNode = { val name = if c.kind == Kind.Object && c.companion.isDefined then From 74c45cf407ee47507cd1a8353af1886b0c2847d3 Mon Sep 17 00:00:00 2001 From: Krzysztof Romanowski Date: Fri, 11 Dec 2020 03:39:11 +0100 Subject: [PATCH 12/12] Allow linking to any members --- .../site/StaticSiteLocationProvider.scala | 39 ++++++++++++++----- scala3doc/src/dotty/dokka/tasty/SymOps.scala | 5 ++- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/scala3doc/src/dotty/dokka/site/StaticSiteLocationProvider.scala b/scala3doc/src/dotty/dokka/site/StaticSiteLocationProvider.scala index 15f8cf3b174d..7b8cf77476fb 100644 --- a/scala3doc/src/dotty/dokka/site/StaticSiteLocationProvider.scala +++ b/scala3doc/src/dotty/dokka/site/StaticSiteLocationProvider.scala @@ -5,13 +5,14 @@ import org.jetbrains.dokka.pages.ContentPage import org.jetbrains.dokka.pages.PageNode import org.jetbrains.dokka.pages.RootPageNode import org.jetbrains.dokka.pages.ModulePage +import org.jetbrains.dokka.pages.ClasslikePageNode import org.jetbrains.dokka.model.DPackage import org.jetbrains.dokka.plugability.DokkaContext import org.jetbrains.dokka.base.resolvers.external._ import org.jetbrains.dokka.base.resolvers.shared._ import org.jetbrains.dokka.base.resolvers.local._ import org.jetbrains.dokka.model.DisplaySourceSet -import dotty.dokka.model.api.withNoOrigin +import dotty.dokka.withNoOrigin import scala.collection.JavaConverters._ import java.nio.file.Paths @@ -86,17 +87,35 @@ class StaticSiteLocationProvider(pageNode: RootPageNode)(using ctx: DokkaContext override val getPathsIndex: JMap[PageNode, JList[String]] = super.getPathsIndex.asScala.mapValuesInPlace(updatePageEntry).asJava + // We should build our own provider at some point + val ourPages: Map[String, ClasslikePageNode] = getPathsIndex.asScala.collect { + case (node: ClasslikePageNode, path) => node.getDri.asScala.head.location -> node + }.toMap + + + override def resolve( + dri: DRI, + sourceSets: JSet[DisplaySourceSet], + context: PageNode): String = + ourPages.get(dri.location).fold(super.resolve(dri, sourceSets, context)){ page => + val path = pathTo(page,context) match + case "" => "" + case path => s"$path.html" + dri.anchor.fold(path)(hash => s"$path#$hash") + } override def pathTo(node: PageNode, context: PageNode): String = - val nodePaths = getPathsIndex.get(node).asScala - val contextPaths = Option(context).fold(Nil)(getPathsIndex.get(_).asScala.dropRight(1)) - val commonPaths = nodePaths.zip(contextPaths).takeWhile{ case (a, b) => a == b }.size - - val contextPath = contextPaths.drop(commonPaths).map(_ => "..") - val nodePath = nodePaths.drop(commonPaths) match - case l if l.isEmpty => Seq("index") - case l => l - (contextPath ++ nodePath).mkString("/") + if node == context then "" + else + val nodePaths = getPathsIndex.get(node).asScala + val contextPaths = Option(context).fold(Nil)(getPathsIndex.get(_).asScala.dropRight(1)) + val commonPaths = nodePaths.zip(contextPaths).takeWhile{ case (a, b) => a == b }.size + + val contextPath = contextPaths.drop(commonPaths).map(_ => "..") + val nodePath = nodePaths.drop(commonPaths) match + case l if l.isEmpty => Seq("index") + case l => l + (contextPath ++ nodePath).mkString("/") val externalLocationProviders: List[(List[Regex], ExternalLocationProvider)] = val sourceSet = ctx.getConfiguration.getSourceSets.asScala(0) diff --git a/scala3doc/src/dotty/dokka/tasty/SymOps.scala b/scala3doc/src/dotty/dokka/tasty/SymOps.scala index 46e3bb53b656..c11be73c1628 100644 --- a/scala3doc/src/dotty/dokka/tasty/SymOps.scala +++ b/scala3doc/src/dotty/dokka/tasty/SymOps.scala @@ -128,8 +128,11 @@ class SymOps[Q <: Quotes](val q: Q): val csym = sym.asInstanceOf[dotc.core.Symbols.Symbol] Option(csym.associatedFile).map(_.path).fold("")(p => s"[origin:$p]") } + // We want package object to point to package + val className = sym.className.filter(_ != "package$") + DRI( - sym.className.fold(sym.packageName)(cn => s"${sym.packageName}.${cn}"), + className.fold(sym.packageName)(cn => s"${sym.packageName}.${cn}"), sym.anchor.getOrElse(""), // TODO do we need any of this fields? null, pointsTo,