summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/compiler/scala/tools/nsc/doc/DocFactory.scala7
-rw-r--r--src/compiler/scala/tools/nsc/doc/Settings.scala7
-rw-r--r--src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala45
-rw-r--r--src/compiler/scala/tools/nsc/doc/html/page/Template.scala23
-rw-r--r--src/compiler/scala/tools/nsc/doc/model/Entity.scala19
-rw-r--r--src/compiler/scala/tools/nsc/doc/model/Links.scala24
-rw-r--r--src/compiler/scala/tools/nsc/doc/model/MemberLookup.scala185
-rw-r--r--src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala67
-rw-r--r--src/compiler/scala/tools/nsc/doc/model/ModelFactoryTypeSupport.scala4
-rw-r--r--src/compiler/scala/tools/nsc/doc/model/TypeEntity.scala5
-rw-r--r--src/compiler/scala/tools/nsc/doc/model/comment/Body.scala7
-rw-r--r--src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala55
-rw-r--r--src/partest/scala/tools/partest/ScaladocModelTest.scala13
-rw-r--r--test/scaladoc/resources/links.scala57
-rw-r--r--test/scaladoc/run/SI-5235.scala4
-rw-r--r--test/scaladoc/run/links.check1
-rw-r--r--test/scaladoc/run/links.scala28
-rw-r--r--test/scaladoc/scalacheck/CommentFactoryTest.scala16
18 files changed, 459 insertions, 108 deletions
diff --git a/src/compiler/scala/tools/nsc/doc/DocFactory.scala b/src/compiler/scala/tools/nsc/doc/DocFactory.scala
index 964227a6a5..9fccc57e44 100644
--- a/src/compiler/scala/tools/nsc/doc/DocFactory.scala
+++ b/src/compiler/scala/tools/nsc/doc/DocFactory.scala
@@ -48,8 +48,8 @@ class DocFactory(val reporter: Reporter, val settings: doc.Settings) { processor
/** Creates a scaladoc site for all symbols defined in this call's `source`,
* as well as those defined in `sources` of previous calls to the same processor.
- * @param files The list of paths (relative to the compiler's source path,
- * or absolute) of files to document. */
+ * @param source The list of paths (relative to the compiler's source path,
+ * or absolute) of files to document or the source code. */
def makeUniverse(source: Either[List[String], String]): Option[Universe] = {
assert(settings.docformat.value == "html")
source match {
@@ -84,7 +84,8 @@ class DocFactory(val reporter: Reporter, val settings: doc.Settings) { processor
with model.ModelFactoryTypeSupport
with model.diagram.DiagramFactory
with model.comment.CommentFactory
- with model.TreeFactory {
+ with model.TreeFactory
+ with model.MemberLookup {
override def templateShouldDocument(sym: compiler.Symbol, inTpl: TemplateImpl) =
extraTemplatesToDocument(sym) || super.templateShouldDocument(sym, inTpl)
}
diff --git a/src/compiler/scala/tools/nsc/doc/Settings.scala b/src/compiler/scala/tools/nsc/doc/Settings.scala
index aae88aa03e..da478121e5 100644
--- a/src/compiler/scala/tools/nsc/doc/Settings.scala
+++ b/src/compiler/scala/tools/nsc/doc/Settings.scala
@@ -171,6 +171,11 @@ class Settings(error: String => Unit, val printMsg: String => Unit = println(_))
"Prevents generating prefixes in types, possibly creating ambiguous references, but significantly speeding up scaladoc."
)
+ val docNoLinkWarnings = BooleanSetting (
+ "-no-link-warnings",
+ "Avoid warnings for ambiguous and incorrect links."
+ )
+
// Somewhere slightly before r18708 scaladoc stopped building unless the
// self-type check was suppressed. I hijacked the slotted-for-removal-anyway
// suppress-vt-warnings option and renamed it for this purpose.
@@ -183,7 +188,7 @@ class Settings(error: String => Unit, val printMsg: String => Unit = println(_))
docDiagramsDotTimeout, docDiagramsDotRestart,
docImplicits, docImplicitsDebug, docImplicitsShowAll,
docDiagramsMaxNormalClasses, docDiagramsMaxImplicitClasses,
- docNoPrefixes
+ docNoPrefixes, docNoLinkWarnings, docRawOutput
)
val isScaladocSpecific: String => Boolean = scaladocSpecific map (_.name)
diff --git a/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala b/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala
index af5e90083e..226ed49aca 100644
--- a/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala
+++ b/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala
@@ -122,17 +122,30 @@ abstract class HtmlPage extends Page { thisPage =>
case Text(text) => xml.Text(text)
case Summary(in) => inlineToHtml(in)
case HtmlTag(tag) => xml.Unparsed(tag)
- case EntityLink(target, template) => template() match {
- case Some(tpl) =>
- templateToHtml(tpl)
- case None =>
- xml.Text(target)
- }
+ case EntityLink(target, link) => linkToHtml(target, link, true)
+ }
+
+ def linkToHtml(text: Inline, link: LinkTo, hasLinks: Boolean) = link match {
+ case LinkToTpl(dtpl) =>
+ if (hasLinks)
+ <a href={ relativeLinkTo(dtpl) } class="extype" name={ dtpl.qualifiedName }>{ inlineToHtml(text) }</a>
+ else
+ <span class="extype" name={ dtpl.qualifiedName }>{ inlineToHtml(text) }</span>
+ case LinkToMember(mbr, inTpl) =>
+ if (hasLinks)
+ <a href={ relativeLinkTo(inTpl) + "#" + mbr.signature } class="extmbr" name={ mbr.qualifiedName }>{ inlineToHtml(text) }</a>
+ else
+ <span class="extmbr" name={ mbr.qualifiedName }>{ inlineToHtml(text) }</span>
+ case Tooltip(tooltip) =>
+ <span class="extype" name={ tooltip }>{ inlineToHtml(text) }</span>
+ // TODO: add case LinkToExternal here
+ case NoLink =>
+ inlineToHtml(text)
}
def typeToHtml(tpes: List[model.TypeEntity], hasLinks: Boolean): NodeSeq = tpes match {
case Nil =>
- sys.error("Internal Scaladoc error")
+ NodeSeq.Empty
case List(tpe) =>
typeToHtml(tpe, hasLinks)
case tpe :: rest =>
@@ -153,21 +166,9 @@ abstract class HtmlPage extends Page { thisPage =>
}
}
def toLinksIn(inPos: Int, starts: List[Int]): NodeSeq = {
- val (tpl, width) = tpe.refEntity(inPos)
- (tpl match {
- case LinkToTpl(dtpl:DocTemplateEntity) if hasLinks =>
- <a href={ relativeLinkTo(dtpl) } class="extype" name={ dtpl.qualifiedName }>{
- string.slice(inPos, inPos + width)
- }</a>
- case LinkToTpl(tpl) =>
- <span class="extype" name={ tpl.qualifiedName }>{ string.slice(inPos, inPos + width) }</span>
- case LinkToMember(mbr, inTpl) if hasLinks =>
- <a href={ relativeLinkTo(inTpl) + "#" + mbr.signature } class="extmbr" name={ mbr.qualifiedName }>{
- string.slice(inPos, inPos + width)
- }</a>
- case LinkToMember(mbr, inTpl) =>
- <span class="extmbr" name={ mbr.qualifiedName }>{ string.slice(inPos, inPos + width) }</span>
- }) ++ toLinksOut(inPos + width, starts.tail)
+ val (link, width) = tpe.refEntity(inPos)
+ val text = comment.Text(string.slice(inPos, inPos + width))
+ linkToHtml(text, link, hasLinks) ++ toLinksOut(inPos + width, starts.tail)
}
if (hasLinks)
toLinksOut(0, tpe.refEntity.keySet.toList)
diff --git a/src/compiler/scala/tools/nsc/doc/html/page/Template.scala b/src/compiler/scala/tools/nsc/doc/html/page/Template.scala
index bba838ddcf..cfd50db99f 100644
--- a/src/compiler/scala/tools/nsc/doc/html/page/Template.scala
+++ b/src/compiler/scala/tools/nsc/doc/html/page/Template.scala
@@ -64,7 +64,7 @@ class Template(universe: doc.Universe, generator: DiagramGenerator, tpl: DocTemp
nonAbsValueMembers partition (_.deprecation.isDefined)
val (concValueMembers, shadowedImplicitMembers) =
- nonDeprValueMembers partition (!Template.isShadowedOrAmbiguousImplicit(_))
+ nonDeprValueMembers partition (!_.isShadowedOrAmbiguousImplicit)
val typeMembers =
tpl.abstractTypes ++ tpl.aliasTypes ++ tpl.templates.filter(x => x.isTrait || x.isClass) sorted
@@ -387,7 +387,7 @@ class Template(universe: doc.Universe, generator: DiagramGenerator, tpl: DocTemp
{ constraintText }
</dd>
} ++ {
- if (Template.isShadowedOrAmbiguousImplicit(mbr)) {
+ if (mbr.isShadowedOrAmbiguousImplicit) {
// These are the members that are shadowing or ambiguating the current implicit
// see ImplicitMemberShadowing trait for more information
val shadowingSuggestion = {
@@ -402,10 +402,10 @@ class Template(universe: doc.Universe, generator: DiagramGenerator, tpl: DocTemp
}
val shadowingWarning: NodeSeq =
- if (Template.isShadowedImplicit(mbr))
+ if (mbr.isShadowedImplicit)
xml.Text("This implicitly inherited member is shadowed by one or more members in this " +
"class.") ++ shadowingSuggestion
- else if (Template.isAmbiguousImplicit(mbr))
+ else if (mbr.isAmbiguousImplicit)
xml.Text("This implicitly inherited member is ambiguous. One or more implicitly " +
"inherited members have similar signatures, so calling this member may produce an ambiguous " +
"implicit conversion compiler error.") ++ shadowingSuggestion
@@ -646,13 +646,13 @@ class Template(universe: doc.Universe, generator: DiagramGenerator, tpl: DocTemp
case PrivateInTemplate(owner) if (owner == mbr.inTemplate) =>
Some(Paragraph(CText("private")))
case PrivateInTemplate(owner) =>
- Some(Paragraph(Chain(List(CText("private["), EntityLink(owner.qualifiedName, () => Some(owner)), CText("]")))))
+ Some(Paragraph(Chain(List(CText("private["), EntityLink(comment.Text(owner.qualifiedName), LinkToTpl(owner)), CText("]")))))
case ProtectedInInstance() =>
Some(Paragraph(CText("protected[this]")))
case ProtectedInTemplate(owner) if (owner == mbr.inTemplate) =>
Some(Paragraph(CText("protected")))
case ProtectedInTemplate(owner) =>
- Some(Paragraph(Chain(List(CText("protected["), EntityLink(owner.qualifiedName, () => Some(owner)), CText("]")))))
+ Some(Paragraph(Chain(List(CText("protected["), EntityLink(comment.Text(owner.qualifiedName), LinkToTpl(owner)), CText("]")))))
case Public() =>
None
}
@@ -669,8 +669,8 @@ class Template(universe: doc.Universe, generator: DiagramGenerator, tpl: DocTemp
<span class="symbol">
{
val nameClass =
- if (Template.isImplicit(mbr))
- if (Template.isShadowedOrAmbiguousImplicit(mbr))
+ if (mbr.isImplicitlyInherited)
+ if (mbr.isShadowedOrAmbiguousImplicit)
"implicit shadowed"
else
"implicit"
@@ -934,12 +934,5 @@ object Template {
/* Vlad: Lesson learned the hard way: don't put any stateful code that references the model here,
* it won't be garbage collected and you'll end up filling the heap with garbage */
- def isImplicit(mbr: MemberEntity) = mbr.byConversion.isDefined
- def isShadowedImplicit(mbr: MemberEntity): Boolean =
- mbr.byConversion.map(_.source.implicitsShadowing.get(mbr).map(_.isShadowed).getOrElse(false)).getOrElse(false)
- def isAmbiguousImplicit(mbr: MemberEntity): Boolean =
- mbr.byConversion.map(_.source.implicitsShadowing.get(mbr).map(_.isAmbiguous).getOrElse(false)).getOrElse(false)
- def isShadowedOrAmbiguousImplicit(mbr: MemberEntity) = isShadowedImplicit(mbr) || isAmbiguousImplicit(mbr)
-
def lowerFirstLetter(s: String) = if (s.length >= 1) s.substring(0,1).toLowerCase() + s.substring(1) else s
}
diff --git a/src/compiler/scala/tools/nsc/doc/model/Entity.scala b/src/compiler/scala/tools/nsc/doc/model/Entity.scala
index 41ba95e072..42db408fee 100644
--- a/src/compiler/scala/tools/nsc/doc/model/Entity.scala
+++ b/src/compiler/scala/tools/nsc/doc/model/Entity.scala
@@ -56,6 +56,12 @@ trait Entity {
/** Whether or not the template was defined in a package object */
def inPackageObject: Boolean
+
+ /** Indicates whether this entity lives in the types namespace (classes, traits, abstract/alias types) */
+ def isType: Boolean
+
+ /** Indicates whether this entity lives in the terms namespace (objects, packages, methods, values) */
+ def isTerm: Boolean
}
object Entity {
@@ -183,6 +189,19 @@ trait MemberEntity extends Entity {
/** The identity of this member, used for linking */
def signature: String
+
+ /** Indicates whether the member is inherited by implicit conversion */
+ def isImplicitlyInherited: Boolean
+
+ /** Indicates whether there is another member with the same name in the template that will take precendence */
+ def isShadowedImplicit: Boolean
+
+ /** Indicates whether there are other implicitly inherited members that have similar signatures (and thus they all
+ * become ambiguous) */
+ def isAmbiguousImplicit: Boolean
+
+ /** Indicates whether the implicitly inherited member is shadowed or ambiguous in its template */
+ def isShadowedOrAmbiguousImplicit: Boolean
}
object MemberEntity {
diff --git a/src/compiler/scala/tools/nsc/doc/model/Links.scala b/src/compiler/scala/tools/nsc/doc/model/Links.scala
new file mode 100644
index 0000000000..b76dee0f14
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/doc/model/Links.scala
@@ -0,0 +1,24 @@
+/* NSC -- new Scala compiler
+ * Copyright 2007-2011 LAMP/EPFL
+ */
+
+package scala.tools.nsc
+package doc
+package model
+
+import scala.collection._
+
+abstract sealed class LinkTo
+case class LinkToTpl(tpl: DocTemplateEntity) extends LinkTo
+case class LinkToMember(mbr: MemberEntity, inTpl: DocTemplateEntity) extends LinkTo
+case class Tooltip(name: String) extends LinkTo { def this(tpl: TemplateEntity) = this(tpl.qualifiedName) }
+// case class LinkToExternal(name: String, url: String) extends LinkTo // for SI-191, whenever Manohar will have time
+case object NoLink extends LinkTo // you should use Tooltip if you have a name from the user, this is only in case all fails
+
+object LinkToTpl {
+ // this makes it easier to create links
+ def apply(tpl: TemplateEntity) = tpl match {
+ case dtpl: DocTemplateEntity => new LinkToTpl(dtpl)
+ case ntpl: TemplateEntity => new Tooltip(ntpl.qualifiedName)
+ }
+}
diff --git a/src/compiler/scala/tools/nsc/doc/model/MemberLookup.scala b/src/compiler/scala/tools/nsc/doc/model/MemberLookup.scala
new file mode 100644
index 0000000000..7b131c13ef
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/doc/model/MemberLookup.scala
@@ -0,0 +1,185 @@
+package scala.tools.nsc
+package doc
+package model
+
+import comment._
+
+import scala.reflect.internal.util.FakePos //Position
+
+/** This trait extracts all required information for documentation from compilation units */
+trait MemberLookup {
+ thisFactory: ModelFactory =>
+
+ import global._
+
+ def memberLookup(pos: Position, query: String, inTplOpt: Option[DocTemplateImpl]): LinkTo = {
+ assert(modelFinished)
+
+ var members = breakMembers(query)
+ //println(query + " => " + members)
+
+ // (1) Lookup in the root package, as most of the links are qualified
+ var linkTo: List[LinkTo] = lookupInRootPackage(pos, members)
+
+ // (2) Recursively go into each
+ if (inTplOpt.isDefined) {
+ var currentTpl = inTplOpt.get
+ while (currentTpl != null && !currentTpl.isRootPackage && (linkTo.isEmpty)) {
+ linkTo = lookupInTemplate(pos, members, currentTpl)
+ currentTpl = currentTpl.inTemplate
+ }
+ if (currentTpl == null) println("\n\n\n\n\nnull found in:" + inTplOpt + "\n\n\n\n\n\n\n\n")
+ }
+
+ // (3) Look at external links
+ if (linkTo.isEmpty) {
+ // TODO: IF THIS IS THE ROOT PACKAGE, LOOK AT EXTERNAL LINKS
+ }
+
+ // (4) if we still haven't found anything, create a tooltip, if we found too many, report
+ if (linkTo.isEmpty){
+ if (!settings.docNoLinkWarnings.value)
+ reporter.warning(pos, "Could not find any member to link for \"" + query + "\".")
+ Tooltip(query)
+ } else {
+ if (linkTo.length > 1) {
+
+ val chosen =
+ if (linkTo.exists(_.isInstanceOf[LinkToMember]))
+ linkTo.collect({case lm: LinkToMember => lm}).min(Ordering[MemberEntity].on[LinkToMember](_.mbr))
+ else
+ linkTo.head
+
+ def linkToString(link: LinkTo) = {
+ val description =
+ link match {
+ case lm@LinkToMember(mbr, inTpl) => " * " + mbr.kind + " \"" + mbr.signature + "\" in " + inTpl.kind + " " + inTpl.qualifiedName
+ case lt@LinkToTpl(tpl) => " * " + tpl.kind + " \"" + tpl.qualifiedName + "\""
+ case other => " * " + other.toString
+ }
+ val chosenInfo =
+ if (link == chosen)
+ " [chosen]"
+ else
+ ""
+ description + chosenInfo + "\n"
+ }
+ if (!settings.docNoLinkWarnings.value)
+ reporter.warning(pos,
+ "The link target \"" + query + "\" is ambiguous. Several (possibly overloaded) members fit the target:\n" +
+ linkTo.map(link => linkToString(link)).mkString +
+ (if (MemberLookup.showExplanation)
+ "\n\n" +
+ "Quick crash course on using Scaladoc links\n" +
+ "==========================================\n" +
+ "Disambiguating terms and types: Prefix terms with '$' and types with '!' in case both names are in use:\n" +
+ " - [[scala.collection.immutable.List!.apply class List's apply method]] and\n" +
+ " - [[scala.collection.immutable.List$.apply object List's apply method]]\n" +
+ "Disambiguating overloaded members: If a term is overloaded, you can indicate the first part of its signature followed by *:\n" +
+ " - [[[scala.collection.immutable.List$.fill[A](Int)(⇒A):List[A]* Fill with a single parameter]]]\n" +
+ " - [[[scala.collection.immutable.List$.fill[A](Int,Int)(⇒A):List[List[A]]* Fill with a two parameters]]]\n" +
+ "Notes: \n" +
+ " - you can use any number of matching square brackets to avoid interference with the signature\n" +
+ " - you can use \\. to escape dots in prefixes (don't forget to use * at the end to match the signature!)\n" +
+ " - you can use \\# to escape hashes, otherwise they will be considered as delimiters, like dots.\n"
+ else "")
+ )
+ chosen
+ } else
+ linkTo.head
+ }
+ }
+
+ private abstract class SearchStrategy
+ private object BothTypeAndTerm extends SearchStrategy
+ private object OnlyType extends SearchStrategy
+ private object OnlyTerm extends SearchStrategy
+
+ private def lookupInRootPackage(pos: Position, members: List[String]) = lookupInTemplate(pos, members, makeRootPackage)
+
+ private def lookupInTemplate(pos: Position, members: List[String], inTpl: DocTemplateImpl): List[LinkTo] = {
+ // Maintaining compatibility with previous links is a bit tricky here:
+ // we have a preference for term names for all terms except for the last, where we prefer a class:
+ // How to do this:
+ // - at each step we do a DFS search with the prefered strategy
+ // - if the search doesn't return any members, we backtrack on the last decision
+ // * we look for terms with the last member's name
+ // * we look for types with the same name, all the way up
+ val result = members match {
+ case Nil =>
+ Nil
+ case mbrName::Nil =>
+ var members = lookupInTemplate(pos, mbrName, inTpl, OnlyType)
+ if (members.isEmpty)
+ members = lookupInTemplate(pos, mbrName, inTpl, OnlyTerm)
+
+ members.map(_ match {
+ case tpl: DocTemplateEntity => LinkToTpl(tpl)
+ case mbr => LinkToMember(mbr, inTpl)
+ })
+
+ case tplName::rest =>
+
+ def completeSearch(mbrs: List[MemberImpl]) =
+ mbrs.collect({case d:DocTemplateImpl => d}).flatMap(tpl => lookupInTemplate(pos, rest, tpl))
+
+ var members = completeSearch(lookupInTemplate(pos, tplName, inTpl, OnlyTerm))
+ if (members.isEmpty)
+ members = completeSearch(lookupInTemplate(pos, tplName, inTpl, OnlyType))
+
+ members
+ }
+ //println("lookupInTemplate(" + members + ", " + inTpl + ") => " + result)
+ result
+ }
+
+ private def lookupInTemplate(pos: Position, member: String, inTpl: DocTemplateImpl, strategy: SearchStrategy): List[MemberImpl] = {
+ val name = member.stripSuffix("$").stripSuffix("!").stripSuffix("*")
+ val result = if (member.endsWith("$"))
+ inTpl.members.filter(mbr => (mbr.name == name) && (mbr.isTerm))
+ else if (member.endsWith("!"))
+ inTpl.members.filter(mbr => (mbr.name == name) && (mbr.isType))
+ else if (member.endsWith("*"))
+ inTpl.members.filter(mbr => (mbr.signature.startsWith(name)))
+ else {
+ if (strategy == BothTypeAndTerm)
+ inTpl.members.filter(_.name == name)
+ else if (strategy == OnlyType)
+ inTpl.members.filter(mbr => (mbr.name == name) && (mbr.isType))
+ else if (strategy == OnlyTerm)
+ inTpl.members.filter(mbr => (mbr.name == name) && (mbr.isTerm))
+ else
+ Nil
+ }
+
+ //println("lookupInTemplate(" + member + ", " + inTpl + ") => " + result)
+ result
+ }
+
+ private def breakMembers(query: String): List[String] = {
+ // Okay, how does this work? Well: you split on . but you don't want to split on \. => thus the ugly regex
+ // query.split((?<=[^\\\\])\\.).map(_.replaceAll("\\."))
+ // The same code, just faster:
+ var members = List[String]()
+ var index = 0
+ var last_index = 0
+ val length = query.length
+ while (index < length) {
+ if ((query.charAt(index) == '.' || query.charAt(index) == '#') &&
+ ((index == 0) || (query.charAt(index-1) != '\\'))) {
+
+ members ::= query.substring(last_index, index).replaceAll("\\\\([#\\.])", "$1")
+ last_index = index + 1
+ }
+ index += 1
+ }
+ if (last_index < length)
+ members ::= query.substring(last_index, length).replaceAll("\\\\\\.", ".")
+ members.reverse
+ }
+}
+
+object MemberLookup {
+ private[this] var _showExplanation = true
+ def showExplanation: Boolean = if (_showExplanation) { _showExplanation = false; true } else false
+} \ No newline at end of file
diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala
index 2ce7927f42..2642551aa8 100644
--- a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala
+++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala
@@ -24,7 +24,8 @@ class ModelFactory(val global: Global, val settings: doc.Settings) {
with ModelFactoryTypeSupport
with DiagramFactory
with CommentFactory
- with TreeFactory =>
+ with TreeFactory
+ with MemberLookup =>
import global._
import definitions.{ ObjectClass, NothingClass, AnyClass, AnyValClass, AnyRefClass }
@@ -92,6 +93,8 @@ class ModelFactory(val global: Global, val settings: doc.Settings) {
def qualifiedName = name
def annotations = sym.annotations.map(makeAnnotation)
def inPackageObject: Boolean = sym.owner.isModuleClass && sym.owner.sourceModule.isPackageObject
+ def isType = sym.name.isTypeName
+ def isTerm = sym.name.isTermName
}
trait TemplateImpl extends EntityImpl with TemplateEntity {
@@ -108,7 +111,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) {
abstract class MemberImpl(sym: Symbol, implConv: ImplicitConversionImpl, inTpl: DocTemplateImpl) extends EntityImpl(sym, inTpl) with MemberEntity {
lazy val comment = {
- val commentTpl =
+ val inRealTpl =
/* Variable precendence order for implicitly added members: Take the variable defifinitions from ...
* 1. the target of the implicit conversion
* 2. the definition template (owner)
@@ -121,7 +124,11 @@ class ModelFactory(val global: Global, val settings: doc.Settings) {
case _ => inTpl
}
} else inTpl
- if (commentTpl != null) thisFactory.comment(sym, commentTpl) else None
+ val thisTpl = this match {
+ case d: DocTemplateImpl => Some(d)
+ case _ => None
+ }
+ if (inRealTpl != null) thisFactory.comment(sym, thisTpl, inRealTpl) else None
}
override def inTemplate = inTpl
override def toRoot: List[MemberImpl] = this :: inTpl.toRoot
@@ -167,9 +174,9 @@ class ModelFactory(val global: Global, val settings: doc.Settings) {
def deprecation =
if (sym.isDeprecated)
Some((sym.deprecationMessage, sym.deprecationVersion) match {
- case (Some(msg), Some(ver)) => parseWiki("''(Since version " + ver + ")'' " + msg, NoPosition)
- case (Some(msg), None) => parseWiki(msg, NoPosition)
- case (None, Some(ver)) => parseWiki("''(Since version " + ver + ")''", NoPosition)
+ case (Some(msg), Some(ver)) => parseWiki("''(Since version " + ver + ")'' " + msg, NoPosition, Some(inTpl))
+ case (Some(msg), None) => parseWiki(msg, NoPosition, Some(inTpl))
+ case (None, Some(ver)) => parseWiki("''(Since version " + ver + ")''", NoPosition, Some(inTpl))
case (None, None) => Body(Nil)
})
else
@@ -177,9 +184,9 @@ class ModelFactory(val global: Global, val settings: doc.Settings) {
def migration =
if(sym.hasMigrationAnnotation)
Some((sym.migrationMessage, sym.migrationVersion) match {
- case (Some(msg), Some(ver)) => parseWiki("''(Changed in version " + ver + ")'' " + msg, NoPosition)
- case (Some(msg), None) => parseWiki(msg, NoPosition)
- case (None, Some(ver)) => parseWiki("''(Changed in version " + ver + ")''", NoPosition)
+ case (Some(msg), Some(ver)) => parseWiki("''(Changed in version " + ver + ")'' " + msg, NoPosition, Some(inTpl))
+ case (Some(msg), None) => parseWiki(msg, NoPosition, Some(inTpl))
+ case (None, Some(ver)) => parseWiki("''(Changed in version " + ver + ")''", NoPosition, Some(inTpl))
case (None, None) => Body(Nil)
})
else
@@ -213,28 +220,34 @@ class ModelFactory(val global: Global, val settings: doc.Settings) {
def byConversion = if (implConv ne null) Some(implConv) else None
lazy val signature = {
- val defParamsString = this match {
+ def defParams(mbr: Any): String = mbr match {
case d: MemberEntity with Def =>
val paramLists: List[String] =
- if (d.valueParams.isEmpty) Nil
- else d.valueParams map (ps => ps map (_.resultType.name) mkString ("(",",",")"))
-
- val tParams = if (d.typeParams.isEmpty) "" else {
- def boundsToString(hi: Option[TypeEntity], lo: Option[TypeEntity]): String = {
- def bound0(bnd: Option[TypeEntity], pre: String): String = bnd match {
- case None => ""
- case Some(tpe) => pre ++ tpe.toString
- }
- bound0(hi, "<:") ++ bound0(lo, ">:")
+ if (d.valueParams.isEmpty) Nil
+ else d.valueParams map (ps => ps map (_.resultType.name) mkString ("(",",",")"))
+ paramLists.mkString
+ case _ => ""
+ }
+
+ def tParams(mbr: Any): String = mbr match {
+ case hk: HigherKinded if !hk.typeParams.isEmpty =>
+ def boundsToString(hi: Option[TypeEntity], lo: Option[TypeEntity]): String = {
+ def bound0(bnd: Option[TypeEntity], pre: String): String = bnd match {
+ case None => ""
+ case Some(tpe) => pre ++ tpe.toString
}
- "[" + d.typeParams.map(tp => tp.variance + tp.name + boundsToString(tp.hi, tp.lo)).mkString(", ") + "]"
+ bound0(hi, "<:") ++ bound0(lo, ">:")
}
-
- tParams + paramLists.mkString
+ "[" + hk.typeParams.map(tp => tp.variance + tp.name + tParams(tp) + boundsToString(tp.hi, tp.lo)).mkString(", ") + "]"
case _ => ""
}
- name + defParamsString +":"+ resultType.name
+
+ (name + tParams(this) + defParams(this) +":"+ resultType.name).replaceAll("\\s","") // no spaces allowed, they break links
}
+ def isImplicitlyInherited = { assert(modelFinished); byConversion.isDefined }
+ def isShadowedImplicit = isImplicitlyInherited && inTpl.implicitsShadowing.get(this).map(_.isShadowed).getOrElse(false)
+ def isAmbiguousImplicit = isImplicitlyInherited && inTpl.implicitsShadowing.get(this).map(_.isAmbiguous).getOrElse(false)
+ def isShadowedOrAmbiguousImplicit = isShadowedImplicit || isAmbiguousImplicit
}
/** A template that is not documented at all. The class is instantiated during lookups, to indicate that the class
@@ -546,7 +559,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) {
import Streamable._
Path(settings.docRootContent.value) match {
case f : File => {
- val rootComment = closing(f.inputStream)(is => parse(slurp(is), "", NoPosition))
+ val rootComment = closing(f.inputStream)(is => parse(slurp(is), "", NoPosition, Option(inTpl)))
Some(rootComment)
}
case _ => None
@@ -658,7 +671,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) {
if (bSym.isGetter && bSym.isLazy)
Some(new NonTemplateMemberImpl(bSym, implConv, inTpl) with Val {
override lazy val comment = // The analyser does not duplicate the lazy val's DocDef when it introduces its accessor.
- thisFactory.comment(bSym.accessed, inTpl.asInstanceOf[DocTemplateImpl]) // This hack should be removed after analyser is fixed.
+ thisFactory.comment(bSym.accessed, None, inTpl.asInstanceOf[DocTemplateImpl]) // This hack should be removed after analyser is fixed.
override def isLazyVal = true
override def useCaseOf = _useCaseOf
})
@@ -744,6 +757,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) {
inTpl.members.find(_.sym == aSym)
}
+ @deprecated("2.10", "Use findLinkTarget instead!")
def findTemplate(query: String): Option[DocTemplateImpl] = {
assert(modelFinished)
docTemplatesCache.values find { (tpl: DocTemplateImpl) => tpl.qualifiedName == query && !packageDropped(tpl) && !tpl.isObject }
@@ -776,7 +790,6 @@ class ModelFactory(val global: Global, val settings: doc.Settings) {
}
}
-
/** */
def makeAnnotation(annot: AnnotationInfo): Annotation = {
val aSym = annot.symbol
diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactoryTypeSupport.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactoryTypeSupport.scala
index 0463ac0420..5efae3257c 100644
--- a/src/compiler/scala/tools/nsc/doc/model/ModelFactoryTypeSupport.scala
+++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactoryTypeSupport.scala
@@ -100,8 +100,8 @@ trait ModelFactoryTypeSupport {
// (1) the owner's class
LinkToMember(bMbr.get.get, oTpl.get) //ugh
else
- // (2) if we still couldn't find the owner, make a noDocTemplate for everything (in the correct owner!)
- LinkToTpl(makeTemplate(bSym, Some(makeTemplate(owner))))
+ // (2) if we still couldn't find the owner, show a tooltip with the qualified name
+ Tooltip(makeQualifiedName(bSym))
}
// SI-4360 Showing prefixes when necessary
diff --git a/src/compiler/scala/tools/nsc/doc/model/TypeEntity.scala b/src/compiler/scala/tools/nsc/doc/model/TypeEntity.scala
index a16e99bf06..643067cca5 100644
--- a/src/compiler/scala/tools/nsc/doc/model/TypeEntity.scala
+++ b/src/compiler/scala/tools/nsc/doc/model/TypeEntity.scala
@@ -9,10 +9,6 @@ package model
import scala.collection._
-abstract sealed class LinkTo
-case class LinkToTpl(tpl: TemplateEntity) extends LinkTo
-case class LinkToMember(mbr: MemberEntity, inTpl: DocTemplateEntity) extends LinkTo
-
/** A type. Note that types and templates contain the same information only for the simplest types. For example, a type
* defines how a template's type parameters are instantiated (as in `List[Cow]`), what the template's prefix is
* (as in `johnsFarm.Cow`), and supports compound or structural types. */
@@ -28,5 +24,4 @@ abstract class TypeEntity {
/** The human-readable representation of this type. */
override def toString = name
-
}
diff --git a/src/compiler/scala/tools/nsc/doc/model/comment/Body.scala b/src/compiler/scala/tools/nsc/doc/model/comment/Body.scala
index ecc3273903..3ab8fc7805 100644
--- a/src/compiler/scala/tools/nsc/doc/model/comment/Body.scala
+++ b/src/compiler/scala/tools/nsc/doc/model/comment/Body.scala
@@ -44,7 +44,6 @@ final case class Body(blocks: Seq[Block]) {
case inlines => Some(Chain(inlines))
}
}
-
}
/** A block-level element of text, such as a paragraph or code block. */
@@ -67,10 +66,14 @@ final case class Bold(text: Inline) extends Inline
final case class Underline(text: Inline) extends Inline
final case class Superscript(text: Inline) extends Inline
final case class Subscript(text: Inline) extends Inline
-final case class EntityLink(target: String, template: () => Option[TemplateEntity]) extends Inline
final case class Link(target: String, title: Inline) extends Inline
final case class Monospace(text: Inline) extends Inline
final case class Text(text: String) extends Inline
+abstract class EntityLink(val title: Inline) extends Inline { def link: LinkTo }
+object EntityLink {
+ def apply(title: Inline, linkTo: LinkTo) = new EntityLink(title) { def link: LinkTo = linkTo}
+ def unapply(el: EntityLink): Option[(Inline, LinkTo)] = Some((el.title, el.link))
+}
final case class HtmlTag(data: String) extends Inline {
def canClose(open: HtmlTag) = {
open.data.stripPrefix("<") == data.stripPrefix("</")
diff --git a/src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala b/src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala
index 2099315cc6..eff899b789 100644
--- a/src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala
+++ b/src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala
@@ -23,7 +23,7 @@ import language.postfixOps
*
* @author Manohar Jonnalagedda
* @author Gilles Dubochet */
-trait CommentFactory { thisFactory: ModelFactory with CommentFactory =>
+trait CommentFactory { thisFactory: ModelFactory with CommentFactory with MemberLookup=>
val global: Global
import global.{ reporter, definitions }
@@ -31,16 +31,16 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory =>
protected val commentCache = mutable.HashMap.empty[(global.Symbol, TemplateImpl), Comment]
def addCommentBody(sym: global.Symbol, inTpl: TemplateImpl, docStr: String, docPos: global.Position): global.Symbol = {
- commentCache += (sym, inTpl) -> parse(docStr, docStr, docPos)
+ commentCache += (sym, inTpl) -> parse(docStr, docStr, docPos, None)
sym
}
- def comment(sym: global.Symbol, inTpl: DocTemplateImpl): Option[Comment] = {
+ def comment(sym: global.Symbol, currentTpl: Option[DocTemplateImpl], inTpl: DocTemplateImpl): Option[Comment] = {
val key = (sym, inTpl)
if (commentCache isDefinedAt key)
Some(commentCache(key))
else {
- val c = defineComment(sym, inTpl)
+ val c = defineComment(sym, currentTpl, inTpl)
if (c isDefined) commentCache += (sym, inTpl) -> c.get
c
}
@@ -50,7 +50,7 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory =>
* cases we have to give some `inTpl` comments (parent class for example)
* to the comment of the symbol.
* This function manages some of those cases : Param accessor and Primary constructor */
- def defineComment(sym: global.Symbol, inTpl: DocTemplateImpl):Option[Comment] = {
+ def defineComment(sym: global.Symbol, currentTpl: Option[DocTemplateImpl], inTpl: DocTemplateImpl):Option[Comment] = {
//param accessor case
// We just need the @param argument, we put it into the body
@@ -87,7 +87,8 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory =>
else {
val rawComment = global.expandedDocComment(sym, inTpl.sym).trim
if (rawComment != "") {
- val c = parse(rawComment, global.rawDocComment(sym), global.docCommentPos(sym))
+ val tplOpt = if (currentTpl.isDefined) currentTpl else Some(inTpl)
+ val c = parse(rawComment, global.rawDocComment(sym), global.docCommentPos(sym), tplOpt)
Some(c)
}
else None
@@ -225,7 +226,8 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory =>
* @param comment The expanded comment string (including start and end markers) to be parsed.
* @param src The raw comment source string.
* @param pos The position of the comment in source. */
- protected def parse(comment: String, src: String, pos: Position): Comment = {
+ protected def parse(comment: String, src: String, pos: Position, inTplOpt: Option[DocTemplateImpl] = None): Comment = {
+ assert(!inTplOpt.isDefined || inTplOpt.get != null)
/** The cleaned raw comment as a list of lines. Cleaning removes comment
* start and end markers, line start markers and unnecessary whitespace. */
@@ -351,7 +353,7 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory =>
val tagsWithoutDiagram = tags.filterNot(pair => pair._1 == inheritDiagramTag || pair._1 == contentDiagramTag)
val bodyTags: mutable.Map[TagKey, List[Body]] =
- mutable.Map(tagsWithoutDiagram mapValues {tag => tag map (parseWiki(_, pos))} toSeq: _*)
+ mutable.Map(tagsWithoutDiagram mapValues {tag => tag map (parseWiki(_, pos, inTplOpt))} toSeq: _*)
def oneTag(key: SimpleTagKey): Option[Body] =
((bodyTags remove key): @unchecked) match {
@@ -384,7 +386,7 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory =>
}
val com = createComment (
- body0 = Some(parseWiki(docBody.toString, pos)),
+ body0 = Some(parseWiki(docBody.toString, pos, inTplOpt)),
authors0 = allTags(SimpleTagKey("author")),
see0 = allTags(SimpleTagKey("see")),
result0 = oneTag(SimpleTagKey("return")),
@@ -420,8 +422,10 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory =>
* - Removed start-of-line star and one whitespace afterwards (if present).
* - Removed all end-of-line whitespace.
* - Only `endOfLine` is used to mark line endings. */
- def parseWiki(string: String, pos: Position): Body = {
- new WikiParser(string, pos).document()
+ def parseWiki(string: String, pos: Position, inTplOpt: Option[DocTemplateImpl]): Body = {
+ assert(!inTplOpt.isDefined || inTplOpt.get != null)
+
+ new WikiParser(string, pos, inTplOpt).document()
}
/** TODO
@@ -429,7 +433,8 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory =>
* @author Ingo Maier
* @author Manohar Jonnalagedda
* @author Gilles Dubochet */
- protected final class WikiParser(val buffer: String, pos: Position) extends CharReader(buffer) { wiki =>
+ protected final class WikiParser(val buffer: String, pos: Position, inTplOpt: Option[DocTemplateImpl]) extends CharReader(buffer) { wiki =>
+ assert(!inTplOpt.isDefined || inTplOpt.get != null)
var summaryParsed = false
@@ -617,7 +622,7 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory =>
else if (check(",,")) subscript()
else if (check("[[")) link()
else {
- readUntil { char == safeTagMarker || check("''") || char == '`' || check("__") || char == '^' || check(",,") || check("[[") || isInlineEnd || checkParaEnded || char == endOfLine }
+ readUntil { char == safeTagMarker || check("''") || char == '`' || check("__") || char == '^' || check(",,") || check("[[") || check("{{") || isInlineEnd || checkParaEnded || char == endOfLine }
Text(getRead())
}
}
@@ -719,29 +724,27 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory =>
def link(): Inline = {
val SchemeUri = """([^:]+:.*)""".r
jump("[[")
- readUntil { check("]]") || check(" ") }
+ var parens = 1
+ readUntil { parens += 1; !check("[") }
+ getRead // clear the buffer
+ val start = "[" * parens
+ val stop = "]" * parens
+ //println("link with " + parens + " matching parens")
+ readUntil { check(stop) || check(" ") }
val target = getRead()
val title =
- if (!check("]]")) Some({
+ if (!check(stop)) Some({
jump(" ")
- inline(check("]]"))
+ inline(check(stop))
})
else None
- jump("]]")
+ jump(stop)
(target, title) match {
case (SchemeUri(uri), optTitle) =>
Link(uri, optTitle getOrElse Text(uri))
case (qualName, optTitle) =>
- optTitle foreach (text => reportError(pos, "entity link to " + qualName + " cannot have a custom title'" + text + "'"))
- // XXX rather than warning here we should allow unqualified names
- // to refer to members of the same package. The "package exists"
- // exclusion is because [[scala]] is used in some scaladoc.
- if (!qualName.contains(".") && !definitions.packageExists(qualName))
- reportError(pos, "entity link to " + qualName + " should be a fully qualified name")
-
- // move the template resolution as late as possible
- EntityLink(qualName, () => findTemplate(qualName))
+ new EntityLink(optTitle getOrElse Text(target)) { def link = memberLookup(pos, target, inTplOpt) }
}
}
diff --git a/src/partest/scala/tools/partest/ScaladocModelTest.scala b/src/partest/scala/tools/partest/ScaladocModelTest.scala
index c89dd2cb8f..fb93e98726 100644
--- a/src/partest/scala/tools/partest/ScaladocModelTest.scala
+++ b/src/partest/scala/tools/partest/ScaladocModelTest.scala
@@ -12,7 +12,7 @@ import scala.tools.nsc.util.CommandLineParser
import scala.tools.nsc.doc.{Settings, DocFactory, Universe}
import scala.tools.nsc.doc.model._
import scala.tools.nsc.reporters.ConsoleReporter
-import scala.tools.nsc.doc.model.comment.Comment
+import scala.tools.nsc.doc.model.comment._
/** A class for testing scaladoc model generation
* - you need to specify the code in the `code` method
@@ -165,10 +165,21 @@ abstract class ScaladocModelTest extends DirectTest {
def extractCommentText(c: Comment) = {
def extractText(body: Any): String = body match {
case s: String => s
+ case s: Seq[_] => s.toList.map(extractText(_)).mkString
case p: Product => p.productIterator.toList.map(extractText(_)).mkString
case _ => ""
}
extractText(c.body)
}
+
+ def countLinks(c: Comment, p: EntityLink => Boolean) = {
+ def countLinks(body: Any): Int = body match {
+ case el: EntityLink if p(el) => 1
+ case s: Seq[_] => s.toList.map(countLinks(_)).sum
+ case p: Product => p.productIterator.toList.map(countLinks(_)).sum
+ case _ => 0
+ }
+ countLinks(c.body)
+ }
}
}
diff --git a/test/scaladoc/resources/links.scala b/test/scaladoc/resources/links.scala
new file mode 100644
index 0000000000..679d0b0dce
--- /dev/null
+++ b/test/scaladoc/resources/links.scala
@@ -0,0 +1,57 @@
+// that would be:
+// SI-5079 "Scaladoc can't link to an object (only a class or trait)"
+// SI-4497 "Links in ScalaDoc - Spec and implementation unsufficient"
+// SI-4224 "Wiki-links should support method targets"
+// SI-3695 "support non-fully-qualified type links in scaladoc comments"
+package scala.test.scaladoc.links {
+ import language.higherKinds
+ class C
+
+ trait Target {
+ type T
+ type S = String
+ class C
+ def foo(i: Int) = 2
+ def foo(s: String) = 3
+ def foo[A[_]](x: A[String]) = 5
+ def foo[A[_[_]]](x: A[List]) = 6
+ val bar: Boolean
+ def baz(c: scala.test.scaladoc.links.C) = 7
+ }
+
+ object Target {
+ type T = Int => Int
+ type S = Int
+ class C
+ def foo(i: Int) = 2
+ def foo(z: String) = 3
+ def foo[A[_]](x: A[String]) = 5
+ def foo[A[_[_]]](x: A[List]) = 6
+ val bar: Boolean = false
+ val onlyInObject = 1
+ def baz(c: scala.test.scaladoc.links.C) = 7
+ }
+
+ /**
+ * Links to the trait:
+ * - [[scala.test.scaladoc.links.Target!.T trait Target -> type T]]
+ * - [[test.scaladoc.links.Target!.S trait Target -> type S]]
+ * - [[scaladoc.links.Target!.foo(Int)* trait Target -> def foo]]
+ * - [[links.Target!.bar trait Target -> def bar]]
+ * - [[[[Target!.foo[A[_[_]]]* trait Target -> def foo with 3 nested tparams]]]] (should exercise nested parens)
+ * - [[Target$.T object Target -> type T]]
+ * - [[Target$.S object Target -> type S]]
+ * - [[Target$.foo(Str* object Target -> def foo]]
+ * - [[Target$.bar object Target -> def bar]]
+ * - [[[[Target$.foo[A[_[_]]]* trait Target -> def foo with 3 nested tparams]]]] (should exercise nested parens)
+ * - [[Target.onlyInObject object Target -> def foo]] (should find the object)
+ * - [[Target$.C object Target -> class C]] (should link directly to C, not as a member)
+ * - [[Target!.C trait Target -> class C]] (should link directly to C, not as a member)
+ * - [[Target$.baz(links\.C)* object Target -> def baz]] (should use dots in prefix)
+ * - [[Target!.baz(links\.C)* trait Target -> def baz]] (should use dots in prefix)
+ * - [[localMethod object TEST -> localMethod]] (should use the current template to resolve link instead of inTpl, that's the package)
+ */
+ object TEST {
+ def localMethod = 3
+ }
+}
diff --git a/test/scaladoc/run/SI-5235.scala b/test/scaladoc/run/SI-5235.scala
index f0c6e1cf17..6295fc7786 100644
--- a/test/scaladoc/run/SI-5235.scala
+++ b/test/scaladoc/run/SI-5235.scala
@@ -79,8 +79,8 @@ object Test extends ScaladocModelTest {
assert(mcReverseType.name == "MyCollection",mcReverseType.name + " == MyCollection")
assert(gcReverseType.refEntity(0)._1 == LinkToTpl(GenericColl),
gcReverse.qualifiedName + "'s return type has a link to " + GenericColl.qualifiedName)
- assert(!scReverseType.refEntity(0)._1.asInstanceOf[LinkToTpl].tpl.isDocTemplate,
- scReverse.qualifiedName + "'s return type does not have links")
+ assert(scReverseType.refEntity(0)._1 == Tooltip("BullSh"),
+ scReverseType.refEntity(0)._1 + " == Tooltip(\"BullSh\")")
assert(mcReverseType.refEntity(0)._1 == LinkToTpl(MyCollection),
mcReverse.qualifiedName + "'s return type has a link to " + MyCollection.qualifiedName)
}
diff --git a/test/scaladoc/run/links.check b/test/scaladoc/run/links.check
new file mode 100644
index 0000000000..619c56180b
--- /dev/null
+++ b/test/scaladoc/run/links.check
@@ -0,0 +1 @@
+Done.
diff --git a/test/scaladoc/run/links.scala b/test/scaladoc/run/links.scala
new file mode 100644
index 0000000000..40ce6368ce
--- /dev/null
+++ b/test/scaladoc/run/links.scala
@@ -0,0 +1,28 @@
+import scala.tools.nsc.doc.model._
+import scala.tools.partest.ScaladocModelTest
+
+// SI-5079 "Scaladoc can't link to an object (only a class or trait)"
+// SI-4497 "Links in ScalaDoc - Spec and implementation unsufficient"
+// SI-4224 "Wiki-links should support method targets"
+// SI-3695 "support non-fully-qualified type links in scaladoc comments"
+object Test extends ScaladocModelTest {
+
+ override def resourceFile = "links.scala"
+
+ // no need for special settings
+ def scaladocSettings = ""
+
+ def testModel(rootPackage: Package) = {
+ // get the quick access implicit defs in scope (_package(s), _class(es), _trait(s), object(s) _method(s), _value(s))
+ import access._
+
+ // just need to check the member exists, access methods will throw an error if there's a problem
+ val base = rootPackage._package("scala")._package("test")._package("scaladoc")._package("links")
+ val TEST = base._object("TEST")
+
+ val memberLinks = countLinks(TEST.comment.get, _.link.isInstanceOf[LinkToMember])
+ val templateLinks = countLinks(TEST.comment.get, _.link.isInstanceOf[LinkToTpl])
+ assert(memberLinks == 14, memberLinks + " == 14 (the member links in object TEST)")
+ assert(templateLinks == 2, templateLinks + " == 2 (the template links in object TEST)")
+ }
+} \ No newline at end of file
diff --git a/test/scaladoc/scalacheck/CommentFactoryTest.scala b/test/scaladoc/scalacheck/CommentFactoryTest.scala
index b7869d5bf4..5e3141bdc0 100644
--- a/test/scaladoc/scalacheck/CommentFactoryTest.scala
+++ b/test/scaladoc/scalacheck/CommentFactoryTest.scala
@@ -10,7 +10,13 @@ import scala.tools.nsc.doc.model.diagram._
class Factory(val g: Global, val s: doc.Settings)
extends doc.model.ModelFactory(g, s) {
- thisFactory: Factory with ModelFactoryImplicitSupport with ModelFactoryTypeSupport with DiagramFactory with CommentFactory with doc.model.TreeFactory =>
+ thisFactory: Factory
+ with ModelFactoryImplicitSupport
+ with ModelFactoryTypeSupport
+ with DiagramFactory
+ with CommentFactory
+ with doc.model.TreeFactory
+ with MemberLookup =>
def strip(c: Comment): Option[Inline] = {
c.body match {
@@ -31,7 +37,13 @@ object Test extends Properties("CommentFactory") {
val settings = new doc.Settings((str: String) => {})
val reporter = new scala.tools.nsc.reporters.ConsoleReporter(settings)
val g = new Global(settings, reporter)
- (new Factory(g, settings) with ModelFactoryImplicitSupport with ModelFactoryTypeSupport with DiagramFactory with CommentFactory with doc.model.TreeFactory)
+ (new Factory(g, settings)
+ with ModelFactoryImplicitSupport
+ with ModelFactoryTypeSupport
+ with DiagramFactory
+ with CommentFactory
+ with doc.model.TreeFactory
+ with MemberLookup)
}
def parse(src: String, dst: Inline) = {