From bb7c4bf3da80d6459247208cfd84fa5996aade6e Mon Sep 17 00:00:00 2001 From: Vlad Ureche Date: Mon, 19 Dec 2011 21:19:44 +0100 Subject: [PATCH 1/5] More general framework for testing --- .../scaladoc/scala/html/HtmlFactoryTest.scala | 73 +++++++++++-------- 1 file changed, 41 insertions(+), 32 deletions(-) diff --git a/test/scaladoc/scala/html/HtmlFactoryTest.scala b/test/scaladoc/scala/html/HtmlFactoryTest.scala index 5b17affbf0d0..271a26d8ef39 100644 --- a/test/scaladoc/scala/html/HtmlFactoryTest.scala +++ b/test/scaladoc/scala/html/HtmlFactoryTest.scala @@ -84,12 +84,7 @@ object Test extends Properties("HtmlFactory") { val html = scala.stripSuffix(".scala") + ".html" createTemplates(scala)(html) } - - /** - * See checkTextOnly(scalaFile: String, checks: List[String]) - */ - def checkText1(scalaFile: String, check: String, debug: Boolean = true): Boolean = checkText(scalaFile, List(check), debug) - + /** * This tests the text without the markup - ex: * @@ -111,20 +106,31 @@ object Test extends Properties("HtmlFactory") { * * NOTE: Comparison is done ignoring all whitespace */ - def checkText(scalaFile: String, checks: List[String], debug: Boolean = true): Boolean = { + def checkText(scalaFile: String, debug: Boolean = true)(checks: (Option[String], String, Boolean)*): Boolean = { val htmlFile = scalaFile.stripSuffix(".scala") + ".html" - val htmlText = createTemplates(scalaFile)(htmlFile).text.replace('→',' ').replaceAll("\\s+","") + val htmlAllFiles = createTemplates(scalaFile) var result = true - for (check <- checks) { - val checkText = check.replace('→',' ').replaceAll("\\s+","") - val checkValue = htmlText.contains(checkText) - if (debug && (!checkValue)) { - Console.err.println("Check failed: ") - Console.err.println("HTML: " + htmlText) - Console.err.println("Check: " + checkText) - } - result &&= checkValue + for ((file, check, expected) <- checks) { + // resolve the file to be checked + val fileName = file match { + case Some(file) => + if (file endsWith ".html") + file + else + file + ".html" + case None => + htmlFile + } + val fileText = htmlAllFiles(htmlFile).text.replace('→',' ').replaceAll("\\s+","") + val checkText = check.replace('→',' ').replaceAll("\\s+","") + val checkValue = fileText.contains(checkText) == expected + if (debug && (!checkValue)) { + Console.err.println("Check failed: ") + Console.err.println("HTML: " + fileText) + Console.err.println("Check: " + checkText) + } + result &&= checkValue } result @@ -428,37 +434,40 @@ object Test extends Properties("HtmlFactory") { } property("Use cases should override their original members") = - checkText1("SI_5054_q1.scala", """def test(): Int""") && - !checkText1("SI_5054_q1.scala", """def test(implicit lost: Int): Int""") - + checkText("SI_5054_q1.scala")( + (None,"""def test(): Int""", true), + (None,"""def test(implicit lost: Int): Int""", true) + ) property("Use cases should keep their flags - final should not be lost") = - checkText1("SI_5054_q2.scala", """final def test(): Int""") + checkText("SI_5054_q2.scala")((None, """final def test(): Int""", true)) property("Use cases should keep their flags - implicit should not be lost") = - checkText1("SI_5054_q3.scala", """implicit def test(): Int""") + checkText("SI_5054_q3.scala")((None, """implicit def test(): Int""", true)) property("Use cases should keep their flags - real abstract should not be lost") = - checkText1("SI_5054_q4.scala", """abstract def test(): Int""") + checkText("SI_5054_q4.scala")((None, """abstract def test(): Int""", true)) property("Use cases should keep their flags - traits should not be affected") = - checkText1("SI_5054_q5.scala", """def test(): Int""") + checkText("SI_5054_q5.scala")((None, """def test(): Int""", true)) property("Use cases should keep their flags - traits should not be affected") = - checkText1("SI_5054_q6.scala", """abstract def test(): Int""") + checkText("SI_5054_q6.scala")((None, """abstract def test(): Int""", true)) property("Use case individual signature test") = - checkText("SI_5054_q7.scala", List( - """abstract def test2(explicit: Int): Int [use case] This takes the explicit value passed.""", - """abstract def test1(): Int [use case] This takes the implicit value in scope.""")) + checkText("SI_5054_q7.scala")( + (None, """abstract def test2(explicit: Int): Int [use case] This takes the explicit value passed.""", true), + (None, """abstract def test1(): Int [use case] This takes the implicit value in scope.""", true) + ) property("Display correct \"Definition classes\"") = - checkText1("SI_5287.scala", - """def method(): Int + checkText("SI_5287.scala")( + (None, + """def method(): Int [use case] The usecase explanation [use case] The usecase explanation - Definition Classes SI_5287 SI_5287_B SI_5287_A""", debug=true) - // explanation appears twice, as small comment and full comment + Definition Classes SI_5287 SI_5287_B SI_5287_A""", true) + ) // the explanation appears twice, as small comment and full comment { val files = createTemplates("basic.scala") From 9001ead879da2f6e1e1f213f37645da472e17e6d Mon Sep 17 00:00:00 2001 From: Vlad Ureche Date: Mon, 19 Dec 2011 22:59:18 +0100 Subject: [PATCH 2/5] Fixed usecase param/tparam/return overriding --- .../scala/tools/nsc/util/DocStrings.scala | 26 +++- .../scaladoc/scala/html/HtmlFactoryTest.scala | 123 +++++++++++++++++- 2 files changed, 142 insertions(+), 7 deletions(-) diff --git a/src/compiler/scala/tools/nsc/util/DocStrings.scala b/src/compiler/scala/tools/nsc/util/DocStrings.scala index 1db6c38b4d74..2c8b77be71eb 100755 --- a/src/compiler/scala/tools/nsc/util/DocStrings.scala +++ b/src/compiler/scala/tools/nsc/util/DocStrings.scala @@ -71,13 +71,35 @@ object DocStrings { * Every section starts with a `@` and extends to the next `@`, or * to the end of the comment string, but excluding the final two * characters which terminate the comment. + * + * Also take usecases into account - they need to expand until the next + * @usecase or the end of the string, as they might include other sections + * of their own */ def tagIndex(str: String, p: Int => Boolean = (idx => true)): List[(Int, Int)] = findAll(str, 0) (idx => str(idx) == '@' && p(idx)) match { case List() => List() - case idxs => idxs zip (idxs.tail ::: List(str.length - 2)) + case idxs => { + val idxs2 = mergeUsecaseSections(str, idxs) + idxs2 zip (idxs2.tail ::: List(str.length - 2)) + } } - + + /** + * Merge sections following an @usecase into the usecase comment, so they + * can override the parent symbol's sections + */ + def mergeUsecaseSections(str: String, idxs: List[Int]): List[Int] = { + idxs.find(str.substring(_).startsWith("@usecase")) match { + case Some(firstUC) => + val commentSections = idxs.take(idxs.indexOf(firstUC)) + val usecaseSections = idxs.drop(idxs.indexOf(firstUC)).filter(str.substring(_).startsWith("@usecase")) + commentSections ::: usecaseSections + case None => + idxs + } + } + /** Does interval `iv` start with given `tag`? */ def startsWithTag(str: String, section: (Int, Int), tag: String): Boolean = diff --git a/test/scaladoc/scala/html/HtmlFactoryTest.scala b/test/scaladoc/scala/html/HtmlFactoryTest.scala index 271a26d8ef39..c2c127e76572 100644 --- a/test/scaladoc/scala/html/HtmlFactoryTest.scala +++ b/test/scaladoc/scala/html/HtmlFactoryTest.scala @@ -111,9 +111,9 @@ object Test extends Properties("HtmlFactory") { val htmlAllFiles = createTemplates(scalaFile) var result = true - for ((file, check, expected) <- checks) { + for ((fileHint, check, expected) <- checks) { // resolve the file to be checked - val fileName = file match { + val fileName = fileHint match { case Some(file) => if (file endsWith ".html") file @@ -122,7 +122,7 @@ object Test extends Properties("HtmlFactory") { case None => htmlFile } - val fileText = htmlAllFiles(htmlFile).text.replace('→',' ').replaceAll("\\s+","") + val fileText = htmlAllFiles(fileName).text.replace('→',' ').replaceAll("\\s+","") val checkText = check.replace('→',' ').replaceAll("\\s+","") val checkValue = fileText.contains(checkText) == expected if (debug && (!checkValue)) { @@ -436,7 +436,7 @@ object Test extends Properties("HtmlFactory") { property("Use cases should override their original members") = checkText("SI_5054_q1.scala")( (None,"""def test(): Int""", true), - (None,"""def test(implicit lost: Int): Int""", true) + (None,"""def test(implicit lost: Int): Int""", false) ) property("Use cases should keep their flags - final should not be lost") = @@ -444,7 +444,7 @@ object Test extends Properties("HtmlFactory") { property("Use cases should keep their flags - implicit should not be lost") = checkText("SI_5054_q3.scala")((None, """implicit def test(): Int""", true)) - + property("Use cases should keep their flags - real abstract should not be lost") = checkText("SI_5054_q4.scala")((None, """abstract def test(): Int""", true)) @@ -469,6 +469,119 @@ object Test extends Properties("HtmlFactory") { Definition Classes SI_5287 SI_5287_B SI_5287_A""", true) ) // the explanation appears twice, as small comment and full comment + + property("Correct comment inheritance for overriding") = + checkText("implicit-inheritance-override.scala")( + (Some("Base"), + """def function[T](arg1: T, arg2: String): Double + The base comment. + The base comment. And another sentence... + T the type of the first argument + arg1 The T term comment + arg2 The string comment + returns The return comment + """, true), + (Some("DerivedA"), + """def function[T](arg1: T, arg2: String): Double + Overriding the comment, the params and returns comments should stay the same. + Overriding the comment, the params and returns comments should stay the same. + T the type of the first argument + arg1 The T term comment + arg2 The string comment + returns The return comment + """, true), + (Some("DerivedB"), + """def function[T](arg1: T, arg2: String): Double + T the type of the first argument + arg1 The overridden T term comment + arg2 The overridden string comment + returns The return comment + """, true), + (Some("DerivedC"), + """def function[T](arg1: T, arg2: String): Double + T the type of the first argument + arg1 The T term comment + arg2 The string comment + returns The overridden return comment + """, true), + (Some("DerivedD"), + """def function[T](arg1: T, arg2: String): Double + T The overriden type parameter comment + arg1 The T term comment + arg2 The string comment + returns The return comment + """, true) + ) + + for (useCaseFile <- List("UseCaseInheritance", "UseCaseOverrideInheritance")) { + property("Correct comment inheritance for usecases") = + checkText("implicit-inheritance-usecase.scala")( + (Some(useCaseFile), + """def missing_arg[T](arg1: T): Double + [use case] + [use case] + T The type parameter + arg1 The T term comment + returns The return comment + """, true), + (Some(useCaseFile), + """def missing_targ(arg1: Int, arg2: String): Double + [use case] + [use case] + arg1 The T term comment + arg2 The string comment + returns The return comment + """, true), + (Some(useCaseFile), + """def overridden_arg1[T](implicit arg1: T, arg2: String): Double + [use case] + [use case] + T The type parameter + arg1 The overridden T term comment + arg2 The string comment + returns The return comment + """, true), + (Some(useCaseFile), + """def overridden_targ[T](implicit arg1: T, arg2: String): Double + [use case] + [use case] + T The overridden type parameter comment + arg1 The T term comment + arg2 The string comment + returns The return comment + """, true), + (Some(useCaseFile), + """def overridden_return[T](implicit arg1: T, arg2: String): Double + [use case] + [use case] + T The type parameter + arg1 The T term comment + arg2 The string comment + returns The overridden return comment + """, true), + (Some(useCaseFile), + """def added_arg[T](implicit arg1: T, arg2: String, arg3: Float): Double + [use case] + [use case] + T The type parameter + arg1 The T term comment + arg2 The string comment + arg3 The added float comment + returns The return comment + """, true), + (Some(useCaseFile), + """def overridden_comment[T](implicit arg1: T, arg2: String): Double + [use case] The overridden comment. + [use case] The overridden comment. + T The type parameter + arg1 The T term comment + arg2 The string comment + returns The return comment + """, true) + ) + } + + { val files = createTemplates("basic.scala") //println(files) From 72ff9b71b92d4e883a5e2becebed27c9a72eb8fa Mon Sep 17 00:00:00 2001 From: Vlad Ureche Date: Tue, 20 Dec 2011 22:55:15 +0100 Subject: [PATCH 3/5] First stab at implementing @inheritdoc After all, not sure this is the best place to put it --- .../scala/tools/nsc/ast/DocComments.scala | 95 +++++++++++++++++-- .../scala/tools/nsc/util/DocStrings.scala | 59 +++++++++++- .../explicit-inheritance-override.scala | 48 ++++++++++ .../explicit-inheritance-usecase.scala | 47 +++++++++ 4 files changed, 241 insertions(+), 8 deletions(-) create mode 100644 test/scaladoc/resources/explicit-inheritance-override.scala create mode 100644 test/scaladoc/resources/explicit-inheritance-usecase.scala diff --git a/src/compiler/scala/tools/nsc/ast/DocComments.scala b/src/compiler/scala/tools/nsc/ast/DocComments.scala index f9c818daf084..65b07ab05c2f 100755 --- a/src/compiler/scala/tools/nsc/ast/DocComments.scala +++ b/src/compiler/scala/tools/nsc/ast/DocComments.scala @@ -60,10 +60,13 @@ trait DocComments { self: Global => else DocComment(docStr).template 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 case Some(sc) => if (ownComment == "") sc - else merge(sc, ownComment, sym) + else expandInheritdoc(sc, merge(sc, ownComment, sym), sym) } } @@ -100,9 +103,10 @@ trait DocComments { self: Global => def useCases(sym: Symbol, site: Symbol): List[(Symbol, String, Position)] = { def getUseCases(dc: DocComment) = { for (uc <- dc.useCases; defn <- uc.expandedDefs(sym, site)) yield - (defn, - expandVariables(merge(cookedDocComment(sym), uc.comment.raw, defn), sym, site), - uc.pos) + (defn, { + val symComment = cookedDocComment(sym) + expandVariables(expandInheritdoc(symComment, merge(symComment, uc.comment.raw, defn), sym), sym, site) + }, uc.pos) } getDocComment(sym) map getUseCases getOrElse List() } @@ -164,7 +168,7 @@ trait DocComments { self: Global => val out = new StringBuilder var copied = 0 var tocopy = startTag(dst, dstSections dropWhile (!isMovable(dst, _))) - + if (copyFirstPara) { val eop = // end of comment body (first para), which is delimited by blank line, or tag, or end of comment (findNext(src, 0)(src.charAt(_) == '\n')) min startTag(src, srcSections) @@ -200,7 +204,84 @@ trait DocComments { self: Global => out.toString } } - + + /** + * Expand @inheritdoc tags + * - for the main comment we transform the @inheritdoc into $super, and the variable expansion can expand it furhter + * - for the @param, @tparam and @throws sections we must replace comments on the spot + * + * This is done separately, for two reasons: + * 1. It takes longer to run compared to merge + * 2. The @inheritdoc annotation should not be used very often + */ + def expandInheritdoc(src: String, dst: String, sym: Symbol): String = + if (dst.indexOf("@inheritdoc") == -1) + dst + else { + val srcSections = tagIndex(src) + val dstSections = tagIndex(dst) + val srcParams = paramDocs(src, "@param", srcSections) + val srcTParams = paramDocs(src, "@tparam", srcSections) + val srcThrows = paramDocs(src, "@throws", srcSections) + val srcTagMap = sectionTagMap(src, srcSections) + + val out = new StringBuilder + + def replaceInheritdoc(src: String, dst: String) = + if (dst.indexOf("@inheritdoc") == -1) + dst + else + dst.replaceAllLiterally("@inheritdoc", src) + + def getSourceSection(section: (Int, Int)): String = { + + def getSectionHeader = extractSectionTag(dst, section) match { + case "@param" => "@param " + extractSectionParam(dst, section) + case "@tparam" => "@tparam " + extractSectionParam(dst, section) + case "@throws" => "@throws " + extractSectionParam(dst, section) + case other => other + } + + def sectionString(param: String, paramMap: Map[String, (Int, Int)]): String = + paramMap.get(param) match { + case Some(section) => + // Cleanup the section tag and parameter + val sectionTextBounds = extractSectionText(src, section) + cleanupSectionText(src.substring(sectionTextBounds._1, sectionTextBounds._2)) + case None => + reporter.info(sym.pos, "The \"" + getSectionHeader + "\" annotation of the " + sym + + " comment contains @inheritdoc, but the corresponding section in the parent is not defined.", true) + "[...]" + } + + if (dst.substring(section._1).startsWith("@param")) + sectionString(extractSectionParam(dst, section), srcParams) + else if (dst.substring(section._1).startsWith("@tparam")) + sectionString(extractSectionParam(dst, section), srcTParams) + else if (dst.substring(section._1).startsWith("@throws")) + sectionString(extractSectionParam(dst, section), srcThrows) + else + sectionString(extractSectionTag(dst, section), srcTagMap) + } + + def mainComment(str: String, sections: List[(Int, Int)]): String = + if (str.trim.length > 3) + str.trim.substring(3, startTag(str, sections)) + else + "" + + // Append main comment + out.append("/**") + out.append(replaceInheritdoc(mainComment(src, srcSections), mainComment(dst, dstSections))) + + // Append sections + for (section <- dstSections) + out.append(replaceInheritdoc(getSourceSection(section), dst.substring(section._1, section._2))) + + out.append("*/") + out.toString + } + /** Maps symbols to the variable -> replacement maps that are defined * in their doc comments */ @@ -295,7 +376,7 @@ trait DocComments { self: Global => */ lazy val (template, defines, useCases) = { val sections = tagIndex(raw) - + val defines = sections filter { startsWithTag(raw, _, "@define") } val usecases = sections filter { startsWithTag(raw, _, "@usecase") } diff --git a/src/compiler/scala/tools/nsc/util/DocStrings.scala b/src/compiler/scala/tools/nsc/util/DocStrings.scala index 2c8b77be71eb..40e6b2250e55 100755 --- a/src/compiler/scala/tools/nsc/util/DocStrings.scala +++ b/src/compiler/scala/tools/nsc/util/DocStrings.scala @@ -26,6 +26,14 @@ object DocStrings { if (start < str.length && isIdentifierPart(str charAt start)) skipIdent(str, start + 1) else start + /** Returns index of string `str` following `start` skipping + * sequence of identifier characters. + */ + def skipTag(str: String, start: Int): Int = + if (start < str.length && (str charAt start) == '@') skipIdent(str, start + 1) + else start + + /** Returns index of string `str` after `start` skipping longest * sequence of space and tab characters, possibly also containing * a single `*` character or the `/``**` sequence. @@ -81,7 +89,8 @@ object DocStrings { case List() => List() case idxs => { val idxs2 = mergeUsecaseSections(str, idxs) - idxs2 zip (idxs2.tail ::: List(str.length - 2)) + val idxs3 = mergeInheritdocSections(str, idxs2) + idxs3 zip (idxs3.tail ::: List(str.length - 2)) } } @@ -100,6 +109,12 @@ object DocStrings { } } + /** + * Merge the @inheritdoc sections, as they never make sense on their own + */ + def mergeInheritdocSections(str: String, idxs: List[Int]): List[Int] = + idxs.filterNot(str.substring(_).startsWith("@inheritdoc")) + /** Does interval `iv` start with given `tag`? */ def startsWithTag(str: String, section: (Int, Int), tag: String): Boolean = @@ -155,4 +170,46 @@ object DocStrings { idx } } + + /** A map from the section tag to section parameters */ + def sectionTagMap(str: String, sections: List[(Int, Int)]): Map[String, (Int, Int)] = + Map() ++ { + for (section <- sections) yield + extractSectionTag(str, section) -> section + } + + /** Extract the section tag, treating the section tag as an indentifier */ + def extractSectionTag(str: String, section: (Int, Int)): String = + str.substring(section._1, skipTag(str, section._1)) + + /** Extract the section parameter */ + def extractSectionParam(str: String, section: (Int, Int)): String = { + assert(str.substring(section._1).startsWith("@param") || + str.substring(section._1).startsWith("@tparam") || + str.substring(section._1).startsWith("@throws")) + + val start = skipWhitespace(str, skipTag(str, section._1)) + val finish = skipIdent(str, start) + + str.substring(start, finish) + } + + /** Extract the section text, except for the tag and comment newlines */ + def extractSectionText(str: String, section: (Int, Int)): (Int, Int) = { + if (str.substring(section._1).startsWith("@param") || + str.substring(section._1).startsWith("@tparam") || + str.substring(section._1).startsWith("@throws")) + (skipWhitespace(str, skipIdent(str, skipWhitespace(str, skipTag(str, section._1)))), section._2) + else + (skipWhitespace(str, skipTag(str, section._1)), section._2) + } + + /** Cleanup section text */ + def cleanupSectionText(str: String) = { + var result = str.trim.replaceAll("\n\\s+\\*\\s+", " \n") + while (result.endsWith("\n")) + result = result.substring(0, str.length - 1) + result + } + } diff --git a/test/scaladoc/resources/explicit-inheritance-override.scala b/test/scaladoc/resources/explicit-inheritance-override.scala new file mode 100644 index 000000000000..c3ccd05b9323 --- /dev/null +++ b/test/scaladoc/resources/explicit-inheritance-override.scala @@ -0,0 +1,48 @@ +// This tests the implicit comment inheritance capabilities of scaladoc for class inheritance (no $super, no @inheritdoc) +class InheritDocBase { + /** + * The base comment. And another sentence... + * + * @param arg1 The T term comment + * @param arg2 The string comment + * @tparam T the type of the first argument + * @throws SomeException if the function is not called with correct parameters + * @return The return comment + * @see The Manual + * @note Be careful! + * @example function[Int](3, "something") + * @author a Scala developer + * @version 0.0.2 + * @since 0.0.1 + * @todo Call mom. + */ + def function[T](arg1: T, arg2: String): Double = 0.0d +} + +class InheritDocDerived extends InheritDocBase { + /** + * Starting line + * + * @inheritdoc + * @inheritdoc + * + * Ending line + * + * @param arg1 Start1 @inheritdoc End1 + * @param arg2 Start2 @inheritdoc End2 + * @param arg3 Start3 ShouldWarn @inheritdoc End3 + * @tparam T StartT @inheritdoc EndT + * @tparam ShouldWarn StartSW @inheritdoc EndSW + * @throws SomeException StartEx @inheritdoc EndEx + * @throws SomeOtherException StartSOE Should Warn @inheritdoc EndSOE + * @return StartRet @inheritdoc EndRet + * @see StartSee @inheritdoc EndSee + * @note StartNote @inheritdoc EndNote + * @example StartExample @inheritdoc EndExample + * @author StartAuthor @inheritdoc EndAuthor + * @version StartVer @inheritdoc EndVer + * @since StartSince @inheritdoc EndSince + * @todo StartTodo @inheritdoc And dad! EndTodo + */ + override def function[T](arg1: T, arg2: String): Double = 1.0d +} \ No newline at end of file diff --git a/test/scaladoc/resources/explicit-inheritance-usecase.scala b/test/scaladoc/resources/explicit-inheritance-usecase.scala new file mode 100644 index 000000000000..7b5c1211ab17 --- /dev/null +++ b/test/scaladoc/resources/explicit-inheritance-usecase.scala @@ -0,0 +1,47 @@ +// This tests the implicit comment inheritance capabilities of scaladoc for usecases (no $super, no @inheritdoc) +/** Testing use case inheritance */ +class UseCaseInheritDoc { + /** + * The base comment. And another sentence... + * + * @param arg1 The T term comment + * @param arg2 The string comment + * @tparam T the type of the first argument + * @throws SomeException if the function is not called with correct parameters + * @return The return comment + * @see The Manual + * @note Be careful! + * @example function[Int](3, "something") + * @author a Scala developer + * @version 0.0.2 + * @since 0.0.1 + * @todo Call mom. + * + * @usecase def function[T](arg1: T, arg2: String): Double + * + * Starting line + * + * @inheritdoc + * @inheritdoc + * + * Ending line + * + * @param arg1 Start1 @inheritdoc End1 + * @param arg2 Start2 @inheritdoc End2 + * @param arg3 Start3 ShouldWarn @inheritdoc End3 + * @tparam T StartT @inheritdoc EndT + * @tparam ShouldWarn StartSW @inheritdoc EndSW + * @throws SomeException StartEx @inheritdoc EndEx + * @throws SomeOtherException StartSOE Should Warn @inheritdoc EndSOE + * @return StartRet @inheritdoc EndRet + * @see StartSee @inheritdoc EndSee + * @note StartNote @inheritdoc EndNote + * @example StartExample @inheritdoc EndExample + * @author StartAuthor @inheritdoc EndAuthor + * @version StartVer @inheritdoc EndVer + * @since StartSince @inheritdoc EndSince + * @todo StartTodo @inheritdoc And dad! EndTodo + */ + def function[T](implicit arg1: T, arg2: String): Double = 0.0d +} + From c3d218b73f9fd1299bd59c64849110159be08393 Mon Sep 17 00:00:00 2001 From: Vlad Ureche Date: Tue, 20 Dec 2011 22:56:15 +0100 Subject: [PATCH 4/5] Forgot the testcases --- .../implicit-inheritance-override.scala | 41 +++++++++++++ .../implicit-inheritance-usecase.scala | 57 +++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 test/scaladoc/resources/implicit-inheritance-override.scala create mode 100644 test/scaladoc/resources/implicit-inheritance-usecase.scala diff --git a/test/scaladoc/resources/implicit-inheritance-override.scala b/test/scaladoc/resources/implicit-inheritance-override.scala new file mode 100644 index 000000000000..85b8e8d543eb --- /dev/null +++ b/test/scaladoc/resources/implicit-inheritance-override.scala @@ -0,0 +1,41 @@ +// This tests the implicit comment inheritance capabilities of scaladoc for class inheritance (no $super, no @inheritdoc) +class Base { + /** + * The base comment. And another sentence... + * + * @param arg1 The T term comment + * @param arg2 The string comment + * @tparam T the type of the first argument + * @return The return comment + */ + def function[T](arg1: T, arg2: String): Double = 0.0d +} + +class DerivedA extends Base { + /** + * Overriding the comment, the params and returns comments should stay the same. + */ + override def function[T](arg1: T, arg2: String): Double = 1.0d +} + +class DerivedB extends Base { + /** + * @param arg1 The overridden T term comment + * @param arg2 The overridden string comment + */ + override def function[T](arg1: T, arg2: String): Double = 2.0d +} + +class DerivedC extends Base { + /** + * @return The overridden return comment + */ + override def function[T](arg1: T, arg2: String): Double = 3.0d +} + +class DerivedD extends Base { + /** + * @tparam T The overriden type parameter comment + */ + override def function[T](arg1: T, arg2: String): Double = 3.0d +} \ No newline at end of file diff --git a/test/scaladoc/resources/implicit-inheritance-usecase.scala b/test/scaladoc/resources/implicit-inheritance-usecase.scala new file mode 100644 index 000000000000..8dd1262e4bb9 --- /dev/null +++ b/test/scaladoc/resources/implicit-inheritance-usecase.scala @@ -0,0 +1,57 @@ +// This tests the implicit comment inheritance capabilities of scaladoc for usecases (no $super, no @inheritdoc) +/** Testing use case inheritance */ +class UseCaseInheritance { + /** + * The base comment. And another sentence... + * + * @param arg1 The T term comment + * @param arg2 The string comment + * @tparam T The type parameter + * @return The return comment + * + * @usecase def missing_arg[T](arg1: T): Double + * + * @usecase def missing_targ(arg1: Int, arg2: String): Double + * + * @usecase def overridden_arg1[T](implicit arg1: T, arg2: String): Double + * @param arg1 The overridden T term comment + * + * @usecase def overridden_targ[T](implicit arg1: T, arg2: String): Double + * @tparam T The overridden type parameter comment + * + * @usecase def overridden_return[T](implicit arg1: T, arg2: String): Double + * @return The overridden return comment + * + * @usecase def added_arg[T](implicit arg1: T, arg2: String, arg3: Float): Double + * @param arg3 The added float comment + * + * @usecase def overridden_comment[T](implicit arg1: T, arg2: String): Double + * The overridden comment. + */ + def function[T](implicit arg1: T, arg2: String): Double = 0.0d +} + +/** Testing the override-use case interaction */ +class UseCaseOverrideInheritance extends UseCaseInheritance { + /** + * @usecase def missing_arg[T](arg1: T): Double + * + * @usecase def missing_targ(arg1: Int, arg2: String): Double + * + * @usecase def overridden_arg1[T](implicit arg1: T, arg2: String): Double + * @param arg1 The overridden T term comment + * + * @usecase def overridden_targ[T](implicit arg1: T, arg2: String): Double + * @tparam T The overridden type parameter comment + * + * @usecase def overridden_return[T](implicit arg1: T, arg2: String): Double + * @return The overridden return comment + * + * @usecase def added_arg[T](implicit arg1: T, arg2: String, arg3: Float): Double + * @param arg3 The added float comment + * + * @usecase def overridden_comment[T](implicit arg1: T, arg2: String): Double + * The overridden comment. + */ + override def function[T](implicit arg1: T, arg2: String): Double = 0.0d +} From 26511b351dac0b1746dbe48b696f83be15c6b369 Mon Sep 17 00:00:00 2001 From: Vlad Ureche Date: Wed, 21 Dec 2011 16:35:35 +0100 Subject: [PATCH 5/5] The final and tested @inheritdoc implementation --- .../scala/tools/nsc/ast/DocComments.scala | 4 +- .../scala/tools/nsc/util/DocStrings.scala | 13 ++-- .../resources/inheritdoc-corner-cases.scala | 68 ++++++++++++++++ .../scaladoc/scala/html/HtmlFactoryTest.scala | 78 +++++++++++++++++++ 4 files changed, 156 insertions(+), 7 deletions(-) create mode 100644 test/scaladoc/resources/inheritdoc-corner-cases.scala diff --git a/src/compiler/scala/tools/nsc/ast/DocComments.scala b/src/compiler/scala/tools/nsc/ast/DocComments.scala index 65b07ab05c2f..4386d1c2b8dc 100755 --- a/src/compiler/scala/tools/nsc/ast/DocComments.scala +++ b/src/compiler/scala/tools/nsc/ast/DocComments.scala @@ -63,7 +63,7 @@ trait DocComments { self: Global => 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 + ownComment.replaceAllLiterally("@inheritdoc", "") case Some(sc) => if (ownComment == "") sc else expandInheritdoc(sc, merge(sc, ownComment, sym), sym) @@ -251,7 +251,7 @@ trait DocComments { self: Global => case None => reporter.info(sym.pos, "The \"" + getSectionHeader + "\" annotation of the " + sym + " comment contains @inheritdoc, but the corresponding section in the parent is not defined.", true) - "[...]" + "" } if (dst.substring(section._1).startsWith("@param")) diff --git a/src/compiler/scala/tools/nsc/util/DocStrings.scala b/src/compiler/scala/tools/nsc/util/DocStrings.scala index 40e6b2250e55..c0b329daf799 100755 --- a/src/compiler/scala/tools/nsc/util/DocStrings.scala +++ b/src/compiler/scala/tools/nsc/util/DocStrings.scala @@ -84,15 +84,18 @@ object DocStrings { * @usecase or the end of the string, as they might include other sections * of their own */ - def tagIndex(str: String, p: Int => Boolean = (idx => true)): List[(Int, Int)] = - findAll(str, 0) (idx => str(idx) == '@' && p(idx)) match { + def tagIndex(str: String, p: Int => Boolean = (idx => true)): List[(Int, Int)] = { + val indices = findAll(str, 0) (idx => str(idx) == '@' && p(idx)) + val indices2 = mergeUsecaseSections(str, indices) + val indices3 = mergeInheritdocSections(str, indices2) + + indices3 match { case List() => List() case idxs => { - val idxs2 = mergeUsecaseSections(str, idxs) - val idxs3 = mergeInheritdocSections(str, idxs2) - idxs3 zip (idxs3.tail ::: List(str.length - 2)) + idxs zip (idxs.tail ::: List(str.length - 2)) } } + } /** * Merge sections following an @usecase into the usecase comment, so they diff --git a/test/scaladoc/resources/inheritdoc-corner-cases.scala b/test/scaladoc/resources/inheritdoc-corner-cases.scala new file mode 100644 index 000000000000..d4937d3685ad --- /dev/null +++ b/test/scaladoc/resources/inheritdoc-corner-cases.scala @@ -0,0 +1,68 @@ +// TEST1: Inherit from multiple classes +trait A { + /** + * Hello 1 comment + */ + def hello1 = 0 +} + +trait B { + /** + * Hello 2 comment + */ + def hello2 = 1 +} + +trait C extends B + +class D extends A with C { + /** + * Inherited: @inheritdoc + */ + override def hello1 = super.hello2 + + /** + * Inherited: @inheritdoc + */ + override def hello2 = super.hello1 +} + +// TEST2: Invalid inherit: no parents +trait E { + /** + * @inheritdoc + */ + def whereDidThisComeFrom +} + +// TEST3: Invalid inherit, but other parents present +trait F extends E { + /** + * @inheritdoc + */ + def howAboutThis +} + + +// TEST4: Inherit from something that inherits: inherit should propagate +trait G extends D { + /** + * @inheritdoc + */ + override def hello1 = 13 + + /** + * @inheritdoc + */ + override def hello2 = 14 +} + +// TEST5: Inherit missing parameters +trait H extends G { + /** + * Missing params + * @throws HelloException @inheritdoc + * @todo @inheritdoc + */ + override def hello1 = 15 +} diff --git a/test/scaladoc/scala/html/HtmlFactoryTest.scala b/test/scaladoc/scala/html/HtmlFactoryTest.scala index c2c127e76572..6aca6c7ea877 100644 --- a/test/scaladoc/scala/html/HtmlFactoryTest.scala +++ b/test/scaladoc/scala/html/HtmlFactoryTest.scala @@ -580,7 +580,85 @@ object Test extends Properties("HtmlFactory") { """, true) ) } + + property("Correct explicit inheritance for override") = + checkText("explicit-inheritance-override.scala")( + (Some("InheritDocDerived"), + """def function[T](arg1: T, arg2: String): Double + Starting line + Starting line + The base comment. And another sentence... + The base comment. And another sentence... + Ending line + T StartT the type of the first argument EndT + arg1 Start1 The T term comment End1 + arg2 Start2 The string comment End2 + returns StartRet The return comment EndRet + Definition Classes InheritDocDerived → InheritDocBase + Example: StartExample function[Int](3, "something") EndExample + Version StartVer 0.0.2 EndVer + Since StartSince 0.0.1 EndSince + Exceptions thrown + SomeException StartEx if the function is not called with correct parameters EndEx + SomeOtherException StartSOE Should Warn EndSOE + To do StartTodo Call mom. And dad! EndTodo + Note StartNote Be careful! EndNote + See also StartSee The Manual EndSee + """, true)) + + property("Correct explicit inheritance for usecase") = + checkText("explicit-inheritance-usecase.scala")( + (Some("UseCaseInheritDoc"), + """def function[T](arg1: T, arg2: String): Double + [use case] Starting line + [use case] Starting line + The base comment. And another sentence... + The base comment. And another sentence... + Ending line + T StartT the type of the first argument EndT + arg1 Start1 The T term comment End1 + arg2 Start2 The string comment End2 + returns StartRet The return comment EndRet + Example: StartExample function[Int](3,"something") EndExample + Version StartVer 0.0.2 EndVer + Since StartSince 0.0.1 EndSince + Exceptions thrown + SomeException StartEx if the function is not called with correct parameters EndEx + SomeOtherException StartSOE Should Warn EndSOE + To do StartTodo Call mom. And dad! EndTodo + Note StartNote Be careful! EndNote + See also StartSee The Manual EndSee + """, true)) + + property("Correct explicit inheritance in corner cases") = + checkText("inheritdoc-corner-cases.scala")( + (Some("D"), + """def hello1: Int + Inherited: Hello 1 comment + Inherited: Hello 1 comment + Definition Classes D → A + """, true), + (Some("D"), + """def hello2: Int + Inherited: Hello 2 comment + Inherited: Hello 2 comment + Definition Classes D → B + """, true), + (Some("G"), + """def hello1: Int + Inherited: Hello 1 comment + Inherited: Hello 1 comment + Definition Classes G → D → A + """, true), + (Some("G"), + """def hello2: Int + Inherited: Hello 2 comment + Inherited: Hello 2 comment + Definition Classes G → D → B + """, true) + // traits E, F and H shouldn't crash scaladoc but we don't need to check the output + ) { val files = createTemplates("basic.scala")