1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-26 20:34:47 +01:00

Fix #1010 - track changes to traits and trait uses

This commit is contained in:
Brown 2018-10-03 13:58:32 -04:00
parent d6952b5f46
commit 33254c43cc
7 changed files with 352 additions and 10 deletions

View File

@ -914,6 +914,8 @@ class ClassChecker extends ClassLikeChecker
return $method_checker;
}
//echo 'Analysing ' . $analyzed_method_id . "\n";
$existing_error_count = IssueBuffer::getErrorCount();
$method_context = clone $class_context;
@ -1000,7 +1002,7 @@ class ClassChecker extends ClassLikeChecker
&& !$class_context->collect_mutations
&& !$is_fake
) {
$codebase->analyzer->setCorrectMethod($source->getFilePath(), strtolower($analyzed_method_id));
$codebase->analyzer->setCorrectMethod($source->getFilePath(), strtolower($actual_method_id));
}
return $method_checker;

View File

@ -308,24 +308,42 @@ class Analyzer
$all_referencing_methods = $project_checker->file_reference_provider->getMethodsReferencing();
foreach ($all_referencing_methods as $member_id => $referencing_method_ids) {
$member_stub = preg_replace('/::.*$/', '::*', $member_id);
if (!isset($all_referencing_methods[$member_stub])) {
$all_referencing_methods[$member_stub] = $referencing_method_ids;
} else {
$all_referencing_methods[$member_stub] += $referencing_method_ids;
}
}
$newly_invalidated_methods = [];
foreach ($changed_members as $file_changed_members) {
foreach ($file_changed_members as $member_id => $_) {
if (isset($all_referencing_methods[$member_id])) {
$newly_invalidated_methods = array_merge($all_referencing_methods[$member_id]);
$newly_invalidated_methods = array_merge(
$all_referencing_methods[$member_id],
$newly_invalidated_methods
);
}
}
}
foreach ($this->correct_methods as $file_path => $correct_methods) {
foreach ($correct_methods as $correct_method_id => $_) {
if (isset($newly_invalidated_methods[$correct_method_id])) {
$correct_method_stub = preg_replace('/::.*$/', '::*', $correct_method_id);
if (isset($newly_invalidated_methods[$correct_method_id])
|| isset($newly_invalidated_methods[$correct_method_stub])
) {
unset($this->correct_methods[$file_path][$correct_method_id]);
}
if (isset($unchanged_members[$file_path])
&& !isset($unchanged_members[$file_path][$correct_method_id])
&& !isset($unchanged_members[$file_path][$correct_method_stub])
) {
unset($this->correct_methods[$file_path][$correct_method_id]);
}

View File

@ -154,6 +154,10 @@ class ClassStatementsDiffer extends Differ
foreach ($diff_elem->old->consts as $const) {
$keep[] = strtolower($name) . '::' . $const->name;
}
} elseif ($diff_elem->old instanceof PhpParser\Node\Stmt\TraitUse) {
foreach ($diff_elem->old->traits as $trait) {
$keep[] = strtolower((string) $trait->getAttribute('resolvedName')) . '::*';
}
}
} elseif ($diff_elem->type === DiffElem::TYPE_KEEP_SIGNATURE) {
if ($diff_elem->old instanceof PhpParser\Node\Stmt\ClassMethod) {
@ -174,6 +178,10 @@ class ClassStatementsDiffer extends Differ
foreach ($diff_elem->old->consts as $const) {
$delete[] = strtolower($name) . '::' . $const->name;
}
} elseif ($diff_elem->old instanceof PhpParser\Node\Stmt\TraitUse) {
foreach ($diff_elem->old->traits as $trait) {
$delete[] = strtolower((string) $trait->getAttribute('resolvedName')) . '::*';
}
}
}
};

View File

@ -42,6 +42,7 @@ class FileStatementsDiffer extends Differ
if (($a instanceof PhpParser\Node\Stmt\Namespace_ && $b instanceof PhpParser\Node\Stmt\Namespace_)
|| ($a instanceof PhpParser\Node\Stmt\Class_ && $b instanceof PhpParser\Node\Stmt\Class_)
|| ($a instanceof PhpParser\Node\Stmt\Interface_ && $b instanceof PhpParser\Node\Stmt\Interface_)
|| ($a instanceof PhpParser\Node\Stmt\Trait_ && $b instanceof PhpParser\Node\Stmt\Trait_)
) {
return (string)$a->name === (string)$b->name;
}
@ -58,7 +59,7 @@ class FileStatementsDiffer extends Differ
$keep = [];
$keep_signature = [];
$delete = [];
$add_or_delete = [];
$diff_map = [];
foreach ($diff as $diff_elem) {
@ -76,12 +77,14 @@ class FileStatementsDiffer extends Differ
$keep = array_merge($keep, $namespace_keep[0]);
$keep_signature = array_merge($keep_signature, $namespace_keep[1]);
$delete = array_merge($delete, $namespace_keep[2]);
$add_or_delete = array_merge($add_or_delete, $namespace_keep[2]);
$diff_map = array_merge($diff_map, $namespace_keep[3]);
} elseif (($diff_elem->old instanceof PhpParser\Node\Stmt\Class_
&& $diff_elem->new instanceof PhpParser\Node\Stmt\Class_)
|| ($diff_elem->old instanceof PhpParser\Node\Stmt\Interface_
&& $diff_elem->new instanceof PhpParser\Node\Stmt\Interface_)
|| ($diff_elem->old instanceof PhpParser\Node\Stmt\Trait_
&& $diff_elem->new instanceof PhpParser\Node\Stmt\Trait_)
) {
$class_keep = ClassStatementsDiffer::diff(
(string) $diff_elem->old->name,
@ -93,12 +96,12 @@ class FileStatementsDiffer extends Differ
$keep = array_merge($keep, $class_keep[0]);
$keep_signature = array_merge($keep_signature, $class_keep[1]);
$delete = array_merge($delete, $class_keep[2]);
$add_or_delete = array_merge($add_or_delete, $class_keep[2]);
$diff_map = array_merge($diff_map, $class_keep[3]);
}
}
}
return [$keep, $keep_signature, $delete, $diff_map];
return [$keep, $keep_signature, $add_or_delete, $diff_map];
}
}

View File

@ -38,6 +38,7 @@ class NamespaceStatementsDiffer extends Differ
function (PhpParser\Node\Stmt $a, PhpParser\Node\Stmt $b, $a_code, $b_code) {
if (($a instanceof PhpParser\Node\Stmt\Class_ && $b instanceof PhpParser\Node\Stmt\Class_)
|| ($a instanceof PhpParser\Node\Stmt\Interface_ && $b instanceof PhpParser\Node\Stmt\Interface_)
|| ($a instanceof PhpParser\Node\Stmt\Trait_ && $b instanceof PhpParser\Node\Stmt\Trait_)
) {
// @todo add check for comments comparison
@ -56,7 +57,7 @@ class NamespaceStatementsDiffer extends Differ
$keep = [];
$keep_signature = [];
$delete = [];
$add_or_delete = [];
$diff_map = [];
foreach ($diff as $diff_elem) {
@ -65,6 +66,8 @@ class NamespaceStatementsDiffer extends Differ
&& $diff_elem->new instanceof PhpParser\Node\Stmt\Class_)
|| ($diff_elem->old instanceof PhpParser\Node\Stmt\Interface_
&& $diff_elem->new instanceof PhpParser\Node\Stmt\Interface_)
|| ($diff_elem->old instanceof PhpParser\Node\Stmt\Trait_
&& $diff_elem->new instanceof PhpParser\Node\Stmt\Trait_)
) {
$class_keep = ClassStatementsDiffer::diff(
($name ? $name . '\\' : '') . $diff_elem->old->name,
@ -76,12 +79,12 @@ class NamespaceStatementsDiffer extends Differ
$keep = array_merge($keep, $class_keep[0]);
$keep_signature = array_merge($keep_signature, $class_keep[1]);
$delete = array_merge($delete, $class_keep[2]);
$add_or_delete = array_merge($add_or_delete, $class_keep[2]);
$diff_map = array_merge($diff_map, $class_keep[3]);
}
}
}
return [$keep, $keep_signature, $delete, $diff_map];
return [$keep, $keep_signature, $add_or_delete, $diff_map];
}
}

View File

@ -767,6 +767,60 @@ class FileDiffTest extends TestCase
[],
[[0, 0], [0, 0], [120, 2]]
],
'sameTrait' => [
'<?php
namespace Foo;
trait T {
public $aB = 5;
const F = 1;
public function foo() {
$a = 1;
}
public function bar() {
$b = 1;
}
}',
'<?php
namespace Foo;
trait T {
public $aB = 5;
const F = 1;
public function foo() {
$a = 1;
}
public function bar() {
$b = 1;
}
}',
['foo\t::$aB', 'foo\t::F', 'foo\t::foo', 'foo\t::bar'],
[],
[],
[[0, 0], [0, 0], [0, 0], [0, 0]]
],
'traitPropertyChange' => [
'<?php
namespace Foo;
trait T {
public $a;
}',
'<?php
namespace Foo;
trait T {
public $b;
}',
[],
[],
['foo\t::$a'],
[]
],
];
}
}

View File

@ -467,6 +467,260 @@ class FileUpdateTest extends TestCase
],
]
],
'dontInvalidateTraitMethods' => [
'start_files' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => '<?php
namespace Foo;
class A {
use T;
public function fooFoo(): void { }
}',
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => '<?php
namespace Foo;
class B {
public function foo(): void {
(new A)->fooFoo();
}
public function bar() : void {
echo (new A)->barBar();
}
public function noReturnType() {}
}',
getcwd() . DIRECTORY_SEPARATOR . 'T.php' => '<?php
namespace Foo;
trait T {
public function barBar(): string {
return "hello";
}
}',
],
'end_files' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => '<?php
namespace Foo;
class A {
use T;
public function fooFoo(?string $foo = null): void { }
}',
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => '<?php
namespace Foo;
class B {
public function foo(): void {
(new A)->fooFoo();
}
public function bar() : void {
echo (new A)->barBar();
}
public function noReturnType() {}
}',
getcwd() . DIRECTORY_SEPARATOR . 'T.php' => '<?php
namespace Foo;
trait T {
public function barBar(): string {
return "hello";
}
}',
],
'initial_correct_methods' => [
getcwd() . DIRECTORY_SEPARATOR . 'T.php' => [
'foo\t::barbar' => true,
],
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [
'foo\a::foofoo' => true,
],
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [
'foo\b::foo' => true,
'foo\b::bar' => true,
'foo\b::noreturntype' => true,
],
],
'unaffected_correct_methods' => [
getcwd() . DIRECTORY_SEPARATOR . 'T.php' => [
'foo\t::barbar' => true,
],
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [],
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [
'foo\b::bar' => true,
'foo\b::noreturntype' => true,
],
],
[
'MissingReturnType' => \Psalm\Config::REPORT_INFO,
]
],
'invalidateTraitMethodsWhenTraitRemoved' => [
'start_files' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => '<?php
namespace Foo;
class A {
use T;
public function fooFoo(): void { }
}',
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => '<?php
namespace Foo;
class B {
public function foo(): void {
(new A)->fooFoo();
}
public function bar() : void {
echo (new A)->barBar();
}
}',
getcwd() . DIRECTORY_SEPARATOR . 'T.php' => '<?php
namespace Foo;
trait T {
public function barBar(): string {
return "hello";
}
}',
],
'end_files' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => '<?php
namespace Foo;
class A {
public function fooFoo(?string $foo = null): void { }
}',
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => '<?php
namespace Foo;
class B {
public function foo(): void {
(new A)->fooFoo();
}
public function bar() : void {
echo (new A)->barBar();
}
}',
getcwd() . DIRECTORY_SEPARATOR . 'T.php' => '<?php
namespace Foo;
trait T {
public function barBar(): string {
return "hello";
}
}',
],
'initial_correct_methods' => [
getcwd() . DIRECTORY_SEPARATOR . 'T.php' => [
'foo\t::barbar' => true,
],
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [
'foo\a::foofoo' => true,
],
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [
'foo\b::foo' => true,
'foo\b::bar' => true,
],
],
'unaffected_correct_methods' => [
getcwd() . DIRECTORY_SEPARATOR . 'T.php' => [
'foo\t::barbar' => true,
],
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [],
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [],
]
],
'invalidateTraitMethodsWhenTraitReplaced' => [
'start_files' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => '<?php
namespace Foo;
class A {
use T;
public function fooFoo(): void { }
}',
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => '<?php
namespace Foo;
class B {
public function foo(): void {
(new A)->fooFoo();
}
public function bar() : void {
echo (new A)->barBar();
}
}',
getcwd() . DIRECTORY_SEPARATOR . 'T.php' => '<?php
namespace Foo;
trait T {
public function barBar(): string {
return "hello";
}
}',
],
'end_files' => [
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => '<?php
namespace Foo;
class A {
public function fooFoo(?string $foo = null): void { }
public function barBar(): int {
return 5;
}
}',
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => '<?php
namespace Foo;
class B {
public function foo(): void {
(new A)->fooFoo();
}
public function bar() : void {
echo (new A)->barBar();
}
}',
getcwd() . DIRECTORY_SEPARATOR . 'T.php' => '<?php
namespace Foo;
trait T {
public function barBar(): string {
return "hello";
}
}',
],
'initial_correct_methods' => [
getcwd() . DIRECTORY_SEPARATOR . 'T.php' => [
'foo\t::barbar' => true,
],
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [
'foo\a::foofoo' => true,
],
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [
'foo\b::foo' => true,
'foo\b::bar' => true,
],
],
'unaffected_correct_methods' => [
getcwd() . DIRECTORY_SEPARATOR . 'T.php' => [
'foo\t::barbar' => true,
],
getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [],
getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [],
]
],
];
}