Skip to content

Commit

Permalink
Validate phpDoc throws types
Browse files Browse the repository at this point in the history
  • Loading branch information
Majkl578 committed May 12, 2018
1 parent 0a4622b commit 2937b21
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 0 deletions.
1 change: 1 addition & 0 deletions conf/config.level2.neon
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ rules:
- PHPStan\Rules\Operators\InvalidUnaryOperationRule
- PHPStan\Rules\PhpDoc\IncompatiblePhpDocTypeRule
- PHPStan\Rules\PhpDoc\InvalidPhpDocTagValueRule
- PHPStan\Rules\PhpDoc\InvalidThrowsPhpDocValueRule

services:
-
Expand Down
63 changes: 63 additions & 0 deletions src/Rules/PhpDoc/InvalidThrowsPhpDocValueRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\PhpDoc;

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Type\FileTypeMapper;
use PHPStan\Type\ObjectType;
use PHPStan\Type\VerbosityLevel;

class InvalidThrowsPhpDocValueRule implements \PHPStan\Rules\Rule
{

/** @var FileTypeMapper */
private $fileTypeMapper;

public function __construct(FileTypeMapper $fileTypeMapper)
{
$this->fileTypeMapper = $fileTypeMapper;
}

public function getNodeType(): string
{
return \PhpParser\Node\FunctionLike::class;
}

/**
* @param \PhpParser\Node\FunctionLike $node
* @param \PHPStan\Analyser\Scope $scope
* @return string[]
*/
public function processNode(Node $node, Scope $scope): array
{
$docComment = $node->getDocComment();
if ($docComment === null) {
return [];
}

$resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc(
$scope->getFile(),
$scope->isInClass() ? $scope->getClassReflection()->getName() : null,
$scope->isInTrait() ? $scope->getTraitReflection()->getName() : null,
$docComment->getText()
);

if ($resolvedPhpDoc->getThrowsTag() === null) {
return [];
}

$phpDocThrowsType = $resolvedPhpDoc->getThrowsTag()->getType();

$isThrowsSuperType = (new ObjectType(\Throwable::class))->isSuperTypeOf($phpDocThrowsType);
if ($isThrowsSuperType->yes()) {
return [];
}

return [sprintf(
'PHPDoc tag @throws with type %s is not subtype of Throwable',
$phpDocThrowsType->describe(VerbosityLevel::typeOnly())
)];
}

}
43 changes: 43 additions & 0 deletions tests/PHPStan/Rules/PhpDoc/InvalidThrowsPhpDocValueRuleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\PhpDoc;

use PHPStan\Type\FileTypeMapper;

class InvalidThrowsPhpDocValueRuleTest extends \PHPStan\Testing\RuleTestCase
{

protected function getRule(): \PHPStan\Rules\Rule
{
return new InvalidThrowsPhpDocValueRule(
$this->getContainer()->getByType(FileTypeMapper::class)
);
}

public function testRule(): void
{
$this->analyse([__DIR__ . '/data/incompatible-throws.php'], [
[
'PHPDoc tag @throws with type Undefined is not subtype of Throwable',
54,
],
[
'PHPDoc tag @throws with type bool is not subtype of Throwable',
61,
],
[
'PHPDoc tag @throws with type DateTimeImmutable is not subtype of Throwable',
68,
],
[
'PHPDoc tag @throws with type DateTimeImmutable|Throwable is not subtype of Throwable',
75,
],
[
'PHPDoc tag @throws with type DateTimeImmutable&IteratorAggregate is not subtype of Throwable',
82,
],
]);
}

}
84 changes: 84 additions & 0 deletions tests/PHPStan/Rules/PhpDoc/data/incompatible-throws.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?php

namespace InvalidPhpDoc;

function noDoc() : void
{
}

/**
* No tag here.
*/
function noThrowsTag()
{
}

/**
* @throws \Exception
*/
function singleClassThrows()
{
}

/**
* @throws \RuntimeException Some comment.
*/
function commentedThrows()
{
}

/**
* @throws \RuntimeException|\LogicException
*/
function unionThrows()
{
}

/**
* @throws \Throwable&\DateTimeInterface
*/
function intersectThrows()
{
}

/**
* @throws (\RuntimeException&\Throwable)|\TypeError
*/
function unionAndIntersectThrows()
{
}

/**
* @throws \Undefined
*/
function undefinedThrows()
{
}

/**
* @throws bool
*/
function scalarThrows()
{
}

/**
* @throws \DateTimeImmutable
*/
function notThrowableThrows()
{
}

/**
* @throws \DateTimeImmutable|\Throwable
*/
function notThrowableInUnionThrows()
{
}

/**
* @throws \DateTimeImmutable&\IteratorAggregate
*/
function notThrowableInIntersectThrows()
{
}

0 comments on commit 2937b21

Please sign in to comment.