Валидация синтаксических деревьев

Основы синтаксического дерева

В Remark и Rehype документ сначала преобразуется в синтаксическое дерево: MDAST (Markdown Abstract Syntax Tree) для Remark и HAST (HTML Abstract Syntax Tree) для Rehype. Эти деревья представляют структуру документа в виде узлов, где каждый узел имеет тип, дочерние элементы и дополнительные свойства. Пример узла MDAST:

{
  "type": "heading",
  "depth": 2,
  "children": [
    {
      "type": "text",
      "value": "Пример заголовка"
    }
  ]
}

Причины валидации

Валидация синтаксических деревьев необходима для:

  • Обеспечения корректности структуры документа перед дальнейшей трансформацией.
  • Предотвращения ошибок при компиляции или экспорте в HTML/React.
  • Проверки соответствия узлов определённым правилам, например, ограничения на заголовки, ссылки или списки.
  • Улучшения безопасности, например, предотвращение XSS при обработке внешнего Markdown.

Структура валидатора

Валидация выполняется на основе три уровня проверки:

  1. Тип узла (type) – проверка допустимых типов узлов для конкретного дерева.
  2. Свойства узла – проверка обязательных и допустимых полей (value, depth, url и др.).
  3. Дочерние узлы (children) – проверка структуры вложенности, например, заголовок не может содержать блоки, только текстовые узлы.

Пример функции валидации узла MDAST:

function validateNode(node) {
  const validTypes = ['heading', 'paragraph', 'text', 'link', 'list', 'listItem'];
  if (!validTypes.includes(node.type)) {
    throw new Error(`Недопустимый тип узла: ${node.type}`);
  }

  switch (node.type) {
    case 'heading':
      if (typeof node.depth !== 'number' || node.depth < 1 || node.depth > 6) {
        throw new Error(`Неправильная глубина заголовка: ${node.depth}`);
      }
      break;
    case 'link':
      if (!node.url || typeof node.url !== 'string') {
        throw new Error('Ссылка должна содержать корректный URL');
      }
      break;
  }

  if (node.children && Array.isArray(node.children)) {
    node.children.forEach(validateNode);
  }
}

Интеграция с Remark

Remark позволяет использовать плагины для валидации на этапе парсинга. Создание кастомного плагина включает:

  • Получение AST через tree.
  • Рекурсивную проверку каждого узла.
  • Сообщение об ошибках через file.message().

Пример плагина:

const visit = require('unist-util-visit');

function remarkValidate() {
  return (tree, file) => {
    visit(tree, (node) => {
      try {
        validateNode(node);
      } catch (error) {
        file.message(error.message, node);
      }
    });
  };
}

const processor = require('remark')()
  .use(remarkValidate);

Интеграция с Rehype

Rehype работает аналогично, но с HAST. Валидация обычно фокусируется на:

  • Корректной структуре HTML-элементов.
  • Валидных атрибутах (src, alt, href).
  • Предотвращении вложенных запрещённых элементов (<a> внутри <a>, <ul> вне <li>).

Пример функции валидации HAST:

function validateHastNode(node) {
  const validTypes = ['element', 'text', 'comment'];
  if (!validTypes.includes(node.type)) {
    throw new Error(`Недопустимый тип HAST узла: ${node.type}`);
  }

  if (node.type === 'element') {
    if (!node.tagName) {
      throw new Error('Элемент должен иметь tagName');
    }
    if (node.children) {
      node.children.forEach(validateHastNode);
    }
  }
}

Практические советы по валидации

  • Рекурсивная проверка – ключевой подход, так как AST может иметь произвольную глубину.
  • Использование unist-util-visit – упрощает обход дерева и фильтрацию по типу узла.
  • Сообщения об ошибках с указанием позиции – повышают удобство отладки (node.position в MDAST/HAST).
  • Сочетание с схемами – можно использовать zod или yup для строгой типизации узлов.
  • Валидация атрибутов и ссылок – критична при генерации HTML или JSX для предотвращения ошибок рендеринга и XSS.

Расширенные техники

  1. Кастомные правила: проверка допустимых заголовков, длины текста, допустимых тегов и атрибутов.
  2. Предобработка AST: иногда узлы требуется нормализовать перед проверкой, например, объединение соседних текстовых узлов.
  3. Интеграция с CI/CD: автоматическая проверка Markdown/HTML файлов перед публикацией.

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