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...]
dresscode lsp
  • 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éž.
  • lsp spustí jazykový server pro editory.

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. Bez cest i bez konfiguračního souboru příkaz skončí chybou, 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
-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
--preset <název> přidá preset; lze uvést vícekrát
--group <název> přidá skupinu pravidel, třeba cleanup nebo modernization; 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. 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ě; fix pak opravený kód vypíše na standardní výstup
--generate-baseline zapíše porušení, která oprava nechá, do baseline místo hlášení; jen u check 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
--version, --help verze, nápověda

U --preset, --rule a explain můžete jméno vestavěného presetu nebo pravidla zkrátit o předponu dresscode/: --preset nette je totéž co --preset dresscode/nette. Jména skupin vendora nemají a píšou se vždycky sama: --group cleanup.

Co konfigurace znamená: config

Konfigurace je pár řádků, ale to, co z nich vzejde, je sto sedmdesá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
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, modernization, types, deprecations, correctness
Style      a tab, the line ending each file mostly has, lines of up to 140 characters
Rules      172 of 199 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/line-length                       the configuration
  dresscode/strict-call                       group correctness, risky fixes only reported
  ...

Not running
  dresscode/useless-else                  turned off by dresscode/nette
  dresscode/octal-notation                it needs PHP 8.1 and the target is 8.0
  dresscode/no-deprecated-members         it needs the types of the code and the configuration sets no types
  6 more that no preset or rule of the configuration mentions

Řá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). 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í:

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

It runs in this project, set by dresscode/nette.

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

Example
  <?php
  $name = "Kafka";
  $greeting = "Hello, $name";
  becomes
  <?php
  $name = 'Kafka';
  $greeting = "Hello, $name";

Příklad je fixtura pravidla, kterou jeho autor vybral jako ukázku, takže ho testovací sada pouští proti pravidlu a ukázka nemůže zastarat. Pravidlo, které jen hlásí, má příklad 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 ji změří z kódu, který už máte, a napíše dresscode.neon. 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
Sample     35 of 35 files in src, tests
Standard   nette, as given
Indent     tab 100% of 32 files
Quotes     single 100% of 591 strings
Conditions no conditions
Namespaced none declared in 35 files
Dry run    2 of 35 sampled files would change

dresscode.neon written.

Vzorek je každý k-tý soubor setříděného seznamu, nejvýš 300, 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, protože jejich obsah nikdo nepíše rukou. Poslední řádek je nasucho spuštěný fix: říká, kolik souborů vzorku by navržená konfigurace změnila, tedy co vás čeká při prvním opravdovém fix.

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 35 of the 35 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:
	- nette

indent: tab  # tab 100% of 32 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 100% of 591 strings

paths:
	- src
	- tests

fileExtensions:
	- php
	- phpt

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 adresářů src, tests, app a lib, které v kořeni opravdu jsou.

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 dresscode/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, 2 warnings, a fix leaves 5, 3 of them risky (strict-call, strict-comparison), fixed with --fix-risky or once their rules are named in fixRisky 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. 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 porušení, která zůstala na uživateli, ve stejném rozvržení jako console, a u každého přepsaného souboru řádek rewritten. Čistý běh nevypíše vůbec nic.

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 má u každého souboru nalezená porušení violations (pravidlo, zpráva, řádek, sloupec, závažnost, zda je oprava riziková, otisk) a porušení remaining, která zbudou po opravě, s pozicemi v opraveném textu. Dál následuje summary s počty, mezi nimi remaining, rizikové opravy, které čekají, soubory se syntaktickou chybou (syntaxErrors) a porušení v baseline, a nakonec warnings.

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 editory a hooky: 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:

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

Cache se u standardního vstupu nepoužívá.