Skip to content

feat(docs): enhance DocumentationMenuItem validation and file checks #3044

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 33 additions & 2 deletions src/Documentation/DocumentationMenuItem.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,20 @@
final readonly class DocumentationMenuItem
{
/**
* @param non-empty-string|null $slug
* @param string $href The URL path for the documentation menu item. Must be a non-empty string starting with '/'
* @param string $label The display text for the menu item. Must be a non-empty string
* @param non-empty-string|null $slug The unique identifier for the documentation file. If null, indicates a menu item without content
* @param bool $isNew Whether this menu item represents new documentation
*/
public function __construct(
private string $href,
private string $label,
private ?string $slug,
private bool $isNew = false,
) {
Assert::notEmpty($href, 'Documentation href cannot be empty');
Assert::startsWith($href, '/', 'Documentation href must start with a forward slash');
Assert::notEmpty($label, 'Documentation label cannot be empty');
}

public function getHref(): string
Expand All @@ -43,12 +49,37 @@ public function getSlug(): ?string
return $this->slug;
}

/**
* Checks if the documentation file exists for this menu item.
*
* @return bool True if the documentation file exists and is readable, false otherwise
*/
public function hasDocumentation(): bool
{
if ($this->slug === null) {
return false;
}

$documentationFilePath = $this->getDocumentationFilePath();
return file_exists($documentationFilePath) && is_readable($documentationFilePath);
}

public function getMarkdownContents(): string
{
Assert::notNull($this->slug);
$documentationFilePath = __DIR__ . '/../../resources/docs/' . $this->slug . '.md';
$documentationFilePath = $this->getDocumentationFilePath();

Assert::fileExists($documentationFilePath);
return FileSystem::read($documentationFilePath);
}

/**
* Gets the full path to the documentation file.
*
* @return string The absolute path to the documentation file
*/
private function getDocumentationFilePath(): string
{
return __DIR__ . '/../../resources/docs/' . $this->slug . '.md';
}
}
46 changes: 42 additions & 4 deletions src/ValueObject/AppliedRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,15 @@

use App\Exception\ShouldNotHappenException;
use Nette\Utils\Strings;
use Webmozart\Assert\Assert;

final readonly class AppliedRule
{
private const EXPECTED_CLASS_PARTS_COUNT = 5;
private const CATEGORY_INDEX = 1;
private const NODE_CLASS_INDEX = 3;
private const SHORT_CLASS_INDEX = 4;

private string $shortRectorClass;

public function __construct(
Expand Down Expand Up @@ -40,16 +46,48 @@ public function getTestFixtureNamespace(): string
public function getTestFixtureDirectoryPath(): string
{
$classParts = explode('\\', $this->rectorClass);

// Validate class structure
Assert::count(
$classParts,
self::EXPECTED_CLASS_PARTS_COUNT,
sprintf(
'Rector class "%s" must have exactly %d parts (e.g. "Rector\\Category\\Node\\SomeRector")',
$this->rectorClass,
self::EXPECTED_CLASS_PARTS_COUNT
)
);

$category = $classParts[1];
$rulesDirectory = 'rules-tests/' . $category;
// Validate and get required parts
$category = $this->getClassPart($classParts, self::CATEGORY_INDEX, 'category');
$nodeClass = $this->getClassPart($classParts, self::NODE_CLASS_INDEX, 'node class');
$shortClass = $this->getClassPart($classParts, self::SHORT_CLASS_INDEX, 'short class');

$nodeClass = $classParts[3];
$shortClass = $classParts[4];
$rulesDirectory = 'rules-tests/' . $category;

return $rulesDirectory . '/Rector/' . $nodeClass . '/' . $shortClass . '/Fixture';
}

/**
* @param string[] $classParts
*/
private function getClassPart(array $classParts, int $index, string $partName): string
{
Assert::keyExists(
$classParts,
$index,
sprintf('Missing %s in rector class "%s"', $partName, $this->rectorClass)
);

$part = $classParts[$index];
Assert::notEmpty(
$part,
sprintf('Empty %s in rector class "%s"', $partName, $this->rectorClass)
);

return $part;
}

/**
* Mimics @see \App\RuleFilter\ValueObject\RuleMetadata::getSlug()
*/
Expand Down