Potlačení a baseline
Občas má kód dobrý důvod rozhodnutí nedodržet a občas nález dnes opravit neumíte. Na první případ je potlačení: umlčí rozhodnutí na řádku, v bloku, v souboru nebo pro celou cestu a zastaví i jeho opravu. Na druhý baseline: rozhodnutí zapnete hned, i když ho váš kód zatím nesplňuje.
Čtyři úrovně
Výjimky z pravidel osobně nemám rád a do kódu je nepíšu; když už, tak v konfiguraci pro celou cestu. Jsou ale místa, kde rozhodnutí prostě nemá pravdu, a tam se hodí přesný nástroj, ne kladivo. Potlačení (anglicky suppression) má čtyři úrovně, od nejužší po nejširší:
| úroveň | jak | kde |
|---|---|---|
| jeden řádek nebo příkaz | // dresscode:ignore |
v kódu |
| blok | dresscode:disable a dresscode:enable |
v kódu |
| celý soubor | dresscode:ignoreFile |
v kódu |
| cesta | keep v přepisu, excludePaths |
v konfiguraci |
Vedle nich stojí baseline, která není výjimka z rozhodnutí, ale z času: zapíše porušení, která dnes opravit neumíte, a hlásit se pak budou jen nová.
Ať zvolíte kteroukoli úroveň, platí jedna věc: potlačené porušení se neopraví. Pravidlo smí kód změnit jen poté, co porušení ohlásilo a hlášení nebylo potlačené, a hlídá to jádro nástroje, ne autor pravidla. Potlačení tedy není jen ticho ve výpisu, ale skutečné vypnutí. Baseline je jiný případ a opravě nebrání nikdy, jak popisuje její oddíl.
Potlačení na řádku
Komentář dresscode:ignore na konci řádku potlačí porušení na tomto řádku. Bez jména potlačí všechno,
s cestou rozhodnutí jen to jedno; víc cest se odděluje čárkou:
$isEmpty = $value == null; // dresscode:ignore expressions.comparison.loose
Místo celé cesty stačí i sekce, která platí pro všechna rozhodnutí pod sebou: // dresscode:ignore spacing
umlčí na řádku všechny mezery.
Komentář na vlastním řádku platí pro to, co začíná na řádku pod ním, i když se to táhne přes několik řádků. Může to být příkaz nebo člen třídy, ale i jediná položka pole nebo argument: komentář nad první položkou pole kryje jen ji, ne celé pole. Tady kryje celý příkaz:
// dresscode:ignore multiline.edges.array, multiline.trailingComma.array
$matrix = [[1, 0, 0],
[0, 1, 0],
[0, 0, 1]];
Proč výjimka platí, napište za dvě pomlčky. Text za nimi DressCode nečte, je pro toho, kdo kód otevře po vás, a stejný zápis používá ESLint i PHP_CodeSniffer:
// dresscode:ignore upgrading.functions.arraySearchFunctions -- a loop is faster than array_any() on this hot path
foreach ($kinds as $kind) {
if ($this->kind === $kind) {
return true;
}
}
Funguje //, # i /* */. Cestu opíšete z výpisu, kde stojí u každého nálezu.
Místo ní DressCode přijme i jméno pravidla PHP CS Fixeru nebo PHP_CodeSnifferu, které jeho rozhodnutí pokrývá, a rozumí
komentářům phpcs:ignore, phpcs:disable, phpcs:enable, phpcs:ignoreFile a
anotaci @phpcsSuppress, takže kdo přechází z jiného nástroje, nemusí do kódu vůbec sáhnout. Na cesty
DressCode je pak přepíše dresscode migrate
suppressions; stejně přepíše i komentář se jménem pravidla ze starší verze DressCode.
Jméno, které žádnému rozhodnutí neodpovídá, nic nepotlačí, a běh na to upozorní, aby v kódu nezůstal komentář, který jen vypadá, že něco dělá:
Comment `dresscode:ignore noIsNull` names `noIsNull`, which is no decision, section or rule, so it silences nothing.
Má-li projekt už vlastní komentář, kterým označuje záměr, třeba // intentionally == u porovnání,
které se nemá zpřísnit, nemusíte ho přepisovat. Klíč suppressionComments přiřadí ke vzoru komentáře
rozhodnutí, která na jeho řádku mlčí, jako by tam stálo dresscode:ignore:
suppressionComments:
'~\bintentionally\s+[!=]=~': expressions.comparison.loose
Vzor je regulární výraz i s oddělovači a porovnává se s textem jednoho komentáře, takže ^ znamená
jeho začátek. Hodnotou je cesta, sekce nebo jejich seznam. Komentář na konci řádku platí pro řádek, komentář na
vlastním řádku pro příkaz pod ním. Standard nette takovou mapu nese: // intentionally == umlčí
expressions.comparison.loose a // @ umlčí mistakes.errorSuppression.
Položka může nést i ukázku komentáře, který vzoru odpovídá, a pak se zapisuje jako mapa:
suppressionComments:
'~^//\s*@\s+\S~':
decisions: mistakes.errorSuppression
example: '// @ <why>'
Ukázku DressCode ověří: místo <why> dosadí slovo a komentář musí vzoru odpovídat. Formát
agent ji nabídne agentovi u porušení toho rozhodnutí jako způsob, jak doloženou výjimku zachovat, se
skutečným důvodem místo <why>; dresscode config ji vypíše u vzoru. Vyšší vrstva, která
napíše stejný vzor, nahradí celou položku i s ukázkou.
Potlačení v bloku
// dresscode:disable file.longLines
$expired = 'Your subscription has expired. Renew it in the account settings to keep access to every premium feature of the shop.';
$declined = 'Your card was declined by the bank. Check the card details or choose another payment method to complete the order.';
// dresscode:enable
disable bez jména vypne všechno až po enable; enable bez jména zapne všechno, co
disable vypnul. Bez enable platí disable do konce souboru.
Potlačení v celém souboru
<?php // dresscode:ignoreFile
Tenhle komentář kdekoli v souboru vypne pro celý soubor všechno. Hodí se pro generovaný kód, který leží mezi ručně
psaným. Když je takových souborů víc, je čistší vyloučit je cestou nebo podle obsahu, viz skipWhen v konfiguraci.
Vypnutí pro cestu
Výjimka, která platí pro celý adresář, patří do konfigurace, ne do stovky souborů. Rozhodnutí pro takovou cestu vypne
přepis s hodnotou keep:
overrides:
-
paths: [legacy]
expressions:
comparison: keep
-
paths: [tests/fixtures]
file:
longLines: keep
Zbytek konfigurace takový soubor zkontroluje normálně. Celé cesty vynechá excludePaths; obojí popisuje
stránka Konfigurace.
Často ale rozhodnutí pro tu cestu nemá zmizet, jen platit s jinou hodnotou: testy mají jiný odstup metod než zdrojový kód, ne žádný. I to umí přepis, který souborům odpovídajícím cestě dá vlastní hodnoty:
overrides:
-
paths: [tests/**.phpt]
blankLines:
aroundMethods: 2
Baseline pro to, co zatím opravit nejde
Nový standard nasadíte jedním commitem: dresscode fix opraví, co opravit jde, a výsledek commitnete
samostatně, jak popisuje Formátování kódu krok za krokem.
Po takovém commitu ale může zůstat něco, co za vás žádný nástroj neudělá. Rozhodnutí
types.declaration.parameter: required chce u každého parametru typ a vy máte tři sta funkcí bez něj.
U každé je potřeba rozmyslet, jaký typ tam patří, a to jsou týdny práce. Dokud rozhodnutí nezapnete, přibývají
s každým commitem další.
Právě na tohle je baseline: soupis porušení, která dnes opravit neumíte. Rozhodnutí díky ní zapnete hned, stará
porušení počkají, až na ně dojde, a každé nové se ohlásí. Totéž platí pro rizikovou opravu (risky fix), kterou
projekt zatím nepřijal, nebo pro řádek, který je delší, než file.lineLength dovolí, a DressCode ho za vás
zalomit neumí.
U statické analýzy je baseline samozřejmost, PHPStan ji má léta. PHP CS Fixer ani PHP_CodeSniffer ji ale nemají a žádost o ni se u obou zadrhla na téže otázce: co s ní má dělat automatická oprava? DressCode odpovídá jednoduše: baseline obsahuje jen to, co oprava nechá. Co opravit jde, se opraví, a baseline hlídá zbytek.
Proto ji příkaz baseline zapisuje až pro kód, na kterém už fix nemá co dělat. Oba příkazy
vezmou cesty z klíče paths v konfiguraci; můžete je také vypsat za příkaz:
dresscode fix
dresscode baseline
Baseline with 312 violations written to `dresscode-baseline.neon`.
Na pořadí záleží. Kdyby fix ještě něco změnil, baseline nic nezapíše, vyjmenuje soubory,
které by se změnily, a skončí s exit kódem 1; do baseline nepatří nic, co jde opravit:
Error: The baseline is generated over code a fix leaves alone: a fix would change 3 files. Run `fix` first. src/Cart.php src/Order.php tests/CartTest.php See https://dresscode.run/suppressing#baseline
Rizikové opravy, které mění chování kódu a které projekt nepřijal v klíči fixRisky, do baseline patří, protože je nástroj
bez vašeho svolení neudělá. Rizikové opravy, o kterých by rozhodly typy nebo nameResolution, do ní nepatří:
jsou to jen varování a zmizí, jakmile DressCode dodáte, co mu chybí. Přepínač --only příkaz
baseline nemá, protože baseline vzniká z celé konfigurace, ne z několika rozhodnutí.
Soubor vznikne vedle konfigurace a ve stejném formátu (.neon vedle dresscode.neon,
.php vedle dresscode.php) a platí hned: pod tímhle jménem ho DressCode najde sám a hlavička výpisu
check ho ukáže na řádku Baseline. Jiné jméno nebo umístění nastavíte klíčem
baseline. Takový soubor pak musí existovat, jinak check skončí chybou konfigurace, aby překlep nebo
smazaný soubor potichu nevypnuly baseline:
baseline: qa/dresscode-baseline.neon
Uvnitř je verze formátu a pro každý soubor seznam porušení s cestou rozhodnutí, zprávou a otiskem:
version: 2
files:
src/Order.php:
-
decision: naming.method
message: The method `get_total` must be written in camelCase.
fingerprint: ea4c48c2e21d2b35
Otisk se počítá z rozhodnutí, obsahu řádku a pořadí porušení toho rozhodnutí na řádku, ne z čísla řádku, takže baseline přežije úpravy jinde v souboru. Zpráva v něm není: v položce stojí pro lidi, aby bylo vidět, o co jde, a když ji nová verze DressCode napíše jinak, baseline porušení pozná dál. Porušení, která baseline zná, se nehlásí, a shrnutí běhu je přizná, aby nikdo nechválil císařovy nové šaty:
OK 312 violations in the baseline in 214 files
Baseline nikdy nebrání opravě. Komentář dresscode:ignore říká „tady rozhodnutí nemá pravdu“, a proto
zastaví i opravu. Baseline říká „tohle zatím neumíme“, a co už umíme, není důvod nechávat. Když se porušení
z baseline stane opravitelným, třeba proto, že projekt přijal rizikové opravy daného rozhodnutí, fix ho
opraví jako každé jiné.
Když nějaké porušení z baseline zmizí, protože ho opravil někdo ručně nebo fix, jakmile to šlo, běh
upozorní, že položka už ničemu neodpovídá
(1 entry of the baseline no longer matches a violation; regenerate it with `dresscode baseline`.). Pusťte
fix a baseline vygenerujte znovu. Běh přitom upozorní jen na položky souborů, které zpracoval, a při
--only jen na položky rozhodnutí, která běžela, takže kontrola části projektu nehlásí jako zastaralé to,
na co se neptala. Baseline se má zmenšovat, a jakmile je prázdná, smažte řádek z konfigurace a soubor s ním.
Baseline z doby, kdy DressCode hlásil po pravidlech, nová verze nepřečte: otisky se počítají jinak, a proto ji odmítne
s radou, ať ji vygenerujete znovu. Stačí pustit fix a dresscode baseline.