
# Transformateurs

Les transformateurs vous permettent de remanier la sortie mise en évidence à chaque étape — de la chaîne source brute à l’arbre HAST final, en passant par la grille de jetons et HTML. shikiphp reprend le modèle de transformateurs de Shiki hook par hook ; les transformateurs se comportent donc comme leurs équivalents JavaScript.

Transmettez-les via l’option [`transformers`](https://shikiphp.inline0.dev/fr/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(),
    ],
]);
```

## Le pipeline

Chaque transformateur est un objet qui implémente `Shikiphp\Transformer\Transformer`. Le pipeline exécute les hooks dans l’ordre lors de la construction d’un bloc :

| Hook | Signature | Quand |
| --- | --- | --- |
| `preprocess` | `(string $code, array &$options, $context): ?string` | avant la tokenisation ; peut réécrire la source et modifier les options |
| `tokens` | `(array $tokens, $context): ?array` | après la tokenisation, sur la grille `ThemedToken` en 2D |
| `root` | `(Element $el, $context): ?Element` | sur la racine HAST |
| `pre` | `(Element $el, $context): ?Element` | sur l’élément `<pre>` |
| `code` | `(Element $el, $context): ?Element` | sur l’élément `<code>` |
| `line` | `(Element $el, int $line, $context): ?Element` | pour chaque élément de ligne (indexation à partir de 1) |
| `span` | `(Element $el, int $line, int $col, Element $lineEl, ThemedToken $token, $context): ?Element` | pour chaque étendue de jeton |
| `postprocess` | `(string $html, array $options, $context): ?string` | sur la chaîne HTML finale sérialisée |

Tous les hooks sont facultatifs. Le renvoi d’une valeur remplace l’entrée ; le renvoi de `null` la laisse inchangée.

`enforce()` contrôle l’ordre entre les transformateurs : `'pre'` s’exécute en premier, `'post'` en dernier et `null` (la valeur par défaut) entre les deux, dans l’ordre d’enregistrement.

## Écrire un transformateur

Étendez `Shikiphp\Transformer\AbstractTransformer` pour ne remplacer que les hooks dont vous avez besoin ; les autres ne font rien. Le contexte du transformateur expose `addClassToHast()` pour ajouter des classes aux éléments.

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

## Transformateurs intégrés

### Transformateurs de notation

Ils lisent les commentaires `// [!code …]` spéciaux dans la source (la syntaxe de commentaire du langage est détectée automatiquement) et les convertissent en classes en supprimant le commentaire lui-même. Il s’agit de ports directs de l’ensemble de notations `@shikijs/transformers`.

#### `NotationHighlight`

`Shikiphp\Transformer\Notation\NotationHighlight` — `// [!code highlight]` (ou `// [!code hl]`) ajoute la classe `highlighted` à sa ligne.

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

#### `NotationDiff`

`Shikiphp\Transformer\Notation\NotationDiff` — `// [!code ++]` marque une ligne comme ajoutée (`diff add`) ; `// [!code --]` la marque comme supprimée (`diff remove`).

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

#### `NotationFocus`

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

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

#### `NotationErrorLevel`

`Shikiphp\Transformer\Notation\NotationErrorLevel` — `// [!code error]`, `// [!code warning]` et `// [!code info]` ajoutent la classe `highlighted` ainsi que `error` / `warning` / `info`.

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

#### `NotationWordHighlight`

`Shikiphp\Transformer\Notation\NotationWordHighlight` — `// [!code word:foo]` (avec `// [!code word:foo:2]` facultatif pour limiter l’effet aux N lignes suivantes) entoure chaque occurrence de `foo` dans une étendue portant `highlighted-word`.

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

### Transformateurs de métadonnées

Ils lisent la **chaîne de métadonnées** de la clôture de code fournie via l’option [`meta`](https://shikiphp.inline0.dev/fr/docs/options#meta) (`meta['__raw']`), comme Shiki lit le texte après le langage d’un bloc de code clôturé.

#### `MetaHighlight`

`Shikiphp\Transformer\MetaHighlight` — met en évidence les lignes indiquées dans une plage de métadonnées comme `{1,3-5}` et ajoute la 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` — met en évidence les mots indiqués dans la chaîne de métadonnées avec la syntaxe `/word/` et ajoute `highlighted-word`.

### Espaces blancs et structure

#### `RenderWhitespace`

`Shikiphp\Transformer\RenderWhitespace` — entoure les espaces et les tabulations d’étendues (classes `space` et `tab` par défaut) pour les rendre visibles. Le constructeur prend `(classSpace, classTab, position)`, où `position` vaut `'all'` (par défaut), `'boundary'`, `'trailing'` ou `'leading'`.

#### `CompactLineOptions`

`Shikiphp\Transformer\CompactLineOptions` — applique des classes à des lignes précises à partir d’une liste d’entrées `{line, classes}`.

```php
use Shikiphp\Transformer\CompactLineOptions;

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

La numérotation des lignes commence à 1. Transmettez l’objet dans l’option `transformers`, comme tout autre transformateur.

#### `RemoveNotationEscape`

`Shikiphp\Transformer\RemoveNotationEscape` — annule l’échappement de la forme `[\!code …]`, qui permet d’écrire un commentaire de notation littéral sans déclencher de transformateur de notation.

#### `RemoveLineBreak`

`Shikiphp\Transformer\RemoveLineBreak` — supprime les nœuds de texte de saut de ligne entre les lignes (Shiki de `transformerRemoveLineBreak`).

### `StyleToClass`

`Shikiphp\Transformer\StyleToClass` — déplace les styles en ligne vers des classes générées et collecte une feuille de style que vous récupérez avec `getCSS()`. Les noms de classe sont des empreintes du style calculées avec Shiki et son `cyrb53` ; la sortie est donc identique octet pour octet à celle de `@shikijs/transformers` 4.2.0, portée comprise :

- Le style `<pre>` devient toujours une classe.
- Les étendues de jeton sont converties uniquement si elles portent un style prédéfini, comme c’est le cas avec une map `themes`. Avec un seul `theme`, les étendues de jeton conservent leurs styles en ligne `style="color:…"`, comme dans 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
```

Le constructeur prend `(classPrefix, classSuffix, classReplacer)` ; le préfixe par défaut est `__shiki_`.
