From 88a93f2bd3dcc81f83fb60911e87091cbde25514 Mon Sep 17 00:00:00 2001 From: Gilles Dubochet Date: Thu, 28 Jan 2010 10:48:38 +0000 Subject: [scaladoc] Comment parsing is improved: * tags in code blocks no longer confuse the parser; * `@note` and `@example` are recognised tags; * Empty comments no longer generate "must start with a sentence" warnings; * `@usecase` parsing works better in some situations with blank comment lines above or below. No review. --- src/compiler/scala/tools/nsc/ast/DocComments.scala | 15 ++--- .../tools/nsc/doc/model/comment/Comment.scala | 37 ++++++++---- .../nsc/doc/model/comment/CommentFactory.scala | 70 +++++++++++++++------- src/compiler/scala/tools/nsc/util/DocStrings.scala | 11 ++-- 4 files changed, 87 insertions(+), 46 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/ast/DocComments.scala b/src/compiler/scala/tools/nsc/ast/DocComments.scala index a6792b3ba7..34b1c97f0c 100755 --- a/src/compiler/scala/tools/nsc/ast/DocComments.scala +++ b/src/compiler/scala/tools/nsc/ast/DocComments.scala @@ -154,9 +154,9 @@ trait DocComments { self: SymbolTable => var tocopy = startTag(dst, dstSections dropWhile (!isMovable(dst, _))) if (copyFirstPara) { - val eop = // end of first para, which is delimited by blank line, or tag, or end of comment - findNext(src, 0) (src.charAt(_) == '\n') min startTag(src, srcSections) - out append src.substring(0, eop) + val eop = // end of comment body (first para), which is delimited by blank line, or tag, or end of comment + (findNext(src, 0)(src.charAt(_) == '\n')) min startTag(src, srcSections) + out append src.substring(0, eop).trim copied = 3 tocopy = 3 } @@ -167,16 +167,13 @@ trait DocComments { self: SymbolTable => case None => srcSec match { case Some((start1, end1)) => - out append dst.substring(copied, tocopy) + out append dst.substring(copied, tocopy).trim copied = tocopy - out append src.substring(start1, end1) + out append src.substring(start1, end1).trim case None => } } - def mergeParam(name: String, srcMap: Map[String, (Int, Int)], dstMap: Map[String, (Int, Int)]) = - mergeSection(srcMap get name, dstMap get name) - for (params <- sym.paramss; param <- params) mergeSection(srcParams get param.name.toString, dstParams get param.name.toString) for (tparam <- sym.typeParams) @@ -280,7 +277,7 @@ trait DocComments { self: SymbolTable => startsWithTag(raw, idx, "@define") || startsWithTag(raw, idx, "@usecase")) val (defines, usecases) = sections partition (startsWithTag(raw, _, "@define")) val end = startTag(raw, sections) -/* + /* println("processing doc comment:") println(raw) println("===========>") 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 97ee9abdf0..762ff843cf 100644 --- a/src/compiler/scala/tools/nsc/doc/model/comment/Comment.scala +++ b/src/compiler/scala/tools/nsc/doc/model/comment/Comment.scala @@ -9,48 +9,59 @@ import scala.collection._ /** A Scaladoc comment and all its tags. * - * '''Note:''' the only instantiation site of this class is in `Parser`. + * '''Note:''' the only instantiation site of this class is in [[CommentFactory]]. * * @author Gilles Dubochet * @author Manohar Jonnalagedda */ abstract class Comment { - /** */ + /** The main body of the comment that describes what the entity does and is. */ def body: Body - /* author|deprecated|param|return|see|since|throws|version|todo|tparam */ + /** A shorter version of the body. Usually, this is the first sentence of the body. */ def short: Inline - /** */ + /** A list of authors. The empty list is used when no author is defined. */ def authors: List[Body] - /** */ + /** A list of other resources to see, including links to other entities or to external documentation. The empty list + * is used when no other resource is mentionned. */ def see: List[Body] - /** */ + /** A description of the result of the entity. Typically, this provides additional information on the domain of the + * result, contractual post-conditions, etc. */ def result: Option[Body] - /** */ + /** A map of exceptions that the entity can throw when accessed, and a description of what they mean. */ def throws: Map[String, Body] - /** */ + /** A map of value parameters, and a description of what they are. Typically, this provides additional information on + * the domain of the parameters, contractual pre-conditions, etc. */ def valueParams: Map[String, Body] - /** */ + /** A map of type parameters, and a description of what they are. Typically, this provides additional information on + * the domain of the parameters. */ def typeParams: Map[String, Body] - /** */ + /** The version number of the entity. There is no formatting or further meaning attached to this value. */ def version: Option[Body] - /** */ + /** A version number of a containing entity where this member-entity was introduced. */ def since: Option[Body] - /** */ + /** An annotation as to expected changes on this entity. */ def todo: List[Body] - /** */ + /** Whether the entity is deprecated. Using the "@deprecated" Scala attribute is prefereable to using this Scaladoc + * tag. */ def deprecated: Option[Body] + /** An additional note concerning the contract of the entity. */ + def note: List[Body] + + /** A usage example related to the entity. */ + def example: List[Body] + 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 4504a97af5..3fb8e4643c 100644 --- a/src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala @@ -51,6 +51,14 @@ final class CommentFactory(val reporter: Reporter) { parser => protected val SymbolTag = new Regex("""\s*@(param|tparam|throws)\s+(\S*)\s*(.*)""") + /** The start of a scaladoc code block */ + protected val CodeBlockStart = + new Regex("""(.*)\{\{\{(.*)""") + + /** The end of a scaladoc code block */ + protected val CodeBlockEnd = + new Regex("""(.*)\}\}\}(.*)""") + /** A key used for a tag map. The key is built from the name of the tag and from the linked symbol if the tag has one. * Equality on tag keys is structural. */ protected sealed abstract class TagKey { @@ -84,23 +92,40 @@ final class CommentFactory(val reporter: Reporter) { parser => * splits the whole comment into main body and tag bodies, then runs the `WikiParser` on each body before creating * the comment instance. * - * @param body The body of the comment parsed until now. - * @param tags All tags parsed until now. - * @param lastTagKey The last parsed tag, or `None` if the tag section hasn't started. Lines that are not tagged - * are part of the previous tag or, if none exists, of the body. - * @param remaining The lines that must still recursively be parsed. */ - def parse0(docBody: String, tags: Map[TagKey, List[String]], lastTagKey: Option[TagKey], remaining: List[String]): Comment = + * @param body The body of the comment parsed until now. + * @param tags All tags parsed until now. + * @param lastTagKey The last parsed tag, or `None` if the tag section hasn't started. Lines that are not tagged + * are part of the previous tag or, if none exists, of the body. + * @param remaining The lines that must still recursively be parsed. + * @param inCodeBlock Whether the next line is part of a code block (in which no tags must be read). */ + def parse0(docBody: String, tags: Map[TagKey, List[String]], lastTagKey: Option[TagKey], remaining: List[String], inCodeBlock: Boolean): Comment = { remaining match { - case SymbolTag(name, sym, body) :: ls => + case CodeBlockStart(before, after) :: ls if (!inCodeBlock) => + if (before.trim != "") + parse0(docBody, tags, lastTagKey, before :: ("{{{" + after) :: ls, false) + else if (after.trim != "") + parse0(docBody, tags, lastTagKey, after :: ls, true) + else + parse0(docBody, tags, lastTagKey, ls, true) + + case CodeBlockEnd(before, after) :: ls => + if (before.trim != "") + parse0(docBody, tags, lastTagKey, before :: ("}}}" + after) :: ls, true) + else if (after.trim != "") + parse0(docBody, tags, lastTagKey, after :: ls, false) + else + parse0(docBody, tags, lastTagKey, ls, false) + + case SymbolTag(name, sym, body) :: ls if (!inCodeBlock) => val key = SymbolTagKey(name, sym) val value = body :: tags.getOrElse(key, Nil) - parse0(docBody, tags + (key -> value), Some(key), ls) + parse0(docBody, tags + (key -> value), Some(key), ls, inCodeBlock) - case SimpleTag(name, body) :: ls => + case SimpleTag(name, body) :: ls if (!inCodeBlock) => val key = SimpleTagKey(name) val value = body :: tags.getOrElse(key, Nil) - parse0(docBody, tags + (key -> value), Some(key), ls) + parse0(docBody, tags + (key -> value), Some(key), ls, inCodeBlock) case line :: ls if (lastTagKey.isDefined) => val key = lastTagKey.get @@ -109,12 +134,11 @@ final class CommentFactory(val reporter: Reporter) { parser => case Some(b :: bs) => (b + endOfLine + line) :: bs case None => oops("lastTagKey set when no tag exists for key") } - parse0(docBody, tags + (key -> value), lastTagKey, ls) + parse0(docBody, tags + (key -> value), lastTagKey, ls, inCodeBlock) case line :: ls => - val newBody = - if (docBody == "") line else docBody + endOfLine + line - parse0(newBody, tags, lastTagKey, ls) + val newBody = if (docBody == "") line else docBody + endOfLine + line + parse0(newBody, tags, lastTagKey, ls, inCodeBlock) case Nil => @@ -163,6 +187,8 @@ final class CommentFactory(val reporter: Reporter) { parser => val since = oneTag(SimpleTagKey("since")) val todo = allTags(SimpleTagKey("todo")) val deprecated = oneTag(SimpleTagKey("deprecated")) + val note = allTags(SimpleTagKey("note")) + val example = allTags(SimpleTagKey("example")) val short = { val shortText = ShortLineEnd.findFirstMatchIn(docBody) match { case None => docBody @@ -172,7 +198,8 @@ final class CommentFactory(val reporter: Reporter) { parser => parseWiki(safeText, pos) match { case Body(Paragraph(inl) :: _) => inl case _ => - reporter.warning(pos, "Comment must start with a sentence") + if (safeText != "") + reporter.warning(pos, "Comment must start with a sentence") Text("") } } @@ -183,9 +210,9 @@ final class CommentFactory(val reporter: Reporter) { parser => com + } } - - parse0("", Map.empty, None, cleaned) + parse0("", Map.empty, None, cleaned, false) } /** Parses a string containing wiki syntax into a `Comment` object. Note that the string is assumed to be clean: @@ -232,7 +259,7 @@ final class CommentFactory(val reporter: Reporter) { parser => jump("{{{") readUntil("}}}") if (char == endOfText) - reporter.warning(pos, "unclosed code block") + reportError(pos, "unclosed code block") else jump("}}}") blockEnded("code block") @@ -245,7 +272,7 @@ final class CommentFactory(val reporter: Reporter) { parser => val text = inline(check(Array.fill(inLevel)('='))) val outLevel = repeatJump("=", inLevel) if (inLevel != outLevel) - reporter.warning(pos, "unbalanced or unclosed heading") + reportError(pos, "unbalanced or unclosed heading") blockEnded("heading") Title(text, inLevel) } @@ -371,7 +398,7 @@ final class CommentFactory(val reporter: Reporter) { parser => /** {{{ eol ::= { whitespace } '\n' }}} */ def blockEnded(blockType: String): Unit = { if (char != endOfLine && char != endOfText) { - reporter.warning(pos, "no additional content on same line after " + blockType) + reportError(pos, "no additional content on same line after " + blockType) jumpUntil(endOfLine) } while (char == endOfLine) @@ -382,6 +409,9 @@ final class CommentFactory(val reporter: Reporter) { parser => char == endOfText || check(Array(endOfLine, endOfLine)) || check(Array(endOfLine, '{', '{', '{')) || check(Array(endOfLine, '\u003D')) } + def reportError(pos: Position, message: String): Unit = + reporter.warning(pos, message) + } protected sealed class CharReader(buffer: Array[Char]) { reader => diff --git a/src/compiler/scala/tools/nsc/util/DocStrings.scala b/src/compiler/scala/tools/nsc/util/DocStrings.scala index 3392ef0577..c0d716fa90 100755 --- a/src/compiler/scala/tools/nsc/util/DocStrings.scala +++ b/src/compiler/scala/tools/nsc/util/DocStrings.scala @@ -31,7 +31,7 @@ object DocStrings { /** Returns index of string `str` after `start` skipping longest * sequence of space and tab characters, possibly also containing - * a single `*' character. + * a single `*' character or the `/``**` sequence. * @pre start == str.length || str(start) == `\n' */ def skipLineLead(str: String, start: Int): Int = @@ -39,16 +39,19 @@ object DocStrings { else { val idx = skipWhitespace(str, start + 1) if (idx < str.length && (str charAt idx) == '*') skipWhitespace(str, idx + 1) + else if (idx + 2 < str.length && (str charAt idx) == '/' && (str charAt (idx + 1)) == '*' && (str charAt (idx + 2)) == '*') + skipWhitespace(str, idx + 3) else idx } - /** Skips to next occurrence of `\n' following index `start`. + /** Skips to next occurrence of `\n' or to the position after the `/``**` sequence following index `start`. */ def skipToEol(str: String, start: Int): Int = - if (start < str.length && (str charAt start) != '\n') skipToEol(str, start + 1) + if (start + 2 < str.length && (str charAt start) == '/' && (str charAt (start + 1)) == '*' && (str charAt (start + 2)) == '*') start + 3 + else if (start < str.length && (str charAt start) != '\n') skipToEol(str, start + 1) else start - /** Returns first index following `start` and starting a line (i.e. after skipLineLead) + /** Returns first index following `start` and starting a line (i.e. after skipLineLead) or starting the comment * which satisfies predicate `p'. */ def findNext(str: String, start: Int)(p: Int => Boolean): Int = { -- cgit v1.2.3