Nette Documentation Preview

syntax
Analýzy
*******

.[perex]
NameResolver a Scope odpovídají na otázky, které si klade každý nástroj nad kódem: co je tohle jméno doopravdy a v jaké funkci a třídě kód stojí.


Co je analýza
=============

Strom říká, jak kód vypadá. Analýza je informace odvozená z celého souboru, kterou strom sám nenese: co znamená jméno `Order` v tomhle souboru, jestli je `$this` k dispozici, které proměnné closure zachycuje. Je to obyčejná třída postavená nad `FileNode`; nic se neregistruje a žádné rozhraní se neimplementuje.

```php
$resolver = new PhpSyntax\Analyses\NameResolver($file);
$scope = new PhpSyntax\Analyses\Scope;
```

Analýza čte strom tak, jak vypadal, když vznikla. Po změně stromu si ji postavte znovu; že se strom změnil, poznáte podle `FileNode::$revision`.


NameResolver
============

Překládá jména podle jmenného prostoru a importů, s tím, co PHP dělá za běhu: nekvalifikovaná funkce nebo konstanta, která ve jmenném prostoru není, spadne do globálního.

```php
$resolver->getNamespace($node);         // 'App\Model', '' v globálním prostoru
$resolver->getClassImports($node);      // alias => plné jméno, platné v místě uzlu
$resolver->getFunctionImports($node);
$resolver->getConstantImports($node);

$resolver->resolveClass($name);         // 'App\Model\Order' z NameNode
$resolver->resolveFunction($name);
$resolver->resolveConstant($name);

$resolver->isGlobalFunctionCall($node, 'sizeof');   // volání globální funkce, případně té jedné
```

`isGlobalFunctionCall()` je otázka, kterou si dřív každý nástroj řešil sám a většinou hůř: uzel musí být volání funkce se jménem (ne `$f()` ani `$obj->f()`), jméno nesmí být klíčové slovo a po překladu musí být globální. Tak se pozná, že `count($a)` uvnitř `namespace App` opravdu volá `count`, a ne `App\count`, pokud taková funkce v souboru není importovaná.

Cesta zpátky je stejně užitečná: jak plné jméno **zapsat** v místě, kde stojí uzel.

```php
$role = PhpSyntax\NameRole::ClassLike;

$resolver->getShortName('Nette\Utils\Strings', $role, $at);   // 'Strings', je importované
$resolver->getShortName('App\Model\Order', $role, $at);       // 'Order'
$resolver->getShortName('Other\Thing', $role, $at);           // '\Other\Thing', jinak by se hledalo v namespace
$resolver->isAliasFree('Strings', $role, $at);                // false, ten alias už něco znamená
```

`getShortName()` vezme v úvahu aliasy, prefixové importy i jmenný prostor a vrátí nejkratší zápis, který na tom místě znamená totéž. `isAliasFree()` odpoví na otázku, kterou si klade každý, kdo chce import doplnit: je tohle jméno tady ještě volné?


Scope
=====

Kde ve struktuře kód stojí:

```php
$scope->getFunction($node);           // FunctionNode, MethodNode, ClosureNode, ArrowFunctionNode, PropertyHookNode nebo null
$scope->getClass($node);              // ClassLikeNode nebo null
$scope->hasThis($node);               // je tu $this: v nestatické metodě, v closure, která ho zachytila
$scope->getCapturedVariables($closure);   // proměnné z use (...)
```

`hasThis()` bere v úvahu i to, že closure `$this` zdědí, ale `static function` ne, a že arrow funkce ho vidí vždy, když ho vidí okolí.


Vlastní analýza
===============

Když víc míst potřebuje tutéž informaci o souboru, napište ji jako třídu s konstruktorem přijímajícím `FileNode`. Nástroj postavený na PhpSyntax si takovou analýzu obvykle staví a zahazuje sám podle revize stromu; [DressCode |dresscode:rule-contract#Analýzy] to dělá za pravidla a přidává k oběma vestavěným ještě `PhpDoc` nad dokumentačními komentáři, protože ten už potřebuje parser phpDocu, který sem nepatří.

Analýzy

NameResolver a Scope odpovídají na otázky, které si klade každý nástroj nad kódem: co je tohle jméno doopravdy a v jaké funkci a třídě kód stojí.

Co je analýza

Strom říká, jak kód vypadá. Analýza je informace odvozená z celého souboru, kterou strom sám nenese: co znamená jméno Order v tomhle souboru, jestli je $this k dispozici, které proměnné closure zachycuje. Je to obyčejná třída postavená nad FileNode; nic se neregistruje a žádné rozhraní se neimplementuje.

$resolver = new PhpSyntax\Analyses\NameResolver($file);
$scope = new PhpSyntax\Analyses\Scope;

Analýza čte strom tak, jak vypadal, když vznikla. Po změně stromu si ji postavte znovu; že se strom změnil, poznáte podle FileNode::$revision.

NameResolver

Překládá jména podle jmenného prostoru a importů, s tím, co PHP dělá za běhu: nekvalifikovaná funkce nebo konstanta, která ve jmenném prostoru není, spadne do globálního.

$resolver->getNamespace($node);         // 'App\Model', '' v globálním prostoru
$resolver->getClassImports($node);      // alias => plné jméno, platné v místě uzlu
$resolver->getFunctionImports($node);
$resolver->getConstantImports($node);

$resolver->resolveClass($name);         // 'App\Model\Order' z NameNode
$resolver->resolveFunction($name);
$resolver->resolveConstant($name);

$resolver->isGlobalFunctionCall($node, 'sizeof');   // volání globální funkce, případně té jedné

isGlobalFunctionCall() je otázka, kterou si dřív každý nástroj řešil sám a většinou hůř: uzel musí být volání funkce se jménem (ne $f() ani $obj->f()), jméno nesmí být klíčové slovo a po překladu musí být globální. Tak se pozná, že count($a) uvnitř namespace App opravdu volá count, a ne App\count, pokud taková funkce v souboru není importovaná.

Cesta zpátky je stejně užitečná: jak plné jméno zapsat v místě, kde stojí uzel.

$role = PhpSyntax\NameRole::ClassLike;

$resolver->getShortName('Nette\Utils\Strings', $role, $at);   // 'Strings', je importované
$resolver->getShortName('App\Model\Order', $role, $at);       // 'Order'
$resolver->getShortName('Other\Thing', $role, $at);           // '\Other\Thing', jinak by se hledalo v namespace
$resolver->isAliasFree('Strings', $role, $at);                // false, ten alias už něco znamená

getShortName() vezme v úvahu aliasy, prefixové importy i jmenný prostor a vrátí nejkratší zápis, který na tom místě znamená totéž. isAliasFree() odpoví na otázku, kterou si klade každý, kdo chce import doplnit: je tohle jméno tady ještě volné?

Scope

Kde ve struktuře kód stojí:

$scope->getFunction($node);           // FunctionNode, MethodNode, ClosureNode, ArrowFunctionNode, PropertyHookNode nebo null
$scope->getClass($node);              // ClassLikeNode nebo null
$scope->hasThis($node);               // je tu $this: v nestatické metodě, v closure, která ho zachytila
$scope->getCapturedVariables($closure);   // proměnné z use (...)

hasThis() bere v úvahu i to, že closure $this zdědí, ale static function ne, a že arrow funkce ho vidí vždy, když ho vidí okolí.

Vlastní analýza

Když víc míst potřebuje tutéž informaci o souboru, napište ji jako třídu s konstruktorem přijímajícím FileNode. Nástroj postavený na PhpSyntax si takovou analýzu obvykle staví a zahazuje sám podle revize stromu; DressCode to dělá za pravidla a přidává k oběma vestavěným ještě PhpDoc nad dokumentačními komentáři, protože ten už potřebuje parser phpDocu, který sem nepatří.