From 6b519262e2c0e6b59a13294b1efbed8691d8d191 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Baptiste=20Clavi=C3=A9?= Date: Wed, 5 Feb 2020 10:22:52 +0100 Subject: [PATCH 1/3] Remove redundant steps for php matcher context Use the full json instead... --- src/PhpMatcher/JsonContext.php | 76 +++------------------------------- 1 file changed, 6 insertions(+), 70 deletions(-) diff --git a/src/PhpMatcher/JsonContext.php b/src/PhpMatcher/JsonContext.php index dfcafc8..a1fd4fb 100644 --- a/src/PhpMatcher/JsonContext.php +++ b/src/PhpMatcher/JsonContext.php @@ -1,20 +1,15 @@ history = $history; $this->factory = new MatcherFactory; - $this->accessor = PropertyAccess::createPropertyAccessor(); } /** @Then the root should match: */ @@ -39,73 +30,18 @@ final public function root_should_match(PyStringNode $pattern): void { $matcher = $this->factory->createMatcher(); - if ($matcher->match($this->getJson(), $pattern->getRaw())) { + if ($matcher->match((string) $this->history->getLastResponse()->getBody(), $pattern->getRaw())) { return; } - throw new InvalidArgumentException( - sprintf( - 'The json root does not match with the given pattern (error : %s)', - $matcher->getError() - ) - ); - } - - /** @Then in the json, :path should match: */ - final public function path_should_match(string $path, PyStringNode $pattern): void - { - $value = $this->getValue($path); - $matcher = $this->factory->createMatcher(); - - $json = json_encode($value); - - if ($matcher->match($json, $pattern->getRaw())) { - return; - } - - throw new InvalidArgumentException( - sprintf( - 'The json path "%s" does not match with the given pattern (error : %s)', - $path, - $matcher->getError() - ) - ); - } - - /** @Then in the json, :path should not match: */ - final public function path_should_not_match(string $path, PyStringNode $pattern): void - { - $value = $this->getValue($path); - $matcher = $this->factory->createMatcher(); - - $json = json_encode($value); - - if (!$matcher->match($json, $pattern->getRaw())) { - return; - } + $error = $matcher->getError(); + assert(is_string($error)); throw new InvalidArgumentException( sprintf( - 'The json path "%s" matches with the given pattern (error : %s)', - $path, - $matcher->getError() + 'The json root does not match with the given pattern (error : %s)', + $error ) ); } - - private function getJson(): ?stdClass - { - return json_decode((string) $this->history->getLastResponse()->getBody()); - } - - private function getValue(string $path) - { - $json = $this->getJson(); - - if (null === $json) { - throw new InvalidArgumentException('Expected a Json valid content, got none'); - } - - return $this->accessor->getValue($json, $path); - } } From 8fb32f9055adc6454ad9bd753e95a4a7b34903bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Baptiste=20Clavi=C3=A9?= Date: Tue, 4 Feb 2020 17:19:33 +0100 Subject: [PATCH 2/3] s/phpstan/psalm --- .github/workflows/ci.yml | 7 +++++-- README.md | 4 ++++ composer.json | 4 +--- phpstan.neon | 19 ------------------- psalm.xml | 19 +++++++++++++++++++ 5 files changed, 29 insertions(+), 24 deletions(-) delete mode 100644 phpstan.neon create mode 100644 psalm.xml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 87eacf1..e9683dc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,5 +17,8 @@ jobs: - name: Install dependencies run: composer install --prefer-dist --no-progress --no-suggest - - name: PHPStan - run: vendor/bin/phpstan analyze + - name: Install Psalm + run: composer require --dev vimeo/psalm:dev-master@dev --no-suggest + + - name: Psalm + run: vendor/bin/psalm --output-format=github --shepherd diff --git a/README.md b/README.md index defbfb2..3370fc4 100644 --- a/README.md +++ b/README.md @@ -92,3 +92,7 @@ features and integration tests. Special thanks goes to [@lunika](https://github.com/lunika), [@rgazelot](https://github.com/rgazelot) and [@krichprollsch](https://github.com/krichprollsch), who helped conceived this extension, and also pushed me to open-source it. + +Badges +------ +![Psalm Shepherd](https://shepherd.dev/github/Taluu/Behapi/coverage.svg) diff --git a/composer.json b/composer.json index 666de43..e1d1388 100644 --- a/composer.json +++ b/composer.json @@ -62,8 +62,6 @@ "symfony/http-client": "^4.3 || ^5.0", "symfony/var-dumper": "^2.8 || ^3.3 || ^4.0", - "phpstan/phpstan": "^0.10", - "phpstan/phpstan-strict-rules": "^0.10", - "phpstan/phpstan-beberlei-assert": "^0.10" + "vimeo/psalm": "^3.8" } } diff --git a/phpstan.neon b/phpstan.neon deleted file mode 100644 index 87e21fa..0000000 --- a/phpstan.neon +++ /dev/null @@ -1,19 +0,0 @@ -parameters: - level: max - paths: - - src - - ignoreErrors: - # have to ignore it, because Symfony config works like that and I'm waaay - # too lazy to properly structure the node builders just to add some asserts - # and stuff like that for (almost) every line. - - '{^Cannot call method arrayNode\\() on Symfony\Component\Config\Definition\Builder\NodeParentInterface|null.$}' - - # have to ignore it until php 7.4, which introduces (co)vrariance on arguments - # and return hint, as beberlei/assert introduced a tiny bc break making it - # impossible to properly extend its classes until php 7.4 - - '{^Call to an undefined method Assert\\AssertionChain::not().}' - - includes: - - vendor/phpstan/phpstan-strict-rules/rules.neon - - vendor/phpstan/phpstan-beberlei-assert/extension.neon diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 0000000..e2cedae --- /dev/null +++ b/psalm.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + From cdfd0d94ee2d58ed67dede574b85cabe06f0c252 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Baptiste=20Clavi=C3=A9?= Date: Wed, 5 Feb 2020 15:12:13 +0100 Subject: [PATCH 3/3] Pass psalm on lvl 2 Level 1 (totallyType) is bothersome because of mixed things (which are actually intended). :{ Adding multiple psalm suppress is bothersome too, and beberlei is not (yet ?) compatible with psalm... --- src/Assert/Assertion.php | 8 +++-- src/Behapi.php | 19 ++++++------ src/Container.php | 5 +-- src/Debug/CliController.php | 6 ++-- src/Debug/Listener.php | 8 +++-- src/Debug/Status.php | 2 +- src/HttpHistory/History.php | 1 + src/HttpHistory/Listener.php | 3 +- src/Json/Context.php | 24 ++++++++++++-- src/Json/EachInCollectionTrait.php | 50 +++++++++++++++++++++++++++--- 10 files changed, 97 insertions(+), 29 deletions(-) diff --git a/src/Assert/Assertion.php b/src/Assert/Assertion.php index 4ded19b..05a2318 100644 --- a/src/Assert/Assertion.php +++ b/src/Assert/Assertion.php @@ -15,8 +15,12 @@ */ abstract class Assertion extends Beberlei\Assertion { - /** @return bool */ - public static function empty($value, $message = null, ?string $propertyPath = null): bool + /** + * @param mixed $value + * @param string|null $message + * @return bool + */ + public static function empty($value, ?string $message = null, ?string $propertyPath = null): bool { return parent::noContent($value, $message, $propertyPath); } diff --git a/src/Behapi.php b/src/Behapi.php index 0c515df..bd811d0 100644 --- a/src/Behapi.php +++ b/src/Behapi.php @@ -24,14 +24,16 @@ final class Behapi implements Extension { const DEBUG_INTROSPECTION_TAG = 'behapi.debug.introspection'; - /** {@inheritDoc} */ - public function getConfigKey() + public function getConfigKey(): string { return 'behapi'; } - /** {@inheritDoc} */ - public function configure(ArrayNodeDefinition $builder) + /** + * @psalm-suppress PossiblyUndefinedMethod + * @psalm-suppress PossiblyNullReference + */ + public function configure(ArrayNodeDefinition $builder): void { $builder ->children() @@ -89,13 +91,11 @@ public function configure(ArrayNodeDefinition $builder) } - /** {@inheritDoc} */ - public function initialize(ExtensionManager $extensionManager) + public function initialize(ExtensionManager $extensionManager): void { } - /** {@inheritDoc} */ - public function load(ContainerBuilder $container, array $config) + public function load(ContainerBuilder $container, array $config): void { $container->register(HttpHistory\History::class, HttpHistory\History::class) ->setPublic(false) @@ -112,8 +112,7 @@ public function load(ContainerBuilder $container, array $config) $this->loadContainer($container, $config); } - /** {@inheritDoc} */ - public function process(ContainerBuilder $container) + public function process(ContainerBuilder $container): void { $dumpers = []; diff --git a/src/Container.php b/src/Container.php index 8aa7b42..fea61c7 100644 --- a/src/Container.php +++ b/src/Container.php @@ -72,12 +72,13 @@ private function getPluginClientBuilder(): PluginClientBuilder $uriFactory = Psr17FactoryDiscovery::findUrlFactory(); $baseUri = $uriFactory->createUri($this->baseUrl); + $httpHistory = $this->services[HttpHistory::class]; - assert($this->services[HttpHistory::class] instanceof HttpHistory); + assert($httpHistory instanceof HttpHistory); $builder = $builder->addPlugin(new ContentLengthPlugin); $builder = $builder->addPlugin(new BaseUriPlugin($baseUri)); - $builder = $builder->addPlugin(new HistoryPlugin($this->services[HttpHistory::class])); + $builder = $builder->addPlugin(new HistoryPlugin($httpHistory)); return $builder; } diff --git a/src/Debug/CliController.php b/src/Debug/CliController.php index 911b3f8..d24576f 100644 --- a/src/Debug/CliController.php +++ b/src/Debug/CliController.php @@ -19,19 +19,19 @@ public function __construct(Status $status) $this->status = $status; } - /** {@inheritDoc} */ - public function configure(Command $command) + public function configure(Command $command): void { $command ->addOption('behapi-debug', null, InputOption::VALUE_NONE, 'Activates the debug mode for behapi'); } - /** {@inheritDoc} */ public function execute(InputInterface $input, OutputInterface $output) { $debug = $input->getOption('behapi-debug'); assert(is_bool($debug)); $this->status->setEnabled($debug); + + return null; } } diff --git a/src/Debug/Listener.php b/src/Debug/Listener.php index 7463050..28df9de 100644 --- a/src/Debug/Listener.php +++ b/src/Debug/Listener.php @@ -48,8 +48,7 @@ public function __construct(Status $status, HttpHistory $history, array $adapter $this->status = $status; } - /** {@inheritDoc} */ - public static function getSubscribedEvents() + public static function getSubscribedEvents(): array { return [ ExampleTested::AFTER => 'debugAfter', @@ -101,6 +100,11 @@ private function debug(MessageInterface $message): void } } + /** + * @psalm-suppress InvalidReturnType + * @psalm-suppress InvalidReturnStatement + * @psalm-suppress UndefinedDocblockClass + */ private function hasTag(ScenarioLikeTested $event, string $tag): bool { $node = $event->getNode(); diff --git a/src/Debug/Status.php b/src/Debug/Status.php index 6a768bf..89432a0 100644 --- a/src/Debug/Status.php +++ b/src/Debug/Status.php @@ -11,7 +11,7 @@ final class Status /** @var bool */ private $enabled = false; - public function setEnabled(bool $enabled) + public function setEnabled(bool $enabled): void { $this->enabled = $enabled; } diff --git a/src/HttpHistory/History.php b/src/HttpHistory/History.php index ea79b7f..bbf0cce 100644 --- a/src/HttpHistory/History.php +++ b/src/HttpHistory/History.php @@ -53,6 +53,7 @@ public function getLastResponse(): ResponseInterface return $response; } + /** @return Generator */ public function getTuples(): Generator { yield from $this->tuples; diff --git a/src/HttpHistory/Listener.php b/src/HttpHistory/Listener.php index c941e9a..6af7b95 100644 --- a/src/HttpHistory/Listener.php +++ b/src/HttpHistory/Listener.php @@ -22,8 +22,7 @@ public function __construct(History $history) $this->history = $history; } - /** {@inheritDoc} */ - public static function getSubscribedEvents() + public static function getSubscribedEvents(): array { return [ ExampleTested::AFTER => ['clear', -99], diff --git a/src/Json/Context.php b/src/Json/Context.php index a08e9e4..7852ca7 100644 --- a/src/Json/Context.php +++ b/src/Json/Context.php @@ -13,6 +13,7 @@ use Symfony\Component\PropertyAccess\PropertyAccessor; use Behapi\Assert\Assert; +use Behapi\Assert\AssertionChain; use Behapi\HttpHistory\History as HttpHistory; use function sprintf; @@ -32,12 +33,13 @@ class Context implements BehatContext /** @var HttpHistory */ private $history; - /** @var string[] */ + /** @var list */ private $contentTypes; /** @var PropertyAccessor */ private $accessor; + /** @param list $contentTypes */ public function __construct(HttpHistory $history, array $contentTypes = ['application/json']) { $this->accessor = PropertyAccess::createPropertyAccessor(); @@ -63,6 +65,7 @@ final public function path_should_be_readable(string $path, ?string $not = null) $assert = Assert::that($this->accessor->isReadable($this->getValue(null), $path)); if ($not !== null) { + assert($assert instanceof AssertionChain); $assert = $assert->not(); } @@ -70,6 +73,8 @@ final public function path_should_be_readable(string $path, ?string $not = null) } /** + * @param mixed $expected + * * @Then in the json, :path should be equal to :expected * @Then in the json, :path should :not be equal to :expected */ @@ -78,6 +83,7 @@ final public function the_json_path_should_be_equal_to(string $path, ?string $no $assert = Assert::that($this->getValue($path)); if ($not !== null) { + assert($assert instanceof AssertionChain); $assert = $assert->not(); } @@ -93,6 +99,7 @@ final public function the_json_path_should_be_py_string(string $path, ?string $n $assert = Assert::that($this->getValue($path)); if ($not !== null) { + assert($assert instanceof AssertionChain); $assert = $assert->not(); } @@ -108,6 +115,7 @@ final public function the_json_path_should_be(string $path, ?string $not = null, $assert = Assert::that($this->getValue($path)); if ($not !== null) { + assert($assert instanceof AssertionChain); $assert = $assert->not(); } @@ -123,6 +131,7 @@ final public function the_json_path_should_be_null(string $path, ?string $not = $assert = Assert::that($this->getValue($path)); if ($not !== null) { + assert($assert instanceof AssertionChain); $assert = $assert->not(); } @@ -136,6 +145,7 @@ final public function the_json_path_should_be_null(string $path, ?string $not = final public function the_json_path_should_be_empty(string $path, ?string $not = null): void { $assert = Assert::that($this->getValue($path)); + assert($assert instanceof AssertionChain); if ($not !== null) { $assert = $assert->not(); @@ -145,6 +155,8 @@ final public function the_json_path_should_be_empty(string $path, ?string $not = } /** + * @param mixed $expected + * * @Then in the json, :path should contain :expected * @Then in the json, :path should :not contain :expected */ @@ -153,13 +165,18 @@ final public function the_json_path_contains(string $path, ?string $not = null, $assert = Assert::that($this->getValue($path)); if ($not !== null) { + assert($assert instanceof AssertionChain); $assert = $assert->not(); } $assert->contains($expected); } - /** @Then in the json, :path collection should contain an element with :value equal to :expected */ + /** + * @param mixed $expected + * + * @Then in the json, :path collection should contain an element with :value equal to :expected + */ final public function the_json_path_collection_contains(string $path, string $value, $expected): void { $collection = $this->getValue($path); @@ -215,7 +232,7 @@ final public function the_json_path_should_be_a_valid_json_encoded_string(string * overwrite it if you want to add supplementary checks or use something * else instead (such as Seldaek's JsonLint package). */ - public function response_should_be_a_valid_json_response() + public function response_should_be_a_valid_json_response(): void { $this->getValue(null); @@ -239,6 +256,7 @@ final public function the_json_path_should_match(string $path, ?string $not = nu $assert = Assert::that($this->getValue($path)); if ($not !== null) { + assert($assert instanceof AssertionChain); $assert = $assert->not(); } diff --git a/src/Json/EachInCollectionTrait.php b/src/Json/EachInCollectionTrait.php index ccdce6a..f679727 100644 --- a/src/Json/EachInCollectionTrait.php +++ b/src/Json/EachInCollectionTrait.php @@ -4,6 +4,7 @@ use Symfony\Component\PropertyAccess\PropertyAccessor; use Behapi\Assert\Assert; +use Behapi\Assert\AssertionChain; trait EachInCollectionTrait { @@ -21,6 +22,7 @@ final public function the_json_path_elements_in_collection_should_have_a_propert $assert = Assert::that($this->getValue(empty($path) ? null : $path)); if ($not !== null) { + assert($assert instanceof AssertionChain); $assert = $assert->not(); } @@ -42,13 +44,23 @@ final public function the_json_each_elements_in_collection_should_be_equal_to(st ->isTraversable() ; - $values = array_map(function ($value) use ($property) { return $this->accessor->getValue($value, $property); }, $values); + $values = array_map( + /** + * @param mixed $value + * @return mixed + */ + function ($value) use ($property) { + return $this->accessor->getValue($value, $property); + }, + $values + ); $assert = Assert::that($values) ->all() ; if ($not !== null) { + assert($assert instanceof AssertionChain); $assert = $assert->not(); } @@ -67,7 +79,16 @@ final public function the_json_each_elements_in_collection_should_be_bool(string ->isTraversable() ; - $values = array_map(function ($value) use ($property) { return $this->accessor->getValue($value, $property); }, $values); + $values = array_map( + /** + * @param mixed $value + * @return mixed + */ + function ($value) use ($property) { + return $this->accessor->getValue($value, $property); + }, + $values + ); $assert = Assert::that($values) ->all() @@ -75,6 +96,7 @@ final public function the_json_each_elements_in_collection_should_be_bool(string ; if ($not !== null) { + assert($assert instanceof AssertionChain); $assert = $assert->not(); } @@ -93,7 +115,16 @@ final public function the_json_each_elements_in_collection_should_be_equal_to_in ->isTraversable() ; - $values = array_map(function ($value) use ($property) { return $this->accessor->getValue($value, $property); }, $values); + $values = array_map( + /** + * @param mixed $value + * @return mixed + */ + function ($value) use ($property) { + return $this->accessor->getValue($value, $property); + }, + $values + ); $assert = Assert::that($values) ->all() @@ -101,6 +132,7 @@ final public function the_json_each_elements_in_collection_should_be_equal_to_in ; if ($not !== null) { + assert($assert instanceof AssertionChain); $assert = $assert->not(); } @@ -119,13 +151,23 @@ final public function the_json_each_elements_in_collection_should_contain(string ->isTraversable() ; - $values = array_map(function ($value) use ($property) { return $this->accessor->getValue($value, $property); }, $values); + $values = array_map( + /** + * @param mixed $value + * @return mixed + */ + function ($value) use ($property) { + return $this->accessor->getValue($value, $property); + }, + $values + ); $assert = Assert::that($values) ->all() ; if ($not !== null) { + assert($assert instanceof AssertionChain); $assert = $assert->not(); }