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


Что у нас получилось — можно посмотреть здесь.




Документация в коде — это прекрасно!


Когда встала задача создания документации к нашему TypeScript API, первое, с чего мы начали — просто табличка в google docs. Мы копировали туда названия наших компонентов, их параметры, а технический писатель уже делал из этого документацию. Это было ужасно. Табличка постоянно устаревала, копировать в нее было неудобно, куча ручной работы.


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


  1. Документация всегда актуальна и точна. Даже если технический писатель забыл дописать текст — в документации будет что-то.
  2. Минимум ручного труда. Время тратится только на написание текста — все остальное делается автоматически. Никакого форматирования, оформления и прочей рутины.
  3. Первичный вариант документации может писать разработчик, так что нет необходимости отдельно объяснять техпису, что к чему. Он просто редактирует существующий текст.
  4. Вся документация отображается в IDE при использовании API.

    Пример отображения документации в IDE

Как все выглядит


В коде документация получается следующего вида:


 /**
   * Register or update value for specified key.
   * @param key Key to identify value in container.
   * @param value Value to inject.
   * @returns Created or existing [DiRecord]{@link DiRecord}
   */
  public register<KeyT, ValueT>(key: KeyT, value: ValueT): Typedin.DiRecord<KeyT, ValueT> {
  }

Для генерации, в первую очередь, нужно установить typedoc. Есть плагины для gulp и grunt.


npm install typedoc -g

Базовую конфигурацию можно посмотреть на примере моей библиотеки typedin. Там используется голый typedoc, без каких-либо заморочек и кастомизации. Такую документацию вы можете сгенерировать для своего проекта, просто добавив одну команду в свои скрипты:


typedoc --readme README.md --name typedin --out ./docs/ --tsconfig tsconfig.prod.json --excludePrivate --excludeNotExported --excludeExternals

После этого в папке docs появится статический сайт, который можно, например, опубликовать через github pages.


Стоит заметить, что документация по использованию typedoc на сайте и на github обычно неактуальная. Для справки используйте:


typedoc --help

Синтаксис комментариев


В целом, синтаксис от jsdoc. То есть, комментарии заключаются в /** */ и используются теги @returns и @param как в примере выше (обычно больше ничего и не требуется). Основное отличие от jsdoc — не нужно писать тип параметров, т.к. он уже есть в коде.


Комментарии поддерживают разметку markdown, что дает широкие возможности для форматирования. Не буду пересказывать весь синтаксис, приведу только самые частые кейсы:


  1. Для того, чтобы отделить один абзац от другого, нужно поместить между ними пустую строку


        /**
          * Этот метод что-то делает, первый абзац.
          * 
          * Но иногда и ничего не делает, второй абзац.
          */
        function doSomething(){
        }

    В принципе, можно использовать <br/>, но это некрасиво.


  2. Для вставки примера кода нужно сделать отступ в 4 пробела + отступы сверху и снизу


       /**
         * Устанавливает значение [controlImpl]{@link controlImpl}, в соответствии с идиомой Pimpl.
         * Метод должен передаваться в качестве значения ref в функции render. Например:
         *
         *     <MyControlImpl ref={this.attachControl} />;
         * 
         * @param control  Ссылка на реализацию контрола.
         */
        protected attachControl(control) {
            this.controlImpl = control;
        }

  3. Для вставки кода прямо в тексте параграфа используются бэктики


        /**
          * При переопределении в дочернем классе должен возвращать новый
          * экземпляр параметров компонента, созданный через оператор `new` (например: `new MyControlParams()`)
          * Данный объект будет присвоен свойству `this.state`.
          */
        protected abstract createParams(): P;

  4. В комментариях можно ссылаться на различные части API при помощи тега @link. Синтаксис у него похож на обычные ссылки markdown:


    • [getParamValue]{@link BaseControlImpl.getParamValue} — ссылка на член другого класса
    • [Constants]{@link Constants} — ссылка на глобальный объект
    • [setParamValues]{@link setParamValues} — ccылка на член этого же класса


Кастомизация


Первое, с чем мы столкнулись — это необходимость кастомизации внешнего вида. Стандартная тема нас не устраивала, хотелось оформления в фирменных цветах компании. Кроме того, в стандартной теме банально не работают контролы управления в верхней части страницы. Все это решается созданием своей темы, на основе стандартной.


Документация утверждает, что все темы наследуются от default темы. То есть, нам нужно разместить в своей теме только те файлы, которые мы изменили. Но проще скопировать всю тему целиком и уже в ней разбираться, что и как нужно поправить.


Тема состоит из стилей, скриптов, различных ресурсов и шаблонов handlebars. Соответственно, в теории, изменяя тему можно сделать все что угодно. Если нужно добавить какую-то сложную логику в шаблоны, можно сделать плагин. Тем не менее, сходу сделать что-то существенное в handlebars-шаблонах у меня не получилось. Я раньше не работал с этим шаблонизатором, возможно в этом дело. Поэтому я ограничился модификацией стилей, скриптов и несущественных правок в шаблонах.


Чтобы добавить свои файлы стилей и скрипты, я разместил файлы web-client.css и web-client.js в папках assets/css и assets/js соответственно, и прописал их в файле layouts\default.hbs:


<link rel="stylesheet" href="{{relativeURL "assets/css/web-client.css"}}">
...    
<script src="{{relativeURL "assets/js/web-client.js"}}"></script>

В целом, получилось по 70 строчек css и js. Благодаря css наша документация имеет оригинальную темную шапку, отличную от стандартной и некоторые другие мелкие изменения. В javascript в основном исправлена работа флагов "показывать protected/inherited". HTML этих флагов также был исправлен в шаблоне partials\header.hbs.


Итого, чтобы настроить под себя генерируемую документацию, необходимо:


  1. Скачать стандартную тему.
  2. Добавить свои css, js и поправить шаблоны.
  3. Добавить флаг --theme <путь к папке с темой> при генерации документации:


    typedoc --theme MyThemeFolder --name typedin --readme README.md --out ./docs/ --tsconfig tsconfig.prod.json --excludePrivate --excludeNotExported --excludeExternals



Так, путем написания несложного css, js и небольшой правки шаблонов можно решить большинство задач по кастомизации. Для более серъезных изменений понадобится разобраться с handlebars и написать плагины.


Заключение


Typedoc пока довольно сырой проект, и мне не удалось найти достойных альтернатив. Удивительно, что эта тема так слабо развита, учитывая что TypeScript все больше набирает популярность. Мне даже не удалось найти ни одной адекватной темы для typedoc — поиск по github выдает некоторые результаты, но все они либо нерабочие, либо не представляют ничего интересного. И это несмотря на то, что npm-пакет typedoc имеет десятки тысяч загрузок в день.


Этой статьей я хотел бы привлечь внимание сообщества к этой теме и к typedoc в частности. Проект остро нуждается в контрибьюторах, нужно развивать утилиту, писать классные темы, актуализировать документацию. Генерация документации из кода — это мегаполезная вещь и хорошего API Reference очень не хватает многим javascript-библиотекам.


Можно с уверенностью сказать, что typedoc — рабочий инструмент, который справляется со своей задачей и достаточно гибок, чтобы удовлетворить большинству потребностей. У нас довольно большой проект, и никаких особых проблем в его работе мы пока не обнаружили. Код разбирается корректно, ничего не падает и работает хорошо.


Единственное, чего ему не хватает — внимания от сообщества. Используйте в своих проектах и делитесь своими наработками. Нашу тему можно скачать на github.

Комментарии (10)


  1. knotri
    07.11.2017 15:26

       * @param key Key to identify value in container.
       * @param value Value to inject.
       * @returns Created or existing [DiRecord]{@link DiRecord}
     public register<KeyT, ValueT>(key: KeyT, value: ValueT): Typedin.DiRecord<KeyT, ValueT>

    То есть вы просто продублировали информацию о типах из тайпскрипта еще раз???
    Это отвратительно. Я уже вижу ситуацию когда параметр функции кто-то перепишет — а документация останется без изменений.


    1. PFight77 Автор
      07.11.2017 16:21

      Это Вы о returns? Там да, пожалуй упоминание типа лишнее. В целом, информацию о типе typedoc берет из кода, дублировать ничего не нужно.


  1. shyr1punk
    07.11.2017 16:21

    Пользуетесь линтингом JSDoc комментариев? Конкретно интересуют правила, которые позволяют избегать дублирования конструкций языка: типов, модификаторов доступа и т.д.


    1. PFight77 Автор
      07.11.2017 16:24

      Не очень понял, что там можно линтить. Поясните, пожалуйста.


      1. shyr1punk
        08.11.2017 10:56
        +1

        Чтобы линтер ругался в случае, который описал knotri в первом комментарии. Когда в JSDoc указан тип, например — это избыточно и может породжать ошибки в дальнейшем.


        1. PFight77 Автор
          08.11.2017 11:38

          Можно, наверное, попробовать сделать плагин для typedoc.


  1. plastilinko
    07.11.2017 17:09

    Да вот с этими средствами автогенерации документации все весьма сложно нынче. По работе требовалось распарсить сложный контейнер из js+php+sql и в итоге остановились на весьма старом мамонте под названием robodoc, часть функционала докрутил руками прямо в исходниках на C, с остальными вариантами на в роде jsdoc или sphinx подружиться не получилось по причине того что они пытаются распарсить весь контейнер под видом одного ЯП, есть еще DITA но там с первого подхода вообще черт ногу сломит.


  1. SaitoNakamura
    08.11.2017 16:29

    Честно говоря зайдя на сайт не очень понял реальную пользу от такой доки, она выглядит как типичные части MSDN, где про метод GetSepulkiFromSepulkary(int id) будет сказано что он достает сепульки из сепулькария и принимает целочисленный id в качестве параметра. Эти доки полезны в качестве тайпингов прямо в IDE, где ты получишь автодополнение или просто просмотр сигнатур, но не на сайте. На сайте гораздо полезней были бы реальные примеры использования (то что пытался делать Stackoverflow Documentation, хотя сам этот проект провалился), взаимодействия между методами, подводные камни и т.п. А сейчас я захожу и вижу

    image

    Куда мне здесь пойти и как этим пользоваться я не очень понимаю.


    1. mayorovp
      08.11.2017 16:32

      Это разные типы документации. И все они нужны.


    1. PFight77 Автор
      08.11.2017 16:34

      Есть еще отдельная документация, это чисто справочник по API. В .NET тоже всю документацию можно посмотреть в IDE, и тем не менее часто удобно зайти на MSDN и посмотреть описание конкретного класса. Здесь подразумевается, что ты уже знаешь название класса, например, и нужно просто посмотреть его API. Ну и разумеется, все это в IDE тоже доступно.


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