diff --git a/.github/workflows/code-style.yml b/.github/workflows/code-style.yml index 44914ca16..0ed1f4384 100644 --- a/.github/workflows/code-style.yml +++ b/.github/workflows/code-style.yml @@ -18,7 +18,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '8.2' + php-version: '8.3' - uses: ramsey/composer-install@v3 with: diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index de1ed301d..ccd9c2e50 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -18,7 +18,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '8.2' + php-version: '8.3' - uses: ramsey/composer-install@v3 with: diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index ea6f5d9ca..b147aa563 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -18,7 +18,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '8.2' + php-version: '8.3' - uses: ramsey/composer-install@v3 with: diff --git a/composer.json b/composer.json index 1dce1a1fa..cce4885c1 100644 --- a/composer.json +++ b/composer.json @@ -46,7 +46,7 @@ "require": { "php": ">=7.4", "ext-json": "*", - "nikic/php-parser": "^4.19", + "nikic/php-parser": "^4.19 || ^5.0", "psr/log": "^1.1 || ^2.0 || ^3.0", "symfony/deprecation-contracts": "^2 || ^3", "symfony/finder": "^5.0 || ^6.0 || ^7.0", @@ -61,10 +61,10 @@ "composer/package-versions-deprecated": "^1.11", "doctrine/annotations": "^2.0", "friendsofphp/php-cs-fixer": "^3.62.0", - "phpstan/phpstan": "^1.6", + "phpstan/phpstan": "^1.6 || ^2.0", "phpunit/phpunit": "^9.0", - "rector/rector": "^1.0", - "vimeo/psalm": "^4.30" + "rector/rector": "^1.0 || ^2.0", + "vimeo/psalm": "^4.30 || ^5.0" }, "conflict": { "symfony/process": ">=6, <6.4.14" @@ -108,7 +108,7 @@ ], "analyse": [ "export XDEBUG_MODE=off && phpstan analyse --memory-limit=2G", - "export XDEBUG_MODE=off && psalm" + "export XDEBUG_MODE=off && psalm --threads=1" ], "spectral-examples": "for ff in `find Examples -name '*.yaml'`; do spectral lint $ff; done", "spectral-scratch": "for ff in `find tests/Fixtures/Scratch -name '*.yaml'`; do spectral lint $ff; done", diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 1dc46f075..19f8bf8e9 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,106 +1,187 @@ parameters: ignoreErrors: - - message: "#^Property OpenApi\\\\Annotations\\\\AbstractAnnotation\\:\\:\\$x \\(array\\\\) does not accept string\\.$#" + message: '#^Property OpenApi\\Annotations\\AbstractAnnotation\:\:\$x \(array\\) does not accept string\.$#' + identifier: assign.propertyType count: 1 path: Examples/processors/schema-query-parameter/SchemaQueryParameter.php - - message: "#^Result of && is always true\\.$#" + message: '#^Attribute class JetBrains\\PhpStorm\\ArrayShape does not exist\.$#' + identifier: attribute.notFound count: 1 - path: Examples/processors/sort-components/SortComponents.php + path: Examples/using-links-php81/User.php - - message: "#^Attribute class JetBrains\\\\PhpStorm\\\\ArrayShape does not exist\\.$#" + message: '#^Call to function method_exists\(\) with ReflectionProperty and ''isPromoted'' will always evaluate to true\.$#' + identifier: function.alreadyNarrowedType count: 1 - path: Examples/using-links-php81/User.php + path: src/Analysers/AttributeAnnotationFactory.php + + - + message: '#^Call to function method_exists\(\) with ReflectionClass\ and ''isEnum'' will always evaluate to true\.$#' + identifier: function.alreadyNarrowedType + count: 1 + path: src/Analysers/ReflectionAnalyser.php - - message: "#^Call to function array_key_exists\\(\\) with string and array\\{\\} will always evaluate to false\\.$#" + message: '#^Access to an undefined property OpenApi\\Annotations\\AbstractAnnotation\:\:\$description\.$#' + identifier: property.notFound count: 1 - path: src/Analysers/TokenScanner.php + path: src/Annotations/AbstractAnnotation.php - - message: "#^Access to an undefined property OpenApi\\\\Annotations\\\\AbstractAnnotation\\:\\:\\$description\\.$#" + message: '#^Access to an undefined property OpenApi\\Annotations\\AbstractAnnotation\:\:\$summary\.$#' + identifier: property.notFound count: 1 path: src/Annotations/AbstractAnnotation.php - - message: "#^Access to an undefined property OpenApi\\\\Annotations\\\\AbstractAnnotation\\:\\:\\$summary\\.$#" + message: '#^Call to function property_exists\(\) with OpenApi\\Annotations\\AbstractAnnotation and ''_context'' will always evaluate to true\.$#' + identifier: function.alreadyNarrowedType count: 1 path: src/Annotations/AbstractAnnotation.php - - message: "#^Variable \\$name might not be defined\\.$#" + message: '#^Variable \$name might not be defined\.$#' + identifier: variable.undefined count: 2 path: src/Annotations/Components.php - - message: "#^Variable \\$type might not be defined\\.$#" + message: '#^Variable \$type might not be defined\.$#' + identifier: variable.undefined count: 1 path: src/Annotations/Components.php - - message: "#^Property OpenApi\\\\Annotations\\\\Flow\\:\\:\\$scopes \\(array\\) does not accept stdClass\\.$#" + message: '#^Property OpenApi\\Annotations\\Flow\:\:\$scopes \(array\) does not accept stdClass\.$#' + identifier: assign.propertyType count: 1 path: src/Annotations/Flow.php - - message: "#^Access to an undefined property object\\:\\:\\$enum\\.$#" + message: '#^Method OpenApi\\Processors\\AugmentParameters\:\:extractVarTypeAndDescription\(\) should return array\ but returns array\\.$#' + identifier: return.type count: 1 - path: src/Annotations/Schema.php + path: src/Processors/AugmentParameters.php - - message: "#^Call to function is_array\\(\\) with bool\\|OpenApi\\\\Annotations\\\\AdditionalProperties will always evaluate to false\\.$#" + message: '#^Strict comparison using \!\=\= between false and string will always evaluate to true\.$#' + identifier: notIdentical.alwaysTrue count: 1 - path: src/Processors/AugmentSchemas.php + path: src/Processors/AugmentParameters.php - - message: "#^Property OpenApi\\\\Annotations\\\\Schema\\:\\:\\$properties \\(array\\\\) does not accept string\\.$#" + message: '#^Method OpenApi\\Processors\\AugmentProperties\:\:extractVarTypeAndDescription\(\) should return array\ but returns array\\.$#' + identifier: return.type count: 1 - path: src/Processors/AugmentSchemas.php + path: src/Processors/AugmentProperties.php - - message: "#^Result of && is always false\\.$#" + message: '#^Strict comparison using \!\=\= between false and string will always evaluate to true\.$#' + identifier: notIdentical.alwaysTrue + count: 1 + path: src/Processors/AugmentProperties.php + + - + message: '#^Property OpenApi\\Annotations\\Schema\:\:\$properties \(array\\) does not accept string\.$#' + identifier: assign.propertyType count: 1 path: src/Processors/AugmentSchemas.php - - message: "#^Parameter \\#1 \\$annotation of method OpenApi\\\\Processors\\\\DocBlockDescriptions\\:\\:description\\(\\) expects OpenApi\\\\Annotations\\\\Operation\\|OpenApi\\\\Annotations\\\\Parameter\\|OpenApi\\\\Annotations\\\\Schema, OpenApi\\\\Annotations\\\\AbstractAnnotation given\\.$#" + message: '#^Method OpenApi\\Processors\\DocBlockDescriptions\:\:extractVarTypeAndDescription\(\) should return array\ but returns array\\.$#' + identifier: return.type + count: 1 + path: src/Processors/DocBlockDescriptions.php + + - + message: '#^Parameter \#1 \$annotation of method OpenApi\\Processors\\DocBlockDescriptions\:\:description\(\) expects OpenApi\\Annotations\\Operation\|OpenApi\\Annotations\\Parameter\|OpenApi\\Annotations\\Schema, OpenApi\\Annotations\\AbstractAnnotation given\.$#' + identifier: argument.type + count: 1 + path: src/Processors/DocBlockDescriptions.php + + - + message: '#^Parameter \#1 \$annotation of method OpenApi\\Processors\\DocBlockDescriptions\:\:summaryAndDescription\(\) expects OpenApi\\Annotations\\Operation\|OpenApi\\Annotations\\Parameter\|OpenApi\\Annotations\\Schema, OpenApi\\Annotations\\AbstractAnnotation given\.$#' + identifier: argument.type count: 1 path: src/Processors/DocBlockDescriptions.php - - message: "#^Parameter \\#1 \\$annotation of method OpenApi\\\\Processors\\\\DocBlockDescriptions\\:\\:summaryAndDescription\\(\\) expects OpenApi\\\\Annotations\\\\Operation\\|OpenApi\\\\Annotations\\\\Parameter\\|OpenApi\\\\Annotations\\\\Schema, OpenApi\\\\Annotations\\\\AbstractAnnotation given\\.$#" + message: '#^Strict comparison using \!\=\= between false and string will always evaluate to true\.$#' + identifier: notIdentical.alwaysTrue count: 1 path: src/Processors/DocBlockDescriptions.php - - message: "#^Property OpenApi\\\\Annotations\\\\Schema\\:\\:\\$examples \\(array\\\\) does not accept string\\.$#" + message: '#^Property OpenApi\\Annotations\\Schema\:\:\$examples \(array\\) does not accept string\.$#' + identifier: assign.propertyType count: 1 path: src/Processors/MergeJsonContent.php - - message: "#^Property OpenApi\\\\Annotations\\\\Schema\\:\\:\\$examples \\(array\\\\) does not accept string\\.$#" + message: '#^Property OpenApi\\Annotations\\Schema\:\:\$examples \(array\\) does not accept string\.$#' + identifier: assign.propertyType count: 1 path: src/Processors/MergeXmlContent.php - - message: "#^Parameter \\#1 \\$callback of function spl_autoload_register expects \\(callable\\(string\\)\\: void\\)\\|null, array\\{Composer\\\\Autoload\\\\ClassLoader, 'findFile'\\} given\\.$#" + message: '#^Parameter \#1 \$callback of function spl_autoload_register expects \(callable\(string\)\: void\)\|null, array\{Composer\\Autoload\\ClassLoader, ''findFile''\} given\.$#' + identifier: argument.type count: 1 path: tests/Analysers/ComposerAutoloaderScannerTest.php - - message: "#^Access to an undefined property OpenApi\\\\Analysers\\\\AnnotationFactoryInterface\\:\\:\\$reflectors\\.$#" + message: '#^Access to an undefined property OpenApi\\Analysers\\AnnotationFactoryInterface\:\:\$reflectors\.$#' + identifier: property.notFound count: 2 path: tests/Analysers/ReflectionAnalyserTest.php - - message: "#^Access to an undefined property Reflector\\:\\:\\$name\\.$#" + message: '#^Access to an undefined property Reflector\:\:\$name\.$#' + identifier: property.notFound count: 1 path: tests/Analysers/ReflectionAnalyserTest.php - - message: "#^Call to an undefined method ReflectionType\\:\\:getName\\(\\)\\.$#" + message: '#^Call to an undefined method ReflectionType\:\:getName\(\)\.$#' + identifier: method.notFound count: 1 path: tests/Annotations/AttributesSyncTest.php + + - + message: '#^Method OpenApi\\Tests\\Processors\\AugmentParametersTest\:\:extractVarTypeAndDescription\(\) should return array\ but returns array\\.$#' + identifier: return.type + count: 1 + path: tests/Processors/AugmentParametersTest.php + + - + message: '#^Strict comparison using \!\=\= between false and string will always evaluate to true\.$#' + identifier: notIdentical.alwaysTrue + count: 1 + path: tests/Processors/AugmentParametersTest.php + + - + message: '#^Method OpenApi\\Tests\\Processors\\AugmentRefsTest\:\:extractVarTypeAndDescription\(\) should return array\ but returns array\\.$#' + identifier: return.type + count: 1 + path: tests/Processors/AugmentRefsTest.php + + - + message: '#^Strict comparison using \!\=\= between false and string will always evaluate to true\.$#' + identifier: notIdentical.alwaysTrue + count: 1 + path: tests/Processors/AugmentRefsTest.php + + - + message: '#^Method OpenApi\\Tests\\Processors\\DocBlockDescriptionsTest\:\:extractVarTypeAndDescription\(\) should return array\ but returns array\\.$#' + identifier: return.type + count: 1 + path: tests/Processors/DocBlockDescriptionsTest.php + + - + message: '#^Strict comparison using \!\=\= between false and string will always evaluate to true\.$#' + identifier: notIdentical.alwaysTrue + count: 1 + path: tests/Processors/DocBlockDescriptionsTest.php diff --git a/phpstan.neon b/phpstan.neon index 2473fada6..7441e96a7 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -12,6 +12,7 @@ parameters: processTimeout: 300.0 excludePaths: - 'tests/Fixtures/*' + treatPhpDocTypesAsCertain: false ignoreErrors: - '#does not accept default value of type #' ## Examples diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 7f2edd1ea..5c064bb4b 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1,8 +1,2 @@ - - - - Yaml::parse($contents) - - - + diff --git a/psalm.xml b/psalm.xml index 670466060..0f3330cff 100644 --- a/psalm.xml +++ b/psalm.xml @@ -6,6 +6,8 @@ xmlns="https://getpsalm.org/schema/config" xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd" errorBaseline="psalm-baseline.xml" + findUnusedBaselineEntry="true" + findUnusedCode="false" phpVersion="8.1" > diff --git a/src/Analysers/AttributeAnnotationFactory.php b/src/Analysers/AttributeAnnotationFactory.php index c158e78ad..29f931d9a 100644 --- a/src/Analysers/AttributeAnnotationFactory.php +++ b/src/Analysers/AttributeAnnotationFactory.php @@ -110,8 +110,6 @@ public function build(\Reflector $reflector, Context $context): array Generator::$context = null; } - $annotations = array_values(array_filter($annotations, fn ($a) => $a instanceof OA\AbstractAnnotation)); - // merge backwards into parents... $isParent = function (OA\AbstractAnnotation $annotation, OA\AbstractAnnotation $possibleParent): bool { // regular annotation hierarchy diff --git a/src/Analysers/TokenScanner.php b/src/Analysers/TokenScanner.php index 2367d260d..3f4896a7d 100644 --- a/src/Analysers/TokenScanner.php +++ b/src/Analysers/TokenScanner.php @@ -50,6 +50,7 @@ public function scanFile(string $filename): array protected function collect_stmts(array $stmts, string $namespace): array { + /** @var array $uses */ $uses = []; $resolve = function (string $name) use ($namespace, &$uses) { if (array_key_exists($name, $uses)) { diff --git a/src/Processors/Concerns/DocblockTrait.php b/src/Processors/Concerns/DocblockTrait.php index dd6590b6b..8ae17f646 100644 --- a/src/Processors/Concerns/DocblockTrait.php +++ b/src/Processors/Concerns/DocblockTrait.php @@ -172,7 +172,7 @@ public function extractDescription(?string $docblock): string /** * Extract property type and description from a `@var` dockblock line. * - * @return array extracted `type` and `description`; values default to `null` + * @return array extracted `type` and `description`; values default to `null` */ public function extractVarTypeAndDescription(?string $docblock): array { diff --git a/src/Processors/MergeIntoOpenApi.php b/src/Processors/MergeIntoOpenApi.php index 95b7c1035..5611ba4f7 100644 --- a/src/Processors/MergeIntoOpenApi.php +++ b/src/Processors/MergeIntoOpenApi.php @@ -48,7 +48,6 @@ public function __invoke(Analysis $analysis) } elseif ( $annotation instanceof OA\AbstractAnnotation && in_array(OA\OpenApi::class, $annotation::$_parents) - && property_exists($annotation, '_context') && false === $annotation->_context->is('nested')) { // A top level annotation. $merge[] = $annotation; diff --git a/tests/Analysers/DocBlockParserTest.php b/tests/Analysers/DocBlockParserTest.php index 8486057b2..98cbb8a87 100644 --- a/tests/Analysers/DocBlockParserTest.php +++ b/tests/Analysers/DocBlockParserTest.php @@ -16,7 +16,7 @@ class DocBlockParserTest extends OpenApiTestCase public function testParseContents(): void { $annotations = $this->annotationsFromDocBlockParser('@OA\Parameter(description="This is my parameter")', self::SWG_ALIAS); - $this->assertIsArray($annotations); + $this->assertNotEmpty($annotations); $parameter = $annotations[0]; $this->assertInstanceOf('OpenApi\Annotations\Parameter', $parameter); $this->assertSame('This is my parameter', $parameter->description); diff --git a/tests/Processors/AugmentSchemasTest.php b/tests/Processors/AugmentSchemasTest.php index 0125249d3..6162ea97f 100644 --- a/tests/Processors/AugmentSchemasTest.php +++ b/tests/Processors/AugmentSchemasTest.php @@ -32,7 +32,7 @@ public function testAugmentSchemas(): void $this->assertSame('Customer', $customer->schema, '@OA\Schema()->schema based on classname'); $this->assertIsArray($customer->properties); - $this->assertCount(10, $customer->properties, '@OA\Property()s are merged into the @OA\Schema of the class'); + $this->assertCount(10, (array) $customer->properties, '@OA\Property()s are merged into the @OA\Schema of the class'); } public function testAugmentSchemasForInterface(): void @@ -51,6 +51,6 @@ public function testAugmentSchemasForInterface(): void $analysis->process([new AugmentSchemas()]); $this->assertIsArray($customer->properties); - $this->assertCount(9, $customer->properties, '@OA\Property()s are merged into the @OA\Schema of the class'); + $this->assertCount(9, (array) $customer->properties, '@OA\Property()s are merged into the @OA\Schema of the class'); } } diff --git a/tests/Processors/MergeJsonContentTest.php b/tests/Processors/MergeJsonContentTest.php index 31a524552..ff9edc527 100644 --- a/tests/Processors/MergeJsonContentTest.php +++ b/tests/Processors/MergeJsonContentTest.php @@ -32,7 +32,7 @@ public function testJsonContent(): void $analysis->process([new MergeJsonContent()]); $this->assertIsArray($response->content); - $this->assertCount(1, $response->content); + $this->assertCount(1, (array) $response->content); $this->assertCount(0, $response->_unmerged); $json = json_decode(json_encode($response), true); $this->assertSame('#/components/schemas/repository', $json['content']['application/json']['schema']['items']['$ref']); @@ -75,7 +75,7 @@ public function testParameter(): void $analysis->process([new MergeJsonContent()]); $this->assertIsArray($parameter->content); - $this->assertCount(1, $parameter->content); + $this->assertCount(1, (array) $parameter->content); $this->assertCount(0, $parameter->_unmerged); $json = json_decode(json_encode($parameter), true); $this->assertSame('query', $json['in']); diff --git a/tests/Processors/MergeXmlContentTest.php b/tests/Processors/MergeXmlContentTest.php index c0f2286c7..8670c5c83 100644 --- a/tests/Processors/MergeXmlContentTest.php +++ b/tests/Processors/MergeXmlContentTest.php @@ -32,7 +32,7 @@ public function testXmlContent(): void $analysis->process([new MergeXmlContent()]); $this->assertIsArray($response->content); - $this->assertCount(1, $response->content); + $this->assertCount(1, (array) $response->content); $this->assertCount(0, $response->_unmerged); $json = json_decode(json_encode($response), true); $this->assertSame('#/components/schemas/repository', $json['content']['application/xml']['schema']['items']['$ref']); @@ -73,7 +73,7 @@ public function testParameter(): void $analysis->process([new MergeXmlContent()]); $this->assertIsArray($parameter->content); - $this->assertCount(1, $parameter->content); + $this->assertCount(1, (array) $parameter->content); $this->assertCount(0, $parameter->_unmerged); $json = json_decode(json_encode($parameter), true); $this->assertSame('query', $json['in']); diff --git a/tests/SerializerTest.php b/tests/SerializerTest.php index c1fff64a1..42897dde5 100644 --- a/tests/SerializerTest.php +++ b/tests/SerializerTest.php @@ -196,7 +196,6 @@ public function testDeserializeAllOfProperty(): void $allOfItem = current($schemaObject->allOf); $this->assertIsObject($allOfItem); $this->assertInstanceOf(OA\Schema::class, $allOfItem); - $this->assertTrue(property_exists($allOfItem, 'ref')); $this->assertNotSame($allOfItem->ref, Generator::UNDEFINED); $this->assertSame('#/components/schemas/SomeSchema', $allOfItem->ref); }