Nette Documentation Preview

syntax
Úpravy
******

.[perex]
Sloty se zapisují přiřazením, nahrazení a odstranění uzlu, práce se seznamy a nový uzel z fragmentu: jak strom měnit tak, aby zůstal konzistentní a po tisku se změnilo jen to, na co jste sáhli.


Zápis do slotu
==============

Slot je vlastnost s hookem, takže se zapisuje přiřazením:

```php
$if->cond = $parser->parseExpression('$order->isPaid()');
$node->replaceChild($old, $new);
$node->setSlot('cond', $expr);     // když jméno slotu znáte až za běhu
```

Hook se postará o to, co byste jinak museli hlídat: odmítne hodnotu, která už má rodiče, adoptuje novou, pustí starou a ohlásí změnu indexu tokenů, který drží pořadí, řádky a sloupce. Typ vlastnosti odmítne hodnotu, která do slotu nepatří, ještě dřív.

Uzel patří právě jednomu stromu. Vložit uzel, který už rodiče má, je výjimka; `clone` dá hlubokou kopii bez rodiče, kterou vložit lze.

Co nemá hook, hlídá jazyk: text a trivia tokenu jsou `private(set)` a mění se metodami ([Trivia |trivia#Zápis]), položky seznamu jsou `protected(set)` a mění se metodami seznamu, `parent` zapisuje strom sám. Není tedy jak strom rozbít zápisem vedle API.


Nahradit a odstranit
====================

```php
$node->replaceWith($other);
$node->remove();
$node->remove(CommentPolicy::Drop);
```

Mezi přiřazením do slotu a `replaceWith()` je rozdíl, na který se přijde až v diffu: **`replaceWith()` zachová trivia kolem starého uzlu, přiřazení ne.**

```php
$ternary->cond = $parser->parseExpression('$a !== []');
// $x = $a !== []? 'yes' : 'no';

$ternary->cond->replaceWith($parser->parseExpression('$a !== []'));
// $x = $a !== [] ? 'yes' : 'no';
```

Fragment z parseru má okraje prázdné, takže přiřazením se ztratí mezera, která patřila starému uzlu. Přiřazení použijte tam, kde slot plníte poprvé nebo kde trivia řešíte sami; `replaceWith()` všude jinde.

`remove()` odstraní položku seznamu: uzel, který stál na řádcích sám, vezme řádky s sebou, jinak zůstane okolní mezera. Komentáře uvnitř odstraňovaného uzlu se podle `CommentPolicy` přesunou k následujícímu tokenu (výchozí), k předchozímu, nebo zahodí; ztratit komentář mlčky nejde.


Seznamy
=======

```php
$stmts->append($stmt);
$stmts->insert($index, $stmt);
$stmts->removeItem($stmt);

$args->append($arg);                  // oddělovač se odvodí z existujících, nebo ', '
$args->insert(0, $arg);
$args->setTrailingSeparator(null);    // pryč s koncovou čárkou
```

`SeparatedNodeList` si oddělovače hlídá sám: při vložení odvodí chybějící čárku z těch, které v seznamu jsou (nebo použije `, ` u jednořádkového seznamu), a ve víceřádkovém seznamu dá položce odsazení souseda; při odstranění vezme čárku za položkou, u poslední tu před ní. `removeItem()` odstraňuje položku ze seznamu, `remove()` na uzlu odstraňuje uzel sám; je to totéž z druhé strany a jmenuje se to jinak jen proto, že PHP nedovolí dvě různé signatury.

`NodeList` příkazů odsazení za vás neřeší, protože příkaz může stát ledaskde. Vložený příkaz proto dostane řádek a odsazení výslovně, a odsazení souseda si přečtěte dřív, než ho vložením připravíte o začátek řádku:

```php
$stmt = $parser->parseStatement('$this->log("total");');
$indent = $return->getFirstToken()->getLineIndentation();

$return->parent->insert($return->parent->indexOf($return), $stmt);
$return->getFirstToken()->ensureLeadingNewline();
$return->getFirstToken()->setIndentation($indent);
$stmt->getFirstToken()->setIndentation($indent);
```


Nový uzel
=========

Nový kód se nestaví z tokenů, parsuje se z řetězce: `Parser::parseExpression()`, `parseStatement()`, `parseType()`, `parseName()` vrátí odpojený uzel s prázdnými trivia na okrajích, připravený k vložení. Kdo chce místo psaní řetězce klonovat existující uzel, musí klonu nejdřív vyčistit okraje přes `setEdgeTrivia()`: klon si nese úvodní trivia originálu, a u prvního příkazu souboru je v nich i `<?php`, které by se ve výstupu objevilo dvakrát.

Nejmenší možná úprava bývá změna textu jednoho tokenu a často stačí:

```php
$call->name->text = 'count';                  // jméno; hook ho přetokenizuje
$string->setValue('Hello', "'");              // hodnotu literálu i s uvozovkou
$token->setText('!==');
```


Co si strom hlídá sám a co ne
=============================

Sám si hlídá rodiče, index a `FileNode::$revision`, které roste s každou změnou; kdo potřebuje vědět, jestli se něco změnilo, porovná revizi před a po, ale nepočítá s tím, že jedna úprava je jedno zvýšení, protože složená úprava jako `remove()` jich udělá několik.

Nehlídá si dvě věci, které zůstávají na vás:

- **Kanonická trivia.** Konec řádku, který uzavírá řádek tokenu, patří do jeho koncových trivia, ne do úvodních trivia dalšího tokenu. `ensureLeadingNewline()` a `setBlankLinesBefore()` to dělají správně; kdo skládá trivia ručně, ať to dodrží, jinak `getTrailingSpace()` zalomení neuvidí.
- **Význam.** Strom vám dovolí nahradit podmínku čímkoli a odstranit `return`; jestli to smíte, je otázka na `isRepeatableRead()`, `matches()` a na vás.

Po úpravě se tiskne `Printer::print($file)` nebo `(string) $file` a výstup je původní soubor se změnami přesně tam, kde jste je udělali.

Úpravy

Sloty se zapisují přiřazením, nahrazení a odstranění uzlu, práce se seznamy a nový uzel z fragmentu: jak strom měnit tak, aby zůstal konzistentní a po tisku se změnilo jen to, na co jste sáhli.

Zápis do slotu

Slot je vlastnost s hookem, takže se zapisuje přiřazením:

$if->cond = $parser->parseExpression('$order->isPaid()');
$node->replaceChild($old, $new);
$node->setSlot('cond', $expr);     // když jméno slotu znáte až za běhu

Hook se postará o to, co byste jinak museli hlídat: odmítne hodnotu, která už má rodiče, adoptuje novou, pustí starou a ohlásí změnu indexu tokenů, který drží pořadí, řádky a sloupce. Typ vlastnosti odmítne hodnotu, která do slotu nepatří, ještě dřív.

Uzel patří právě jednomu stromu. Vložit uzel, který už rodiče má, je výjimka; clone dá hlubokou kopii bez rodiče, kterou vložit lze.

Co nemá hook, hlídá jazyk: text a trivia tokenu jsou private(set) a mění se metodami (Trivia), položky seznamu jsou protected(set) a mění se metodami seznamu, parent zapisuje strom sám. Není tedy jak strom rozbít zápisem vedle API.

Nahradit a odstranit

$node->replaceWith($other);
$node->remove();
$node->remove(CommentPolicy::Drop);

Mezi přiřazením do slotu a replaceWith() je rozdíl, na který se přijde až v diffu: replaceWith() zachová trivia kolem starého uzlu, přiřazení ne.

$ternary->cond = $parser->parseExpression('$a !== []');
// $x = $a !== []? 'yes' : 'no';

$ternary->cond->replaceWith($parser->parseExpression('$a !== []'));
// $x = $a !== [] ? 'yes' : 'no';

Fragment z parseru má okraje prázdné, takže přiřazením se ztratí mezera, která patřila starému uzlu. Přiřazení použijte tam, kde slot plníte poprvé nebo kde trivia řešíte sami; replaceWith() všude jinde.

remove() odstraní položku seznamu: uzel, který stál na řádcích sám, vezme řádky s sebou, jinak zůstane okolní mezera. Komentáře uvnitř odstraňovaného uzlu se podle CommentPolicy přesunou k následujícímu tokenu (výchozí), k předchozímu, nebo zahodí; ztratit komentář mlčky nejde.

Seznamy

$stmts->append($stmt);
$stmts->insert($index, $stmt);
$stmts->removeItem($stmt);

$args->append($arg);                  // oddělovač se odvodí z existujících, nebo ', '
$args->insert(0, $arg);
$args->setTrailingSeparator(null);    // pryč s koncovou čárkou

SeparatedNodeList si oddělovače hlídá sám: při vložení odvodí chybějící čárku z těch, které v seznamu jsou (nebo použije , ` u jednořádkového seznamu), a ve víceřádkovém seznamu dá položce odsazení souseda; při odstranění vezme čárku za položkou, u poslední tu před ní. `removeItem() odstraňuje položku ze seznamu, remove() na uzlu odstraňuje uzel sám; je to totéž z druhé strany a jmenuje se to jinak jen proto, že PHP nedovolí dvě různé signatury.

NodeList příkazů odsazení za vás neřeší, protože příkaz může stát ledaskde. Vložený příkaz proto dostane řádek a odsazení výslovně, a odsazení souseda si přečtěte dřív, než ho vložením připravíte o začátek řádku:

$stmt = $parser->parseStatement('$this->log("total");');
$indent = $return->getFirstToken()->getLineIndentation();

$return->parent->insert($return->parent->indexOf($return), $stmt);
$return->getFirstToken()->ensureLeadingNewline();
$return->getFirstToken()->setIndentation($indent);
$stmt->getFirstToken()->setIndentation($indent);

Nový uzel

Nový kód se nestaví z tokenů, parsuje se z řetězce: Parser::parseExpression(), parseStatement(), parseType(), parseName() vrátí odpojený uzel s prázdnými trivia na okrajích, připravený k vložení. Kdo chce místo psaní řetězce klonovat existující uzel, musí klonu nejdřív vyčistit okraje přes setEdgeTrivia(): klon si nese úvodní trivia originálu, a u prvního příkazu souboru je v nich i <?php, které by se ve výstupu objevilo dvakrát.

Nejmenší možná úprava bývá změna textu jednoho tokenu a často stačí:

$call->name->text = 'count';                  // jméno; hook ho přetokenizuje
$string->setValue('Hello', "'");              // hodnotu literálu i s uvozovkou
$token->setText('!==');

Co si strom hlídá sám a co ne

Sám si hlídá rodiče, index a FileNode::$revision, které roste s každou změnou; kdo potřebuje vědět, jestli se něco změnilo, porovná revizi před a po, ale nepočítá s tím, že jedna úprava je jedno zvýšení, protože složená úprava jako remove() jich udělá několik.

Nehlídá si dvě věci, které zůstávají na vás:

  • Kanonická trivia. Konec řádku, který uzavírá řádek tokenu, patří do jeho koncových trivia, ne do úvodních trivia dalšího tokenu. ensureLeadingNewline() a setBlankLinesBefore() to dělají správně; kdo skládá trivia ručně, ať to dodrží, jinak getTrailingSpace() zalomení neuvidí.
  • Význam. Strom vám dovolí nahradit podmínku čímkoli a odstranit return; jestli to smíte, je otázka na isRepeatableRead(), matches() a na vás.

Po úpravě se tiskne Printer::print($file) nebo (string) $file a výstup je původní soubor se změnami přesně tam, kde jste je udělali.