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-11-02 07:29:00 +01:00
|
|
|
use PhpParser;
|
|
|
|
use Psalm\Config;
|
|
|
|
use Psalm\Exception\DocblockParseException;
|
2016-07-26 00:37:44 +02:00
|
|
|
use Psalm\Issue\DeprecatedMethod;
|
2016-11-02 07:29:00 +01:00
|
|
|
use Psalm\Issue\InaccessibleMethod;
|
2016-07-26 00:37:44 +02:00
|
|
|
use Psalm\Issue\InvalidDocblock;
|
|
|
|
use Psalm\Issue\InvalidStaticInvocation;
|
2016-11-02 07:29:00 +01:00
|
|
|
use Psalm\Issue\UndefinedMethod;
|
|
|
|
use Psalm\IssueBuffer;
|
2016-08-13 20:20:46 +02:00
|
|
|
use Psalm\StatementsSource;
|
|
|
|
use Psalm\Type;
|
2016-01-08 00:28:27 +01:00
|
|
|
|
2016-08-15 21:14:28 +02:00
|
|
|
class MethodChecker extends FunctionLikeChecker
|
2016-01-08 00:28:27 +01:00
|
|
|
{
|
2016-11-02 07:29:00 +01:00
|
|
|
const VISIBILITY_PUBLIC = 1;
|
|
|
|
const VISIBILITY_PROTECTED = 2;
|
|
|
|
const VISIBILITY_PRIVATE = 3;
|
|
|
|
|
2016-10-15 06:12:57 +02:00
|
|
|
/**
|
|
|
|
* @var array<string,string>
|
|
|
|
*/
|
2016-08-14 00:54:49 +02:00
|
|
|
protected static $method_files = [];
|
2016-10-15 06:12:57 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @var array<string,array<\Psalm\FunctionLikeParameter>>
|
|
|
|
*/
|
2016-08-14 00:54:49 +02:00
|
|
|
protected static $method_params = [];
|
2016-10-15 06:12:57 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @var array<string,string>
|
|
|
|
*/
|
2016-08-14 00:54:49 +02:00
|
|
|
protected static $method_namespaces = [];
|
2016-10-23 07:57:11 +02:00
|
|
|
|
2016-11-02 07:29:00 +01:00
|
|
|
/**
|
|
|
|
* @var array<string,Type\Union>
|
|
|
|
*/
|
2016-08-14 00:54:49 +02:00
|
|
|
protected static $method_return_types = [];
|
2016-11-01 05:39:41 +01:00
|
|
|
|
2016-11-02 07:29:00 +01:00
|
|
|
/**
|
|
|
|
* @var array<string, string>
|
|
|
|
*/
|
2016-08-15 19:37:21 +02:00
|
|
|
protected static $cased_method_ids = [];
|
2016-11-01 05:39:41 +01:00
|
|
|
|
2016-11-02 07:29:00 +01:00
|
|
|
/**
|
|
|
|
* @var array<string, bool>
|
|
|
|
*/
|
2016-08-14 00:54:49 +02:00
|
|
|
protected static $static_methods = [];
|
2016-10-15 06:12:57 +02:00
|
|
|
|
|
|
|
/**
|
2016-11-01 05:39:41 +01:00
|
|
|
* @var array<string, string>
|
2016-10-15 06:12:57 +02:00
|
|
|
*/
|
2016-08-15 05:24:16 +02:00
|
|
|
protected static $declaring_methods = [];
|
2016-10-15 06:12:57 +02:00
|
|
|
|
|
|
|
/**
|
2016-11-01 05:39:41 +01:00
|
|
|
* @var array<string, array<string>>
|
2016-10-15 06:12:57 +02:00
|
|
|
*/
|
|
|
|
protected static $overridden_methods = [];
|
2016-11-01 05:39:41 +01:00
|
|
|
|
2016-11-02 07:29:00 +01:00
|
|
|
/**
|
|
|
|
* @var array<string, bool>
|
|
|
|
*/
|
2016-08-14 00:54:49 +02:00
|
|
|
protected static $have_reflected = [];
|
2016-11-01 05:39:41 +01:00
|
|
|
|
2016-11-02 07:29:00 +01:00
|
|
|
/**
|
|
|
|
* @var array<string, bool>
|
|
|
|
*/
|
2016-08-14 00:54:49 +02:00
|
|
|
protected static $have_registered = [];
|
2016-11-01 05:39:41 +01:00
|
|
|
|
2016-11-02 07:29:00 +01:00
|
|
|
/**
|
|
|
|
* @var array<string, string>
|
|
|
|
*/
|
2016-08-14 00:54:49 +02:00
|
|
|
protected static $method_visibility = [];
|
2016-11-01 05:39:41 +01:00
|
|
|
|
2016-11-02 07:29:00 +01:00
|
|
|
/**
|
|
|
|
* @var array<string, array<int, string>>
|
|
|
|
*/
|
2016-08-14 00:54:49 +02:00
|
|
|
protected static $method_suppress = [];
|
2016-11-01 05:39:41 +01:00
|
|
|
|
2016-11-02 07:29:00 +01:00
|
|
|
/**
|
|
|
|
* @var array<string, bool>
|
|
|
|
*/
|
2016-08-14 00:54:49 +02:00
|
|
|
protected static $deprecated_methods = [];
|
2016-04-18 19:31:59 +02:00
|
|
|
|
2016-10-18 23:55:07 +02:00
|
|
|
/**
|
|
|
|
* A dictionary of variadic methods
|
2016-11-02 07:29:00 +01:00
|
|
|
*
|
2016-11-01 05:39:41 +01:00
|
|
|
* @var array<string, bool>
|
2016-10-18 23:55:07 +02:00
|
|
|
*/
|
|
|
|
protected static $variadic_methods = [];
|
|
|
|
|
2016-10-30 17:46:18 +01:00
|
|
|
/**
|
|
|
|
* @param PhpParser\Node\FunctionLike $function
|
|
|
|
* @param StatementsSource $source
|
|
|
|
* @param array $this_vars
|
|
|
|
*/
|
|
|
|
public function __construct($function, StatementsSource $source, array $this_vars = [])
|
2016-04-16 22:28:25 +02:00
|
|
|
{
|
2016-10-15 06:12:57 +02:00
|
|
|
if (!$function instanceof PhpParser\Node\Stmt\ClassMethod) {
|
|
|
|
throw new \InvalidArgumentException('Must be called with a ClassMethod');
|
|
|
|
}
|
|
|
|
|
2016-04-16 22:28:25 +02:00
|
|
|
parent::__construct($function, $source);
|
|
|
|
|
2016-10-15 06:12:57 +02:00
|
|
|
$this->registerMethod($function);
|
|
|
|
$this->is_static = $function->isStatic();
|
2016-04-16 22:28:25 +02:00
|
|
|
}
|
|
|
|
|
2016-10-15 06:12:57 +02:00
|
|
|
/**
|
|
|
|
* @param string $method_id
|
2016-11-07 05:29:54 +01:00
|
|
|
* @return array<\Psalm\FunctionLikeParameter>|null
|
2016-10-15 06:12:57 +02:00
|
|
|
*/
|
2016-04-16 22:28:25 +02:00
|
|
|
public static function getMethodParams($method_id)
|
|
|
|
{
|
2016-08-15 05:24:16 +02:00
|
|
|
self::registerClassMethod($method_id);
|
|
|
|
|
2016-11-07 05:29:54 +01:00
|
|
|
if ($method_id = self::getDeclaringMethodId($method_id)) {
|
|
|
|
return self::$method_params[$method_id];
|
|
|
|
}
|
2016-04-16 22:28:25 +02:00
|
|
|
}
|
|
|
|
|
2016-10-18 23:55:07 +02:00
|
|
|
/**
|
|
|
|
* @param string $method_id
|
|
|
|
* @return boolean
|
|
|
|
*/
|
|
|
|
public static function isVariadic($method_id)
|
|
|
|
{
|
|
|
|
self::registerClassMethod($method_id);
|
|
|
|
|
|
|
|
$method_id = self::getDeclaringMethodId($method_id);
|
|
|
|
|
|
|
|
return isset(self::$variadic_methods[$method_id]);
|
|
|
|
}
|
|
|
|
|
2016-10-09 23:54:58 +02:00
|
|
|
/**
|
|
|
|
* @param string $method_id
|
|
|
|
* @return Type\Union|null
|
|
|
|
*/
|
2016-04-16 22:28:25 +02:00
|
|
|
public static function getMethodReturnTypes($method_id)
|
|
|
|
{
|
2016-08-15 05:24:16 +02:00
|
|
|
self::registerClassMethod($method_id);
|
|
|
|
|
2016-11-07 05:29:54 +01:00
|
|
|
/** @var string */
|
2016-10-15 06:12:57 +02:00
|
|
|
$method_id = self::getDeclaringMethodId($method_id);
|
2016-04-16 22:28:25 +02:00
|
|
|
|
2016-10-15 06:12:57 +02:00
|
|
|
if (self::$method_return_types[$method_id]) {
|
|
|
|
return clone self::$method_return_types[$method_id];
|
|
|
|
}
|
|
|
|
|
|
|
|
$overridden_method_ids = self::getOverriddenMethodIds($method_id);
|
|
|
|
|
|
|
|
foreach ($overridden_method_ids as $overridden_method_id) {
|
|
|
|
if (self::$method_return_types[$overridden_method_id]) {
|
2016-10-16 00:55:17 +02:00
|
|
|
$implementary_return_type = self::$method_return_types[$overridden_method_id];
|
|
|
|
|
|
|
|
if ($implementary_return_type->isNull()) {
|
|
|
|
return Type::getVoid();
|
|
|
|
}
|
|
|
|
|
|
|
|
return $implementary_return_type;
|
2016-10-15 06:12:57 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
2016-04-16 22:28:25 +02:00
|
|
|
}
|
|
|
|
|
2016-04-27 00:42:48 +02:00
|
|
|
/**
|
2016-11-02 07:29:00 +01:00
|
|
|
* @param \ReflectionMethod $method
|
|
|
|
* @return null
|
2016-04-27 00:42:48 +02:00
|
|
|
*/
|
2016-08-15 05:24:16 +02:00
|
|
|
public static function extractReflectionMethodInfo(\ReflectionMethod $method)
|
2016-04-16 22:28:25 +02:00
|
|
|
{
|
2016-10-15 06:12:57 +02:00
|
|
|
$method_id = $method->class . '::' . strtolower((string)$method->name);
|
2016-08-15 19:37:21 +02:00
|
|
|
self::$cased_method_ids[$method_id] = $method->class . '::' . $method->name;
|
2016-06-17 22:58:15 +02:00
|
|
|
|
2016-08-15 05:24:16 +02:00
|
|
|
if (isset(self::$have_reflected[$method_id])) {
|
2016-11-02 07:29:00 +01:00
|
|
|
return null;
|
2016-06-17 22:58:15 +02:00
|
|
|
}
|
|
|
|
|
2016-10-15 06:12:57 +02:00
|
|
|
/** @var \ReflectionClass */
|
|
|
|
$declaring_class = $method->getDeclaringClass();
|
|
|
|
|
2016-08-14 00:54:49 +02:00
|
|
|
self::$have_reflected[$method_id] = true;
|
2016-04-16 22:28:25 +02:00
|
|
|
|
2016-08-14 00:54:49 +02:00
|
|
|
self::$static_methods[$method_id] = $method->isStatic();
|
|
|
|
self::$method_files[$method_id] = $method->getFileName();
|
2016-10-15 06:12:57 +02:00
|
|
|
self::$method_namespaces[$method_id] = $declaring_class->getNamespaceName();
|
|
|
|
self::$declaring_methods[$method_id] = $declaring_class->name . '::' . strtolower((string)$method->getName());
|
2016-11-02 07:29:00 +01:00
|
|
|
self::$method_visibility[$method_id] = $method->isPrivate()
|
|
|
|
? self::VISIBILITY_PRIVATE
|
|
|
|
: ($method->isProtected() ? self::VISIBILITY_PROTECTED : self::VISIBILITY_PUBLIC);
|
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-08-14 00:54:49 +02:00
|
|
|
self::$method_params[$method_id] = [];
|
2016-07-29 20:48:04 +02:00
|
|
|
|
2016-10-15 06:12:57 +02:00
|
|
|
/** @var \ReflectionParameter $param */
|
2016-08-14 05:26:45 +02:00
|
|
|
foreach ($params as $param) {
|
|
|
|
$param_array = self::getReflectionParamArray($param);
|
|
|
|
self::$method_params[$method_id][] = $param_array;
|
|
|
|
$method_param_names[$param->name] = true;
|
2016-10-09 23:54:58 +02:00
|
|
|
$method_param_types[$param->name] = $param_array->type;
|
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-08-14 00:54:49 +02:00
|
|
|
self::$method_return_types[$method_id] = $return_type;
|
2016-11-02 07:29:00 +01:00
|
|
|
return null;
|
2016-04-16 22:28:25 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Determines whether a given method is static or not
|
2016-11-02 07:29:00 +01:00
|
|
|
*
|
2016-11-01 05:39:41 +01:00
|
|
|
* @param string $method_id
|
|
|
|
* @param string $file_name
|
|
|
|
* @param int $line_number
|
|
|
|
* @param array<int, string> $suppressed_issues
|
2016-11-02 07:29:00 +01:00
|
|
|
* @return bool
|
2016-04-16 22:28:25 +02:00
|
|
|
*/
|
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-08-15 05:24:16 +02:00
|
|
|
self::registerClassMethod($method_id);
|
|
|
|
|
2016-11-07 05:29:54 +01:00
|
|
|
/** @var string */
|
2016-10-15 06:12:57 +02:00
|
|
|
$method_id = self::getDeclaringMethodId($method_id);
|
2016-04-16 22:28:25 +02:00
|
|
|
|
2016-08-14 00:54:49 +02:00
|
|
|
if (!self::$static_methods[$method_id]) {
|
2016-07-22 19:29:46 +02:00
|
|
|
if (IssueBuffer::accepts(
|
2016-11-01 05:39:41 +01:00
|
|
|
new InvalidStaticInvocation(
|
|
|
|
'Method ' . MethodChecker::getCasedMethodId($method_id) . ' is not static',
|
|
|
|
$file_name,
|
|
|
|
$line_number
|
|
|
|
),
|
2016-07-22 19:29:46 +02:00
|
|
|
$suppressed_issues
|
|
|
|
)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2016-11-02 07:29:00 +01:00
|
|
|
|
|
|
|
return true;
|
2016-04-16 22:28:25 +02:00
|
|
|
}
|
|
|
|
|
2016-11-02 07:29:00 +01:00
|
|
|
/**
|
|
|
|
* @param PhpParser\Node\Stmt\ClassMethod $method
|
2016-11-05 01:48:01 +01:00
|
|
|
* @return null|false
|
2016-11-02 07:29:00 +01:00
|
|
|
*/
|
2016-08-14 00:54:49 +02:00
|
|
|
protected function registerMethod(PhpParser\Node\Stmt\ClassMethod $method)
|
2016-04-16 22:28:25 +02:00
|
|
|
{
|
2016-11-07 23:29:51 +01:00
|
|
|
$method_id = $this->fq_class_name . '::' . strtolower($method->name);
|
|
|
|
$cased_method_id = self::$cased_method_ids[$method_id] = $this->fq_class_name . '::' . $method->name;
|
2016-05-22 18:14:48 +02:00
|
|
|
|
2016-08-14 00:54:49 +02:00
|
|
|
if (isset(self::$have_reflected[$method_id]) || isset(self::$have_registered[$method_id])) {
|
|
|
|
$this->suppressed_issues = self::$method_suppress[$method_id];
|
2016-07-22 19:29:46 +02:00
|
|
|
|
2016-11-02 07:29:00 +01:00
|
|
|
return null;
|
2016-05-22 18:14:48 +02:00
|
|
|
}
|
|
|
|
|
2016-08-14 00:54:49 +02:00
|
|
|
self::$have_registered[$method_id] = true;
|
2016-04-16 22:28:25 +02:00
|
|
|
|
2016-08-15 05:24:16 +02:00
|
|
|
self::$declaring_methods[$method_id] = $method_id;
|
2016-08-14 00:54:49 +02:00
|
|
|
self::$static_methods[$method_id] = $method->isStatic();
|
2016-04-16 22:28:25 +02:00
|
|
|
|
2016-08-14 00:54:49 +02:00
|
|
|
self::$method_namespaces[$method_id] = $this->namespace;
|
|
|
|
self::$method_files[$method_id] = $this->file_name;
|
2016-04-16 22:28:25 +02:00
|
|
|
|
2016-06-25 00:18:11 +02:00
|
|
|
if ($method->isPrivate()) {
|
2016-08-14 00:54:49 +02:00
|
|
|
self::$method_visibility[$method_id] = self::VISIBILITY_PRIVATE;
|
2016-11-02 07:29:00 +01:00
|
|
|
} elseif ($method->isProtected()) {
|
2016-08-14 00:54:49 +02:00
|
|
|
self::$method_visibility[$method_id] = self::VISIBILITY_PROTECTED;
|
2016-11-02 07:29:00 +01:00
|
|
|
} else {
|
2016-08-14 00:54:49 +02:00
|
|
|
self::$method_visibility[$method_id] = self::VISIBILITY_PUBLIC;
|
2016-04-16 22:28:25 +02:00
|
|
|
}
|
|
|
|
|
2016-08-14 00:54:49 +02:00
|
|
|
self::$method_params[$method_id] = [];
|
2016-04-16 22:28:25 +02:00
|
|
|
|
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-11-02 07:29:00 +01:00
|
|
|
$param_array = $this->getTranslatedParam(
|
|
|
|
$param,
|
2016-11-07 23:29:51 +01:00
|
|
|
$this->fq_class_name,
|
2016-11-02 07:29:00 +01:00
|
|
|
$this->namespace,
|
|
|
|
$this->getAliasedClasses()
|
|
|
|
);
|
|
|
|
|
2016-08-14 00:54:49 +02:00
|
|
|
self::$method_params[$method_id][] = $param_array;
|
2016-10-09 23:54:58 +02:00
|
|
|
$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-10-15 06:12:57 +02:00
|
|
|
$doc_comment = $method->getDocComment();
|
2016-06-25 00:18:11 +02:00
|
|
|
|
2016-10-15 06:12:57 +02:00
|
|
|
self::$method_suppress[$method_id] = [];
|
2016-07-22 19:29:46 +02:00
|
|
|
|
2016-10-19 03:54:08 +02:00
|
|
|
if (isset($method->returnType)) {
|
2016-10-23 07:57:11 +02:00
|
|
|
$return_type = Type::parseString(
|
|
|
|
is_string($method->returnType)
|
|
|
|
? $method->returnType
|
2016-11-07 23:31:02 +01:00
|
|
|
: ClassLikeChecker::getFullyQualifiedClassFromName(
|
2016-11-02 07:29:00 +01:00
|
|
|
$method->returnType,
|
|
|
|
$this->namespace,
|
|
|
|
$this->getAliasedClasses()
|
|
|
|
)
|
2016-10-23 07:57:11 +02:00
|
|
|
);
|
2016-10-19 03:54:08 +02:00
|
|
|
}
|
|
|
|
|
2016-10-15 06:12:57 +02:00
|
|
|
if ($doc_comment) {
|
2016-10-28 16:54:20 +02:00
|
|
|
$docblock_info = null;
|
2016-10-15 06:12:57 +02:00
|
|
|
|
2016-10-28 16:54:20 +02:00
|
|
|
try {
|
|
|
|
$docblock_info = CommentChecker::extractDocblockInfo((string)$doc_comment);
|
2016-11-02 07:29:00 +01:00
|
|
|
} catch (DocblockParseException $e) {
|
2016-10-28 16:54:20 +02:00
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
new InvalidDocblock(
|
2016-11-07 23:07:59 +01:00
|
|
|
'Invalid type passed in docblock for ' . $cased_method_id,
|
2016-10-28 16:54:20 +02:00
|
|
|
$this->getCheckedFileName(),
|
2016-10-28 16:58:49 +02:00
|
|
|
$method->getLine()
|
2016-10-28 16:54:20 +02:00
|
|
|
)
|
|
|
|
)) {
|
|
|
|
return false;
|
|
|
|
}
|
2016-10-18 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
2016-10-28 16:54:20 +02:00
|
|
|
if ($docblock_info) {
|
|
|
|
if ($docblock_info['deprecated']) {
|
|
|
|
self::$deprecated_methods[$method_id] = true;
|
2016-10-15 06:12:57 +02:00
|
|
|
}
|
|
|
|
|
2016-10-28 16:54:20 +02:00
|
|
|
if ($docblock_info['variadic']) {
|
|
|
|
self::$variadic_methods[$method_id] = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->suppressed_issues = $docblock_info['suppress'];
|
|
|
|
self::$method_suppress[$method_id] = $this->suppressed_issues;
|
|
|
|
|
|
|
|
if ($config->use_docblock_types) {
|
|
|
|
if ($docblock_info['return_type']) {
|
2016-11-02 07:29:00 +01:00
|
|
|
$return_type = Type::parseString(
|
|
|
|
$this->fixUpLocalType(
|
|
|
|
(string)$docblock_info['return_type'],
|
2016-11-07 23:29:51 +01:00
|
|
|
$this->fq_class_name,
|
2016-11-02 07:29:00 +01:00
|
|
|
$this->namespace,
|
|
|
|
$this->getAliasedClasses()
|
|
|
|
)
|
|
|
|
);
|
2016-10-28 16:54:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if ($docblock_info['params']) {
|
|
|
|
$this->improveParamsFromDocblock(
|
|
|
|
$docblock_info['params'],
|
|
|
|
$method_param_names,
|
|
|
|
self::$method_params[$method_id],
|
|
|
|
$method->getLine()
|
|
|
|
);
|
|
|
|
}
|
2016-10-15 06:12:57 +02:00
|
|
|
}
|
2016-06-25 00:18:11 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-14 00:54:49 +02:00
|
|
|
self::$method_return_types[$method_id] = $return_type;
|
2016-11-02 07:29:00 +01:00
|
|
|
return null;
|
2016-04-16 22:28:25 +02:00
|
|
|
}
|
|
|
|
|
2016-10-15 06:12:57 +02:00
|
|
|
/**
|
|
|
|
* @param string $return_type
|
|
|
|
* @param string $method_id
|
|
|
|
* @return string
|
|
|
|
*/
|
2016-08-14 00:54:49 +02:00
|
|
|
protected static function fixUpReturnType($return_type, $method_id)
|
2016-04-16 22:28:25 +02:00
|
|
|
{
|
|
|
|
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
|
|
|
|
2016-10-03 22:39:42 +02:00
|
|
|
foreach ($return_type_tokens as $i => &$return_type_token) {
|
2016-04-16 22:28:25 +02:00
|
|
|
if ($return_type_token[0] === '\\') {
|
|
|
|
$return_type_token = substr($return_type_token, 1);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2016-10-03 22:39:42 +02:00
|
|
|
if (in_array($return_type_token, ['<', '>', '|', '?', ',', '{', '}', ':'])) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isset($return_type_token[$i + 1]) && $return_type_token[$i + 1] === ':') {
|
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])) {
|
2016-11-07 23:29:51 +01:00
|
|
|
$fq_class_name = explode('::', $method_id)[0];
|
2016-04-16 22:28:25 +02:00
|
|
|
|
|
|
|
if ($return_type_token === '$this') {
|
2016-11-07 23:29:51 +01:00
|
|
|
$return_type_token = $fq_class_name;
|
2016-04-16 22:28:25 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2016-11-07 23:31:02 +01:00
|
|
|
$return_type_token = FileChecker::getFullyQualifiedClassFromNameInFile(
|
2016-10-15 06:12:57 +02:00
|
|
|
$return_type_token,
|
|
|
|
self::$method_namespaces[$method_id],
|
|
|
|
self::$method_files[$method_id]
|
|
|
|
);
|
2016-04-16 22:28:25 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return implode('', $return_type_tokens);
|
|
|
|
}
|
|
|
|
|
2016-04-27 00:42:48 +02:00
|
|
|
/**
|
2016-10-15 06:12:57 +02:00
|
|
|
* @param string $method_id
|
|
|
|
* @param string $file_name
|
|
|
|
* @param int $line_number
|
2016-11-02 07:29:00 +01:00
|
|
|
* @param array $suppressed_issues
|
2016-07-24 22:21:42 +02:00
|
|
|
* @return bool|null
|
2016-04-27 00:42:48 +02:00
|
|
|
*/
|
2016-11-02 07:29:00 +01:00
|
|
|
public static function checkMethodExists($method_id, $file_name, $line_number, array $suppressed_issues)
|
2016-10-23 18:24:53 +02:00
|
|
|
{
|
|
|
|
if (self::methodExists($method_id)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
new UndefinedMethod('Method ' . $method_id . ' does not exist', $file_name, $line_number),
|
2016-11-02 07:29:00 +01:00
|
|
|
$suppressed_issues
|
2016-10-23 18:24:53 +02:00
|
|
|
)) {
|
|
|
|
return false;
|
|
|
|
}
|
2016-11-02 07:29:00 +01:00
|
|
|
|
|
|
|
return null;
|
2016-10-23 18:24:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Whether or not a given method exists
|
2016-11-02 07:29:00 +01:00
|
|
|
*
|
2016-10-23 18:24:53 +02:00
|
|
|
* @param string $method_id
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public static function methodExists($method_id)
|
2016-04-16 22:28:25 +02:00
|
|
|
{
|
2016-08-15 19:59:02 +02:00
|
|
|
// remove trailing backslash if it exists
|
|
|
|
$method_id = preg_replace('/^\\\\/', '', $method_id);
|
2016-08-15 19:37:21 +02:00
|
|
|
$method_parts = explode('::', $method_id);
|
2016-10-18 23:55:07 +02:00
|
|
|
$method_parts[1] = strtolower($method_parts[1]);
|
|
|
|
$method_id = implode('::', $method_parts);
|
2016-08-15 19:37:21 +02:00
|
|
|
|
2016-08-15 05:24:16 +02:00
|
|
|
self::registerClassMethod($method_id);
|
2016-04-18 19:31:59 +02:00
|
|
|
|
2016-08-15 05:24:16 +02:00
|
|
|
if (isset(self::$declaring_methods[$method_id])) {
|
2016-07-24 22:21:42 +02:00
|
|
|
return true;
|
2016-07-23 16:58:53 +02:00
|
|
|
}
|
|
|
|
|
2016-10-18 23:55:07 +02:00
|
|
|
if ($method_parts[1] === '__construct') {
|
2016-11-02 07:29:00 +01:00
|
|
|
// @todo ?
|
2016-10-18 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
2016-10-22 23:35:59 +02:00
|
|
|
if (FunctionChecker::inCallMap($method_id)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-10-23 18:24:53 +02:00
|
|
|
return false;
|
2016-04-18 19:31:59 +02:00
|
|
|
}
|
|
|
|
|
2016-10-15 06:12:57 +02:00
|
|
|
/**
|
|
|
|
* @param string $method_id
|
|
|
|
* @return void
|
|
|
|
*/
|
2016-08-15 08:12:27 +02:00
|
|
|
public static function registerClassMethod($method_id)
|
2016-04-18 19:31:59 +02:00
|
|
|
{
|
2016-08-15 05:24:16 +02:00
|
|
|
ClassLikeChecker::registerClass(explode('::', $method_id)[0]);
|
2016-04-18 19:31:59 +02:00
|
|
|
}
|
|
|
|
|
2016-10-15 06:12:57 +02:00
|
|
|
/**
|
|
|
|
* @param string $method_id
|
|
|
|
* @param string $file_name
|
|
|
|
* @param int $line_number
|
2016-11-02 07:29:00 +01:00
|
|
|
* @param array $suppressed_issues
|
2016-10-15 06:12:57 +02:00
|
|
|
* @return false|null
|
|
|
|
*/
|
2016-11-02 07:29:00 +01:00
|
|
|
public static function checkMethodNotDeprecated($method_id, $file_name, $line_number, array $suppressed_issues)
|
2016-07-22 19:29:46 +02:00
|
|
|
{
|
2016-08-15 05:24:16 +02:00
|
|
|
self::registerClassMethod($method_id);
|
2016-07-22 19:29:46 +02:00
|
|
|
|
2016-08-14 00:54:49 +02:00
|
|
|
if (isset(self::$deprecated_methods[$method_id])) {
|
2016-07-22 19:29:46 +02:00
|
|
|
if (IssueBuffer::accepts(
|
2016-11-02 07:29:00 +01:00
|
|
|
new DeprecatedMethod(
|
|
|
|
'The method ' . MethodChecker::getCasedMethodId($method_id) . ' has been marked as deprecated',
|
|
|
|
$file_name,
|
|
|
|
$line_number
|
|
|
|
),
|
|
|
|
$suppressed_issues
|
2016-07-22 19:29:46 +02:00
|
|
|
)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2016-11-02 07:29:00 +01:00
|
|
|
|
|
|
|
return null;
|
2016-07-22 19:29:46 +02:00
|
|
|
}
|
|
|
|
|
2016-04-27 00:42:48 +02:00
|
|
|
/**
|
2016-08-13 17:10:43 +02:00
|
|
|
* @param string $method_id
|
2016-11-01 05:39:41 +01:00
|
|
|
* @param string|null $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
|
2016-11-02 07:29:00 +01:00
|
|
|
* @param array $suppressed_issues
|
2016-06-14 01:57:32 +02:00
|
|
|
* @return false|null
|
2016-04-27 00:42:48 +02:00
|
|
|
*/
|
2016-11-02 07:29:00 +01:00
|
|
|
public static function checkMethodVisibility(
|
|
|
|
$method_id,
|
|
|
|
$calling_context,
|
|
|
|
StatementsSource $source,
|
|
|
|
$line_number,
|
|
|
|
array $suppressed_issues
|
|
|
|
) {
|
2016-08-15 05:24:16 +02:00
|
|
|
self::registerClassMethod($method_id);
|
|
|
|
|
2016-10-15 06:12:57 +02:00
|
|
|
$declared_method_id = self::getDeclaringMethodId($method_id);
|
2016-04-18 19:31:59 +02:00
|
|
|
|
|
|
|
$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
|
|
|
|
2016-08-15 05:24:16 +02:00
|
|
|
if (!isset(self::$method_visibility[$declared_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-11-02 07:29:00 +01:00
|
|
|
$suppressed_issues
|
2016-06-06 02:25:16 +02:00
|
|
|
)) {
|
|
|
|
return false;
|
|
|
|
}
|
2016-04-18 19:31:59 +02:00
|
|
|
}
|
|
|
|
|
2016-11-07 23:31:02 +01:00
|
|
|
if ($source->getSource() instanceof TraitChecker && $method_class === $source->getFullyQualifiedClass()) {
|
2016-11-02 07:29:00 +01:00
|
|
|
return null;
|
2016-08-13 17:10:43 +02:00
|
|
|
}
|
|
|
|
|
2016-08-15 05:24:16 +02:00
|
|
|
switch (self::$method_visibility[$declared_method_id]) {
|
2016-04-18 19:31:59 +02:00
|
|
|
case self::VISIBILITY_PUBLIC:
|
2016-11-02 07:29:00 +01:00
|
|
|
return null;
|
2016-04-18 19:31:59 +02:00
|
|
|
|
|
|
|
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(
|
2016-11-02 07:29:00 +01:00
|
|
|
'Cannot access private method ' . MethodChecker::getCasedMethodId($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
|
|
|
|
),
|
2016-11-02 07:29:00 +01:00
|
|
|
$suppressed_issues
|
2016-06-06 02:25:16 +02:00
|
|
|
)) {
|
|
|
|
return false;
|
|
|
|
}
|
2016-04-18 19:31:59 +02:00
|
|
|
}
|
2016-11-02 07:29:00 +01:00
|
|
|
|
|
|
|
return null;
|
2016-04-18 19:31:59 +02:00
|
|
|
|
|
|
|
case self::VISIBILITY_PROTECTED:
|
|
|
|
if ($method_class === $calling_context) {
|
2016-11-02 07:29:00 +01:00
|
|
|
return null;
|
2016-04-18 19:31:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!$calling_context) {
|
2016-06-26 21:18:40 +02:00
|
|
|
if (IssueBuffer::accepts(
|
2016-11-02 07:29:00 +01:00
|
|
|
new InaccessibleMethod(
|
|
|
|
'Cannot access protected method ' . $method_id,
|
|
|
|
$source->getFileName(),
|
|
|
|
$line_number
|
|
|
|
),
|
|
|
|
$suppressed_issues
|
2016-06-06 02:25:16 +02:00
|
|
|
)) {
|
|
|
|
return false;
|
|
|
|
}
|
2016-11-01 05:39:41 +01:00
|
|
|
|
2016-11-02 07:29:00 +01:00
|
|
|
return null;
|
2016-04-18 19:31:59 +02:00
|
|
|
}
|
|
|
|
|
2016-11-02 07:29:00 +01:00
|
|
|
if (ClassChecker::classExtends($method_class, $calling_context) &&
|
|
|
|
MethodChecker::methodExists($calling_context . '::' . $method_name)
|
|
|
|
) {
|
|
|
|
return null;
|
2016-04-30 20:14:22 +02:00
|
|
|
}
|
|
|
|
|
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(
|
2016-11-02 07:29:00 +01:00
|
|
|
'Cannot access protected method ' . MethodChecker::getCasedMethodId($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
|
|
|
|
),
|
2016-11-02 07:29:00 +01:00
|
|
|
$suppressed_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-11-02 07:29:00 +01:00
|
|
|
|
|
|
|
return null;
|
2016-04-16 22:28:25 +02:00
|
|
|
}
|
2016-04-17 18:27:47 +02:00
|
|
|
|
2016-10-15 06:12:57 +02:00
|
|
|
/**
|
|
|
|
* @param string $method_id
|
|
|
|
* @param string $declaring_method_id
|
2016-11-02 07:29:00 +01:00
|
|
|
* @return void
|
2016-10-15 06:12:57 +02:00
|
|
|
*/
|
|
|
|
public static function setDeclaringMethodId($method_id, $declaring_method_id)
|
2016-04-17 18:27:47 +02:00
|
|
|
{
|
2016-08-15 05:24:16 +02:00
|
|
|
self::$declaring_methods[$method_id] = $declaring_method_id;
|
2016-04-17 18:27:47 +02:00
|
|
|
}
|
2016-04-27 00:18:49 +02:00
|
|
|
|
2016-10-12 07:38:29 +02:00
|
|
|
/**
|
|
|
|
* @param string $method_id
|
2016-11-07 05:29:54 +01:00
|
|
|
* @return string|null
|
2016-10-12 07:38:29 +02:00
|
|
|
*/
|
2016-10-15 06:12:57 +02:00
|
|
|
public static function getDeclaringMethodId($method_id)
|
2016-05-16 05:06:03 +02:00
|
|
|
{
|
2016-11-07 05:29:54 +01:00
|
|
|
if (isset(self::$declaring_methods[$method_id])) {
|
|
|
|
return self::$declaring_methods[$method_id];
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
2016-05-16 05:06:03 +02:00
|
|
|
}
|
|
|
|
|
2016-10-15 06:12:57 +02:00
|
|
|
/**
|
|
|
|
* @param string $method_id
|
|
|
|
* @param string $overridden_method_id
|
2016-11-02 07:29:00 +01:00
|
|
|
* @return void
|
2016-10-15 06:12:57 +02:00
|
|
|
*/
|
|
|
|
public static function setOverriddenMethodId($method_id, $overridden_method_id)
|
|
|
|
{
|
|
|
|
self::$overridden_methods[$method_id][] = $overridden_method_id;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $method_id
|
|
|
|
* @return array<string>
|
|
|
|
*/
|
|
|
|
public static function getOverriddenMethodIds($method_id)
|
|
|
|
{
|
|
|
|
return isset(self::$overridden_methods[$method_id]) ? self::$overridden_methods[$method_id] : [];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $method_id
|
|
|
|
* @return string
|
|
|
|
*/
|
2016-08-15 19:37:21 +02:00
|
|
|
public static function getCasedMethodId($method_id)
|
|
|
|
{
|
2016-10-15 06:12:57 +02:00
|
|
|
$method_id = self::getDeclaringMethodId($method_id);
|
2016-08-15 19:37:21 +02:00
|
|
|
return self::$cased_method_ids[$method_id];
|
|
|
|
}
|
|
|
|
|
2016-11-02 07:29:00 +01:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
2016-06-16 02:16:40 +02:00
|
|
|
public static function clearCache()
|
|
|
|
{
|
2016-08-14 00:54:49 +02:00
|
|
|
self::$method_files = [];
|
|
|
|
self::$method_params = [];
|
2016-08-15 19:37:21 +02:00
|
|
|
self::$cased_method_ids = [];
|
2016-08-14 00:54:49 +02:00
|
|
|
self::$method_namespaces = [];
|
|
|
|
self::$method_return_types = [];
|
|
|
|
self::$static_methods = [];
|
2016-08-15 05:24:16 +02:00
|
|
|
self::$declaring_methods = [];
|
2016-08-14 00:54:49 +02:00
|
|
|
self::$have_reflected = [];
|
|
|
|
self::$have_registered = [];
|
|
|
|
self::$method_visibility = [];
|
2016-11-05 03:04:55 +01:00
|
|
|
self::$overridden_methods = [];
|
2016-06-16 02:16:40 +02:00
|
|
|
}
|
2016-02-04 15:22:46 +01:00
|
|
|
}
|