diff --git a/hooks/TestCaseHandler.php b/hooks/TestCaseHandler.php index ce5ec1b..091708c 100644 --- a/hooks/TestCaseHandler.php +++ b/hooks/TestCaseHandler.php @@ -53,7 +53,17 @@ class TestCaseHandler implements AfterClassLikeVisitInterface, AfterClassLikeAna return null; } - /** @var MethodStorage $method_storage */ + // add a fake reference to test class to prevent it from being marked as unused + // it would have been easier to add a suppression, but that's only possible + // since 3.0.17 (vimeo/psalm#1353) + // + // This should always pass, we're calling it for the side-effect + // of adding self-reference + + if (!$codebase->classOrInterfaceExists($class_storage->name, $class_storage->location)) { + return null; + } + foreach ($class_storage->methods as $method_name => $method_storage) { if (!$method_storage->location) { continue; @@ -69,6 +79,13 @@ class TestCaseHandler implements AfterClassLikeVisitInterface, AfterClassLikeAna $method_id = $class_storage->name . '::' . $method_storage->cased_name; + if (0 !== strpos($method_storage->cased_name, 'test') + && !isset($specials['test'])) { + continue; // skip non-test methods + } + + $method_storage->suppressed_issues[] = 'PossiblyUnusedMethod'; + if (!isset($specials['dataProvider'])) { continue; } @@ -79,7 +96,8 @@ class TestCaseHandler implements AfterClassLikeVisitInterface, AfterClassLikeAna $provider_docblock_location = clone $method_storage->location; $provider_docblock_location->setCommentLine($line); - if (!$codebase->methodExists($provider_method_id)) { + // 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, diff --git a/tests/acceptance/TestCase.feature b/tests/acceptance/TestCase.feature index e4c9d39..31778ff 100644 --- a/tests/acceptance/TestCase.feature +++ b/tests/acceptance/TestCase.feature @@ -41,6 +41,7 @@ Feature: TestCase Then I see these errors | Type | Message | | InvalidArgument | Argument 1 of PHPUnit\Framework\TestCase::expectException expects class-string, NS\MyTestCase::class provided | + And I see no other errors Scenario: TestCase::expectException() accepts throwables Given I have the following code @@ -144,6 +145,7 @@ Feature: TestCase Then I see these errors | Type | Message | | MissingConstructor | NS\MyTestCase has an uninitialized variable $this->i, but no constructor | + And I see no other errors Scenario: Missing data provider is reported Given I have the following code @@ -192,6 +194,7 @@ Feature: TestCase Then I see these errors | Type | Message | | InvalidReturnType | Providers must return iterable>, iterable provided | + And I see no other errors Scenario: Valid iterable data provider is allowed Given I have the following code @@ -240,6 +243,7 @@ Feature: TestCase Then I see these errors | Type | Message | | InvalidReturnType | Providers must return iterable>, Generator provided | + And I see no other errors Scenario: Valid generator data provider is allowed Given I have the following code @@ -288,6 +292,7 @@ Feature: TestCase Then I see these errors | Type | Message | | InvalidReturnType | Providers must return iterable>, array provided | + And I see no other errors Scenario: Valid array data provider is allowed Given I have the following code @@ -363,6 +368,7 @@ Feature: TestCase Then I see these errors | Type | Message | | InvalidArgument | Argument 1 of NS\MyTestCase::testSomething expects int, string provided by NS\MyTestCase::provide():(iterable) | + And I see no other errors Scenario: Invalid dataset array is reported Given I have the following code @@ -388,6 +394,7 @@ Feature: TestCase Then I see these errors | Type | Message | | PossiblyInvalidArgument | Argument 1 of NS\MyTestCase::testSomething expects int, string\|int provided by NS\MyTestCase::provide():(iterable>) | + And I see no other errors Scenario: Shape dataset with missing params is reported Given I have the following code @@ -413,3 +420,119 @@ Feature: TestCase Then I see these errors | Type | Message | | TooFewArguments | Too few arguments for NS\MyTestCase::testSomething - expecting 2 but saw 1 provided by NS\MyTestCase::provide():(iterable) | + And I see no other errors + + Scenario: Referenced providers are not marked as unused + Given I have the following code + """ + class MyTestCase extends TestCase + { + /** @return iterable */ + public function provide() { + yield "data set name" => [1]; + } + /** + * @return void + * @psalm-suppress UnusedMethod + * @dataProvider provide + */ + public function testSomething(int $int) { + $this->assertEquals(1, $int); + } + } + new MyTestCase; + """ + When I run Psalm with dead code detection + Then I see no errors + + Scenario: Unreferenced providers are marked as unused + Given I have the following code + """ + class MyTestCase extends TestCase + { + /** @return iterable */ + public function provide() { + yield "data set name" => [1]; + } + /** + * @return void + * @psalm-suppress UnusedMethod + */ + public function testSomething(int $int) { + $this->assertEquals(1, $int); + } + } + new MyTestCase; + """ + When I run Psalm with dead code detection + Then I see these errors + | Type | Message | + | PossiblyUnusedMethod | Cannot find public calls to method NS\MyTestCase::provide | + And I see no other errors + + Scenario: Test method are never marked as unused + Given I have the following code + """ + class MyTestCase extends TestCase + { + /** + * @return void + */ + public function testSomething(int $int) { + $this->assertEquals(1, $int); + } + /** + * @return void + * @test + */ + public function somethingElse(int $int) { + $this->assertEquals(1, $int); + } + } + new MyTestCase; + """ + When I run Psalm with dead code detection + Then I see no errors + + Scenario: Unreferenced non-test methods are marked as unused + Given I have the following code + """ + class MyTestCase extends TestCase + { + /** + * @return void + */ + public function somethingElse(int $int) { + $this->assertEquals(1, $int); + } + } + new MyTestCase; + """ + When I run Psalm with dead code detection + Then I see these errors + | Type | Message | + | PossiblyUnusedMethod | Cannot find public calls to method NS\MyTestCase::somethingElse | + And I see no other errors + + Scenario: Unreferenced TestCase descendants are never marked as unused + Given I have the following code + """ + class MyTestCase extends TestCase + { + } + """ + When I run Psalm with dead code detection + Then I see no errors + + Scenario: Unreferenced non-test classes are marked as unused + Given I have the following code + """ + class UtilityClass + { + } + """ + When I run Psalm with dead code detection + Then I see these errors + | Type | Message | + | UnusedClass | Class NS\UtilityClass is never used | + And I see no other errors