2016-01-08 00:28:27 +01:00
|
|
|
<?php
|
2018-11-06 03:57:36 +01:00
|
|
|
namespace Psalm\Internal\Analyzer;
|
2016-01-08 00:28:27 +01:00
|
|
|
|
2016-11-02 07:29:00 +01:00
|
|
|
use PhpParser;
|
2018-11-06 03:57:36 +01:00
|
|
|
use Psalm\Codebase;
|
2020-08-17 04:53:53 +02:00
|
|
|
use Psalm\CodeLocation\DocblockTypeLocation;
|
2016-08-13 20:20:46 +02:00
|
|
|
use Psalm\Context;
|
2018-08-07 19:17:23 +02:00
|
|
|
use Psalm\Exception\UnpreparedAnalysisException;
|
2018-11-12 16:46:55 +01:00
|
|
|
use Psalm\Internal\FileManipulation\FileManipulationBuffer;
|
2020-08-17 04:53:53 +02:00
|
|
|
use Psalm\Internal\Type\TypeAlias\LinkableTypeAlias;
|
|
|
|
use Psalm\Issue\InvalidTypeImport;
|
2019-03-24 21:17:14 +01:00
|
|
|
use Psalm\Issue\UncaughtThrowInGlobalScope;
|
2017-05-19 06:48:26 +02:00
|
|
|
use Psalm\IssueBuffer;
|
2016-11-02 07:29:00 +01:00
|
|
|
use Psalm\StatementsSource;
|
2017-01-02 21:31:18 +01:00
|
|
|
use Psalm\Type;
|
2019-06-26 22:52:29 +02:00
|
|
|
use function implode;
|
|
|
|
use function strtolower;
|
|
|
|
use function strpos;
|
|
|
|
use function array_keys;
|
|
|
|
use function count;
|
2016-08-13 20:20:46 +02:00
|
|
|
|
2018-12-02 00:37:49 +01:00
|
|
|
/**
|
|
|
|
* @internal
|
2020-08-06 16:18:55 +02:00
|
|
|
* @psalm-consistent-constructor
|
2018-12-02 00:37:49 +01:00
|
|
|
*/
|
2018-11-06 03:57:36 +01:00
|
|
|
class FileAnalyzer extends SourceAnalyzer 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
|
|
|
/**
|
2018-05-30 22:19:18 +02:00
|
|
|
* @var string|null
|
2018-05-23 05:38:27 +02:00
|
|
|
*/
|
2018-05-30 22:19:18 +02:00
|
|
|
protected $root_file_path;
|
2018-05-23 05:38:27 +02:00
|
|
|
|
|
|
|
/**
|
2018-05-30 22:19:18 +02:00
|
|
|
* @var string|null
|
2017-01-07 20:35:07 +01:00
|
|
|
*/
|
2018-05-30 22:19:18 +02:00
|
|
|
protected $root_file_name;
|
2017-01-07 20:35:07 +01:00
|
|
|
|
|
|
|
/**
|
2018-05-23 05:38:27 +02:00
|
|
|
* @var array<string, bool>
|
2017-01-07 20:35:07 +01:00
|
|
|
*/
|
2018-11-18 17:39:14 +01:00
|
|
|
private $required_file_paths = [];
|
2017-01-07 20:35:07 +01:00
|
|
|
|
2018-05-30 22:19:18 +02:00
|
|
|
/**
|
|
|
|
* @var array<string, bool>
|
|
|
|
*/
|
2018-11-18 17:39:14 +01:00
|
|
|
private $parent_file_paths = [];
|
2018-05-30 22:19:18 +02:00
|
|
|
|
2017-01-07 20:35:07 +01:00
|
|
|
/**
|
2019-08-18 18:25:48 +02:00
|
|
|
* @var array<string>
|
2017-01-07 20:35:07 +01:00
|
|
|
*/
|
2018-11-18 17:39:14 +01:00
|
|
|
private $suppressed_issues = [];
|
2017-01-07 20:35:07 +01:00
|
|
|
|
2016-11-13 00:51:48 +01:00
|
|
|
/**
|
|
|
|
* @var array<string, array<string, string>>
|
2016-10-31 20:42:20 +01:00
|
|
|
*/
|
2018-11-18 17:39:14 +01:00
|
|
|
private $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>>
|
|
|
|
*/
|
2018-11-18 17:39:14 +01:00
|
|
|
private $namespace_aliased_classes_flipped = [];
|
2016-11-13 00:51:48 +01:00
|
|
|
|
2019-06-06 04:13:33 +02:00
|
|
|
/**
|
|
|
|
* @var array<string, array<string, string>>
|
|
|
|
*/
|
|
|
|
private $namespace_aliased_classes_flipped_replaceable = [];
|
|
|
|
|
2017-01-02 21:31:18 +01:00
|
|
|
/**
|
2018-11-06 03:57:36 +01:00
|
|
|
* @var array<string, InterfaceAnalyzer>
|
2017-01-02 21:31:18 +01:00
|
|
|
*/
|
2018-11-18 17:39:14 +01:00
|
|
|
public $interface_analyzers_to_analyze = [];
|
2017-01-02 21:31:18 +01:00
|
|
|
|
|
|
|
/**
|
2020-02-15 02:54:26 +01:00
|
|
|
* @var array<lowercase-string, ClassAnalyzer>
|
2017-01-02 21:31:18 +01:00
|
|
|
*/
|
2018-11-18 17:39:14 +01:00
|
|
|
public $class_analyzers_to_analyze = [];
|
2017-01-02 21:31:18 +01:00
|
|
|
|
|
|
|
/**
|
2018-01-28 22:52:57 +01:00
|
|
|
* @var null|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
|
|
|
|
|
|
|
/**
|
2018-11-06 03:57:36 +01:00
|
|
|
* @var ProjectAnalyzer
|
2017-01-02 21:31:18 +01:00
|
|
|
*/
|
2018-11-11 18:01:14 +01:00
|
|
|
public $project_analyzer;
|
2017-01-02 21:31:18 +01:00
|
|
|
|
2018-11-06 03:57:36 +01:00
|
|
|
/**
|
|
|
|
* @var Codebase
|
|
|
|
*/
|
|
|
|
public $codebase;
|
|
|
|
|
2019-06-05 17:15:52 +02:00
|
|
|
/**
|
|
|
|
* @var int
|
|
|
|
*/
|
|
|
|
private $first_statement_offset = -1;
|
|
|
|
|
2019-11-25 17:44:54 +01:00
|
|
|
/** @var ?\Psalm\Internal\Provider\NodeDataProvider */
|
|
|
|
private $node_data;
|
|
|
|
|
2020-06-13 21:48:12 +02:00
|
|
|
/** @var ?Type\Union */
|
|
|
|
private $return_type;
|
|
|
|
|
2020-09-07 01:36:47 +02:00
|
|
|
public function __construct(ProjectAnalyzer $project_analyzer, string $file_path, string $file_name)
|
2018-01-21 18:44:46 +01:00
|
|
|
{
|
2018-11-06 03:57:36 +01:00
|
|
|
$this->source = $this;
|
2016-12-31 00:08:07 +01:00
|
|
|
$this->file_path = $file_path;
|
2018-01-21 18:44:46 +01:00
|
|
|
$this->file_name = $file_name;
|
2018-11-11 18:01:14 +01:00
|
|
|
$this->project_analyzer = $project_analyzer;
|
|
|
|
$this->codebase = $project_analyzer->getCodebase();
|
2017-07-25 22:11:02 +02:00
|
|
|
}
|
2016-12-28 18:59:51 +01:00
|
|
|
|
2018-10-17 21:52:26 +02:00
|
|
|
public function analyze(
|
2020-09-07 01:36:47 +02:00
|
|
|
?Context $file_context = null,
|
|
|
|
bool $preserve_analyzers = false,
|
|
|
|
?Context $global_context = null
|
2020-09-12 17:24:05 +02:00
|
|
|
): void {
|
2018-11-11 18:01:14 +01:00
|
|
|
$codebase = $this->project_analyzer->getCodebase();
|
2018-02-21 19:54:11 +01:00
|
|
|
|
|
|
|
$file_storage = $codebase->file_storage_provider->get($this->file_path);
|
|
|
|
|
2018-10-17 21:52:26 +02:00
|
|
|
if (!$file_storage->deep_scan && !$codebase->server_mode) {
|
2018-08-07 19:17:23 +02:00
|
|
|
throw new UnpreparedAnalysisException('File ' . $this->file_path . ' has not been properly scanned');
|
2018-02-21 19:54:11 +01:00
|
|
|
}
|
|
|
|
|
2019-01-20 17:10:12 +01:00
|
|
|
if ($file_storage->has_visitor_issues) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-07-25 22:11:02 +02:00
|
|
|
if ($file_context) {
|
|
|
|
$this->context = $file_context;
|
|
|
|
}
|
2017-02-01 01:21:33 +01:00
|
|
|
|
2017-07-25 22:11:02 +02:00
|
|
|
if (!$this->context) {
|
|
|
|
$this->context = new Context();
|
|
|
|
}
|
2016-11-02 07:29:00 +01:00
|
|
|
|
2018-08-29 21:10:56 +02:00
|
|
|
if ($codebase->config->useStrictTypesForFile($this->file_path)) {
|
|
|
|
$this->context->strict_types = true;
|
|
|
|
}
|
|
|
|
|
2017-12-06 06:56:00 +01:00
|
|
|
$this->context->is_global = true;
|
2019-03-11 14:54:41 +01:00
|
|
|
$this->context->defineGlobals();
|
2019-03-24 21:17:14 +01:00
|
|
|
$this->context->collect_exceptions = $codebase->config->check_for_throws_in_global_scope;
|
2017-12-06 06:56:00 +01:00
|
|
|
|
2018-10-17 21:52:26 +02:00
|
|
|
try {
|
|
|
|
$stmts = $codebase->getStatementsForFile($this->file_path);
|
|
|
|
} catch (PhpParser\Error $e) {
|
|
|
|
return;
|
|
|
|
}
|
2020-08-10 16:49:53 +02:00
|
|
|
foreach ($codebase->config->before_file_checks as $plugin_class) {
|
|
|
|
$plugin_class::beforeAnalyzeFile($this, $this->context, $file_storage, $codebase);
|
2019-08-18 16:40:07 +02:00
|
|
|
}
|
2016-11-02 07:29:00 +01:00
|
|
|
|
2019-06-05 17:15:52 +02:00
|
|
|
if ($codebase->alter_code) {
|
|
|
|
foreach ($stmts as $stmt) {
|
|
|
|
if (!$stmt instanceof PhpParser\Node\Stmt\Declare_) {
|
|
|
|
$this->first_statement_offset = (int) $stmt->getAttribute('startFilePos');
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-25 22:11:02 +02:00
|
|
|
$leftover_stmts = $this->populateCheckers($stmts);
|
2016-07-24 23:06:54 +02:00
|
|
|
|
2019-11-25 17:44:54 +01:00
|
|
|
$this->node_data = new \Psalm\Internal\Provider\NodeDataProvider();
|
|
|
|
$statements_analyzer = new StatementsAnalyzer($this, $this->node_data);
|
|
|
|
|
2020-03-29 00:54:55 +01:00
|
|
|
foreach ($file_storage->docblock_issues as $docblock_issue) {
|
|
|
|
IssueBuffer::add($docblock_issue);
|
|
|
|
}
|
|
|
|
|
2017-01-02 21:31:18 +01:00
|
|
|
// if there are any leftover statements, evaluate them,
|
|
|
|
// in turn causing the classes/interfaces be evaluated
|
|
|
|
if ($leftover_stmts) {
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer->analyze($leftover_stmts, $this->context, $global_context, true);
|
2020-06-13 21:48:12 +02:00
|
|
|
|
|
|
|
foreach ($leftover_stmts as $leftover_stmt) {
|
|
|
|
if ($leftover_stmt instanceof PhpParser\Node\Stmt\Return_) {
|
|
|
|
if ($leftover_stmt->expr) {
|
2020-06-13 22:45:54 +02:00
|
|
|
$this->return_type = $statements_analyzer->node_data->getType($leftover_stmt->expr)
|
|
|
|
?: Type::getMixed();
|
2020-06-13 21:48:12 +02:00
|
|
|
} else {
|
|
|
|
$this->return_type = Type::getVoid();
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2016-12-28 18:59:51 +01:00
|
|
|
}
|
|
|
|
|
2017-01-02 21:31:18 +01:00
|
|
|
// check any leftover interfaces not already evaluated
|
2018-11-11 18:01:14 +01:00
|
|
|
foreach ($this->interface_analyzers_to_analyze as $interface_analyzer) {
|
|
|
|
$interface_analyzer->analyze();
|
2016-12-28 18:59:51 +01:00
|
|
|
}
|
|
|
|
|
2017-01-02 21:31:18 +01:00
|
|
|
// check any leftover classes not already evaluated
|
2018-10-17 21:52:26 +02:00
|
|
|
|
2018-11-11 18:01:14 +01:00
|
|
|
foreach ($this->class_analyzers_to_analyze as $class_analyzer) {
|
|
|
|
$class_analyzer->analyze(null, $this->context);
|
2016-08-05 21:11:20 +02:00
|
|
|
}
|
|
|
|
|
2018-11-11 18:01:14 +01:00
|
|
|
if (!$preserve_analyzers) {
|
|
|
|
$this->class_analyzers_to_analyze = [];
|
2018-11-18 17:39:14 +01:00
|
|
|
$this->interface_analyzers_to_analyze = [];
|
2017-01-12 03:37:53 +01:00
|
|
|
}
|
2019-03-24 21:17:14 +01:00
|
|
|
|
|
|
|
if ($codebase->config->check_for_throws_in_global_scope) {
|
|
|
|
$uncaught_throws = $statements_analyzer->getUncaughtThrows($this->context);
|
2019-03-29 00:43:14 +01:00
|
|
|
foreach ($uncaught_throws as $possibly_thrown_exception => $codelocations) {
|
|
|
|
foreach ($codelocations as $codelocation) {
|
2019-03-29 00:50:29 +01:00
|
|
|
// issues are suppressed in ThrowAnalyzer, CallAnalyzer, etc.
|
2019-03-29 00:43:14 +01:00
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
new UncaughtThrowInGlobalScope(
|
|
|
|
$possibly_thrown_exception . ' is thrown but not caught in global scope',
|
|
|
|
$codelocation
|
2019-03-29 00:50:29 +01:00
|
|
|
)
|
2019-03-29 00:43:14 +01:00
|
|
|
)) {
|
|
|
|
// fall through
|
|
|
|
}
|
2019-03-24 21:17:14 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-08-10 16:49:53 +02:00
|
|
|
|
2020-08-17 04:53:53 +02:00
|
|
|
// validate type imports
|
|
|
|
if ($file_storage->type_aliases) {
|
|
|
|
foreach ($file_storage->type_aliases as $alias) {
|
|
|
|
if ($alias instanceof LinkableTypeAlias) {
|
|
|
|
$location = new DocblockTypeLocation(
|
|
|
|
$this->getSource(),
|
|
|
|
$alias->start_offset,
|
|
|
|
$alias->end_offset,
|
|
|
|
$alias->line_number
|
|
|
|
);
|
|
|
|
$fq_source_classlike = $alias->declaring_fq_classlike_name;
|
|
|
|
if (ClassLikeAnalyzer::checkFullyQualifiedClassLikeName(
|
|
|
|
$this->getSource(),
|
|
|
|
$fq_source_classlike,
|
|
|
|
$location,
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
$this->suppressed_issues,
|
|
|
|
true,
|
|
|
|
false,
|
|
|
|
true,
|
|
|
|
true
|
|
|
|
) === false) {
|
|
|
|
continue;
|
2020-09-22 06:44:31 +02:00
|
|
|
}
|
2020-08-17 04:53:53 +02:00
|
|
|
|
|
|
|
$referenced_class_storage = $codebase->classlike_storage_provider->get($fq_source_classlike);
|
|
|
|
if (!isset($referenced_class_storage->type_aliases[$alias->alias_name])) {
|
|
|
|
IssueBuffer::accepts(
|
|
|
|
new InvalidTypeImport(
|
|
|
|
'Type alias ' . $alias->alias_name
|
|
|
|
. ' imported from ' . $fq_source_classlike
|
|
|
|
. ' is not defined on the source class',
|
|
|
|
$location
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-10 16:49:53 +02:00
|
|
|
foreach ($codebase->config->after_file_checks as $plugin_class) {
|
|
|
|
$plugin_class::afterAnalyzeFile($this, $this->context, $file_storage, $codebase);
|
|
|
|
}
|
2016-01-08 00:28:27 +01:00
|
|
|
}
|
|
|
|
|
2017-07-25 22:11:02 +02:00
|
|
|
/**
|
2018-04-17 18:16:25 +02:00
|
|
|
* @param array<int, PhpParser\Node\Stmt> $stmts
|
2017-07-25 22:11:02 +02:00
|
|
|
*
|
2018-04-17 18:16:25 +02:00
|
|
|
* @return array<int, PhpParser\Node\Stmt>
|
2017-07-25 22:11:02 +02:00
|
|
|
*/
|
2020-09-04 22:26:33 +02:00
|
|
|
public function populateCheckers(array $stmts): array
|
2017-07-25 22:11:02 +02:00
|
|
|
{
|
|
|
|
$leftover_stmts = [];
|
|
|
|
|
|
|
|
foreach ($stmts as $stmt) {
|
2017-10-20 01:19:29 +02:00
|
|
|
if ($stmt instanceof PhpParser\Node\Stmt\ClassLike) {
|
2018-11-06 03:57:36 +01:00
|
|
|
$this->populateClassLikeAnalyzers($stmt);
|
2017-07-25 22:11:02 +02:00
|
|
|
} elseif ($stmt instanceof PhpParser\Node\Stmt\Namespace_) {
|
|
|
|
$namespace_name = $stmt->name ? implode('\\', $stmt->name->parts) : '';
|
|
|
|
|
2018-11-11 18:01:14 +01:00
|
|
|
$namespace_analyzer = new NamespaceAnalyzer($stmt, $this);
|
|
|
|
$namespace_analyzer->collectAnalyzableInformation();
|
2017-07-25 22:11:02 +02:00
|
|
|
|
2018-11-11 18:01:14 +01:00
|
|
|
$this->namespace_aliased_classes[$namespace_name] = $namespace_analyzer->getAliases()->uses;
|
2017-07-25 22:11:02 +02:00
|
|
|
$this->namespace_aliased_classes_flipped[$namespace_name] =
|
2018-11-11 18:01:14 +01:00
|
|
|
$namespace_analyzer->getAliasedClassesFlipped();
|
2019-06-06 04:13:33 +02:00
|
|
|
$this->namespace_aliased_classes_flipped_replaceable[$namespace_name] =
|
|
|
|
$namespace_analyzer->getAliasedClassesFlippedReplaceable();
|
2017-07-25 22:11:02 +02:00
|
|
|
} elseif ($stmt instanceof PhpParser\Node\Stmt\Use_) {
|
|
|
|
$this->visitUse($stmt);
|
|
|
|
} elseif ($stmt instanceof PhpParser\Node\Stmt\GroupUse) {
|
|
|
|
$this->visitGroupUse($stmt);
|
2018-02-25 16:43:54 +01:00
|
|
|
} else {
|
2017-10-20 01:19:29 +02:00
|
|
|
if ($stmt instanceof PhpParser\Node\Stmt\If_) {
|
|
|
|
foreach ($stmt->stmts as $if_stmt) {
|
|
|
|
if ($if_stmt instanceof PhpParser\Node\Stmt\ClassLike) {
|
2018-11-06 03:57:36 +01:00
|
|
|
$this->populateClassLikeAnalyzers($if_stmt);
|
2017-10-20 01:19:29 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-25 22:11:02 +02:00
|
|
|
$leftover_stmts[] = $stmt;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $leftover_stmts;
|
|
|
|
}
|
|
|
|
|
2017-10-20 01:19:29 +02:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
2018-11-06 03:57:36 +01:00
|
|
|
private function populateClassLikeAnalyzers(PhpParser\Node\Stmt\ClassLike $stmt)
|
2017-10-20 01:19:29 +02:00
|
|
|
{
|
2020-08-03 08:20:58 +02:00
|
|
|
if ($stmt instanceof PhpParser\Node\Stmt\Class_) {
|
|
|
|
if (!$stmt->name) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// this can happen when stubbing
|
|
|
|
if (!$this->codebase->classExists($stmt->name->name)) {
|
|
|
|
return;
|
|
|
|
}
|
2017-10-20 01:19:29 +02:00
|
|
|
|
2019-12-31 14:04:51 +01:00
|
|
|
|
2018-11-11 18:01:14 +01:00
|
|
|
$class_analyzer = new ClassAnalyzer($stmt, $this, $stmt->name->name);
|
2017-10-20 01:19:29 +02:00
|
|
|
|
2018-11-11 18:01:14 +01:00
|
|
|
$fq_class_name = $class_analyzer->getFQCLN();
|
2017-10-20 01:19:29 +02:00
|
|
|
|
2018-11-11 18:01:14 +01:00
|
|
|
$this->class_analyzers_to_analyze[strtolower($fq_class_name)] = $class_analyzer;
|
2017-10-20 01:19:29 +02:00
|
|
|
} elseif ($stmt instanceof PhpParser\Node\Stmt\Interface_) {
|
2020-08-03 08:20:58 +02:00
|
|
|
if (!$stmt->name) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// this can happen when stubbing
|
|
|
|
if (!$this->codebase->interfaceExists($stmt->name->name)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-11-11 18:01:14 +01:00
|
|
|
$class_analyzer = new InterfaceAnalyzer($stmt, $this, $stmt->name->name);
|
2017-10-20 01:19:29 +02:00
|
|
|
|
2018-11-11 18:01:14 +01:00
|
|
|
$fq_class_name = $class_analyzer->getFQCLN();
|
2017-10-20 01:19:29 +02:00
|
|
|
|
2018-11-11 18:01:14 +01:00
|
|
|
$this->interface_analyzers_to_analyze[$fq_class_name] = $class_analyzer;
|
2017-10-20 01:19:29 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-12 17:24:05 +02:00
|
|
|
public function addNamespacedClassAnalyzer(string $fq_class_name, ClassAnalyzer $class_analyzer): void
|
2017-02-02 00:11:00 +01:00
|
|
|
{
|
2018-11-11 18:01:14 +01:00
|
|
|
$this->class_analyzers_to_analyze[strtolower($fq_class_name)] = $class_analyzer;
|
2017-02-02 00:11:00 +01:00
|
|
|
}
|
|
|
|
|
2020-09-12 17:24:05 +02:00
|
|
|
public function addNamespacedInterfaceAnalyzer(string $fq_class_name, InterfaceAnalyzer $interface_analyzer): void
|
2017-02-02 00:11:00 +01:00
|
|
|
{
|
2018-11-11 18:01:14 +01:00
|
|
|
$this->interface_analyzers_to_analyze[strtolower($fq_class_name)] = $interface_analyzer;
|
2017-02-02 00:11:00 +01:00
|
|
|
}
|
|
|
|
|
2017-01-12 03:37:53 +01:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
2020-02-15 02:54:26 +01:00
|
|
|
public function getMethodMutations(
|
|
|
|
\Psalm\Internal\MethodIdentifier $method_id,
|
|
|
|
Context $this_context,
|
|
|
|
bool $from_project_analyzer = false
|
|
|
|
) {
|
|
|
|
$fq_class_name = $method_id->fq_class_name;
|
|
|
|
$method_name = $method_id->method_name;
|
|
|
|
$fq_class_name_lc = strtolower($fq_class_name);
|
2017-06-30 07:24:45 +02:00
|
|
|
|
2020-02-15 02:54:26 +01:00
|
|
|
if (isset($this->class_analyzers_to_analyze[$fq_class_name_lc])) {
|
|
|
|
$class_analyzer_to_examine = $this->class_analyzers_to_analyze[$fq_class_name_lc];
|
2017-07-25 22:11:02 +02:00
|
|
|
} else {
|
2019-03-01 23:30:55 +01:00
|
|
|
if (!$from_project_analyzer) {
|
2019-03-02 21:07:26 +01:00
|
|
|
$this->project_analyzer->getMethodMutations(
|
|
|
|
$method_id,
|
|
|
|
$this_context,
|
|
|
|
$this->getRootFilePath(),
|
|
|
|
$this->getRootFileName()
|
|
|
|
);
|
2019-03-01 23:30:55 +01:00
|
|
|
}
|
2017-06-30 07:24:45 +02:00
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$call_context = new Context($this_context->self);
|
2019-03-01 23:30:55 +01:00
|
|
|
$call_context->collect_mutations = $this_context->collect_mutations;
|
2018-01-24 19:38:53 +01:00
|
|
|
$call_context->collect_initializations = $this_context->collect_initializations;
|
2020-04-21 06:04:47 +02:00
|
|
|
$call_context->collect_nonprivate_initializations = $this_context->collect_nonprivate_initializations;
|
2018-01-24 19:38:53 +01:00
|
|
|
$call_context->initialized_methods = $this_context->initialized_methods;
|
2017-06-30 07:24:45 +02:00
|
|
|
$call_context->include_location = $this_context->include_location;
|
2020-03-26 17:35:27 +01:00
|
|
|
$call_context->calling_method_id = $this_context->calling_method_id;
|
2017-01-12 03:37:53 +01:00
|
|
|
|
2018-01-28 18:01:51 +01:00
|
|
|
foreach ($this_context->vars_possibly_in_scope as $var => $_) {
|
2017-01-12 03:37:53 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-18 20:21:06 +02:00
|
|
|
if (!isset($this_context->vars_in_scope['$this'])) {
|
|
|
|
throw new \UnexpectedValueException('Should exist');
|
|
|
|
}
|
|
|
|
|
2017-01-12 03:37:53 +01:00
|
|
|
$call_context->vars_in_scope['$this'] = $this_context->vars_in_scope['$this'];
|
|
|
|
|
2018-11-11 18:01:14 +01:00
|
|
|
$class_analyzer_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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-15 02:54:26 +01:00
|
|
|
public function getFunctionLikeAnalyzer(\Psalm\Internal\MethodIdentifier $method_id) : ?FunctionLikeAnalyzer
|
2019-05-31 07:47:35 +02:00
|
|
|
{
|
2020-02-15 02:54:26 +01:00
|
|
|
$fq_class_name = $method_id->fq_class_name;
|
|
|
|
$method_name = $method_id->method_name;
|
|
|
|
|
|
|
|
$fq_class_name_lc = strtolower($fq_class_name);
|
2019-05-31 07:47:35 +02:00
|
|
|
|
2020-02-15 02:54:26 +01:00
|
|
|
if (!isset($this->class_analyzers_to_analyze[$fq_class_name_lc])) {
|
2019-05-31 17:55:24 +02:00
|
|
|
return null;
|
2019-05-31 07:47:35 +02:00
|
|
|
}
|
|
|
|
|
2020-02-15 02:54:26 +01:00
|
|
|
$class_analyzer_to_examine = $this->class_analyzers_to_analyze[$fq_class_name_lc];
|
2019-05-31 07:47:35 +02:00
|
|
|
|
|
|
|
return $class_analyzer_to_examine->getFunctionLikeAnalyzer($method_name);
|
|
|
|
}
|
|
|
|
|
2020-09-04 22:26:33 +02:00
|
|
|
public function getNamespace(): ?string
|
2016-01-20 00:27:06 +01:00
|
|
|
{
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2016-11-13 00:51:48 +01:00
|
|
|
/**
|
|
|
|
* @return array<string, string>
|
|
|
|
*/
|
2020-09-07 01:36:47 +02:00
|
|
|
public function getAliasedClassesFlipped(?string $namespace_name = null): array
|
2016-11-13 00:51:48 +01:00
|
|
|
{
|
|
|
|
if ($namespace_name && isset($this->namespace_aliased_classes_flipped[$namespace_name])) {
|
|
|
|
return $this->namespace_aliased_classes_flipped[$namespace_name];
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->aliased_classes_flipped;
|
|
|
|
}
|
|
|
|
|
2019-06-06 04:13:33 +02:00
|
|
|
/**
|
|
|
|
* @return array<string, string>
|
|
|
|
*/
|
2020-09-07 01:36:47 +02:00
|
|
|
public function getAliasedClassesFlippedReplaceable(?string $namespace_name = null): array
|
2019-06-06 04:13:33 +02:00
|
|
|
{
|
|
|
|
if ($namespace_name && isset($this->namespace_aliased_classes_flipped_replaceable[$namespace_name])) {
|
|
|
|
return $this->namespace_aliased_classes_flipped_replaceable[$namespace_name];
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->aliased_classes_flipped_replaceable;
|
|
|
|
}
|
|
|
|
|
2020-09-12 17:24:05 +02:00
|
|
|
public static function clearCache(): void
|
2016-03-23 18:05:25 +01:00
|
|
|
{
|
2020-05-14 01:12:45 +02:00
|
|
|
\Psalm\Internal\Type\TypeTokenizer::clearCache();
|
2019-05-17 00:36:36 +02:00
|
|
|
\Psalm\Internal\Codebase\Reflection::clearCache();
|
|
|
|
\Psalm\Internal\Codebase\Functions::clearCache();
|
2016-12-08 21:57:18 +01:00
|
|
|
IssueBuffer::clearCache();
|
2018-01-06 01:49:27 +01:00
|
|
|
FileManipulationBuffer::clearCache();
|
2018-11-06 03:57:36 +01:00
|
|
|
FunctionLikeAnalyzer::clearCache();
|
|
|
|
\Psalm\Internal\Provider\ClassLikeStorageProvider::deleteAll();
|
|
|
|
\Psalm\Internal\Provider\FileStorageProvider::deleteAll();
|
|
|
|
\Psalm\Internal\Provider\FileReferenceProvider::clearCache();
|
2016-05-10 20:00:44 +02:00
|
|
|
}
|
2016-11-06 05:59:29 +01:00
|
|
|
|
2020-09-04 22:26:33 +02:00
|
|
|
public function getFileName(): string
|
2017-01-07 20:35:07 +01:00
|
|
|
{
|
|
|
|
return $this->file_name;
|
|
|
|
}
|
|
|
|
|
2020-09-04 22:26:33 +02:00
|
|
|
public function getFilePath(): string
|
2017-01-07 20:35:07 +01:00
|
|
|
{
|
|
|
|
return $this->file_path;
|
|
|
|
}
|
|
|
|
|
2020-09-12 17:24:05 +02:00
|
|
|
public function getRootFileName(): string
|
2017-01-07 20:35:07 +01:00
|
|
|
{
|
2018-05-30 22:19:18 +02:00
|
|
|
return $this->root_file_name ?: $this->file_name;
|
2017-01-07 20:35:07 +01:00
|
|
|
}
|
|
|
|
|
2020-09-12 17:24:05 +02:00
|
|
|
public function getRootFilePath(): string
|
2017-01-07 20:35:07 +01:00
|
|
|
{
|
2018-05-30 22:19:18 +02:00
|
|
|
return $this->root_file_path ?: $this->file_path;
|
2018-05-23 05:38:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
2020-09-07 01:36:47 +02:00
|
|
|
public function setRootFilePath(string $file_path, string $file_name)
|
2018-05-23 05:38:27 +02:00
|
|
|
{
|
2018-05-30 22:19:18 +02:00
|
|
|
$this->root_file_name = $file_name;
|
|
|
|
$this->root_file_path = $file_path;
|
2018-05-23 05:38:27 +02:00
|
|
|
}
|
|
|
|
|
2020-09-12 17:24:05 +02:00
|
|
|
public function addRequiredFilePath(string $file_path): void
|
2018-05-23 05:38:27 +02:00
|
|
|
{
|
2018-05-30 22:19:18 +02:00
|
|
|
$this->required_file_paths[$file_path] = true;
|
|
|
|
}
|
2018-05-23 05:38:27 +02:00
|
|
|
|
2020-09-12 17:24:05 +02:00
|
|
|
public function addParentFilePath(string $file_path): void
|
2018-05-30 22:19:18 +02:00
|
|
|
{
|
|
|
|
$this->parent_file_paths[$file_path] = true;
|
2018-05-23 05:38:27 +02:00
|
|
|
}
|
|
|
|
|
2020-09-07 01:36:47 +02:00
|
|
|
public function hasParentFilePath(string $file_path): bool
|
2018-05-23 05:38:27 +02:00
|
|
|
{
|
2018-05-30 22:19:18 +02:00
|
|
|
return $this->file_path === $file_path || isset($this->parent_file_paths[$file_path]);
|
2018-05-23 05:38:27 +02:00
|
|
|
}
|
|
|
|
|
2020-09-07 01:36:47 +02:00
|
|
|
public function hasAlreadyRequiredFilePath(string $file_path): bool
|
2018-05-23 05:38:27 +02:00
|
|
|
{
|
2018-05-30 18:23:53 +02:00
|
|
|
return isset($this->required_file_paths[$file_path]);
|
2018-05-23 05:38:27 +02:00
|
|
|
}
|
|
|
|
|
2018-05-30 22:19:18 +02:00
|
|
|
/**
|
|
|
|
* @return array<int, string>
|
|
|
|
*/
|
2020-09-04 22:26:33 +02:00
|
|
|
public function getRequiredFilePaths(): array
|
2018-05-30 22:19:18 +02:00
|
|
|
{
|
|
|
|
return array_keys($this->required_file_paths);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return array<int, string>
|
|
|
|
*/
|
2020-09-04 22:26:33 +02:00
|
|
|
public function getParentFilePaths(): array
|
2018-05-30 22:19:18 +02:00
|
|
|
{
|
|
|
|
return array_keys($this->parent_file_paths);
|
|
|
|
}
|
|
|
|
|
2020-09-04 22:26:33 +02:00
|
|
|
public function getRequireNesting(): int
|
2018-05-23 05:38:27 +02:00
|
|
|
{
|
2018-05-30 22:19:18 +02:00
|
|
|
return count($this->parent_file_paths);
|
2017-01-07 20:35:07 +01:00
|
|
|
}
|
|
|
|
|
2017-11-26 22:03:17 +01:00
|
|
|
/**
|
2019-08-18 18:25:48 +02:00
|
|
|
* @return array<string>
|
2017-11-26 22:03:17 +01:00
|
|
|
*/
|
2020-09-04 22:26:33 +02:00
|
|
|
public function getSuppressedIssues(): array
|
2017-01-07 20:35:07 +01:00
|
|
|
{
|
|
|
|
return $this->suppressed_issues;
|
|
|
|
}
|
|
|
|
|
2017-10-27 00:19:19 +02:00
|
|
|
/**
|
|
|
|
* @param array<int, string> $new_issues
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function addSuppressedIssues(array $new_issues)
|
|
|
|
{
|
2019-08-18 18:25:48 +02:00
|
|
|
if (isset($new_issues[0])) {
|
|
|
|
$new_issues = \array_combine($new_issues, $new_issues);
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->suppressed_issues = $new_issues + $this->suppressed_issues;
|
2017-10-27 00:19:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param array<int, string> $new_issues
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function removeSuppressedIssues(array $new_issues)
|
|
|
|
{
|
2019-08-18 18:25:48 +02:00
|
|
|
if (isset($new_issues[0])) {
|
|
|
|
$new_issues = \array_combine($new_issues, $new_issues);
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->suppressed_issues = \array_diff_key($this->suppressed_issues, $new_issues);
|
2017-10-27 00:19:19 +02:00
|
|
|
}
|
|
|
|
|
2020-09-04 22:26:33 +02:00
|
|
|
public function getFQCLN(): ?string
|
2017-01-07 20:35:07 +01:00
|
|
|
{
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2020-09-04 22:26:33 +02:00
|
|
|
public function getParentFQCLN(): ?string
|
2019-05-25 17:51:09 +02:00
|
|
|
{
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2020-09-04 22:26:33 +02:00
|
|
|
public function getClassName(): ?string
|
2017-01-09 06:26:40 +01:00
|
|
|
{
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2018-12-18 05:29:27 +01:00
|
|
|
/**
|
2019-03-22 20:59:10 +01:00
|
|
|
* @return array<string, array<string, array{Type\Union}>>|null
|
2018-12-18 05:29:27 +01:00
|
|
|
*/
|
2020-09-04 22:26:33 +02:00
|
|
|
public function getTemplateTypeMap(): ?array
|
2018-12-18 05:29:27 +01:00
|
|
|
{
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2020-09-04 22:26:33 +02:00
|
|
|
public function isStatic(): bool
|
2017-01-07 20:35:07 +01:00
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
2017-03-20 04:30:20 +01:00
|
|
|
|
2020-08-28 18:42:55 +02:00
|
|
|
/**
|
|
|
|
* @psalm-mutation-free
|
|
|
|
*/
|
2018-11-06 03:57:36 +01:00
|
|
|
public function getFileAnalyzer() : FileAnalyzer
|
2017-03-20 04:30:20 +01:00
|
|
|
{
|
|
|
|
return $this;
|
|
|
|
}
|
2018-11-06 03:57:36 +01:00
|
|
|
|
2020-08-28 18:48:33 +02:00
|
|
|
/**
|
|
|
|
* @psalm-mutation-free
|
|
|
|
*/
|
2018-11-06 03:57:36 +01:00
|
|
|
public function getProjectAnalyzer() : ProjectAnalyzer
|
|
|
|
{
|
2018-11-11 18:01:14 +01:00
|
|
|
return $this->project_analyzer;
|
2018-11-06 03:57:36 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public function getCodebase() : Codebase
|
|
|
|
{
|
|
|
|
return $this->codebase;
|
|
|
|
}
|
2019-06-05 17:15:52 +02:00
|
|
|
|
|
|
|
public function getFirstStatementOffset() : int
|
|
|
|
{
|
|
|
|
return $this->first_statement_offset;
|
|
|
|
}
|
2019-06-30 03:06:21 +02:00
|
|
|
|
2019-11-25 17:44:54 +01:00
|
|
|
public function getNodeTypeProvider() : \Psalm\NodeTypeProvider
|
|
|
|
{
|
|
|
|
if (!$this->node_data) {
|
|
|
|
throw new \UnexpectedValueException('There should be a node type provider');
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->node_data;
|
|
|
|
}
|
|
|
|
|
2020-06-13 21:48:12 +02:00
|
|
|
public function getReturnType() : ?Type\Union
|
|
|
|
{
|
|
|
|
return $this->return_type;
|
|
|
|
}
|
|
|
|
|
2019-06-30 03:13:02 +02:00
|
|
|
public function clearSourceBeforeDestruction() : void
|
2019-06-30 03:06:21 +02:00
|
|
|
{
|
2019-06-30 03:13:02 +02:00
|
|
|
/** @psalm-suppress PossiblyNullPropertyAssignmentValue */
|
2019-06-30 03:06:21 +02:00
|
|
|
$this->source = null;
|
|
|
|
}
|
2016-01-08 00:28:27 +01:00
|
|
|
}
|