1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-30 04:39:00 +01:00

Check for not-set-in-constructor errors across traits too

This commit is contained in:
Matthew Brown 2017-01-27 08:28:21 -07:00
parent 98d4ced24f
commit c485a3d056
3 changed files with 88 additions and 31 deletions

View File

@ -501,23 +501,33 @@ abstract class ClassLikeChecker extends SourceChecker implements StatementsSourc
$config = Config::getInstance();
if (!$config->excludeIssueInFile('PropertyNotSetInConstructor', $this->getFilePath())) {
$uninitialized_variable = null;
$uninitialized_property = null;
$uninitialized_variables = [];
$uninitialized_properties = [];
foreach ($storage->appearing_property_ids as $property_name => $appearing_property_id) {
if (explode('::', $appearing_property_id)[0] !== $fq_class_name) {
continue;
}
$property_class_name = self::getDeclaringClassForProperty($appearing_property_id);
$property_class_storage = self::$storage[strtolower((string)$property_class_name)];
$property_class_name = self::getDeclaringClassForProperty($appearing_property_id);
$property = $property_class_storage->properties[$property_name];
foreach ($storage->properties as $property_name => $property) {
if (!$property->has_default &&
$property->type &&
!$property->type->isMixed() &&
!$property->type->isNullable() &&
!$property->is_static
) {
$uninitialized_variable = '$this->' . $property_name;
$uninitialized_property = $property;
$uninitialized_variables[] = '$this->' . $property_name;
$uninitialized_properties[$property_name] = $property;
break;
}
}
if ($uninitialized_variable && $uninitialized_property) {
if ($uninitialized_properties) {
if (isset($storage->methods['__construct']) && $constructor_checker) {
$method_context = clone $class_context;
$method_context->collect_initializations = true;
@ -525,24 +535,14 @@ abstract class ClassLikeChecker extends SourceChecker implements StatementsSourc
$constructor_checker->analyze($method_context, null, true);
foreach ($storage->properties as $property_name => $property) {
if ($property->has_default ||
!$property->type ||
$property->type->isMixed() ||
$property->type->isNullable() ||
$property->is_static ||
!$property->location
) {
continue;
}
foreach ($uninitialized_properties as $property_name => $property) {
if (!isset($method_context->vars_in_scope['$this->' . $property_name])) {
throw new \UnexpectedValueException('$this->' . $property_name . ' should be in scope');
}
$end_type = $method_context->vars_in_scope['$this->' . $property_name];
if (!$end_type->initialized) {
if (!$end_type->initialized && $property->location) {
$property_id = $this->fq_class_name . '::$' . $property_name;
if (IssueBuffer::accepts(
@ -557,16 +557,20 @@ abstract class ClassLikeChecker extends SourceChecker implements StatementsSourc
}
}
}
} elseif ($uninitialized_property->location) {
if (IssueBuffer::accepts(
new MissingConstructor(
$fq_class_name . ' has an unitiialized variable ' . $uninitialized_variable .
', but no constructor',
$uninitialized_property->location
),
$this->source->getSuppressedIssues()
)) {
// fall through
} elseif (!$this instanceof TraitChecker) {
$first_uninitialized_property = array_shift($uninitialized_properties);
if ($first_uninitialized_property->location) {
if (IssueBuffer::accepts(
new MissingConstructor(
$fq_class_name . ' has an unitiialized variable ' . $uninitialized_variables[0] .
', but no constructor',
$first_uninitialized_property->location
),
$this->source->getSuppressedIssues()
)) {
// fall through
}
}
}
}

View File

@ -753,6 +753,59 @@ class PropertyTypeTest extends PHPUnit_Framework_TestCase
$file_checker->visitAndAnalyzeMethods($context);
}
/**
* @expectedException \Psalm\Exception\CodeException
* @expectedExceptionMessage PropertyNotSetInConstructor
* @return void
*/
public function testDefinedInTraitNotSetInEmptyConstructor()
{
$this->project_checker->registerFile(
getcwd() . '/somefile.php',
'<?php
trait A {
/** @var string **/
public $a;
}
class B {
use A;
public function __construct() {
}
}'
);
$file_checker = new FileChecker(getcwd() . '/somefile.php', $this->project_checker);
$context = new Context();
$file_checker->visitAndAnalyzeMethods($context);
}
/**
* @return void
*/
public function testDefinedInTraitSetInConstructor()
{
$this->project_checker->registerFile(
getcwd() . '/somefile.php',
'<?php
trait A {
/** @var string **/
public $a;
}
class B {
use A;
public function __construct() {
$this->a = "hello";
}
}'
);
$file_checker = new FileChecker(getcwd() . '/somefile.php', $this->project_checker);
$context = new Context();
$file_checker->visitAndAnalyzeMethods($context);
}
/**
* @return void
*/

View File

@ -113,7 +113,7 @@ class TraitTest extends PHPUnit_Framework_TestCase
$stmts = self::$parser->parse('<?php
trait T {
/** @var string */
private $fooFoo;
private $fooFoo = "";
}
class B {
@ -137,7 +137,7 @@ class TraitTest extends PHPUnit_Framework_TestCase
$stmts = self::$parser->parse('<?php
trait T {
/** @var string */
protected $fooFoo;
protected $fooFoo = "";
}
class B {
@ -161,7 +161,7 @@ class TraitTest extends PHPUnit_Framework_TestCase
$stmts = self::$parser->parse('<?php
trait T {
/** @var string */
public $fooFoo;
public $fooFoo = "";
}
class B {