Тестирование токенайзеров

Marked — это мощная библиотека для парсинга Markdown в JavaScript, которая позволяет преобразовывать текст с разметкой Markdown в HTML. В основе её работы лежит система лексического анализа, где Markdown-текст сначала разбивается на токены, а затем каждый токен преобразуется в соответствующий HTML-элемент.

Токенайзер — это компонент, отвечающий за разделение исходного текста на отдельные токены. Токены представляют собой логические блоки Markdown: заголовки, списки, параграфы, ссылки, изображения, цитаты, коды и пр. В Marked существует несколько уровней токенизации: базовая (inline) и блочная (block), каждая из которых имеет свои особенности.


Блочная токенизация

Блочный токенайзер отвечает за обработку больших структурных элементов документа. Примеры блочных токенов:

  • heading — заголовки, например # Заголовок 1
  • paragraph — обычный текстовый абзац
  • list — нумерованные и маркированные списки
  • blockquote — блоки цитат
  • code — блоки кода с поддержкой языка
  • hr — горизонтальные линии

Принцип работы:

  1. Токенайзер получает текст Markdown построчно.
  2. Сравнивает каждую строку с регулярными выражениями для блочных структур.
  3. Если структура найдена, создаётся соответствующий объект токена с полями, отражающими тип, содержимое и дополнительные параметры (например, уровень заголовка).
  4. Необработанные строки передаются в следующий шаг токенизации.

Пример структуры токена заголовка:

{
  type: 'heading',
  depth: 2,
  text: 'Пример заголовка'
}

Inline-токенизация

Inline-токенизация обрабатывает внутреннее содержание блочных элементов, например выделение текста, ссылки и изображения. Основные inline-токены:

  • em и strong — курсив и жирный текст
  • link — гиперссылки
  • image — изображения
  • codespan — встроенный код
  • del — зачёркнутый текст

Inline-токенайзер работает рекурсивно: он получает текст блочного токена и ищет последовательности Markdown-разметки, создавая вложенные токены.

Пример токена для ссылки:

{
  type: 'link',
  href: 'https://example.com',
  title: 'Описание ссылки',
  text: 'Перейти на сайт'
}

Настройка токенайзеров в Marked

Marked предоставляет возможность пользовательской настройки токенайзеров через объект Tokenizer. Это позволяет:

  • Добавлять собственные токены
  • Переопределять стандартные правила
  • Контролировать приоритет обработки

Пример создания кастомного токенайзера для обработки выделения текста двойными кавычками:

const { marked } = require('marked');

class CustomTokenizer extends marked.Tokenizer {
  strong(src) {
    const match = /^""(.+?)""/.exec(src);
    if (match) {
      return {
        type: 'strong',
        raw: match[0],
        text: match[1]
      };
    }
    return super.strong(src);
  }
}

marked.use({ tokenizer: new CustomTokenizer() });

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


Тестирование токенайзеров

Тестирование токенайзеров критически важно для проверки корректности разбора Markdown и предотвращения ошибок в последующем рендеринге.

Основные подходы к тестированию:

  1. Юнит-тесты на отдельные токены Для каждого типа токена создаются тестовые строки, проверяется правильность распознавания и формирование объекта токена.

    Пример теста для заголовка:

    const tokenizer = new marked.Tokenizer();
    const token = tokenizer.heading('# Заголовок');
    console.assert(token.type === 'heading', 'Неверный тип токена');
    console.assert(token.depth === 1, 'Неверная глубина заголовка');
    console.assert(token.text === 'Заголовок', 'Неверный текст заголовка');
  2. Тесты на inline-токены Inline-токены часто тестируются с рекурсивными комбинациями, например, текст с ссылкой внутри жирного текста.

    const token = tokenizer.strong('**[Ссылка](https://example.com)**');
    console.assert(token.type === 'strong', 'Ожидался strong');
    console.assert(token.tokens[0].type === 'link', 'Внутри должен быть link');
  3. Интеграционные тесты Проверка полного парсинга Markdown-документа с различными уровнями вложенности. Тест фиксирует соответствие структуры токенов ожидаемой схеме.

Важные аспекты при тестировании:

  • Совмещение блочной и inline-токенизации: необходимо убедиться, что блоки правильно разделяются, а внутренние токены не теряются.
  • Обработка ошибок: Markdown может содержать некорректные конструкции. Токенайзер должен корректно их распознавать и либо игнорировать, либо преобразовывать в безопасный HTML.
  • Поддержка пользовательских токенов: любые добавленные токены должны интегрироваться с существующей системой токенизации.

Практические советы

  • Всегда тестировать краевые случаи, например пустые строки, неправильные разметки, вложенные списки и цитаты.
  • Использовать регулярные выражения осторожно — они являются сердцем токенайзеров, и малейшая ошибка может нарушить всю парсинговую цепочку.
  • Разделять тесты для блочной и inline-токенизации — это упрощает локализацию ошибок.
  • Документировать все кастомные токены, чтобы их поведение было понятно при дальнейшем использовании или поддержке библиотеки.

Вывод по тестированию токенайзеров

Система токенов в Marked — это гибкая и мощная основа для разбора Markdown. Качественное тестирование обеспечивает:

  • Корректную интерпретацию разметки
  • Безопасность рендеринга HTML
  • Расширяемость через кастомные токенайзеры
  • Предсказуемое поведение при изменении или добавлении новых типов токенов

Тщательная настройка и проверка токенайзеров позволяет создавать надёжные и масштабируемые Markdown-приложения.