Skip to content

Commit

Permalink
Added support for external providers
Browse files Browse the repository at this point in the history
Fixes #44
  • Loading branch information
weirdan committed Jan 10, 2020
1 parent a577f90 commit fc18507
Show file tree
Hide file tree
Showing 2 changed files with 177 additions and 4 deletions.
34 changes: 30 additions & 4 deletions hooks/TestCaseHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,27 @@ public static function afterStatementAnalysis(
$provider_docblock_location = clone $method_storage->location;
$provider_docblock_location->setCommentLine($line);

$apparent_provider_method_name = $class_storage->name . '::' . (string) $provider;
if (false !== strpos($provider, '::')) {
[$class_name, $method_id] = explode('::', $provider);
$fq_class_name = Type::getFQCLNFromString($class_name, $statements_source->getAliases());

if (!$codebase->classOrInterfaceExists($fq_class_name, $provider_docblock_location)) {
IssueBuffer::accepts(new Issue\UndefinedClass(
'Class ' . $fq_class_name . ' does not exist',
$provider_docblock_location,
$fq_class_name
));
continue;
}
$apparent_provider_method_name = $fq_class_name . '::' . $method_id;
} else {
$apparent_provider_method_name = $class_storage->name . '::' . (string) $provider;
}

$apparent_provider_method_name = preg_replace('/\(\s*\)$/', '', $apparent_provider_method_name);

$provider_method_id = $codebase->getDeclaringMethodId($apparent_provider_method_name);

// methodExists also can mark methods as used (weird, but handy)
if (null === $provider_method_id) {
IssueBuffer::accepts(new Issue\UndefinedMethod(
'Provider method ' . $apparent_provider_method_name . ' is not defined',
Expand All @@ -158,6 +175,7 @@ public static function afterStatementAnalysis(
continue;
}

// methodExists also can mark methods as used (weird, but handy)
$provider_method_exists = $codebase->methodExists(
$provider_method_id,
$provider_docblock_location,
Expand Down Expand Up @@ -301,7 +319,7 @@ function (
}
};

/** @var Type\Atomic\TArray|Type\Atomic\ObjectLike $dataset_type */
/** @var Type\Atomic\TArray|Type\Atomic\ObjectLike|Type\Atomic\TList $dataset_type */
$dataset_type = $provider_return_type->type_params[1]->getTypes()['array'];

if ($dataset_type instanceof Type\Atomic\TArray) {
Expand All @@ -313,7 +331,15 @@ function (
}
$checkParam($potential_argument_type, $param->type, $param->is_optional, $param_offset);
}
} else {
} elseif ($dataset_type instanceof Type\Atomic\TList) {
$potential_argument_type = $dataset_type->type_param;
foreach ($method_storage->params as $param_offset => $param) {
if (!$param->type) {
continue;
}
$checkParam($potential_argument_type, $param->type, $param->is_optional, $param_offset);
}
} else { // ObjectLike
// iterate over all params checking if corresponding value type is acceptable
// let's hope properties are sorted in array order
$potential_argument_types = array_values($dataset_type->properties);
Expand Down
147 changes: 147 additions & 0 deletions tests/acceptance/TestCase.feature
Original file line number Diff line number Diff line change
Expand Up @@ -1062,3 +1062,150 @@ Feature: TestCase
"""
When I run Psalm with dead code detection
Then I see no errors


@ExternalProviders
Scenario: External providers are allowed
Given I have the following code
"""
class External {
/** @return iterable<string, array<int,int>> */
public function provide(): iterable {
yield "dataset name" => [1];
}
}
class MyTestCase extends TestCase {
/** @dataProvider External::provide */
public function testSomething(int $_p): void {}
}
"""
When I run Psalm
Then I see no errors

@ExternalProviders
Scenario: External providers with parens are allowed
Given I have the following code
"""
class External {
/** @return iterable<string, array<int,int>> */
public function provide(): iterable {
yield "dataset name" => [1];
}
}
class MyTestCase extends TestCase {
/** @dataProvider External::provide() */
public function testSomething(int $_p): void {}
}
"""
When I run Psalm
Then I see no errors

@ExternalProviders
Scenario: External fully qualified providers are allowed
Given I have the following code
"""
class External {
/** @return iterable<string, array<int,int>> */
public function provide(): iterable {
yield "dataset name" => [1];
}
}
class MyTestCase extends TestCase {
/** @dataProvider \NS\External::provide */
public function testSomething(int $_p): void {}
}
"""
When I run Psalm
Then I see no errors

@ExternalProviders
Scenario: Missing external provider classes are reported
Given I have the following code
"""
class MyTestCase extends TestCase {
/** @dataProvider External::provide */
public function testSomething(int $_p): void {}
}
"""
When I run Psalm
Then I see these errors
| Type | Message |
| UndefinedClass | Class NS\External does not exist |


@ExternalProviders
Scenario: External providers are not marked as unused
Given I have the following code
"""
class External {
/** @return iterable<string, array<int,int>> */
public function provide(): iterable {
yield "dataset name" => [1];
}
}
class MyTestCase extends TestCase {
/** @dataProvider External::provide */
public function testSomething(int $_p): void {}
}
"""
When I run Psalm with dead code detection
Then I see no errors

@ExternalProviders
Scenario: Mismatched external providers are reported
Given I have the following code
"""
class External {
/** @return iterable<string, array<int,string>> */
public function provide(): iterable {
yield "dataset name" => ["1"];
}
}
class MyTestCase extends TestCase {
/** @dataProvider External::provide */
public function testSomething(int $_p): void {}
}
"""
When I run Psalm
Then I see these errors
| Type | Message |
| InvalidArgument | Argument 1 of NS\MyTestCase::testSomething expects int, string provided by NS\External::provide():(iterable<string, array<int, string>>) |

@List
Scenario: Providers returning list are ok
Given I have the following code
"""
class MyTestCase extends TestCase {
/** @return iterable<string, list<int>> */
public function provide(): iterable {
yield "dataset name" => [1];
}
/** @dataProvider provide */
public function testSomething(int $_p): void {}
}
"""
When I run Psalm
Then I see no errors

@List
Scenario: Providers returning mismatching list are reported
Given I have the following code
"""
class MyTestCase extends TestCase {
/** @return iterable<string, list<string>> */
public function provide(): iterable {
yield "dataset name" => ["1"];
}
/** @dataProvider provide */
public function testSomething(int $_p): void {}
}
"""
When I run Psalm
Then I see these errors
| Type | Message |
| InvalidArgument | Argument 1 of NS\MyTestCase::testSomething expects int, string provided by NS\MyTestCase::provide():(iterable<string, list<string>>) |

0 comments on commit fc18507

Please sign in to comment.