
# Transformadores

Os transformadores permitem remodelar a saída realçada em cada etapa — da
string de origem bruta, passando pela grade de tokens, até a árvore HAST final e HTML.
shikiphp implementa o modelo de transformadores de Shiki hook por hook, para que os transformadores se comportem
da mesma forma que seus equivalentes em JavaScript.

Passe-os pela opção [`transformers`](https://shikiphp.inline0.dev/pt/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(),
    ],
]);
```

## O pipeline

Cada transformador é um objeto que implementa `Shikiphp\Transformer\Transformer`.
O pipeline executa os hooks em ordem durante a construção de um único bloco:

| Hook | Assinatura | Quando |
| --- | --- | --- |
| `preprocess` | `(string $code, array &$options, $context): ?string` | antes da tokenização; pode reescrever a origem e alterar opções |
| `tokens` | `(array $tokens, $context): ?array` | após a tokenização, na grade 2D `ThemedToken` |
| `root` | `(Element $el, $context): ?Element` | na raiz HAST |
| `pre` | `(Element $el, $context): ?Element` | no elemento `<pre>` |
| `code` | `(Element $el, $context): ?Element` | no elemento `<code>` |
| `line` | `(Element $el, int $line, $context): ?Element` | por elemento de linha (base 1) |
| `span` | `(Element $el, int $line, int $col, Element $lineEl, ThemedToken $token, $context): ?Element` | por trecho de token |
| `postprocess` | `(string $html, array $options, $context): ?string` | na string HTML serializada final |

Todos os hooks são opcionais. Retornar um valor substitui a entrada; retornar `null`
a mantém inalterada.

`enforce()` controla a ordem entre transformadores: `'pre'` é executado primeiro, `'post'`
por último e `null` (o padrão) entre eles, na ordem de registro.

## Criando um transformador

Estenda `Shikiphp\Transformer\AbstractTransformer` para substituir apenas os hooks
necessários; os demais não fazem nada. O contexto do transformador expõe
`addClassToHast()` para adicionar classes aos elementos.

```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" …>
```

## Transformadores integrados

### Transformadores de notação

Eles leem comentários especiais `// [!code …]` na origem (a sintaxe de comentário do
idioma é detectada automaticamente) e os convertem em classes,
removendo o próprio comentário. São implementações diretas do conjunto de
notações `@shikijs/transformers`.

#### `NotationHighlight`

`Shikiphp\Transformer\Notation\NotationHighlight` — `// [!code highlight]` (ou
`// [!code hl]`) adiciona a classe `highlighted` à linha.

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

#### `NotationDiff`

`Shikiphp\Transformer\Notation\NotationDiff` — `// [!code ++]` marca uma linha como
adicionada (`diff add`), e `// [!code --]` como removida (`diff remove`).

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

#### `NotationFocus`

`Shikiphp\Transformer\Notation\NotationFocus` — `// [!code focus]` adiciona a
classe `focused`.

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

#### `NotationErrorLevel`

`Shikiphp\Transformer\Notation\NotationErrorLevel` — `// [!code error]`,
`// [!code warning]` e `// [!code info]` adicionam a classe `highlighted`, além de
`error` / `warning` / `info`.

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

#### `NotationWordHighlight`

`Shikiphp\Transformer\Notation\NotationWordHighlight` — `// [!code word:foo]`
(opcionalmente `// [!code word:foo:2]` para limitar às próximas N linhas) envolve cada
ocorrência de `foo` em um span com `highlighted-word`.

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

### Transformadores de metadados

Eles leem a **string de metadados** do bloco de código fornecida pela opção
[`meta`](https://shikiphp.inline0.dev/pt/docs/options#meta) (`meta['__raw']`), como Shiki lê
o texto após o idioma de um bloco de código delimitado.

#### `MetaHighlight`

`Shikiphp\Transformer\MetaHighlight` — realça linhas indicadas em um intervalo de metadados
como `{1,3-5}`, adicionando a classe `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` — realça palavras indicadas na
string de metadados com a sintaxe `/word/`, adicionando `highlighted-word`.

### Espaços em branco e estrutura

#### `RenderWhitespace`

`Shikiphp\Transformer\RenderWhitespace` — envolve caracteres de espaço e tabulação em
spans (classes `space` e `tab` por padrão) para que possam ser exibidos visualmente.
O construtor recebe `(classSpace, classTab, position)`, em que `position` é
`'all'` (padrão), `'boundary'`, `'trailing'` ou `'leading'`.

#### `CompactLineOptions`

`Shikiphp\Transformer\CompactLineOptions` — aplica classes a linhas específicas
com base em uma lista de entradas `{line, classes}`.

```php
use Shikiphp\Transformer\CompactLineOptions;

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

Os números das linhas começam em 1. Passe o objeto na opção `transformers` como faria
com qualquer outro transformador.

#### `RemoveNotationEscape`

`Shikiphp\Transformer\RemoveNotationEscape` — remove o escape da forma `[\!code …]`
usada para escrever um comentário de notação literal sem acionar um
transformador de notação.

#### `RemoveLineBreak`

`Shikiphp\Transformer\RemoveLineBreak` — remove os nós de texto de quebra de linha
entre linhas (Shiki `transformerRemoveLineBreak`).

### `StyleToClass`

`Shikiphp\Transformer\StyleToClass` — transfere estilos inline para classes geradas
e reúne uma folha de estilos que você pode obter com `getCSS()`. Os nomes das classes usam um hash do estilo
com Shiki `cyrb53`, então a saída é idêntica byte a byte a `@shikijs/transformers`
4.2.0, inclusive seu escopo:

- O estilo `<pre>` sempre se torna uma classe.
- Os spans de token só são convertidos quando incluem um estilo pré-construído, como
  ocorre com um mapa `themes`. Com um único `theme`, os spans de token mantêm seu
  estilo inline `style="color:…"`, como em 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
```

O construtor recebe `(classPrefix, classSuffix, classReplacer)`; o prefixo
padrão é `__shiki_`.
