Příkazová řádka

Příkazy check a fix, výpis toho, co konfigurace znamená, vysvětlení pravidel s příkladem na terminál i jako dokument, převod cizí konfigurace, všechny přepínače, formáty výstupu pro terminál i pro CI, exit kódy, cache a paralelní běh.

Příkazy

dresscode check [cesty...]
dresscode fix [cesty...]
dresscode config
dresscode explain [pravidlo]
dresscode rules
dresscode init
dresscode import <soubor>
dresscode migrate-suppressions [cesty...]
  • check ohlásí porušení a nic nezapíše.
  • fix opraví, co pravidla umějí, a zbytek ohlásí. Soubor se zapíše jen tehdy, když se změnil a výsledek se znovu naparsuje na totéž; soubor se syntaktickou chybou nebo s pravidlem, které selhalo, zůstane nedotčený, a stejně tak soubor, který mezitím uložil editor nebo jiný nástroj.
  • config vypíše, k čemu konfigurace vede: která pravidla běží, s jakými volbami, kdo je nastavil a co přebil, a proč ostatní neběží. Viz níže.
  • explain řekne o pravidle, k čemu je, jaké má v tomhle projektu volby a ukáže příklad před opravou a po ní; bez jména pravidla totéž o všech pravidlech, která běží. Viz níže.
  • rules vypíše všechna známá pravidla: hvězdičkou označí ta, která v aktuální konfiguraci platí, a u každého uvede fázi, popis a jména pravidel jiných nástrojů, která pokrývá. Hodí se, když hledáte, jak se co jmenuje.
  • init napíše dresscode.neon podle toho, jak je projekt psaný: odsazení, uvozovky a tvar podmínek změří na vzorku souborů. Viz níže.
  • import převede konfiguraci PHP CS Fixeru nebo PHP_CodeSniffer, viz Přechod na DressCode.
  • migrate-suppressions přepíše komentáře phpcs:* na dresscode:*, viz tamtéž.

Cesty jsou soubory nebo adresáře relativně k aktuálnímu adresáři a mají přednost před klíčem paths z konfigurace. Adresář, ve kterém leží některé cesty z paths, se ale zúží jen na ně, takže dresscode fix . v kořeni projektu nesáhne mimo paths a check i fix to hned na začátku výpisu ohlásí slovy narrowed to the configured paths. Adresář nebo soubor mimo paths se vezme tak, jak jste ho zadali. Když nezadáte cesty a konfigurace žádné neuvádí, ať už v ní chybí klíč paths, nebo konfigurační soubor vůbec není, příkaz skončí chybou No paths given and none configured., protože nechce hádat, co má kontrolovat.

Přepínače

přepínač význam
-c, --config <soubor> konfigurační soubor místo nejbližšího dresscode.neon nebo dresscode.php (případně jejich šablony s příponou .dist)
-f, --format <název> formát výstupu, viz níže
--diff u check ukáže, co by fix změnil; u fix to, co změnil; jen ve formátu console
--preset <název> přidá preset, který běží i bez konfiguračního souboru; lze uvést vícekrát
--group <název> přidá skupinu pravidel, třeba cleanup nebo modernization, která běží i bez konfiguračního souboru; lze uvést vícekrát
--rule <název>=on nebo =off zapne nebo vypne pravidlo pro tenhle běh; lze uvést vícekrát
--only <název> zúží běh na jmenovaná pravidla; jméno presetu nebo skupiny znamená všechna jejich pravidla. Jde použít i u config, explain a rules. Lze uvést vícekrát, viz níže
--fix-risky udělá rizikové opravy všech pravidel, nejen těch, která konfigurace jmenuje v klíči fixRisky; ohlásí se tak jako tak
--max-warnings <n> víc než n varování znamená exit kód 1; bez něj varování exit kód nemění
--stdin <cesta> čte kód ze standardního vstupu, jako by to byl soubor na dané cestě; nejde kombinovat s cestami, viz níže
--skip-excluded vynechá i soubor zadaný jménem, pokud ho konfigurace vylučuje v excludePaths; pro hooky a nástroje, které předávají jména souborů
--generate-baseline zapíše porušení, která oprava nechá, do baseline místo hlášení; jen u check, bez --fix-risky a --only a nad kódem, na kterém fix nemá co opravit
--file <cesta> u config: co platí pro tenhle jeden soubor, včetně přepisů v overrides
--json u config: totéž jako data
--output <soubor> u explain: zapíše vysvětlení do souboru jako Markdown
--no-cache zpracuje každý soubor, i ten, o kterém se ví, že je čistý
--jobs <n> počet pracovních procesů; 1 znamená běh v jediném procesu
--strict-rules pravidlo, které poruší svůj kontrakt, je chyba, ne varování; pro vývoj vlastních pravidel
--no-color výstup bez barev; totéž zařídí proměnná prostředí NO_COLOR, barvy naopak vynutí FORCE_COLOR
--version, --help verze, nápověda

U --preset, --rule, --only a explain se vestavěný preset nebo pravidlo píše krátce: --preset nette je totéž co --preset dresscode/nette. Preset nebo pravidlo z rozšíření se píše celé i s vendorem, viz Presety a vrstvy. Jména skupin vendora nemají: --group cleanup.

Co konfigurace znamená: config

Konfigurace je pár řádků, ale to, co z nich vzejde, je přes sto padesát pravidel s volbami z několika vrstev. dresscode config to vypíše tak, jak to vidí běh: čte totéž vyřešení, kterým se řídí pravidla i cache, takže nemůže tvrdit něco jiného, než co se opravdu stane.

DRESS|CODE 1.0.0
Config     /var/www/shop/dresscode.neon
Target     PHP 8.2 from composer.json
Presets    dresscode/psr12, dresscode/per, dresscode/nette-style, dresscode/nette
Groups     cleanup, types, correctness
Style      a tab, the line ending each file mostly has, lines of up to 140 characters
Names      certain, the namespaces declare no function and no constant
Rules      155 of 211 run

  dresscode/name-casing                       dresscode/nette
      classes                         PascalCase              dresscode/nette
      constants                       PascalCase              dresscode/nette (over dresscode/psr12 UPPER_CASE, dresscode/per UPPER_CASE)
  dresscode/braces-position                   dresscode/nette-style
      emptyBodies                     ownLine                 dresscode/nette-style (over dresscode/per sameLine)
  dresscode/strict-call                       group correctness, risky fixes only reported, only warns
  dresscode/string-quotes                     the configuration
  ...

Not running
  dresscode/class-name-reference-for-string-literal it needs the types of the code and the configuration sets no types
  dresscode/useless-parentheses-around-new it needs PHP 8.4 and the target is 8.2
  dresscode/useless-overriding-method     it needs the types of the code and the configuration sets no types
  dresscode/no-manual-empty-string-test   it needs the types of the code and the configuration sets no types
  52 more that no preset or rule of the configuration mentions

Řádek Names říká, jak se berou nekvalifikovaná jména funkcí a konstant ve jmenných prostorech, a když konfigurace nějaké jmenuje, vypíše je pod sebou. Řádek Groups říká, ke kterým skupinám pravidel se konfigurace doresolvovala, a pravidlo, které zapnula skupina, má jako vrstvu group <jméno>. U každého pravidla stojí vrstva, která ho zapnula, u každé volby, kterou některá vrstva jmenovala, výsledná hodnota, kdo ji nastavil a co tím přebil. Pravidlo s rizikovými opravami navíc říká, jestli jste je přijali (risky fixes accepted), nebo jestli se jen ohlásí (risky fixes only reported), a pravidlo z klíče warnings dostane příznak only warns. Když projekt používá upgradovací data knihoven, přibude řádek Packages s počtem upgradovacích souborů a pod ním každý balíček s verzí, se kterou musí kód fungovat, se souborem, odkud data pocházejí, a s verzemi, na které se dá ještě upgradovat. Pravidlo, které neběží, říká proč: vypnul ho preset nebo konfigurace, chybí mu verze PHP, typy nebo knihovna, kterou hlídá, zapíná ho jen přepis, nebo o něm nikdo nemluvil. --file cesta ukáže totéž pro jeden soubor, tedy i s přepisy, které pro něj platí, a --json dá výstup jako data pro nástroje.

Vysvětlení pravidel: explain

Jméno pravidla ve výpisu je odkaz, ne vysvětlení. dresscode explain <pravidlo> k němu dá popis, fázi, volby s hodnotou a vrstvou, která ji v tomhle projektu nastavila, a příklad kódu před opravou a po ní:

DRESS|CODE 1.0.0
Config     /var/www/shop/dresscode.neon
Target     PHP 8.2 from composer.json

dresscode/string-quotes
Decides which quotes a string that needs neither kind is written with.
stage Structure

It runs in this project, set by the configuration.

Options
  quotes                     single                  (default)
      The quotes a plain string is written with

Example
  <?php
  $name = "Kafka";
  $greeting = "Hello, $name";
  $path = "a\tb";
  becomes
  <?php
  $name = 'Kafka';
  $greeting = "Hello, $name";
  $path = "a\tb";

Příklad je fixtura, tedy soubory s kódem před opravou (.code), po ní (.expected) a s očekávanými hlášeními (.violations), kterou autor pravidla vybral jako ukázku a přibalil k balíčku v adresáři examples/. Vestavěná pravidla mají ukázky zařazené v testech DressCode, takže nemohou zastarat; u pravidla z rozšíření to platí, když si je jeho autor pustí ve vlastních testech. Pravidlo může mít příkladů víc, pak jsou očíslované (Example 1, Example 2), a příklad, který pravidlu nastavuje volby, je vypíše za popiskem with {...}. Příklad nemá každé pravidlo; kde ho autor nepřipravil, explain ho vynechá. Pravidlo, které jen hlásí, soubor .expected nemá a jeho příklad je bez „becomes“. Diff toho, co by fix udělal s vaším kódem, ukáže check --diff.

Bez jména pravidla dresscode explain vysvětlí všechna pravidla, která v projektu běží. S --output je zapíše do souboru jako Markdown: z čeho se konfigurace skládá a u každého pravidla popis, volby, jak je má tenhle projekt, a příklad, seskupeno podle oblastí kódu.

dresscode explain --output docs/rules.md

Dokument vzniká z téže konfigurace, kterou se kontroluje kód. Zvyklosti z code review a všechno, na co žádné pravidlo není, v něm nejsou.

Konfigurace naměřená z kódu: init

První konfiguraci nemusíte psát: dresscode init v kořeni projektu změří kód, který už máte, a ušije z toho dresscode.neon na míru. Nic nehádá. Pro každou hodnotu rozhodnutí pustí to pravidlo, které by ji potom opravovalo, a co pravidlo nahlásí, s tou hodnotou nesouhlasí. Měří se tedy týmž nástrojem, který bude kód kontrolovat, a měřítko se s ním nemůže rozejít.

dresscode init
DRESS|CODE 1.0.0
Sample     3 of 3 files in src, tests
Standard   per, not chosen by measure; the dry runs below count what each would change, not which is nearest, and '--preset' writes another
Indent     tab 100% of 2 files
Quotes     single 86%, double 14% of 7 strings
Conditions no conditions
Namespaces no function and no constant declared, resolved for certain
Dry run    per         2 of 3 sampled files would change, the one written
           psr12       2 of 3
           nette       3 of 3
           symfony     3 of 3

dresscode.neon written, made to measure.

Vzorek je každý k-tý soubor setříděného seznamu, nejvýš 300 souborů a 2 MB, aby měření trvalo vteřiny i ve velkém projektu. Kromě výchozích vyloučených cest jdou stranou adresáře fixtures, Fixtures a expected, soubory větší než 100 kB a soubory, které v hlavičce říkají, že je vygeneroval nástroj (@generated, „do not edit“), protože nic z toho nikdo nepíše rukou. Ty z těchto tří adresářů, které v projektu najde, zapíše i do klíče excludePaths, aby je vynechávala i každá další kontrola. Kolik souborů vynechal pro velikost nebo jako vygenerované, řekne řádek Sample. Řádky Dry run jsou nasucho spuštěný fix: říkají, kolik souborů vzorku by každý ze čtyř standardů změnil, tedy co vás čeká při prvním opravdovém fix. Se zadaným --preset je řádek jen jeden, pro zvolený standard.

Měří se tři rozhodnutí, a každé jinou jednotkou, podle toho, jak často se v kódu vyskytuje:

  • odsazení po souborech, protože je to vlastnost souboru: tab, 4 nebo 2;
  • uvozovky po řetězcích, protože se rozhodují u každého znovu. Pravidlo je obousměrné, takže jedno spuštění spočítá řetězce psané dvojitě a druhé ty jednoduché a jmenovatel je jejich součet;
  • tvar víceřádkové podmínky po podmínkách; třetí běh, který povolí oba tvary, spočítá podmínky, které nejsou ani v jednom.

Zapíše se jen to, co kód říká dost jasně. Hodnota, se kterou souhlasí aspoň 70 % míst, se napíše jako hodnota i s procenty. Kde hranice nedosáhne žádná, ale obě dohromady ano, napíše se u tvaru podmínky tolerance [perLine, compact], tedy „oba tvary projdou“; u uvozovek, kde tolerance nedává smysl, se napíše keep. A kde ani to ne, nenapíše se nic a rozhoduje standard. Klíč fixRisky nenapíše init nikdy: souhlas s opravami, které mohou změnit chování kódu, se z kódu změřit nedá.

# Written by dresscode init from 3 of the 3 files. A number is the share of the places a decision
# appears in that already agree with its value; what the file does not name, the standard decides.

presets:
	- per
	# not chosen by measure; the other complete standards are psr12, nette and symfony

indent: tab  # tab 100% of 2 files

# the namespaces of the scope declare no function and no constant, so an unqualified name in a namespace
# is resolved for certain; one declared later is reported until it is listed
nameResolution: certain

rules:
	string-quotes: single  # single 86%, double 14% of 7 strings

paths:
	- src
	- tests

Vedle tří rozhodnutí zapíše init i to, co deklarují jmenné prostory. Tady nejde o většinu, ale o úplnost, a tak vzorek nestačí: init projde všechny soubory v cestách, najde funkce a konstanty deklarované ve jmenných prostorech, zapíše jejich seznamy do klíče namespaces a k nim nameResolution: certain. Soubor, který nejde naparsovat, může deklarovat cokoli, a proto init jistotu v takovém případě nezapíše a v komentáři jmenuje první takový soubor a řekne, kolik je dalších. Když mezi nainstalovanými balíčky najde symfony/dependency-injection, navrhne i preset symfony-configurator, který zná funkce, jimiž Symfony konfiguruje služby. Co ty klíče znamenají, popisuje stránka Funkce a konstanty ve jmenných prostorech.

Standard init neměří: vezme ten, který mu dáte přepínačem --preset, a bez něj napíše per s komentářem, že ostatní tři jsou psr12, nette a symfony. Cesty bere z autoloadu v composer.json a přidá k nim adresáře src, tests, test, app, lib, bin, cron a www*, které v kořeni opravdu jsou; když nenajde žádný, vezme celý kořen. Najde-li v nich soubory .phpt, zapíše i klíč fileExtensions.

Než se soubor zapíše, init si ho sám přečte a nechá vyřešit, jestli z něj vyjdou tytéž hodnoty, které naměřil. Konfiguraci, která už existuje, nikdy nepřepíše: návrh vypíše na výstup a skončí s kódem 2, takže si ho můžete prohlédnout nebo přesměrovat do souboru.

Jedno pravidlo najednou: –only

--only zúží běh na vyjmenovaná pravidla. Jméno presetu znamená všechna pravidla, která skládá, jméno skupiny všechna pravidla té skupiny, a přepínač se dá uvést vícekrát:

dresscode fix --only string-quotes
dresscode fix --only cleanup --only modernization

Hodí se při přechodu na DressCode a u velkých úklidů: jedno pravidlo, jeden commit, jedno review. Filtruje až to, k čemu konfigurace dojde, takže pravidlo nezapíná ani nepřenastavuje: na to je --rule, a ten se uplatní dřív. Jméno, které v téhle konfiguraci nic nespouští, je chyba, ne tiše prázdný běh. Klíč konfiguračního souboru to není: je to rozhodnutí jednoho běhu, ne projektu.

fix --only opraví jen to jedno pravidlo a zbytku se nedotkne, takže příští úplný fix klidně změní víc; to je záměr, ne nedokončená práce. Ostatní pravidla mezitím ve výpisu dresscode config říkají the run is narrowed to other rules. Baseline se zúženým během generovat nedá, protože by zapomněla, co našla ostatní pravidla.

Formáty výstupu

Přepínač --format volí celý tvar výstupu, ne jeho detail; formáty se nekombinují.

console je výchozí volba pro terminál: hlavička s konfigurací, cílovou verzí PHP a rozsahem kontroly, pak jednotlivé soubory s porušeními (řádek, sloupec, zpráva, pravidlo) a nakonec shrnutí. Porušení pravidla z klíče warnings má značku warning místo error a shrnutí ho počítá zvlášť. U check shrnutí řekne i to, kolik porušení by po fix zbylo a kolik z nich jsou rizikové opravy, které čekají na svolení, a u kterých pravidel:

FOUND  36 violations, 3 of them risky (strict-call, strict-comparison), fixed once their rules are named in fixRisky or with --fix-risky, 2 warnings, a fix leaves 5 in 12 of 214 files

Porušení, které v souboru vzniklo až opravou jiného porušení z téhož běhu, vypíše konzole jako poznámku pod tím, z něhož plyne, typicky prázdný řádek nebo odsazení u zalomení, které oprava vložila. Shrnutí pak řekne, kolik porušení plyne z ostatních. V ostatních formátech je to porušení jako každé jiné s údajem derivedFrom.

src/Cart.php
  error    1:7  A line break after the opening tag  blank-lines
                followed by blank-lines on line 1

fix vypíše každý soubor, který přepsal, s poznámkou rewritten a pod ním to, co opravený text pořád porušuje, s řádky a sloupci v opraveném souboru. Které porušení oprava odstranila, se z výsledného textu poznat nedá, a tak shrnutí počítá nalezená a zbylá porušení; co přesně se změnilo, ukáže --diff:

src/Cart.php  rewritten
src/Order.php  rewritten
  error  12:1  The line is 133 characters long, the limit is 120  line-length

FIXED  36 violations found, 1 remaining in 12 of 214 files

Barvy se vypnou samy, jakmile výstup nejde do terminálu.

github se zvolí sám, když běh probíhá jako krok GitHub Actions; se --stdin ne, tam platí jen to, co zadáte přepínačem --format. Každé porušení je anotace, která se ukáže přímo v diffu pull requestu; u fix jsou to porušení, která oprava nechala.

bare je pro Git hooky a nástroje, které výstup čtou: bez hlavičky a bez shrnutí, jen to, co zůstalo na uživateli, tedy porušení, syntaktické chyby, selhání pravidel a varování k souborům, ve stejném rozvržení jako console, a u každého přepsaného souboru řádek rewritten. Čistý běh na standardní výstup nevypíše nic; varování ke konfiguraci jdou ve všech formátech na standardní chybový výstup.

src/Cart.php  rewritten
src/Order.php
  error  12:1  The line is 133 characters long, the limit is 120  line-length

json je strojově čitelný a jeho tvar se v minoritních verzích nemění. Pole files obsahuje jen soubory, o kterých je co říct, čisté v něm nejsou. U každého jsou nalezená porušení violations (pravidlo, zpráva, řádek, sloupec, závažnost, zda je oprava riziková risky a zda ji běh odmítl refused, otisk, derivedFrom), porušení remaining, která zbudou po opravě, s pozicemi v opraveném textu, varování souboru warnings, syntaktická chyba error se zprávou a řádkem, selhání pravidla failure a příznaky changed (oprava text mění) a written (soubor se zapsal). Dál následuje summary s počty files, violations, remaining, riskyDeferred (rizikové opravy, které čekají), changedFiles, syntaxErrors, failures a baselined (porušení v baseline) a nakonec warnings k celému běhu.

checkstyle je XML, kterému rozumí Jenkins, nástroj cs2pr a další nástroje pro CI; u fix v něm stejně jako u github jsou porušení, která oprava nechala.

Exit kódy

kód význam
0 čisto; u fix také tehdy, když opravený text už nic neporušuje; varování bez prahu --max-warnings exit kód nemění
1 zůstala porušení, soubor nejde parsovat, nebo je varování víc, než --max-warnings dovolí
2 selhání: špatná konfigurace, neznámé pravidlo, pravidlo, které vyhodilo výjimku nebo se s jiným zacyklilo, soubor změněný během opravy

Selhání jednoho souboru běh nezastaví: soubor se ohlásí jako selhavší, nic se do něj nezapíše a ostatní se zpracují dál. Riziková oprava, která čeká na svolení, je porušení jako každé jiné a drží exit kód na 1, dokud ji někdo neudělá nebo nezapíše do baseline.

Cache a paralelní běh

DressCode si pamatuje každý soubor, který prošel čistě: jeho cestu a otisk obsahu, a spolu s nimi otisk konfigurace, která pro něj platí (včetně přepisů pro jeho cestu), cílové verze PHP, baseline a verzí nainstalovaných balíčků. Cesta v klíči je proto, že tentýž obsah může ve dvou souborech dostat dva verdikty, třeba kvůli přepisu nebo pravidlu, které se na cestu dívá. Soubor se stejnou cestou, obsahem a konfigurací příště přeskočí; jakmile se kterákoli z těch věcí změní, zpracuje ho znovu. Počty ve shrnutí i varování o zastaralých položkách baseline vyjdou s cache stejně jako bez ní. Obsah, který zapsal fix, si DressCode zapamatuje jen v projektu bez baseline. Cache leží v systémovém dočasném adresáři, nebo tam, kam ukazuje cacheDir v konfiguraci; --no-cache ji pro jeden běh obejde.

Soubory, které cache nepokryje, se rozdělí mezi pracovní procesy. Výchozí počet odpovídá počtu procesorů, nejvýš však jeden proces na čtyři soubory, protože spuštění procesu něco stojí. --jobs 1 běží bez nich, což se hodí při ladění vlastního pravidla, a --jobs 8 se vyplatí na stroji s mnoha jádry, kde by výchozí odhad byl zbytečně nízký. S procesy běží i --generate-baseline, takže generování netrvá déle než kontrola.

Standardní vstup

--stdin je rozhraní pro hooky a další nástroje: obsah přijde na standardním vstupu, cesta říká, která pravidla pro něj platí (podle ní se vyhodnotí přepisy pro cesty), a fix vrátí opravený kód na standardní výstup místo toho, aby zapisoval do souboru. Report pak fix posílá na standardní chybový výstup, aby se s kódem nepletl:

git show :src/Cart.php | dresscode check --stdin src/Cart.php

Cesty k dalším souborům se k --stdin přidat nedají. Cache se u standardního vstupu nepoužívá a klíč excludePaths se nevyhodnocuje: kód se zkontroluje, i když cesta leží ve vyloučené části projektu.