Skip to content

Commit d26b384

Browse files
authored
Merge pull request scala#5246 from jodersky/javadoc
SI-4826 Retain javadoc comments in scaladoc [ci: last-only]
2 parents ca57d02 + 543d719 commit d26b384

File tree

12 files changed

+171
-74
lines changed

12 files changed

+171
-74
lines changed

src/compiler/scala/tools/nsc/Global.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
411411
override val initial = true
412412
}
413413

414-
import syntaxAnalyzer.{ UnitScanner, UnitParser }
414+
import syntaxAnalyzer.{ UnitScanner, UnitParser, JavaUnitParser }
415415

416416
// !!! I think we're overdue for all these phase objects being lazy vals.
417417
// There's no way for a Global subclass to provide a custom typer
@@ -1042,6 +1042,8 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
10421042
def newUnitParser(code: String, filename: String = "<console>"): UnitParser =
10431043
newUnitParser(newCompilationUnit(code, filename))
10441044

1045+
def newJavaUnitParser(unit: CompilationUnit): JavaUnitParser = new JavaUnitParser(unit)
1046+
10451047
/** A Run is a single execution of the compiler on a set of units.
10461048
*/
10471049
class Run extends RunContextApi with RunReporting with RunParsing {

src/compiler/scala/tools/nsc/ast/parser/SyntaxAnalyzer.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ abstract class SyntaxAnalyzer extends SubComponent with Parsers with MarkupParse
8282
}
8383

8484
private def initialUnitBody(unit: CompilationUnit): Tree = {
85-
if (unit.isJava) new JavaUnitParser(unit).parse()
85+
if (unit.isJava) newJavaUnitParser(unit).parse()
8686
else if (currentRun.parsing.incompleteHandled) newUnitParser(unit).parse()
8787
else newUnitParser(unit).smartParse()
8888
}

src/compiler/scala/tools/nsc/javac/JavaParsers.scala

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {
111111
def arrayOf(tpt: Tree) =
112112
AppliedTypeTree(scalaDot(tpnme.Array), List(tpt))
113113

114-
def blankExpr = Ident(nme.WILDCARD)
114+
def blankExpr = EmptyTree
115115

116116
def makePackaging(pkg: RefTree, stats: List[Tree]): PackageDef =
117117
atPos(pkg.pos) { PackageDef(pkg, stats) }
@@ -135,6 +135,11 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {
135135
DefDef(Modifiers(Flags.JAVA), nme.CONSTRUCTOR, List(), List(vparams), TypeTree(), blankExpr)
136136
}
137137

138+
/** A hook for joining the comment associated with a definition.
139+
* Overridden by scaladoc.
140+
*/
141+
def joinComment(trees: => List[Tree]): List[Tree] = trees
142+
138143
// ------------- general parsing ---------------------------
139144

140145
/** skip parent or brace enclosed sequence of things */
@@ -581,7 +586,7 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {
581586
case CLASS | ENUM | INTERFACE | AT =>
582587
typeDecl(if (definesInterface(parentToken)) mods | Flags.STATIC else mods)
583588
case _ =>
584-
termDecl(mods, parentToken)
589+
joinComment(termDecl(mods, parentToken))
585590
}
586591

587592
def makeCompanionObject(cdef: ClassDef, statics: List[Tree]): Tree =
@@ -833,10 +838,10 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {
833838
}
834839

835840
def typeDecl(mods: Modifiers): List[Tree] = in.token match {
836-
case ENUM => enumDecl(mods)
837-
case INTERFACE => interfaceDecl(mods)
841+
case ENUM => joinComment(enumDecl(mods))
842+
case INTERFACE => joinComment(interfaceDecl(mods))
838843
case AT => annotationDecl(mods)
839-
case CLASS => classDecl(mods)
844+
case CLASS => joinComment(classDecl(mods))
840845
case _ => in.nextToken(); syntaxError("illegal start of type declaration", skipIt = true); List(errorTypeTree)
841846
}
842847

src/compiler/scala/tools/nsc/javac/JavaScanners.scala

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -577,21 +577,29 @@ trait JavaScanners extends ast.parser.ScannersCommon {
577577
}
578578
}
579579

580-
protected def skipComment(): Boolean = {
581-
@tailrec def skipLineComment(): Unit = in.ch match {
582-
case CR | LF | SU =>
583-
case _ => in.next; skipLineComment()
584-
}
585-
@tailrec def skipJavaComment(): Unit = in.ch match {
586-
case SU => incompleteInputError("unclosed comment")
587-
case '*' => in.next; if (in.ch == '/') in.next else skipJavaComment()
588-
case _ => in.next; skipJavaComment()
589-
}
590-
in.ch match {
591-
case '/' => in.next ; skipLineComment() ; true
592-
case '*' => in.next ; skipJavaComment() ; true
593-
case _ => false
594-
}
580+
protected def putCommentChar(): Unit = in.next()
581+
582+
protected def skipBlockComment(isDoc: Boolean): Unit = in.ch match {
583+
case SU => incompleteInputError("unclosed comment")
584+
case '*' => putCommentChar() ; if (in.ch == '/') putCommentChar() else skipBlockComment(isDoc)
585+
case _ => putCommentChar() ; skipBlockComment(isDoc)
586+
}
587+
588+
protected def skipLineComment(): Unit = in.ch match {
589+
case CR | LF | SU =>
590+
case _ => putCommentChar() ; skipLineComment()
591+
}
592+
593+
protected def skipComment(): Boolean = in.ch match {
594+
case '/' => putCommentChar() ; skipLineComment() ; true
595+
case '*' =>
596+
putCommentChar()
597+
in.ch match {
598+
case '*' => skipBlockComment(isDoc = true)
599+
case _ => skipBlockComment(isDoc = false)
600+
}
601+
true
602+
case _ => false
595603
}
596604

597605
// Identifiers ---------------------------------------------------------------

src/compiler/scala/tools/nsc/typechecker/Typers.scala

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2247,9 +2247,10 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
22472247
transformedOrTyped(ddef.rhs, EXPRmode, tpt1.tpe)
22482248
}
22492249

2250-
if (meth.isClassConstructor && !isPastTyper && !meth.owner.isSubClass(AnyValClass)) {
2251-
// At this point in AnyVal there is no supercall, which will blow up
2252-
// in computeParamAliases; there's nothing to be computed for Anyval anyway.
2250+
if (meth.isClassConstructor && !isPastTyper && !meth.owner.isSubClass(AnyValClass) && !meth.isJava) {
2251+
// There are no supercalls for AnyVal or constructors from Java sources, which
2252+
// would blow up in computeParamAliases; there's nothing to be computed for them
2253+
// anyway.
22532254
if (meth.isPrimaryConstructor)
22542255
computeParamAliases(meth.owner, vparamss1, rhs1)
22552256
else

src/scaladoc/scala/tools/nsc/doc/ScaladocAnalyzer.scala

Lines changed: 73 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -101,52 +101,6 @@ trait ScaladocAnalyzer extends Analyzer {
101101
abstract class ScaladocSyntaxAnalyzer[G <: Global](val global: G) extends SyntaxAnalyzer {
102102
import global._
103103

104-
class ScaladocJavaUnitParser(unit: CompilationUnit) extends {
105-
override val in = new ScaladocJavaUnitScanner(unit)
106-
} with JavaUnitParser(unit) { }
107-
108-
class ScaladocJavaUnitScanner(unit: CompilationUnit) extends JavaUnitScanner(unit) {
109-
/** buffer for the documentation comment
110-
*/
111-
var docBuffer: StringBuilder = null
112-
113-
/** add the given character to the documentation buffer
114-
*/
115-
protected def putDocChar(c: Char) {
116-
if (docBuffer ne null) docBuffer.append(c)
117-
}
118-
119-
override protected def skipComment(): Boolean = {
120-
if (in.ch == '/') {
121-
do {
122-
in.next
123-
} while ((in.ch != CR) && (in.ch != LF) && (in.ch != SU))
124-
true
125-
} else if (in.ch == '*') {
126-
docBuffer = null
127-
in.next
128-
val scaladoc = ("/**", "*/")
129-
if (in.ch == '*')
130-
docBuffer = new StringBuilder(scaladoc._1)
131-
do {
132-
do {
133-
if (in.ch != '*' && in.ch != SU) {
134-
in.next; putDocChar(in.ch)
135-
}
136-
} while (in.ch != '*' && in.ch != SU)
137-
while (in.ch == '*') {
138-
in.next; putDocChar(in.ch)
139-
}
140-
} while (in.ch != '/' && in.ch != SU)
141-
if (in.ch == '/') in.next
142-
else incompleteInputError("unclosed comment")
143-
true
144-
} else {
145-
false
146-
}
147-
}
148-
}
149-
150104
class ScaladocUnitScanner(unit0: CompilationUnit, patches0: List[BracePatch]) extends UnitScanner(unit0, patches0) {
151105

152106
private var docBuffer: StringBuilder = null // buffer for comments (non-null while scanning)
@@ -259,4 +213,77 @@ abstract class ScaladocSyntaxAnalyzer[G <: Global](val global: G) extends Syntax
259213
else trees
260214
}
261215
}
216+
217+
class ScaladocJavaUnitScanner(unit: CompilationUnit) extends JavaUnitScanner(unit) {
218+
219+
private val docBuffer: StringBuilder = new StringBuilder
220+
private var inDocComment = false
221+
private var docStart: Int = 0
222+
private var lastDoc: DocComment = null
223+
224+
// get last doc comment
225+
def flushDoc(): DocComment = try lastDoc finally lastDoc = null
226+
227+
override protected def putCommentChar(): Unit = {
228+
if (inDocComment) docBuffer append in.ch
229+
in.next
230+
}
231+
232+
override protected def skipBlockComment(isDoc: Boolean): Unit = {
233+
// condition is true when comment is entered the first time,
234+
// i.e. immediately after "/*" and when current character is "*"
235+
if (!inDocComment && isDoc) {
236+
docBuffer append "/*"
237+
docStart = currentPos.start
238+
inDocComment = true
239+
}
240+
super.skipBlockComment(isDoc)
241+
}
242+
243+
override protected def skipComment(): Boolean = {
244+
val skipped = super.skipComment()
245+
if (skipped && inDocComment) {
246+
val raw = docBuffer.toString
247+
val position = Position.range(unit.source, docStart, docStart, in.cpos)
248+
lastDoc = DocComment(raw, position)
249+
signalParsedDocComment(raw, position)
250+
docBuffer.setLength(0) // clear buffer
251+
inDocComment = false
252+
true
253+
} else {
254+
skipped
255+
}
256+
}
257+
258+
}
259+
260+
class ScaladocJavaUnitParser(unit: CompilationUnit) extends {
261+
override val in = new ScaladocJavaUnitScanner(unit)
262+
} with JavaUnitParser(unit) {
263+
264+
override def joinComment(trees: => List[Tree]): List[Tree] = {
265+
val doc = in.flushDoc()
266+
267+
if ((doc ne null) && doc.raw.length > 0) {
268+
log(s"joinComment(doc=$doc)")
269+
val joined = trees map { t =>
270+
DocDef(doc, t) setPos {
271+
if (t.pos.isDefined) {
272+
val pos = doc.pos.withEnd(t.pos.end)
273+
pos.makeTransparent
274+
} else {
275+
t.pos
276+
}
277+
}
278+
}
279+
joined.find(_.pos.isOpaqueRange) foreach { main =>
280+
val mains = List(main)
281+
joined foreach { t => if (t ne main) ensureNonOverlapping(t, mains) }
282+
}
283+
joined
284+
} else {
285+
trees
286+
}
287+
}
288+
}
262289
}

src/scaladoc/scala/tools/nsc/doc/ScaladocGlobal.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ trait ScaladocGlobalTrait extends Global {
1313

1414
override val useOffsetPositions = false
1515
override def newUnitParser(unit: CompilationUnit) = new syntaxAnalyzer.ScaladocUnitParser(unit, Nil)
16+
override def newJavaUnitParser(unit: CompilationUnit) = new syntaxAnalyzer.ScaladocJavaUnitParser(unit)
1617

1718
override lazy val syntaxAnalyzer = new ScaladocSyntaxAnalyzer[outer.type](outer) {
1819
val runsAfter = List[String]()
@@ -40,6 +41,8 @@ class ScaladocGlobal(settings: doc.Settings, reporter: Reporter) extends Global(
4041
phasesSet += analyzer.typerFactory
4142
}
4243
override def forScaladoc = true
44+
override def createJavadoc = true
45+
4346
override lazy val analyzer = new {
4447
val global: ScaladocGlobal.this.type = ScaladocGlobal.this
4548
} with ScaladocAnalyzer

src/scaladoc/scala/tools/partest/ScaladocModelTest.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ abstract class ScaladocModelTest extends DirectTest {
8181
private[this] var settings: doc.Settings = null
8282

8383
// create a new scaladoc compiler
84-
private[this] def newDocFactory: DocFactory = {
84+
def newDocFactory: DocFactory = {
8585
settings = new doc.Settings(_ => ())
8686
settings.scaladocQuietRun = true // yaay, no more "model contains X documentable templates"!
8787
val args = extraSettings + " " + scaladocSettings

test/files/run/t5699.check

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
[[syntax trees at end of parser]] // annodef.java
22
package <empty> {
33
object MyAnnotation extends {
4-
def <init>() = _
4+
def <init>()
55
};
66
class MyAnnotation extends scala.annotation.Annotation with _root_.java.lang.annotation.Annotation with scala.annotation.ClassfileAnnotation {
7-
def <init>() = _;
7+
def <init>();
88
def value(): String
99
}
1010
}

test/scaladoc/resources/SI-4826.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package test.scaladoc;
2+
3+
/**
4+
* Testing java comments. The presence of a :marker:
5+
* tag is verified by tests.
6+
*/
7+
public class JavaComments {
8+
9+
/**
10+
* Compute the answer to the ultimate question of life, the
11+
* universe, and everything. :marker:
12+
* @param factor scaling factor to the answer
13+
* @return the answer to everything (42) scaled by factor
14+
*/
15+
public int answer(int factor) {
16+
return 42 * factor;
17+
}
18+
19+
}
20+

test/scaladoc/run/SI-4826.check

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Done.

test/scaladoc/run/SI-4826.scala

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import scala.tools.nsc.doc.Universe
2+
import scala.tools.nsc.doc.model._
3+
import scala.tools.partest.ScaladocModelTest
4+
5+
object Test extends ScaladocModelTest {
6+
7+
override def resourceFile = "SI-4826.java"
8+
9+
// overridden to pass explicit files to newDocFactory.makeUniverse (rather than code strings)
10+
// since the .java file extension is required
11+
override def model: Option[Universe] = {
12+
val path = resourcePath + "/" + resourceFile
13+
newDocFactory.makeUniverse(Left(List(path)))
14+
}
15+
16+
// no need for special settings
17+
def scaladocSettings = ""
18+
19+
def testModel(rootPackage: Package) = {
20+
import access._
21+
val Tag = ":marker:"
22+
23+
val base = rootPackage._package("test")._package("scaladoc")
24+
val clazz = base._class("JavaComments")
25+
val method = clazz._method("answer")
26+
27+
assert(extractCommentText(clazz.comment.get).contains(Tag))
28+
assert(extractCommentText(method.comment.get).contains(Tag))
29+
}
30+
}

0 commit comments

Comments
 (0)