From 7aa26bae9cc7bf8cdc1db989292794106e6230b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Wer=C5=82os?= Date: Fri, 4 Jul 2025 16:12:28 +0200 Subject: [PATCH] Introduce `shipmonk/phpstan-rules` --- .dev-tools/composer.json | 1 + .dev-tools/composer.lock | 37 ++++++++++++++++++- .dev-tools/phpstan.neon | 3 +- src/Analyzer/Analysis/SwitchAnalysis.php | 2 +- src/Fixer/IssetToArrayKeyExistsFixer.php | 2 + src/Fixer/StringableInterfaceFixer.php | 1 + tests/AutoReview/TestsCodeTest.php | 14 +++---- .../SingleSpaceAfterStatementFixerTest.php | 6 +-- tests/FixersTest.php | 4 +- 9 files changed, 55 insertions(+), 15 deletions(-) diff --git a/.dev-tools/composer.json b/.dev-tools/composer.json index 7e876070..fc4825f1 100644 --- a/.dev-tools/composer.json +++ b/.dev-tools/composer.json @@ -13,6 +13,7 @@ "phpstan/phpstan-phpunit": "^2.0.6", "phpstan/phpstan-strict-rules": "^2.0.4", "shipmonk/composer-dependency-analyser": "^1.8.3", + "shipmonk/phpstan-rules": "^4.1.4", "squizlabs/php_codesniffer": "^3.13.2", "tomasvotruba/type-coverage": "^2.0.2", "vimeo/psalm": "^6.12" diff --git a/.dev-tools/composer.lock b/.dev-tools/composer.lock index c5d9107f..ace501f2 100644 --- a/.dev-tools/composer.lock +++ b/.dev-tools/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "0d7bb777898746275cb9b509f52d9a02", + "content-hash": "52bc6ed7538d86ec86cb832d332c1464", "packages": [ { "name": "amphp/amp", @@ -2204,6 +2204,41 @@ ], "description": "Fast detection of composer dependency issues (dead dependencies, shadow dependencies, misplaced dependencies)" }, + { + "name": "shipmonk/phpstan-rules", + "version": "4.1.4", + "source": { + "type": "git", + "url": "https://github.com/shipmonk-rnd/phpstan-rules.git", + "reference": "69059a59c0a6ae2091bd279b2507a579fded835a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/shipmonk-rnd/phpstan-rules/zipball/69059a59c0a6ae2091bd279b2507a579fded835a", + "reference": "69059a59c0a6ae2091bd279b2507a579fded835a" + }, + "require": { + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.1.8" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "ShipMonk\\PHPStan\\": "src/" + } + }, + "license": [ + "MIT" + ], + "description": "Various extra strict PHPStan rules we found useful in ShipMonk." + }, { "name": "spatie/array-to-xml", "version": "3.4.0", diff --git a/.dev-tools/phpstan.neon b/.dev-tools/phpstan.neon index e18ac6c1..52b72b5c 100644 --- a/.dev-tools/phpstan.neon +++ b/.dev-tools/phpstan.neon @@ -7,7 +7,8 @@ parameters: - ../vendor/autoload.php level: max ignoreErrors: - - '~internal (class|method) PhpCsFixer(CustomFixers(Dev)?)?\\~' + - message: '~internal (class|method) PhpCsFixer(CustomFixers(Dev)?)?\\~' + - identifier: shipmonk.checkedExceptionInYieldingMethod paths: - ../src - ../tests diff --git a/src/Analyzer/Analysis/SwitchAnalysis.php b/src/Analyzer/Analysis/SwitchAnalysis.php index 305976c0..9e43a72d 100644 --- a/src/Analyzer/Analysis/SwitchAnalysis.php +++ b/src/Analyzer/Analysis/SwitchAnalysis.php @@ -20,7 +20,7 @@ final class SwitchAnalysis private int $casesEnd; /** @var list */ - private array $cases = []; + private array $cases; /** * @param list $cases diff --git a/src/Fixer/IssetToArrayKeyExistsFixer.php b/src/Fixer/IssetToArrayKeyExistsFixer.php index 0fee4081..bb607100 100644 --- a/src/Fixer/IssetToArrayKeyExistsFixer.php +++ b/src/Fixer/IssetToArrayKeyExistsFixer.php @@ -85,7 +85,9 @@ public function fix(\SplFileInfo $file, Tokens $tokens): void $keyStartIndex = $tokens->getNextMeaningfulToken($openBrackets); \assert(\is_int($keyStartIndex)); + $keyEndIndex = $tokens->getPrevMeaningfulToken($closeBrackets); + \assert(\is_int($keyEndIndex)); $keyTokens = []; for ($i = $keyStartIndex; $i <= $keyEndIndex; $i++) { diff --git a/src/Fixer/StringableInterfaceFixer.php b/src/Fixer/StringableInterfaceFixer.php index 497c87d5..3015b364 100644 --- a/src/Fixer/StringableInterfaceFixer.php +++ b/src/Fixer/StringableInterfaceFixer.php @@ -151,6 +151,7 @@ private static function getInterfaces(Tokens $tokens, int $classKeywordIndex, in $interface = ''; for ( $index = $tokens->getNextMeaningfulToken($implementsIndex); + // @phpstan-ignore-next-line $index < $classOpenBraceIndex; $index = $tokens->getNextMeaningfulToken($index) ) { diff --git a/tests/AutoReview/TestsCodeTest.php b/tests/AutoReview/TestsCodeTest.php index 1d1f03bb..1941a96a 100644 --- a/tests/AutoReview/TestsCodeTest.php +++ b/tests/AutoReview/TestsCodeTest.php @@ -94,7 +94,7 @@ public function testDataProvidersKeys(string $className): void \assert($dataSet instanceof \Iterator); $keyType = null; - foreach (\array_keys(\iterator_to_array($dataSet)) as $key) { + foreach (\array_keys(\iterator_to_array($dataSet, true)) as $key) { // based on the type of first key determine what type should be for all keys if ($keyType === null) { $keyType = \is_int($key) ? 'int' : 'string'; @@ -127,9 +127,9 @@ public function testDataProvidersValues(string $className): void $dataProviders = self::getDataProviders($className); foreach ($dataProviders as $dataProvider) { - /** @var \Iterator> $dataSet */ - $dataSet = $dataProvider->invoke(null); - $dataSet = \iterator_to_array($dataSet); + /** @var \Iterator> $dataSetIterator */ + $dataSetIterator = $dataProvider->invoke(null); + $dataSet = \iterator_to_array($dataSetIterator, false); $doNotChangeCases = []; foreach ($dataSet as $value) { @@ -170,7 +170,7 @@ public static function provideTestClassCases(): iterable ->notName('autoload.php') ->in(__DIR__ . '/..'); - $tests = []; + $testsArray = []; /** @var SplFileInfo $file */ foreach ($finder as $file) { @@ -180,10 +180,10 @@ public static function provideTestClassCases(): iterable } $className .= '\\' . $file->getBasename('.php'); - $tests[$className] = [$className]; + $testsArray[$className] = [$className]; } - $tests = new \ArrayIterator($tests); + $tests = new \ArrayIterator($testsArray); } return $tests; diff --git a/tests/Fixer/SingleSpaceAfterStatementFixerTest.php b/tests/Fixer/SingleSpaceAfterStatementFixerTest.php index 11e26fd6..53ba382d 100644 --- a/tests/Fixer/SingleSpaceAfterStatementFixerTest.php +++ b/tests/Fixer/SingleSpaceAfterStatementFixerTest.php @@ -308,12 +308,12 @@ public function testTokenIsUseful(int $token): void $property->setValue($fixer, \array_diff($tokens, [$token])); - $tokens = Tokens::fromCode(self::EXAMPLE_WITH_ALL_TOKENS); - $fixer->fix(self::createSplFileInfoDouble(), $tokens); + $allTokens = Tokens::fromCode(self::EXAMPLE_WITH_ALL_TOKENS); + $fixer->fix(self::createSplFileInfoDouble(), $allTokens); self::assertNotSame( $expectedTokens->generateCode(), - $tokens->generateCode(), + $allTokens->generateCode(), \sprintf('Removing token %s did not broke fixing', Token::getNameForId($token)), ); } diff --git a/tests/FixersTest.php b/tests/FixersTest.php index 25d85220..b51bdf8c 100644 --- a/tests/FixersTest.php +++ b/tests/FixersTest.php @@ -62,13 +62,13 @@ public static function provideFixerIsInCollectionCases(): iterable } /** - * @return array + * @return list */ private static function fixerNamesFromCollection(): array { return \array_map( static fn (FixerInterface $fixer): string => $fixer->getName(), - \iterator_to_array(new Fixers()), + \iterator_to_array(new Fixers(), false), ); } }