From e5e6d673cf669e3c9a7643aedd02213e4f7bbddf Mon Sep 17 00:00:00 2001 From: Eugene Vigdorchik Date: Wed, 21 Nov 2012 16:06:36 +0400 Subject: Extract base scaladoc functionality for the IDE. --- src/compiler/scala/tools/nsc/ast/DocComments.scala | 39 +- src/compiler/scala/tools/nsc/doc/DocFactory.scala | 2 +- .../tools/nsc/doc/base/CommentFactoryBase.scala | 955 +++++++++++++++++ src/compiler/scala/tools/nsc/doc/base/LinkTo.scala | 15 + .../tools/nsc/doc/base/MemberLookupBase.scala | 230 +++++ .../scala/tools/nsc/doc/base/comment/Body.scala | 91 ++ .../scala/tools/nsc/doc/base/comment/Comment.scala | 134 +++ .../scala/tools/nsc/doc/html/HtmlPage.scala | 9 +- .../scala/tools/nsc/doc/html/page/Source.scala | 1 - .../scala/tools/nsc/doc/html/page/Template.scala | 3 + .../html/page/diagram/DotDiagramGenerator.scala | 2 +- .../scala/tools/nsc/doc/model/CommentFactory.scala | 114 ++ .../scala/tools/nsc/doc/model/Entity.scala | 2 +- .../scala/tools/nsc/doc/model/LinkTo.scala | 24 - .../scala/tools/nsc/doc/model/MemberLookup.scala | 225 +--- .../scala/tools/nsc/doc/model/ModelFactory.scala | 33 +- .../doc/model/ModelFactoryImplicitSupport.scala | 4 +- .../nsc/doc/model/ModelFactoryTypeSupport.scala | 6 +- .../scala/tools/nsc/doc/model/TypeEntity.scala | 2 +- .../scala/tools/nsc/doc/model/comment/Body.scala | 91 -- .../tools/nsc/doc/model/comment/Comment.scala | 135 --- .../nsc/doc/model/comment/CommentFactory.scala | 1085 -------------------- .../doc/model/diagram/DiagramDirectiveParser.scala | 3 +- .../nsc/doc/model/diagram/DiagramFactory.scala | 3 +- src/compiler/scala/tools/nsc/interactive/Doc.scala | 50 + .../scala/tools/nsc/interactive/Global.scala | 1 + .../tests/core/PresentationCompilerInstance.scala | 3 +- .../scala/tools/nsc/typechecker/Typers.scala | 3 +- .../scala/tools/partest/ScaladocModelTest.scala | 2 +- test/files/presentation/doc.check | 48 + test/files/presentation/doc.scala | 71 ++ test/files/presentation/doc/src/Test.scala | 1 + .../memory-leaks/MemoryLeaksTest.scala | 5 +- test/scaladoc/run/SI-191-deprecated.scala | 3 +- test/scaladoc/run/SI-191.scala | 3 +- test/scaladoc/run/SI-3314.scala | 3 +- test/scaladoc/run/SI-5235.scala | 3 +- test/scaladoc/run/links.scala | 7 +- test/scaladoc/scalacheck/CommentFactoryTest.scala | 3 +- 39 files changed, 1791 insertions(+), 1623 deletions(-) create mode 100755 src/compiler/scala/tools/nsc/doc/base/CommentFactoryBase.scala create mode 100755 src/compiler/scala/tools/nsc/doc/base/LinkTo.scala create mode 100755 src/compiler/scala/tools/nsc/doc/base/MemberLookupBase.scala create mode 100755 src/compiler/scala/tools/nsc/doc/base/comment/Body.scala create mode 100644 src/compiler/scala/tools/nsc/doc/base/comment/Comment.scala create mode 100644 src/compiler/scala/tools/nsc/doc/model/CommentFactory.scala delete mode 100644 src/compiler/scala/tools/nsc/doc/model/LinkTo.scala delete mode 100644 src/compiler/scala/tools/nsc/doc/model/comment/Body.scala delete mode 100644 src/compiler/scala/tools/nsc/doc/model/comment/Comment.scala delete mode 100644 src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala create mode 100755 src/compiler/scala/tools/nsc/interactive/Doc.scala create mode 100755 test/files/presentation/doc.check create mode 100755 test/files/presentation/doc.scala create mode 100755 test/files/presentation/doc/src/Test.scala diff --git a/src/compiler/scala/tools/nsc/ast/DocComments.scala b/src/compiler/scala/tools/nsc/ast/DocComments.scala index 5a4be5125d..e635c5e87d 100755 --- a/src/compiler/scala/tools/nsc/ast/DocComments.scala +++ b/src/compiler/scala/tools/nsc/ast/DocComments.scala @@ -19,11 +19,17 @@ import scala.collection.mutable */ trait DocComments { self: Global => - var cookedDocComments = Map[Symbol, String]() + val cookedDocComments = mutable.HashMap[Symbol, String]() /** The raw doc comment map */ val docComments = mutable.HashMap[Symbol, DocComment]() + def clearDocComments() { + cookedDocComments.clear() + docComments.clear() + defs.clear() + } + /** Associate comment with symbol `sym` at position `pos`. */ def docComment(sym: Symbol, docStr: String, pos: Position = NoPosition) = if ((sym ne null) && (sym ne NoSymbol)) @@ -55,25 +61,20 @@ trait DocComments { self: Global => * If a symbol does not have a doc comment but some overridden version of it does, * the doc comment of the overridden version is copied instead. */ - def cookedDocComment(sym: Symbol, docStr: String = ""): String = cookedDocComments.get(sym) match { - case Some(comment) => - comment - case None => - val ownComment = if (docStr.length == 0) docComments get sym map (_.template) getOrElse "" + def cookedDocComment(sym: Symbol, docStr: String = ""): String = cookedDocComments.getOrElseUpdate(sym, { + val ownComment = if (docStr.length == 0) docComments get sym map (_.template) getOrElse "" else DocComment(docStr).template - val comment = superComment(sym) match { - case None => - if (ownComment.indexOf("@inheritdoc") != -1) - reporter.warning(sym.pos, "The comment for " + sym + - " contains @inheritdoc, but no parent comment is available to inherit from.") - ownComment.replaceAllLiterally("@inheritdoc", "") - case Some(sc) => - if (ownComment == "") sc - else expandInheritdoc(sc, merge(sc, ownComment, sym), sym) - } - cookedDocComments += (sym -> comment) - comment - } + superComment(sym) match { + case None => + if (ownComment.indexOf("@inheritdoc") != -1) + reporter.warning(sym.pos, "The comment for " + sym + + " contains @inheritdoc, but no parent comment is available to inherit from.") + ownComment.replaceAllLiterally("@inheritdoc", "") + case Some(sc) => + if (ownComment == "") sc + else expandInheritdoc(sc, merge(sc, ownComment, sym), sym) + } + }) /** The cooked doc comment of symbol `sym` after variable expansion, or "" if missing. * diff --git a/src/compiler/scala/tools/nsc/doc/DocFactory.scala b/src/compiler/scala/tools/nsc/doc/DocFactory.scala index 642e330a57..a091b04993 100644 --- a/src/compiler/scala/tools/nsc/doc/DocFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/DocFactory.scala @@ -80,7 +80,7 @@ class DocFactory(val reporter: Reporter, val settings: doc.Settings) { processor with model.ModelFactoryImplicitSupport with model.ModelFactoryTypeSupport with model.diagram.DiagramFactory - with model.comment.CommentFactory + with model.CommentFactory with model.TreeFactory with model.MemberLookup { override def templateShouldDocument(sym: compiler.Symbol, inTpl: DocTemplateImpl) = diff --git a/src/compiler/scala/tools/nsc/doc/base/CommentFactoryBase.scala b/src/compiler/scala/tools/nsc/doc/base/CommentFactoryBase.scala new file mode 100755 index 0000000000..f60d56d9bb --- /dev/null +++ b/src/compiler/scala/tools/nsc/doc/base/CommentFactoryBase.scala @@ -0,0 +1,955 @@ +/* NSC -- new Scala compiler + * Copyright 2007-2012 LAMP/EPFL + * @author Manohar Jonnalagedda + */ + +package scala.tools.nsc +package doc +package base + +import base.comment._ +import reporters.Reporter +import scala.collection._ +import scala.util.matching.Regex +import scala.annotation.switch +import scala.reflect.internal.util.{NoPosition, Position} +import scala.language.postfixOps + +/** The comment parser transforms raw comment strings into `Comment` objects. + * Call `parse` to run the parser. Note that the parser is stateless and + * should only be built once for a given Scaladoc run. + * + * @param reporter The reporter on which user messages (error, warnings) should be printed. + * + * @author Manohar Jonnalagedda + * @author Gilles Dubochet */ +trait CommentFactoryBase { this: MemberLookupBase => + + val global: Global + import global.{ reporter, definitions, Symbol } + + /* 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, + inheritDiagram0: List[String] = List.empty, + contentDiagram0: List[String] = List.empty, + group0: Option[Body] = None, + groupDesc0: Map[String,Body] = Map.empty, + groupNames0: Map[String,Body] = Map.empty, + groupPrio0: Map[String,Body] = Map.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 inheritDiagram = inheritDiagram0 + val contentDiagram = contentDiagram0 + val groupDesc = groupDesc0 + val group = + group0 match { + case Some(Body(List(Paragraph(Chain(List(Summary(Text(groupId)))))))) => Some(groupId.toString.trim) + case _ => None + } + val groupPrio = groupPrio0 flatMap { + case (group, body) => + try { + body match { + case Body(List(Paragraph(Chain(List(Summary(Text(prio))))))) => List(group -> prio.trim.toInt) + case _ => List() + } + } catch { + case _: java.lang.NumberFormatException => List() + } + } + val groupNames = groupNames0 flatMap { + case (group, body) => + try { + body match { + case Body(List(Paragraph(Chain(List(Summary(Text(name))))))) if (!name.trim.contains("\n")) => List(group -> (name.trim)) + case _ => List() + } + } catch { + case _: java.lang.NumberFormatException => List() + } + } + + } + + protected val endOfText = '\u0003' + protected val endOfLine = '\u000A' + + /** Something that should not have happened, happened, and Scaladoc should exit. */ + protected def oops(msg: String): Nothing = + throw FatalError("program logic: " + msg) + + /** The body of a line, dropping the (optional) start star-marker, + * one leading whitespace and all trailing whitespace. */ + protected val CleanCommentLine = + new Regex("""(?:\s*\*\s?)?(.*)""") + + /** Dangerous HTML tags that should be replaced by something safer, + * such as wiki syntax, or that should be dropped. */ + protected val DangerousTags = + new Regex("""<(/?(div|ol|ul|li|h[1-6]|p))( [^>]*)?/?>|""") + + /** Maps a dangerous HTML tag to a safe wiki replacement, or an empty string + * if it cannot be salvaged. */ + protected def htmlReplacement(mtch: Regex.Match): String = mtch.group(1) match { + case "p" | "div" => "\n\n" + case "h1" => "\n= " + case "/h1" => " =\n" + case "h2" => "\n== " + case "/h2" => " ==\n" + case "h3" => "\n=== " + case "/h3" => " ===\n" + case "h4" | "h5" | "h6" => "\n==== " + case "/h4" | "/h5" | "/h6" => " ====\n" + case "li" => "\n * - " + case _ => "" + } + + /** Javadoc tags that should be replaced by something useful, such as wiki + * syntax, or that should be dropped. */ + protected val JavadocTags = + new Regex("""\{\@(code|docRoot|inheritDoc|link|linkplain|literal|value)([^}]*)\}""") + + /** Maps a javadoc tag to a useful wiki replacement, or an empty string if it cannot be salvaged. */ + protected def javadocReplacement(mtch: Regex.Match): String = mtch.group(1) match { + case "code" => "`" + mtch.group(2) + "`" + case "docRoot" => "" + case "inheritDoc" => "" + case "link" => "`" + mtch.group(2) + "`" + case "linkplain" => "`" + mtch.group(2) + "`" + case "literal" => mtch.group(2) + case "value" => "`" + mtch.group(2) + "`" + case _ => "" + } + + /** Safe HTML tags that can be kept. */ + protected val SafeTags = + new Regex("""((&\w+;)|(&#\d+;)|(]*)?/?>))""") + + 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+(.*)""") + + /** A Scaladoc tag linked to a symbol. Returns the name of the tag, the name + * of the symbol, and the rest of the line. */ + protected val SymbolTag = + new Regex("""\s*@(param|tparam|throws|groupdesc|groupname|groupprio)\s+(\S*)\s*(.*)""") + + /** The start of a scaladoc code block */ + protected val CodeBlockStart = + new Regex("""(.*?)((?:\{\{\{)|(?:\u000E]*)?>\u000E))(.*)""") + + /** The end of a scaladoc code block */ + protected val CodeBlockEnd = + new Regex("""(.*?)((?:\}\}\})|(?:\u000E\u000E))(.*)""") + + /** 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 { + def name: String + } + + protected final case class SimpleTagKey(name: String) extends TagKey + protected final case class SymbolTagKey(name: String, symbol: String) extends TagKey + + /** Parses a raw comment string into a `Comment` object. + * @param comment The expanded comment string (including start and end markers) to be parsed. + * @param src The raw comment source string. + * @param pos The position of the comment in source. */ + protected def parseAtSymbol(comment: String, src: String, pos: Position, siteOpt: Option[Symbol] = None): Comment = { + /** The cleaned raw comment as a list of lines. Cleaning removes comment + * start and end markers, line start markers and unnecessary whitespace. */ + def clean(comment: String): List[String] = { + def cleanLine(line: String): String = { + //replaceAll removes trailing whitespaces + line.replaceAll("""\s+$""", "") match { + case CleanCommentLine(ctl) => ctl + case tl => tl + } + } + val strippedComment = comment.trim.stripPrefix("/*").stripSuffix("*/") + val safeComment = DangerousTags.replaceAllIn(strippedComment, { htmlReplacement(_) }) + val javadoclessComment = JavadocTags.replaceAllIn(safeComment, { javadocReplacement(_) }) + val markedTagComment = + SafeTags.replaceAllIn(javadoclessComment, { mtch => + java.util.regex.Matcher.quoteReplacement(safeTagMarker + mtch.matched + safeTagMarker) + }) + markedTagComment.lines.toList map (cleanLine(_)) + } + + /** Parses a comment (in the form of a list of lines) to a `Comment` + * instance, recursively on lines. To do so, it splits the whole comment + * into main body and tag bodies, then runs the `WikiParser` on each body + * before creating the comment instance. + * + * @param docBody 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: StringBuilder, + tags: Map[TagKey, List[String]], + lastTagKey: Option[TagKey], + remaining: List[String], + inCodeBlock: Boolean + ): Comment = remaining match { + + case CodeBlockStart(before, marker, after) :: ls if (!inCodeBlock) => + if (!before.trim.isEmpty && !after.trim.isEmpty) + parse0(docBody, tags, lastTagKey, before :: marker :: after :: ls, false) + else if (!before.trim.isEmpty) + parse0(docBody, tags, lastTagKey, before :: marker :: ls, false) + else if (!after.trim.isEmpty) + parse0(docBody, tags, lastTagKey, marker :: after :: ls, true) + else lastTagKey match { + case Some(key) => + val value = + ((tags get key): @unchecked) match { + case Some(b :: bs) => (b + endOfLine + marker) :: bs + case None => oops("lastTagKey set when no tag exists for key") + } + parse0(docBody, tags + (key -> value), lastTagKey, ls, true) + case None => + parse0(docBody append endOfLine append marker, tags, lastTagKey, ls, true) + } + + case CodeBlockEnd(before, marker, after) :: ls => + if (!before.trim.isEmpty && !after.trim.isEmpty) + parse0(docBody, tags, lastTagKey, before :: marker :: after :: ls, true) + if (!before.trim.isEmpty) + parse0(docBody, tags, lastTagKey, before :: marker :: ls, true) + else if (!after.trim.isEmpty) + parse0(docBody, tags, lastTagKey, marker :: after :: ls, false) + else lastTagKey match { + case Some(key) => + val value = + ((tags get key): @unchecked) match { + case Some(b :: bs) => (b + endOfLine + marker) :: bs + case None => oops("lastTagKey set when no tag exists for key") + } + parse0(docBody, tags + (key -> value), lastTagKey, ls, false) + case None => + parse0(docBody append endOfLine append marker, 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, inCodeBlock) + + 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, 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 = + ((tags get key): @unchecked) match { + 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, inCodeBlock) + + case line :: ls => + if (docBody.length > 0) docBody append endOfLine + docBody append line + 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 stripTags=List(inheritDiagramTag, contentDiagramTag, SimpleTagKey("template"), SimpleTagKey("documentable")) + val tagsWithoutDiagram = tags.filterNot(pair => stripTags.contains(pair._1)) + + val bodyTags: mutable.Map[TagKey, List[Body]] = + mutable.Map(tagsWithoutDiagram mapValues {tag => tag map (parseWikiAtSymbol(_, pos, siteOpt))} toSeq: _*) + + def oneTag(key: SimpleTagKey): Option[Body] = + ((bodyTags remove key): @unchecked) match { + case Some(r :: rs) => + if (!rs.isEmpty) reporter.warning(pos, "Only one '@" + key.name + "' tag is allowed") + Some(r) + case None => None + } + + def allTags(key: SimpleTagKey): List[Body] = + (bodyTags remove key) getOrElse Nil + + def allSymsOneTag(key: TagKey): Map[String, Body] = { + val keys: Seq[SymbolTagKey] = + bodyTags.keys.toSeq flatMap { + case stk: SymbolTagKey if (stk.name == key.name) => Some(stk) + case stk: SimpleTagKey if (stk.name == key.name) => + reporter.warning(pos, "Tag '@" + stk.name + "' must be followed by a symbol name") + None + case _ => None + } + val pairs: Seq[(String, Body)] = + for (key <- keys) yield { + val bs = (bodyTags remove key).get + if (bs.length > 1) + reporter.warning(pos, "Only one '@" + key.name + "' tag for symbol " + key.symbol + " is allowed") + (key.symbol, bs.head) + } + Map.empty[String, Body] ++ pairs + } + + val com = createComment ( + body0 = Some(parseWikiAtSymbol(docBody.toString, pos, siteOpt)), + 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, + group0 = oneTag(SimpleTagKey("group")), + groupDesc0 = allSymsOneTag(SimpleTagKey("groupdesc")), + groupNames0 = allSymsOneTag(SimpleTagKey("groupname")), + groupPrio0 = allSymsOneTag(SimpleTagKey("groupprio")) + ) + + for ((key, _) <- bodyTags) + reporter.warning(pos, "Tag '@" + key.name + "' is not recognised") + + com + + } + + parse0(new StringBuilder(comment.size), Map.empty, None, clean(comment), false) + + } + + /** Parses a string containing wiki syntax into a `Comment` object. + * Note that the string is assumed to be clean: + * - Removed Scaladoc start and end markers. + * - 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. */ + def parseWikiAtSymbol(string: String, pos: Position, siteOpt: Option[Symbol]): Body = new WikiParser(string, pos, siteOpt).document() + + /** TODO + * + * @author Ingo Maier + * @author Manohar Jonnalagedda + * @author Gilles Dubochet */ + protected final class WikiParser(val buffer: String, pos: Position, siteOpt: Option[Symbol]) extends CharReader(buffer) { wiki => + var summaryParsed = false + + def document(): Body = { + val blocks = new mutable.ListBuffer[Block] + while (char != endOfText) + blocks += block() + Body(blocks.toList) + } + + /* BLOCKS */ + + /** {{{ block ::= code | title | hrule | para }}} */ + def block(): Block = { + if (checkSkipInitWhitespace("{{{")) + code() + else if (checkSkipInitWhitespace('=')) + title() + else if (checkSkipInitWhitespace("----")) + hrule() + else if (checkList) + listBlock + else { + para() + } + } + + /** listStyle ::= '-' spc | '1.' spc | 'I.' spc | 'i.' spc | 'A.' spc | 'a.' spc + * Characters used to build lists and their constructors */ + protected val listStyles = Map[String, (Seq[Block] => Block)]( // TODO Should this be defined at some list companion? + "- " -> ( UnorderedList(_) ), + "1. " -> ( OrderedList(_,"decimal") ), + "I. " -> ( OrderedList(_,"upperRoman") ), + "i. " -> ( OrderedList(_,"lowerRoman") ), + "A. " -> ( OrderedList(_,"upperAlpha") ), + "a. " -> ( OrderedList(_,"lowerAlpha") ) + ) + + /** Checks if the current line is formed with more than one space and one the listStyles */ + def checkList = + (countWhitespace > 0) && (listStyles.keys exists { checkSkipInitWhitespace(_) }) + + /** {{{ + * nListBlock ::= nLine { mListBlock } + * nLine ::= nSpc listStyle para '\n' + * }}} + * Where n and m stand for the number of spaces. When `m > n`, a new list is nested. */ + def listBlock: Block = { + + /** Consumes one list item block and returns it, or None if the block is + * not a list or a different list. */ + def listLine(indent: Int, style: String): Option[Block] = + if (countWhitespace > indent && checkList) + Some(listBlock) + else if (countWhitespace != indent || !checkSkipInitWhitespace(style)) + None + else { + jumpWhitespace() + jump(style) + val p = Paragraph(inline(false)) + blockEnded("end of list line ") + Some(p) + } + + /** Consumes all list item blocks (possibly with nested lists) of the + * same list and returns the list block. */ + def listLevel(indent: Int, style: String): Block = { + val lines = mutable.ListBuffer.empty[Block] + var line: Option[Block] = listLine(indent, style) + while (line.isDefined) { + lines += line.get + line = listLine(indent, style) + } + val constructor = listStyles(style) + constructor(lines) + } + + val indent = countWhitespace + val style = (listStyles.keys find { checkSkipInitWhitespace(_) }).getOrElse(listStyles.keys.head) + listLevel(indent, style) + } + + def code(): Block = { + jumpWhitespace() + jump("{{{") + val str = readUntil("}}}") + if (char == endOfText) + reportError(pos, "unclosed code block") + else + jump("}}}") + blockEnded("code block") + Code(normalizeIndentation(str)) + } + + /** {{{ title ::= ('=' inline '=' | "==" inline "==" | ...) '\n' }}} */ + def title(): Block = { + jumpWhitespace() + val inLevel = repeatJump('=') + val text = inline(check("=" * inLevel)) + val outLevel = repeatJump('=', inLevel) + if (inLevel != outLevel) + reportError(pos, "unbalanced or unclosed heading") + blockEnded("heading") + Title(text, inLevel) + } + + /** {{{ hrule ::= "----" { '-' } '\n' }}} */ + def hrule(): Block = { + jumpWhitespace() + repeatJump('-') + blockEnded("horizontal rule") + HorizontalRule() + } + + /** {{{ para ::= inline '\n' }}} */ + def para(): Block = { + val p = + if (summaryParsed) + Paragraph(inline(false)) + else { + val s = summary() + val r = + if (checkParaEnded) List(s) else List(s, inline(false)) + summaryParsed = true + Paragraph(Chain(r)) + } + while (char == endOfLine && char != endOfText) + nextChar() + p + } + + /* INLINES */ + + val OPEN_TAG = "^<([A-Za-z]+)( [^>]*)?(/?)>$".r + val CLOSE_TAG = "^$".r + private def readHTMLFrom(begin: HtmlTag): String = { + val list = mutable.ListBuffer.empty[String] + val stack = mutable.ListBuffer.empty[String] + + begin.close match { + case Some(HtmlTag(CLOSE_TAG(s))) => + stack += s + case _ => + return "" + } + + do { + val str = readUntil { char == safeTagMarker || char == endOfText } + nextChar() + + list += str + + str match { + case OPEN_TAG(s, _, standalone) => { + if (standalone != "/") { + stack += s + } + } + case CLOSE_TAG(s) => { + if (s == stack.last) { + stack.remove(stack.length-1) + } + } + case _ => ; + } + } while (stack.length > 0 && char != endOfText) + + list mkString "" + } + + def inline(isInlineEnd: => Boolean): Inline = { + + def inline0(): Inline = { + if (char == safeTagMarker) { + val tag = htmlTag() + HtmlTag(tag.data + readHTMLFrom(tag)) + } + else if (check("'''")) bold() + else if (check("''")) italic() + else if (check("`")) monospace() + else if (check("__")) underline() + else if (check("^")) superscript() + else if (check(",,")) subscript() + else if (check("[[")) link() + else { + val str = readUntil { char == safeTagMarker || check("''") || char == '`' || check("__") || char == '^' || check(",,") || check("[[") || isInlineEnd || checkParaEnded || char == endOfLine } + Text(str) + } + } + + val inlines: List[Inline] = { + val iss = mutable.ListBuffer.empty[Inline] + iss += inline0() + while (!isInlineEnd && !checkParaEnded) { + val skipEndOfLine = if (char == endOfLine) { + nextChar() + true + } else { + false + } + + val current = inline0() + (iss.last, current) match { + case (Text(t1), Text(t2)) if skipEndOfLine => + iss.update(iss.length - 1, Text(t1 + endOfLine + t2)) + case (i1, i2) if skipEndOfLine => + iss ++= List(Text(endOfLine.toString), i2) + case _ => iss += current + } + } + iss.toList + } + + inlines match { + case Nil => Text("") + case i :: Nil => i + case is => Chain(is) + } + + } + + def htmlTag(): HtmlTag = { + jump(safeTagMarker) + val read = readUntil(safeTagMarker) + if (char != endOfText) jump(safeTagMarker) + HtmlTag(read) + } + + def bold(): Inline = { + jump("'''") + val i = inline(check("'''")) + jump("'''") + Bold(i) + } + + def italic(): Inline = { + jump("''") + val i = inline(check("''")) + jump("''") + Italic(i) + } + + def monospace(): Inline = { + jump("`") + val i = inline(check("`")) + jump("`") + Monospace(i) + } + + def underline(): Inline = { + jump("__") + val i = inline(check("__")) + jump("__") + Underline(i) + } + + def superscript(): Inline = { + jump("^") + val i = inline(check("^")) + if (jump("^")) { + Superscript(i) + } else { + Chain(Seq(Text("^"), i)) + } + } + + def subscript(): Inline = { + jump(",,") + val i = inline(check(",,")) + jump(",,") + Subscript(i) + } + + def summary(): Inline = { + val i = inline(check(".")) + Summary( + if (jump(".")) + Chain(List(i, Text("."))) + else + i + ) + } + + def link(): Inline = { + val SchemeUri = """([a-z]+:.*)""".r + jump("[[") + var parens = 2 + repeatJump('[') + val start = "[" * parens + val stop = "]" * parens + //println("link with " + parens + " matching parens") + val target = readUntil { check(stop) || check(" ") } + val title = + if (!check(stop)) Some({ + jump(" ") + inline(check(stop)) + }) + else None + jump(stop) + + (target, title) match { + case (SchemeUri(uri), optTitle) => + Link(uri, optTitle getOrElse Text(uri)) + case (qualName, optTitle) => + makeEntityLink(optTitle getOrElse Text(target), pos, target, siteOpt) + } + } + + /* UTILITY */ + + /** {{{ eol ::= { whitespace } '\n' }}} */ + def blockEnded(blockType: String): Unit = { + if (char != endOfLine && char != endOfText) { + reportError(pos, "no additional content on same line after " + blockType) + jumpUntil(endOfLine) + } + while (char == endOfLine) + nextChar() + } + + /** + * 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 + * another indented line + * if (condition) + * then do something; + * ^ this is the least whitespace prefix + * }}} + */ + def normalizeIndentation(_code: String): String = { + + var code = _code.trim + var maxSkip = Integer.MAX_VALUE + var crtSkip = 0 + var wsArea = true + var index = 0 + var firstLine = true + var emptyLine = true + + while (index < code.length) { + code(index) match { + case ' ' => + if (wsArea) + crtSkip += 1 + case c => + wsArea = (c == '\n') + 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 + } + index += 1 + } + + if (maxSkip == 0) + code + else { + index = 0 + val builder = new StringBuilder + while (index < code.length) { + builder.append(code(index)) + if (code(index) == '\n') { + // we want to skip as many spaces are available, if there are less spaces (like on empty lines, do not + // over-consume them) + index += 1 + val limit = index + maxSkip + while ((index < code.length) && (code(index) == ' ') && index < limit) + index += 1 + } + else + index += 1 + } + builder.toString + } + } + + def checkParaEnded(): Boolean = { + (char == endOfText) || + ((char == endOfLine) && { + val poff = offset + nextChar() // read EOL + val ok = { + checkSkipInitWhitespace(endOfLine) || + checkSkipInitWhitespace('=') || + checkSkipInitWhitespace("{{{") || + checkList || + checkSkipInitWhitespace('\u003D') + } + offset = poff + ok + }) + } + + def reportError(pos: Position, message: String) { + reporter.warning(pos, message) + } + } + + protected sealed class CharReader(buffer: String) { reader => + + var offset: Int = 0 + def char: Char = + if (offset >= buffer.length) endOfText else buffer charAt offset + + final def nextChar() { + offset += 1 + } + + final def check(chars: String): Boolean = { + val poff = offset + val ok = jump(chars) + offset = poff + ok + } + + def checkSkipInitWhitespace(c: Char): Boolean = { + val poff = offset + jumpWhitespace() + val ok = jump(c) + offset = poff + ok + } + + def checkSkipInitWhitespace(chars: String): Boolean = { + val poff = offset + jumpWhitespace() + val (ok0, chars0) = + if (chars.charAt(0) == ' ') + (offset > poff, chars substring 1) + else + (true, chars) + val ok = ok0 && jump(chars0) + offset = poff + ok + } + + def countWhitespace: Int = { + var count = 0 + val poff = offset + while (isWhitespace(char) && char != endOfText) { + nextChar() + count += 1 + } + offset = poff + count + } + + /* JUMPERS */ + + /** jumps a character and consumes it + * @return true only if the correct character has been jumped */ + final def jump(ch: Char): Boolean = { + if (char == ch) { + nextChar() + true + } + else false + } + + /** jumps all the characters in chars, consuming them in the process. + * @return true only if the correct characters have been jumped */ + final def jump(chars: String): Boolean = { + var index = 0 + while (index < chars.length && char == chars.charAt(index) && char != endOfText) { + nextChar() + index += 1 + } + index == chars.length + } + + final def repeatJump(c: Char, max: Int = Int.MaxValue): Int = { + var count = 0 + while (jump(c) && count < max) + count += 1 + count + } + + final def jumpUntil(ch: Char): Int = { + var count = 0 + while (char != ch && char != endOfText) { + nextChar() + count += 1 + } + count + } + + final def jumpUntil(chars: String): Int = { + assert(chars.length > 0) + var count = 0 + val c = chars.charAt(0) + while (!check(chars) && char != endOfText) { + nextChar() + while (char != c && char != endOfText) { + nextChar() + count += 1 + } + } + count + } + + final def jumpUntil(pred: => Boolean): Int = { + var count = 0 + while (!pred && char != endOfText) { + nextChar() + count += 1 + } + count + } + + def jumpWhitespace() = jumpUntil(!isWhitespace(char)) + + /* READERS */ + + final def readUntil(c: Char): String = { + withRead { + while (char != c && char != endOfText) { + nextChar() + } + } + } + + final def readUntil(chars: String): String = { + assert(chars.length > 0) + withRead { + val c = chars.charAt(0) + while (!check(chars) && char != endOfText) { + nextChar() + while (char != c && char != endOfText) + nextChar() + } + } + } + + final def readUntil(pred: => Boolean): String = { + withRead { + while (char != endOfText && !pred) { + nextChar() + } + } + } + + private def withRead(read: => Unit): String = { + val start = offset + read + buffer.substring(start, offset) + } + + + /* CHARS CLASSES */ + + def isWhitespace(c: Char) = c == ' ' || c == '\t' + + } + +} diff --git a/src/compiler/scala/tools/nsc/doc/base/LinkTo.scala b/src/compiler/scala/tools/nsc/doc/base/LinkTo.scala new file mode 100755 index 0000000000..c11179800c --- /dev/null +++ b/src/compiler/scala/tools/nsc/doc/base/LinkTo.scala @@ -0,0 +1,15 @@ +/* NSC -- new Scala compiler + * Copyright 2007-2013 LAMP/EPFL + */ + +package scala.tools.nsc +package doc +package base + +import scala.collection._ + +sealed trait LinkTo +final case class LinkToMember[Mbr, Tpl](mbr: Mbr, tpl: Tpl) extends LinkTo +final case class LinkToTpl[Tpl](tpl: Tpl) extends LinkTo +final case class LinkToExternal(name: String, url: String) extends LinkTo +final case class Tooltip(name: String) extends LinkTo diff --git a/src/compiler/scala/tools/nsc/doc/base/MemberLookupBase.scala b/src/compiler/scala/tools/nsc/doc/base/MemberLookupBase.scala new file mode 100755 index 0000000000..f3a5660dc4 --- /dev/null +++ b/src/compiler/scala/tools/nsc/doc/base/MemberLookupBase.scala @@ -0,0 +1,230 @@ +package scala.tools.nsc +package doc +package base + +import comment._ + +/** This trait extracts all required information for documentation from compilation units. + * The base trait has been extracted to allow getting light-weight documentation + * for a particular symbol in the IDE.*/ +trait MemberLookupBase { + + val global: Global + val settings: doc.Settings + + import global._ + def internalLink(sym: Symbol, site: Symbol): Option[LinkTo] + def chooseLink(links: List[LinkTo]): LinkTo + + import global._ + import definitions.{ NothingClass, AnyClass, AnyValClass, AnyRefClass, ListClass } + import rootMirror.{RootPackage, EmptyPackage} + + private def isRoot(s: Symbol) = s.isRootSymbol || s.isEmptyPackage || s.isEmptyPackageClass + + def makeEntityLink(title: Inline, pos: Position, query: String, siteOpt: Option[Symbol]) = + new EntityLink(title) { lazy val link = memberLookup(pos, query, siteOpt) } + + def memberLookup(pos: Position, query: String, siteOpt: Option[Symbol]): LinkTo = { + var members = breakMembers(query) + + // (1) First look in the root package, as most of the links are qualified + val fromRoot = lookupInRootPackage(pos, members) + + // (2) Or recursively go into each containing template. + val fromParents = siteOpt.fold(Stream.empty[Symbol]) { s => + Stream.iterate(s)(_.owner) + }.takeWhile (!isRoot(_)).map { + lookupInTemplate(pos, members, _) + } + + val syms = (fromRoot +: fromParents) find (!_.isEmpty) getOrElse Nil + + val links = syms flatMap { case (sym, site) => internalLink(sym, site) } match { + case Nil => + // (3) Look at external links + syms.flatMap { case (sym, owner) => + // reconstruct the original link + def linkName(sym: Symbol) = { + def nameString(s: Symbol) = s.nameString + (if ((s.isModule || s.isModuleClass) && !s.isPackage) "$" else "") + val packageSuffix = if (sym.isPackage) ".package" else "" + + sym.ownerChain.reverse.filterNot(isRoot(_)).map(nameString(_)).mkString(".") + packageSuffix + } + + if (sym.isClass || sym.isModule || sym.isTrait || sym.isPackage) + findExternalLink(sym, linkName(sym)) + else if (owner.isClass || owner.isModule || owner.isTrait || owner.isPackage) + findExternalLink(sym, linkName(owner) + "@" + externalSignature(sym)) + else + None + } + case links => links + } + links match { + case Nil => + if (!settings.docNoLinkWarnings.value) + reporter.warning(pos, "Could not find any member to link for \"" + query + "\".") + // (4) if we still haven't found anything, create a tooltip + Tooltip(query) + case List(l) => l + case links => + val chosen = chooseLink(links) + def linkToString(link: LinkTo) = { + val chosenInfo = + if (link == chosen) " [chosen]" else "" + link.toString + chosenInfo + "\n" + } + if (!settings.docNoLinkWarnings.value) + reporter.warning(pos, + "The link target \"" + query + "\" is ambiguous. Several (possibly overloaded) members fit the target:\n" + + links.map(linkToString).mkString + + (if (MemberLookup.showExplanation) + "\n\n" + + "Quick crash course on using Scaladoc links\n" + + "==========================================\n" + + "Disambiguating terms and types: Prefix terms with '$' and types with '!' in case both names are in use:\n" + + " - [[scala.collection.immutable.List!.apply class List's apply method]] and\n" + + " - [[scala.collection.immutable.List$.apply object List's apply method]]\n" + + "Disambiguating overloaded members: If a term is overloaded, you can indicate the first part of its signature followed by *:\n" + + " - [[[scala.collection.immutable.List$.fill[A](Int)(⇒A):List[A]* Fill with a single parameter]]]\n" + + " - [[[scala.collection.immutable.List$.fill[A](Int,Int)(⇒A):List[List[A]]* Fill with a two parameters]]]\n" + + "Notes: \n" + + " - you can use any number of matching square brackets to avoid interference with the signature\n" + + " - you can use \\. to escape dots in prefixes (don't forget to use * at the end to match the signature!)\n" + + " - you can use \\# to escape hashes, otherwise they will be considered as delimiters, like dots.\n" + else "") + ) + chosen + } + } + + private abstract class SearchStrategy + private object BothTypeAndTerm extends SearchStrategy + private object OnlyType extends SearchStrategy + private object OnlyTerm extends SearchStrategy + + private def lookupInRootPackage(pos: Position, members: List[String]) = + lookupInTemplate(pos, members, EmptyPackage) ::: lookupInTemplate(pos, members, RootPackage) + + private def lookupInTemplate(pos: Position, members: List[String], container: Symbol): List[(Symbol, Symbol)] = { + // Maintaining compatibility with previous links is a bit tricky here: + // we have a preference for term names for all terms except for the last, where we prefer a class: + // How to do this: + // - at each step we do a DFS search with the prefered strategy + // - if the search doesn't return any members, we backtrack on the last decision + // * we look for terms with the last member's name + // * we look for types with the same name, all the way up + val result = members match { + case Nil => Nil + case mbrName::Nil => + var syms = lookupInTemplate(pos, mbrName, container, OnlyType) map ((_, container)) + if (syms.isEmpty) + syms = lookupInTemplate(pos, mbrName, container, OnlyTerm) map ((_, container)) + syms + + case tplName::rest => + def completeSearch(syms: List[Symbol]) = + syms flatMap (lookupInTemplate(pos, rest, _)) + + completeSearch(lookupInTemplate(pos, tplName, container, OnlyTerm)) match { + case Nil => completeSearch(lookupInTemplate(pos, tplName, container, OnlyType)) + case syms => syms + } + } + //println("lookupInTemplate(" + members + ", " + container + ") => " + result) + result + } + + private def lookupInTemplate(pos: Position, member: String, container: Symbol, strategy: SearchStrategy): List[Symbol] = { + val name = member.stripSuffix("$").stripSuffix("!").stripSuffix("*") + def signatureMatch(sym: Symbol): Boolean = externalSignature(sym).startsWith(name) + + // We need to cleanup the bogus classes created by the .class file parser. For example, [[scala.Predef]] resolves + // to (bogus) class scala.Predef loaded by the class loader -- which we need to eliminate by looking at the info + // and removing NoType classes + def cleanupBogusClasses(syms: List[Symbol]) = { syms.filter(_.info != NoType) } + + def syms(name: Name) = container.info.nonPrivateMember(name.encodedName).alternatives + def termSyms = cleanupBogusClasses(syms(newTermName(name))) + def typeSyms = cleanupBogusClasses(syms(newTypeName(name))) + + val result = if (member.endsWith("$")) + termSyms + else if (member.endsWith("!")) + typeSyms + else if (member.endsWith("*")) + cleanupBogusClasses(container.info.nonPrivateDecls) filter signatureMatch + else + if (strategy == BothTypeAndTerm) + termSyms ::: typeSyms + else if (strategy == OnlyType) + typeSyms + else if (strategy == OnlyTerm) + termSyms + else + Nil + + //println("lookupInTemplate(" + member + ", " + container + ") => " + result) + result + } + + private def breakMembers(query: String): List[String] = { + // Okay, how does this work? Well: you split on . but you don't want to split on \. => thus the ugly regex + // query.split((?<=[^\\\\])\\.).map(_.replaceAll("\\.")) + // The same code, just faster: + var members = List[String]() + var index = 0 + var last_index = 0 + val length = query.length + while (index < length) { + if ((query.charAt(index) == '.' || query.charAt(index) == '#') && + ((index == 0) || (query.charAt(index-1) != '\\'))) { + + val member = query.substring(last_index, index).replaceAll("\\\\([#\\.])", "$1") + // we want to allow javadoc-style links [[#member]] -- which requires us to remove empty members from the first + // elemnt in the list + if ((member != "") || (!members.isEmpty)) + members ::= member + last_index = index + 1 + } + index += 1 + } + if (last_index < length) + members ::= query.substring(last_index, length).replaceAll("\\\\\\.", ".") + members.reverse + } + + + def findExternalLink(sym: Symbol, name: String): Option[LinkToExternal] = { + val sym1 = + if (sym == AnyClass || sym == AnyRefClass || sym == AnyValClass || sym == NothingClass) ListClass + else if (sym.isPackage) + /* Get package object which has associatedFile ne null */ + sym.info.member(newTermName("package")) + else sym + Option(sym1.associatedFile) flatMap (_.underlyingSource) flatMap { src => + val path = src.path + settings.extUrlMapping get path map { url => + LinkToExternal(name, url + "#" + name) + } + } orElse { + // Deprecated option. + settings.extUrlPackageMapping find { + case (pkg, _) => name startsWith pkg + } map { + case (_, url) => LinkToExternal(name, url + "#" + name) + } + } + } + + def externalSignature(sym: Symbol) = { + sym.info // force it, otherwise we see lazy types + (sym.nameString + sym.signatureString).replaceAll("\\s", "") + } +} + +object MemberLookup { + private[this] var _showExplanation = true + def showExplanation: Boolean = if (_showExplanation) { _showExplanation = false; true } else false +} diff --git a/src/compiler/scala/tools/nsc/doc/base/comment/Body.scala b/src/compiler/scala/tools/nsc/doc/base/comment/Body.scala new file mode 100755 index 0000000000..02e662da85 --- /dev/null +++ b/src/compiler/scala/tools/nsc/doc/base/comment/Body.scala @@ -0,0 +1,91 @@ +/* NSC -- new Scala compiler + * Copyright 2007-2013 LAMP/EPFL + * @author Manohar Jonnalagedda + */ + +package scala.tools.nsc +package doc +package base +package comment + +import scala.collection._ + +import java.net.URL + +/** A body of text. A comment has a single body, which is composed of + * at least one block. Inside every body is exactly one summary (see + * [[scala.tools.nsc.doc.model.comment.Summary]]). */ +final case class Body(blocks: Seq[Block]) { + + /** The summary text of the comment body. */ + lazy val summary: Option[Inline] = { + def summaryInBlock(block: Block): Seq[Inline] = block match { + case Title(text, _) => summaryInInline(text) + case Paragraph(text) => summaryInInline(text) + case UnorderedList(items) => items flatMap summaryInBlock + case OrderedList(items, _) => items flatMap summaryInBlock + case DefinitionList(items) => items.values.toSeq flatMap summaryInBlock + case _ => Nil + } + def summaryInInline(text: Inline): Seq[Inline] = text match { + case Summary(text) => List(text) + case Chain(items) => items flatMap summaryInInline + case Italic(text) => summaryInInline(text) + case Bold(text) => summaryInInline(text) + case Underline(text) => summaryInInline(text) + case Superscript(text) => summaryInInline(text) + case Subscript(text) => summaryInInline(text) + case Link(_, title) => summaryInInline(title) + case _ => Nil + } + (blocks flatMap { summaryInBlock(_) }).toList match { + case Nil => None + case inline :: Nil => Some(inline) + case inlines => Some(Chain(inlines)) + } + } +} + +/** A block-level element of text, such as a paragraph or code block. */ +sealed abstract class Block + +final case class Title(text: Inline, level: Int) extends Block +final case class Paragraph(text: Inline) extends Block +final case class Code(data: String) extends Block +final case class UnorderedList(items: Seq[Block]) extends Block +final case class OrderedList(items: Seq[Block], style: String) extends Block +final case class DefinitionList(items: SortedMap[Inline, Block]) extends Block +final case class HorizontalRule() extends Block + +/** An section of text inside a block, possibly with formatting. */ +sealed abstract class Inline + +final case class Chain(items: Seq[Inline]) extends Inline +final case class Italic(text: Inline) extends Inline +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 Link(target: String, title: Inline) extends Inline +final case class Monospace(text: Inline) extends Inline +final case class Text(text: String) extends Inline +abstract class EntityLink(val title: Inline) extends Inline { def link: LinkTo } +object EntityLink { + def apply(title: Inline, linkTo: LinkTo) = new EntityLink(title) { def link: LinkTo = linkTo } + def unapply(el: EntityLink): Option[(Inline, LinkTo)] = Some((el.title, el.link)) +} +final case class HtmlTag(data: String) extends Inline { + def canClose(open: HtmlTag) = { + open.data.stripPrefix("<") == data.stripPrefix(" + list foreach scan + case tag: HtmlTag => { + if (stack.length > 0 && tag.canClose(stack.last)) { + stack.remove(stack.length-1) + } else { + tag.close match { + case Some(t) => + stack += t + case None => + ; + } + } + } + case _ => + ; + } + } + scan(inline) + Chain(List(inline) ++ stack.reverse) + } + + /** A shorter version of the body. Usually, this is the first sentence of the body. */ + def short: Inline = { + body.summary match { + case Some(s) => + closeHtmlTags(s) + case _ => + Text("") + } + } + + /** 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] + + /** The comment as it appears in the source text. */ + def source: Option[String] + + /** 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] + + /** The group this member is part of */ + def group: Option[String] + + /** Member group descriptions */ + def groupDesc: Map[String,Body] + + /** Member group names (overriding the short tag) */ + def groupNames: Map[String,String] + + /** Member group priorities */ + def groupPrio: Map[String,Int] + + override def toString = + body.toString + "\n" + + (authors map ("@author " + _.toString)).mkString("\n") + + (result map ("@return " + _.toString)).mkString("\n") + + (version map ("@version " + _.toString)).mkString +} diff --git a/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala b/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala index e8131e242b..69da322418 100644 --- a/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala +++ b/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala @@ -7,8 +7,9 @@ package scala.tools.nsc package doc package html +import base._ +import base.comment._ import model._ -import comment._ import scala.xml.{XML, NodeSeq} import scala.xml.dtd.{DocType, PublicID} @@ -126,12 +127,12 @@ abstract class HtmlPage extends Page { thisPage => } def linkToHtml(text: Inline, link: LinkTo, hasLinks: Boolean) = link match { - case LinkToTpl(dtpl) => + case LinkToTpl(dtpl: TemplateEntity) => if (hasLinks) { inlineToHtml(text) } else { inlineToHtml(text) } - case LinkToMember(mbr, inTpl) => + case LinkToMember(mbr: MemberEntity, inTpl: TemplateEntity) => if (hasLinks) { inlineToHtml(text) } else @@ -140,7 +141,7 @@ abstract class HtmlPage extends Page { thisPage => { inlineToHtml(text) } case LinkToExternal(name, url) => { inlineToHtml(text) } - case NoLink => + case _ => inlineToHtml(text) } diff --git a/src/compiler/scala/tools/nsc/doc/html/page/Source.scala b/src/compiler/scala/tools/nsc/doc/html/page/Source.scala index 807a1bc11a..68289b7474 100644 --- a/src/compiler/scala/tools/nsc/doc/html/page/Source.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/Source.scala @@ -9,7 +9,6 @@ package html package page import model._ -import comment._ import scala.xml.{NodeSeq, Unparsed} import java.io.File 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 008999e09f..e885e9c56e 100644 --- a/src/compiler/scala/tools/nsc/doc/html/page/Template.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/Template.scala @@ -8,6 +8,9 @@ package doc package html package page +import base._ +import base.comment._ + import model._ import model.diagram._ import diagram._ diff --git a/src/compiler/scala/tools/nsc/doc/html/page/diagram/DotDiagramGenerator.scala b/src/compiler/scala/tools/nsc/doc/html/page/diagram/DotDiagramGenerator.scala index 304c534bdc..847367838c 100644 --- a/src/compiler/scala/tools/nsc/doc/html/page/diagram/DotDiagramGenerator.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/diagram/DotDiagramGenerator.scala @@ -74,7 +74,7 @@ class DotDiagramGenerator(settings: doc.Settings) extends DiagramGenerator { def textTypeEntity(text: String) = new TypeEntity { val name = text - def refEntity: SortedMap[Int, (LinkTo, Int)] = SortedMap() + def refEntity: SortedMap[Int, (base.LinkTo, Int)] = SortedMap() } // it seems dot chokes on node names over 8000 chars, so let's limit the size of the string diff --git a/src/compiler/scala/tools/nsc/doc/model/CommentFactory.scala b/src/compiler/scala/tools/nsc/doc/model/CommentFactory.scala new file mode 100644 index 0000000000..9ba89146c0 --- /dev/null +++ b/src/compiler/scala/tools/nsc/doc/model/CommentFactory.scala @@ -0,0 +1,114 @@ +/* NSC -- new Scala compiler + * Copyright 2007-2013 LAMP/EPFL + * @author Manohar Jonnalagedda + */ + +package scala.tools.nsc +package doc +package model + +import base.comment._ + +import reporters.Reporter +import scala.collection._ +import scala.reflect.internal.util.{NoPosition, Position} +import scala.language.postfixOps + +/** The comment parser transforms raw comment strings into `Comment` objects. + * Call `parse` to run the parser. Note that the parser is stateless and + * should only be built once for a given Scaladoc run. + * + * @param reporter The reporter on which user messages (error, warnings) should be printed. + * + * @author Manohar Jonnalagedda + * @author Gilles Dubochet */ +trait CommentFactory extends base.CommentFactoryBase { + thisFactory: ModelFactory with CommentFactory with MemberLookup => + + val global: Global + import global.{ reporter, definitions, Symbol } + + protected val commentCache = mutable.HashMap.empty[(Symbol, TemplateImpl), Comment] + + def addCommentBody(sym: Symbol, inTpl: TemplateImpl, docStr: String, docPos: global.Position): Symbol = { + commentCache += (sym, inTpl) -> parse(docStr, docStr, docPos, None) + sym + } + + def comment(sym: Symbol, currentTpl: Option[DocTemplateImpl], inTpl: DocTemplateImpl): Option[Comment] = { + val key = (sym, inTpl) + if (commentCache isDefinedAt key) + Some(commentCache(key)) + else { + val c = defineComment(sym, currentTpl, inTpl) + if (c isDefined) commentCache += (sym, inTpl) -> c.get + c + } + } + + /** A comment is usualy created by the parser, however for some special + * cases we have to give some `inTpl` comments (parent class for example) + * to the comment of the symbol. + * This function manages some of those cases : Param accessor and Primary constructor */ + def defineComment(sym: Symbol, currentTpl: Option[DocTemplateImpl], inTpl: DocTemplateImpl):Option[Comment] = { + + //param accessor case + // We just need the @param argument, we put it into the body + if( sym.isParamAccessor && + inTpl.comment.isDefined && + inTpl.comment.get.valueParams.isDefinedAt(sym.encodedName)) { + val comContent = Some(inTpl.comment.get.valueParams(sym.encodedName)) + Some(createComment(body0 = comContent)) + } + + // Primary constructor case + // We need some content of the class definition : @constructor for the body, + // @param and @deprecated, we can add some more if necessary + else if (sym.isPrimaryConstructor && inTpl.comment.isDefined ) { + val tplComment = inTpl.comment.get + // If there is nothing to put into the comment there is no need to create it + if(tplComment.constructor.isDefined || + tplComment.throws != Map.empty || + tplComment.valueParams != Map.empty || + tplComment.typeParams != Map.empty || + tplComment.deprecated.isDefined + ) + Some(createComment( body0 = tplComment.constructor, + throws0 = tplComment.throws, + valueParams0 = tplComment.valueParams, + typeParams0 = tplComment.typeParams, + deprecated0 = tplComment.deprecated + )) + else None + } + + //other comment cases + // parse function will make the comment + else { + val rawComment = global.expandedDocComment(sym, inTpl.sym).trim + if (rawComment != "") { + val tplOpt = if (currentTpl.isDefined) currentTpl else Some(inTpl) + val c = parse(rawComment, global.rawDocComment(sym), global.docCommentPos(sym), tplOpt) + Some(c) + } + else None + } + + } + + protected def parse(comment: String, src: String, pos: Position, inTplOpt: Option[DocTemplateImpl] = None): Comment = { + assert(!inTplOpt.isDefined || inTplOpt.get != null) + parseAtSymbol(comment, src, pos, inTplOpt map (_.sym)) + } + + /** Parses a string containing wiki syntax into a `Comment` object. + * Note that the string is assumed to be clean: + * - Removed Scaladoc start and end markers. + * - 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. */ + def parseWiki(string: String, pos: Position, inTplOpt: Option[DocTemplateImpl]): Body = { + assert(!inTplOpt.isDefined || inTplOpt.get != null) + parseWikiAtSymbol(string,pos, inTplOpt map (_.sym)) + } +} diff --git a/src/compiler/scala/tools/nsc/doc/model/Entity.scala b/src/compiler/scala/tools/nsc/doc/model/Entity.scala index c3f9101f17..cbc1a23d44 100644 --- a/src/compiler/scala/tools/nsc/doc/model/Entity.scala +++ b/src/compiler/scala/tools/nsc/doc/model/Entity.scala @@ -9,7 +9,7 @@ package doc package model import scala.collection._ -import comment._ +import base.comment._ import diagram._ /** An entity in a Scaladoc universe. Entities are declarations in the program and correspond to symbols in the diff --git a/src/compiler/scala/tools/nsc/doc/model/LinkTo.scala b/src/compiler/scala/tools/nsc/doc/model/LinkTo.scala deleted file mode 100644 index 6c13d5a6d3..0000000000 --- a/src/compiler/scala/tools/nsc/doc/model/LinkTo.scala +++ /dev/null @@ -1,24 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2007-2013 LAMP/EPFL - */ - -package scala.tools.nsc -package doc -package model - -import scala.collection._ - -abstract sealed class LinkTo -final case class LinkToTpl(tpl: DocTemplateEntity) extends LinkTo -final case class LinkToMember(mbr: MemberEntity, inTpl: DocTemplateEntity) extends LinkTo -final case class Tooltip(name: String) extends LinkTo { def this(tpl: TemplateEntity) = this(tpl.qualifiedName) } -final case class LinkToExternal(name: String, url: String) extends LinkTo -case object NoLink extends LinkTo // you should use Tooltip if you have a name from the user, this is only in case all fails - -object LinkToTpl { - // this makes it easier to create links - def apply(tpl: TemplateEntity) = tpl match { - case dtpl: DocTemplateEntity => new LinkToTpl(dtpl) - case ntpl: TemplateEntity => new Tooltip(ntpl.qualifiedName) - } -} diff --git a/src/compiler/scala/tools/nsc/doc/model/MemberLookup.scala b/src/compiler/scala/tools/nsc/doc/model/MemberLookup.scala index 5257db1610..b1105196b7 100644 --- a/src/compiler/scala/tools/nsc/doc/model/MemberLookup.scala +++ b/src/compiler/scala/tools/nsc/doc/model/MemberLookup.scala @@ -2,227 +2,30 @@ package scala.tools.nsc package doc package model -import comment._ - -import scala.reflect.internal.util.FakePos //Position +import base._ /** This trait extracts all required information for documentation from compilation units */ -trait MemberLookup { +trait MemberLookup extends base.MemberLookupBase { thisFactory: ModelFactory => import global._ - import rootMirror.RootPackage, rootMirror.EmptyPackage - - def makeEntityLink(title: Inline, pos: Position, query: String, inTplOpt: Option[DocTemplateImpl]) = - new EntityLink(title) { lazy val link = memberLookup(pos, query, inTplOpt) } - - def memberLookup(pos: Position, query: String, inTplOpt: Option[DocTemplateImpl]): LinkTo = { - assert(modelFinished) - - var members = breakMembers(query) - //println(query + " => " + members) - - // (1) First look in the root package, as most of the links are qualified - val fromRoot = lookupInRootPackage(pos, members) - - // (2) Or recursively go into each containing template. - val fromParents = inTplOpt.fold(Stream.empty[DocTemplateImpl]) { tpl => - Stream.iterate(tpl)(_.inTemplate) - }.takeWhile (tpl => tpl != null && !tpl.isRootPackage).map { tpl => - lookupInTemplate(pos, members, tpl.asInstanceOf[EntityImpl].sym) - } - - val syms = (fromRoot +: fromParents) find (!_.isEmpty) getOrElse Nil - val linkTo = createLinks(syms) match { - case Nil if !syms.isEmpty => - // (3) Look at external links - syms.flatMap { case (sym, owner) => - - // reconstruct the original link - def linkName(sym: Symbol) = { - def isRoot(s: Symbol) = s.isRootSymbol || s.isEmptyPackage || s.isEmptyPackageClass - def nameString(s: Symbol) = s.nameString + (if ((s.isModule || s.isModuleClass) && !s.isPackage) "$" else "") - val packageSuffix = if (sym.isPackage) ".package" else "" - sym.ownerChain.reverse.filterNot(isRoot(_)).map(nameString(_)).mkString(".") + packageSuffix - } - - if (sym.isClass || sym.isModule || sym.isTrait || sym.isPackage) - findExternalLink(sym, linkName(sym)) - else if (owner.isClass || owner.isModule || owner.isTrait || owner.isPackage) - findExternalLink(sym, linkName(owner) + "@" + externalSignature(sym)) - else - None + override def internalLink(sym: Symbol, site: Symbol): Option[LinkTo] = + findTemplateMaybe(sym) match { + case Some(tpl) => Some(LinkToTpl(tpl)) + case None => + findTemplateMaybe(site) flatMap { inTpl => + inTpl.members find (_.asInstanceOf[EntityImpl].sym == sym) map (LinkToMember(_, inTpl)) } - case links => links } - //println(createLinks(syms)) - //println(linkTo) - - // (4) if we still haven't found anything, create a tooltip, if we found too many, report - if (linkTo.isEmpty){ - if (!settings.docNoLinkWarnings.value) - reporter.warning(pos, "Could not find any member to link for \"" + query + "\".") - Tooltip(query) - } else { - if (linkTo.length > 1) { - - val chosen = - if (linkTo.exists(_.isInstanceOf[LinkToMember])) - linkTo.collect({case lm: LinkToMember => lm}).min(Ordering[MemberEntity].on[LinkToMember](_.mbr)) - else - linkTo.head - - def linkToString(link: LinkTo) = { - val description = - link match { - case lm@LinkToMember(mbr, inTpl) => " * " + mbr.kind + " \"" + mbr.signature + "\" in " + inTpl.kind + " " + inTpl.qualifiedName - case lt@LinkToTpl(tpl) => " * " + tpl.kind + " \"" + tpl.qualifiedName + "\"" - case other => " * " + other.toString - } - val chosenInfo = - if (link == chosen) - " [chosen]" - else - "" - description + chosenInfo + "\n" - } - if (!settings.docNoLinkWarnings.value) - reporter.warning(pos, - "The link target \"" + query + "\" is ambiguous. Several (possibly overloaded) members fit the target:\n" + - linkTo.map(link => linkToString(link)).mkString + - (if (MemberLookup.showExplanation) - "\n\n" + - "Quick crash course on using Scaladoc links\n" + - "==========================================\n" + - "Disambiguating terms and types: Prefix terms with '$' and types with '!' in case both names are in use:\n" + - " - [[scala.collection.immutable.List!.apply class List's apply method]] and\n" + - " - [[scala.collection.immutable.List$.apply object List's apply method]]\n" + - "Disambiguating overloaded members: If a term is overloaded, you can indicate the first part of its signature followed by *:\n" + - " - [[[scala.collection.immutable.List$.fill[A](Int)(⇒A):List[A]* Fill with a single parameter]]]\n" + - " - [[[scala.collection.immutable.List$.fill[A](Int,Int)(⇒A):List[List[A]]* Fill with a two parameters]]]\n" + - "Notes: \n" + - " - you can use any number of matching square brackets to avoid interference with the signature\n" + - " - you can use \\. to escape dots in prefixes (don't forget to use * at the end to match the signature!)\n" + - " - you can use \\# to escape hashes, otherwise they will be considered as delimiters, like dots.\n" - else "") - ) - chosen - } else - linkTo.head + override def chooseLink(links: List[LinkTo]): LinkTo = { + val mbrs = links.collect { + case lm@LinkToMember(mbr: MemberEntity, _) => (mbr, lm) } - } - - private abstract class SearchStrategy - private object BothTypeAndTerm extends SearchStrategy - private object OnlyType extends SearchStrategy - private object OnlyTerm extends SearchStrategy - - private def lookupInRootPackage(pos: Position, members: List[String]) = - lookupInTemplate(pos, members, EmptyPackage) ::: lookupInTemplate(pos, members, RootPackage) - - private def createLinks(syms: List[(Symbol, Symbol)]): List[LinkTo] = - syms.flatMap { case (sym, owner) => - findTemplateMaybe(sym) match { - case Some(tpl) => LinkToTpl(tpl) :: Nil - case None => - findTemplateMaybe(owner) flatMap { inTpl => - inTpl.members find (_.asInstanceOf[EntityImpl].sym == sym) map (LinkToMember(_, inTpl)) - } - } - } - - private def lookupInTemplate(pos: Position, members: List[String], container: Symbol): List[(Symbol, Symbol)] = { - // Maintaining compatibility with previous links is a bit tricky here: - // we have a preference for term names for all terms except for the last, where we prefer a class: - // How to do this: - // - at each step we do a DFS search with the prefered strategy - // - if the search doesn't return any members, we backtrack on the last decision - // * we look for terms with the last member's name - // * we look for types with the same name, all the way up - val result = members match { - case Nil => Nil - case mbrName::Nil => - var syms = lookupInTemplate(pos, mbrName, container, OnlyType) map ((_, container)) - if (syms.isEmpty) - syms = lookupInTemplate(pos, mbrName, container, OnlyTerm) map ((_, container)) - syms - - case tplName::rest => - def completeSearch(syms: List[Symbol]) = - syms flatMap (lookupInTemplate(pos, rest, _)) - - completeSearch(lookupInTemplate(pos, tplName, container, OnlyTerm)) match { - case Nil => completeSearch(lookupInTemplate(pos, tplName, container, OnlyType)) - case syms => syms - } - } - //println("lookupInTemplate(" + members + ", " + container + ") => " + result) - result - } - - private def lookupInTemplate(pos: Position, member: String, container: Symbol, strategy: SearchStrategy): List[Symbol] = { - val name = member.stripSuffix("$").stripSuffix("!").stripSuffix("*") - def signatureMatch(sym: Symbol): Boolean = externalSignature(sym).startsWith(name) - - // We need to cleanup the bogus classes created by the .class file parser. For example, [[scala.Predef]] resolves - // to (bogus) class scala.Predef loaded by the class loader -- which we need to eliminate by looking at the info - // and removing NoType classes - def cleanupBogusClasses(syms: List[Symbol]) = { syms.filter(_.info != NoType) } - - def syms(name: Name) = container.info.nonPrivateMember(name.encodedName).alternatives - def termSyms = cleanupBogusClasses(syms(newTermName(name))) - def typeSyms = cleanupBogusClasses(syms(newTypeName(name))) - - val result = if (member.endsWith("$")) - termSyms - else if (member.endsWith("!")) - typeSyms - else if (member.endsWith("*")) - cleanupBogusClasses(container.info.nonPrivateDecls) filter signatureMatch + if (mbrs.isEmpty) + links.head else - if (strategy == BothTypeAndTerm) - termSyms ::: typeSyms - else if (strategy == OnlyType) - typeSyms - else if (strategy == OnlyTerm) - termSyms - else - Nil - - //println("lookupInTemplate(" + member + ", " + container + ") => " + result) - result - } - - private def breakMembers(query: String): List[String] = { - // Okay, how does this work? Well: you split on . but you don't want to split on \. => thus the ugly regex - // query.split((?<=[^\\\\])\\.).map(_.replaceAll("\\.")) - // The same code, just faster: - var members = List[String]() - var index = 0 - var last_index = 0 - val length = query.length - while (index < length) { - if ((query.charAt(index) == '.' || query.charAt(index) == '#') && - ((index == 0) || (query.charAt(index-1) != '\\'))) { - - val member = query.substring(last_index, index).replaceAll("\\\\([#\\.])", "$1") - // we want to allow javadoc-style links [[#member]] -- which requires us to remove empty members from the first - // elemnt in the list - if ((member != "") || (!members.isEmpty)) - members ::= member - last_index = index + 1 - } - index += 1 - } - if (last_index < length) - members ::= query.substring(last_index, length).replaceAll("\\\\\\.", ".") - members.reverse + mbrs.min(Ordering[MemberEntity].on[(MemberEntity, LinkTo)](_._1))._2 } } - -object MemberLookup { - private[this] var _showExplanation = true - def showExplanation: Boolean = if (_showExplanation) { _showExplanation = false; true } else false -} diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala index 3ae1210ebf..4c7f5dc9c6 100644 --- a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala @@ -4,8 +4,8 @@ package scala.tools.nsc package doc package model -import comment._ - +import base._ +import base.comment._ import diagram._ import scala.collection._ @@ -1085,31 +1085,12 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { { val rawComment = global.expandedDocComment(bSym, inTpl.sym) rawComment.contains("@template") || rawComment.contains("@documentable") } - def findExternalLink(sym: Symbol, name: String): Option[LinkTo] = { - val sym1 = - if (sym == AnyClass || sym == AnyRefClass || sym == AnyValClass || sym == NothingClass) ListClass - else if (sym.isPackage) - /* Get package object which has associatedFile ne null */ - sym.info.member(newTermName("package")) - else sym - Option(sym1.associatedFile) flatMap (_.underlyingSource) flatMap { src => - val path = src.path - settings.extUrlMapping get path map { url => - LinkToExternal(name, url + "#" + name) - } - } orElse { - // Deprecated option. - settings.extUrlPackageMapping find { - case (pkg, _) => name startsWith pkg - } map { - case (_, url) => LinkToExternal(name, url + "#" + name) - } + object LinkToTpl { + // this makes it easier to create links + def apply(tpl: TemplateEntity): LinkTo = tpl match { + case dtpl: DocTemplateEntity => new LinkToTpl(dtpl) + case _ => new Tooltip(tpl.qualifiedName) } } - - def externalSignature(sym: Symbol) = { - sym.info // force it, otherwise we see lazy types - (sym.nameString + sym.signatureString).replaceAll("\\s", "") - } } diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala index 5334de3797..f88251b22e 100644 --- a/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala +++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala @@ -10,8 +10,6 @@ package scala.tools.nsc package doc package model -import comment._ - import scala.collection._ import scala.util.matching.Regex @@ -608,4 +606,4 @@ trait ModelFactoryImplicitSupport { 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/ModelFactoryTypeSupport.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactoryTypeSupport.scala index 942ccaf1ba..87dc615c8e 100644 --- a/src/compiler/scala/tools/nsc/doc/model/ModelFactoryTypeSupport.scala +++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactoryTypeSupport.scala @@ -4,8 +4,7 @@ package scala.tools.nsc package doc package model -import comment._ - +import base._ import diagram._ import scala.collection._ @@ -24,7 +23,8 @@ trait ModelFactoryTypeSupport { with ModelFactoryTypeSupport with DiagramFactory with CommentFactory - with TreeFactory => + with TreeFactory + with MemberLookup => import global._ import definitions.{ ObjectClass, NothingClass, AnyClass, AnyValClass, AnyRefClass } diff --git a/src/compiler/scala/tools/nsc/doc/model/TypeEntity.scala b/src/compiler/scala/tools/nsc/doc/model/TypeEntity.scala index e4a053e115..cf5c1fb3fb 100644 --- a/src/compiler/scala/tools/nsc/doc/model/TypeEntity.scala +++ b/src/compiler/scala/tools/nsc/doc/model/TypeEntity.scala @@ -20,7 +20,7 @@ abstract class TypeEntity { /** Maps which parts of this type's name reference entities. The map is indexed by the position of the first * character that reference some entity, and contains the entity and the position of the last referenced * character. The referenced character ranges do not to overlap or nest. The map is sorted by position. */ - def refEntity: SortedMap[Int, (LinkTo, Int)] + def refEntity: SortedMap[Int, (base.LinkTo, Int)] /** The human-readable representation of this type. */ override def toString = name diff --git a/src/compiler/scala/tools/nsc/doc/model/comment/Body.scala b/src/compiler/scala/tools/nsc/doc/model/comment/Body.scala deleted file mode 100644 index 3e5e634e18..0000000000 --- a/src/compiler/scala/tools/nsc/doc/model/comment/Body.scala +++ /dev/null @@ -1,91 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2007-2013 LAMP/EPFL - * @author Manohar Jonnalagedda - */ - -package scala.tools.nsc -package doc -package model -package comment - -import scala.collection._ - -import java.net.URL - -/** A body of text. A comment has a single body, which is composed of - * at least one block. Inside every body is exactly one summary (see - * [[scala.tools.nsc.doc.model.comment.Summary]]). */ -final case class Body(blocks: Seq[Block]) { - - /** The summary text of the comment body. */ - lazy val summary: Option[Inline] = { - def summaryInBlock(block: Block): Seq[Inline] = block match { - case Title(text, _) => summaryInInline(text) - case Paragraph(text) => summaryInInline(text) - case UnorderedList(items) => items flatMap summaryInBlock - case OrderedList(items, _) => items flatMap summaryInBlock - case DefinitionList(items) => items.values.toSeq flatMap summaryInBlock - case _ => Nil - } - def summaryInInline(text: Inline): Seq[Inline] = text match { - case Summary(text) => List(text) - case Chain(items) => items flatMap summaryInInline - case Italic(text) => summaryInInline(text) - case Bold(text) => summaryInInline(text) - case Underline(text) => summaryInInline(text) - case Superscript(text) => summaryInInline(text) - case Subscript(text) => summaryInInline(text) - case Link(_, title) => summaryInInline(title) - case _ => Nil - } - (blocks flatMap { summaryInBlock(_) }).toList match { - case Nil => None - case inline :: Nil => Some(inline) - case inlines => Some(Chain(inlines)) - } - } -} - -/** A block-level element of text, such as a paragraph or code block. */ -sealed abstract class Block - -final case class Title(text: Inline, level: Int) extends Block -final case class Paragraph(text: Inline) extends Block -final case class Code(data: String) extends Block -final case class UnorderedList(items: Seq[Block]) extends Block -final case class OrderedList(items: Seq[Block], style: String) extends Block -final case class DefinitionList(items: SortedMap[Inline, Block]) extends Block -final case class HorizontalRule() extends Block - -/** An section of text inside a block, possibly with formatting. */ -sealed abstract class Inline - -final case class Chain(items: Seq[Inline]) extends Inline -final case class Italic(text: Inline) extends Inline -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 Link(target: String, title: Inline) extends Inline -final case class Monospace(text: Inline) extends Inline -final case class Text(text: String) extends Inline -abstract class EntityLink(val title: Inline) extends Inline { def link: LinkTo } -object EntityLink { - def apply(title: Inline, linkTo: LinkTo) = new EntityLink(title) { def link: LinkTo = linkTo } - def unapply(el: EntityLink): Option[(Inline, LinkTo)] = Some((el.title, el.link)) -} -final case class HtmlTag(data: String) extends Inline { - def canClose(open: HtmlTag) = { - open.data.stripPrefix("<") == data.stripPrefix(" - list foreach scan - case tag: HtmlTag => { - if (stack.length > 0 && tag.canClose(stack.last)) { - stack.remove(stack.length-1) - } else { - tag.close match { - case Some(t) => - stack += t - case None => - ; - } - } - } - case _ => - ; - } - } - scan(inline) - Chain(List(inline) ++ stack.reverse) - } - - /** A shorter version of the body. Usually, this is the first sentence of the body. */ - def short: Inline = { - body.summary match { - case Some(s) => - closeHtmlTags(s) - case _ => - Text("") - } - } - - /** 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] - - /** The comment as it appears in the source text. */ - def source: Option[String] - - /** 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] - - /** The group this member is part of */ - def group: Option[String] - - /** Member group descriptions */ - def groupDesc: Map[String,Body] - - /** Member group names (overriding the short tag) */ - def groupNames: Map[String,String] - - /** Member group priorities */ - def groupPrio: Map[String,Int] - - override def toString = - body.toString + "\n" + - (authors map ("@author " + _.toString)).mkString("\n") + - (result map ("@return " + _.toString)).mkString("\n") + - (version map ("@version " + _.toString)).mkString - -} diff --git a/src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala b/src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala deleted file mode 100644 index 40057bbb52..0000000000 --- a/src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala +++ /dev/null @@ -1,1085 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2007-2013 LAMP/EPFL - * @author Manohar Jonnalagedda - */ - -package scala.tools.nsc -package doc -package model -package comment - -import reporters.Reporter -import scala.collection._ -import scala.util.matching.Regex -import scala.annotation.switch -import scala.reflect.internal.util.{NoPosition, Position} -import scala.language.postfixOps - -/** The comment parser transforms raw comment strings into `Comment` objects. - * Call `parse` to run the parser. Note that the parser is stateless and - * should only be built once for a given Scaladoc run. - * - * @param reporter The reporter on which user messages (error, warnings) should be printed. - * - * @author Manohar Jonnalagedda - * @author Gilles Dubochet */ -trait CommentFactory { thisFactory: ModelFactory with CommentFactory with MemberLookup=> - - val global: Global - import global.{ reporter, definitions } - - protected val commentCache = mutable.HashMap.empty[(global.Symbol, TemplateImpl), Comment] - - def addCommentBody(sym: global.Symbol, inTpl: TemplateImpl, docStr: String, docPos: global.Position): global.Symbol = { - commentCache += (sym, inTpl) -> parse(docStr, docStr, docPos, None) - sym - } - - def comment(sym: global.Symbol, currentTpl: Option[DocTemplateImpl], inTpl: DocTemplateImpl): Option[Comment] = { - val key = (sym, inTpl) - if (commentCache isDefinedAt key) - Some(commentCache(key)) - else { - val c = defineComment(sym, currentTpl, inTpl) - if (c isDefined) commentCache += (sym, inTpl) -> c.get - c - } - } - - /** A comment is usualy created by the parser, however for some special - * cases we have to give some `inTpl` comments (parent class for example) - * to the comment of the symbol. - * This function manages some of those cases : Param accessor and Primary constructor */ - def defineComment(sym: global.Symbol, currentTpl: Option[DocTemplateImpl], inTpl: DocTemplateImpl):Option[Comment] = { - - //param accessor case - // We just need the @param argument, we put it into the body - if( sym.isParamAccessor && - inTpl.comment.isDefined && - inTpl.comment.get.valueParams.isDefinedAt(sym.encodedName)) { - val comContent = Some(inTpl.comment.get.valueParams(sym.encodedName)) - Some(createComment(body0 = comContent)) - } - - // Primary constructor case - // We need some content of the class definition : @constructor for the body, - // @param and @deprecated, we can add some more if necessary - else if (sym.isPrimaryConstructor && inTpl.comment.isDefined ) { - val tplComment = inTpl.comment.get - // If there is nothing to put into the comment there is no need to create it - if(tplComment.constructor.isDefined || - tplComment.throws != Map.empty || - tplComment.valueParams != Map.empty || - tplComment.typeParams != Map.empty || - tplComment.deprecated.isDefined - ) - Some(createComment( body0 = tplComment.constructor, - throws0 = tplComment.throws, - valueParams0 = tplComment.valueParams, - typeParams0 = tplComment.typeParams, - deprecated0 = tplComment.deprecated - )) - else None - } - - //other comment cases - // parse function will make the comment - else { - val rawComment = global.expandedDocComment(sym, inTpl.sym).trim - if (rawComment != "") { - val tplOpt = if (currentTpl.isDefined) currentTpl else Some(inTpl) - val c = parse(rawComment, global.rawDocComment(sym), global.docCommentPos(sym), tplOpt) - Some(c) - } - else None - } - - } - - /* 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, - inheritDiagram0: List[String] = List.empty, - contentDiagram0: List[String] = List.empty, - group0: Option[Body] = None, - groupDesc0: Map[String,Body] = Map.empty, - groupNames0: Map[String,Body] = Map.empty, - groupPrio0: Map[String,Body] = Map.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 inheritDiagram = inheritDiagram0 - val contentDiagram = contentDiagram0 - val groupDesc = groupDesc0 - val group = - group0 match { - case Some(Body(List(Paragraph(Chain(List(Summary(Text(groupId)))))))) => Some(groupId.toString.trim) - case _ => None - } - val groupPrio = groupPrio0 flatMap { - case (group, body) => - try { - body match { - case Body(List(Paragraph(Chain(List(Summary(Text(prio))))))) => List(group -> prio.trim.toInt) - case _ => List() - } - } catch { - case _: java.lang.NumberFormatException => List() - } - } - val groupNames = groupNames0 flatMap { - case (group, body) => - try { - body match { - case Body(List(Paragraph(Chain(List(Summary(Text(name))))))) if (!name.trim.contains("\n")) => List(group -> (name.trim)) - case _ => List() - } - } catch { - case _: java.lang.NumberFormatException => List() - } - } - - } - - protected val endOfText = '\u0003' - protected val endOfLine = '\u000A' - - /** Something that should not have happened, happened, and Scaladoc should exit. */ - protected def oops(msg: String): Nothing = - throw FatalError("program logic: " + msg) - - /** The body of a line, dropping the (optional) start star-marker, - * one leading whitespace and all trailing whitespace. */ - protected val CleanCommentLine = - new Regex("""(?:\s*\*\s?)?(.*)""") - - /** Dangerous HTML tags that should be replaced by something safer, - * such as wiki syntax, or that should be dropped. */ - protected val DangerousTags = - new Regex("""<(/?(div|ol|ul|li|h[1-6]|p))( [^>]*)?/?>|""") - - /** Maps a dangerous HTML tag to a safe wiki replacement, or an empty string - * if it cannot be salvaged. */ - protected def htmlReplacement(mtch: Regex.Match): String = mtch.group(1) match { - case "p" | "div" => "\n\n" - case "h1" => "\n= " - case "/h1" => " =\n" - case "h2" => "\n== " - case "/h2" => " ==\n" - case "h3" => "\n=== " - case "/h3" => " ===\n" - case "h4" | "h5" | "h6" => "\n==== " - case "/h4" | "/h5" | "/h6" => " ====\n" - case "li" => "\n * - " - case _ => "" - } - - /** Javadoc tags that should be replaced by something useful, such as wiki - * syntax, or that should be dropped. */ - protected val JavadocTags = - new Regex("""\{\@(code|docRoot|inheritDoc|link|linkplain|literal|value)([^}]*)\}""") - - /** Maps a javadoc tag to a useful wiki replacement, or an empty string if it cannot be salvaged. */ - protected def javadocReplacement(mtch: Regex.Match): String = mtch.group(1) match { - case "code" => "`" + mtch.group(2) + "`" - case "docRoot" => "" - case "inheritDoc" => "" - case "link" => "`" + mtch.group(2) + "`" - case "linkplain" => "`" + mtch.group(2) + "`" - case "literal" => mtch.group(2) - case "value" => "`" + mtch.group(2) + "`" - case _ => "" - } - - /** Safe HTML tags that can be kept. */ - protected val SafeTags = - new Regex("""((&\w+;)|(&#\d+;)|(]*)?/?>))""") - - 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+(.*)""") - - /** A Scaladoc tag linked to a symbol. Returns the name of the tag, the name - * of the symbol, and the rest of the line. */ - protected val SymbolTag = - new Regex("""\s*@(param|tparam|throws|groupdesc|groupname|groupprio)\s+(\S*)\s*(.*)""") - - /** The start of a scaladoc code block */ - protected val CodeBlockStart = - new Regex("""(.*?)((?:\{\{\{)|(?:\u000E]*)?>\u000E))(.*)""") - - /** The end of a scaladoc code block */ - protected val CodeBlockEnd = - new Regex("""(.*?)((?:\}\}\})|(?:\u000E\u000E))(.*)""") - - /** 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 { - def name: String - } - - protected final case class SimpleTagKey(name: String) extends TagKey - protected final case class SymbolTagKey(name: String, symbol: String) extends TagKey - - /** Parses a raw comment string into a `Comment` object. - * @param comment The expanded comment string (including start and end markers) to be parsed. - * @param src The raw comment source string. - * @param pos The position of the comment in source. */ - protected def parse(comment: String, src: String, pos: Position, inTplOpt: Option[DocTemplateImpl] = None): Comment = { - assert(!inTplOpt.isDefined || inTplOpt.get != null) - - /** The cleaned raw comment as a list of lines. Cleaning removes comment - * start and end markers, line start markers and unnecessary whitespace. */ - def clean(comment: String): List[String] = { - def cleanLine(line: String): String = { - //replaceAll removes trailing whitespaces - line.replaceAll("""\s+$""", "") match { - case CleanCommentLine(ctl) => ctl - case tl => tl - } - } - val strippedComment = comment.trim.stripPrefix("/*").stripSuffix("*/") - val safeComment = DangerousTags.replaceAllIn(strippedComment, { htmlReplacement(_) }) - val javadoclessComment = JavadocTags.replaceAllIn(safeComment, { javadocReplacement(_) }) - val markedTagComment = - SafeTags.replaceAllIn(javadoclessComment, { mtch => - java.util.regex.Matcher.quoteReplacement(safeTagMarker + mtch.matched + safeTagMarker) - }) - markedTagComment.lines.toList map (cleanLine(_)) - } - - /** Parses a comment (in the form of a list of lines) to a `Comment` - * instance, recursively on lines. To do so, it splits the whole comment - * into main body and tag bodies, then runs the `WikiParser` on each body - * before creating the comment instance. - * - * @param docBody 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: StringBuilder, - tags: Map[TagKey, List[String]], - lastTagKey: Option[TagKey], - remaining: List[String], - inCodeBlock: Boolean - ): Comment = remaining match { - - case CodeBlockStart(before, marker, after) :: ls if (!inCodeBlock) => - if (!before.trim.isEmpty && !after.trim.isEmpty) - parse0(docBody, tags, lastTagKey, before :: marker :: after :: ls, false) - else if (!before.trim.isEmpty) - parse0(docBody, tags, lastTagKey, before :: marker :: ls, false) - else if (!after.trim.isEmpty) - parse0(docBody, tags, lastTagKey, marker :: after :: ls, true) - else lastTagKey match { - case Some(key) => - val value = - ((tags get key): @unchecked) match { - case Some(b :: bs) => (b + endOfLine + marker) :: bs - case None => oops("lastTagKey set when no tag exists for key") - } - parse0(docBody, tags + (key -> value), lastTagKey, ls, true) - case None => - parse0(docBody append endOfLine append marker, tags, lastTagKey, ls, true) - } - - case CodeBlockEnd(before, marker, after) :: ls => - if (!before.trim.isEmpty && !after.trim.isEmpty) - parse0(docBody, tags, lastTagKey, before :: marker :: after :: ls, true) - if (!before.trim.isEmpty) - parse0(docBody, tags, lastTagKey, before :: marker :: ls, true) - else if (!after.trim.isEmpty) - parse0(docBody, tags, lastTagKey, marker :: after :: ls, false) - else lastTagKey match { - case Some(key) => - val value = - ((tags get key): @unchecked) match { - case Some(b :: bs) => (b + endOfLine + marker) :: bs - case None => oops("lastTagKey set when no tag exists for key") - } - parse0(docBody, tags + (key -> value), lastTagKey, ls, false) - case None => - parse0(docBody append endOfLine append marker, 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, inCodeBlock) - - 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, 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 = - ((tags get key): @unchecked) match { - 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, inCodeBlock) - - case line :: ls => - if (docBody.length > 0) docBody append endOfLine - docBody append line - 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 stripTags=List(inheritDiagramTag, contentDiagramTag, SimpleTagKey("template"), SimpleTagKey("documentable")) - val tagsWithoutDiagram = tags.filterNot(pair => stripTags.contains(pair._1)) - - val bodyTags: mutable.Map[TagKey, List[Body]] = - mutable.Map(tagsWithoutDiagram mapValues {tag => tag map (parseWiki(_, pos, inTplOpt))} toSeq: _*) - - def oneTag(key: SimpleTagKey): Option[Body] = - ((bodyTags remove key): @unchecked) match { - case Some(r :: rs) => - if (!rs.isEmpty) reporter.warning(pos, "Only one '@" + key.name + "' tag is allowed") - Some(r) - case None => None - } - - def allTags(key: SimpleTagKey): List[Body] = - (bodyTags remove key) getOrElse Nil - - def allSymsOneTag(key: TagKey): Map[String, Body] = { - val keys: Seq[SymbolTagKey] = - bodyTags.keys.toSeq flatMap { - case stk: SymbolTagKey if (stk.name == key.name) => Some(stk) - case stk: SimpleTagKey if (stk.name == key.name) => - reporter.warning(pos, "Tag '@" + stk.name + "' must be followed by a symbol name") - None - case _ => None - } - val pairs: Seq[(String, Body)] = - for (key <- keys) yield { - val bs = (bodyTags remove key).get - if (bs.length > 1) - reporter.warning(pos, "Only one '@" + key.name + "' tag for symbol " + key.symbol + " is allowed") - (key.symbol, bs.head) - } - Map.empty[String, Body] ++ pairs - } - - val com = createComment ( - body0 = Some(parseWiki(docBody.toString, pos, inTplOpt)), - 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, - group0 = oneTag(SimpleTagKey("group")), - groupDesc0 = allSymsOneTag(SimpleTagKey("groupdesc")), - groupNames0 = allSymsOneTag(SimpleTagKey("groupname")), - groupPrio0 = allSymsOneTag(SimpleTagKey("groupprio")) - ) - - for ((key, _) <- bodyTags) - reporter.warning(pos, "Tag '@" + key.name + "' is not recognised") - - com - - } - - parse0(new StringBuilder(comment.size), Map.empty, None, clean(comment), false) - - } - - /** Parses a string containing wiki syntax into a `Comment` object. - * Note that the string is assumed to be clean: - * - Removed Scaladoc start and end markers. - * - 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. */ - def parseWiki(string: String, pos: Position, inTplOpt: Option[DocTemplateImpl]): Body = { - assert(!inTplOpt.isDefined || inTplOpt.get != null) - - new WikiParser(string, pos, inTplOpt).document() - } - - /** TODO - * - * @author Ingo Maier - * @author Manohar Jonnalagedda - * @author Gilles Dubochet */ - protected final class WikiParser(val buffer: String, pos: Position, inTplOpt: Option[DocTemplateImpl]) extends CharReader(buffer) { wiki => - assert(!inTplOpt.isDefined || inTplOpt.get != null) - - var summaryParsed = false - - def document(): Body = { - nextChar() - val blocks = new mutable.ListBuffer[Block] - while (char != endOfText) - blocks += block() - Body(blocks.toList) - } - - /* BLOCKS */ - - /** {{{ block ::= code | title | hrule | para }}} */ - def block(): Block = { - if (checkSkipInitWhitespace("{{{")) - code() - else if (checkSkipInitWhitespace('=')) - title() - else if (checkSkipInitWhitespace("----")) - hrule() - else if (checkList) - listBlock - else { - para() - } - } - - /** listStyle ::= '-' spc | '1.' spc | 'I.' spc | 'i.' spc | 'A.' spc | 'a.' spc - * Characters used to build lists and their constructors */ - protected val listStyles = Map[String, (Seq[Block] => Block)]( // TODO Should this be defined at some list companion? - "- " -> ( UnorderedList(_) ), - "1. " -> ( OrderedList(_,"decimal") ), - "I. " -> ( OrderedList(_,"upperRoman") ), - "i. " -> ( OrderedList(_,"lowerRoman") ), - "A. " -> ( OrderedList(_,"upperAlpha") ), - "a. " -> ( OrderedList(_,"lowerAlpha") ) - ) - - /** Checks if the current line is formed with more than one space and one the listStyles */ - def checkList = - (countWhitespace > 0) && (listStyles.keys exists { checkSkipInitWhitespace(_) }) - - /** {{{ - * nListBlock ::= nLine { mListBlock } - * nLine ::= nSpc listStyle para '\n' - * }}} - * Where n and m stand for the number of spaces. When `m > n`, a new list is nested. */ - def listBlock: Block = { - - /** Consumes one list item block and returns it, or None if the block is - * not a list or a different list. */ - def listLine(indent: Int, style: String): Option[Block] = - if (countWhitespace > indent && checkList) - Some(listBlock) - else if (countWhitespace != indent || !checkSkipInitWhitespace(style)) - None - else { - jumpWhitespace() - jump(style) - val p = Paragraph(inline(false)) - blockEnded("end of list line ") - Some(p) - } - - /** Consumes all list item blocks (possibly with nested lists) of the - * same list and returns the list block. */ - def listLevel(indent: Int, style: String): Block = { - val lines = mutable.ListBuffer.empty[Block] - var line: Option[Block] = listLine(indent, style) - while (line.isDefined) { - lines += line.get - line = listLine(indent, style) - } - val constructor = listStyles(style) - constructor(lines) - } - - val indent = countWhitespace - val style = (listStyles.keys find { checkSkipInitWhitespace(_) }).getOrElse(listStyles.keys.head) - listLevel(indent, style) - } - - def code(): Block = { - jumpWhitespace() - jump("{{{") - readUntil("}}}") - if (char == endOfText) - reportError(pos, "unclosed code block") - else - jump("}}}") - blockEnded("code block") - Code(normalizeIndentation(getRead)) - } - - /** {{{ title ::= ('=' inline '=' | "==" inline "==" | ...) '\n' }}} */ - def title(): Block = { - jumpWhitespace() - val inLevel = repeatJump("=") - val text = inline(check("=" * inLevel)) - val outLevel = repeatJump("=", inLevel) - if (inLevel != outLevel) - reportError(pos, "unbalanced or unclosed heading") - blockEnded("heading") - Title(text, inLevel) - } - - /** {{{ hrule ::= "----" { '-' } '\n' }}} */ - def hrule(): Block = { - jumpWhitespace() - repeatJump("-") - blockEnded("horizontal rule") - HorizontalRule() - } - - /** {{{ para ::= inline '\n' }}} */ - def para(): Block = { - val p = - if (summaryParsed) - Paragraph(inline(false)) - else { - val s = summary() - val r = - if (checkParaEnded) List(s) else List(s, inline(false)) - summaryParsed = true - Paragraph(Chain(r)) - } - while (char == endOfLine && char != endOfText) - nextChar() - p - } - - /* INLINES */ - - val OPEN_TAG = "^<([A-Za-z]+)( [^>]*)?(/?)>$".r - val CLOSE_TAG = "^$".r - private def readHTMLFrom(begin: HtmlTag): String = { - val list = mutable.ListBuffer.empty[String] - val stack = mutable.ListBuffer.empty[String] - - begin.close match { - case Some(HtmlTag(CLOSE_TAG(s))) => - stack += s - case _ => - return "" - } - - do { - readUntil { char == safeTagMarker || char == endOfText } - val str = getRead() - nextChar() - - list += str - - str match { - case OPEN_TAG(s, _, standalone) => { - if (standalone != "/") { - stack += s - } - } - case CLOSE_TAG(s) => { - if (s == stack.last) { - stack.remove(stack.length-1) - } - } - case _ => ; - } - } while (stack.length > 0 && char != endOfText) - - list mkString "" - } - - def inline(isInlineEnd: => Boolean): Inline = { - - def inline0(): Inline = { - if (char == safeTagMarker) { - val tag = htmlTag() - HtmlTag(tag.data + readHTMLFrom(tag)) - } - else if (check("'''")) bold() - else if (check("''")) italic() - else if (check("`")) monospace() - else if (check("__")) underline() - else if (check("^")) superscript() - else if (check(",,")) subscript() - else if (check("[[")) link() - else { - readUntil { char == safeTagMarker || check("''") || char == '`' || check("__") || char == '^' || check(",,") || check("[[") || isInlineEnd || checkParaEnded || char == endOfLine } - Text(getRead()) - } - } - - val inlines: List[Inline] = { - val iss = mutable.ListBuffer.empty[Inline] - iss += inline0() - while (!isInlineEnd && !checkParaEnded) { - val skipEndOfLine = if (char == endOfLine) { - nextChar() - true - } else { - false - } - - val current = inline0() - (iss.last, current) match { - case (Text(t1), Text(t2)) if skipEndOfLine => - iss.update(iss.length - 1, Text(t1 + endOfLine + t2)) - case (i1, i2) if skipEndOfLine => - iss ++= List(Text(endOfLine.toString), i2) - case _ => iss += current - } - } - iss.toList - } - - inlines match { - case Nil => Text("") - case i :: Nil => i - case is => Chain(is) - } - - } - - def htmlTag(): HtmlTag = { - jump(safeTagMarker) - readUntil(safeTagMarker) - if (char != endOfText) jump(safeTagMarker) - var read = getRead - HtmlTag(read) - } - - def bold(): Inline = { - jump("'''") - val i = inline(check("'''")) - jump("'''") - Bold(i) - } - - def italic(): Inline = { - jump("''") - val i = inline(check("''")) - jump("''") - Italic(i) - } - - def monospace(): Inline = { - jump("`") - val i = inline(check("`")) - jump("`") - Monospace(i) - } - - def underline(): Inline = { - jump("__") - val i = inline(check("__")) - jump("__") - Underline(i) - } - - def superscript(): Inline = { - jump("^") - val i = inline(check("^")) - if (jump("^")) { - Superscript(i) - } else { - Chain(Seq(Text("^"), i)) - } - } - - def subscript(): Inline = { - jump(",,") - val i = inline(check(",,")) - jump(",,") - Subscript(i) - } - - def summary(): Inline = { - val i = inline(check(".")) - Summary( - if (jump(".")) - Chain(List(i, Text("."))) - else - i - ) - } - - def link(): Inline = { - val SchemeUri = """([a-z]+:.*)""".r - jump("[[") - var parens = 1 - readUntil { parens += 1; !check("[") } - getRead // clear the buffer - val start = "[" * parens - val stop = "]" * parens - //println("link with " + parens + " matching parens") - readUntil { check(stop) || check(" ") } - val target = getRead() - val title = - if (!check(stop)) Some({ - jump(" ") - inline(check(stop)) - }) - else None - jump(stop) - - (target, title) match { - case (SchemeUri(uri), optTitle) => - Link(uri, optTitle getOrElse Text(uri)) - case (qualName, optTitle) => - makeEntityLink(optTitle getOrElse Text(target), pos, target, inTplOpt) - } - } - - /* UTILITY */ - - /** {{{ eol ::= { whitespace } '\n' }}} */ - def blockEnded(blockType: String): Unit = { - if (char != endOfLine && char != endOfText) { - reportError(pos, "no additional content on same line after " + blockType) - jumpUntil(endOfLine) - } - while (char == endOfLine) - nextChar() - } - - /** - * 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 - * another indented line - * if (condition) - * then do something; - * ^ this is the least whitespace prefix - * }}} - */ - def normalizeIndentation(_code: String): String = { - - var code = _code.trim - var maxSkip = Integer.MAX_VALUE - var crtSkip = 0 - var wsArea = true - var index = 0 - var firstLine = true - var emptyLine = true - - while (index < code.length) { - code(index) match { - case ' ' => - if (wsArea) - crtSkip += 1 - case c => - wsArea = (c == '\n') - 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 - } - index += 1 - } - - if (maxSkip == 0) - code - else { - index = 0 - val builder = new StringBuilder - while (index < code.length) { - builder.append(code(index)) - if (code(index) == '\n') { - // we want to skip as many spaces are available, if there are less spaces (like on empty lines, do not - // over-consume them) - index += 1 - val limit = index + maxSkip - while ((index < code.length) && (code(index) == ' ') && index < limit) - index += 1 - } - else - index += 1 - } - builder.toString - } - } - - def checkParaEnded(): Boolean = { - (char == endOfText) || - ((char == endOfLine) && { - val poff = offset - val pc = char - nextChar() // read EOL - val ok = { - checkSkipInitWhitespace(endOfLine) || - checkSkipInitWhitespace('=') || - checkSkipInitWhitespace("{{{") || - checkList || - checkSkipInitWhitespace('\u003D') - } - offset = poff - char = pc - ok - }) - } - - def reportError(pos: Position, message: String) { - reporter.warning(pos, message) - } - } - - protected sealed class CharReader(buffer: String) { reader => - - var char: Char = _ - var offset: Int = 0 - - final def nextChar() { - if (offset >= buffer.length) - char = endOfText - else { - char = buffer charAt offset - offset += 1 - } - } - - final def check(chars: String): Boolean = { - val poff = offset - val pc = char - val ok = jump(chars) - offset = poff - char = pc - ok - } - - def checkSkipInitWhitespace(c: Char): Boolean = { - val poff = offset - val pc = char - jumpWhitespace() - val ok = jump(c) - offset = poff - char = pc - ok - } - - def checkSkipInitWhitespace(chars: String): Boolean = { - val poff = offset - val pc = char - jumpWhitespace() - val (ok0, chars0) = - if (chars.charAt(0) == ' ') - (offset > poff, chars substring 1) - else - (true, chars) - val ok = ok0 && jump(chars0) - offset = poff - char = pc - ok - } - - def countWhitespace: Int = { - var count = 0 - val poff = offset - val pc = char - while (isWhitespace(char) && char != endOfText) { - nextChar() - count += 1 - } - offset = poff - char = pc - count - } - - /* JUMPERS */ - - /** jumps a character and consumes it - * @return true only if the correct character has been jumped */ - final def jump(ch: Char): Boolean = { - if (char == ch) { - nextChar() - true - } - else false - } - - /** jumps all the characters in chars, consuming them in the process. - * @return true only if the correct characters have been jumped */ - final def jump(chars: String): Boolean = { - var index = 0 - while (index < chars.length && char == chars.charAt(index) && char != endOfText) { - nextChar() - index += 1 - } - index == chars.length - } - - final def checkedJump(chars: String): Boolean = { - val poff = offset - val pc = char - val ok = jump(chars) - if (!ok) { - offset = poff - char = pc - } - ok - } - - final def repeatJump(chars: String, max: Int): Int = { - var count = 0 - var more = true - while (more && count < max) { - if (!checkedJump(chars)) - more = false - else - count += 1 - } - count - } - - final def repeatJump(chars: String): Int = { - var count = 0 - var more = true - while (more) { - if (!checkedJump(chars)) - more = false - else - count += 1 - } - count - } - - final def jumpUntil(ch: Char): Int = { - var count = 0 - while (char != ch && char != endOfText) { - nextChar() - count += 1 - } - count - } - - final def jumpUntil(chars: String): Int = { - assert(chars.length > 0) - var count = 0 - val c = chars.charAt(0) - while (!check(chars) && char != endOfText) { - nextChar() - while (char != c && char != endOfText) { - nextChar() - count += 1 - } - } - count - } - - final def jumpUntil(pred: => Boolean): Int = { - var count = 0 - while (!pred && char != endOfText) { - nextChar() - count += 1 - } - count - } - - def jumpWhitespace() = jumpUntil(!isWhitespace(char)) - - /* READERS */ - - private val readBuilder = new mutable.StringBuilder - - final def getRead(): String = { - val bld = readBuilder.toString - readBuilder.clear() - if (bld.length < 6) bld.intern else bld - } - - final def readUntil(ch: Char): Int = { - var count = 0 - while (char != ch && char != endOfText) { - readBuilder += char - nextChar() - } - count - } - - final def readUntil(chars: String): Int = { - assert(chars.length > 0) - var count = 0 - val c = chars.charAt(0) - while (!check(chars) && char != endOfText) { - readBuilder += char - nextChar() - while (char != c && char != endOfText) { - readBuilder += char - nextChar() - } - } - count - } - - final def readUntil(pred: => Boolean): Int = { - var count = 0 - while (!pred && char != endOfText) { - readBuilder += char - nextChar() - } - count - } - - /* CHARS CLASSES */ - - def isWhitespace(c: Char) = c == ' ' || c == '\t' - - } - -} diff --git a/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramDirectiveParser.scala b/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramDirectiveParser.scala index 49cfaffc2e..cd60865ce7 100644 --- a/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramDirectiveParser.scala +++ b/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramDirectiveParser.scala @@ -3,7 +3,6 @@ package model package diagram import model._ -import comment.CommentFactory import java.util.regex.{Pattern, Matcher} import scala.util.matching.Regex @@ -259,4 +258,4 @@ trait DiagramDirectiveParser { 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 index db2d0c0175..175b4a6472 100644 --- a/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala @@ -3,7 +3,6 @@ package model package diagram import model._ -import comment.CommentFactory import scala.collection.mutable // statistics @@ -25,7 +24,7 @@ trait DiagramFactory extends DiagramDirectiveParser { // the following can used for hardcoding different relations into the diagram, for bootstrapping purposes def aggregationNode(text: String) = - NormalNode(new TypeEntity { val name = text; val refEntity = SortedMap[Int, (LinkTo, Int)]() }, None)() + NormalNode(new TypeEntity { val name = text; val refEntity = SortedMap[Int, (base.LinkTo, Int)]() }, None)() /** Create the inheritance diagram for this template */ def makeInheritanceDiagram(tpl: DocTemplateImpl): Option[Diagram] = { diff --git a/src/compiler/scala/tools/nsc/interactive/Doc.scala b/src/compiler/scala/tools/nsc/interactive/Doc.scala new file mode 100755 index 0000000000..4d02de6198 --- /dev/null +++ b/src/compiler/scala/tools/nsc/interactive/Doc.scala @@ -0,0 +1,50 @@ +/* NSC -- new Scala compiler + * Copyright 2007-2012 LAMP/EPFL + * @author Eugene Vigdorchik + */ + +package scala.tools.nsc +package interactive + +import doc.base._ +import comment._ +import scala.xml.NodeSeq + +sealed trait DocResult +final case class UrlResult(url: String) extends DocResult +final case class HtmlResult(comment: Comment) extends DocResult + +abstract class Doc(val settings: doc.Settings) extends MemberLookupBase with CommentFactoryBase { + + override val global: interactive.Global + import global._ + + def chooseLink(links: List[LinkTo]): LinkTo + + override def internalLink(sym: Symbol, site: Symbol): Option[LinkTo] = + ask { () => + if (sym.isClass || sym.isModule) + Some(LinkToTpl(sym)) + else + if ((site.isClass || site.isModule) && site.info.members.toList.contains(sym)) + Some(LinkToMember(sym, site)) + else + None + } + + def retrieve(sym: Symbol, site: Symbol): Option[DocResult] = { + val sig = ask { () => externalSignature(sym) } + findExternalLink(sym, sig) map { link => UrlResult(link.url) } orElse { + val resp = new Response[Tree] + // Ensure docComment tree is type-checked. + val pos = ask { () => docCommentPos(sym) } + askTypeAt(pos, resp) + resp.get.left.toOption flatMap { _ => + ask { () => + val comment = parseAtSymbol(expandedDocComment(sym), rawDocComment(sym), pos, Some(site)) + Some(HtmlResult(comment)) + } + } + } + } +} diff --git a/src/compiler/scala/tools/nsc/interactive/Global.scala b/src/compiler/scala/tools/nsc/interactive/Global.scala index 2ab389445f..0fc4fcaaf7 100644 --- a/src/compiler/scala/tools/nsc/interactive/Global.scala +++ b/src/compiler/scala/tools/nsc/interactive/Global.scala @@ -74,6 +74,7 @@ class Global(settings: Settings, _reporter: Reporter, projectName: String = "") if (verboseIDE) println("[%s][%s]".format(projectName, msg)) override def forInteractive = true + override def forScaladoc = settings.isScaladoc /** A map of all loaded files to the rich compilation units that correspond to them. */ diff --git a/src/compiler/scala/tools/nsc/interactive/tests/core/PresentationCompilerInstance.scala b/src/compiler/scala/tools/nsc/interactive/tests/core/PresentationCompilerInstance.scala index 5c1837b3bf..ea2333a65b 100644 --- a/src/compiler/scala/tools/nsc/interactive/tests/core/PresentationCompilerInstance.scala +++ b/src/compiler/scala/tools/nsc/interactive/tests/core/PresentationCompilerInstance.scala @@ -8,6 +8,7 @@ import scala.reflect.internal.util.Position /** Trait encapsulating the creation of a presentation compiler's instance.*/ private[tests] trait PresentationCompilerInstance extends TestSettings { protected val settings = new Settings + protected def docSettings: doc.Settings = new doc.Settings(_ => ()) protected val compilerReporter: CompilerReporter = new InteractiveReporter { override def compiler = PresentationCompilerInstance.this.compiler } @@ -28,4 +29,4 @@ private[tests] trait PresentationCompilerInstance extends TestSettings { reporter.println("\tbootClassPath: %s".format(settings.bootclasspath.value)) reporter.println("\tverbose: %b".format(settings.verbose.value)) } -} \ No newline at end of file +} diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index f2d7adc628..4d320de173 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -48,6 +48,7 @@ trait Typers extends Modes with Adaptations with Tags { resetContexts() resetImplicits() transformed.clear() + clearDocComments() } object UnTyper extends Traverser { @@ -5138,8 +5139,8 @@ trait Typers extends Modes with Adaptations with Tags { } def typedDocDef(docdef: DocDef) = { - val comment = docdef.comment if (forScaladoc && (sym ne null) && (sym ne NoSymbol)) { + val comment = docdef.comment docComments(sym) = comment comment.defineVariables(sym) val typer1 = newTyper(context.makeNewScope(tree, context.owner)) diff --git a/src/partest/scala/tools/partest/ScaladocModelTest.scala b/src/partest/scala/tools/partest/ScaladocModelTest.scala index e7134d0271..b9abff69d8 100644 --- a/src/partest/scala/tools/partest/ScaladocModelTest.scala +++ b/src/partest/scala/tools/partest/ScaladocModelTest.scala @@ -12,7 +12,7 @@ import scala.tools.nsc.util.CommandLineParser import scala.tools.nsc.doc.{Settings, DocFactory, Universe} import scala.tools.nsc.doc.model._ import scala.tools.nsc.doc.model.diagram._ -import scala.tools.nsc.doc.model.comment._ +import scala.tools.nsc.doc.base.comment._ import scala.tools.nsc.reporters.ConsoleReporter /** A class for testing scaladoc model generation diff --git a/test/files/presentation/doc.check b/test/files/presentation/doc.check new file mode 100755 index 0000000000..62b3bfc2e3 --- /dev/null +++ b/test/files/presentation/doc.check @@ -0,0 +1,48 @@ +body:Body(List(Paragraph(Chain(List(Summary(Chain(List(Text(This is a test comment), Text(.)))), Text( +)))))) +@example:Body(List(Paragraph(Chain(List(Summary(Monospace(Text("abb".permutations = Iterator(abb, bab, bba))))))))) +@version: +@since: +@todo: +@note: +@see: +body:Body(List(Paragraph(Chain(List(Summary(Chain(List(Text(This is a test comment), Text(.)))), Text( +)))))) +@example:Body(List(Paragraph(Chain(List(Summary(Monospace(Text("abb".permutations = Iterator(abb, bab, bba))))))))) +@version:Body(List(Paragraph(Chain(List(Summary(Chain(List(Text(1), Text(.)))), Text(0, 09/07/2012)))))) +@since: +@todo: +@note: +@see: +body:Body(List(Paragraph(Chain(List(Summary(Chain(List(Text(This is a test comment), Text(.)))), Text( +)))))) +@example:Body(List(Paragraph(Chain(List(Summary(Monospace(Text("abb".permutations = Iterator(abb, bab, bba))))))))) +@version:Body(List(Paragraph(Chain(List(Summary(Chain(List(Text(1), Text(.)))), Text(0, 09/07/2012)))))) +@since:Body(List(Paragraph(Chain(List(Summary(Chain(List(Text(2), Text(.)))), Text(10)))))) +@todo: +@note: +@see: +body:Body(List(Paragraph(Chain(List(Summary(Chain(List(Text(This is a test comment), Text(.)))), Text( +)))))) +@example:Body(List(Paragraph(Chain(List(Summary(Monospace(Text("abb".permutations = Iterator(abb, bab, bba))))))))) +@version:Body(List(Paragraph(Chain(List(Summary(Chain(List(Text(1), Text(.)))), Text(0, 09/07/2012)))))) +@since:Body(List(Paragraph(Chain(List(Summary(Chain(List(Text(2), Text(.)))), Text(10)))))) +@todo:Body(List(Paragraph(Chain(List(Summary(Text(this method is unsafe))))))) +@note: +@see: +body:Body(List(Paragraph(Chain(List(Summary(Chain(List(Text(This is a test comment), Text(.)))), Text( +)))))) +@example:Body(List(Paragraph(Chain(List(Summary(Monospace(Text("abb".permutations = Iterator(abb, bab, bba))))))))) +@version:Body(List(Paragraph(Chain(List(Summary(Chain(List(Text(1), Text(.)))), Text(0, 09/07/2012)))))) +@since:Body(List(Paragraph(Chain(List(Summary(Chain(List(Text(2), Text(.)))), Text(10)))))) +@todo:Body(List(Paragraph(Chain(List(Summary(Text(this method is unsafe))))))) +@note:Body(List(Paragraph(Chain(List(Summary(Text(Don't inherit!))))))) +@see: +body:Body(List(Paragraph(Chain(List(Summary(Chain(List(Text(This is a test comment), Text(.)))), Text( +)))))) +@example:Body(List(Paragraph(Chain(List(Summary(Monospace(Text("abb".permutations = Iterator(abb, bab, bba))))))))) +@version:Body(List(Paragraph(Chain(List(Summary(Chain(List(Text(1), Text(.)))), Text(0, 09/07/2012)))))) +@since:Body(List(Paragraph(Chain(List(Summary(Chain(List(Text(2), Text(.)))), Text(10)))))) +@todo:Body(List(Paragraph(Chain(List(Summary(Text(this method is unsafe))))))) +@note:Body(List(Paragraph(Chain(List(Summary(Text(Don't inherit!))))))) +@see:Body(List(Paragraph(Chain(List(Summary(Text(some other method))))))) diff --git a/test/files/presentation/doc.scala b/test/files/presentation/doc.scala new file mode 100755 index 0000000000..4b0d6baa1f --- /dev/null +++ b/test/files/presentation/doc.scala @@ -0,0 +1,71 @@ +import scala.tools.nsc.doc +import scala.tools.nsc.doc.base.LinkTo +import scala.tools.nsc.doc.base.comment._ +import scala.tools.nsc.interactive._ +import scala.tools.nsc.interactive.tests._ +import scala.tools.nsc.util._ +import scala.tools.nsc.io._ + +object Test extends InteractiveTest { + override val settings: doc.Settings = docSettings + + val tags = Seq( + "@example `\"abb\".permutations = Iterator(abb, bab, bba)`", + "@version 1.0, 09/07/2012", + "@since 2.10", + "@todo this method is unsafe", + "@note Don't inherit!", + "@see some other method" + ) + + val comment = "This is a test comment." + val caret = "" + + def text(nTags: Int) = + """|/** %s + | + | * %s */ + |trait Commented {} + |class User(c: %sCommented)""".stripMargin.format(comment, tags take nTags mkString "\n", caret) + + override def main(args: Array[String]) { + val documenter = new Doc(settings) { + val global: compiler.type = compiler + + def chooseLink(links: List[LinkTo]): LinkTo = links.head + } + for (i <- 1 to tags.length) { + val markedText = text(i) + val idx = markedText.indexOf(caret) + val fileText = markedText.substring(0, idx) + markedText.substring(idx + caret.length) + val source = sourceFiles(0) + val batch = new BatchSourceFile(source.file, fileText.toCharArray) + val reloadResponse = new Response[Unit] + compiler.askReload(List(batch), reloadResponse) + reloadResponse.get.left.toOption match { + case None => + reporter.println("Couldn't reload") + case Some(_) => + val treeResponse = new compiler.Response[compiler.Tree] + val pos = compiler.rangePos(batch, idx, idx, idx) + compiler.askTypeAt(pos, treeResponse) + treeResponse.get.left.toOption match { + case Some(tree) => + val sym = tree.tpe.typeSymbol + documenter.retrieve(sym, sym.owner) match { + case Some(HtmlResult(comment)) => + import comment._ + val tags: List[(String, Iterable[Body])] = + List(("@example", example), ("@version", version), ("@since", since.toList), ("@todo", todo), ("@note", note), ("@see", see)) + val str = ("body:" + body + "\n") + + tags.map{ case (name, bodies) => name + ":" + bodies.mkString("\n") }.mkString("\n") + reporter.println(str) + case Some(_) => reporter.println("Got unexpected result") + case None => reporter.println("Got no result") + } + case None => reporter.println("Couldn't find a typedTree") + } + } + } + } +} diff --git a/test/files/presentation/doc/src/Test.scala b/test/files/presentation/doc/src/Test.scala new file mode 100755 index 0000000000..fcc1554994 --- /dev/null +++ b/test/files/presentation/doc/src/Test.scala @@ -0,0 +1 @@ +object Test \ No newline at end of file diff --git a/test/files/presentation/memory-leaks/MemoryLeaksTest.scala b/test/files/presentation/memory-leaks/MemoryLeaksTest.scala index a5533a623a..159097cc10 100644 --- a/test/files/presentation/memory-leaks/MemoryLeaksTest.scala +++ b/test/files/presentation/memory-leaks/MemoryLeaksTest.scala @@ -5,6 +5,7 @@ import java.util.Calendar import scala.tools.nsc.interactive.tests._ import scala.tools.nsc.util._ import scala.tools.nsc.io._ +import scala.tools.nsc.doc /** This test runs the presentation compiler on the Scala compiler project itself and records memory consumption. * @@ -24,6 +25,8 @@ import scala.tools.nsc.io._ object Test extends InteractiveTest { final val mega = 1024 * 1024 + override val settings: doc.Settings = docSettings + override def execute(): Unit = memoryConsumptionTest() def batchSource(name: String) = @@ -120,4 +123,4 @@ object Test extends InteractiveTest { r.totalMemory() - r.freeMemory() } -} \ No newline at end of file +} diff --git a/test/scaladoc/run/SI-191-deprecated.scala b/test/scaladoc/run/SI-191-deprecated.scala index 746aa9c598..4ed24ff8d1 100755 --- a/test/scaladoc/run/SI-191-deprecated.scala +++ b/test/scaladoc/run/SI-191-deprecated.scala @@ -1,5 +1,6 @@ import scala.tools.nsc.doc.model._ -import scala.tools.nsc.doc.model.comment._ +import scala.tools.nsc.doc.base._ +import scala.tools.nsc.doc.base.comment._ import scala.tools.partest.ScaladocModelTest import java.net.{URI, URL} import java.io.File diff --git a/test/scaladoc/run/SI-191.scala b/test/scaladoc/run/SI-191.scala index 0fb28145c3..6fb5339d66 100755 --- a/test/scaladoc/run/SI-191.scala +++ b/test/scaladoc/run/SI-191.scala @@ -1,5 +1,6 @@ import scala.tools.nsc.doc.model._ -import scala.tools.nsc.doc.model.comment._ +import scala.tools.nsc.doc.base._ +import scala.tools.nsc.doc.base.comment._ import scala.tools.partest.ScaladocModelTest import java.net.{URI, URL} import java.io.File diff --git a/test/scaladoc/run/SI-3314.scala b/test/scaladoc/run/SI-3314.scala index fe220b08af..c856fe46a8 100644 --- a/test/scaladoc/run/SI-3314.scala +++ b/test/scaladoc/run/SI-3314.scala @@ -1,3 +1,4 @@ +import scala.tools.nsc.doc.base._ import scala.tools.nsc.doc.model._ import scala.tools.partest.ScaladocModelTest @@ -88,4 +89,4 @@ object Test extends ScaladocModelTest { assert(bar.valueParams(0)(0).resultType.name == "(AnyRef { type Lambda[X] <: Either[A,X] })#Lambda[String]", bar.valueParams(0)(0).resultType.name + " == (AnyRef { type Lambda[X] <: Either[A,X] })#Lambda[String]") } -} \ No newline at end of file +} diff --git a/test/scaladoc/run/SI-5235.scala b/test/scaladoc/run/SI-5235.scala index 6295fc7786..c6b7922bb8 100644 --- a/test/scaladoc/run/SI-5235.scala +++ b/test/scaladoc/run/SI-5235.scala @@ -1,3 +1,4 @@ +import scala.tools.nsc.doc.base._ import scala.tools.nsc.doc.model._ import scala.tools.nsc.doc.model.diagram._ import scala.tools.partest.ScaladocModelTest @@ -84,4 +85,4 @@ object Test extends ScaladocModelTest { assert(mcReverseType.refEntity(0)._1 == LinkToTpl(MyCollection), mcReverse.qualifiedName + "'s return type has a link to " + MyCollection.qualifiedName) } -} \ No newline at end of file +} diff --git a/test/scaladoc/run/links.scala b/test/scaladoc/run/links.scala index 0c67857e1c..fde24edb2a 100644 --- a/test/scaladoc/run/links.scala +++ b/test/scaladoc/run/links.scala @@ -1,3 +1,4 @@ +import scala.tools.nsc.doc.base._ import scala.tools.nsc.doc.model._ import scala.tools.partest.ScaladocModelTest @@ -23,9 +24,9 @@ object Test extends ScaladocModelTest { val base = rootPackage._package("scala")._package("test")._package("scaladoc")._package("links") val TEST = base._object("TEST") - val memberLinks = countLinks(TEST.comment.get, _.link.isInstanceOf[LinkToMember]) - val templateLinks = countLinks(TEST.comment.get, _.link.isInstanceOf[LinkToTpl]) + val memberLinks = countLinks(TEST.comment.get, _.link.isInstanceOf[LinkToMember[_, _]]) + val templateLinks = countLinks(TEST.comment.get, _.link.isInstanceOf[LinkToTpl[_]]) assert(memberLinks == 17, memberLinks + " == 17 (the member links in object TEST)") assert(templateLinks == 6, templateLinks + " == 6 (the template links in object TEST)") } -} \ No newline at end of file +} diff --git a/test/scaladoc/scalacheck/CommentFactoryTest.scala b/test/scaladoc/scalacheck/CommentFactoryTest.scala index 5e3141bdc0..96174d29d1 100644 --- a/test/scaladoc/scalacheck/CommentFactoryTest.scala +++ b/test/scaladoc/scalacheck/CommentFactoryTest.scala @@ -3,8 +3,7 @@ import org.scalacheck.Prop._ import scala.tools.nsc.Global import scala.tools.nsc.doc -import scala.tools.nsc.doc.model._ -import scala.tools.nsc.doc.model.comment._ +import scala.tools.nsc.doc.base.comment._ import scala.tools.nsc.doc.model._ import scala.tools.nsc.doc.model.diagram._ -- cgit v1.2.3