diff --git a/src/Psalm/Checker/FileChecker.php b/src/Psalm/Checker/FileChecker.php index a6df0d644..61c6955e6 100644 --- a/src/Psalm/Checker/FileChecker.php +++ b/src/Psalm/Checker/FileChecker.php @@ -33,7 +33,7 @@ class FileChecker extends SourceChecker implements StatementsSource protected $actual_file_path; /** - * @var array + * @var array */ protected $suppressed_issues = []; @@ -497,6 +497,26 @@ class FileChecker extends SourceChecker implements StatementsSource return $this->suppressed_issues; } + /** + * @param array $new_issues + * + * @return void + */ + public function addSuppressedIssues(array $new_issues) + { + $this->suppressed_issues = array_merge($new_issues, $this->suppressed_issues); + } + + /** + * @param array $new_issues + * + * @return void + */ + public function removeSuppressedIssues(array $new_issues) + { + $this->suppressed_issues = array_diff($this->suppressed_issues, $new_issues); + } + public function getFQCLN() { return null; diff --git a/src/Psalm/Checker/FunctionLikeChecker.php b/src/Psalm/Checker/FunctionLikeChecker.php index abba5461d..859449662 100644 --- a/src/Psalm/Checker/FunctionLikeChecker.php +++ b/src/Psalm/Checker/FunctionLikeChecker.php @@ -1321,6 +1321,26 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo return $this->suppressed_issues; } + /** + * @param array $new_issues + * + * @return void + */ + public function addSuppressedIssues(array $new_issues) + { + $this->suppressed_issues = array_merge($new_issues, $this->suppressed_issues); + } + + /** + * @param array $new_issues + * + * @return void + */ + public function removeSuppressedIssues(array $new_issues) + { + $this->suppressed_issues = array_diff($this->suppressed_issues, $new_issues); + } + /** * Adds a suppressed issue, useful when creating a method checker from scratch * diff --git a/src/Psalm/Checker/SourceChecker.php b/src/Psalm/Checker/SourceChecker.php index a0aa52bb5..634b50f1a 100644 --- a/src/Psalm/Checker/SourceChecker.php +++ b/src/Psalm/Checker/SourceChecker.php @@ -161,6 +161,34 @@ abstract class SourceChecker implements StatementsSource return $this->source->getSuppressedIssues(); } + /** + * @param array $new_issues + * + * @return void + */ + public function addSuppressedIssues(array $new_issues) + { + if ($this->source === null) { + throw new \UnexpectedValueException('$source cannot be null'); + } + + return $this->source->addSuppressedIssues($new_issues); + } + + /** + * @param array $new_issues + * + * @return void + */ + public function removeSuppressedIssues(array $new_issues) + { + if ($this->source === null) { + throw new \UnexpectedValueException('$source cannot be null'); + } + + return $this->source->removeSuppressedIssues($new_issues); + } + /** * @return string */ diff --git a/src/Psalm/Checker/StatementsChecker.php b/src/Psalm/Checker/StatementsChecker.php index 59fef1f4d..c60f4bf7f 100644 --- a/src/Psalm/Checker/StatementsChecker.php +++ b/src/Psalm/Checker/StatementsChecker.php @@ -129,6 +129,33 @@ class StatementsChecker extends SourceChecker implements StatementsSource } */ + $new_issues = null; + + if ($docblock = $stmt->getDocComment()) { + $comments = CommentChecker::parseDocComment((string)$docblock); + if (isset($comments['specials']['psalm-suppress'])) { + $suppressed = array_filter( + array_map( + /** + * @param string $line + * + * @return string + */ + function ($line) { + return explode(' ', trim($line))[0]; + }, + $comments['specials']['psalm-suppress'] + ) + ); + + if ($suppressed) { + $new_issues = array_diff($suppressed, $this->source->getSuppressedIssues()); + /** @psalm-suppress TypeCoercion */ + $this->addSuppressedIssues($new_issues); + } + } + } + if ($stmt instanceof PhpParser\Node\Stmt\If_) { IfChecker::analyze($this, $stmt, $context, $loop_context); } elseif ($stmt instanceof PhpParser\Node\Stmt\TryCatch) { @@ -348,6 +375,11 @@ class StatementsChecker extends SourceChecker implements StatementsSource return false; } } + + if ($new_issues) { + /** @psalm-suppress TypeCoercion */ + $this->removeSuppressedIssues($new_issues); + } } return null; diff --git a/src/Psalm/StatementsSource.php b/src/Psalm/StatementsSource.php index 3db359917..6d05b44a4 100644 --- a/src/Psalm/StatementsSource.php +++ b/src/Psalm/StatementsSource.php @@ -82,4 +82,18 @@ interface StatementsSource * @return array */ public function getSuppressedIssues(); + + /** + * @param array $new_issues + * + * @return void + */ + public function addSuppressedIssues(array $new_issues); + + /** + * @param array $new_issues + * + * @return void + */ + public function removeSuppressedIssues(array $new_issues); } diff --git a/src/Psalm/TraitSource.php b/src/Psalm/TraitSource.php index ee90435f5..a1ed8c63e 100644 --- a/src/Psalm/TraitSource.php +++ b/src/Psalm/TraitSource.php @@ -140,4 +140,22 @@ class TraitSource implements StatementsSource { return []; } + + /** + * @param array $new_issues + * + * @return void + */ + public function addSuppressedIssues(array $new_issues) + { + } + + /** + * @param array $new_issues + * + * @return void + */ + public function removeSuppressedIssues(array $new_issues) + { + } } diff --git a/tests/AssertTest.php b/tests/AssertTest.php new file mode 100644 index 000000000..711c2a641 --- /dev/null +++ b/tests/AssertTest.php @@ -0,0 +1,54 @@ + [ + 'foo(); + } + + function takesA(A $a) : void { + assertInstanceOfB($a); + $a->foo(); + }', + ], + ]; + } + + /** + * @return array + */ + public function providerFileCheckerInvalidCodeParse() + { + return []; + } +} diff --git a/tests/IssueSuppressionTest.php b/tests/IssueSuppressionTest.php index 15d607cdd..563ddefcd 100644 --- a/tests/IssueSuppressionTest.php +++ b/tests/IssueSuppressionTest.php @@ -4,6 +4,7 @@ namespace Psalm\Tests; class IssueSuppressionTest extends TestCase { use Traits\FileCheckerValidCodeParseTestTrait; + use Traits\FileCheckerInvalidCodeParseTestTrait; /** * @return array @@ -24,6 +25,24 @@ class IssueSuppressionTest extends TestCase } }', ], + 'undefinedClassOneLine' => [ + ' [ + ' [ ' [ + ' 'UndefinedClass - src/somefile.php:8 - Class or interface C', + ], + 'undefinedClassOneLineInFileAfter' => [ + ' 'UndefinedClass - src/somefile.php:6 - Class or interface C', + ], + ]; + } }