summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorVlad Ureche <vlad.ureche@gmail.com>2012-06-13 16:35:58 +0200
committerVlad Ureche <vlad.ureche@gmail.com>2012-06-14 10:43:44 +0200
commit831f09bb6d00c152bd8aef3ce8bf9e200080fe36 (patch)
tree9959919ab28c10cbcaebd0f10932152e56abea16 /src
parent1c2d466804b22f388ab4d66a034327539ac2e50f (diff)
downloadscala-831f09bb6d00c152bd8aef3ce8bf9e200080fe36.tar.gz
scala-831f09bb6d00c152bd8aef3ce8bf9e200080fe36.tar.bz2
scala-831f09bb6d00c152bd8aef3ce8bf9e200080fe36.zip
Scaladoc class diagrams part 1
This commit contains model changes required for adding class diagrams to scaladoc. It also contains an improved implicit shadowing computation, which hides the shadowed implicitly inherited members from the main view and gives instructions on how to access them. This is joint work with Damien Obrist (@damienobrist) on supporting diagram generation in scaladoc, as part of Damien's semester project in the LAMP laborarory at EPFL. The full history is located at: https://github.com/damienobrist/scala/tree/feature/diagrams-dev Commit summary: - diagrams model - diagram settings (Settings.scala, ScalaDoc.scala) - diagram model object (Entity.scala, Diagram.scala) - model: tracking direct superclasses and subclasses, implicit conversions from and to (ModelFactory.scala) - diagram object computation (DiagramFactory.scala, DocFactory.scala) - capacity to filter diagrams (CommentFactory.scala, DiagramDirectiveParser.scala) - diagram statistics object (DiagramStats.scala) - delayed link evaluation (Body.scala, Comment.scala) - tests - improved implicits shadowing information - model shadowing computation (ModelFactoryImplicitSupport.scala, Entity.scala) - html generation for shadowing information (Template.scala) - tests Also fixes an issue reported by @dragos, where single-line comment expansion would lead to the comment disappearing. Review by @kzys, @pedrofurla.
Diffstat (limited to 'src')
-rw-r--r--src/compiler/scala/tools/nsc/ScalaDoc.scala3
-rw-r--r--src/compiler/scala/tools/nsc/doc/DocFactory.scala6
-rw-r--r--src/compiler/scala/tools/nsc/doc/Settings.scala58
-rw-r--r--src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala16
-rw-r--r--src/compiler/scala/tools/nsc/doc/html/Page.scala14
-rw-r--r--src/compiler/scala/tools/nsc/doc/html/page/Index.scala2
-rw-r--r--src/compiler/scala/tools/nsc/doc/html/page/IndexScript.scala2
-rw-r--r--src/compiler/scala/tools/nsc/doc/html/page/Template.scala91
-rw-r--r--src/compiler/scala/tools/nsc/doc/html/page/diagram/DiagramStats.scala58
-rw-r--r--src/compiler/scala/tools/nsc/doc/html/resource/lib/template.css6
-rw-r--r--src/compiler/scala/tools/nsc/doc/model/Entity.scala96
-rw-r--r--src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala109
-rw-r--r--src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala134
-rw-r--r--src/compiler/scala/tools/nsc/doc/model/comment/Body.scala2
-rw-r--r--src/compiler/scala/tools/nsc/doc/model/comment/Comment.scala6
-rw-r--r--src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala140
-rw-r--r--src/compiler/scala/tools/nsc/doc/model/diagram/Diagram.scala143
-rw-r--r--src/compiler/scala/tools/nsc/doc/model/diagram/DiagramDirectiveParser.scala248
-rw-r--r--src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala197
-rw-r--r--src/partest/scala/tools/partest/ScaladocModelTest.scala4
20 files changed, 1173 insertions, 162 deletions
diff --git a/src/compiler/scala/tools/nsc/ScalaDoc.scala b/src/compiler/scala/tools/nsc/ScalaDoc.scala
index 5a4b4172c6..c6fdb4b891 100644
--- a/src/compiler/scala/tools/nsc/ScalaDoc.scala
+++ b/src/compiler/scala/tools/nsc/ScalaDoc.scala
@@ -20,7 +20,8 @@ class ScalaDoc {
def process(args: Array[String]): Boolean = {
var reporter: ConsoleReporter = null
- val docSettings = new doc.Settings(msg => reporter.error(FakePos("scaladoc"), msg + "\n scaladoc -help gives more information"))
+ val docSettings = new doc.Settings(msg => reporter.error(FakePos("scaladoc"), msg + "\n scaladoc -help gives more information"),
+ msg => reporter.printMessage(msg))
reporter = new ConsoleReporter(docSettings) {
// need to do this so that the Global instance doesn't trash all the
// symbols just because there was an error
diff --git a/src/compiler/scala/tools/nsc/doc/DocFactory.scala b/src/compiler/scala/tools/nsc/doc/DocFactory.scala
index e2e1ddf065..c6a9422974 100644
--- a/src/compiler/scala/tools/nsc/doc/DocFactory.scala
+++ b/src/compiler/scala/tools/nsc/doc/DocFactory.scala
@@ -81,6 +81,7 @@ class DocFactory(val reporter: Reporter, val settings: doc.Settings) { processor
new { override val global: compiler.type = compiler }
with model.ModelFactory(compiler, settings)
with model.ModelFactoryImplicitSupport
+ with model.diagram.DiagramFactory
with model.comment.CommentFactory
with model.TreeFactory {
override def templateShouldDocument(sym: compiler.Symbol) =
@@ -90,11 +91,12 @@ class DocFactory(val reporter: Reporter, val settings: doc.Settings) { processor
modelFactory.makeModel match {
case Some(madeModel) =>
- if (settings.reportModel)
+ if (!settings.scaladocQuietRun)
println("model contains " + modelFactory.templatesCount + " documentable templates")
Some(madeModel)
case None =>
- println("no documentable class found in compilation units")
+ if (!settings.scaladocQuietRun)
+ println("no documentable class found in compilation units")
None
}
diff --git a/src/compiler/scala/tools/nsc/doc/Settings.scala b/src/compiler/scala/tools/nsc/doc/Settings.scala
index 4458889d55..f8b2711c0c 100644
--- a/src/compiler/scala/tools/nsc/doc/Settings.scala
+++ b/src/compiler/scala/tools/nsc/doc/Settings.scala
@@ -11,8 +11,9 @@ import java.lang.System
import language.postfixOps
/** An extended version of compiler settings, with additional Scaladoc-specific options.
- * @param error A function that prints a string to the appropriate error stream. */
-class Settings(error: String => Unit) extends scala.tools.nsc.Settings(error) {
+ * @param error A function that prints a string to the appropriate error stream
+ * @param print A function that prints the string, without any extra boilerplate of error */
+class Settings(error: String => Unit, val printMsg: String => Unit = println(_)) extends scala.tools.nsc.Settings(error) {
/** A setting that defines in which format the documentation is output. ''Note:'' this setting is currently always
* `html`. */
@@ -104,6 +105,12 @@ class Settings(error: String => Unit) extends scala.tools.nsc.Settings(error) {
"(for example conversions that require Numeric[String] to be in scope)"
)
+ val docImplicitsSoundShadowing = BooleanSetting (
+ "-implicits-sound-shadowing",
+ "Use a sound implicit shadowing calculation. Note: this interacts badly with usecases, so " +
+ "only use it if you haven't defined usecase for implicitly inherited members."
+ )
+
val docDiagrams = BooleanSetting (
"-diagrams",
"Create inheritance diagrams for classes, traits and packages."
@@ -116,10 +123,44 @@ class Settings(error: String => Unit) extends scala.tools.nsc.Settings(error) {
val docDiagramsDotPath = PathSetting (
"-diagrams-dot-path",
- "The path to the dot executable used to generate the inheritance diagrams. Ex: /usr/bin/dot",
+ "The path to the dot executable used to generate the inheritance diagrams. Eg: /usr/bin/dot",
"dot" // by default, just pick up the system-wide dot
)
+ /** The maxium nuber of normal classes to show in the diagram */
+ val docDiagramsMaxNormalClasses = IntSetting(
+ "-diagrams-max-classes",
+ "The maximum number of superclasses or subclasses to show in a diagram",
+ 15,
+ None,
+ _ => None
+ )
+
+ /** The maxium nuber of implcit classes to show in the diagram */
+ val docDiagramsMaxImplicitClasses = IntSetting(
+ "-diagrams-max-implicits",
+ "The maximum number of implicitly converted classes to show in a diagram",
+ 10,
+ None,
+ _ => None
+ )
+
+ val docDiagramsDotTimeout = IntSetting(
+ "-diagrams-dot-timeout",
+ "The timeout before the graphviz dot util is forecefully closed, in seconds (default: 10)",
+ 10,
+ None,
+ _ => None
+ )
+
+ val docDiagramsDotRestart = IntSetting(
+ "-diagrams-dot-restart",
+ "The number of times to restart a malfunctioning dot process before disabling diagrams (default: 5)",
+ 5,
+ None,
+ _ => None
+ )
+
// 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.
@@ -129,14 +170,16 @@ class Settings(error: String => Unit) extends scala.tools.nsc.Settings(error) {
def scaladocSpecific = Set[Settings#Setting](
docformat, doctitle, docfooter, docversion, docUncompilable, docsourceurl, docgenerator, docRootContent, useStupidTypes,
docDiagrams, docDiagramsDebug, docDiagramsDotPath,
- docImplicits, docImplicitsDebug, docImplicitsShowAll
+ docDiagramsDotTimeout, docDiagramsDotRestart,
+ docImplicits, docImplicitsDebug, docImplicitsShowAll,
+ docDiagramsMaxNormalClasses, docDiagramsMaxImplicitClasses
)
val isScaladocSpecific: String => Boolean = scaladocSpecific map (_.name)
override def isScaladoc = true
- // unset by the testsuite, we don't need to count the entities in the model
- var reportModel = true
+ // set by the testsuite, when checking test output
+ var scaladocQuietRun = false
/**
* This is the hardcoded area of Scaladoc. This is where "undesirable" stuff gets eliminated. I know it's not pretty,
@@ -182,7 +225,8 @@ class Settings(error: String => Unit) extends scala.tools.nsc.Settings(error) {
"scala.Predef.any2stringfmt",
"scala.Predef.any2stringadd",
"scala.Predef.any2ArrowAssoc",
- "scala.Predef.any2Ensuring")
+ "scala.Predef.any2Ensuring",
+ "scala.collection.TraversableOnce.alternateImplicit")
/** There's a reason all these are specialized by hand but documenting each of them is beyond the point */
val arraySkipConversions = List(
diff --git a/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala b/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala
index e3da8bddea..34e2f7bc53 100644
--- a/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala
+++ b/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala
@@ -116,11 +116,25 @@ abstract class HtmlPage extends Page { thisPage =>
case Superscript(in) => <sup>{ inlineToHtml(in) }</sup>
case Subscript(in) => <sub>{ inlineToHtml(in) }</sub>
case Link(raw, title) => <a href={ raw }>{ inlineToHtml(title) }</a>
- case EntityLink(entity) => templateToHtml(entity)
case Monospace(in) => <code>{ inlineToHtml(in) }</code>
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)
+ }
+ }
+
+ def typeToHtml(tpes: List[model.TypeEntity], hasLinks: Boolean): NodeSeq = tpes match {
+ case Nil =>
+ sys.error("Internal Scaladoc error")
+ case List(tpe) =>
+ typeToHtml(tpe, hasLinks)
+ case tpe :: rest =>
+ typeToHtml(tpe, hasLinks) ++ scala.xml.Text(" with ") ++ typeToHtml(rest, hasLinks)
}
def typeToHtml(tpe: model.TypeEntity, hasLinks: Boolean): NodeSeq = {
diff --git a/src/compiler/scala/tools/nsc/doc/html/Page.scala b/src/compiler/scala/tools/nsc/doc/html/Page.scala
index c5bf3e0e37..59f4c81632 100644
--- a/src/compiler/scala/tools/nsc/doc/html/Page.scala
+++ b/src/compiler/scala/tools/nsc/doc/html/Page.scala
@@ -83,18 +83,4 @@ abstract class Page {
}
relativize(thisPage.path.reverse, destPath.reverse).mkString("/")
}
-
- def isExcluded(dtpl: DocTemplateEntity) = {
- val qname = dtpl.qualifiedName
- ( ( qname.startsWith("scala.Tuple") || qname.startsWith("scala.Product") ||
- qname.startsWith("scala.Function") || qname.startsWith("scala.runtime.AbstractFunction")
- ) && !(
- qname == "scala.Tuple1" || qname == "scala.Tuple2" ||
- qname == "scala.Product" || qname == "scala.Product1" || qname == "scala.Product2" ||
- qname == "scala.Function" || qname == "scala.Function1" || qname == "scala.Function2" ||
- qname == "scala.runtime.AbstractFunction0" || qname == "scala.runtime.AbstractFunction1" ||
- qname == "scala.runtime.AbstractFunction2"
- )
- )
- }
}
diff --git a/src/compiler/scala/tools/nsc/doc/html/page/Index.scala b/src/compiler/scala/tools/nsc/doc/html/page/Index.scala
index 8ed13e0da2..0e894a03bf 100644
--- a/src/compiler/scala/tools/nsc/doc/html/page/Index.scala
+++ b/src/compiler/scala/tools/nsc/doc/html/page/Index.scala
@@ -61,7 +61,7 @@ class Index(universe: doc.Universe, index: doc.Index) extends HtmlPage {
}
<ol class="templates">{
val tpls: Map[String, Seq[DocTemplateEntity]] =
- (pack.templates filter (t => !t.isPackage && !isExcluded(t) )) groupBy (_.name)
+ (pack.templates filter (t => !t.isPackage && !universe.settings.hardcoded.isExcluded(t.qualifiedName) )) groupBy (_.name)
val placeholderSeq: NodeSeq = <div class="placeholder"></div>
diff --git a/src/compiler/scala/tools/nsc/doc/html/page/IndexScript.scala b/src/compiler/scala/tools/nsc/doc/html/page/IndexScript.scala
index 7edd4937c4..1b0599d145 100644
--- a/src/compiler/scala/tools/nsc/doc/html/page/IndexScript.scala
+++ b/src/compiler/scala/tools/nsc/doc/html/page/IndexScript.scala
@@ -68,7 +68,7 @@ class IndexScript(universe: doc.Universe, index: doc.Index) extends Page {
def allPackagesWithTemplates = {
Map(allPackages.map((key) => {
- key -> key.templates.filter(t => !t.isPackage && !isExcluded(t))
+ key -> key.templates.filter(t => !t.isPackage && !universe.settings.hardcoded.isExcluded(t.qualifiedName))
}) : _*)
}
}
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 66189a6854..ce0e6b7856 100644
--- a/src/compiler/scala/tools/nsc/doc/html/page/Template.scala
+++ b/src/compiler/scala/tools/nsc/doc/html/page/Template.scala
@@ -8,10 +8,11 @@ package doc
package html
package page
-import model._
import scala.xml.{ NodeSeq, Text, UnprefixedAttribute }
import language.postfixOps
+import model._
+
class Template(universe: doc.Universe, tpl: DocTemplateEntity) extends HtmlPage {
val path =
@@ -41,9 +42,12 @@ class Template(universe: doc.Universe, tpl: DocTemplateEntity) extends HtmlPage
val (absValueMembers, nonAbsValueMembers) =
valueMembers partition (_.isAbstract)
- val (deprValueMembers, concValueMembers) =
+ val (deprValueMembers, nonDeprValueMembers) =
nonAbsValueMembers partition (_.deprecation.isDefined)
+ val (concValueMembers, shadowedImplicitMembers) =
+ nonDeprValueMembers partition (!Template.isShadowedOrAmbiguousImplicit(_))
+
val typeMembers =
tpl.abstractTypes ++ tpl.aliasTypes ++ tpl.templates.filter(x => x.isTrait || x.isClass) sorted
@@ -163,6 +167,13 @@ class Template(universe: doc.Universe, tpl: DocTemplateEntity) extends HtmlPage
</div>
}
+ { if (shadowedImplicitMembers.isEmpty) NodeSeq.Empty else
+ <div id="values" class="values members">
+ <h3>Shadowed Implict Value Members</h3>
+ <ol>{ shadowedImplicitMembers map (memberToHtml(_)) }</ol>
+ </div>
+ }
+
{ if (deprValueMembers.isEmpty) NodeSeq.Empty else
<div id="values" class="values members">
<h3>Deprecated Value Members</h3>
@@ -243,11 +254,13 @@ class Template(universe: doc.Universe, tpl: DocTemplateEntity) extends HtmlPage
case _ => ""
}
val memberComment = memberToCommentHtml(mbr, false)
- <li name={ mbr.definitionName } visbl={ if (mbr.visibility.isProtected) "prt" else "pub" }
- data-isabs={ mbr.isAbstract.toString } fullComment={ if(memberComment.isEmpty) "no" else "yes" }>
- <a id={ mbr.name +defParamsString +":"+ mbr.resultType.name}/>
- { signature(mbr, false) }
- { memberComment }
+ <li name={ mbr.definitionName }
+ visbl={ if (mbr.visibility.isProtected) "prt" else "pub" }
+ data-isabs={ mbr.isAbstract.toString }
+ fullComment={ if(memberComment.filter(_.label=="div").isEmpty) "no" else "yes" }>
+ <a id={ mbr.name +defParamsString +":"+ mbr.resultType.name}/>
+ { signature(mbr, false) }
+ { memberComment }
</li>
}
@@ -299,6 +312,7 @@ class Template(universe: doc.Universe, tpl: DocTemplateEntity) extends HtmlPage
<p class="comment cmt">{ inlineToHtml(mbr.comment.get.short) }</p>
def memberToCommentBodyHtml(mbr: MemberEntity, isSelf: Boolean, isReduced: Boolean = false): NodeSeq = {
+ val s = universe.settings
val memberComment =
if (mbr.comment.isEmpty) NodeSeq.Empty
@@ -354,7 +368,8 @@ class Template(universe: doc.Universe, tpl: DocTemplateEntity) extends HtmlPage
}
val implicitInformation = mbr.byConversion match {
- case Some(conv) =>
+ case Some(info) =>
+ val conv = info.conversion
<dt class="implicit">Implicit information</dt> ++
{
val targetType = typeToHtml(conv.targetType, true)
@@ -387,6 +402,35 @@ class Template(universe: doc.Universe, tpl: DocTemplateEntity) extends HtmlPage
{ targetType } performed by method { conversionMethod } in { conversionOwner }.
{ constraintText }
</dd>
+ } ++ {
+ if (Template.isShadowedOrAmbiguousImplicit(mbr)) {
+ // These are the members that are shadowing or ambiguating the current implicit
+ // see ImplicitMemberShadowing trait for more information
+ val shadowingSuggestion = {
+ val params = mbr match {
+ case d: Def => d.valueParams map (_ map (_ name) mkString("(", ", ", ")")) mkString
+ case _ => "" // no parameters
+ }
+ <br/> ++ xml.Text("To access this member you can use a ") ++
+ <a href="http://stackoverflow.com/questions/2087250/what-is-the-purpose-of-type-ascription-in-scala"
+ target="_blank">type ascription</a> ++ xml.Text(":") ++
+ <br/> ++ <div class="cmt"><pre>{"(" + Template.lowerFirstLetter(tpl.name) + ": " + conv.targetType.name + ")." + mbr.name + params }</pre></div>
+ }
+
+ val shadowingWarning: NodeSeq =
+ if (Template.isShadowedImplicit(mbr))
+ xml.Text("This implicitly inherited member is shadowed by one or more members in this " +
+ "class.") ++ shadowingSuggestion
+ else if (Template.isAmbiguousImplicit(mbr))
+ 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
+ else NodeSeq.Empty
+
+ <dt class="implicit">Shadowing</dt> ++
+ <dd>{ shadowingWarning }</dd>
+
+ } else NodeSeq.Empty
}
case _ =>
NodeSeq.Empty
@@ -562,11 +606,11 @@ class Template(universe: doc.Universe, tpl: DocTemplateEntity) extends HtmlPage
}
val subclasses = mbr match {
- case dtpl: DocTemplateEntity if isSelf && !isReduced && dtpl.subClasses.nonEmpty =>
+ case dtpl: DocTemplateEntity if isSelf && !isReduced && dtpl.allSubClasses.nonEmpty =>
<div class="toggleContainer block">
<span class="toggle">Known Subclasses</span>
<div class="subClasses hiddenContent">{
- templatesToHtml(dtpl.subClasses.sortBy(_.name), xml.Text(", "))
+ templatesToHtml(dtpl.allSubClasses.sortBy(_.name), xml.Text(", "))
}</div>
</div>
case _ => NodeSeq.Empty
@@ -605,13 +649,13 @@ class Template(universe: doc.Universe, tpl: DocTemplateEntity) extends HtmlPage
case PrivateInTemplate(owner) if (owner == mbr.inTemplate) =>
Some(Paragraph(CText("private")))
case PrivateInTemplate(owner) =>
- Some(Paragraph(Chain(List(CText("private["), EntityLink(owner), CText("]")))))
+ Some(Paragraph(Chain(List(CText("private["), EntityLink(owner.qualifiedName, () => Some(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), CText("]")))))
+ Some(Paragraph(Chain(List(CText("protected["), EntityLink(owner.qualifiedName, () => Some(owner)), CText("]")))))
case Public() =>
None
}
@@ -627,7 +671,15 @@ class Template(universe: doc.Universe, tpl: DocTemplateEntity) extends HtmlPage
</span>
<span class="symbol">
{
- val nameClass = if (mbr.byConversion.isDefined) "implicit" else "name"
+ val nameClass =
+ if (Template.isImplicit(mbr))
+ if (Template.isShadowedOrAmbiguousImplicit(mbr))
+ "implicit shadowed"
+ else
+ "implicit"
+ else
+ "name"
+
val nameHtml = {
val value = if (mbr.isConstructor) tpl.name else mbr.name
val span = if (mbr.deprecation.isDefined)
@@ -699,8 +751,8 @@ class Template(universe: doc.Universe, tpl: DocTemplateEntity) extends HtmlPage
}
}{ if (isReduced) NodeSeq.Empty else {
mbr match {
- case tpl: DocTemplateEntity if tpl.parentType.isDefined =>
- <span class="result"> extends { typeToHtml(tpl.parentType.get, hasLinks) }</span>
+ case tpl: DocTemplateEntity if !tpl.parentTypes.isEmpty =>
+ <span class="result"> extends { typeToHtml(tpl.parentTypes.map(_._2), hasLinks) }</span>
case tme: MemberEntity if (tme.isDef || tme.isVal || tme.isLazyVal || tme.isVar) =>
<span class="result">: { typeToHtml(tme.resultType, hasLinks) }</span>
@@ -870,5 +922,14 @@ class Template(universe: doc.Universe, tpl: DocTemplateEntity) extends HtmlPage
xml.Text(ub.typeParamName + " is a subclass of " + ub.upperBound.name + " (" + ub.typeParamName + " <: ") ++
typeToHtml(ub.upperBound, true) ++ xml.Text(")")
}
+}
+
+object Template {
+
+ def isImplicit(mbr: MemberEntity) = mbr.byConversion.isDefined
+ def isShadowedImplicit(mbr: MemberEntity) = mbr.byConversion.isDefined && mbr.byConversion.get.isShadowed
+ def isAmbiguousImplicit(mbr: MemberEntity) = mbr.byConversion.isDefined && mbr.byConversion.get.isAmbiguous
+ 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/html/page/diagram/DiagramStats.scala b/src/compiler/scala/tools/nsc/doc/html/page/diagram/DiagramStats.scala
new file mode 100644
index 0000000000..16d859894f
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/doc/html/page/diagram/DiagramStats.scala
@@ -0,0 +1,58 @@
+/**
+ * @author Vlad Ureche
+ */
+package scala.tools.nsc.doc
+package html.page.diagram
+
+object DiagramStats {
+
+ class TimeTracker(title: String) {
+ var totalTime: Long = 0l
+ var maxTime: Long = 0l
+ var instances: Int = 0
+
+ def addTime(ms: Long) = {
+ if (maxTime < ms)
+ maxTime = ms
+ totalTime += ms
+ instances += 1
+ }
+
+ def printStats(print: String => Unit) = {
+ if (instances == 0)
+ print(title + ": no stats gathered")
+ else {
+ print(" " + title)
+ print(" " + "=" * title.length)
+ print(" count: " + instances + " items")
+ print(" total time: " + totalTime + " ms")
+ print(" average time: " + (totalTime/instances) + " ms")
+ print(" maximum time: " + maxTime + " ms")
+ print("")
+ }
+ }
+ }
+
+ private[this] val filterTrack = new TimeTracker("diagrams model filtering")
+ private[this] val modelTrack = new TimeTracker("diagrams model generation")
+ private[this] val dotGenTrack = new TimeTracker("dot diagram generation")
+ private[this] val dotRunTrack = new TimeTracker("dot process runnning")
+ private[this] val svgTrack = new TimeTracker("svg processing")
+
+ def printStats(settings: Settings) = {
+ if (settings.docDiagramsDebug.value) {
+ settings.printMsg("\nDiagram generation running time breakdown:\n")
+ filterTrack.printStats(settings.printMsg)
+ modelTrack.printStats(settings.printMsg)
+ dotGenTrack.printStats(settings.printMsg)
+ dotRunTrack.printStats(settings.printMsg)
+ svgTrack.printStats(settings.printMsg)
+ }
+ }
+
+ def addFilterTime(ms: Long) = filterTrack.addTime(ms)
+ def addModelTime(ms: Long) = modelTrack.addTime(ms)
+ def addDotGenerationTime(ms: Long) = dotGenTrack.addTime(ms)
+ def addDotRunningTime(ms: Long) = dotRunTrack.addTime(ms)
+ def addSvgTime(ms: Long) = svgTrack.addTime(ms)
+} \ No newline at end of file
diff --git a/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.css b/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.css
index 5a1779bba5..b25f0d0b3f 100644
--- a/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.css
+++ b/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.css
@@ -333,6 +333,10 @@ div.members > ol > li:last-child {
color: darkgreen;
}
+.signature .symbol .shadowed {
+ color: darkseagreen;
+}
+
.signature .symbol .params > .implicit {
font-style: italic;
}
@@ -802,4 +806,4 @@ div.fullcomment dl.paramcmts > dd {
#mbrsel .showall span {
color: #4C4C4C;
font-weight: bold;
-}*/ \ No newline at end of file
+}*/
diff --git a/src/compiler/scala/tools/nsc/doc/model/Entity.scala b/src/compiler/scala/tools/nsc/doc/model/Entity.scala
index 6488847049..5d1413abd4 100644
--- a/src/compiler/scala/tools/nsc/doc/model/Entity.scala
+++ b/src/compiler/scala/tools/nsc/doc/model/Entity.scala
@@ -10,7 +10,7 @@ package model
import scala.collection._
import comment._
-
+import diagram._
/** An entity in a Scaladoc universe. Entities are declarations in the program and correspond to symbols in the
* compiler. Entities model the following Scala concepts:
@@ -86,9 +86,20 @@ trait TemplateEntity extends Entity {
/** Whether this template is a case class. */
def isCaseClass: Boolean
+ /** The type of this entity, with type members */
+ def ownType: TypeEntity
+
/** The self-type of this template, if it differs from the template type. */
def selfType : Option[TypeEntity]
+ /** The full template name `[kind] qualifiedName` */
+ def fullName =
+ (if (isPackage) "package "
+ else if (isCaseClass) "case class "
+ else if (isClass) "class "
+ else if (isTrait) "trait "
+ else if (isObject) "object "
+ else "") + qualifiedName
}
@@ -167,8 +178,12 @@ trait MemberEntity extends Entity {
/** Whether this member is abstract. */
def isAbstract: Boolean
+ /** If this symbol is a use case, the useCaseOf will contain the member it was derived from, containing the full
+ * signature and the complete parameter descriptions. */
+ def useCaseOf: Option[MemberEntity] = None
+
/** If this member originates from an implicit conversion, we set the implicit information to the correct origin */
- def byConversion: Option[ImplicitConversion]
+ def byConversion: Option[ImplicitConversionInfo]
}
object MemberEntity {
// Oh contravariance, contravariance, wherefore art thou contravariance?
@@ -206,8 +221,10 @@ trait DocTemplateEntity extends TemplateEntity with MemberEntity {
* only if the `docsourceurl` setting has been set. */
def sourceUrl: Option[java.net.URL]
- /** The direct super-type of this template. */
- def parentType: Option[TypeEntity]
+ /** The direct super-type of this template
+ e.g: {{{class A extends B[C[Int]] with D[E]}}} will have two direct parents: class B and D
+ NOTE: we are dropping the refinement here! */
+ def parentTypes: List[(TemplateEntity, TypeEntity)]
@deprecated("Use `linearizationTemplates` and `linearizationTypes` instead", "2.9.0")
def linearization: List[(TemplateEntity, TypeEntity)]
@@ -220,9 +237,13 @@ trait DocTemplateEntity extends TemplateEntity with MemberEntity {
* This template's linearization contains all of its direct and indirect super-types. */
def linearizationTypes: List[TypeEntity]
- /**All class, trait and object templates for which this template is a direct or indirect super-class or super-trait.
- * Only templates for which documentation is available in the universe (`DocTemplateEntity`) are listed. */
- def subClasses: List[DocTemplateEntity]
+ /** All class, trait and object templates for which this template is a direct or indirect super-class or super-trait.
+ * Only templates for which documentation is available in the universe (`DocTemplateEntity`) are listed. */
+ def allSubClasses: List[DocTemplateEntity]
+
+ /** All class, trait and object templates for which this template is a *direct* super-class or super-trait.
+ * Only templates for which documentation is available in the universe (`DocTemplateEntity`) are listed. */
+ def directSubClasses: List[DocTemplateEntity]
/** All members of this template. If this template is a package, only templates for which documentation is available
* in the universe (`DocTemplateEntity`) are listed. */
@@ -250,6 +271,19 @@ trait DocTemplateEntity extends TemplateEntity with MemberEntity {
/** The implicit conversions this template (class or trait, objects and packages are not affected) */
def conversions: List[ImplicitConversion]
+
+ /** Classes that can be implcitly converted to this class */
+ def incomingImplicitlyConvertedClasses: List[DocTemplateEntity]
+
+ /** Classes to which this class can be implicitly converted to
+ NOTE: Some classes might not be included in the scaladoc run so they will be NoDocTemplateEntities */
+ def outgoingImplicitlyConvertedClasses: List[(TemplateEntity, TypeEntity)]
+
+ /** If this template takes place in inheritance and implicit conversion relations, it will be shown in this diagram */
+ def inheritanceDiagram: Option[Diagram]
+
+ /** If this template contains other templates, such as classes and traits, they will be shown in this diagram */
+ def contentDiagram: Option[Diagram]
}
@@ -305,10 +339,6 @@ trait NonTemplateMemberEntity extends MemberEntity {
* It corresponds to a real member, and provides a simplified, yet compatible signature for that member. */
def isUseCase: Boolean
- /** If this symbol is a use case, the useCaseOf will contain the member it was derived from, containing the full
- * signature and the complete parameter descriptions. */
- def useCaseOf: Option[MemberEntity]
-
/** Whether this member is a bridge member. A bridge member does only exist for binary compatibility reasons
* and should not appear in ScalaDoc. */
def isBridge: Boolean
@@ -427,6 +457,15 @@ trait ImplicitConversion {
/** The result type after the conversion */
def targetType: TypeEntity
+ /** The result type after the conversion
+ * Note: not all targetTypes have a corresponding template. Examples include conversions resulting in refinement
+ * types. Need to check it's not option!
+ */
+ def targetTemplate: Option[TemplateEntity]
+
+ /** The components of the implicit conversion type parents */
+ def targetTypeComponents: List[(TemplateEntity, TypeEntity)]
+
/** The entity for the method that performed the conversion, if it's documented (or just its name, otherwise) */
def convertorMethod: Either[MemberEntity, String]
@@ -446,12 +485,39 @@ trait ImplicitConversion {
def members: List[MemberEntity]
}
-/** A trait that encapsulates a constraint necessary for implicit conversion */
-trait Constraint {
- // /** The implicit conversion during which this constraint appears */
- // def conversion: ImplicitConversion
+trait ImplicitConversionInfo {
+ /** The implicit conversion this member originates from */
+ def conversion: ImplicitConversion
+
+ /** The shadowing information for this member */
+ def shadowing: ImplicitMemberShadowing
+
+ /* Quick getters */
+ def isShadowed: Boolean = !shadowing.shadowingMembers.isEmpty
+ def isAmbiguous: Boolean = !shadowing.ambiguatingMembers.isEmpty
}
+/** Shadowing captures the information that the member is shadowed by some other members
+ * There are two cases of implicitly added member shadowing:
+ * 1) shadowing from a original class member (the class already has that member)
+ * in this case, it won't be possible to call the member directly, the type checker will fail attempting to adapt
+ * the call arguments (or if they fit it will call the original class' method)
+ * 2) shadowing from other possible implicit conversions ()
+ * this will result in an ambiguous implicit converion error
+ */
+trait ImplicitMemberShadowing {
+ /** The members that shadow the current entry use .inTemplate to get to the template name */
+ def shadowingMembers: List[MemberEntity]
+
+ /** The members that ambiguate this implicit conversion
+ Note: for ambiguatingMembers you have the following invariant:
+ assert(ambiguatingMembers.foreach(_.byConversion.isDefined) */
+ def ambiguatingMembers: List[MemberEntity]
+}
+
+/** A trait that encapsulates a constraint necessary for implicit conversion */
+trait Constraint
+
/** A constraint involving a type parameter which must be in scope */
trait ImplicitInScopeConstraint extends Constraint {
/** The type of the implicit value required */
diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala
index 3dd77d47da..eb916333ab 100644
--- a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala
+++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala
@@ -6,6 +6,8 @@ package model
import comment._
+import diagram._
+
import scala.collection._
import scala.util.matching.Regex
@@ -17,7 +19,7 @@ import model.{ RootPackage => RootPackageEntity }
/** This trait extracts all required information for documentation from compilation units */
class ModelFactory(val global: Global, val settings: doc.Settings) {
- thisFactory: ModelFactory with ModelFactoryImplicitSupport with CommentFactory with TreeFactory =>
+ thisFactory: ModelFactory with ModelFactoryImplicitSupport with DiagramFactory with CommentFactory with TreeFactory =>
import global._
import definitions.{ ObjectClass, NothingClass, AnyClass, AnyValClass, AnyRefClass }
@@ -89,6 +91,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) {
def isObject = sym.isModule && !sym.isPackage
def isCaseClass = sym.isCaseClass
def isRootPackage = false
+ def ownType = makeType(sym.tpe, this)
def selfType = if (sym.thisSym eq sym) None else Some(makeType(sym.thisSym.typeOfThis, this))
}
@@ -96,7 +99,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) {
def isDocTemplate = false
}
- abstract class MemberImpl(sym: Symbol, implConv: ImplicitConversionImpl = null, inTpl: => DocTemplateImpl) extends EntityImpl(sym, inTpl) with MemberEntity {
+ abstract class MemberImpl(sym: Symbol, implConv: ImplicitConversionInfoImpl = null, inTpl: => DocTemplateImpl) extends EntityImpl(sym, inTpl) with MemberEntity {
lazy val comment =
if (inTpl == null) None else thisFactory.comment(sym, inTpl)
override def inTemplate = inTpl
@@ -170,7 +173,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) {
case NullaryMethodType(res) => resultTpe(res)
case _ => tpe
}
- val tpe = if (implConv eq null) sym.tpe else implConv.toType memberInfo sym
+ val tpe = if (implConv eq null) sym.tpe else implConv.conversion.toType memberInfo sym
makeTypeInTemplateContext(resultTpe(tpe), inTemplate, sym)
}
def isDef = false
@@ -226,14 +229,26 @@ class ModelFactory(val global: Global, val settings: doc.Settings) {
}
else None
}
- def parentType = {
- if (sym.isPackage || sym == AnyClass) None else {
+
+ def parentTemplates =
+ if (sym.isPackage || sym == AnyClass)
+ List()
+ else
+ sym.tpe.parents.flatMap { tpe: Type =>
+ val tSym = tpe.typeSymbol
+ if (tSym != NoSymbol)
+ List(makeTemplate(tSym))
+ else
+ List()
+ } filter (_.isInstanceOf[DocTemplateEntity])
+
+ def parentTypes =
+ if (sym.isPackage || sym == AnyClass) List() else {
val tps = sym.tpe.parents map { _.asSeenFrom(sym.thisType, sym) }
- Some(makeType(RefinedType(tps, EmptyScope), inTpl))
+ makeParentTypes(RefinedType(tps, EmptyScope), inTpl)
}
- }
- protected def linearizationFromSymbol(symbol: Symbol) = {
+ protected def linearizationFromSymbol(symbol: Symbol): List[(TemplateEntity, TypeEntity)] = {
symbol.ancestors map { ancestor =>
val typeEntity = makeType(symbol.info.baseType(ancestor), this)
val tmplEntity = makeTemplate(ancestor) match {
@@ -248,6 +263,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) {
def linearizationTemplates = linearization map { _._1 }
def linearizationTypes = linearization map { _._2 }
+ /* Subclass cache */
private lazy val subClassesCache = (
if (noSubclassCache(sym)) null
else mutable.ListBuffer[DocTemplateEntity]()
@@ -256,16 +272,47 @@ class ModelFactory(val global: Global, val settings: doc.Settings) {
if (subClassesCache != null)
subClassesCache += sc
}
- def subClasses = if (subClassesCache == null) Nil else subClassesCache.toList
+ def allSubClasses = if (subClassesCache == null) Nil else subClassesCache.toList
+ def directSubClasses = allSubClasses.filter(_.parentTypes.map(_._1).contains(this))
+
+ /* Implcitly convertible class cache */
+ private var implicitlyConvertibleClassesCache: mutable.ListBuffer[DocTemplateEntity] = null
+ def registerImplicitlyConvertibleClass(sc: DocTemplateEntity): Unit = {
+ if (implicitlyConvertibleClassesCache == null)
+ implicitlyConvertibleClassesCache = mutable.ListBuffer[DocTemplateEntity]()
+ implicitlyConvertibleClassesCache += sc
+ }
+
+ def incomingImplicitlyConvertedClasses: List[DocTemplateEntity] =
+ if (implicitlyConvertibleClassesCache == null)
+ List()
+ else
+ implicitlyConvertibleClassesCache.toList
val conversions = if (settings.docImplicits.value) makeImplicitConversions(sym, this) else Nil
+ val outgoingImplicitlyConvertedClasses: List[(TemplateEntity, TypeEntity)] = conversions flatMap (conv =>
+ if (!implicitExcluded(conv.conversionQualifiedName))
+ conv.targetTypeComponents map {
+ case pair@(template, tpe) =>
+ template match {
+ case d: DocTemplateImpl => d.registerImplicitlyConvertibleClass(this)
+ case _ => // nothing
+ }
+ pair
+ }
+ else List()
+ )
+
lazy val memberSyms =
// Only this class's constructors are part of its members, inherited constructors are not.
sym.info.members.filter(s => localShouldDocument(s) && (!s.isConstructor || s.owner == sym) && !isPureBridge(sym) )
- val members = (memberSyms.flatMap(makeMember(_, null, this))) :::
- (conversions.flatMap((_.members))) // also take in the members from implicit conversions
+ // in members, we also take in the members from implicit conversions
+ lazy val ownMembers = (memberSyms.flatMap(makeMember(_, null, this)))
+ lazy val allOwnMembers = (ownMembers ::: ownMembers.flatMap(_.useCaseOf.map(_.asInstanceOf[MemberImpl]))).distinct
+
+ val members = ownMembers ::: (conversions.flatMap((_.members)))
val templates = members collect { case c: DocTemplateEntity => c }
val methods = members collect { case d: Def => d }
@@ -280,6 +327,10 @@ class ModelFactory(val global: Global, val settings: doc.Settings) {
Some(makeDocTemplate(comSym, inTpl))
case _ => None
}
+
+ // We make the diagram a lazy val, since we're not sure we'll include the diagrams in the page
+ lazy val inheritanceDiagram = makeInheritanceDiagram(this)
+ lazy val contentDiagram = makeContentDiagram(this)
}
abstract class PackageImpl(sym: Symbol, inTpl: => PackageImpl) extends DocTemplateImpl(sym, inTpl) with Package {
@@ -296,18 +347,18 @@ class ModelFactory(val global: Global, val settings: doc.Settings) {
abstract class RootPackageImpl(sym: Symbol) extends PackageImpl(sym, null) with RootPackageEntity
- abstract class NonTemplateMemberImpl(sym: Symbol, implConv: ImplicitConversionImpl, inTpl: => DocTemplateImpl) extends MemberImpl(sym, implConv, inTpl) with NonTemplateMemberEntity {
+ abstract class NonTemplateMemberImpl(sym: Symbol, implConv: ImplicitConversionInfoImpl, inTpl: => DocTemplateImpl) extends MemberImpl(sym, implConv, inTpl) with NonTemplateMemberEntity {
override def qualifiedName = optimize(inTemplate.qualifiedName + "#" + name)
lazy val definitionName =
if (implConv == null) optimize(inDefinitionTemplates.head.qualifiedName + "#" + name)
- else optimize(implConv.conversionQualifiedName + "#" + name)
+ else optimize(implConv.conversion.conversionQualifiedName + "#" + name)
def isUseCase = sym.isSynthetic
def isBridge = sym.isBridge
}
- abstract class NonTemplateParamMemberImpl(sym: Symbol, implConv: ImplicitConversionImpl, inTpl: => DocTemplateImpl) extends NonTemplateMemberImpl(sym, implConv, inTpl) {
+ abstract class NonTemplateParamMemberImpl(sym: Symbol, implConv: ImplicitConversionInfoImpl, inTpl: => DocTemplateImpl) extends NonTemplateMemberImpl(sym, implConv, inTpl) {
def valueParams = {
- val info = if (implConv eq null) sym.info else implConv.toType memberInfo sym
+ val info = if (implConv eq null) sym.info else implConv.conversion.toType memberInfo sym
info.paramss map { ps => (ps.zipWithIndex) map { case (p, i) =>
if (p.nameString contains "$") makeValueParam(p, inTpl, optimize("arg" + i)) else makeValueParam(p, inTpl)
}}
@@ -335,7 +386,6 @@ class ModelFactory(val global: Global, val settings: doc.Settings) {
def typeParams =
sym.typeParams map (makeTypeParam(_, inTemplate))
}
-
/* ============== MAKER METHODS ============== */
/** */
@@ -483,8 +533,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) {
}
/** */
- // TODO: Should be able to override the type
- def makeMember(aSym: Symbol, implConv: ImplicitConversionImpl, inTpl: => DocTemplateImpl): List[MemberImpl] = {
+ def makeMember(aSym: Symbol, implConv: ImplicitConversionInfoImpl, inTpl: => DocTemplateImpl): List[MemberImpl] = {
def makeMember0(bSym: Symbol, _useCaseOf: Option[MemberImpl]): Option[MemberImpl] = {
if (bSym.isGetter && bSym.isLazy)
@@ -504,10 +553,10 @@ class ModelFactory(val global: Global, val settings: doc.Settings) {
if (bSym == definitions.Object_synchronized) {
val cSymInfo = (bSym.info: @unchecked) match {
case PolyType(ts, MethodType(List(bp), mt)) =>
- val cp = bp.cloneSymbol.setInfo(definitions.byNameType(bp.info))
+ val cp = bp.cloneSymbol.setPos(bp.pos).setInfo(definitions.byNameType(bp.info))
PolyType(ts, MethodType(List(cp), mt))
}
- bSym.cloneSymbol.setInfo(cSymInfo)
+ bSym.cloneSymbol.setPos(bSym.pos).setInfo(cSymInfo)
}
else bSym
}
@@ -619,6 +668,20 @@ class ModelFactory(val global: Global, val settings: doc.Settings) {
makeType(tpe, inTpl)
}
+ /** Get the types of the parents of the current class, ignoring the refinements */
+ def makeParentTypes(aType: Type, inTpl: => TemplateImpl): List[(TemplateEntity, TypeEntity)] = aType match {
+ case RefinedType(parents, defs) =>
+ val ignoreParents = Set[Symbol](AnyClass, ObjectClass)
+ val filtParents = parents filterNot (x => ignoreParents(x.typeSymbol))
+ filtParents.map(parent => {
+ val templateEntity = makeTemplate(parent.typeSymbol)
+ val typeEntity = makeType(parent, inTpl)
+ (templateEntity, typeEntity)
+ })
+ case _ =>
+ List((makeTemplate(aType.typeSymbol), makeType(aType, inTpl)))
+ }
+
/** */
def makeType(aType: Type, inTpl: => TemplateImpl): TypeEntity = {
def templatePackage = closestPackage(inTpl.sym)
@@ -738,4 +801,10 @@ class ModelFactory(val global: Global, val settings: doc.Settings) {
/** Filter '@bridge' methods only if *they don't override non-bridge methods*. See SI-5373 for details */
def isPureBridge(sym: Symbol) = sym.isBridge && sym.allOverriddenSymbols.forall(_.isBridge)
+
+ // the classes that are excluded from the index should also be excluded from the diagrams
+ def classExcluded(clazz: TemplateEntity): Boolean = settings.hardcoded.isExcluded(clazz.qualifiedName)
+
+ // the implicit conversions that are excluded from the pages should not appear in the diagram
+ def implicitExcluded(convertorMethod: String): Boolean = settings.hardcoded.commonConversionTargets.contains(convertorMethod)
}
diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala
index c3525037cd..64205104aa 100644
--- a/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala
+++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala
@@ -63,8 +63,8 @@ trait ModelFactoryImplicitSupport {
// debugging:
val DEBUG: Boolean = settings.docImplicitsDebug.value
val ERROR: Boolean = true // currently we show all errors
- @inline final def debug(msg: => String) = if (DEBUG) println(msg)
- @inline final def error(msg: => String) = if (ERROR) println(msg)
+ @inline final def debug(msg: => String) = if (DEBUG) settings.printMsg(msg)
+ @inline final def error(msg: => String) = if (ERROR) settings.printMsg(msg)
/** This is a flag that indicates whether to eliminate implicits that cannot be satisfied within the current scope.
* For example, if an implicit conversion requires that there is a Numeric[T] in scope:
@@ -96,6 +96,17 @@ trait ModelFactoryImplicitSupport {
def targetType: TypeEntity = makeType(toType, inTpl)
+ def targetTemplate: Option[TemplateEntity] = toType match {
+ // @Vlad: I'm being extra conservative in template creation -- I don't want to create templates for complex types
+ // such as refinement types because the template can't represent the type corectly (a template corresponds to a
+ // package, class, trait or object)
+ case t: TypeRef => Some(makeTemplate(t.sym))
+ case RefinedType(parents, decls) => None
+ case _ => error("Scaladoc implicits: Could not create template for: " + toType + " of type " + toType.getClass); None
+ }
+
+ def targetTypeComponents: List[(TemplateEntity, TypeEntity)] = makeParentTypes(toType, inTpl)
+
def convertorOwner: TemplateEntity =
if (convSym != NoSymbol)
makeTemplate(convSym.owner)
@@ -126,20 +137,15 @@ trait ModelFactoryImplicitSupport {
lazy val constraints: List[Constraint] = constrs
- val members: List[MemberEntity] = {
+ private val memberImpls: List[MemberImpl] = {
// Obtain the members inherited by the implicit conversion
- var memberSyms = toType.members.filter(implicitShouldDocument(_))
- val existingMembers = sym.info.members
+ val memberSyms = toType.members.filter(implicitShouldDocument(_))
+ val existingSyms = sym.info.members
// Debugging part :)
debug(sym.nameString + "\n" + "=" * sym.nameString.length())
debug(" * conversion " + convSym + " from " + sym.tpe + " to " + toType)
- // Members inherited by implicit conversions cannot override actual members
- memberSyms = memberSyms.filterNot((sym1: Symbol) =>
- existingMembers.exists(sym2 => sym1.name == sym2.name &&
- !isDistinguishableFrom(toType.memberInfo(sym1), sym.info.memberInfo(sym2))))
-
debug(" -> full type: " + toType)
if (constraints.length != 0) {
debug(" -> constraints: ")
@@ -149,10 +155,89 @@ trait ModelFactoryImplicitSupport {
memberSyms foreach (sym => debug(" - "+ sym.decodedName +" : " + sym.info))
debug("")
- memberSyms.flatMap((makeMember(_, this, inTpl)))
+ memberSyms.flatMap({ aSym =>
+ makeTemplate(aSym.owner) match {
+ case d: DocTemplateImpl =>
+ // we can't just pick up nodes from the previous template, although that would be very convenient:
+ // they need the byConversion field to be attached to themselves -- this is design decision I should
+ // revisit soon
+ //
+ // d.ownMembers.collect({
+ // // it's either a member or has a couple of usecases it's hidden behind
+ // case m: MemberImpl if m.sym == aSym =>
+ // m // the member itself
+ // case m: MemberImpl if m.useCaseOf.isDefined && m.useCaseOf.get.asInstanceOf[MemberImpl].sym == aSym =>
+ // m.useCaseOf.get.asInstanceOf[MemberImpl] // the usecase
+ // })
+ makeMember(aSym, new ImplicitConversionInfoImpl(this), d)
+ case _ =>
+ // should only happen if the code for this template is not part of the scaladoc run =>
+ // members won't have any comments
+ makeMember(aSym, new ImplicitConversionInfoImpl(this), inTpl)
+ }
+ })
+ }
+
+ def members: List[MemberEntity] = memberImpls
+
+ def populateShadowingTables(allConvs: List[ImplicitConversionImpl]): Unit = {
+
+ // TODO: This is not clean, we need to put sym.info.members here instead of tpl.memberSyms to avoid
+ // the localShouldDocument(_) filtering in ModelFactory
+ val originalClassMembers = inTpl.memberSyms
+ val otherConversions = allConvs.filterNot(_ == this)
+ assert(otherConversions.length == allConvs.length - 1)
+
+ for (member <- memberImpls) {
+ // for each member in our list
+ val sym1 = member.sym
+ val tpe1 = toType.memberInfo(sym1)
+
+ // check if it's shadowed by a member in the original class
+ var shadowedBySyms: List[Symbol] = List()
+ for (sym2 <- originalClassMembers)
+ if (sym1.name == sym2.name) {
+ val shadowed = !settings.docImplicitsSoundShadowing.value || {
+ val tpe2 = inTpl.sym.info.memberInfo(sym2)
+ !isDistinguishableFrom(tpe1, tpe2)
+ }
+ if (shadowed)
+ shadowedBySyms ::= sym2
+ }
+
+ val shadowedByMembers = inTpl.allOwnMembers.filter((mb: MemberImpl) => shadowedBySyms.contains(mb.sym))
+
+ // check if it's shadowed by another member
+ var ambiguousByMembers: List[MemberEntity] = List()
+ for (conv <- otherConversions)
+ for (member2 <- conv.memberImpls) {
+ val sym2 = member2.sym
+ if (sym1.name == sym2.name) {
+ val tpe2 = conv.toType.memberInfo(sym2)
+ // Ambiguity should be an equivalence relation
+ val ambiguated = !isDistinguishableFrom(tpe1, tpe2) || !isDistinguishableFrom(tpe2, tpe1)
+ if (ambiguated)
+ ambiguousByMembers ::= member2
+ }
+ }
+
+ // we finally have the shadowing info
+ val shadowing = new ImplicitMemberShadowing {
+ def shadowingMembers: List[MemberEntity] = shadowedByMembers
+ def ambiguatingMembers: List[MemberEntity] = ambiguousByMembers
+ }
+
+ member.byConversion.get.asInstanceOf[ImplicitConversionInfoImpl].shadowing = shadowing
+ }
}
}
+ class ImplicitConversionInfoImpl(
+ val conversion: ImplicitConversionImpl) extends ImplicitConversionInfo {
+ // this will be updated as a side effect
+ var shadowing: ImplicitMemberShadowing = null
+ }
+
/* ============== MAKER METHODS ============== */
/**
@@ -171,18 +256,22 @@ trait ModelFactoryImplicitSupport {
val results = global.analyzer.allViewsFrom(sym.tpe, context, sym.typeParams)
var conversions = results.flatMap(result => makeImplicitConversion(sym, result._1, result._2, context, inTpl))
- conversions = conversions.filterNot(_.members.isEmpty)
+ // also keep empty conversions, so they appear in diagrams
+ // conversions = conversions.filter(!_.members.isEmpty)
// Filter out specialized conversions from array
if (sym == ArrayClass)
- conversions = conversions.filterNot((conv: ImplicitConversion) =>
+ conversions = conversions.filterNot((conv: ImplicitConversionImpl) =>
hardcoded.arraySkipConversions.contains(conv.conversionQualifiedName))
// Filter out non-sensical conversions from value types
if (isPrimitiveValueType(sym.tpe))
- conversions = conversions.filter((ic: ImplicitConversion) =>
+ conversions = conversions.filter((ic: ImplicitConversionImpl) =>
hardcoded.valueClassFilter(sym.nameString, ic.conversionQualifiedName))
+ // side-effecting and ugly shadowing table population -- ugly but effective
+ conversions.map(_.populateShadowingTables(conversions))
+
// Put the class-specific conversions in front
val (ownConversions, commonConversions) =
conversions.partition(conv => !hardcoded.commonConversionTargets.contains(conv.conversionQualifiedName))
@@ -218,7 +307,7 @@ trait ModelFactoryImplicitSupport {
* - we also need to transform implicit parameters in the view's signature into constraints, such that Numeric[T4]
* appears as a constraint
*/
- def makeImplicitConversion(sym: Symbol, result: SearchResult, constrs: List[TypeConstraint], context: Context, inTpl: => DocTemplateImpl): List[ImplicitConversion] =
+ def makeImplicitConversion(sym: Symbol, result: SearchResult, constrs: List[TypeConstraint], context: Context, inTpl: => DocTemplateImpl): List[ImplicitConversionImpl] =
if (result.tree == EmptyTree) Nil
else {
// `result` will contain the type of the view (= implicit conversion method)
@@ -493,8 +582,8 @@ trait ModelFactoryImplicitSupport {
// - common methods (in Any, AnyRef, Object) as they are automatically removed
// - private and protected members (not accessible following an implicit conversion)
// - members starting with _ (usually reserved for internal stuff)
- localShouldDocument(aSym) && (!aSym.isConstructor) && (aSym.owner != ObjectClass) &&
- (aSym.owner != AnyClass) && (aSym.owner != AnyRefClass) &&
+ localShouldDocument(aSym) && (!aSym.isConstructor) && (aSym.owner != AnyValClass) &&
+ (aSym.owner != AnyClass) && (aSym.owner != ObjectClass) &&
(!aSym.isProtected) && (!aSym.isPrivate) && (!aSym.name.startsWith("_")) &&
(aSym.isMethod || aSym.isGetter || aSym.isSetter) &&
(aSym.nameString != "getClass")
@@ -509,12 +598,11 @@ trait ModelFactoryImplicitSupport {
def isDistinguishableFrom(t1: Type, t2: Type): Boolean =
if (t1.paramss.map(_.length) == t2.paramss.map(_.length)) {
for ((t1p, t2p) <- t1.paramss.flatten zip t2.paramss.flatten)
- if (!isSubType(t1 memberInfo t1p, t2 memberInfo t2p))
- return true // if on the corresponding parameter you give a type that is in t1 but not in t2
- // example:
- // def foo(a: Either[Int, Double]): Int = 3
- // def foo(b: Left[T1]): Int = 6
- // a.foo(Right(4.5d)) prints out 3 :)
+ if (!isSubType(t1 memberInfo t1p, t2 memberInfo t2p))
+ return true // if on the corresponding parameter you give a type that is in t1 but not in t2
+ // def foo(a: Either[Int, Double]): Int = 3
+ // def foo(b: Left[T1]): Int = 6
+ // a.foo(Right(4.5d)) prints out 3 :)
false
} else true // the member structure is different foo(3, 5) vs foo(3)(5)
} \ No newline at end of file
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 ef4047cebf..ecc3273903 100644
--- a/src/compiler/scala/tools/nsc/doc/model/comment/Body.scala
+++ b/src/compiler/scala/tools/nsc/doc/model/comment/Body.scala
@@ -67,8 +67,8 @@ 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 EntityLink(target: TemplateEntity) extends Inline
final case class Monospace(text: Inline) extends Inline
final case class Text(text: String) extends Inline
final case class HtmlTag(data: String) extends Inline {
diff --git a/src/compiler/scala/tools/nsc/doc/model/comment/Comment.scala b/src/compiler/scala/tools/nsc/doc/model/comment/Comment.scala
index 914275dd8d..7b70683db5 100644
--- a/src/compiler/scala/tools/nsc/doc/model/comment/Comment.scala
+++ b/src/compiler/scala/tools/nsc/doc/model/comment/Comment.scala
@@ -108,6 +108,12 @@ abstract class Comment {
/** A description for the primary constructor */
def constructor: Option[Body]
+ /** A set of diagram directives for the inheritance diagram */
+ def inheritDiagram: List[String]
+
+ /** A set of diagram directives for the content diagram */
+ def contentDiagram: List[String]
+
override def toString =
body.toString + "\n" +
(authors map ("@author " + _.toString)).mkString("\n") +
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 996223b9f9..a46be37d60 100644
--- a/src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala
+++ b/src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala
@@ -97,37 +97,41 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory =>
/* Creates comments with necessary arguments */
def createComment (
- body0: Option[Body] = None,
- authors0: List[Body] = List.empty,
- see0: List[Body] = List.empty,
- result0: Option[Body] = None,
- throws0: Map[String,Body] = Map.empty,
- valueParams0: Map[String,Body] = Map.empty,
- typeParams0: Map[String,Body] = Map.empty,
- version0: Option[Body] = None,
- since0: Option[Body] = None,
- todo0: List[Body] = List.empty,
- deprecated0: Option[Body] = None,
- note0: List[Body] = List.empty,
- example0: List[Body] = List.empty,
- constructor0: Option[Body] = None,
- source0: Option[String] = None
+ body0: Option[Body] = None,
+ authors0: List[Body] = List.empty,
+ see0: List[Body] = List.empty,
+ result0: Option[Body] = None,
+ throws0: Map[String,Body] = Map.empty,
+ valueParams0: Map[String,Body] = Map.empty,
+ typeParams0: Map[String,Body] = Map.empty,
+ version0: Option[Body] = None,
+ since0: Option[Body] = None,
+ todo0: List[Body] = List.empty,
+ deprecated0: Option[Body] = None,
+ note0: List[Body] = List.empty,
+ example0: List[Body] = List.empty,
+ constructor0: Option[Body] = None,
+ source0: Option[String] = None,
+ inheritDiagram0: List[String] = List.empty,
+ contentDiagram0: List[String] = List.empty
) : Comment = new Comment{
- val body = if(body0 isDefined) body0.get else Body(Seq.empty)
- val authors = authors0
- val see = see0
- val result = result0
- val throws = throws0
- val valueParams = valueParams0
- val typeParams = typeParams0
- val version = version0
- val since = since0
- val todo = todo0
- val deprecated = deprecated0
- val note = note0
- val example = example0
- val constructor = constructor0
- val source = source0
+ val body = if(body0 isDefined) body0.get else Body(Seq.empty)
+ val authors = authors0
+ val see = see0
+ val result = result0
+ val throws = throws0
+ val valueParams = valueParams0
+ val typeParams = typeParams0
+ val version = version0
+ val since = since0
+ val todo = todo0
+ val deprecated = deprecated0
+ val note = note0
+ val example = example0
+ val constructor = constructor0
+ val source = source0
+ val inheritDiagram = inheritDiagram0
+ val contentDiagram = contentDiagram0
}
protected val endOfText = '\u0003'
@@ -186,6 +190,10 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory =>
protected val safeTagMarker = '\u000E'
+ /** A Scaladoc tag not linked to a symbol and not followed by text */
+ protected val SingleTag =
+ new Regex("""\s*@(\S+)\s*""")
+
/** A Scaladoc tag not linked to a symbol. Returns the name of the tag, and the rest of the line. */
protected val SimpleTag =
new Regex("""\s*@(\S+)\s+(.*)""")
@@ -306,6 +314,11 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory =>
val value = body :: tags.getOrElse(key, Nil)
parse0(docBody, tags + (key -> value), Some(key), ls, inCodeBlock)
+ case SingleTag(name) :: ls if (!inCodeBlock) =>
+ val key = SimpleTagKey(name)
+ val value = "" :: tags.getOrElse(key, Nil)
+ parse0(docBody, tags + (key -> value), Some(key), ls, inCodeBlock)
+
case line :: ls if (lastTagKey.isDefined) =>
val key = lastTagKey.get
val value =
@@ -321,9 +334,24 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory =>
parse0(docBody, tags, lastTagKey, ls, inCodeBlock)
case Nil =>
+ // Take the {inheritance, content} diagram keys aside, as it doesn't need any parsing
+ val inheritDiagramTag = SimpleTagKey("inheritanceDiagram")
+ val contentDiagramTag = SimpleTagKey("contentDiagram")
+
+ val inheritDiagramText: List[String] = tags.get(inheritDiagramTag) match {
+ case Some(list) => list
+ case None => List.empty
+ }
+
+ val contentDiagramText: List[String] = tags.get(contentDiagramTag) match {
+ case Some(list) => list
+ case None => List.empty
+ }
+
+ val tagsWithoutDiagram = tags.filterNot(pair => pair._1 == inheritDiagramTag || pair._1 == contentDiagramTag)
val bodyTags: mutable.Map[TagKey, List[Body]] =
- mutable.Map(tags mapValues {tag => tag map (parseWiki(_, pos))} toSeq: _*)
+ mutable.Map(tagsWithoutDiagram mapValues {tag => tag map (parseWiki(_, pos))} toSeq: _*)
def oneTag(key: SimpleTagKey): Option[Body] =
((bodyTags remove key): @unchecked) match {
@@ -356,21 +384,23 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory =>
}
val com = createComment (
- body0 = Some(parseWiki(docBody.toString, pos)),
- authors0 = allTags(SimpleTagKey("author")),
- see0 = allTags(SimpleTagKey("see")),
- result0 = oneTag(SimpleTagKey("return")),
- throws0 = allSymsOneTag(SimpleTagKey("throws")),
- valueParams0 = allSymsOneTag(SimpleTagKey("param")),
- typeParams0 = allSymsOneTag(SimpleTagKey("tparam")),
- version0 = oneTag(SimpleTagKey("version")),
- since0 = oneTag(SimpleTagKey("since")),
- todo0 = allTags(SimpleTagKey("todo")),
- deprecated0 = oneTag(SimpleTagKey("deprecated")),
- note0 = allTags(SimpleTagKey("note")),
- example0 = allTags(SimpleTagKey("example")),
- constructor0 = oneTag(SimpleTagKey("constructor")),
- source0 = Some(clean(src).mkString("\n"))
+ body0 = Some(parseWiki(docBody.toString, pos)),
+ authors0 = allTags(SimpleTagKey("author")),
+ see0 = allTags(SimpleTagKey("see")),
+ result0 = oneTag(SimpleTagKey("return")),
+ throws0 = allSymsOneTag(SimpleTagKey("throws")),
+ valueParams0 = allSymsOneTag(SimpleTagKey("param")),
+ typeParams0 = allSymsOneTag(SimpleTagKey("tparam")),
+ version0 = oneTag(SimpleTagKey("version")),
+ since0 = oneTag(SimpleTagKey("since")),
+ todo0 = allTags(SimpleTagKey("todo")),
+ deprecated0 = oneTag(SimpleTagKey("deprecated")),
+ note0 = allTags(SimpleTagKey("note")),
+ example0 = allTags(SimpleTagKey("example")),
+ constructor0 = oneTag(SimpleTagKey("constructor")),
+ source0 = Some(clean(src).mkString("\n")),
+ inheritDiagram0 = inheritDiagramText,
+ contentDiagram0 = contentDiagramText
)
for ((key, _) <- bodyTags)
@@ -686,13 +716,6 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory =>
)
}
- def entityLink(query: String): Inline = findTemplate(query) match {
- case Some(tpl) =>
- EntityLink(tpl)
- case None =>
- Text(query)
- }
-
def link(): Inline = {
val SchemeUri = """([^:]+:.*)""".r
jump("[[")
@@ -717,7 +740,8 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory =>
if (!qualName.contains(".") && !definitions.packageExists(qualName))
reportError(pos, "entity link to " + qualName + " should be a fully qualified name")
- entityLink(qualName)
+ // move the template resolution as late as possible
+ EntityLink(qualName, () => findTemplate(qualName))
}
}
@@ -733,8 +757,8 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory =>
nextChar()
}
- /**
- * Eliminates the (common) leading spaces in all lines, based on the first line
+ /**
+ * Eliminates the (common) leading spaces in all lines, based on the first line
* For indented pieces of code, it reduces the indent to the least whitespace prefix:
* {{{
* indented example
@@ -757,11 +781,11 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory =>
while (index < code.length) {
code(index) match {
case ' ' =>
- if (wsArea)
+ if (wsArea)
crtSkip += 1
case c =>
wsArea = (c == '\n')
- maxSkip = if (firstLine || emptyLine) maxSkip else if (maxSkip <= crtSkip) maxSkip else crtSkip
+ maxSkip = if (firstLine || emptyLine) maxSkip else if (maxSkip <= crtSkip) maxSkip else crtSkip
crtSkip = if (c == '\n') 0 else crtSkip
firstLine = if (c == '\n') false else firstLine
emptyLine = if (c == '\n') true else false
diff --git a/src/compiler/scala/tools/nsc/doc/model/diagram/Diagram.scala b/src/compiler/scala/tools/nsc/doc/model/diagram/Diagram.scala
new file mode 100644
index 0000000000..28a8c7d37d
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/doc/model/diagram/Diagram.scala
@@ -0,0 +1,143 @@
+package scala.tools.nsc.doc
+package model
+package diagram
+
+import model._
+
+/**
+ * The diagram base classes
+ *
+ * @author Damien Obrist
+ * @author Vlad Ureche
+ */
+abstract class Diagram {
+ def nodes: List[Node]
+ def edges: List[(Node, List[Node])]
+ def isPackageDiagram = false
+ def isClassDiagram = false
+ def depthInfo: DepthInfo
+}
+
+case class PackageDiagram(nodes:List[/*Class*/Node], edges:List[(Node, List[Node])]) extends Diagram {
+ override def isPackageDiagram = true
+ lazy val depthInfo = new PackageDiagramDepth(this)
+}
+
+/** A class diagram */
+case class ClassDiagram(thisNode: ThisNode,
+ superClasses: List[/*Class*/Node],
+ subClasses: List[/*Class*/Node],
+ incomingImplicits: List[ImplicitNode],
+ outgoingImplicits: List[ImplicitNode]) extends Diagram {
+ def nodes = thisNode :: superClasses ::: subClasses ::: incomingImplicits ::: outgoingImplicits
+ def edges = (thisNode -> (superClasses ::: outgoingImplicits)) ::
+ (subClasses ::: incomingImplicits).map(_ -> List(thisNode))
+
+ override def isClassDiagram = true
+ lazy val depthInfo = new DepthInfo {
+ def maxDepth = 3
+ def nodeDepth(node: Node) =
+ if (node == thisNode) 1
+ else if (superClasses.contains(node)) 0
+ else if (subClasses.contains(node)) 2
+ else if (incomingImplicits.contains(node) || outgoingImplicits.contains(node)) 1
+ else -1
+ }
+}
+
+trait DepthInfo {
+ /** Gives the maximum depth */
+ def maxDepth: Int
+ /** Gives the depth of any node in the diagram or -1 if the node is not in the diagram */
+ def nodeDepth(node: Node): Int
+}
+
+abstract class Node {
+ def name = tpe.name
+ def tpe: TypeEntity
+ def tpl: Option[TemplateEntity]
+ /** shortcut to get a DocTemplateEntity */
+ def doctpl: Option[DocTemplateEntity] = tpl match {
+ case Some(tpl) => tpl match {
+ case d: DocTemplateEntity => Some(d)
+ case _ => None
+ }
+ case _ => None
+ }
+ /* shortcuts to find the node type without matching */
+ def isThisNode = false
+ def isNormalNode = false
+ def isClassNode = if (tpl.isDefined) (tpl.get.isClass || tpl.get.qualifiedName == "scala.AnyRef") else false
+ def isTraitNode = if (tpl.isDefined) tpl.get.isTrait else false
+ def isObjectNode= if (tpl.isDefined) tpl.get.isObject else false
+ def isOtherNode = !(isClassNode || isTraitNode || isObjectNode)
+ def isImplicitNode = false
+ def isOutsideNode = false
+}
+
+// different matchers, allowing you to use the pattern matcher against any node
+// NOTE: A ThisNode or ImplicitNode can at the same time be ClassNode/TraitNode/OtherNode, not exactly according to
+// case class specification -- thus a complete match would be:
+// node match {
+// case ThisNode(tpe, _) => /* case for this node, you can still use .isClass, .isTrait and .isOther */
+// case ImplicitNode(tpe, _) => /* case for an implicit node, you can still use .isClass, .isTrait and .isOther */
+// case _ => node match {
+// case ClassNode(tpe, _) => /* case for a non-this, non-implicit Class node */
+// case TraitNode(tpe, _) => /* case for a non-this, non-implicit Trait node */
+// case OtherNode(tpe, _) => /* case for a non-this, non-implicit Other node */
+// }
+// }
+object Node { def unapply(n: Node): Option[(TypeEntity, Option[TemplateEntity])] = Some((n.tpe, n.tpl)) }
+object ClassNode { def unapply(n: Node): Option[(TypeEntity, Option[TemplateEntity])] = if (n.isClassNode) Some((n.tpe, n.tpl)) else None }
+object TraitNode { def unapply(n: Node): Option[(TypeEntity, Option[TemplateEntity])] = if (n.isTraitNode) Some((n.tpe, n.tpl)) else None }
+object ObjectNode { def unapply(n: Node): Option[(TypeEntity, Option[TemplateEntity])] = if (n.isObjectNode) Some((n.tpe, n.tpl)) else None }
+object OutsideNode { def unapply(n: Node): Option[(TypeEntity, Option[TemplateEntity])] = if (n.isOutsideNode) Some((n.tpe, n.tpl)) else None }
+object OtherNode { def unapply(n: Node): Option[(TypeEntity, Option[TemplateEntity])] = if (n.isOtherNode) Some((n.tpe, n.tpl)) else None }
+
+
+
+/** The node for the current class */
+case class ThisNode(tpe: TypeEntity, tpl: Option[TemplateEntity]) extends Node { override def isThisNode = true }
+
+/** The usual node */
+case class NormalNode(tpe: TypeEntity, tpl: Option[TemplateEntity]) extends Node { override def isNormalNode = true }
+
+/** A class or trait the thisnode can be converted to by an implicit conversion
+ * TODO: I think it makes more sense to use the tpe links to templates instead of the TemplateEntity for implicit nodes
+ * since some implicit conversions convert the class to complex types that cannot be represented as a single tmeplate
+ */
+case class ImplicitNode(tpe: TypeEntity, tpl: Option[TemplateEntity]) extends Node { override def isImplicitNode = true }
+
+/** An outside node is shown in packages when a class from a different package makes it to the package diagram due to
+ * its relation to a class in the package (and @contentDiagram showInheritedNodes annotation) */
+case class OutsideNode(tpe: TypeEntity, tpl: Option[TemplateEntity]) extends Node { override def isOutsideNode = true }
+
+
+// Computing and offering node depth information
+class PackageDiagramDepth(pack: PackageDiagram) extends DepthInfo {
+ private[this] var _maxDepth = 0
+ private[this] var _nodeDepth = Map[Node, Int]()
+ private[this] var seedNodes = Set[Node]()
+ private[this] val invertedEdges: Map[Node, List[Node]] =
+ pack.edges.flatMap({case (node: Node, outgoing: List[Node]) => outgoing.map((_, node))}).groupBy(_._1).map({case (k, values) => (k, values.map(_._2))}).withDefaultValue(Nil)
+ private[this] val directEdges: Map[Node, List[Node]] = pack.edges.toMap.withDefaultValue(Nil)
+
+ // seed base nodes, to minimize noise - they can't all have parents, else there would only be cycles
+ seedNodes ++= pack.nodes.filter(directEdges(_).isEmpty)
+
+ while (!seedNodes.isEmpty) {
+ var newSeedNodes = Set[Node]()
+ for (node <- seedNodes) {
+ val depth = 1 + (-1 :: directEdges(node).map(_nodeDepth.getOrElse(_, -1))).max
+ if (depth != _nodeDepth.getOrElse(node, -1)) {
+ _nodeDepth += (node -> depth)
+ newSeedNodes ++= invertedEdges(node)
+ if (depth > _maxDepth) _maxDepth = depth
+ }
+ }
+ seedNodes = newSeedNodes
+ }
+
+ val maxDepth = _maxDepth
+ def nodeDepth(node: Node) = _nodeDepth.getOrElse(node, -1)
+} \ No newline at end of file
diff --git a/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramDirectiveParser.scala b/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramDirectiveParser.scala
new file mode 100644
index 0000000000..c06b5d50b7
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramDirectiveParser.scala
@@ -0,0 +1,248 @@
+package scala.tools.nsc.doc
+package model
+package diagram
+
+import model._
+import comment.CommentFactory
+import java.util.regex.{Pattern, Matcher}
+import scala.util.matching.Regex
+
+// statistics
+import html.page.diagram.DiagramStats
+
+/**
+ * This trait takes care of parsing @{inheritance, content}Diagram annotations
+ *
+ * @author Damien Obrist
+ * @author Vlad Ureche
+ */
+trait DiagramDirectiveParser {
+ this: ModelFactory with DiagramFactory with CommentFactory with TreeFactory =>
+
+ ///// DIAGRAM FILTERS //////////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * The DiagramFilter trait directs the diagram engine about the way the diagram should be displayed
+ *
+ * Vlad: There's an explanation I owe to people using diagrams and not finding a way to hide a specific class from
+ * all diagrams at once. So why did I choose to allow you to only control the diagrams at class level? So, the
+ * reason is you would break the separate scaladoc compilation:
+ * If you have an "@diagram hideMyClass" annotation in class A and you run scaladoc on it along with its subclass B
+ * A will not appear in B's diagram. But if you scaladoc only on B, A's comment will not be parsed and the
+ * instructions to hide class A from all diagrams will not be available. Thus I prefer to force you to control the
+ * diagrams of each class locally. The problem does not appear with scalac, as scalac stores all its necessary
+ * information (like scala signatures) serialized in the .class file. But we couldn't store doc comments in the class
+ * file, could we? (Turns out we could, but that's another story)
+ *
+ * Any flaming for this decision should go to scala-internals@googlegroups.com
+ */
+ trait DiagramFilter {
+ /** A flag to hide the diagram completely */
+ def hideDiagram: Boolean
+ /** Hide incoming implicit conversions (for type hierarchy diagrams) */
+ def hideIncomingImplicits: Boolean
+ /** Hide outgoing implicit conversions (for type hierarchy diagrams) */
+ def hideOutgoingImplicits: Boolean
+ /** Hide superclasses (for type hierarchy diagrams) */
+ def hideSuperclasses: Boolean
+ /** Hide subclasses (for type hierarchy diagrams) */
+ def hideSubclasses: Boolean
+ /** Show related classes from other objects/traits/packages (for content diagrams) */
+ def showInheritedNodes: Boolean
+ /** Hide a node from the diagram */
+ def hideNode(clazz: TemplateEntity): Boolean
+ /** Hide an edge from the diagram */
+ def hideEdge(clazz1: TemplateEntity, clazz2: TemplateEntity): Boolean
+ }
+
+ /** Main entry point into this trait: generate the filter for inheritance diagrams */
+ def makeInheritanceDiagramFilter(template: DocTemplateImpl): DiagramFilter = {
+ val defaultFilter = if (template.isClass || template.isTrait) FullDiagram else NoDiagramAtAll
+ if (template.comment.isDefined)
+ makeDiagramFilter(template, template.comment.get.inheritDiagram, defaultFilter, true)
+ else
+ defaultFilter
+ }
+
+ /** Main entry point into this trait: generate the filter for content diagrams */
+ def makeContentDiagramFilter(template: DocTemplateImpl): DiagramFilter = {
+ val defaultFilter = if (template.isPackage || template.isObject) FullDiagram else NoDiagramAtAll
+ if (template.comment.isDefined)
+ makeDiagramFilter(template, template.comment.get.contentDiagram, defaultFilter, false)
+ else
+ defaultFilter
+ }
+
+ protected var tFilter = 0l
+ protected var tModel = 0l
+
+ /** Show the entire diagram, no filtering */
+ case object FullDiagram extends DiagramFilter {
+ val hideDiagram: Boolean = false
+ val hideIncomingImplicits: Boolean = false
+ val hideOutgoingImplicits: Boolean = false
+ val hideSuperclasses: Boolean = false
+ val hideSubclasses: Boolean = false
+ val showInheritedNodes: Boolean = false
+ def hideNode(clazz: TemplateEntity): Boolean = false
+ def hideEdge(clazz1: TemplateEntity, clazz2: TemplateEntity): Boolean = false
+ }
+
+ /** Hide the diagram completely, no need for special filtering */
+ case object NoDiagramAtAll extends DiagramFilter {
+ val hideDiagram: Boolean = true
+ val hideIncomingImplicits: Boolean = true
+ val hideOutgoingImplicits: Boolean = true
+ val hideSuperclasses: Boolean = true
+ val hideSubclasses: Boolean = true
+ val showInheritedNodes: Boolean = false
+ def hideNode(clazz: TemplateEntity): Boolean = true
+ def hideEdge(clazz1: TemplateEntity, clazz2: TemplateEntity): Boolean = true
+ }
+
+ /** The AnnotationDiagramFilter trait directs the diagram engine according to an annotation
+ * TODO: Should document the annotation, for now see parseDiagramAnnotation in ModelFactory.scala */
+ case class AnnotationDiagramFilter(hideDiagram: Boolean,
+ hideIncomingImplicits: Boolean,
+ hideOutgoingImplicits: Boolean,
+ hideSuperclasses: Boolean,
+ hideSubclasses: Boolean,
+ showInheritedNodes: Boolean,
+ hideNodesFilter: List[Pattern],
+ hideEdgesFilter: List[(Pattern, Pattern)]) extends DiagramFilter {
+
+ def hideNode(clazz: TemplateEntity): Boolean = {
+ val qualifiedName = clazz.qualifiedName
+ for (hideFilter <- hideNodesFilter)
+ if (hideFilter.matcher(qualifiedName).matches) {
+ // println(hideFilter + ".matcher(" + qualifiedName + ").matches = " + hideFilter.matcher(qualifiedName).matches)
+ return true
+ }
+ false
+ }
+
+ def hideEdge(clazz1: TemplateEntity, clazz2: TemplateEntity): Boolean = {
+ val clazz1Name = clazz1.qualifiedName
+ val clazz2Name = clazz2.qualifiedName
+ for ((clazz1Filter, clazz2Filter) <- hideEdgesFilter) {
+ if (clazz1Filter.matcher(clazz1Name).matches &&
+ clazz2Filter.matcher(clazz2Name).matches) {
+ // println(clazz1Filter + ".matcher(" + clazz1Name + ").matches = " + clazz1Filter.matcher(clazz1Name).matches)
+ // println(clazz2Filter + ".matcher(" + clazz2Name + ").matches = " + clazz2Filter.matcher(clazz2Name).matches)
+ return true
+ }
+ }
+ false
+ }
+ }
+
+ // TODO: This could certainly be improved -- right now the only regex is *, but there's no way to match a single identifier
+ private val NodeSpecRegex = "\\\"[A-Za-z\\*][A-Za-z\\.\\*]*\\\""
+ private val NodeSpecPattern = Pattern.compile(NodeSpecRegex)
+ private val EdgeSpecRegex = "\\(" + NodeSpecRegex + "\\s*\\->\\s*" + NodeSpecRegex + "\\)"
+ private val EdgeSpecPattern = Pattern.compile(NodeSpecRegex)
+ // And the composed regexes:
+ private val HideNodesRegex = new Regex("^hideNodes(\\s*" + NodeSpecRegex + ")+$")
+ private val HideEdgesRegex = new Regex("^hideEdges(\\s*" + EdgeSpecRegex + ")+$")
+
+ private def makeDiagramFilter(template: DocTemplateImpl,
+ directives: List[String],
+ defaultFilter: DiagramFilter,
+ isInheritanceDiagram: Boolean): DiagramFilter = directives match {
+
+ // if there are no specific diagram directives, return the default filter (either FullDiagram or NoDiagramAtAll)
+ case Nil =>
+ defaultFilter
+
+ // compute the exact filters. By including the annotation, the diagram is autmatically added
+ case _ =>
+ tFilter -= System.currentTimeMillis
+ var hideDiagram0: Boolean = false
+ var hideIncomingImplicits0: Boolean = false
+ var hideOutgoingImplicits0: Boolean = false
+ var hideSuperclasses0: Boolean = false
+ var hideSubclasses0: Boolean = false
+ var showInheritedNodes0: Boolean = false
+ var hideNodesFilter0: List[Pattern] = Nil
+ var hideEdgesFilter0: List[(Pattern, Pattern)] = Nil
+
+ def warning(message: String) = {
+ // we need the position from the package object (well, ideally its comment, but yeah ...)
+ val sym = if (template.sym.isPackage) template.sym.info.member(global.nme.PACKAGE) else template.sym
+ assert((sym != global.NoSymbol) || (sym == global.definitions.RootPackage))
+ global.reporter.warning(sym.pos, message)
+ }
+
+ def preparePattern(className: String) =
+ "^" + className.stripPrefix("\"").stripSuffix("\"").replaceAll("\\.", "\\\\.").replaceAll("\\*", ".*") + "$"
+
+ // separate entries:
+ val entries = directives.foldRight("")(_ + " " + _).split(",").map(_.trim)
+ for (entry <- entries)
+ entry match {
+ case "hideDiagram" =>
+ hideDiagram0 = true
+ case "hideIncomingImplicits" if isInheritanceDiagram =>
+ hideIncomingImplicits0 = true
+ case "hideOutgoingImplicits" if isInheritanceDiagram =>
+ hideOutgoingImplicits0 = true
+ case "hideSuperclasses" if isInheritanceDiagram =>
+ hideSuperclasses0 = true
+ case "hideSubclasses" if isInheritanceDiagram =>
+ hideSubclasses0 = true
+ case "showInheritedNodes" if !isInheritanceDiagram =>
+ showInheritedNodes0 = true
+ case HideNodesRegex(last) =>
+ val matcher = NodeSpecPattern.matcher(entry)
+ while (matcher.find()) {
+ val classPattern = Pattern.compile(preparePattern(matcher.group()))
+ hideNodesFilter0 ::= classPattern
+ }
+ case HideEdgesRegex(last) =>
+ val matcher = NodeSpecPattern.matcher(entry)
+ while (matcher.find()) {
+ val class1Pattern = Pattern.compile(preparePattern(matcher.group()))
+ assert(matcher.find()) // it's got to be there, just matched it!
+ val class2Pattern = Pattern.compile(preparePattern(matcher.group()))
+ hideEdgesFilter0 ::= ((class1Pattern, class2Pattern))
+ }
+ case "" =>
+ // don't need to do anything about it
+ case _ =>
+ warning("Could not understand diagram annotation in " + template.fullName + ": unmatched entry \"" +
+ entry + "\".\n" +
+ " This could be because:\n" +
+ " - you forgot to separate entries by commas\n" +
+ " - you used a tag that is not allowed in the current context (like @contentDiagram hideSuperclasses)\n"+
+ " - you did not use one of the allowed tags (see docs.scala-lang.org for scaladoc annotations)")
+ }
+ val result =
+ if (hideDiagram0)
+ NoDiagramAtAll
+ else if ((hideNodesFilter0.isEmpty) &&
+ (hideEdgesFilter0.isEmpty) &&
+ (hideIncomingImplicits0 == false) &&
+ (hideOutgoingImplicits0 == false) &&
+ (hideSuperclasses0 == false) &&
+ (hideSubclasses0 == false) &&
+ (showInheritedNodes0 == false) &&
+ (hideDiagram0 == false))
+ FullDiagram
+ else
+ AnnotationDiagramFilter(
+ hideDiagram = hideDiagram0,
+ hideIncomingImplicits = hideIncomingImplicits0,
+ hideOutgoingImplicits = hideOutgoingImplicits0,
+ hideSuperclasses = hideSuperclasses0,
+ hideSubclasses = hideSubclasses0,
+ showInheritedNodes = showInheritedNodes0,
+ hideNodesFilter = hideNodesFilter0,
+ hideEdgesFilter = hideEdgesFilter0)
+
+ if (settings.docDiagramsDebug.value && result != NoDiagramAtAll && result != FullDiagram)
+ settings.printMsg(template.fullName + " filter: " + result)
+ tFilter += System.currentTimeMillis
+
+ result
+ }
+} \ No newline at end of file
diff --git a/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala b/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala
new file mode 100644
index 0000000000..4ae5e7a5cb
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala
@@ -0,0 +1,197 @@
+package scala.tools.nsc.doc
+package model
+package diagram
+
+import model._
+import comment.CommentFactory
+import collection.mutable
+
+// statistics
+import html.page.diagram.DiagramStats
+
+/**
+ * This trait takes care of generating the diagram for classes and packages
+ *
+ * @author Damien Obrist
+ * @author Vlad Ureche
+ */
+trait DiagramFactory extends DiagramDirectiveParser {
+ this: ModelFactory with DiagramFactory with CommentFactory with TreeFactory =>
+
+ /** Create the inheritance diagram for this template */
+ def makeInheritanceDiagram(tpl: DocTemplateImpl): Option[Diagram] = {
+
+ tFilter = 0
+ tModel = -System.currentTimeMillis
+
+ // the diagram filter
+ val diagramFilter = makeInheritanceDiagramFilter(tpl)
+
+ val result =
+ if (diagramFilter == NoDiagramAtAll)
+ None
+ else {
+ // the main node
+ val thisNode = ThisNode(tpl.ownType, Some(tpl))
+
+ // superclasses
+ var superclasses = List[Node]()
+ tpl.parentTypes.collect { case p: (TemplateEntity, TypeEntity) if !classExcluded(p._1) => p } foreach {
+ t: (TemplateEntity, TypeEntity) =>
+ val n = NormalNode(t._2, Some(t._1))
+ superclasses ::= n
+ }
+ val filteredSuperclasses = if (diagramFilter.hideSuperclasses) Nil else superclasses
+
+ // incoming implcit conversions
+ lazy val incomingImplicitNodes = tpl.incomingImplicitlyConvertedClasses.map(tpl => ImplicitNode(tpl.ownType, Some(tpl)))
+ val filteredIncomingImplicits = if (diagramFilter.hideIncomingImplicits) Nil else incomingImplicitNodes
+
+ // subclasses
+ val subclasses = tpl.directSubClasses.flatMap {
+ case d: TemplateEntity if !classExcluded(d) => List(NormalNode(d.ownType, Some(d)))
+ case _ => Nil
+ }
+ val filteredSubclasses = if (diagramFilter.hideSubclasses) Nil else subclasses
+
+ // outgoing implicit coversions
+ lazy val implicitNodes = tpl.outgoingImplicitlyConvertedClasses.map(pair => ImplicitNode(pair._2, Some(pair._1)))
+ val filteredImplicitOutgoingNodes = if (diagramFilter.hideOutgoingImplicits) Nil else implicitNodes
+
+ // final diagram filter
+ filterDiagram(ClassDiagram(thisNode, filteredSuperclasses.reverse, filteredSubclasses.reverse, filteredIncomingImplicits, filteredImplicitOutgoingNodes), diagramFilter)
+ }
+
+ tModel += System.currentTimeMillis
+ DiagramStats.addFilterTime(tFilter)
+ DiagramStats.addModelTime(tModel-tFilter)
+
+ result
+ }
+
+ /** Create the content diagram for this template */
+ def makeContentDiagram(pack: DocTemplateImpl): Option[Diagram] = {
+
+ tFilter = 0
+ tModel = -System.currentTimeMillis
+
+ // the diagram filter
+ val diagramFilter = makeContentDiagramFilter(pack)
+
+ val result =
+ if (diagramFilter == NoDiagramAtAll)
+ None
+ else {
+ var mapNodes = Map[DocTemplateEntity, Node]()
+ var nodesShown = Set[DocTemplateEntity]()
+ var edgesAll = List[(DocTemplateEntity, List[DocTemplateEntity])]()
+
+ // classes is the entire set of classes and traits in the package, they are the superset of nodes in the diagram
+ // we collect classes, traits and objects without a companion, which are usually used as values(e.g. scala.None)
+ val dnodes = pack.members collect {
+ case d: DocTemplateEntity if d.isClass || d.isTrait || (d.isObject && !d.companion.isDefined) &&
+ ((d.inTemplate == pack) || diagramFilter.showInheritedNodes) => d
+ }
+
+ // for each node, add its subclasses
+ for (node <- dnodes if !classExcluded(node)) {
+ val superClasses = node.parentTypes.collect {
+ case (tpl: DocTemplateEntity, tpe) if tpl.inTemplate == pack && !classExcluded(tpl) => tpl
+ case (tpl: DocTemplateEntity, tpe) if tpl.inTemplate != pack && !classExcluded(tpl) && diagramFilter.showInheritedNodes && (pack.members contains tpl) => tpl
+ }
+
+ if (!superClasses.isEmpty) {
+ nodesShown += node
+ nodesShown ++= superClasses
+ }
+
+ edgesAll ::= node -> superClasses
+ mapNodes += node -> (if (node.inTemplate == pack) NormalNode(node.ownType, Some(node)) else OutsideNode(node.ownType, Some(node)))
+ }
+
+ if (nodesShown.isEmpty)
+ None
+ else {
+ val nodes = dnodes.filter(nodesShown.contains(_)).map(mapNodes(_))
+ val edges = edgesAll.map(pair => (mapNodes(pair._1), pair._2.map(mapNodes(_)))).filterNot(pair => pair._2.isEmpty)
+ filterDiagram(PackageDiagram(nodes, edges), diagramFilter)
+ }
+ }
+
+ tModel += System.currentTimeMillis
+ DiagramStats.addFilterTime(tFilter)
+ DiagramStats.addModelTime(tModel-tFilter)
+
+ result
+ }
+
+ /** Diagram filtering logic */
+ private def filterDiagram(diagram: Diagram, diagramFilter: DiagramFilter): Option[Diagram] = {
+ tFilter -= System.currentTimeMillis
+
+ val result =
+ if (diagramFilter == FullDiagram)
+ Some(diagram)
+ else if (diagramFilter == NoDiagramAtAll)
+ None
+ else {
+ // Final diagram, with the filtered nodes and edges
+ diagram match {
+ case ClassDiagram(thisNode, _, _, _, _) if diagramFilter.hideNode(thisNode.tpl.get) =>
+ None
+
+ case ClassDiagram(thisNode, superClasses, subClasses, incomingImplicits, outgoingImplicits) =>
+
+ def hideIncoming(node: Node): Boolean =
+ if (node.tpl.isDefined) diagramFilter.hideNode(node.tpl.get) || diagramFilter.hideEdge(node.tpl.get, thisNode.tpl.get)
+ else false // hopefully we won't need to fallback here
+
+ def hideOutgoing(node: Node): Boolean =
+ if (node.tpl.isDefined) diagramFilter.hideNode(node.tpl.get) || diagramFilter.hideEdge(thisNode.tpl.get, node.tpl.get)
+ else false // hopefully we won't need to fallback here
+
+ // println(thisNode)
+ // println(superClasses.map(cl => "super: " + cl + " " + hideOutgoing(cl)).mkString("\n"))
+ // println(subClasses.map(cl => "sub: " + cl + " " + hideIncoming(cl)).mkString("\n"))
+ Some(ClassDiagram(thisNode,
+ superClasses.filterNot(hideOutgoing(_)),
+ subClasses.filterNot(hideIncoming(_)),
+ incomingImplicits.filterNot(hideIncoming(_)),
+ outgoingImplicits.filterNot(hideOutgoing(_))))
+
+ case PackageDiagram(nodes0, edges0) =>
+ // Filter out all edges that:
+ // (1) are sources of hidden classes
+ // (2) are manually hidden by the user
+ // (3) are destinations of hidden classes
+ val edges: List[(Node, List[Node])] =
+ diagram.edges.flatMap({
+ case (source@Node(_, Some(tpl1)), dests) if !diagramFilter.hideNode(tpl1) =>
+ val dests2 = dests.collect({ case node@Node(_, Some(tpl2)) if (!(diagramFilter.hideEdge(tpl1, tpl2) || diagramFilter.hideNode(tpl2))) => node })
+ if (dests2 != Nil)
+ List((source, dests2))
+ else
+ Nil
+ case _ => Nil
+ })
+
+ // Only show the the non-isolated nodes
+ // TODO: Decide if we really want to hide package members, I'm not sure that's a good idea (!!!)
+ // TODO: Does .distinct cause any stability issues?
+ val sourceNodes = edges.map(_._1)
+ val sinkNodes = edges.map(_._2).flatten
+ val nodes = (sourceNodes ::: sinkNodes).distinct
+ Some(PackageDiagram(nodes, edges))
+ }
+ }
+
+ tFilter += System.currentTimeMillis
+
+ // eliminate all empty diagrams
+ if (result.isDefined && result.get.edges.forall(_._2.isEmpty))
+ None
+ else
+ result
+ }
+
+}
diff --git a/src/partest/scala/tools/partest/ScaladocModelTest.scala b/src/partest/scala/tools/partest/ScaladocModelTest.scala
index 142f2baea5..de5354d4a0 100644
--- a/src/partest/scala/tools/partest/ScaladocModelTest.scala
+++ b/src/partest/scala/tools/partest/ScaladocModelTest.scala
@@ -81,9 +81,9 @@ abstract class ScaladocModelTest extends DirectTest {
private[this] var settings: Settings = null
// create a new scaladoc compiler
- def newDocFactory: DocFactory = {
+ private[this] def newDocFactory: DocFactory = {
settings = new Settings(_ => ())
- settings.reportModel = false // yaay, no more "model contains X documentable templates"!
+ settings.scaladocQuietRun = true // yaay, no more "model contains X documentable templates"!
val args = extraSettings + " " + scaladocSettings
val command = new ScalaDoc.Command((CommandLineParser tokenize (args)), settings)
val docFact = new DocFactory(new ConsoleReporter(settings), settings)