1
0
mirror of https://github.com/danog/psalm.git synced 2024-12-14 02:07:37 +01:00
psalm/src/Psalm/Checker/ClassMethodChecker.php

407 lines
14 KiB
PHP
Raw Normal View History

2016-01-08 00:28:27 +01:00
<?php
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;
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
2016-08-14 05:26:45 +02:00
class ClassMethodChecker extends FunctionLikeChecker
2016-01-08 00:28:27 +01:00
{
protected static $method_comments = [];
protected static $method_files = [];
protected static $method_params = [];
protected static $method_namespaces = [];
protected static $method_return_types = [];
2016-08-15 19:37:21 +02:00
protected static $cased_method_ids = [];
protected static $static_methods = [];
2016-08-15 05:24:16 +02:00
protected static $declaring_methods = [];
protected static $existing_methods = [];
protected static $have_reflected = [];
protected static $have_registered = [];
protected static $inherited_methods = [];
protected static $declaring_class = [];
protected static $method_visibility = [];
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;
public function __construct(PhpParser\Node\FunctionLike $function, StatementsSource $source, array $this_vars = [])
{
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-20 19:35:22 +02:00
}
}
public static function getMethodParams($method_id)
{
2016-08-15 05:24:16 +02:00
self::registerClassMethod($method_id);
$method_id = self::getDeclaringMethod($method_id);
return self::$method_params[$method_id];
}
public static function getMethodReturnTypes($method_id)
{
2016-08-15 05:24:16 +02:00
self::registerClassMethod($method_id);
$method_id = self::getDeclaringMethod($method_id);
return self::$method_return_types[$method_id] ? clone self::$method_return_types[$method_id] : null;
}
2016-04-27 00:42:48 +02:00
/**
* @return void
*/
2016-08-15 05:24:16 +02:00
public static function extractReflectionMethodInfo(\ReflectionMethod $method)
{
2016-08-15 19:37:21 +02:00
$method_id = $method->class . '::' . strtolower($method->name);
self::$cased_method_ids[$method_id] = $method->class . '::' . $method->name;
2016-08-15 05:24:16 +02:00
if (isset(self::$have_reflected[$method_id])) {
return;
}
self::$have_reflected[$method_id] = true;
self::$static_methods[$method_id] = $method->isStatic();
self::$method_files[$method_id] = $method->getFileName();
self::$method_namespaces[$method_id] = $method->getDeclaringClass()->getNamespaceName();
2016-08-15 19:37:21 +02:00
self::$declaring_methods[$method_id] = $method->getDeclaringClass()->name . '::' . strtolower($method->getName());
self::$method_visibility[$method_id] = $method->isPrivate() ?
2016-04-18 19:31:59 +02:00
self::VISIBILITY_PRIVATE :
($method->isProtected() ? self::VISIBILITY_PROTECTED : self::VISIBILITY_PUBLIC);
2016-08-05 21:11:20 +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
self::$method_params[$method_id] = [];
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;
$method_param_types[$param->name] = $param_array['type'];
}
2016-06-15 01:22:29 +02:00
$return_types = null;
2016-06-25 00:18:11 +02:00
$config = Config::getInstance();
2016-06-25 00:18:11 +02:00
$return_type = null;
self::$method_return_types[$method_id] = $return_type;
}
/**
* Determines whether a given method is static or not
* @param string $method_id
*/
public static function checkMethodStatic($method_id, $file_name, $line_number, array $suppressed_issues)
{
2016-08-15 05:24:16 +02:00
self::registerClassMethod($method_id);
$method_id = self::getDeclaringMethod($method_id);
if (!self::$static_methods[$method_id]) {
if (IssueBuffer::accepts(
2016-08-15 19:37:21 +02:00
new InvalidStaticInvocation('Method ' . ClassMethodChecker::getCasedMethodId($method_id) . ' is not static', $file_name, $line_number),
$suppressed_issues
)) {
return false;
}
}
}
protected function registerMethod(PhpParser\Node\Stmt\ClassMethod $method)
{
2016-08-15 19:37:21 +02:00
$method_id = $this->absolute_class . '::' . strtolower($method->name);
self::$cased_method_ids[$method_id] = $this->absolute_class . '::' . $method->name;
if (isset(self::$have_reflected[$method_id]) || isset(self::$have_registered[$method_id])) {
$this->suppressed_issues = self::$method_suppress[$method_id];
return;
}
self::$have_registered[$method_id] = true;
2016-08-15 05:24:16 +02:00
self::$declaring_methods[$method_id] = $method_id;
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;
2016-06-25 00:18:11 +02:00
}
elseif ($method->isProtected()) {
self::$method_visibility[$method_id] = self::VISIBILITY_PROTECTED;
}
2016-06-24 00:45:46 +02:00
else {
self::$method_visibility[$method_id] = self::VISIBILITY_PUBLIC;
}
self::$method_params[$method_id] = [];
2016-06-25 00:18:11 +02:00
$method_param_names = [];
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;
2016-08-11 01:21:03 +02:00
$method_param_names[$param->name] = $param_array['type'];
}
2016-06-25 00:18:11 +02:00
$config = Config::getInstance();
$return_type = null;
$docblock_info = CommentChecker::extractDocblockInfo($method->getDocComment());
2016-06-25 00:18:11 +02:00
if ($docblock_info['deprecated']) {
self::$deprecated_methods[$method_id] = true;
}
$this->suppressed_issues = $docblock_info['suppress'];
self::$method_suppress[$method_id] = $this->suppressed_issues;
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(
$this->fixUpLocalType(
2016-06-29 22:42:30 +02:00
$docblock_info['return_type'],
$this->absolute_class,
$this->namespace,
$this->getAliasedClasses()
2016-06-29 22:42:30 +02:00
)
);
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],
2016-08-11 01:21:03 +02:00
$method->getLine()
);
2016-06-25 00:18:11 +02:00
}
}
self::$method_return_types[$method_id] = $return_type;
}
protected static function fixUpReturnType($return_type, $method_id)
{
if (strpos($return_type, '[') !== false) {
$return_type = Type::convertSquareBrackets($return_type);
}
$return_type_tokens = Type::tokenize($return_type);
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, ['<', '>', '|'])) {
continue;
}
$return_type_token = Type::fixScalarTerms($return_type_token);
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
/**
* @return bool|null
2016-04-27 00:42:48 +02:00
*/
public static function checkMethodExists($method_id, $file_name, $line_number, array $suppresssed_issues)
{
// remove trailing backslash if it exists
$method_id = preg_replace('/^\\\\/', '', $method_id);
2016-08-15 19:37:21 +02:00
$cased_method_id = $method_id;
$method_parts = explode('::', $method_id);
$method_id = $method_parts[0] . '::' . strtolower($method_parts[1]);
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])) {
return true;
2016-07-23 16:58:53 +02:00
}
2016-06-26 21:18:40 +02:00
if (IssueBuffer::accepts(
2016-08-15 19:37:21 +02:00
new UndefinedMethod('Method ' . $cased_method_id . ' does not exist', $file_name, $line_number),
$suppresssed_issues
)) {
return false;
}
2016-04-18 19:31:59 +02:00
}
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
}
public static function checkMethodNotDeprecated($method_id, $file_name, $line_number, array $suppresssed_issues)
{
2016-08-15 05:24:16 +02:00
self::registerClassMethod($method_id);
if (isset(self::$deprecated_methods[$method_id])) {
if (IssueBuffer::accepts(
2016-08-15 19:37:21 +02:00
new DeprecatedMethod('The method ' . ClassMethodChecker::getCasedMethodId($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
* @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
{
2016-08-15 05:24:16 +02:00
self::registerClassMethod($method_id);
$declared_method_id = self::getDeclaringMethod($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),
$suppresssed_issues
)) {
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-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:
return;
case self::VISIBILITY_PRIVATE:
if (!$calling_context || $method_class !== $calling_context) {
2016-06-26 21:18:40 +02:00
if (IssueBuffer::accepts(
new InaccessibleMethod(
2016-08-15 19:37:21 +02:00
'Cannot access private method ' . ClassMethodChecker::getCasedMethodId($method_id) . ' from context ' . $calling_context,
2016-08-13 17:10:43 +02:00
$source->getFileName(),
$line_number
),
$suppresssed_issues
)) {
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),
$suppresssed_issues
)) {
return false;
}
2016-04-18 19:31:59 +02:00
}
if (ClassChecker::classExtends($method_class, $calling_context) && method_exists($calling_context, $method_name)) {
2016-04-30 20:14:22 +02:00
return;
}
if (!ClassChecker::classExtends($calling_context, $method_class)) {
2016-06-26 21:18:40 +02:00
if (IssueBuffer::accepts(
new InaccessibleMethod(
2016-08-15 19:37:21 +02:00
'Cannot access protected method ' . ClassMethodChecker::getCasedMethodId($method_id) . ' from context ' . $calling_context,
2016-08-13 17:10:43 +02:00
$source->getFileName(),
$line_number
),
$suppresssed_issues
)) {
return false;
}
2016-04-18 19:31:59 +02:00
}
}
}
2016-08-15 05:24:16 +02:00
public static function setDeclaringMethod($method_id, $declaring_method_id)
{
2016-08-15 05:24:16 +02:00
self::$declaring_methods[$method_id] = $declaring_method_id;
}
public static function getDeclaringMethod($method_id)
{
2016-08-15 05:24:16 +02:00
return self::$declaring_methods[$method_id];
}
2016-08-15 19:37:21 +02:00
public static function getCasedMethodId($method_id)
{
$method_id = self::getDeclaringMethod($method_id);
return self::$cased_method_ids[$method_id];
}
2016-06-16 02:16:40 +02:00
public static function clearCache()
{
self::$method_comments = [];
self::$method_files = [];
self::$method_params = [];
2016-08-15 19:37:21 +02:00
self::$cased_method_ids = [];
self::$method_namespaces = [];
self::$method_return_types = [];
self::$static_methods = [];
2016-08-15 05:24:16 +02:00
self::$declaring_methods = [];
self::$existing_methods = [];
self::$have_reflected = [];
self::$have_registered = [];
self::$inherited_methods = [];
self::$declaring_class = [];
self::$method_visibility = [];
2016-06-16 02:16:40 +02:00
}
2016-02-04 15:22:46 +01:00
}