
# Transformadores

Los transformadores permiten modificar la salida resaltada en cada etapa: desde la cadena de origen sin procesar, pasando por la cuadrícula de tokens, hasta el árbol HAST final y HTML. shikiphp traslada el modelo de transformadores de Shiki hook por hook, por lo que se comportan igual que sus equivalentes de JavaScript.

Páselos mediante la opción [`transformers`](https://shikiphp.inline0.dev/es/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(),
    ],
]);
```

## El proceso

Cada transformador es un objeto que implementa `Shikiphp\Transformer\Transformer`. El flujo ejecuta los hooks en orden al construir un único bloque:

| Hook | Firma | Cuándo |
| --- | --- | --- |
| `preprocess` | `(string $code, array &$options, $context): ?string` | antes de la tokenización; puede reescribir el código fuente y modificar las opciones |
| `tokens` | `(array $tokens, $context): ?array` | después de la tokenización, en la cuadrícula 2D `ThemedToken` |
| `root` | `(Element $el, $context): ?Element` | en la raíz HAST |
| `pre` | `(Element $el, $context): ?Element` | en el elemento `<pre>` |
| `code` | `(Element $el, $context): ?Element` | en el elemento `<code>` |
| `line` | `(Element $el, int $line, $context): ?Element` | por elemento de línea (base 1) |
| `span` | `(Element $el, int $line, int $col, Element $lineEl, ThemedToken $token, $context): ?Element` | por tramo de token |
| `postprocess` | `(string $html, array $options, $context): ?string` | en la cadena HTML serializada final |

Todos los hooks son opcionales. Devolver un valor reemplaza la entrada; devolver `null` la deja sin cambios.

`enforce()` controla el orden entre transformadores: `'pre'` se ejecuta primero, `'post'` al final y `null` (el valor predeterminado) en medio, según el orden de registro.

## Cómo escribir un transformador

Extiende `Shikiphp\Transformer\AbstractTransformer` para sobrescribir solo los hooks que necesites; el resto no hará nada. El contexto del transformador expone `addClassToHast()` para añadir clases a los 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 notación

Estos leen comentarios especiales `// [!code …]` en el código fuente (la sintaxis de comentarios del lenguaje se detecta automáticamente) y los convierten en clases, eliminando el comentario. Son traslados directos del conjunto de notaciones de `@shikijs/transformers`.

#### `NotationHighlight`

`Shikiphp\Transformer\Notation\NotationHighlight` — `// [!code highlight]` (o `// [!code hl]`) añade la clase `highlighted` a su línea.

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

#### `NotationDiff`

`Shikiphp\Transformer\Notation\NotationDiff` — `// [!code ++]` marca una línea como añadida (`diff add`) y `// [!code --]` como eliminada (`diff remove`).

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

#### `NotationFocus`

`Shikiphp\Transformer\Notation\NotationFocus` — `// [!code focus]` añade la clase `focused`.

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

#### `NotationErrorLevel`

`Shikiphp\Transformer\Notation\NotationErrorLevel` — `// [!code error]`, `// [!code warning]` y `// [!code info]` añaden la clase `highlighted`, además 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 limitarlo a las siguientes N líneas) envuelve cada aparición de `foo` en un tramo que incluye `highlighted-word`.

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

### Transformadores de metadatos

Estos leen la **cadena meta** del bloque de código, proporcionada mediante la opción [`meta`](https://shikiphp.inline0.dev/es/docs/options#meta) (`meta['__raw']`), de la misma forma que Shiki lee el texto después del lenguaje de un bloque de código delimitado.

#### `MetaHighlight`

`Shikiphp\Transformer\MetaHighlight` — resalta las líneas indicadas en un rango de metadatos, como `{1,3-5}`, y añade la clase `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` — resalta las palabras indicadas en la cadena de metadatos con la sintaxis `/word/` y añade `highlighted-word`.

### Espacios en blanco y estructura

#### `RenderWhitespace`

`Shikiphp\Transformer\RenderWhitespace` — envuelve los espacios y tabuladores en tramos (las clases `space` y `tab` de forma predeterminada) para que se puedan representar visualmente. El constructor recibe `(classSpace, classTab, position)`, donde `position` puede ser `'all'` (predeterminado), `'boundary'`, `'trailing'` o `'leading'`.

#### `CompactLineOptions`

`Shikiphp\Transformer\CompactLineOptions` — aplica clases a líneas específicas a partir de una lista de entradas `{line, classes}`.

```php
use Shikiphp\Transformer\CompactLineOptions;

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

La numeración de líneas empieza en 1. Pasa el objeto en la opción `transformers` como cualquier otro transformador.

#### `RemoveNotationEscape`

`Shikiphp\Transformer\RemoveNotationEscape` — desescapa la forma `[\!code …]`, que permite escribir un comentario de notación literal sin activar un transformador de notación.

#### `RemoveLineBreak`

`Shikiphp\Transformer\RemoveLineBreak` — elimina los nodos de texto de salto de línea entre líneas (Shiki de `transformerRemoveLineBreak`).

### `StyleToClass`

`Shikiphp\Transformer\StyleToClass` — mueve los estilos en línea a clases generadas y recopila una hoja de estilos que puedes obtener con `getCSS()`. Los nombres de clase calculan un hash del estilo con Shiki`cyrb53`, de modo que la salida es idéntica byte a byte a la de `@shikijs/transformers`4.2.0, incluido su ámbito:

- El estilo `<pre>` siempre se convierte en una clase.
- Los tramos de tokens solo se convierten cuando contienen un estilo preconstruido, como ocurre con un mapa `themes`. Con un único `theme`, los tramos de tokens conservan su `style="color:…"` en línea, igual que en 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
```

El constructor recibe `(classPrefix, classSuffix, classReplacer)`; el prefijo predeterminado es `__shiki_`.
