
# Transformer

Transformer memungkinkan Anda mengubah bentuk keluaran yang disorot pada setiap tahap — dari string sumber mentah, melalui kisi token, hingga pohon HAST final dan HTML.
shikiphp mengadaptasi model transformer Shiki hook demi hook, sehingga transformer berperilaku sama dengan padanannya di JavaScript.

Teruskan melalui opsi [`transformers`](https://shikiphp.inline0.dev/id/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(),
    ],
]);
```

## Pipeline

Setiap transformer adalah objek yang mengimplementasikan `Shikiphp\Transformer\Transformer`.
Pipeline menjalankan hook secara berurutan saat membangun satu blok:

| Hook | Tanda tangan | Kapan |
| --- | --- | --- |
| `preprocess` | `(string $code, array &$options, $context): ?string` | sebelum tokenisasi; dapat mengubah sumber dan memodifikasi opsi |
| `tokens` | `(array $tokens, $context): ?array` | setelah tokenisasi, pada kisi 2D `ThemedToken` |
| `root` | `(Element $el, $context): ?Element` | pada akar HAST |
| `pre` | `(Element $el, $context): ?Element` | pada elemen `<pre>` |
| `code` | `(Element $el, $context): ?Element` | pada elemen `<code>` |
| `line` | `(Element $el, int $line, $context): ?Element` | untuk setiap elemen baris (indeks mulai dari 1) |
| `span` | `(Element $el, int $line, int $col, Element $lineEl, ThemedToken $token, $context): ?Element` | untuk setiap rentang token |
| `postprocess` | `(string $html, array $options, $context): ?string` | pada string HTML serialisasi final |

Setiap hook bersifat opsional. Mengembalikan nilai akan mengganti input; mengembalikan `null` akan membiarkannya tetap sama.

`enforce()` mengatur urutan antar-transformer: `'pre'` dijalankan pertama, `'post'` dijalankan terakhir, dan `null` (default) dijalankan di antaranya sesuai urutan pendaftaran.

## Menulis transformer

Perluas `Shikiphp\Transformer\AbstractTransformer` agar Anda hanya perlu mengganti hook yang diperlukan; sisanya tetap tanpa operasi. Konteks transformer menyediakan `addClassToHast()` untuk menambahkan kelas ke elemen.

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

## Transformer bawaan

### Transformer notasi

Transformer ini membaca komentar `// [!code …]` khusus dalam sumber (sintaks komentar bahasa dideteksi secara otomatis) dan mengubahnya menjadi kelas, lalu menghapus komentarnya. Transformer ini merupakan adaptasi langsung dari kumpulan notasi `@shikijs/transformers`.

#### `NotationHighlight`

`Shikiphp\Transformer\Notation\NotationHighlight` — `// [!code highlight]` (atau `// [!code hl]`) menambahkan kelas `highlighted` ke barisnya.

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

#### `NotationDiff`

`Shikiphp\Transformer\Notation\NotationDiff` — `// [!code ++]` menandai baris sebagai ditambahkan (`diff add`), `// [!code --]` sebagai dihapus (`diff remove`).

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

#### `NotationFocus`

`Shikiphp\Transformer\Notation\NotationFocus` — `// [!code focus]` menambahkan kelas `focused`.

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

#### `NotationErrorLevel`

`Shikiphp\Transformer\Notation\NotationErrorLevel` — `// [!code error]`, `// [!code warning]`, dan `// [!code info]` menambahkan kelas `highlighted` beserta `error` / `warning` / `info`.

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

#### `NotationWordHighlight`

`Shikiphp\Transformer\Notation\NotationWordHighlight` — `// [!code word:foo]` (opsional `// [!code word:foo:2]` untuk membatasi hingga N baris berikutnya) membungkus setiap kemunculan `foo` dalam rentang yang memuat `highlighted-word`.

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

### Transformer meta

Transformer ini membaca **string meta** code fence yang diberikan melalui opsi [`meta`](https://shikiphp.inline0.dev/id/docs/options#meta) (`meta['__raw']`), seperti Shiki membaca teks setelah bahasa pada blok kode berpagar.

#### `MetaHighlight`

`Shikiphp\Transformer\MetaHighlight` — menyorot baris yang disebutkan dalam rentang meta seperti `{1,3-5}`, dengan menambahkan kelas `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` — menyorot kata yang disebutkan dalam string meta dengan sintaks `/word/`, dengan menambahkan `highlighted-word`.

### Spasi dan struktur

#### `RenderWhitespace`

`Shikiphp\Transformer\RenderWhitespace` — membungkus karakter spasi dan tab dalam rentang (kelas `space` dan `tab` secara default) agar dapat ditampilkan secara visual.
Konstruktor menerima `(classSpace, classTab, position)`, dengan `position` berupa `'all'` (default), `'boundary'`, `'trailing'`, atau `'leading'`.

#### `CompactLineOptions`

`Shikiphp\Transformer\CompactLineOptions` — menerapkan kelas pada baris tertentu dari daftar entri `{line, classes}`.

```php
use Shikiphp\Transformer\CompactLineOptions;

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

Nomor baris dimulai dari 1. Teruskan objek melalui opsi `transformers` seperti transformer lainnya.

#### `RemoveNotationEscape`

`Shikiphp\Transformer\RemoveNotationEscape` — menghapus escape pada bentuk `[\!code …]` yang digunakan untuk menulis komentar notasi literal tanpa memicu transformer notasi.

#### `RemoveLineBreak`

`Shikiphp\Transformer\RemoveLineBreak` — menghapus node teks pemisah baris di antara baris (Shiki milik `transformerRemoveLineBreak`).

### `StyleToClass`

`Shikiphp\Transformer\StyleToClass` — memindahkan gaya inline ke dalam kelas yang dihasilkan dan mengumpulkan stylesheet yang dapat Anda ambil dengan `getCSS()`. Nama kelas menggunakan hash gaya dengan Shiki milik `cyrb53`, sehingga keluaran identik byte demi byte dengan `@shikijs/transformers` 4.2.0, termasuk cakupannya:

- Gaya `<pre>` selalu menjadi kelas.
- Rentang token hanya dikonversi jika memuat gaya yang telah dibuat sebelumnya, seperti pada peta `themes`. Dengan satu `theme`, rentang token tetap menggunakan gaya inline `style="color:…"`, seperti pada 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
```

Konstruktor menerima `(classPrefix, classSuffix, classReplacer)`; prefiks defaultnya adalah `__shiki_`.
