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;
|
2016-08-13 20:20:46 +02:00
|
|
|
use Psalm\Config;
|
|
|
|
use Psalm\Context;
|
2017-02-01 01:21:33 +01:00
|
|
|
use Psalm\Issue\DuplicateClass;
|
2017-05-19 06:48:26 +02:00
|
|
|
use Psalm\IssueBuffer;
|
2017-02-18 19:41:27 +01:00
|
|
|
use Psalm\Provider\FileProvider;
|
2016-11-02 07:29:00 +01:00
|
|
|
use Psalm\StatementsSource;
|
2016-12-31 00:08:07 +01:00
|
|
|
use Psalm\Storage\FileStorage;
|
2017-01-02 21:31:18 +01:00
|
|
|
use Psalm\Type;
|
2016-08-13 20:20:46 +02:00
|
|
|
|
2016-11-21 03:49:06 +01:00
|
|
|
class FileChecker extends SourceChecker implements StatementsSource
|
2016-01-08 00:28:27 +01:00
|
|
|
{
|
2017-01-07 20:35:07 +01:00
|
|
|
use CanAlias;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
protected $file_name;
|
|
|
|
|
2016-10-14 06:53:43 +02:00
|
|
|
/**
|
|
|
|
* @var string
|
|
|
|
*/
|
2016-12-04 01:11:30 +01:00
|
|
|
protected $file_path;
|
2016-10-14 06:53:43 +02:00
|
|
|
|
2017-01-07 20:35:07 +01:00
|
|
|
/**
|
|
|
|
* @var string|null
|
|
|
|
*/
|
2017-01-08 01:33:33 +01:00
|
|
|
protected $actual_file_name;
|
2017-01-07 20:35:07 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @var string|null
|
|
|
|
*/
|
2017-01-08 01:33:33 +01:00
|
|
|
protected $actual_file_path;
|
2017-01-07 20:35:07 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @var array<string, string>
|
|
|
|
*/
|
|
|
|
protected $suppressed_issues = [];
|
|
|
|
|
2016-11-13 00:51:48 +01:00
|
|
|
/**
|
|
|
|
* @var array<string, array<string, string>>
|
2016-10-31 20:42:20 +01:00
|
|
|
*/
|
2016-08-14 00:54:49 +02:00
|
|
|
protected $namespace_aliased_classes = [];
|
2016-02-18 21:05:13 +01:00
|
|
|
|
2016-11-13 00:51:48 +01:00
|
|
|
/**
|
|
|
|
* @var array<string, array<string, string>>
|
|
|
|
*/
|
|
|
|
protected $namespace_aliased_classes_flipped = [];
|
|
|
|
|
2016-10-31 20:42:20 +01:00
|
|
|
/**
|
2017-02-02 00:11:00 +01:00
|
|
|
* @var array<int, \PhpParser\Node\Stmt>
|
2016-10-31 20:42:20 +01:00
|
|
|
*/
|
2016-08-14 00:54:49 +02:00
|
|
|
protected $preloaded_statements = [];
|
2016-04-04 01:41:54 +02:00
|
|
|
|
2016-11-01 05:39:41 +01:00
|
|
|
/**
|
|
|
|
* @var bool
|
|
|
|
*/
|
2016-01-11 17:05:24 +01:00
|
|
|
public static $show_notices = true;
|
2016-01-08 00:28:27 +01:00
|
|
|
|
2016-10-14 06:53:43 +02:00
|
|
|
/**
|
2016-12-31 00:08:07 +01:00
|
|
|
* A list of data useful to analyse files
|
|
|
|
*
|
|
|
|
* @var array<string, FileStorage>
|
|
|
|
*/
|
|
|
|
public static $storage = [];
|
|
|
|
|
2017-01-02 21:31:18 +01:00
|
|
|
/**
|
|
|
|
* @var array<string, ClassLikeChecker>
|
|
|
|
*/
|
2017-02-02 00:11:00 +01:00
|
|
|
protected $interface_checkers_to_visit = [];
|
2017-01-02 21:31:18 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @var array<string, ClassLikeChecker>
|
|
|
|
*/
|
2017-02-02 00:11:00 +01:00
|
|
|
protected $class_checkers_to_visit = [];
|
2017-01-02 21:31:18 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @var array<int, ClassLikeChecker>
|
|
|
|
*/
|
2017-02-02 00:11:00 +01:00
|
|
|
protected $class_checkers_to_analyze = [];
|
2017-01-02 21:31:18 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @var array<string, FunctionChecker>
|
|
|
|
*/
|
|
|
|
protected $function_checkers = [];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var array<int, NamespaceChecker>
|
|
|
|
*/
|
|
|
|
protected $namespace_checkers = [];
|
|
|
|
|
2017-06-06 16:11:34 +02:00
|
|
|
/**
|
|
|
|
* @var array<string, bool>
|
|
|
|
*/
|
|
|
|
private $included_file_paths = [];
|
|
|
|
|
2017-01-02 21:31:18 +01:00
|
|
|
/**
|
2017-01-12 06:54:41 +01:00
|
|
|
* @var Context
|
2017-01-02 21:31:18 +01:00
|
|
|
*/
|
2017-01-12 06:54:41 +01:00
|
|
|
public $context;
|
2017-01-02 21:31:18 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @var ProjectChecker
|
|
|
|
*/
|
|
|
|
public $project_checker;
|
|
|
|
|
2016-12-31 00:08:07 +01:00
|
|
|
/**
|
2017-02-02 00:11:00 +01:00
|
|
|
* @var bool
|
2016-10-14 06:53:43 +02:00
|
|
|
*/
|
2017-02-02 00:11:00 +01:00
|
|
|
protected $will_analyze;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $file_path
|
|
|
|
* @param ProjectChecker $project_checker
|
|
|
|
* @param array<int, PhpParser\Node\Stmt>|null $preloaded_statements
|
|
|
|
* @param bool $will_analyze
|
2017-06-06 16:11:34 +02:00
|
|
|
* @param array<string, bool> $included_file_paths
|
2017-02-02 00:11:00 +01:00
|
|
|
*/
|
|
|
|
public function __construct(
|
|
|
|
$file_path,
|
|
|
|
ProjectChecker $project_checker,
|
|
|
|
array $preloaded_statements = null,
|
2017-06-06 16:11:34 +02:00
|
|
|
$will_analyze = true,
|
|
|
|
array $included_file_paths = []
|
2017-02-02 00:11:00 +01:00
|
|
|
) {
|
2016-12-31 00:08:07 +01:00
|
|
|
$this->file_path = $file_path;
|
2016-12-08 04:38:57 +01:00
|
|
|
$this->file_name = Config::getInstance()->shortenFileName($this->file_path);
|
2017-01-02 21:31:18 +01:00
|
|
|
$this->project_checker = $project_checker;
|
2017-02-02 00:11:00 +01:00
|
|
|
$this->will_analyze = $will_analyze;
|
2016-04-04 01:41:54 +02:00
|
|
|
|
2016-12-31 00:08:07 +01:00
|
|
|
if (!isset(self::$storage[$file_path])) {
|
|
|
|
self::$storage[$file_path] = new FileStorage();
|
|
|
|
}
|
|
|
|
|
2016-04-04 01:41:54 +02:00
|
|
|
if ($preloaded_statements) {
|
2016-08-14 00:54:49 +02:00
|
|
|
$this->preloaded_statements = $preloaded_statements;
|
2016-04-04 01:41:54 +02:00
|
|
|
}
|
2017-01-12 06:54:41 +01:00
|
|
|
|
2017-01-17 00:33:04 +01:00
|
|
|
$this->context = new Context();
|
2017-02-27 05:09:18 +01:00
|
|
|
$this->context->collect_references = $project_checker->collect_references;
|
2017-02-13 04:12:31 +01:00
|
|
|
$this->context->vars_in_scope['$argc'] = Type::getInt();
|
|
|
|
$this->context->vars_in_scope['$argv'] = new Type\Union([
|
|
|
|
new Type\Atomic\TArray([
|
|
|
|
Type::getInt(),
|
|
|
|
Type::getString(),
|
2017-05-27 02:05:57 +02:00
|
|
|
]),
|
2017-02-13 04:12:31 +01:00
|
|
|
]);
|
2017-06-06 16:11:34 +02:00
|
|
|
|
|
|
|
$included_file_paths[$file_path] = true;
|
|
|
|
$this->included_file_paths = $included_file_paths;
|
2016-01-08 00:28:27 +01:00
|
|
|
}
|
|
|
|
|
2016-11-02 07:29:00 +01:00
|
|
|
/**
|
|
|
|
* @param Context|null $file_context
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2017-01-02 21:31:18 +01:00
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function visit(Context $file_context = null)
|
|
|
|
{
|
2017-01-12 06:54:41 +01:00
|
|
|
$this->context = $file_context ?: $this->context;
|
2016-07-24 23:06:54 +02:00
|
|
|
|
2016-10-20 20:26:03 +02:00
|
|
|
$config = Config::getInstance();
|
|
|
|
|
2016-06-18 20:45:55 +02:00
|
|
|
$stmts = $this->getStatements();
|
2016-01-08 00:28:27 +01:00
|
|
|
|
2016-12-25 02:08:58 +01:00
|
|
|
/** @var array<int, PhpParser\Node\Expr|PhpParser\Node\Stmt> */
|
2016-01-20 00:27:06 +01:00
|
|
|
$leftover_stmts = [];
|
|
|
|
|
2017-01-02 21:31:18 +01:00
|
|
|
$statements_checker = new StatementsChecker($this);
|
2016-08-15 05:24:16 +02:00
|
|
|
|
2017-02-01 02:47:16 +01:00
|
|
|
$predefined_classlikes = $config->getPredefinedClassLikes();
|
2017-02-01 01:21:33 +01:00
|
|
|
|
2017-01-02 21:31:18 +01:00
|
|
|
$function_stmts = [];
|
2016-12-28 18:59:51 +01:00
|
|
|
|
2016-01-08 00:28:27 +01:00
|
|
|
foreach ($stmts as $stmt) {
|
2017-02-01 01:21:33 +01:00
|
|
|
if ($stmt instanceof PhpParser\Node\Stmt\ClassLike && $stmt->name) {
|
|
|
|
if (isset($predefined_classlikes[strtolower($stmt->name)])) {
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
new DuplicateClass(
|
|
|
|
'Class ' . $stmt->name . ' has already been defined internally',
|
2017-06-21 20:22:52 +02:00
|
|
|
new \Psalm\CodeLocation($this, $stmt, null, true)
|
2017-02-01 01:21:33 +01:00
|
|
|
)
|
|
|
|
)) {
|
|
|
|
// fall through
|
|
|
|
}
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($stmt instanceof PhpParser\Node\Stmt\Class_) {
|
2017-01-02 21:31:18 +01:00
|
|
|
$class_checker = new ClassChecker($stmt, $this, $stmt->name);
|
2016-11-02 07:29:00 +01:00
|
|
|
|
2017-01-02 21:31:18 +01:00
|
|
|
$fq_class_name = $class_checker->getFQCLN();
|
|
|
|
|
2017-02-02 00:11:00 +01:00
|
|
|
$this->class_checkers_to_visit[$fq_class_name] = $class_checker;
|
|
|
|
if ($this->will_analyze) {
|
|
|
|
$this->class_checkers_to_analyze[] = $class_checker;
|
|
|
|
}
|
2017-02-01 01:21:33 +01:00
|
|
|
} elseif ($stmt instanceof PhpParser\Node\Stmt\Interface_) {
|
2017-01-02 21:31:18 +01:00
|
|
|
$class_checker = new InterfaceChecker($stmt, $this, $stmt->name);
|
2016-11-02 07:29:00 +01:00
|
|
|
|
2017-01-02 21:31:18 +01:00
|
|
|
$fq_class_name = $class_checker->getFQCLN();
|
|
|
|
|
2017-02-02 00:11:00 +01:00
|
|
|
$this->interface_checkers_to_visit[$fq_class_name] = $class_checker;
|
2017-02-01 01:21:33 +01:00
|
|
|
} elseif ($stmt instanceof PhpParser\Node\Stmt\Trait_) {
|
2017-02-08 00:27:28 +01:00
|
|
|
new TraitChecker($stmt, $this, $stmt->name);
|
2017-02-01 01:21:33 +01:00
|
|
|
}
|
|
|
|
} elseif ($stmt instanceof PhpParser\Node\Stmt\Namespace_) {
|
|
|
|
$namespace_name = $stmt->name ? implode('\\', $stmt->name->parts) : '';
|
2016-07-24 23:06:54 +02:00
|
|
|
|
2017-02-01 01:21:33 +01:00
|
|
|
$namespace_checker = new NamespaceChecker($stmt, $this);
|
|
|
|
$namespace_checker->visit();
|
2017-01-02 21:31:18 +01:00
|
|
|
|
2017-02-01 01:21:33 +01:00
|
|
|
$this->namespace_aliased_classes[$namespace_name] = $namespace_checker->getAliasedClasses();
|
|
|
|
$this->namespace_aliased_classes_flipped[$namespace_name] =
|
|
|
|
$namespace_checker->getAliasedClassesFlipped();
|
2017-01-02 21:31:18 +01:00
|
|
|
} elseif ($stmt instanceof PhpParser\Node\Stmt\Function_) {
|
|
|
|
$function_stmts[] = $stmt;
|
2017-01-08 17:24:01 +01:00
|
|
|
} elseif ($stmt instanceof PhpParser\Node\Stmt\Use_) {
|
|
|
|
$this->visitUse($stmt);
|
|
|
|
} elseif ($stmt instanceof PhpParser\Node\Stmt\GroupUse) {
|
|
|
|
$this->visitGroupUse($stmt);
|
2016-11-02 07:29:00 +01:00
|
|
|
} else {
|
2016-01-20 00:27:06 +01:00
|
|
|
$leftover_stmts[] = $stmt;
|
2016-01-08 00:28:27 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-02 21:31:18 +01:00
|
|
|
$function_checkers = [];
|
|
|
|
|
|
|
|
// hoist functions to the top
|
|
|
|
foreach ($function_stmts as $stmt) {
|
2017-01-06 07:07:11 +01:00
|
|
|
$function_checkers[$stmt->name] = new FunctionChecker($stmt, $this);
|
2017-02-24 01:36:51 +01:00
|
|
|
$function_id = (string)$function_checkers[$stmt->name]->getMethodId();
|
2017-01-02 21:31:18 +01:00
|
|
|
$this->function_checkers[$function_id] = $function_checkers[$stmt->name];
|
|
|
|
}
|
|
|
|
|
|
|
|
// if there are any leftover statements, evaluate them,
|
|
|
|
// in turn causing the classes/interfaces be evaluated
|
|
|
|
if ($leftover_stmts) {
|
2017-01-07 21:09:47 +01:00
|
|
|
$statements_checker->analyze($leftover_stmts, $this->context);
|
2016-12-28 18:59:51 +01:00
|
|
|
}
|
|
|
|
|
2017-01-02 21:31:18 +01:00
|
|
|
// check any leftover interfaces not already evaluated
|
2017-02-02 00:11:00 +01:00
|
|
|
foreach ($this->interface_checkers_to_visit as $interface_checker) {
|
2017-01-07 21:09:47 +01:00
|
|
|
$interface_checker->visit();
|
2016-12-28 18:59:51 +01:00
|
|
|
}
|
|
|
|
|
2017-01-02 21:31:18 +01:00
|
|
|
// check any leftover classes not already evaluated
|
2017-02-02 00:11:00 +01:00
|
|
|
foreach ($this->class_checkers_to_visit as $class_checker) {
|
2017-01-07 21:09:47 +01:00
|
|
|
$class_checker->visit();
|
2017-01-02 21:31:18 +01:00
|
|
|
}
|
2016-12-29 04:11:50 +01:00
|
|
|
|
2017-02-02 00:11:00 +01:00
|
|
|
$this->class_checkers_to_visit = [];
|
|
|
|
$this->interface_checkers_to_visit = [];
|
2017-01-02 21:31:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-05-25 04:07:49 +02:00
|
|
|
* @param bool $update_docblocks
|
|
|
|
* @param bool $preserve_checkers
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2017-01-02 21:31:18 +01:00
|
|
|
* @return void
|
|
|
|
*/
|
2017-01-12 03:37:53 +01:00
|
|
|
public function analyze($update_docblocks = false, $preserve_checkers = false)
|
2017-01-02 21:31:18 +01:00
|
|
|
{
|
|
|
|
$config = Config::getInstance();
|
|
|
|
|
2017-02-02 00:11:00 +01:00
|
|
|
foreach ($this->class_checkers_to_analyze as $class_checker) {
|
2017-01-07 21:57:25 +01:00
|
|
|
$class_checker->analyze(null, $this->context, $update_docblocks);
|
2016-08-05 21:11:20 +02:00
|
|
|
}
|
|
|
|
|
2017-01-02 21:31:18 +01:00
|
|
|
foreach ($this->function_checkers as $function_checker) {
|
2017-01-17 00:33:04 +01:00
|
|
|
$function_context = new Context($this->context->self);
|
2017-02-27 05:09:18 +01:00
|
|
|
$function_context->collect_references = $this->project_checker->collect_references;
|
2017-01-07 21:09:47 +01:00
|
|
|
$function_checker->analyze($function_context, $this->context);
|
2017-01-02 21:31:18 +01:00
|
|
|
|
2017-03-24 23:34:46 +01:00
|
|
|
if ($config->reportIssueInFile('InvalidReturnType', $this->file_path)) {
|
2017-01-02 21:31:18 +01:00
|
|
|
/** @var string */
|
|
|
|
$method_id = $function_checker->getMethodId();
|
|
|
|
|
2017-02-10 04:54:06 +01:00
|
|
|
$function_storage = FunctionChecker::getStorage($method_id, $this->file_path);
|
|
|
|
|
|
|
|
if (!$function_storage->has_template_return_type) {
|
|
|
|
$return_type = $function_storage->return_type;
|
|
|
|
|
|
|
|
$return_type_location = $function_storage->return_type_location;
|
|
|
|
|
|
|
|
$function_checker->verifyReturnType(
|
|
|
|
false,
|
|
|
|
$return_type,
|
|
|
|
null,
|
|
|
|
$return_type_location
|
|
|
|
);
|
|
|
|
}
|
2017-01-02 21:31:18 +01:00
|
|
|
}
|
2016-08-05 21:11:20 +02:00
|
|
|
}
|
|
|
|
|
2017-01-12 03:37:53 +01:00
|
|
|
if (!$preserve_checkers) {
|
2017-02-02 00:11:00 +01:00
|
|
|
$this->class_checkers_to_analyze = [];
|
2017-01-12 03:37:53 +01:00
|
|
|
$this->function_checkers = [];
|
|
|
|
}
|
2016-08-05 21:11:20 +02:00
|
|
|
|
2017-02-18 19:41:27 +01:00
|
|
|
if ($update_docblocks) {
|
|
|
|
\Psalm\Mutator\FileMutator::updateDocblocks($this->file_path);
|
2016-11-13 05:59:31 +01:00
|
|
|
}
|
2016-01-08 00:28:27 +01:00
|
|
|
}
|
|
|
|
|
2017-02-02 00:11:00 +01:00
|
|
|
/**
|
|
|
|
* @param string $fq_class_name
|
|
|
|
* @param ClassChecker $class_checker
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2017-02-02 00:11:00 +01:00
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function addNamespacedClassChecker($fq_class_name, ClassChecker $class_checker)
|
|
|
|
{
|
|
|
|
$this->class_checkers_to_visit[$fq_class_name] = $class_checker;
|
|
|
|
if ($this->will_analyze) {
|
|
|
|
$this->class_checkers_to_analyze[] = $class_checker;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $fq_class_name
|
|
|
|
* @param InterfaceChecker $interface_checker
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2017-02-02 00:11:00 +01:00
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function addNamespacedInterfaceChecker($fq_class_name, InterfaceChecker $interface_checker)
|
|
|
|
{
|
|
|
|
$this->interface_checkers_to_visit[$fq_class_name] = $interface_checker;
|
|
|
|
}
|
|
|
|
|
2017-03-19 20:37:07 +01:00
|
|
|
/**
|
|
|
|
* @param string $function_id
|
|
|
|
* @param FunctionChecker $function_checker
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2017-03-19 20:37:07 +01:00
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function addNamespacedFunctionChecker($function_id, FunctionChecker $function_checker)
|
|
|
|
{
|
|
|
|
$this->function_checkers[$function_id] = $function_checker;
|
|
|
|
}
|
|
|
|
|
2017-01-12 03:37:53 +01:00
|
|
|
/**
|
2017-01-12 06:54:41 +01:00
|
|
|
* @param string $method_id
|
2017-01-12 03:37:53 +01:00
|
|
|
* @param Context $this_context
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2017-01-12 03:37:53 +01:00
|
|
|
* @return void
|
|
|
|
*/
|
2017-01-12 06:54:41 +01:00
|
|
|
public function getMethodMutations($method_id, Context &$this_context)
|
2017-01-12 03:37:53 +01:00
|
|
|
{
|
2017-01-12 06:54:41 +01:00
|
|
|
list($fq_class_name, $method_name) = explode('::', $method_id);
|
2017-06-30 07:24:45 +02:00
|
|
|
|
|
|
|
$class_checker_to_examine = null;
|
|
|
|
|
|
|
|
foreach ($this->class_checkers_to_analyze as $class_checker) {
|
|
|
|
if (strtolower($class_checker->getFQCLN()) === strtolower($fq_class_name)) {
|
|
|
|
$class_checker_to_examine = $class_checker;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$class_checker_to_examine) {
|
|
|
|
$this->project_checker->getMethodMutations($method_id, $this_context);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$call_context = new Context($this_context->self);
|
2017-01-12 03:37:53 +01:00
|
|
|
$call_context->collect_mutations = true;
|
2017-06-30 07:24:45 +02:00
|
|
|
$call_context->include_location = $this_context->include_location;
|
2017-01-12 03:37:53 +01:00
|
|
|
|
|
|
|
foreach ($this_context->vars_possibly_in_scope as $var => $type) {
|
|
|
|
if (strpos($var, '$this->') === 0) {
|
|
|
|
$call_context->vars_possibly_in_scope[$var] = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($this_context->vars_in_scope as $var => $type) {
|
|
|
|
if (strpos($var, '$this->') === 0) {
|
|
|
|
$call_context->vars_in_scope[$var] = $type;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$call_context->vars_in_scope['$this'] = $this_context->vars_in_scope['$this'];
|
|
|
|
|
2017-06-30 07:24:45 +02:00
|
|
|
$class_checker_to_examine->getMethodMutations($method_name, $call_context);
|
2017-01-12 06:54:41 +01:00
|
|
|
|
2017-01-19 05:19:36 +01:00
|
|
|
foreach ($call_context->vars_possibly_in_scope as $var => $_) {
|
2017-01-12 03:37:53 +01:00
|
|
|
$this_context->vars_possibly_in_scope[$var] = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($call_context->vars_in_scope as $var => $type) {
|
|
|
|
$this_context->vars_in_scope[$var] = $type;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-14 06:53:43 +02:00
|
|
|
/**
|
2017-01-02 21:31:18 +01:00
|
|
|
* @param Context|null $file_context
|
2017-05-25 04:07:49 +02:00
|
|
|
* @param bool $update_docblocks
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2017-01-02 21:31:18 +01:00
|
|
|
* @return void
|
2016-10-14 06:53:43 +02:00
|
|
|
*/
|
2017-01-07 21:09:47 +01:00
|
|
|
public function visitAndAnalyzeMethods(Context $file_context = null, $update_docblocks = false)
|
2016-01-11 20:21:29 +01:00
|
|
|
{
|
2017-03-24 23:34:46 +01:00
|
|
|
$this->project_checker->registerAnalyzableFile($this->file_path);
|
2017-01-02 21:31:18 +01:00
|
|
|
$this->visit($file_context);
|
2017-01-07 21:57:25 +01:00
|
|
|
$this->analyze($update_docblocks);
|
2016-06-20 06:38:13 +02:00
|
|
|
}
|
2016-02-04 22:05:36 +01:00
|
|
|
|
2017-01-07 23:24:43 +01:00
|
|
|
/**
|
|
|
|
* Used when checking single files with multiple classlike declarations
|
|
|
|
*
|
|
|
|
* @param string $fq_class_name
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2017-01-07 23:24:43 +01:00
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function containsUnEvaluatedClassLike($fq_class_name)
|
|
|
|
{
|
2017-02-02 00:11:00 +01:00
|
|
|
return isset($this->interface_checkers_to_visit[$fq_class_name]) ||
|
|
|
|
isset($this->class_checkers_to_visit[$fq_class_name]);
|
2017-01-07 23:24:43 +01:00
|
|
|
}
|
|
|
|
|
2016-06-20 06:38:13 +02:00
|
|
|
/**
|
2017-01-02 21:31:18 +01:00
|
|
|
* When evaluating a file, we wait until a class is actually used to evaluate its contents
|
2016-11-02 07:29:00 +01:00
|
|
|
*
|
2017-01-02 21:31:18 +01:00
|
|
|
* @param string $fq_class_name
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2017-01-02 21:31:18 +01:00
|
|
|
* @return null|false
|
2016-06-20 06:38:13 +02:00
|
|
|
*/
|
2017-02-01 05:24:33 +01:00
|
|
|
public function evaluateClassLike($fq_class_name)
|
2016-01-08 00:28:27 +01:00
|
|
|
{
|
2017-02-02 00:11:00 +01:00
|
|
|
if (isset($this->interface_checkers_to_visit[$fq_class_name])) {
|
|
|
|
$interface_checker = $this->interface_checkers_to_visit[$fq_class_name];
|
2017-01-09 04:31:18 +01:00
|
|
|
|
2017-02-02 00:11:00 +01:00
|
|
|
unset($this->interface_checkers_to_visit[$fq_class_name]);
|
2017-01-09 04:31:18 +01:00
|
|
|
|
|
|
|
if ($interface_checker->visit() === false) {
|
2017-01-02 21:31:18 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
2016-01-08 00:28:27 +01:00
|
|
|
}
|
|
|
|
|
2017-02-02 00:11:00 +01:00
|
|
|
if (isset($this->class_checkers_to_visit[$fq_class_name])) {
|
|
|
|
$class_checker = $this->class_checkers_to_visit[$fq_class_name];
|
2017-01-09 04:31:18 +01:00
|
|
|
|
2017-02-02 00:11:00 +01:00
|
|
|
unset($this->class_checkers_to_visit[$fq_class_name]);
|
2017-01-09 04:31:18 +01:00
|
|
|
|
|
|
|
if ($class_checker->visit(null, $this->context) === false) {
|
2017-01-02 21:31:18 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->project_checker->visitFileForClassLike($fq_class_name);
|
2016-01-08 00:28:27 +01:00
|
|
|
}
|
2016-01-08 04:52:26 +01:00
|
|
|
|
2016-04-27 00:42:48 +02:00
|
|
|
/**
|
2017-02-02 00:11:00 +01:00
|
|
|
* @return array<int, \PhpParser\Node\Stmt>
|
2016-04-27 00:42:48 +02:00
|
|
|
*/
|
2016-06-18 20:45:55 +02:00
|
|
|
protected function getStatements()
|
|
|
|
{
|
2016-11-02 07:29:00 +01:00
|
|
|
return $this->preloaded_statements
|
|
|
|
? $this->preloaded_statements
|
2017-02-18 19:41:27 +01:00
|
|
|
: FileProvider::getStatementsForFile(
|
|
|
|
$this->project_checker,
|
|
|
|
$this->file_path,
|
|
|
|
$this->project_checker->debug_output
|
|
|
|
);
|
2016-06-18 20:45:55 +02:00
|
|
|
}
|
|
|
|
|
2017-01-08 01:07:58 +01:00
|
|
|
/**
|
|
|
|
* @param string $file_path
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2017-01-08 01:07:58 +01:00
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function fileExists($file_path)
|
|
|
|
{
|
|
|
|
return file_exists($file_path) || isset($this->project_checker->fake_files[$file_path]);
|
|
|
|
}
|
|
|
|
|
2016-04-27 00:42:48 +02:00
|
|
|
/**
|
|
|
|
* @return null
|
|
|
|
*/
|
2016-01-20 00:27:06 +01:00
|
|
|
public function getNamespace()
|
|
|
|
{
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2016-10-14 06:53:43 +02:00
|
|
|
/**
|
|
|
|
* @param string|null $namespace_name
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2016-11-13 00:51:48 +01:00
|
|
|
* @return array<string, string>
|
2016-10-14 06:53:43 +02:00
|
|
|
*/
|
2016-01-20 00:27:06 +01:00
|
|
|
public function getAliasedClasses($namespace_name = null)
|
|
|
|
{
|
2016-08-14 00:54:49 +02:00
|
|
|
if ($namespace_name && isset($this->namespace_aliased_classes[$namespace_name])) {
|
|
|
|
return $this->namespace_aliased_classes[$namespace_name];
|
2016-01-20 00:27:06 +01:00
|
|
|
}
|
2016-01-30 00:48:09 +01:00
|
|
|
|
2016-08-14 00:54:49 +02:00
|
|
|
return $this->aliased_classes;
|
2016-01-20 00:27:06 +01:00
|
|
|
}
|
|
|
|
|
2016-11-13 00:51:48 +01:00
|
|
|
/**
|
|
|
|
* @param string|null $namespace_name
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2016-11-13 00:51:48 +01:00
|
|
|
* @return array<string, string>
|
|
|
|
*/
|
|
|
|
public function getAliasedClassesFlipped($namespace_name = null)
|
|
|
|
{
|
|
|
|
if ($namespace_name && isset($this->namespace_aliased_classes_flipped[$namespace_name])) {
|
|
|
|
return $this->namespace_aliased_classes_flipped[$namespace_name];
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->aliased_classes_flipped;
|
|
|
|
}
|
|
|
|
|
2016-11-02 07:29:00 +01:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
2016-08-10 07:09:47 +02:00
|
|
|
public static function clearCache()
|
2016-03-23 18:05:25 +01:00
|
|
|
{
|
2016-12-31 00:08:07 +01:00
|
|
|
self::$storage = [];
|
2016-08-15 17:01:50 +02:00
|
|
|
|
|
|
|
ClassLikeChecker::clearCache();
|
2016-10-21 00:12:13 +02:00
|
|
|
FunctionChecker::clearCache();
|
2016-10-21 00:16:17 +02:00
|
|
|
StatementsChecker::clearCache();
|
2016-12-08 21:57:18 +01:00
|
|
|
IssueBuffer::clearCache();
|
2017-01-27 07:23:12 +01:00
|
|
|
FunctionLikeChecker::clearCache();
|
2016-05-10 20:00:44 +02:00
|
|
|
}
|
2016-11-06 05:59:29 +01:00
|
|
|
|
2017-01-07 20:35:07 +01:00
|
|
|
/**
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function getFileName()
|
|
|
|
{
|
|
|
|
return $this->file_name;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function getFilePath()
|
|
|
|
{
|
|
|
|
return $this->file_path;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-01-08 01:33:33 +01:00
|
|
|
* @param string $file_name
|
|
|
|
* @param string $file_path
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2017-01-07 20:35:07 +01:00
|
|
|
* @return void
|
|
|
|
*/
|
2017-01-08 01:33:33 +01:00
|
|
|
public function setFileName($file_name, $file_path)
|
2017-01-07 20:35:07 +01:00
|
|
|
{
|
2017-01-08 01:33:33 +01:00
|
|
|
$this->actual_file_name = $this->file_name;
|
|
|
|
$this->actual_file_path = $this->file_path;
|
|
|
|
|
|
|
|
$this->file_name = $file_name;
|
|
|
|
$this->file_path = $file_path;
|
2017-01-07 20:35:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function getCheckedFileName()
|
|
|
|
{
|
2017-01-08 01:33:33 +01:00
|
|
|
return $this->actual_file_name ?: $this->file_name;
|
2017-01-07 20:35:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function getCheckedFilePath()
|
|
|
|
{
|
2017-01-08 01:33:33 +01:00
|
|
|
return $this->actual_file_path ?: $this->file_path;
|
2017-01-07 20:35:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public function getSuppressedIssues()
|
|
|
|
{
|
|
|
|
return $this->suppressed_issues;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getFQCLN()
|
|
|
|
{
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2017-01-09 06:26:40 +01:00
|
|
|
public function getClassName()
|
|
|
|
{
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2017-01-07 20:35:07 +01:00
|
|
|
public function isStatic()
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
2017-03-20 04:30:20 +01:00
|
|
|
|
|
|
|
public function getFileChecker()
|
|
|
|
{
|
|
|
|
return $this;
|
|
|
|
}
|
2017-06-06 16:11:34 +02:00
|
|
|
|
|
|
|
/** @return array<string, bool> */
|
|
|
|
public function getIncludedFilePaths()
|
|
|
|
{
|
|
|
|
return $this->included_file_paths;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $file_path
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function addIncludedFilePath($file_path)
|
|
|
|
{
|
|
|
|
$this->included_file_paths[$file_path] = true;
|
|
|
|
}
|
2016-01-08 00:28:27 +01:00
|
|
|
}
|