From 79614a45efded076947a52f3544c5a8e112e5594 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Zyba=C5=82a?= Date: Mon, 14 Dec 2020 16:08:50 +0100 Subject: [PATCH] Fix inproper rendering of HTML documentation elements. Fix Assertion Errors on non-existent positions --- scala3doc-testcases/src/tests/htmlTests.scala | 112 +++++++++++++++++- .../src/dotty/dokka/tasty/BasicSupport.scala | 5 +- .../dotty/dokka/tasty/ClassLikeSupport.scala | 8 +- .../dotty/dokka/tasty/SyntheticSupport.scala | 15 ++- .../dokka/tasty/comments/wiki/Parser.scala | 2 +- .../ScalaCommentToContentConverter.scala | 9 ++ .../src/dotty/renderers/MemberRenderer.scala | 2 +- 7 files changed, 141 insertions(+), 12 deletions(-) diff --git a/scala3doc-testcases/src/tests/htmlTests.scala b/scala3doc-testcases/src/tests/htmlTests.scala index 0132f217c197..72f331add0d9 100644 --- a/scala3doc-testcases/src/tests/htmlTests.scala +++ b/scala3doc-testcases/src/tests/htmlTests.scala @@ -26,4 +26,114 @@ package htmlTests * * */ -class HtmlTest \ No newline at end of file +class HtmlTest + +/** Implements functionality for printing Scala values on the terminal. For reading values + * use [[scala.io.StdIn$ StdIn]]. + * Also defines constants for marking up text on ANSI terminals. + * + * == Console Output == + * + * Use the print methods to output text. + * {{{ + * scala> Console.printf( + * "Today the outside temperature is a balmy %.1f°C. %<.1f°C beats the previous record of %.1f°C.\n", + * -137.0, + * -135.05) + * Today the outside temperature is a balmy -137.0°C. -137.0°C beats the previous record of -135.1°C. + * }}} + * + * == ANSI escape codes == + * Use the ANSI escape codes for colorizing console output either to STDOUT or STDERR. + * {{{ + * import Console.{GREEN, RED, RESET, YELLOW_B, UNDERLINED} + * + * object PrimeTest { + * + * def isPrime(): Unit = { + * + * val candidate = io.StdIn.readInt().ensuring(_ > 1) + * + * val prime = (2 to candidate - 1).forall(candidate % _ != 0) + * + * if (prime) + * Console.println(s"\${RESET}\${GREEN}yes\${RESET}") + * else + * Console.err.println(s"\${RESET}\${YELLOW_B}\${RED}\${UNDERLINED}NO!\${RESET}") + * } + * + * def main(args: Array[String]): Unit = isPrime() + * + * } + * }}} + * + * + * + * + * + * + * + * + *
\$ scala PrimeTest
1234567891
yes
\$ scala PrimeTest
56474
NO!
+ * + * == IO redefinition == + * + * Use IO redefinition to temporarily swap in a different set of input and/or output streams. In this example the stream based + * method above is wrapped into a function. + * + * {{{ + * import java.io.{ByteArrayOutputStream, StringReader} + * + * object FunctionalPrimeTest { + * + * def isPrime(candidate: Int): Boolean = { + * + * val input = new StringReader(s"\$candidate\n") + * val outCapture = new ByteArrayOutputStream + * val errCapture = new ByteArrayOutputStream + * + * Console.withIn(input) { + * Console.withOut(outCapture) { + * Console.withErr(errCapture) { + * PrimeTest.isPrime() + * } + * } + * } + * + * if (outCapture.toByteArray.nonEmpty) // "yes" + * true + * else if (errCapture.toByteArray.nonEmpty) // "NO!" + * false + * else throw new IllegalArgumentException(candidate.toString) + * } + * + * def main(args: Array[String]): Unit = { + * val primes = (2 to 50) filter (isPrime) + * println(s"First primes: \$primes") + * } + * + * } + * }}} + * + * + * + * + * + *
\$ scala FunctionalPrimeTest
First primes: Vector(2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47)
+ * + * @syntax wiki + * @groupname console-output Console Output + * @groupprio console-output 30 + * @groupdesc console-output These methods provide output via the console. + * + * @groupname io-default IO Defaults + * @groupprio io-default 50 + * @groupdesc io-default These values provide direct access to the standard IO channels + * + * @groupname io-redefinition IO Redefinition + * @groupprio io-redefinition 60 + * @groupdesc io-redefinition These methods allow substituting alternative streams for the duration of + * a body of code. Threadsafe by virtue of [[scala.util.DynamicVariable]]. + * + */ +object Console \ No newline at end of file diff --git a/scala3doc/src/dotty/dokka/tasty/BasicSupport.scala b/scala3doc/src/dotty/dokka/tasty/BasicSupport.scala index e157b9fbc830..d2713bb7e7fb 100644 --- a/scala3doc/src/dotty/dokka/tasty/BasicSupport.scala +++ b/scala3doc/src/dotty/dokka/tasty/BasicSupport.scala @@ -6,6 +6,7 @@ import collection.JavaConverters._ import dotty.dokka._ import dotty.dokka.model.api.Annotation import dotty.dokka.model.api.TastyDocumentableSource +import scala.quoted._ trait BasicSupport: self: TastyParser => @@ -36,8 +37,8 @@ trait BasicSupport: def documentation2 = sym.docstring.map(preparseComment(_, sym.tree)) - def source = - val path = Some(sym.pos.get.sourceFile.jpath).filter(_ != null).map(_.toAbsolutePath).map(_.toString) + def source(using Quotes) = + val path = sym.pos.filter(isValidPos(_)).map(_.sourceFile.jpath).filter(_ != null).map(_.toAbsolutePath).map(_.toString) path.map(TastyDocumentableSource(_, sym.pos.get.startLine)) def getAnnotations(): List[Annotation] = diff --git a/scala3doc/src/dotty/dokka/tasty/ClassLikeSupport.scala b/scala3doc/src/dotty/dokka/tasty/ClassLikeSupport.scala index d430b020c068..d6a1417aeb6f 100644 --- a/scala3doc/src/dotty/dokka/tasty/ClassLikeSupport.scala +++ b/scala3doc/src/dotty/dokka/tasty/ClassLikeSupport.scala @@ -103,7 +103,7 @@ trait ClassLikeSupport: kindForClasslike(classDef), classDef.symbol.getAnnotations(), selfSiangture, - classDef.symbol.source, + classDef.symbol.source(using qctx), graph = graph ), compositeExt @@ -366,7 +366,7 @@ trait ClassLikeSupport: methodKind, methodSymbol.getAnnotations(), method.returnTpt.dokkaType.asSignature, - methodSymbol.source, + methodSymbol.source(using qctx), origin ) ) @@ -420,7 +420,7 @@ trait ClassLikeSupport: Kind.Type(!isTreeAbstract(typeDef.rhs), typeDef.symbol.isOpaque, generics), typeDef.symbol.getAnnotations(), tpeTree.dokkaType.asSignature, - typeDef.symbol.source + typeDef.symbol.source(using qctx) ) ) @@ -438,7 +438,7 @@ trait ClassLikeSupport: kind, valDef.symbol.getAnnotations(), valDef.tpt.tpe.dokkaType.asSignature, - valDef.symbol.source + valDef.symbol.source(using qctx) ) ) diff --git a/scala3doc/src/dotty/dokka/tasty/SyntheticSupport.scala b/scala3doc/src/dotty/dokka/tasty/SyntheticSupport.scala index d2a0dc6c58e2..7e923b5007a9 100644 --- a/scala3doc/src/dotty/dokka/tasty/SyntheticSupport.scala +++ b/scala3doc/src/dotty/dokka/tasty/SyntheticSupport.scala @@ -37,12 +37,12 @@ trait SyntheticsSupport: def getAllMembers: List[Symbol] = hackGetAllMembers(using qctx)(s) + def isValidPos(pos: Position) = + if hackExists(using qctx)(pos) then pos.start != pos.end else false + def isSyntheticField(c: Symbol) = c.flags.is(Flags.CaseAccessor) || (c.flags.is(Flags.Module) && !c.flags.is(Flags.Given)) - def isValidPos(pos: Position) = - pos.start != pos.end - def constructorWithoutParamLists(c: ClassDef): Boolean = !isValidPos(c.constructor.pos) || { val end = c.constructor.pos.end @@ -88,6 +88,15 @@ trait SyntheticsSupport: baseTypes.asInstanceOf[List[(Symbol, TypeRepr)]] } + def hackExists(using Quotes)(rpos: qctx.reflect.Position) = { + import qctx.reflect._ + import dotty.tools.dotc + import dotty.tools.dotc.util.Spans._ + given dotc.core.Contexts.Context = qctx.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx + val pos = rpos.asInstanceOf[dotc.util.SourcePosition] + pos.exists + } + def getSupertypes(using Quotes)(c: ClassDef) = hackGetSupertypes(c).tail def typeForClass(c: ClassDef): TypeRepr = diff --git a/scala3doc/src/dotty/dokka/tasty/comments/wiki/Parser.scala b/scala3doc/src/dotty/dokka/tasty/comments/wiki/Parser.scala index caf51e1e69d9..94cda29c05a7 100644 --- a/scala3doc/src/dotty/dokka/tasty/comments/wiki/Parser.scala +++ b/scala3doc/src/dotty/dokka/tasty/comments/wiki/Parser.scala @@ -180,7 +180,7 @@ final class Parser( stack.length > 0 && char != endOfText }) do {} - list mkString "" + list mkString } def getInline(isInlineEnd: => Boolean): Inline = { diff --git a/scala3doc/src/dotty/dokka/transformers/ScalaCommentToContentConverter.scala b/scala3doc/src/dotty/dokka/transformers/ScalaCommentToContentConverter.scala index ce2db16b5c7c..33b873fa8732 100644 --- a/scala3doc/src/dotty/dokka/transformers/ScalaCommentToContentConverter.scala +++ b/scala3doc/src/dotty/dokka/transformers/ScalaCommentToContentConverter.scala @@ -17,6 +17,15 @@ object ScalaCommentToContentConverter extends DocTagToContentConverter { styles: JSet[? <: Style], extra: PropertyContainer[ContentNode] ): JList[ContentNode] = docTag match { + case p: P => + val group = super.buildContent(p, dci, sourceSets, styles, extra).get(0).asInstanceOf[ContentGroup] + List(group.copy( + group.getChildren, + group.getDci, + group.getSourceSets, + Set(TextStyle.Block).asJava, + group.getExtra + )).asJava case docTag: A => val superRes = super.buildContent(docTag, dci, sourceSets, styles, extra).get(0) val res = superRes.withNewExtras(superRes.getExtra plus ExtraLinkAttributes( diff --git a/scala3doc/src/dotty/renderers/MemberRenderer.scala b/scala3doc/src/dotty/renderers/MemberRenderer.scala index 75fab3b7a182..95f7eef82350 100644 --- a/scala3doc/src/dotty/renderers/MemberRenderer.scala +++ b/scala3doc/src/dotty/renderers/MemberRenderer.scala @@ -11,8 +11,8 @@ import dotty.dokka.translators.FilterAttributes class MemberRenderer(signatureRenderer: SignatureRenderer, buildNode: ContentNode => String)(using DocContext): - private val converter = new DocTagToContentConverter() import signatureRenderer._ + private val converter = ScalaCommentToContentConverter def renderDocPart(d: DocPart): AppliedTag = val sb = StringBuilder()