Skip to content

Commit

Permalink
Add new mdoc:warn modifier. (#308)
Browse files Browse the repository at this point in the history
Previously, it was possible to assert that a code fence does not compile
but it was not possible to assert that a code fence compiles with a
warning. Now, it's possible to use the new `mdoc:warn` modifier to
assert that a program compiles successfully but with warnings.
  • Loading branch information
olafurpg authored Mar 19, 2020
1 parent 84218ad commit cb7d401
Show file tree
Hide file tree
Showing 12 changed files with 123 additions and 23 deletions.
33 changes: 33 additions & 0 deletions docs/modifiers.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,39 @@ val x: String = ""
> Note that `fail` does not assert that the program compiles but crashes at
> runtime. To assert runtime exceptions, use the `crash` modifier.
## `warn`

The `warn` modifier is similar to `fail` except that it asserts the code
compiles successfully but with a warning message.

````scala mdoc:mdoc
```scala mdoc:warn
List(1) match {
case Nil =>
}
```
````

The build fails when a `warn` code fence compiles without warnings.

````scala mdoc:mdoc:crash
```scala mdoc:warn
List(1) match {
case head :: tail =>
case Nil =>
}
```
````

The build also fails when a `warn` code fence fails to compile, even if the
program has a warning. Use `fail` in these cases instead.

````scala mdoc:mdoc:crash
```scala mdoc:warn
val x: Int = ""
```
````

## `crash`

The `crash` modifier asserts that the code block throws an exception at runtime
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ final class FailInstrumenter(sections: List[SectionInput], i: Int) {
} else if (section.mod.isNest) {
nest.nest()
}
if (j == i || !section.mod.isFail) {
if (j == i || !section.mod.isFailOrWarn) {
sb.println(section.input.text)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ class FenceInput(ctx: Context, baseInput: Input) {
}

private def isValid(info: Text, mod: Modifier): Boolean = {
if (mod.isFail && mod.isCrash) {
if (mod.isFailOrWarn && mod.isCrash) {
invalidCombination(info, "crash", "fail")
} else if (mod.isSilent && mod.isInvisible) {
invalidCombination(info, "silent", "invisible")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class Instrumenter(sections: List[SectionInput]) {
nest.nest()
}
sb.println("\n$doc.startSection();")
if (section.mod.isFail) {
if (section.mod.isFailOrWarn) {
sb.println(s"$$doc.startStatement(${position(section.source.pos)});")
val out = new FailInstrumenter(sections, i).instrument()
val literal = Instrumenter.stringLiteral(out)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,9 @@ class MarkdownCompiler(
out.toString()
}

def hasErrors: Boolean = sreporter.hasErrors
def hasWarnings: Boolean = sreporter.hasWarnings

def compileSources(input: Input, vreporter: Reporter, edit: TokenEditDistance): Unit = {
clearTarget()
sreporter.reset()
Expand Down
2 changes: 2 additions & 0 deletions mdoc/src/main/scala/mdoc/internal/markdown/Mod.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package mdoc.internal.markdown
sealed abstract class Mod extends Product with Serializable
object Mod {
case object Fail extends Mod
case object Warn extends Mod
case object Crash extends Mod
case object Silent extends Mod
case object Passthrough extends Mod
Expand Down Expand Up @@ -30,6 +31,7 @@ object Mod {
ResetClass,
ResetObject,
Fail,
Warn,
Crash,
Silent,
ToString,
Expand Down
8 changes: 7 additions & 1 deletion mdoc/src/main/scala/mdoc/internal/markdown/Modifier.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ import mdoc.internal.markdown.Mod._
*/
sealed abstract class Modifier(val mods: Set[Mod]) {
def isDefault: Boolean = mods.isEmpty
def isFailOrWarn: Boolean = isFail || isWarn
def isFail: Boolean = mods(Fail)
def isWarn: Boolean = mods(Warn)
def isPassthrough: Boolean = mods(Passthrough)
def isString: Boolean = this.isInstanceOf[Modifier.Str]
def isPre: Boolean = this.isInstanceOf[Modifier.Pre]
Expand All @@ -42,7 +44,11 @@ object Modifier {
}
object Fail {
def unapply(m: Modifier): Boolean =
m.isFail
m.isFailOrWarn
}
object Warn {
def unapply(m: Modifier): Boolean =
m.isWarn
}
object PrintVariable {
def unapply(m: Modifier): Boolean =
Expand Down
27 changes: 19 additions & 8 deletions mdoc/src/main/scala/mdoc/internal/markdown/Renderer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ object Renderer {
val stats = section.source.stats.lift
val input = section.source.pos.input
val totalStats = section.source.stats.length
if (section.mod.isFail) {
if (section.mod.isFailOrWarn) {
sb.print(section.input.text)
}
section.section.statements.zip(section.source.stats).zipWithIndex.foreach {
Expand All @@ -113,12 +113,12 @@ object Renderer {
previousStatement.pos.end
}
val leadingTrivia = Position.Range(input, leadingStart, pos.start)
if (!section.mod.isFail) {
if (!section.mod.isFailOrWarn) {
sb.append(leadingTrivia.text)
}
val endOfLinePosition =
Position.Range(pos.input, pos.startLine, pos.startColumn, pos.endLine, Int.MaxValue)
if (!section.mod.isFail) {
if (!section.mod.isFailOrWarn) {
sb.append(endOfLinePosition.text)
}
if (statement.out.nonEmpty) {
Expand All @@ -129,7 +129,7 @@ object Renderer {
statement.binders.zipWithIndex.foreach {
case (binder, i) =>
section.mod match {
case Modifier.Fail() =>
case Modifier.Fail() | Modifier.Warn() =>
sb.append('\n')
binder.value match {
case FailSection(instrumented, startLine, startColumn, endLine, endColumn) =>
Expand All @@ -138,11 +138,22 @@ object Renderer {
Input.String(instrumented),
section.source.pos
)
if (compiled.isEmpty) {
val tpos = new RangePosition(startLine, startColumn, endLine, endColumn)
val tpos = new RangePosition(startLine, startColumn, endLine, endColumn)
val pos = tpos.toMeta(section)
if (section.mod.isWarn && compiler.hasErrors) {
reporter.error(
tpos.toMeta(section),
s"Expected compile error but statement typechecked successfully"
pos,
s"Expected compile warnings but program failed to compile"
)
} else if (section.mod.isWarn && !compiler.hasWarnings) {
reporter.error(
pos,
s"Expected compile warnings but program compiled successfully without warnings"
)
} else if (section.mod.isFail && !compiler.hasErrors) {
reporter.error(
pos,
s"Expected compile errors but program compiled successfully without errors"
)
}
appendFreshMultiline(sb, compiled)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ abstract class BaseMarkdownSuite extends munit.FunSuite {
expected: String,
settings: Settings = baseSettings,
compat: Map[String, String] = Map.empty
): Unit = {
)(implicit loc: munit.Location): Unit = {
test(name) {
val reporter = newReporter()
val context = newContext(settings, reporter)
Expand Down
9 changes: 4 additions & 5 deletions tests/unit/src/test/scala/tests/markdown/ErrorSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,10 @@ class ErrorSuite extends BaseMarkdownSuite {
|List(1)
|```
""".stripMargin,
"""
|error: silent.md:7:1: Expected compile error but statement typechecked successfully
|List(1)
|^^^^^^^
""".stripMargin
"""|error: silent.md:7:1: Expected compile errors but program compiled successfully without errors
|List(1)
|^^^^^^^
|""".stripMargin
)
checkError(
"parse-error",
Expand Down
9 changes: 4 additions & 5 deletions tests/unit/src/test/scala/tests/markdown/FailSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,10 @@ class FailSuite extends BaseMarkdownSuite {
|1.to(2)
|```
""".stripMargin,
"""
|error: fail-success.md:3:1: Expected compile error but statement typechecked successfully
|1.to(2)
|^^^^^^^
|""".stripMargin
"""|error: fail-success.md:3:1: Expected compile errors but program compiled successfully without errors
|1.to(2)
|^^^^^^^
|""".stripMargin
)

// Compile-error causes nothing to run
Expand Down
47 changes: 47 additions & 0 deletions tests/unit/src/test/scala/tests/markdown/WarnSuite.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package tests.markdown

class WarnSuite extends BaseMarkdownSuite {

check(
"warn",
"""
|```scala mdoc:warn
|List(1) match { case Nil => }
|```
""".stripMargin,
"""|```scala
|List(1) match { case Nil => }
|// warning: match may not be exhaustive.
|// It would fail on the following input: List(_)
|// List(1) match { case Nil => }
|// ^^^^^^^
|```
|""".stripMargin
)

checkError(
"error",
"""
|```scala mdoc:warn
|val x: Int = ""
|```
""".stripMargin,
"""|error: error.md:3:1: Expected compile warnings but program failed to compile
|val x: Int = ""
|^^^^^^^^^^^^^^^
|""".stripMargin
)

checkError(
"success",
"""
|```scala mdoc:warn
|val x: Int = 42
|```
""".stripMargin,
"""|error: success.md:3:1: Expected compile warnings but program compiled successfully without warnings
|val x: Int = 42
|^^^^^^^^^^^^^^^
|""".stripMargin
)
}

0 comments on commit cb7d401

Please sign in to comment.