Skip to content

Commit

Permalink
Merge pull request #166 from olafurpg/compile-only
Browse files Browse the repository at this point in the history
Implement `compile-only` modifier for JVM and Scala.js.
  • Loading branch information
olafurpg authored May 4, 2019
2 parents 94451f9 + 20698b7 commit bc4e779
Show file tree
Hide file tree
Showing 15 changed files with 231 additions and 15 deletions.
1 change: 1 addition & 0 deletions .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
version=2.0.0-RC7
maxColumn = 100
project.git=true
align = none
Expand Down
Binary file modified bin/scalafmt
Binary file not shown.
17 changes: 17 additions & 0 deletions docs/js.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,23 @@ setInterval(() => {
}, 1000)
```

### `:compile-only`

Use `:compile-only` to validate that a code example compiles successfully
without evaluating it at runtime.

````md
```scala mdoc:js:compile-only
org.scalajs.dom.window.setInterval(() => {
println("I'm only compiled, never executed")
}, 1000)
```
````

Note that `compile-only` blocks do not automatically have access to a `node` DOM
element. Create a leading code fence with `shared:invisible` to expose a hidden
`node` instance in the scope of a `compile-only` code block.

## Loading HTML

By default, the `node` variable points to an empty div element. Prefix the code
Expand Down
12 changes: 12 additions & 0 deletions docs/modifiers.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,18 @@ List("with quotes")
```
````

## `compile-only`

The `compile-only` modifier ensures the code example compiles without evaluating
the program at runtime. This can be helpful to demonstrate code examples that
perform side-effects.

````scala mdoc:mdoc
```scala mdoc:compile-only
val name = scala.io.StdIn.readLine("Enter your name: ")
```
````

## `scastie`

The `scastie` modifier transforms a Scala code block into a
Expand Down
8 changes: 7 additions & 1 deletion mdoc-js/src/main/scala/mdoc/modifiers/JsModifier.scala
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,12 @@ class JsModifier extends mdoc.PreModifier {
val code: String =
if (mods.isShared) {
input.text
} else if (mods.isCompileOnly) {
new CodeBuilder()
.println(s"""object ${gensym.fresh("compile")} {""")
.println(input.text)
.println("}")
.toString
} else {
new CodeBuilder()
.println(s""" @_root_.scala.scalajs.js.annotation.JSExportTopLevel("$id") """)
Expand All @@ -213,7 +219,7 @@ class JsModifier extends mdoc.PreModifier {
runs += code
new CodeBuilder()
.printlnIf(!mods.isInvisible, s"```scala\n${input.text}\n```")
.printlnIf(!mods.isShared, s"""<div id="$id" data-mdoc-js>$body</div>""")
.printlnIf(mods.isEntrypoint, s"""<div id="$id" data-mdoc-js>$body</div>""")
.toString
}
}
19 changes: 16 additions & 3 deletions mdoc-js/src/main/scala/mdoc/modifiers/JsMods.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import mdoc.internal.pos.PositionSyntax._
import scala.annotation.tailrec
import scala.meta.inputs.Input

class JsMods private (mods: Set[String]) {
class JsMods private (val mods: Set[String]) {
def isShared: Boolean = mods("shared")
def isInvisible: Boolean = mods("invisible")
def isCompileOnly: Boolean = mods("compile-only")
def isEntrypoint: Boolean = !isShared && !isCompileOnly
}

object JsMods {
val all = Set("shared", "invisible")
val all = Set("shared", "invisible", "compile-only")
def parse(info: Input, reporter: Reporter): Option[JsMods] = {
val text = info.text
@tailrec def loop(from: Int, accum: Set[String]): Option[Set[String]] = {
Expand All @@ -34,6 +36,17 @@ object JsMods {
}
}
}
loop(0, Set.empty).map(new JsMods(_))
loop(0, Set.empty).map(new JsMods(_)).flatMap { mods =>
if (mods.isCompileOnly && mods.mods.size > 1) {
val others = (mods.mods - "compile-only").mkString(", ")
reporter.error(
info.toPosition.addStart(0),
s"compile-only cannot be used in combination with $others"
)
None
} else {
Some(mods)
}
}
}
}
18 changes: 9 additions & 9 deletions mdoc-sbt/src/test/scala/tests/RelativizeSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class RelativizeSuite extends FunSuite with DiffAssertions {
"<body>",
"</body>",
"<body></body>",
"</html>",
"</html>"
)
val obtained = StringFS
.asString(root)
Expand All @@ -44,7 +44,7 @@ class RelativizeSuite extends FunSuite with DiffAssertions {
"""
|/index.html
|<a href="docs/about.html"></a>
|""".stripMargin,
|""".stripMargin
)

check(
Expand All @@ -56,7 +56,7 @@ class RelativizeSuite extends FunSuite with DiffAssertions {
"""
|/index.html
|<img src="docs/about.html">
|""".stripMargin,
|""".stripMargin
)

check(
Expand All @@ -68,7 +68,7 @@ class RelativizeSuite extends FunSuite with DiffAssertions {
"""
|/index.html
|<script src="myscript.js"></script>
|""".stripMargin,
|""".stripMargin
)

check(
Expand All @@ -80,7 +80,7 @@ class RelativizeSuite extends FunSuite with DiffAssertions {
"""
|/index.html
|<link href="docs/about.html">
|""".stripMargin,
|""".stripMargin
)

check(
Expand All @@ -97,7 +97,7 @@ class RelativizeSuite extends FunSuite with DiffAssertions {
|<a href="../index.html"></a>
|/index.html
|<a href="docs/about.html"></a>
|""".stripMargin,
|""".stripMargin
)

check(
Expand All @@ -111,7 +111,7 @@ class RelativizeSuite extends FunSuite with DiffAssertions {
|/index.html
|<a href="index.html#header"></a>
|<a href="docs/about.html#header"></a>
|""".stripMargin,
|""".stripMargin
)

check(
Expand All @@ -123,7 +123,7 @@ class RelativizeSuite extends FunSuite with DiffAssertions {
"""
|/index.html
|<a href="https://cdnjs.cloudflare.com"></a>
|""".stripMargin,
|""".stripMargin
)

check(
Expand All @@ -145,7 +145,7 @@ class RelativizeSuite extends FunSuite with DiffAssertions {
|<a href="../users/index.html"></a>
|/users/index.html
|Users
|""".stripMargin,
|""".stripMargin
)

}
12 changes: 12 additions & 0 deletions mdoc/src/main/scala/mdoc/internal/markdown/BlockInput.scala
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,18 @@ class BlockInput(ctx: Context, baseInput: Input) {
invalidCombination(block, "crash", "fail")
} else if (mod.isSilent && mod.isInvisible) {
invalidCombination(block, "silent", "invisible")
} else if (mod.isCompileOnly) {
val others = mod.mods - Mod.CompileOnly
if (others.isEmpty) {
true
} else {
val all = others.map(_.toString.toLowerCase).mkString(", ")
invalid(
block,
s"""compile-only cannot be used in combination with $all"""
)
false
}
} else {
true
}
Expand Down
8 changes: 8 additions & 0 deletions mdoc/src/main/scala/mdoc/internal/markdown/Instrumenter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ class Instrumenter(sections: List[SectionInput]) {
.append(");")
printBinder(binder, section.source.pos)
sb.println("\n$doc.endStatement();")
} else if (section.mod.isCompileOnly) {
section.source.stats.foreach { stat =>
sb.println(s"$$doc.startStatement(${position(stat.pos)});")
sb.println("\n$doc.endStatement();")
}
sb.println(s"""object ${gensym.fresh("compile")} {""")
sb.println(section.source.pos.text)
sb.println("\n}")
} else {
section.source.stats.foreach { stat =>
sb.println(s"$$doc.startStatement(${position(stat.pos)});")
Expand Down
4 changes: 4 additions & 0 deletions mdoc/src/main/scala/mdoc/internal/markdown/Mod.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ object Mod {
case object Silent extends Mod
case object Passthrough extends Mod
case object Invisible extends Mod
case object CompileOnly extends Mod {
override def toString: String = "compile-only"
}
case object Reset extends Mod
case object ResetClass extends Mod {
override def toString: String = "reset-class"
Expand All @@ -18,6 +21,7 @@ object Mod {
def all: List[Mod] = List(
Passthrough,
Invisible,
CompileOnly,
Reset,
ResetClass,
Fail,
Expand Down
5 changes: 3 additions & 2 deletions mdoc/src/main/scala/mdoc/internal/markdown/Modifier.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import mdoc.internal.markdown.Mod._
*
* Currently, only supports parsing one modifier per code block.
*/
sealed abstract class Modifier(mods: Set[Mod]) {
sealed abstract class Modifier(val mods: Set[Mod]) {
def isDefault: Boolean = mods.isEmpty
def isFail: Boolean = mods(Fail)
def isPassthrough: Boolean = mods(Passthrough)
Expand All @@ -24,6 +24,7 @@ sealed abstract class Modifier(mods: Set[Mod]) {
def isCrash: Boolean = mods(Crash)
def isSilent: Boolean = mods(Silent)
def isInvisible: Boolean = mods(Invisible)
def isCompileOnly: Boolean = mods(CompileOnly)
def isReset: Boolean = mods(Reset) || isResetClass
def isResetClass: Boolean = mods(ResetClass)
def isToString: Boolean = mods(ToString)
Expand Down Expand Up @@ -57,7 +58,7 @@ object Modifier {
}
}

case class Builtin(mods: Set[Mod]) extends Modifier(mods)
case class Builtin(override val mods: Set[Mod]) extends Modifier(mods)
case class Str(mod: StringModifier, info: String) extends Modifier(Set.empty)
case class Post(mod: mdoc.PostModifier, info: String) extends Modifier(Set.empty)
case class Pre(mod: mdoc.PreModifier, info: String) extends Modifier(Set.empty)
Expand Down
82 changes: 82 additions & 0 deletions tests/unit/src/test/scala/tests/markdown/CompileOnlySuite.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package tests.markdown

class CompileOnlySuite extends BaseMarkdownSuite {
check(
"compile-only",
"""
|```scala mdoc:compile-only
|println(System.in.read())
|```
""".stripMargin,
"""|```scala
|println(System.in.read())
|```
""".stripMargin
)

check(
"reuse",
"""
|```scala mdoc
|val message = "Enter: "
|```
|```scala mdoc:compile-only
|println(message)
|println(System.in.read())
|```
|```scala mdoc
|println(message)
|```
""".stripMargin,
// assert it's possible to reference variables from non-compile-only blocks
"""|```scala
|val message = "Enter: "
|// message: String = "Enter: "
|```
|
|```scala
|println(message)
|println(System.in.read())
|```
|
|```scala
|println(message)
|// Enter:
|```
""".stripMargin
)

checkError(
"no-reuse",
"""
|```scala mdoc:compile-only
|val message = "Enter: "
|```
|```scala mdoc
|println(message)
|```
""".stripMargin,
// assert it's not possible to reference variables from compile-only blocks
"""|error: no-reuse.md:6:9: not found: value message
|println(message)
| ^^^^^^^
""".stripMargin
)

checkError(
"error",
"""
|```scala mdoc:compile-only
|val x: String = 42
|```
""".stripMargin,
// Validate that compile errors are reported and fail the build.
"""|error: error.md:3:17: type mismatch;
| found : Int(42)
| required: String
|val x: String = 42
| ^^
""".stripMargin
)

}
7 changes: 7 additions & 0 deletions tests/unit/src/test/scala/tests/markdown/JsModsSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,11 @@ class JsModsSuite extends BaseMarkdownSuite {
| ^^^
""".stripMargin
)
checkError(
"compile-only:invisible",
"""|error: <input>:1:1: compile-only cannot be used in combination with invisible
|compile-only:invisible
|^^^^^^^^^^^^^^^^^^^^^^
""".stripMargin
)
}
28 changes: 28 additions & 0 deletions tests/unit/src/test/scala/tests/markdown/JsSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,34 @@ class JsSuite extends BaseMarkdownSuite {
""".stripMargin
)

check(
"compile-only",
"""
|```scala mdoc:compile-only
|println(42)
|```
""".stripMargin,
"""|```scala
|println(42)
|```
""".stripMargin
)

checkError(
"compile-only-error",
"""
|```scala mdoc:compile-only
|val x: String = 42
|```
""".stripMargin,
"""|error: compile-only-error.md:3:17: type mismatch;
| found : Int(42)
| required: String
|val x: String = 42
| ^^
""".stripMargin
)

// It's easy to mess up stripMargin multiline strings when generating code with strings.
check(
"stripMargin",
Expand Down
Loading

0 comments on commit bc4e779

Please sign in to comment.