From a1c9ad501b4193fcc2f18c28e5733e98a72069b8 Mon Sep 17 00:00:00 2001 From: Brown Date: Fri, 17 May 2019 12:11:21 -0400 Subject: [PATCH] Fix #1654 - understand templated completions --- src/Psalm/Codebase.php | 81 +++++++++++++++++++ .../LanguageServer/Server/TextDocument.php | 56 +------------ tests/LanguageServer/CompletionTest.php | 56 +++++++++++++ 3 files changed, 138 insertions(+), 55 deletions(-) diff --git a/src/Psalm/Codebase.php b/src/Psalm/Codebase.php index af311b3e5..996dee3a3 100644 --- a/src/Psalm/Codebase.php +++ b/src/Psalm/Codebase.php @@ -1118,6 +1118,87 @@ class Codebase return [$recent_type, $gap]; } + /** + * @return array + */ + 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); diff --git a/src/Psalm/Internal/LanguageServer/Server/TextDocument.php b/src/Psalm/Internal/LanguageServer/Server/TextDocument.php index 7fd4accd3..9e8c68434 100644 --- a/src/Psalm/Internal/LanguageServer/Server/TextDocument.php +++ b/src/Psalm/Internal/LanguageServer/Server/TextDocument.php @@ -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'); } diff --git a/tests/LanguageServer/CompletionTest.php b/tests/LanguageServer/CompletionTest.php index 7163946a6..79d54418f 100644 --- a/tests/LanguageServer/CompletionTest.php +++ b/tests/LanguageServer/CompletionTest.php @@ -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', + 't = $t; + } + + public function otherFunction() : void + } + + class A { + /** @var C */ + 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', '->'], $completion_data); + + $completion_items = $codebase->getCompletionItemsForClassishThing($completion_data[0], $completion_data[1]); + + $this->assertCount(3, $completion_items); + } }