
# Перетворювачі

Перетворювачі дають змогу змінювати підсвічений вивід на кожному етапі — від
початкового рядка коду через сітку токенів до підсумкового дерева HAST і HTML.
Порт shikiphp точно відтворює модель перетворювачів Shiki, хук за хуком, тож перетворювачі працюють так само, як їхні відповідники в JavaScript.

Передайте їх через параметр [`transformers`](https://shikiphp.inline0.dev/uk/docs/options#transformers):

```php
use Shikiphp\Shikiphp;
use Shikiphp\Transformer\Notation\NotationHighlight;
use Shikiphp\Transformer\Notation\NotationDiff;

$code = <<<'JS'
console.log('this line is highlighted') // [!code highlight]
console.log('old line') // [!code --]
console.log('new line') // [!code ++]
JS;

echo Shikiphp::codeToHtml($code, [
    'lang'         => 'js',
    'theme'        => 'vitesse-dark',
    'transformers' => [
        new NotationHighlight(),
        new NotationDiff(),
    ],
]);
```

## Конвеєр

Кожен перетворювач — це об’єкт, що реалізує `Shikiphp\Transformer\Transformer`.
Конвеєр послідовно запускає хуки під час побудови одного блока:

| Хук | Сигнатура | Коли виконується |
| --- | --- | --- |
| `preprocess` | `(string $code, array &$options, $context): ?string` | до токенізації; може змінити вихідний код і параметри |
| `tokens` | `(array $tokens, $context): ?array` | після токенізації, на двовимірній сітці `ThemedToken` |
| `root` | `(Element $el, $context): ?Element` | для кореня HAST |
| `pre` | `(Element $el, $context): ?Element` | для елемента `<pre>` |
| `code` | `(Element $el, $context): ?Element` | для елемента `<code>` |
| `line` | `(Element $el, int $line, $context): ?Element` | для кожного елемента рядка (нумерація від 1) |
| `span` | `(Element $el, int $line, int $col, Element $lineEl, ThemedToken $token, $context): ?Element` | для кожного діапазону токена |
| `postprocess` | `(string $html, array $options, $context): ?string` | для остаточного серіалізованого рядка HTML |

Усі хуки необов’язкові. Повернення значення замінює вхідні дані; повернення `null`
залишає їх без змін.

За порядок виконання перетворювачів відповідає `enforce()`: спершу виконується `'pre'`, останнім — `'post'`,
а `null` (стандартне значення) — між ними, у порядку реєстрації.

## Створення перетворювача

Розширте `Shikiphp\Transformer\AbstractTransformer`, щоб перевизначати лише потрібні вам хуки; решта залишаться порожніми операціями. Контекст трансформера надає `addClassToHast()` для додавання класів до елементів.

```php
use Shikiphp\Hast\Element;
use Shikiphp\Shikiphp;
use Shikiphp\Transformer\AbstractTransformer;
use Shikiphp\Transformer\TransformerContext;

final class AddLanguageClass extends AbstractTransformer
{
    public function pre(Element $element, TransformerContext $context): ?Element
    {
        if ($context->lang !== null) {
            $context->addClassToHast($element, 'language-' . $context->lang);
        }

        return $element;
    }
}

echo Shikiphp::codeToHtml('const answer = 42', [
    'lang'         => 'js',
    'theme'        => 'nord',
    'transformers' => [new AddLanguageClass()],
]);
// <pre class="shiki nord language-js" …>
```

## Вбудовані трансформери

### Трансформери нотацій

Вони читають спеціальні коментарі `// [!code …]` у вихідному коді (синтаксис коментарів мови визначається автоматично) і перетворюють їх на класи, видаляючи сам коментар. Це прямі порти набору нотацій `@shikijs/transformers`.

#### `NotationHighlight`

`Shikiphp\Transformer\Notation\NotationHighlight` — `// [!code highlight]` (або `// [!code hl]`) додає до свого рядка клас `highlighted`.

```js
console.log('kept plain')
console.log('this line is highlighted') // [!code highlight]
```

#### `NotationDiff`

`Shikiphp\Transformer\Notation\NotationDiff` — `// [!code ++]` позначає рядок як доданий (`diff add`), а `// [!code --]` — як видалений (`diff remove`).

```js
console.log('old line') // [!code --]
console.log('new line') // [!code ++]
```

#### `NotationFocus`

`Shikiphp\Transformer\Notation\NotationFocus` — `// [!code focus]` додає клас `focused`.

```js
console.log('focus on me') // [!code focus]
```

#### `NotationErrorLevel`

`Shikiphp\Transformer\Notation\NotationErrorLevel` — `// [!code error]`, `// [!code warning]` і `// [!code info]` додають клас `highlighted`, а також `error` / `warning` / `info`.

```js
throwSomething() // [!code error]
maybeRisky()     // [!code warning]
```

#### `NotationWordHighlight`

`Shikiphp\Transformer\Notation\NotationWordHighlight` — `// [!code word:foo]` (необов’язково `// [!code word:foo:2]`, щоб обмежити дію наступними N рядками) обгортає кожен збіг `foo` у span з атрибутом `highlighted-word`.

```js
// [!code word:config]
const config = loadConfig()
```

### Метатрансформери

Вони читають **рядок метаданих** блоку коду, переданий через опцію [`meta`](https://shikiphp.inline0.dev/uk/docs/options#meta) (`meta['__raw']`), так само як Shiki читає текст після назви мови в огородженому блоці коду.

#### `MetaHighlight`

`Shikiphp\Transformer\MetaHighlight` — підсвічує рядки, зазначені в діапазоні метаданих, наприклад `{1,3-5}`, додаючи клас `highlighted`.

```php
use Shikiphp\Shikiphp;
use Shikiphp\Transformer\MetaHighlight;

$code = "const a = 1\nconst b = 2\nconst c = 3\nconst d = 4\nconst e = 5";

echo Shikiphp::codeToHtml($code, [
    'lang'         => 'js',
    'theme'        => 'nord',
    'meta'         => ['__raw' => '{1,3-5}'],
    'transformers' => [new MetaHighlight()],
]);
```

#### `MetaWordHighlight`

`Shikiphp\Transformer\MetaWordHighlight` — підсвічує слова, зазначені в рядку метаданих за допомогою синтаксису `/word/`, додаючи `highlighted-word`.

### Пробіли та структура

#### `RenderWhitespace`

`Shikiphp\Transformer\RenderWhitespace` — обгортає пробіли й символи табуляції у span (класи `space` і `tab` за замовчуванням), щоб їх можна було візуально відобразити. Конструктор приймає `(classSpace, classTab, position)`, де `position` — це `'all'` (за замовчуванням), `'boundary'`, `'trailing'` або `'leading'`.

#### `CompactLineOptions`

`Shikiphp\Transformer\CompactLineOptions` — застосовує класи до окремих рядків зі списку записів `{line, classes}`.

```php
use Shikiphp\Transformer\CompactLineOptions;

$transformer = new CompactLineOptions([
    ['line' => 1, 'classes' => 'highlighted'],
    ['line' => 3, 'classes' => ['highlighted', 'error']],
]);
```

Нумерація рядків починається з 1. Передайте об’єкт в опції `transformers`, як і будь-який інший трансформер.

#### `RemoveNotationEscape`

`Shikiphp\Transformer\RemoveNotationEscape` — скасовує екранування форми `[\!code …]`, якою записують буквальний коментар нотації, не запускаючи трансформер нотації.

#### `RemoveLineBreak`

`Shikiphp\Transformer\RemoveLineBreak` — видаляє текстові вузли перенесення рядка між рядками (`transformerRemoveLineBreak` у Shiki).

### `StyleToClass`

`Shikiphp\Transformer\StyleToClass` — переносить вбудовані стилі до згенерованих класів і збирає таблицю стилів, яку можна отримати через `getCSS()`. Назви класів є хешами стилю, обчисленими за допомогою `cyrb53` у Shiki, тож результат побайтово ідентичний 4.2.0 `@shikijs/transformers`, включно з його областю дії:

- Стиль `<pre>` завжди перетворюється на клас.
- Span токенів перетворюються лише тоді, коли мають попередньо створений стиль, як у випадку з мапою `themes`. Якщо використано один `theme`, span токенів зберігають вбудований `style="color:…"`, як і в Shiki.

```php
use Shikiphp\Shikiphp;
use Shikiphp\Transformer\StyleToClass;

$code = 'const answer = 42';
$styleToClass = new StyleToClass();

$html = Shikiphp::codeToHtml($code, [
    'lang'         => 'js',
    'themes'       => ['light' => 'github-light', 'dark' => 'github-dark'],
    'transformers' => [$styleToClass],
]);

$css = $styleToClass->getCSS(); // collected stylesheet for the classes above
```

Конструктор приймає `(classPrefix, classSuffix, classReplacer)`; префікс за замовчуванням — `__shiki_`.
