From aab6881cd229ba647564e649d95821490784b3ae Mon Sep 17 00:00:00 2001 From: Can Vural Date: Sat, 12 Dec 2020 21:54:18 +0100 Subject: [PATCH] Add static analysis with PHPStan --- .github/workflows/ci.yml | 28 ++++++- composer.json | 4 + phpstan-baseline.neon | 77 +++++++++++++++++++ phpstan.neon | 11 +++ src/PSR7/SchemaFactory/JsonFactory.php | 1 + src/PSR7/SpecFinder.php | 8 +- .../BodyValidator/MultipartValidator.php | 5 +- src/PSR7/Validators/SecurityValidator.php | 9 ++- src/Schema/BreadCrumb.php | 6 +- tests/PSR7/OperationAddressTest.php | 2 - 10 files changed, 133 insertions(+), 18 deletions(-) create mode 100644 phpstan-baseline.neon create mode 100644 phpstan.neon diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b8ab87dd..a1371365 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,8 +4,8 @@ on: [push, pull_request] jobs: - build: - name: Build + tests: + name: Tests runs-on: ubuntu-latest continue-on-error: ${{ matrix.experimental }} strategy: @@ -68,3 +68,27 @@ jobs: - name: Run tests run: ./vendor/bin/phpcs + + static-analysis: + name: Static analysis + runs-on: ubuntu-latest + + strategy: + matrix: + php: + - '7.2' + + steps: + - name: Set up PHP + uses: shivammathur/setup-php@2.1.0 + with: + php-version: ${{ matrix.php }} + + - name: Checkout code + uses: actions/checkout@v2 + + - name: Download dependencies + run: composer update --no-interaction --prefer-dist --optimize-autoloader --prefer-stable + + - name: Run PHPStan + run: ./vendor/bin/phpstan analyse --memory-limit 512M diff --git a/composer.json b/composer.json index d571d71a..ca61682e 100644 --- a/composer.json +++ b/composer.json @@ -36,6 +36,10 @@ "require-dev": { "doctrine/coding-standard": "^8.0", "guzzlehttp/psr7": "^1.5", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12.59", + "phpstan/phpstan-phpunit": "^0.12.16", + "phpstan/phpstan-webmozart-assert": "^0.12.7", "phpunit/phpunit": "^7|^8|^9", "symfony/cache": "^5.1" }, diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 00000000..421d541d --- /dev/null +++ b/phpstan-baseline.neon @@ -0,0 +1,77 @@ +parameters: + ignoreErrors: + - + message: "#^Parameter \\#4 \\$prev of static method League\\\\OpenAPIValidation\\\\PSR7\\\\Exception\\\\Validation\\\\InvalidCookies\\:\\:becauseValueDoesNotMatchSchema\\(\\) expects League\\\\OpenAPIValidation\\\\Schema\\\\Exception\\\\SchemaMismatch, Throwable\\|null given\\.$#" + count: 1 + path: src/PSR7/Validators/CookiesValidator/ServerRequestCookieValidator.php + + - + message: "#^Parameter \\#1 \\$openApiSpec of class League\\\\OpenAPIValidation\\\\PSR7\\\\PathFinder constructor expects cebe\\\\openapi\\\\spec\\\\OpenApi, cebe\\\\openapi\\\\SpecObjectInterface given\\.$#" + count: 2 + path: tests/FromCommunity/Issue79Test.php + + - + message: "#^Generator expects value type string, array\\ given\\.$#" + count: 7 + path: tests/FromCommunity/Issue79Test.php + + - + message: "#^Parameter \\#1 \\$openApiSpec of class League\\\\OpenAPIValidation\\\\PSR7\\\\PathFinder constructor expects cebe\\\\openapi\\\\spec\\\\OpenApi, cebe\\\\openapi\\\\SpecObjectInterface given\\.$#" + count: 4 + path: tests/PSR7/PathFinderTest.php + + - + message: "#^Method League\\\\OpenAPIValidation\\\\Tests\\\\PSR7\\\\Validators\\\\BodyValidator\\\\MultipartValidatorTest\\:\\:dataProviderMultipartRed\\(\\) should return array\\\\> but returns array\\\\>\\.$#" + count: 1 + path: tests/PSR7/Validators/BodyValidator/MultipartValidatorTest.php + + - + message: "#^Method League\\\\OpenAPIValidation\\\\Tests\\\\PSR7\\\\Validators\\\\BodyValidatorTest\\:\\:dataProviderGreen\\(\\) should return array\\\\> but returns array\\\\>\\.$#" + count: 1 + path: tests/PSR7/Validators/BodyValidatorTest.php + + - + message: "#^Method League\\\\OpenAPIValidation\\\\Tests\\\\PSR7\\\\Validators\\\\BodyValidatorTest\\:\\:dataProviderRed\\(\\) should return array\\\\> but returns array\\\\>\\.$#" + count: 1 + path: tests/PSR7/Validators/BodyValidatorTest.php + + - + message: "#^Method League\\\\OpenAPIValidation\\\\Tests\\\\Schema\\\\Keywords\\\\PatternTest\\:\\:validDataProvider\\(\\) should return array\\\\> but returns array\\\\>\\.$#" + count: 1 + path: tests/Schema/Keywords/PatternTest.php + + - + message: "#^Method League\\\\OpenAPIValidation\\\\Tests\\\\Schema\\\\Keywords\\\\TypeTest\\:\\:validDataProvider\\(\\) should return array\\\\> but returns array\\\\|bool\\|float\\|int\\|string\\|null\\>\\>\\.$#" + count: 1 + path: tests/Schema/Keywords/TypeTest.php + + - + message: "#^Method League\\\\OpenAPIValidation\\\\Tests\\\\Schema\\\\Keywords\\\\TypeTest\\:\\:invalidDataProvider\\(\\) should return array\\\\> but returns array\\\\|float\\|int\\|stdClass\\|string\\>\\>\\.$#" + count: 1 + path: tests/Schema/Keywords/TypeTest.php + + - + message: "#^Method League\\\\OpenAPIValidation\\\\Tests\\\\Schema\\\\Keywords\\\\UniqueItemsTest\\:\\:dataProviderGreen\\(\\) should return array\\\\> but returns array\\\\|int\\>\\|bool\\|float\\|int\\|string\\>\\|string\\>\\>\\.$#" + count: 1 + path: tests/Schema/Keywords/UniqueItemsTest.php + + - + message: "#^Method League\\\\OpenAPIValidation\\\\Tests\\\\Schema\\\\Keywords\\\\UniqueItemsTest\\:\\:dataProviderRed\\(\\) should return array\\\\> but returns array\\\\|int\\>\\|bool\\|int\\|string\\>\\|string\\>\\>\\.$#" + count: 1 + path: tests/Schema/Keywords/UniqueItemsTest.php + + - + message: "#^Access to an undefined property cebe\\\\openapi\\\\SpecObjectInterface\\:\\:\\$schema\\.$#" + count: 2 + path: tests/Schema/SchemaValidatorTest.php + + - + message: "#^Method League\\\\OpenAPIValidation\\\\Tests\\\\Schema\\\\TypeFormats\\\\StringDateTimeTest\\:\\:dateTimeGreenDataProvider\\(\\) should return array\\ but returns array\\\\>\\.$#" + count: 1 + path: tests/Schema/TypeFormats/StringDateTimeTest.php + + - + message: "#^Method League\\\\OpenAPIValidation\\\\Tests\\\\Schema\\\\TypeFormats\\\\StringDateTimeTest\\:\\:dateTimeRedDataProvider\\(\\) should return array\\ but returns array\\\\>\\.$#" + count: 1 + path: tests/Schema/TypeFormats/StringDateTimeTest.php + diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 00000000..2c4a22ee --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,11 @@ +includes: + - phpstan-baseline.neon +parameters: + level: 6 + paths: + - src + - tests + treatPhpDocTypesAsCertain: false + ignoreErrors: + - '#Unsafe usage of new static\(\)\.#' + - '#Method [a-zA-Z0-9_\\]+::[a-zA-Z0-9_]+\(\) should return cebe\\openapi\\spec\\OpenApi but returns cebe\\openapi\\SpecObjectInterface\.#' \ No newline at end of file diff --git a/src/PSR7/SchemaFactory/JsonFactory.php b/src/PSR7/SchemaFactory/JsonFactory.php index 3a927c0a..a48c8826 100644 --- a/src/PSR7/SchemaFactory/JsonFactory.php +++ b/src/PSR7/SchemaFactory/JsonFactory.php @@ -12,6 +12,7 @@ final class JsonFactory extends StringFactory { public function createSchema(): OpenApi { + /** @var OpenApi $schema */ $schema = Reader::readFromJson($this->getContent()); $schema->resolveReferences(new ReferenceContext($schema, '/')); diff --git a/src/PSR7/SpecFinder.php b/src/PSR7/SpecFinder.php index d524a012..8d586895 100644 --- a/src/PSR7/SpecFinder.php +++ b/src/PSR7/SpecFinder.php @@ -6,7 +6,6 @@ use cebe\openapi\exceptions\TypeErrorException; use cebe\openapi\spec\Callback; -use cebe\openapi\spec\Header; use cebe\openapi\spec\Header as HeaderSpec; use cebe\openapi\spec\MediaType; use cebe\openapi\spec\OpenApi; @@ -210,7 +209,7 @@ public function findResponseSpec($addr): ResponseSpec $operation = $this->findOperationSpec($addr); - $response = $operation->responses->getResponse($addr->responseCode()); + $response = $operation->responses->getResponse((string) $addr->responseCode()); if (! $response) { $response = $operation->responses->getResponse('default'); @@ -228,7 +227,7 @@ public function findResponseSpec($addr): ResponseSpec } /** - * @return Header[] + * @return array * * @throws NoPath */ @@ -244,7 +243,9 @@ public function findHeaderSpecs(OperationAddress $addr): array // 1. Collect operation level headers from "parameters" keyword // An API call may require that custom headers be sent with an HTTP request. OpenAPI lets you define custom // request headers as in: header parameters. + /** @var array $headerSpecs */ $headerSpecs = []; + foreach ($spec->parameters as $p) { if ($p->in !== 'header') { continue; @@ -262,6 +263,7 @@ public function findHeaderSpecs(OperationAddress $addr): array // 2. Collect path-level headers from "parameters" keyword // Path level params are fall-backs $pathSpec = $this->findPathSpec($addr); + foreach ($pathSpec->parameters as $p) { if ($p->in !== 'header') { continue; diff --git a/src/PSR7/Validators/BodyValidator/MultipartValidator.php b/src/PSR7/Validators/BodyValidator/MultipartValidator.php index 1dfc1830..a129fb15 100644 --- a/src/PSR7/Validators/BodyValidator/MultipartValidator.php +++ b/src/PSR7/Validators/BodyValidator/MultipartValidator.php @@ -83,9 +83,6 @@ public function validate(OperationAddress $addr, MessageInterface $message): voi } } - /** - * @param MediaType[] $mediaTypeSpecs - */ private function validatePlainBodyMultipart( OperationAddress $addr, MessageInterface $message, @@ -198,7 +195,7 @@ private function parseMultipartData(OperationAddress $addr, StreamedPart $docume return $multipartData; } - private function detectEncondingContentType(Encoding $encoding, StreamedPart $part, Schema $partSchema): ?string + private function detectEncondingContentType(Encoding $encoding, StreamedPart $part, Schema $partSchema): string { $contentType = $encoding->contentType; diff --git a/src/PSR7/Validators/SecurityValidator.php b/src/PSR7/Validators/SecurityValidator.php index 834300b2..e3b9e6fa 100755 --- a/src/PSR7/Validators/SecurityValidator.php +++ b/src/PSR7/Validators/SecurityValidator.php @@ -17,6 +17,7 @@ use League\OpenAPIValidation\Schema\Exception\InvalidSchema; use Psr\Http\Message\MessageInterface; use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ServerRequestInterface; use function count; use function preg_match; @@ -45,7 +46,7 @@ public function validate(OperationAddress $addr, MessageInterface $message): voi // Security schemes combined via OR are alternatives – any one can be used in the given context. // Security schemes combined via AND must be used simultaneously in the same request. // @see https://swagger.io/docs/specification/authentication/ - if (! ($message instanceof RequestInterface)) { + if (! ($message instanceof ServerRequestInterface)) { return; } @@ -55,7 +56,7 @@ public function validate(OperationAddress $addr, MessageInterface $message): voi /** * @throws ValidationFailed */ - private function validateServerRequest(OperationAddress $addr, RequestInterface $request): void + private function validateServerRequest(OperationAddress $addr, ServerRequestInterface $request): void { $securitySpecs = $this->finder->findSecuritySpecs($addr); @@ -84,7 +85,7 @@ private function validateServerRequest(OperationAddress $addr, RequestInterface */ private function validateSecurityScheme( OperationAddress $addr, - RequestInterface $request, + ServerRequestInterface $request, SecurityRequirement $spec ): void { // Here I implement AND-union @@ -162,7 +163,7 @@ private function validateHTTPSecurityScheme( */ private function validateApiKeySecurityScheme( OperationAddress $addr, - RequestInterface $request, + ServerRequestInterface $request, SecurityScheme $securityScheme ): void { switch ($securityScheme->in) { diff --git a/src/Schema/BreadCrumb.php b/src/Schema/BreadCrumb.php index d7f5a186..5dfa4270 100644 --- a/src/Schema/BreadCrumb.php +++ b/src/Schema/BreadCrumb.php @@ -14,9 +14,9 @@ // It can address an index in the compound array(object) class BreadCrumb { - /** @var string */ + /** @var string|null */ protected $compoundIndex; - /** @var self link to a previous crumb */ + /** @var self|null link to a previous crumb */ protected $prevCrumb; /** @@ -24,7 +24,7 @@ class BreadCrumb */ public function __construct($compoundIndex = null) { - if (! is_scalar($compoundIndex) && ($compoundIndex !== null)) { + if (($compoundIndex !== null) && ! is_scalar($compoundIndex)) { throw new RuntimeException(sprintf('BreadCrumb cannot have non-scalar index: %s', $compoundIndex)); } diff --git a/tests/PSR7/OperationAddressTest.php b/tests/PSR7/OperationAddressTest.php index 67cc4792..66a606e7 100644 --- a/tests/PSR7/OperationAddressTest.php +++ b/tests/PSR7/OperationAddressTest.php @@ -50,8 +50,6 @@ public function dataProviderParseRed(): array } /** - * @param mixed[] $result - * * @dataProvider dataProviderParseRed */ public function testItThrowsIfParsingNotPossible(string $spec, string $url): void