Konfigurace
Veškerá konfigurace sídlí v jediném souboru v kořeni projektu: v dresscode.neon, případně
v dresscode.php, pokud dáváte přednost čistému PHP. Neskládáte v něm desítky abstraktních pravidel, ale
klíč po klíči přímočaře popisujete svůj kód: zápis blankLines.aroundMethods: 1 zkrátka znamená jeden
prázdný řádek kolem metod. Základní kostru určí vybraný standard, konkrétní záměry doplní sady a jakékoliv vlastní
odchylky si dopíšete sami – ať už pro celý projekt, nebo pro jeho specifickou část.
Konfigurační soubor
Takhle vypadá ucelená konfigurace projektu, který staví na standardu PER Coding Style 3.1, odstraňuje zbytečný mrtvý kód a několik drobností si upravuje po svém:
use:
- perCs
- cleanup
paths:
- src
- tests
excludePaths:
- tests/fixtures
braces:
position:
class: nextLine
blankLines:
aroundMethods: 1
beforeStatement:
return: 1
spacing:
concatenation: '$a.$b'
Naprosto stejný výsledek v PHP, kde každý klíč z NEONu představuje pojmenovaný argument konstruktoru třídy
DressCode\Config a jednotlivá rozhodnutí jsou seskupena v poli decisions:
<?php declare(strict_types=1);
use DressCode\Config;
return new Config(
use: ['perCs', 'cleanup'],
paths: ['src', 'tests'],
excludePaths: ['tests/fixtures'],
decisions: [
'braces' => ['position' => ['class' => 'nextLine']],
'blankLines' => ['aroundMethods' => 1, 'beforeStatement' => ['return' => 1]],
'spacing' => ['concatenation' => '$a.$b'],
],
);
V dalším textu budeme používat syntaxi NEONu, protože je pro lidské oko nejpřehlednější a nejsnáze se čte.
Všechny identifikátory v konfiguraci (klíče, hodnoty i názvy presetů) se zapisují v camelCase. Jednoprvkový seznam
můžete v NEONu zapsat i jako prostou skalární hodnotu – zápis paths: src je naprosto rovnocenný
paths: [src]. Toto zjednodušení platí pro use, paths, excludePaths,
fileExtensions, fixRisky, reportOnly i pro cesty v sekci overrides.
Konfigurační soubor DressCode automaticky vyhledává od aktuálního pracovního adresáře směrem nahoru ke kořeni disku.
Složka, v níž soubor najde, se stane kořenem projektu a všechny cesty uvedené v konfiguraci jsou relativní právě
k ní. Pokud konfigurační soubor chybí a standard zadáte na příkazové řádce přepínačem --use, stane se
kořenem složka nejbližšího composer.json (a teprve bez něj aktuální adresář). Běh spuštěný
z jakéhokoliv vnořeného podadresáře tak vždy pracuje se stejnými relativními cestami i se sdílenou cache. Vedle hlavní
konfigurace můžete verzovat šablonu dresscode.neon.dist (či .php.dist); lokální soubor bez
přípony .dist pak tuto šablonu kompletně nahradí (ideální pro lokální vývojářské odchylky). Mít vedle
sebe současně dresscode.neon a dresscode.php je chyba – žádný z formátů nemá přednost.
Zcela jiný soubor můžete vynutit přepínačem --config.
Překlep v názvu klíče DressCode v tichosti nepřejde. Okamžitě při načítání nahlásí chybu a sám nabídne
nejbližší správný název: Key `spacing.cal` is unknown; write `spacing.call`. Stejně nekompromisně dopadne
nepovolená hodnota – výpis rovnou vyjmenuje všechny hodnoty, které daný klíč přijímá. Zcela bez konfiguračního
souboru DressCode odmítne běžet a doporučí vám příkaz dresscode init, protože žádný vlastní styl nikomu
nevnucuje. Výjimkou je pouze situace, kdy standard zadáte jednorázově na příkazové řádce:
dresscode check src --use perCs.
Rozhodnutí: co klíč jmenuje a jak se píše
Každý klíč v konfiguraci pojmenovává konkrétní prvek v kódu, nikoliv interní pravidlo, které jej hlídá. Mezera
mezi názvem funkce a závorkou se jmenuje spacing.call, umístění složené závorky třídy je
braces.position.class a prázdné řádky kolem metod definuje blankLines.aroundMethods. Zadaná hodnota
pak určuje, jak má daný prvek v kódu reálně vypadat: zda musí či nesmí být přítomen (required,
forbidden), nebo jakou přesnou podobu má mít – slovem (nextLine), ukázkou kódu
('$a.$b'), číslem (1), seznamem či mapou. Takovému konfiguračnímu páru klíč–hodnota říkáme
rozhodnutí (decision) a jeho plnou cestu uvidíte přímo u každého nálezu v terminálu:
src/Cart.php error 9:52 Expected a line break before the opening brace. braces.position.class
Tuto cestu zapíšete do konfigurace, když chcete změnit její chování; použijete ji v komentáři
dresscode:ignore, když má jedno konkrétní místo zůstat beze změny; a zadáte ji za příkaz
dresscode explain, když si chcete nechat do detailu osvětlit její význam a povolené hodnoty. Rozhodnutí jsou
rozdělena do zhruba dvacítky logických sekcí (od file až po upgrading) a každá má svou vlastní
stránku; kompletní mapu všech parametrů najdete na stránce Pravidla.
Proč je tento přístup tak elegantní, vynikne například u prázdných řádků. Podívejte se na těchto šest přímočarých řádků v DressCode:
blankLines:
afterNamespace: 1
afterImports: 1
aroundMethods: 1
bodyStart: 0
bodyEnd: 0
beforeStatement:
return: 1
V PHP CS Fixeru tentýž záměr vyžaduje kombinaci šesti různých pravidel: blank_line_after_namespace,
single_line_after_imports, no_blank_lines_after_class_opening, class_attributes_separation,
no_extra_blank_lines (se seznamem tokenů curly_brace_block) a blank_line_before_statement.
V PHP_CodeSnifferu se Slevomatem k tomu potřebujete devět sniffů, z nichž jeden má pět konfiguračních vlastností.
Všechny tři konfigurace udělají z téhož kódu naprosto identický výsledek – propastný rozdíl je však v tom, jak
rychle v nich na první pohled přečtete, co vlastně říkají.
Klíč, který vy ani žádný preset výslovně neuvedete, nevyžaduje v kódu nic. A tam, kde nechcete uplatnit ani
pravidlo předepsané standardem, jednoduše napíšete keep: „o tomto nerozhoduj, kód nech přesně tak, jak
je“. Hodnota keep představuje univerzální pokyn „nesahej na to“ – přijímá ji každé rozhodnutí
vyžadující určitou syntaxi a zapsaná na úrovni celé sekce vypne úplně vše pod ní:
blankLines: keep # o prázdných řádcích nerozhoduje vůbec nic
expressions:
comparison:
yoda: keep # Yoda podmínky smějí zůstat přesně tak, jak jsou napsané
Přehled všech povolených typů hodnot (od slov přes číselné rozsahy až po mapy) detailně popisuje stránka Pravidla.
Standardy a sady
Nemusíte ručně vymýšlet stovky konfiguračních voleb. Konfigurace stojí na pouhých dvou základních otázkách, na které dokážete odpovědět okamžitě.
Jak má kód vizuálně vypadat? To řeší standard – ucelený preset definující celkový vzhled kódu. Zvolíte si právě jeden:
| standard | co to je |
|---|---|
psr12 |
PSR-12, oddíl po oddílu přesně podle specifikace |
perCs |
PER Coding Style 3.1 v plném rozsahu, přímý nástupce PSR-12 |
nette |
Nette Coding Standard, PER Coding Style s tabulátory a rozvržením knihoven Nette |
symfony |
Symfony Coding Standards podle sady @Symfony z PHP CS Fixeru |
laravel |
výchozí styl nástroje Laravel Pint, jeho preset laravel |
Co po kódu požadujete navíc kromě vzhledu? K tomu slouží sady – presety reprezentující konkrétní záměr. O vzhledu a formátování nerozhodují, a proto se se zvoleným standardem nikdy nepřou:
| sada | co rozhoduje |
|---|---|
modernizations |
nahrazuje starší zápisy modernější syntaxí nového PHP |
deprecations |
opravuje nebo hlásí prvky, které novější PHP či knihovny označily za zastaralé |
cleanup |
bezpečně odstraňuje zbytečný kód, který v programu nic nedělá |
correctness |
opravuje konstrukce, které jsou s vysokou pravděpodobností chybou |
types |
doplňuje a sjednocuje typové deklarace tam, kde jim PHP rozumí |
compilerOptimizations |
zapisuje globální funkce a konstanty do tvaru optimalizovaného kompilátorem |
Obojí se zapisuje do klíče use: standard na prvním místě a požadované sady za ním (samostatný standard
lze zapsat i bez seznamu: use: perCs):
use:
- perCs
- cleanup
- modernizations
Sada vyjadřuje ucelený záměr, nikoliv neměnný seznam pravidel. Místo dvaceti dílčích rozhodnutí zkrátka zapíšete
modernizations – a když se DressCode v další verzi naučí novou modernizaci, sada vám ji automaticky
přinese, aniž byste museli v konfiguraci změnit jediné písmeno. Rozhodnutí, které vyžaduje novější verzi PHP, než
jakou projekt povoluje, přitom tiše čeká v pozadí, dokud nepovýšíte cílové PHP. Standard nette řeší
vedle formátování i hygienu kódu, protože k Nette neodmyslitelně patří: sám o sobě zapíná sady
cleanup, types a correctness, přičemž několik jejich pravidel vrací na
keep (například nativní typy parametrů a vlastností). Sady modernizations a
deprecations nenese žádný standard, protože přechod na novější PHP je krokem, pro který se musíte
rozhodnout sami.
Každý preset je obyčejný soubor se stejnou strukturou, jakou má vaše konfigurace. Kompletní výpis všech presetů
i s podrobnými komentáři najdete na stránce Standardy a sady.
Vestavěné presety se uvádějí zkráceně: plné jméno má formát vendor/název a výchozím vendorem je
dresscode – zápis perCs je tedy zkratkou za dresscode/perCs (v plném tvaru je
vypisují příkazy dresscode config a dresscode explain). Zkráceně se zapisují i oficiální
balíčky od vendoru dresscode, například upgrade-nette namísto dresscode/upgrade-nette;
presety z pluginů jiných autorů se uvádějí vždy celým názvem. Klíč use přijímá i přímou relativní
cestu k externímu souboru, například use: [perCs, ../shared/house.neon].
Vrstvy
Výsledná efektivní hodnota každého rozhodnutí se pro každý soubor skládá z pevných vrstev – od nejnižší po nejvyšší:
- presety vyjmenované v klíči
usev pořadí zápisu (každý nad presety, ze kterých sám vychází), - samotná rozhodnutí vaší projektové konfigurace,
- přepisy pro vybrané části projektu, kterým daný soubor vyhovuje (v pořadí zápisu),
- přímé volby z příkazové řádky (
--use,--set).
Vyšší vrstva přepisuje pouze to, co sama výslovně zmíní. Způsob sloučení závisí na datovém typu hodnoty:
- slovo, kódová ukázka, číslo či boolean nahradí hodnotu z nižší vrstvy jako celek;
- seznam nahradí celý seznam z nižší vrstvy (včetně výchozího): zápis
hygiene.aliasFunctions: [core, mysqli]vynutí pouze tyto dvě skupiny aliasů, ať preset říkal cokoliv. Pokud chcete stávající seznam pouze rozšířit, vložte do něj symbol...zastupující hodnoty z nižší vrstvy:hygiene.aliasFunctions: [..., mysqli]; - mapa názvů se slučuje položku po položce; položka s hodnotou
keepdané jméno zcela vyřadí ze všech nižších vrstev včetně balíčků. Konkrétní název má vždy přednost před vzorem s hvězdičkou*bez ohledu na vrstvu. Vykřičník za názvem mapy (replacedClasses!:) nahradí celou mapu z nižších vrstev naráz; - sekce a vnořené klíče se slučují rekurzivně klíč po klíči – změna jednoho parametru z presetu tedy znamená přepsání jediného řádku;
keepzapsané u celé sekce či nadřazeného klíče zruší vše pod ním a vyšší vrstva znovu aktivuje pouze ty klíče, které sama výslovně jmenuje.
Každý preset se v hierarchii aplikuje pouze jednou – v místě, kde se objeví poprvé. Pokud jej uvedete znovu, nic se nezmění a DressCode vás na to upozorní:
Warning: The configuration uses preset `dresscode/perCs`, which preset `dresscode/nette` already brings; the entry does nothing.
Totéž platí pro sady, které standard integruje do sebe. Standard nette staví na sadách cleanup,
types a correctness, přičemž některá jejich rozhodnutí cíleně vypíná (např.
types.declaration.parameter). Pokud napíšete use: [nette, types], sada types leží
v hierarchii pouze jednou pod standardem nette – vypnutá rozhodnutí se tím znovu neaktivují a běh vypíše
varování. Pokud je chcete zapnout, musíte je uvést přímo:
use: nette
types:
declaration:
parameter: required
property: required
return: required
Na pořadí sekcí v souboru vůbec nezáleží – v jakém pořadí pravidla reálně běží, to si řídí DressCode
sám. Odkud se jednotlivé hodnoty vzaly, vám přehledně ukáže příkaz dresscode config v podobě konfiguračního souboru s původní
vrstvou uvedenou v komentáři u každého řádku.
Verze PHP a balíčků
Pro jakou verzi PHP je kód psaný, představuje základní vlastnost projektu a DressCode ji automaticky vyčte ze sekce
require.php v composer.json. Rozhodnutí zapisující modernější syntaxi PHP se uplatní pouze tehdy,
pokud této syntaxi rozumí každá verze, kterou Composer do projektu pustí. Při zadání ^8.1 tedy rozhoduje PHP
8.1, i kdyby projekt lokálně běžel na PHP 8.4. Spodní hranici spolehlivě posune direktiva config.platform.php
v composer.json (kterou Composer sám od sebe nikdy nemění): s podmínkou ^8.1 a platformou
8.3 rozhoduje PHP 8.3. Z otevřených podmínek bez spodní meze (např. <8.4 nebo *)
nelze minimální verzi určit, a proto platí výchozí PHP 8.0. Starší verze než PHP 8.0 DressCode nepodporuje: prověří
kód jako pro PHP 8.0 a upozorní vás, že automatická oprava může zapsat konstrukce nekompatibilní se starším PHP. Odkud
přesně cílová verze pochází, ukazuje řádek Target v hlavičce výpisu každého příkazu.
Odlišnou verzi můžete zapsat do klíče targets (pokud se od Composeru liší nebo ji z něj nástroj
nedokáže přečíst), případně do lokálního přepisu, pokud má část projektu běžet na jiném PHP:
targets:
php: '8.2'
Podobně jako verzi PHP může pravidlo vyžadovat i přítomnost konkrétního balíčku v určité verzi, pokud
kontroluje jeho API. Verze balíčku se zjišťuje z composer.json a z nainstalovaných balíčků ve složce
vendor. Rozhodující je verze, na které projekt staví: u přímých závislostí je to nejnižší možná verze
povolená podmínkou, nikoliv verze zrovna stažená na disku. Balíček vyžadovaný bez spodní meze (*) nebo
přítomný pouze jako nepřímá závislost může nabývat jakékoliv verze – pravidlo vyžadující jeho konkrétní verzi
se proto neuplatní a jeho migrační data se budou pouze hlásit bez automatických oprav. Požadovanou verzi balíčku můžete
nastavit i v klíči targets, například když při postupném upgradu knihoven připravujete
kód na novou verzi dříve, než ji fyzicky nainstalujete. Verzi uvádějte vždy v uvozovkách, protože NEON by zápis
3.10 jinak interpretoval jako číslo 3.1:
targets:
php: '8.2'
nette/forms: '3.3'
Jiné hodnoty pro část projektu
Reálný projekt málokdy dodržuje stejná pravidla úplně všude. Kód knihovny chcete mít maximálně výkonný, testy si
definují vlastní pomocné funkce a šablony generátoru vyžadují jiné odsazení. Lokální přepis v klíči
overrides platí pro soubory odpovídající jeho masce paths. Může obsahovat libovolná rozhodnutí,
presety v use, cílové verze v targets, namespaces, nameResolution,
fixRisky, reportOnly, severity i suppressionComments:
use: nette
nameResolution: certain
overrides:
# produkční kód knihovny píše jména tak, aby je kompilátor optimalizoval
-
paths: [src]
use: [compilerOptimizations]
# testy si v namespace deklarují vlastní pomocné funkce
-
paths: [tests]
nameResolution: uncertain
blankLines:
aroundMethods: 1
severity:
phpdoc.property.plainComment: warning
# šablony generátoru se odsazují dvěma mezerami
-
paths: [resources/stubs]
indentation:
unit: '2 spaces'
Výsledek v praxi:
- Soubory ve složce
srczískají ke standardunettenavíc saducompilerOptimizations, která upraví globální funkce a konstanty tak, aby je kompilátor PHP mohl přímo optimalizovat. - Ve složce
testsříká direktivanameResolution: uncertain, že seznamy funkcí z konfigurace zde nejsou kompletní; opravy závisející na globálních funkcích se zde nahlásí pouze jako riziková varování (podrobnosti viz Funkce a konstanty ve jmenných prostorech). Mezi metodami stačí jeden prázdný řádek a obyčejný jednořádkový komentář nad vlastností vyvolá pouze varování namísto chyby. - Šablony v
resources/stubsse odsazují dvěma mezerami; ve všem ostatním přebírají pravidla zbytku projektu.
Zápis cest se řídí stejnými pravidly jako excludePaths a vychází z kořene projektu. Soubor vyhovující
více přepisům přebírá jejich pravidla postupně v pořadí zápisu. Rozhodnutí, které pro danou složku nemá platit
vůbec, získá v přepisu hodnotu keep. DressCode validuje všechny přepisy hned při startu běhu (i kdyby dané
složce neodpovídal žádný soubor), takže případný překlep odhalí okamžitě i s označením
problematického bloku:
Error: The override for `tests`: Key `expresions` is unknown; write `expressions`.
Finální konfiguraci platnou pro konkrétní soubor vám kdykoliv ukáže příkaz
dresscode config --file tests/CartTest.php.
Varování a rizikové opravy
Každé porušení pravidla je standardně chybou, která shodí CI build, dokud ji konfigurace nezmírní. O zmírnění se
stará klíč severity, který mapuje názvy na warning nebo error. Názvem může být
konkrétní rozhodnutí, skupina, sekce nebo celý preset a platí hierarchicky pro vše pod sebou; v rámci jedné vrstvy
vítězí nejkonkrétnější zápis. Varování hlídá naprosto stejné pravidlo jako chyba a příkaz fix jej
opraví zcela totožně – rozdíl je pouze v tom, že výstup jej počítá odděleně a exit kód zůstává 0
(vlastní limit nastavíte volbou --max-warnings <n>). To se skvěle hodí u věcí, které nástroj neumí
bezpečně sám přeformátovat, především u dlouhých řádků:
severity:
file.longLines: warning
phpdoc: warning
Závažnost netvoří součást samotné hodnoty rozhodnutí, a proto se s novou hodnotou automaticky nemění: váže se
k hodnotě nastavené ve stejné nebo nižší vrstvě. Standardy psr12 a perCs tímto způsobem
změkčují dlouhé řádky na varování. Pokud však projekt sám natvrdo nastaví file.longLines: forbidden a
závažnost neuvede, stanou se dlouhé řádky opět plnohodnotnou chybou, protože o nich nově rozhodl projekt.
Jako riziková oprava (risky fix) se označuje zásah, který by mohl změnit chování programu za běhu – a jde vždy
o vlastnost konkrétního místa v kódu. Doplnění parametru true do in_array($role, $roles) je
naprosto bezpečné tam, kde pole prokazatelně obsahuje hodnoty stejného typu, ale jinde může způsobit, že kód přestane
nacházet hodnoty dříve zachycené volným porovnáním. DressCode proto bezpečné výskyty opraví automaticky a rizikový
výskyt pouze ohlásí i s informací o tom, co by o něm dokázalo rozhodnout:
- Datový typ hodnoty. Rozhodly by o něm typy z PHPStanu, které DressCode přečte z vašeho projektu.
- Jednoznačnost funkce. Nekvalifikované
is_null()v namespace může představovat vaši vlastní funkciApp\is_null(). To spolehlivě rozhodne nastavenínameResolution: certain. - Váš lidský úsudek. Pravidlo
file.strictTypes.declarationzapne deklaraci striktních typů, což mění sémantiku jazyka ze své podstaty; pravidlofunctions.staticClosurezase nedokáže zaručit, zda anonymní funkci někde jinde nevážete k objektu přes$closure->bindTo(). Zde žádná statická analýza nepomůže – rozhodnout musíte vy.
Ve výstupu nese rizikové místo stav risky a pod chybovou zprávou najdete větu vysvětlující konkrétní
hrozbu:
src/Users.php
risky 17:16 The `in_array()` call must pass `$strict = true`. mistakes.strictArgument
Risky because a strict search no longer finds a value of another type.
Pouhé uvedení rozhodnutí v konfiguraci (ať už přímo, nebo prostřednictvím presetu) neznamená automatické svolení
s rizikovými zásahy. Souhlas uděluje projekt v klíči fixRisky – seznamu rozhodnutí, jejichž rizikové
opravy výslovně akceptuje (namísto jednotlivého rozhodnutí lze uvést celou sekci či preset). Jedinou sekcí se stejným
názvem, jako má sada, je sekce types: samotnou sekci zapíšete jako types.*, sadu celým názvem
dresscode/types (holý název by byl nejednoznačný a DressCode jej odmítne). Preset tento souhlas udělit
nemůže. Běžný Git hook ani editor proto nepotřebují žádné speciální přepínače: automaticky opraví to, co projekt
schválil v konfiguraci, a zbytek pouze nahlásí. Přepínač --fix-risky povolí rizikové opravy u všech
pravidel jednorázově; --fix-risky=mistakes.strictArgument povolí pouze dané vybrané pravidlo:
fixRisky:
- mistakes.strictArgument
- hygiene.classNameNotation
Seznam fixRisky se přes jednotlivé vrstvy sčítá, takže lokální přepis v overrides může
povolit rizikové opravy pouze pro vybranou část projektu. Bez výslovného souhlasu se rizikové místo pouze nahlásí a jeho
závažnost se řídí povahou rizika: kde by postačila typová analýza či nameResolution, tam jde o pouhé
varování (aby zbytečně nepadal build), kde oprava mění chování z podstaty věci, tam jde o plnohodnotnou chybu.
Závěrečný souhrn čekající rizikové opravy přehledně sečte a přesně poradí, jak je vyřešit:
2 risky fixes wait, the type is unknown: set `typeAnalysis: phpstan`. See https://dresscode.run/types#enable 1 risky fix waits, a name may reach a function of the namespace: run `dresscode init`, which lists what the namespaces declare, or set `nameResolution: certain` if they declare nothing. See https://dresscode.run/namespaces#name-resolution
Opačnou situaci řeší klíč reportOnly: definuje rozhodnutí, sekce či presety, jejichž porušení se má
pouze hlásit, ale nikdy automaticky neopravovat. To oceníte při postupném zavádění nástroje, kdy chcete mít nejprve
přehled o stavu kódu, nebo u pravidel, která chcete opravovat výhradně ručně. Seznam se přes vrstvy sčítá a přepis
jej může definovat lokálně; preset jej zadat nemůže. Pravidlo v reportOnly nečeká ani na svolení
s rizikovou opravou, protože žádné úpravy nenabízí:
reportOnly:
- multiline
- mistakes.strictArgument
Kde rozhoduje váš úsudek, spusťte dresscode fix --ask-risky: bezpečné opravy proběhnou standardně a
u zbývajících rizikových míst se vás nástroj v terminálu zeptá na každé zvlášť i s ukázkou diffu. Co je
v pořádku plošně, povolte v fixRisky; specifické výjimky označte komentářem dresscode:ignore.
Cesty
Klíč paths definuje sledované cesty: soubory a adresáře relativní ke kořeni projektu. Cesty zadané na
příkazové řádce mají vždy přednost; adresář obsahující některé cesty z paths se však automaticky
zúží výhradně na ně (viz Příkazová řádka).
Klíč excludePaths cesty vynechává a funguje výhradně aditivně: k výchozímu systémovému seznamu
(vendor, node_modules, temp, tmp, log a všem skrytým složkám
a souborům začínajícím tečkou) přidá vaše vlastní pravidla (a totéž udělá každý připojený plugin). Nikdo
nemůže vrátit zpět cestu, kterou jiná vrstva vyloučila.
paths:
- src
- tests
excludePaths:
- tests/fixtures
- '*.generated.php'
Vzor obsahující lomítko je pevně ukotven k projektovému kořeni – zápis tests/fixtures tedy označuje
přesně tento konkrétní adresář. Vzor bez lomítka odpovídá souboru či složce v libovolné hloubce zanoření.
Jednoduchá hvězdička zastupuje jakékoliv znaky kromě lomítka, dvojitá hvězdička ** pokrývá i lomítka
(zápis tests/**.phpt tedy zachytí všechny testy v jakékoliv podsložce). Dvojitá hvězdička s lomítkem
/**/ představuje libovolný počet adresářů (stejně jako v .gitignore), takže
src/**/Test.php najde i src/Test.php. Otazník zastupuje jeden znak kromě lomítka, hranaté závorky
[abc] jeden z uvedených znaků a [!abc] jakýkoliv jiný znak. Neplatný vzor (např. s neuzavřenou
závorkou) DressCode okamžitě odmítne při načítání konfigurace.
Klíč fileExtensions určuje zpracovávané přípony (výchozí je pouze php); projekt s testy
v Nette Testeru sem přidá phpt. Vynechání souborů podle jejich obsahu (například strojově generovaný kód
rozpoznaný podle hlavičky) obstará direktiva skipWhen, které předáte callback v podobě entity. Callback obdrží obsah souboru a jeho relativní cestu a vrátí true,
pokud se má soubor přeskočit:
skipWhen: App\CodeStyle\GeneratedFilter::isGenerated(...)
Typy a jmenné prostory
Dvě klíčové informace o projektu DressCode z jednoho izolovaného souboru poznat nedokáže – předáváte mu je proto v konfiguraci.
Direktiva typeAnalysis: phpstan zapíná čtení typových informací z PHPStanu nainstalovaného v projektu
(s jeho phpstan.neon a pluginy). Teprve s nimi se aktivují rozhodnutí, která bez znalosti typů nemají smysl
(především bezpečný přepis zastaralého API knihoven). Jde o vlastnost celého projektu – preset ani lokální přepis v
overrides ji nastavit nemohou. Co všechno typová analýza přináší a jak funguje, popisuje stránka Typy z PHPStanu.
Klíč namespaces vyjmenuje funkce a konstanty deklarované v jmenných prostorech projektu a direktiva
nameResolution: certain potvrdí, že žádné další neexistují. DressCode díky tomu s jistotou ví, zda
nekvalifikované strlen() v namespace odkazuje na globální funkci, a související opravy přestanou být
rizikové. Důvody podrobně vysvětluje stránka Funkce a
konstanty ve jmenných prostorech.
typeAnalysis: phpstan
nameResolution: certain
namespaces:
constants:
- 'App\{VERSION, DEBUG}'
Pluginy
Plugin je třída implementující rozhraní DressCode\Plugin, přes kterou Composer balíček přináší do
projektu vlastní pravidla, presety, analýzy, vyloučené cesty i filtry souborů. Balíček svůj plugin deklaruje v
composer.json v sekci extra.dresscode.plugin a DressCode jej po instalaci najde v souboru
vendor/composer/installed.json. Načte jej však výhradně tehdy, když mu to projekt výslovně povolí: uvedením
názvu balíčku či třídy pluginu v klíči use. Plugin totiž obsahuje spustitelný PHP kód, který běží
při každé kontrole, a projekt jej musí schválit naprosto stejně, jako Composer vyžaduje schválení svých vlastních
pluginů. Plugin nainstalovaného balíčku, který v use chybí, se nenačte a běh na to vypíše varování
(výjimkou jsou pouze pluginy deklarované přímo v kořenovém composer.json vašeho projektu a pluginy
migračních balíčků načítané příkazem dresscode upgrade today). Pokud by se plugin balíčku jmenoval
stejně jako některý preset, vznikla by kolize a DressCode běh zastaví chybou. Plugin vytvořený přímo uvnitř projektu
uvedete v klíči use vedle presetů – buď názvem třídy, nebo entitou
s argumenty:
use:
- acme/coding-rules
- Acme\CodeStyle\Plugin
- perCs
- acme/house
První řádek povolí plugin balíčku acme/coding-rules (samotný název balíčku žádný preset nezapíná).
Plugin o stylu kódu sám nerozhoduje: jeho pravidla sídlí ve vlastní sekci pojmenované podle pluginu a jeho presety se pouze
stanou dostupnými v katalogu. Co z nich projekt reálně využije, to si sám určí v use a v příslušné
sekci. Styl projektu je tak vždy přehledně čitelný přímo v jeho vlastní konfiguraci. Plugin patří celému projektu, a
proto jej smí uvádět výhradně hlavní konfigurace – pokus o zápis pluginu do use uvnitř presetu či
lokálního přepisu odmítne DressCode chybou
Plugin `Acme\CodeStyle\Plugin` is used by the configuration of the project, never by a preset or an override. Jak
plugin napsat, popisuje stránka Vlastní preset a plugin.
Balíček může nést i migrační data – soubory popisující přejmenované a odstraněné prvky knihoven; cestu k nim
ohlásí pod extra.dresscode.upgrading-dir. Samotná data žádné pravidlo nezapnou: uplatní se až ve chvíli, kdy
projekt nastaví upgrading.packages: yes (což automaticky dělají sady deprecations i
modernizations), a teprve poté aktivují specifické přepisy kódu ze svého pluginu (samotný plugin se při
check a fix načte pouze tehdy, když jej jmenuje use). Stejným způsobem zapsaná data
o prvcích zavržených samotným PHP nese DressCode přímo v sobě a aktivuje je požadavek
upgrading.php.deprecatedApi: forbidden (více na stránce Jak
funguje upgrade knihoven).
Objekty a callbacky
Tam, kde konfigurace v PHP předává instanci objektu nebo spustitelný callback, se v syntaxi NEONu zapisuje entita. Vypadá jako běžné volání v PHP (znáte ji například z konfigurace Nette DI):
App\CodeStyle\Filter(8.1, strict: true)vytvoří instanci třídy (argumenty mohou být pojmenované),App\CodeStyle\Filter::create(8.1)zavolá statickou tovární metodu a použije její návratovou hodnotu,App\CodeStyle\Filter::isGenerated(...)předá samotnou metodu jako first-class callable,App\CodeStyle\Filter(8.1)::withLimit(100)zavolá metodu nad výsledkem předchozího volání.
Entitu přijímají přesně tři konfigurační místa: plugin v klíči use, direktiva skipWhen a
továrna analýzy v klíči analyses (zapisovaná jako dvojice třídy a entity):
use:
- App\CodeStyle\Plugin(strict: true)
- perCs
skipWhen: App\CodeStyle\GeneratedFilter::isGenerated(...)
rules:
- App\CodeStyle\ExceptionMessagePeriodRule
analyses:
- App\CodeStyle\Analyses\Imports
App\CodeStyle\Analyses\Calls: App\CodeStyle\Analyses\Calls::create(...)
Direktiva skipWhen a továrna analýzy se vyhodnotí okamžitě při načítání konfigurace; plugin z
use se vytvoří až ve chvíli, kdy má DressCode ověřeno, že jeho třída skutečně implementuje rozhraní
pluginu. Konfigurace cizího repozitáře, který si pouze stáhnete a zkontrolujete, tak pouhým názvem třídy nemůže spustit
cizí škodlivý kód. Projektové pravidlo se v NEONu uvádí výhradně názvem třídy. Pravidlo s vlastními závislostmi
vytváří továrna pokaždé, když jej DressCode potřebuje (například samostatně pro soubory každého přepisu); taková
továrna se zapisuje jako běžná uzávěra (closure) v souboru dresscode.php:
return new Config(
use: ['perCs'],
rules: [
App\CodeStyle\ExceptionMessagePeriodRule::class,
App\CodeStyle\CacheKeyRule::class => fn() => new App\CodeStyle\CacheKeyRule(App\CodeStyle\Keys::load()),
],
);
Příkazová řádka jako nejvyšší vrstva
Příkazová řádka představuje nejvyšší vrstvu hierarchie a má přednost před projektovou konfigurací i před všemi
lokálními přepisy. Volba --use přidá preset či plugin, --set nastaví konkrétnímu rozhodnutí
hodnotu v syntaxi NEONu a --only zúží běh na vybraná rozhodnutí, sekce či presety, aniž by měnil jejich
nastavené hodnoty:
dresscode fix --set hygiene.is_null=forbidden # stávající konfigurace obohacená o toto pravidlo
dresscode fix --only cleanup # pouze rozhodnutí patřící do sady cleanup
dresscode check src --use psr12 # cizí projekt kontrolovaný bez konfiguračního souboru
Tyto přepínače nejsou klíči konfiguračního souboru: rozhodují o jednom konkrétním běhu, nikoliv o projektu jako celku. Jak je využít k otestování pravidel, začlenění změn v jediném commitu nebo k postupnému úklidu kódu, popisuje následující kapitola.
Jedno rozhodnutí na jeden běh: –set a –only
Volba --set nastaví rozhodnutí hodnotu přesně tak, jako by byla zapsána v konfiguraci, a --only
omezí běh na vybrané prvky. Obě volby lze opakovat a společně pokrývají všechny situace, kdy potřebujete s pravidly
experimentovat bez trvalých zásahů do konfiguračního souboru. Hodnota za rovnítkem = se zapisuje v syntaxi
NEONu – jde tedy o naprosto stejný zápis jako v souboru; ukázku kódu obsahující mezery uzavřete do uvozovek, aby ji
nerozdělil shell:
dresscode check --set hygiene.is_null=forbidden
dresscode check --set blankLines.aroundMethods=1
dresscode check --set 'spacing.arrowFunction="fn ($x) => $x"'
Přepínač --only žádné hodnoty nemění. Konfigurační strom se vyhodnotí kompletně celý a
--only z něj pouze vybere pravidla, která se mají v daném běhu hlásit a opravovat. Nevybraná rozhodnutí
sice mlčí, ale jejich hodnoty plně platí pro kód generovaný vybranými pravidly: když --only spustí přepis
na výraz match v projektu odsazovaném čtyřmi mezerami, nový blok získá korektní čtyřmezerové
odsazení.
Vyzkoušet rozhodnutí, které v konfiguraci nemáte
Zajímá vás, co přesně by s vaším kódem udělalo rozhodnutí, o kterém konfigurace dosud mlčí nebo které váš
standard ponechává na keep? Nastavte mu hodnotu a spusťte jej izolovaně:
dresscode check --set controlFlow.elseAfterReturn=forbidden --only controlFlow.elseAfterReturn --diff
src/Discount.php
error 12:5 Useless else, because the branches before it always leave. controlFlow.elseAfterReturn
--- src/Discount.php
+++ src/Discount.php
@@ -9,8 +9,7 @@
{
if ($count > 10) {
return 0.1;
- } else {
- return 0.0;
}
+ return 0.0;
}
}
FOUND 1 violation, a fix leaves none in 1 file
Ostatní pravidla v tomto režimu neběží, a proto v diffu zůstal příkaz return odsazený o úroveň
hlouběji – správné odsazení má totiž na starosti rozhodnutí indentation.unit. Samotná volba
--only bez --set pravidlo nespustí, pokud jej konfigurace aktivně nezapíná (nemělo by totiž
žádnou efektivní hodnotu); chybová zpráva vás na to upozorní a doporučí přidat --set:
Error: Option `--only` names decision `controlFlow.elseAfterReturn`, which has no rule that runs here; `--set controlFlow.elseAfterReturn=<value>` decides it for the run.
Zavést nové rozhodnutí jedním commitem
Když se rozhodnete zavést nové pravidlo, chcete mít v Gitu čistý commit obsahující pouze jeho změny – a to rovnou
perfektně zformátované podle vašeho zavedeného standardu. V projektu, který je vůči své stávající konfiguraci
čistý, k tomu stačí samotný přepínač --set:
dresscode fix --set controlFlow.elseAfterReturn=forbidden
Spustí se celá konfigurace obohacená o toto jediné pravidlo. Co jeho oprava v kódu posune, to ostatní formátovací
pravidla okamžitě srovnají: příkaz return z předchozí ukázky skončí s dokonalým odsazením. Všechno
ostatní už bylo v pořádku, takže výsledný diff obsahuje výhradně úpravy nového pravidla. Pak stačí hodnotu zapsat do
konfigurace a obojí najednou commitnout.
Uklidit starý kód po krocích
Rozsáhlý úklid v legacy projektu se v jednom obřím diffu velmi špatně reviduje. Rozdělte jej přehledně podle záměru: vytvořte samostatný commit pro každou sadu nebo pro jednotlivé sekce:
dresscode fix --only cleanup
dresscode fix --only correctness
dresscode fix --only blankLines
dresscode fix
Pouze sada types nese stejný název jako celá sekce – zde ji proto uveďte celým názvem
dresscode/types a sekci jako types.* (holý název by byl nejednoznačný). Název sady či presetu v
--only spustí výhradně ta rozhodnutí, která konfigurace reálně zapíná. Přepínač sám o sobě žádné
hodnoty nepřebíjí, takže neaktivuje pravidla, která jste v konfiguraci záměrně nechali na keep. O ničem
přitom nelže: pokud jste některá pravidla ze sady v konfiguraci vypnuli, běh přesně vypíše která:
Warning: Option `--only` keeps 29 of the 30 rules of preset `dresscode/cleanup` that may run here, without `controlFlow.elseAfterReturn`, `controlFlow.elseifAfterReturn`, which the configuration turns off.
Sadu, kterou konfigurace vůbec nepoužívá, připojíte přepínačem --use. Příkaz
dresscode fix --use modernizations přepíše kód na novější PHP a výsledek rovnou zformátuje podle vašeho
standardu; kombinace --use modernizations --only modernizations spustí sadu zcela samostatně (přesně toto
chování doporučí nápověda, pokud volba --only jmenuje preset, který konfigurace neobsahuje celý). Název,
který by neaktivoval vůbec nic, skončí chybou namísto tichého prázdného běhu.
Volba fix --only upraví výhradně vybrané prvky a zbytku se nedotkne – závěrečný plný fix
proto ještě může doladit detaily (například odsazení posunuté dílčími úpravami). To je záměr: každý commit nese
jeden logický typ změny a poslední představuje čisté finální zformátování. Příkaz baseline volbu
--only nepodporuje záměrně, protože baseline nesmí zapomenout nálezy ostatních pravidel.
Zkontrolovat cizí projekt bez konfigurace
Bez konfiguračního souboru DressCode nezačne, protože žádný výchozí styl nevnucuje. Standard mu však můžete pro
jeden běh předat přepínačem --use a v projektu se nevytvoří ani bajt:
dresscode check src --use psr12
Hlavička výpisu v terminálu potvrdí: Config none, using psr12. Připojit můžete i sady: příkaz
dresscode check src --use perCs --use modernizations vám vedle formátování ukáže i možnosti modernizace na
novější PHP. Jakmile budete chtít konfiguraci uložit natrvalo, nejvhodnější pravidla z kódu změří dresscode init.
Další klíče
baseline: cesta k souboru evidujícímu stávající chyby, které se nemají hlásit; generuje jej příkazdresscode baseline(viz Potlačení a baseline).suppressionComments: vlastní regulární výrazy pro komentáře projektu, které dokáží umlčet rozhodnutí na svém řádku (viz Potlačení a baseline).cacheDir: adresář pro ukládání otisků čistých souborů a stypeAnalysis: phpstani cache PHPStanu (výchozím umístěním je složkadresscodev systémovém dočasném adresáři).rulesaruleUrl: registrace vlastních pravidel projektu podle názvu třídy a URL adresa jejich webové dokumentace se zástupným symbolem{slug}, na kterou terminál odkazuje u každého jejich nálezu.analyses: registrace vlastních analytických tříd pro vlastní pravidla; analýza stejné třídy zapsaná v konfiguraci má přednost před verzí z pluginu.
Výslednou podobu všech těchto nastavení vám kdykoliv zobrazí příkaz dresscode config: vypíše vyřešenou
konfiguraci v syntaxi souboru, u každé hodnoty komentářem doloží zdrojovou vrstvu a u neaktivních rozhodnutí vysvětlí
důvod vyřazení. S volbou --file totéž provede pro konkrétní soubor (včetně zohlednění
overrides), s --format json vydá data pro strojové zpracování.
Co nakonec platí: dresscode config
Konfigurace může mít jen pár řádků, ale ve výsledku z ní vzejdou stovky rozhodnutí poskládaných z několika
vrstev. Příkaz dresscode config je přehledně zobrazí tak, jak je vidí samotné jádro: ve formátu
konfiguračního souboru, kde u každého řádku komentář uvádí vrstvu, která jej nastavila. Čerpá ze stejné vyřešené
konfigurace, kterou se řídí pravidla i cache – nemůže tedy tvrdit nic jiného, než co se reálně stane.
DRESS|CODE 1.3.0
Config /var/www/shop/dresscode.neon
Target PHP 8.2 from `composer.json`
Use dresscode/psr12, dresscode/perCs, dresscode/cleanup, dresscode/types, dresscode/correctness, dresscode/nette, dresscode/modernizations
Targets php 8.2
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
Comments silence decisions on their line
~\bintentionally\s+[!=]=~ expressions.comparison.loose, mistakes.strictArgument
~^//\s*@\s+\S~ mistakes.errorSuppression
Decisions 339 of 423 set by a layer, the others asking for nothing
file:
lineLength: 140 # dresscode/nette
strictTypes:
declaration: required # dresscode/nette
position: sameLine # dresscode/nette
...
spacing:
arrowFunction: 'fn ($x) => $x' # the configuration
...
classes:
overridingMethod:
parentCallOnly: forbidden # dresscode/cleanup, no effect: types
...
expressions:
comparison:
loose: forbidden # dresscode/nette
...
mistakes:
strictArgument: required # dresscode/correctness, risky fixes accepted
...
upgrading:
functions:
arrayFirstLast: required # dresscode/modernizations, no effect: php
Řádek Use jmenuje nejprve pluginy a poté presety v přesném pořadí jejich aplikace včetně jejich vlastní
dědičnosti: nette staví na perCs a sadách cleanup, types a
correctness, samotný perCs vychází z psr12. Řádek Targets uvádí
cílovou verzi PHP a knihoven, a řádek Names potvrzuje způsob překladu nekvalifikovaných funkcí a konstant v namespaces.
U každé hodnoty stojí zdrojová vrstva: název presetu, the configuration, nebo konkrétní přepis.
U neaktivních rozhodnutí je vysvětlen důvod: no effect: php značí potřebu novějšího PHP, než jaké
projekt povoluje; no effect: types znamená chybějící typovou analýzu. Rozhodnutí se schválenými rizikovými opravami nese poznámku
risky fixes accepted; pravidlo změkčené klíčem severity nese only warns. Pokud projekt
využívá migrační data knihoven nebo data PHP, přibude řádek
Packages s výčtem balíčků (včetně php), verzí vyžadovanou kódem, cestou k datovému souboru
a verzemi, které by přinesl posun spodní meze. Verze označená (reported) leží v rozsahu, který Composer už
nyní umožňuje nainstalovat, takže její novinky se pouze reportují:
upgrading further to 3.3 (reported), 4.0.
Volba --file cesta ukáže totéž pro konkrétní soubor (včetně zohlednění všech aplikovaných přepisů),
přepínač --preset nette zobrazí samotný preset i s jeho dědičností. S volbou --format json
získáte strukturovaná data pro externí nástroje: klíče prostředí (use, targets,
nameResolution…), v sekci decisions každé rozhodnutí podle cesty s hodnotou (value),
vrstvou (layer), důvodem neaktivity (inactive) a příznakem výběru přes --only
(selected), a pod klíčem rules třídy pravidel s informací o tom, zda jsou aktivní.
Vysvětlení rozhodnutí: dresscode explain
Cesta rozhodnutí ve výpisu chyb slouží jako jednoznačný odkaz, nikoliv jako vyčerpávající vysvětlení. Příkaz
dresscode explain <cesta> k ní zobrazí podrobný popis, výčet všech povolených hodnot a jejich význam,
aktuální hodnotu v tomto projektu včetně zdrojové vrstvy a srovnání s tím, jak se k věci staví zavedené
standardy:
`spacing.arrowFunction` The space between `fn` and its parenthesis. By default `keep`. Takes: `'fn($x) => $x'` (no space before the parenthesis); `'fn ($x) => $x'` (a single space); `keep` Here `'fn ($x) => $x'`, set by the configuration. The standards: perCs `'fn($x) => $x'`, psr12 `keep`, nette `'fn($x) => $x'`, symfony `'fn ($x) => $x'`. Rule `DressCode\Rules\Whitespace\ConstructSpacingRule`, stage Formatting. See https://dresscode.run/decisions/spacing.arrowFunction
Zadáním názvu sekce (dresscode explain blankLines) vysvětlíte všechna její rozhodnutí naráz. Bez
argumentů příkaz popíše kompletní konfiguraci projektu rozdělenou po sekcích (vypíše pouze to, o čem některá vrstva
reálně rozhoduje). S přepínačem --format markdown vygeneruje ucelenou projektovou dokumentaci v Markdownu na
standardní výstup; do souboru ji jednoduše přesměrujete:
dresscode explain --format markdown > docs/coding-style.md
Tento dokument vzniká přímo z živé konfigurace, podle které se kód reálně kontroluje. Neobsahuje žádné zastaralé dohody z code review ani nepodložené teorie – popisuje přesně to, co projekt skutečně vymáhá.
Všechna rozhodnutí, která instalace zná: dresscode catalogue
Příkaz dresscode catalogue přehledně vypíše kompletní katalog všech rozhodnutí, která instalace zná
(vestavěná, z pluginů i z vlastních projektových pravidel) – každé s popisem a s názvy pravidel jiných
nástrojů, která pokrývá. Hvězdičkou označí ta, která vaše konfigurace aktivně zapíná:
* file.finalLineEndings Exactly one line ending after the last line; a file ending in HTML is content (covers single_blank_line_at_eof, PSR2.Files.EndFileNewline) * file.openingTag The opening tag of PHP (covers full_opening_tag, Generic.PHP.DisallowShortOpenTag) * file.lineEnding The line ending of every line, which the code written new takes too; under `keep` that follows the file (covers line_ending, Generic.Files.LineEndings) * file.lineLength The widest line, by which what spreads over lines is split (covers Generic.Files.LineLength, SlevomatCodingStandard.Classes.RequireMultiLineMethodSignature, SlevomatCodingStandard.ControlStructures.RequireMultiLineCondition, SlevomatCodingStandard.Files.LineLength) file.lineLengthExcept What a line too wide leaves as it is: a construct named is not spread over lines for its width, and a line it stands on, one a regular expression matches, or with `strings` one wide only for its string literals, is not reported (covers Generic.Files.LineLength, SlevomatCodingStandard.Files.LineLength) ...
Katalog oceníte ve chvíli, kdy hledáte, jak se daná věc v DressCode jmenuje, nebo kam se podělo konkrétní pravidlo
z jiného nástroje. S přepínačem --format json získáte katalog jako strukturovaná data: u každého
rozhodnutí jeho typ, obor hodnot, popis, doplňující poznámky, hodnoty čtyř hlavních standardů i mapování na cizí
pravidla. Ze stejného zdroje vzniká i stránka Pravidla na tomto webu.