From 5b347bf912f8277474c1c1e93279c0ea4d95ccbe Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Fri, 15 Jun 2018 08:55:18 +0200 Subject: [PATCH 1/4] Add `:doc` command to REPL This command is used to display the documentation associated to the given expression. For instance: ```scala scala> /** A class */ class A scala> /** An object */ object O { /** A def */ def foo = 0 } scala> :doc new A /** A class */ scala> :doc O /** An object */ scala> :doc O.foo /** A def */ ``` --- .../src/dotty/tools/repl/ParseResult.scala | 11 ++ .../src/dotty/tools/repl/ReplCompiler.scala | 39 +++++ .../src/dotty/tools/repl/ReplDriver.scala | 9 +- compiler/test/dotty/tools/repl/DocTests.scala | 160 ++++++++++++++++++ 4 files changed, 218 insertions(+), 1 deletion(-) create mode 100644 compiler/test/dotty/tools/repl/DocTests.scala diff --git a/compiler/src/dotty/tools/repl/ParseResult.scala b/compiler/src/dotty/tools/repl/ParseResult.scala index 11f9cd0d72e7..40f76d77ca32 100644 --- a/compiler/src/dotty/tools/repl/ParseResult.scala +++ b/compiler/src/dotty/tools/repl/ParseResult.scala @@ -60,6 +60,15 @@ object TypeOf { val command = ":type" } +/** + * A command that is used to display the documentation associated with + * the given expression. + */ +case class DocOf(expr: String) extends Command +object DocOf { + val command = ":doc" +} + /** `:imports` lists the imports that have been explicitly imported during the * session */ @@ -89,6 +98,7 @@ case object Help extends Command { |:load interpret lines in a file |:quit exit the interpreter |:type evaluate the type of the given expression + |:doc print the documentation for the given expresssion |:imports show import history |:reset reset the repl to its initial state, forgetting all session entries """.stripMargin @@ -117,6 +127,7 @@ object ParseResult { case Imports.command => Imports case Load.command => Load(arg) case TypeOf.command => TypeOf(arg) + case DocOf.command => DocOf(arg) case _ => UnknownCommand(cmd) } case _ => diff --git a/compiler/src/dotty/tools/repl/ReplCompiler.scala b/compiler/src/dotty/tools/repl/ReplCompiler.scala index 825799429e4b..85b75c77911c 100644 --- a/compiler/src/dotty/tools/repl/ReplCompiler.scala +++ b/compiler/src/dotty/tools/repl/ReplCompiler.scala @@ -3,6 +3,7 @@ package dotty.tools.repl import dotty.tools.backend.jvm.GenBCode import dotty.tools.dotc.ast.Trees._ import dotty.tools.dotc.ast.{tpd, untpd} +import dotty.tools.dotc.core.Comments.CommentsContext import dotty.tools.dotc.core.Contexts._ import dotty.tools.dotc.core.Decorators._ import dotty.tools.dotc.core.Flags._ @@ -10,6 +11,7 @@ import dotty.tools.dotc.core.Names._ import dotty.tools.dotc.core.Phases import dotty.tools.dotc.core.Phases.Phase import dotty.tools.dotc.core.StdNames._ +import dotty.tools.dotc.core.Symbols._ import dotty.tools.dotc.reporting.diagnostic.messages import dotty.tools.dotc.typer.{FrontEnd, ImportInfo} import dotty.tools.dotc.util.Positions._ @@ -164,6 +166,43 @@ class ReplCompiler(val directory: AbstractFile) extends Compiler { } } + def docOf(expr: String)(implicit state: State): Result[String] = { + implicit val ctx: Context = state.context + + def pickSymbol(symbol: Symbol): Symbol = { + if (symbol.is(Module, butNot = ModuleClass)) symbol.moduleClass + if (symbol.isConstructor) symbol.owner + else symbol + } + + def extractSymbol(tree: tpd.Tree): Symbol = { + tree match { + case tpd.closureDef(defdef) => defdef.rhs.symbol + case _ => tree.symbol + } + } + + typeCheck(expr).map { + case v @ ValDef(_, _, Block(stats, _)) if stats.nonEmpty => + val stat = stats.last.asInstanceOf[tpd.Tree] + if (stat.tpe.isError) stat.tpe.show + else { + val symbol = pickSymbol(extractSymbol(stat)) + val doc = + for { docCtx <- ctx.docCtx + doc <- docCtx.docstrings.get(symbol) } yield doc.raw + + doc.getOrElse(s"// No doc for ${symbol.show}") + } + + case _ => + """Couldn't display the documentation for your expression, so sorry :( + | + |Please report this to my masters at github.com/lampepfl/dotty + """.stripMargin + } + } + final def typeCheck(expr: String, errorsAllowed: Boolean = false)(implicit state: State): Result[tpd.ValDef] = { def wrapped(expr: String, sourceFile: SourceFile, state: State)(implicit ctx: Context): Result[untpd.PackageDef] = { diff --git a/compiler/src/dotty/tools/repl/ReplDriver.scala b/compiler/src/dotty/tools/repl/ReplDriver.scala index 278a1203d5bb..3d9b9bb6184d 100644 --- a/compiler/src/dotty/tools/repl/ReplDriver.scala +++ b/compiler/src/dotty/tools/repl/ReplDriver.scala @@ -63,7 +63,7 @@ class ReplDriver(settings: Array[String], /** Create a fresh and initialized context with IDE mode enabled */ private[this] def initialCtx = { - val rootCtx = initCtx.fresh.addMode(Mode.ReadPositions).addMode(Mode.Interactive) + val rootCtx = initCtx.fresh.addMode(Mode.ReadPositions).addMode(Mode.Interactive).addMode(Mode.ReadComments) val ictx = setup(settings, rootCtx)._2 ictx.base.initialize()(ictx) ictx @@ -338,6 +338,13 @@ class ReplDriver(settings: Array[String], ) state + case DocOf(expr) => + compiler.docOf(expr)(newRun(state)).fold( + displayErrors, + res => out.println(SyntaxHighlighting(res)) + ) + state + case Quit => // end of the world! state diff --git a/compiler/test/dotty/tools/repl/DocTests.scala b/compiler/test/dotty/tools/repl/DocTests.scala new file mode 100644 index 000000000000..f6e5d3d15126 --- /dev/null +++ b/compiler/test/dotty/tools/repl/DocTests.scala @@ -0,0 +1,160 @@ +package dotty.tools +package repl + +import org.junit.Test +import org.junit.Assert.assertEquals + +class DocTests extends ReplTest { + @Test def docOfDef = + fromInitialState { implicit s => run("/** doc */ def foo = 0") } + .andThen { implicit s => + storedOutput() + run(":doc foo") + assertEquals("/** doc */", storedOutput().trim) + } + + @Test def docOfVal = + fromInitialState { implicit s => run("/** doc */ val foo = 0") } + .andThen { implicit s => + storedOutput() + run(":doc foo") + assertEquals("/** doc */", storedOutput().trim) + } + + @Test def docOfObject = + fromInitialState { implicit s => run("/** doc */ object Foo") } + .andThen { implicit s => + storedOutput() + run(":doc Foo") + assertEquals("/** doc */", storedOutput().trim) + } + + @Test def docOfClass = + fromInitialState { implicit s => run("/** doc */ class Foo") } + .andThen { implicit s => + storedOutput() + run(":doc new Foo") + assertEquals("/** doc */", storedOutput().trim) + } + + @Test def docOfTrait = + fromInitialState { implicit s => run("/** doc */ trait Foo") } + .andThen { implicit s => + storedOutput() + run(":doc new Foo") + assertEquals("/** doc */", storedOutput().trim) + } + + @Test def docOfDefInObject = + fromInitialState { implicit s => run("object O { /** doc */ def foo = 0 }") } + .andThen { implicit s => + storedOutput() + run(":doc O.foo") + assertEquals("/** doc */", storedOutput().trim) + } + + @Test def docOfValInObject = + fromInitialState { implicit s => run("object O { /** doc */ val foo = 0 }") } + .andThen { implicit s => + storedOutput() + run(":doc O.foo") + assertEquals("/** doc */", storedOutput().trim) + } + + @Test def docOfObjectInObject = + fromInitialState { implicit s => run("object O { /** doc */ object Foo }") } + .andThen { implicit s => + storedOutput() + run(":doc O.Foo") + assertEquals("/** doc */", storedOutput().trim) + } + + @Test def docOfClassInObject = + fromInitialState { implicit s => run("object O { /** doc */ class Foo }") } + .andThen { implicit s => + storedOutput() + run(":doc new O.Foo") + assertEquals("/** doc */", storedOutput().trim) + } + + @Test def docOfTraitInObject = + fromInitialState { implicit s => run("object O { /** doc */ trait Foo }") } + .andThen { implicit s => + storedOutput() + run(":doc new O.Foo") + assertEquals("/** doc */", storedOutput().trim) + } + + @Test def docOfDetInClass = + fromInitialState { implicit s => run("class C { /** doc */ def foo = 0 }") } + .andThen { implicit s => run("val c = new C") } + .andThen { implicit s => + storedOutput() + run(":doc c.foo") + assertEquals("/** doc */", storedOutput().trim) + } + + @Test def docOfVatInClass = + fromInitialState { implicit s => run("class C { /** doc */ val foo = 0 }") } + .andThen { implicit s => run("val c = new C") } + .andThen { implicit s => + storedOutput() + run(":doc c.foo") + assertEquals("/** doc */", storedOutput().trim) + } + + @Test def docOfObjectInClass = + fromInitialState { implicit s => run("class C { /** doc */ object Foo }") } + .andThen { implicit s => run("val c = new C") } + .andThen { implicit s => + storedOutput() + run(":doc c.Foo") + assertEquals("/** doc */", storedOutput().trim) + } + + @Test def docOfClassInClass = + fromInitialState { implicit s => run("class C { /** doc */ class Foo }") } + .andThen { implicit s => run("val c = new C") } + .andThen { implicit s => + storedOutput() + run(":doc new c.Foo") + assertEquals("/** doc */", storedOutput().trim) + } + + @Test def docOfTraitInClass = + fromInitialState { implicit s => run("class C { /** doc */ trait Foo }") } + .andThen { implicit s => run("val c = new C") } + .andThen { implicit s => + storedOutput() + run(":doc new c.Foo") + assertEquals("/** doc */", storedOutput().trim) + } + + @Test def docOfOverloadedDef = + fromInitialState { implicit s => + run("""object O { + |/** doc0 */ def foo(x: Int) = x + |/** doc1 */ def foo(x: String) = x + |}""".stripMargin) + } + .andThen { implicit s => + storedOutput() + run(":doc O.foo(_: Int)") + assertEquals("/** doc0 */", storedOutput().trim) + s + } + .andThen { implicit s => + run(":doc O.foo(_: String)") + assertEquals("/** doc1 */", storedOutput().trim) + } + + @Test def docOfInherited = + fromInitialState { implicit s => run("class C { /** doc */ def foo = 0 }") } + .andThen { implicit s => run("object O extends C") } + .andThen { implicit s => + storedOutput() + run(":doc O.foo") + assertEquals("/** doc */", storedOutput().trim) + } + +} From 6fb9c64bd7b0de662ab37d45ccd80bf365b69442 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Fri, 15 Jun 2018 11:29:23 +0200 Subject: [PATCH 2/4] Handle overridden symbols in doc command in REPL --- .../src/dotty/tools/repl/ReplCompiler.scala | 35 ++++++++----- compiler/test/dotty/tools/repl/DocTests.scala | 49 +++++++++++++++++++ 2 files changed, 73 insertions(+), 11 deletions(-) diff --git a/compiler/src/dotty/tools/repl/ReplCompiler.scala b/compiler/src/dotty/tools/repl/ReplCompiler.scala index 85b75c77911c..48b2936953f2 100644 --- a/compiler/src/dotty/tools/repl/ReplCompiler.scala +++ b/compiler/src/dotty/tools/repl/ReplCompiler.scala @@ -169,12 +169,7 @@ class ReplCompiler(val directory: AbstractFile) extends Compiler { def docOf(expr: String)(implicit state: State): Result[String] = { implicit val ctx: Context = state.context - def pickSymbol(symbol: Symbol): Symbol = { - if (symbol.is(Module, butNot = ModuleClass)) symbol.moduleClass - if (symbol.isConstructor) symbol.owner - else symbol - } - + /** Extract the (possibly) documented symbol from `tree` */ def extractSymbol(tree: tpd.Tree): Symbol = { tree match { case tpd.closureDef(defdef) => defdef.rhs.symbol @@ -182,17 +177,35 @@ class ReplCompiler(val directory: AbstractFile) extends Compiler { } } + /** + * Adapt `symbol` so that we get the one that holds the documentation. + * Return also the symbols that `symbol` overrides`. + */ + def pickSymbols(symbol: Symbol): Iterator[Symbol] = { + val selectedSymbol = { + if (symbol.is(Module, butNot = ModuleClass)) symbol.moduleClass + if (symbol.isConstructor) symbol.owner + else symbol + } + + Iterator(selectedSymbol) ++ selectedSymbol.allOverriddenSymbols + } + typeCheck(expr).map { case v @ ValDef(_, _, Block(stats, _)) if stats.nonEmpty => val stat = stats.last.asInstanceOf[tpd.Tree] if (stat.tpe.isError) stat.tpe.show else { - val symbol = pickSymbol(extractSymbol(stat)) val doc = - for { docCtx <- ctx.docCtx - doc <- docCtx.docstrings.get(symbol) } yield doc.raw - - doc.getOrElse(s"// No doc for ${symbol.show}") + ctx.docCtx.flatMap { docCtx => + val symbols = pickSymbols(extractSymbol(stat)) + symbols.collectFirst { + case sym if docCtx.docstrings.contains(sym) => + docCtx.docstrings(sym).raw + } + } + + doc.getOrElse(s"// No doc for `${expr}`") } case _ => diff --git a/compiler/test/dotty/tools/repl/DocTests.scala b/compiler/test/dotty/tools/repl/DocTests.scala index f6e5d3d15126..036d91d959e6 100644 --- a/compiler/test/dotty/tools/repl/DocTests.scala +++ b/compiler/test/dotty/tools/repl/DocTests.scala @@ -157,4 +157,53 @@ class DocTests extends ReplTest { assertEquals("/** doc */", storedOutput().trim) } + @Test def docOfOverride = + fromInitialState { implicit s => + run("""abstract class A { + |/** doc0 */ def foo(x: Int): Int = x + 1 + |/** doc1 */ def foo(x: String): String = x + "foo" + |}""".stripMargin) + } + .andThen { implicit s => + run("""object O extends A { + | override def foo(x: Int): Int = x + | /** overridden doc */ override def foo(x: String): String = x + |}""".stripMargin) + } + .andThen { implicit s => + storedOutput() + run(":doc O.foo(_: Int)") + assertEquals("/** doc0 */", storedOutput().trim) + s + } + .andThen { implicit s => + run(":doc O.foo(_: String)") + assertEquals("/** overridden doc */", storedOutput().trim) + } + + @Test def docOfOverrideObject = + fromInitialState { implicit s => + run("""abstract class A { + | abstract class Companion { /** doc0 */ def bar: Int } + | /** companion */ def foo: Companion + |}""".stripMargin) + .andThen { implicit s => + run("""object O extends A { + | override object foo extends Companion { + | override def bar: Int = 0 + | } + |}""".stripMargin) + } + .andThen { implicit s => + storedOutput() + run(":doc O.foo") + assertEquals("/** companion */", storedOutput().trim) + s + } + .andThen { implicit s => + run(":doc O.foo.bar") + assertEquals("/** doc0 */", storedOutput().trim) + } + } + } From df5b967ffe420825df6764afee6568f0aef7afc6 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Mon, 2 Jul 2018 14:20:23 +0200 Subject: [PATCH 3/4] Simplify symbol extraction for doc in REPL --- .../src/dotty/tools/repl/ReplCompiler.scala | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/compiler/src/dotty/tools/repl/ReplCompiler.scala b/compiler/src/dotty/tools/repl/ReplCompiler.scala index 48b2936953f2..262602f2fbd3 100644 --- a/compiler/src/dotty/tools/repl/ReplCompiler.scala +++ b/compiler/src/dotty/tools/repl/ReplCompiler.scala @@ -3,6 +3,7 @@ package dotty.tools.repl import dotty.tools.backend.jvm.GenBCode import dotty.tools.dotc.ast.Trees._ import dotty.tools.dotc.ast.{tpd, untpd} +import dotty.tools.dotc.ast.tpd.TreeOps import dotty.tools.dotc.core.Comments.CommentsContext import dotty.tools.dotc.core.Contexts._ import dotty.tools.dotc.core.Decorators._ @@ -169,26 +170,27 @@ class ReplCompiler(val directory: AbstractFile) extends Compiler { def docOf(expr: String)(implicit state: State): Result[String] = { implicit val ctx: Context = state.context - /** Extract the (possibly) documented symbol from `tree` */ - def extractSymbol(tree: tpd.Tree): Symbol = { - tree match { - case tpd.closureDef(defdef) => defdef.rhs.symbol - case _ => tree.symbol - } - } - /** - * Adapt `symbol` so that we get the one that holds the documentation. - * Return also the symbols that `symbol` overrides`. + * Extract the "selected" symbol from `tree`. + * + * Because the REPL typechecks an expression, special syntax is needed to get the documentation + * of certain symbols: + * + * - To select the documentation of classes, the user needs to pass a call to the class' constructor + * (e.g. `new Foo` to select `class Foo`) + * - When methods are overloaded, the user needs to enter a lambda to specify which functions he wants + * (e.g. `foo(_: Int)` to select `def foo(x: Int)` instead of `def foo(x: String)` + * + * This function returns the right symbol for the received expression, and all the symbols that are + * overridden. */ - def pickSymbols(symbol: Symbol): Iterator[Symbol] = { - val selectedSymbol = { - if (symbol.is(Module, butNot = ModuleClass)) symbol.moduleClass - if (symbol.isConstructor) symbol.owner - else symbol + def extractSymbols(tree: tpd.Tree): Iterator[Symbol] = { + val sym = tree match { + case tree if tree.isInstantiation => tree.symbol.owner + case tpd.closureDef(defdef) => defdef.rhs.symbol + case _ => tree.symbol } - - Iterator(selectedSymbol) ++ selectedSymbol.allOverriddenSymbols + Iterator(sym) ++ sym.allOverriddenSymbols } typeCheck(expr).map { @@ -198,7 +200,7 @@ class ReplCompiler(val directory: AbstractFile) extends Compiler { else { val doc = ctx.docCtx.flatMap { docCtx => - val symbols = pickSymbols(extractSymbol(stat)) + val symbols = extractSymbols(stat) symbols.collectFirst { case sym if docCtx.docstrings.contains(sym) => docCtx.docstrings(sym).raw From 6a9e06784008a24226118133f978fe10ae540910 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Mon, 2 Jul 2018 16:53:53 +0200 Subject: [PATCH 4/4] Address review comments --- .../src/dotty/tools/repl/ReplCompiler.scala | 19 +- compiler/test/dotty/tools/repl/DocTests.scala | 243 +++++++----------- 2 files changed, 104 insertions(+), 158 deletions(-) diff --git a/compiler/src/dotty/tools/repl/ReplCompiler.scala b/compiler/src/dotty/tools/repl/ReplCompiler.scala index 262602f2fbd3..567b3ad2daea 100644 --- a/compiler/src/dotty/tools/repl/ReplCompiler.scala +++ b/compiler/src/dotty/tools/repl/ReplCompiler.scala @@ -194,20 +194,17 @@ class ReplCompiler(val directory: AbstractFile) extends Compiler { } typeCheck(expr).map { - case v @ ValDef(_, _, Block(stats, _)) if stats.nonEmpty => + case ValDef(_, _, Block(stats, _)) if stats.nonEmpty => val stat = stats.last.asInstanceOf[tpd.Tree] if (stat.tpe.isError) stat.tpe.show else { - val doc = - ctx.docCtx.flatMap { docCtx => - val symbols = extractSymbols(stat) - symbols.collectFirst { - case sym if docCtx.docstrings.contains(sym) => - docCtx.docstrings(sym).raw - } - } - - doc.getOrElse(s"// No doc for `${expr}`") + val docCtx = ctx.docCtx.get + val symbols = extractSymbols(stat) + val doc = symbols.collectFirst { + case sym if docCtx.docstrings.contains(sym) => + docCtx.docstrings(sym).raw + } + doc.getOrElse(s"// No doc for `${expr}`") } case _ => diff --git a/compiler/test/dotty/tools/repl/DocTests.scala b/compiler/test/dotty/tools/repl/DocTests.scala index 036d91d959e6..4cf8e03bb31e 100644 --- a/compiler/test/dotty/tools/repl/DocTests.scala +++ b/compiler/test/dotty/tools/repl/DocTests.scala @@ -5,205 +5,154 @@ import org.junit.Test import org.junit.Assert.assertEquals class DocTests extends ReplTest { + @Test def docOfDef = - fromInitialState { implicit s => run("/** doc */ def foo = 0") } - .andThen { implicit s => - storedOutput() - run(":doc foo") - assertEquals("/** doc */", storedOutput().trim) + eval("/** doc */ def foo = 0").andThen { implicit s => + assertEquals("/** doc */", doc("foo")) } @Test def docOfVal = - fromInitialState { implicit s => run("/** doc */ val foo = 0") } - .andThen { implicit s => - storedOutput() - run(":doc foo") - assertEquals("/** doc */", storedOutput().trim) + eval("/** doc */ val foo = 0").andThen { implicit s => + assertEquals("/** doc */", doc("foo")) } @Test def docOfObject = - fromInitialState { implicit s => run("/** doc */ object Foo") } - .andThen { implicit s => - storedOutput() - run(":doc Foo") - assertEquals("/** doc */", storedOutput().trim) + eval("/** doc */ object Foo").andThen { implicit s => + assertEquals("/** doc */", doc("Foo")) } @Test def docOfClass = - fromInitialState { implicit s => run("/** doc */ class Foo") } - .andThen { implicit s => - storedOutput() - run(":doc new Foo") - assertEquals("/** doc */", storedOutput().trim) + eval("/** doc */ class Foo").andThen { implicit s => + assertEquals("/** doc */", doc("new Foo")) } @Test def docOfTrait = - fromInitialState { implicit s => run("/** doc */ trait Foo") } - .andThen { implicit s => - storedOutput() - run(":doc new Foo") - assertEquals("/** doc */", storedOutput().trim) + eval("/** doc */ trait Foo").andThen { implicit s => + assertEquals("/** doc */", doc("new Foo")) } @Test def docOfDefInObject = - fromInitialState { implicit s => run("object O { /** doc */ def foo = 0 }") } - .andThen { implicit s => - storedOutput() - run(":doc O.foo") - assertEquals("/** doc */", storedOutput().trim) + eval("object O { /** doc */ def foo = 0 }").andThen { implicit s => + assertEquals("/** doc */", doc("O.foo")) } @Test def docOfValInObject = - fromInitialState { implicit s => run("object O { /** doc */ val foo = 0 }") } - .andThen { implicit s => - storedOutput() - run(":doc O.foo") - assertEquals("/** doc */", storedOutput().trim) + eval("object O { /** doc */ val foo = 0 }").andThen { implicit s => + assertEquals("/** doc */", doc("O.foo")) } @Test def docOfObjectInObject = - fromInitialState { implicit s => run("object O { /** doc */ object Foo }") } - .andThen { implicit s => - storedOutput() - run(":doc O.Foo") - assertEquals("/** doc */", storedOutput().trim) + eval("object O { /** doc */ object Foo }").andThen { implicit s => + assertEquals("/** doc */", doc("O.Foo")) } @Test def docOfClassInObject = - fromInitialState { implicit s => run("object O { /** doc */ class Foo }") } - .andThen { implicit s => - storedOutput() - run(":doc new O.Foo") - assertEquals("/** doc */", storedOutput().trim) + eval("object O { /** doc */ class Foo }").andThen { implicit s => + assertEquals("/** doc */", doc("new O.Foo")) } @Test def docOfTraitInObject = - fromInitialState { implicit s => run("object O { /** doc */ trait Foo }") } - .andThen { implicit s => - storedOutput() - run(":doc new O.Foo") - assertEquals("/** doc */", storedOutput().trim) + eval("object O { /** doc */ trait Foo }").andThen { implicit s => + assertEquals("/** doc */", doc("new O.Foo")) } - @Test def docOfDetInClass = - fromInitialState { implicit s => run("class C { /** doc */ def foo = 0 }") } - .andThen { implicit s => run("val c = new C") } - .andThen { implicit s => - storedOutput() - run(":doc c.foo") - assertEquals("/** doc */", storedOutput().trim) + @Test def docOfDefInClass = + eval( + """class C { /** doc */ def foo = 0 } + |val c = new C + """.stripMargin).andThen { implicit s => + assertEquals("/** doc */", doc("c.foo")) } - @Test def docOfVatInClass = - fromInitialState { implicit s => run("class C { /** doc */ val foo = 0 }") } - .andThen { implicit s => run("val c = new C") } - .andThen { implicit s => - storedOutput() - run(":doc c.foo") - assertEquals("/** doc */", storedOutput().trim) + @Test def docOfValInClass = + eval( + """class C { /** doc */ val foo = 0 } + |val c = new C + """.stripMargin).andThen { implicit s => + assertEquals("/** doc */", doc("c.foo")) } @Test def docOfObjectInClass = - fromInitialState { implicit s => run("class C { /** doc */ object Foo }") } - .andThen { implicit s => run("val c = new C") } - .andThen { implicit s => - storedOutput() - run(":doc c.Foo") - assertEquals("/** doc */", storedOutput().trim) + eval( + """class C { /** doc */ object Foo } + |val c = new C + """.stripMargin).andThen { implicit s => + assertEquals("/** doc */", doc("c.Foo")) } @Test def docOfClassInClass = - fromInitialState { implicit s => run("class C { /** doc */ class Foo }") } - .andThen { implicit s => run("val c = new C") } - .andThen { implicit s => - storedOutput() - run(":doc new c.Foo") - assertEquals("/** doc */", storedOutput().trim) + eval( + """class C { /** doc */ class Foo } + |val c = new C + """.stripMargin).andThen { implicit s => + assertEquals("/** doc */", doc("new c.Foo")) } @Test def docOfTraitInClass = - fromInitialState { implicit s => run("class C { /** doc */ trait Foo }") } - .andThen { implicit s => run("val c = new C") } - .andThen { implicit s => - storedOutput() - run(":doc new c.Foo") - assertEquals("/** doc */", storedOutput().trim) + eval( + """class C { /** doc */ trait Foo } + |val c = new C + """.stripMargin).andThen { implicit s => + assertEquals("/** doc */", doc("new c.Foo")) } @Test def docOfOverloadedDef = - fromInitialState { implicit s => - run("""object O { - |/** doc0 */ def foo(x: Int) = x - |/** doc1 */ def foo(x: String) = x - |}""".stripMargin) - } - .andThen { implicit s => - storedOutput() - run(":doc O.foo(_: Int)") - assertEquals("/** doc0 */", storedOutput().trim) - s - } - .andThen { implicit s => - run(":doc O.foo(_: String)") - assertEquals("/** doc1 */", storedOutput().trim) + eval( + """object O { + | /** doc0 */ def foo(x: Int) = x + | /** doc1 */ def foo(x: String) = x + |} + """.stripMargin).andThen { implicit s => + assertEquals("/** doc0 */", doc("O.foo(_: Int)")) + assertEquals("/** doc1 */", doc("O.foo(_: String)")) } @Test def docOfInherited = - fromInitialState { implicit s => run("class C { /** doc */ def foo = 0 }") } - .andThen { implicit s => run("object O extends C") } - .andThen { implicit s => - storedOutput() - run(":doc O.foo") - assertEquals("/** doc */", storedOutput().trim) + eval( + """class C { /** doc */ def foo = 0 } + |object O extends C + """.stripMargin).andThen { implicit s => + assertEquals("/** doc */", doc("O.foo")) } @Test def docOfOverride = - fromInitialState { implicit s => - run("""abstract class A { - |/** doc0 */ def foo(x: Int): Int = x + 1 - |/** doc1 */ def foo(x: String): String = x + "foo" - |}""".stripMargin) - } - .andThen { implicit s => - run("""object O extends A { - | override def foo(x: Int): Int = x - | /** overridden doc */ override def foo(x: String): String = x - |}""".stripMargin) - } - .andThen { implicit s => - storedOutput() - run(":doc O.foo(_: Int)") - assertEquals("/** doc0 */", storedOutput().trim) - s - } - .andThen { implicit s => - run(":doc O.foo(_: String)") - assertEquals("/** overridden doc */", storedOutput().trim) + eval( + """abstract class A { + | /** doc0 */ def foo(x: Int): Int = x + 1 + | /** doc1 */ def foo(x: String): String = x + "foo" + |} + |object O extends A { + | override def foo(x: Int): Int = x + | /** overridden doc */ override def foo(x: String): String = x + |} + """.stripMargin).andThen { implicit s => + assertEquals("/** doc0 */", doc("O.foo(_: Int)")) + assertEquals("/** overridden doc */", doc("O.foo(_: String)")) } @Test def docOfOverrideObject = - fromInitialState { implicit s => - run("""abstract class A { - | abstract class Companion { /** doc0 */ def bar: Int } - | /** companion */ def foo: Companion - |}""".stripMargin) - .andThen { implicit s => - run("""object O extends A { - | override object foo extends Companion { - | override def bar: Int = 0 - | } - |}""".stripMargin) - } - .andThen { implicit s => - storedOutput() - run(":doc O.foo") - assertEquals("/** companion */", storedOutput().trim) - s - } - .andThen { implicit s => - run(":doc O.foo.bar") - assertEquals("/** doc0 */", storedOutput().trim) - } - } + eval( + """abstract class A { + | abstract class Companion { /** doc0 */ def bar: Int } + | /** companion */ def foo: Companion + |} + |object O extends A { + | override object foo extends Companion { + | override def bar: Int = 0 + | } + |} + """.stripMargin).andThen { implicit s => + assertEquals("/** companion */", doc("O.foo")) + assertEquals("/** doc0 */", doc("O.foo.bar")) + } + + private def eval(code: String): State = + fromInitialState { implicit s => run(code) } + + private def doc(expr: String)(implicit s: State): String = { + storedOutput() + run(s":doc $expr") + storedOutput().trim + } }