Merge pull request #17 from weirdan/fix-grandchild-missingconstructor

Fix MissingConstructor on TestCase grandchildren
This commit is contained in:
Bruce Weirdan 2019-02-27 15:37:48 +02:00 committed by GitHub
commit 24571b9a0c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 72 additions and 10 deletions

View File

@ -4,6 +4,7 @@ namespace Psalm\PhpUnitPlugin\Hooks;
use PHPUnit\Framework\TestCase;
use PhpParser\Node\Stmt\ClassLike;
use PhpParser\Node\Stmt\ClassMethod;
use Psalm\CodeLocation;
use Psalm\Codebase;
use Psalm\DocComment;
use Psalm\FileSource;
@ -11,6 +12,7 @@ use Psalm\IssueBuffer;
use Psalm\Issue;
use Psalm\PhpUnitPlugin\Exception\UnsupportedPsalmVersion;
use Psalm\Plugin\Hook\AfterClassLikeAnalysisInterface;
use Psalm\Plugin\Hook\AfterClassLikeExistenceCheckInterface;
use Psalm\Plugin\Hook\AfterClassLikeVisitInterface;
use Psalm\StatementsSource;
use Psalm\Storage\ClassLikeStorage;
@ -19,24 +21,54 @@ use Psalm\Storage\MethodStorage;
use Psalm\Type;
use Psalm\Type\Atomic\TIterable;
class TestCaseHandler implements AfterClassLikeVisitInterface, AfterClassLikeAnalysisInterface
class TestCaseHandler implements
AfterClassLikeVisitInterface,
AfterClassLikeAnalysisInterface,
AfterClassLikeExistenceCheckInterface
{
/** @var bool */
private static $suppressed = false;
/**
* TODO: move to new hook (afterCodebasePopulation?)
* {@inheritDoc}
*/
public static function afterClassLikeExistenceCheck(
string $fq_class_name,
CodeLocation $code_location,
StatementsSource $statements_source,
Codebase $codebase,
array &$file_replacements = []
) {
if (self::$suppressed) {
return;
}
self::$suppressed = true;
foreach ($codebase->classlike_storage_provider->getAll() as $name => $storage) {
$meta = (array) ($storage->custom_metadata[__NAMESPACE__] ?? []);
if ($codebase->classExtends($name, TestCase::class) && ($meta['hasInitializers'] ?? false)) {
$storage->suppressed_issues[] = 'MissingConstructor';
}
}
}
/**
* {@inheritDoc}
*/
public static function afterClassLikeVisit(
ClassLike $classNode,
ClassLikeStorage $classStorage,
ClassLike $class_node,
ClassLikeStorage $class_storage,
FileSource $statements_source,
Codebase $codebase,
array &$file_replacements = []
) {
if (!$codebase->classExtends($classStorage->name, TestCase::class)) {
return;
}
if (self::hasInitializers($classStorage, $classNode)) {
$classStorage->suppressed_issues[] = 'MissingConstructor';
if (self::hasInitializers($class_storage, $class_node)) {
if (!isset($class_storage->custom_metadata)) {
/** @psalm-suppress UndefinedPropertyAssignment */
$class_storage->custom_metadata = [];
}
$class_storage->custom_metadata[__NAMESPACE__] = ['hasInitializers' => true];
}
}
@ -380,7 +412,7 @@ class TestCaseHandler implements AfterClassLikeVisitInterface, AfterClassLikeAna
foreach ($storage->methods as $method => $_) {
$stmt_method = $stmt->getMethod($method);
if (!$stmt_method) {
throw new \RuntimeException('Failed to find ' . $method);
continue;
}
if (self::isBeforeInitializer($stmt_method)) {
return true;

View File

@ -650,3 +650,33 @@ Feature: TestCase
| Type | Message |
| InvalidArgument | Argument 1 of NS\MyTestCase::testSomething has no default value, but possibly undefined int provided by NS\MyTestCase::provide():(iterable<string, array{0?:int}>) |
And I see no other errors
Scenario: Stateful grandchild test case with setUp produces no MissingConstructor
Given I have the following code
"""
use Prophecy\Prophecy\ObjectProphecy;
class BaseTestCase extends TestCase {}
interface I { public function work(): int; }
class MyTestCase extends BaseTestCase
{
/** @var ObjectProphecy<I> */
private $i;
/** @return void */
public function setUp() {
$this->i = $this->prophesize(I::class);
}
/** @return void */
public function testSomething() {
$this->i->work()->willReturn(1);;
$i = $this->i->reveal();
$this->assertEquals(1, $i->work());
}
}
"""
When I run Psalm
Then I see no errors