diff --git a/src/Psalm/CodeLocation/DocblockTypeLocation.php b/src/Psalm/CodeLocation/DocblockTypeLocation.php index ca4079eed..9cb2c4809 100644 --- a/src/Psalm/CodeLocation/DocblockTypeLocation.php +++ b/src/Psalm/CodeLocation/DocblockTypeLocation.php @@ -5,6 +5,15 @@ use PhpParser; class DocblockTypeLocation extends \Psalm\CodeLocation { + /** @var int */ + public $raw_file_start; + + /** @var int */ + public $raw_file_end; + + /** @var int */ + public $raw_line_number; + public function __construct( \Psalm\FileSource $file_source, int $file_start, @@ -14,6 +23,11 @@ class DocblockTypeLocation extends \Psalm\CodeLocation $this->file_start = $file_start; // matches how CodeLocation works $this->file_end = $file_end - 1; + + $this->raw_file_start = $file_start; + $this->raw_file_end = $file_end; + $this->raw_line_number = $line_number; + $this->file_path = $file_source->getFilePath(); $this->file_name = $file_source->getFileName(); $this->single_line = false; diff --git a/src/Psalm/Internal/Codebase/Analyzer.php b/src/Psalm/Internal/Codebase/Analyzer.php index 2bdb4c3c2..9f23798b9 100644 --- a/src/Psalm/Internal/Codebase/Analyzer.php +++ b/src/Psalm/Internal/Codebase/Analyzer.php @@ -1028,6 +1028,20 @@ class Analyzer ]; } + /** + * @return void + */ + public function addDocblockType( + string $file_path, + \Psalm\CodeLocation\DocblockTypeLocation $code_location, + string $docblock_type + ) { + $this->type_map[$file_path][$code_location->raw_file_start] = [ + $code_location->raw_file_end, + $docblock_type + ]; + } + /** * @param array $aliases * @return void diff --git a/src/Psalm/Internal/Type/ParseTree.php b/src/Psalm/Internal/Type/ParseTree.php index aaf0e4a02..d395b9359 100644 --- a/src/Psalm/Internal/Type/ParseTree.php +++ b/src/Psalm/Internal/Type/ParseTree.php @@ -502,6 +502,7 @@ class ParseTree $new_leaf = new ParseTree\Value( $type_token[0] . '::' . $nexter_token[0], $type_token[1], + $type_token[1] + 2 + strlen($nexter_token[0]), $new_parent ); @@ -517,6 +518,7 @@ class ParseTree $new_leaf = new ParseTree\Value( $type_token[0], $type_token[1], + $type_token[1] + strlen($type_token[0]), $new_parent ); break; diff --git a/src/Psalm/Internal/Type/ParseTree/Value.php b/src/Psalm/Internal/Type/ParseTree/Value.php index 04a581e7b..4c684e685 100644 --- a/src/Psalm/Internal/Type/ParseTree/Value.php +++ b/src/Psalm/Internal/Type/ParseTree/Value.php @@ -14,15 +14,25 @@ class Value extends \Psalm\Internal\Type\ParseTree /** * @var int */ - public $offset; + public $offset_start; + + /** + * @var int + */ + public $offset_end; /** * @param string $value * @param \Psalm\Internal\Type\ParseTree|null $parent */ - public function __construct(string $value, int $offset, \Psalm\Internal\Type\ParseTree $parent = null) - { - $this->offset = $offset; + public function __construct( + string $value, + int $offset_start, + int $offset_end, + \Psalm\Internal\Type\ParseTree $parent = null + ) { + $this->offset_start = $offset_start; + $this->offset_end = $offset_end; $this->value = $value; $this->parent = $parent; } diff --git a/src/Psalm/Type.php b/src/Psalm/Type.php index 315ed9e01..1c4a6994e 100644 --- a/src/Psalm/Type.php +++ b/src/Psalm/Type.php @@ -128,7 +128,8 @@ abstract class Type $only_token[0] = self::fixScalarTerms($only_token[0], $php_version); $atomic = Atomic::create($only_token[0], $php_version, $template_type_map); - $atomic->offset = 0; + $atomic->offset_start = 0; + $atomic->offset_end = strlen($only_token[0]); return new Union([$atomic]); } @@ -656,7 +657,8 @@ abstract class Type $atomic_type = Atomic::create($atomic_type_string, $php_version, $template_type_map); - $atomic_type->offset = $parse_tree->offset; + $atomic_type->offset_start = $parse_tree->offset_start; + $atomic_type->offset_end = $parse_tree->offset_end; return $atomic_type; } diff --git a/src/Psalm/Type/Atomic.php b/src/Psalm/Type/Atomic.php index 36b5a42a2..fec931219 100644 --- a/src/Psalm/Type/Atomic.php +++ b/src/Psalm/Type/Atomic.php @@ -70,7 +70,12 @@ abstract class Atomic /** * @var ?int */ - public $offset; + public $offset_start; + + /** + * @var ?int + */ + public $offset_end; /** * @param string $value @@ -343,6 +348,25 @@ abstract class Atomic } if ($this instanceof TNamedObject) { + $codebase = $source->getCodebase(); + + if ($code_location instanceof CodeLocation\DocblockTypeLocation + && $codebase->store_node_types + && $this->offset_start !== null + && $this->offset_end !== null + ) { + $codebase->analyzer->addDocblockType( + $source->getFilePath(), + new CodeLocation\DocblockTypeLocation( + $source, + $code_location->raw_file_start + $this->offset_start, + $code_location->raw_file_start + $this->offset_end, + $code_location->raw_line_number + ), + $this->value + ); + } + if (!isset($phantom_classes[strtolower($this->value)]) && ClassLikeAnalyzer::checkFullyQualifiedClassLikeName( $source, @@ -360,10 +384,29 @@ abstract class Atomic if ($this->extra_types) { foreach ($this->extra_types as $extra_type) { - if ($extra_type instanceof TTemplateParam) { + if ($extra_type instanceof TTemplateParam + || $extra_type instanceof Type\Atomic\TObjectWithProperties + ) { continue; } + if ($code_location instanceof CodeLocation\DocblockTypeLocation + && $codebase->store_node_types + && $extra_type->offset_start !== null + && $extra_type->offset_end !== null + ) { + $codebase->analyzer->addDocblockType( + $source->getFilePath(), + new CodeLocation\DocblockTypeLocation( + $source, + $code_location->raw_file_start + $extra_type->offset_start, + $code_location->raw_file_start + $extra_type->offset_end, + $code_location->raw_line_number + ), + $extra_type->value + ); + } + if (!isset($phantom_classes[strtolower($extra_type->value)]) && ClassLikeAnalyzer::checkFullyQualifiedClassLikeName( $source, diff --git a/tests/LanguageServer/SymbolLookupTest.php b/tests/LanguageServer/SymbolLookupTest.php index acf69cbaa..54dd82043 100644 --- a/tests/LanguageServer/SymbolLookupTest.php +++ b/tests/LanguageServer/SymbolLookupTest.php @@ -244,9 +244,6 @@ class SymbolLookupTest extends \Psalm\Tests\TestCase namespace B; class A { - /** @var int|null */ - protected $a; - public function foo(int $i) : string { return "hello"; } @@ -261,10 +258,41 @@ class SymbolLookupTest extends \Psalm\Tests\TestCase $codebase->scanFiles(); $this->analyzeFile('somefile.php', new Context()); - $symbol_at_position = $codebase->getReferenceAtPosition('somefile.php', new Position(12, 33)); + $symbol_at_position = $codebase->getReferenceAtPosition('somefile.php', new Position(9, 33)); $this->assertNotNull($symbol_at_position); $this->assertSame('B\A::foo()', $symbol_at_position[0]); } + + /** + * @return void + */ + public function testGetTypeInDocblock() + { + $codebase = $this->project_analyzer->getCodebase(); + $config = $codebase->config; + $config->throw_exception = false; + + $this->addFile( + 'somefile.php', + 'file_provider->openFile('somefile.php'); + $codebase->scanFiles(); + $this->analyzeFile('somefile.php', new Context()); + + $symbol_at_position = $codebase->getReferenceAtPosition('somefile.php', new Position(4, 35)); + + $this->assertNotNull($symbol_at_position); + + $this->assertSame('type: Exception', $symbol_at_position[0]); + } }