Проверка фрагмента
Основная функциональность документации — помочь пользователям понять и правильно использовать проект. Иногда часть проекта нуждается в нескольких словах, чтобы показать ее использование, но бывают моменты, когда описания недостаточно, и нет ничего лучше, чем подробный пример.
Удобный способ предоставления примеров в документации — создание фрагментов кода, представляющих использование заданной функциональности. Проблема фрагментов кода в том, что одновременно с разработкой проекта их нужно обновлять. Иногда изменения в одной части проекта могут нарушить работу примеров в других частях. Количество фрагментов и количество времени, прошедшего с момента их написания, не позволяет запомнить каждое место, где нужно их исправить. Через какое-то время наступает понимание, что документация — полный бардак и нужно пройтись по всем примерам и переписать их.
Многие проекты Scala 2 используют markdown документацию с проверкой типов с помощью
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
, который представляет одну из функций: Assert compilation errors.
```scala sc:fail
List(1,2,3).toMap
```
Обзор функций
Assert compilation errors
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
Контекст
В Scaladoc внедрён механизм переноса, предоставляющий контекст для каждого фрагмента. Эта предварительная обработка выполняется автоматически для всех фрагментов в строках документации.
Например, предположим, что необходимо задокументировать метод slice
в файле collection.List
для того, чтобы объяснить, как он работает,
сравнив его с комбинацией методов 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)
Snippet includes
При написании фрагментов кода часто требуется механизм повторного использования кода из одного фрагмента в другом. Например, взгляните на следующий фрагмент документации:
Чтобы успешно скомпилировать последний фрагмент, нужно иметь ранее объявленные определения в области видимости. Для этого сценария — и, возможно, для многих других — добавлена новая функция: включение сниппета. Она позволяет повторно использовать код из одного фрагмента в другом, что снижает избыточность и повышает удобство сопровождения.
Чтобы настроить это, добавьте аргумент sc-name
к фрагменту,
который необходимо включить в более поздний блок кода: ```scala sc-name:<snippet-name>
где snippet-name
должен быть уникальным в пределах файла и не может содержать пробелы и запятые.
Затем в более позднем блоке кода в документации используйте аргумент sc-compile-with
во scala
фрагменте,
который должен "включать" предыдущий блок кода: ```scala sc-compile-with:<snippet-name>(,<snippet-name>)+
где snippet-name
- имя фрагмента, который должен быть включен.
После настройки этой функции в примере код выглядит так:
и вывод выглядит так:
Можно указать более одного включения. Обратите внимание, что порядок, в котором они указаны, определяет порядок включения.
можно включать только фрагменты, определенные над целевым фрагментом.
Расширенная конфигурация
Часто включение проверки фрагментов для всех фрагментов не является желаемым уровнем контроля, поскольку варианты использования могут быть более сложными. Для таких ситуаций подготовлен инструмент, чтобы пользователи могли настроить его под свои нужды.
Доступные флаги
Чтобы обеспечить больший контроль, компилятор фрагмента предоставляет три флага, которые позволяют изменить его поведение:
compile
- включает проверку сниппетовnocompile
- отключает проверку сниппетовfail
- включает проверку сниппета с подтверждением ошибки компиляции
Настройки на основе пути
Для большей гибкости вместо установки одного флага для управления всеми сниппетами в проекте
его можно установить только для определенного пути, добавив префикс <path>=
перед флагом.
Например:
-snippet-compiler:docs=compile
- устанавливает флаг compile
для сниппетов в docs
.
Если 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
```
Ссылки: