1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-30 04:39:00 +01:00

Fix #1115 - allow a parent @throws to capture children

This commit is contained in:
Matthew Brown 2018-12-05 22:50:16 -05:00
parent b7710f7cb9
commit 5f34f6c478
3 changed files with 360 additions and 304 deletions

View File

@ -683,7 +683,24 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer implements Statements
if ($context->possibly_thrown_exceptions) { if ($context->possibly_thrown_exceptions) {
$ignored_exceptions = array_change_key_case($codebase->config->ignored_exceptions); $ignored_exceptions = array_change_key_case($codebase->config->ignored_exceptions);
$undocumented_throws = array_diff_key($context->possibly_thrown_exceptions, $storage->throws); $undocumented_throws = [];
foreach ($context->possibly_thrown_exceptions as $possibly_thrown_exception => $_) {
$is_expected = false;
foreach ($storage->throws as $expected_exception => $_) {
if ($expected_exception === $possibly_thrown_exception
|| $codebase->classExtends($possibly_thrown_exception, $expected_exception)
) {
$is_expected = true;
break;
}
}
if (!$is_expected) {
$undocumented_throws[$possibly_thrown_exception] = true;
}
}
foreach ($undocumented_throws as $possibly_thrown_exception => $_) { foreach ($undocumented_throws as $possibly_thrown_exception => $_) {
if (isset($ignored_exceptions[strtolower($possibly_thrown_exception)])) { if (isset($ignored_exceptions[strtolower($possibly_thrown_exception)])) {

View File

@ -195,309 +195,6 @@ class AnnotationTest extends TestCase
$this->analyzeFile('somefile.php', new Context()); $this->analyzeFile('somefile.php', new Context());
} }
/**
* @expectedException \Psalm\Exception\CodeException
* @expectedExceptionMessage MissingThrowsDocblock
*
* @return void
*/
public function testUndocumentedThrow()
{
Config::getInstance()->check_for_throws_docblock = true;
$this->addFile(
'somefile.php',
'<?php
function foo(int $x, int $y) : int {
if ($y === 0) {
throw new \RangeException("Cannot divide by zero");
}
if ($y < 0) {
throw new \InvalidArgumentException("This is also bad");
}
return intdiv($x, $y);
}'
);
$context = new Context();
$this->analyzeFile('somefile.php', $context);
}
/**
* @return void
*/
public function testDocumentedThrow()
{
Config::getInstance()->check_for_throws_docblock = true;
$this->addFile(
'somefile.php',
'<?php
/**
* @throws RangeException
* @throws InvalidArgumentException
*/
function foo(int $x, int $y) : int {
if ($y === 0) {
throw new \RangeException("Cannot divide by zero");
}
if ($y < 0) {
throw new \InvalidArgumentException("This is also bad");
}
return intdiv($x, $y);
}'
);
$context = new Context();
$this->analyzeFile('somefile.php', $context);
}
/**
* @expectedException \Psalm\Exception\CodeException
* @expectedExceptionMessage MissingThrowsDocblock
*
* @return void
*/
public function testUndocumentedThrowInFunctionCall()
{
Config::getInstance()->check_for_throws_docblock = true;
$this->addFile(
'somefile.php',
'<?php
/**
* @throws RangeException
* @throws InvalidArgumentException
*/
function foo(int $x, int $y) : int {
if ($y === 0) {
throw new \RangeException("Cannot divide by zero");
}
if ($y < 0) {
throw new \InvalidArgumentException("This is also bad");
}
return intdiv($x, $y);
}
function bar(int $x, int $y) : void {
foo($x, $y);
}'
);
$context = new Context();
$this->analyzeFile('somefile.php', $context);
}
/**
* @return void
*/
public function testDocumentedThrowInFunctionCall()
{
Config::getInstance()->check_for_throws_docblock = true;
$this->addFile(
'somefile.php',
'<?php
/**
* @throws RangeException
* @throws InvalidArgumentException
*/
function foo(int $x, int $y) : int {
if ($y === 0) {
throw new \RangeException("Cannot divide by zero");
}
if ($y < 0) {
throw new \InvalidArgumentException("This is also bad");
}
return intdiv($x, $y);
}
/**
* @throws RangeException
* @throws InvalidArgumentException
*/
function bar(int $x, int $y) : void {
foo($x, $y);
}'
);
$context = new Context();
$this->analyzeFile('somefile.php', $context);
}
/**
* @return void
*/
public function testDocumentedThrowInFunctionCallWithoutThrow()
{
Config::getInstance()->check_for_throws_docblock = true;
$this->addFile(
'somefile.php',
'<?php
class Foo
{
/**
* @throws \TypeError
*/
public static function notReallyThrowing(int $a): string
{
if ($a > 0) {
return "";
}
return (string) $a;
}
public function test(): string
{
try {
return self::notReallyThrowing(2);
} catch (\Throwable $E) {
return "";
}
}
}'
);
$context = new Context();
$this->analyzeFile('somefile.php', $context);
}
/**
* @return void
*/
public function testCaughtThrowInFunctionCall()
{
Config::getInstance()->check_for_throws_docblock = true;
$this->addFile(
'somefile.php',
'<?php
/**
* @throws RangeException
* @throws InvalidArgumentException
*/
function foo(int $x, int $y) : int {
if ($y === 0) {
throw new \RangeException("Cannot divide by zero");
}
if ($y < 0) {
throw new \InvalidArgumentException("This is also bad");
}
return intdiv($x, $y);
}
function bar(int $x, int $y) : void {
try {
foo($x, $y);
} catch (RangeException $e) {
} catch (InvalidArgumentException $e) {}
}'
);
$context = new Context();
$this->analyzeFile('somefile.php', $context);
}
/**
* @expectedException \Psalm\Exception\CodeException
* @expectedExceptionMessage MissingThrowsDocblock
*
* @return void
*/
public function testUncaughtThrowInFunctionCall()
{
Config::getInstance()->check_for_throws_docblock = true;
$this->addFile(
'somefile.php',
'<?php
/**
* @throws RangeException
* @throws InvalidArgumentException
*/
function foo(int $x, int $y) : int {
if ($y === 0) {
throw new \RangeException("Cannot divide by zero");
}
if ($y < 0) {
throw new \InvalidArgumentException("This is also bad");
}
return intdiv($x, $y);
}
function bar(int $x, int $y) : void {
try {
foo($x, $y);
} catch (\RangeException $e) {
}
}'
);
$context = new Context();
$this->analyzeFile('somefile.php', $context);
}
/**
* @return void
*/
public function testCaughtAllThrowInFunctionCall()
{
Config::getInstance()->check_for_throws_docblock = true;
$this->addFile(
'somefile.php',
'<?php
/**
* @throws RangeException
* @throws InvalidArgumentException
*/
function foo(int $x, int $y) : int {
if ($y === 0) {
throw new \RangeException("Cannot divide by zero");
}
if ($y < 0) {
throw new \InvalidArgumentException("This is also bad");
}
return intdiv($x, $y);
}
function bar(int $x, int $y) : void {
try {
foo($x, $y);
} catch (Exception $e) {}
}'
);
$context = new Context();
$this->analyzeFile('somefile.php', $context);
}
/** /**
* @return array * @return array
*/ */

View File

@ -0,0 +1,342 @@
<?php
namespace Psalm\Tests;
use Psalm\Config;
use Psalm\Context;
class ThrowsAnnotationTest extends TestCase
{
/**
* @expectedException \Psalm\Exception\CodeException
* @expectedExceptionMessage MissingThrowsDocblock
*
* @return void
*/
public function testUndocumentedThrow()
{
Config::getInstance()->check_for_throws_docblock = true;
$this->addFile(
'somefile.php',
'<?php
function foo(int $x, int $y) : int {
if ($y === 0) {
throw new \RangeException("Cannot divide by zero");
}
if ($y < 0) {
throw new \InvalidArgumentException("This is also bad");
}
return intdiv($x, $y);
}'
);
$context = new Context();
$this->analyzeFile('somefile.php', $context);
}
/**
* @return void
*/
public function testDocumentedThrow()
{
Config::getInstance()->check_for_throws_docblock = true;
$this->addFile(
'somefile.php',
'<?php
/**
* @throws RangeException
* @throws InvalidArgumentException
*/
function foo(int $x, int $y) : int {
if ($y === 0) {
throw new \RangeException("Cannot divide by zero");
}
if ($y < 0) {
throw new \InvalidArgumentException("This is also bad");
}
return intdiv($x, $y);
}'
);
$context = new Context();
$this->analyzeFile('somefile.php', $context);
}
/**
* @return void
*/
public function testDocumentedParentThrow()
{
Config::getInstance()->check_for_throws_docblock = true;
$this->addFile(
'somefile.php',
'<?php
/**
* @throws Exception
*/
function foo(int $x, int $y) : int {
if ($y === 0) {
throw new \RangeException("Cannot divide by zero");
}
if ($y < 0) {
throw new \InvalidArgumentException("This is also bad");
}
return intdiv($x, $y);
}'
);
$context = new Context();
$this->analyzeFile('somefile.php', $context);
}
/**
* @expectedException \Psalm\Exception\CodeException
* @expectedExceptionMessage MissingThrowsDocblock
*
* @return void
*/
public function testUndocumentedThrowInFunctionCall()
{
Config::getInstance()->check_for_throws_docblock = true;
$this->addFile(
'somefile.php',
'<?php
/**
* @throws RangeException
* @throws InvalidArgumentException
*/
function foo(int $x, int $y) : int {
if ($y === 0) {
throw new \RangeException("Cannot divide by zero");
}
if ($y < 0) {
throw new \InvalidArgumentException("This is also bad");
}
return intdiv($x, $y);
}
function bar(int $x, int $y) : void {
foo($x, $y);
}'
);
$context = new Context();
$this->analyzeFile('somefile.php', $context);
}
/**
* @return void
*/
public function testDocumentedThrowInFunctionCall()
{
Config::getInstance()->check_for_throws_docblock = true;
$this->addFile(
'somefile.php',
'<?php
/**
* @throws RangeException
* @throws InvalidArgumentException
*/
function foo(int $x, int $y) : int {
if ($y === 0) {
throw new \RangeException("Cannot divide by zero");
}
if ($y < 0) {
throw new \InvalidArgumentException("This is also bad");
}
return intdiv($x, $y);
}
/**
* @throws RangeException
* @throws InvalidArgumentException
*/
function bar(int $x, int $y) : void {
foo($x, $y);
}'
);
$context = new Context();
$this->analyzeFile('somefile.php', $context);
}
/**
* @return void
*/
public function testDocumentedThrowInFunctionCallWithoutThrow()
{
Config::getInstance()->check_for_throws_docblock = true;
$this->addFile(
'somefile.php',
'<?php
class Foo
{
/**
* @throws \TypeError
*/
public static function notReallyThrowing(int $a): string
{
if ($a > 0) {
return "";
}
return (string) $a;
}
public function test(): string
{
try {
return self::notReallyThrowing(2);
} catch (\Throwable $E) {
return "";
}
}
}'
);
$context = new Context();
$this->analyzeFile('somefile.php', $context);
}
/**
* @return void
*/
public function testCaughtThrowInFunctionCall()
{
Config::getInstance()->check_for_throws_docblock = true;
$this->addFile(
'somefile.php',
'<?php
/**
* @throws RangeException
* @throws InvalidArgumentException
*/
function foo(int $x, int $y) : int {
if ($y === 0) {
throw new \RangeException("Cannot divide by zero");
}
if ($y < 0) {
throw new \InvalidArgumentException("This is also bad");
}
return intdiv($x, $y);
}
function bar(int $x, int $y) : void {
try {
foo($x, $y);
} catch (RangeException $e) {
} catch (InvalidArgumentException $e) {}
}'
);
$context = new Context();
$this->analyzeFile('somefile.php', $context);
}
/**
* @expectedException \Psalm\Exception\CodeException
* @expectedExceptionMessage MissingThrowsDocblock
*
* @return void
*/
public function testUncaughtThrowInFunctionCall()
{
Config::getInstance()->check_for_throws_docblock = true;
$this->addFile(
'somefile.php',
'<?php
/**
* @throws RangeException
* @throws InvalidArgumentException
*/
function foo(int $x, int $y) : int {
if ($y === 0) {
throw new \RangeException("Cannot divide by zero");
}
if ($y < 0) {
throw new \InvalidArgumentException("This is also bad");
}
return intdiv($x, $y);
}
function bar(int $x, int $y) : void {
try {
foo($x, $y);
} catch (\RangeException $e) {
}
}'
);
$context = new Context();
$this->analyzeFile('somefile.php', $context);
}
/**
* @return void
*/
public function testCaughtAllThrowInFunctionCall()
{
Config::getInstance()->check_for_throws_docblock = true;
$this->addFile(
'somefile.php',
'<?php
/**
* @throws RangeException
* @throws InvalidArgumentException
*/
function foo(int $x, int $y) : int {
if ($y === 0) {
throw new \RangeException("Cannot divide by zero");
}
if ($y < 0) {
throw new \InvalidArgumentException("This is also bad");
}
return intdiv($x, $y);
}
function bar(int $x, int $y) : void {
try {
foo($x, $y);
} catch (Exception $e) {}
}'
);
$context = new Context();
$this->analyzeFile('somefile.php', $context);
}
}