Позиционная информация

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

Структура позиционной информации

Каждый узел AST (Abstract Syntax Tree) в Remark или Rehype может содержать объект position, который описывает, где именно в исходном тексте находится этот узел. Формат position обычно включает два свойства:

  • start — начальная позиция узла.
  • end — конечная позиция узла.

Каждое из этих свойств содержит следующие координаты:

  • line — номер строки в исходном тексте (начинается с 1).
  • column — номер колонки в строке (начинается с 1).
  • offset — смещение символа от начала документа (начинается с 0).

Пример структуры позиционной информации:

{
  "type": "paragraph",
  "children": [
    {
      "type": "text",
      "value": "Hello, world!",
      "position": {
        "start": { "line": 1, "column": 1, "offset": 0 },
        "end": { "line": 1, "column": 14, "offset": 13 }
      }
    }
  ],
  "position": {
    "start": { "line": 1, "column": 1, "offset": 0 },
    "end": { "line": 1, "column": 14, "offset": 13 }
  }
}

Использование позиции для обработки ошибок

Позиционная информация позволяет точно локализовать ошибки. Например, линтеры на основе Remark могут выдавать сообщения об ошибках с указанием строки и столбца:

import { lint } from "remark-lint";
import { unified } from "unified";
import remarkParse from "remark-parse";

const processor = unified()
  .use(remarkParse)
  .use(lint);

const file = processor.processSync("Hello **world**!");

file.messages.forEach(msg => {
  console.log(`Ошибка: ${msg.reason} на строке ${msg.line}, колонка ${msg.column}`);
});

Вывод будет включать точное положение выделенного элемента в тексте.

Модификация текста с учётом позиции

При трансформации AST знание позиции узлов помогает корректно заменять или удалять части текста без нарушения структуры документа. Пример замены текста:

import { unified } from "unified";
import remarkParse from "remark-parse";
import remarkStringify from "remark-stringify";

const processor = unified().use(remarkParse).use(remarkStringify);

const tree = processor.parse("Hello world!");
const textNode = tree.children[0].children[0];

// Замена текста с использованием позиции
const start = textNode.position.start.offset;
const end = textNode.position.end.offset;
const original = "Hello world!";
const updated = original.slice(0, start) + "Hi universe!" + original.slice(end);

console.log(updated); // Hi universe!

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

Позиционная информация в Rehype

Для Rehype, который работает с HTML, структура position аналогична Remark, но дополнительно учитывает атрибуты и сущности HTML. Это важно при обработке сложных документов:

  • startTag — начало тега, включая < и имя.
  • endTag — конец тега, включая > и возможные закрывающие символы.
  • start и end — позиция всего узла, как у Remark.

Пример узла Rehype с позицией:

{
  "type": "element",
  "tagName": "p",
  "children": [
    {
      "type": "text",
      "value": "Пример текста",
      "position": {
        "start": { "line": 2, "column": 4, "offset": 15 },
        "end": { "line": 2, "column": 16, "offset": 27 }
      }
    }
  ],
  "position": {
    "start": { "line": 2, "column": 1, "offset": 12 },
    "end": { "line": 2, "column": 20, "offset": 31 }
  }
}

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

  • Сохранять позиции при трансформациях — большинство плагинов Remark/Rehype автоматически сохраняют position, но при ручном создании узлов нужно задавать позиции явно.
  • Использовать offset для операций с исходной строкой — это надежнее, чем работать с line и column напрямую, особенно при мультистрочных узлах.
  • Проверять наличие позиции — не все узлы AST могут иметь position, например, синтетические узлы, создаваемые плагинами. При обработке лучше использовать условие if (node.position).

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