mirror of
https://github.com/danog/psalm.git
synced 2024-11-26 20:34:47 +01:00
Fix #1115 - allow a parent @throws to capture children
This commit is contained in:
parent
b7710f7cb9
commit
5f34f6c478
@ -683,7 +683,24 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer implements Statements
|
||||
if ($context->possibly_thrown_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 => $_) {
|
||||
if (isset($ignored_exceptions[strtolower($possibly_thrown_exception)])) {
|
||||
|
@ -195,309 +195,6 @@ class AnnotationTest extends TestCase
|
||||
$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
|
||||
*/
|
||||
|
342
tests/ThrowsAnnotationTest.php
Normal file
342
tests/ThrowsAnnotationTest.php
Normal 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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user