2016-01-08 00:28:27 +01:00
|
|
|
<?php
|
|
|
|
|
2016-08-13 20:20:46 +02:00
|
|
|
namespace Psalm\Checker;
|
2016-01-08 00:28:27 +01:00
|
|
|
|
2016-07-26 00:37:44 +02:00
|
|
|
use Psalm\Issue\UndefinedMethod;
|
|
|
|
use Psalm\Issue\InaccessibleMethod;
|
|
|
|
use Psalm\Issue\DeprecatedMethod;
|
|
|
|
use Psalm\Issue\InvalidDocblock;
|
|
|
|
use Psalm\Issue\InvalidStaticInvocation;
|
2016-08-13 20:20:46 +02:00
|
|
|
use Psalm\StatementsSource;
|
|
|
|
use Psalm\Config;
|
|
|
|
use Psalm\Type;
|
|
|
|
use Psalm\IssueBuffer;
|
2016-02-04 15:22:46 +01:00
|
|
|
use PhpParser;
|
2016-01-08 00:28:27 +01:00
|
|
|
|
|
|
|
class ClassMethodChecker extends FunctionChecker
|
|
|
|
{
|
2016-04-16 22:28:25 +02:00
|
|
|
protected static $_method_comments = [];
|
|
|
|
protected static $_method_files = [];
|
|
|
|
protected static $_method_params = [];
|
|
|
|
protected static $_method_namespaces = [];
|
|
|
|
protected static $_method_return_types = [];
|
|
|
|
protected static $_static_methods = [];
|
|
|
|
protected static $_declaring_classes = [];
|
|
|
|
protected static $_existing_methods = [];
|
2016-04-17 17:22:18 +02:00
|
|
|
protected static $_have_reflected = [];
|
2016-04-17 18:27:47 +02:00
|
|
|
protected static $_have_registered = [];
|
|
|
|
protected static $_inherited_methods = [];
|
2016-05-22 18:14:48 +02:00
|
|
|
protected static $_declaring_class = [];
|
2016-04-18 19:31:59 +02:00
|
|
|
protected static $_method_visibility = [];
|
2016-07-22 19:29:46 +02:00
|
|
|
protected static $_method_suppress = [];
|
|
|
|
protected static $_deprecated_methods = [];
|
2016-04-18 19:31:59 +02:00
|
|
|
|
|
|
|
const VISIBILITY_PUBLIC = 1;
|
|
|
|
const VISIBILITY_PROTECTED = 2;
|
|
|
|
const VISIBILITY_PRIVATE = 3;
|
2016-04-16 22:28:25 +02:00
|
|
|
|
2016-05-16 05:06:03 +02:00
|
|
|
public function __construct(PhpParser\Node\FunctionLike $function, StatementsSource $source, array $this_vars = [])
|
2016-04-16 22:28:25 +02:00
|
|
|
{
|
|
|
|
parent::__construct($function, $source);
|
|
|
|
|
2016-04-20 19:35:22 +02:00
|
|
|
if ($function instanceof PhpParser\Node\Stmt\ClassMethod) {
|
|
|
|
$this->_registerMethod($function);
|
|
|
|
$this->_is_static = $function->isStatic();
|
|
|
|
}
|
2016-04-16 22:28:25 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public static function getMethodParams($method_id)
|
|
|
|
{
|
2016-04-18 19:31:59 +02:00
|
|
|
self::_populateData($method_id);
|
2016-04-16 22:28:25 +02:00
|
|
|
|
|
|
|
return self::$_method_params[$method_id];
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function getMethodReturnTypes($method_id)
|
|
|
|
{
|
2016-04-18 19:31:59 +02:00
|
|
|
self::_populateData($method_id);
|
2016-04-16 22:28:25 +02:00
|
|
|
|
2016-06-16 07:19:52 +02:00
|
|
|
return self::$_method_return_types[$method_id] ? clone self::$_method_return_types[$method_id] : null;
|
2016-04-16 22:28:25 +02:00
|
|
|
}
|
|
|
|
|
2016-04-27 00:42:48 +02:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
2016-04-17 17:22:18 +02:00
|
|
|
public static function extractReflectionMethodInfo($method_id)
|
2016-04-16 22:28:25 +02:00
|
|
|
{
|
2016-05-22 18:14:48 +02:00
|
|
|
if (isset(self::$_have_reflected[$method_id]) || isset(self::$_have_registered[$method_id])) {
|
2016-04-17 17:22:18 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-06-17 22:58:15 +02:00
|
|
|
try {
|
|
|
|
$method = new \ReflectionMethod($method_id);
|
|
|
|
}
|
|
|
|
catch (\ReflectionException $e) {
|
|
|
|
// maybe it's an old-timey constructor
|
|
|
|
|
|
|
|
$absolute_class = explode('::', $method_id)[0];
|
2016-06-17 23:34:52 +02:00
|
|
|
$class_name = array_pop(explode('\\', $absolute_class));
|
2016-06-17 22:58:15 +02:00
|
|
|
|
|
|
|
$alt_method_id = $absolute_class . '::' . $class_name;
|
|
|
|
|
|
|
|
$method = new \ReflectionMethod($alt_method_id);
|
|
|
|
}
|
|
|
|
|
2016-04-17 17:22:18 +02:00
|
|
|
self::$_have_reflected[$method_id] = true;
|
2016-04-16 22:28:25 +02:00
|
|
|
|
|
|
|
self::$_static_methods[$method_id] = $method->isStatic();
|
|
|
|
self::$_method_files[$method_id] = $method->getFileName();
|
|
|
|
self::$_method_namespaces[$method_id] = $method->getDeclaringClass()->getNamespaceName();
|
2016-05-22 18:14:48 +02:00
|
|
|
self::$_declaring_classes[$method_id] = $method->getDeclaringClass()->name . '::' . $method->getName();
|
2016-04-18 19:31:59 +02:00
|
|
|
self::$_method_visibility[$method_id] = $method->isPrivate() ?
|
|
|
|
self::VISIBILITY_PRIVATE :
|
|
|
|
($method->isProtected() ? self::VISIBILITY_PROTECTED : self::VISIBILITY_PUBLIC);
|
2016-04-16 22:28:25 +02:00
|
|
|
|
2016-08-05 21:11:20 +02:00
|
|
|
|
2016-04-16 22:28:25 +02:00
|
|
|
$params = $method->getParameters();
|
|
|
|
|
2016-06-25 00:18:11 +02:00
|
|
|
$method_param_names = [];
|
2016-07-10 22:09:09 +02:00
|
|
|
$method_param_types = [];
|
2016-06-25 00:18:11 +02:00
|
|
|
|
2016-04-16 22:28:25 +02:00
|
|
|
self::$_method_params[$method_id] = [];
|
|
|
|
foreach ($params as $param) {
|
2016-07-10 22:09:09 +02:00
|
|
|
$param_type_string = null;
|
2016-04-16 22:28:25 +02:00
|
|
|
|
|
|
|
if ($param->isArray()) {
|
2016-07-10 22:09:09 +02:00
|
|
|
$param_type_string = 'array';
|
2016-04-16 22:28:25 +02:00
|
|
|
|
2016-07-13 17:19:07 +02:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
$param_class = null;
|
|
|
|
|
|
|
|
try {
|
|
|
|
$param_class = $param->getClass();
|
|
|
|
}
|
|
|
|
catch (\ReflectionException $e) {
|
|
|
|
// do nothing
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($param_class && self::$_method_files[$method_id]) {
|
|
|
|
$param_type_string = $param->getClass()->getName();
|
|
|
|
}
|
2016-04-16 22:28:25 +02:00
|
|
|
}
|
|
|
|
|
2016-07-11 16:19:06 +02:00
|
|
|
$is_nullable = false;
|
|
|
|
|
2016-07-29 20:48:04 +02:00
|
|
|
$is_optional = $param->isOptional();
|
|
|
|
|
2016-04-16 22:28:25 +02:00
|
|
|
try {
|
|
|
|
$is_nullable = $param->getDefaultValue() === null;
|
2016-06-16 02:16:40 +02:00
|
|
|
|
2016-07-10 22:09:09 +02:00
|
|
|
if ($param_type_string && $is_nullable) {
|
|
|
|
$param_type_string .= '|null';
|
2016-06-16 02:16:40 +02:00
|
|
|
}
|
2016-04-16 22:28:25 +02:00
|
|
|
}
|
|
|
|
catch (\ReflectionException $e) {
|
|
|
|
// do nothing
|
|
|
|
}
|
|
|
|
|
2016-07-10 22:09:09 +02:00
|
|
|
$param_name = $param->getName();
|
|
|
|
$param_type = $param_type_string ? Type::parseString($param_type_string) : Type::getMixed();
|
|
|
|
|
|
|
|
$method_param_names[$param_name] = true;
|
|
|
|
$method_param_types[$param_name] = $param_type;
|
2016-06-25 00:18:11 +02:00
|
|
|
|
2016-04-16 22:28:25 +02:00
|
|
|
self::$_method_params[$method_id][] = [
|
2016-07-10 22:09:09 +02:00
|
|
|
'name' => $param_name,
|
2016-04-16 22:28:25 +02:00
|
|
|
'by_ref' => $param->isPassedByReference(),
|
2016-07-10 22:09:09 +02:00
|
|
|
'type' => $param_type,
|
2016-07-29 20:48:04 +02:00
|
|
|
'is_nullable' => $is_nullable,
|
|
|
|
'is_optional' => $is_optional,
|
2016-04-16 22:28:25 +02:00
|
|
|
];
|
|
|
|
}
|
|
|
|
|
2016-06-15 01:22:29 +02:00
|
|
|
$return_types = null;
|
2016-04-16 22:28:25 +02:00
|
|
|
|
2016-06-25 00:18:11 +02:00
|
|
|
$config = Config::getInstance();
|
2016-04-16 22:28:25 +02:00
|
|
|
|
2016-06-25 00:18:11 +02:00
|
|
|
$return_type = null;
|
|
|
|
|
2016-07-22 19:29:46 +02:00
|
|
|
$docblock_info = CommentChecker::extractDocblockInfo($method->getDocComment());
|
2016-06-25 00:18:11 +02:00
|
|
|
|
2016-07-22 19:29:46 +02:00
|
|
|
if ($docblock_info['deprecated']) {
|
|
|
|
self::$_deprecated_methods[$method_id] = true;
|
|
|
|
}
|
|
|
|
|
2016-08-05 21:11:20 +02:00
|
|
|
self::$_method_return_types[$method_id] = [];
|
2016-07-22 19:29:46 +02:00
|
|
|
self::$_method_suppress[$method_id] = $docblock_info['suppress'];
|
|
|
|
|
|
|
|
if ($config->use_docblock_types) {
|
2016-06-25 00:18:11 +02:00
|
|
|
if ($docblock_info['return_type']) {
|
2016-08-05 21:11:20 +02:00
|
|
|
|
2016-06-25 00:18:11 +02:00
|
|
|
$return_type = Type::parseString(
|
|
|
|
self::_fixUpReturnType($docblock_info['return_type'], $method_id)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($docblock_info['params']) {
|
|
|
|
foreach ($docblock_info['params'] as $docblock_param) {
|
2016-07-11 16:15:32 +02:00
|
|
|
$docblock_param_name = $docblock_param['name'];
|
2016-06-25 00:18:11 +02:00
|
|
|
|
2016-07-26 20:58:45 +02:00
|
|
|
if (isset($method_param_names[$docblock_param_name])) {
|
2016-07-10 22:09:09 +02:00
|
|
|
foreach (self::$_method_params[$method_id] as &$param_info) {
|
2016-07-11 16:15:32 +02:00
|
|
|
if ($param_info['name'] === $docblock_param_name) {
|
|
|
|
$docblock_param_type_string = $docblock_param['type'];
|
|
|
|
|
|
|
|
$existing_param_type = $param_info['type'];
|
|
|
|
|
|
|
|
$new_param_type = Type::parseString(
|
|
|
|
self::_fixUpReturnType($docblock_param_type_string, $method_id)
|
2016-07-10 22:09:09 +02:00
|
|
|
);
|
2016-07-11 16:15:32 +02:00
|
|
|
|
2016-07-12 01:57:01 +02:00
|
|
|
// only fix the type if we're dealing with an undefined or generic type
|
|
|
|
if ($existing_param_type->isMixed() || $new_param_type->hasGeneric()) {
|
|
|
|
$existing_param_type_nullable = $param_info['is_nullable'];
|
|
|
|
|
|
|
|
if ($existing_param_type_nullable && !$new_param_type->isNullable()) {
|
|
|
|
$new_param_type->types['null'] = Type::getNull(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
$param_info['type'] = $new_param_type;
|
2016-07-11 16:15:32 +02:00
|
|
|
}
|
|
|
|
|
2016-07-10 22:09:09 +02:00
|
|
|
}
|
|
|
|
}
|
2016-06-25 00:18:11 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-04-16 22:28:25 +02:00
|
|
|
}
|
2016-06-25 00:18:11 +02:00
|
|
|
|
|
|
|
self::$_method_return_types[$method_id] = $return_type;
|
2016-04-16 22:28:25 +02:00
|
|
|
}
|
|
|
|
|
2016-04-17 18:27:47 +02:00
|
|
|
protected static function _copyToChildMethod($method_id, $child_method_id)
|
2016-04-17 17:22:18 +02:00
|
|
|
{
|
2016-04-17 18:27:47 +02:00
|
|
|
if (!isset(self::$_have_registered[$method_id]) && !isset(self::$_have_reflected[$method_id])) {
|
|
|
|
self::extractReflectionMethodInfo($method_id);
|
|
|
|
}
|
|
|
|
|
2016-04-18 19:31:59 +02:00
|
|
|
if (self::$_method_visibility[$method_id] !== self::VISIBILITY_PRIVATE) {
|
|
|
|
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::$_static_methods[$child_method_id] = self::$_static_methods[$method_id];
|
|
|
|
self::$_method_visibility[$child_method_id] = self::$_method_visibility[$method_id];
|
2016-04-17 17:22:18 +02:00
|
|
|
|
2016-04-18 19:31:59 +02:00
|
|
|
self::$_declaring_classes[$child_method_id] = self::$_declaring_classes[$method_id];
|
|
|
|
self::$_existing_methods[$child_method_id] = 1;
|
|
|
|
}
|
2016-04-17 17:22:18 +02:00
|
|
|
}
|
|
|
|
|
2016-04-16 22:28:25 +02:00
|
|
|
/**
|
|
|
|
* Determines whether a given method is static or not
|
|
|
|
* @param string $method_id
|
|
|
|
*/
|
2016-07-22 19:29:46 +02:00
|
|
|
public static function checkMethodStatic($method_id, $file_name, $line_number, array $suppressed_issues)
|
2016-04-16 22:28:25 +02:00
|
|
|
{
|
2016-04-18 19:31:59 +02:00
|
|
|
self::_populateData($method_id);
|
2016-04-16 22:28:25 +02:00
|
|
|
|
2016-07-22 19:29:46 +02:00
|
|
|
if (!self::$_static_methods[$method_id]) {
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
new InvalidStaticInvocation('Method ' . $method_id . ' is not static', $file_name, $line_number),
|
|
|
|
$suppressed_issues
|
|
|
|
)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2016-04-16 22:28:25 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
protected function _registerMethod(PhpParser\Node\Stmt\ClassMethod $method)
|
|
|
|
{
|
|
|
|
$method_id = $this->_absolute_class . '::' . $method->name;
|
2016-05-22 18:14:48 +02:00
|
|
|
|
|
|
|
if (isset(self::$_have_reflected[$method_id]) || isset(self::$_have_registered[$method_id])) {
|
2016-07-22 19:29:46 +02:00
|
|
|
$this->_suppressed_issues = self::$_method_suppress[$method_id];
|
|
|
|
|
2016-05-22 18:14:48 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-04-17 18:27:47 +02:00
|
|
|
self::$_have_registered[$method_id] = true;
|
2016-04-16 22:28:25 +02:00
|
|
|
|
2016-05-22 18:14:48 +02:00
|
|
|
self::$_declaring_classes[$method_id] = $method_id;
|
2016-04-16 22:28:25 +02:00
|
|
|
self::$_static_methods[$method_id] = $method->isStatic();
|
|
|
|
|
|
|
|
self::$_method_namespaces[$method_id] = $this->_namespace;
|
|
|
|
self::$_method_files[$method_id] = $this->_file_name;
|
|
|
|
self::$_existing_methods[$method_id] = 1;
|
|
|
|
|
2016-06-25 00:18:11 +02:00
|
|
|
if ($method->isPrivate()) {
|
|
|
|
self::$_method_visibility[$method_id] = self::VISIBILITY_PRIVATE;
|
|
|
|
}
|
|
|
|
elseif ($method->isProtected()) {
|
|
|
|
self::$_method_visibility[$method_id] = self::VISIBILITY_PROTECTED;
|
2016-04-17 17:22:18 +02:00
|
|
|
}
|
2016-06-24 00:45:46 +02:00
|
|
|
else {
|
2016-06-25 00:18:11 +02:00
|
|
|
self::$_method_visibility[$method_id] = self::VISIBILITY_PUBLIC;
|
2016-04-16 22:28:25 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
self::$_method_params[$method_id] = [];
|
|
|
|
|
2016-06-25 00:18:11 +02:00
|
|
|
$method_param_names = [];
|
|
|
|
|
2016-04-16 22:28:25 +02:00
|
|
|
foreach ($method->getParams() as $param) {
|
2016-08-11 01:21:03 +02:00
|
|
|
$param_array = $this->getParamArray($param);
|
|
|
|
self::$_method_params[$method_id][] = $param_array;
|
|
|
|
$method_param_names[$param->name] = $param_array['type'];
|
2016-04-16 22:28:25 +02:00
|
|
|
}
|
2016-06-25 00:18:11 +02:00
|
|
|
|
|
|
|
$config = Config::getInstance();
|
|
|
|
$return_type = null;
|
|
|
|
|
2016-07-22 19:29:46 +02:00
|
|
|
$docblock_info = CommentChecker::extractDocblockInfo($method->getDocComment());
|
2016-06-25 00:18:11 +02:00
|
|
|
|
2016-07-22 19:29:46 +02:00
|
|
|
if ($docblock_info['deprecated']) {
|
|
|
|
self::$_deprecated_methods[$method_id] = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->_suppressed_issues = $docblock_info['suppress'];
|
2016-08-11 01:21:03 +02:00
|
|
|
self::$_method_suppress[$method_id] = $this->_suppressed_issues;
|
2016-07-22 19:29:46 +02:00
|
|
|
|
|
|
|
if ($config->use_docblock_types) {
|
2016-06-25 00:18:11 +02:00
|
|
|
if ($docblock_info['return_type']) {
|
2016-06-29 22:42:30 +02:00
|
|
|
$return_type =
|
|
|
|
Type::parseString(
|
2016-07-12 06:50:16 +02:00
|
|
|
$this->fixUpLocalType(
|
2016-06-29 22:42:30 +02:00
|
|
|
$docblock_info['return_type'],
|
2016-07-12 06:50:16 +02:00
|
|
|
$this->_absolute_class,
|
2016-06-29 22:42:30 +02:00
|
|
|
$this->_namespace,
|
|
|
|
$this->_aliased_classes
|
|
|
|
)
|
|
|
|
);
|
2016-06-25 00:18:11 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if ($docblock_info['params']) {
|
2016-08-11 01:21:03 +02:00
|
|
|
$this->improveParamsFromDocblock(
|
|
|
|
$docblock_info['params'],
|
|
|
|
$method_param_names,
|
|
|
|
self::$_method_params[$method_id],
|
|
|
|
$method->getLine()
|
|
|
|
);
|
2016-06-25 00:18:11 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
self::$_method_return_types[$method_id] = $return_type;
|
2016-04-16 22:28:25 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
protected static function _fixUpReturnType($return_type, $method_id)
|
|
|
|
{
|
|
|
|
if (strpos($return_type, '[') !== false) {
|
2016-07-12 06:50:16 +02:00
|
|
|
$return_type = Type::convertSquareBrackets($return_type);
|
2016-04-16 22:28:25 +02:00
|
|
|
}
|
|
|
|
|
2016-07-12 06:50:16 +02:00
|
|
|
$return_type_tokens = Type::tokenize($return_type);
|
2016-04-16 22:28:25 +02:00
|
|
|
|
|
|
|
foreach ($return_type_tokens as &$return_type_token) {
|
|
|
|
if ($return_type_token[0] === '\\') {
|
|
|
|
$return_type_token = substr($return_type_token, 1);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2016-06-14 07:23:57 +02:00
|
|
|
if (in_array($return_type_token, ['<', '>', '|'])) {
|
2016-04-16 22:28:25 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2016-07-12 06:50:16 +02:00
|
|
|
$return_type_token = Type::fixScalarTerms($return_type_token);
|
|
|
|
|
2016-04-16 22:28:25 +02:00
|
|
|
if ($return_type_token[0] === strtoupper($return_type_token[0])) {
|
|
|
|
$absolute_class = explode('::', $method_id)[0];
|
|
|
|
|
|
|
|
if ($return_type_token === '$this') {
|
|
|
|
$return_type_token = $absolute_class;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$return_type_token = FileChecker::getAbsoluteClassFromNameInFile($return_type_token, self::$_method_namespaces[$method_id], self::$_method_files[$method_id]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return implode('', $return_type_tokens);
|
|
|
|
}
|
|
|
|
|
2016-04-27 00:42:48 +02:00
|
|
|
/**
|
2016-07-24 22:21:42 +02:00
|
|
|
* @return bool|null
|
2016-04-27 00:42:48 +02:00
|
|
|
*/
|
2016-07-22 19:29:46 +02:00
|
|
|
public static function checkMethodExists($method_id, $file_name, $line_number, array $suppresssed_issues)
|
2016-04-16 22:28:25 +02:00
|
|
|
{
|
|
|
|
if (isset(self::$_existing_methods[$method_id])) {
|
2016-07-24 22:21:42 +02:00
|
|
|
return true;
|
2016-04-16 22:28:25 +02:00
|
|
|
}
|
|
|
|
|
2016-04-18 19:31:59 +02:00
|
|
|
$method_parts = explode('::', $method_id);
|
|
|
|
|
|
|
|
if (method_exists($method_parts[0], $method_parts[1])) {
|
2016-04-16 22:28:25 +02:00
|
|
|
self::$_existing_methods[$method_id] = 1;
|
2016-07-24 22:21:42 +02:00
|
|
|
return true;
|
2016-04-18 19:31:59 +02:00
|
|
|
}
|
2016-04-16 22:28:25 +02:00
|
|
|
|
2016-07-23 16:58:53 +02:00
|
|
|
if (isset(self::$_have_registered[$method_id])) {
|
|
|
|
self::$_existing_methods[$method_id] = 1;
|
2016-07-24 22:21:42 +02:00
|
|
|
return true;
|
2016-07-23 16:58:53 +02:00
|
|
|
}
|
|
|
|
|
2016-06-26 21:18:40 +02:00
|
|
|
if (IssueBuffer::accepts(
|
2016-07-22 19:29:46 +02:00
|
|
|
new UndefinedMethod('Method ' . $method_id . ' does not exist', $file_name, $line_number),
|
|
|
|
$suppresssed_issues
|
2016-06-06 02:25:16 +02:00
|
|
|
)) {
|
|
|
|
return false;
|
|
|
|
}
|
2016-04-18 19:31:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
protected static function _populateData($method_id)
|
|
|
|
{
|
2016-05-22 18:14:48 +02:00
|
|
|
if (!isset(self::$_have_reflected[$method_id]) && !isset(self::$_have_registered[$method_id])) {
|
2016-04-18 19:31:59 +02:00
|
|
|
if (isset(self::$_inherited_methods[$method_id])) {
|
|
|
|
self::_copyToChildMethod(self::$_inherited_methods[$method_id], $method_id);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
self::extractReflectionMethodInfo($method_id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-22 19:29:46 +02:00
|
|
|
public static function checkMethodNotDeprecated($method_id, $file_name, $line_number, array $suppresssed_issues)
|
|
|
|
{
|
|
|
|
self::_populateData($method_id);
|
|
|
|
|
|
|
|
if (isset(self::$_deprecated_methods[$method_id])) {
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
new DeprecatedMethod('The method ' . $method_id . ' has been marked as deprecated', $file_name, $line_number),
|
|
|
|
$suppresssed_issues
|
|
|
|
)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-27 00:42:48 +02:00
|
|
|
/**
|
2016-08-13 17:10:43 +02:00
|
|
|
* @param string $method_id
|
|
|
|
* @param string $calling_context
|
2016-08-13 17:28:06 +02:00
|
|
|
* @param StatementsSource $source
|
2016-08-13 17:10:43 +02:00
|
|
|
* @param int $line_number
|
|
|
|
* @param array $suppresssed_issues
|
2016-06-14 01:57:32 +02:00
|
|
|
* @return false|null
|
2016-04-27 00:42:48 +02:00
|
|
|
*/
|
2016-08-13 17:10:43 +02:00
|
|
|
public static function checkMethodVisibility($method_id, $calling_context, StatementsSource $source, $line_number, array $suppresssed_issues)
|
2016-04-18 19:31:59 +02:00
|
|
|
{
|
|
|
|
self::_populateData($method_id);
|
|
|
|
|
|
|
|
$method_class = explode('::', $method_id)[0];
|
2016-04-30 20:14:22 +02:00
|
|
|
$method_name = explode('::', $method_id)[1];
|
2016-04-18 19:31:59 +02:00
|
|
|
|
|
|
|
if (!isset(self::$_method_visibility[$method_id])) {
|
2016-06-26 21:18:40 +02:00
|
|
|
if (IssueBuffer::accepts(
|
2016-08-13 17:10:43 +02:00
|
|
|
new InaccessibleMethod('Cannot access method ' . $method_id, $source->getFileName(), $line_number),
|
2016-07-22 19:29:46 +02:00
|
|
|
$suppresssed_issues
|
2016-06-06 02:25:16 +02:00
|
|
|
)) {
|
|
|
|
return false;
|
|
|
|
}
|
2016-04-18 19:31:59 +02:00
|
|
|
}
|
|
|
|
|
2016-08-13 17:10:43 +02:00
|
|
|
if ($source->getSource() instanceof TraitChecker && $method_class === $source->getAbsoluteClass()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-04-18 19:31:59 +02:00
|
|
|
switch (self::$_method_visibility[$method_id]) {
|
|
|
|
case self::VISIBILITY_PUBLIC:
|
|
|
|
return;
|
|
|
|
|
|
|
|
case self::VISIBILITY_PRIVATE:
|
|
|
|
if (!$calling_context || $method_class !== $calling_context) {
|
2016-06-26 21:18:40 +02:00
|
|
|
if (IssueBuffer::accepts(
|
2016-07-22 19:29:46 +02:00
|
|
|
new InaccessibleMethod(
|
|
|
|
'Cannot access private method ' . $method_id . ' from context ' . $calling_context,
|
2016-08-13 17:10:43 +02:00
|
|
|
$source->getFileName(),
|
2016-07-22 19:29:46 +02:00
|
|
|
$line_number
|
|
|
|
),
|
|
|
|
$suppresssed_issues
|
2016-06-06 02:25:16 +02:00
|
|
|
)) {
|
|
|
|
return false;
|
|
|
|
}
|
2016-04-18 19:31:59 +02:00
|
|
|
}
|
|
|
|
return;
|
|
|
|
|
|
|
|
case self::VISIBILITY_PROTECTED:
|
|
|
|
if ($method_class === $calling_context) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$calling_context) {
|
2016-06-26 21:18:40 +02:00
|
|
|
if (IssueBuffer::accepts(
|
2016-08-13 17:10:43 +02:00
|
|
|
new InaccessibleMethod('Cannot access protected method ' . $method_id, $source->getFileName(), $line_number),
|
2016-07-22 19:29:46 +02:00
|
|
|
$suppresssed_issues
|
2016-06-06 02:25:16 +02:00
|
|
|
)) {
|
|
|
|
return false;
|
|
|
|
}
|
2016-04-18 19:31:59 +02:00
|
|
|
}
|
|
|
|
|
2016-07-25 05:40:23 +02:00
|
|
|
if (ClassChecker::classExtends($method_class, $calling_context) && method_exists($calling_context, $method_name)) {
|
2016-04-30 20:14:22 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-07-25 05:40:23 +02:00
|
|
|
if (!ClassChecker::classExtends($calling_context, $method_class)) {
|
2016-06-26 21:18:40 +02:00
|
|
|
if (IssueBuffer::accepts(
|
2016-07-22 19:29:46 +02:00
|
|
|
new InaccessibleMethod(
|
|
|
|
'Cannot access protected method ' . $method_id . ' from context ' . $calling_context,
|
2016-08-13 17:10:43 +02:00
|
|
|
$source->getFileName(),
|
2016-07-22 19:29:46 +02:00
|
|
|
$line_number
|
|
|
|
),
|
|
|
|
$suppresssed_issues
|
2016-06-06 02:25:16 +02:00
|
|
|
)) {
|
|
|
|
return false;
|
|
|
|
}
|
2016-04-18 19:31:59 +02:00
|
|
|
}
|
2016-04-16 22:28:25 +02:00
|
|
|
}
|
|
|
|
}
|
2016-04-17 18:27:47 +02:00
|
|
|
|
|
|
|
public static function registerInheritedMethod($parent_method_id, $method_id)
|
|
|
|
{
|
2016-05-22 18:14:48 +02:00
|
|
|
// only register the method if it's not already there
|
|
|
|
if (!isset(self::$_declaring_classes[$method_id])) {
|
|
|
|
self::$_declaring_classes[$method_id] = $parent_method_id;
|
|
|
|
}
|
|
|
|
|
2016-04-17 18:27:47 +02:00
|
|
|
self::$_inherited_methods[$method_id] = $parent_method_id;
|
|
|
|
}
|
2016-04-27 00:18:49 +02:00
|
|
|
|
2016-05-22 18:14:48 +02:00
|
|
|
public static function getDeclaringMethod($method_id)
|
2016-05-16 05:06:03 +02:00
|
|
|
{
|
2016-05-22 18:14:48 +02:00
|
|
|
if (isset(self::$_declaring_classes[$method_id])) {
|
|
|
|
return self::$_declaring_classes[$method_id];
|
2016-05-16 05:06:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$method_name = explode('::', $method_id)[1];
|
|
|
|
|
|
|
|
$parent_method_id = (new \ReflectionMethod($method_id))->getDeclaringClass()->getName() . '::' . $method_name;
|
|
|
|
|
2016-05-22 18:14:48 +02:00
|
|
|
self::$_declaring_classes[$method_id] = $parent_method_id;
|
2016-05-16 05:06:03 +02:00
|
|
|
|
|
|
|
return $parent_method_id;
|
|
|
|
}
|
|
|
|
|
2016-04-27 00:18:49 +02:00
|
|
|
public static function getNewDocblocksForFile($file_name)
|
|
|
|
{
|
|
|
|
return isset(self::$_new_docblocks[$file_name]) ? self::$_new_docblocks[$file_name] : [];
|
|
|
|
}
|
2016-06-16 02:16:40 +02:00
|
|
|
|
|
|
|
public static function clearCache()
|
|
|
|
{
|
|
|
|
self::$_method_comments = [];
|
|
|
|
self::$_method_files = [];
|
|
|
|
self::$_method_params = [];
|
|
|
|
self::$_method_namespaces = [];
|
|
|
|
self::$_method_return_types = [];
|
|
|
|
self::$_static_methods = [];
|
|
|
|
self::$_declaring_classes = [];
|
|
|
|
self::$_existing_methods = [];
|
|
|
|
self::$_have_reflected = [];
|
|
|
|
self::$_have_registered = [];
|
|
|
|
self::$_inherited_methods = [];
|
|
|
|
self::$_declaring_class = [];
|
|
|
|
self::$_method_visibility = [];
|
|
|
|
self::$_new_docblocks = [];
|
|
|
|
}
|
2016-02-04 15:22:46 +01:00
|
|
|
}
|