mirror of
https://github.com/danog/psalm-plugin-phpunit.git
synced 2024-11-30 04:29:08 +01:00
Merge pull request #22 from weirdan/traits-in-tests
Added support for test methods and providers in traits
This commit is contained in:
commit
2fcf618ba9
@ -8,6 +8,7 @@ use SimpleXMLElement;
|
||||
use Psalm\Plugin\PluginEntryPointInterface;
|
||||
use Psalm\Plugin\RegistrationInterface;
|
||||
|
||||
/** @psalm-suppress UnusedClass */
|
||||
class Plugin implements PluginEntryPointInterface
|
||||
{
|
||||
/** @return void */
|
||||
|
@ -121,22 +121,30 @@ class TestCaseHandler implements
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($class_storage->methods as $method_name => $method_storage) {
|
||||
foreach ($class_storage->declaring_method_ids as $method_name_lc => $declaring_method_id) {
|
||||
$method_name = $codebase->getCasedMethodId($class_storage->name . '::' . $method_name_lc);
|
||||
$method_storage = $codebase->methods->getStorage($declaring_method_id);
|
||||
list($declaring_method_class, $declaring_method_name) = explode('::', $declaring_method_id);
|
||||
$declaring_class_storage = $codebase->classlike_storage_provider->get($declaring_method_class);
|
||||
|
||||
$declaring_class_node = $class_node;
|
||||
if ($declaring_class_storage->is_trait) {
|
||||
$declaring_class_node = $codebase->classlikes->getTraitNode($declaring_class_storage->name);
|
||||
}
|
||||
|
||||
if (!$method_storage->location) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$stmt_method = $class_node->getMethod($method_name);
|
||||
$stmt_method = $declaring_class_node->getMethod($declaring_method_name);
|
||||
|
||||
if (!$stmt_method) {
|
||||
throw new \RuntimeException('Failed to find ' . $method_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
$specials = self::getSpecials($stmt_method);
|
||||
|
||||
$method_id = $class_storage->name . '::' . $method_storage->cased_name;
|
||||
|
||||
if (0 !== strpos($method_storage->cased_name, 'test')
|
||||
if (0 !== strpos($method_name_lc, 'test')
|
||||
&& !isset($specials['test'])) {
|
||||
continue; // skip non-test methods
|
||||
}
|
||||
@ -148,23 +156,25 @@ class TestCaseHandler implements
|
||||
}
|
||||
|
||||
foreach ($specials['dataProvider'] as $line => $provider) {
|
||||
$provider_method_id = $class_storage->name . '::' . (string) $provider;
|
||||
|
||||
$provider_docblock_location = clone $method_storage->location;
|
||||
$provider_docblock_location->setCommentLine($line);
|
||||
|
||||
// methodExists also can mark methods as used (weird, but handy)
|
||||
if (!$codebase->methodExists($provider_method_id, $provider_docblock_location, $method_id)) {
|
||||
IssueBuffer::accepts(new Issue\UndefinedMethod(
|
||||
'Provider method ' . $provider_method_id . ' is not defined',
|
||||
$provider_docblock_location,
|
||||
$provider_method_id
|
||||
));
|
||||
$apparent_provider_method_name = $class_storage->name . '::' . (string) $provider;
|
||||
$provider_method_id = $codebase->getDeclaringMethodId($apparent_provider_method_name);
|
||||
|
||||
// methodExists also can mark methods as used (weird, but handy)
|
||||
if (null === $provider_method_id
|
||||
|| !$codebase->methodExists($provider_method_id, $provider_docblock_location, $declaring_method_id)
|
||||
) {
|
||||
IssueBuffer::accepts(new Issue\UndefinedMethod(
|
||||
'Provider method ' . $apparent_provider_method_name . ' is not defined',
|
||||
$provider_docblock_location,
|
||||
$apparent_provider_method_name
|
||||
));
|
||||
continue;
|
||||
}
|
||||
|
||||
$provider_return_type = $codebase->getMethodReturnType($provider_method_id, $classStorage->name);
|
||||
$provider_return_type = $codebase->getMethodReturnType($provider_method_id, $_);
|
||||
assert(null !== $provider_return_type);
|
||||
|
||||
$provider_return_type_string = $provider_return_type->getId();
|
||||
@ -228,7 +238,7 @@ class TestCaseHandler implements
|
||||
int $param_offset
|
||||
) use (
|
||||
$codebase,
|
||||
$method_id,
|
||||
$method_name,
|
||||
$provider_method_id,
|
||||
$provider_return_type_string,
|
||||
$provider_docblock_location
|
||||
@ -242,7 +252,7 @@ class TestCaseHandler implements
|
||||
// ok
|
||||
} elseif (self::canTypeBeContainedByType($codebase, $potential_argument_type, $param_type)) {
|
||||
IssueBuffer::accepts(new Issue\PossiblyInvalidArgument(
|
||||
'Argument ' . ($param_offset + 1) . ' of ' . $method_id
|
||||
'Argument ' . ($param_offset + 1) . ' of ' . $method_name
|
||||
. ' expects ' . $param_type->getId() . ', '
|
||||
. $potential_argument_type->getId() . ' provided'
|
||||
. ' by ' . $provider_method_id . '():(' . $provider_return_type_string . ')',
|
||||
@ -250,7 +260,7 @@ class TestCaseHandler implements
|
||||
));
|
||||
} elseif ($potential_argument_type->possibly_undefined && !$param->default_type) {
|
||||
IssueBuffer::accepts(new Issue\InvalidArgument(
|
||||
'Argument ' . ($param_offset + 1) . ' of ' . $method_id
|
||||
'Argument ' . ($param_offset + 1) . ' of ' . $method_name
|
||||
. ' has no default value, but possibly undefined '
|
||||
. $potential_argument_type->getId() . ' provided'
|
||||
. ' by ' . $provider_method_id . '():(' . $provider_return_type_string . ')',
|
||||
@ -258,7 +268,7 @@ class TestCaseHandler implements
|
||||
));
|
||||
} else {
|
||||
IssueBuffer::accepts(new Issue\InvalidArgument(
|
||||
'Argument ' . ($param_offset + 1) . ' of ' . $method_id
|
||||
'Argument ' . ($param_offset + 1) . ' of ' . $method_name
|
||||
. ' expects ' . $param_type->getId() . ', '
|
||||
. $potential_argument_type->getId() . ' provided'
|
||||
. ' by ' . $provider_method_id . '():(' . $provider_return_type_string . ')',
|
||||
@ -283,13 +293,13 @@ class TestCaseHandler implements
|
||||
|
||||
if (count($potential_argument_types) < $method_storage->required_param_count) {
|
||||
IssueBuffer::accepts(new Issue\TooFewArguments(
|
||||
'Too few arguments for ' . $method_id
|
||||
'Too few arguments for ' . $method_name
|
||||
. ' - expecting ' . $method_storage->required_param_count
|
||||
. ' but saw ' . count($potential_argument_types)
|
||||
. ' provided by ' . $provider_method_id . '()'
|
||||
. ':(' . $provider_return_type_string . ')',
|
||||
$provider_docblock_location,
|
||||
$method_id
|
||||
$method_name
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -747,3 +747,113 @@ Feature: TestCase
|
||||
"""
|
||||
When I run Psalm
|
||||
Then I see no errors
|
||||
|
||||
Scenario: Test methods in traits are not marked as unused
|
||||
Given I have the following code
|
||||
"""
|
||||
trait MyTestTrait {
|
||||
/** @return void */
|
||||
public function testSomething() {}
|
||||
}
|
||||
class MyTestCase extends TestCase {
|
||||
use MyTestTrait;
|
||||
}
|
||||
"""
|
||||
When I run Psalm with dead code detection
|
||||
Then I see no errors
|
||||
|
||||
Scenario: Inherited test methods are not marked as unused
|
||||
Given I have the following code
|
||||
"""
|
||||
abstract class IntermediateTest extends TestCase {
|
||||
/** @return void */
|
||||
public function testSomething() {}
|
||||
}
|
||||
class MyTestCase extends IntermediateTest {
|
||||
}
|
||||
"""
|
||||
When I run Psalm with dead code detection
|
||||
Then I see no errors
|
||||
|
||||
Scenario: Data providers in traits are not marked as unused
|
||||
Given I have the following code
|
||||
"""
|
||||
trait MyTestTrait {
|
||||
/** @return iterable<int,array{int}> */
|
||||
public function provide() { return [[1]]; }
|
||||
}
|
||||
class MyTestCase extends TestCase {
|
||||
use MyTestTrait;
|
||||
/**
|
||||
* @return void
|
||||
* @dataProvider provide
|
||||
*/
|
||||
public function testSomething(int $_i) {}
|
||||
}
|
||||
"""
|
||||
When I run Psalm with dead code detection
|
||||
Then I see no errors
|
||||
|
||||
Scenario: Data providers in test case when test methods are in trait are not marked as unused
|
||||
Given I have the following code
|
||||
"""
|
||||
trait MyTestTrait {
|
||||
/**
|
||||
* @return void
|
||||
* @dataProvider provide
|
||||
*/
|
||||
public function testSomething(int $_i) {}
|
||||
}
|
||||
class MyTestCase extends TestCase {
|
||||
use MyTestTrait;
|
||||
/** @return iterable<int,array{int}> */
|
||||
public function provide() { return [[1]]; }
|
||||
}
|
||||
"""
|
||||
When I run Psalm with dead code detection
|
||||
Then I see no errors
|
||||
|
||||
Scenario: Renamed imported test methods are validated
|
||||
Given I have the following code
|
||||
"""
|
||||
trait MyTestTrait {
|
||||
/**
|
||||
* @return void
|
||||
* @dataProvider provide
|
||||
*/
|
||||
public function foo(int $_i) {}
|
||||
}
|
||||
class MyTestCase extends TestCase {
|
||||
use MyTestTrait { foo as testAnything; }
|
||||
}
|
||||
"""
|
||||
When I run Psalm
|
||||
Then I see these errors
|
||||
| Type | Message |
|
||||
| UndefinedMethod | Provider method NS\MyTestCase::provide is not defined |
|
||||
And I see no other errors
|
||||
|
||||
Scenario: Test methods and providers in trait used by a test case are validated
|
||||
Given I have the following code
|
||||
"""
|
||||
trait MyTestTrait {
|
||||
/**
|
||||
* @return void
|
||||
* @dataProvider provide
|
||||
*/
|
||||
public function testSomething(string $_p) {}
|
||||
/**
|
||||
* @return iterable<int,int[]>
|
||||
*/
|
||||
public function provide() { return [[1]]; }
|
||||
}
|
||||
|
||||
class MyTestCase extends TestCase {
|
||||
use MyTestTrait;
|
||||
}
|
||||
"""
|
||||
When I run Psalm
|
||||
Then I see these errors
|
||||
| Type | Message |
|
||||
| InvalidArgument | Argument 1 of NS\MyTestTrait::testSomething expects string, int provided by NS\MyTestTrait::provide():(iterable<int, array<array-key, int>>) |
|
||||
And I see no other errors
|
||||
|
Loading…
Reference in New Issue
Block a user