Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
105 commits
Select commit Hold shift + click to select a range
324ea20
Support PHPStan/Psalm syntax
mspirkov Nov 7, 2025
34b2350
support `array{}` and `object{}`
mspirkov Nov 7, 2025
c5e6916
Closure and callable test
mspirkov Nov 7, 2025
d6bb26b
Fix `Error: Call to undefined method phpDocumentor\Reflection\DocBloc…
mspirkov Nov 7, 2025
b8f38ed
changelog
mspirkov Nov 7, 2025
c087bf8
Merge branch 'fix-get-parameters' into support-phpstan-syntax
mspirkov Nov 7, 2025
ba72721
use snapshots
mspirkov Nov 7, 2025
96d2325
check files count
mspirkov Nov 7, 2025
80414a8
check files count
mspirkov Nov 7, 2025
d191e7e
remove `value-of`
mspirkov Nov 7, 2025
497944b
refactor
mspirkov Nov 7, 2025
caa309e
refactor
mspirkov Nov 7, 2025
b01aab4
refactor
mspirkov Nov 7, 2025
57229dd
types
mspirkov Nov 7, 2025
4f69eab
style
mspirkov Nov 7, 2025
4b81659
remove support 7.2, 7.3
mspirkov Nov 7, 2025
6f9d4ce
fix deprecation errors for 8.1
mspirkov Nov 7, 2025
20924bb
remove dead code + use private constants
mspirkov Nov 9, 2025
d61627a
urls to constants
mspirkov Nov 9, 2025
e0c4cb4
phpstan doc links
mspirkov Nov 9, 2025
18eacaf
common generics mechanism
mspirkov Nov 9, 2025
300ad87
fix + refactor
mspirkov Nov 9, 2025
5c8dd46
more tests
mspirkov Nov 10, 2025
6460dc4
clean tests
mspirkov Nov 10, 2025
35b6dbf
support templates
mspirkov Nov 10, 2025
9a1cdaf
pass parent docs in childs
mspirkov Nov 10, 2025
dfdad13
use fqcn
mspirkov Nov 10, 2025
8ff4abf
fix return and param types, when no docblock
mspirkov Nov 10, 2025
410cc7c
support conditional types in methods
mspirkov Nov 10, 2025
f655393
support conditional types in `@method` annotation
mspirkov Nov 10, 2025
003c65e
clean snapshots
mspirkov Nov 10, 2025
a976e1a
Fix PHP8.1 `trim` deprecation error
mspirkov Nov 10, 2025
7ac42f5
Merge branch 'fix-trim-deprecations' into support-phpstan-syntax
mspirkov Nov 10, 2025
2709bea
Merge branch 'master' into support-phpstan-syntax
mspirkov Nov 11, 2025
5d259b5
improve TypeHelper
mspirkov Nov 13, 2025
154f834
support array shapes and object shapes, collecting errors
mspirkov Nov 13, 2025
c62ee76
support PHPStan/Psalm types without imports
mspirkov Nov 13, 2025
33eb9da
Merge branch 'master' into support-phpstan-syntax
mspirkov Nov 15, 2025
3a3f412
Fix deprecation errors `The expression value will become of type Expr…
mspirkov Nov 15, 2025
105d5fb
self review
mspirkov Nov 15, 2025
2f02107
style
mspirkov Nov 15, 2025
bd7bc0f
Merge branch 'fix-deprecations' into support-phpstan-syntax
mspirkov Nov 15, 2025
210de7a
refactor TypeAnalyzer
mspirkov Nov 15, 2025
277a434
support int range
mspirkov Nov 15, 2025
f879bdc
remove templates
mspirkov Nov 15, 2025
300e59e
forcePhpStanLink
mspirkov Nov 15, 2025
a7e9a59
support intersection types + one error on one type in TypeAnalyzer
mspirkov Nov 15, 2025
cff3c5a
use `TypeAnalyzer` instead of `preg_match`
mspirkov Nov 15, 2025
d0129bd
fix and refactor conditional types processing
mspirkov Nov 15, 2025
9cdb82a
add tests for mixed in `@method`
mspirkov Nov 15, 2025
ddb41f7
support all PHPStan types + refactor
mspirkov Nov 16, 2025
7013f39
refactor PhpDocTagFactory
mspirkov Nov 16, 2025
6be5e00
refactor `PhpDocTagParser` and `TypeAnalyzer`
mspirkov Nov 17, 2025
630c766
update PHPSTAN_TYPES_DOC_LINKS
mspirkov Nov 17, 2025
4ef0688
refactor
mspirkov Nov 17, 2025
6ad50f0
refactor + fix + tests
mspirkov Nov 17, 2025
ad3fdf6
fix phpdoc in BaseDoc
mspirkov Nov 17, 2025
f21bacf
fix phpdoc in BaseDoc
mspirkov Nov 17, 2025
5f295ca
author in PseudoTypeDoc
mspirkov Nov 17, 2025
ab7bb4b
Exclude commits with CS changes from Git Blame
mspirkov Nov 17, 2025
b2f96e4
self review
mspirkov Nov 17, 2025
b1282c9
Merge branch 'fix-git-blame' into support-phpstan-syntax
mspirkov Nov 17, 2025
6aef355
Merge branch 'master' into support-phpstan-syntax
mspirkov Nov 17, 2025
b6e5d27
support PHPStan/Psalm type imports
mspirkov Nov 17, 2025
cf3d98f
self review
mspirkov Nov 17, 2025
b29fb05
fix tests
mspirkov Nov 18, 2025
8b43dd0
remove PHP 8.5 from checks
mspirkov Nov 18, 2025
1b3829c
processing unions
mspirkov Nov 18, 2025
3196c96
add tests
mspirkov Nov 18, 2025
67fba4d
add tests
mspirkov Nov 18, 2025
b3de48c
array shapes without keys
mspirkov Nov 18, 2025
e60abcc
apply fix from phpDocumentor/TypeResolver
mspirkov Nov 18, 2025
f5abe0a
refactor
mspirkov Nov 18, 2025
8d01228
restoring all types from `Mixed_` + refactoring + optimization
mspirkov Nov 18, 2025
99e45d5
support offset types
mspirkov Nov 18, 2025
8be9c10
Use latest TypeResolver version
mspirkov Nov 21, 2025
d59a244
Use TypeResolver Value Objects instead of strings + add tests
mspirkov Nov 21, 2025
ab5af4a
Add links to shapes
mspirkov Nov 21, 2025
672f0ee
refactor
mspirkov Nov 21, 2025
d3eb815
refactor
mspirkov Nov 22, 2025
cf1bd47
Add links for `$this` and replace `static` type with current type
mspirkov Nov 22, 2025
b635bb7
Remove useless condition
mspirkov Nov 22, 2025
0d31514
Fix naming + add PHPDoc annotations
mspirkov Nov 22, 2025
b46d6e1
Use `phpdocumentor/type-resolver` dev version
mspirkov Nov 22, 2025
2a9ca83
Refactor
mspirkov Nov 23, 2025
487444b
Merge branch 'master' into support-phpstan-syntax
mspirkov Nov 23, 2025
860b47e
self review
mspirkov Nov 23, 2025
1e5dddd
Fix + refactor
mspirkov Nov 23, 2025
50fffe5
Refactor
mspirkov Nov 23, 2025
85ef2fd
Refactor
mspirkov Nov 23, 2025
6021983
Use latest TypeResolver
mspirkov Nov 25, 2025
eded6ba
Merge branch 'master' into support-phpstan-syntax
mspirkov Nov 25, 2025
17c8c8c
fix phpstan errors
mspirkov Nov 25, 2025
d708b26
Refactor
mspirkov Nov 25, 2025
68d553d
Fix CS
mspirkov Nov 25, 2025
87768c3
Refactor
mspirkov Nov 25, 2025
692b12d
Merge branch 'master' into support-phpstan-syntax
mspirkov Nov 26, 2025
2a2ecd7
Fix `static[]` processing
mspirkov Nov 26, 2025
311cc2b
Fix `types.json` generation
mspirkov Nov 26, 2025
a423688
self review
mspirkov Nov 26, 2025
b94cc23
use latest type resolver
mspirkov Nov 26, 2025
4c43b38
fix tests
mspirkov Nov 26, 2025
e2f8f48
fix tests
mspirkov Nov 26, 2025
73fdbb7
Replace `static` everywhere
mspirkov Nov 26, 2025
8e1f1b6
changelog
mspirkov Nov 26, 2025
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
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
Yii Framework 2 apidoc extension Change Log
===========================================

3.0.9 under development
4.0.0 under development
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are quite a few changes that break backward compatibility, so I suggest changing it to 4.0.0.

-----------------------

- Bug #338: Fix deprecation error `Using null as an array offset is deprecated, use an empty string instead` (mspirkov)
- Enh #337: Log invalid tags (mspirkov)
- Enh #339: Add support for PHPStan/Psalm syntax (mspirkov)
- Enh #339: Add support for intersection types (mspirkov)
- Bug #339: Fix the mechanism for replacing `static` with FQCN (mspirkov)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This mechanism already existed, but it didn't work well. You can view examples in the demo PR.
For example: https://github.com/mspirkov/yiiframework.com/pull/1/files#diff-8a9950c5cd9d026539178094cdc0d0e9898d121f72538c6489a5a5a31c0f8190

- Bug #339: Fix processing of multidimensional arrays (mspirkov)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Multidimensional arrays were processed, but no links were created. You can view examples in the demo PR.
For example: https://github.com/mspirkov/yiiframework.com/pull/1/files#diff-7acb57053cdcc5e391d9abe74d134e2514b83a5ec83afb891afac1d936298584



3.0.8 November 24, 2025
Expand Down
1 change: 0 additions & 1 deletion commands/GuideController.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
use yii\apidoc\renderers\GuideRenderer;
use yii\helpers\Console;
use yii\helpers\FileHelper;
use Yii;
use yii\helpers\Json;

/**
Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
"php": "^7.4 || ^8.0",
"yiisoft/yii2": "~2.0.16",
"yiisoft/yii2-bootstrap": "~2.0.0",
"phpdocumentor/reflection": "^5.1.0 || ^6.0.0",
"phpdocumentor/reflection": "^5.3.0 || ^6.0.0",
"phpdocumentor/type-resolver": "^1.11",
"nikic/php-parser": "^4.0 || ^5.0",
"cebe/js-search": "~0.9.0",
"cebe/markdown": "^1.0",
Expand Down
41 changes: 30 additions & 11 deletions helpers/TypeHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

namespace yii\apidoc\helpers;

use phpDocumentor\Reflection\PseudoTypes\Conditional;
use phpDocumentor\Reflection\PseudoTypes\ConditionalForParameter;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\Types\AggregatedType;

Expand All @@ -18,24 +20,41 @@
final class TypeHelper
{
/**
* @return string[]
* @return Type[]
*/
public static function splitType(?Type $type): array
public static function getTypesByAggregatedType(AggregatedType $compound): array
{
if ($type === null) {
return [];
$types = [];
foreach ($compound as $type) {
$types[] = $type;
}

// TODO: Don't split the Intersection
if (!$type instanceof AggregatedType) {
return [(string) $type];
}
return $types;
}

/**
* @param Conditional|ConditionalForParameter $type
* @return Type[] Possible unique types.
*/
public static function getPossibleTypesByConditionalType(Type $type): array
{
$types = [];
foreach ($type as $childType) {
$types[] = (string) $childType;

foreach ([$type->getIf(), $type->getElse()] as $innerType) {
if ($innerType instanceof Conditional || $innerType instanceof ConditionalForParameter) {
$types = array_merge($types, self::getPossibleTypesByConditionalType($innerType));
} elseif ($innerType instanceof AggregatedType) {
$types = array_merge($types, self::getTypesByAggregatedType($innerType));
} else {
$types[] = $innerType;
}
}

return $types;
$uniqueTypes = [];
foreach ($types as $innerType) {
$uniqueTypes[(string) $innerType] = $innerType;
}

return array_values($uniqueTypes);
}
}
101 changes: 97 additions & 4 deletions models/BaseDoc.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,15 @@
use phpDocumentor\Reflection\DocBlock\Tags\Generic;
use phpDocumentor\Reflection\DocBlock\Tags\InvalidTag;
use phpDocumentor\Reflection\DocBlock\Tags\Since;
use phpDocumentor\Reflection\DocBlock\Tags\Template;
use phpDocumentor\Reflection\FqsenResolver;
use phpDocumentor\Reflection\Php\Class_;
use phpDocumentor\Reflection\Php\Constant;
use phpDocumentor\Reflection\Php\Interface_;
use phpDocumentor\Reflection\Php\Method;
use phpDocumentor\Reflection\Php\Property;
use phpDocumentor\Reflection\Php\Trait_;
use phpDocumentor\Reflection\TypeResolver;
use yii\base\BaseObject;
use yii\helpers\StringHelper;

Expand All @@ -32,14 +35,26 @@
*/
class BaseDoc extends BaseObject
{
private const PHPSTAN_TYPE_ANNOTATION_NAME = 'phpstan-type';
private const PSALM_TYPE_ANNOTATION_NAME = 'psalm-type';

private const PHPSTAN_IMPORT_TYPE_ANNOTATION_NAME = 'phpstan-import-type';
private const PSALM_IMPORT_TYPE_ANNOTATION_NAME = 'psalm-import-type';

private const INHERITDOC_TAG_NAME = 'inheritdoc';
private const TODO_TAG_NAME = 'todo';

/**
* @var \phpDocumentor\Reflection\Types\Context|null
*/
public $phpDocContext;
/**
* @var string|null
*/
public $name;
/**
* @var string|null
*/
public $fullName;
public $sourceFile;
public $startLine;
Expand All @@ -64,6 +79,30 @@ class BaseDoc extends BaseObject
* @var Generic[]
*/
public $todos = [];
/**
* @var array<string, Template>
*/
public $templates = [];
/**
* @var self|null
*/
public $parent = null;
/**
* @var array<string, PseudoTypeDoc>
*/
public array $phpStanTypes = [];
/**
* @var array<string, PseudoTypeDoc>
*/
public array $psalmTypes = [];
/**
* @var array<string, PseudoTypeImportDoc>
*/
public array $phpStanTypeImports = [];
/**
* @var array<string, PseudoTypeImportDoc>
*/
public array $psalmTypeImports = [];

/**
* Checks if doc has tag of a given name
Expand Down Expand Up @@ -126,18 +165,24 @@ public function getPackageName()
}

/**
* @param self|null $parent
* @param Class_|Method|Trait_|Interface_|Property|Constant|null $reflector
* @param Context|null $context
* @param array $config
*/
public function __construct($reflector = null, $context = null, $config = [])
public function __construct($parent = null, $reflector = null, $context = null, $config = [])
{
parent::__construct($config);

$this->parent = $parent;

if ($reflector === null) {
return;
}

$fqsenResolver = new FqsenResolver();
$typeResolver = new TypeResolver($fqsenResolver);

// base properties
$this->fullName = trim((string) $reflector->getFqsen(), '\\()');

Expand All @@ -160,7 +205,7 @@ public function __construct($reflector = null, $context = null, $config = [])
return;
}

$this->shortDescription = StringHelper::mb_ucfirst($docBlock->getSummary());;
$this->shortDescription = StringHelper::mb_ucfirst($docBlock->getSummary());
if (empty($this->shortDescription) && !($this instanceof PropertyDoc) && $context !== null && !$docBlock->getTagsByName(self::INHERITDOC_TAG_NAME)) {
$context->warnings[] = [
'line' => $this->startLine,
Expand Down Expand Up @@ -192,9 +237,57 @@ public function __construct($reflector = null, $context = null, $config = [])
$this->deprecatedSince = $tag->getVersion();
$this->deprecatedReason = (string) $tag->getDescription();
unset($this->tags[$i]);
} elseif ($tag instanceof Generic && $tag->getName() === self::TODO_TAG_NAME) {
$this->todos[] = $tag;
} elseif ($tag instanceof Template) {
$fqsen = $fqsenResolver->resolve($tag->getTemplateName(), $this->phpDocContext);
$this->templates[(string) $fqsen] = $tag;
unset($this->tags[$i]);
} elseif ($tag instanceof Generic) {
if ($tag->getName() === self::TODO_TAG_NAME) {
$this->todos[] = $tag;
unset($this->tags[$i]);
} elseif ($tag->getName() === self::PHPSTAN_TYPE_ANNOTATION_NAME) {
$tagData = explode(' ', trim($tag->getDescription()), 2);
$phpStanType = new PseudoTypeDoc(
PseudoTypeDoc::TYPE_PHPSTAN,
$this,
trim($tagData[0]),
$typeResolver->resolve(trim($tagData[1]), $this->phpDocContext)
);
$fqsen = $fqsenResolver->resolve($phpStanType->name, $this->phpDocContext);
$this->phpStanTypes[(string) $fqsen] = $phpStanType;
unset($this->tags[$i]);
} elseif ($tag->getName() === self::PSALM_TYPE_ANNOTATION_NAME) {
$tagData = explode('=', trim($tag->getDescription()), 2);
$psalmType = new PseudoTypeDoc(
PseudoTypeDoc::TYPE_PSALM,
$this,
trim($tagData[0]),
$typeResolver->resolve(trim($tagData[1]), $this->phpDocContext)
);
$fqsen = $fqsenResolver->resolve($psalmType->name, $this->phpDocContext);
$this->psalmTypes[(string) $fqsen] = $psalmType;
unset($this->tags[$i]);
} elseif ($tag->getName() === self::PHPSTAN_IMPORT_TYPE_ANNOTATION_NAME) {
$tagData = explode(' from ', trim($tag->getDescription()), 2);
$phpStanTypeImport = new PseudoTypeImportDoc(
PseudoTypeImportDoc::TYPE_PHPSTAN,
trim($tagData[0]),
$fqsenResolver->resolve(trim($tagData[1]), $this->phpDocContext)
);
$fqsen = $fqsenResolver->resolve($phpStanTypeImport->typeName, $this->phpDocContext);
$this->phpStanTypeImports[(string) $fqsen] = $phpStanTypeImport;
unset($this->tags[$i]);
} elseif ($tag->getName() === self::PSALM_IMPORT_TYPE_ANNOTATION_NAME) {
$tagData = explode(' from ', trim($tag->getDescription()), 2);
$psalmTypeImport = new PseudoTypeImportDoc(
PseudoTypeImportDoc::TYPE_PSALM,
trim($tagData[0]),
$fqsenResolver->resolve(trim($tagData[1]), $this->phpDocContext)
);
$fqsen = $fqsenResolver->resolve($psalmTypeImport->typeName, $this->phpDocContext);
$this->psalmTypeImports[(string) $fqsen] = $psalmTypeImport;
unset($this->tags[$i]);
}
} elseif ($tag instanceof InvalidTag && $context !== null) {
$exception = $tag->getException();
$message = 'Invalid tag: ' . $tag->render() . '.';
Expand Down
10 changes: 7 additions & 3 deletions models/ClassDoc.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

namespace yii\apidoc\models;

use phpDocumentor\Reflection\Php\Class_;

/**
* Represents API documentation information for a `class`.
*
Expand Down Expand Up @@ -91,7 +93,9 @@ public function getNativeEvents()
}

/**
* @inheritdoc
* @param Class_|null $reflector
* @param Context|null $context
* @param array $config
*/
public function __construct($reflector = null, $context = null, $config = [])
{
Expand All @@ -118,11 +122,11 @@ public function __construct($reflector = null, $context = null, $config = [])
foreach ($reflector->getConstants() as $constantReflector) {
$docBlock = $constantReflector->getDocBlock();
if ($docBlock !== null && count($docBlock->getTagsByName('event')) > 0) {
$event = new EventDoc($constantReflector, null, [], $docBlock);
$event = new EventDoc($this, $constantReflector, null, [], $docBlock);
$event->definedBy = $this->name;
$this->events[$event->name] = $event;
} else {
$constant = new ConstDoc($constantReflector);
$constant = new ConstDoc($this, $constantReflector);
$constant->definedBy = $this->name;
$this->constants[$constant->name] = $constant;
}
Expand Down
8 changes: 6 additions & 2 deletions models/ConstDoc.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
*/
class ConstDoc extends BaseDoc
{
/**
* @var string|null
*/
public $definedBy;
/**
* @var string|null
Expand All @@ -26,14 +29,15 @@ class ConstDoc extends BaseDoc


/**
* @param ClassDoc|TraitDoc $parent
* @param Constant|null $reflector
* @param Context|null $context
* @param array $config
* @param DocBlock|null $docBlock
*/
public function __construct($reflector = null, $context = null, $config = [], $docBlock = null)
public function __construct($parent, $reflector = null, $context = null, $config = [], $docBlock = null)
{
parent::__construct($reflector, $context, $config);
parent::__construct($parent, $reflector, $context, $config);

if ($reflector === null) {
return;
Expand Down
Loading
Loading