2023-11-17 12:29:42 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
namespace Psalm\Tests\Internal\Codebase;
|
|
|
|
|
|
|
|
use Psalm\Codebase;
|
|
|
|
use Psalm\Context;
|
|
|
|
use Psalm\Internal\Analyzer\ProjectAnalyzer;
|
|
|
|
use Psalm\Internal\Provider\FakeFileProvider;
|
|
|
|
use Psalm\Internal\Provider\Providers;
|
|
|
|
use Psalm\Tests\Internal\Provider\FakeFileReferenceCacheProvider;
|
|
|
|
use Psalm\Tests\Internal\Provider\ParserInstanceCacheProvider;
|
|
|
|
use Psalm\Tests\Internal\Provider\ProjectCacheProvider;
|
|
|
|
use Psalm\Tests\TestCase;
|
|
|
|
use Psalm\Tests\TestConfig;
|
|
|
|
|
|
|
|
use function array_map;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Fat tests for method `getCompletionItemsForClassishThing` of class `Psalm\Codebase`.
|
|
|
|
*/
|
|
|
|
final class MethodGetCompletionItemsForClassishThingTest extends TestCase
|
|
|
|
{
|
|
|
|
private Codebase $codebase;
|
|
|
|
|
|
|
|
public function setUp(): void
|
|
|
|
{
|
|
|
|
parent::setUp();
|
|
|
|
|
|
|
|
$this->file_provider = new FakeFileProvider();
|
|
|
|
|
|
|
|
$config = new TestConfig();
|
|
|
|
|
|
|
|
$providers = new Providers(
|
|
|
|
$this->file_provider,
|
|
|
|
new ParserInstanceCacheProvider(),
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
new FakeFileReferenceCacheProvider(),
|
|
|
|
new ProjectCacheProvider(),
|
|
|
|
);
|
|
|
|
|
|
|
|
$this->codebase = new Codebase($config, $providers);
|
|
|
|
|
|
|
|
$this->project_analyzer = new ProjectAnalyzer(
|
|
|
|
$config,
|
|
|
|
$providers,
|
|
|
|
null,
|
|
|
|
[],
|
|
|
|
1,
|
|
|
|
null,
|
|
|
|
$this->codebase,
|
|
|
|
);
|
|
|
|
|
|
|
|
$this->project_analyzer->setPhpVersion('7.3', 'tests');
|
|
|
|
$this->project_analyzer->getCodebase()->store_node_types = true;
|
|
|
|
|
|
|
|
$this->codebase->config->throw_exception = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return list<string>
|
|
|
|
*/
|
|
|
|
protected function getCompletionLabels(string $content, string $class_name, string $gap): array
|
|
|
|
{
|
|
|
|
$this->addFile('somefile.php', $content);
|
|
|
|
|
|
|
|
$this->analyzeFile('somefile.php', new Context());
|
|
|
|
|
|
|
|
$items = $this->codebase->getCompletionItemsForClassishThing($class_name, $gap, true);
|
|
|
|
|
|
|
|
return array_map(fn($item) => $item->label, $items);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return iterable<array-key, array{0: string}>
|
|
|
|
*/
|
|
|
|
public function providerGaps(): iterable
|
|
|
|
{
|
|
|
|
return [
|
|
|
|
'object-gap' => ['->'],
|
|
|
|
'static-gap' => ['::'],
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @dataProvider providerGaps
|
|
|
|
*/
|
|
|
|
public function testSimpleOnceClass(string $gap): void
|
|
|
|
{
|
|
|
|
$content = <<<'EOF'
|
|
|
|
<?php
|
|
|
|
namespace B;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @property int $magicObjProp1
|
|
|
|
* @property-read string $magicObjProp2
|
|
|
|
* @method int magicObjMethod()
|
|
|
|
* @method static string magicStaticMethod()
|
|
|
|
*/
|
|
|
|
class A {
|
|
|
|
public $publicObjProp;
|
|
|
|
protected $protectedObjProp;
|
|
|
|
private $privateObjProp;
|
|
|
|
|
|
|
|
public static $publicStaticProp;
|
|
|
|
protected static $protectedStaticProp;
|
|
|
|
private static $privateStaticProp;
|
|
|
|
|
|
|
|
public function publicObjMethod() {}
|
|
|
|
protected function protectedObjMethod() {}
|
|
|
|
private function privateObjMethod() {}
|
|
|
|
|
|
|
|
public static function publicStaticMethod() {}
|
|
|
|
protected static function protectedStaticMethod() {}
|
|
|
|
private static function privateStaticMethod() {}
|
|
|
|
}
|
|
|
|
EOF;
|
|
|
|
|
|
|
|
$actual_labels = $this->getCompletionLabels($content, 'B\A', $gap);
|
|
|
|
|
|
|
|
$expected_labels = [
|
|
|
|
'->' => [
|
|
|
|
'magicObjProp1',
|
|
|
|
'magicObjProp2',
|
|
|
|
|
|
|
|
'magicObjMethod',
|
|
|
|
|
2023-11-17 12:41:53 +01:00
|
|
|
'publicObjProp',
|
|
|
|
'protectedObjProp',
|
|
|
|
'privateObjProp',
|
2023-11-17 12:29:42 +01:00
|
|
|
|
|
|
|
'publicObjMethod',
|
|
|
|
'protectedObjMethod',
|
|
|
|
'privateObjMethod',
|
|
|
|
|
|
|
|
'publicStaticMethod',
|
|
|
|
'protectedStaticMethod',
|
|
|
|
'privateStaticMethod',
|
|
|
|
],
|
|
|
|
'::' => [
|
|
|
|
'magicStaticMethod',
|
|
|
|
|
2023-11-17 12:41:53 +01:00
|
|
|
'publicStaticProp',
|
|
|
|
'protectedStaticProp',
|
|
|
|
'privateStaticProp',
|
2023-11-17 12:29:42 +01:00
|
|
|
|
|
|
|
'publicStaticMethod',
|
|
|
|
'protectedStaticMethod',
|
|
|
|
'privateStaticMethod',
|
|
|
|
],
|
|
|
|
];
|
|
|
|
|
|
|
|
$this->assertEqualsCanonicalizing($expected_labels[$gap], $actual_labels);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @dataProvider providerGaps
|
|
|
|
*/
|
|
|
|
public function testAbstractClass(string $gap): void
|
|
|
|
{
|
|
|
|
$content = <<<'EOF'
|
|
|
|
<?php
|
|
|
|
namespace B;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @property int $magicObjProp1
|
|
|
|
* @property-read string $magicObjProp2
|
|
|
|
* @method int magicObjMethod()
|
|
|
|
* @method static string magicStaticMethod()
|
|
|
|
*/
|
|
|
|
abstract class A {
|
|
|
|
public $publicObjProp;
|
|
|
|
protected $protectedObjProp;
|
|
|
|
private $privateObjProp;
|
|
|
|
|
|
|
|
public static $publicStaticProp;
|
|
|
|
protected static $protectedStaticProp;
|
|
|
|
private static $privateStaticProp;
|
|
|
|
|
|
|
|
abstract public function abstractPublicMethod();
|
|
|
|
abstract protected function abstractProtectedMethod();
|
|
|
|
|
|
|
|
public function publicObjMethod() {}
|
|
|
|
protected function protectedObjMethod() {}
|
|
|
|
private function privateObjMethod() {}
|
|
|
|
|
|
|
|
public static function publicStaticMethod() {}
|
|
|
|
protected static function protectedStaticMethod() {}
|
|
|
|
private static function privateStaticMethod() {}
|
|
|
|
}
|
|
|
|
EOF;
|
|
|
|
|
|
|
|
$actual_labels = $this->getCompletionLabels($content, 'B\A', $gap);
|
|
|
|
|
|
|
|
$expected_labels = [
|
|
|
|
'->' => [
|
|
|
|
'magicObjProp1',
|
|
|
|
'magicObjProp2',
|
|
|
|
|
|
|
|
'magicObjMethod',
|
|
|
|
|
2023-11-17 12:41:53 +01:00
|
|
|
'publicObjProp',
|
|
|
|
'protectedObjProp',
|
|
|
|
'privateObjProp',
|
2023-11-17 12:29:42 +01:00
|
|
|
|
|
|
|
'abstractPublicMethod',
|
|
|
|
'abstractProtectedMethod',
|
|
|
|
|
|
|
|
'publicObjMethod',
|
|
|
|
'protectedObjMethod',
|
|
|
|
'privateObjMethod',
|
|
|
|
|
|
|
|
'publicStaticMethod',
|
|
|
|
'protectedStaticMethod',
|
|
|
|
'privateStaticMethod',
|
|
|
|
],
|
|
|
|
'::' => [
|
|
|
|
'magicStaticMethod',
|
|
|
|
|
2023-11-17 12:41:53 +01:00
|
|
|
'publicStaticProp',
|
|
|
|
'protectedStaticProp',
|
|
|
|
'privateStaticProp',
|
2023-11-17 12:29:42 +01:00
|
|
|
|
|
|
|
'publicStaticMethod',
|
|
|
|
'protectedStaticMethod',
|
|
|
|
'privateStaticMethod',
|
|
|
|
],
|
|
|
|
];
|
|
|
|
|
|
|
|
$this->assertEqualsCanonicalizing($expected_labels[$gap], $actual_labels);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @dataProvider providerGaps
|
|
|
|
*/
|
|
|
|
public function testUseTrait(string $gap): void
|
|
|
|
{
|
|
|
|
$content = <<<'EOF'
|
|
|
|
<?php
|
|
|
|
namespace B;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @property int $magicObjProp1
|
|
|
|
* @property-read string $magicObjProp2
|
|
|
|
* @method int magicObjMethod()
|
|
|
|
* @method static string magicStaticMethod()
|
|
|
|
*/
|
|
|
|
trait C {
|
|
|
|
public $publicObjProp;
|
|
|
|
protected $protectedObjProp;
|
|
|
|
private $privateObjProp;
|
|
|
|
|
|
|
|
public static $publicStaticProp;
|
|
|
|
protected static $protectedStaticProp;
|
|
|
|
private static $privateStaticProp;
|
|
|
|
|
|
|
|
abstract public function abstractPublicMethod();
|
|
|
|
abstract protected function abstractProtectedMethod();
|
|
|
|
|
|
|
|
public function publicObjMethod() {}
|
|
|
|
protected function protectedObjMethod() {}
|
|
|
|
private function privateObjMethod() {}
|
|
|
|
|
|
|
|
public static function publicStaticMethod() {}
|
|
|
|
protected static function protectedStaticMethod() {}
|
|
|
|
private static function privateStaticMethod() {}
|
|
|
|
}
|
|
|
|
|
|
|
|
class A {
|
|
|
|
use C;
|
|
|
|
}
|
|
|
|
EOF;
|
|
|
|
|
|
|
|
$actual_labels = $this->getCompletionLabels($content, 'B\A', $gap);
|
|
|
|
|
|
|
|
$expected_labels = [
|
|
|
|
'->' => [
|
|
|
|
'magicObjProp1',
|
|
|
|
'magicObjProp2',
|
|
|
|
|
|
|
|
'magicObjMethod',
|
|
|
|
|
2023-11-17 12:41:53 +01:00
|
|
|
'publicObjProp',
|
|
|
|
'protectedObjProp',
|
|
|
|
'privateObjProp',
|
2023-11-14 13:26:47 +01:00
|
|
|
|
|
|
|
'abstractPublicMethod',
|
|
|
|
'abstractProtectedMethod',
|
|
|
|
|
|
|
|
'publicObjMethod',
|
|
|
|
'protectedObjMethod',
|
|
|
|
'privateObjMethod',
|
|
|
|
|
|
|
|
'publicStaticMethod',
|
|
|
|
'protectedStaticMethod',
|
|
|
|
'privateStaticMethod',
|
2023-11-17 12:29:42 +01:00
|
|
|
],
|
|
|
|
'::' => [
|
2023-11-14 13:35:32 +01:00
|
|
|
'magicStaticMethod',
|
2023-11-17 12:41:53 +01:00
|
|
|
'publicStaticProp',
|
|
|
|
'protectedStaticProp',
|
|
|
|
'privateStaticProp',
|
2023-11-14 13:26:47 +01:00
|
|
|
|
|
|
|
'publicStaticMethod',
|
|
|
|
'protectedStaticMethod',
|
|
|
|
'privateStaticMethod',
|
2023-11-17 12:29:42 +01:00
|
|
|
],
|
|
|
|
];
|
|
|
|
|
|
|
|
$this->assertEqualsCanonicalizing($expected_labels[$gap], $actual_labels);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @dataProvider providerGaps
|
|
|
|
*/
|
|
|
|
public function testUseTraitWithAbstractClass(string $gap): void
|
|
|
|
{
|
|
|
|
$content = <<<'EOF'
|
|
|
|
<?php
|
|
|
|
namespace B;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @property int $magicObjProp1
|
|
|
|
* @property-read string $magicObjProp2
|
|
|
|
* @method int magicObjMethod()
|
|
|
|
* @method static string magicStaticMethod()
|
|
|
|
*/
|
|
|
|
trait C {
|
|
|
|
public $publicObjProp;
|
|
|
|
protected $protectedObjProp;
|
|
|
|
private $privateObjProp;
|
|
|
|
|
|
|
|
public static $publicStaticProp;
|
|
|
|
protected static $protectedStaticProp;
|
|
|
|
private static $privateStaticProp;
|
|
|
|
|
|
|
|
abstract public function abstractPublicMethod();
|
|
|
|
abstract protected function abstractProtectedMethod();
|
|
|
|
|
|
|
|
public function publicObjMethod() {}
|
|
|
|
protected function protectedObjMethod() {}
|
|
|
|
private function privateObjMethod() {}
|
|
|
|
|
|
|
|
public static function publicStaticMethod() {}
|
|
|
|
protected static function protectedStaticMethod() {}
|
|
|
|
private static function privateStaticMethod() {}
|
|
|
|
}
|
|
|
|
|
|
|
|
abstract class A {
|
|
|
|
use C;
|
|
|
|
}
|
|
|
|
EOF;
|
|
|
|
|
|
|
|
$actual_labels = $this->getCompletionLabels($content, 'B\A', $gap);
|
|
|
|
|
|
|
|
$expected_labels = [
|
|
|
|
'->' => [
|
|
|
|
'magicObjProp1',
|
|
|
|
'magicObjProp2',
|
|
|
|
|
|
|
|
'magicObjMethod',
|
|
|
|
|
2023-11-17 12:41:53 +01:00
|
|
|
'publicObjProp',
|
|
|
|
'protectedObjProp',
|
|
|
|
'privateObjProp',
|
2023-11-14 13:26:47 +01:00
|
|
|
|
|
|
|
'abstractPublicMethod',
|
|
|
|
'abstractProtectedMethod',
|
|
|
|
|
|
|
|
'publicObjMethod',
|
|
|
|
'protectedObjMethod',
|
|
|
|
'privateObjMethod',
|
|
|
|
|
|
|
|
'publicStaticMethod',
|
|
|
|
'protectedStaticMethod',
|
|
|
|
'privateStaticMethod',
|
2023-11-17 12:29:42 +01:00
|
|
|
],
|
|
|
|
'::' => [
|
2023-11-14 13:35:32 +01:00
|
|
|
'magicStaticMethod',
|
2023-11-17 12:41:53 +01:00
|
|
|
'publicStaticProp',
|
|
|
|
'protectedStaticProp',
|
|
|
|
'privateStaticProp',
|
2023-11-14 13:26:47 +01:00
|
|
|
|
|
|
|
'publicStaticMethod',
|
|
|
|
'protectedStaticMethod',
|
|
|
|
'privateStaticMethod',
|
2023-11-17 12:29:42 +01:00
|
|
|
],
|
|
|
|
];
|
|
|
|
|
|
|
|
$this->assertEqualsCanonicalizing($expected_labels[$gap], $actual_labels);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @dataProvider providerGaps
|
|
|
|
*/
|
|
|
|
public function testClassWithExtends(string $gap): void
|
|
|
|
{
|
|
|
|
$content = <<<'EOF'
|
|
|
|
<?php
|
|
|
|
namespace B;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @property int $magicObjProp1
|
|
|
|
* @property-read string $magicObjProp2
|
|
|
|
* @method int magicObjMethod()
|
|
|
|
* @method static string magicStaticMethod()
|
|
|
|
*/
|
|
|
|
class C {
|
|
|
|
public $publicObjProp;
|
|
|
|
protected $protectedObjProp;
|
|
|
|
private $privateObjProp;
|
|
|
|
|
|
|
|
public static $publicStaticProp;
|
|
|
|
protected static $protectedStaticProp;
|
|
|
|
private static $privateStaticProp;
|
|
|
|
|
|
|
|
public function publicObjMethod() {}
|
|
|
|
protected function protectedObjMethod() {}
|
|
|
|
private function privateObjMethod() {}
|
|
|
|
|
|
|
|
public static function publicStaticMethod() {}
|
|
|
|
protected static function protectedStaticMethod() {}
|
|
|
|
private static function privateStaticMethod() {}
|
|
|
|
}
|
|
|
|
|
|
|
|
class A extends C {
|
|
|
|
|
|
|
|
}
|
|
|
|
EOF;
|
|
|
|
|
|
|
|
$actual_labels = $this->getCompletionLabels($content, 'B\A', $gap);
|
|
|
|
|
|
|
|
$expected_labels = [
|
|
|
|
'->' => [
|
|
|
|
'magicObjProp1',
|
|
|
|
'magicObjProp2',
|
|
|
|
|
|
|
|
'magicObjMethod',
|
|
|
|
|
2023-11-17 12:41:53 +01:00
|
|
|
'publicObjProp',
|
|
|
|
'protectedObjProp',
|
2023-11-14 13:26:47 +01:00
|
|
|
|
|
|
|
'publicObjMethod',
|
|
|
|
'protectedObjMethod',
|
|
|
|
|
|
|
|
'publicStaticMethod',
|
|
|
|
'protectedStaticMethod',
|
2023-11-17 12:29:42 +01:00
|
|
|
],
|
|
|
|
'::' => [
|
2023-11-14 12:32:20 +01:00
|
|
|
'magicStaticMethod',
|
2023-11-17 12:41:53 +01:00
|
|
|
'publicStaticProp',
|
|
|
|
'protectedStaticProp',
|
2023-11-14 13:26:47 +01:00
|
|
|
|
|
|
|
'publicStaticMethod',
|
|
|
|
'protectedStaticMethod',
|
2023-11-17 12:29:42 +01:00
|
|
|
],
|
|
|
|
];
|
|
|
|
|
|
|
|
$this->assertEqualsCanonicalizing($expected_labels[$gap], $actual_labels);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @dataProvider providerGaps
|
|
|
|
*/
|
|
|
|
public function testAstractClassWithInterface(string $gap): void
|
|
|
|
{
|
|
|
|
$content = <<<'EOF'
|
|
|
|
<?php
|
|
|
|
namespace B;
|
|
|
|
|
|
|
|
interface C {
|
|
|
|
public function publicObjMethod();
|
|
|
|
protected function protectedObjMethod();
|
|
|
|
}
|
|
|
|
|
|
|
|
abstract class A implements C {
|
|
|
|
abstract public function publicObjMethod();
|
|
|
|
abstract protected function protectedObjMethod();
|
|
|
|
}
|
|
|
|
EOF;
|
|
|
|
|
|
|
|
$actual_labels = $this->getCompletionLabels($content, 'B\A', $gap);
|
|
|
|
|
|
|
|
$expected_labels = [
|
|
|
|
'->' => [
|
|
|
|
'publicObjMethod',
|
|
|
|
'protectedObjMethod',
|
|
|
|
],
|
|
|
|
'::' => [],
|
|
|
|
];
|
|
|
|
|
|
|
|
$this->assertEqualsCanonicalizing($expected_labels[$gap], $actual_labels);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @dataProvider providerGaps
|
|
|
|
*/
|
|
|
|
public function testClassWithAnnotationMixin(string $gap): void
|
|
|
|
{
|
|
|
|
$content = <<<'EOF'
|
|
|
|
<?php
|
|
|
|
namespace B;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @property int $magicObjProp1
|
|
|
|
* @property-read string $magicObjProp2
|
|
|
|
* @method int magicObjMethod()
|
|
|
|
* @method static string magicStaticMethod()
|
|
|
|
*/
|
|
|
|
class C {
|
|
|
|
public $publicObjProp;
|
|
|
|
protected $protectedObjProp;
|
|
|
|
private $privateObjProp;
|
|
|
|
|
|
|
|
public static $publicStaticProp;
|
|
|
|
protected static $protectedStaticProp;
|
|
|
|
private static $privateStaticProp;
|
|
|
|
|
|
|
|
public function publicObjMethod() {}
|
|
|
|
protected function protectedObjMethod() {}
|
|
|
|
private function privateObjMethod() {}
|
|
|
|
|
|
|
|
public static function publicStaticMethod() {}
|
|
|
|
protected static function protectedStaticMethod() {}
|
|
|
|
private static function privateStaticMethod() {}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @mixin C
|
|
|
|
*/
|
|
|
|
class A {
|
|
|
|
|
|
|
|
}
|
|
|
|
EOF;
|
|
|
|
|
|
|
|
$actual_labels = $this->getCompletionLabels($content, 'B\A', $gap);
|
|
|
|
|
|
|
|
$expected_labels = [
|
|
|
|
'->' => [],
|
|
|
|
'::' => [],
|
|
|
|
];
|
|
|
|
|
|
|
|
$this->assertEqualsCanonicalizing($expected_labels[$gap], $actual_labels);
|
|
|
|
}
|
|
|
|
}
|