1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-21 21:31:13 +01:00

Fix #1654 - understand templated completions

This commit is contained in:
Brown 2019-05-17 12:11:21 -04:00
parent 92ef851b9c
commit a1c9ad501b
3 changed files with 138 additions and 55 deletions

View File

@ -1118,6 +1118,87 @@ class Codebase
return [$recent_type, $gap];
}
/**
* @return array<int, \LanguageServerProtocol\CompletionItem>
*/
public function getCompletionItemsForClassishThing(string $type_string, string $gap) : array
{
$instance_completion_items = [];
$static_completion_items = [];
$type = Type::parseString($type_string);
$completion_items = [];
foreach ($type->getTypes() as $atomic_type) {
if ($atomic_type instanceof Type\Atomic\TNamedObject) {
try {
$class_storage = $this->classlike_storage_provider->get($atomic_type->value);
foreach ($class_storage->appearing_method_ids as $declaring_method_id) {
$method_storage = $this->methods->getStorage($declaring_method_id);
$instance_completion_items[] = new \LanguageServerProtocol\CompletionItem(
(string)$method_storage,
\LanguageServerProtocol\CompletionItemKind::METHOD,
null,
null,
null,
null,
$method_storage->cased_name . '()'
);
}
foreach ($class_storage->declaring_property_ids as $property_name => $declaring_class) {
$property_storage = $this->properties->getStorage(
$declaring_class . '::$' . $property_name
);
$instance_completion_items[] = new \LanguageServerProtocol\CompletionItem(
$property_storage->getInfo() . ' $' . $property_name,
\LanguageServerProtocol\CompletionItemKind::PROPERTY,
null,
null,
null,
null,
($gap === '::' ? '$' : '') . $property_name
);
}
foreach ($class_storage->class_constant_locations as $const_name => $_) {
$static_completion_items[] = new \LanguageServerProtocol\CompletionItem(
'const ' . $const_name,
\LanguageServerProtocol\CompletionItemKind::VARIABLE,
null,
null,
null,
null,
$const_name
);
}
} catch (\Exception $e) {
error_log($e->getMessage());
continue;
}
}
if ($gap === '->') {
$completion_items = array_merge(
$completion_items,
$instance_completion_items
);
} else {
$completion_items = array_merge(
$completion_items,
$instance_completion_items,
$static_completion_items
);
}
}
return $completion_items;
}
private static function getPositionFromOffset(int $offset, string $file_contents) : Position
{
$file_contents = substr($file_contents, 0, $offset);

View File

@ -264,61 +264,7 @@ class TextDocument
$completion_items = [];
if ($gap === '->' || $gap === '::') {
$instance_completion_items = [];
$static_completion_items = [];
try {
$class_storage = $this->codebase->classlike_storage_provider->get($recent_type);
foreach ($class_storage->appearing_method_ids as $declaring_method_id) {
$method_storage = $this->codebase->methods->getStorage($declaring_method_id);
$instance_completion_items[] = new CompletionItem(
(string)$method_storage,
CompletionItemKind::METHOD,
null,
null,
null,
null,
$method_storage->cased_name . '()'
);
}
foreach ($class_storage->declaring_property_ids as $property_name => $declaring_class) {
$property_storage = $this->codebase->properties->getStorage(
$declaring_class . '::$' . $property_name
);
$instance_completion_items[] = new CompletionItem(
$property_storage->getInfo() . ' $' . $property_name,
CompletionItemKind::PROPERTY,
null,
null,
null,
null,
($gap === '::' ? '$' : '') . $property_name
);
}
foreach ($class_storage->class_constant_locations as $const_name => $_) {
$static_completion_items[] = new CompletionItem(
'const ' . $const_name,
CompletionItemKind::VARIABLE,
null,
null,
null,
null,
$const_name
);
}
} catch (\Exception $e) {
error_log($e->getMessage());
return new Success([]);
}
$completion_items = $gap === '->'
? $instance_completion_items
: array_merge($instance_completion_items, $static_completion_items);
$completion_items = $this->codebase->getCompletionItemsForClassishThing($recent_type, $gap);
error_log('Found ' . count($completion_items) . ' items');
}

View File

@ -201,4 +201,60 @@ class CompletionTest extends \Psalm\Tests\TestCase
$this->assertSame(['B\C', '->'], $codebase->getCompletionDataAtPosition('somefile.php', new Position(16, 39)));
}
/**
* @return void
*/
public function testCompletionOnTemplatedThisProperty()
{
$codebase = $this->project_analyzer->getCodebase();
$config = $codebase->config;
$config->throw_exception = false;
$this->addFile(
'somefile.php',
'<?php
namespace B;
/** @template T */
class C {
/** @var T */
private $t;
/** @param T $t */
public function __construct($t) {
$this->t = $t;
}
public function otherFunction() : void
}
class A {
/** @var C<string> */
protected $cee_me;
public function __construct() {
$this->cee_me = new C("hello");
}
public function foo() : void {
$this->cee_me->
}
}'
);
$codebase = $this->project_analyzer->getCodebase();
$codebase->file_provider->openFile('somefile.php');
$codebase->scanFiles();
$this->analyzeFile('somefile.php', new Context());
$completion_data = $codebase->getCompletionDataAtPosition('somefile.php', new Position(25, 39));
$this->assertSame(['B\C<string>', '->'], $completion_data);
$completion_items = $codebase->getCompletionItemsForClassishThing($completion_data[0], $completion_data[1]);
$this->assertCount(3, $completion_items);
}
}