1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-22 05:41:20 +01:00

Apply the partial diff hack earlier

This commit is contained in:
Matt Brown 2021-02-15 15:32:35 -05:00
parent 09281aba25
commit 77feecb370
2 changed files with 88 additions and 39 deletions

View File

@ -158,13 +158,50 @@ class PartialParserVisitor extends PhpParser\NodeVisitorAbstract
$fake_class = '<?php class _ {' . $method_contents . '}';
$extra_characters = [];
// To avoid a parser error during completion we replace
//
// Foo::
// if (...) {}
//
// with
//
// Foo::;
// if (...) {}
//
// When we insert the extra semicolon we have to keep track of the places
// we inserted it, and then shift the AST node offsets accordingly after parsing
// is complete.
//
// If anyone's unlucky enough to have a static method named "if" with a newline
// before the method name e.g.
//
// Foo::
// if(...);
//
// This transformation will break that.
$fake_class = \preg_replace_callback(
'/(->|::)(\n\s*(if|list)\s*\()/',
function (array $match) use (&$extra_characters) {
/**
* @var array<int, array{int, int}> $match
* @psalm-suppress MixedArrayAssignment
*/
$extra_characters[] = $match[2][1];
return $match[1][0] . ';' . $match[2][0];
},
$fake_class,
-1,
$count,
\PREG_OFFSET_CAPTURE
);
$replacement_stmts = $this->parser->parse(
$fake_class,
$error_handler
) ?: [];
$extra_characters = [];
if (!$replacement_stmts
|| !$replacement_stmts[0] instanceof PhpParser\Node\Stmt\ClassLike
|| count($replacement_stmts[0]->stmts) !== 1
@ -183,43 +220,6 @@ class PartialParserVisitor extends PhpParser\NodeVisitorAbstract
// changes "): {" to ") {"
$hacky_class_fix = preg_replace('/(\)[\s]*):([\s]*\{)/', '$1 $2', $hacky_class_fix);
// To avoid a parser error during completion we replace
//
// Foo::
// if (...) {}
//
// with
//
// Foo::;
// if (...) {}
//
// When we insert the extra semicolon we have to keep track of the places
// we inserted it, and then shift the AST node offsets accordingly after parsing
// is complete.
//
// If anyone's unlucky enough to have a static method named "if" with a newline
// before the method name e.g.
//
// Foo::
// if(...);
//
// This transformation will break that.
$hacky_class_fix = \preg_replace_callback(
'/(->|::)(\n\s*if\s*\()/',
function (array $match) use (&$extra_characters) {
/**
* @var array<int, array{int, int}> $match
* @psalm-suppress MixedArrayAssignment
*/
$extra_characters[] = $match[2][1];
return $match[1][0] . ';' . $match[2][0];
},
$hacky_class_fix,
-1,
$count,
\PREG_OFFSET_CAPTURE
);
if ($hacky_class_fix !== $fake_class) {
$replacement_stmts = $this->parser->parse(
$hacky_class_fix,

View File

@ -193,6 +193,55 @@ class CompletionTest extends \Psalm\Tests\TestCase
$this->assertSame(['B\A', '::', 223], $codebase->getCompletionDataAtPosition('somefile.php', new Position(8, 27)));
}
public function testCompletionOnSelfWithListBelow(): void
{
$codebase = $this->project_analyzer->getCodebase();
$config = $codebase->config;
$config->throw_exception = false;
$this->addFile(
'somefile.php',
'<?php
namespace B;
class A {
/** @var int|null */
protected static $a;
public function foo() : self {
A
list($a, $b) = $c;
}
}'
);
$codebase->file_provider->openFile('somefile.php');
$codebase->scanFiles();
$this->analyzeFile('somefile.php', new Context());
$codebase->addTemporaryFileChanges(
'somefile.php',
'<?php
namespace B;
class A {
/** @var int|null */
protected static $a;
public function foo() : self {
A::
list($a, $b) = $c;
}
}'
);
$codebase->reloadFiles($this->project_analyzer, ['somefile.php']);
$codebase->analyzer->analyzeFiles($this->project_analyzer, 1, false);
$this->assertSame(['B\A', '::', 223], $codebase->getCompletionDataAtPosition('somefile.php', new Position(8, 27)));
}
public function testCompletionOnThisProperty(): void
{
$codebase = $this->project_analyzer->getCodebase();