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.

verze: 1.0