mirror of
https://github.com/danog/psalm.git
synced 2025-01-21 21:31:13 +01:00
Fix #1656 - allow chained call completion
This commit is contained in:
parent
a1c9ad501b
commit
963d5bb901
@ -1091,15 +1091,22 @@ class Codebase
|
||||
|
||||
krsort($type_map);
|
||||
|
||||
$gap = null;
|
||||
|
||||
foreach ($type_map as $start_pos => list($end_pos, $possible_type)) {
|
||||
if ($offset < $start_pos) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($offset - $end_pos === 2 || $offset - $end_pos === 3) {
|
||||
$recent_type = $possible_type;
|
||||
$candidate_gap = substr($file_contents, $end_pos, 2);
|
||||
|
||||
break;
|
||||
if ($candidate_gap === '->' || $candidate_gap === '::') {
|
||||
$gap = $candidate_gap;
|
||||
$recent_type = $possible_type;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($offset - $end_pos > 3) {
|
||||
@ -1109,12 +1116,11 @@ class Codebase
|
||||
|
||||
if (!$recent_type
|
||||
|| $recent_type === 'mixed'
|
||||
|| !$gap
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$gap = substr($file_contents, $end_pos, 2);
|
||||
|
||||
return [$recent_type, $gap];
|
||||
}
|
||||
|
||||
|
@ -325,7 +325,8 @@ class MethodCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\
|
||||
$codebase->analyzer->addNodeType(
|
||||
$statements_analyzer->getFilePath(),
|
||||
$stmt->name,
|
||||
(string) $stmt->inferredType
|
||||
(string) $stmt->inferredType,
|
||||
$stmt
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -890,7 +890,8 @@ class StaticCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\
|
||||
$codebase->analyzer->addNodeType(
|
||||
$statements_analyzer->getFilePath(),
|
||||
$stmt->name,
|
||||
(string) $stmt->inferredType
|
||||
(string) $stmt->inferredType,
|
||||
$stmt
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -902,10 +902,14 @@ class Analyzer
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function addNodeType(string $file_path, PhpParser\Node $node, string $node_type)
|
||||
{
|
||||
public function addNodeType(
|
||||
string $file_path,
|
||||
PhpParser\Node $node,
|
||||
string $node_type,
|
||||
PhpParser\Node $parent_node = null
|
||||
) {
|
||||
$this->type_map[$file_path][(int)$node->getAttribute('startFilePos')] = [
|
||||
(int)$node->getAttribute('endFilePos') + 1,
|
||||
($parent_node ? (int)$parent_node->getAttribute('endFilePos') : (int)$node->getAttribute('endFilePos')) + 1,
|
||||
$node_type
|
||||
];
|
||||
}
|
||||
|
@ -202,6 +202,88 @@ class CompletionTest extends \Psalm\Tests\TestCase
|
||||
$this->assertSame(['B\C', '->'], $codebase->getCompletionDataAtPosition('somefile.php', new Position(16, 39)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testCompletionOnThisPropertyWithCharacter()
|
||||
{
|
||||
$codebase = $this->project_analyzer->getCodebase();
|
||||
$config = $codebase->config;
|
||||
$config->throw_exception = false;
|
||||
|
||||
$this->addFile(
|
||||
'somefile.php',
|
||||
'<?php
|
||||
namespace B;
|
||||
|
||||
class C {
|
||||
public function otherFunction() : void
|
||||
}
|
||||
|
||||
class A {
|
||||
/** @var C */
|
||||
protected $cee_me;
|
||||
|
||||
public function __construct() {
|
||||
$this->cee_me = new C();
|
||||
}
|
||||
|
||||
public function foo() : void {
|
||||
$this->cee_me->o
|
||||
}
|
||||
}'
|
||||
);
|
||||
|
||||
$codebase = $this->project_analyzer->getCodebase();
|
||||
|
||||
$codebase->file_provider->openFile('somefile.php');
|
||||
$codebase->scanFiles();
|
||||
$this->analyzeFile('somefile.php', new Context());
|
||||
|
||||
$this->assertSame(['B\C', '->'], $codebase->getCompletionDataAtPosition('somefile.php', new Position(16, 40)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testCompletionOnThisPropertyWithAnotherCharacter()
|
||||
{
|
||||
$codebase = $this->project_analyzer->getCodebase();
|
||||
$config = $codebase->config;
|
||||
$config->throw_exception = false;
|
||||
|
||||
$this->addFile(
|
||||
'somefile.php',
|
||||
'<?php
|
||||
namespace B;
|
||||
|
||||
class C {
|
||||
public function otherFunction() : void
|
||||
}
|
||||
|
||||
class A {
|
||||
/** @var C */
|
||||
protected $cee_me;
|
||||
|
||||
public function __construct() {
|
||||
$this->cee_me = new C();
|
||||
}
|
||||
|
||||
public function foo() : void {
|
||||
$this->cee_me->ot
|
||||
}
|
||||
}'
|
||||
);
|
||||
|
||||
$codebase = $this->project_analyzer->getCodebase();
|
||||
|
||||
$codebase->file_provider->openFile('somefile.php');
|
||||
$codebase->scanFiles();
|
||||
$this->analyzeFile('somefile.php', new Context());
|
||||
|
||||
$this->assertSame(null, $codebase->getCompletionDataAtPosition('somefile.php', new Position(16, 41)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
@ -257,4 +339,104 @@ class CompletionTest extends \Psalm\Tests\TestCase
|
||||
|
||||
$this->assertCount(3, $completion_items);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testCompletionOnMethodReturnValue()
|
||||
{
|
||||
$codebase = $this->project_analyzer->getCodebase();
|
||||
$config = $codebase->config;
|
||||
$config->throw_exception = false;
|
||||
|
||||
$this->addFile(
|
||||
'somefile.php',
|
||||
'<?php
|
||||
namespace B;
|
||||
class A {
|
||||
public function foo() : self {
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
function (A $a) {
|
||||
$a->foo()->
|
||||
}
|
||||
'
|
||||
);
|
||||
|
||||
$codebase->file_provider->openFile('somefile.php');
|
||||
$codebase->scanFiles();
|
||||
|
||||
$this->analyzeFile('somefile.php', new Context());
|
||||
$this->assertSame(['B\A', '->'], $codebase->getCompletionDataAtPosition('somefile.php', new Position(9, 31)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testCompletionOnMethodArgument()
|
||||
{
|
||||
$codebase = $this->project_analyzer->getCodebase();
|
||||
$config = $codebase->config;
|
||||
$config->throw_exception = false;
|
||||
|
||||
$this->addFile(
|
||||
'somefile.php',
|
||||
'<?php
|
||||
namespace B;
|
||||
class A {
|
||||
public function foo(A $a) : self {
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
class C {}
|
||||
|
||||
function (A $a, C $c) {
|
||||
$a->foo($c->)
|
||||
}
|
||||
'
|
||||
);
|
||||
|
||||
$codebase->file_provider->openFile('somefile.php');
|
||||
$codebase->scanFiles();
|
||||
|
||||
$this->analyzeFile('somefile.php', new Context());
|
||||
$this->assertSame(['B\C', '->'], $codebase->getCompletionDataAtPosition('somefile.php', new Position(11, 32)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testCompletionOnMethodReturnValueWithArgument()
|
||||
{
|
||||
$codebase = $this->project_analyzer->getCodebase();
|
||||
$config = $codebase->config;
|
||||
$config->throw_exception = false;
|
||||
|
||||
$this->addFile(
|
||||
'somefile.php',
|
||||
'<?php
|
||||
namespace B;
|
||||
class A {
|
||||
public function foo(A $a) : self {
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
class C {}
|
||||
|
||||
function (A $a, C $c) {
|
||||
$a->foo($c)->
|
||||
}
|
||||
'
|
||||
);
|
||||
|
||||
$codebase->file_provider->openFile('somefile.php');
|
||||
$codebase->scanFiles();
|
||||
|
||||
$this->analyzeFile('somefile.php', new Context());
|
||||
$this->assertSame(['B\A', '->'], $codebase->getCompletionDataAtPosition('somefile.php', new Position(11, 33)));
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user