Skip to content

Commit

Permalink
Fix #1654 - understand templated completions
Browse files Browse the repository at this point in the history
  • Loading branch information
muglug committed May 17, 2019
1 parent 92ef851 commit a1c9ad5
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 55 deletions.
81 changes: 81 additions & 0 deletions src/Psalm/Codebase.php
Expand Up @@ -1118,6 +1118,87 @@ public function getCompletionDataAtPosition(string $file_path, Position $positio
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);
Expand Down
56 changes: 1 addition & 55 deletions src/Psalm/Internal/LanguageServer/Server/TextDocument.php
Expand Up @@ -264,61 +264,7 @@ public function completion(TextDocumentIdentifier $textDocument, Position $posit
$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');
}
Expand Down
56 changes: 56 additions & 0 deletions tests/LanguageServer/CompletionTest.php
Expand Up @@ -201,4 +201,60 @@ public function foo() : void {

$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);
}
}

0 comments on commit a1c9ad5

Please sign in to comment.