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

784 lines
26 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;
use Psalm\Storage\MethodStorage;
use Psalm\Type;
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);
}
2016-10-15 06:12:57 +02:00
/**
* @param string $method_id
2017-05-27 02:16:18 +02:00
*
* @return array<int, \Psalm\FunctionLikeParameter>
2016-10-15 06:12:57 +02:00
*/
public static function getMethodParams(ProjectChecker $project_checker, $method_id)
{
if ($method_id = self::getDeclaringMethodId($project_checker, $method_id)) {
$storage = self::getStorage($project_checker, $method_id);
if ($storage) {
return $storage->params;
}
2016-11-07 05:29:54 +01:00
}
throw new \UnexpectedValueException('Cannot get method params for ' . $method_id);
}
/**
* @param string $method_id
2017-05-27 02:16:18 +02:00
*
2017-05-25 04:07:49 +02:00
* @return bool
*/
public static function isVariadic(ProjectChecker $project_checker, $method_id)
{
$method_id = (string)self::getDeclaringMethodId($project_checker, $method_id);
list($fq_class_name, $method_name) = explode('::', $method_id);
return $project_checker->classlike_storage_provider->get($fq_class_name)->methods[$method_name]->variadic;
}
/**
2017-02-10 02:35:17 +01:00
* @param string $method_id
2017-05-27 02:16:18 +02:00
*
* @return Type\Union|null
*/
public static function getMethodReturnType(ProjectChecker $project_checker, $method_id)
{
$method_id = self::getDeclaringMethodId($project_checker, $method_id);
if (!$method_id) {
return null;
}
list($fq_class_name, $method_name) = explode('::', $method_id);
if (!ClassLikeChecker::isUserDefined($project_checker, $fq_class_name)
&& FunctionChecker::inCallMap($method_id)
) {
return FunctionChecker::getReturnTypeFromCallMap($method_id);
}
$storage = self::getStorage($project_checker, $method_id);
if (!$storage) {
throw new \UnexpectedValueException('$storage should not be null for ' . $method_id);
2016-10-15 06:12:57 +02:00
}
if ($storage->return_type) {
return $storage->return_type;
}
2016-10-15 06:12:57 +02:00
$class_storage = $project_checker->classlike_storage_provider->get($fq_class_name);
foreach ($class_storage->overridden_method_ids[$method_name] as $overridden_method_id) {
$overridden_storage = self::getStorage($project_checker, $overridden_method_id);
if ($overridden_storage && $overridden_storage->return_type) {
if ($overridden_storage->return_type->isNull()) {
return Type::getVoid();
}
return $overridden_storage->return_type;
2016-10-15 06:12:57 +02:00
}
}
return null;
}
/**
* @param string $method_id
* @param CodeLocation|null $defined_location
2017-05-27 02:16:18 +02:00
*
* @return CodeLocation|null
*/
public static function getMethodReturnTypeLocation(
ProjectChecker $project_checker,
$method_id,
CodeLocation &$defined_location = null
) {
$method_id = self::getDeclaringMethodId($project_checker, $method_id);
if ($method_id === null) {
return null;
}
$storage = self::getStorage($project_checker, $method_id);
if (!$storage) {
throw new \UnexpectedValueException('$storage should not be null for ' . $method_id);
}
if (!$storage->return_type_location) {
$overridden_method_ids = self::getOverriddenMethodIds($project_checker, $method_id);
foreach ($overridden_method_ids as $overridden_method_id) {
$overridden_storage = self::getStorage($project_checker, $overridden_method_id);
if ($overridden_storage && $overridden_storage->return_type_location) {
$defined_location = $overridden_storage->return_type_location;
break;
}
}
}
return $storage->return_type_location;
}
2016-04-27 00:42:48 +02:00
/**
2016-11-02 07:29:00 +01:00
* @param \ReflectionMethod $method
Refactor scanning and analysis, introducing multithreading (#191) * Add failing test * Add visitor to soup up classlike references * Move a whole bunch of code into the visitor * Move some methods back, move onto analysis stage * Use the getAliases method everywhere * Fix refs * Fix more refs * Fix some tests * Fix more tests * Fix include tests * Shift config class finding to project checker and fix bugs * Fix a few more tests * transition test to new syntax * Remove var_dump * Delete a bunch of code and fix mutation test * Remove unnecessary visitation * Transition to better mocked out file provider, breaking some cached statement loading * Use different scheme for naming anonymous classes * Fix anonymous class issues * Refactor file/statement loading * Add specific property types * Fix mapped property assignment * Improve how we deal with traits * Fix trait checking * Pass Psalm checks * Add multi-process support * Delay console output until the end * Remove PHP 7 syntax * Update file storage with classes * Fix scanning individual files and add reflection return types * Always turn XDebug off * Add quicker method of getting method mutations * Queue return types for crawling * Interpret all strings as possible classes once we see a `get_class` call * Check invalid return types again * Fix template namespacing issues * Default to class-insensitive file names for includes * Don’t overwrite existing issues data * Add var docblocks for scanning * Add null check * Fix loading of external classes in templates * Only try to populate class when we haven’t yet seen it’s not a class * Fix trait property accessibility * Only ever improve docblock param type * Make param replacement more robust * Fix static const missing inferred type * Fix a few more tests * Register constant definitions * Fix trait aliasing * Skip constant type tests for now * Fix linting issues * Make sure caching is off for tests * Remove unnecessary return * Use emulative parser if on PHP 5.6 * Cache parser for faster first-time parse * Fix constant resolution when scanning classes * Remove test that’s beyond a practical scope * Add back --diff support * Add --help for --threads * Remove unused vars
2017-07-25 22:11:02 +02:00
* @param ProjectChecker $project_checker
2017-05-27 02:16:18 +02:00
*
2016-11-02 07:29:00 +01:00
* @return null
2016-04-27 00:42:48 +02:00
*/
Refactor scanning and analysis, introducing multithreading (#191) * Add failing test * Add visitor to soup up classlike references * Move a whole bunch of code into the visitor * Move some methods back, move onto analysis stage * Use the getAliases method everywhere * Fix refs * Fix more refs * Fix some tests * Fix more tests * Fix include tests * Shift config class finding to project checker and fix bugs * Fix a few more tests * transition test to new syntax * Remove var_dump * Delete a bunch of code and fix mutation test * Remove unnecessary visitation * Transition to better mocked out file provider, breaking some cached statement loading * Use different scheme for naming anonymous classes * Fix anonymous class issues * Refactor file/statement loading * Add specific property types * Fix mapped property assignment * Improve how we deal with traits * Fix trait checking * Pass Psalm checks * Add multi-process support * Delay console output until the end * Remove PHP 7 syntax * Update file storage with classes * Fix scanning individual files and add reflection return types * Always turn XDebug off * Add quicker method of getting method mutations * Queue return types for crawling * Interpret all strings as possible classes once we see a `get_class` call * Check invalid return types again * Fix template namespacing issues * Default to class-insensitive file names for includes * Don’t overwrite existing issues data * Add var docblocks for scanning * Add null check * Fix loading of external classes in templates * Only try to populate class when we haven’t yet seen it’s not a class * Fix trait property accessibility * Only ever improve docblock param type * Make param replacement more robust * Fix static const missing inferred type * Fix a few more tests * Register constant definitions * Fix trait aliasing * Skip constant type tests for now * Fix linting issues * Make sure caching is off for tests * Remove unnecessary return * Use emulative parser if on PHP 5.6 * Cache parser for faster first-time parse * Fix constant resolution when scanning classes * Remove test that’s beyond a practical scope * Add back --diff support * Add --help for --threads * Remove unused vars
2017-07-25 22:11:02 +02:00
public static function extractReflectionMethodInfo(\ReflectionMethod $method, ProjectChecker $project_checker)
{
$method_name = strtolower($method->getName());
$class_storage = $project_checker->classlike_storage_provider->get($method->class);
if (isset($class_storage->methods[strtolower($method_name)])) {
return;
}
$method_id = $method->class . '::' . $method_name;
$storage = $class_storage->methods[strtolower($method_name)] = new MethodStorage();
$storage->cased_name = $method->name;
if (strtolower((string)$method->name) === strtolower((string)$method->class)) {
self::setDeclaringMethodId(
$project_checker,
$method->class . '::__construct',
$method->class . '::' . $method_name
);
self::setAppearingMethodId(
$project_checker,
$method->class . '::__construct',
$method->class . '::' . $method_name
);
2016-11-21 20:36:06 +01:00
}
2016-10-15 06:12:57 +02:00
/** @var \ReflectionClass */
$declaring_class = $method->getDeclaringClass();
$storage->is_static = $method->isStatic();
$storage->namespace = $declaring_class->getNamespaceName();
$class_storage->declaring_method_ids[$method_name] =
$declaring_class->name . '::' . strtolower((string)$method->getName());
2016-12-31 17:49:04 +01:00
$class_storage->inheritable_method_ids[$method_name] = $class_storage->declaring_method_ids[$method_name];
2016-12-31 17:49:04 +01:00
$class_storage->appearing_method_ids[$method_name] = $class_storage->declaring_method_ids[$method_name];
$class_storage->overridden_method_ids[$method_name] = [];
Refactor scanning and analysis, introducing multithreading (#191) * Add failing test * Add visitor to soup up classlike references * Move a whole bunch of code into the visitor * Move some methods back, move onto analysis stage * Use the getAliases method everywhere * Fix refs * Fix more refs * Fix some tests * Fix more tests * Fix include tests * Shift config class finding to project checker and fix bugs * Fix a few more tests * transition test to new syntax * Remove var_dump * Delete a bunch of code and fix mutation test * Remove unnecessary visitation * Transition to better mocked out file provider, breaking some cached statement loading * Use different scheme for naming anonymous classes * Fix anonymous class issues * Refactor file/statement loading * Add specific property types * Fix mapped property assignment * Improve how we deal with traits * Fix trait checking * Pass Psalm checks * Add multi-process support * Delay console output until the end * Remove PHP 7 syntax * Update file storage with classes * Fix scanning individual files and add reflection return types * Always turn XDebug off * Add quicker method of getting method mutations * Queue return types for crawling * Interpret all strings as possible classes once we see a `get_class` call * Check invalid return types again * Fix template namespacing issues * Default to class-insensitive file names for includes * Don’t overwrite existing issues data * Add var docblocks for scanning * Add null check * Fix loading of external classes in templates * Only try to populate class when we haven’t yet seen it’s not a class * Fix trait property accessibility * Only ever improve docblock param type * Make param replacement more robust * Fix static const missing inferred type * Fix a few more tests * Register constant definitions * Fix trait aliasing * Skip constant type tests for now * Fix linting issues * Make sure caching is off for tests * Remove unnecessary return * Use emulative parser if on PHP 5.6 * Cache parser for faster first-time parse * Fix constant resolution when scanning classes * Remove test that’s beyond a practical scope * Add back --diff support * Add --help for --threads * Remove unused vars
2017-07-25 22:11:02 +02:00
try {
$storage->return_type = FunctionChecker::getReturnTypeFromCallMap($method_id);
$storage->return_type->queueClassLikesForScanning($project_checker);
} catch (\InvalidArgumentException $e) {
// do nothing
}
$storage->visibility = $method->isPrivate()
? ClassLikeChecker::VISIBILITY_PRIVATE
: ($method->isProtected() ? ClassLikeChecker::VISIBILITY_PROTECTED : ClassLikeChecker::VISIBILITY_PUBLIC);
2016-08-05 21:11:20 +02:00
$possible_params = FunctionChecker::getParamsFromCallMap($method_id);
2016-06-25 00:18:11 +02:00
if ($possible_params === null) {
$params = $method->getParameters();
$storage->params = [];
/** @var \ReflectionParameter $param */
foreach ($params as $param) {
$param_array = self::getReflectionParamData($param);
$storage->params[] = $param_array;
$storage->param_types[$param->name] = $param_array->type;
}
} else {
Refactor scanning and analysis, introducing multithreading (#191) * Add failing test * Add visitor to soup up classlike references * Move a whole bunch of code into the visitor * Move some methods back, move onto analysis stage * Use the getAliases method everywhere * Fix refs * Fix more refs * Fix some tests * Fix more tests * Fix include tests * Shift config class finding to project checker and fix bugs * Fix a few more tests * transition test to new syntax * Remove var_dump * Delete a bunch of code and fix mutation test * Remove unnecessary visitation * Transition to better mocked out file provider, breaking some cached statement loading * Use different scheme for naming anonymous classes * Fix anonymous class issues * Refactor file/statement loading * Add specific property types * Fix mapped property assignment * Improve how we deal with traits * Fix trait checking * Pass Psalm checks * Add multi-process support * Delay console output until the end * Remove PHP 7 syntax * Update file storage with classes * Fix scanning individual files and add reflection return types * Always turn XDebug off * Add quicker method of getting method mutations * Queue return types for crawling * Interpret all strings as possible classes once we see a `get_class` call * Check invalid return types again * Fix template namespacing issues * Default to class-insensitive file names for includes * Don’t overwrite existing issues data * Add var docblocks for scanning * Add null check * Fix loading of external classes in templates * Only try to populate class when we haven’t yet seen it’s not a class * Fix trait property accessibility * Only ever improve docblock param type * Make param replacement more robust * Fix static const missing inferred type * Fix a few more tests * Register constant definitions * Fix trait aliasing * Skip constant type tests for now * Fix linting issues * Make sure caching is off for tests * Remove unnecessary return * Use emulative parser if on PHP 5.6 * Cache parser for faster first-time parse * Fix constant resolution when scanning classes * Remove test that’s beyond a practical scope * Add back --diff support * Add --help for --threads * Remove unused vars
2017-07-25 22:11:02 +02:00
foreach ($possible_params[0] as $param) {
if ($param->type) {
$param->type->queueClassLikesForScanning($project_checker);
}
Refactor scanning and analysis, introducing multithreading (#191) * Add failing test * Add visitor to soup up classlike references * Move a whole bunch of code into the visitor * Move some methods back, move onto analysis stage * Use the getAliases method everywhere * Fix refs * Fix more refs * Fix some tests * Fix more tests * Fix include tests * Shift config class finding to project checker and fix bugs * Fix a few more tests * transition test to new syntax * Remove var_dump * Delete a bunch of code and fix mutation test * Remove unnecessary visitation * Transition to better mocked out file provider, breaking some cached statement loading * Use different scheme for naming anonymous classes * Fix anonymous class issues * Refactor file/statement loading * Add specific property types * Fix mapped property assignment * Improve how we deal with traits * Fix trait checking * Pass Psalm checks * Add multi-process support * Delay console output until the end * Remove PHP 7 syntax * Update file storage with classes * Fix scanning individual files and add reflection return types * Always turn XDebug off * Add quicker method of getting method mutations * Queue return types for crawling * Interpret all strings as possible classes once we see a `get_class` call * Check invalid return types again * Fix template namespacing issues * Default to class-insensitive file names for includes * Don’t overwrite existing issues data * Add var docblocks for scanning * Add null check * Fix loading of external classes in templates * Only try to populate class when we haven’t yet seen it’s not a class * Fix trait property accessibility * Only ever improve docblock param type * Make param replacement more robust * Fix static const missing inferred type * Fix a few more tests * Register constant definitions * Fix trait aliasing * Skip constant type tests for now * Fix linting issues * Make sure caching is off for tests * Remove unnecessary return * Use emulative parser if on PHP 5.6 * Cache parser for faster first-time parse * Fix constant resolution when scanning classes * Remove test that’s beyond a practical scope * Add back --diff support * Add --help for --threads * Remove unused vars
2017-07-25 22:11:02 +02:00
}
$storage->params = $possible_params[0];
}
$storage->required_param_count = 0;
foreach ($storage->params as $i => $param) {
if (!$param->is_optional) {
$storage->required_param_count = $i + 1;
}
}
2016-11-02 07:29:00 +01:00
return null;
}
/**
* 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
) {
2016-11-07 05:29:54 +01:00
/** @var string */
$method_id = self::getDeclaringMethodId($project_checker, $method_id);
$storage = self::getStorage($project_checker, $method_id);
if (!$storage) {
throw new \UnexpectedValueException('$storage should not be null');
}
if (!$storage->is_static) {
if ($self_call) {
if (IssueBuffer::accepts(
new NonStaticSelfCall(
'Method ' . MethodChecker::getCasedMethodId($project_checker, $method_id) .
' is not static, but is called ' .
'using self::',
$code_location
),
$suppressed_issues
)) {
return false;
}
} else {
if (IssueBuffer::accepts(
new InvalidStaticInvocation(
'Method ' . MethodChecker::getCasedMethodId($project_checker, $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
) {
if (self::methodExists($project_checker, $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
}
/**
* Whether or not a given method exists
2016-11-02 07:29:00 +01:00
*
* @param string $method_id
* @param ProjectChecker $project_checker
* @param CodeLocation|null $code_location
2017-05-27 02:16:18 +02:00
*
2016-10-23 18:24:53 +02:00
* @return bool
*/
public static function methodExists(
ProjectChecker $project_checker,
$method_id,
CodeLocation $code_location = null
) {
// remove trailing backslash if it exists
$method_id = preg_replace('/^\\\\/', '', $method_id);
list($fq_class_name, $method_name) = explode('::', $method_id);
$method_name = strtolower($method_name);
$method_id = $fq_class_name . '::' . $method_name;
2016-08-15 19:37:21 +02:00
2016-11-21 20:36:06 +01:00
$old_method_id = null;
$class_storage = $project_checker->classlike_storage_provider->get($fq_class_name);
if (isset($class_storage->declaring_method_ids[$method_name])) {
if ($project_checker->collect_references && $code_location) {
$declaring_method_id = $class_storage->declaring_method_ids[$method_name];
list($declaring_method_class, $declaring_method_name) = explode('::', $declaring_method_id);
$declaring_class_storage = $project_checker->classlike_storage_provider->get($declaring_method_class);
$declaring_method_storage = $declaring_class_storage->methods[strtolower($declaring_method_name)];
2017-02-28 00:24:20 +01:00
if ($declaring_method_storage->referencing_locations === null) {
$declaring_method_storage->referencing_locations = [];
}
$declaring_method_storage->referencing_locations[$code_location->file_path][] = $code_location;
if (isset($declaring_class_storage->overridden_method_ids[$declaring_method_name])) {
$overridden_method_ids = $declaring_class_storage->overridden_method_ids[$declaring_method_name];
foreach ($overridden_method_ids as $overridden_method_id) {
list($overridden_method_class, $overridden_method_name) = explode('::', $overridden_method_id);
$class_storage = $project_checker->classlike_storage_provider->get($overridden_method_class);
$method_storage = $class_storage->methods[strtolower($overridden_method_name)];
2017-02-28 00:24:20 +01:00
if ($method_storage->referencing_locations === null) {
$method_storage->referencing_locations = [];
}
$method_storage->referencing_locations[$code_location->file_path][] = $code_location;
}
}
}
return true;
2016-07-23 16:58:53 +02:00
}
if ($class_storage->abstract && isset($class_storage->overridden_method_ids[$method_name])) {
return true;
}
2016-11-21 20:36:06 +01:00
// support checking oldstyle constructors
if ($method_name === '__construct') {
$method_name_parts = explode('\\', $fq_class_name);
$old_constructor_name = array_pop($method_name_parts);
$old_method_id = $fq_class_name . '::' . $old_constructor_name;
}
2016-11-21 20:36:06 +01:00
if (FunctionChecker::inCallMap($method_id) || ($old_method_id && FunctionChecker::inCallMap($method_id))) {
2016-10-22 23:35:59 +02:00
return true;
}
2016-10-23 18:24:53 +02:00
return false;
2016-04-18 19:31:59 +02:00
}
/**
* @param string $method_id
2017-05-27 02:16:18 +02:00
*
* @return MethodStorage
*/
public static function getStorage(ProjectChecker $project_checker, $method_id)
{
list($fq_class_name, $method_name) = explode('::', $method_id);
$class_storage = $project_checker->classlike_storage_provider->get($fq_class_name);
if (!isset($class_storage->methods[strtolower($method_name)])) {
throw new \UnexpectedValueException('$storage should not be null for ' . $method_id);
}
return $class_storage->methods[strtolower($method_name)];
}
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
) {
$method_id = (string) self::getDeclaringMethodId($project_checker, $method_id);
$storage = self::getStorage($project_checker, $method_id);
if ($storage->deprecated) {
if (IssueBuffer::accepts(
2016-11-02 07:29:00 +01:00
new DeprecatedMethod(
'The method ' . MethodChecker::getCasedMethodId($project_checker, $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;
}
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
* @param CodeLocation $code_location
2016-11-02 07:29:00 +01:00
* @param array $suppressed_issues
*
* @return bool
*/
public static function isMethodVisible(
$method_id,
$calling_context,
StatementsSource $source
) {
$project_checker = $source->getFileChecker()->project_checker;
$declaring_method_id = self::getDeclaringMethodId($project_checker, $method_id);
if (!$declaring_method_id) {
$method_name = explode('::', $method_id)[1];
if ($method_name === '__construct') {
return true;
}
throw new \UnexpectedValueException('$declaring_method_id not expected to be null here');
}
$appearing_method_id = self::getAppearingMethodId($project_checker, $method_id);
$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 true;
}
}
list($declaring_method_class) = explode('::', $declaring_method_id);
if ($source->getSource() instanceof TraitChecker && $declaring_method_class === $source->getFQCLN()) {
return true;
}
$storage = self::getStorage($project_checker, $declaring_method_id);
if (!$storage) {
throw new \UnexpectedValueException('$storage should not be null for ' . $declaring_method_id);
}
switch ($storage->visibility) {
case ClassLikeChecker::VISIBILITY_PUBLIC:
return true;
case ClassLikeChecker::VISIBILITY_PRIVATE:
if (!$calling_context || $appearing_method_class !== $calling_context) {
return false;
}
return true;
case ClassLikeChecker::VISIBILITY_PROTECTED:
if (!$calling_context) {
return false;
}
if ($appearing_method_class
&& ClassChecker::classExtends($project_checker, $appearing_method_class, $calling_context)
) {
return true;
}
if ($appearing_method_class
&& !ClassChecker::classExtends($project_checker, $calling_context, $appearing_method_class)
) {
return false;
}
}
return true;
}
/**
* @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;
$declaring_method_id = self::getDeclaringMethodId($project_checker, $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');
}
$appearing_method_id = self::getAppearingMethodId($project_checker, $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
}
$storage = self::getStorage($project_checker, $declaring_method_id);
if (!$storage) {
throw new \UnexpectedValueException('$storage should not be null for ' . $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(
'Cannot access private method ' .
MethodChecker::getCasedMethodId($project_checker, $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
&& ClassChecker::classExtends($project_checker, $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
&& !ClassChecker::classExtends($project_checker, $calling_context, $appearing_method_class)
) {
2016-06-26 21:18:40 +02:00
if (IssueBuffer::accepts(
new InaccessibleMethod(
'Cannot access protected method ' .
MethodChecker::getCasedMethodId($project_checker, $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
/**
* @param string $method_id
* @param string $declaring_method_id
2017-05-27 02:16:18 +02:00
*
2016-11-02 07:29:00 +01:00
* @return void
2016-10-15 06:12:57 +02:00
*/
public static function setDeclaringMethodId(
ProjectChecker $project_checker,
$method_id,
$declaring_method_id
) {
list($fq_class_name, $method_name) = explode('::', $method_id);
2016-12-31 01:36:35 +01:00
$class_storage = $project_checker->classlike_storage_provider->get($fq_class_name);
$class_storage->declaring_method_ids[$method_name] = $declaring_method_id;
}
2016-12-31 17:49:04 +01:00
/**
* @param string $method_id
* @param string $appearing_method_id
2017-05-27 02:16:18 +02:00
*
2016-12-31 17:49:04 +01:00
* @return void
*/
public static function setAppearingMethodId(ProjectChecker $project_checker, $method_id, $appearing_method_id)
2016-12-31 17:49:04 +01:00
{
list($fq_class_name, $method_name) = explode('::', $method_id);
$class_storage = $project_checker->classlike_storage_provider->get($fq_class_name);
$class_storage->appearing_method_ids[$method_name] = $appearing_method_id;
2016-12-31 17:49:04 +01:00
}
2016-10-12 07:38:29 +02:00
/**
* @param string $method_id
2017-05-27 02:16:18 +02:00
*
2016-11-07 05:29:54 +01:00
* @return string|null
2016-10-12 07:38:29 +02:00
*/
public static function getDeclaringMethodId(ProjectChecker $project_checker, $method_id)
{
2017-01-12 03:37:53 +01:00
$method_id = strtolower($method_id);
list($fq_class_name, $method_name) = explode('::', $method_id);
2016-11-07 05:29:54 +01:00
$class_storage = $project_checker->classlike_storage_provider->get($fq_class_name);
if (isset($class_storage->declaring_method_ids[$method_name])) {
return $class_storage->declaring_method_ids[$method_name];
}
if ($class_storage->abstract && isset($class_storage->overridden_method_ids[$method_name])) {
return $class_storage->overridden_method_ids[$method_name][0];
}
}
2016-12-31 17:49:04 +01:00
/**
* Get the class this method appears in (vs is declared in, which could give a trait)
*
* @param string $method_id
2017-05-27 02:16:18 +02:00
*
2016-12-31 17:49:04 +01:00
* @return string|null
*/
public static function getAppearingMethodId(ProjectChecker $project_checker, $method_id)
2016-12-31 17:49:04 +01:00
{
2017-01-12 03:37:53 +01:00
$method_id = strtolower($method_id);
2016-12-31 17:49:04 +01:00
2017-01-12 03:37:53 +01:00
list($fq_class_name, $method_name) = explode('::', $method_id);
2017-01-09 05:58:06 +01:00
$class_storage = $project_checker->classlike_storage_provider->get($fq_class_name);
if (isset($class_storage->appearing_method_ids[$method_name])) {
return $class_storage->appearing_method_ids[$method_name];
2016-12-31 17:49:04 +01:00
}
}
2016-10-15 06:12:57 +02:00
/**
* @param string $method_id
* @param string $overridden_method_id
2017-05-27 02:16:18 +02:00
*
2016-11-02 07:29:00 +01:00
* @return void
2016-10-15 06:12:57 +02:00
*/
public static function setOverriddenMethodId(ProjectChecker $project_checker, $method_id, $overridden_method_id)
2016-10-15 06:12:57 +02:00
{
list($fq_class_name, $method_name) = explode('::', $method_id);
$class_storage = $project_checker->classlike_storage_provider->get($fq_class_name);
$class_storage->overridden_method_ids[$method_name][] = $overridden_method_id;
2016-10-15 06:12:57 +02:00
}
/**
* @param string $method_id
2017-05-27 02:16:18 +02:00
*
2016-10-15 06:12:57 +02:00
* @return array<string>
*/
public static function getOverriddenMethodIds(ProjectChecker $project_checker, $method_id)
2016-10-15 06:12:57 +02:00
{
list($fq_class_name, $method_name) = explode('::', $method_id);
$class_storage = $project_checker->classlike_storage_provider->get($fq_class_name);
2016-12-31 01:36:35 +01:00
if (isset($class_storage->overridden_method_ids[$method_name])) {
return $class_storage->overridden_method_ids[$method_name];
}
return [];
2016-10-15 06:12:57 +02:00
}
/**
* @param string $original_method_id
2017-05-27 02:16:18 +02:00
*
2016-10-15 06:12:57 +02:00
* @return string
*/
public static function getCasedMethodId(ProjectChecker $project_checker, $original_method_id)
2016-08-15 19:37:21 +02:00
{
$method_id = self::getDeclaringMethodId($project_checker, $original_method_id);
if ($method_id === null) {
throw new \UnexpectedValueException('Cannot get declaring method id for ' . $original_method_id);
}
$storage = self::getStorage($project_checker, $method_id);
if (!$storage) {
throw new \UnexpectedValueException('$storage should not be null for ' . $method_id);
}
list($fq_class_name) = explode('::', $method_id);
2017-05-25 04:07:49 +02:00
return $fq_class_name . '::' . $storage->cased_name;
2016-08-15 19:37:21 +02:00
}
2016-02-04 15:22:46 +01:00
}