diff --git a/CNAME b/CNAME new file mode 100644 index 0000000..82870cd --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +phpdoc.daniil.it diff --git a/bin/phpdoc b/bin/phpdoc index fd81b5c..1355293 100755 --- a/bin/phpdoc +++ b/bin/phpdoc @@ -5,13 +5,18 @@ use danog\PhpDoc\PhpDocBuilder; if ($argc < 2) { $me = $argv[0]; - fprintf(stderr, "Usage: $me filePath [namespace]\n"); + fprintf(STDERR, "Usage: $me filePath [namespace]".PHP_EOL); die(1); } +if (!class_exists(PhpDocBuilder::class)) { + require 'vendor/autoload.php'; +} + $path = $argv[1]; $namespace = $argv[2] ?? ''; + PhpDocBuilder::fromNamespace($namespace) ->setOutput($path) ->run(); diff --git a/docs/danog/PhpDoc/PhpDocBuilder.md b/docs/danog/PhpDoc/PhpDocBuilder.md new file mode 100644 index 0000000..a523360 --- /dev/null +++ b/docs/danog/PhpDoc/PhpDocBuilder.md @@ -0,0 +1,122 @@ +--- +title: danog\PhpDoc\PhpDocBuilder: PHP documentation builder. +description: + +--- +# `danog\PhpDoc\PhpDocBuilder` +[Back to index](../../index.md) + +> Author: Daniil Gentili + + +PHP documentation builder. + + + +## Method list: +* `fromNamespace(string $namespace): self` +* `setAuthors(\phpDocumentor\Reflection\DocBlock\Tags\Author[] $authors): self` +* `setMode(int $mode): self` +* `setFilter(callable $ignore): self` +* `setOutput(string $output): self` +* `setName(string $name): self` +* `setDescription(string $description): self` +* `setImage(string $image): self` +* `run(): self` + +## Methods: +### `fromNamespace(string $namespace): self` + +Create docblock builder. + + +Parameters: +* `$namespace`: `string` Namespace (defaults to package namespace) + + + +### `setAuthors(\phpDocumentor\Reflection\DocBlock\Tags\Author[] $authors): self` + +Set authors. + + +Parameters: +* `$authors`: `\phpDocumentor\Reflection\DocBlock\Tags\Author[]` Authors + + +#### See also: +* `\phpDocumentor\Reflection\DocBlock\Tags\Author` + + + + +### `setMode(int $mode): self` + +Set scan mode. + + +Parameters: +* `$mode`: `int` Scan mode. + + + +### `setFilter(callable $ignore): self` + +Set filter to ignore certain classes. + + +Parameters: +* `$ignore`: `callable` + Full type: + ``` + callable(class-string) + ``` + + + +### `setOutput(string $output): self` + +Set output directory. + + +Parameters: +* `$output`: `string` Output directory + + + +### `setName(string $name): self` + +Set project name. + + +Parameters: +* `$name`: `string` Name + + + +### `setDescription(string $description): self` + +Set project description. + + +Parameters: +* `$description`: `string` Project description + + + +### `setImage(string $image): self` + +Set project image. + + +Parameters: +* `$image`: `string` Project image + + + +### `run(): self` + +Run documentation builder. + + + diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..8f6601d --- /dev/null +++ b/docs/index.md @@ -0,0 +1,15 @@ +--- +title: danog/phpdoc +description: Simple markdown PHPDOC documentation generator with psalm type annotation support. +--- +# `danog/phpdoc` + +Simple markdown PHPDOC documentation generator with psalm type annotation support. + + + + +## Classes +* [\danog\PhpDoc\PhpDocBuilder: PHP documentation builder.](danog/PhpDoc/PhpDocBuilder.md) + + diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 0000000..8dc065a --- /dev/null +++ b/psalm.xml @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/src/PhpDoc/ClassDoc.php b/src/PhpDoc/ClassDoc.php index f3eec02..af3b76e 100644 --- a/src/PhpDoc/ClassDoc.php +++ b/src/PhpDoc/ClassDoc.php @@ -9,6 +9,11 @@ use ReflectionClass; use ReflectionClassConstant; use ReflectionMethod; +/** + * Class documentation builder. + * + * @internal + */ class ClassDoc extends GenericDoc { /** @@ -33,7 +38,7 @@ class ClassDoc extends GenericDoc $this->name = $reflectionClass->getName(); $doc = $reflectionClass->getDocComment(); if (!$doc) { - \fprintf(STDERR, $reflectionClass->getName()." has no PHPDOC\n"); + \fprintf(STDERR, $reflectionClass->getName()." has no PHPDOC".PHP_EOL); $this->ignore = true; return; } @@ -43,7 +48,8 @@ class ClassDoc extends GenericDoc $tags = $doc->getTags(); foreach ($tags as $tag) { - if ($tag instanceof Property) { + if ($tag instanceof Property && $tag->getVariableName()) { + /** @psalm-suppress InvalidPropertyAssignmentValue */ $this->properties[$tag->getVariableName()] = [ $tag->getType(), $tag->getDescription() @@ -55,6 +61,7 @@ class ClassDoc extends GenericDoc [$varName, $description] = \explode(" ", $description, 2); $type = \str_replace('@property ', '', $type); $description ??= ''; + /** @psalm-suppress InvalidPropertyAssignmentValue */ $this->properties[$varName] = [ $type, $description @@ -101,6 +108,11 @@ class ClassDoc extends GenericDoc $this->methods = \array_filter($this->methods, fn (MethodDoc $doc): bool => !$doc->shouldIgnore()); } + /** + * Generate markdown for class. + * + * @return string + */ public function format(): string { $init = parent::format(); diff --git a/src/PhpDoc/FunctionDoc.php b/src/PhpDoc/FunctionDoc.php index 45b69d7..1bc0d5d 100644 --- a/src/PhpDoc/FunctionDoc.php +++ b/src/PhpDoc/FunctionDoc.php @@ -4,15 +4,26 @@ namespace danog\PhpDoc\PhpDoc; use ReflectionFunction; +/** + * Function documentation builder. + * + * @internal + */ class FunctionDoc extends MethodDoc { + /** + * Constructor. + * + * @param PhpDoc $builder + * @param ReflectionFunction $reflectionClass + */ public function __construct(PhpDoc $builder, ReflectionFunction $reflectionClass) { $this->builder = $builder; - $this->nameGenericDoc = $reflectionClass->getName(); + $this->name = $reflectionClass->getName(); $doc = $reflectionClass->getDocComment(); if (!$doc) { - \fprintf(STDERR, $reflectionClass->getName()." has no PHPDOC\n"); + \fprintf(STDERR, $reflectionClass->getName()." has no PHPDOC".PHP_EOL); $this->ignore = true; return; } diff --git a/src/PhpDoc/GenericDoc.php b/src/PhpDoc/GenericDoc.php index 61f653d..498909b 100644 --- a/src/PhpDoc/GenericDoc.php +++ b/src/PhpDoc/GenericDoc.php @@ -2,7 +2,6 @@ namespace danog\PhpDoc\PhpDoc; -use danog\PhpDoc\PhpDocBuilder; use phpDocumentor\Reflection\DocBlock; use phpDocumentor\Reflection\DocBlock\Description; use phpDocumentor\Reflection\DocBlock\Tags\Author; @@ -12,7 +11,14 @@ use phpDocumentor\Reflection\DocBlock\Tags\Reference\Fqsen; use phpDocumentor\Reflection\DocBlock\Tags\Reference\Url; use phpDocumentor\Reflection\DocBlock\Tags\See; use phpDocumentor\Reflection\Fqsen as ReflectionFqsen; +use ReflectionClass; +use ReflectionFunction; +/** + * Generic documentation builder. + * + * @internal + */ abstract class GenericDoc { /** @@ -59,6 +65,12 @@ abstract class GenericDoc * Fully qualified class name. */ private string $resolvedClassName; + /** + * Constructor. + * + * @param DocBlock $doc + * @param ReflectionClass|ReflectionFunction $reflectionClass + */ public function __construct(DocBlock $doc, $reflectionClass) { $empty = []; @@ -89,6 +101,11 @@ abstract class GenericDoc $this->authors = \array_unique($this->authors); } + /** + * Get see also list. + * + * @return string + */ public function seeAlso(): string { $empty = []; @@ -145,6 +162,11 @@ abstract class GenericDoc } return $seeAlso; } + /** + * Generate markdown. + * + * @return string + */ public function format(): string { $authors = ''; @@ -154,8 +176,8 @@ abstract class GenericDoc $seeAlso = $this->seeAlso(); $image = $this->builder->getImage(); $index = ''; - $count = count(explode('\\', $this->resolvedClassName)) - 1; - $index .= str_repeat($count, '../'); + $count = \count(\explode('\\', $this->resolvedClassName)) - 2; + $index .= \str_repeat('../', $count); $index .= 'index.md'; return <<builder->resolveTypeAlias($this->className, $o, $resolved); + $result = $this->builder->resolveTypeAlias($this->className, $type, $resolved); foreach ($resolved as $type) { if (PhpDoc::isScalar($type)) { continue; @@ -196,6 +224,11 @@ abstract class GenericDoc return $result; } + /** + * Whether we should not store this class. + * + * @return boolean + */ public function shouldIgnore(): bool { return $this->ignore; diff --git a/src/PhpDoc/MethodDoc.php b/src/PhpDoc/MethodDoc.php index 6b14014..e1c87f0 100644 --- a/src/PhpDoc/MethodDoc.php +++ b/src/PhpDoc/MethodDoc.php @@ -9,12 +9,23 @@ use phpDocumentor\Reflection\DocBlock\Tags\Return_; use ReflectionFunctionAbstract; use ReflectionMethod; +/** + * Method documentation builder. + * + * @internal + */ class MethodDoc extends GenericDoc { private Return_ $return; private string $psalmReturn; private array $params = []; private array $psalmParams = []; + /** + * Constructor. + * + * @param PhpDoc $phpDocBuilder + * @param ReflectionFunctionAbstract $method + */ public function __construct(PhpDoc $phpDocBuilder, ReflectionFunctionAbstract $method) { $this->builder = $phpDocBuilder; @@ -23,9 +34,9 @@ class MethodDoc extends GenericDoc if (!$doc) { $this->ignore = true; if ($method instanceof ReflectionMethod) { - \fprintf(STDERR, $method->getDeclaringClass()->getName().'::'.$method->getName().' has no PHPDOC!\n'); + \fprintf(STDERR, $method->getDeclaringClass()->getName().'::'.$method->getName().' has no PHPDOC!'.PHP_EOL); } else { - \fprintf(STDERR, $method->getName().' has no PHPDOC!\n'); + \fprintf(STDERR, $method->getName()." has no PHPDOC!".PHP_EOL); } return; } @@ -74,6 +85,11 @@ class MethodDoc extends GenericDoc } } + /** + * Get method signature. + * + * @return string + */ public function getSignature(): string { $sig = $this->name; @@ -91,6 +107,11 @@ class MethodDoc extends GenericDoc } return $sig; } + /** + * Generate markdown for method. + * + * @return string + */ public function format(): string { $sig = '### `'.$this->getSignature()."`"; diff --git a/src/PhpDoc/PhpDoc.php b/src/PhpDoc/PhpDoc.php index 9629103..6fee359 100644 --- a/src/PhpDoc/PhpDoc.php +++ b/src/PhpDoc/PhpDoc.php @@ -24,6 +24,11 @@ use phpDocumentor\Reflection\DocBlockFactory; use ReflectionClass; use ReflectionFunction; +/** + * Documentation builder. + * + * @internal + */ class PhpDoc { /** @@ -33,7 +38,7 @@ class PhpDoc /** * Scan mode. */ - private int $mode; + private int $mode = ClassFinder::ALLOW_ALL | ClassFinder::RECURSIVE_MODE; /** * Docblock factory. */ @@ -64,7 +69,7 @@ class PhpDoc /** * Project image. */ - private string $image; + private string $image = ''; /** * Use map. * @@ -162,7 +167,7 @@ class PhpDoc */ public function run(): self { - $classList = ClassFinder::getClassesInNamespace($this->namespace, $this->mode | ClassFinder::RECURSIVE_MODE); + $classList = ClassFinder::getClassesInNamespace($this->namespace, $this->mode); foreach ($classList as $class) { $this->addTypeAliases($class); } @@ -213,11 +218,11 @@ class PhpDoc $path .= '.md'; if ($class instanceof FunctionDoc) { $functions .= "* [$line]($path)\n"; - } else if ($reflectionClass->isTrait()) { + } elseif ($reflectionClass->isTrait()) { $traits .= "* [$line]($path)\n"; - } else if ($reflectionClass->isAbstract()) { + } elseif ($reflectionClass->isAbstract()) { $abstract .= "* [$line]($path)\n"; - } else if ($reflectionClass->isInterface()) { + } elseif ($reflectionClass->isInterface()) { $interfaces .= "* [$line]($path)\n"; } else { $classes .= "* [$line]($path)\n"; @@ -233,16 +238,20 @@ class PhpDoc $traits = $traits ? "## Traits\n$traits" : ''; $abstract = $abstract ? "## Abstract classes\n$abstract" : ''; $interfaces = $interfaces ? "## Interfaces\n$interfaces" : ''; - $functions = $functions ? "## Functions\n$functions" : ''; - + $classes = $classes ? "## Classes\n$classes" : ''; + + $description = explode("\n", $this->description); + $description = $description[0] ?? ''; + $image = $this->getImage(); $index = <<title - description: $this->description$image + title: $this->name + description: $description$image --- - # `$this->title` + # `$this->name` + $description $functions $interfaces @@ -259,7 +268,7 @@ class PhpDoc $handle = \fopen(self::createDir($fName), 'w+'); \fwrite($handle, $index); \fclose($handle); - + return $this; } /** @@ -301,13 +310,20 @@ class PhpDoc if (str_ends_with($name, '[]')) { return $this->resolveTypeAlias($fromClass, \substr($name, 0, -2), $resolved)."[]"; } - $name = \rtrim(\ltrim($name, '('), ')'); + if ($name[0] === '(' && $name[strlen($name) - 1] === ')') { + $name = $this->resolveTypeAlias($fromClass, substr($name, 1, -1), $resolved); + return "($name)"; + } if (\count($split = self::splitOnWithoutParenthesis('|', $name)) > 1) { foreach ($split as &$name) { $name = $this->resolveTypeAlias($fromClass, $name, $resolved); } return \implode('|', $split); } + if (str_starts_with($name, 'callable(')) { + $name = $this->resolveTypeAlias($fromClass, substr($name, 9, -1), $resolved); + return "callable($name)"; + } if (str_starts_with($name, 'array{')) { $new = ''; $split = self::splitOnWithoutParenthesis(',', \substr($name, 6, -1)); @@ -426,6 +442,7 @@ class PhpDoc { $name = $class->getName(); $fName = $this->output; + $fName .= DIRECTORY_SEPARATOR; $fName .= \str_replace('\\', DIRECTORY_SEPARATOR, $name); $fName .= '.md'; diff --git a/src/PhpDocBuilder.php b/src/PhpDocBuilder.php index 1638e72..8a2036a 100644 --- a/src/PhpDocBuilder.php +++ b/src/PhpDocBuilder.php @@ -19,7 +19,11 @@ namespace danog\PhpDoc; use danog\PhpDoc\PhpDoc\PhpDoc; +use phpDocumentor\Reflection\DocBlock\Tags\Author; +/** + * PHP documentation builder. + */ final class PhpDocBuilder { /**