Nette Documentation Preview

syntax
Procházení
**********

.[perex]
Traverser, potomci a předci, sousední tokeny a pozice: jak se ve stromu pohybovat a jak se ptát, zda jsou dva výrazy stejné nebo bezpečně opakovatelné.


Shora dolů
==========

`Traverser` projde strom do hloubky v pořadí zdrojáku a zavolá `enter` při vstupu do uzlu nebo tokenu a `leave` při výstupu:

```php
use PhpSyntax\Traverser;

$calls = [];
(new Traverser(enter: function (Node|Token $node) use (&$calls) {
	if ($node instanceof FunctionCallNode || $node instanceof MethodCallNode) {
		$calls[] = $node->getFirstToken()->getLine() . ': ' . $node;
	}
}))->traverse($file);
```

Callback smí uzel, který dostal, nahradit nebo odstranit; traverser pak do něj ani do jeho náhrady nesestoupí, `leave` pro něj nezavolá, sourozence odpojené mezitím přeskočí a uzly vložené mezitím nechá dalšímu průchodu. Díky tomu se dá měnit strom přímo během průchodu bez zvláštního režimu.

Když nepotřebujete pořadí ani události, stačí snímek:

```php
foreach ($file->getDescendants(FunctionCallNode::class) as $call) {
	// ...
}
```

`getDescendants()` vrátí pole všech potomků dané třídy (nebo všech), a protože je to pole pořízené předem, je bezpečné během procházení strom měnit.


Zdola nahoru a do stran
=======================

Každý uzel i token zná svého rodiče (`$node->parent`) a od něj se dá jít výš:

```php
$method = $call->findAncestor(MethodNode::class);   // nejbližší předek dané třídy, nebo null
$class = $call->findAncestor(ClassLikeNode::class);
```

Po tokenech se chodí do stran: `$token->getNext()` a `getPrevious()` je následující a předchozí token v souboru bez ohledu na strukturu. `$node->getFirstToken()` a `getLastToken()` jsou okraje uzlu, takže "token před uzlem" je `$node->getFirstToken()->getPrevious()`.

Uzel se chová jako řetězec: `(string) $node` je jeho text včetně trivia uvnitř, což se hodí do hlášek i při ladění.


Pozice
======

```php
$token->getLine();       // řádek v aktuálním stavu stromu
$token->getColumn();     // sloupec
$token->getOffset();     // bajtový offset
$token->originalLine;    // řádek v původním souboru, nikdy se nemění
$node->getStartLine();   // řádky uzlu
$node->getEndLine();
```

Rozdíl mezi `getLine()` a `originalLine` je podstatný, jakmile strom měníte: první sleduje změny, druhé je pozice, kterou zná uživatel ze svého souboru, a do hlášení patří ta druhá. U uzlu vloženého za běhu je `originalLine` `null`. Pozice počítá index tokenů, který se staví líně a po změně stromu se opravuje jen tam, kde je potřeba, takže dotaz hned po změně stojí tolik, jak daleko od změny leží.


Dvě otázky, které si klade polovina pravidel
============================================

```php
$a->matches($b);          // jsou ty dva uzly stejné, token po tokenu, bez ohledu na mezery?
$expr->isRepeatableRead(); // dá se výraz vyhodnotit podruhé, aniž by se něco změnilo?
```

`matches()` porovnává texty tokenů, ne trivia, takže `$a->b` a `$a -> b` si odpovídají. `isRepeatableRead()` říká `true` pro proměnné, přístupy k vlastnostem a prvkům, konstanty a literály, a `false` pro volání, `new`, přiřazení, `++` a cokoli, co může mít vedlejší účinek. Dohromady odpovídají na otázku "opakuje tenhle ternár svou podmínku a smím ji vyhodnotit jen jednou", a jsou ve stromu právě proto, aby si ji každé pravidlo neřešilo znovu.

Procházení

Traverser, potomci a předci, sousední tokeny a pozice: jak se ve stromu pohybovat a jak se ptát, zda jsou dva výrazy stejné nebo bezpečně opakovatelné.

Shora dolů

Traverser projde strom do hloubky v pořadí zdrojáku a zavolá enter při vstupu do uzlu nebo tokenu a leave při výstupu:

use PhpSyntax\Traverser;

$calls = [];
(new Traverser(enter: function (Node|Token $node) use (&$calls) {
	if ($node instanceof FunctionCallNode || $node instanceof MethodCallNode) {
		$calls[] = $node->getFirstToken()->getLine() . ': ' . $node;
	}
}))->traverse($file);

Callback smí uzel, který dostal, nahradit nebo odstranit; traverser pak do něj ani do jeho náhrady nesestoupí, leave pro něj nezavolá, sourozence odpojené mezitím přeskočí a uzly vložené mezitím nechá dalšímu průchodu. Díky tomu se dá měnit strom přímo během průchodu bez zvláštního režimu.

Když nepotřebujete pořadí ani události, stačí snímek:

foreach ($file->getDescendants(FunctionCallNode::class) as $call) {
	// ...
}

getDescendants() vrátí pole všech potomků dané třídy (nebo všech), a protože je to pole pořízené předem, je bezpečné během procházení strom měnit.

Zdola nahoru a do stran

Každý uzel i token zná svého rodiče ($node->parent) a od něj se dá jít výš:

$method = $call->findAncestor(MethodNode::class);   // nejbližší předek dané třídy, nebo null
$class = $call->findAncestor(ClassLikeNode::class);

Po tokenech se chodí do stran: $token->getNext() a getPrevious() je následující a předchozí token v souboru bez ohledu na strukturu. $node->getFirstToken() a getLastToken() jsou okraje uzlu, takže „token před uzlem“ je $node->getFirstToken()->getPrevious().

Uzel se chová jako řetězec: (string) $node je jeho text včetně trivia uvnitř, což se hodí do hlášek i při ladění.

Pozice

$token->getLine();       // řádek v aktuálním stavu stromu
$token->getColumn();     // sloupec
$token->getOffset();     // bajtový offset
$token->originalLine;    // řádek v původním souboru, nikdy se nemění
$node->getStartLine();   // řádky uzlu
$node->getEndLine();

Rozdíl mezi getLine() a originalLine je podstatný, jakmile strom měníte: první sleduje změny, druhé je pozice, kterou zná uživatel ze svého souboru, a do hlášení patří ta druhá. U uzlu vloženého za běhu je originalLine null. Pozice počítá index tokenů, který se staví líně a po změně stromu se opravuje jen tam, kde je potřeba, takže dotaz hned po změně stojí tolik, jak daleko od změny leží.

Dvě otázky, které si klade polovina pravidel

$a->matches($b);          // jsou ty dva uzly stejné, token po tokenu, bez ohledu na mezery?
$expr->isRepeatableRead(); // dá se výraz vyhodnotit podruhé, aniž by se něco změnilo?

matches() porovnává texty tokenů, ne trivia, takže $a->b a $a -> b si odpovídají. isRepeatableRead() říká true pro proměnné, přístupy k vlastnostem a prvkům, konstanty a literály, a false pro volání, new, přiřazení, ++ a cokoli, co může mít vedlejší účinek. Dohromady odpovídají na otázku „opakuje tenhle ternár svou podmínku a smím ji vyhodnotit jen jednou“, a jsou ve stromu právě proto, aby si ji každé pravidlo neřešilo znovu.