Přepis cizího pravidla
Jak přepsat sniff z PHP_CodeSniffer nebo fixer z PHP CS Fixeru: číst testy, ne zdroják, přeložit otázky z tokenů na strom a zahodit obranný kód, který strom nepotřebuje.
Pro koho to je
Máte vlastní sniff nebo fixer, který léta hlídá něco, co žádný standard neumí, a chcete ho mít i tady. Tenhle postup je psaný tak, aby podle něj šlo pracovat krok za krokem, a stejně dobře ho zvládne agent, kterému cizí pravidlo předložíte. Většinu pravidel v DressCode tak ostatně přepsali.
Napřed rozmysl, jestli přepis vůbec potřebujete: dresscode rules vypíše u každého pravidla jména z PHP
CS Fixeru, PHP_CodeSniffer a Slevomatu, která pokrývá. Přepisujte jen to, co v tom seznamu není.
1. Specifikaci vezměte z testů, ne ze zdrojáku
Zdroják cizího pravidla popisuje, jak se věc obchází nad plochým polem tokenů: kde se couvá, co se počítá, kdy se to
vzdá. Přesně tahle informace je tady k ničemu. Co má cenu, jsou testovací případy: dvojice kódu před a po,
hraniční případy, které autor za roky nasbíral. Ty přeneste skoro doslova do fixtur (.code,
.expected, .violations), viz Testování
pravidel. Fixtura je vaše zadání a zároveň důkaz, že jste nic neztratili.
2. Přeložte otázky z tokenů na strom
Každý cyklus přes tokeny v originále je ve skutečnosti otázka na strukturu. Překlad, který se opakuje pořád dokola:
| v originále | v DressCode |
|---|---|
getPrevMeaningfulToken() v cyklu, dokud se nenajde začátek výrazu |
slot uzlu: $node->cond, $node->args, nebo $node->parent |
| ruční počítání závorek k nalezení konce bloku | openParen a closeParen, openBrace a closeBrace uzlu |
T_STRING a hádání z kontextu, co to je |
konkrétní třída uzlu: NameNode, IdentifierNode, FunctionCallNode |
| vlastní pomocník „je to globální funkce“, „v jakém jsme namespace“ | NameResolver::isGlobalFunctionCall(), getNamespace(), resolveClass() |
vlastní pomocník „jsme uvnitř metody“, „je tu $this“ |
Scope::getFunction(), getClass(), hasThis() |
| porovnání dvou úseků tokenů | Node::matches() |
kontrola, že v úseku není ++, volání a podobně |
Node::isRepeatableRead() |
| hledání komentáře mezi tokeny | Token::hasCommentUpTo(), Node::hasComment() |
$phpcsFile->addFixableError() a pak fixer->replaceToken() |
report() v podmínce a pak setter nebo replaceWith() |
Tokens::insertAt() s ručně sestavenými tokeny |
Parser::parseExpression() nebo parseStatement() a insert() do seznamu |
Kde originál pracoval s indexy tokenů, pracujte s uzly; kde četl text, ptejte se stromu. Které uzly a sloty existují, říká reference uzlů.
3. Zahoďte obranný kód
Velká část cizího pravidla existuje proto, aby se nespletlo: aby [ nebyl index místo pole, aby se
nepočítala závorka uvnitř řetězce, aby komentář uprostřed volání nerozbil hledání. Se stromem ta možnost nevzniká,
takže ten kód nemá co přenášet. Pokušení přeložit originál řádek po řádku je silné, zvlášť pro agenta; proti
němu stojí jednoduchá zkouška: každý řádek nového pravidla musí odpovídat na otázku o kódu, ne o tokenech.
Řádek, který řeší, co všechno může stát mezi dvěma tokeny, jde ven.
Výsledek bývá několikanásobně kratší. Pravidlo, které má po přepisu víc než sto řádků, je podezřelé; buď dělá víc věcí najednou a má se rozdělit, nebo v něm zůstal překlad místo přepisu.
4. Bezpečnost jinak než originál
Originál často hlídal vedlejší účinky výčtem: T_INC, T_DEC, možná volání. Tady je na to
isRepeatableRead(), a je přísnější i přesnější. Pravidlo, které bylo v PHP CS Fixeru risky, po přepisu
risky být nemusí, pokud se ptá stromu; pravidlo, které mění význam programu ze své podstaty, zůstane rozhodnutím
uživatele a stránka pravidla to má říct.
5. Jméno a stará jména
Nové pravidlo dostane jméno podle konvence (vendor/slug, stav, ne krok). Jméno původního sniffu nebo fixeru
si uživatelé ponesou v komentářích phpcs:ignore; DressCode překládá cizí jména pro vestavěná pravidla,
u vlastního je nahraďte přímo v kódu (migrate-suppressions vypíše, která jména nezná, a to je přesně
ten seznam).
6. Ověřte na cizích fixturách i na vlastních
Cizí testovací případy prošly? Pak přidejte, co v nich chybělo, protože strom vidí víc: komentář uvnitř
konstrukce, konstrukci přes několik řádků, alternativní syntaxi, kód mezi kusy HTML. Nakonec fix nad větším
cizím kódem a pohled na diff.
Licence
Přenášíte chování a testovací případy, ne kód. PHP_CodeSniffer je pod licencí BSD-3-Clause, PHP CS Fixer a Slevomat pod MIT; obojí dovoluje odvozenou práci s uvedením autorství. Fixtury převzaté z cizího projektu označte v hlavičce souboru původem a licencí. Pravidlo napsané podle tohoto postupu cizí kód neobsahuje, protože z něj nezbylo co přenést.