此文档页面特定于 Scala 3,并且可能涵盖 Scala 2 中不可用的新概念。除非另有说明,此页面中的所有代码示例均假定你使用的是 Scala 3。
文档的主要功能是帮助人们正确理解和使用项目。有时项目的某一部分需要几句话来展示其用法,但每个开发人员都知道,有时描述是不够的,没有什么比一个好的示例更好的了。
在文档中提供示例的一种便捷方法是创建代码段来演示给定功能的用法。代码段的问题在于,它们需要随着项目开发而更新。有时项目某一部分的更改可能会破坏其他部分中的示例。代码段的数量以及自编写代码段以来经过的时间使得无法记住需要修复代码段的所有位置。一段时间后,你意识到你的文档一团糟,你需要浏览所有示例并重写它们。
许多 Scala 2 项目使用类型检查标记文档,其中包含 tut 或 mdoc。几乎每个人都至少听说过这些工具。由于它们非常有用,并且 Scala 社区已成功采用它们,因此我们计划将 tut 和 mdoc 的功能合并到编译器中,以便将其与 Scaladoc 一起开箱即用。
开始
默认情况下,所有代码段的代码段验证均已关闭。可以通过向 Scaladoc 添加以下参数来打开它
-snippet-compiler:compile
例如,在 sbt 中,配置如下所示
Compile / doc / scalacOptions ++= Seq("-snippet-compiler:compile")
此选项为项目文档中的所有 scala
代码段启用了代码段编译器,并识别 ```scala 块中的所有代码段。目前,代码段验证在 Markdown 中编写的文档字符串和静态网站中均有效。
如果您正在启动一个新项目,此配置对您来说应该足够了。但是,如果您要迁移现有项目,您可能希望禁用某些当前无法更新的代码段的编译。
为此,请将 nocompile
标志直接添加到 scala
代码段
```scala sc:nocompile
// under the hood `map` is transformed into
List(1).map( _ + 1)(<implicits>)
```
但是,有时编译失败是一种预期行为,例如,故意演示错误。对于这种情况,我们公开了一个标志 fail
,它引入了我们的一个功能:断言编译错误。
```scala sc:fail
List(1,2,3).toMap
```
有关更全面的解释和更复杂的配置(例如基于路径的标志设置),请参阅 高级配置 部分。
功能概述
断言编译错误
Scala 是一种静态类型编程语言。有时,文档应该提到代码不应该编译的情况,或者作者希望提供从某些编译错误中恢复的方法。
例如,此代码
List(1,2,3).toMap
产生此输出
At 18:21:
List(1,2,3).toMap
Error: Cannot prove that Int <:< (K, V)
where: K is a type variable with constraint
V is a type variable with constraint
.
展示在编译时失败的代码的示例可能非常重要。例如,您可以展示如何保护库免受不正确代码的侵害。另一个用例是展示常见错误以及如何解决它们。考虑到这些用例,我们决定提供功能来检查标记的代码段是否未编译。
对于故意编译失败的代码段,例如以下代码段,请将 fail
标志添加到代码段
```scala sc:fail
List(1,2,3).toMap
```
代码段验证通过并在文档中显示预期的编译错误。
对于无错误编译的代码段
```scala sc:fail
List((1,2), (2,3)).toMap
```
结果输出如下所示
In static site (./docs/docs/index.md):
Error: Snippet should not compile but compiled succesfully
上下文
我们的目标是尽可能地让代码片段的行为就像它们在给定范围内(例如,在特定包中或类内)定义的那样。我们相信这会给代码片段带来自然的感觉。为了实现这一点,我们实现了一个包装机制,为每个代码片段提供了一个上下文。此预处理会自动对文档字符串中的所有代码片段执行。
例如,假设我们想要记录 collection.List
中的方法 slice
。我们想要通过将它与 drop
和 take
方法的组合进行比较来解释它的工作原理,因此使用类似这样的代码片段
slice(2, 5) == drop(2).take(3)
展示此示例是首先想到的事情之一,但正如你可能猜到的那样,如果没有上下文功能,这是无法编译的。
除了我们的主要目标之外,它还减少了代码片段的样板,因为你不需要导入同一个包的成员并实例化已记录的类。
对于那些好奇我们上下文机制如何工作的人来说,预处理后的代码片段如下所示
package scala.collection
trait Snippet[A] { self: List[A] =>
slice(2,5) == drop(2).take(3)
}
隐藏代码
尽管具有上面描述的上下文功能,但有时作者需要向范围提供更多元素。然而,一方面,大量导入和必要的类的初始化可能会导致可读性下降。但另一方面,我们已经读到很多意见,人们希望能够看到整个代码。对于第二种情况,我们为代码片段引入了特殊语法,它隐藏了代码的某些片段(例如 import
语句),但也允许通过单击一次在文档中展开该代码。
示例
//{
import scala.collection.immutable.List
//}
val intList: List[Int] = List(1, 2, 3)
代码片段包含
在编写代码片段时,我们经常需要一种机制来在另一个代码片段中重用一个代码片段中的代码。例如,看看以下文档片段:
要成功编译最后一个代码片段,我们需要在范围内预先声明定义。对于此场景(可能还有更多场景),我们添加了一项新功能:代码片段包含。这允许你在另一个代码片段中重用一个代码片段中的代码,从而减少冗余并提高可维护性。
要配置此功能,只需向你想要在后面的代码块中包含的代码片段添加一个 sc-name
参数:```scala sc-name:<snippet-name>
其中 snippet-name
应在文件范围内唯一,且不能包含空格和逗号。
然后,在文档的后面代码块中,在 scala
代码段中使用 sc-compile-with
参数,该参数应“包含”前面的代码块:```scala sc-compile-with:<snippet-name>(,<snippet-name>)+
其中 snippet-name
是应包含的代码段的名称。
在我们的示例中配置此功能后,代码如下所示:
输出如下所示:
你可以指定多个包含项。请注意,指定它们的顺序定义了包含的顺序。
警告:你只能包含在目标代码段上方定义的代码段。
高级配置
通常,对所有代码段启用代码段验证并不是适当的控制级别,因为用例可能更复杂。我们为这种情况准备了我们的工具,即允许用户根据自己的需要进行调整。
可用标志
为了提供更多控制,代码段编译器公开了三个标志,让你可以更改其行为
compile
- 启用代码段检查nocompile
- 禁用代码段检查fail
- 启用代码段检查,并进行编译错误断言
基于路径的设置
为了提高灵活性,你可以仅为特定路径设置一个标志来控制项目中的所有代码段,方法是在标志前添加 <path>=
前缀。例如
-snippet-compiler:docs=compile
- 为 docs
文件中的代码段设置 compile
标志。如果 docs
是一个目录,则会为 docs
中的所有文件设置标志
此外,-snippet-compiler
选项可以通过多个设置进行控制,这些设置以逗号分隔。例如
-snippet-compiler:docs=compile,library/src=compile,library/src/scala/quoted=nocompile,library/src/scala/compiletime=fail
标志是通过最长的前缀匹配选择的,因此我们可以定义一个通用设置,然后针对更具体的路径更改该默认行为。
-snippet-compiler:compile,library/src/scala/quoted=nocompile,library/src/scala/compiletime=fail
没有路径前缀的标志(例如本例中的 compile
标志)被视为默认标志。
直接在代码片段中覆盖
CLI 参数是一种为特定文件设置标志的良好机制。但是,此方法不能用于为特定代码片段配置代码片段编译器。例如,作者想要编写一个应该失败的代码片段,以及其他应该编译的代码片段。我们再次考虑了这一点,并添加了一个功能,可以在代码片段中直接覆盖设置。这些参数位于代码片段信息部分
```scala <snippet-compiler-args>
// snippet
```
例如,要为特定代码片段配置代码片段检查,请将以下参数添加到其代码片段信息部分,其中 flag
是上面列出的可用标志之一(例如,compile
、nocompile
或 fail
)
sc:<flag>
作为一个具体示例,此代码展示了如何在单个代码片段中使用 fail
标志
```scala sc:fail
val itShouldFail: Int = List(1.1, 2, 3).head
```