From 524440deeef0b9c2e6c43d5d9af84f5aea2f1b6b Mon Sep 17 00:00:00 2001 From: dubochet Date: Wed, 3 Feb 2010 17:03:58 +0000 Subject: [PATCH] [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 --- build.xml | 11 +++- src/compiler/scala/tools/ant/Scaladoc.scala | 20 +++++++ .../scala/tools/nsc/ast/parser/Parsers.scala | 18 +++--- .../scala/tools/nsc/doc/Settings.scala | 6 +- .../tools/nsc/doc/html/page/Template.scala | 59 +++++++++---------- .../scala/tools/nsc/doc/model/Entity.scala | 4 +- .../tools/nsc/doc/model/ModelFactory.scala | 37 ++++++++---- .../doc/model/comment/CommentFactory.scala | 6 +- 8 files changed, 106 insertions(+), 55 deletions(-) diff --git a/build.xml b/build.xml index 39748a90e..3bc22fb7e 100644 --- a/build.xml +++ b/build.xml @@ -1269,7 +1269,10 @@ DOCUMENTATION @@ -1289,6 +1292,7 @@ DOCUMENTATION + @@ -1351,7 +1355,10 @@ DOCUMENTATION diff --git a/src/compiler/scala/tools/ant/Scaladoc.scala b/src/compiler/scala/tools/ant/Scaladoc.scala index 793531655..cb3bd23c7 100644 --- a/src/compiler/scala/tools/ant/Scaladoc.scala +++ b/src/compiler/scala/tools/ant/Scaladoc.scala @@ -107,6 +107,9 @@ class Scaladoc extends MatchingTask { /** The document version, to be added to the title. */ 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 */ private var addParams: String = "" @@ -264,6 +267,22 @@ class Scaladoc extends MatchingTask { encoding = Some(input) } + /** Sets the docversion attribute. + * + * @param input The value of docversion. + */ + def setDocversion(input: String) { + docversion = Some(input) + } + + /** Sets the docsourceurl attribute. + * + * @param input The value of docsourceurl. + */ + def setDocsourceurl(input: String) { + docsourceurl = Some(input) + } + /** Sets the doctitle attribute. * * @param input The value of doctitle. @@ -497,6 +516,7 @@ class Scaladoc extends MatchingTask { if (!encoding.isEmpty) docSettings.encoding.value = encoding.get if (!doctitle.isEmpty) docSettings.doctitle.value = decodeEscapes(doctitle.get) if (!docversion.isEmpty) docSettings.docversion.value = decodeEscapes(docversion.get) + if (!docsourceurl.isEmpty) docSettings.docsourceurl.value =decodeEscapes(docsourceurl.get) docSettings.deprecation.value = deprecation docSettings.unchecked.value = unchecked log("Scaladoc params = '" + addParams + "'", Project.MSG_DEBUG) diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala index a9146d0c7..d8e9b1000 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala @@ -2469,11 +2469,15 @@ self => val stats = new ListBuffer[Tree] while (in.token != RBRACE && in.token != EOF) { if (in.token == PACKAGE) { - in.flushDoc val start = in.skipToken() - stats += { - if (in.token == OBJECT) makePackageObject(start, objectDef(in.offset, NoMods)) - else packaging(start) + stats ++= { + if (in.token == OBJECT) { + joinComment(List(makePackageObject(start, objectDef(in.offset, NoMods)))) + } + else { + in.flushDoc + List(packaging(start)) + } } } else if (in.token == IMPORT) { in.flushDoc @@ -2631,15 +2635,15 @@ self => while (in.token == SEMI) in.nextToken() val start = in.offset if (in.token == PACKAGE) { - in.flushDoc - in.nextToken() + in.nextToken() if (in.token == OBJECT) { - ts += makePackageObject(start, objectDef(in.offset, NoMods)) + ts ++= joinComment(List(makePackageObject(start, objectDef(in.offset, NoMods)))) if (in.token != EOF) { acceptStatSep() ts ++= topStatSeq() } } else { + in.flushDoc val pkg = qualId() newLineOptWhenFollowedBy(LBRACE) if (in.token == EOF) { diff --git a/src/compiler/scala/tools/nsc/doc/Settings.scala b/src/compiler/scala/tools/nsc/doc/Settings.scala index ce628584a..df3cdf6ee 100644 --- a/src/compiler/scala/tools/nsc/doc/Settings.scala +++ b/src/compiler/scala/tools/nsc/doc/Settings.scala @@ -25,7 +25,11 @@ class Settings(error: String => Unit) extends scala.tools.nsc.Settings(error) { /** A setting that defines the overall version number of the documentation, typically the version of the library being * 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", "") - + + /** 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. suppressVTWarn.value = true 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 ab9ed5114..6b985da7f 100644 --- a/src/compiler/scala/tools/nsc/doc/html/page/Template.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/Template.scala @@ -41,7 +41,7 @@ class Template(tpl: DocTemplateEntity) extends HtmlPage { val body = - + { if (tpl.isRootPackage || tpl.inTemplate.isRootPackage) NodeSeq.Empty else @@ -170,6 +170,15 @@ class Template(tpl: DocTemplateEntity) extends HtmlPage { attributes: { fvs map { fv => { inlineToHtml(fv.text) ++ xml.Text(" ") } } } } + { tpl.companion match { + case Some(companion) if isSelf => +
+ Go to: companion +
+ case _ => + NodeSeq.Empty + } + } { val inDefTpls = mbr.inDefinitionTemplates if (inDefTpls.tail.isEmpty && (inDefTpls.head == mbr.inTemplate)) NodeSeq.Empty else {
@@ -178,29 +187,29 @@ class Template(tpl: DocTemplateEntity) extends HtmlPage { } } { mbr match { - case dtpl: DocTemplateEntity if isSelf => - val subClss = dtpl.subClasses - if (subClss.isEmpty) NodeSeq.Empty else -
- known subclasses: { templatesToHtml(dtpl.subClasses, xml.Text(", ")) } -
+ case dtpl: DocTemplateEntity if (isSelf && !dtpl.subClasses.isEmpty) => +
+ known subclasses: { templatesToHtml(dtpl.subClasses, xml.Text(", ")) } +
case _ => NodeSeq.Empty } } + { mbr match { + case dtpl: DocTemplateEntity if (isSelf && dtpl.sourceUrl.isDefined) => + val sourceUrl = tpl.sourceUrl.get + + case _ => NodeSeq.Empty + } + } + { if(mbr.deprecation.isEmpty) NodeSeq.Empty else +
    deprecated: + {
  1. { bodyToHtml(mbr.deprecation.get) }
  2. } +
+ } { for(comment <- mbr.comment.toList) yield { - { if(!comment.deprecated.isEmpty) -
    deprecated: - { for(body <- comment.deprecated.toList) yield
  1. {bodyToHtml(body)}
  2. } -
- else NodeSeq.Empty - } - { if(mbr.isDeprecated) -
    deprecated: - { for(str <- mbr.deprecationMessage.toList) yield
  1. {str}
  2. } -
- else NodeSeq.Empty - } { if(!comment.version.isEmpty)
    version { for(body <- comment.version.toList) yield
  1. {bodyToHtml(body)}
  2. } @@ -231,15 +240,6 @@ class Template(tpl: DocTemplateEntity) extends HtmlPage { } }} - { tpl.companion match { - case Some(companion) if isSelf => -
    - Go to: companion -
    - case _ => - NodeSeq.Empty - } - } def kindToString(mbr: MemberEntity): String = mbr match { @@ -260,12 +260,11 @@ class Template(tpl: DocTemplateEntity) extends HtmlPage { /** name, tparams, params, result */ def signature(mbr: MemberEntity, isSelf: Boolean): NodeSeq = { - val isDeprecated = mbr.isDeprecated || (!mbr.comment.isEmpty && !mbr.comment.get.deprecated.isEmpty) def inside(hasLinks: Boolean): NodeSeq = { kindToString(mbr) } - { if (mbr.isConstructor) tpl.name else mbr.name }{ + { if (mbr.isConstructor) tpl.name else mbr.name }{ def tparamsToHtml(tpss: List[TypeParam]): NodeSeq = if (tpss.isEmpty) NodeSeq.Empty else { def tparam0(tp: TypeParam): NodeSeq = diff --git a/src/compiler/scala/tools/nsc/doc/model/Entity.scala b/src/compiler/scala/tools/nsc/doc/model/Entity.scala index 53ac6740c..7be902cd5 100644 --- a/src/compiler/scala/tools/nsc/doc/model/Entity.scala +++ b/src/compiler/scala/tools/nsc/doc/model/Entity.scala @@ -43,9 +43,8 @@ trait MemberEntity extends Entity { def definitionName: String def visibility: Option[Paragraph] def flags: List[Paragraph] + def deprecation: Option[Body] def inheritedFrom: List[TemplateEntity] - def isDeprecated: Boolean - def deprecationMessage: Option[String] def resultType: TypeEntity def isDef: Boolean def isVal: Boolean @@ -61,6 +60,7 @@ trait MemberEntity extends Entity { trait DocTemplateEntity extends TemplateEntity with MemberEntity { def toRoot: List[DocTemplateEntity] def inSource: Option[(io.AbstractFile, Int)] + def sourceUrl: Option[java.net.URL] def typeParams: List[TypeParam] def parentType: Option[TypeEntity] def linearization: List[TemplateEntity] diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala index 90632259b..4fb65d776 100644 --- a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala @@ -18,14 +18,14 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { extractor = private var droppedPackages = 0 def templatesCount = templatesCache.size - droppedPackages - + /** */ def makeModel: Package = makePackage(RootPackage, null) getOrElse { throw new Error("no documentable class found in compilation units") } object commentator { - private val factory = new CommentFactory(reporter) + val factory = new CommentFactory(reporter) 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 = if (inTpl == null) makePackage(RootPackage, null).toList - else if (sym.owner == inTpl.sym) - inTpl :: Nil else makeTemplate(sym.owner) :: (sym.allOverriddenSymbols map { inhSym => makeTemplate(inhSym.owner) }) 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")) 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 = if (inTemplate.sym == this.sym.owner || inTemplate.sym.isPackage) Nil else 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 isDef = 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 toRoot: List[DocTemplateImpl] = this :: inTpl.toRoot 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 parentType = if (sym.isPackage) None else @@ -169,7 +186,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { extractor = subClassesCache += sc } 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. sym.info.nonPrivateMembers.filter(x => (!x.isConstructor || x.owner==sym)) 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 inheritedFrom = Nil override def isRootPackage = true - override protected def memberSyms = + override protected lazy val memberSyms = (bSym.info.members ++ EmptyPackage.info.members) filter { s => s != EmptyPackage && s != RootPackage } @@ -358,13 +375,13 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { extractor = }) else if (bSym.isPackage) 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)) } else 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 else { val allSyms = useCases(aSym, inTpl.sym) map { case (bSym, bComment, bPos) => 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 15fc99f92..51a8b0b34 100644 --- a/src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala @@ -29,10 +29,10 @@ final class CommentFactory(val reporter: Reporter) { parser => throw FatalError("program logic: " + msg) protected val CleanHtml = - new Regex("""""") + new Regex("""""") protected val ShortLineEnd = - new Regex("""\.||<(hr|table)\s*/?>""") + new Regex("""\.|""") /** The body of a comment, dropping start and end markers. */ 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 all end-of-line whitespace. * * 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() /** TODO