1
0
mirror of https://github.com/danog/psalm.git synced 2024-12-15 02:47:02 +01:00
psalm/src/Psalm/Checker/MethodChecker.php

340 lines
11 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-11-02 07:29:00 +01:00
use PhpParser;
use Psalm\CodeLocation;
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\InvalidStaticInvocation;
use Psalm\Issue\NonStaticSelfCall;
2016-11-02 07:29:00 +01:00
use Psalm\Issue\UndefinedMethod;
use Psalm\IssueBuffer;
use Psalm\StatementsSource;
2016-01-08 00:28:27 +01:00
class MethodChecker extends FunctionLikeChecker
2016-01-08 00:28:27 +01:00
{
2016-10-30 17:46:18 +01:00
/**
* @param PhpParser\Node\FunctionLike $function
* @param StatementsSource $source
2016-12-17 06:48:31 +01:00
* @psalm-suppress MixedAssignment
2016-10-30 17:46:18 +01:00
*/
public function __construct($function, StatementsSource $source)
{
2016-10-15 06:12:57 +02:00
if (!$function instanceof PhpParser\Node\Stmt\ClassMethod) {
throw new \InvalidArgumentException('Must be called with a ClassMethod');
}
parent::__construct($function, $source);
}
/**
* Determines whether a given method is static or not
2016-11-02 07:29:00 +01:00
*
* @param string $method_id
* @param bool $self_call
* @param CodeLocation $code_location
* @param array<string> $suppressed_issues
2017-05-27 02:16:18 +02:00
*
2016-11-02 07:29:00 +01:00
* @return bool
*/
2017-01-12 03:37:53 +01:00
public static function checkStatic(
$method_id,
$self_call,
ProjectChecker $project_checker,
CodeLocation $code_location,
array $suppressed_issues
) {
2018-02-04 00:52:35 +01:00
$codebase_methods = $project_checker->codebase->methods;
2016-11-07 05:29:54 +01:00
/** @var string */
2018-02-04 00:52:35 +01:00
$method_id = $codebase_methods->getDeclaringMethodId($method_id);
2018-02-04 00:52:35 +01:00
$storage = $codebase_methods->getStorage($method_id);
if (!$storage->is_static) {
if ($self_call) {
if (IssueBuffer::accepts(
new NonStaticSelfCall(
2018-02-04 00:52:35 +01:00
'Method ' . $codebase_methods->getCasedMethodId($method_id) .
' is not static, but is called ' .
'using self::',
$code_location
),
$suppressed_issues
)) {
return false;
}
} else {
if (IssueBuffer::accepts(
new InvalidStaticInvocation(
2018-02-04 00:52:35 +01:00
'Method ' . $codebase_methods->getCasedMethodId($method_id) .
' is not static, but is called ' .
'statically',
$code_location
),
$suppressed_issues
)) {
return false;
}
}
}
2016-11-02 07:29:00 +01:00
return true;
}
2016-04-27 00:42:48 +02:00
/**
* @param string $method_id
* @param CodeLocation $code_location
* @param array $suppressed_issues
2017-05-27 02:16:18 +02:00
*
* @return bool|null
2016-04-27 00:42:48 +02:00
*/
public static function checkMethodExists(
ProjectChecker $project_checker,
$method_id,
CodeLocation $code_location,
array $suppressed_issues
) {
2018-02-01 06:50:01 +01:00
if ($project_checker->codebase->methodExists($method_id, $code_location)) {
2016-10-23 18:24:53 +02:00
return true;
}
if (IssueBuffer::accepts(
new UndefinedMethod('Method ' . $method_id . ' does not exist', $code_location),
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
}
2016-10-15 06:12:57 +02:00
/**
* @param string $method_id
* @param CodeLocation $code_location
* @param array $suppressed_issues
2017-05-27 02:16:18 +02:00
*
2016-10-15 06:12:57 +02:00
* @return false|null
*/
public static function checkMethodNotDeprecated(
ProjectChecker $project_checker,
$method_id,
CodeLocation $code_location,
array $suppressed_issues
) {
2018-02-04 00:52:35 +01:00
$codebase_methods = $project_checker->codebase->methods;
$method_id = (string) $codebase_methods->getDeclaringMethodId($method_id);
$storage = $codebase_methods->getStorage($method_id);
if ($storage->deprecated) {
if (IssueBuffer::accepts(
2016-11-02 07:29:00 +01:00
new DeprecatedMethod(
2018-02-04 00:52:35 +01:00
'The method ' . $codebase_methods->getCasedMethodId($method_id) .
' has been marked as deprecated',
$code_location
2016-11-02 07:29:00 +01:00
),
$suppressed_issues
)) {
return false;
}
}
2016-11-02 07:29:00 +01:00
return null;
}
/**
* @param string $method_id
* @param string|null $calling_context
* @param StatementsSource $source
* @param CodeLocation $code_location
* @param array $suppressed_issues
2017-05-27 02:16:18 +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,
CodeLocation $code_location,
2016-11-02 07:29:00 +01:00
array $suppressed_issues
) {
$project_checker = $source->getFileChecker()->project_checker;
2018-02-01 06:50:01 +01:00
$codebase = $project_checker->codebase;
2018-02-04 00:52:35 +01:00
$codebase_methods = $codebase->methods;
$codebase_classlikes = $codebase->classlikes;
2018-02-04 00:52:35 +01:00
$declaring_method_id = $codebase_methods->getDeclaringMethodId($method_id);
2016-04-18 19:31:59 +02:00
2017-07-09 20:50:57 +02:00
if (!$declaring_method_id) {
$method_name = explode('::', $method_id)[1];
if ($method_name === '__construct') {
return null;
}
throw new \UnexpectedValueException('$declaring_method_id not expected to be null here');
}
2018-02-04 00:52:35 +01:00
$appearing_method_id = $codebase_methods->getAppearingMethodId($method_id);
2017-07-09 20:50:57 +02:00
$appearing_method_class = null;
if ($appearing_method_id) {
list($appearing_method_class) = explode('::', $appearing_method_id);
// if the calling class is the same, we know the method exists, so it must be visible
if ($appearing_method_class === $calling_context) {
return null;
}
2016-12-12 19:50:46 +01:00
}
2017-07-09 20:50:57 +02:00
list($declaring_method_class) = explode('::', $declaring_method_id);
if ($source->getSource() instanceof TraitChecker && $declaring_method_class === $source->getFQCLN()) {
2016-11-02 07:29:00 +01:00
return null;
2016-08-13 17:10:43 +02:00
}
2018-02-04 00:52:35 +01:00
$storage = $project_checker->codebase->methods->getStorage($declaring_method_id);
switch ($storage->visibility) {
case ClassLikeChecker::VISIBILITY_PUBLIC:
2016-11-02 07:29:00 +01:00
return null;
2016-04-18 19:31:59 +02:00
case ClassLikeChecker::VISIBILITY_PRIVATE:
if (!$calling_context || $appearing_method_class !== $calling_context) {
2016-06-26 21:18:40 +02:00
if (IssueBuffer::accepts(
new InaccessibleMethod(
2018-02-04 00:52:35 +01:00
'Cannot access private method ' . $codebase_methods->getCasedMethodId($method_id) .
2016-11-02 07:29:00 +01:00
' from context ' . $calling_context,
$code_location
),
2016-11-02 07:29:00 +01:00
$suppressed_issues
)) {
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 ClassLikeChecker::VISIBILITY_PROTECTED:
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,
$code_location
2016-11-02 07:29:00 +01:00
),
$suppressed_issues
)) {
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
}
if ($appearing_method_class
2018-02-04 00:52:35 +01:00
&& $codebase_classlikes->classExtends($appearing_method_class, $calling_context)
) {
2016-11-02 07:29:00 +01:00
return null;
2016-04-30 20:14:22 +02:00
}
if ($appearing_method_class
2018-02-04 00:52:35 +01:00
&& !$codebase_classlikes->classExtends($calling_context, $appearing_method_class)
) {
2016-06-26 21:18:40 +02:00
if (IssueBuffer::accepts(
new InaccessibleMethod(
2018-02-04 00:52:35 +01:00
'Cannot access protected method ' . $codebase_methods->getCasedMethodId($method_id) .
2016-11-02 07:29:00 +01:00
' from context ' . $calling_context,
$code_location
),
2016-11-02 07:29:00 +01:00
$suppressed_issues
)) {
return false;
}
2016-04-18 19:31:59 +02:00
}
}
2016-11-02 07:29:00 +01:00
return null;
}
2016-10-15 06:12:57 +02:00
/**
2018-02-04 00:52:35 +01:00
* @param string $method_id
* @param string|null $calling_context
* @param StatementsSource $source
2017-05-27 02:16:18 +02:00
*
2018-02-04 00:52:35 +01:00
* @return bool
2016-12-31 17:49:04 +01:00
*/
2018-02-04 00:52:35 +01:00
public static function isMethodVisible(
$method_id,
2018-02-04 00:52:35 +01:00
$calling_context,
StatementsSource $source
) {
2018-02-04 00:52:35 +01:00
$project_checker = $source->getFileChecker()->project_checker;
$codebase = $project_checker->codebase;
2017-01-12 03:37:53 +01:00
2018-02-04 00:52:35 +01:00
$declaring_method_id = $codebase->methods->getDeclaringMethodId($method_id);
2016-11-07 05:29:54 +01:00
2018-02-04 00:52:35 +01:00
if (!$declaring_method_id) {
$method_name = explode('::', $method_id)[1];
2018-02-04 00:52:35 +01:00
if ($method_name === '__construct') {
return true;
}
2018-02-04 00:52:35 +01:00
throw new \UnexpectedValueException('$declaring_method_id not expected to be null here');
}
2018-02-04 00:52:35 +01:00
$appearing_method_id = $codebase->methods->getAppearingMethodId($method_id);
2016-12-31 17:49:04 +01:00
2018-02-04 00:52:35 +01:00
$appearing_method_class = null;
2017-01-09 05:58:06 +01:00
2018-02-04 00:52:35 +01:00
if ($appearing_method_id) {
list($appearing_method_class) = explode('::', $appearing_method_id);
2018-02-04 00:52:35 +01:00
// if the calling class is the same, we know the method exists, so it must be visible
if ($appearing_method_class === $calling_context) {
return true;
}
2016-12-31 17:49:04 +01:00
}
2018-02-04 00:52:35 +01:00
list($declaring_method_class) = explode('::', $declaring_method_id);
2018-02-04 00:52:35 +01:00
if ($source->getSource() instanceof TraitChecker && $declaring_method_class === $source->getFQCLN()) {
return true;
}
2018-02-04 00:52:35 +01:00
$storage = $codebase->methods->getStorage($declaring_method_id);
2016-10-15 06:12:57 +02:00
2018-02-04 00:52:35 +01:00
switch ($storage->visibility) {
case ClassLikeChecker::VISIBILITY_PUBLIC:
return true;
2018-02-04 00:52:35 +01:00
case ClassLikeChecker::VISIBILITY_PRIVATE:
if (!$calling_context || $appearing_method_class !== $calling_context) {
return false;
}
2016-12-31 01:36:35 +01:00
2018-02-04 00:52:35 +01:00
return true;
2016-12-31 01:36:35 +01:00
2018-02-04 00:52:35 +01:00
case ClassLikeChecker::VISIBILITY_PROTECTED:
if (!$calling_context) {
return false;
}
2016-10-15 06:12:57 +02:00
2018-02-04 00:52:35 +01:00
if ($appearing_method_class
&& $codebase->classExtends($appearing_method_class, $calling_context)
) {
return true;
}
2018-02-04 00:52:35 +01:00
if ($appearing_method_class
&& !$codebase->classExtends($calling_context, $appearing_method_class)
) {
return false;
}
}
2018-02-04 00:52:35 +01:00
return true;
2016-08-15 19:37:21 +02:00
}
2016-02-04 15:22:46 +01:00
}