2017-02-08 06:28:26 +01:00
|
|
|
<?php
|
2021-12-15 04:58:32 +01:00
|
|
|
|
2017-02-08 06:28:26 +01:00
|
|
|
namespace Psalm\Tests;
|
|
|
|
|
2018-01-29 05:41:11 +01:00
|
|
|
use Psalm\Config;
|
2017-07-25 23:04:58 +02:00
|
|
|
use Psalm\Context;
|
2021-12-03 20:29:06 +01:00
|
|
|
use Psalm\Exception\CodeException;
|
2021-12-03 20:11:20 +01:00
|
|
|
use Psalm\Internal\Analyzer\ProjectAnalyzer;
|
2021-07-02 01:10:21 +02:00
|
|
|
use Psalm\Internal\Provider\FakeFileProvider;
|
2021-12-03 20:11:20 +01:00
|
|
|
use Psalm\Internal\Provider\Providers;
|
2020-08-23 16:32:07 +02:00
|
|
|
use Psalm\Internal\RuntimeCaches;
|
2021-11-12 09:24:39 +01:00
|
|
|
use Psalm\IssueBuffer;
|
2021-12-04 21:55:53 +01:00
|
|
|
use Psalm\Tests\Internal\Provider\FakeParserCacheProvider;
|
2017-02-08 06:28:26 +01:00
|
|
|
|
2021-11-12 09:24:39 +01:00
|
|
|
use function getcwd;
|
2021-12-03 21:07:25 +01:00
|
|
|
use function preg_quote;
|
|
|
|
use function strpos;
|
2021-11-12 09:24:39 +01:00
|
|
|
|
|
|
|
use const DIRECTORY_SEPARATOR;
|
|
|
|
|
2017-04-25 05:45:02 +02:00
|
|
|
class UnusedCodeTest extends TestCase
|
2017-02-08 06:28:26 +01:00
|
|
|
{
|
2022-12-16 19:58:47 +01:00
|
|
|
protected ProjectAnalyzer $project_analyzer;
|
2017-02-08 06:28:26 +01:00
|
|
|
|
2021-12-05 18:51:26 +01:00
|
|
|
public function setUp(): void
|
2017-02-08 06:28:26 +01:00
|
|
|
{
|
2020-08-23 16:32:07 +02:00
|
|
|
RuntimeCaches::clearAll();
|
2017-07-25 22:11:02 +02:00
|
|
|
|
2021-07-02 01:10:21 +02:00
|
|
|
$this->file_provider = new FakeFileProvider();
|
2017-07-25 22:11:02 +02:00
|
|
|
|
2021-12-03 20:11:20 +01:00
|
|
|
$this->project_analyzer = new ProjectAnalyzer(
|
2018-01-21 16:22:04 +01:00
|
|
|
new TestConfig(),
|
2021-12-03 20:11:20 +01:00
|
|
|
new Providers(
|
2018-09-28 22:18:45 +02:00
|
|
|
$this->file_provider,
|
2022-12-18 17:15:15 +01:00
|
|
|
new FakeParserCacheProvider(),
|
|
|
|
),
|
2017-07-25 22:11:02 +02:00
|
|
|
);
|
|
|
|
|
2018-11-11 18:01:14 +01:00
|
|
|
$this->project_analyzer->getCodebase()->reportUnusedCode();
|
2021-11-27 01:06:33 +01:00
|
|
|
$this->project_analyzer->setPhpVersion('7.3', 'tests');
|
2017-02-08 06:28:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-11-06 03:57:36 +01:00
|
|
|
* @dataProvider providerValidCodeParse
|
2022-11-05 22:34:42 +01:00
|
|
|
* @param array<string> $ignored_issues
|
2017-07-25 23:04:58 +02:00
|
|
|
*/
|
2022-12-15 03:26:17 +01:00
|
|
|
public function testValidCode(string $code, array $ignored_issues = []): void
|
2017-07-25 23:04:58 +02:00
|
|
|
{
|
2018-07-13 23:44:50 +02:00
|
|
|
$test_name = $this->getTestName();
|
2021-12-03 21:07:25 +01:00
|
|
|
if (strpos($test_name, 'SKIPPED-') !== false) {
|
2017-07-25 23:04:58 +02:00
|
|
|
$this->markTestSkipped('Skipped due to a bug.');
|
|
|
|
}
|
|
|
|
|
2018-01-21 16:22:04 +01:00
|
|
|
$file_path = self::$src_dir_path . 'somefile.php';
|
2017-07-25 23:04:58 +02:00
|
|
|
|
|
|
|
$this->addFile(
|
2018-01-21 16:22:04 +01:00
|
|
|
$file_path,
|
2022-12-18 17:15:15 +01:00
|
|
|
$code,
|
2017-07-25 23:04:58 +02:00
|
|
|
);
|
|
|
|
|
2021-11-27 01:06:33 +01:00
|
|
|
$this->project_analyzer->setPhpVersion('8.0', 'tests');
|
2021-03-31 16:08:52 +02:00
|
|
|
|
2022-11-05 22:34:42 +01:00
|
|
|
foreach ($ignored_issues as $error_level) {
|
2018-11-11 18:01:14 +01:00
|
|
|
$this->project_analyzer->getCodebase()->config->setCustomErrorLevel($error_level, Config::REPORT_SUPPRESS);
|
2018-01-29 18:13:44 +01:00
|
|
|
}
|
|
|
|
|
2020-03-28 21:30:56 +01:00
|
|
|
$this->analyzeFile($file_path, new Context(), false);
|
2018-02-17 23:45:30 +01:00
|
|
|
|
2019-12-02 21:24:01 +01:00
|
|
|
$this->project_analyzer->consolidateAnalyzedData();
|
2019-08-18 22:59:56 +02:00
|
|
|
|
2021-12-03 20:11:20 +01:00
|
|
|
IssueBuffer::processUnusedSuppressions($this->project_analyzer->getCodebase()->file_provider);
|
2017-07-25 23:04:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-11-06 03:57:36 +01:00
|
|
|
* @dataProvider providerInvalidCodeParse
|
2022-11-05 22:34:42 +01:00
|
|
|
* @param array<string> $ignored_issues
|
2017-02-08 06:28:26 +01:00
|
|
|
*/
|
2022-12-15 03:26:17 +01:00
|
|
|
public function testInvalidCode(string $code, string $error_message, array $ignored_issues = []): void
|
2017-02-08 06:28:26 +01:00
|
|
|
{
|
2021-12-03 21:07:25 +01:00
|
|
|
if (strpos($this->getTestName(), 'SKIPPED-') !== false) {
|
2017-07-25 23:04:58 +02:00
|
|
|
$this->markTestSkipped();
|
|
|
|
}
|
|
|
|
|
2021-12-03 20:29:06 +01:00
|
|
|
$this->expectException(CodeException::class);
|
2022-01-19 19:29:16 +01:00
|
|
|
$this->expectExceptionMessageMatches('/\b' . preg_quote($error_message, '/') . '\b/');
|
2017-02-08 06:28:26 +01:00
|
|
|
|
2018-01-21 16:22:04 +01:00
|
|
|
$file_path = self::$src_dir_path . 'somefile.php';
|
|
|
|
|
2022-11-05 22:34:42 +01:00
|
|
|
foreach ($ignored_issues as $error_level) {
|
2018-11-11 18:01:14 +01:00
|
|
|
$this->project_analyzer->getCodebase()->config->setCustomErrorLevel($error_level, Config::REPORT_SUPPRESS);
|
2018-01-29 05:41:11 +01:00
|
|
|
}
|
|
|
|
|
2017-07-25 22:11:02 +02:00
|
|
|
$this->addFile(
|
2018-01-21 16:22:04 +01:00
|
|
|
$file_path,
|
2022-12-18 17:15:15 +01:00
|
|
|
$code,
|
2017-07-25 22:11:02 +02:00
|
|
|
);
|
2017-02-08 06:28:26 +01:00
|
|
|
|
2020-03-28 21:30:56 +01:00
|
|
|
$this->analyzeFile($file_path, new Context(), false);
|
2018-02-17 23:45:30 +01:00
|
|
|
|
2019-12-02 21:24:01 +01:00
|
|
|
$this->project_analyzer->consolidateAnalyzedData();
|
2019-08-18 22:59:56 +02:00
|
|
|
|
2021-12-03 20:11:20 +01:00
|
|
|
IssueBuffer::processUnusedSuppressions($this->project_analyzer->getCodebase()->file_provider);
|
2017-02-08 06:28:26 +01:00
|
|
|
}
|
|
|
|
|
2021-11-12 09:24:39 +01:00
|
|
|
public function testSeesClassesUsedAfterUnevaluatedCodeIssue(): void
|
|
|
|
{
|
|
|
|
$this->project_analyzer->getConfig()->throw_exception = false;
|
|
|
|
$file_path = getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'somefile.php';
|
|
|
|
|
|
|
|
$this->addFile(
|
|
|
|
$file_path,
|
|
|
|
'<?php
|
|
|
|
if (rand(0, 1)) {
|
|
|
|
throw new Exception("foo");
|
|
|
|
echo "bar";
|
|
|
|
} else {
|
|
|
|
$f = new Foo();
|
|
|
|
$f->bar();
|
|
|
|
}
|
|
|
|
|
|
|
|
class Foo {
|
|
|
|
function bar(): void{
|
|
|
|
echo "foo";
|
|
|
|
}
|
|
|
|
}
|
2022-12-18 17:15:15 +01:00
|
|
|
',
|
2021-11-12 09:24:39 +01:00
|
|
|
);
|
|
|
|
$this->analyzeFile($file_path, new Context(), false);
|
|
|
|
$this->project_analyzer->consolidateAnalyzedData();
|
|
|
|
|
|
|
|
$this->assertSame(1, IssueBuffer::getErrorCount());
|
|
|
|
$issue = IssueBuffer::getIssuesDataForFile($file_path)[0];
|
|
|
|
$this->assertSame('UnevaluatedCode', $issue->type);
|
|
|
|
$this->assertSame(4, $issue->line_from);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function testSeesUnusedClassReferencedByUnevaluatedCode(): void
|
|
|
|
{
|
|
|
|
$this->project_analyzer->getConfig()->throw_exception = false;
|
|
|
|
$file_path = getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'somefile.php';
|
|
|
|
|
|
|
|
$this->addFile(
|
|
|
|
$file_path,
|
|
|
|
'<?php
|
|
|
|
if (rand(0, 1)) {
|
|
|
|
throw new Exception("foo");
|
|
|
|
$f = new Foo();
|
|
|
|
$f->bar();
|
|
|
|
} else {
|
|
|
|
echo "bar";
|
|
|
|
}
|
|
|
|
|
|
|
|
class Foo {
|
|
|
|
function bar(): void{
|
|
|
|
echo "foo";
|
|
|
|
}
|
|
|
|
}
|
2022-12-18 17:15:15 +01:00
|
|
|
',
|
2021-11-12 09:24:39 +01:00
|
|
|
);
|
|
|
|
$this->analyzeFile($file_path, new Context(), false);
|
|
|
|
$this->project_analyzer->consolidateAnalyzedData();
|
|
|
|
|
|
|
|
$this->assertSame(3, IssueBuffer::getErrorCount());
|
|
|
|
$issue = IssueBuffer::getIssuesDataForFile($file_path)[2];
|
|
|
|
$this->assertSame('UnusedClass', $issue->type);
|
|
|
|
$this->assertSame(10, $issue->line_from);
|
|
|
|
}
|
|
|
|
|
2017-02-08 06:28:26 +01:00
|
|
|
/**
|
2022-11-12 02:14:21 +01:00
|
|
|
* @return array<string, array{code:string}>
|
2017-02-08 06:28:26 +01:00
|
|
|
*/
|
2020-09-12 17:24:05 +02:00
|
|
|
public function providerValidCodeParse(): array
|
2017-02-08 06:28:26 +01:00
|
|
|
{
|
2017-04-25 05:45:02 +02:00
|
|
|
return [
|
2017-11-11 00:08:17 +01:00
|
|
|
'magicCall' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2017-11-11 00:08:17 +01:00
|
|
|
class A {
|
|
|
|
/** @var string */
|
|
|
|
private $value = "default";
|
|
|
|
|
|
|
|
/** @param string[] $args */
|
|
|
|
public function __call(string $name, array $args) {
|
|
|
|
if (count($args) == 1) {
|
|
|
|
$this->modify($name, $args[0]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-11 21:50:45 +01:00
|
|
|
private function modify(string $name, string $value): void {
|
2019-02-22 06:50:41 +01:00
|
|
|
call_user_func([$this, "modify" . $name], $value);
|
2017-11-11 00:08:17 +01:00
|
|
|
}
|
|
|
|
|
2018-01-11 21:50:45 +01:00
|
|
|
public function modifyFoo(string $value): void {
|
2017-11-11 00:08:17 +01:00
|
|
|
$this->value = $value;
|
|
|
|
}
|
2018-01-11 05:29:18 +01:00
|
|
|
|
|
|
|
public function getFoo() : string {
|
|
|
|
return $this->value;
|
|
|
|
}
|
2017-11-11 00:08:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
$m = new A();
|
|
|
|
$m->foo("value");
|
2018-01-11 05:29:18 +01:00
|
|
|
$m->modifyFoo("value2");
|
|
|
|
echo $m->getFoo();',
|
2017-11-11 00:08:17 +01:00
|
|
|
],
|
2018-06-10 05:10:42 +02:00
|
|
|
'usedTraitMethodWithExplicitCall' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2017-12-29 17:26:28 +01:00
|
|
|
class A {
|
2018-01-11 21:50:45 +01:00
|
|
|
public function foo(): void {
|
2017-12-29 17:26:28 +01:00
|
|
|
echo "parent method";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
trait T {
|
2018-01-11 21:50:45 +01:00
|
|
|
public function foo(): void {
|
2017-12-29 17:26:28 +01:00
|
|
|
echo "trait method";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class B extends A {
|
|
|
|
use T;
|
|
|
|
}
|
|
|
|
|
|
|
|
(new A)->foo();
|
|
|
|
(new B)->foo();',
|
|
|
|
],
|
2017-12-30 01:38:01 +01:00
|
|
|
'usedInterfaceMethod' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2017-12-30 01:38:01 +01:00
|
|
|
interface I {
|
2018-01-11 21:50:45 +01:00
|
|
|
public function foo(): void;
|
2017-12-30 01:38:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
class A implements I {
|
2018-01-11 21:50:45 +01:00
|
|
|
public function foo(): void {}
|
2017-12-30 01:38:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
(new A)->foo();',
|
|
|
|
],
|
2018-01-29 05:41:11 +01:00
|
|
|
'constructorIsUsed' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2018-01-29 05:41:11 +01:00
|
|
|
class A {
|
|
|
|
public function __construct() {
|
|
|
|
$this->foo();
|
|
|
|
}
|
|
|
|
private function foo() : void {}
|
|
|
|
}
|
|
|
|
$a = new A();
|
|
|
|
echo (bool) $a;',
|
|
|
|
],
|
2018-02-17 18:02:31 +01:00
|
|
|
'everythingUsed' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2018-02-17 18:02:31 +01:00
|
|
|
interface I {
|
2019-07-29 02:42:35 +02:00
|
|
|
public function foo() : void;
|
2018-02-17 18:02:31 +01:00
|
|
|
}
|
|
|
|
class B implements I {
|
|
|
|
public function foo() : void {}
|
|
|
|
}
|
|
|
|
|
|
|
|
class A
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* @var I
|
|
|
|
*/
|
|
|
|
private $i;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param int[] $as
|
|
|
|
*/
|
|
|
|
public function __construct(array $as) {
|
2019-02-13 19:32:19 +01:00
|
|
|
$this->i = new B();
|
2018-02-17 18:02:31 +01:00
|
|
|
|
|
|
|
foreach ($as as $a) {
|
|
|
|
$this->a($a, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-10 18:57:13 +02:00
|
|
|
private function a(int $a, int $b): void
|
2018-02-17 18:02:31 +01:00
|
|
|
{
|
|
|
|
$this->v($a, $b);
|
|
|
|
|
|
|
|
$this->i->foo();
|
|
|
|
}
|
|
|
|
|
|
|
|
private function v(int $a, int $b): void
|
|
|
|
{
|
|
|
|
if ($a + $b > 0) {
|
|
|
|
throw new \RuntimeException("");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
new A([1, 2, 3]);',
|
|
|
|
],
|
2018-02-27 17:39:26 +01:00
|
|
|
'unusedParamWithUnderscore' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2018-02-27 17:39:26 +01:00
|
|
|
function foo(int $_) : void {}
|
|
|
|
|
|
|
|
foo(4);',
|
|
|
|
],
|
|
|
|
'unusedParamWithUnusedPrefix' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2018-02-27 17:39:26 +01:00
|
|
|
function foo(int $unusedArg) : void {}
|
|
|
|
|
|
|
|
foo(4);',
|
|
|
|
],
|
2019-08-21 19:12:13 +02:00
|
|
|
'usedFunctionCall' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2019-08-21 19:12:13 +02:00
|
|
|
$a = strlen("goodbye");
|
|
|
|
echo $a;',
|
|
|
|
],
|
2018-02-27 17:39:26 +01:00
|
|
|
'possiblyUnusedParamWithUnderscore' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2018-02-27 17:39:26 +01:00
|
|
|
class A {
|
|
|
|
public static function foo(int $_ = null) : void {}
|
|
|
|
}
|
|
|
|
|
|
|
|
A::foo();',
|
|
|
|
],
|
|
|
|
'possiblyUnusedParamWithUnusedPrefix' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2018-02-27 17:39:26 +01:00
|
|
|
class A {
|
|
|
|
public static function foo(int $unusedArg = null) : void {}
|
|
|
|
}
|
|
|
|
|
|
|
|
A::foo();',
|
|
|
|
],
|
2018-03-07 17:16:56 +01:00
|
|
|
'usedClass' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2018-03-07 17:16:56 +01:00
|
|
|
class A { }
|
|
|
|
new A();',
|
|
|
|
],
|
2018-06-10 05:10:42 +02:00
|
|
|
'usedTraitMethodWithImplicitCall' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2018-06-10 05:10:42 +02:00
|
|
|
class A {
|
|
|
|
public function foo() : void {}
|
|
|
|
}
|
|
|
|
trait T {
|
|
|
|
public function foo() : void {}
|
|
|
|
}
|
|
|
|
class B extends A {
|
|
|
|
use T;
|
|
|
|
}
|
|
|
|
function takesA(A $a) : void {
|
|
|
|
$a->foo();
|
|
|
|
}
|
2019-03-23 19:27:54 +01:00
|
|
|
takesA(new B);',
|
2018-06-10 05:10:42 +02:00
|
|
|
],
|
2018-08-07 19:17:23 +02:00
|
|
|
'usedMethodInTryCatch' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2018-08-07 19:17:23 +02:00
|
|
|
class A {
|
|
|
|
protected function getC() : C {
|
|
|
|
return new C;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
class C {
|
|
|
|
public function foo() : void {}
|
|
|
|
}
|
|
|
|
|
|
|
|
class B extends A {
|
|
|
|
public function bar() : void {
|
|
|
|
$c = $this->getC();
|
|
|
|
|
2019-05-02 23:21:02 +02:00
|
|
|
foreach ([1, 2, 3] as $_) {
|
2018-08-07 19:17:23 +02:00
|
|
|
try {
|
|
|
|
$c->foo();
|
|
|
|
} catch (Exception $e) {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
(new B)->bar();',
|
|
|
|
],
|
2018-07-12 18:12:28 +02:00
|
|
|
'suppressPrivateUnusedMethod' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2018-07-12 18:12:28 +02:00
|
|
|
class A {
|
|
|
|
/**
|
|
|
|
* @psalm-suppress UnusedMethod
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
private function foo() {}
|
|
|
|
}
|
|
|
|
|
|
|
|
new A();',
|
|
|
|
],
|
2018-12-13 23:20:29 +01:00
|
|
|
'abstractMethodImplementerCoveredByParentCall' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2018-12-13 23:20:29 +01:00
|
|
|
abstract class Foobar {
|
|
|
|
public function doIt(): void {
|
|
|
|
$this->inner();
|
|
|
|
}
|
|
|
|
|
|
|
|
abstract protected function inner(): void;
|
|
|
|
}
|
|
|
|
|
|
|
|
class MyFooBar extends Foobar {
|
|
|
|
protected function inner(): void {
|
|
|
|
// Do nothing
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$myFooBar = new MyFooBar();
|
|
|
|
$myFooBar->doIt();',
|
|
|
|
],
|
2019-04-17 21:45:40 +02:00
|
|
|
'methodUsedAsCallable' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2019-04-17 21:45:40 +02:00
|
|
|
class C {
|
|
|
|
public static function foo() : void {}
|
|
|
|
}
|
|
|
|
|
|
|
|
function takesCallable(callable $c) : void {
|
|
|
|
$c();
|
|
|
|
}
|
|
|
|
|
|
|
|
takesCallable([C::class, "foo"]);',
|
|
|
|
],
|
2019-04-17 22:41:35 +02:00
|
|
|
'propertyAndMethodOverriddenDownstream' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2019-04-17 22:41:35 +02:00
|
|
|
class A {
|
|
|
|
/** @var string */
|
|
|
|
public $foo = "hello";
|
|
|
|
|
|
|
|
public function bar() : void {}
|
|
|
|
}
|
|
|
|
|
|
|
|
class B extends A {
|
|
|
|
/** @var string */
|
|
|
|
public $foo = "goodbye";
|
|
|
|
|
|
|
|
public function bar() : void {}
|
|
|
|
}
|
|
|
|
|
|
|
|
function foo(A $a) : void {
|
|
|
|
echo $a->foo;
|
|
|
|
$a->bar();
|
|
|
|
}
|
|
|
|
|
|
|
|
foo(new B());',
|
|
|
|
],
|
2019-04-17 22:55:15 +02:00
|
|
|
'protectedPropertyOverriddenDownstream' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2019-04-17 22:55:15 +02:00
|
|
|
|
|
|
|
class C {
|
2021-05-21 15:25:57 +02:00
|
|
|
protected int $foo = 1;
|
2019-08-30 22:40:32 +02:00
|
|
|
public function bar() : void {
|
|
|
|
$this->foo = 5;
|
2019-04-17 22:55:15 +02:00
|
|
|
}
|
2021-05-21 15:25:57 +02:00
|
|
|
|
|
|
|
public function getFoo(): void {
|
|
|
|
echo $this->foo;
|
|
|
|
}
|
2019-04-17 22:55:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
class D extends C {
|
2021-05-21 15:25:57 +02:00
|
|
|
protected int $foo = 2;
|
2019-04-17 22:55:15 +02:00
|
|
|
}
|
|
|
|
|
2021-05-21 15:25:57 +02:00
|
|
|
(new D)->bar();
|
|
|
|
(new D)->getFoo();',
|
2019-04-17 22:55:15 +02:00
|
|
|
],
|
2019-06-16 00:49:58 +02:00
|
|
|
'usedClassAfterExtensionLoaded' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2019-06-16 00:49:58 +02:00
|
|
|
class A {
|
|
|
|
public function __construct() {}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (extension_loaded("fdsfdsfd")) {
|
|
|
|
new A();
|
|
|
|
}',
|
|
|
|
],
|
2019-06-26 03:46:18 +02:00
|
|
|
'usedParamInIf' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2019-06-26 03:46:18 +02:00
|
|
|
class O {}
|
|
|
|
class C {
|
|
|
|
private bool $a = false;
|
|
|
|
public array $_types = [];
|
|
|
|
|
|
|
|
private static function mirror(array $a) : array {
|
|
|
|
return $a;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param class-string<O>|null $type
|
|
|
|
*/
|
2021-06-10 18:57:13 +02:00
|
|
|
public function addType(?string $type, array $ids = array()): void
|
2019-06-26 03:46:18 +02:00
|
|
|
{
|
|
|
|
if ($this->a) {
|
|
|
|
$ids = self::mirror($ids);
|
|
|
|
}
|
|
|
|
$this->_types[$type ?: ""] = new ArrayObject($ids);
|
2021-06-10 18:57:13 +02:00
|
|
|
return;
|
2019-06-26 03:46:18 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-05 22:24:00 +02:00
|
|
|
(new C)->addType(null);',
|
2019-06-26 03:46:18 +02:00
|
|
|
],
|
2019-06-27 16:17:11 +02:00
|
|
|
'usedMethodAfterClassExists' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2019-06-27 16:17:11 +02:00
|
|
|
class A {
|
|
|
|
public static function bar() : void {}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (class_exists(A::class)) {
|
|
|
|
A::bar();
|
2019-07-05 22:24:00 +02:00
|
|
|
}',
|
2019-06-27 16:17:11 +02:00
|
|
|
],
|
2019-07-31 23:35:56 +02:00
|
|
|
'usedParamInLoopBeforeBreak' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2019-07-31 23:35:56 +02:00
|
|
|
class Foo {}
|
|
|
|
|
|
|
|
function takesFoo(Foo $foo1, Foo $foo2): Foo {
|
|
|
|
while (rand(0, 1)) {
|
|
|
|
echo get_class($foo1);
|
|
|
|
|
|
|
|
if (rand(0, 1)) {
|
|
|
|
$foo1 = $foo2;
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $foo1;
|
|
|
|
}',
|
|
|
|
],
|
|
|
|
'usedParamInLoopBeforeContinue' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2019-07-31 23:35:56 +02:00
|
|
|
class Foo {}
|
|
|
|
|
|
|
|
function takesFoo(Foo $foo1, Foo $foo2): Foo {
|
|
|
|
while (rand(0, 1)) {
|
|
|
|
echo get_class($foo1);
|
|
|
|
|
|
|
|
if (rand(0, 1)) {
|
|
|
|
$foo1 = $foo2;
|
|
|
|
|
2019-07-31 23:42:01 +02:00
|
|
|
continue;
|
2019-07-31 23:35:56 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $foo1;
|
|
|
|
}',
|
|
|
|
],
|
|
|
|
'usedParamInLoopBeforeWithChangeContinue' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2019-07-31 23:35:56 +02:00
|
|
|
class Foo {}
|
|
|
|
|
|
|
|
class Bar {
|
|
|
|
public static function build(Foo $foo) : ?self {
|
|
|
|
echo get_class($foo);
|
|
|
|
return new self();
|
|
|
|
}
|
|
|
|
|
|
|
|
public function produceFoo(): Foo {
|
|
|
|
return new Foo();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function takesFoo(Foo $foo): Foo {
|
|
|
|
while (rand(0, 1)) {
|
|
|
|
$bar = Bar::build($foo);
|
|
|
|
|
|
|
|
if ($bar) {
|
|
|
|
$foo = $bar->produceFoo();
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $foo;
|
|
|
|
}',
|
|
|
|
],
|
2019-08-18 22:59:56 +02:00
|
|
|
'suppressUnusedMethod' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2019-08-18 22:59:56 +02:00
|
|
|
class A {
|
|
|
|
/**
|
|
|
|
* @psalm-suppress UnusedMethod
|
|
|
|
*/
|
|
|
|
public function foo() : void {}
|
|
|
|
}
|
|
|
|
|
2022-12-18 17:15:15 +01:00
|
|
|
new A();',
|
2019-08-18 22:59:56 +02:00
|
|
|
],
|
2019-08-23 04:06:22 +02:00
|
|
|
'usedFunctionInCall' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2019-08-23 04:06:22 +02:00
|
|
|
function fooBar(): void {}
|
|
|
|
|
|
|
|
$foo = "foo";
|
|
|
|
$bar = "bar";
|
|
|
|
|
|
|
|
($foo . ucfirst($bar))();',
|
|
|
|
],
|
2019-08-23 16:59:59 +02:00
|
|
|
'usedParamInUnknownMethodConcat' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2019-08-23 16:59:59 +02:00
|
|
|
/**
|
|
|
|
* @psalm-suppress MixedMethodCall
|
|
|
|
*/
|
|
|
|
function foo(string $s, object $o) : void {
|
|
|
|
$o->foo("COUNT{$s}");
|
2022-12-18 17:15:15 +01:00
|
|
|
}',
|
2019-08-23 16:59:59 +02:00
|
|
|
],
|
2019-08-26 06:47:46 +02:00
|
|
|
'usedFunctioninMethodCallName' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2019-08-26 06:47:46 +02:00
|
|
|
class Foo {
|
|
|
|
/**
|
|
|
|
* @psalm-suppress MixedArgument
|
|
|
|
*/
|
2020-03-06 03:24:08 +01:00
|
|
|
public function bar(string $request): void {
|
2019-08-26 06:47:46 +02:00
|
|
|
/** @var mixed $action */
|
|
|
|
$action = "";
|
|
|
|
$this->{"execute" . ucfirst($action)}($request);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-18 17:15:15 +01:00
|
|
|
(new Foo)->bar("request");',
|
2019-08-26 06:47:46 +02:00
|
|
|
],
|
2019-08-30 23:26:55 +02:00
|
|
|
'usedMethodCallForExternalMutationFreeClass' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2019-08-30 23:26:55 +02:00
|
|
|
/**
|
|
|
|
* @psalm-external-mutation-free
|
|
|
|
*/
|
|
|
|
class A {
|
|
|
|
private string $foo;
|
|
|
|
|
|
|
|
public function __construct(string $foo) {
|
|
|
|
$this->foo = $foo;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setFoo(string $foo) : void {
|
|
|
|
$this->foo = $foo;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getFoo() : string {
|
|
|
|
return $this->foo;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$a = new A("hello");
|
|
|
|
$a->setFoo($a->getFoo() . "cool");',
|
|
|
|
],
|
2019-08-31 00:06:45 +02:00
|
|
|
'functionUsedAsArrayKeyInc' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2019-08-31 00:06:45 +02:00
|
|
|
/** @param array<int, int> $arr */
|
|
|
|
function inc(array $arr) : array {
|
|
|
|
$arr[strlen("hello")]++;
|
|
|
|
return $arr;
|
2022-12-18 17:15:15 +01:00
|
|
|
}',
|
2019-08-31 00:06:45 +02:00
|
|
|
],
|
2019-08-31 16:02:11 +02:00
|
|
|
'pureFunctionUsesMethodBeforeReturning' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2019-08-31 16:02:11 +02:00
|
|
|
/** @psalm-external-mutation-free */
|
|
|
|
class Counter {
|
|
|
|
private int $count = 0;
|
|
|
|
|
|
|
|
public function __construct(int $count) {
|
|
|
|
$this->count = $count;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function increment() : void {
|
|
|
|
$this->count++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @psalm-pure */
|
|
|
|
function makesACounter(int $i) : Counter {
|
|
|
|
$c = new Counter($i);
|
|
|
|
$c->increment();
|
|
|
|
return $c;
|
|
|
|
}',
|
|
|
|
],
|
2023-02-21 02:20:53 +01:00
|
|
|
'setRawCookieImpure' => [
|
|
|
|
'code' => '<?php
|
|
|
|
setrawcookie(
|
|
|
|
"name",
|
|
|
|
"value",
|
|
|
|
);',
|
|
|
|
],
|
2019-09-09 18:11:04 +02:00
|
|
|
'usedUsort' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2019-09-09 18:11:04 +02:00
|
|
|
/** @param string[] $arr */
|
|
|
|
function foo(array $arr) : array {
|
|
|
|
usort($arr, "strnatcasecmp");
|
|
|
|
return $arr;
|
2022-12-18 17:15:15 +01:00
|
|
|
}',
|
2019-09-09 18:11:04 +02:00
|
|
|
],
|
2019-09-10 03:28:56 +02:00
|
|
|
'allowArrayMapWithClosure' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2019-09-10 03:28:56 +02:00
|
|
|
$a = [1, 2, 3];
|
|
|
|
|
2022-12-18 17:15:15 +01:00
|
|
|
array_map(function($i) { echo $i;}, $a);',
|
2019-09-10 03:28:56 +02:00
|
|
|
],
|
2019-12-11 16:13:22 +01:00
|
|
|
'usedAssertFunction' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2019-12-11 16:13:22 +01:00
|
|
|
/**
|
|
|
|
* @param mixed $v
|
|
|
|
* @psalm-pure
|
|
|
|
* @psalm-assert int $v
|
|
|
|
*/
|
|
|
|
function assertInt($v):void {
|
|
|
|
if (!is_int($v)) {
|
|
|
|
throw new \RuntimeException();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-pure
|
|
|
|
* @param mixed $i
|
|
|
|
*/
|
|
|
|
function takesMixed($i) : int {
|
|
|
|
assertInt($i);
|
|
|
|
return $i;
|
2022-12-18 17:15:15 +01:00
|
|
|
}',
|
2019-12-11 16:13:22 +01:00
|
|
|
],
|
2023-03-15 10:06:36 +01:00
|
|
|
'usedFunctionCallInEval' => [
|
|
|
|
'code' => '<?php
|
|
|
|
eval(str_repeat("a", 10));',
|
|
|
|
],
|
2019-12-11 18:06:10 +01:00
|
|
|
'usedFunctionCallInsideSwitchWithTernary' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2019-12-11 18:06:10 +01:00
|
|
|
function getArg(string $method) : void {
|
|
|
|
switch (strtolower($method ?: "")) {
|
|
|
|
case "post":
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "get":
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
2022-12-18 17:15:15 +01:00
|
|
|
}',
|
2019-12-31 13:56:35 +01:00
|
|
|
],
|
|
|
|
'ignoreSerializerSerialize' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2019-12-31 13:56:35 +01:00
|
|
|
class Foo implements Serializable {
|
|
|
|
public function serialize() : string {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
|
|
|
public function unserialize($_serialized) : void {}
|
|
|
|
}
|
|
|
|
|
2022-12-18 17:15:15 +01:00
|
|
|
new Foo();',
|
2019-12-31 13:56:35 +01:00
|
|
|
],
|
2022-11-02 22:52:42 +01:00
|
|
|
'ignoreSerializeAndUnserialize' => [
|
2022-11-08 10:45:21 +01:00
|
|
|
'code' => '<?php
|
2022-11-02 22:52:42 +01:00
|
|
|
class Foo
|
|
|
|
{
|
|
|
|
public function __sleep(): array
|
|
|
|
{
|
|
|
|
throw new BadMethodCallException();
|
|
|
|
}
|
|
|
|
public function __wakeup(): void
|
|
|
|
{
|
|
|
|
throw new BadMethodCallException();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-03 18:11:01 +01:00
|
|
|
function test(Foo|int $foo, mixed $bar, iterable $baz): bool {
|
2022-11-02 22:52:42 +01:00
|
|
|
try {
|
|
|
|
serialize(new Foo());
|
2022-11-03 11:25:24 +01:00
|
|
|
serialize([new Foo()]);
|
2022-11-03 14:39:48 +01:00
|
|
|
serialize([[new Foo()]]);
|
|
|
|
serialize($foo);
|
2022-11-03 18:11:01 +01:00
|
|
|
serialize($bar);
|
|
|
|
serialize($baz);
|
2022-11-02 22:52:42 +01:00
|
|
|
unserialize("");
|
|
|
|
} catch (\Throwable) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
2022-12-18 17:15:15 +01:00
|
|
|
}',
|
2022-11-02 22:52:42 +01:00
|
|
|
],
|
2020-01-02 00:33:12 +01:00
|
|
|
'useIteratorMethodsWhenCallingForeach' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2022-01-26 18:46:02 +01:00
|
|
|
/** @psalm-suppress UnimplementedInterfaceMethod, MissingTemplateParam */
|
2020-01-02 00:33:12 +01:00
|
|
|
class IterableResult implements \Iterator {
|
|
|
|
public function current() {
|
2021-01-05 23:49:55 +01:00
|
|
|
return null;
|
2020-01-02 00:33:12 +01:00
|
|
|
}
|
2020-04-27 16:03:16 +02:00
|
|
|
|
|
|
|
public function key() {
|
|
|
|
return 5;
|
|
|
|
}
|
2020-01-02 00:33:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
$items = new IterableResult();
|
|
|
|
|
2022-12-18 17:15:15 +01:00
|
|
|
foreach ($items as $_item) {}',
|
2020-01-02 00:33:12 +01:00
|
|
|
],
|
2020-03-12 16:42:01 +01:00
|
|
|
'usedThroughNewClassStringOfBase' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2020-08-06 16:18:55 +02:00
|
|
|
/**
|
|
|
|
* @psalm-consistent-constructor
|
|
|
|
*/
|
2020-03-12 16:42:01 +01:00
|
|
|
abstract class FooBase {
|
|
|
|
public final function __construct() {}
|
|
|
|
|
|
|
|
public function baz() : void {
|
|
|
|
echo "hello";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-template T as FooBase
|
|
|
|
* @psalm-param class-string<T> $type
|
|
|
|
* @psalm-return T
|
|
|
|
*/
|
|
|
|
function createFoo($type): FooBase {
|
|
|
|
return new $type();
|
|
|
|
}
|
|
|
|
|
|
|
|
class Foo extends FooBase {}
|
|
|
|
|
2022-12-18 17:15:15 +01:00
|
|
|
createFoo(Foo::class)->baz();',
|
2020-03-12 16:42:01 +01:00
|
|
|
],
|
2020-06-20 00:02:39 +02:00
|
|
|
'usedMethodReferencedByString' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2020-06-20 00:02:39 +02:00
|
|
|
class A {
|
|
|
|
static function b(): void {}
|
|
|
|
}
|
|
|
|
$methodRef = "A::b";
|
|
|
|
$methodRef();',
|
|
|
|
],
|
|
|
|
'usedMethodReferencedByStringWithLeadingBackslash' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2020-06-20 00:02:39 +02:00
|
|
|
class A {
|
|
|
|
static function b(): void {}
|
|
|
|
}
|
|
|
|
$methodRef = "\A::b";
|
|
|
|
$methodRef();',
|
|
|
|
],
|
2020-07-16 19:44:51 +02:00
|
|
|
'arrayPushFunctionCall' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2020-07-16 19:44:51 +02:00
|
|
|
$a = [];
|
|
|
|
|
|
|
|
array_push($a, strlen("hello"));
|
|
|
|
|
2022-12-18 17:15:15 +01:00
|
|
|
echo $a[0];',
|
2020-07-16 19:44:51 +02:00
|
|
|
],
|
2020-09-30 18:28:13 +02:00
|
|
|
'callMethodThatUpdatesStaticVar' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2020-09-30 18:28:13 +02:00
|
|
|
class References {
|
|
|
|
/**
|
|
|
|
* @var array<string, string>
|
|
|
|
*/
|
2021-07-18 01:39:23 +02:00
|
|
|
public static $foo = [];
|
2020-09-30 18:28:13 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @param array<string, string> $map
|
|
|
|
*/
|
|
|
|
public function bar(array $map) : void {
|
|
|
|
self::$foo += $map;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-18 17:15:15 +01:00
|
|
|
(new References)->bar(["a" => "b"]);',
|
2020-09-30 18:28:13 +02:00
|
|
|
],
|
2020-10-21 20:41:15 +02:00
|
|
|
'promotedPropertyIsUsed' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2020-10-21 20:41:15 +02:00
|
|
|
class Test {
|
|
|
|
public function __construct(public int $id, public string $name) {}
|
|
|
|
}
|
|
|
|
|
|
|
|
$test = new Test(1, "ame");
|
|
|
|
echo $test->id;
|
2022-12-18 17:15:15 +01:00
|
|
|
echo $test->name;',
|
2020-10-21 20:41:15 +02:00
|
|
|
],
|
2021-03-24 00:34:12 +01:00
|
|
|
'unusedNoReturnFunctionCall' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-03-24 00:34:12 +01:00
|
|
|
/**
|
|
|
|
* @return no-return
|
|
|
|
*
|
|
|
|
* @pure
|
|
|
|
*
|
|
|
|
* @throws RuntimeException
|
|
|
|
*/
|
|
|
|
function invariant_violation(string $message): void
|
|
|
|
{
|
|
|
|
throw new RuntimeException($message);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @pure
|
|
|
|
*/
|
|
|
|
function reverse(string $string): string
|
|
|
|
{
|
|
|
|
if ("" === $string) {
|
|
|
|
invariant_violation("i do not like empty strings.");
|
|
|
|
}
|
|
|
|
|
|
|
|
return strrev($string);
|
2022-12-18 17:15:15 +01:00
|
|
|
}',
|
2021-03-24 00:34:12 +01:00
|
|
|
],
|
2021-03-25 14:05:59 +01:00
|
|
|
'unusedByReferenceFunctionCall' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-03-25 14:05:59 +01:00
|
|
|
function bar(string &$str): string
|
|
|
|
{
|
|
|
|
$str .= "foo";
|
|
|
|
|
|
|
|
return $str;
|
|
|
|
}
|
|
|
|
|
|
|
|
function baz(): string
|
|
|
|
{
|
|
|
|
$f = "foo";
|
|
|
|
bar($f);
|
|
|
|
|
|
|
|
return $f;
|
2022-12-18 17:15:15 +01:00
|
|
|
}',
|
2021-03-25 14:05:59 +01:00
|
|
|
],
|
|
|
|
'unusedVoidByReferenceFunctionCall' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-03-25 14:05:59 +01:00
|
|
|
function bar(string &$str): void
|
|
|
|
{
|
|
|
|
$str .= "foo";
|
|
|
|
}
|
|
|
|
|
|
|
|
function baz(): string
|
|
|
|
{
|
|
|
|
$f = "foo";
|
|
|
|
bar($f);
|
|
|
|
|
|
|
|
return $f;
|
2022-12-18 17:15:15 +01:00
|
|
|
}',
|
2021-03-25 14:05:59 +01:00
|
|
|
],
|
|
|
|
'unusedNamedByReferenceFunctionCall' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-03-25 14:05:59 +01:00
|
|
|
function bar(string $c = "", string &$str = ""): string
|
|
|
|
{
|
|
|
|
$c .= $str;
|
|
|
|
$str .= $c;
|
|
|
|
|
|
|
|
return $c;
|
|
|
|
}
|
|
|
|
|
|
|
|
function baz(): string
|
|
|
|
{
|
|
|
|
$f = "foo";
|
|
|
|
bar(str: $f);
|
|
|
|
|
|
|
|
return $f;
|
2022-12-18 17:15:15 +01:00
|
|
|
}',
|
2021-03-25 14:05:59 +01:00
|
|
|
],
|
|
|
|
'unusedNamedByReferenceFunctionCallV2' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-03-25 14:05:59 +01:00
|
|
|
function bar(string &$st, string &$str = ""): string
|
|
|
|
{
|
|
|
|
$st .= $str;
|
|
|
|
|
|
|
|
return $st;
|
|
|
|
}
|
|
|
|
|
|
|
|
function baz(): string
|
|
|
|
{
|
|
|
|
$f = "foo";
|
|
|
|
bar(st: $f);
|
|
|
|
|
|
|
|
return $f;
|
|
|
|
}',
|
|
|
|
],
|
|
|
|
'unusedNamedByReferenceFunctionCallV3' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-03-25 14:05:59 +01:00
|
|
|
function bar(string &$st, ?string &$str = ""): string
|
|
|
|
{
|
|
|
|
$st .= (string) $str;
|
|
|
|
|
|
|
|
return $st;
|
|
|
|
}
|
|
|
|
|
|
|
|
function baz(): string
|
|
|
|
{
|
|
|
|
$f = "foo";
|
|
|
|
bar(st: $f, str: $c);
|
|
|
|
|
|
|
|
return $f;
|
|
|
|
}',
|
2021-03-31 16:03:08 +02:00
|
|
|
],
|
|
|
|
'functionCallUsedInThrow' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-03-31 16:03:08 +02:00
|
|
|
/**
|
|
|
|
* @psalm-pure
|
|
|
|
*/
|
|
|
|
function getException(): \Exception
|
|
|
|
{
|
|
|
|
return new \Exception();
|
|
|
|
}
|
|
|
|
|
2022-12-18 17:15:15 +01:00
|
|
|
throw getException();',
|
2021-03-31 16:03:08 +02:00
|
|
|
],
|
2021-03-31 16:08:52 +02:00
|
|
|
'nullableMethodCallIsUsed' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-03-31 16:08:52 +02:00
|
|
|
final class Test {
|
|
|
|
public function test(): void {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
final class TestFactory {
|
|
|
|
/**
|
|
|
|
* @psalm-pure
|
|
|
|
*/
|
|
|
|
public function create(bool $returnNull): ?Test {
|
|
|
|
if ($returnNull) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return new Test();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$factory = new TestFactory();
|
2021-04-05 03:17:12 +02:00
|
|
|
$factory->create(false)?->test();
|
|
|
|
|
|
|
|
$exception = new \Exception();
|
|
|
|
|
2022-12-18 17:15:15 +01:00
|
|
|
throw ($exception->getPrevious() ?? $exception);',
|
2021-03-31 16:08:52 +02:00
|
|
|
],
|
2021-05-21 15:25:57 +02:00
|
|
|
'publicPropertyReadInFile' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-05-21 15:25:57 +02:00
|
|
|
class A {
|
|
|
|
public string $a;
|
|
|
|
|
|
|
|
public function __construct() {
|
|
|
|
$this->a = "hello";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$foo = new A();
|
|
|
|
echo $foo->a;',
|
|
|
|
],
|
|
|
|
'publicPropertyReadInMethod' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-05-21 15:25:57 +02:00
|
|
|
class A {
|
|
|
|
public string $a = "hello";
|
|
|
|
}
|
|
|
|
|
|
|
|
class B {
|
|
|
|
public function foo(A $a): void {
|
|
|
|
if ($a->a === "goodbye") {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
(new B)->foo(new A());',
|
|
|
|
],
|
|
|
|
'privatePropertyReadInMethod' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-05-21 15:25:57 +02:00
|
|
|
class A {
|
|
|
|
private string $a;
|
|
|
|
|
|
|
|
public function __construct() {
|
|
|
|
$this->a = "hello";
|
|
|
|
}
|
|
|
|
|
|
|
|
public function emitA(): void {
|
|
|
|
echo $this->a;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
(new A())->emitA();',
|
|
|
|
],
|
2021-06-10 20:18:15 +02:00
|
|
|
'fluentMethodsAllowed' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-06-10 20:18:15 +02:00
|
|
|
class A {
|
|
|
|
public function foo(): static {
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function bar(): static {
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
(new A())->foo()->bar();',
|
|
|
|
],
|
2021-06-12 22:13:29 +02:00
|
|
|
'unusedInterfaceReturnValueWithImplementingClassSuppressed' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-06-10 20:39:01 +02:00
|
|
|
interface IWorker {
|
|
|
|
/** @psalm-suppress PossiblyUnusedReturnValue */
|
|
|
|
public function work(): bool;
|
|
|
|
}
|
|
|
|
|
|
|
|
class Worker implements IWorker{
|
|
|
|
public function work(): bool {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function f(IWorker $worker): void {
|
|
|
|
$worker->work();
|
|
|
|
}
|
|
|
|
|
|
|
|
f(new Worker());',
|
|
|
|
],
|
2021-06-12 22:13:29 +02:00
|
|
|
'interfaceReturnValueWithImplementingAndAbstractClass' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-06-12 22:13:29 +02:00
|
|
|
interface IWorker {
|
|
|
|
public function work(): int;
|
|
|
|
}
|
|
|
|
|
|
|
|
class AbstractWorker implements IWorker {
|
|
|
|
public function work(): int {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class Worker extends AbstractWorker {
|
|
|
|
public function work(): int {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class AnotherWorker extends AbstractWorker {}
|
|
|
|
|
|
|
|
function f(IWorker $worker): void {
|
|
|
|
echo $worker->work();
|
|
|
|
}
|
|
|
|
|
|
|
|
f(new Worker());
|
|
|
|
f(new AnotherWorker());',
|
|
|
|
],
|
2021-06-25 15:11:27 +02:00
|
|
|
'methodReturnValueUsedInThrow' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-06-25 15:11:27 +02:00
|
|
|
class A {
|
|
|
|
public function foo() : Exception {
|
|
|
|
return new Exception;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
throw (new A)->foo();
|
2022-12-18 17:15:15 +01:00
|
|
|
',
|
2021-06-25 15:11:27 +02:00
|
|
|
],
|
|
|
|
'staticMethodReturnValueUsedInThrow' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-06-25 15:11:27 +02:00
|
|
|
class A {
|
|
|
|
public static function foo() : Exception {
|
|
|
|
return new Exception;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
throw A::foo();
|
2022-12-18 17:15:15 +01:00
|
|
|
',
|
2021-06-25 15:11:27 +02:00
|
|
|
],
|
2021-07-21 00:15:51 +02:00
|
|
|
'variableUsedAsUnaryMinusOperand' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-07-21 00:15:51 +02:00
|
|
|
function f(): int
|
|
|
|
{
|
|
|
|
$a = 1;
|
|
|
|
$b = -$a;
|
|
|
|
return $b;
|
|
|
|
}
|
|
|
|
',
|
|
|
|
],
|
|
|
|
'variableUsedAsUnaryPlusOperand' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-07-21 00:15:51 +02:00
|
|
|
function f(): int
|
|
|
|
{
|
|
|
|
$a = 1;
|
|
|
|
$b = +$a;
|
|
|
|
return $b;
|
|
|
|
}
|
|
|
|
',
|
|
|
|
],
|
2021-08-29 00:26:52 +02:00
|
|
|
'variableUsedInBacktick' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-08-29 00:26:52 +02:00
|
|
|
$used = "echo";
|
|
|
|
/** @psalm-suppress ForbiddenCode */
|
|
|
|
`$used`;
|
|
|
|
',
|
|
|
|
],
|
2021-11-05 18:09:40 +01:00
|
|
|
'notUnevaluatedFunction' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-11-05 18:18:34 +01:00
|
|
|
/** @return never */
|
|
|
|
function neverReturns(){
|
2021-11-05 18:09:40 +01:00
|
|
|
die();
|
|
|
|
}
|
|
|
|
unrelated();
|
|
|
|
neverReturns();
|
|
|
|
|
2021-11-05 18:34:59 +01:00
|
|
|
function unrelated():void{
|
2021-11-05 18:09:40 +01:00
|
|
|
echo "hello";
|
|
|
|
}',
|
|
|
|
],
|
2021-11-06 19:00:36 +01:00
|
|
|
'NotUnusedWhenAssert' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-11-06 19:00:36 +01:00
|
|
|
|
|
|
|
class A {
|
|
|
|
public function getVal(?string $val): string {
|
|
|
|
$this->assert($val);
|
|
|
|
|
|
|
|
return $val;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-assert string $val
|
|
|
|
* @psalm-mutation-free
|
|
|
|
*/
|
|
|
|
private function assert(?string $val): void {
|
|
|
|
if (null === $val) {
|
|
|
|
throw new Exception();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$a = new A();
|
2021-11-06 20:00:31 +01:00
|
|
|
echo $a->getVal(null);',
|
2021-11-06 19:00:36 +01:00
|
|
|
],
|
2021-11-22 21:29:57 +01:00
|
|
|
'NotUnusedWhenThrows' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-11-22 21:29:57 +01:00
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
/** @psalm-immutable */
|
|
|
|
final class UserList
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* @throws InvalidArgumentException
|
|
|
|
*/
|
|
|
|
public function validate(): void
|
|
|
|
{
|
|
|
|
// Some validation happens here
|
|
|
|
throw new \InvalidArgumentException();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$a = new UserList();
|
|
|
|
$a->validate();
|
|
|
|
',
|
|
|
|
],
|
2021-11-10 12:45:12 +01:00
|
|
|
'__halt_compiler_no_usage_check' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-11-10 12:45:12 +01:00
|
|
|
exit(0);
|
|
|
|
__halt_compiler();
|
|
|
|
foobar
|
|
|
|
',
|
|
|
|
],
|
2021-12-11 17:42:05 +01:00
|
|
|
'usedPropertyAsAssignmentKey' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-12-11 17:42:05 +01:00
|
|
|
class A {
|
|
|
|
public string $foo = "bar";
|
|
|
|
public array $bar = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
$a = new A();
|
|
|
|
$a->bar[$a->foo] = "bar";
|
|
|
|
print_r($a->bar);',
|
|
|
|
],
|
2022-12-22 15:43:55 +01:00
|
|
|
'psalm-api with unused class' => [
|
|
|
|
'code' => <<<'PHP'
|
|
|
|
<?php
|
|
|
|
/** @psalm-api */
|
|
|
|
class A {}
|
|
|
|
PHP,
|
|
|
|
],
|
|
|
|
'psalm-api with unused public and protected property' => [
|
|
|
|
'code' => <<<'PHP'
|
|
|
|
<?php
|
|
|
|
/** @psalm-api */
|
|
|
|
class A {
|
|
|
|
public int $b = 0;
|
|
|
|
protected int $c = 0;
|
|
|
|
}
|
|
|
|
PHP,
|
|
|
|
],
|
|
|
|
'psalm-api with unused public and protected method' => [
|
|
|
|
'code' => <<<'PHP'
|
|
|
|
<?php
|
|
|
|
/** @psalm-api */
|
|
|
|
class A {
|
|
|
|
public function b(): void {}
|
|
|
|
protected function c(): void {}
|
|
|
|
}
|
|
|
|
PHP,
|
|
|
|
],
|
2023-02-23 08:52:10 +01:00
|
|
|
'psalm-api on unused public method' => [
|
|
|
|
'code' => <<<'PHP'
|
|
|
|
<?php
|
|
|
|
class A {
|
|
|
|
/** @psalm-api */
|
|
|
|
public function b(): void {}
|
|
|
|
}
|
|
|
|
new A;
|
|
|
|
PHP,
|
|
|
|
],
|
2023-03-20 12:25:06 +01:00
|
|
|
'api with unused class' => [
|
|
|
|
'code' => <<<'PHP'
|
|
|
|
<?php
|
|
|
|
/** @api */
|
|
|
|
class A {}
|
|
|
|
PHP,
|
|
|
|
],
|
|
|
|
'api on unused public method' => [
|
|
|
|
'code' => <<<'PHP'
|
|
|
|
<?php
|
|
|
|
class A {
|
|
|
|
/** @api */
|
|
|
|
public function b(): void {}
|
|
|
|
}
|
|
|
|
new A;
|
|
|
|
PHP,
|
|
|
|
],
|
2017-04-25 05:45:02 +02:00
|
|
|
];
|
2017-02-08 06:28:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-11-12 02:14:21 +01:00
|
|
|
* @return array<string,array{code:string,error_message:string,ignored_issues?:list<string>}>
|
2017-02-08 06:28:26 +01:00
|
|
|
*/
|
2020-09-12 17:24:05 +02:00
|
|
|
public function providerInvalidCodeParse(): array
|
2017-02-08 06:28:26 +01:00
|
|
|
{
|
2017-04-25 05:45:02 +02:00
|
|
|
return [
|
|
|
|
'unusedClass' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2017-04-25 05:45:02 +02:00
|
|
|
class A { }',
|
2017-05-27 02:05:57 +02:00
|
|
|
'error_message' => 'UnusedClass',
|
2017-04-25 05:45:02 +02:00
|
|
|
],
|
|
|
|
'publicUnusedMethod' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2017-04-25 05:45:02 +02:00
|
|
|
class A {
|
|
|
|
/** @return void */
|
|
|
|
public function foo() {}
|
|
|
|
}
|
2017-05-04 20:25:58 +02:00
|
|
|
|
2017-04-25 05:45:02 +02:00
|
|
|
new A();',
|
2017-05-27 02:05:57 +02:00
|
|
|
'error_message' => 'PossiblyUnusedMethod',
|
2017-04-25 05:45:02 +02:00
|
|
|
],
|
2018-01-22 05:48:58 +01:00
|
|
|
'possiblyUnusedParam' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2018-01-22 05:48:58 +01:00
|
|
|
class A {
|
|
|
|
/** @return void */
|
|
|
|
public function foo(int $i) {}
|
|
|
|
}
|
|
|
|
|
|
|
|
(new A)->foo(4);',
|
2021-12-03 21:25:22 +01:00
|
|
|
'error_message' => 'PossiblyUnusedParam - src' . DIRECTORY_SEPARATOR
|
2019-05-13 22:01:41 +02:00
|
|
|
. 'somefile.php:4:49 - Param #1 is never referenced in this method',
|
2018-01-22 05:48:58 +01:00
|
|
|
],
|
|
|
|
'unusedParam' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2018-01-22 05:48:58 +01:00
|
|
|
function foo(int $i) {}
|
|
|
|
|
|
|
|
foo(4);',
|
|
|
|
'error_message' => 'UnusedParam',
|
|
|
|
],
|
|
|
|
'possiblyUnusedProperty' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2018-01-22 05:48:58 +01:00
|
|
|
class A {
|
|
|
|
/** @var string */
|
|
|
|
public $foo = "hello";
|
|
|
|
}
|
|
|
|
|
|
|
|
$a = new A();',
|
|
|
|
'error_message' => 'PossiblyUnusedProperty',
|
2022-01-13 19:49:37 +01:00
|
|
|
'ignored_issues' => ['UnusedVariable'],
|
2018-01-22 05:48:58 +01:00
|
|
|
],
|
2021-12-11 17:42:05 +01:00
|
|
|
'possiblyUnusedPropertyWrittenNeverRead' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-12-11 17:42:05 +01:00
|
|
|
class A {
|
|
|
|
/** @var string */
|
|
|
|
public $foo = "hello";
|
|
|
|
}
|
|
|
|
|
|
|
|
$a = new A();
|
|
|
|
$a->foo = "bar";',
|
|
|
|
'error_message' => 'PossiblyUnusedProperty',
|
2022-01-13 19:49:37 +01:00
|
|
|
'ignored_issues' => ['UnusedVariable'],
|
2021-12-11 17:42:05 +01:00
|
|
|
],
|
|
|
|
'possiblyUnusedPropertyWithArrayWrittenNeverRead' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-12-11 17:42:05 +01:00
|
|
|
class A {
|
|
|
|
/** @var list<string> */
|
|
|
|
public array $foo = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
$a = new A();
|
|
|
|
$a->foo[] = "bar";',
|
|
|
|
'error_message' => 'PossiblyUnusedProperty',
|
2022-01-13 19:49:37 +01:00
|
|
|
'ignored_issues' => ['UnusedVariable'],
|
2021-12-11 17:42:05 +01:00
|
|
|
],
|
2018-01-22 05:48:58 +01:00
|
|
|
'unusedProperty' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2018-01-22 05:48:58 +01:00
|
|
|
class A {
|
|
|
|
/** @var string */
|
|
|
|
private $foo = "hello";
|
|
|
|
}
|
|
|
|
|
|
|
|
$a = new A();',
|
|
|
|
'error_message' => 'UnusedProperty',
|
2022-01-13 19:49:37 +01:00
|
|
|
'ignored_issues' => ['UnusedVariable'],
|
2018-01-22 05:48:58 +01:00
|
|
|
],
|
2017-04-25 05:45:02 +02:00
|
|
|
'privateUnusedMethod' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2017-04-25 05:45:02 +02:00
|
|
|
class A {
|
|
|
|
/** @return void */
|
|
|
|
private function foo() {}
|
|
|
|
}
|
2017-05-04 20:25:58 +02:00
|
|
|
|
2017-04-25 05:45:02 +02:00
|
|
|
new A();',
|
2017-05-27 02:05:57 +02:00
|
|
|
'error_message' => 'UnusedMethod',
|
|
|
|
],
|
2017-12-30 16:54:01 +01:00
|
|
|
'unevaluatedCode' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2018-01-11 21:50:45 +01:00
|
|
|
function foo(): void {
|
2017-12-30 16:54:01 +01:00
|
|
|
return;
|
|
|
|
$a = "foo";
|
|
|
|
}',
|
|
|
|
'error_message' => 'UnevaluatedCode',
|
|
|
|
],
|
2018-06-10 05:10:42 +02:00
|
|
|
'unusedTraitMethodInParent' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2018-06-10 05:10:42 +02:00
|
|
|
class A {
|
|
|
|
public function foo() : void {}
|
|
|
|
}
|
|
|
|
trait T {
|
|
|
|
public function foo() : void {}
|
|
|
|
|
|
|
|
public function bar() : void {}
|
|
|
|
}
|
|
|
|
class B extends A {
|
|
|
|
use T;
|
|
|
|
}
|
|
|
|
function takesA(A $a) : void {
|
|
|
|
$a->foo();
|
|
|
|
}
|
|
|
|
takesA(new B);',
|
|
|
|
'error_message' => 'PossiblyUnusedMethod',
|
|
|
|
],
|
2018-06-19 22:14:51 +02:00
|
|
|
'unusedRecursivelyUsedMethod' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2018-06-19 22:14:51 +02:00
|
|
|
class C {
|
|
|
|
public function foo() : void {
|
|
|
|
if (rand(0, 1)) {
|
|
|
|
$this->foo();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public function bar() : void {}
|
|
|
|
}
|
|
|
|
|
|
|
|
(new C)->bar();',
|
|
|
|
'error_message' => 'PossiblyUnusedMethod',
|
|
|
|
],
|
|
|
|
'unusedRecursivelyUsedStaticMethod' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2018-06-19 22:14:51 +02:00
|
|
|
class C {
|
|
|
|
public static function foo() : void {
|
|
|
|
if (rand(0, 1)) {
|
|
|
|
self::foo();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public function bar() : void {}
|
|
|
|
}
|
|
|
|
|
|
|
|
(new C)->bar();',
|
|
|
|
'error_message' => 'PossiblyUnusedMethod',
|
|
|
|
],
|
2019-08-21 19:12:13 +02:00
|
|
|
'unusedFunctionCall' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2019-08-21 19:12:13 +02:00
|
|
|
strlen("goodbye");',
|
|
|
|
'error_message' => 'UnusedFunctionCall',
|
|
|
|
],
|
2020-07-17 00:14:15 +02:00
|
|
|
'unusedMethodCallSimple' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2020-07-16 22:19:29 +02:00
|
|
|
final class A {
|
2019-08-30 22:40:32 +02:00
|
|
|
private string $foo;
|
|
|
|
|
|
|
|
public function __construct(string $foo) {
|
|
|
|
$this->foo = $foo;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getFoo() : string {
|
|
|
|
return $this->foo;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$a = new A("hello");
|
|
|
|
$a->getFoo();',
|
|
|
|
'error_message' => 'UnusedMethodCall',
|
|
|
|
],
|
2019-04-17 22:41:35 +02:00
|
|
|
'propertyOverriddenDownstreamAndNotUsed' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2019-04-17 22:41:35 +02:00
|
|
|
class A {
|
|
|
|
/** @var string */
|
|
|
|
public $foo = "hello";
|
|
|
|
}
|
|
|
|
|
|
|
|
class B extends A {
|
|
|
|
/** @var string */
|
|
|
|
public $foo = "goodbye";
|
|
|
|
}
|
|
|
|
|
|
|
|
new B();',
|
|
|
|
'error_message' => 'PossiblyUnusedProperty',
|
|
|
|
],
|
2019-07-22 05:29:16 +02:00
|
|
|
'propertyUsedOnlyInConstructor' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2019-07-22 05:29:16 +02:00
|
|
|
class A {
|
|
|
|
/** @var int */
|
|
|
|
private $used;
|
|
|
|
|
|
|
|
/** @var int */
|
|
|
|
private $unused;
|
|
|
|
|
|
|
|
/** @var int */
|
|
|
|
private static $staticUnused;
|
|
|
|
|
|
|
|
public function __construct() {
|
|
|
|
$this->used = 4;
|
|
|
|
$this->unused = 4;
|
|
|
|
self::$staticUnused = 4;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function handle(): void
|
|
|
|
{
|
|
|
|
$this->used++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
(new A())->handle();',
|
|
|
|
'error_message' => 'UnusedProperty',
|
|
|
|
],
|
2019-08-30 23:26:55 +02:00
|
|
|
'unusedMethodCallForExternalMutationFreeClass' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2019-08-30 23:26:55 +02:00
|
|
|
/**
|
|
|
|
* @psalm-external-mutation-free
|
|
|
|
*/
|
|
|
|
class A {
|
|
|
|
private string $foo;
|
|
|
|
|
|
|
|
public function __construct(string $foo) {
|
|
|
|
$this->foo = $foo;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setFoo(string $foo) : void {
|
|
|
|
$this->foo = $foo;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function foo() : void {
|
|
|
|
(new A("hello"))->setFoo("goodbye");
|
|
|
|
}',
|
|
|
|
'error_message' => 'UnusedMethodCall',
|
|
|
|
],
|
2019-08-30 23:34:58 +02:00
|
|
|
'unusedMethodCallForGeneratingMethod' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2019-08-30 23:34:58 +02:00
|
|
|
/**
|
|
|
|
* @psalm-external-mutation-free
|
|
|
|
*/
|
|
|
|
class A {
|
|
|
|
private string $foo;
|
|
|
|
|
|
|
|
public function __construct(string $foo) {
|
|
|
|
$this->foo = $foo;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getFoo() : string {
|
|
|
|
return "abular" . $this->foo;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-pure
|
|
|
|
*/
|
|
|
|
function makeA(string $s) : A {
|
|
|
|
return new A($s);
|
|
|
|
}
|
|
|
|
|
|
|
|
function foo() : void {
|
|
|
|
makeA("hello")->getFoo();
|
|
|
|
}',
|
|
|
|
'error_message' => 'UnusedMethodCall',
|
|
|
|
],
|
2019-08-31 06:47:12 +02:00
|
|
|
'annotatedMutationFreeUnused' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2019-08-31 06:47:12 +02:00
|
|
|
class A {
|
|
|
|
private string $s;
|
|
|
|
|
|
|
|
public function __construct(string $s) {
|
|
|
|
$this->s = $s;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @psalm-mutation-free */
|
|
|
|
public function getShort() : string {
|
|
|
|
return substr($this->s, 0, 5);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$a = new A("hello");
|
|
|
|
$a->getShort();',
|
|
|
|
'error_message' => 'UnusedMethodCall',
|
2019-09-03 19:49:15 +02:00
|
|
|
],
|
2020-03-09 23:55:35 +01:00
|
|
|
'dateTimeImmutable' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2019-09-03 19:49:15 +02:00
|
|
|
function foo(DateTimeImmutable $dt) : void {
|
|
|
|
$dt->modify("+1 day");
|
|
|
|
}',
|
|
|
|
'error_message' => 'UnusedMethodCall',
|
|
|
|
],
|
2019-12-16 17:46:10 +01:00
|
|
|
'unusedClassReferencesItself' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2019-12-16 17:46:10 +01:00
|
|
|
class A {}
|
|
|
|
|
|
|
|
class AChild extends A {
|
|
|
|
public function __construct() {
|
|
|
|
self::foo();
|
|
|
|
}
|
|
|
|
public static function foo() : void {}
|
|
|
|
}',
|
|
|
|
'error_message' => 'UnusedClass',
|
|
|
|
],
|
2020-03-15 19:31:41 +01:00
|
|
|
'returnInBothIfConditions' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2020-03-15 19:31:41 +01:00
|
|
|
|
|
|
|
function doAThing(): bool {
|
|
|
|
if (rand(0, 1)) {
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}',
|
|
|
|
'error_message' => 'UnevaluatedCode',
|
|
|
|
],
|
|
|
|
'unevaluatedCodeAfterReturnInFinally' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2020-03-15 19:31:41 +01:00
|
|
|
function noOp(): void {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
function doAThing(): bool {
|
|
|
|
try {
|
|
|
|
noOp();
|
|
|
|
} finally {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}',
|
|
|
|
'error_message' => 'UnevaluatedCode',
|
|
|
|
],
|
2021-03-25 14:05:59 +01:00
|
|
|
'UnusedFunctionCallWithOptionalByReferenceParameter' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-03-25 14:05:59 +01:00
|
|
|
/**
|
|
|
|
* @pure
|
|
|
|
*/
|
|
|
|
function bar(string $c, string &$str = ""): string
|
|
|
|
{
|
|
|
|
$c .= $str;
|
|
|
|
|
|
|
|
return $c;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @pure
|
|
|
|
*/
|
|
|
|
function baz(): string
|
|
|
|
{
|
|
|
|
$f = "foo";
|
|
|
|
bar($f);
|
|
|
|
|
|
|
|
return $f;
|
|
|
|
}',
|
|
|
|
'error_message' => 'UnusedFunctionCall',
|
|
|
|
],
|
|
|
|
'UnusedFunctionCallWithOptionalByReferenceParameterV2' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-03-25 14:05:59 +01:00
|
|
|
/**
|
|
|
|
* @pure
|
|
|
|
*/
|
|
|
|
function bar(string $st, string &$str = ""): string
|
|
|
|
{
|
|
|
|
$st .= $str;
|
|
|
|
|
|
|
|
return $st;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @pure
|
|
|
|
*/
|
|
|
|
function baz(): string
|
|
|
|
{
|
|
|
|
$f = "foo";
|
|
|
|
bar(st: $f);
|
|
|
|
|
|
|
|
return $f;
|
|
|
|
}',
|
|
|
|
'error_message' => 'UnusedFunctionCall',
|
|
|
|
],
|
2021-05-21 15:25:57 +02:00
|
|
|
'propertyWrittenButNotRead' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-05-21 15:25:57 +02:00
|
|
|
class A {
|
|
|
|
public string $a = "hello";
|
|
|
|
public string $b = "world";
|
|
|
|
|
|
|
|
public function __construct() {
|
|
|
|
$this->a = "hello";
|
|
|
|
$this->b = "world";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$foo = new A();
|
|
|
|
echo $foo->a;',
|
|
|
|
'error_message' => 'PossiblyUnusedProperty',
|
|
|
|
],
|
2021-06-10 20:39:01 +02:00
|
|
|
'unusedInterfaceReturnValue' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-06-10 20:39:01 +02:00
|
|
|
interface I {
|
|
|
|
public function work(): bool;
|
|
|
|
}
|
|
|
|
|
|
|
|
function f(I $worker): void {
|
|
|
|
$worker->work();
|
|
|
|
}',
|
|
|
|
'error_message' => 'PossiblyUnusedReturnValue',
|
|
|
|
],
|
2021-06-12 22:13:29 +02:00
|
|
|
'unusedInterfaceReturnValueWithImplementingClass' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-06-10 20:39:01 +02:00
|
|
|
interface IWorker {
|
|
|
|
public function work(): bool;
|
|
|
|
}
|
|
|
|
|
|
|
|
class Worker implements IWorker{
|
|
|
|
public function work(): bool {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function f(IWorker $worker): void {
|
|
|
|
$worker->work();
|
|
|
|
}
|
|
|
|
|
|
|
|
f(new Worker());',
|
|
|
|
'error_message' => 'PossiblyUnusedReturnValue',
|
|
|
|
],
|
2021-06-12 22:13:29 +02:00
|
|
|
'interfaceWithImplementingClassMethodUnused' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-06-12 22:13:29 +02:00
|
|
|
interface IWorker {
|
|
|
|
public function work(): void;
|
|
|
|
}
|
|
|
|
|
|
|
|
class Worker implements IWorker {
|
|
|
|
public function work(): void {}
|
|
|
|
}
|
|
|
|
|
|
|
|
function f(IWorker $worker): void {
|
|
|
|
echo get_class($worker);
|
|
|
|
}
|
|
|
|
|
|
|
|
f(new Worker());',
|
|
|
|
'error_message' => 'PossiblyUnusedMethod',
|
|
|
|
],
|
2021-10-11 17:33:02 +02:00
|
|
|
'UnusedFunctionInDoubleConditional' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-10-11 17:33:02 +02:00
|
|
|
$list = [];
|
|
|
|
|
|
|
|
if (rand(0,1) && rand(0,1)) {
|
|
|
|
array_merge($list, []);
|
|
|
|
};
|
|
|
|
',
|
|
|
|
'error_message' => 'UnusedFunctionCall',
|
|
|
|
],
|
2021-11-01 22:45:17 +01:00
|
|
|
'functionNeverUnevaluatedCode' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-11-01 22:45:17 +01:00
|
|
|
/** @return never */
|
|
|
|
function neverReturns() {
|
|
|
|
die();
|
|
|
|
}
|
|
|
|
|
|
|
|
function f(): void {
|
|
|
|
neverReturns();
|
|
|
|
echo "hello";
|
|
|
|
}
|
|
|
|
',
|
|
|
|
'error_message' => 'UnevaluatedCode',
|
|
|
|
],
|
|
|
|
'methodNeverUnevaluatedCode' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-11-01 22:45:17 +01:00
|
|
|
class A{
|
|
|
|
/** @return never */
|
|
|
|
function neverReturns() {
|
|
|
|
die();
|
|
|
|
}
|
|
|
|
|
|
|
|
function f(): void {
|
|
|
|
$this->neverReturns();
|
|
|
|
echo "hello";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
',
|
|
|
|
'error_message' => 'UnevaluatedCode',
|
|
|
|
],
|
|
|
|
'exitNeverUnevaluatedCode' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-11-01 22:45:17 +01:00
|
|
|
function f(): void {
|
|
|
|
exit();
|
|
|
|
echo "hello";
|
|
|
|
}
|
|
|
|
',
|
|
|
|
'error_message' => 'UnevaluatedCode',
|
|
|
|
],
|
2021-11-10 13:06:16 +01:00
|
|
|
'exitInlineHtml' => [
|
2022-01-13 19:49:37 +01:00
|
|
|
'code' => '<?php
|
2021-11-10 13:06:16 +01:00
|
|
|
exit(0);
|
2023-02-16 03:04:26 +01:00
|
|
|
?' . '>foo
|
2021-11-10 13:06:16 +01:00
|
|
|
',
|
|
|
|
'error_message' => 'UnevaluatedCode',
|
|
|
|
],
|
2022-12-03 05:51:22 +01:00
|
|
|
'noCrashOnReadonlyStaticProp' => [
|
|
|
|
'code' => '<?php
|
|
|
|
/** @psalm-immutable */
|
|
|
|
final class C { public int $val = 2; }
|
|
|
|
|
|
|
|
final class A {
|
|
|
|
private static C $prop;
|
|
|
|
public static function f()
|
|
|
|
{
|
|
|
|
self::$prop->val = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
',
|
|
|
|
'error_message' => 'InaccessibleProperty',
|
|
|
|
],
|
2022-12-22 15:43:55 +01:00
|
|
|
'psalm-api with unused private property' => [
|
|
|
|
'code' => <<<'PHP'
|
|
|
|
<?php
|
|
|
|
/** @psalm-api */
|
|
|
|
class A {
|
|
|
|
private int $b = 0;
|
|
|
|
}
|
|
|
|
PHP,
|
|
|
|
'error_message' => 'UnusedProperty',
|
|
|
|
],
|
|
|
|
'psalm-api with final class and unused protected property' => [
|
|
|
|
'code' => <<<'PHP'
|
|
|
|
<?php
|
|
|
|
/** @psalm-api */
|
|
|
|
final class A {
|
|
|
|
protected int $b = 0;
|
|
|
|
}
|
|
|
|
PHP,
|
|
|
|
'error_message' => 'PossiblyUnusedProperty',
|
|
|
|
],
|
|
|
|
'psalm-api with unused private method' => [
|
|
|
|
'code' => <<<'PHP'
|
|
|
|
<?php
|
|
|
|
/** @psalm-api */
|
|
|
|
class A {
|
|
|
|
private function b(): void {}
|
|
|
|
}
|
|
|
|
PHP,
|
|
|
|
'error_message' => 'UnusedMethod',
|
|
|
|
],
|
|
|
|
'psalm-api with final class and unused protected method' => [
|
|
|
|
'code' => <<<'PHP'
|
|
|
|
<?php
|
|
|
|
/** @psalm-api */
|
|
|
|
final class A {
|
|
|
|
protected function b(): void {}
|
|
|
|
}
|
|
|
|
PHP,
|
|
|
|
'error_message' => 'PossiblyUnusedMethod',
|
|
|
|
],
|
|
|
|
'psalm-api with unused class and unused param' => [
|
|
|
|
'code' => <<<'PHP'
|
|
|
|
<?php
|
|
|
|
/** @psalm-api */
|
|
|
|
class A {
|
|
|
|
public function b(int $c): void {}
|
|
|
|
}
|
|
|
|
PHP,
|
|
|
|
'error_message' => 'PossiblyUnusedParam',
|
|
|
|
],
|
|
|
|
'unused param' => [
|
|
|
|
'code' => <<<'PHP'
|
|
|
|
<?php
|
|
|
|
/** @psalm-suppress UnusedClass */
|
|
|
|
class A {
|
|
|
|
public function b(int $c): void {}
|
|
|
|
}
|
|
|
|
PHP,
|
|
|
|
'error_message' => 'PossiblyUnusedParam',
|
|
|
|
],
|
2023-02-16 03:04:26 +01:00
|
|
|
'unused param tag' => [
|
|
|
|
'code' => <<<'PHP'
|
|
|
|
<?php
|
|
|
|
/**
|
|
|
|
* @param string $param
|
|
|
|
*/
|
|
|
|
function f(): void {}
|
|
|
|
PHP,
|
|
|
|
'error_message' => 'UnusedDocblockParam',
|
|
|
|
],
|
2017-04-25 05:45:02 +02:00
|
|
|
];
|
2017-02-08 06:28:26 +01:00
|
|
|
}
|
|
|
|
}
|