Merge pull request #431 from phpDocumentor/phpstan-improvements

More phpstan checks for better code
This commit is contained in:
Jaap van Otterdijk
2026-01-04 16:27:49 +01:00
committed by GitHub
40 changed files with 693 additions and 199 deletions
+2 -1
View File
@@ -29,7 +29,8 @@
"phpstan/phpstan-mockery": "^1.1",
"phpstan/extension-installer": "^1.1",
"phpstan/phpstan-webmozart-assert": "^1.2",
"psalm/phar": "^5.26"
"psalm/phar": "^5.26",
"shipmonk/dead-code-detector": "^0.5.1"
},
"autoload": {
"psr-4": {
Generated
+70 -1
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "69aa0af5bad8d21df103f96a6e42e6dc",
"content-hash": "2af32665428eb750a10f182d03577049",
"packages": [
{
"name": "doctrine/deprecations",
@@ -2391,6 +2391,75 @@
],
"time": "2020-09-28T06:39:44+00:00"
},
{
"name": "shipmonk/dead-code-detector",
"version": "0.5.1",
"source": {
"type": "git",
"url": "https://github.com/shipmonk-rnd/dead-code-detector.git",
"reference": "bcfa0a0ca5c2610422a141d6430727738dbcda36"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/shipmonk-rnd/dead-code-detector/zipball/bcfa0a0ca5c2610422a141d6430727738dbcda36",
"reference": "bcfa0a0ca5c2610422a141d6430727738dbcda36",
"shasum": ""
},
"require": {
"php": "^7.4 || ^8.0",
"phpstan/phpstan": "^1.11.7"
},
"require-dev": {
"doctrine/orm": "^2.19 || ^3.0",
"editorconfig-checker/editorconfig-checker": "^10.3.0",
"ergebnis/composer-normalize": "^2.28",
"nette/application": "^3.1",
"nette/component-model": "^3.0",
"nette/utils": "^3.0 || ^4.0",
"nikic/php-parser": "^4.19",
"phpstan/phpstan-phpunit": "^1.1.1",
"phpstan/phpstan-strict-rules": "^1.2.3",
"phpstan/phpstan-symfony": "^1.4",
"phpunit/phpunit": "^9.5.20",
"shipmonk/composer-dependency-analyser": "^1.6",
"shipmonk/name-collision-detector": "^2.0.0",
"shipmonk/phpstan-rules": "^3.1",
"slevomat/coding-standard": "^8.15.0",
"symfony/contracts": "^2.5 || ^3.0",
"symfony/event-dispatcher": "^5.4 || ^6.0 || ^7.0",
"symfony/http-kernel": "^5.4 || ^6.0 || ^7.0",
"symfony/routing": "^5.4 || ^6.0 || ^7.0"
},
"type": "phpstan-extension",
"extra": {
"phpstan": {
"includes": [
"rules.neon"
]
}
},
"autoload": {
"psr-4": {
"ShipMonk\\PHPStan\\DeadCode\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "Dead code detector to find unused PHP code via PHPStan extension.",
"keywords": [
"PHPStan",
"dead code",
"static analysis",
"unused code"
],
"support": {
"issues": "https://github.com/shipmonk-rnd/dead-code-detector/issues",
"source": "https://github.com/shipmonk-rnd/dead-code-detector/tree/0.5.1"
},
"time": "2024-11-05T09:28:57+00:00"
},
{
"name": "theseer/tokenizer",
"version": "1.3.1",
+21
View File
@@ -0,0 +1,21 @@
parameters:
ignoreErrors:
-
message: "#^Unused phpDocumentor\\\\Reflection\\\\DocBlock\\\\ExampleFinder\\:\\:getExampleDirectories$#"
count: 1
path: src/DocBlock/ExampleFinder.php
-
message: "#^Unused phpDocumentor\\\\Reflection\\\\DocBlock\\\\ExampleFinder\\:\\:setExampleDirectories$#"
count: 1
path: src/DocBlock/ExampleFinder.php
-
message: "#^Unused phpDocumentor\\\\Reflection\\\\DocBlock\\\\ExampleFinder\\:\\:setSourceDirectory$#"
count: 1
path: src/DocBlock/ExampleFinder.php
-
message: "#^Unused phpDocumentor\\\\Reflection\\\\DocBlock\\\\Tags\\\\Factory\\\\MethodParameterFactory\\:\\:formatNull$#"
count: 1
path: src/DocBlock/Tags/Factory/MethodParameterFactory.php
+10
View File
@@ -1,7 +1,17 @@
includes:
- phpstan-baseline.neon
parameters:
level: max
ignoreErrors:
- '#Method phpDocumentor\\Reflection\\DocBlock\\StandardTagFactory::createTag\(\) should return phpDocumentor\\Reflection\\DocBlock\\Tag but returns mixed#'
- '#Offset 2 on array\{string, 28, int\} on left side of \?\? always exists and is not nullable\.#'
-
path: src/DocBlockFactoryInterface.php
identifier: shipmonk.deadMethod
-
path: src/DocBlock/TagFactory.php
identifier: shipmonk.deadMethod
paths:
- src
- tests/unit
+22 -37
View File
@@ -1,39 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/8.0/phpunit.xsd"
colors="true"
convertDeprecationsToExceptions="false"
beStrictAboutOutputDuringTests="false"
forceCoversAnnotation="true"
verbose="true"
bootstrap="vendor/autoload.php"
>
<testsuites>
<testsuite name="unit">
<directory>./tests/unit</directory>
</testsuite>
<testsuite name="integration">
<directory>./tests/integration</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">./src/</directory>
</whitelist>
</filter>
<logging>
<log type="coverage-html"
target="build/coverage"
lowUpperBound="35"
highLowerBound="70"/>
<log type="coverage-clover" target="build/logs/clover.xml"/>
</logging>
<listeners>
<listener
class="Mockery\Adapter\Phpunit\TestListener"
file="vendor/mockery/mockery/library/Mockery/Adapter/Phpunit/TestListener.php"
/>
</listeners>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd" colors="true" convertDeprecationsToExceptions="false" beStrictAboutOutputDuringTests="false" forceCoversAnnotation="true" verbose="true" bootstrap="vendor/autoload.php">
<coverage>
<include>
<directory suffix=".php">./src/</directory>
</include>
<report>
<clover outputFile="build/logs/clover.xml"/>
<html outputDirectory="build/coverage" lowUpperBound="35" highLowerBound="70"/>
</report>
</coverage>
<testsuites>
<testsuite name="unit">
<directory>./tests/unit</directory>
</testsuite>
<testsuite name="integration">
<directory>./tests/integration</directory>
</testsuite>
</testsuites>
<logging/>
<listeners>
<listener class="Mockery\Adapter\Phpunit\TestListener" file="vendor/mockery/mockery/library/Mockery/Adapter/Phpunit/TestListener.php"/>
</listeners>
</phpunit>
+4 -4
View File
@@ -22,14 +22,14 @@ use phpDocumentor\Reflection\DocBlock\Tags\Factory\ExtendsFactory;
use phpDocumentor\Reflection\DocBlock\Tags\Factory\Factory;
use phpDocumentor\Reflection\DocBlock\Tags\Factory\ImplementsFactory;
use phpDocumentor\Reflection\DocBlock\Tags\Factory\MethodFactory;
use phpDocumentor\Reflection\DocBlock\Tags\Factory\MixinFactory;
use phpDocumentor\Reflection\DocBlock\Tags\Factory\ParamFactory;
use phpDocumentor\Reflection\DocBlock\Tags\Factory\PropertyFactory;
use phpDocumentor\Reflection\DocBlock\Tags\Factory\PropertyReadFactory;
use phpDocumentor\Reflection\DocBlock\Tags\Factory\PropertyWriteFactory;
use phpDocumentor\Reflection\DocBlock\Tags\Factory\ReturnFactory;
use phpDocumentor\Reflection\DocBlock\Tags\Factory\TemplateExtendsFactory;
use phpDocumentor\Reflection\DocBlock\Tags\Factory\TemplateCovariantFactory;
use phpDocumentor\Reflection\DocBlock\Tags\Factory\TemplateFactory;
use phpDocumentor\Reflection\DocBlock\Tags\Factory\TemplateImplementsFactory;
use phpDocumentor\Reflection\DocBlock\Tags\Factory\ThrowsFactory;
use phpDocumentor\Reflection\DocBlock\Tags\Factory\VarFactory;
use phpDocumentor\Reflection\DocBlock\Tags\Generic;
@@ -151,11 +151,11 @@ final class StandardTagFactory implements TagFactory
new PropertyReadFactory($typeResolver, $descriptionFactory),
new PropertyWriteFactory($typeResolver, $descriptionFactory),
new MethodFactory($typeResolver, $descriptionFactory),
new MixinFactory($typeResolver, $descriptionFactory),
new ImplementsFactory($typeResolver, $descriptionFactory),
new ExtendsFactory($typeResolver, $descriptionFactory),
new TemplateFactory($typeResolver, $descriptionFactory),
new TemplateImplementsFactory($typeResolver, $descriptionFactory),
new TemplateExtendsFactory($typeResolver, $descriptionFactory),
new TemplateCovariantFactory($typeResolver, $descriptionFactory),
new ThrowsFactory($typeResolver, $descriptionFactory),
);
@@ -18,6 +18,7 @@ use phpDocumentor\Reflection\DocBlock\Tags\InvalidTag;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use PHPStan\PhpDocParser\Lexer\Lexer;
use PHPStan\PhpDocParser\Parser\ConstExprParser;
use PHPStan\PhpDocParser\Parser\ParserException;
use PHPStan\PhpDocParser\Parser\PhpDocParser;
use PHPStan\PhpDocParser\Parser\TokenIterator;
use PHPStan\PhpDocParser\Parser\TypeParser;
@@ -59,13 +60,17 @@ class AbstractPHPStanFactory implements Factory
public function create(string $tagLine, ?TypeContext $context = null): Tag
{
$tokens = $this->tokenizeLine($tagLine . "\n");
$ast = $this->parser->parseTag($tokens);
if (property_exists($ast->value, 'description') === true) {
$ast->value->setAttribute(
'description',
rtrim($ast->value->description . $tokens->joinUntil(Lexer::TOKEN_END), "\n")
);
try {
$tokens = $this->tokenizeLine($tagLine . "\n");
$ast = $this->parser->parseTag($tokens);
if (property_exists($ast->value, 'description') === true) {
$ast->value->setAttribute(
'description',
rtrim($ast->value->description . $tokens->joinUntil(Lexer::TOKEN_END), "\n")
);
}
} catch (ParserException $e) {
return InvalidTag::create($tagLine, '')->withError($e);
}
if ($context === null) {
@@ -80,6 +85,8 @@ class AbstractPHPStanFactory implements Factory
}
} catch (RuntimeException $e) {
return InvalidTag::create((string) $ast->value, 'method')->withError($e);
} catch (ParserException $e) {
return InvalidTag::create((string) $ast->value, $ast->name)->withError($e);
}
return InvalidTag::create(
@@ -34,7 +34,7 @@ final class MethodParameterFactory
{
$method = 'format' . ucfirst(gettype($defaultValue));
if (method_exists($this, $method)) {
return ' = ' . $this->{$method}($defaultValue);
return $this->{$method}($defaultValue);
}
return '';
@@ -6,11 +6,12 @@ namespace phpDocumentor\Reflection\DocBlock\Tags\Factory;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\DocBlock\Tag;
use phpDocumentor\Reflection\DocBlock\Tags\TemplateExtends;
use phpDocumentor\Reflection\DocBlock\Tags\TemplateCovariant;
use phpDocumentor\Reflection\TypeResolver;
use phpDocumentor\Reflection\Types\Context;
use PHPStan\PhpDocParser\Ast\PhpDoc\ExtendsTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use Webmozart\Assert\Assert;
use function is_string;
@@ -18,7 +19,7 @@ use function is_string;
/**
* @internal This class is not part of the BC promise of this library.
*/
final class TemplateExtendsFactory implements PHPStanFactory
final class TemplateCovariantFactory implements PHPStanFactory
{
private DescriptionFactory $descriptionFactory;
private TypeResolver $typeResolver;
@@ -31,21 +32,21 @@ final class TemplateExtendsFactory implements PHPStanFactory
public function supports(PhpDocTagNode $node, Context $context): bool
{
return $node->value instanceof ExtendsTagValueNode && $node->name === '@template-extends';
return $node->value instanceof TemplateTagValueNode && $node->name === '@template-covariant';
}
public function create(PhpDocTagNode $node, Context $context): Tag
{
$tagValue = $node->value;
Assert::isInstanceOf($tagValue, ExtendsTagValueNode::class);
Assert::isInstanceOf($tagValue, TemplateTagValueNode::class);
$description = $tagValue->getAttribute('description');
if (is_string($description) === false) {
$description = $tagValue->description;
}
return new TemplateExtends(
$this->typeResolver->createType($tagValue->type, $context),
return new TemplateCovariant(
$this->typeResolver->createType(new IdentifierTypeNode($tagValue->name), $context),
$this->descriptionFactory->create($description, $context)
);
}
@@ -1,52 +0,0 @@
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection\DocBlock\Tags\Factory;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\DocBlock\Tag;
use phpDocumentor\Reflection\DocBlock\Tags\TemplateImplements;
use phpDocumentor\Reflection\TypeResolver;
use phpDocumentor\Reflection\Types\Context;
use PHPStan\PhpDocParser\Ast\PhpDoc\ImplementsTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
use Webmozart\Assert\Assert;
use function is_string;
/**
* @internal This class is not part of the BC promise of this library.
*/
final class TemplateImplementsFactory implements PHPStanFactory
{
private DescriptionFactory $descriptionFactory;
private TypeResolver $typeResolver;
public function __construct(TypeResolver $typeResolver, DescriptionFactory $descriptionFactory)
{
$this->descriptionFactory = $descriptionFactory;
$this->typeResolver = $typeResolver;
}
public function supports(PhpDocTagNode $node, Context $context): bool
{
return $node->value instanceof ImplementsTagValueNode && $node->name === '@template-implements';
}
public function create(PhpDocTagNode $node, Context $context): Tag
{
$tagValue = $node->value;
Assert::isInstanceOf($tagValue, ImplementsTagValueNode::class);
$description = $tagValue->getAttribute('description');
if (is_string($description) === false) {
$description = $tagValue->description;
}
return new TemplateImplements(
$this->typeResolver->createType($tagValue->type, $context),
$this->descriptionFactory->create($description, $context)
);
}
}
+1 -1
View File
@@ -84,7 +84,7 @@ final class MethodParameter
'$' . $this->getName() .
(
$this->defaultValue !== self::NO_DEFAULT_VALUE ?
(new MethodParameterFactory())->format($this->defaultValue) :
' = ' . (new MethodParameterFactory())->format($this->defaultValue) :
''
);
}
-44
View File
@@ -13,17 +13,10 @@ declare(strict_types=1);
namespace phpDocumentor\Reflection\DocBlock\Tags;
use InvalidArgumentException;
use phpDocumentor\Reflection\DocBlock\Tag;
use phpDocumentor\Reflection\Exception\CannotCreateTag;
use phpDocumentor\Reflection\Type;
use function in_array;
use function sprintf;
use function strlen;
use function substr;
use function trim;
abstract class TagWithType extends BaseTag
{
/** @var ?Type */
@@ -42,43 +35,6 @@ abstract class TagWithType extends BaseTag
throw new CannotCreateTag('Typed tag cannot be created');
}
/**
* @return string[]
*/
protected static function extractTypeFromBody(string $body): array
{
$type = '';
$nestingLevel = 0;
for ($i = 0, $iMax = strlen($body); $i < $iMax; $i++) {
$character = $body[$i];
if ($nestingLevel === 0 && trim($character) === '') {
break;
}
$type .= $character;
if (in_array($character, ['<', '(', '[', '{'])) {
$nestingLevel++;
continue;
}
if (in_array($character, ['>', ')', ']', '}'])) {
$nestingLevel--;
continue;
}
}
if ($nestingLevel < 0 || $nestingLevel > 0) {
throw new InvalidArgumentException(
sprintf('Could not find type in %s, please check for malformed notations', $body)
);
}
$description = trim(substr($body, strlen($type)));
return [$type, $description];
}
public function __toString(): string
{
if ($this->description) {
+2 -9
View File
@@ -13,9 +13,9 @@ declare(strict_types=1);
namespace phpDocumentor\Reflection\DocBlock\Tags;
use Doctrine\Deprecations\Deprecation;
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\Tag;
use phpDocumentor\Reflection\Exception\CannotCreateTag;
use phpDocumentor\Reflection\Type;
/**
@@ -51,14 +51,7 @@ final class Template extends BaseTag
*/
public static function create(string $body): ?Tag
{
Deprecation::trigger(
'phpdocumentor/reflection-docblock',
'https://github.com/phpDocumentor/ReflectionDocBlock/issues/361',
'Create using static factory is deprecated, this method should not be called directly
by library consumers',
);
return null;
throw new CannotCreateTag('Template tag cannot be created');
}
public function getTemplateName(): string
+4 -1
View File
@@ -10,7 +10,10 @@ use phpDocumentor\Reflection\FqsenResolver;
final class CustomParam implements Tag
{
/** @var string|null */
public $myParam;
/** @var FqsenResolver|null */
public $fqsenResolver;
public function getName() : string
@@ -18,7 +21,7 @@ final class CustomParam implements Tag
return 'spy';
}
public static function create($body, FqsenResolver $fqsenResolver = null, ?string $myParam = null)
public static function create(string $body, FqsenResolver $fqsenResolver = null, ?string $myParam = null)
{
$tag = new self();
$tag->fqsenResolver = $fqsenResolver;
+2 -3
View File
@@ -7,11 +7,10 @@ namespace phpDocumentor\Reflection\Assets;
use phpDocumentor\Reflection\DocBlock\Tag;
use phpDocumentor\Reflection\DocBlock\Tags\Formatter;
use phpDocumentor\Reflection\DocBlock\Tags\Formatter\PassthroughFormatter;
use phpDocumentor\Reflection\FqsenResolver;
use phpDocumentor\Reflection\DocBlock\Tags\Factory\StaticMethod;
final class CustomServiceClass implements Tag
{
/** @var Formatter|null */
public $formatter;
public function getName() : string
@@ -19,7 +18,7 @@ final class CustomServiceClass implements Tag
return 'spy';
}
public static function create($body, PassthroughFormatter $formatter = null)
public static function create(string $body, PassthroughFormatter $formatter = null)
{
$tag = new self();
$tag->formatter = $formatter;
+2 -1
View File
@@ -11,6 +11,7 @@ use phpDocumentor\Reflection\DocBlock\Tags\Factory\StaticMethod;
final class CustomServiceInterface implements Tag
{
/** @var Formatter|null */
public $formatter;
public function getName() : string
@@ -18,7 +19,7 @@ final class CustomServiceInterface implements Tag
return 'spy';
}
public static function create($body, Formatter $formatter = null)
public static function create(string $body, Formatter $formatter = null)
{
$tag = new self();
$tag->formatter = $formatter;
+1
View File
@@ -20,6 +20,7 @@ use phpDocumentor\Reflection\Types\Context;
class CustomTagFactory implements Factory
{
/** @var CustomServiceClass|null */
public $class;
public function create(string $tagLine, ?Context $context = null, CustomServiceClass $class = null): Tag
@@ -13,7 +13,6 @@ declare(strict_types=1);
namespace phpDocumentor\Reflection\DocBlock;
use Exception;
use Mockery as m;
use phpDocumentor\Reflection\DocBlock\Tags\InvalidTag;
use phpDocumentor\Reflection\DocBlock\Tags\Link as LinkTag;
@@ -216,7 +215,7 @@ DESCRIPTION;
$tagFactory->shouldReceive('create')
->once()
->with('@see $name', $context)
->andReturn(InvalidTag::create('$name', 'see', new Exception()));
->andReturn(InvalidTag::create('$name', 'see'));
$factory = new DescriptionFactory($tagFactory);
$description = $factory->create($contents, $context);
+17 -9
View File
@@ -15,6 +15,7 @@ namespace phpDocumentor\Reflection\DocBlock;
use InvalidArgumentException;
use Mockery as m;
use Name\Spaced\Tag;
use phpDocumentor\Reflection\Assets\CustomParam;
use phpDocumentor\Reflection\Assets\CustomServiceClass;
use phpDocumentor\Reflection\Assets\CustomServiceInterface;
@@ -134,10 +135,10 @@ class StandardTagFactoryTest extends TestCase
public function testAnEmptyContextIsCreatedIfNoneIsProvided(): void
{
$fqsen = '\Tag';
$resolver = m::mock(FqsenResolver::class)
->shouldReceive('resolve')
$resolver = m::mock(FqsenResolver::class);
$resolver->allows('resolve')
->with('Tag', m::type(Context::class))
->andReturn(new Fqsen($fqsen))
->andReturns(new Fqsen($fqsen))
->getMock();
$descriptionFactory = m::mock(DescriptionFactory::class);
$descriptionFactory->shouldIgnoreMissing();
@@ -246,18 +247,14 @@ class StandardTagFactoryTest extends TestCase
'The tag "@my-täg " does not seem to be wellformed, please check it for errors'
);
$typeResolver = new TypeResolver();
$fqsenResolver = new FqsenResolver();
$tagFactory = StandardTagFactory::createInstance($fqsenResolver);
$descriptionFactory = new DescriptionFactory($tagFactory);
$context = new Context('');
$tagFactory = StandardTagFactory::createInstance(
$fqsenResolver,
['my-täg' => Author::class]
);
$tag = $tagFactory->create('@my-täg ', $context);
$tagFactory->create('@my-täg ', $context);
}
/**
@@ -291,6 +288,7 @@ class StandardTagFactoryTest extends TestCase
$tagFactory->addParameter('myParam', 'myValue');
$spy = $tagFactory->create('@spy');
self::assertInstanceOf(CustomParam::class, $spy);
$this->assertSame($resolver, $spy->fqsenResolver);
$this->assertSame('myValue', $spy->myParam);
}
@@ -311,6 +309,7 @@ class StandardTagFactoryTest extends TestCase
$spy = $tagFactory->create('@spy');
self::assertInstanceOf(CustomServiceClass::class, $spy);
$this->assertSame($service, $spy->formatter);
}
@@ -331,6 +330,7 @@ class StandardTagFactoryTest extends TestCase
$spy = $tagFactory->create('@spy');
self::assertInstanceOf(CustomServiceInterface::class, $spy);
$this->assertSame($service, $spy->formatter);
}
@@ -366,7 +366,10 @@ class StandardTagFactoryTest extends TestCase
$resolver = m::mock(FqsenResolver::class);
$tagFactory = StandardTagFactory::createInstance($resolver);
// phpcs:ignore SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly.ReferenceViaFullyQualifiedName
$tagFactory->registerTagHandler(\Name\Spaced\Tag::class, Author::class);
$tagFactory->registerTagHandler(
Tag::class, // @phpstan-ignore class.notFound
Author::class
);
}
/**
@@ -380,6 +383,7 @@ class StandardTagFactoryTest extends TestCase
$this->expectException('InvalidArgumentException');
$resolver = m::mock(FqsenResolver::class);
$tagFactory = StandardTagFactory::createInstance($resolver);
//@phpstan-ignore argument.type
$tagFactory->registerTagHandler('my-tag', '');
}
@@ -394,6 +398,8 @@ class StandardTagFactoryTest extends TestCase
$this->expectException('InvalidArgumentException');
$resolver = m::mock(FqsenResolver::class);
$tagFactory = StandardTagFactory::createInstance($resolver);
//@phpstan-ignore argument.type
$tagFactory->registerTagHandler('my-tag', 'IDoNotExist');
}
@@ -408,6 +414,8 @@ class StandardTagFactoryTest extends TestCase
$this->expectException('InvalidArgumentException');
$resolver = m::mock(FqsenResolver::class);
$tagFactory = StandardTagFactory::createInstance($resolver);
//@phpstan-ignore argument.type
$tagFactory->registerTagHandler('my-tag', 'stdClass');
}
+3
View File
@@ -82,6 +82,7 @@ class AuthorTest extends TestCase
$fixture = new Author($expected, '[email protected]');
self::assertInstanceOf(Author::class, $fixture);
$this->assertSame($expected, $fixture->getAuthorName());
}
@@ -95,6 +96,7 @@ class AuthorTest extends TestCase
$fixture = new Author('Mike van Riel', $expected);
self::assertInstanceOf(Author::class, $fixture);
$this->assertSame($expected, $fixture->getEmail());
}
@@ -156,6 +158,7 @@ class AuthorTest extends TestCase
{
$fixture = Author::create($input);
self::assertInstanceOf(Author::class, $fixture);
$this->assertSame($output, (string) $fixture);
$this->assertSame($name, $fixture->getAuthorName());
$this->assertSame($email, $fixture->getEmail());
+6
View File
@@ -23,6 +23,7 @@ class ExampleTest extends TestCase
public function testExampleWithoutContent(): void
{
$tag = Example::create('"example1.php"');
self::assertInstanceOf(Example::class, $tag);
$this->assertEquals('"example1.php"', $tag->getContent());
$this->assertEquals('', $tag->getDescription());
$this->assertEquals('example', $tag->getName());
@@ -39,6 +40,7 @@ class ExampleTest extends TestCase
public function testWithDescription(): void
{
$tag = Example::create('"example1.php" some text');
self::assertInstanceOf(Example::class, $tag);
$this->assertEquals('example1.php', $tag->getFilePath());
$this->assertEquals('some text', $tag->getDescription());
}
@@ -54,6 +56,7 @@ class ExampleTest extends TestCase
public function testStartlineIsParsed(): void
{
$tag = Example::create('"example1.php" 10');
self::assertInstanceOf(Example::class, $tag);
$this->assertEquals('example1.php', $tag->getFilePath());
$this->assertEquals(10, $tag->getStartingLine());
}
@@ -70,6 +73,7 @@ class ExampleTest extends TestCase
public function testAllowOmittingLineCount(): void
{
$tag = Example::create('"example1.php" 10 some text');
self::assertInstanceOf(Example::class, $tag);
$this->assertEquals('example1.php', $tag->getFilePath());
$this->assertEquals(10, $tag->getStartingLine());
$this->assertEquals('some text', $tag->getDescription());
@@ -87,6 +91,7 @@ class ExampleTest extends TestCase
public function testLengthIsParsed(): void
{
$tag = Example::create('"example1.php" 10 5');
self::assertInstanceOf(Example::class, $tag);
$this->assertEquals('example1.php', $tag->getFilePath());
$this->assertEquals(10, $tag->getStartingLine());
$this->assertEquals(5, $tag->getLineCount());
@@ -159,6 +164,7 @@ class ExampleTest extends TestCase
string $content
): void {
$tag = Example::create($input);
self::assertInstanceOf(Example::class, $tag);
$this->assertSame($filePath, $tag->getFilePath());
$this->assertSame($startLine, $tag->getStartingLine());
$this->assertSame($lineCount, $tag->getLineCount());
@@ -0,0 +1,111 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags\Factory;
use Exception;
use Mockery as m;
use phpDocumentor\Reflection\DocBlock\Tag;
use phpDocumentor\Reflection\DocBlock\Tags\InvalidTag;
use PHPStan\PhpDocParser\Parser\ParserException;
use PHPUnit\Framework\TestCase;
use RuntimeException;
/**
* @uses \phpDocumentor\Reflection\DocBlock\Tags\Factory\AbstractPHPStanFactory
* @uses \phpDocumentor\Reflection\DocBlock\Tags\InvalidTag
*
* @coversDefaultClass \phpDocumentor\Reflection\DocBlock\Tags\Factory\AbstractPHPStanFactory
* @covers ::<private>
*/
class AbstractPHPStanFactoryTest extends TestCase
{
/**
* Call Mockery::close after each test.
*/
public function tearDown(): void
{
m::close();
}
/**
* @covers ::create
*/
public function testCreateReturnsTagFromSupportingFactory(): void
{
$tag = m::mock(Tag::class);
$factory = m::mock(PHPStanFactory::class);
$factory->shouldReceive('supports')->andReturn(true);
$factory->shouldReceive('create')->andReturn($tag);
$sut = new AbstractPHPStanFactory($factory);
$result = $sut->create('@param string $param');
self::assertSame($tag, $result);
}
/**
* @covers ::create
*/
public function testCreateReturnsInvalidTagWhenNoFactorySupports(): void
{
$factory = m::mock(PHPStanFactory::class);
$factory->shouldReceive('supports')->andReturn(false);
$sut = new AbstractPHPStanFactory($factory);
$result = $sut->create('@unknown string $param');
self::assertInstanceOf(InvalidTag::class, $result);
self::assertEquals('@unknown', $result->getName());
}
/**
* @covers ::create
*/
public function testCreateReturnsInvalidTagWithErrorOnFactoryRuntimeException(): void
{
$factory = m::mock(PHPStanFactory::class);
$factory->shouldReceive('supports')->andReturn(true);
$factory->shouldReceive('create')->andThrow(new RuntimeException('Factory error'));
$sut = new AbstractPHPStanFactory($factory);
$result = $sut->create('@param string $param');
self::assertInstanceOf(InvalidTag::class, $result);
self::assertInstanceOf(Exception::class, $result->getException());
self::assertEquals('Factory error', $result->getException()->getMessage());
}
/**
* @covers ::create
*/
public function testCreateReturnsInvalidTagWithErrorOnFactoryParserException(): void
{
$exception = m::mock(ParserException::class);
$exception->shouldReceive('getMessage')->andReturn('Parser error');
$factory = m::mock(PHPStanFactory::class);
$factory->shouldReceive('supports')->andReturn(true);
$factory->shouldReceive('create')->andThrow($exception);
$sut = new AbstractPHPStanFactory($factory);
$result = $sut->create('@param string $param');
self::assertInstanceOf(InvalidTag::class, $result);
self::assertSame($exception, $result->getException());
}
}
@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags\Factory;
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\Tags\Extends_;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\PseudoTypes\Generic;
use phpDocumentor\Reflection\Types\Context;
use phpDocumentor\Reflection\Types\Object_;
final class ExtendsFactoryTest extends TagFactoryTestCase
{
/**
* @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\ExtendsFactory::__construct
* @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\ExtendsFactory::create
* @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\ExtendsFactory::supports
*/
public function testExtendsIsCreated(): void
{
$ast = $this->parseTag('@extends SomeClass<OtherType>');
$factory = new ExtendsFactory($this->giveTypeResolver(), $this->givenDescriptionFactory());
$context = new Context('global');
self::assertTrue($factory->supports($ast, $context));
self::assertEquals(
new Extends_(
new Generic(new Fqsen('\\SomeClass'), [new Object_(new Fqsen('\\OtherType'))]),
new Description('')
),
$factory->create($ast, $context)
);
}
}
@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags\Factory;
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\Tags\Implements_;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\PseudoTypes\Generic;
use phpDocumentor\Reflection\Types\Context;
use phpDocumentor\Reflection\Types\Object_;
final class ImplementsFactoryTest extends TagFactoryTestCase
{
/**
* @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\ImplementsFactory::__construct
* @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\ImplementsFactory::create
* @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\ImplementsFactory::supports
*/
public function testImplementsIsCreated(): void
{
$ast = $this->parseTag('@implements SomeClass<OtherType>');
$factory = new ImplementsFactory($this->giveTypeResolver(), $this->givenDescriptionFactory());
$context = new Context('global');
self::assertTrue($factory->supports($ast, $context));
self::assertEquals(
new Implements_(
new Generic(new Fqsen('\\SomeClass'), [new Object_(new Fqsen('\\OtherType'))]),
new Description('')
),
$factory->create($ast, $context)
);
}
}
@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags\Factory;
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\Tags\Mixin;
use phpDocumentor\Reflection\Types\Context;
use phpDocumentor\Reflection\Types\String_;
final class MixinFactoryTest extends TagFactoryTestCase
{
/**
* @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\MixinFactory::__construct
* @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\MixinFactory::create
* @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\MixinFactory::supports
*/
public function testMixinIsCreated(): void
{
$ast = $this->parseTag('@mixin string');
$factory = new MixinFactory($this->giveTypeResolver(), $this->givenDescriptionFactory());
$context = new Context('global');
self::assertTrue($factory->supports($ast, $context));
self::assertEquals(
new Mixin(
new String_(),
new Description('')
),
$factory->create($ast, $context)
);
}
}
@@ -48,7 +48,7 @@ final class ParamFactoryTest extends TagFactoryTestCase
}
/**
* @return array<array-key, string|Param>
* @return array<int, array<int, string|Param|InvalidTag>>
*/
public function paramInputProvider(): array
{
@@ -27,23 +27,16 @@ use PHPStan\PhpDocParser\Parser\TypeParser;
use PHPStan\PhpDocParser\ParserConfig;
use PHPUnit\Framework\TestCase;
use function class_exists;
use function property_exists;
abstract class TagFactoryTestCase extends TestCase
{
public function parseTag(string $tag): PhpDocTagNode
{
if (class_exists(ParserConfig::class)) {
$config = new ParserConfig([]);
$lexer = new Lexer($config);
$constParser = new ConstExprParser($config);
$phpDocParser = new PhpDocParser($config, new TypeParser($config, $constParser), $constParser);
} else {
$lexer = new Lexer();
$constParser = new ConstExprParser();
$phpDocParser = new PhpDocParser(new TypeParser($constParser), $constParser);
}
$config = new ParserConfig(['indexes' => true, 'lines' => true]);
$lexer = new Lexer($config);
$constParser = new ConstExprParser($config);
$phpDocParser = new PhpDocParser($config, new TypeParser($config, $constParser), $constParser);
$tagNode = $phpDocParser->parseTag(new TokenIterator($lexer->tokenize($tag)));
if (property_exists($tagNode->value, 'description') === true) {
@@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags\Factory;
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\Tag;
use phpDocumentor\Reflection\DocBlock\Tags\TemplateCovariant;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Types\Context;
use phpDocumentor\Reflection\Types\Object_;
use phpDocumentor\Reflection\Types\String_;
final class TemplateCovariantFactoryTest extends TagFactoryTestCase
{
/**
* @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\TemplateCovariantFactory::__construct
* @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\TemplateCovariantFactory::create
* @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\TemplateCovariantFactory::supports
* @dataProvider templateCovariantInputProvider
*/
public function testTemplateCovariantIsCreated(string $input, Tag $expected): void
{
$ast = $this->parseTag($input);
$factory = new TemplateCovariantFactory($this->giveTypeResolver(), $this->givenDescriptionFactory());
$context = new Context('global');
self::assertTrue($factory->supports($ast, $context));
self::assertEquals(
$expected,
$factory->create($ast, $context)
);
}
/**
* @return array<int, array<int, string|TemplateCovariant>>
*/
public function templateCovariantInputProvider(): array
{
return [
[
'@template-covariant string',
new TemplateCovariant(
new String_(),
new Description('')
),
],
[
'@template-covariant SomeClass Description',
new TemplateCovariant(
new Object_(new Fqsen('\SomeClass')),
new Description('Description')
),
],
[
'@template-covariant SomeClass',
new TemplateCovariant(
new Object_(new Fqsen('\SomeClass')),
new Description('')
),
],
];
}
}
@@ -0,0 +1,80 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags\Factory;
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\Tag;
use phpDocumentor\Reflection\DocBlock\Tags\Template;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Types\Context;
use phpDocumentor\Reflection\Types\Mixed_;
use phpDocumentor\Reflection\Types\Object_;
final class TemplateFactoryTest extends TagFactoryTestCase
{
/**
* @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\TemplateFactory::__construct
* @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\TemplateFactory::create
* @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\TemplateFactory::supports
* @dataProvider templateInputProvider
*/
public function testTemplateIsCreated(string $input, Tag $expected): void
{
$ast = $this->parseTag($input);
$factory = new TemplateFactory($this->giveTypeResolver(), $this->givenDescriptionFactory());
$context = new Context('global');
self::assertTrue($factory->supports($ast, $context));
self::assertEquals(
$expected,
$factory->create($ast, $context)
);
}
/**
* @return array<int, array<int, string|Template>>
*/
public function templateInputProvider(): array
{
return [
[
'@template T',
new Template(
'T',
new Mixed_(),
new Mixed_(),
new Description('')
),
],
[
'@template T of SomeClass Description',
new Template(
'T',
new Object_(new Fqsen('\SomeClass')),
new Mixed_(),
new Description('Description')
),
],
[
'@template T of SomeClass = Default',
new Template(
'T',
new Object_(new Fqsen('\SomeClass')),
new Object_(new Fqsen('\Default')),
new Description('')
),
],
];
}
}
@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags\Factory;
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\Tags\Throws;
use phpDocumentor\Reflection\Types\Context;
use phpDocumentor\Reflection\Types\String_;
final class ThrowsFactoryTest extends TagFactoryTestCase
{
/**
* @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\ThrowsFactory::__construct
* @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\ThrowsFactory::create
* @covers \phpDocumentor\Reflection\DocBlock\Tags\Factory\ThrowsFactory::supports
*/
public function testThrowsIsCreated(): void
{
$ast = $this->parseTag('@throws string');
$factory = new ThrowsFactory($this->giveTypeResolver(), $this->givenDescriptionFactory());
$context = new Context('global');
self::assertTrue($factory->supports($ast, $context));
self::assertEquals(
new Throws(
new String_(),
new Description('')
),
$factory->create($ast, $context)
);
}
}
+8 -2
View File
@@ -10,6 +10,7 @@ use PHPUnit\Framework\TestCase;
use Throwable;
use function fopen;
use function is_string;
use function serialize;
use function unserialize;
@@ -57,9 +58,12 @@ final class InvalidTagTest extends TestCase
self::assertSame('name', $tag->getName());
self::assertSame('@name Body', $tag->render());
self::assertSame($parentException, $tag->getException());
self::assertSame($e, $tag->getException()->getPrevious());
$trace = $tag->getException()->getPrevious()->getTrace();
if (isset($trace[0]['args'])) { // Not set by default on 7.4
self::assertTrue(is_string($trace[0]['args'][0]));
self::assertStringStartsWith('(Closure at', $trace[0]['args'][0]);
self::assertStringContainsString(__FILE__, $trace[0]['args'][0]);
}
@@ -70,7 +74,7 @@ final class InvalidTagTest extends TestCase
private function throwExceptionFromClosureWithClosureArgument(): void
{
$function = static function (): void {
$function = static function (?callable $foo = null): void {
throw new InvalidArgumentException();
};
@@ -87,9 +91,11 @@ final class InvalidTagTest extends TestCase
self::assertSame('name', $tag->getName());
self::assertSame('@name Body', $tag->render());
self::assertSame($parentException, $tag->getException());
self::assertSame($e, $tag->getException()->getPrevious());
$trace = $tag->getException()->getPrevious()->getTrace();
if (isset($trace[0]['args'])) { // Not set by default on 7.4
self::assertTrue(is_string($trace[0]['args'][0]));
self::assertStringStartsWith(
'resource(stream)',
$trace[0]['args'][0]
@@ -102,7 +108,7 @@ final class InvalidTagTest extends TestCase
private function throwExceptionWithResourceArgument(): void
{
$function = static function (): void {
$function = static function ($file): void {
throw new InvalidArgumentException();
};
@@ -81,6 +81,10 @@ class MethodParameterTest extends TestCase
sprintf('%s $argument = %s', $type, $defaultValueStr),
(string) $fixture
);
$this->assertSame(
$defaultValueStr,
$fixture->getDefaultValue()
);
}
/**
+13
View File
@@ -225,6 +225,19 @@ class MethodTest extends TestCase
);
}
/**
* @covers ::__construct
* @covers ::getReturnType
*/
public function testReturnsReference(): void
{
$expected = new String_();
$fixture = new Method('myMethod', [], $expected);
$this->assertFalse($fixture->returnsReference());
}
/**
* @covers ::create
*/
+15
View File
@@ -153,4 +153,19 @@ class ParamTest extends TestCase
$this->assertSame('string ...$myParameter Description', (string) $fixture);
}
/**
* @uses \phpDocumentor\Reflection\DocBlock\Description
*
* @covers ::__construct
* @covers \phpDocumentor\Reflection\DocBlock\Tags\Param::isReference
*/
public function testIsReference(): void
{
$expected = new Description('Description');
$fixture = new Param('1.0', null, false, $expected);
$this->assertFalse($fixture->isReference());
}
}
+2
View File
@@ -158,6 +158,7 @@ class SinceTest extends TestCase
$fixture = Since::create('1.0 My Description', $descriptionFactory, $context);
self::assertInstanceOf(Since::class, $fixture);
$this->assertSame('1.0 My Description', (string) $fixture);
$this->assertSame($version, $fixture->getVersion());
$this->assertSame($description, $fixture->getDescription());
@@ -178,6 +179,7 @@ class SinceTest extends TestCase
$fixture = Since::create('', $descriptionFactory, new Context(''));
self::assertInstanceOf(Since::class, $fixture);
$this->assertSame('', (string) $fixture);
$this->assertSame(null, $fixture->getVersion());
$this->assertSame(null, $fixture->getDescription());
+2
View File
@@ -227,6 +227,8 @@ class SourceTest extends TestCase
public function testExceptionIsThrownIfLineCountIsNotIntegerOrNull(): void
{
$this->expectException('InvalidArgumentException');
//@phpstan-ignore argument.type
new Source('1', []);
}
}
+2
View File
@@ -158,6 +158,7 @@ class VersionTest extends TestCase
$fixture = Version::create('1.0 My Description', $descriptionFactory, $context);
self::assertInstanceOf(Version::class, $fixture);
$this->assertSame('1.0 My Description', (string) $fixture);
$this->assertSame($version, $fixture->getVersion());
$this->assertSame($description, $fixture->getDescription());
@@ -178,6 +179,7 @@ class VersionTest extends TestCase
$fixture = Version::create('', $descriptionFactory, new Context(''));
self::assertInstanceOf(Version::class, $fixture);
$this->assertSame('', (string) $fixture);
$this->assertSame(null, $fixture->getVersion());
$this->assertSame(null, $fixture->getDescription());
+1 -1
View File
@@ -200,7 +200,7 @@ DOCBLOCK;
}
/**
* @return string[]
* @return array<int, array<int, string>>
*/
public function provideSummaryAndDescriptions(): array
{
+3 -2
View File
@@ -108,8 +108,9 @@ class DocBlockTest extends TestCase
public function testDocBlockAllowsOnlyTags(): void
{
$this->expectException('InvalidArgumentException');
$tags = [null];
$fixture = new DocBlock('', null, $tags);
$tags = [null];
// @phpstan-ignore argument.type
new DocBlock('', null, $tags);
}
/**
+6 -2
View File
@@ -41,8 +41,12 @@ final class PregSplitTest extends TestCase
public function testPregSplitThrowsOnError(): void
{
//We need to disable the error handler for phpunit... because we expect some errors here
$this->errorHandler = set_error_handler(static function (): void {
}, E_WARNING);
$this->errorHandler = set_error_handler(
static function (int $i, string $s, string $s2, int $x, ?array $trace = null): bool {
return true;
},
E_WARNING
);
$this->expectException(PcreException::class);
Utils::pregSplit('~InvalidRegular)Expression~', 'some word');