summaryrefslogtreecommitdiff
path: root/src/compiler
diff options
context:
space:
mode:
authorVlad Ureche <vlad.ureche@gmail.com>2012-07-12 00:31:25 +0200
committerVlad Ureche <vlad.ureche@gmail.com>2012-07-16 23:41:44 +0200
commitdc70d1b7bd193ff42e9bed5d80f632cffb85a667 (patch)
treeed8cd8eff307a6d817e2c65829f06b2cb66d8080 /src/compiler
parent929415a3f4d5d6261d10cc6d28720c5241716bae (diff)
downloadscala-dc70d1b7bd193ff42e9bed5d80f632cffb85a667.tar.gz
scala-dc70d1b7bd193ff42e9bed5d80f632cffb85a667.tar.bz2
scala-dc70d1b7bd193ff42e9bed5d80f632cffb85a667.zip
SI-3695 SI-4224 SI-4497 SI-5079 scaladoc links
Adds the ability to link to members, classes and objects in scaladoc. The links can now be either qualified names or relative names, they both work. See the test/scaladoc/resources/links.scala for a usage example. Also introduced -no-link-warnings scaladoc flag, in case the build output gets swamped with link warnings.
Diffstat (limited to 'src/compiler')
-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
12 files changed, 345 insertions, 103 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) }
}
}