1
0
mirror of https://github.com/danog/psalm.git synced 2024-12-02 09:37:59 +01:00

Add support for checking existing properties

This commit is contained in:
Matthew Brown 2016-01-29 18:48:09 -05:00
parent 8d7d761a99
commit 32b4853c7b
6 changed files with 126 additions and 24 deletions

View File

@ -13,6 +13,8 @@ class ClassChecker implements StatementsSource
protected $_namespace; protected $_namespace;
protected $_aliased_classes; protected $_aliased_classes;
protected $_absolute_class; protected $_absolute_class;
protected $_class_properties = [];
protected $_has_custom_get = false;
protected static $_existing_classes = []; protected static $_existing_classes = [];
@ -35,12 +37,23 @@ class ClassChecker implements StatementsSource
$leftover_stmts = []; $leftover_stmts = [];
try {
new \ReflectionMethod($this->_absolute_class . '::__get');
$this->_has_custom_get = true;
} catch (\ReflectionException $e) {}
foreach ($this->_class->stmts as $stmt) { foreach ($this->_class->stmts as $stmt) {
if ($stmt instanceof PhpParser\Node\Stmt\ClassMethod) { if ($stmt instanceof PhpParser\Node\Stmt\ClassMethod) {
$method_checker = new ClassMethodChecker($stmt, $this); $method_checker = new ClassMethodChecker($stmt, $this);
$method_checker->check(); $method_checker->check();
} else { } else {
if ($stmt instanceof PhpParser\Node\Stmt\Property) {
foreach ($stmt->props as $property) {
$this->_class_properties[] = $property->name;
}
}
$leftover_stmts[] = $stmt; $leftover_stmts[] = $stmt;
} }
} }
@ -140,8 +153,23 @@ class ClassChecker implements StatementsSource
return $this->_file_name; return $this->_file_name;
} }
public function getClassChecker()
{
return $this;
}
public function isStatic() public function isStatic()
{ {
return false; return false;
} }
public function hasCustomGet()
{
return $this->_has_custom_get;
}
public function getPropertyNames()
{
return $this->_class_properties;
}
} }

View File

@ -185,6 +185,11 @@ class FileChecker implements StatementsSource
return $this->_class_name; return $this->_class_name;
} }
public function getClassChecker()
{
return null;
}
public function getClassExtends() public function getClassExtends()
{ {
return null; return null;

View File

@ -15,6 +15,7 @@ class FunctionChecker implements StatementsSource
protected $_is_static = false; protected $_is_static = false;
protected $_absolute_class; protected $_absolute_class;
protected $_statements_checker; protected $_statements_checker;
protected $_source;
protected $_function_params = []; protected $_function_params = [];
protected $_function_return_types = []; protected $_function_return_types = [];
@ -28,6 +29,7 @@ class FunctionChecker implements StatementsSource
$this->_class_extends = $source->getClassExtends(); $this->_class_extends = $source->getClassExtends();
$this->_file_name = $source->getFileName(); $this->_file_name = $source->getFileName();
$this->_absolute_class = $source->getAbsoluteClass(); $this->_absolute_class = $source->getAbsoluteClass();
$this->_source = $source;
$this->_statements_checker = new StatementsChecker($this, substr($this->_file_name, -4) === '.php'); $this->_statements_checker = new StatementsChecker($this, substr($this->_file_name, -4) === '.php');
@ -88,6 +90,11 @@ class FunctionChecker implements StatementsSource
return $this->_class_name; return $this->_class_name;
} }
public function getClassChecker()
{
return $this->_source->getClassChecker();
}
public function getClassExtends() public function getClassExtends()
{ {
return $this->_class_extends; return $this->_class_extends;

View File

@ -81,6 +81,11 @@ class NamespaceChecker implements StatementsSource
return null; return null;
} }
public function getClassChecker()
{
return null;
}
public function getClassExtends() public function getClassExtends()
{ {
return null; return null;

View File

@ -35,6 +35,7 @@ class StatementsChecker
protected static $_static_methods = []; protected static $_static_methods = [];
protected static $_declaring_classes = []; protected static $_declaring_classes = [];
protected static $_existing_static_vars = []; protected static $_existing_static_vars = [];
protected static $_existing_properties = [];
public function __construct(StatementsSource $source = null, $check_variables = true) public function __construct(StatementsSource $source = null, $check_variables = true)
{ {
@ -571,7 +572,23 @@ class StatementsChecker
{ {
if ($stmt->var instanceof PhpParser\Node\Expr\Variable) { if ($stmt->var instanceof PhpParser\Node\Expr\Variable) {
if ($stmt->var->name === 'this') { if ($stmt->var->name === 'this') {
$class_checker = $this->_source->getClassChecker();
if ($class_checker) {
if ($class_checker->hasCustomGet()) {
// do nothing
} elseif (is_string($stmt->name)) {
$property_names = $class_checker->getPropertyNames();
if (!in_array($stmt->name, $property_names)) {
if (!self::_propertyExists($this->_absolute_class . '::' . $stmt->name)) {
throw new CodeException('$this->' . $stmt->name . ' is not defined', $this->_file_name, $stmt->getLine());
}
}
}
} else {
throw new CodeException('Cannot use $this when not inside class', $this->_file_name, $stmt->getLine());
}
} else { } else {
$this->_checkVariable($stmt->var, $vars_in_scope, $vars_possibly_in_scope); $this->_checkVariable($stmt->var, $vars_in_scope, $vars_possibly_in_scope);
} }
@ -705,23 +722,6 @@ class StatementsChecker
$vars_possibly_in_scope[$stmt->var->name] = true; $vars_possibly_in_scope[$stmt->var->name] = true;
$this->registerVariable($stmt->var->name, $stmt->var->getLine()); $this->registerVariable($stmt->var->name, $stmt->var->getLine());
} elseif ($stmt->var instanceof PhpParser\Node\Expr\List_) {
foreach ($stmt->var->vars as $var) {
if ($var) {
$vars_in_scope[$var->name] = true;
$vars_possibly_in_scope[$var->name] = true;
$this->registerVariable($var->name, $var->getLine());
}
}
} else if ($stmt->var instanceof PhpParser\Node\Expr\ArrayDimFetch && $stmt->var->var instanceof PhpParser\Node\Expr\Variable) {
// if it's an array assignment
$vars_in_scope[$stmt->var->var->name] = true;
$vars_possibly_in_scope[$stmt->var->var->name] = true;
$this->registerVariable($stmt->var->var->name, $stmt->var->var->getLine());
}
if ($stmt->var instanceof PhpParser\Node\Expr\Variable && is_string($stmt->var->name)) {
$comments = []; $comments = [];
$doc_comment = $stmt->getDocComment(); $doc_comment = $stmt->getDocComment();
@ -763,6 +763,26 @@ class StatementsChecker
$vars_in_scope[$stmt->var->name] = $stmt->expr->returnType; $vars_in_scope[$stmt->var->name] = $stmt->expr->returnType;
} }
} }
} elseif ($stmt->var instanceof PhpParser\Node\Expr\List_) {
foreach ($stmt->var->vars as $var) {
if ($var) {
$vars_in_scope[$var->name] = true;
$vars_possibly_in_scope[$var->name] = true;
$this->registerVariable($var->name, $var->getLine());
}
}
} else if ($stmt->var instanceof PhpParser\Node\Expr\ArrayDimFetch && $stmt->var->var instanceof PhpParser\Node\Expr\Variable) {
// if it's an array assignment
$vars_in_scope[$stmt->var->var->name] = true;
$vars_possibly_in_scope[$stmt->var->var->name] = true;
$this->registerVariable($stmt->var->var->name, $stmt->var->var->getLine());
} else if ($stmt->var instanceof PhpParser\Node\Expr\PropertyFetch) {
if ($stmt->var->var->name === 'this') {
self::$_existing_properties[$this->_absolute_class . '::' . $stmt->var->name] = 1;
}
} }
} }
@ -1117,6 +1137,41 @@ class StatementsChecker
} }
} }
public static function _getClassProperties(\ReflectionClass $reflection_class, $absolute_class_name)
{
$properties = $reflection_class->getProperties();
$props_arr = [];
foreach ($properties as $reflection_property){
if ($reflection_property->isPrivate() || $reflection_property->isStatic()) {
continue;
}
self::$_existing_properties[$absolute_class_name . '::' . $reflection_property->getName()] = 1;
}
$parent_reflection_class = $reflection_class->getParentClass();
if ($parent_reflection_class){
self::_getClassProperties($parent_reflection_class, $absolute_class_name);
}
}
protected static function _propertyExists($property_id)
{
if (isset(self::$_existing_properties[$property_id])) {
return true;
}
$absolute_class = explode('::', $property_id)[0];
$reflection_class = new \ReflectionClass($absolute_class);
self::_getClassProperties($reflection_class, $absolute_class);
return isset(self::$_existing_properties[$property_id]);
}
protected static function _methodExists($method_id) protected static function _methodExists($method_id)
{ {
if (isset(self::$_existing_methods[$method_id])) { if (isset(self::$_existing_methods[$method_id])) {

View File

@ -12,6 +12,8 @@ interface StatementsSource
public function getClassName(); public function getClassName();
public function getClassChecker();
/** /**
* @return \PhpParser\Node\Name * @return \PhpParser\Node\Name
*/ */