From ba67f0bd50b9787f8a165aef0375b48f02a61af3 Mon Sep 17 00:00:00 2001 From: Ruud Kamphuis Date: Fri, 24 Jan 2025 09:39:42 +0100 Subject: [PATCH 01/10] IgnoreErrorExtension This allows for more flexibility in ignoring errors. Some use cases: * Ignore `missingType.iterableValue` on controller actions: Rule: when the method is public and it has `#[Route]` attribute or the class has `#[AsController]` attribute. * Ignore `should return int but returns int|null` on `getId` for entities. Rule: class needs to have `#[Entity]` attribute. * Ignore `never returns null so it can be removed from the return type` Rule: method needs to have `#[GraphQL\Field]` attribute. * Enforce missingCheckedExceptionInThrows partially, only for specific classes. --- src/Analyser/AnalyserResultFinalizer.php | 11 +++++++ src/Analyser/FileAnalyser.php | 22 ++++++++++++- src/Analyser/IgnoreErrorExtension.php | 32 +++++++++++++++++++ src/Analyser/IgnoreErrorExtensionProvider.php | 22 +++++++++++++ 4 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 src/Analyser/IgnoreErrorExtension.php create mode 100644 src/Analyser/IgnoreErrorExtensionProvider.php diff --git a/src/Analyser/AnalyserResultFinalizer.php b/src/Analyser/AnalyserResultFinalizer.php index b88b3e0d31..ba51f37b97 100644 --- a/src/Analyser/AnalyserResultFinalizer.php +++ b/src/Analyser/AnalyserResultFinalizer.php @@ -19,6 +19,7 @@ final class AnalyserResultFinalizer public function __construct( private RuleRegistry $ruleRegistry, + private IgnoreErrorExtensionProvider $ignoreErrorExtensionProvider, private RuleErrorTransformer $ruleErrorTransformer, private ScopeFactory $scopeFactory, private LocalIgnoresProcessor $localIgnoresProcessor, @@ -92,6 +93,16 @@ public function finalize(AnalyserResult $analyserResult, bool $onlyFiles, bool $ } } + $tempCollectorErrors = array_filter($tempCollectorErrors, function (string $error) use ($scope, $node) : bool { + foreach ($this->ignoreErrorExtensionProvider->getExtensions() as $ignoreErrorExtension) { + if ($ignoreErrorExtension->ignore($error, $node, $scope)) { + return false; + } + } + + return true; + }); + $errors = $analyserResult->getUnorderedErrors(); $locallyIgnoredErrors = $analyserResult->getLocallyIgnoredErrors(); $allLinesToIgnore = $analyserResult->getLinesToIgnore(); diff --git a/src/Analyser/FileAnalyser.php b/src/Analyser/FileAnalyser.php index 570c637092..5cf9e44730 100644 --- a/src/Analyser/FileAnalyser.php +++ b/src/Analyser/FileAnalyser.php @@ -51,6 +51,7 @@ public function __construct( private NodeScopeResolver $nodeScopeResolver, private Parser $parser, private DependencyResolver $dependencyResolver, + private IgnoreErrorExtensionProvider $ignoreErrorExtensionProvider, private RuleErrorTransformer $ruleErrorTransformer, private LocalIgnoresProcessor $localIgnoresProcessor, ) @@ -142,7 +143,15 @@ public function analyseFile( } foreach ($ruleErrors as $ruleError) { - $temporaryFileErrors[] = $this->ruleErrorTransformer->transform($ruleError, $scope, $nodeType, $node->getStartLine()); + $error = $this->ruleErrorTransformer->transform($ruleError, $scope, $nodeType, $node->getStartLine()); + + foreach ($this->ignoreErrorExtensionProvider->getExtensions() as $ignoreErrorExtension) { + if ($ignoreErrorExtension->ignore($error, $node, $scope)) { + continue 2; + } + } + + $temporaryFileErrors[] = $error; } } @@ -281,6 +290,17 @@ public function analyseFile( unset($unmatchedLineIgnores[$fileKey]); } + $fileErrors = array_filter($fileErrors, function (Error $error) use ($scope) : bool { + foreach ($this->ignoreErrorExtensionProvider->getExtensions() as $ignoreErrorExtension) { + if ($ignoreErrorExtension->ignore($error, null, $scope)) { + return false; + } + } + + return true; + }); + + return new FileAnalyserResult( $fileErrors, $this->filteredPhpErrors, diff --git a/src/Analyser/IgnoreErrorExtension.php b/src/Analyser/IgnoreErrorExtension.php new file mode 100644 index 0000000000..6a74605a01 --- /dev/null +++ b/src/Analyser/IgnoreErrorExtension.php @@ -0,0 +1,32 @@ +container->getServicesByTag(IgnoreErrorExtension::EXTENSION_TAG); + } + +} From f0f284ef030588edd46c63cb3a7fe9e897ec5b81 Mon Sep 17 00:00:00 2001 From: Ruud Kamphuis Date: Mon, 17 Feb 2025 11:03:59 +0100 Subject: [PATCH 02/10] Rename ignore to shouldIgnore --- src/Analyser/AnalyserResultFinalizer.php | 2 +- src/Analyser/FileAnalyser.php | 4 ++-- src/Analyser/IgnoreErrorExtension.php | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Analyser/AnalyserResultFinalizer.php b/src/Analyser/AnalyserResultFinalizer.php index ba51f37b97..cb4df8c824 100644 --- a/src/Analyser/AnalyserResultFinalizer.php +++ b/src/Analyser/AnalyserResultFinalizer.php @@ -95,7 +95,7 @@ public function finalize(AnalyserResult $analyserResult, bool $onlyFiles, bool $ $tempCollectorErrors = array_filter($tempCollectorErrors, function (string $error) use ($scope, $node) : bool { foreach ($this->ignoreErrorExtensionProvider->getExtensions() as $ignoreErrorExtension) { - if ($ignoreErrorExtension->ignore($error, $node, $scope)) { + if ($ignoreErrorExtension->shouldIgnore($error, $node, $scope)) { return false; } } diff --git a/src/Analyser/FileAnalyser.php b/src/Analyser/FileAnalyser.php index 5cf9e44730..f92169ccd7 100644 --- a/src/Analyser/FileAnalyser.php +++ b/src/Analyser/FileAnalyser.php @@ -146,7 +146,7 @@ public function analyseFile( $error = $this->ruleErrorTransformer->transform($ruleError, $scope, $nodeType, $node->getStartLine()); foreach ($this->ignoreErrorExtensionProvider->getExtensions() as $ignoreErrorExtension) { - if ($ignoreErrorExtension->ignore($error, $node, $scope)) { + if ($ignoreErrorExtension->shouldIgnore($error, $node, $scope)) { continue 2; } } @@ -292,7 +292,7 @@ public function analyseFile( $fileErrors = array_filter($fileErrors, function (Error $error) use ($scope) : bool { foreach ($this->ignoreErrorExtensionProvider->getExtensions() as $ignoreErrorExtension) { - if ($ignoreErrorExtension->ignore($error, null, $scope)) { + if ($ignoreErrorExtension->shouldIgnore($error, null, $scope)) { return false; } } diff --git a/src/Analyser/IgnoreErrorExtension.php b/src/Analyser/IgnoreErrorExtension.php index 6a74605a01..04b95e675a 100644 --- a/src/Analyser/IgnoreErrorExtension.php +++ b/src/Analyser/IgnoreErrorExtension.php @@ -27,6 +27,6 @@ interface IgnoreErrorExtension public const EXTENSION_TAG = 'phpstan.ignoreErrorExtension'; - public function ignore(Error $error, ?Node $node, Scope $scope): bool; + public function shouldIgnore(Error $error, ?Node $node, Scope $scope): bool; } From c4d5dc6496da86c509b9aa42d807d5b576281f77 Mon Sep 17 00:00:00 2001 From: Ruud Kamphuis Date: Mon, 17 Feb 2025 11:06:07 +0100 Subject: [PATCH 03/10] Skip non ignorable errors --- src/Analyser/AnalyserResultFinalizer.php | 6 +++++- src/Analyser/FileAnalyser.php | 12 +++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Analyser/AnalyserResultFinalizer.php b/src/Analyser/AnalyserResultFinalizer.php index cb4df8c824..3455fbf5e3 100644 --- a/src/Analyser/AnalyserResultFinalizer.php +++ b/src/Analyser/AnalyserResultFinalizer.php @@ -93,7 +93,11 @@ public function finalize(AnalyserResult $analyserResult, bool $onlyFiles, bool $ } } - $tempCollectorErrors = array_filter($tempCollectorErrors, function (string $error) use ($scope, $node) : bool { + $tempCollectorErrors = array_filter($tempCollectorErrors, function (Error $error) use ($scope, $node) : bool { + if (! $error->canBeIgnored()) { + return true; + } + foreach ($this->ignoreErrorExtensionProvider->getExtensions() as $ignoreErrorExtension) { if ($ignoreErrorExtension->shouldIgnore($error, $node, $scope)) { return false; diff --git a/src/Analyser/FileAnalyser.php b/src/Analyser/FileAnalyser.php index f92169ccd7..b8c8575ac6 100644 --- a/src/Analyser/FileAnalyser.php +++ b/src/Analyser/FileAnalyser.php @@ -145,9 +145,11 @@ public function analyseFile( foreach ($ruleErrors as $ruleError) { $error = $this->ruleErrorTransformer->transform($ruleError, $scope, $nodeType, $node->getStartLine()); - foreach ($this->ignoreErrorExtensionProvider->getExtensions() as $ignoreErrorExtension) { - if ($ignoreErrorExtension->shouldIgnore($error, $node, $scope)) { - continue 2; + if ($error->canBeIgnored()) { + foreach ($this->ignoreErrorExtensionProvider->getExtensions() as $ignoreErrorExtension) { + if ($ignoreErrorExtension->shouldIgnore($error, $node, $scope)) { + continue 2; + } } } @@ -291,6 +293,10 @@ public function analyseFile( } $fileErrors = array_filter($fileErrors, function (Error $error) use ($scope) : bool { + if (! $error->canBeIgnored()) { + return true; + } + foreach ($this->ignoreErrorExtensionProvider->getExtensions() as $ignoreErrorExtension) { if ($ignoreErrorExtension->shouldIgnore($error, null, $scope)) { return false; From 69700cbb8e4d859f2db59dc2399df3e5dad1afb4 Mon Sep 17 00:00:00 2001 From: Ruud Kamphuis Date: Mon, 17 Feb 2025 11:09:26 +0100 Subject: [PATCH 04/10] Remove $tempCollectorErrors filter --- src/Analyser/AnalyserResultFinalizer.php | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/Analyser/AnalyserResultFinalizer.php b/src/Analyser/AnalyserResultFinalizer.php index 3455fbf5e3..8e910f5979 100644 --- a/src/Analyser/AnalyserResultFinalizer.php +++ b/src/Analyser/AnalyserResultFinalizer.php @@ -89,23 +89,19 @@ public function finalize(AnalyserResult $analyserResult, bool $onlyFiles, bool $ } foreach ($ruleErrors as $ruleError) { - $tempCollectorErrors[] = $this->ruleErrorTransformer->transform($ruleError, $scope, $nodeType, $node->getStartLine()); - } - } - - $tempCollectorErrors = array_filter($tempCollectorErrors, function (Error $error) use ($scope, $node) : bool { - if (! $error->canBeIgnored()) { - return true; - } + $error = $this->ruleErrorTransformer->transform($ruleError, $scope, $nodeType, $node->getStartLine()); - foreach ($this->ignoreErrorExtensionProvider->getExtensions() as $ignoreErrorExtension) { - if ($ignoreErrorExtension->shouldIgnore($error, $node, $scope)) { - return false; + if ( $error->canBeIgnored()) { + foreach ($this->ignoreErrorExtensionProvider->getExtensions() as $ignoreErrorExtension) { + if ($ignoreErrorExtension->shouldIgnore($error, $node, $scope)) { + continue 2; + } + } } - } - return true; - }); + $tempCollectorErrors[] = $error; + } + } $errors = $analyserResult->getUnorderedErrors(); $locallyIgnoredErrors = $analyserResult->getLocallyIgnoredErrors(); From 0a59f41abbb8783345552f28d94471f64c8d0c5c Mon Sep 17 00:00:00 2001 From: Ruud Kamphuis Date: Mon, 17 Feb 2025 11:12:43 +0100 Subject: [PATCH 05/10] Add IgnoreErrorExtensionProvider service --- conf/config.neon | 3 +++ 1 file changed, 3 insertions(+) diff --git a/conf/config.neon b/conf/config.neon index b9f6445b08..b7e91bd3d9 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -442,6 +442,9 @@ services: arguments: parser: @defaultAnalysisParser + - + class: PHPStan\Analyser\IgnoreErrorExtensionProvider + - class: PHPStan\Analyser\LocalIgnoresProcessor From cfe9f209fcb5614f45bc8601665efa035ee2b6d4 Mon Sep 17 00:00:00 2001 From: Ruud Kamphuis Date: Mon, 17 Feb 2025 11:26:53 +0100 Subject: [PATCH 06/10] Remove filtering for files --- src/Analyser/FileAnalyser.php | 16 +--------------- src/Analyser/IgnoreErrorExtension.php | 2 +- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/src/Analyser/FileAnalyser.php b/src/Analyser/FileAnalyser.php index b8c8575ac6..99cb8cd555 100644 --- a/src/Analyser/FileAnalyser.php +++ b/src/Analyser/FileAnalyser.php @@ -15,6 +15,7 @@ use PHPStan\Parser\Parser; use PHPStan\Parser\ParserErrorsException; use PHPStan\Rules\Registry as RuleRegistry; +use function array_filter; use function array_keys; use function array_unique; use function array_values; @@ -292,21 +293,6 @@ public function analyseFile( unset($unmatchedLineIgnores[$fileKey]); } - $fileErrors = array_filter($fileErrors, function (Error $error) use ($scope) : bool { - if (! $error->canBeIgnored()) { - return true; - } - - foreach ($this->ignoreErrorExtensionProvider->getExtensions() as $ignoreErrorExtension) { - if ($ignoreErrorExtension->shouldIgnore($error, null, $scope)) { - return false; - } - } - - return true; - }); - - return new FileAnalyserResult( $fileErrors, $this->filteredPhpErrors, diff --git a/src/Analyser/IgnoreErrorExtension.php b/src/Analyser/IgnoreErrorExtension.php index 04b95e675a..54ff6d2422 100644 --- a/src/Analyser/IgnoreErrorExtension.php +++ b/src/Analyser/IgnoreErrorExtension.php @@ -27,6 +27,6 @@ interface IgnoreErrorExtension public const EXTENSION_TAG = 'phpstan.ignoreErrorExtension'; - public function shouldIgnore(Error $error, ?Node $node, Scope $scope): bool; + public function shouldIgnore(Error $error, Node $node, Scope $scope): bool; } From 76fa4914435983943a7841bb43924fc364c19c26 Mon Sep 17 00:00:00 2001 From: Ruud Kamphuis Date: Mon, 17 Feb 2025 11:26:58 +0100 Subject: [PATCH 07/10] Fix tests --- src/Testing/RuleTestCase.php | 5 +++++ tests/PHPStan/Analyser/AnalyserTest.php | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/src/Testing/RuleTestCase.php b/src/Testing/RuleTestCase.php index b40a8ebca0..6827daca35 100644 --- a/src/Testing/RuleTestCase.php +++ b/src/Testing/RuleTestCase.php @@ -2,11 +2,13 @@ namespace PHPStan\Testing; +use Nette\DI\Container; use PhpParser\Node; use PHPStan\Analyser\Analyser; use PHPStan\Analyser\AnalyserResultFinalizer; use PHPStan\Analyser\Error; use PHPStan\Analyser\FileAnalyser; +use PHPStan\Analyser\IgnoreErrorExtensionProvider; use PHPStan\Analyser\InternalError; use PHPStan\Analyser\LocalIgnoresProcessor; use PHPStan\Analyser\NodeScopeResolver; @@ -15,6 +17,7 @@ use PHPStan\Collectors\Collector; use PHPStan\Collectors\Registry as CollectorRegistry; use PHPStan\Dependency\DependencyResolver; +use PHPStan\DependencyInjection\Nette\NetteContainer; use PHPStan\DependencyInjection\Type\DynamicThrowTypeExtensionProvider; use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; use PHPStan\DependencyInjection\Type\ParameterOutTypeExtensionProvider; @@ -113,6 +116,7 @@ private function getAnalyser(DirectRuleRegistry $ruleRegistry): Analyser $nodeScopeResolver, $this->getParser(), self::getContainer()->getByType(DependencyResolver::class), + new IgnoreErrorExtensionProvider(self::getContainer()), new RuleErrorTransformer(), new LocalIgnoresProcessor(), ); @@ -192,6 +196,7 @@ public function gatherAnalyserErrors(array $files): array $finalizer = new AnalyserResultFinalizer( $ruleRegistry, + new IgnoreErrorExtensionProvider(self::getContainer()), new RuleErrorTransformer(), $this->createScopeFactory($this->createReflectionProvider(), $this->getTypeSpecifier()), new LocalIgnoresProcessor(), diff --git a/tests/PHPStan/Analyser/AnalyserTest.php b/tests/PHPStan/Analyser/AnalyserTest.php index 6162106ac5..bacd85a967 100644 --- a/tests/PHPStan/Analyser/AnalyserTest.php +++ b/tests/PHPStan/Analyser/AnalyserTest.php @@ -2,6 +2,7 @@ namespace PHPStan\Analyser; +use Nette\DI\Container; use PhpParser\Lexer; use PhpParser\NodeVisitor\NameResolver; use PhpParser\Parser\Php7; @@ -10,6 +11,7 @@ use PHPStan\Collectors\Registry as CollectorRegistry; use PHPStan\Dependency\DependencyResolver; use PHPStan\Dependency\ExportedNodeResolver; +use PHPStan\DependencyInjection\Nette\NetteContainer; use PHPStan\DependencyInjection\Type\DynamicThrowTypeExtensionProvider; use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; use PHPStan\DependencyInjection\Type\ParameterOutTypeExtensionProvider; @@ -666,6 +668,7 @@ private function runAnalyser( $finalizer = new AnalyserResultFinalizer( new DirectRuleRegistry([]), + new IgnoreErrorExtensionProvider(new NetteContainer(new Container([]))), new RuleErrorTransformer(), $this->createScopeFactory( $this->createReflectionProvider(), @@ -742,6 +745,7 @@ private function createAnalyser(): Analyser new IgnoreLexer(), ), new DependencyResolver($fileHelper, $reflectionProvider, new ExportedNodeResolver($fileTypeMapper, new ExprPrinter(new Printer())), $fileTypeMapper), + new IgnoreErrorExtensionProvider(new NetteContainer(new Container([]))), new RuleErrorTransformer(), new LocalIgnoresProcessor(), ); From 43919cb1c4d4ec3111ad2591cac239c279787d05 Mon Sep 17 00:00:00 2001 From: Ruud Kamphuis Date: Mon, 17 Feb 2025 11:28:21 +0100 Subject: [PATCH 08/10] Fix CS --- src/Analyser/AnalyserResultFinalizer.php | 2 +- src/Analyser/FileAnalyser.php | 1 - src/Testing/RuleTestCase.php | 2 -- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Analyser/AnalyserResultFinalizer.php b/src/Analyser/AnalyserResultFinalizer.php index 8e910f5979..56fde0132e 100644 --- a/src/Analyser/AnalyserResultFinalizer.php +++ b/src/Analyser/AnalyserResultFinalizer.php @@ -91,7 +91,7 @@ public function finalize(AnalyserResult $analyserResult, bool $onlyFiles, bool $ foreach ($ruleErrors as $ruleError) { $error = $this->ruleErrorTransformer->transform($ruleError, $scope, $nodeType, $node->getStartLine()); - if ( $error->canBeIgnored()) { + if ($error->canBeIgnored()) { foreach ($this->ignoreErrorExtensionProvider->getExtensions() as $ignoreErrorExtension) { if ($ignoreErrorExtension->shouldIgnore($error, $node, $scope)) { continue 2; diff --git a/src/Analyser/FileAnalyser.php b/src/Analyser/FileAnalyser.php index 99cb8cd555..969154c3c8 100644 --- a/src/Analyser/FileAnalyser.php +++ b/src/Analyser/FileAnalyser.php @@ -15,7 +15,6 @@ use PHPStan\Parser\Parser; use PHPStan\Parser\ParserErrorsException; use PHPStan\Rules\Registry as RuleRegistry; -use function array_filter; use function array_keys; use function array_unique; use function array_values; diff --git a/src/Testing/RuleTestCase.php b/src/Testing/RuleTestCase.php index 6827daca35..e48e757bfe 100644 --- a/src/Testing/RuleTestCase.php +++ b/src/Testing/RuleTestCase.php @@ -2,7 +2,6 @@ namespace PHPStan\Testing; -use Nette\DI\Container; use PhpParser\Node; use PHPStan\Analyser\Analyser; use PHPStan\Analyser\AnalyserResultFinalizer; @@ -17,7 +16,6 @@ use PHPStan\Collectors\Collector; use PHPStan\Collectors\Registry as CollectorRegistry; use PHPStan\Dependency\DependencyResolver; -use PHPStan\DependencyInjection\Nette\NetteContainer; use PHPStan\DependencyInjection\Type\DynamicThrowTypeExtensionProvider; use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; use PHPStan\DependencyInjection\Type\ParameterOutTypeExtensionProvider; From cef37c0f09170022427ae30d13a60a5b9c14c6d6 Mon Sep 17 00:00:00 2001 From: Ruud Kamphuis Date: Mon, 17 Feb 2025 11:49:46 +0100 Subject: [PATCH 09/10] Add e2e test that demonstrates IgnoreErrorExtension --- .github/workflows/e2e-tests.yml | 4 ++ e2e/ignore-error-extension/.gitignore | 2 + e2e/ignore-error-extension/composer.json | 7 ++++ .../phpstan-baseline.neon | 20 +++++++++ e2e/ignore-error-extension/phpstan.neon.dist | 13 ++++++ ...trollerActionReturnTypeIgnoreExtension.php | 41 +++++++++++++++++++ .../src/HomepageController.php | 29 +++++++++++++ 7 files changed, 116 insertions(+) create mode 100644 e2e/ignore-error-extension/.gitignore create mode 100644 e2e/ignore-error-extension/composer.json create mode 100644 e2e/ignore-error-extension/phpstan-baseline.neon create mode 100644 e2e/ignore-error-extension/phpstan.neon.dist create mode 100644 e2e/ignore-error-extension/src/ControllerActionReturnTypeIgnoreExtension.php create mode 100644 e2e/ignore-error-extension/src/HomepageController.php diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 0d90c4116c..5e574eb6d2 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -247,6 +247,10 @@ jobs: cd e2e/bug-12606 export CONFIGTEST=test ../../bin/phpstan + - script: | + cd e2e/ignore-error-extension + composer install + ../../bin/phpstan steps: - name: "Checkout" diff --git a/e2e/ignore-error-extension/.gitignore b/e2e/ignore-error-extension/.gitignore new file mode 100644 index 0000000000..de4a392c33 --- /dev/null +++ b/e2e/ignore-error-extension/.gitignore @@ -0,0 +1,2 @@ +/vendor +/composer.lock diff --git a/e2e/ignore-error-extension/composer.json b/e2e/ignore-error-extension/composer.json new file mode 100644 index 0000000000..f8a4e6ebed --- /dev/null +++ b/e2e/ignore-error-extension/composer.json @@ -0,0 +1,7 @@ +{ + "autoload": { + "psr-4": { + "App\\": "src/" + } + } +} diff --git a/e2e/ignore-error-extension/phpstan-baseline.neon b/e2e/ignore-error-extension/phpstan-baseline.neon new file mode 100644 index 0000000000..717f56107f --- /dev/null +++ b/e2e/ignore-error-extension/phpstan-baseline.neon @@ -0,0 +1,20 @@ +parameters: + ignoreErrors: + - + message: '#^Method App\\HomepageController\:\:contactAction\(\) has parameter \$someUnrelatedError with no type specified\.$#' + identifier: missingType.parameter + count: 1 + path: src/HomepageController.php + + - + message: '#^Method App\\HomepageController\:\:getSomething\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/HomepageController.php + + - + message: '#^Method App\\HomepageController\:\:homeAction\(\) has parameter \$someUnrelatedError with no type specified\.$#' + identifier: missingType.parameter + count: 1 + path: src/HomepageController.php + diff --git a/e2e/ignore-error-extension/phpstan.neon.dist b/e2e/ignore-error-extension/phpstan.neon.dist new file mode 100644 index 0000000000..18dcfecd28 --- /dev/null +++ b/e2e/ignore-error-extension/phpstan.neon.dist @@ -0,0 +1,13 @@ +includes: + - phpstan-baseline.neon + +parameters: + level: 9 + paths: + - src + +services: + - + class: App\ControllerActionReturnTypeIgnoreExtension + tags: + - phpstan.ignoreErrorExtension diff --git a/e2e/ignore-error-extension/src/ControllerActionReturnTypeIgnoreExtension.php b/e2e/ignore-error-extension/src/ControllerActionReturnTypeIgnoreExtension.php new file mode 100644 index 0000000000..dc7b0dab5a --- /dev/null +++ b/e2e/ignore-error-extension/src/ControllerActionReturnTypeIgnoreExtension.php @@ -0,0 +1,41 @@ +getIdentifier() !== 'missingType.iterableValue') { + return false; + } + + // @phpstan-ignore phpstanApi.instanceofAssumption + if (! $node instanceof InClassMethodNode) { + return false; + } + + if (! str_ends_with($node->getClassReflection()->getName(), 'Controller')) { + return false; + } + + if (! str_ends_with($node->getMethodReflection()->getName(), 'Action')) { + return false; + } + + if (! $node->getMethodReflection()->isPublic()) { + return false; + } + + return true; + } +} diff --git a/e2e/ignore-error-extension/src/HomepageController.php b/e2e/ignore-error-extension/src/HomepageController.php new file mode 100644 index 0000000000..d55c955157 --- /dev/null +++ b/e2e/ignore-error-extension/src/HomepageController.php @@ -0,0 +1,29 @@ + 'Homepage', + 'something' => $this->getSomething(), + ]; + } + + public function contactAction($someUnrelatedError): array + { + return [ + 'title' => 'Contact', + 'something' => $this->getSomething(), + ]; + } + + private function getSomething(): array + { + return []; + } +} From 7f37ecc5ae75fc32afa7badac8fe74fbfb9d4c0d Mon Sep 17 00:00:00 2001 From: Ruud Kamphuis Date: Fri, 21 Feb 2025 09:56:35 +0100 Subject: [PATCH 10/10] Add tests for collectors --- .../phpstan-baseline.neon | 24 +++++++++++ e2e/ignore-error-extension/phpstan.neon.dist | 12 ++++++ .../src/ClassCollector.php | 29 +++++++++++++ e2e/ignore-error-extension/src/ClassRule.php | 43 +++++++++++++++++++ .../ControllerClassNameIgnoreExtension.php | 34 +++++++++++++++ 5 files changed, 142 insertions(+) create mode 100644 e2e/ignore-error-extension/src/ClassCollector.php create mode 100644 e2e/ignore-error-extension/src/ClassRule.php create mode 100644 e2e/ignore-error-extension/src/ControllerClassNameIgnoreExtension.php diff --git a/e2e/ignore-error-extension/phpstan-baseline.neon b/e2e/ignore-error-extension/phpstan-baseline.neon index 717f56107f..8c53510373 100644 --- a/e2e/ignore-error-extension/phpstan-baseline.neon +++ b/e2e/ignore-error-extension/phpstan-baseline.neon @@ -1,5 +1,29 @@ parameters: ignoreErrors: + - + message: '#^This is an error from a rule that uses a collector$#' + identifier: class.name + count: 1 + path: src/ClassCollector.php + + - + message: '#^This is an error from a rule that uses a collector$#' + identifier: class.name + count: 1 + path: src/ClassRule.php + + - + message: '#^This is an error from a rule that uses a collector$#' + identifier: class.name + count: 1 + path: src/ControllerActionReturnTypeIgnoreExtension.php + + - + message: '#^This is an error from a rule that uses a collector$#' + identifier: class.name + count: 1 + path: src/ControllerClassNameIgnoreExtension.php + - message: '#^Method App\\HomepageController\:\:contactAction\(\) has parameter \$someUnrelatedError with no type specified\.$#' identifier: missingType.parameter diff --git a/e2e/ignore-error-extension/phpstan.neon.dist b/e2e/ignore-error-extension/phpstan.neon.dist index 18dcfecd28..bc04b24e67 100644 --- a/e2e/ignore-error-extension/phpstan.neon.dist +++ b/e2e/ignore-error-extension/phpstan.neon.dist @@ -7,7 +7,19 @@ parameters: - src services: + - + class: App\ClassCollector + tags: + - phpstan.collector + - + class: App\ClassRule + tags: + - phpstan.rules.rule - class: App\ControllerActionReturnTypeIgnoreExtension tags: - phpstan.ignoreErrorExtension + - + class: App\ControllerClassNameIgnoreExtension + tags: + - phpstan.ignoreErrorExtension diff --git a/e2e/ignore-error-extension/src/ClassCollector.php b/e2e/ignore-error-extension/src/ClassCollector.php new file mode 100644 index 0000000000..03011e44fc --- /dev/null +++ b/e2e/ignore-error-extension/src/ClassCollector.php @@ -0,0 +1,29 @@ + + */ +final class ClassCollector implements Collector +{ + public function getNodeType(): string + { + return Node\Stmt\Class_::class; + } + + public function processNode(Node $node, Scope $scope) : ?array + { + if ($node->name === null) { + return null; + } + + return [$node->name->name, $node->getStartLine()]; + } +} diff --git a/e2e/ignore-error-extension/src/ClassRule.php b/e2e/ignore-error-extension/src/ClassRule.php new file mode 100644 index 0000000000..17283bafe5 --- /dev/null +++ b/e2e/ignore-error-extension/src/ClassRule.php @@ -0,0 +1,43 @@ + + */ +final class ClassRule implements Rule +{ + #[Override] + public function getNodeType() : string + { + return CollectedDataNode::class; + } + + #[Override] + public function processNode(Node $node, Scope $scope) : array + { + $errors = []; + + foreach ($node->get(ClassCollector::class) as $file => $data) { + foreach ($data as [$className, $line]) { + $errors[] = RuleErrorBuilder::message('This is an error from a rule that uses a collector') + ->file($file) + ->line($line) + ->identifier('class.name') + ->build(); + } + } + + return $errors; + } + +} diff --git a/e2e/ignore-error-extension/src/ControllerClassNameIgnoreExtension.php b/e2e/ignore-error-extension/src/ControllerClassNameIgnoreExtension.php new file mode 100644 index 0000000000..b52b4f7ef1 --- /dev/null +++ b/e2e/ignore-error-extension/src/ControllerClassNameIgnoreExtension.php @@ -0,0 +1,34 @@ +getIdentifier() !== 'class.name') { + return false; + } + + // @phpstan-ignore phpstanApi.instanceofAssumption + if (!$node instanceof CollectedDataNode) { + return false; + } + + if (!str_ends_with($error->getFile(), 'Controller.php')) { + return false; + } + + return true; + } +}