openjdk.ruOpenJDK на русском

JEP 467: Markdown Documentation Comments

Комментарии документации в формате Markdown

ОтветственныйJonathan Gibbons
ТипFeature
ОбластьSE
СтатусClosed / Delivered
Выпуск23
Компонентtools / javadoc(tool)
Обсуждениеjavadoc dash dev at openjdk dot org
РецензентыRon Pressler
ОдобренPaul Sandoz
Создан2023/09/11 17:45
Обновлён2025/05/05 21:09
Задача8316039

Аннотация

Разрешить писать комментарии документации JavaDoc на Markdown, а не только на смеси HTML и тегов JavaDoc @.

Цели

  • Упростить написание комментариев API-документации и их чтение в исходном коде: добавить возможность использовать в комментариях документации синтаксис Markdown наряду с элементами HTML и тегами JavaDoc.

  • Не ухудшить интерпретацию существующих комментариев документации.

  • Расширить Compiler Tree API, чтобы другие инструменты, которые анализируют комментарии документации, могли обрабатывать содержимое на Markdown в этих комментариях.

Что не является целью

  • Автоматическое преобразование существующих комментариев документации в синтаксис Markdown не является целью.

Мотивация

Комментарии документации — это комментарии особого вида в исходном коде. Они стоят рядом с объявлениями, которые документируют. В комментариях документации в исходном коде на Java текст размечается сочетанием HTML и собственных тегов JavaDoc.

В 1995 году выбор HTML в качестве языка разметки был разумным. HTML — мощный стандартизированный язык, и в то время он был очень популярен. Как язык разметки, который обрабатывают веб-браузеры, он и сегодня не менее популярен. Но с 1995 года HTML стал гораздо менее популярен как разметка, которую люди пишут вручную: писать его утомительно, а читать трудно. Сейчас его чаще генерируют из другого языка разметки, который лучше подходит для людей. Раз HTML утомительно писать, то и хорошо отформатированные комментарии документации писать утомительно. Это ещё утомительнее из-за того, что многие начинающие разработчики плохо владеют HTML: люди всё реже пишут на нём вручную.

Встроенные теги JavaDoc, например {@link} и {@code}, тоже громоздки и ещё менее знакомы разработчикам: автору часто приходится смотреть в документации, как их использовать. Недавний анализ комментариев документации в исходном коде JDK показал, что более 95 % использований встроенных тегов приходится на фрагменты кода и ссылки на другие места документации. Значит, более простые формы этих конструкций были бы кстати.

Markdown — популярный язык разметки для простых документов: его легко читать, легко писать и легко преобразовать в HTML. Комментарии документации обычно не являются сложными структурированными документами. Для конструкций, которые в них обычно встречаются (абзацы, списки, оформленный текст и ссылки), в Markdown есть более простые формы, чем в HTML. Для конструкций, которые Markdown напрямую не поддерживает, в нём можно использовать и HTML.

Возможность использовать Markdown в комментариях документации объединила бы лучшее из обоих миров. Для самых распространённых конструкций появился бы краткий синтаксис, а HTML-разметка и теги JavaDoc понадобились бы реже, причём специализированные теги по-прежнему можно было бы использовать для возможностей, которых нет в Markdown. Комментарии документации в исходном коде стало бы проще писать и читать, а генерировать из них можно было бы такую же API-документацию, как и раньше.

Описание

В качестве примера использования Markdown в комментарии документации возьмём комментарий к java.lang.Object.hashCode:

/**
 * Returns a hash code value for the object. This method is
 * supported for the benefit of hash tables such as those provided by
 * {@link java.util.HashMap}.
 * <p>
 * The general contract of {@code hashCode} is:
 * <ul>
 * <li>Whenever it is invoked on the same object more than once during
 *     an execution of a Java application, the {@code hashCode} method
 *     must consistently return the same integer, provided no information
 *     used in {@code equals} comparisons on the object is modified.
 *     This integer need not remain consistent from one execution of an
 *     application to another execution of the same application.
 * <li>If two objects are equal according to the {@link
 *     #equals(Object) equals} method, then calling the {@code
 *     hashCode} method on each of the two objects must produce the
 *     same integer result.
 * <li>It is <em>not</em> required that if two objects are unequal
 *     according to the {@link #equals(Object) equals} method, then
 *     calling the {@code hashCode} method on each of the two objects
 *     must produce distinct integer results.  However, the programmer
 *     should be aware that producing distinct integer results for
 *     unequal objects may improve the performance of hash tables.
 * </ul>
 *
 * @implSpec
 * As far as is reasonably practical, the {@code hashCode} method defined
 * by class {@code Object} returns distinct integers for distinct objects.
 *
 * @return  a hash code value for this object.
 * @see     java.lang.Object#equals(java.lang.Object)
 * @see     java.lang.System#identityHashCode
 */

Тот же комментарий можно записать, выразив его структуру и оформление на Markdown, без HTML и лишь с несколькими встроенными тегами JavaDoc:

/// Returns a hash code value for the object. This method is
/// supported for the benefit of hash tables such as those provided by
/// [java.util.HashMap].
///
/// The general contract of `hashCode` is:
///
///   - Whenever it is invoked on the same object more than once during
///     an execution of a Java application, the `hashCode` method
///     must consistently return the same integer, provided no information
///     used in `equals` comparisons on the object is modified.
///     This integer need not remain consistent from one execution of an
///     application to another execution of the same application.
///   - If two objects are equal according to the
///     [equals][#equals(Object)] method, then calling the
///     `hashCode` method on each of the two objects must produce the
///     same integer result.
///   - It is _not_ required that if two objects are unequal
///     according to the [equals][#equals(Object)] method, then
///     calling the `hashCode` method on each of the two objects
///     must produce distinct integer results.  However, the programmer
///     should be aware that producing distinct integer results for
///     unequal objects may improve the performance of hash tables.
///
/// @implSpec
/// As far as is reasonably practical, the `hashCode` method defined
/// by class `Object` returns distinct integers for distinct objects.
///
/// @return  a hash code value for this object.
/// @see     java.lang.Object#equals(java.lang.Object)
/// @see     java.lang.System#identityHashCode

(В этом примере мы намеренно не вносим косметических изменений, например не переносим строки текста заново, чтобы было проще сравнить версии «до» и «после».)

Основные отличия:

  • На использование Markdown указывает новая форма комментария документации, в которой каждая строка начинается с ///, а не с традиционного синтаксиса /** ... */.

  • Элемент HTML <p> не нужен: пустая строка обозначает разрыв абзаца.

  • Элементы HTML <ul> и <li> заменены маркерами маркированного списка Markdown: начало каждого пункта списка обозначается символом -.

  • Элемент HTML <em> заменён символами подчёркивания (_), которые обозначают смену шрифта.

  • Теги {@code ...} заменены обратными кавычками (`...`), которые обозначают моноширинный шрифт.

  • Теги {@link ...} для ссылок на другие элементы программы заменены расширенными формами reference links Markdown.

  • Блочные теги, например @implSpec, @return и @see, в целом не меняются, только их содержимое теперь тоже записывается на Markdown, как здесь обратные кавычки в содержимом тега @implSpec.

Вот снимок экрана, на котором две версии показаны рядом и выделены различия между ними:

A screenshot of the differences

Использование /// для комментариев документации на Markdown

Мы используем /// для комментариев на Markdown, чтобы решить две проблемы традиционных комментариев /**.

  • Блочный комментарий, начинающийся с /*, не может содержать последовательность символов */ (JLS §3.7). В комментарии документации всё чаще включают примеры кода. Из-за этого ограничения без неудобных обходных приёмов нельзя привести пример, который содержит вложенные комментарии /*...*/ или выражения с символами */.

    В комментариях // на символы в оставшейся части строки нет никаких ограничений.

  • В традиционном комментарии документации, начинающемся с /**, пробельные символы в начале каждой строки с одной или несколькими звёздочками после них необязательны. Если в строках комментария таких звёздочек нет, возникает неоднозначность с конструкциями Markdown, которые сами начинаются со звёздочки: выделением, пунктами списка и тематическими разрывами.

    В комментариях /// такой неоднозначности не бывает никогда.

Менять синтаксис языка Java, чтобы разрешить новые формы комментариев, нельзя. Поэтому любой новый стиль комментариев документации должен быть либо традиционным блочным комментарием /* ... */, либо последовательностью однострочных комментариев //.

Всё сказанное выше обосновывает выбор однострочных комментариев вместо традиционных, но остаётся вопрос, как отличить комментарии документации от других однострочных комментариев. Мы используем дополнительный символ /, по аналогии с дополнительным символом * в начале традиционных комментариев документации. Кроме того, хотя это и не главный довод, в других языках, где поддерживаются однострочные комментарии документации, например в C#, Dart и Rust, /// уже довольно давно успешно используется для комментариев документации.

Синтаксис

Комментарии документации на Markdown пишутся в варианте Markdown CommonMark. Благодаря улучшениям ссылок удобно ссылаться на другие элементы программы. Поддерживаются простые таблицы GFM с вертикальными чертами, а также все теги JavaDoc.

Чтобы сослаться на элемент, объявленный в другом месте вашего API, используйте расширенную форму reference link Markdown, в которой метка ссылки получается из стандартной ссылки JavaDoc на сам элемент.

Чтобы создать простую ссылку, текст которой получается из идентификатора элемента, просто заключите ссылку на элемент в квадратные скобки. Например, чтобы сослаться на java.util.List, можно написать [java.util.List] или просто [List], если в коде есть инструкция import для java.util.List. Текст ссылки будет выведен моноширинным шрифтом. Такая ссылка равнозначна стандартному тегу JavaDoc {@link ...}.

Ссылаться можно на любой вид элемента программы:

/// - a module [java.base/]
/// - a package [java.util]
/// - a class [String]
/// - a field [String#CASE_INSENSITIVE_ORDER]
/// - a method [String#chars()]

Чтобы создать ссылку с другим текстом, используйте форму [text][element]. Например, чтобы создать ссылку на java.util.List с текстом a list, можно написать [a list][List]. Ссылка будет выведена текущим шрифтом, но внутри текста можно использовать разметку форматирования. Такая ссылка равнозначна стандартному тегу JavaDoc {@linkplain ...}.

Например:

/// - [the `java.base` module][java.base/]
/// - [the `java.util` package][java.util]
/// - [a class][String]
/// - [a field][String#CASE_INSENSITIVE_ORDER]
/// - [a method][String#chars()]

В reference links любые квадратные скобки нужно экранировать. Это может понадобиться в ссылке на метод с параметром-массивом: например, ссылку на String.copyValueOf(char[]) нужно записать как [String#copyValueOf(char\[\])].

Можно использовать и все остальные формы ссылок Markdown, в том числе ссылки на URL, но ссылки на другие элементы программы, скорее всего, будут встречаться чаще всего.

Таблицы

Поддерживаются простые таблицы с синтаксисом GitHub Flavored Markdown. Например:

/// | Latin | Greek |
/// |-------|-------|
/// | a     | alpha |
/// | b     | beta  |
/// | c     | gamma |

Подписи и другие возможности, которые могут понадобиться для доступности, не поддерживаются. В таких случаях по-прежнему рекомендуется использовать таблицы HTML.

Теги JavaDoc

В комментариях документации на Markdown можно использовать теги JavaDoc: как встроенные теги, например {@inheritDoc}, так и блочные теги, например @param и @return:

/// {@inheritDoc}
/// In addition, this methods calls [#wait()].
///
/// @param i the index
public void m(int i) ...

Теги JavaDoc нельзя использовать внутри буквального текста, например в code spans (`...`) или в блоках кода, то есть в блоках текста, которые либо набраны с отступом, либо заключены в fences, такие как ``` или ~~~. Иными словами, последовательности символов @... и {@...} не имеют особого значения внутри code spans и блоков кода:

/// The following code span contains literal text, and not a JavaDoc tag:
/// `{@inheritDoc}`
///
/// In the following indented code block, `@Override` is an annotation,
/// and not a JavaDoc tag:
///
///     @Override
///     public void m() ...
///
/// Likewise, in the following fenced code block, `@Override` is an annotation,
/// and not a JavaDoc tag:
///
/// ```
/// @Override
/// public void m() ...
/// ```

Если тег может содержать текст с разметкой, то в комментарии документации на Markdown эта разметка тоже записывается на Markdown:

/// @param l   the list, or `null` if no list is available

Тег {@inheritDoc} включает документацию метода из одного или нескольких супертипов. Формат комментария с этим тегом не обязательно должен совпадать с форматом комментария, документация из которого наследуется:

interface Base {
    /** A method. */
    void m()
}

class Derived implements Base {
    /// {@inheritDoc}
    public void m() { }
}

В комментариях документации на Markdown можно использовать пользовательские теги JavaDoc. Например, в документации JDK мы определяем и используем {@jls ...} как краткую форму ссылок на Java Language Specification, а также блочные теги @implSpec и @implNote, которые открывают разделы с определёнными сведениями:

/// For more information on comments, see {@jls 3.7 Comments}.
///
/// @implSpec
/// This implementation does nothing.
public void doSomething() { }

Отдельные файлы Markdown

Файлы Markdown в подкаталогах doc-files обрабатываются как положено, примерно так же, как HTML-файлы в таких каталогах. Теги JavaDoc в таких файлах обрабатываются. Заголовок страницы берётся из первого заголовка в файле. Метаданные YAML, например в том виде, в каком их поддерживает обработчик Markdown Pandoc, не поддерживаются.

Файл с содержимым для генерируемой обзорной страницы верхнего уровня тоже может быть файлом Markdown.

Подсветка синтаксиса и встроенные языки

После открывающего fence в fenced code block может идти info string. Первое слово info string используется для получения имени CSS-класса в соответствующем сгенерированном HTML. Библиотеки JavaScript также могут использовать его для подсветки синтаксиса (например, Prism) и отрисовки диаграмм (например, Mermaid).

Например, вместе с подходящими библиотеками такой блок покажет фрагмент кода CSS с подсветкой синтаксиса:

/// ```css
/// p { color: red }
/// ```

Добавить в документацию библиотеки JavaScript можно с помощью параметра javadoc --add-script.

Подробности синтаксиса

Горизонтальные пробельные символы в начале и в конце каждой строки текста на Markdown могут быть значимыми, поэтому содержимое комментария документации на Markdown определяется так:

  • Из каждой строки удаляются все пробельные символы в начале и три начальных символа /.

  • Строки сдвигаются влево: начальные пробельные символы удаляются до тех пор, пока у непустой строки с наименьшим числом начальных пробельных символов их не останется совсем.

  • Остальные начальные пробельные символы и все пробельные символы в конце каждой строки сохраняются, потому что они могут быть значимыми. Например, пробельные символы в начале строки могут обозначать блок кода с отступом или продолжение пункта списка, а пробельные символы в конце строки — принудительный перенос строки.

(Правило удаления случайных начальных пробельных символов похоже на правило для String.stripIndent(), только завершающие пустые строки обрабатывать не нужно.)

Никаких ограничений на символы, которые могут стоять после /// в каждой строке комментария, нет. В частности, комментарий может содержать примеры кода, в которых могут быть собственные комментарии:

/// Here is an example:
///
/// ```
/// /** Hello World! */
/// public class HelloWorld {
///     public static void main(String... args) {
///         System.out.println("Hello World!"); // the traditional example
///     }
/// }
/// ```

Комментарии до конца строки (//) не только визуально отличают новый вид документирующих комментариев, но и снимают ограничения на содержимое комментария, присущие традиционным комментариям (/* ... */). В частности, внутри традиционного комментария нельзя использовать последовательность символов */ (JLS §3.7), хотя это может понадобиться при написании примеров кода с традиционными комментариями, строк с glob-выражениями и строк с регулярными выражениями.

Чтобы пустая строка вошла в комментарий, она должна начинаться с необязательных пробельных символов, за которыми следует ///:

/// This is an example ...
///
/// ... of a 3-line comment containing a blank line.

Полностью пустая строка приводит к тому, что комментарии до и после неё считаются отдельными комментариями. В этом случае все комментарии, кроме последнего, отбрасываются, и только последний считается документирующим комментарием для объявления, которое может за ним следовать:

/// This comment will be treated as a "dangling comment" and will be ignored.

/// This is the comment for the following declaration.
public void m() { }

То же относится к любому другому комментарию, который не начинается с /// и может оказаться между двумя комментариями ///.

API и реализация

Разобранные документирующие комментарии представлены элементами пакета com.sun.source.doctree в Compiler Tree API.

Мы вводим новый тип узла дерева, RawTextTree, который содержит неинтерпретированный текст, а также новый вид узла дерева, DocTree.Kind.MARKDOWN, который обозначает Markdown-содержимое в RawTextTree. Мы добавляем соответствующие новые методы visitRawText в DocTreeVisitor и его подтипы DocTreeScanner и DocTreePathScanner.

Узлы RawTextTree вида MARKDOWN представляют Markdown-содержимое, включая конструкции HTML, но без тегов JavaDoc, таких как {@inheritDoc} и @param.

Текст Markdown обрабатывается в два этапа:

  1. Разбор — Markdown-комментарии разбираются в последовательность узлов RawTextTree, каждый из которых имеет вид DocTree.Kind.MARKDOWN и содержит Markdown-содержимое, вперемежку со стандартными узлами DocTree для строчных и блочных тегов. Строчные и блочные теги разбираются так же, как в традиционных документирующих комментариях, за исключением того, что содержимое тегов тоже разбирается как Markdown. Последовательность узлов сохраняется в узле DocCommentTree обычным образом.

    В отличие от традиционного документирующего комментария, конструкции HTML не разбираются в соответствующие узлы DocTree, потому что для этого пришлось бы учитывать слишком большую часть окружающего контекста.

    Затем Markdown-содержимое в DocCommentTree, полученном при первоначальном разборе, просматривается в поисках ссылок по метке, у которых нет связанного определения ссылки и у которых метка ссылки синтаксически соответствует ссылке на элемент программы. Каждая такая ссылка заменяется эквивалентным узлом, представляющим либо {@link ...}, либо {@linkplain ...}.

  2. Отрисовка — инструмент javadoc преобразует DocCommentTree в HTML, пригодный для включения в генерируемую страницу.

    Любая последовательность узлов RawTextTree и других узлов преобразуется в одну строку, содержащую текст узлов RawTextTree, в которой не-Markdown-содержимое замещено символом Unicode OBJECT REPLACEMENT CHARACTER (U+FFFC). Полученная строка обрабатывается Markdown-процессором, после чего символы U+FFFC в результате заменяются отрисованными формами узлов с не-Markdown-содержимым.

    Бо́льшая часть отрисовки устроена просто, но особое внимание уделяется заголовкам Markdown:

    • Уровень заголовка корректируется в зависимости от окружающего контекста. Это относится как к заголовкам, изначально записанным в документирующем комментарии в стиле ATX (уровень обозначается префиксом из символов #), так и к заголовкам в стиле Setext (уровень обозначается подчёркиванием символами = или -).

      Например, заголовок уровня 1 в документирующем комментарии к модулю, пакету или классу отрисовывается на генерируемой странице как заголовок уровня 2, а заголовок уровня 1 в документирующем комментарии к полю, конструктору или методу отрисовывается как заголовок уровня 4.

      Эта корректировка применяется только к заголовкам Markdown, но не к непосредственно использованным заголовкам HTML.

    • В отрисованный HTML добавляется атрибут-идентификатор id, чтобы на заголовок было легко сослаться из другого места. Идентификатор генерируется из содержимого заголовка так же, как и другие идентификаторы, которые генерирует javadoc. (Ссылку на заголовок легко получить, щёлкнув по всплывающему значку ссылки при просмотре заголовка в браузере.)

    • Текст заголовка добавляется в основной поисковый индекс генерируемой документации.

Реализация использует внутреннюю копию известной библиотеки commonmark-java. Использование этой библиотеки намеренно не раскрывается ни в каком публичном поддерживаемом API JDK.

Бо́льшая часть описанных здесь возможностей относится к инструменту javadoc из JDK и к Compiler Tree API в модуле jdk.javadoc. Однако в стандартном API Java есть одно место, где использование нового стиля документирующих комментариев будет заметно: метод javax.lang.model.util.Elements.getDocComment в модуле java.compiler, который возвращает нормализованный текст документирующего комментария к объявлению, если он есть. Мы обновим этот метод, чтобы он охватывал комментарии ///. Кроме того, поскольку вид комментария влияет на его интерпретацию, мы добавим новый метод, позволяющий определить, использует ли документирующий комментарий к объявлению традиционную форму блочного комментария /** ...*/ или новую форму комментария до конца строки ///.

Дальнейшая работа

Можно было бы распознавать некоторые шаблонные способы использования заголовков с соответствующим содержимым после них и преобразовывать их в эквивалентные теги JavaDoc.

Например, заголовок Parameters, за которым следует список имён параметров с их описаниями, можно было бы преобразовать в эквивалентные теги @param:

  • Комментарий

    # Parameters
    
    * x   the x coordinate
    * y   the y coordinate
  • Преобразование

    @param x   the x coordinate
    @param y   the y coordinate

Аналогичный подход можно было бы принять для списка исключений, которые может выбросить метод:

  • Комментарий

    # Throws
    
    * NullPointerException      if the first parameter is `null`
    * NullPointerException      if the second parameter is `null`
    * IllegalArgumentException  if an argument is not accepted
  • Преобразование

    @throws NullPointerException      if the first parameter is `null`
    @throws NullPointerException      if the second parameter is `null`
    @throws IllegalArgumentException  if an argument is not accepted

Описание возвращаемого значения метода всегда должно быть только одно, поэтому в этом случае список не нужен:

  • Комментарий

    # Returns
    
    the square root of the argument
  • Преобразование

    @return the square root of the argument

Предлагаемые формы действительно выглядят как обычный Markdown, но они занимают больше места по вертикали. Разработчики могут предпочесть более краткие формы со старыми тегами JavaDoc.

Распространить этот подход на все блочные теги, включая пользовательские, может быть трудно, но в кодовой базе JDK всего на пять тегов (@param, @return, @see, @throws и @since) приходится более 90 % всех случаев использования блочных тегов.

Альтернативы

Подключаемая реализация

Вместо того чтобы использовать конкретную реализацию парсера Markdown, мы могли бы поддержать другие Markdown-процессоры, задаваемые пользователем, с разными диалектами Markdown. Однако такой подход мог бы привести к несогласованности при генерации документации, охватывающей разные библиотеки, при незначительной видимой выгоде.

Преобразование большей части Markdown в HTML

Мы могли бы преобразовывать больше конструкций Markdown в эквивалентные узлы DocTree, представляющие простой текст, HTML и теги JavaDoc. У такого подхода было бы преимущество: клиентам API могло бы не требоваться знать, что исходный текст комментария был написан на Markdown. Но у него есть и ряд недостатков:

  • Чем дальше представление от исходного синтаксического дерева, тем труднее выдавать точные и уместные диагностические сообщения, если они понадобятся. Например, сообщения о синтетическом элементе <table> могут сбивать с толку, если в исходном комментарии такого элемента явно нет.

  • При синтезе узлов DocTree для элементов HTML, полученных из конструкций Markdown, трудно указать точную позицию, которая связывала бы узел с его местом в исходном комментарии, поскольку в исходном комментарии у узла нет представления. В лучшем случае можно указать близкую позицию. У этой проблемы есть аналог в компиляторе Java, javac, при назначении позиций синтетическим элементам, таким как конструктор без аргументов по умолчанию, или мостовым методам.

  • Общее решение затруднено, потому что для него нужно знать все теги JavaDoc, которые могут встретиться: многие теги допускают богатое содержимое, например Markdown или HTML, в части своего содержимого, но не во всём.

    Например, за тегом @param перед описанием следует имя параметра, и это имя может быть заключено в <...>, если это имя параметра типа. Интерпретировать такое имя как фрагмент HTML было бы неправильно. Аналогично, за тегом @serialField перед описанием следуют имя и тип. Это стандартные теги, известные стандартному доклету, но доклет также допускает использование тегов, определённых пользователем.

Строчные теги

Использование большинства блочных тегов можно было бы заменить шаблонным использованием заголовков и следующего за ними содержимого, но для большинства менее распространённых строчных тегов такого эквивалента нет. Из них чаще всего используется {@inheritDoc}, и очевидного аналога в Markdown у него нет. Вместо того чтобы изобретать альтернативный синтаксис ради самого синтаксиса, лучше, по-видимому, сохранить существующий синтаксис строчных тегов.

Markdown в комментариях /**...*/

Как описано выше, у использования /// для документирующих комментариев много преимуществ. Если оставить эти причины в стороне и захотеть разбирать Markdown, встроенный в традиционные комментарии /**...*/, вместо введения комментариев /// или в дополнение к нему, то возможны два варианта: либо считать все существующие комментарии /** Markdown-комментариями, либо в каждом комментарии /** каким-то образом указывать, Markdown-комментарий это или традиционный.

Считать существующие комментарии Markdown-комментариями нельзя, потому что Markdown и HTML — разные языки с разными синтаксическими правилами. В HTML пробельные символы значимы только как буквальный текст в элементе <pre>. В Markdown, напротив, вертикальные пробелы могут обозначать разрыв абзаца, начальные горизонтальные пробелы — блок кода с отступом или вложенный список, а пробелы в конце строки — принудительный перенос строки, эквивалентный <br> в HTML. Кроме того, правила использования HTML в документах Markdown довольно запутанны и неинтуитивны. Наконец, в коде JDK много примеров квадратных скобок в поясняющем тексте, которые рискуют быть истолкованы как ссылки на элементы программы, например The information is returned as a two-dimensional array (array[x][y]).

Указывать вид документирующего комментария внутри каждого комментария /** можно, но это непривлекательно. Например, можно было бы ставить короткую строку сразу после начального /**, чтобы обозначить, что последующий текст следует считать Markdown:

/**md
 * Hello _World!_
 */

Когда мы сделали прототип этого подхода, он в целом не понравился: в небольших комментариях его сочли слишком навязчивым, а в больших — слишком незаметным.

Настраиваемые стили комментариев

Мы могли бы создать настраиваемую систему, которая принимает одни документирующие комментарии /** ... */ на Markdown, а другие на HTML. Однако неясно, было бы у такого механизма какое-либо существенное преимущество перед более явным использованием комментариев /// для комментариев на Markdown и сохранением /** ... */ для комментариев на HTML.

Риски и допущения

  • Для преобразования Markdown в HTML реализация использует стороннюю библиотеку commonmark-java. Если эту библиотеку перестанут сопровождать, нам придётся сопровождать её форк для использования в JDK или найти равноценную альтернативу.

  • Есть риск, что в сгенерированной документации API будет больше ошибок, поскольку возможности проверки на некорректный код ограничены, а авторы иногда забывают проверить сгенерированный вид своей документации.

    Например, если в традиционном документирующем комментарии абзац содержит незакрытый тег code, например {@code abc, при вызове JavaDoc будет выдано диагностическое сообщение, а в сгенерированной документации он будет отображён как ▶ invalid @code. В Markdown по спецификации эквивалентный незакрытый фрагмент кода `abc считается обычным текстом и так и будет отображён, без соответствующего диагностического сообщения.