1
0
mirror of https://github.com/danog/phpdoc.git synced 2024-11-26 12:04:47 +01:00
This commit is contained in:
Daniil Gentili 2020-10-15 18:09:30 +02:00
parent 864295ff3f
commit 78cb0ae5da
Signed by: danog
GPG Key ID: 8C1BE3B34B230CA7
11 changed files with 281 additions and 25 deletions

1
CNAME Normal file
View File

@ -0,0 +1 @@
phpdoc.daniil.it

View File

@ -5,13 +5,18 @@ use danog\PhpDoc\PhpDocBuilder;
if ($argc < 2) { if ($argc < 2) {
$me = $argv[0]; $me = $argv[0];
fprintf(stderr, "Usage: $me filePath [namespace]\n"); fprintf(STDERR, "Usage: $me filePath [namespace]".PHP_EOL);
die(1); die(1);
} }
if (!class_exists(PhpDocBuilder::class)) {
require 'vendor/autoload.php';
}
$path = $argv[1]; $path = $argv[1];
$namespace = $argv[2] ?? ''; $namespace = $argv[2] ?? '';
PhpDocBuilder::fromNamespace($namespace) PhpDocBuilder::fromNamespace($namespace)
->setOutput($path) ->setOutput($path)
->run(); ->run();

View File

@ -0,0 +1,122 @@
---
title: danog\PhpDoc\PhpDocBuilder: PHP documentation builder.
description:
---
# `danog\PhpDoc\PhpDocBuilder`
[Back to index](../../index.md)
> Author: Daniil Gentili <daniil@daniil.it>
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.

15
docs/index.md Normal file
View File

@ -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)

15
psalm.xml Normal file
View File

@ -0,0 +1,15 @@
<?xml version="1.0"?>
<psalm
errorLevel="5"
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
>
<projectFiles>
<directory name="src" />
<ignoreFiles>
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
</psalm>

View File

@ -9,6 +9,11 @@ use ReflectionClass;
use ReflectionClassConstant; use ReflectionClassConstant;
use ReflectionMethod; use ReflectionMethod;
/**
* Class documentation builder.
*
* @internal
*/
class ClassDoc extends GenericDoc class ClassDoc extends GenericDoc
{ {
/** /**
@ -33,7 +38,7 @@ class ClassDoc extends GenericDoc
$this->name = $reflectionClass->getName(); $this->name = $reflectionClass->getName();
$doc = $reflectionClass->getDocComment(); $doc = $reflectionClass->getDocComment();
if (!$doc) { if (!$doc) {
\fprintf(STDERR, $reflectionClass->getName()." has no PHPDOC\n"); \fprintf(STDERR, $reflectionClass->getName()." has no PHPDOC".PHP_EOL);
$this->ignore = true; $this->ignore = true;
return; return;
} }
@ -43,7 +48,8 @@ class ClassDoc extends GenericDoc
$tags = $doc->getTags(); $tags = $doc->getTags();
foreach ($tags as $tag) { foreach ($tags as $tag) {
if ($tag instanceof Property) { if ($tag instanceof Property && $tag->getVariableName()) {
/** @psalm-suppress InvalidPropertyAssignmentValue */
$this->properties[$tag->getVariableName()] = [ $this->properties[$tag->getVariableName()] = [
$tag->getType(), $tag->getType(),
$tag->getDescription() $tag->getDescription()
@ -55,6 +61,7 @@ class ClassDoc extends GenericDoc
[$varName, $description] = \explode(" ", $description, 2); [$varName, $description] = \explode(" ", $description, 2);
$type = \str_replace('@property ', '', $type); $type = \str_replace('@property ', '', $type);
$description ??= ''; $description ??= '';
/** @psalm-suppress InvalidPropertyAssignmentValue */
$this->properties[$varName] = [ $this->properties[$varName] = [
$type, $type,
$description $description
@ -101,6 +108,11 @@ class ClassDoc extends GenericDoc
$this->methods = \array_filter($this->methods, fn (MethodDoc $doc): bool => !$doc->shouldIgnore()); $this->methods = \array_filter($this->methods, fn (MethodDoc $doc): bool => !$doc->shouldIgnore());
} }
/**
* Generate markdown for class.
*
* @return string
*/
public function format(): string public function format(): string
{ {
$init = parent::format(); $init = parent::format();

View File

@ -4,15 +4,26 @@ namespace danog\PhpDoc\PhpDoc;
use ReflectionFunction; use ReflectionFunction;
/**
* Function documentation builder.
*
* @internal
*/
class FunctionDoc extends MethodDoc class FunctionDoc extends MethodDoc
{ {
/**
* Constructor.
*
* @param PhpDoc $builder
* @param ReflectionFunction $reflectionClass
*/
public function __construct(PhpDoc $builder, ReflectionFunction $reflectionClass) public function __construct(PhpDoc $builder, ReflectionFunction $reflectionClass)
{ {
$this->builder = $builder; $this->builder = $builder;
$this->nameGenericDoc = $reflectionClass->getName(); $this->name = $reflectionClass->getName();
$doc = $reflectionClass->getDocComment(); $doc = $reflectionClass->getDocComment();
if (!$doc) { if (!$doc) {
\fprintf(STDERR, $reflectionClass->getName()." has no PHPDOC\n"); \fprintf(STDERR, $reflectionClass->getName()." has no PHPDOC".PHP_EOL);
$this->ignore = true; $this->ignore = true;
return; return;
} }

View File

@ -2,7 +2,6 @@
namespace danog\PhpDoc\PhpDoc; namespace danog\PhpDoc\PhpDoc;
use danog\PhpDoc\PhpDocBuilder;
use phpDocumentor\Reflection\DocBlock; use phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\DocBlock\Description; use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\Tags\Author; 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\Reference\Url;
use phpDocumentor\Reflection\DocBlock\Tags\See; use phpDocumentor\Reflection\DocBlock\Tags\See;
use phpDocumentor\Reflection\Fqsen as ReflectionFqsen; use phpDocumentor\Reflection\Fqsen as ReflectionFqsen;
use ReflectionClass;
use ReflectionFunction;
/**
* Generic documentation builder.
*
* @internal
*/
abstract class GenericDoc abstract class GenericDoc
{ {
/** /**
@ -59,6 +65,12 @@ abstract class GenericDoc
* Fully qualified class name. * Fully qualified class name.
*/ */
private string $resolvedClassName; private string $resolvedClassName;
/**
* Constructor.
*
* @param DocBlock $doc
* @param ReflectionClass|ReflectionFunction $reflectionClass
*/
public function __construct(DocBlock $doc, $reflectionClass) public function __construct(DocBlock $doc, $reflectionClass)
{ {
$empty = []; $empty = [];
@ -89,6 +101,11 @@ abstract class GenericDoc
$this->authors = \array_unique($this->authors); $this->authors = \array_unique($this->authors);
} }
/**
* Get see also list.
*
* @return string
*/
public function seeAlso(): string public function seeAlso(): string
{ {
$empty = []; $empty = [];
@ -145,6 +162,11 @@ abstract class GenericDoc
} }
return $seeAlso; return $seeAlso;
} }
/**
* Generate markdown.
*
* @return string
*/
public function format(): string public function format(): string
{ {
$authors = ''; $authors = '';
@ -154,8 +176,8 @@ abstract class GenericDoc
$seeAlso = $this->seeAlso(); $seeAlso = $this->seeAlso();
$image = $this->builder->getImage(); $image = $this->builder->getImage();
$index = ''; $index = '';
$count = count(explode('\\', $this->resolvedClassName)) - 1; $count = \count(\explode('\\', $this->resolvedClassName)) - 2;
$index .= str_repeat($count, '../'); $index .= \str_repeat('../', $count);
$index .= 'index.md'; $index .= 'index.md';
return <<<EOF return <<<EOF
--- ---
@ -174,10 +196,16 @@ abstract class GenericDoc
$seeAlso $seeAlso
EOF; EOF;
} }
public function resolveTypeAlias(string $o): string /**
* Resolve type alias.
*
* @param string $type Type
* @return string
*/
public function resolveTypeAlias(string $type): string
{ {
$resolved = []; $resolved = [];
$result = $this->builder->resolveTypeAlias($this->className, $o, $resolved); $result = $this->builder->resolveTypeAlias($this->className, $type, $resolved);
foreach ($resolved as $type) { foreach ($resolved as $type) {
if (PhpDoc::isScalar($type)) { if (PhpDoc::isScalar($type)) {
continue; continue;
@ -196,6 +224,11 @@ abstract class GenericDoc
return $result; return $result;
} }
/**
* Whether we should not store this class.
*
* @return boolean
*/
public function shouldIgnore(): bool public function shouldIgnore(): bool
{ {
return $this->ignore; return $this->ignore;

View File

@ -9,12 +9,23 @@ use phpDocumentor\Reflection\DocBlock\Tags\Return_;
use ReflectionFunctionAbstract; use ReflectionFunctionAbstract;
use ReflectionMethod; use ReflectionMethod;
/**
* Method documentation builder.
*
* @internal
*/
class MethodDoc extends GenericDoc class MethodDoc extends GenericDoc
{ {
private Return_ $return; private Return_ $return;
private string $psalmReturn; private string $psalmReturn;
private array $params = []; private array $params = [];
private array $psalmParams = []; private array $psalmParams = [];
/**
* Constructor.
*
* @param PhpDoc $phpDocBuilder
* @param ReflectionFunctionAbstract $method
*/
public function __construct(PhpDoc $phpDocBuilder, ReflectionFunctionAbstract $method) public function __construct(PhpDoc $phpDocBuilder, ReflectionFunctionAbstract $method)
{ {
$this->builder = $phpDocBuilder; $this->builder = $phpDocBuilder;
@ -23,9 +34,9 @@ class MethodDoc extends GenericDoc
if (!$doc) { if (!$doc) {
$this->ignore = true; $this->ignore = true;
if ($method instanceof ReflectionMethod) { 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 { } else {
\fprintf(STDERR, $method->getName().' has no PHPDOC!\n'); \fprintf(STDERR, $method->getName()." has no PHPDOC!".PHP_EOL);
} }
return; return;
} }
@ -74,6 +85,11 @@ class MethodDoc extends GenericDoc
} }
} }
/**
* Get method signature.
*
* @return string
*/
public function getSignature(): string public function getSignature(): string
{ {
$sig = $this->name; $sig = $this->name;
@ -91,6 +107,11 @@ class MethodDoc extends GenericDoc
} }
return $sig; return $sig;
} }
/**
* Generate markdown for method.
*
* @return string
*/
public function format(): string public function format(): string
{ {
$sig = '### `'.$this->getSignature()."`"; $sig = '### `'.$this->getSignature()."`";

View File

@ -24,6 +24,11 @@ use phpDocumentor\Reflection\DocBlockFactory;
use ReflectionClass; use ReflectionClass;
use ReflectionFunction; use ReflectionFunction;
/**
* Documentation builder.
*
* @internal
*/
class PhpDoc class PhpDoc
{ {
/** /**
@ -33,7 +38,7 @@ class PhpDoc
/** /**
* Scan mode. * Scan mode.
*/ */
private int $mode; private int $mode = ClassFinder::ALLOW_ALL | ClassFinder::RECURSIVE_MODE;
/** /**
* Docblock factory. * Docblock factory.
*/ */
@ -64,7 +69,7 @@ class PhpDoc
/** /**
* Project image. * Project image.
*/ */
private string $image; private string $image = '';
/** /**
* Use map. * Use map.
* *
@ -162,7 +167,7 @@ class PhpDoc
*/ */
public function run(): self 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) { foreach ($classList as $class) {
$this->addTypeAliases($class); $this->addTypeAliases($class);
} }
@ -213,11 +218,11 @@ class PhpDoc
$path .= '.md'; $path .= '.md';
if ($class instanceof FunctionDoc) { if ($class instanceof FunctionDoc) {
$functions .= "* [$line]($path)\n"; $functions .= "* [$line]($path)\n";
} else if ($reflectionClass->isTrait()) { } elseif ($reflectionClass->isTrait()) {
$traits .= "* [$line]($path)\n"; $traits .= "* [$line]($path)\n";
} else if ($reflectionClass->isAbstract()) { } elseif ($reflectionClass->isAbstract()) {
$abstract .= "* [$line]($path)\n"; $abstract .= "* [$line]($path)\n";
} else if ($reflectionClass->isInterface()) { } elseif ($reflectionClass->isInterface()) {
$interfaces .= "* [$line]($path)\n"; $interfaces .= "* [$line]($path)\n";
} else { } else {
$classes .= "* [$line]($path)\n"; $classes .= "* [$line]($path)\n";
@ -233,16 +238,20 @@ class PhpDoc
$traits = $traits ? "## Traits\n$traits" : ''; $traits = $traits ? "## Traits\n$traits" : '';
$abstract = $abstract ? "## Abstract classes\n$abstract" : ''; $abstract = $abstract ? "## Abstract classes\n$abstract" : '';
$interfaces = $interfaces ? "## Interfaces\n$interfaces" : ''; $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(); $image = $this->getImage();
$index = <<<EOF $index = <<<EOF
--- ---
title: $this->title title: $this->name
description: $this->description$image description: $description$image
--- ---
# `$this->title` # `$this->name`
$description
$functions $functions
$interfaces $interfaces
@ -259,7 +268,7 @@ class PhpDoc
$handle = \fopen(self::createDir($fName), 'w+'); $handle = \fopen(self::createDir($fName), 'w+');
\fwrite($handle, $index); \fwrite($handle, $index);
\fclose($handle); \fclose($handle);
return $this; return $this;
} }
/** /**
@ -301,13 +310,20 @@ class PhpDoc
if (str_ends_with($name, '[]')) { if (str_ends_with($name, '[]')) {
return $this->resolveTypeAlias($fromClass, \substr($name, 0, -2), $resolved)."[]"; 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) { if (\count($split = self::splitOnWithoutParenthesis('|', $name)) > 1) {
foreach ($split as &$name) { foreach ($split as &$name) {
$name = $this->resolveTypeAlias($fromClass, $name, $resolved); $name = $this->resolveTypeAlias($fromClass, $name, $resolved);
} }
return \implode('|', $split); 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{')) { if (str_starts_with($name, 'array{')) {
$new = ''; $new = '';
$split = self::splitOnWithoutParenthesis(',', \substr($name, 6, -1)); $split = self::splitOnWithoutParenthesis(',', \substr($name, 6, -1));
@ -426,6 +442,7 @@ class PhpDoc
{ {
$name = $class->getName(); $name = $class->getName();
$fName = $this->output; $fName = $this->output;
$fName .= DIRECTORY_SEPARATOR;
$fName .= \str_replace('\\', DIRECTORY_SEPARATOR, $name); $fName .= \str_replace('\\', DIRECTORY_SEPARATOR, $name);
$fName .= '.md'; $fName .= '.md';

View File

@ -19,7 +19,11 @@
namespace danog\PhpDoc; namespace danog\PhpDoc;
use danog\PhpDoc\PhpDoc\PhpDoc; use danog\PhpDoc\PhpDoc\PhpDoc;
use phpDocumentor\Reflection\DocBlock\Tags\Author;
/**
* PHP documentation builder.
*/
final class PhpDocBuilder final class PhpDocBuilder
{ {
/** /**