diff --git a/composer.json b/composer.json index ee083a10..f5231d66 100644 --- a/composer.json +++ b/composer.json @@ -55,6 +55,7 @@ ] }, "require-dev": { + "mrpunyapal/peststan": "^0.2.5", "pestphp/pest-dev-tools": "^4.1.0", "pestphp/pest-plugin-browser": "^4.3.1", "pestphp/pest-plugin-type-coverage": "^4.0.4", diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 99bbdec9..8374d3bd 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,11 +1,5 @@ parameters: ignoreErrors: - - - message: '#^Parameter \#1 of callable callable\(Pest\\Expectation\\)\: Pest\\Arch\\Contracts\\ArchExpectation expects Pest\\Expectation\, Pest\\Expectation\ given\.$#' - identifier: argument.type - count: 1 - path: src/ArchPresets/AbstractPreset.php - - message: '#^Trait Pest\\Concerns\\Expectable is used zero times and is not analysed\.$#' identifier: trait.unused @@ -24,12 +18,6 @@ parameters: count: 1 path: src/Concerns/Testable.php - - - message: '#^Loose comparison using \!\= between \(Closure\|null\) and false will always evaluate to false\.$#' - identifier: notEqual.alwaysFalse - count: 1 - path: src/Expectation.php - - message: '#^Method Pest\\Expectation\:\:and\(\) should return Pest\\Expectation\ but returns \(Pest\\Expectation&TAndValue\)\|Pest\\Expectation\\.$#' identifier: return.type @@ -102,78 +90,12 @@ parameters: count: 1 path: src/PendingCalls/TestCall.php - - - message: '#^Parameter \#1 \$argv of class Symfony\\Component\\Console\\Input\\ArgvInput constructor expects list\\|null, array\ given\.$#' - identifier: argument.type - count: 1 - path: src/Plugins/Parallel.php - - - - message: '#^Parameter \#13 \$testRunnerTriggeredDeprecationEvents of class PHPUnit\\TestRunner\\TestResult\\TestResult constructor expects list\, array given\.$#' - identifier: argument.type - count: 1 - path: src/Plugins/Parallel/Paratest/WrapperRunner.php - - - - message: '#^Parameter \#14 \$testRunnerTriggeredWarningEvents of class PHPUnit\\TestRunner\\TestResult\\TestResult constructor expects list\, array given\.$#' - identifier: argument.type - count: 1 - path: src/Plugins/Parallel/Paratest/WrapperRunner.php - - - - message: '#^Parameter \#15 \$errors of class PHPUnit\\TestRunner\\TestResult\\TestResult constructor expects list\, array given\.$#' - identifier: argument.type - count: 1 - path: src/Plugins/Parallel/Paratest/WrapperRunner.php - - - - message: '#^Parameter \#16 \$deprecations of class PHPUnit\\TestRunner\\TestResult\\TestResult constructor expects list\, array given\.$#' - identifier: argument.type - count: 1 - path: src/Plugins/Parallel/Paratest/WrapperRunner.php - - - - message: '#^Parameter \#17 \$notices of class PHPUnit\\TestRunner\\TestResult\\TestResult constructor expects list\, array given\.$#' - identifier: argument.type - count: 1 - path: src/Plugins/Parallel/Paratest/WrapperRunner.php - - - - message: '#^Parameter \#18 \$warnings of class PHPUnit\\TestRunner\\TestResult\\TestResult constructor expects list\, array given\.$#' - identifier: argument.type - count: 1 - path: src/Plugins/Parallel/Paratest/WrapperRunner.php - - - - message: '#^Parameter \#19 \$phpDeprecations of class PHPUnit\\TestRunner\\TestResult\\TestResult constructor expects list\, array given\.$#' - identifier: argument.type - count: 1 - path: src/Plugins/Parallel/Paratest/WrapperRunner.php - - - - message: '#^Parameter \#20 \$phpNotices of class PHPUnit\\TestRunner\\TestResult\\TestResult constructor expects list\, array given\.$#' - identifier: argument.type - count: 1 - path: src/Plugins/Parallel/Paratest/WrapperRunner.php - - - - message: '#^Parameter \#21 \$phpWarnings of class PHPUnit\\TestRunner\\TestResult\\TestResult constructor expects list\, array given\.$#' - identifier: argument.type - count: 1 - path: src/Plugins/Parallel/Paratest/WrapperRunner.php - - message: '#^Parameter \#4 \$testErroredEvents of class PHPUnit\\TestRunner\\TestResult\\TestResult constructor expects list\, array given\.$#' identifier: argument.type count: 1 path: src/Plugins/Parallel/Paratest/WrapperRunner.php - - - message: '#^Parameter \#5 \$testFailedEvents of class PHPUnit\\TestRunner\\TestResult\\TestResult constructor expects list\, array given\.$#' - identifier: argument.type - count: 1 - path: src/Plugins/Parallel/Paratest/WrapperRunner.php - - message: '#^Parameter \#7 \$testSuiteSkippedEvents of class PHPUnit\\TestRunner\\TestResult\\TestResult constructor expects list\, array given\.$#' identifier: argument.type diff --git a/phpstan-pest-extension.neon b/phpstan-pest-extension.neon new file mode 100644 index 00000000..af649b7f --- /dev/null +++ b/phpstan-pest-extension.neon @@ -0,0 +1,5 @@ +services: + - + class: Pest\PHPStan\HigherOrderExpectationTypeExtension + tags: + - phpstan.broker.expressionTypeResolverExtension diff --git a/phpstan.neon b/phpstan.neon index 391daf0b..19f6ae4e 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,5 +1,7 @@ includes: - phpstan-baseline.neon + - phpstan-pest-extension.neon + - vendor/mrpunyapal/peststan/extension.neon parameters: level: 7 @@ -7,6 +9,3 @@ parameters: - src reportUnmatchedIgnoredErrors: false - - ignoreErrors: - - "#type mixed is not subtype of native#" diff --git a/src/ArchPresets/AbstractPreset.php b/src/ArchPresets/AbstractPreset.php index 3b612812..0dc9ce30 100644 --- a/src/ArchPresets/AbstractPreset.php +++ b/src/ArchPresets/AbstractPreset.php @@ -53,7 +53,7 @@ abstract class AbstractPreset // @pest-arch-ignore-line /** * Runs the given callback for each namespace. * - * @param callable(Expectation): ArchExpectation ...$callbacks + * @param callable(Expectation): ArchExpectation ...$callbacks */ final public function eachUserNamespace(callable ...$callbacks): void { diff --git a/src/Functions.php b/src/Functions.php index 42331a56..1cf97fcc 100644 --- a/src/Functions.php +++ b/src/Functions.php @@ -4,7 +4,6 @@ declare(strict_types=1); use Pest\Browser\Api\ArrayablePendingAwaitablePage; use Pest\Browser\Api\PendingAwaitablePage; -use Pest\Concerns\Expectable; use Pest\Configuration; use Pest\Exceptions\AfterAllWithinDescribe; use Pest\Exceptions\BeforeAllWithinDescribe; @@ -62,8 +61,6 @@ if (! function_exists('beforeEach')) { * Runs the given closure before each test in the current file. * * @param-closure-this TestCase $closure - * - * @return HigherOrderTapProxy|Expectable|TestCall|TestCase|mixed */ function beforeEach(?Closure $closure = null): BeforeEachCall { @@ -92,8 +89,6 @@ if (! function_exists('describe')) { * Adds the given closure as a group of tests. The first argument * is the group description; the second argument is a closure * that contains the group tests. - * - * @return HigherOrderTapProxy|Expectable|TestCall|TestCase|mixed */ function describe(string $description, Closure $tests): DescribeCall { @@ -136,7 +131,7 @@ if (! function_exists('test')) { * * @param-closure-this TestCase $closure * - * @return Expectable|TestCall|TestCase|mixed + * @return ($description is string ? TestCall : HigherOrderTapProxy|TestCall) */ function test(?string $description = null, ?Closure $closure = null): HigherOrderTapProxy|TestCall { @@ -157,33 +152,22 @@ if (! function_exists('it')) { * a closure that contains the test expectations. * * @param-closure-this TestCase $closure - * - * @return Expectable|TestCall|TestCase|mixed */ function it(string $description, ?Closure $closure = null): TestCall { $description = sprintf('it %s', $description); - /** @var TestCall $test */ - $test = test($description, $closure); - - return $test; + return test($description, $closure); } } if (! function_exists('todo')) { /** * Creates a new test that is marked as "todo". - * - * @return Expectable|TestCall|TestCase|mixed */ function todo(string $description): TestCall { - $test = test($description); - - assert($test instanceof TestCall); - - return $test->todo(); + return test($description)->todo(); } } @@ -192,8 +176,6 @@ if (! function_exists('afterEach')) { * Runs the given closure after each test in the current file. * * @param-closure-this TestCase $closure - * - * @return Expectable|HigherOrderTapProxy|TestCall|mixed */ function afterEach(?Closure $closure = null): AfterEachCall { diff --git a/src/PHPStan/HigherOrderExpectationTypeExtension.php b/src/PHPStan/HigherOrderExpectationTypeExtension.php new file mode 100644 index 00000000..7b7cdfec --- /dev/null +++ b/src/PHPStan/HigherOrderExpectationTypeExtension.php @@ -0,0 +1,57 @@ +name instanceof Identifier) { + return null; + } + + $varType = $scope->getType($expr->var); + + if (! (new ObjectType(HigherOrderExpectation::class))->isSuperTypeOf($varType)->yes()) { + return null; + } + + if (! $this->reflectionProvider->hasClass(HigherOrderExpectation::class)) { + return null; + } + + $propertyName = $expr->name->name; + $classReflection = $this->reflectionProvider->getClass(HigherOrderExpectation::class); + + if (! $classReflection->hasNativeProperty($propertyName)) { + return null; + } + + return $varType->getProperty($propertyName, $scope)->getReadableType(); + } +} diff --git a/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap b/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap index 75439fc3..f0e77b0d 100644 --- a/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap +++ b/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap @@ -130,6 +130,8 @@ CODE COVERAGE OPTIONS: --coverage ..... Generate code coverage report and output to standard output --coverage --min Set the minimum required coverage percentage, and fail if not met + --coverage --exactly Set the exact required coverage percentage, and fail if not met + --coverage --only-covered Hide files with 0% coverage from the code coverage report --coverage-clover [file] Write code coverage report in Clover XML format to file --coverage-openclover [file] Write code coverage report in OpenClover XML format to file --coverage-cobertura [file] Write code coverage report in Cobertura XML format to file