PHP API

Jak DressCode spustit z vlastního kódu: načtení konfigurace, Runner, výsledky běhu a vlastní reporter, který si výstup zpracuje po svém.

Kdy sáhnout po API

Příkazová řádka pokryje průběžnou integraci, Git hooky i editory. API potřebujete tehdy, když DressCode zapojujete do vlastního nástroje: do generátoru, který má vyrobený kód rovnou naformátovat, do migračního skriptu, do služby, která kontroluje kód z formuláře, nebo když chcete výsledky v podobě, kterou žádný z vestavěných formátů nedává.

Běh nad projektem

Konfigurace se načte stejně jako z příkazové řádky, z ní se postaví Runner a ten dostane seznam souborů a reporter:

use DressCode\Config\Loader;
use DressCode\Config\RunnerFactory;
use DressCode\Reporters\JsonReporter;

[$config, $root] = new Loader()->load(file: null, directory: getcwd());
$runner = new RunnerFactory()->createRunner($config, $root);

$files = $runner->findFiles(['src', 'tests']);
$result = $runner->run($files, fix: false, reporter: new JsonReporter(STDOUT));

exit($result->getExitCode());

Loader::load() najde dresscode.neon nebo dresscode.php od zadaného adresáře směrem nahoru (nebo vezme soubor, který mu určíte) a vrátí konfiguraci a kořenový adresář projektu; bez konfiguračního souboru platí výchozí preset, nebo konfigurace, kterou předáte třetím argumentem. Runner::findFiles() rozvine cesty podle klíčů paths, vyloučení (i těch z rozšíření) a přípon z konfigurace, run() je zpracuje a výsledky posílá reporteru soubor po souboru. Pravidlo nebo analýzu, kterou konfigurace staví closurou, popisuje jen text konfiguračního souboru, a proto createRunner() takový běh cachuje, jen když dostane cestu k tomu souboru v argumentu configFile.

RunnerFactory je zatím označená @internal, takže se její podoba může v minoritní verzi změnit. Je to jediné hrubé místo tohohle API a zároveň jediná cesta, jak Runner postavit. Komu to vadí, ať volá rovnou celý příkaz, jehož rozhraní stabilní je.

RunResult obsahuje FileResult pro každý soubor, jen bez kódu a výstupu, aby velký projekt nezůstal celý v paměti (ty dostane reporter v reportFile()), a k tomu souhrnná čísla: countViolations() (porušení souborů, jak byly načtené), countLeftAfterFix() (kolik jich po opravě zbude, v check i ve fix), countRemaining() (co zůstalo na uživateli: v check všechna porušení, ve fix ta, která oprava nechala), countRiskyDeferred() (rizikové opravy, které čekají na svolení) a listRiskyDeferredRules() (jména jejich pravidel), countChangedFiles(), countSyntaxErrors() (soubory, které nejdou parsovat), countFailures() (soubory, kde pravidlo selhalo), vlastnost baselined s počtem porušení, která umlčela baseline, a getExitCode() podle stejných pravidel jako na příkazové řádce. První tři počty přijmou volitelnou závažnost, Severity::Error nebo Severity::Warning.

Kdo konfiguraci staví v PHP místo načítání ze souboru, napíše new Config(...) se stejnými klíči jako NEON a přepisy pro část projektu jako objekty Override. Metoda createRunner() přijme navíc profil commandLine, který leží nad vším jako přepínače --preset, --group a --rule, a seznam only, který běh zúží jako --only:

use DressCode\Config;
use DressCode\Config\RunnerFactory;
use DressCode\Group;
use DressCode\Override;
use DressCode\Profile;

$config = new Config(
	presets: ['dresscode/nette'],
	groups: [Group::OptimizedCalls],
	overrides: [new Override(['tests'], nameResolution: 'uncertain')],
	paths: ['src', 'tests'],
);
$runner = new RunnerFactory()->createRunner($config, getcwd(), commandLine: new Profile(rules: ['dresscode/line-length' => false]));

Jeden soubor nebo řetězec

Pro kód, který neleží na disku, nebo pro jeden soubor bez hledání:

$result = $runner->processFile('src/Cart.php', $code);

foreach ($result->violations as $violation) {
	echo "$violation->line: $violation->message ($violation->ruleName)\n";
}

$fixed = $result->output;

processFile() nikdy nezapisuje. Cesta říká, která pravidla pro kód platí (podle ní se vyhodnotí přepisy pro cesty), $result->output je opravený kód, $result->isChanged() řekne, jestli se od vstupu liší, a $result->remaining jsou porušení, která opravený kód pořád má. Naproti tomu processPath() soubor přečte a s fix: true i zapíše. Ani jedno nepoužívá cache.

FileResult nese cestu, původní kód a výstup (výsledky, které si nechá RunResult, je už nemají, reporter je dostane v reportFile()), seznam objektů Violation nalezených v původním kódu (pravidlo, zpráva, řádek, sloupec, závažnost, jestli je oprava riziková, otisk pro baseline), seznam remaining s porušeními výstupu a s pozicemi v něm, tedy přesně s tím, co ohlásí příští kontrola opraveného souboru, dále varování a případnou chybu parsování nebo selhání pravidla.

Vlastní reporter

Reporter je rozhraní se třemi metodami. Výsledky mu chodí v pořadí vstupu, shrnutí na konci:

use DressCode\FileResult;
use DressCode\Reporter;
use DressCode\RunResult;

final class CountingReporter implements Reporter
{
	private array $byRule = [];


	public function start(int $fileCount, bool $fix): void
	{
	}


	public function reportFile(FileResult $result): void
	{
		foreach ($result->violations as $violation) {
			$this->byRule[$violation->ruleName] = ($this->byRule[$violation->ruleName] ?? 0) + 1;
		}
	}


	public function finish(RunResult $result): void
	{
		arsort($this->byRule);
		foreach ($this->byRule as $rule => $count) {
			printf("%5d  %s\n", $count, $rule);
		}
	}
}

Takový reporter po prvním běhu nad starým projektem řekne, která tři pravidla dělají devadesát procent všech porušení, a to je přesně informace, podle které se rozhoduje, co vypnout a co opravit. Vestavěné reportery (ConsoleReporter, JsonReporter, CheckstyleReporter, GithubReporter) jsou dobrý vzor k nahlédnutí.

Vlastní příkaz

Kdo balí DressCode do vlastní binárky (tak vznikl příkaz ecs v Nette Coding Standardu), spustí v procesu Console\Application a dá jí konfiguraci, která platí, když projekt žádnou vlastní nemá:

use DressCode\Config;
use DressCode\Console\Application;

$application = new Application(defaultConfig: new Config(extensions: [Nette\CodingStandard\Extension::class], presets: ['dresscode/nette']));
exit($application->run($argv));

Všechno ostatní, tedy příkazy, přepínače, formáty i paralelní běh, zůstává na příkazové řádce.