Scaladoc

片段檢查

語言
此文件頁面專屬於 Scala 3,且可能會涵蓋 Scala 2 中沒有的新概念。除非另有說明,否則此頁面中的所有程式碼範例都假設您使用的是 Scala 3。

文件的主要功能是幫助人們正確了解和使用專案。有時,專案的一部分需要幾個字詞來顯示其用法,但每個開發人員都知道,有時候描述是不夠的,而沒有什麼比一個好的範例更棒了。

在文件中提供範例的便利方法是建立程式碼片段,以呈現特定功能的用法。程式碼片段的問題是,它們需要與專案開發同步更新。有時,專案中某個部分的變更可能會損壞其他部分的範例。片段的數量和自撰寫以來經過的時間,會讓人無法記住需要修復的所有位置。一段時間後,您會發現文件完全混亂,您需要檢閱所有範例並重新撰寫它們。

許多 Scala 2 專案使用帶有 tutmdoc 的類型檢查標記文件。幾乎所有人都聽過這些工具。由於它們非常有用,且 Scala 社群已成功採用它們,因此我們計畫將 tut 和 mdoc 的功能納入編譯器,以便在 Scaladoc 中隨附提供。

開始使用

預設情況下,所有程式碼片段驗證均已關閉。可透過將下列參數新增至 Scaladoc 來開啟

-snippet-compiler:compile

例如,在 sbt 中,組態如下所示

Compile / doc / scalacOptions ++= Seq("-snippet-compiler:compile")

此選項會為專案文件中的所有 scala 程式碼片段開啟程式碼片段編譯器,並辨識 ```scala 區塊中的所有程式碼片段。目前,程式碼片段驗證在 Markdown 編寫的 docstring 和靜態網站中均可運作。

如果您要開始新的專案,此組態對您而言應已足夠。但是,如果您要移轉現有的專案,您可能想要停用目前無法更新的某些程式碼片段的編譯。

為執行此動作,請將 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 方法。我們希望透過將其與 droptake 方法的組合進行比較來說明其運作方式,因此使用類似以下的程式片段

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 是上面列出的可用旗標之一(例如 compilenocompilefail

sc:<flag>

以下程式碼為特定範例,說明如何在個別程式碼片段中使用 fail 旗標

```scala sc:fail
val itShouldFail: Int = List(1.1, 2, 3).head
```

此頁面的貢獻者