From d91aab15be353b1edabec92e3447c70b78fedf85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tinjo=20Sch=C3=B6ni?= <32767367+tscni@users.noreply.github.com> Date: Wed, 29 Nov 2023 02:06:20 +0100 Subject: [PATCH 01/10] Restore support for null coalesce on match expressions https://github.com/vimeo/psalm/pull/10068 added isset restrictions that didn't consider null coalesces on match expressions. This restores that support by converting the match expression to a virtual variable for the isset analysis, similar to other incompatible expressions. --- .../Expression/BinaryOp/CoalesceAnalyzer.php | 1 + tests/MatchTest.php | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/CoalesceAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/CoalesceAnalyzer.php index 0a52166699a..72194f55224 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/CoalesceAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/CoalesceAnalyzer.php @@ -39,6 +39,7 @@ public static function analyze( || $root_expr instanceof PhpParser\Node\Expr\MethodCall || $root_expr instanceof PhpParser\Node\Expr\StaticCall || $root_expr instanceof PhpParser\Node\Expr\Cast + || $root_expr instanceof PhpParser\Node\Expr\Match_ || $root_expr instanceof PhpParser\Node\Expr\NullsafePropertyFetch || $root_expr instanceof PhpParser\Node\Expr\NullsafeMethodCall || $root_expr instanceof PhpParser\Node\Expr\Ternary diff --git a/tests/MatchTest.php b/tests/MatchTest.php index e281a200e97..64f2c4f5e0d 100644 --- a/tests/MatchTest.php +++ b/tests/MatchTest.php @@ -167,6 +167,21 @@ function process(Obj1|Obj2 $obj): int|string 'ignored_issues' => [], 'php_version' => '8.0', ], + 'nullCoalesce' => [ + 'code' => <<<'PHP' + null, + true => 1, + } ?? 2; + PHP, + 'assertions' => [ + '$match' => 'int', + ], + 'ignored_issues' => [], + 'php_version' => '8.0', + ], ]; } From 4f458b46bc6f86391c48341f0e7c049635a343af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tinjo=20Sch=C3=B6ni?= <32767367+tscni@users.noreply.github.com> Date: Wed, 29 Nov 2023 23:08:44 +0100 Subject: [PATCH 02/10] Fix static magic method pureness not being inherited from traits https://github.com/vimeo/psalm/pull/10385 "broke" this by propagating pseudo static methods from traits to using classes. `AtomicStaticCallAnalyzer` was then not capable of dealing with this, because now these static pseudo methods actually exist. As long as the methods from traits aren't actually transferred to the using class, it seems right that the logic in `AtomicStaticCallAnalyzer` uses `::getDeclaringMethodId()` instead of `::getAppearingMethodId()` for this purpose. --- .../StaticMethod/AtomicStaticCallAnalyzer.php | 8 +- tests/PureAnnotationTest.php | 75 +++++++++++++++++++ 2 files changed, 79 insertions(+), 4 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/AtomicStaticCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/AtomicStaticCallAnalyzer.php index e42024d08f6..ad9fd724f4e 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/AtomicStaticCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/AtomicStaticCallAnalyzer.php @@ -566,12 +566,12 @@ private static function handleNamedCall( true, $context->insideUse(), )) { - $callstatic_appearing_id = $codebase->methods->getAppearingMethodId($callstatic_id); - assert($callstatic_appearing_id !== null); + $callstatic_declaring_id = $codebase->methods->getDeclaringMethodId($callstatic_id); + assert($callstatic_declaring_id !== null); $callstatic_pure = false; $callstatic_mutation_free = false; - if ($codebase->methods->hasStorage($callstatic_appearing_id)) { - $callstatic_storage = $codebase->methods->getStorage($callstatic_appearing_id); + if ($codebase->methods->hasStorage($callstatic_declaring_id)) { + $callstatic_storage = $codebase->methods->getStorage($callstatic_declaring_id); $callstatic_pure = $callstatic_storage->pure; $callstatic_mutation_free = $callstatic_storage->mutation_free; } diff --git a/tests/PureAnnotationTest.php b/tests/PureAnnotationTest.php index f27632baed6..57c27e7b113 100644 --- a/tests/PureAnnotationTest.php +++ b/tests/PureAnnotationTest.php @@ -446,6 +446,81 @@ function gimmeFoo(): MyEnum return MyEnum::FOO(); }', ], + 'pureThroughCallStaticInTrait' => [ + 'code' => ' [ + 'code' => ' [ + 'code' => ' [ 'code' => ' Date: Fri, 1 Dec 2023 12:03:24 +0100 Subject: [PATCH 03/10] Use correct file path while adding unused suppressions for virtual __constructs --- src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php b/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php index 4e93704e2c3..7b7a9972c98 100644 --- a/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php @@ -186,7 +186,7 @@ public function analyze( || !in_array("UnusedPsalmSuppress", $storage->suppressed_issues) ) { foreach ($storage->suppressed_issues as $offset => $issue_name) { - IssueBuffer::addUnusedSuppression($this->getFilePath(), $offset, $issue_name); + IssueBuffer::addUnusedSuppression($storage->location->file_path, $offset, $issue_name); } } } From 8111319fc3eea807789f0c6f1d8211a823ef3fc9 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Fri, 1 Dec 2023 12:25:04 +0100 Subject: [PATCH 04/10] Fix --- src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php b/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php index 7b7a9972c98..920ff547287 100644 --- a/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php @@ -186,7 +186,13 @@ public function analyze( || !in_array("UnusedPsalmSuppress", $storage->suppressed_issues) ) { foreach ($storage->suppressed_issues as $offset => $issue_name) { - IssueBuffer::addUnusedSuppression($storage->location->file_path, $offset, $issue_name); + IssueBuffer::addUnusedSuppression( + $storage->location !== null + ? $storage->location->file_path + : $this->getFilePath(), + $offset, + $issue_name + ); } } } From 461cd184e5fc571bf82777a9efacfd1308b110d2 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Fri, 1 Dec 2023 12:25:30 +0100 Subject: [PATCH 05/10] cs-fix --- src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php b/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php index 920ff547287..3df1a6a1b0b 100644 --- a/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php @@ -191,7 +191,7 @@ public function analyze( ? $storage->location->file_path : $this->getFilePath(), $offset, - $issue_name + $issue_name, ); } } From 59fd539ab98b3401c6f25b57aaa8f00d163129e5 Mon Sep 17 00:00:00 2001 From: rarila Date: Fri, 1 Dec 2023 18:01:57 +0100 Subject: [PATCH 06/10] Fix POSIX only detection of absolute paths --- src/Psalm/Config/FileFilter.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Psalm/Config/FileFilter.php b/src/Psalm/Config/FileFilter.php index 4ef5f993c4c..dd8fb31186a 100644 --- a/src/Psalm/Config/FileFilter.php +++ b/src/Psalm/Config/FileFilter.php @@ -7,6 +7,7 @@ use RecursiveDirectoryIterator; use RecursiveIteratorIterator; use SimpleXMLElement; +use Symfony\Component\Filesystem\Path; use function array_filter; use function array_map; @@ -127,7 +128,7 @@ public static function loadFromArray( $resolve_symlinks = (bool) ($directory['resolveSymlinks'] ?? false); $declare_strict_types = (bool) ($directory['useStrictTypes'] ?? false); - if ($directory_path[0] === '/' && DIRECTORY_SEPARATOR === '/') { + if (Path::isAbsolute($directory_path)) { /** @var non-empty-string */ $prospective_directory_path = $directory_path; } else { From c6bf949c712b1565170d7ae1203d776ae18bfb6d Mon Sep 17 00:00:00 2001 From: kkmuffme <11071985+kkmuffme@users.noreply.github.com> Date: Sat, 2 Dec 2023 09:04:37 +0100 Subject: [PATCH 07/10] Fix CLI -r error Fix https://github.com/vimeo/psalm/issues/10418 --- src/Psalm/Internal/CliUtils.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Psalm/Internal/CliUtils.php b/src/Psalm/Internal/CliUtils.php index cdd4e281311..e90f73e4e5d 100644 --- a/src/Psalm/Internal/CliUtils.php +++ b/src/Psalm/Internal/CliUtils.php @@ -231,7 +231,7 @@ public static function getArguments(): array } if ($input_path[0] === '-' && strlen($input_path) === 2) { - if ($input_path[1] === 'c' || $input_path[1] === 'f') { + if ($input_path[1] === 'c' || $input_path[1] === 'f' || $input_path[1] === 'r') { ++$i; } continue; @@ -271,7 +271,7 @@ public static function getPathsToCheck($f_paths): ?array $input_path = $input_paths[$i]; if ($input_path[0] === '-' && strlen($input_path) === 2) { - if ($input_path[1] === 'c' || $input_path[1] === 'f') { + if ($input_path[1] === 'c' || $input_path[1] === 'f' || $input_path[1] === 'r') { ++$i; } continue; @@ -287,6 +287,7 @@ public static function getPathsToCheck($f_paths): ?array $ignored_arguments = array( 'config', 'printer', + 'root', ); if (in_array(substr($input_path, 2), $ignored_arguments, true)) { From 6eba2f564c499262bad320d6f697a3bdfc09e0fd Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sat, 2 Dec 2023 12:02:55 +0100 Subject: [PATCH 08/10] Fix return type of DOMXPath::query This can also return namespace nodes, which are not a child class of DOMNode. --- stubs/extensions/dom.phpstub | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stubs/extensions/dom.phpstub b/stubs/extensions/dom.phpstub index f52153787d6..2520a479902 100644 --- a/stubs/extensions/dom.phpstub +++ b/stubs/extensions/dom.phpstub @@ -975,7 +975,7 @@ class DOMXPath public function evaluate(string $expression, ?DOMNode $contextNode = null, bool $registerNodeNS = true): mixed {} /** - * @return DOMNodeList|false + * @return DOMNodeList|false */ public function query(string $expression, ?DOMNode $contextNode = null, bool $registerNodeNS = true): mixed {} From b03b846682e2ee6b90c2c2742faa7b93821a7c81 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Thu, 30 Nov 2023 13:48:32 +0100 Subject: [PATCH 09/10] Emit UnusedPsalmSuppress issues for suppressed issues already removed by plugins --- psalm-baseline.xml | 28 ++++++++++++++++++++- src/Psalm/IssueBuffer.php | 13 ++++++---- tests/CodebaseTest.php | 51 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 6 deletions(-) diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 49462a40934..12ccf4812d9 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1,5 +1,5 @@ - + tags['variablesfrom'][0]]]> @@ -350,6 +350,32 @@ $cs[0] + + + $config_file_path !== null + + + getArgument('pluginName')]]> + getOption('config')]]> + + + + + $config_file_path !== null + + + getArgument('pluginName')]]> + getOption('config')]]> + + + + + $config_file_path !== null + + + getOption('config')]]> + + $callable_method_name diff --git a/src/Psalm/IssueBuffer.php b/src/Psalm/IssueBuffer.php index 9ca29ef701b..33fc16bc800 100644 --- a/src/Psalm/IssueBuffer.php +++ b/src/Psalm/IssueBuffer.php @@ -133,6 +133,14 @@ final class IssueBuffer */ public static function accepts(CodeIssue $e, array $suppressed_issues = [], bool $is_fixable = false): bool { + $config = Config::getInstance(); + $project_analyzer = ProjectAnalyzer::getInstance(); + $codebase = $project_analyzer->getCodebase(); + $event = new BeforeAddIssueEvent($e, $is_fixable, $codebase); + if ($config->eventDispatcher->dispatchBeforeAddIssue($event) === false) { + return false; + } + if (self::isSuppressed($e, $suppressed_issues)) { return false; } @@ -258,11 +266,6 @@ public static function add(CodeIssue $e, bool $is_fixable = false): bool $project_analyzer = ProjectAnalyzer::getInstance(); $codebase = $project_analyzer->getCodebase(); - $event = new BeforeAddIssueEvent($e, $is_fixable, $codebase); - if ($config->eventDispatcher->dispatchBeforeAddIssue($event) === false) { - return false; - } - $fqcn_parts = explode('\\', get_class($e)); $issue_type = array_pop($fqcn_parts); diff --git a/tests/CodebaseTest.php b/tests/CodebaseTest.php index f5291649826..254922c2060 100644 --- a/tests/CodebaseTest.php +++ b/tests/CodebaseTest.php @@ -6,6 +6,7 @@ use PhpParser\Node\Stmt\Class_; use Psalm\Codebase; use Psalm\Context; +use Psalm\Exception\CodeException; use Psalm\Exception\UnpopulatedClasslikeException; use Psalm\Issue\InvalidReturnStatement; use Psalm\Issue\InvalidReturnType; @@ -21,6 +22,9 @@ use function array_map; use function array_values; use function get_class; +use function getcwd; + +use const DIRECTORY_SEPARATOR; class CodebaseTest extends TestCase { @@ -246,4 +250,51 @@ public static function beforeAddIssue(BeforeAddIssueEvent $event): ?bool $this->analyzeFile('somefile.php', new Context); self::assertSame(0, IssueBuffer::getErrorCount()); } + /** + * @test + */ + public function addingCodeIssueIsMarkedAsRedundant(): void + { + $this->expectException(CodeException::class); + $this->expectExceptionMessage('UnusedPsalmSuppress'); + + $this->addFile( + (string) getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'somefile.php', + 'getIssue(); + if ($issue->code_location->file_path !== (string) getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'somefile.php') { + return null; + } + if ($issue instanceof InvalidReturnStatement && $event->isFixable() === false) { + return false; + } elseif ($issue instanceof InvalidReturnType && $event->isFixable() === true) { + return false; + } + return null; + } + }; + + (new PluginRegistrationSocket($this->codebase->config, $this->codebase)) + ->registerHooksFromClass(get_class($eventHandler)); + + $this->analyzeFile( + (string) getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'somefile.php', + new Context, + ); + } } From ee5e4b800f01e183c7366badde4ef39be7663623 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Sun, 3 Dec 2023 12:36:14 +0100 Subject: [PATCH 10/10] Update --- psalm.xml.dist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/psalm.xml.dist b/psalm.xml.dist index 1452823757e..816cdc02e87 100644 --- a/psalm.xml.dist +++ b/psalm.xml.dist @@ -12,7 +12,7 @@ limitMethodComplexity="true" errorBaseline="psalm-baseline.xml" findUnusedPsalmSuppress="true" - findUnusedBaselineEntry="true" + findUnusedBaselineEntry="false" >