1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-30 04:39:00 +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 $_aliased_classes;
protected $_absolute_class;
protected $_class_properties = [];
protected $_has_custom_get = false;
protected static $_existing_classes = [];
@ -35,12 +37,23 @@ class ClassChecker implements StatementsSource
$leftover_stmts = [];
try {
new \ReflectionMethod($this->_absolute_class . '::__get');
$this->_has_custom_get = true;
} catch (\ReflectionException $e) {}
foreach ($this->_class->stmts as $stmt) {
if ($stmt instanceof PhpParser\Node\Stmt\ClassMethod) {
$method_checker = new ClassMethodChecker($stmt, $this);
$method_checker->check();
} else {
if ($stmt instanceof PhpParser\Node\Stmt\Property) {
foreach ($stmt->props as $property) {
$this->_class_properties[] = $property->name;
}
}
$leftover_stmts[] = $stmt;
}
}
@ -140,8 +153,23 @@ class ClassChecker implements StatementsSource
return $this->_file_name;
}
public function getClassChecker()
{
return $this;
}
public function isStatic()
{
return false;
}
public function hasCustomGet()
{
return $this->_has_custom_get;
}
public function getPropertyNames()
{
return $this->_class_properties;
}
}

View File

@ -51,7 +51,7 @@ class FileChecker implements StatementsSource
} elseif ($stmt instanceof PhpParser\Node\Stmt\Trait_) {
// @todo check trait
} elseif ($stmt instanceof PhpParser\Node\Stmt\Namespace_) {
$namespace_name = implode('\\', $stmt->name->parts);
@ -90,7 +90,7 @@ class FileChecker implements StatementsSource
{
if (isset(self::$_file_checkers[$file_name])) {
$aliased_classes = self::$_file_checkers[$file_name]->getAliasedClasses($namespace);
} else {
$file_checker = new FileChecker($file_name);
$file_checker->check(false);
@ -171,7 +171,7 @@ class FileChecker implements StatementsSource
if ($namespace_name && isset(self::$_namespace_aliased_classes[$namespace_name])) {
return self::$_namespace_aliased_classes[$namespace_name];
}
return $this->_aliased_classes;
}
@ -185,6 +185,11 @@ class FileChecker implements StatementsSource
return $this->_class_name;
}
public function getClassChecker()
{
return null;
}
public function getClassExtends()
{
return null;

View File

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

View File

@ -11,7 +11,7 @@ class NamespaceChecker implements StatementsSource
protected $_contained_classes = [];
protected $_aliased_classes = [];
protected $_file_name;
public function __construct(\PhpParser\Node\Stmt\Namespace_ $namespace, StatementsSource $source)
{
$this->_namespace = $namespace;
@ -33,10 +33,10 @@ class NamespaceChecker implements StatementsSource
}
} elseif ($stmt instanceof PhpParser\Node\Stmt\Interface_) {
// @todo check interface
} elseif ($stmt instanceof PhpParser\Node\Stmt\Trait_) {
// @todo check trait
} elseif ($stmt instanceof PhpParser\Node\Stmt\Use_) {
foreach ($stmt->uses as $use) {
$this->_aliased_classes[$use->alias] = implode('\\', $use->name->parts);
@ -81,6 +81,11 @@ class NamespaceChecker implements StatementsSource
return null;
}
public function getClassChecker()
{
return null;
}
public function getClassExtends()
{
return null;

View File

@ -35,6 +35,7 @@ class StatementsChecker
protected static $_static_methods = [];
protected static $_declaring_classes = [];
protected static $_existing_static_vars = [];
protected static $_existing_properties = [];
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->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 {
$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;
$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 = [];
$doc_comment = $stmt->getDocComment();
@ -763,6 +763,26 @@ class StatementsChecker
$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)
{
if (isset(self::$_existing_methods[$method_id])) {

View File

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