diff --git a/scala3doc/src/dotty/dokka/model/api/api.scala b/scala3doc/src/dotty/dokka/model/api/api.scala index 4ba85604945a..ba73d137953c 100644 --- a/scala3doc/src/dotty/dokka/model/api/api.scala +++ b/scala3doc/src/dotty/dokka/model/api/api.scala @@ -49,7 +49,7 @@ enum Modifier(val name: String, val prefix: Boolean): case Opaque extends Modifier("opaque", true) case Open extends Modifier("open", true) -case class ExtensionTarget(name: String, signature: Signature, dri: DRI) +case class ExtensionTarget(name: String, signature: Signature, dri: DRI, position: Long) case class ImplicitConversion(from: DRI, to: DRI) trait ImplicitConversionProvider { def conversion: Option[ImplicitConversion] } trait Classlike diff --git a/scala3doc/src/dotty/dokka/tasty/ClassLikeSupport.scala b/scala3doc/src/dotty/dokka/tasty/ClassLikeSupport.scala index 6de72f1fb18d..6907c79bdec4 100644 --- a/scala3doc/src/dotty/dokka/tasty/ClassLikeSupport.scala +++ b/scala3doc/src/dotty/dokka/tasty/ClassLikeSupport.scala @@ -30,7 +30,7 @@ trait ClassLikeSupport: object DClass: def apply[T >: DClass](classDef: ClassDef)( dri: DRI = classDef.symbol.dri, - name: String = classDef.name, + name: String = classDef.symbol.normalizedName, signatureOnly: Boolean = false, modifiers: Seq[Modifier] = classDef.symbol.getExtraModifiers(), ): DClass = @@ -109,7 +109,12 @@ trait ClassLikeSupport: private def parseMember(s: Tree): Option[Member] = processTreeOpt(s)(s match case dd: DefDef if !dd.symbol.isHiddenByVisibility && !dd.symbol.isSyntheticFunc && dd.symbol.isExtensionMethod => dd.symbol.extendedSymbol.map { extSym => - val target = ExtensionTarget(extSym.symbol.name, extSym.tpt.dokkaType.asSignature, extSym.tpt.symbol.dri) + val target = ExtensionTarget( + extSym.symbol.normalizedName, + extSym.tpt.dokkaType.asSignature, + extSym.tpt.symbol.dri, + extSym.symbol.pos.start + ) parseMethod(dd.symbol, kind = Kind.Extension(target)) } // TODO check given methods? @@ -164,7 +169,7 @@ trait ClassLikeSupport: private def parseInheritedMember(s: Tree): Option[Member] = processTreeOpt(s)(s match case c: ClassDef if c.symbol.shouldDocumentClasslike && !c.symbol.isGiven => Some(parseClasslike(c, signatureOnly = true)) case other => parseMember(other) - ).map(_.withOrigin(Origin.InheritedFrom(s.symbol.owner.name, s.symbol.owner.dri))) + ).map(_.withOrigin(Origin.InheritedFrom(s.symbol.owner.normalizedName, s.symbol.owner.dri))) extension (c: ClassDef): def membersToDocument = c.body.filterNot(_.symbol.isHiddenByVisibility) @@ -197,7 +202,7 @@ trait ClassLikeSupport: }.toList def getParameterModifier(parameter: Symbol): String = - val fieldSymbol = c.symbol.field(parameter.name) + val fieldSymbol = c.symbol.field(parameter.normalizedName) if fieldSymbol.flags.is(Flags.Mutable) then "var " else if fieldSymbol.flags.is(Flags.ParamAccessor) && !c.symbol.flags.is(Flags.Case) && !fieldSymbol.flags.is(Flags.Private) then "val " else "" @@ -221,7 +226,6 @@ trait ClassLikeSupport: def parseObject(classDef: ClassDef, signatureOnly: Boolean = false)(using ctx: Context): DClass = DClass(classDef)( - name = classDef.name.stripSuffix("$"), // All objects are final so we do not need final modifer! modifiers = classDef.symbol.getExtraModifiers().filter(_ != Modifier.Final), signatureOnly = signatureOnly @@ -266,11 +270,7 @@ trait ClassLikeSupport: Kind.Implicit(Kind.Def, None) else kind - val name = methodKind match - case Kind.Constructor => "this" - case Kind.Given(_, _) => methodSymbol.name.stripPrefix("given_") - case Kind.Extension(_) => methodSymbol.name.stripPrefix("extension_") - case _ => methodSymbol.name + val name = method.symbol.normalizedName new DFunction( methodSymbol.dri, @@ -302,7 +302,7 @@ trait ClassLikeSupport: def parseArgument(argument: ValDef, prefix: Symbol => String, isExtendedSymbol: Boolean = false, isGrouped: Boolean = false): DParameter = new DParameter( argument.symbol.dri, - prefix(argument.symbol) + argument.symbol.name, + prefix(argument.symbol) + argument.symbol.normalizedName, argument.symbol.documentation.asJava, null, argument.tpt.dokkaType, @@ -320,7 +320,7 @@ trait ClassLikeSupport: else "" new DTypeParameter( - Invariance(TypeParameter(argument.symbol.dri, variancePrefix + argument.symbol.name, null)), + Invariance(TypeParameter(argument.symbol.dri, variancePrefix + argument.symbol.normalizedName, null)), argument.symbol.documentation.asJava, null, JList(argument.rhs.dokkaType), @@ -343,7 +343,7 @@ trait ClassLikeSupport: new DProperty( typeDef.symbol.dri, - typeDef.name, + typeDef.symbol.normalizedName, /*documentation =*/ typeDef.symbol.documentation.asJava, /*expectPresentInSet =*/ null, // unused /*sources =*/ JMap(), @@ -374,7 +374,7 @@ trait ClassLikeSupport: new DProperty( valDef.symbol.dri, - valDef.name, + valDef.symbol.normalizedName, /*documentation =*/ valDef.symbol.documentation.asJava, /*expectPresentInSet =*/ null, // unused /*sources =*/ JMap(), diff --git a/scala3doc/src/dotty/dokka/tasty/NameNormalizer.scala b/scala3doc/src/dotty/dokka/tasty/NameNormalizer.scala new file mode 100644 index 000000000000..a5ffdf900910 --- /dev/null +++ b/scala3doc/src/dotty/dokka/tasty/NameNormalizer.scala @@ -0,0 +1,13 @@ +package dotty.dokka.tasty + +import dotty.dokka._ + +trait NameNormalizer { self: TastyParser => + import qctx.reflect._ + extension (s: Symbol) def normalizedName: String = { + val withoutGivenPrefix = if s.isGiven then s.name.stripPrefix("given_") else s.name + val withoutObjectSuffix = if s.flags.is(Flags.Object) then withoutGivenPrefix.stripSuffix("$") else withoutGivenPrefix + val constructorNormalizedName = if s.isClassConstructor then "this" else withoutObjectSuffix + constructorNormalizedName + } +} diff --git a/scala3doc/src/dotty/dokka/tasty/TastyParser.scala b/scala3doc/src/dotty/dokka/tasty/TastyParser.scala index 67985b4626b9..f5fad4985a31 100644 --- a/scala3doc/src/dotty/dokka/tasty/TastyParser.scala +++ b/scala3doc/src/dotty/dokka/tasty/TastyParser.scala @@ -157,7 +157,7 @@ trait DokkaBaseTastyInspector: /** Parses a single Tasty compilation unit. */ case class TastyParser(qctx: QuoteContext, inspector: DokkaBaseTastyInspector, config: DottyDokkaConfig) - extends ScaladocSupport with BasicSupport with TypesSupport with ClassLikeSupport with SyntheticsSupport with PackageSupport: + extends ScaladocSupport with BasicSupport with TypesSupport with ClassLikeSupport with SyntheticsSupport with PackageSupport with NameNormalizer: import qctx.reflect._ def sourceSet = inspector.sourceSet @@ -168,7 +168,7 @@ case class TastyParser(qctx: QuoteContext, inspector: DokkaBaseTastyInspector, c private def errorMsg[T](a: Any, m: => String, e: Throwable): Option[T] = val msg = try m catch case e: Throwable => a.toString - println(s"ERROR: tree is faling: msg") + println(s"ERROR: tree is faling: $msg") e.printStackTrace() throw e diff --git a/scala3doc/src/dotty/dokka/tasty/TypesSupport.scala b/scala3doc/src/dotty/dokka/tasty/TypesSupport.scala index ccdeb4f6bba9..140aab376625 100644 --- a/scala3doc/src/dotty/dokka/tasty/TypesSupport.scala +++ b/scala3doc/src/dotty/dokka/tasty/TypesSupport.scala @@ -61,7 +61,7 @@ trait TypesSupport: private def link(symbol: Symbol)(using cxt: Context): List[JProjection] = { val suffix = if symbol.isValDef then texts(".type") else Nil - (new TypeParameter(symbol.dri, symbol.name, null)) :: suffix + (new TypeParameter(symbol.dri, symbol.normalizedName, null)) :: suffix } private def commas(lists: List[List[JProjection]]) = lists match diff --git a/scala3doc/src/dotty/dokka/transformers/ImplicitMembersExtensionTransformer.scala b/scala3doc/src/dotty/dokka/transformers/ImplicitMembersExtensionTransformer.scala index cac96da63409..d54791474999 100644 --- a/scala3doc/src/dotty/dokka/transformers/ImplicitMembersExtensionTransformer.scala +++ b/scala3doc/src/dotty/dokka/transformers/ImplicitMembersExtensionTransformer.scala @@ -14,18 +14,25 @@ import dotty.dokka.model.api._ class ImplicitMembersExtensionTransformer(ctx: DokkaContext) 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 expandMember(outerMembers: Seq[Member])(c: Member): Member = - val companion = c match - case classlike: DClass => ClasslikeExtension.getFrom(classlike).flatMap(_.companion).flatMap(classlikeMap.get) - case _ => None + val companion = retrieveCompanion(c) val allParents = c.parents.flatMap(p => classlikeMap.get(p.dri)) - val parentCompanions = allParents.flatMap { - case cls: DClasslike => ClasslikeExtension.getFrom(cls).flatMap(_.companion).flatMap(classlikeMap.get) - case _ => None - } + val parentCompanions = allParents.flatMap(retrieveCompanion) // TODO (#220): We can expand this on generic etc val implictSources = outerMembers ++ companion.toSeq ++ parentCompanions @@ -34,7 +41,7 @@ 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.DefinedWithin) => + case m @ Member(_, _, _, Kind.Extension(ExtensionTarget(_, _, MyDri, _)), Origin.DefinedWithin) => Seq(m.withOrigin(Origin.ExtensionFrom(source.name, source.dri)).withKind(Kind.Def)) case m @ Member(_, _, _, conversionProvider: ImplicitConversionProvider, Origin.DefinedWithin) => conversionProvider.conversion match diff --git a/scala3doc/src/dotty/dokka/translators/ScalaContentBuilder.scala b/scala3doc/src/dotty/dokka/translators/ScalaContentBuilder.scala index e15296aa88a8..466da52762e6 100644 --- a/scala3doc/src/dotty/dokka/translators/ScalaContentBuilder.scala +++ b/scala3doc/src/dotty/dokka/translators/ScalaContentBuilder.scala @@ -248,6 +248,14 @@ class ScalaPageContentBuilder( def signature(d: Documentable) = addChildren(signatureProvider.signature(d).asScala.toList) + private def buildSignature(d: Documentable, s: Signature) = signatureProvider.asInstanceOf[ScalaSignatureProvider].signature(d, s) + + def signature(d: Documentable, s: Signature) = addChild(buildSignature(d, s)) + + def inlineSignature(d: Documentable, s: Signature) = addChildren( + buildSignature(d, s).getChildren.asScala.toSeq + ) + def defaultHeaders = List( contentForDRIs( dris = mainDRI, diff --git a/scala3doc/src/dotty/dokka/translators/ScalaPageCreator.scala b/scala3doc/src/dotty/dokka/translators/ScalaPageCreator.scala index 6f2fabeaf713..0965a5c18d44 100644 --- a/scala3doc/src/dotty/dokka/translators/ScalaPageCreator.scala +++ b/scala3doc/src/dotty/dokka/translators/ScalaPageCreator.scala @@ -317,12 +317,20 @@ class ScalaPageCreator( case _ => withNamedTags } + 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 + } + d match - case null => withCompanion + case null => withExtensionInformation case m: Member => sourceLinks.pathTo(m).fold(withCompanion){ link => val sourceSets = m.getSourceSets.asScala.toSet - withCompanion.cell(sourceSets = sourceSets)(_.text("Source")) + withExtensionInformation.cell(sourceSets = sourceSets)(_.text("Source")) .cell(sourceSets = sourceSets)(_.resolvedLink("(source)", link)) } } diff --git a/scala3doc/src/dotty/dokka/translators/ScalaSignatureProvider.scala b/scala3doc/src/dotty/dokka/translators/ScalaSignatureProvider.scala index 35911cbd1e18..dafac1e8690b 100644 --- a/scala3doc/src/dotty/dokka/translators/ScalaSignatureProvider.scala +++ b/scala3doc/src/dotty/dokka/translators/ScalaSignatureProvider.scala @@ -31,7 +31,12 @@ class ScalaSignatureProvider(contentConverter: CommentsToContentConverter, logge def driLink(text: String, dri: DRI): SignatureBuilder = ContentNodeBuilder(builder.driLink(text, dri)) } - override def signature(documentable: Documentable) = + def signature(d: Member, s: Signature) = signatureContent(d){ builder => + val res = ContentNodeBuilder(builder).signature(s) + res.asInstanceOf[ContentNodeBuilder].builder + } + + override def signature(documentable: Member) = JList(signatureContent(documentable){ builder => val withAnnotations = ContentNodeBuilder(builder).annotationsBlock(documentable) val res = ScalaSignatureProvider.rawSignature(documentable, withAnnotations)