scala.tools.nsc.doc.model.diagram.DiagramDirectiveParser.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of scala-compiler Show documentation
Show all versions of scala-compiler Show documentation
Compiler for the Scala Programming Language
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 =>
import this.global.definitions.AnyRefClass
///// 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 [email protected]
*/
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 hideInheritedNodes: Boolean
/** Hide a node from the diagram */
def hideNode(clazz: Node): Boolean
/** Hide an edge from the diagram */
def hideEdge(clazz1: Node, clazz2: Node): 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 || template.sym == AnyRefClass)
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 hideInheritedNodes: Boolean = false
def hideNode(clazz: Node): Boolean = false
def hideEdge(clazz1: Node, clazz2: Node): 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 hideInheritedNodes: Boolean = true
def hideNode(clazz: Node): Boolean = true
def hideEdge(clazz1: Node, clazz2: Node): 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,
hideInheritedNodes: Boolean,
hideNodesFilter: List[Pattern],
hideEdgesFilter: List[(Pattern, Pattern)]) extends DiagramFilter {
private[this] def getName(n: Node): String =
if (n.tpl.isDefined)
n.tpl.get.qualifiedName
else
n.name
def hideNode(clazz: Node): Boolean = {
val qualifiedName = getName(clazz)
for (hideFilter <- hideNodesFilter)
if (hideFilter.matcher(qualifiedName).matches) {
// println(hideFilter + ".matcher(" + qualifiedName + ").matches = " + hideFilter.matcher(qualifiedName).matches)
return true
}
false
}
def hideEdge(clazz1: Node, clazz2: Node): Boolean = {
val clazz1Name = getName(clazz1)
val clazz2Name = getName(clazz2)
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 hideInheritedNodes0: 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 "hideInheritedNodes" if !isInheritanceDiagram =>
hideInheritedNodes0 = 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.kind + " " + template.qualifiedName +
": 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) &&
(hideInheritedNodes0 == false) &&
(hideDiagram0 == false))
FullDiagram
else
AnnotationDiagramFilter(
hideDiagram = hideDiagram0,
hideIncomingImplicits = hideIncomingImplicits0,
hideOutgoingImplicits = hideOutgoingImplicits0,
hideSuperclasses = hideSuperclasses0,
hideSubclasses = hideSubclasses0,
hideInheritedNodes = hideInheritedNodes0,
hideNodesFilter = hideNodesFilter0,
hideEdgesFilter = hideEdgesFilter0)
if (settings.docDiagramsDebug.value && result != NoDiagramAtAll && result != FullDiagram)
settings.printMsg(template.kind + " " + template.qualifiedName + " filter: " + result)
tFilter += System.currentTimeMillis
result
}
}