Konfigurace

Konfigurace je jeden soubor v kořeni projektu: dresscode.neon, nebo dresscode.php, pokud dáváte přednost PHP. Řeknete v něm, podle jakého standardu se kontroluje a co z něj chcete mít jinak. A to s přesností, jakou jinde nenajdete: každá část projektu může mít vlastní presety, vlastní styl, vlastní verzi PHP, vlastní seznam rizikových oprav, které přijímáte, i vlastní pravidla, která jen varují. Zdrojáky, testy i šablony generátoru tak dostanou každý přesně to, co potřebují, a pořád je to jeden krátký soubor.

Konfigurační soubor

Oba formáty umějí přesně totéž: každý klíč NEONu je pojmenovaný argument konstruktoru třídy DressCode\Config. Kde PHP předává objekt nebo callback, napíše se v NEONu entita. Následující dva soubory dělají totéž:

presets:
	- dresscode/per

lineLength: 100

rules:
	dresscode/ordered-imports: true
	dresscode/line-length: true
	dresscode/useless-else: keep

paths:
	- src
	- tests

excludePaths:
	- tests/fixtures
<?php declare(strict_types=1);

use DressCode\Config;

return new Config(
	presets: ['dresscode/per'],
	lineLength: 100,
	rules: [
		'dresscode/ordered-imports' => true,
		'dresscode/line-length' => true,
		'dresscode/useless-else' => 'keep',
	],
	paths: ['src', 'tests'],
	excludePaths: ['tests/fixtures'],
);

Dál na téhle stránce píšeme NEON, protože se čte lépe. Převod do PHP je vždy mechanický a editor vám v PHP napoví každý klíč.

Soubor se hledá od aktuálního adresáře směrem nahoru a kořenem projektu se stane adresář, ve kterém se našel. Všechny cesty v konfiguraci jsou relativní k němu. Vedle něj může ležet šablona dresscode.neon.dist (nebo .php.dist), kterou commitujete; soubor bez .dist ji pak celou nahradí a hodí se pro místní odchylku, kterou commitovat nechcete. Mít vedle sebe NEON i PHP je chyba, ne přednost jednoho z nich. Jiný soubor vnutíte přepínačem --config.

Překlep v klíči je chyba při načtení, ne tiše přeskočený řádek, a hláška nabídne klíč, který jste nejspíš mysleli (Unexpected item 'path', did you mean 'paths'?). Stejně dopadne překlep ve jménu pravidla nebo presetu (Unknown rule 'strict-comparsion'. Did you mean 'strict-comparison'?) a chybná hodnota volby řekne, která vrstva ji nastavila. Hodnota, kterou klíč přijmout nemůže, třeba eol: lf, je chyba se jménem souboru, ať ji napíšete do NEONu, nebo do PHP. Bez konfiguračního souboru platí preset dresscode/per, cesty se zadávají na příkazové řádce a jiný preset se vybere přepínačem --preset.

Jeden tvar pro projekt, jeho části i presety

Nejdůležitější věc, kterou o konfiguraci DressCode potřebujete vědět: to, co rozhoduje o zpracování souboru, má všude stejný tvar. Říkáme mu profil a tvoří ho tyhle klíče:

klíč co říká
presets ze kterých presetů se vychází
groups které skupiny pravidel běží
rules která pravidla běží a s jakými volbami
indent, eol, lineLength odsazení, konec řádku a nejdelší řádek
php pro jakou verzi PHP je kód psaný
types odkud se berou typy kódu
namespaces které funkce a konstanty deklarují jmenné prostory
nameResolution jestli jsou ty seznamy úplné
fixRisky u kterých pravidel přijímáte rizikové opravy
warnings která pravidla jen varují

Profil má celý projekt: to jsou klíče nejvyšší úrovně vašeho souboru. Vlastní profil může mít každá část projektu, třeba testy nebo jeden adresář. A preset není nic jiného než profil pod jménem. Co umíte napsat pro celý projekt, umíte tedy napsat i pro testy nebo do presetu, který sdílíte mezi projekty, a naopak: preset, který jinde zapínáte pro celý projekt, můžete zapnout jen pro jeho část.

Pět klíčů zůstává rozhodnutím projektu a preset je nastavit nemůže: verze PHP (php), odkud se berou typy (types), úplnost seznamů (nameResolution), souhlas s rizikovými opravami (fixRisky) a pravidla, která jen varují (warnings). Preset zná standard, váš kód znáte jen vy. Zbylé klíče konfigurace pak říkají, co se kontroluje a co si projekt bere z balíčků. Kdo co smí nastavit, shrnuje tabulka:

klíče konfigurace přepis preset rozšíření
presets, groups, rules, indent, eol, lineLengthnamespaces ano ano ano ne
php, nameResolution, fixRiskywarnings ano ano ne ne
types ano ne ne ne
overrides, paths, fileExtensions, baselinecacheDir ano ne ne ne
extensions, excludePaths, skipWhenanalyses ano ne ne ano

Presety a vrstvy

Preset je pojmenovaný profil. Čtyři vestavěné presety jsou standardy: rozhodují o všem, od odsazení po importy, a projekt si jeden z nich vezme za základ: dresscode/psr12, dresscode/per podle PER Coding Style 3.1 (potomek PSR-12 a výchozí volba), dresscode/nette a dresscode/symfony. Vedle nich stojí dresscode/nette-style, samotné rozvržení kódu podle Nette, které existuje kvůli standardu dresscode/nette, a dresscode/symfony-configurator, který žádné pravidlo nezapíná a jen vyjmenuje funkce, které Symfony deklaruje ve svém jmenném prostoru. Co přesně každý z nich zapíná, ukazuje přehled presetů.

presets:
	- dresscode/per

Jméno vestavěného presetu nebo pravidla si můžete zkrátit o předponu dresscode/: per znamená dresscode/per a line-length znamená dresscode/line-length. Hodí se to hlavně na příkazové řádce a v komentářích dresscode:ignore, kam jméno opisujete z výpisu; v konfiguračním souboru, který commitujete, je čitelnější celé jméno. Zkratka hledá jen mezi vestavěnými, takže preset nebo pravidlo z rozšíření se píše vždycky celé i s vendorem.

Pro každý soubor se profily skládají do vrstev, od nejnižší po nejvyšší:

  1. presety vaší konfigurace, každý nad presety, ze kterých vychází,
  2. klíče vaší konfigurace, přičemž skupiny leží pod pravidly téhož profilu,
  3. přepisy, kterým soubor odpovídá, v pořadí zápisu, každý opět nad svými presety,
  4. příkazová řádka (--preset, --group, --rule).

Preset, který už leží níž, se podruhé nepokládá. Vyšší vrstva mění jen to, co sama říká, a jak to změní, záleží na druhu hodnoty:

  • Hodnota (indent, eol, lineLength, php, nameResolution) platí z nejvyšší vrstvy, která ji uvádí.
  • Seznam (namespaces, fixRisky, warnings) se přes vrstvy sčítá. Přepis pro testy tak k seznamu projektu přidává a nic z něj neubere.
  • Volby pravidla se slévají. Mapa voleb se slévá po klíčích: když preset nastaví pravidlu dresscode/braces-position devět voleb a vy uvedete jednu, platí ta vaše a zbylých osm zůstane, jak je nastavil preset. Skalár a seznam naopak nahrazují celou hodnotu: multiLine: [arguments] u dresscode/trailing-comma znamená jen argumenty, ať preset říkal cokoli, takže kdo chce přidávat, opíše i zděděné položky. A keep na úrovni pravidla maže všechno, co o něm vrstvy pod ním řekly; mapa uvedená v další vrstvě pak začíná znovu od výchozích hodnot pravidla.

Odkud každá hodnota přišla a co přebila, vypíše dresscode config.

Skupiny pravidel

Katalog má skoro dvě stě pravidel a skládat si z nich konfiguraci položku po položce je práce, kterou za vás udělají skupiny. Konfigurace pak stojí na dvou otázkách: jak má kód vypadat, na to odpovídá jeden standard, a co po něm chcete kromě vzhledu, na to odpovídá šest skupin. Skupina je jméno záměru, ne výčet: napíšete cleanup a s výchozími volbami běží každé pravidlo, které odklízí kód, co je v souboru pro nic.

presets:
	- dresscode/per

groups:
	- cleanup
	- types

Skupin je právě šest a jiné jméno je chyba, která vypíše seznam platných:

skupina co zapíná příklad pravidla
cleanup kód, který je tam pro nic unused-imports, useless-else
modernization konstrukce, kterou cílová verze PHP umí, tam kde totéž říká starší zápis nullsafe-operator, match-for-simple-switch
types typy zapsané tam, kde je čte PHP type-hint-required, override-attribute-required
deprecations co cílová verze PHP nebo knihovna zavrhla či zrušila no-deprecated-functions, no-deprecated-classes
correctness to, co je nejspíš omyl no-duplicate-assignment, no-unreachable-catch
optimized-calls volání ve tvaru, který PHP optimalizuje už při kompilaci optimized-call-notation, no-is-null

Který standard které skupiny nese a co je v nich za pravidla, najdete v přehledu presetů a v přehledu pravidel; každá stránka pravidla má svou skupinu v řádku faktů pod perexem. Standard dresscode/nette zapíná cleanup, modernization, types, deprecations a correctness, ostatní standardy žádnou; skupinu si k nim přidáte sami.

Šest jmen je celý seznam, který se musíte naučit, a nepovyroste: jména jsou uzavřený výčet, takže je nerozšíří ani balíček s vlastními pravidly. Jeho pravidlo se přihlásí do jedné z těch šesti skupin a projektu, který ji zapnul, začne pracovat bez jediného řádku navíc. Katalog roste, seznam jmen ne.

Tři věci, které skupina záměrně nedělá:

  • Skupina nenese volby. Zapne pravidlo s výchozími hodnotami. Chcete-li u některého jinou, napište ho do rules: skupina leží pod pravidly téhož profilu, takže projekt má vždycky poslední slovo. Stejně tak cokoli ze skupiny vypnete jménem s hodnotou keep.
  • Skupina o vzhledu kódu nerozhoduje. Mezery, odsazení, uvozovky, pořadí členů a další rozhodnutí o vzhledu jsou věcí standardu. Pravidlo, které je rozhodnutím o vzhledu, nebo politika, ke které seznam dodává projekt (forbidden-functions), proto do žádné skupiny nepatří a zapíná se jménem.
  • Skupina žádné pravidlo nejmenuje. Pravidlo, které v projektu běžet nemůže, protože cílí na novější PHP, chce typy nebo knihovnu, kterou projekt nemá, ze skupiny tiše vypadne. Konfigurace kvůli tomu nespadne a nemusíte ji upravovat, když projekt zvedne verzi PHP nebo zapne typy: pravidlo prostě začne běžet.
groups:
	- cleanup

rules:
	dresscode/useless-else: keep      # ze skupiny nechci tohle
	dresscode/unused-imports:         # a tomuhle měním volbu
		searchAnnotations: false

Na jeden běh přidá skupinu přepínač --group cleanup (i opakovaně) a --only cleanup běh na její pravidla naopak zúží, což se hodí, když chcete velkou opravu rozdělit do samostatných commitů. Které skupiny nakonec platí, vypíše dresscode config na řádku Groups a u každého pravidla ze skupiny stojí vrstva group cleanup.

Styl, verze PHP a jmenné prostory

Styl je odsazovací jednotka, konec řádku a délka řádku, každé pod svým klíčem. Každé platí z nejvyšší vrstvy, která ho říká, obvykle tedy ze standardu, a když ho neříká žádná, pak tabulátor, ten konec řádku, který v souboru převládá, a žádný limit délky. Určit si ho samozřejmě můžete sami:

indent: 4
eol: LF
lineLength: 100

Hodnota indent je počet mezer, nebo tab. Hodnota eol je LF nebo CRLF (velkými písmeny, jak se ty zkratky píšou; lf je chyba), dále majority pro sjednocení každého souboru na jeho převažující konec řádku (výchozí), nebo platform pro konec řádku stroje, na kterém DressCode běží.

Hodnota lineLength je nejdelší řádek, který projde, v šířce, jak ho vidíte: tabulátor se počítá do další zarážky. false délku neomezuje, i když ji vrstva pod ním nastavila. Standardy dresscode/psr12 a dresscode/per mají 120 znaků, dresscode/nette 140 znaků a dresscode/symfony žádný limit. Délka je jedna pro všechna pravidla, která ji potřebují: dresscode/line-length ohlásí širší řádek, dresscode/multi-line-signature a dresscode/multi-line-condition rozlomí signaturu a podmínku na řádku širším než limit, dresscode/binary-operator-spacing nepřitáhne přiřazení na řádek levé strany, kdyby tím řádek limit přesáhl, a dresscode/multi-line-array podle ní skládá položky dlouhého pole za sebe.

Cílová verze PHP je vlastnost projektu a čte se z composer.json jako nejnižší verze, kterou dovoluje require.php; pravidla pro novější syntaxi se pod ní sama vynechají. DressCode rozumí běžným zápisům omezení: ^8.1, ~8.1, >=8.1 s horní mezí i bez ní, 8.1 - 8.3 a jejich kombinacím přes ||. Z jiného tvaru, třeba <8.4 nebo *, nejnižší verzi poznat nejde, a tak platí výchozí PHP 8.0. Starší verzi než PHP 8.0 DressCode nepodporuje: když ji projekt dovoluje nebo ji nastavíte, zkontroluje kód jako PHP 8.0 a upozorní, že oprava může zapsat syntaxi, kterou starší PHP nezná. Klíč php tedy uvádějte jen tehdy, když se verze od composer.json liší, nebo když ji z něj DressCode nepřečte. Do přepisu ho napište, když má část projektu běžet na jiném PHP. Uvozovky psát můžete a nemusíte, obojí je správně:

php: 8.2

Zápis php: '8.2' znamená totéž. Verze bez uvozovek je pro NEON číslo a DressCode ho čte jako verzi, jejíž minor je jednociferný, což každé vydané PHP mělo: php: 8 i php: 8.0 tedy znamenají 8.0.

Stejně jako verzi PHP může pravidlo vyžadovat i balíček v nějaké verzi, když hlídá jeho API; pravidlo pro nové API nette/forms 3.3 se v projektu, který ho nemá, prostě nespustí. Verze balíčku se čte z composer.json a z instalovaných balíčků vedle něj a měří se tím, na čem projekt stojí: u balíčku, který si projekt vyžaduje sám, je to nejnižší verze, kterou jeho omezení dovolí, protože kód psaný pro novější by se rozbil všude, kam to omezení pustí starší. U balíčku, který přišel jen s jiným, rozhoduje ta nainstalovaná. Pravidlo se tedy zapne, až když zvednete omezení ve svém composer.json.

Funkce a konstanty ve jmenných prostorech, které projekt deklaruje, vyjmenuje klíč namespaces se seznamy functions a constants, a klíč nameResolution: certain řekne, že jiné nejsou. DressCode pak ví, jestli nekvalifikované strlen() ve jmenném prostoru znamená globální funkci, a opravy, které na tom stojí, přestanou být rizikové. Položky se píšou jako v příkazu use, i se skupinami, které patří do uvozovek. Bez nameResolution platí uncertain a takové opravy čekají na svolení. Proč na tom tolik záleží, vysvětluje stránka Funkce a konstanty ve jmenných prostorech.

nameResolution: certain
namespaces:
	constants:
		- 'App\{VERSION, DEBUG}'

Seznamy se sčítají přes všechny vrstvy: presety do nich přidávají funkce svých frameworků a přepis funkce, které deklaruje jen jeho část projektu. Klíč nameResolution nastaví konfigurace nebo přepis, preset ne: framework zná preset, váš kód jen vy.

Typy z PHPStanu

Klíč types: phpstan řekne, že se typy kódu berou z PHPStanu nainstalovaného v projektu, s jeho konfigurací phpstan.neon a s jeho rozšířeními. Až s ním můžou běžet pravidla, která bez typů nedávají smysl, třeba přepis zastaralého API knihoven nebo #[\Override] u přepsané metody:

types: phpstan

Stejně jako verze PHP je to rozhodnutí projektu, a preset ho proto nastavit nemůže; uvádí se na nejvyšší úrovni konfigurace. Pravidlo, které typy potřebuje a které zapíná preset, se bez klíče types potichu vynechá. Pravidlo, které jste zapnuli jménem, je bez klíče types chyba konfigurace, protože běh, ve kterém by pravidlo chybělo, by tvrdil, že je kód v pořádku. Co všechno typy umožní a co stojí, popisuje stránka Typy z PHPStanu.

Pravidla a jejich volby

Klíč rules je mapa, ve které je jménu pravidla přiřazena jedna ze čtyř věcí:

  • true zapne pravidlo s výchozími volbami;
  • keep říká, že pravidlo nic nevynucuje: kód zůstane, jak je. Je to jediné slovo pro „nesahej na to“, stejné u celého pravidla i u jedné jeho volby (false z PHP zápisu znamená totéž);
  • hodnota u pravidla, které je jedním rozhodnutím: dresscode/yoda: forbidden, dresscode/string-quotes: single, dresscode/kind-in-class-name: required. Je to zkratka za mapu s jedinou volbou a stránka pravidla říká, kterou;
  • mapa voleb.

Jména jsou ta z přehledu pravidel a z výpisu dresscode check; volby každého pravidla najdete i s příklady na jeho stránce a vypíše je dresscode explain <pravidlo>.

rules:
	dresscode/strict-comparison: true
	dresscode/useless-else: keep
	dresscode/string-quotes: single
	dresscode/trailing-comma:
		multiLine: [arrays, arguments]
	dresscode/multi-line-array:
		shape: keep

Poslední řádek ukazuje keep uvnitř mapy: volba, která pojmenovává rozhodnutí (tvar, polohu, zarovnání), ho přijme a pravidlo pak tohle jedno rozhodnutí nevynucuje, ale zbytek hlídá dál. Bez toho by se muselo vypnout celé.

Dvě věci, na kterých se dá zakopnout:

  • Seznam ve volbě nahrazuje výchozí seznam, neslučuje se s ním. multiLine: [arguments] zapne koncovou čárku u argumentů a vypne ji u polí, i když u polí byla v presetu. Chcete-li přidávat, opište i zděděné hodnoty. Mapa se naopak slévá, takže u ní stačí uvést jen klíč, který měníte.
  • Jméno pravidla z jiného nástroje není platný klíč. no_unused_imports ani SlevomatCodingStandard.Namespaces.UnusedUses sem nepatří. DressCode je zná a v chybové hlášce vám řekne, které jeho pravidlo jim odpovídá, ale do konfigurace patří jméno jeho. Celý cizí konfigurační soubor převede příkaz dresscode import.

Místo jména lze všude použít název třídy, což se hodí u vlastních pravidel, která pak nemusíte nikde registrovat:

rules:
	App\CodeStyle\ExceptionMessagePeriodRule: true

Na jeden běh se pravidlo zapíná a vypíná z příkazové řádky: --rule dresscode/line-length=off.

Jiný profil pro část projektu

Projekt málokdy vypadá všude stejně. Knihovní kód chcete co nejrychlejší, testy mají vlastní hlavičku a vlastní pomocné funkce, šablony generátoru jiné odsazení. Většina nástrojů na to má jen jedno: pravidlo pro některé cesty vypnout. DressCode dá každé části projektu celý vlastní profil. Přepis v klíči overrides platí pro soubory, které odpovídají jeho paths, a smí v něm stát cokoli, co smí stát v profilu projektu, presety počínaje:

presets:
	- dresscode/nette

nameResolution: certain

overrides:
	# knihovna píše volání tak, jak je PHP umí optimalizovat
	- paths: [src]
	  groups: [optimized-calls]

	# testy si ve svém jmenném prostoru deklarují pomocné funkce
	- paths: [tests]
	  nameResolution: uncertain
	  rules: {dresscode/blank-lines: {afterImports: 2}}
	  warnings: [dresscode/property-phpdoc-required]

	# šablony generátoru se odsazují dvěma mezerami
	- paths: [resources/stubs]
	  indent: 2

Co z toho vyjde:

  • Soubory v src dostanou k Nette Coding Standardu navíc skupinu optimized-calls, která zapíše volání tak, aby je PHP mohlo optimalizovat. Přepis leží nad klíči konfigurace, takže pravidlu, které nastavují oba, dá poslední slovo přepis; o volbách, které přepis nezmíní, dál rozhoduje projekt.
  • V tests říká nameResolution: uncertain, že seznamy funkcí z konfigurace pro testy úplné nejsou. Opravy, které stojí na tom, že je funkce globální, se tam jen ohlásí jako rizikové a hlídač nevyjmenovaných deklarací tam mlčí, takže pomocná funkce testu do konfigurace psát nemusíte. Zdrojáky přitom dál těží z jistoty; podrobnosti jsou na stránce Funkce a konstanty ve jmenných prostorech.
  • Tamtéž {afterImports: 2} změní jediný klíč a ostatní čísla z presetu zůstanou, a warnings přidá k seznamu projektu pravidlo, které v testech jen varuje.
  • Šablony v resources/stubs se odsazují dvěma mezerami a ve všem ostatním platí totéž co ve zbytku projektu.

Cesty mají stejnou syntaxi jako excludePaths a počítají se od kořene projektu. Soubor, který odpovídá několika přepisům, dostane jejich vrstvy v pořadí zápisu, takže pozdější přepis má poslední slovo. Hodnota keep pravidlo pro tyto soubory vypne a pozdější přepis ho pro soubory, které odpovídají oběma, může zase zapnout. Pravidlo, které má pro část projektu platit jen s jinou hodnotou, nevypínejte; dejte mu tu hodnotu v přepisu.

Každý přepis DressCode projde hned na začátku běhu, i když mu žádný soubor neodpovídá, takže překlep ve jménu pravidla nebo presetu i volbu, kterou pravidlo nezná, ohlásí rovnou a se jménem přepisu:

Error: Unknown rule 'dresscode/strict-comparsion'. Did you mean 'dresscode/strict-comparison'? (in the override for tests)

Co pro konkrétní soubor nakonec platí, ukáže dresscode config --file tests/CartTest.phpt: presety, styl, jistotu jmen i každou volbu s vrstvou, která ji nastavila.

Varování a rizikové opravy

Každé porušení je chyba a shodí build, dokud ho konfigurace nezměkčí. Pravidlo v seznamu warnings dál hlásí a výpis ho počítá zvlášť, ale exit kód zůstane 0; práh dá přepínač --max-warnings <n>. Hodí se to u toho, co fix neopraví, hlavně u délky řádku; u opravitelných pravidel je po fix stejně prázdno.

warnings:
	- dresscode/line-length

Riziková oprava je taková, která může změnit, co kód dělá. Většinou je to vlastnost konkrétního místa: get_class($x) na $x::class je bezpečné, dokud $x nemůže být null, takže dresscode/modern-class-name-reference opraví, co bezpečně umí, a rizikový výskyt jen ohlásí. Jiné pravidlo ale bezpečné místo z kódu poznat nemůže, a pak je riziková každá jeho oprava: dresscode/strict-call doplní do in_array() parametr $strict = true, a kód, který spoléhal na volné porovnání, pak může najít něco jiného. Shrnutí řekne, kolik rizikových oprav čeká, a ve formátu json má každé takové porušení příznak risky.

Zapnout pravidlo, ať jménem, nebo presetem, ještě neznamená souhlasit s jeho rizikovými opravami. Souhlas dává projekt klíčem fixRisky, seznamem pravidel, jejichž rizikové opravy přijímá; fix pak udělá jen ty a ostatní ohlásí. Preset tenhle souhlas dát nemůže a DressCode preset, který by se o to pokusil, odmítne. Git hook nebo editor proto nepotřebují žádný přepínač: opraví, co projekt přijal, a zbytek ohlásí. Přepínač --fix-risky dovolí rizikové opravy všech pravidel na jeden běh:

fixRisky:
	- dresscode/strict-call
	- dresscode/modern-class-name-reference

Oba seznamy se přes vrstvy sčítají, takže přepis může jen pro svou část projektu přijmout rizikové opravy, které jinde přijmout nechcete, nebo nechat pravidlo jen varovat.

Bez souhlasu se rizikové místo hlásí jako každé jiné porušení, které se neopravilo, a exit kód je 1. Ticho by znamenalo, že „rizikové“ je synonymum pro „ignorované“. Shrnutí přitom jmenuje pravidla, jejichž opravy čekají, takže víte, co do seznamu napsat.

Jestli jste rizikové opravy pravidla přijali, ukáže dresscode config: u pravidla stojí risky fixes accepted, a u pravidla, jehož riziková je každá oprava a svolení zatím nemá, risky fixes only reported; pravidlo, které jen varuje, nese only warns. Jméno v fixRisky, jehož pravidlo neběží nikde, ani v žádném přepisu, ohlásí každý běh jako varování, aby v konfiguraci nezůstal řádek, který nic nedělá.

Cesty

paths říká, co se kontroluje: soubory a adresáře relativně ke kořeni projektu. Cesty zadané na příkazové řádce mají přednost před konfigurací; adresář, ve kterém některé cesty z paths leží, se ale zúží jen na ně, viz Příkazová řádka.

excludePaths naopak cesty vynechává a jen přidává: k výchozímu seznamu (vendor, node_modules, temp, tmp, log a všechny adresáře začínající tečkou) přidá vaše, a totéž udělá každé rozšíření. Nikdo nemůže vrátit zpátky to, co jiný vyloučil, takže se nestane, že by rozšíření omylem zapnulo kontrolu vendor.

paths:
	- src
	- tests

excludePaths:
	- tests/fixtures
	- '*.generated.php'

Vzor s lomítkem je ukotvený ke kořeni, takže tests/fixtures je právě ten jeden adresář. Vzor bez lomítka odpovídá jménu souboru nebo adresáře v jakékoli hloubce, takže fixtures vynechá každý adresář toho jména. Hvězdička zastupuje cokoli kromě lomítka, dvě hvězdičky i lomítka, takže tests/**.phpt jsou všechny testy v libovolné hloubce.

Jednotlivé pravidlo vypnete jen pro některé cesty přepisem s hodnotou keep; zbytek pravidel takový soubor zkontroluje normálně:

overrides:
	- paths: [legacy]
	  rules: {dresscode/strict-comparison: keep}

fileExtensions říká, které přípony se berou jako PHP (výchozí je jen php); projekt s testy Nette Testeru přidá phpt. Soubor podle jeho obsahu, třeba generovaný kód podle hlavičky, vynechá skipWhen, kterému předáte callback jako entitu:

skipWhen: App\CodeStyle\GeneratedFilter::isGenerated(...)

Callback dostane obsah souboru a jeho cestu a vrátí true, když se má soubor přeskočit. Filtr může přinést i rozšíření; soubor se pak přeskočí, když ho přeskočí kterýkoli z nich.

Rozšíření

Klíč extensions vyjmenuje třídy, které do projektu něco přinášejí. Rozšíření (extension) je to, co má pro projekt balíček: pravidla a presety pod jmény, analýzy, vyloučené cesty a filtr souborů. Takhle se do DressCode zapojuje Nette Coding Standard:

extensions:
	- Nette\CodingStandard\Extension

presets:
	- dresscode/nette
	- nette/clean-code

Rozšíření o tom, jak se kód píše, nerozhoduje: nezapne preset ani pravidlo a nenastaví styl. To je vždycky rozhodnutí projektu, a proto ho vždycky přečtete v jeho vlastním souboru. Rozšíření jen zpřístupní jména, takže nette/clean-code v ukázce si vybral projekt.

V klíči extensions může stát i třída pravidla nebo presetu. Tím dostane své jméno: pravidlo pak zapnete jménem v rules, na jeden běh v --rule a potlačíte ho v komentáři dresscode:ignore, preset zapnete jménem v presets, obojí v konfiguraci i v přepisu. Jak rozšíření napsat, popisuje Vlastní preset a rozšíření.

Balíček může své rozšíření ohlásit i sám, ve svém composer.json pod klíčem extra.dresscode.extension. DressCode ho pak po instalaci najde v vendor/composer/installed.json a do extensions ho psát nemusíte. Stejně najde soubor deprecations.neon, který knihovna uvede pod extra.dresscode.deprecations a ve kterém stojí, co její verze přejmenovaly, viz Aktualizace knihoven. Ani jedno přitom žádné pravidlo nezapne: pravidla z balíčku jen dostanou svá jména a data se uplatní jen v pravidlech, která jste zapnuli sami, ať jménem, nebo skupinou deprecations. Živý příklad nese nette/forms: po jeho instalaci ví dresscode/no-deprecated-classes, že se z Nette\Forms\IControl stalo Nette\Forms\Control, a vy jste neudělali nic než to pravidlo zapnuli.

Objekty a callbacky

Kde zápis v PHP předává objekt nebo callback, napíše se v NEONu entita. Vypadá jako volání v PHP a znáte ji třeba z konfigurace Nette DI:

  • App\CodeStyle\Filter(8.1, strict: true) vytvoří objekt; argumenty mohou být pojmenované,
  • App\CodeStyle\Filter::create(8.1) zavolá statickou metodu a použije, co vrátí,
  • App\CodeStyle\Filter::isGenerated(...) předá samotnou metodu jako callback,
  • App\CodeStyle\Filter(8.1)::withLimit(100) zavolá metodu na tom, co vrátil předchozí článek řetězu.

Argumentem může být i další entita. Entitu přijímají čtyři klíče, rules včetně pravidel v přepisech:

extensions:
	- App\CodeStyle\Extension(strict: true)

skipWhen: App\CodeStyle\GeneratedFilter::isGenerated(...)

rules:
	App\CodeStyle\CacheKeyRule: App\CodeStyle\CacheKeyRule(App\CodeStyle\Keys::load())

analyses:
	- App\CodeStyle\Analyses\Imports
	App\CodeStyle\Analyses\Calls: App\CodeStyle\Analyses\Calls::create(...)

V PHP je to totéž, jen napsané jako kód:

return new Config(
	extensions: [new App\CodeStyle\Extension(strict: true)],
	rules: [
		App\CodeStyle\CacheKeyRule::class => fn() => new App\CodeStyle\CacheKeyRule(App\CodeStyle\Keys::load()),
	],
	skipWhen: App\CodeStyle\GeneratedFilter::isGenerated(...),
	analyses: [
		App\CodeStyle\Analyses\Imports::class,
		App\CodeStyle\Analyses\Calls::class => App\CodeStyle\Analyses\Calls::create(...),
	],
);

Rozšíření, skipWhen a továrna analýzy se vyhodnotí hned při načtení konfigurace. U pravidla je entita recept: pravidlo s vlastními závislostmi se podle něj postaví pokaždé, když ho DressCode potřebuje, třeba zvlášť pro soubory každého přepisu. Při načtení se jen ověří, že třída nebo metoda existuje, takže překlep je chyba konfigurace hned, ne až během kontroly.

Příkazová řádka

Přepínače --preset, --group a --rule jsou nejvyšší vrstva: leží nad konfigurací i nad všemi přepisy, takže --rule dresscode/line-length=off vypne pravidlo pro jeden běh opravdu všude. Přepínač --only běh zúží na jmenovaná pravidla, presety a skupiny a --fix-risky dovolí všechny rizikové opravy. Klíči konfiguračního souboru nejsou: jsou to rozhodnutí jednoho běhu, ne projektu. Všechny přepínače popisuje stránka Příkazová řádka.

Další klíče

  • baseline: soubor se soupisem porušení, která zatím opravit nejde a nemají se hlásit; jak vzniká a kdy se hodí, popisuje Potlačení pravidel a baseline.
  • cacheDir: kam si DressCode ukládá, které soubory už prošly čistě; výchozí je systémový dočasný adresář.
  • analyses: registrace vlastní analýzy pro vlastní pravidla; analýza, kterou jádro samo nepostaví, dostane továrnu jako entitu. Analýza stejné třídy z konfigurace má přednost před tou z rozšíření.

Co z toho všeho nakonec platí, ukáže dresscode config: u každého pravidla, které běží, volby s hodnotou, vrstvou, která ji nastavila, a s tím, co přebila, a u pravidla, které neběží, důvod. S --file totéž pro jeden soubor, i s přepisy, které pro něj platí. dresscode 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 jména pravidel jiných nástrojů, která pokrývá.