respect-validation/src-dev/Markdown/Linters/ValidatorTemplatesLinter.php
Henrique Moody 98150c7065
Add doc linter to check outdated templates
We don't often change the tempaltes of validators, but when we do it's
extremely important that the documentation of the validators match the
exact template the validator has.
2026-01-13 23:37:06 -07:00

99 lines
3 KiB
PHP

<?php
/*
* Copyright (c) Alexandre Gomes Gaigalas <alganet@gmail.com>
* SPDX-License-Identifier: MIT
*/
declare(strict_types=1);
namespace Respect\Dev\Markdown\Linters;
use ReflectionClass;
use Respect\Dev\Markdown\Content;
use Respect\Dev\Markdown\File;
use Respect\Dev\Markdown\Linter;
use Respect\Validation\Message\Template;
use UnexpectedValueException;
use function array_find_key;
use function basename;
use function preg_match;
use function preg_replace;
use function sprintf;
use function str_contains;
use function str_starts_with;
final readonly class ValidatorTemplatesLinter implements Linter
{
public function lint(File $file): File
{
if (!str_contains($file->filename, '/validators/') || str_contains($file->filename, '/validators/index.md')) {
return $file;
}
$validator = basename($file->filename, '.md');
$templates = $this->getTemplates($validator);
if ($templates === []) {
return $file;
}
$descriptions = [];
try {
$currentTemplates = $file->content->getSection('## Templates');
$templateLines = $currentTemplates->toArray();
foreach ($templateLines as $index => $currentLine) {
if (!str_starts_with($currentLine, '### ')) {
continue;
}
$id = preg_replace('/^### `.+::([A-Z_]+)`/', '$1', $currentLine);
if (!isset($templateLines[$index + 2]) || !preg_match('/^[A-Z]/', $templateLines[$index + 2])) {
continue;
}
$descriptions[$id] = $templateLines[$index + 2];
}
} catch (UnexpectedValueException) {
// No existing Templates section, that's fine
}
$content = new Content();
$content->h2('Templates');
foreach ($templates as $id => $template) {
$content->h3(sprintf('`%s::%s`', $validator, $id));
if (isset($descriptions[$id])) {
$content->paragraph($descriptions[$id]);
$content->emptyLine();
}
$content->table(['Mode', 'Template'], [
['`default`', $template->default],
['`inverted`', $template->inverted],
]);
}
return $file->withContent($file->content->withSection($content));
}
/** @return array<string, Template> */
private function getTemplates(string $validator): array
{
$reflectionClass = new ReflectionClass('Respect\\Validation\\Validators\\' . $validator);
$constants = $reflectionClass->getConstants();
$templates = [];
foreach ($reflectionClass->getAttributes(Template::class) as $attribute) {
$template = $attribute->newInstance();
$id = array_find_key($constants, static fn($constant) => $template->id === $constant);
if ($id === null) {
continue;
}
$templates[$id] = $template;
}
return $templates;
}
}