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

Register more methods to prevent namespacing difficulties when reflecting

This commit is contained in:
Matthew Brown 2016-04-17 11:22:18 -04:00
parent 4adae3eb06
commit 851c6418d3
7 changed files with 102 additions and 51 deletions

View File

@ -5,6 +5,9 @@ namespace CodeInspector;
use PhpParser;
use PhpParser\Error;
use PhpParser\ParserFactory;
use ReflectionClass;
use ReflectionException;
use ReflectionMethod;
class ClassChecker implements StatementsSource
{
@ -16,9 +19,14 @@ class ClassChecker implements StatementsSource
protected $_class_properties = [];
protected $_has_custom_get = false;
/** @var string|null */
protected $_parent_class;
protected static $_existing_classes = [];
protected static $_implementing_classes = [];
protected static $_class_methods = [];
public function __construct(PhpParser\Node\Stmt\Class_ $class, StatementsSource $source, $absolute_class)
{
$this->_class = $class;
@ -27,22 +35,26 @@ class ClassChecker implements StatementsSource
$this->_file_name = $source->getFileName();
$this->_absolute_class = $absolute_class;
$this->_parent_class = $this->_class->extends ? ClassChecker::getAbsoluteClassFromName($this->_class->extends, $this->_namespace, $this->_aliased_classes) : null;
self::$_existing_classes[$absolute_class] = 1;
}
public function check()
{
if ($this->_class->extends instanceof PhpParser\Node\Name) {
self::checkClassName($this->_class->extends, $this->_namespace, $this->_aliased_classes, $this->_file_name);
if ($this->_parent_class) {
self::checkAbsoluteClass($this->_parent_class, $this->_class, $this->_file_name);
$this->_registerInheritedMethods();
}
$leftover_stmts = [];
try {
new \ReflectionMethod($this->_absolute_class . '::__get');
new ReflectionMethod($this->_absolute_class . '::__get');
$this->_has_custom_get = true;
} catch (\ReflectionException $e) {}
} catch (ReflectionException $e) {}
$method_checkers = [];
@ -101,7 +113,7 @@ class ClassChecker implements StatementsSource
}
if (class_exists($absolute_class, true) && strpos($absolute_class, '\\') === false) {
$reflection_class = new \ReflectionClass($absolute_class);
$reflection_class = new ReflectionClass($absolute_class);
if ($reflection_class->getName() !== $absolute_class) {
throw new CodeException('Class ' . $absolute_class . ' has wrong casing', $file_name, $stmt->getLine());
@ -164,9 +176,9 @@ class ClassChecker implements StatementsSource
return $this->_class->name;
}
public function getClassExtends()
public function getParentClass()
{
return $this->_class->extends;
return $this->_parent_class;
}
public function getFileName()
@ -214,4 +226,28 @@ class ClassChecker implements StatementsSource
return true;
}
protected function _registerInheritedMethods()
{
if (!isset($_class_methods[$this->_parent_class])) {
$class_methods = [];
$reflection_class = new ReflectionClass($this->_parent_class);
$reflection_methods = $reflection_class->getMethods(ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED);
foreach ($reflection_methods as $reflection_method) {
$class_methods[] = $reflection_method->getName();
}
}
else {
$class_methods = $_class_methods[$this->_parent_class];
}
foreach ($class_methods as $method_name) {
$parent_class_method = $this->_parent_class . '::' . $method_name;
ClassMethodChecker::extractReflectionMethodInfo($parent_class_method);
ClassMethodChecker::copyToChildMethod($parent_class_method, $this->_absolute_class . '::' . $method_name);
}
}
}

View File

@ -14,6 +14,7 @@ class ClassMethodChecker extends FunctionChecker
protected static $_static_methods = [];
protected static $_declaring_classes = [];
protected static $_existing_methods = [];
protected static $_have_reflected = [];
const TYPE_REGEX = '(\\\?[A-Za-z0-9\<\>\[\]|\\\]+[A-Za-z0-9\<\>\[\]]|\$[a-zA-Z_0-9\<\>\[\]]+)';
@ -27,7 +28,7 @@ class ClassMethodChecker extends FunctionChecker
public static function getMethodParams($method_id)
{
if (!isset(self::$_method_params[$method_id])) {
self::_extractReflectionMethodInfo($method_id);
self::extractReflectionMethodInfo($method_id);
}
return self::$_method_params[$method_id];
@ -36,7 +37,7 @@ class ClassMethodChecker extends FunctionChecker
public static function getMethodReturnTypes($method_id)
{
if (!isset(self::$_method_return_types[$method_id])) {
self::_extractReflectionMethodInfo($method_id);
self::extractReflectionMethodInfo($method_id);
}
$return_types = self::$_method_return_types[$method_id];
@ -44,9 +45,14 @@ class ClassMethodChecker extends FunctionChecker
return $return_types;
}
protected static function _extractReflectionMethodInfo($method_id)
public static function extractReflectionMethodInfo($method_id)
{
if (isset(self::$_have_reflected[$method_id])) {
return;
}
$method = new \ReflectionMethod($method_id);
self::$_have_reflected[$method_id] = true;
self::$_static_methods[$method_id] = $method->isStatic();
self::$_method_files[$method_id] = $method->getFileName();
@ -123,6 +129,17 @@ class ClassMethodChecker extends FunctionChecker
self::$_method_return_types[$method_id] = $return_types;
}
public static function copyToChildMethod($method_id, $child_method_id)
{
self::$_method_files[$child_method_id] = self::$_method_files[$method_id];
self::$_method_params[$child_method_id] = self::$_method_params[$method_id];
self::$_method_namespaces[$child_method_id] = self::$_method_namespaces[$method_id];
self::$_method_return_types[$child_method_id] = self::$_method_return_types[$method_id];
self::$_declaring_classes[$child_method_id] = self::$_declaring_classes[$method_id];
self::$_existing_methods[$child_method_id] = 1;
}
/**
* Determines whether a given method is static or not
* @param string $method_id
@ -131,7 +148,7 @@ class ClassMethodChecker extends FunctionChecker
public static function isGivenMethodStatic($method_id)
{
if (!isset(self::$_static_methods[$method_id])) {
self::_extractReflectionMethodInfo($method_id);
self::extractReflectionMethodInfo($method_id);
}
return self::$_static_methods[$method_id];
@ -149,45 +166,43 @@ class ClassMethodChecker extends FunctionChecker
self::$_method_files[$method_id] = $this->_file_name;
self::$_existing_methods[$method_id] = 1;
if (!isset(self::$_method_return_types[$method_id])) {
$comments = StatementsChecker::parseDocComment($method->getDocComment());
$comments = StatementsChecker::parseDocComment($method->getDocComment());
$return_types = [];
$return_types = [];
if (isset($comments['specials']['return'])) {
$return_blocks = explode(' ', $comments['specials']['return'][0]);
foreach ($return_blocks as $block) {
if ($block) {
if ($block && preg_match('/^' . self::TYPE_REGEX . '$/', $block)) {
$return_types = explode('|', $block);
break;
}
if (isset($comments['specials']['return'])) {
$return_blocks = explode(' ', $comments['specials']['return'][0]);
foreach ($return_blocks as $block) {
if ($block) {
if ($block && preg_match('/^' . self::TYPE_REGEX . '$/', $block)) {
$return_types = explode('|', $block);
break;
}
}
}
if (isset($comments['specials']['call'])) {
self::$_method_custom_calls[$method_id] = [];
$call_blocks = $comments['specials']['call'];
foreach ($comments['specials']['call'] as $block) {
if ($block) {
self::$_method_custom_calls[$method_id][] = trim($block);
}
}
}
$return_types = array_filter($return_types, function ($entry) {
return !empty($entry) && $entry !== '[type]';
});
foreach ($return_types as &$return_type) {
$return_type = $this->_fixUpLocalReturnType($return_type, $method_id, $this->_namespace, $this->_aliased_classes);
}
self::$_method_return_types[$method_id] = $return_types;
}
if (isset($comments['specials']['call'])) {
self::$_method_custom_calls[$method_id] = [];
$call_blocks = $comments['specials']['call'];
foreach ($comments['specials']['call'] as $block) {
if ($block) {
self::$_method_custom_calls[$method_id][] = trim($block);
}
}
}
$return_types = array_filter($return_types, function ($entry) {
return !empty($entry) && $entry !== '[type]';
});
foreach ($return_types as &$return_type) {
$return_type = $this->_fixUpLocalReturnType($return_type, $method_id, $this->_namespace, $this->_aliased_classes);
}
self::$_method_return_types[$method_id] = $return_types;
self::$_method_params[$method_id] = [];
foreach ($method->getParams() as $param) {

View File

@ -207,7 +207,7 @@ class FileChecker implements StatementsSource
return null;
}
public function getClassExtends()
public function getParentClass()
{
return null;
}

View File

@ -26,7 +26,7 @@ class FunctionChecker implements StatementsSource
$this->_aliased_classes = $source->getAliasedClasses();
$this->_namespace = $source->getNamespace();
$this->_class_name = $source->getClassName();
$this->_class_extends = $source->getClassExtends();
$this->_class_extends = $source->getParentClass();
$this->_file_name = $source->getFileName();
$this->_absolute_class = $source->getAbsoluteClass();
$this->_source = $source;
@ -108,7 +108,7 @@ class FunctionChecker implements StatementsSource
return $this->_source->getClassChecker();
}
public function getClassExtends()
public function getParentClass()
{
return $this->_class_extends;
}

View File

@ -86,7 +86,7 @@ class NamespaceChecker implements StatementsSource
return null;
}
public function getClassExtends()
public function getParentClass()
{
return null;
}

View File

@ -54,7 +54,7 @@ class StatementsChecker
$this->_is_static = $this->_source->isStatic();
$this->_absolute_class = $this->_source->getAbsoluteClass();
$this->_class_name = $this->_source->getClassName();
$this->_class_extends = $this->_source->getClassExtends();
$this->_class_extends = $this->_source->getParentClass();
$this->_type_checker = new TypeChecker($source, $this);
}
@ -1216,7 +1216,7 @@ class StatementsChecker
throw new CodeException('Cannot call method on parent as this class does not extend another', $this->_file_name, $stmt->getLine());
}
$absolute_class = ClassChecker::getAbsoluteClassFromName($this->_class_extends, $this->_namespace, $this->_aliased_classes);
$absolute_class = $this->_class_extends;
} else {
$absolute_class = ($this->_namespace ? $this->_namespace . '\\' : '') . $this->_class_name;
}
@ -1393,7 +1393,7 @@ class StatementsChecker
if (count($stmt->class->parts) === 1 && in_array($stmt->class->parts[0], ['self', 'static', 'parent'])) {
if ($stmt->class->parts[0] === 'parent') {
$absolute_class = ClassChecker::getAbsoluteClassFromName($this->_class_extends, $this->_namespace, $this->_aliased_classes);
$absolute_class = $this->_class_extends;
} else {
$absolute_class = ($this->_namespace ? $this->_namespace . '\\' : '') . $this->_class_name;
}

View File

@ -17,7 +17,7 @@ interface StatementsSource
/**
* @return \PhpParser\Node\Name
*/
public function getClassExtends();
public function getParentClass();
public function getFileName();