[scaladoc] Optional link to source (set parameter "-doc-source-url"). Support for commenting packages (using package objects). Contributed by Perdo Furlanetto. Also: small performance improvements, short comment extraction is more robust (but no HTML tags allowed in first sentence), small code clean-ups. Checked by dubochet, no review.

git-svn-id: http://lampsvn.epfl.ch/svn-repos/scala/scala/trunk@20778 5e8d7ff9-d8ef-0310-90f0-a4852d11357a
This commit is contained in:
dubochet 2010-02-03 17:03:58 +00:00
parent 800326e386
commit 524440deee
8 changed files with 106 additions and 55 deletions

View File

@ -1269,7 +1269,10 @@ DOCUMENTATION
<mkdir dir="${build-docs.dir}/library"/> <mkdir dir="${build-docs.dir}/library"/>
<scaladoc <scaladoc
destdir="${build-docs.dir}/library" destdir="${build-docs.dir}/library"
doctitle="Scala ${version.number} API" doctitle="Scala Standard Libray"
docversion="${version.number}"
docsourceurl="https://lampsvn.epfl.ch/trac/scala/browser/scala/trunk/src/"
sourcepath="${src.dir}"
classpathref="pack.classpath"> classpathref="pack.classpath">
<src> <src>
<files includes="${src.dir}/actors"/> <files includes="${src.dir}/actors"/>
@ -1289,6 +1292,7 @@ DOCUMENTATION
<exclude name="runtime/ScalaRunTime.scala"/> <exclude name="runtime/ScalaRunTime.scala"/>
<exclude name="runtime/StreamCons.scala"/> <exclude name="runtime/StreamCons.scala"/>
<exclude name="runtime/StringAdd.scala"/> <exclude name="runtime/StringAdd.scala"/>
<exclude name="scala/swing/test/**"/>
</scaladoc> </scaladoc>
<touch file="${build-docs.dir}/library.complete" verbose="no"/> <touch file="${build-docs.dir}/library.complete" verbose="no"/>
<stopwatch name="docs.lib.timer" action="total"/> <stopwatch name="docs.lib.timer" action="total"/>
@ -1351,7 +1355,10 @@ DOCUMENTATION
<mkdir dir="${build-docs.dir}/compiler"/> <mkdir dir="${build-docs.dir}/compiler"/>
<scaladoc <scaladoc
destdir="${build-docs.dir}/compiler" destdir="${build-docs.dir}/compiler"
doctitle="Scala Compiler ${version.number} API" doctitle="Scala Compiler"
docversion="${version.number}"
docsourceurl="https://lampsvn.epfl.ch/trac/scala/browser/scala/trunk/src/"
sourcepath="${src.dir}"
classpathref="pack.classpath" classpathref="pack.classpath"
srcdir="${src.dir}/compiler"> srcdir="${src.dir}/compiler">
<include name="**/*.scala"/> <include name="**/*.scala"/>

View File

@ -107,6 +107,9 @@ class Scaladoc extends MatchingTask {
/** The document version, to be added to the title. */ /** The document version, to be added to the title. */
private var docversion: Option[String] = None private var docversion: Option[String] = None
/** Instruct the compiler to generate links to sources */
private var docsourceurl: Option[String] = None
/** Instruct the compiler to use additional parameters */ /** Instruct the compiler to use additional parameters */
private var addParams: String = "" private var addParams: String = ""
@ -264,6 +267,22 @@ class Scaladoc extends MatchingTask {
encoding = Some(input) encoding = Some(input)
} }
/** Sets the <code>docversion</code> attribute.
*
* @param input The value of <code>docversion</code>.
*/
def setDocversion(input: String) {
docversion = Some(input)
}
/** Sets the <code>docsourceurl</code> attribute.
*
* @param input The value of <code>docsourceurl</code>.
*/
def setDocsourceurl(input: String) {
docsourceurl = Some(input)
}
/** Sets the <code>doctitle</code> attribute. /** Sets the <code>doctitle</code> attribute.
* *
* @param input The value of <code>doctitle</code>. * @param input The value of <code>doctitle</code>.
@ -497,6 +516,7 @@ class Scaladoc extends MatchingTask {
if (!encoding.isEmpty) docSettings.encoding.value = encoding.get if (!encoding.isEmpty) docSettings.encoding.value = encoding.get
if (!doctitle.isEmpty) docSettings.doctitle.value = decodeEscapes(doctitle.get) if (!doctitle.isEmpty) docSettings.doctitle.value = decodeEscapes(doctitle.get)
if (!docversion.isEmpty) docSettings.docversion.value = decodeEscapes(docversion.get) if (!docversion.isEmpty) docSettings.docversion.value = decodeEscapes(docversion.get)
if (!docsourceurl.isEmpty) docSettings.docsourceurl.value =decodeEscapes(docsourceurl.get)
docSettings.deprecation.value = deprecation docSettings.deprecation.value = deprecation
docSettings.unchecked.value = unchecked docSettings.unchecked.value = unchecked
log("Scaladoc params = '" + addParams + "'", Project.MSG_DEBUG) log("Scaladoc params = '" + addParams + "'", Project.MSG_DEBUG)

View File

@ -2469,11 +2469,15 @@ self =>
val stats = new ListBuffer[Tree] val stats = new ListBuffer[Tree]
while (in.token != RBRACE && in.token != EOF) { while (in.token != RBRACE && in.token != EOF) {
if (in.token == PACKAGE) { if (in.token == PACKAGE) {
in.flushDoc
val start = in.skipToken() val start = in.skipToken()
stats += { stats ++= {
if (in.token == OBJECT) makePackageObject(start, objectDef(in.offset, NoMods)) if (in.token == OBJECT) {
else packaging(start) joinComment(List(makePackageObject(start, objectDef(in.offset, NoMods))))
}
else {
in.flushDoc
List(packaging(start))
}
} }
} else if (in.token == IMPORT) { } else if (in.token == IMPORT) {
in.flushDoc in.flushDoc
@ -2631,15 +2635,15 @@ self =>
while (in.token == SEMI) in.nextToken() while (in.token == SEMI) in.nextToken()
val start = in.offset val start = in.offset
if (in.token == PACKAGE) { if (in.token == PACKAGE) {
in.flushDoc
in.nextToken() in.nextToken()
if (in.token == OBJECT) { if (in.token == OBJECT) {
ts += makePackageObject(start, objectDef(in.offset, NoMods)) ts ++= joinComment(List(makePackageObject(start, objectDef(in.offset, NoMods))))
if (in.token != EOF) { if (in.token != EOF) {
acceptStatSep() acceptStatSep()
ts ++= topStatSeq() ts ++= topStatSeq()
} }
} else { } else {
in.flushDoc
val pkg = qualId() val pkg = qualId()
newLineOptWhenFollowedBy(LBRACE) newLineOptWhenFollowedBy(LBRACE)
if (in.token == EOF) { if (in.token == EOF) {

View File

@ -26,6 +26,10 @@ class Settings(error: String => Unit) extends scala.tools.nsc.Settings(error) {
* documented. 'Note:'' This setting is currently not used. */ * documented. 'Note:'' This setting is currently not used. */
val docversion = StringSetting ("-doc-version", "doc-version", "An optional version number, to be appended to the title", "") val docversion = StringSetting ("-doc-version", "doc-version", "An optional version number, to be appended to the title", "")
/** A setting that defines a URL to be concatenated with source locations and show a link to source files.
* If needed the sourcepath option can be used to exclude undesired initial part of the link to sources */
val docsourceurl = StringSetting ("-doc-source-url", "url", "The URL prefix where documentation will link to sources", "")
// working around issue described in r18708. // working around issue described in r18708.
suppressVTWarn.value = true suppressVTWarn.value = true

View File

@ -170,6 +170,15 @@ class Template(tpl: DocTemplateEntity) extends HtmlPage {
attributes: { fvs map { fv => { inlineToHtml(fv.text) ++ xml.Text(" ") } } } attributes: { fvs map { fv => { inlineToHtml(fv.text) ++ xml.Text(" ") } } }
</div> </div>
} }
{ tpl.companion match {
case Some(companion) if isSelf =>
<div class="block">
Go to: <a href={relativeLinkTo(companion)}>companion</a>
</div>
case _ =>
NodeSeq.Empty
}
}
{ val inDefTpls = mbr.inDefinitionTemplates { val inDefTpls = mbr.inDefinitionTemplates
if (inDefTpls.tail.isEmpty && (inDefTpls.head == mbr.inTemplate)) NodeSeq.Empty else { if (inDefTpls.tail.isEmpty && (inDefTpls.head == mbr.inTemplate)) NodeSeq.Empty else {
<div class="block"> <div class="block">
@ -178,29 +187,29 @@ class Template(tpl: DocTemplateEntity) extends HtmlPage {
} }
} }
{ mbr match { { mbr match {
case dtpl: DocTemplateEntity if isSelf => case dtpl: DocTemplateEntity if (isSelf && !dtpl.subClasses.isEmpty) =>
val subClss = dtpl.subClasses
if (subClss.isEmpty) NodeSeq.Empty else
<div class="block"> <div class="block">
known subclasses: { templatesToHtml(dtpl.subClasses, xml.Text(", ")) } known subclasses: { templatesToHtml(dtpl.subClasses, xml.Text(", ")) }
</div> </div>
case _ => NodeSeq.Empty case _ => NodeSeq.Empty
} }
} }
{ mbr match {
case dtpl: DocTemplateEntity if (isSelf && dtpl.sourceUrl.isDefined) =>
val sourceUrl = tpl.sourceUrl.get
<div class="block">
source: { <a href={ sourceUrl.toString }>{ Text(new java.io.File(sourceUrl.getPath).getName) }</a> }
</div>
case _ => NodeSeq.Empty
}
}
{ if(mbr.deprecation.isEmpty) NodeSeq.Empty else
<div class="block"><ol>deprecated:
{ <li>{ bodyToHtml(mbr.deprecation.get) }</li> }
</ol></div>
}
{ for(comment <- mbr.comment.toList) yield { { for(comment <- mbr.comment.toList) yield {
<xml:group> <xml:group>
{ if(!comment.deprecated.isEmpty)
<div class="block"><ol>deprecated:
{ for(body <- comment.deprecated.toList) yield <li>{bodyToHtml(body)}</li> }
</ol></div>
else NodeSeq.Empty
}
{ if(mbr.isDeprecated)
<div class="block"><ol>deprecated:
{ for(str <- mbr.deprecationMessage.toList) yield <li>{str}</li> }
</ol></div>
else NodeSeq.Empty
}
{ if(!comment.version.isEmpty) { if(!comment.version.isEmpty)
<div class="block"><ol>version <div class="block"><ol>version
{ for(body <- comment.version.toList) yield <li>{bodyToHtml(body)}</li> } { for(body <- comment.version.toList) yield <li>{bodyToHtml(body)}</li> }
@ -231,15 +240,6 @@ class Template(tpl: DocTemplateEntity) extends HtmlPage {
} }
</xml:group> </xml:group>
}} }}
{ tpl.companion match {
case Some(companion) if isSelf =>
<div class="block">
Go to: <a href={relativeLinkTo(companion)}>companion</a>
</div>
case _ =>
NodeSeq.Empty
}
}
</xml:group> </xml:group>
def kindToString(mbr: MemberEntity): String = mbr match { def kindToString(mbr: MemberEntity): String = mbr match {
@ -260,12 +260,11 @@ class Template(tpl: DocTemplateEntity) extends HtmlPage {
/** name, tparams, params, result */ /** name, tparams, params, result */
def signature(mbr: MemberEntity, isSelf: Boolean): NodeSeq = { def signature(mbr: MemberEntity, isSelf: Boolean): NodeSeq = {
val isDeprecated = mbr.isDeprecated || (!mbr.comment.isEmpty && !mbr.comment.get.deprecated.isEmpty)
def inside(hasLinks: Boolean): NodeSeq = def inside(hasLinks: Boolean): NodeSeq =
<xml:group> <xml:group>
<span class="kind">{ kindToString(mbr) }</span> <span class="kind">{ kindToString(mbr) }</span>
<span class="symbol"> <span class="symbol">
<span class={"name" + (if(isDeprecated) " deprecated" else "") }>{ if (mbr.isConstructor) tpl.name else mbr.name }</span>{ <span class={"name" + (if (mbr.deprecation.isDefined) " deprecated" else "") }>{ if (mbr.isConstructor) tpl.name else mbr.name }</span>{
def tparamsToHtml(tpss: List[TypeParam]): NodeSeq = def tparamsToHtml(tpss: List[TypeParam]): NodeSeq =
if (tpss.isEmpty) NodeSeq.Empty else { if (tpss.isEmpty) NodeSeq.Empty else {
def tparam0(tp: TypeParam): NodeSeq = def tparam0(tp: TypeParam): NodeSeq =

View File

@ -43,9 +43,8 @@ trait MemberEntity extends Entity {
def definitionName: String def definitionName: String
def visibility: Option[Paragraph] def visibility: Option[Paragraph]
def flags: List[Paragraph] def flags: List[Paragraph]
def deprecation: Option[Body]
def inheritedFrom: List[TemplateEntity] def inheritedFrom: List[TemplateEntity]
def isDeprecated: Boolean
def deprecationMessage: Option[String]
def resultType: TypeEntity def resultType: TypeEntity
def isDef: Boolean def isDef: Boolean
def isVal: Boolean def isVal: Boolean
@ -61,6 +60,7 @@ trait MemberEntity extends Entity {
trait DocTemplateEntity extends TemplateEntity with MemberEntity { trait DocTemplateEntity extends TemplateEntity with MemberEntity {
def toRoot: List[DocTemplateEntity] def toRoot: List[DocTemplateEntity]
def inSource: Option[(io.AbstractFile, Int)] def inSource: Option[(io.AbstractFile, Int)]
def sourceUrl: Option[java.net.URL]
def typeParams: List[TypeParam] def typeParams: List[TypeParam]
def parentType: Option[TypeEntity] def parentType: Option[TypeEntity]
def linearization: List[TemplateEntity] def linearization: List[TemplateEntity]

View File

@ -25,7 +25,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { extractor =
object commentator { object commentator {
private val factory = new CommentFactory(reporter) val factory = new CommentFactory(reporter)
private val commentCache = mutable.HashMap.empty[(Symbol, TemplateImpl), Comment] private val commentCache = mutable.HashMap.empty[(Symbol, TemplateImpl), Comment]
@ -94,8 +94,6 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { extractor =
def inDefinitionTemplates = def inDefinitionTemplates =
if (inTpl == null) if (inTpl == null)
makePackage(RootPackage, null).toList makePackage(RootPackage, null).toList
else if (sym.owner == inTpl.sym)
inTpl :: Nil
else else
makeTemplate(sym.owner) :: (sym.allOverriddenSymbols map { inhSym => makeTemplate(inhSym.owner) }) makeTemplate(sym.owner) :: (sym.allOverriddenSymbols map { inhSym => makeTemplate(inhSym.owner) })
def visibility = { def visibility = {
@ -121,11 +119,18 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { extractor =
if (!sym.isModule && (sym hasFlag Flags.FINAL)) fgs += Paragraph(Text("final")) if (!sym.isModule && (sym hasFlag Flags.FINAL)) fgs += Paragraph(Text("final"))
fgs.toList fgs.toList
} }
def deprecation =
if (sym.isDeprecated && sym.deprecationMessage.isDefined)
Some(commentator.factory.parseWiki(sym.deprecationMessage.get, NoPosition))
else if (sym.isDeprecated)
Some(Body(Nil))
else if (comment.isDefined)
comment.get.deprecated
else
None
def inheritedFrom = def inheritedFrom =
if (inTemplate.sym == this.sym.owner || inTemplate.sym.isPackage) Nil else if (inTemplate.sym == this.sym.owner || inTemplate.sym.isPackage) Nil else
makeTemplate(this.sym.owner) :: (sym.allOverriddenSymbols map { os => makeTemplate(os.owner) }) makeTemplate(this.sym.owner) :: (sym.allOverriddenSymbols map { os => makeTemplate(os.owner) })
def isDeprecated = sym.isDeprecated
def deprecationMessage = sym.deprecationMessage
def resultType = makeType(sym.tpe.finalResultType, inTemplate, sym) def resultType = makeType(sym.tpe.finalResultType, inTemplate, sym)
def isDef = false def isDef = false
def isVal = false def isVal = false
@ -150,6 +155,18 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { extractor =
override def definitionName = optimize(inDefinitionTemplates.head.qualifiedName + "." + name) override def definitionName = optimize(inDefinitionTemplates.head.qualifiedName + "." + name)
override def toRoot: List[DocTemplateImpl] = this :: inTpl.toRoot override def toRoot: List[DocTemplateImpl] = this :: inTpl.toRoot
def inSource = if (sym.sourceFile != null) Some(sym.sourceFile, sym.pos.line) else None def inSource = if (sym.sourceFile != null) Some(sym.sourceFile, sym.pos.line) else None
def sourceUrl = {
def fixPath(s: String) = s.replaceAll(java.io.File.separator, "/")
val assumedSourceRoot: String = {
val fixed = fixPath(settings.sourcepath.value)
if (fixed endsWith "/") fixed.dropRight(1) else fixed
}
if (!settings.docsourceurl.isDefault)
inSource map { case (file, _) =>
new java.net.URL(settings.docsourceurl.value + "/" + fixPath(file.path).replaceFirst("^" + assumedSourceRoot, ""))
}
else None
}
def typeParams = if (sym.isClass) sym.typeParams map (makeTypeParam(_, this)) else Nil def typeParams = if (sym.isClass) sym.typeParams map (makeTypeParam(_, this)) else Nil
def parentType = def parentType =
if (sym.isPackage) None else if (sym.isPackage) None else
@ -169,7 +186,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { extractor =
subClassesCache += sc subClassesCache += sc
} }
def subClasses = subClassesCache.toList def subClasses = subClassesCache.toList
protected def memberSyms = protected lazy val memberSyms =
// Only this class's constructors are part of its members, inherited constructors are not. // Only this class's constructors are part of its members, inherited constructors are not.
sym.info.nonPrivateMembers.filter(x => (!x.isConstructor || x.owner==sym)) sym.info.nonPrivateMembers.filter(x => (!x.isConstructor || x.owner==sym))
val members = memberSyms flatMap (makeMember(_, this)) val members = memberSyms flatMap (makeMember(_, this))
@ -231,7 +248,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { extractor =
override def qualifiedName = "_root_" override def qualifiedName = "_root_"
override def inheritedFrom = Nil override def inheritedFrom = Nil
override def isRootPackage = true override def isRootPackage = true
override protected def memberSyms = override protected lazy val memberSyms =
(bSym.info.members ++ EmptyPackage.info.members) filter { s => (bSym.info.members ++ EmptyPackage.info.members) filter { s =>
s != EmptyPackage && s != RootPackage s != EmptyPackage && s != RootPackage
} }
@ -358,13 +375,13 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { extractor =
}) })
else if (bSym.isPackage) else if (bSym.isPackage)
inTpl match { case inPkg: PackageImpl => makePackage(bSym, inPkg) } inTpl match { case inPkg: PackageImpl => makePackage(bSym, inPkg) }
else if ((bSym.isClass || bSym.isModule) && (bSym.sourceFile != null) && bSym.isPublic && !bSym.isLocal) { else if ((bSym.isClass || bSym.isModule) && bSym.isPublic && !bSym.isLocal) {
(inTpl.toRoot find (_.sym == bSym )) orElse Some(makeDocTemplate(bSym, inTpl)) (inTpl.toRoot find (_.sym == bSym )) orElse Some(makeDocTemplate(bSym, inTpl))
} }
else else
None None
} }
if (!aSym.isPublic || (aSym hasFlag Flags.SYNTHETIC) || (aSym hasFlag Flags.BRIDGE) || aSym.isLocal || aSym.isModuleClass || aSym.isPackageObject || aSym.isMixinConstructor) if ((!aSym.isPackage && aSym.sourceFile == null) || !aSym.isPublic || (aSym hasFlag Flags.SYNTHETIC) || aSym.isLocal || aSym.isModuleClass || aSym.isPackageObject || aSym.isMixinConstructor)
Nil Nil
else { else {
val allSyms = useCases(aSym, inTpl.sym) map { case (bSym, bComment, bPos) => val allSyms = useCases(aSym, inTpl.sym) map { case (bSym, bComment, bPos) =>

View File

@ -29,10 +29,10 @@ final class CommentFactory(val reporter: Reporter) { parser =>
throw FatalError("program logic: " + msg) throw FatalError("program logic: " + msg)
protected val CleanHtml = protected val CleanHtml =
new Regex("""</?(p|h\d|pre|dl|dt|dd|ol|ul|li|blockquote|div|hr|br|br)\s*/?>""") new Regex("""</?(p|h\d|pre|dl|dt|dd|ol|ul|li|blockquote|div|hr|br|br).*/?>""")
protected val ShortLineEnd = protected val ShortLineEnd =
new Regex("""\.|</(p|h\d|pre|dd|li|div|blockquote)>|<(hr|table)\s*/?>""") new Regex("""\.|</?.*>""")
/** The body of a comment, dropping start and end markers. */ /** The body of a comment, dropping start and end markers. */
protected val CleanComment = protected val CleanComment =
@ -220,7 +220,7 @@ final class CommentFactory(val reporter: Reporter) { parser =>
* * Removed start-of-line star and one whitespace afterwards (if present). * * Removed start-of-line star and one whitespace afterwards (if present).
* * Removed all end-of-line whitespace. * * Removed all end-of-line whitespace.
* * Only `endOfLine` is used to mark line endings. */ * * Only `endOfLine` is used to mark line endings. */
protected def parseWiki(string: String, pos: Position): Body = def parseWiki(string: String, pos: Position): Body =
new WikiParser(string.toArray, pos).document() new WikiParser(string.toArray, pos).document()
/** TODO /** TODO