1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-22 22:01:48 +01:00
psalm/tests/FileUpdates/AnalyzedMethodTest.php
Bruce Weirdan d19aad7db1
Display target PHP version
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.
2021-11-27 02:18:09 +02:00

1361 lines
50 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 getcwd;
use function strpos;
use const DIRECTORY_SEPARATOR;
class AnalyzedMethodTest extends \Psalm\Tests\TestCase
{
public function setUp() : void
{
parent::setUp();
$this->file_provider = new FakeFileProvider();
$config = new TestConfig();
$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 providerTestValidUpdates
*
* @param array<string, string> $start_files
* @param array<string, string> $end_files
* @param array<string, string> $error_levels
*
*/
public function testValidInclude(
array $start_files,
array $end_files,
array $initial_analyzed_methods,
array $unaffected_analyzed_methods,
array $error_levels = []
): void {
$test_name = $this->getTestName();
if (strpos($test_name, 'SKIPPED-') !== false) {
$this->markTestSkipped('Skipped due to a bug.');
}
$this->project_analyzer->getCodebase()->diff_methods = true;
$codebase = $this->project_analyzer->getCodebase();
$config = $codebase->config;
$config->throw_exception = false;
foreach ($error_levels as $error_type => $error_level) {
$config->setCustomErrorLevel($error_type, $error_level);
}
foreach ($start_files as $file_path => $contents) {
$this->file_provider->registerFile($file_path, $contents);
$codebase->addFilesToAnalyze([$file_path => $file_path]);
}
$codebase->scanFiles();
$this->assertSame([], $codebase->analyzer->getAnalyzedMethods());
$codebase->analyzer->analyzeFiles($this->project_analyzer, 1, false);
$this->assertSame(
$initial_analyzed_methods,
$codebase->analyzer->getAnalyzedMethods(),
'initial analyzed methods are not the same'
);
foreach ($end_files as $file_path => $contents) {
$this->file_provider->registerFile($file_path, $contents);
}
$codebase->reloadFiles($this->project_analyzer, array_keys($end_files));
$codebase->analyzer->loadCachedResults($this->project_analyzer);
$this->assertSame(
$unaffected_analyzed_methods,
$codebase->analyzer->getAnalyzedMethods(),
'unaffected analyzed methods are not the same'
);
}
/**
* @return array<string,array{start_files:array<string,string>,end_files:array<string,string>,initial_analyzed_methods:array<string,array<string,int>>,unaffected_analyzed_methods:array<string,array<string,int>>,4?:array<string,string>}>
*/
public function providerTestValidUpdates(): array
{
return [
'basicRequire' => [
'start_files' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => '<?php
namespace Foo;
class A{
public function fooFoo(): void {
}
public function barBar(): string {
return "hello";
}
}',
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => '<?php
namespace Foo;
class B {
public function foo(): void {
(new A)->fooFoo();
}
public function bar() : void {
echo (new A)->barBar();
}
public function noReturnType() {}
}',
],
'end_files' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => '<?php
namespace Foo;
class A{
public function fooFoo(?string $foo = null): void {
}
public function barBar(): string {
return "hello";
}
}',
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => '<?php
namespace Foo;
class B {
public function foo(): void {
(new A)->fooFoo();
}
public function bar() : void {
echo (new A)->barBar();
}
public function noReturnType() {}
}',
],
'initial_analyzed_methods' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [
'foo\a::foofoo' => 1,
'foo\a::barbar' => 1,
],
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [
'foo\b::foo' => 1,
'foo\b::bar' => 1,
'foo\b::noreturntype' => 1,
],
],
'unaffected_analyzed_methods' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [
'foo\a::barbar' => 1,
],
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [
'foo\b::bar' => 1,
'foo\b::noreturntype' => 1,
],
],
[
'MissingReturnType' => \Psalm\Config::REPORT_INFO,
],
],
'invalidateAfterPropertyChange' => [
'start_files' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => '<?php
namespace Foo;
class A {
/** @var string */
public $foo = "bar";
}',
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => '<?php
namespace Foo;
class B {
public function foo() : string {
return (new A)->foo;
}
public function bar() : void {
$a = new A();
}
}',
],
'end_files' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => '<?php
namespace Foo;
class A {
/** @var int */
public $foo = 5;
}',
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => '<?php
namespace Foo;
class B {
public function foo() : string {
return (new A)->foo;
}
public function bar() : void {
$a = new A();
}
}',
],
'initial_analyzed_methods' => [
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [
'foo\b::foo' => 1,
'foo\b::bar' => 1,
],
],
'unaffected_analyzed_methods' => [
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [
'foo\b::bar' => 1,
],
],
],
'invalidateAfterStaticPropertyChange' => [
'start_files' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => '<?php
namespace Foo;
class A {
/** @var string */
public static $foo = "bar";
}',
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => '<?php
namespace Foo;
class B {
public function foo() : string {
return A::$foo;
}
public function bar() : void {
$a = new A();
}
}',
],
'end_files' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => '<?php
namespace Foo;
class A {
/** @var int */
public static $foo = 5;
}',
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => '<?php
namespace Foo;
class B {
public function foo() : string {
return A::$foo;
}
public function bar() : void {
$a = new A();
}
}',
],
'initial_analyzed_methods' => [
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [
'foo\b::foo' => 1,
'foo\b::bar' => 1,
],
],
'unaffected_analyzed_methods' => [
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [
'foo\b::bar' => 1,
],
],
],
'invalidateAfterStaticFlipPropertyChange' => [
'start_files' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => '<?php
namespace Foo;
class A {
/** @var string */
public static $foo = "bar";
}',
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => '<?php
namespace Foo;
class B {
public function foo() : string {
return A::$foo;
}
public function bar() : void {
$a = new A();
}
}',
],
'end_files' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => '<?php
namespace Foo;
class A {
/** @var string */
public $foo = "bar";
}',
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => '<?php
namespace Foo;
class B {
public function foo() : string {
return A::$foo;
}
public function bar() : void {
$a = new A();
}
}',
],
'initial_analyzed_methods' => [
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [
'foo\b::foo' => 1,
'foo\b::bar' => 1,
],
],
'unaffected_analyzed_methods' => [
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [
'foo\b::bar' => 1,
],
],
],
'invalidateAfterConstantChange' => [
'start_files' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => '<?php
namespace Foo;
class A {
public const FOO = "bar";
}',
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => '<?php
namespace Foo;
class B {
public function foo() : string {
return A::FOO;
}
public function bar() : void {
$a = new A();
}
}',
],
'end_files' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => '<?php
namespace Foo;
class A {
public const FOO = 5;
}',
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => '<?php
namespace Foo;
class B {
public function foo() : string {
return A::FOO;
}
public function bar() : void {
$a = new A();
}
}',
],
'initial_analyzed_methods' => [
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [
'foo\b::foo' => 1,
'foo\b::bar' => 1,
],
],
'unaffected_analyzed_methods' => [
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [
'foo\b::bar' => 1,
],
],
],
'dontInvalidateTraitMethods' => [
'start_files' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => '<?php
namespace Foo;
class A {
use T;
public function fooFoo(): void { }
}',
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => '<?php
namespace Foo;
class B {
public function foo(): void {
(new A)->fooFoo();
}
public function bar() : void {
echo (new A)->barBar();
}
public function noReturnType() {}
}',
getcwd() . DIRECTORY_SEPARATOR . 'T.php' => '<?php
namespace Foo;
trait T {
public function barBar(): string {
return "hello";
}
}',
],
'end_files' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => '<?php
namespace Foo;
class A {
use T;
public function fooFoo(?string $foo = null): void { }
}',
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => '<?php
namespace Foo;
class B {
public function foo(): void {
(new A)->fooFoo();
}
public function bar() : void {
echo (new A)->barBar();
}
public function noReturnType() {}
}',
getcwd() . DIRECTORY_SEPARATOR . 'T.php' => '<?php
namespace Foo;
trait T {
public function barBar(): string {
return "hello";
}
}',
],
'initial_analyzed_methods' => [
getcwd() . DIRECTORY_SEPARATOR . 'T.php' => [
'foo\a::barbar&foo\t::barbar' => 1,
],
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [
'foo\a::foofoo' => 1,
],
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [
'foo\b::foo' => 1,
'foo\b::bar' => 1,
'foo\b::noreturntype' => 1,
],
],
'unaffected_analyzed_methods' => [
getcwd() . DIRECTORY_SEPARATOR . 'T.php' => [
'foo\a::barbar&foo\t::barbar' => 1,
],
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [],
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [
'foo\b::bar' => 1,
'foo\b::noreturntype' => 1,
],
],
[
'MissingReturnType' => \Psalm\Config::REPORT_INFO,
],
],
'invalidateTraitMethodsWhenTraitRemoved' => [
'start_files' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => '<?php
namespace Foo;
class A {
use T;
public function fooFoo(?string $foo = null): void { }
}',
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => '<?php
namespace Foo;
class B {
public function foo(): void {
(new A)->fooFoo();
}
public function bar() : void {
echo (new A)->barBar();
}
}',
getcwd() . DIRECTORY_SEPARATOR . 'T.php' => '<?php
namespace Foo;
trait T {
public function barBar(): string {
return "hello";
}
}',
],
'end_files' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => '<?php
namespace Foo;
class A {
public function fooFoo(): void { }
}',
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => '<?php
namespace Foo;
class B {
public function foo(): void {
(new A)->fooFoo();
}
public function bar() : void {
echo (new A)->barBar();
}
}',
getcwd() . DIRECTORY_SEPARATOR . 'T.php' => '<?php
namespace Foo;
trait T {
public function barBar(): string {
return "hello";
}
}',
],
'initial_analyzed_methods' => [
getcwd() . DIRECTORY_SEPARATOR . 'T.php' => [
'foo\a::barbar&foo\t::barbar' => 1,
],
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [
'foo\a::foofoo' => 1,
],
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [
'foo\b::foo' => 1,
'foo\b::bar' => 1,
],
],
'unaffected_analyzed_methods' => [
getcwd() . DIRECTORY_SEPARATOR . 'T.php' => [],
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [],
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [],
],
],
'invalidateTraitMethodsWhenTraitReplaced' => [
'start_files' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => '<?php
namespace Foo;
class A {
use T;
public function fooFoo(): void { }
}',
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => '<?php
namespace Foo;
class B {
public function foo(): void {
(new A)->fooFoo();
}
public function bar() : void {
echo (new A)->barBar();
}
}',
getcwd() . DIRECTORY_SEPARATOR . 'T.php' => '<?php
namespace Foo;
trait T {
public function barBar(): string {
return "hello";
}
}',
],
'end_files' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => '<?php
namespace Foo;
class A {
public function fooFoo(?string $foo = null): void { }
public function barBar(): int {
return 5;
}
}',
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => '<?php
namespace Foo;
class B {
public function foo(): void {
(new A)->fooFoo();
}
public function bar() : void {
echo (new A)->barBar();
}
}',
getcwd() . DIRECTORY_SEPARATOR . 'T.php' => '<?php
namespace Foo;
trait T {
public function barBar(): string {
return "hello";
}
}',
],
'initial_analyzed_methods' => [
getcwd() . DIRECTORY_SEPARATOR . 'T.php' => [
'foo\a::barbar&foo\t::barbar' => 1,
],
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [
'foo\a::foofoo' => 1,
],
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [
'foo\b::foo' => 1,
'foo\b::bar' => 1,
],
],
'unaffected_analyzed_methods' => [
getcwd() . DIRECTORY_SEPARATOR . 'T.php' => [],
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [],
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [],
],
],
'invalidateTraitMethodsWhenMethodChanged' => [
'start_files' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => '<?php
namespace Foo;
class A {
use T;
public function fooFoo(): void { }
}',
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => '<?php
namespace Foo;
class B {
public function foo(): void {
(new A)->fooFoo();
}
public function bar() : void {
echo (new A)->barBar();
}
}',
getcwd() . DIRECTORY_SEPARATOR . 'T.php' => '<?php
namespace Foo;
trait T {
public function barBar(): string {
return "hello";
}
public function bat(): string {
return "hello";
}
}',
],
'end_files' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => '<?php
namespace Foo;
class A {
use T;
public function fooFoo(?string $foo = null): void { }
}',
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => '<?php
namespace Foo;
class B {
public function foo(): void {
(new A)->fooFoo();
}
public function bar() : void {
echo (new A)->barBar();
}
}',
getcwd() . DIRECTORY_SEPARATOR . 'T.php' => '<?php
namespace Foo;
trait T {
public function barBar(): int {
return 5;
}
public function bat(): string {
return "hello";
}
}',
],
'initial_analyzed_methods' => [
getcwd() . DIRECTORY_SEPARATOR . 'T.php' => [
'foo\a::barbar&foo\t::barbar' => 1,
'foo\a::bat&foo\t::bat' => 1,
],
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [
'foo\a::foofoo' => 1,
],
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [
'foo\b::foo' => 1,
'foo\b::bar' => 1,
],
],
'unaffected_analyzed_methods' => [
getcwd() . DIRECTORY_SEPARATOR . 'T.php' => [
'foo\a::bat&foo\t::bat' => 1,
],
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [],
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [],
],
],
'invalidateTraitMethodsWhenMethodSuperimposed' => [
'start_files' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => '<?php
namespace Foo;
class A {
use T;
}',
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => '<?php
namespace Foo;
class B {
public function bar() : string {
return (new A)->barBar();
}
}',
getcwd() . DIRECTORY_SEPARATOR . 'T.php' => '<?php
namespace Foo;
trait T {
public function barBar(): string {
return "hello";
}
}',
],
'end_files' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => '<?php
namespace Foo;
class A {
use T;
public function barBar(): int {
return 5;
}
}',
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => '<?php
namespace Foo;
class B {
public function bar() : string {
return (new A)->barBar();
}
}',
getcwd() . DIRECTORY_SEPARATOR . 'T.php' => '<?php
namespace Foo;
trait T {
public function barBar(): string {
return "hello";
}
}',
],
'initial_analyzed_methods' => [
getcwd() . DIRECTORY_SEPARATOR . 'T.php' => [
'foo\a::barbar&foo\t::barbar' => 1,
],
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [
'foo\b::bar' => 1,
],
],
'unaffected_analyzed_methods' => [
getcwd() . DIRECTORY_SEPARATOR . 'T.php' => [],
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [],
],
],
'dontInvalidateConstructor' => [
'start_files' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => '<?php
namespace Foo;
class A {
/** @var string */
public $foo;
public function __construct() {
$this->setFoo();
}
private function setFoo() : void {
$this->reallySetFoo();
}
private function reallySetFoo() : void {
$this->foo = "bar";
}
}',
],
'end_files' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => '<?php
namespace Foo;
class A {
/** @var string */
public $foo;
public function __construct() {
$this->setFoo();
}
private function setFoo() : void {
$this->reallySetFoo();
}
private function reallySetFoo() : void {
$this->foo = "bar";
}
}',
],
'initial_analyzed_methods' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [
'foo\a::__construct' => 2,
'foo\a::setfoo' => 1,
'foo\a::reallysetfoo' => 1,
],
],
'unaffected_analyzed_methods' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [
'foo\a::__construct' => 2,
'foo\a::setfoo' => 1,
'foo\a::reallysetfoo' => 1,
],
],
],
'invalidateConstructorWhenDependentMethodChanges' => [
'start_files' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => '<?php
namespace Foo;
class A {
/** @var string */
public $foo;
public function __construct() {
$this->setFoo();
}
private function setFoo() : void {
$this->reallySetFoo();
}
private function reallySetFoo() : void {
$this->foo = "bar";
}
}',
],
'end_files' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => '<?php
namespace Foo;
class A {
/** @var string */
public $foo;
public function __construct() {
$this->setFoo();
}
private function setFoo() : void {
$this->reallySetFoo();
}
private function reallySetFoo() : void {
//$this->foo = "bar";
}
}',
],
'initial_analyzed_methods' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [
'foo\a::__construct' => 2,
'foo\a::setfoo' => 1,
'foo\a::reallysetfoo' => 1,
],
],
'unaffected_analyzed_methods' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [
'foo\a::setfoo' => 1,
],
],
],
'invalidateConstructorWhenDependentMethodInSubclassChanges' => [
'start_files' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => '<?php
namespace Foo;
abstract class A {
public function __construct() {
$this->setFoo();
}
abstract protected function setFoo() : void;
}',
getcwd() . DIRECTORY_SEPARATOR . 'AChild.php' => '<?php
namespace Foo;
class AChild extends A {
/** @var string */
public $foo;
protected function setFoo() : void {
$this->reallySetFoo();
}
private function reallySetFoo() : void {
$this->foo = "bar";
}
}',
],
'end_files' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => '<?php
namespace Foo;
abstract class A {
public function __construct() {
$this->setFoo();
}
abstract protected function setFoo() : void;
}',
getcwd() . DIRECTORY_SEPARATOR . 'AChild.php' => '<?php
namespace Foo;
class AChild extends A {
/** @var string */
public $foo;
protected function setFoo() : void {
$this->reallySetFoo();
}
private function reallySetFoo() : void {
//$this->foo = "bar";
}
}',
],
'initial_analyzed_methods' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [
'foo\a::__construct' => 1,
'foo\a::setfoo' => 1,
],
getcwd() . DIRECTORY_SEPARATOR . 'AChild.php' => [
'foo\achild::setfoo' => 1,
'foo\achild::reallysetfoo' => 1,
'foo\achild::__construct' => 2,
],
],
'unaffected_analyzed_methods' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [
'foo\a::__construct' => 1,
'foo\a::setfoo' => 1,
],
getcwd() . DIRECTORY_SEPARATOR . 'AChild.php' => [
'foo\achild::setfoo' => 1,
],
],
],
'invalidateConstructorWhenDependentMethodInSubclassChanges2' => [
'start_files' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => '<?php
namespace Foo;
class A {
/** @var string */
public $foo;
public function __construct() {
$this->setFoo();
}
protected function setFoo() : void {
$this->foo = "bar";
}
}',
getcwd() . DIRECTORY_SEPARATOR . 'AChild.php' => '<?php
namespace Foo;
class AChild extends A {
public function __construct() {
parent::__construct();
}
}',
],
'end_files' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => '<?php
namespace Foo;
class A {
/** @var string */
public $foo;
public function __construct() {
$this->setFoo();
}
protected function setFoo() : void {
$this->foo = "baz";
}
}',
getcwd() . DIRECTORY_SEPARATOR . 'AChild.php' => '<?php
namespace Foo;
class AChild extends A {
public function __construct() {
parent::__construct();
}
}',
],
'initial_analyzed_methods' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [
'foo\a::__construct' => 2,
'foo\a::setfoo' => 1,
],
getcwd() . DIRECTORY_SEPARATOR . 'AChild.php' => [
'foo\achild::__construct' => 2,
],
],
'unaffected_analyzed_methods' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [],
getcwd() . DIRECTORY_SEPARATOR . 'AChild.php' => [],
],
],
'invalidateConstructorWhenDependentTraitMethodChanges' => [
'start_files' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => '<?php
namespace Foo;
class A {
use T;
/** @var string */
public $foo;
public function __construct() {
$this->setFoo();
}
}',
getcwd() . DIRECTORY_SEPARATOR . 'T.php' => '<?php
namespace Foo;
trait T {
private function setFoo() : void {
$this->foo = "bar";
}
}',
],
'end_files' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => '<?php
namespace Foo;
class A {
use T;
/** @var string */
public $foo;
public function __construct() {
$this->setFoo();
}
}',
getcwd() . DIRECTORY_SEPARATOR . 'T.php' => '<?php
namespace Foo;
trait T {
private function setFoo() : void {
//$this->foo = "bar";
}
}',
],
'initial_analyzed_methods' => [
getcwd() . DIRECTORY_SEPARATOR . 'T.php' => [
'foo\a::setfoo&foo\t::setfoo' => 1,
],
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [
'foo\a::__construct' => 2,
],
],
'unaffected_analyzed_methods' => [
getcwd() . DIRECTORY_SEPARATOR . 'T.php' => [],
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [],
],
],
'rescanPropertyAssertingMethod' => [
'start_files' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => '<?php
namespace Foo;
class A {
/** @var string */
private $foo;
public function __construct() {}
public function bar() : void {
if ($this->foo === null) {}
}
}',
],
'end_files' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => '<?php
namespace Foo;
class A {
/** @var string|null */
private $foo;
public function __construct() {}
public function bar() : void {
if ($this->foo === null) {}
}
}',
],
'initial_analyzed_methods' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [
'foo\a::__construct' => 2,
'foo\a::bar' => 1,
],
],
'unaffected_analyzed_methods' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [
'foo\a::__construct' => 2,
],
],
[
'PropertyNotSetInConstructor' => \Psalm\Config::REPORT_INFO,
'DocblockTypeContradiction' => \Psalm\Config::REPORT_INFO,
'RedundantConditionGivenDocblockType' => \Psalm\Config::REPORT_INFO,
],
],
'noChangeAfterSyntaxError' => [
'start_files' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => '<?php
namespace Foo;
class A {
private ?string $foo;
public function __construct() {}
public function bar() : void {
if ($this->foo === null) {}
}
}',
],
'end_files' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => '<?php
namespace Foo;
class A {
private ?string $foo
public function __construct() {}
public function bar() : void {
if ($this->foo === null) {}
}
}',
],
'initial_analyzed_methods' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [
'foo\a::__construct' => 2,
'foo\a::bar' => 1,
],
],
'unaffected_analyzed_methods' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [
'foo\a::__construct' => 2,
'foo\a::bar' => 1
],
],
],
'nothingBeforeSyntaxError' => [
'start_files' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => '<?php
namespace Foo;
class A {
private ?string $foo
public function __construct() {}
public function bar() : void {
if ($this->foo === null) {}
}
}',
],
'end_files' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => '<?php
namespace Foo;
class A {
private ?string $foo;
public function __construct() {}
public function bar() : void {
if ($this->foo === null) {}
}
}',
],
'initial_analyzed_methods' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [
'foo\a::__construct' => 2,
'foo\a::bar' => 1,
],
],
'unaffected_analyzed_methods' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [
'foo\a::__construct' => 2,
'foo\a::bar' => 1
],
],
],
'modifyPropertyOfChildClass' => [
'start_files' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => '<?php
namespace Foo;
abstract class A {
protected $arr = [1, 2, 3];
protected string $b;
public function __construct(int $a, string $b) {
echo $this->arr[$a];
$this->b = $b;
}
}',
getcwd() . DIRECTORY_SEPARATOR . 'AChild.php' => '<?php
namespace Foo;
class AChild extends A {
public $arr = [1, 2, 3, 4];
}',
],
'end_files' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => '<?php
namespace Foo;
abstract class A {
protected $arr = [1, 2, 3];
protected string $b;
public function __construct(int $a, string $b) {
echo $this->arr[$a];
$this->b = $b;
}
}',
getcwd() . DIRECTORY_SEPARATOR . 'AChild.php' => '<?php
namespace Foo;
class AChild extends A {
protected $arr;
}',
],
'initial_analyzed_methods' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [
'foo\a::__construct' => 2,
],
getcwd() . DIRECTORY_SEPARATOR . 'AChild.php' => [
'foo\achild::__construct' => 2,
],
],
'unaffected_analyzed_methods' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [
'foo\a::__construct' => 2,
],
getcwd() . DIRECTORY_SEPARATOR . 'AChild.php' => []
],
],
];
}
public function testFileMapsUpdated(): void
{
$codebase = $this->project_analyzer->getCodebase();
$config = $codebase->config;
$config->throw_exception = false;
$this->file_provider->registerFile('somefile.php', '
<?php
function foo() : void {
}
foo();
');
$codebase->addFilesToAnalyze(['somefile.php' => 'somefile.php']);
$codebase->scanFiles();
$codebase->analyzer->analyzeFiles($this->project_analyzer, 1, false);
$maps = $codebase->analyzer->getMapsForFile('somefile.php');
$this->assertNotEmpty($maps[0]);
$this->file_provider->setOpenContents('somefile.php', '');
$codebase->reloadFiles($this->project_analyzer, ['somefile.php']);
$codebase->analyzer->analyzeFiles($this->project_analyzer, 1, false);
$updated_maps = $codebase->analyzer->getMapsForFile('somefile.php');
$this->assertSame([], $updated_maps[0]);
$this->assertSame([], $updated_maps[1]);
$this->assertSame([], $updated_maps[2]);
}
}