8000 Feature/inheritdoc and proper usecase comment inheritance/override by VladUreche · Pull Request #70 · scala/scala · GitHub
[go: up one dir, main page]

Skip to content

Feature/inheritdoc and proper usecase comment inheritance/override #70

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 89 additions & 8 deletions src/compiler/scala/tools/nsc/ast/DocComments.scala
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,13 @@ trait DocComments { self: Global =>
else DocComment(docStr).template
superComment(sym) match {
case None =>
ownComment
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", "<invalid inheritdoc annotation>")
case Some(sc) =>
if (ownComment == "") sc
else merge(sc, ownComment, sym)
else expandInheritdoc(sc, merge(sc, ownComment, sym), sym)
}
}

Expand Down Expand Up @@ -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()
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
"<invalid inheritdoc annotation>"
}

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
*/
Expand Down Expand Up @@ -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") }

Expand Down
90 changes: 86 additions & 4 deletions src/compiler/scala/tools/nsc/util/DocStrings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -71,13 +79,45 @@ 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 {
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 => idxs zip (idxs.tail ::: List(str.length - 2))
case idxs => {
idxs zip (idxs.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
}
}

/**
* 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 =
Expand Down Expand Up @@ -133,4 +173,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
}

}
48 changes: 48 additions & 0 deletions test/scaladoc/resources/explicit-inheritance-override.scala
Original file line number Diff line number Diff line change
@@ -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
}
47 changes: 47 additions & 0 deletions test/scaladoc/resources/explicit-inheritance-usecase.scala
Original file line number Diff line number Diff line change
@@ -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
}

41 changes: 41 additions & 0 deletions test/scaladoc/resources/implicit-inheritance-override.scala
Original file line number Diff line number Diff line change
@@ -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
}
Loading
0