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:
parent
4adae3eb06
commit
851c6418d3
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -207,7 +207,7 @@ class FileChecker implements StatementsSource
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getClassExtends()
|
||||
public function getParentClass()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ class NamespaceChecker implements StatementsSource
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getClassExtends()
|
||||
public function getParentClass()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ interface StatementsSource
|
||||
/**
|
||||
* @return \PhpParser\Node\Name
|
||||
*/
|
||||
public function getClassExtends();
|
||||
public function getParentClass();
|
||||
|
||||
public function getFileName();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user