Přepis pravidla z jiného nástroje
Sniff z PHP_CodeSnifferu nebo fixer z PHP CS Fixeru přepíšete do DressCode v šesti krocích. Specifikaci vezmete z testů, ne ze zdrojového kódu, otázky na tokeny přeložíte na otázky na strom a obranný kód zahodíte, protože nad stromem nemá čemu bránit.
Pro koho to je
Máte vlastní sniff (pravidlo PHP_CodeSnifferu) nebo fixer (pravidlo PHP CS Fixeru), který léta hlídá něco, co žádný standard neumí, a chcete ho mít i tady. Tenhle postup je napsaný tak, aby se podle něj dalo pracovat krok za krokem, a stejně dobře ho zvládne agent, kterému cizí pravidlo předložíte. Většinu vestavěných pravidel DressCode ostatně takhle přepsali agenti.
Nejdřív si ale ověřte, jestli přepis vůbec potřebujete: dresscode catalogue u každého rozhodnutí
vypíše jména z PHP CS Fixeru, PHP_CodeSnifferu a Slevomatu, která pokrývá. Přepisujte jen to, co v tom
seznamu není.
1. Specifikaci vezměte z testů, ne ze zdrojáku
Zdrojový kód cizího pravidla popisuje, jak se problém obchází nad plochým polem tokenů: kde se couvá, co se počítá, kdy to pravidlo vzdá. Přesně tahle informace je tady k ničemu. Co cenu má, jsou testovací případy: dvojice kódu před opravou a po ní a hraniční případy, které autor za roky nasbíral. Ty přeneste skoro doslova do fixtur. Fixtura je vaše zadání a zároveň důkaz, že jste cestou nic neztratili.
2. Přeložte otázky z tokenů na strom
Každý cyklus přes tokeny je v originále ve skutečnosti otázka na strukturu kódu. Tenhle překlad se opakuje pořád dokola:
| v originále | v DressCode |
|---|---|
getPrevMeaningfulToken() v cyklu, dokud se nenajde začátek výrazu |
slot uzlu: $node->condition, $node->arguments nebo $node->parent |
| ruční počítání závorek, aby se našel konec bloku | sloty openParen a closeParen, openBrace a closeBrace |
T_STRING a hádání z kontextu, co to vlastně je |
konkrétní třída uzlu: NameNode, IdentifierNode, FunctionCallNode |
| vlastní pomocník na „je to globální funkce“ a „v jakém jsme jmenném prostoru“ | GlobalCalls::findFunction(), findUncertainty(), NameResolver::getNamespace(),
resolveClass() |
vlastní pomocník na „jsme uvnitř metody“ a „je tu $this“ |
Scope::findFunction(), findClass(), hasThis() |
| porovnání dvou úseků tokenů | Node::matches() |
kontrola, že v úseku není ++, volání a podobně |
ExpressionNode::isRepeatableRead() |
| hledání komentáře mezi tokeny | Token::hasCommentUpTo(), Node::hasInnerComment() |
$phpcsFile->addFixableError() a pak $fixer->replaceToken() |
report() v podmínce a teprve za ní zápis do slotu 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á přehled uzlů.
3. Zahoďte obranný kód
Velká část cizího pravidla existuje jen proto, aby se nespletlo: aby si [ pole nespletlo s přístupem
k prvku, aby se nepočítala závorka uvnitř řetězce, aby komentář uprostřed volání nerozbil hledání. Nad stromem
taková možnost nevzniká, takže ten kód není třeba přenášet. Pokušení překládat originál řádek po řádku je
silné, zvlášť pro agenta, a 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 přes 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 řešte 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(), které je přísnější i přesnější. Pravidlo, které v PHP CS Fixeru dělalo rizikovou
opravu (risky fix), ji po přepisu dělat nemusí, pokud se ptá stromu. Kde bezpečný případ z kódu poznat nejde, hlaste
opravu jako rizikovou u každého takového výskytu, report(..., risk: Risk::TypeUnknown) s tím, co by ho
rozhodlo, viz kontrakt pravidla;
udělá se pak jen se svolením projektu. Pravidlo, které mění význam programu ze své podstaty, hlásí s
Risk::BehaviorChanges každý výskyt, a jeho opravy tak budou vždy čekat na svolení uživatele.
5. Rozhodnutí místo voleb
Volby cizího pravidla se nepřenášejí jedna k jedné. Nejdřív si řekněte, jakou věc v kódu pravidlo hlídá, a tu
pojmenujte cestou rozhodnutí podle konvence: v sekci
project nebo v sekci vašeho pluginu, stav, ne krok. Co bylo v originále přepínačem mezi dvěma tvary, je
hodnota toho rozhodnutí (sameLine, nextLine), co bylo zapnutím a vypnutím, je forbidden
nebo required proti keep, a seznam výjimek je parametr. Volbu, která jen obcházela slabinu tokenů,
třeba „ignoruj vnořené závorky“, zahoďte spolu s obranným kódem. Jak rozhodnutí ohlásit, popisuje Pravidlo do detailu.
Jméno původního sniffu nebo fixeru ale uživatelům zůstane v komentářích phpcs:ignore. Cizí jména
překládá DressCode jen na rozhodnutí jádra, takže u vlastního pravidla je v kódu nahraďte cestou nového rozhodnutí:
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
Prošly cizí testovací případy? Pak přidejte to, co v nich chybělo, protože strom vidí víc: komentář uvnitř
konstrukce, konstrukci přes několik řádků, alternativní syntaxi, kód prokládaný kusy HTML. Nakonec pusťte
fix nad větším cizím kódem a podívejte se na diff.
Licence originálu
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 nakonec nezbylo co přenášet.