mirror of
https://github.com/danog/psalm.git
synced 2024-12-03 10:07:52 +01:00
d19aad7db1
Historically it was often not quite clear to users what PHP version Psalm assumes, and why. This PR addresses this issue by printing the version and where we got it from right before scanning the files.
472 lines
18 KiB
PHP
472 lines
18 KiB
PHP
<?php
|
|
namespace Psalm\Tests\FileUpdates;
|
|
|
|
use Psalm\Internal\Analyzer\ProjectAnalyzer;
|
|
use Psalm\Internal\Provider\FakeFileProvider;
|
|
use Psalm\Internal\Provider\Providers;
|
|
use Psalm\Tests\Internal\Provider;
|
|
use Psalm\Tests\TestConfig;
|
|
|
|
use function array_keys;
|
|
use function count;
|
|
use function getcwd;
|
|
|
|
use const DIRECTORY_SEPARATOR;
|
|
|
|
class ErrorFixTest extends \Psalm\Tests\TestCase
|
|
{
|
|
public function setUp() : void
|
|
{
|
|
parent::setUp();
|
|
|
|
$this->file_provider = new FakeFileProvider();
|
|
|
|
$config = new TestConfig();
|
|
$config->throw_exception = false;
|
|
|
|
$providers = new Providers(
|
|
$this->file_provider,
|
|
new \Psalm\Tests\Internal\Provider\ParserInstanceCacheProvider(),
|
|
null,
|
|
null,
|
|
new Provider\FakeFileReferenceCacheProvider(),
|
|
new \Psalm\Tests\Internal\Provider\ProjectCacheProvider()
|
|
);
|
|
|
|
$this->project_analyzer = new ProjectAnalyzer(
|
|
$config,
|
|
$providers
|
|
);
|
|
$this->project_analyzer->setPhpVersion('7.3', 'tests');
|
|
}
|
|
|
|
/**
|
|
* @dataProvider providerTestErrorFix
|
|
*
|
|
* @param array<int, array<string, string>> $files
|
|
* @param array<int, int> $error_counts
|
|
* @param array<string, string> $error_levels
|
|
*
|
|
*/
|
|
public function testErrorFix(
|
|
array $files,
|
|
array $error_counts,
|
|
array $error_levels = []
|
|
): void {
|
|
$this->project_analyzer->getCodebase()->diff_methods = true;
|
|
|
|
$codebase = $this->project_analyzer->getCodebase();
|
|
|
|
$config = $codebase->config;
|
|
|
|
foreach ($error_levels as $error_type => $error_level) {
|
|
$config->setCustomErrorLevel($error_type, $error_level);
|
|
}
|
|
|
|
$analyzed_files = [];
|
|
|
|
for ($i = 0; $i < count($files); ++$i) {
|
|
$batch = $files[$i];
|
|
|
|
foreach ($batch as $file_path => $contents) {
|
|
$this->file_provider->registerFile($file_path, $contents);
|
|
|
|
if (!isset($analyzed_files[$file_path])) {
|
|
$codebase->addFilesToAnalyze([$file_path => $file_path]);
|
|
$analyzed_files[$file_path] = true;
|
|
}
|
|
}
|
|
|
|
if ($i === 0) {
|
|
$codebase->scanFiles();
|
|
} else {
|
|
$codebase->reloadFiles($this->project_analyzer, array_keys($batch));
|
|
}
|
|
|
|
$codebase->analyzer->analyzeFiles($this->project_analyzer, 1, false);
|
|
|
|
$expected_count = 0;
|
|
|
|
$data = \Psalm\IssueBuffer::clear();
|
|
|
|
foreach ($data as $file_issues) {
|
|
$expected_count += count($file_issues);
|
|
}
|
|
|
|
$this->assertSame($error_counts[$i], $expected_count);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return array<string,array{files: array<int, array<string,string>>,error_counts:array<int,int>,error_levels?:array<string,string>}>
|
|
*/
|
|
public function providerTestErrorFix(): array
|
|
{
|
|
return [
|
|
'fixMissingColonSyntaxError' => [
|
|
'files' => [
|
|
[
|
|
getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => '<?php
|
|
namespace Foo;
|
|
|
|
class A {
|
|
public function foo() : void {
|
|
$a = 5;
|
|
echo $a;
|
|
}
|
|
}',
|
|
],
|
|
[
|
|
getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => '<?php
|
|
namespace Foo;
|
|
|
|
class A {
|
|
public function foo() : void {
|
|
$a = 5
|
|
echo $a;
|
|
}
|
|
}',
|
|
],
|
|
[
|
|
getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => '<?php
|
|
namespace Foo;
|
|
|
|
class A {
|
|
public function foo() : void {
|
|
$a = 5;
|
|
echo $a;
|
|
}
|
|
}',
|
|
],
|
|
],
|
|
'error_counts' => [0, 1, 0],
|
|
],
|
|
'addReturnTypesToSingleMethod' => [
|
|
'files' => [
|
|
[
|
|
getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => '<?php
|
|
namespace Foo;
|
|
|
|
class A {
|
|
public function foo() {
|
|
return 5;
|
|
}
|
|
|
|
public function bar() {
|
|
return $this->foo();
|
|
}
|
|
}',
|
|
],
|
|
[
|
|
getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => '<?php
|
|
namespace Foo;
|
|
|
|
class A {
|
|
public function foo() : int {
|
|
return 5;
|
|
}
|
|
|
|
public function bar() {
|
|
return $this->foo();
|
|
}
|
|
}',
|
|
],
|
|
[
|
|
getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => '<?php
|
|
namespace Foo;
|
|
|
|
class A {
|
|
public function foo() : int {
|
|
return 5;
|
|
}
|
|
|
|
public function bar() : int {
|
|
return $this->foo();
|
|
}
|
|
}',
|
|
],
|
|
],
|
|
'error_counts' => [2, 1, 0],
|
|
[
|
|
'MissingReturnType' => \Psalm\Config::REPORT_INFO,
|
|
],
|
|
],
|
|
'traitMethodRenameFirstCorrect' => [
|
|
'files' => [
|
|
[
|
|
getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => '<?php
|
|
namespace Foo;
|
|
|
|
class A {
|
|
use T;
|
|
public function foo() : void {
|
|
echo $this->bar();
|
|
}
|
|
}',
|
|
getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'T.php' => '<?php
|
|
namespace Foo;
|
|
|
|
trait T {
|
|
public function bar() : string {
|
|
return "hello";
|
|
}
|
|
}',
|
|
],
|
|
[
|
|
getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => '<?php
|
|
namespace Foo;
|
|
|
|
class A {
|
|
use T;
|
|
public function foo() : void {
|
|
echo $this->bar();
|
|
}
|
|
}',
|
|
getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'T.php' => '<?php
|
|
namespace Foo;
|
|
|
|
trait T {
|
|
public function bat() : string {
|
|
return "hello";
|
|
}
|
|
}',
|
|
],
|
|
[
|
|
getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => '<?php
|
|
namespace Foo;
|
|
|
|
class A {
|
|
use T;
|
|
public function foo() : void {
|
|
echo $this->bar();
|
|
}
|
|
}',
|
|
getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'T.php' => '<?php
|
|
namespace Foo;
|
|
|
|
trait T {
|
|
public function bar() : string {
|
|
return "hello";
|
|
}
|
|
}',
|
|
],
|
|
],
|
|
'error_counts' => [0, 2, 0],
|
|
],
|
|
'traitMethodRenameFirstError' => [
|
|
'files' => [
|
|
[
|
|
getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => '<?php
|
|
namespace Foo;
|
|
|
|
class A {
|
|
use T;
|
|
public function foo() : void {
|
|
echo $this->bar();
|
|
}
|
|
}',
|
|
getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'T.php' => '<?php
|
|
namespace Foo;
|
|
|
|
trait T {
|
|
public function bat() : string {
|
|
return "hello";
|
|
}
|
|
}',
|
|
],
|
|
[
|
|
getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => '<?php
|
|
namespace Foo;
|
|
|
|
class A {
|
|
use T;
|
|
public function foo() : void {
|
|
echo $this->bar();
|
|
}
|
|
}',
|
|
getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'T.php' => '<?php
|
|
namespace Foo;
|
|
|
|
trait T {
|
|
public function bar() : string {
|
|
return "hello";
|
|
}
|
|
}',
|
|
],
|
|
[
|
|
getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => '<?php
|
|
namespace Foo;
|
|
|
|
class A {
|
|
use T;
|
|
public function foo() : void {
|
|
echo $this->bar();
|
|
}
|
|
}',
|
|
getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'T.php' => '<?php
|
|
namespace Foo;
|
|
|
|
trait T {
|
|
public function bar() : string {
|
|
return "hello";
|
|
}
|
|
}',
|
|
],
|
|
],
|
|
'error_counts' => [2, 0, 0],
|
|
],
|
|
'addSuppressions' => [
|
|
'files' => [
|
|
[
|
|
getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => '<?php
|
|
class C {
|
|
public function foo(array $a) : void {
|
|
foreach ($a as $b) {
|
|
$b->bar();
|
|
}
|
|
}
|
|
}',
|
|
],
|
|
[
|
|
getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => '<?php
|
|
class C {
|
|
public function foo(array $a) : void {
|
|
/**
|
|
* @psalm-suppress MixedAssignment
|
|
*/
|
|
foreach ($a as $b) {
|
|
$b->bar();
|
|
}
|
|
}
|
|
}',
|
|
],
|
|
[
|
|
getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => '<?php
|
|
class C {
|
|
public function foo(array $a) : void {
|
|
/**
|
|
* @psalm-suppress MixedAssignment
|
|
*/
|
|
foreach ($a as $b) {
|
|
/**
|
|
* @psalm-suppress MixedMethodCall
|
|
*/
|
|
$b->bar();
|
|
}
|
|
}
|
|
}',
|
|
],
|
|
],
|
|
'error_counts' => [2, 1, 0],
|
|
],
|
|
'fixDefault' => [
|
|
'files' => [
|
|
[
|
|
getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => '<?php
|
|
class C {
|
|
/** @var string */
|
|
public $foo = 5;
|
|
}',
|
|
],
|
|
[
|
|
getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => '<?php
|
|
class C {
|
|
/** @var string */
|
|
public $foo = "hello";
|
|
}',
|
|
],
|
|
],
|
|
'error_counts' => [1, 0],
|
|
],
|
|
'changeContent' => [
|
|
'files' => [
|
|
[
|
|
getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => '<?php
|
|
function add(int $a, int $b): int {
|
|
return $a + $b;
|
|
}',
|
|
],
|
|
[
|
|
getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'B.php' => '<?php
|
|
function hasMethod(object $input, string $method): bool {
|
|
return (new ReflectionClass($input))
|
|
->hasMethod($method);
|
|
}',
|
|
],
|
|
[
|
|
getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'C.php' => '<?php
|
|
function add(int $a, int $b): int {
|
|
return $a + $b;
|
|
}',
|
|
],
|
|
[
|
|
getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'D.php' => '<?php
|
|
function hasMethod(object $input, string $method): bool {
|
|
return (new ReflectionClass($input))
|
|
->hasMethod($method);
|
|
}',
|
|
],
|
|
],
|
|
'error_counts' => [0, 0, 0, 0],
|
|
],
|
|
'missingConstructorForTwoVars' => [
|
|
'files' => [
|
|
[
|
|
getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => '<?php
|
|
class A {
|
|
protected int $x;
|
|
protected int $y;
|
|
}'
|
|
],
|
|
[
|
|
getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => '<?php
|
|
class A {
|
|
protected int $x = 0;
|
|
protected int $y;
|
|
}'
|
|
],
|
|
],
|
|
'error_counts' => [2, 1],
|
|
],
|
|
'missingConstructorForInheritedProperties' => [
|
|
'files' => [
|
|
[
|
|
getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => '<?php
|
|
abstract class A {
|
|
public int $x;
|
|
public int $y;
|
|
}
|
|
|
|
class B extends A {
|
|
public function __construct() {}
|
|
}'
|
|
],
|
|
[
|
|
getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => '<?php
|
|
abstract class A {
|
|
public int $x = 0;
|
|
public int $y;
|
|
}
|
|
|
|
class B extends A {
|
|
public function __construct() {}
|
|
}'
|
|
],
|
|
[
|
|
getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => '<?php
|
|
abstract class A {
|
|
public int $x = 0;
|
|
public int $y = 0;
|
|
}
|
|
|
|
class B extends A {
|
|
public function __construct() {}
|
|
}'
|
|
],
|
|
],
|
|
'error_counts' => [2, 1, 0],
|
|
],
|
|
];
|
|
}
|
|
}
|