PHP 8.6 vychází 19. listopadu. DressCode pro něj kód přepisuje už teď. 16 dní Přechod na novější PHP →

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šší:

  1. presety vyjmenované v klíči use v pořadí zápisu (každý nad presety, ze kterých sám vychází),
  2. samotná rozhodnutí vaší projektové konfigurace,
  3. přepisy pro vybrané části projektu, kterým daný soubor vyhovuje (v pořadí zápisu),
  4. 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 keep dané 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;
  • keep zapsané 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 src získají ke standardu nette navíc sadu compilerOptimizations, která upraví globální funkce a konstanty tak, aby je kompilátor PHP mohl přímo optimalizovat.
  • Ve složce tests říká direktiva nameResolution: 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/stubs se 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í funkci App\is_null(). To spolehlivě rozhodne nastavení nameResolution: certain.
  • Váš lidský úsudek. Pravidlo file.strictTypes.declaration zapne deklaraci striktních typů, což mění sémantiku jazyka ze své podstaty; pravidlo functions.staticClosure zase 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říkaz dresscode 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 s typeAnalysis: phpstan i cache PHPStanu (výchozím umístěním je složka dresscode v systémovém dočasném adresáři).
  • rules a ruleUrl: 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.

Zobrazit na GitHubu · Nahlásit problém se stránkou na GitHubu · 2. 11. 2026