1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-29 20:28:59 +01:00

Allow InvalidCatch and InvalidThrow to be suppressed per-class

Ref #411
This commit is contained in:
Matt Brown 2018-09-21 11:35:51 -04:00
parent 1ed61f42b9
commit db3ddaa42c
6 changed files with 182 additions and 19 deletions

View File

@ -150,7 +150,7 @@
<xs:element name="InvalidArrayAssignment" type="IssueHandlerType" minOccurs="0" />
<xs:element name="InvalidArrayOffset" type="IssueHandlerType" minOccurs="0" />
<xs:element name="InvalidCast" type="IssueHandlerType" minOccurs="0" />
<xs:element name="InvalidCatch" type="IssueHandlerType" minOccurs="0" />
<xs:element name="InvalidCatch" type="ClassIssueHandlerType" minOccurs="0" />
<xs:element name="InvalidClass" type="ClassIssueHandlerType" minOccurs="0" />
<xs:element name="InvalidClone" type="IssueHandlerType" minOccurs="0" />
<xs:element name="InvalidParamDefault" type="IssueHandlerType" minOccurs="0" />
@ -173,7 +173,7 @@
<xs:element name="InvalidStaticInvocation" type="IssueHandlerType" minOccurs="0" />
<xs:element name="InvalidStaticVariable" type="IssueHandlerType" minOccurs="0" />
<xs:element name="InvalidStringClass" type="IssueHandlerType" minOccurs="0" />
<xs:element name="InvalidThrow" type="IssueHandlerType" minOccurs="0" />
<xs:element name="InvalidThrow" type="ClassIssueHandlerType" minOccurs="0" />
<xs:element name="InvalidToString" type="IssueHandlerType" minOccurs="0" />
<xs:element name="LessSpecificImplementedReturnType" type="IssueHandlerType" minOccurs="0" />
<xs:element name="LessSpecificReturnStatement" type="IssueHandlerType" minOccurs="0" />

View File

@ -176,7 +176,8 @@ class TryChecker
if (IssueBuffer::accepts(
new InvalidCatch(
'Class/interface ' . $fq_catch_class . ' cannot be caught',
new CodeLocation($statements_checker->getSource(), $stmt)
new CodeLocation($statements_checker->getSource(), $stmt),
$fq_catch_class
),
$statements_checker->getSuppressedIssues()
)) {

View File

@ -33,20 +33,26 @@ class ThrowChecker
$file_checker = $statements_checker->getFileChecker();
$project_checker = $file_checker->project_checker;
if (!TypeChecker::isContainedBy($project_checker->codebase, $throw_type, $exception_type)) {
if (IssueBuffer::accepts(
new InvalidThrow(
'Cannot throw ' . $throw_type . ' as it does not extend Exception or implement Throwable',
new CodeLocation($file_checker, $stmt)
),
$statements_checker->getSuppressedIssues()
)) {
return false;
}
} elseif ($context->collect_exceptions) {
foreach ($throw_type->getTypes() as $throw_atomic_type) {
if ($throw_atomic_type instanceof TNamedObject) {
$context->possibly_thrown_exceptions[$throw_atomic_type->value] = true;
foreach ($throw_type->getTypes() as $throw_type_part) {
$throw_type_candidate = new Union([$throw_type_part]);
if (!TypeChecker::isContainedBy($project_checker->codebase, $throw_type_candidate, $exception_type)) {
if (IssueBuffer::accepts(
new InvalidThrow(
'Cannot throw ' . $throw_type_part
. ' as it does not extend Exception or implement Throwable',
new CodeLocation($file_checker, $stmt),
(string) $throw_type_part
),
$statements_checker->getSuppressedIssues()
)) {
return false;
}
} elseif ($context->collect_exceptions) {
foreach ($throw_type->getTypes() as $throw_atomic_type) {
if ($throw_atomic_type instanceof TNamedObject) {
$context->possibly_thrown_exceptions[$throw_atomic_type->value] = true;
}
}
}
}

View File

@ -1,6 +1,6 @@
<?php
namespace Psalm\Issue;
class InvalidCatch extends CodeIssue
class InvalidCatch extends ClassIssue
{
}

View File

@ -1,6 +1,6 @@
<?php
namespace Psalm\Issue;
class InvalidThrow extends CodeIssue
class InvalidThrow extends ClassIssue
{
}

View File

@ -764,6 +764,162 @@ class ConfigTest extends TestCase
$this->analyzeFile($file_path, new Context());
}
/**
* @expectedException \Psalm\Exception\CodeException
* @expectedExceptionMessage InvalidCatch
* @return void
*/
public function testValidThrowInvalidCatch()
{
$this->project_checker = $this->getProjectCheckerWithConfig(
TestConfig::loadFromXML(
dirname(__DIR__),
'<?xml version="1.0"?>
<psalm>
<issueHandlers>
<InvalidThrow>
<errorLevel type="suppress">
<referencedClass name="I" />
</errorLevel>
</InvalidThrow>
</issueHandlers>
</psalm>'
)
);
$file_path = getcwd() . '/src/somefile.php';
$this->addFile(
$file_path,
'<?php
interface I {}
class E extends Exception implements I {}
function foo() : void {
throw new E();
}
function handleThrow(I $e) : void {
echo "about to throw";
throw $e;
}
try {
foo();
} catch (I $e) {
handleThrow($e);
}'
);
$this->analyzeFile($file_path, new Context());
}
/**
* @expectedException \Psalm\Exception\CodeException
* @expectedExceptionMessage InvalidThrow
* @return void
*/
public function testInvalidThrowValidCatch()
{
$this->project_checker = $this->getProjectCheckerWithConfig(
TestConfig::loadFromXML(
dirname(__DIR__),
'<?xml version="1.0"?>
<psalm>
<issueHandlers>
<InvalidCatch>
<errorLevel type="suppress">
<referencedClass name="I" />
</errorLevel>
</InvalidCatch>
</issueHandlers>
</psalm>'
)
);
$file_path = getcwd() . '/src/somefile.php';
$this->addFile(
$file_path,
'<?php
interface I {}
class E extends Exception implements I {}
function foo() : void {
throw new E();
}
function handleThrow(I $e) : void {
echo "about to throw";
throw $e;
}
try {
foo();
} catch (I $e) {
handleThrow($e);
}'
);
$this->analyzeFile($file_path, new Context());
}
/**
* @return void
*/
public function testValidThrowValidCatch()
{
$this->project_checker = $this->getProjectCheckerWithConfig(
TestConfig::loadFromXML(
dirname(__DIR__),
'<?xml version="1.0"?>
<psalm>
<issueHandlers>
<InvalidCatch>
<errorLevel type="suppress">
<referencedClass name="I" />
</errorLevel>
</InvalidCatch>
<InvalidThrow>
<errorLevel type="suppress">
<referencedClass name="I" />
</errorLevel>
</InvalidThrow>
</issueHandlers>
</psalm>'
)
);
$file_path = getcwd() . '/src/somefile.php';
$this->addFile(
$file_path,
'<?php
interface I {}
class E extends Exception implements I {}
function foo() : void {
throw new E();
}
function handleThrow(I $e) : void {
echo "about to throw";
throw $e;
}
try {
foo();
} catch (I $e) {
handleThrow($e);
}'
);
$this->analyzeFile($file_path, new Context());
}
/**
* @return void
*/