Skip to content
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

Insruction parameters doc generation #149

Merged
merged 4 commits into from
Mar 27, 2024
Merged
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
12 changes: 6 additions & 6 deletions packages/documentator/docs/Commands/preprocess.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ Where:

* `<target>` - File path.
* `<parameters>` - additional parameters
* `summary` - Include the class summary? (default `true`)
* `description` - Include the class description? (default `true`)
* `summary: bool = true` - Include the class summary?
* `description: bool = true` - Include the class description?

Includes the docblock of the first PHP class/interface/trait/enum/etc
from `<target>` file. Inline tags include as is except `@see`/`@link`
Expand All @@ -44,8 +44,8 @@ which will be replaced to FQCN (if possible). Other tags are ignored.

* `<target>` - Directory path.
* `<parameters>` - additional parameters
* `depth` - Default is `0` (no nested directories). The `null` removes limits.
* `template` - Blade template
* `depth: array|string|int|null = 0` - [Directory Depth](https://symfony.com/doc/current/components/finder.html#directory-depth) (eg the `0` means no nested directories, the `null` removes limits).
* `template: string = 'default'` - Blade template.

Returns the list of `*.md` files in the `<target>` directory. Each file
must have `# Header` as the first construction. The first paragraph
Expand Down Expand Up @@ -79,7 +79,7 @@ Includes the `<target>` file.

* `<target>` - Directory path.
* `<parameters>` - additional parameters
* `template` - Blade template
* `template: string = 'default'` - Blade template.

Generates package list from `<target>` directory. The readme file will be
used to determine package name and summary.
Expand All @@ -88,7 +88,7 @@ used to determine package name and summary.

* `<target>` - File path.
* `<parameters>` - additional parameters
* `data` - Array of variables (`${name}`) to replace (required).
* `data: array` - Array of variables (`${name}`) to replace.

Includes the `<target>` as a template.

Expand Down
85 changes: 84 additions & 1 deletion packages/documentator/src/Commands/Preprocess.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,28 @@
use LastDragon_ru\LaraASP\Documentator\Package;
use LastDragon_ru\LaraASP\Documentator\Preprocessor\Contracts\ParameterizableInstruction;
use LastDragon_ru\LaraASP\Documentator\Preprocessor\Preprocessor;
use LastDragon_ru\LaraASP\Documentator\Utils\PhpDoc;
use LastDragon_ru\LaraASP\Serializer\Contracts\Serializable;
use Override;
use ReflectionClass;
use ReflectionProperty;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Finder\Finder;

use function array_map;
use function explode;
use function getcwd;
use function gettype;
use function implode;
use function is_a;
use function is_scalar;
use function ksort;
use function rtrim;
use function str_replace;
use function strtr;
use function trim;
use function var_export;

/**
* @see Preprocessor
Expand Down Expand Up @@ -116,7 +127,7 @@ protected function getInstructionsHelp(Preprocessor $preprocessor): string {
$desc = $instruction::getDescription();
$target = $instruction::getTargetDescription();
$params = is_a($instruction, ParameterizableInstruction::class, true)
? $instruction::getParametersDescription()
? $this->getInstructionParameters($instruction)
: null;

if ($target !== null && $params !== null) {
Expand Down Expand Up @@ -160,4 +171,76 @@ protected function getInstructionsHelp(Preprocessor $preprocessor): string {

return implode("\n\n", $help);
}

/**
* @template T of Serializable
*
* @param class-string<ParameterizableInstruction<T>> $instruction
*
* @return array<string, string>
*/
protected function getInstructionParameters(string $instruction): array {
// Explicit? (deprecated)
$parameters = $instruction::getParametersDescription();

if ($parameters) {
return $parameters;
}

// Nope
$class = new ReflectionClass($instruction::getParameters());
$properties = $class->getProperties(ReflectionProperty::IS_PUBLIC);
$parameters = [];

foreach ($properties as $property) {
// Ignored?
if (!$property->isPublic() || $property->isStatic()) {
continue;
}

// Name
$name = $property->getName();
$definition = $name;
$hasDefault = $property->hasDefaultValue();
$theDefault = $hasDefault
? $property->getDefaultValue()
: null;

if ($property->hasType()) {
$definition = "{$definition}: {$property->getType()}";
}

if ($property->isPromoted()) {
foreach ($class->getConstructor()?->getParameters() ?? [] as $parameter) {
if ($parameter->getName() === $name) {
$hasDefault = $parameter->isDefaultValueAvailable();
$theDefault = $hasDefault
? $parameter->getDefaultValue()
: null;

break;
}
}
}

if ($hasDefault) {
$default = is_scalar($theDefault) ? var_export($theDefault, true) : '<'.gettype($theDefault).'>';
$definition = "{$definition} = {$default}";
} else {
// empty
}

// Description
$doc = new PhpDoc($property->getDocComment() ?: null);
$description = $doc->getSummary() ?: '_No description provided_.';
$description = trim(
implode(' ', array_map(rtrim(...), explode("\n", str_replace("\r\n", "\n", $description)))),
);

// Add
$parameters[$definition] = $description;
}

return $parameters;
}
}
109 changes: 109 additions & 0 deletions packages/documentator/src/Commands/PreprocessTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<?php declare(strict_types = 1);

namespace LastDragon_ru\LaraASP\Documentator\Commands;

use LastDragon_ru\LaraASP\Documentator\Preprocessor\Contracts\ParameterizableInstruction;
use LastDragon_ru\LaraASP\Documentator\Testing\Package\TestCase;
use LastDragon_ru\LaraASP\Serializer\Contracts\Serializable;
use Override;
use PHPUnit\Framework\Attributes\CoversClass;

/**
* @internal
*/
#[CoversClass(Preprocess::class)]
final class PreprocessTest extends TestCase {
public function testGetInstructionParameters(): void {
$command = new class() extends Preprocess {
/**
* @inheritDoc
*/
#[Override]
public function getInstructionParameters(string $instruction): array {
return parent::getInstructionParameters($instruction);
}
};

self::assertEquals(
[
'publicPropertyWithoutDefaultValue: int' => 'Description.',
'publicPropertyWithDefaultValue: float = 123.0' => '_No description provided_.',
'publicPromotedPropertyWithoutDefaultValue: int' => 'Description.',
'publicPromotedPropertyWithDefaultValue: int = 321' => '_No description provided_.',
],
$command->getInstructionParameters(
PreprocessTest__ParameterizableInstruction::class,
),
);
}
}

// @phpcs:disable PSR1.Classes.ClassDeclaration.MultipleClasses
// @phpcs:disable Squiz.Classes.ValidClassName.NotCamelCaps

/**
* @internal
* @noinspection PhpMultipleClassesDeclarationsInOneFile
*
* @implements ParameterizableInstruction<PreprocessTest__ParameterizableInstructionParameters>
*/
class PreprocessTest__ParameterizableInstruction implements ParameterizableInstruction {
#[Override]
public static function getName(): string {
return 'test:parameterizable-instruction';
}

#[Override]
public static function getDescription(): string {
return 'Description description description description description.';
}

#[Override]
public static function getTargetDescription(): ?string {
return 'Target target target target target.';
}

#[Override]
public static function getParameters(): string {
return PreprocessTest__ParameterizableInstructionParameters::class;
}

/**
* @inheritDoc
*/
#[Override]
public static function getParametersDescription(): array {
return [];
}

#[Override]
public function process(string $path, string $target, Serializable $parameters): string {
return $path;
}
}

/**
* @internal
* @noinspection PhpMultipleClassesDeclarationsInOneFile
*/
class PreprocessTest__ParameterizableInstructionParameters implements Serializable {
public static bool $publicStaticProperty = true;

/**
* Description.
*/
public int $publicPropertyWithoutDefaultValue;
public float $publicPropertyWithDefaultValue = 123;

public function __construct(
/**
* Description.
*/
public int $publicPromotedPropertyWithoutDefaultValue,
public int $publicPromotedPropertyWithDefaultValue = 321,
protected bool $protectedProperty = true,
protected bool $privateProperty = true,
) {
$this->publicPropertyWithoutDefaultValue = 0;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ interface ParameterizableInstruction extends Instruction {
public static function getParameters(): string;

/**
* @return non-empty-array<string, string>
* @deprecated %{VERSION} Use docblock instead.
*
* @return array<string, string>
*/
public static function getParametersDescription(): array;

Expand Down
Loading
Loading