2017-07-25 22:11:02 +02:00
|
|
|
<?php
|
2020-03-15 04:54:42 +01:00
|
|
|
namespace Psalm\Internal\PhpVisitor;
|
2017-07-25 22:11:02 +02:00
|
|
|
|
2019-07-05 22:24:00 +02:00
|
|
|
use function array_pop;
|
|
|
|
use function count;
|
|
|
|
use function end;
|
|
|
|
use function implode;
|
|
|
|
use function in_array;
|
|
|
|
use function is_string;
|
2017-07-25 22:11:02 +02:00
|
|
|
use PhpParser;
|
|
|
|
use Psalm\Aliases;
|
2018-01-21 19:38:51 +01:00
|
|
|
use Psalm\Codebase;
|
2017-07-25 22:11:02 +02:00
|
|
|
use Psalm\CodeLocation;
|
|
|
|
use Psalm\Exception\DocblockParseException;
|
2017-10-22 07:04:35 +02:00
|
|
|
use Psalm\Exception\TypeParseTreeException;
|
2018-02-23 21:39:33 +01:00
|
|
|
use Psalm\FileSource;
|
2019-07-05 22:24:00 +02:00
|
|
|
use Psalm\Internal\Analyzer\ClassLikeAnalyzer;
|
|
|
|
use Psalm\Internal\Analyzer\CommentAnalyzer;
|
2020-05-19 18:56:23 +02:00
|
|
|
use Psalm\Internal\Analyzer\Statements\Expression\SimpleTypeInferer;
|
2019-07-05 22:24:00 +02:00
|
|
|
use Psalm\Internal\Scanner\FileScanner;
|
|
|
|
use Psalm\Internal\Scanner\PhpStormMetaScanner;
|
2020-05-14 01:29:59 +02:00
|
|
|
use Psalm\Internal\Type\TypeAlias;
|
2020-05-14 01:12:45 +02:00
|
|
|
use Psalm\Internal\Type\TypeParser;
|
2017-07-25 22:11:02 +02:00
|
|
|
use Psalm\Issue\InvalidDocblock;
|
2017-07-29 21:05:06 +02:00
|
|
|
use Psalm\Storage\FileStorage;
|
2017-07-25 22:11:02 +02:00
|
|
|
use Psalm\Storage\MethodStorage;
|
|
|
|
use Psalm\Type;
|
2019-06-26 22:52:29 +02:00
|
|
|
use function strpos;
|
2019-07-05 22:24:00 +02:00
|
|
|
use function strtolower;
|
2017-07-25 22:11:02 +02:00
|
|
|
|
2018-12-02 00:37:49 +01:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
2020-10-15 19:23:35 +02:00
|
|
|
class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements FileSource
|
2017-07-25 22:11:02 +02:00
|
|
|
{
|
2020-10-24 01:53:04 +02:00
|
|
|
/**
|
|
|
|
* @var Aliases
|
|
|
|
*/
|
2018-01-21 18:44:46 +01:00
|
|
|
private $aliases;
|
2017-07-25 22:11:02 +02:00
|
|
|
|
|
|
|
/**
|
2017-08-14 21:46:01 +02:00
|
|
|
* @var string[]
|
2017-07-25 22:11:02 +02:00
|
|
|
*/
|
2018-01-21 18:44:46 +01:00
|
|
|
private $fq_classlike_names = [];
|
2017-07-25 22:11:02 +02:00
|
|
|
|
2020-10-24 01:53:04 +02:00
|
|
|
/**
|
|
|
|
* @var FileScanner
|
|
|
|
*/
|
2018-01-21 18:44:46 +01:00
|
|
|
private $file_scanner;
|
2017-07-25 22:11:02 +02:00
|
|
|
|
2020-10-24 01:53:04 +02:00
|
|
|
/**
|
|
|
|
* @var Codebase
|
|
|
|
*/
|
2018-01-21 19:38:51 +01:00
|
|
|
private $codebase;
|
|
|
|
|
2020-10-24 01:53:04 +02:00
|
|
|
/**
|
|
|
|
* @var string
|
|
|
|
*/
|
2018-01-21 18:44:46 +01:00
|
|
|
private $file_path;
|
2017-07-25 22:11:02 +02:00
|
|
|
|
2020-10-24 01:53:04 +02:00
|
|
|
/**
|
|
|
|
* @var bool
|
|
|
|
*/
|
2018-01-21 18:44:46 +01:00
|
|
|
private $scan_deep;
|
2017-07-25 22:11:02 +02:00
|
|
|
|
2020-10-24 01:53:04 +02:00
|
|
|
/**
|
|
|
|
* @var FileStorage
|
|
|
|
*/
|
2018-01-21 18:44:46 +01:00
|
|
|
private $file_storage;
|
2017-07-29 21:05:06 +02:00
|
|
|
|
2020-10-24 01:53:04 +02:00
|
|
|
/**
|
|
|
|
* @var array<Reflector\FunctionLikeNodeScanner>
|
|
|
|
*/
|
|
|
|
private $functionlike_node_scanners = [];
|
2017-11-07 20:46:53 +01:00
|
|
|
|
2020-10-24 01:53:04 +02:00
|
|
|
/**
|
|
|
|
* @var array<Reflector\ClassLikeNodeScanner>
|
|
|
|
*/
|
|
|
|
private $classlike_node_scanners = [];
|
2019-02-07 18:25:57 +01:00
|
|
|
|
2020-10-24 01:53:04 +02:00
|
|
|
/**
|
|
|
|
* @var array<class-string<\Psalm\Plugin\Hook\AfterClassLikeVisitInterface>>
|
|
|
|
*/
|
|
|
|
private $after_classlike_check_plugins;
|
2019-02-07 18:25:57 +01:00
|
|
|
|
2020-10-24 01:53:04 +02:00
|
|
|
/**
|
|
|
|
* @var PhpParser\Node\Name|null
|
|
|
|
*/
|
2019-06-04 22:36:32 +02:00
|
|
|
private $namespace_name;
|
|
|
|
|
2020-10-24 01:53:04 +02:00
|
|
|
/**
|
|
|
|
* @var PhpParser\Node\Expr|null
|
|
|
|
*/
|
2019-06-10 05:50:18 +02:00
|
|
|
private $exists_cond_expr;
|
|
|
|
|
|
|
|
/**
|
2019-07-11 00:58:56 +02:00
|
|
|
* @var ?int
|
2019-06-10 05:50:18 +02:00
|
|
|
*/
|
2019-07-11 00:58:56 +02:00
|
|
|
private $skip_if_descendants = null;
|
2019-06-10 05:50:18 +02:00
|
|
|
|
2018-07-15 23:23:17 +02:00
|
|
|
/**
|
2020-05-14 01:29:59 +02:00
|
|
|
* @var array<string, TypeAlias>
|
2018-07-15 23:23:17 +02:00
|
|
|
*/
|
|
|
|
private $type_aliases = [];
|
|
|
|
|
2020-10-01 21:07:25 +02:00
|
|
|
/**
|
|
|
|
* @var array<int, bool>
|
|
|
|
*/
|
|
|
|
private $bad_classes = [];
|
|
|
|
|
2018-02-23 04:22:31 +01:00
|
|
|
public function __construct(
|
|
|
|
Codebase $codebase,
|
2020-10-24 01:53:04 +02:00
|
|
|
FileScanner $file_scanner,
|
|
|
|
FileStorage $file_storage
|
2018-02-23 04:22:31 +01:00
|
|
|
) {
|
2018-01-21 19:38:51 +01:00
|
|
|
$this->codebase = $codebase;
|
2018-01-21 18:44:46 +01:00
|
|
|
$this->file_scanner = $file_scanner;
|
2018-01-21 19:38:51 +01:00
|
|
|
$this->file_path = $file_scanner->file_path;
|
2018-01-21 18:44:46 +01:00
|
|
|
$this->scan_deep = $file_scanner->will_analyze;
|
|
|
|
$this->file_storage = $file_storage;
|
2019-06-30 03:32:26 +02:00
|
|
|
$this->aliases = $this->file_storage->aliases = new Aliases();
|
2020-10-24 01:53:04 +02:00
|
|
|
$this->after_classlike_check_plugins = $this->codebase->config->after_visit_classlikes;
|
2017-07-25 22:11:02 +02:00
|
|
|
}
|
|
|
|
|
2020-09-13 22:39:06 +02:00
|
|
|
public function enterNode(PhpParser\Node $node): ?int
|
2017-07-25 22:11:02 +02:00
|
|
|
{
|
2018-07-22 04:24:33 +02:00
|
|
|
foreach ($node->getComments() as $comment) {
|
2020-08-18 15:34:07 +02:00
|
|
|
if ($comment instanceof PhpParser\Comment\Doc && !$node instanceof PhpParser\Node\Stmt\ClassLike) {
|
2020-06-07 18:00:55 +02:00
|
|
|
$self_fqcln = $node instanceof PhpParser\Node\Stmt\ClassLike
|
|
|
|
&& $node->name !== null
|
|
|
|
? ($this->aliases->namespace ? $this->aliases->namespace . '\\' : '') . $node->name->name
|
|
|
|
: null;
|
|
|
|
|
2018-07-22 04:24:33 +02:00
|
|
|
try {
|
2020-11-05 05:25:08 +01:00
|
|
|
$type_aliases = Reflector\ClassLikeNodeScanner::getTypeAliasesFromComment(
|
2019-06-01 17:53:32 +02:00
|
|
|
$comment,
|
2018-07-22 04:24:33 +02:00
|
|
|
$this->aliases,
|
2020-06-07 18:00:55 +02:00
|
|
|
$this->type_aliases,
|
|
|
|
$self_fqcln
|
2018-07-22 04:24:33 +02:00
|
|
|
);
|
|
|
|
|
2020-05-14 06:41:50 +02:00
|
|
|
foreach ($type_aliases as $type_alias) {
|
|
|
|
// finds issues, if there are any
|
|
|
|
TypeParser::parseTokens($type_alias->replacement_tokens);
|
2018-07-22 04:24:33 +02:00
|
|
|
}
|
|
|
|
|
2020-05-14 06:41:50 +02:00
|
|
|
$this->type_aliases += $type_aliases;
|
2018-07-22 04:24:33 +02:00
|
|
|
} catch (DocblockParseException $e) {
|
2020-03-29 00:54:55 +01:00
|
|
|
$this->file_storage->docblock_issues[] = new InvalidDocblock(
|
2020-12-02 00:26:15 +01:00
|
|
|
$e->getMessage(),
|
2020-03-29 00:54:55 +01:00
|
|
|
new CodeLocation($this->file_scanner, $node, null, true)
|
|
|
|
);
|
2018-07-22 04:24:33 +02:00
|
|
|
} catch (TypeParseTreeException $e) {
|
2020-03-29 00:54:55 +01:00
|
|
|
$this->file_storage->docblock_issues[] = new InvalidDocblock(
|
2020-12-02 00:26:15 +01:00
|
|
|
$e->getMessage(),
|
2020-03-29 00:54:55 +01:00
|
|
|
new CodeLocation($this->file_scanner, $node, null, true)
|
|
|
|
);
|
2018-07-22 04:24:33 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-25 22:11:02 +02:00
|
|
|
if ($node instanceof PhpParser\Node\Stmt\Namespace_) {
|
2020-10-24 01:53:04 +02:00
|
|
|
$this->handleNamespace($node);
|
2017-07-25 22:11:02 +02:00
|
|
|
} elseif ($node instanceof PhpParser\Node\Stmt\Use_) {
|
2020-10-24 01:53:04 +02:00
|
|
|
$this->handleUse($node);
|
2017-07-25 22:11:02 +02:00
|
|
|
} elseif ($node instanceof PhpParser\Node\Stmt\GroupUse) {
|
2020-10-24 01:53:04 +02:00
|
|
|
$this->handleGroupUse($node);
|
2017-07-25 22:11:02 +02:00
|
|
|
} elseif ($node instanceof PhpParser\Node\Stmt\ClassLike) {
|
2019-06-28 16:17:59 +02:00
|
|
|
if ($this->skip_if_descendants) {
|
2020-09-13 22:39:06 +02:00
|
|
|
return null;
|
2019-06-28 16:17:59 +02:00
|
|
|
}
|
|
|
|
|
2020-10-24 01:53:04 +02:00
|
|
|
$classlike_node_scanner = new Reflector\ClassLikeNodeScanner(
|
|
|
|
$this->codebase,
|
|
|
|
$this->file_storage,
|
|
|
|
$this->file_scanner,
|
|
|
|
$this->aliases,
|
|
|
|
$this->namespace_name
|
|
|
|
);
|
|
|
|
|
|
|
|
$this->classlike_node_scanners[] = $classlike_node_scanner;
|
|
|
|
|
|
|
|
if ($classlike_node_scanner->start($node) === false) {
|
2020-10-01 21:07:25 +02:00
|
|
|
$this->bad_classes[\spl_object_id($node)] = true;
|
|
|
|
return PhpParser\NodeTraverser::DONT_TRAVERSE_CHILDREN;
|
2018-01-21 18:44:46 +01:00
|
|
|
}
|
2017-07-25 22:11:02 +02:00
|
|
|
|
2020-10-24 01:53:04 +02:00
|
|
|
$this->type_aliases += $classlike_node_scanner->type_aliases;
|
2017-07-25 22:11:02 +02:00
|
|
|
} elseif ($node instanceof PhpParser\Node\Stmt\TryCatch) {
|
|
|
|
foreach ($node->catches as $catch) {
|
|
|
|
foreach ($catch->types as $catch_type) {
|
2018-11-06 03:57:36 +01:00
|
|
|
$catch_fqcln = ClassLikeAnalyzer::getFQCLNFromNameObject($catch_type, $this->aliases);
|
2017-07-25 22:11:02 +02:00
|
|
|
|
2017-11-15 03:56:29 +01:00
|
|
|
if (!in_array(strtolower($catch_fqcln), ['self', 'static', 'parent'], true)) {
|
2020-04-02 23:17:55 +02:00
|
|
|
$this->codebase->scanner->queueClassLikeForScanning($catch_fqcln);
|
2018-02-23 04:22:31 +01:00
|
|
|
$this->file_storage->referenced_classlikes[strtolower($catch_fqcln)] = $catch_fqcln;
|
2017-07-25 22:11:02 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-11-28 00:07:38 +01:00
|
|
|
} elseif ($node instanceof PhpParser\Node\FunctionLike) {
|
2019-06-28 16:17:59 +02:00
|
|
|
if ($node instanceof PhpParser\Node\Stmt\Function_
|
|
|
|
|| $node instanceof PhpParser\Node\Stmt\ClassMethod
|
|
|
|
) {
|
|
|
|
if ($this->skip_if_descendants) {
|
2020-09-13 22:39:06 +02:00
|
|
|
return null;
|
2019-06-28 16:17:59 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-24 01:53:04 +02:00
|
|
|
$classlike_storage = null;
|
2017-07-25 22:11:02 +02:00
|
|
|
|
2020-10-24 01:53:04 +02:00
|
|
|
if ($this->classlike_node_scanners) {
|
|
|
|
$classlike_node_scanner = end($this->classlike_node_scanners);
|
|
|
|
$classlike_storage = $classlike_node_scanner->storage;
|
|
|
|
}
|
|
|
|
|
|
|
|
$functionlike_types = [];
|
|
|
|
|
2020-10-27 15:01:17 +01:00
|
|
|
foreach ($this->functionlike_node_scanners as $functionlike_node_scanner) {
|
2020-10-24 01:53:04 +02:00
|
|
|
$functionlike_storage = $functionlike_node_scanner->storage;
|
2020-10-27 15:01:17 +01:00
|
|
|
$functionlike_types += $functionlike_storage->template_types ?? [];
|
2019-02-10 22:23:31 +01:00
|
|
|
}
|
|
|
|
|
2020-10-24 01:53:04 +02:00
|
|
|
$functionlike_node_scanner = new Reflector\FunctionLikeNodeScanner(
|
|
|
|
$this->codebase,
|
|
|
|
$this->file_scanner,
|
|
|
|
$this->file_storage,
|
|
|
|
$this->aliases,
|
|
|
|
$this->type_aliases,
|
|
|
|
$classlike_storage,
|
|
|
|
$functionlike_types
|
|
|
|
);
|
|
|
|
|
|
|
|
$functionlike_node_scanner->start($node);
|
|
|
|
|
|
|
|
$this->functionlike_node_scanners[] = $functionlike_node_scanner;
|
|
|
|
|
2020-10-27 20:41:04 +01:00
|
|
|
if ($classlike_storage
|
|
|
|
&& $this->codebase->php_major_version >= 8
|
|
|
|
&& $node instanceof PhpParser\Node\Stmt\ClassMethod
|
|
|
|
&& strtolower($node->name->name) === '__tostring'
|
|
|
|
) {
|
2020-10-28 14:06:52 +01:00
|
|
|
if ($classlike_storage->is_interface) {
|
|
|
|
$classlike_storage->parent_interfaces['stringable'] = 'Stringable';
|
|
|
|
} else {
|
|
|
|
$classlike_storage->class_implements['stringable'] = 'Stringable';
|
|
|
|
}
|
|
|
|
|
2020-10-30 00:41:10 +01:00
|
|
|
if (\PHP_VERSION_ID >= 80000) {
|
|
|
|
$this->codebase->scanner->queueClassLikeForScanning('Stringable');
|
|
|
|
}
|
2020-10-27 20:41:04 +01:00
|
|
|
}
|
|
|
|
|
2017-07-25 22:11:02 +02:00
|
|
|
if (!$this->scan_deep) {
|
|
|
|
return PhpParser\NodeTraverser::DONT_TRAVERSE_CHILDREN;
|
|
|
|
}
|
2018-05-31 04:09:46 +02:00
|
|
|
} elseif ($node instanceof PhpParser\Node\Stmt\Global_) {
|
2020-10-24 01:53:04 +02:00
|
|
|
$functionlike_node_scanner = end($this->functionlike_node_scanners);
|
2018-05-31 04:09:46 +02:00
|
|
|
|
2020-10-24 01:53:04 +02:00
|
|
|
if ($functionlike_node_scanner && $functionlike_node_scanner->storage) {
|
2018-05-31 04:09:46 +02:00
|
|
|
foreach ($node->vars as $var) {
|
|
|
|
if ($var instanceof PhpParser\Node\Expr\Variable) {
|
2018-05-31 04:56:46 +02:00
|
|
|
if (is_string($var->name) && $var->name !== 'argv' && $var->name !== 'argc') {
|
2018-05-31 04:09:46 +02:00
|
|
|
$var_id = '$' . $var->name;
|
|
|
|
|
2020-10-24 01:53:04 +02:00
|
|
|
$functionlike_node_scanner->storage->global_variables[$var_id] = true;
|
2018-05-31 04:09:46 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-07-25 22:11:02 +02:00
|
|
|
} elseif ($node instanceof PhpParser\Node\Stmt\TraitUse) {
|
2020-01-17 16:25:05 +01:00
|
|
|
if ($this->skip_if_descendants) {
|
2020-09-13 22:39:06 +02:00
|
|
|
return null;
|
2020-01-17 16:25:05 +01:00
|
|
|
}
|
|
|
|
|
2020-10-24 01:53:04 +02:00
|
|
|
if (!$this->classlike_node_scanners) {
|
|
|
|
throw new \LogicException('$this->classlike_node_scanners should not be empty');
|
2017-07-25 22:11:02 +02:00
|
|
|
}
|
|
|
|
|
2020-10-24 01:53:04 +02:00
|
|
|
$classlike_node_scanner = end($this->classlike_node_scanners);
|
2017-07-25 22:11:02 +02:00
|
|
|
|
2020-10-24 01:53:04 +02:00
|
|
|
$classlike_node_scanner->handleTraitUse($node);
|
|
|
|
} elseif ($node instanceof PhpParser\Node\Stmt\Const_) {
|
|
|
|
foreach ($node->consts as $const) {
|
|
|
|
$const_type = SimpleTypeInferer::infer(
|
|
|
|
$this->codebase,
|
|
|
|
new \Psalm\Internal\Provider\NodeDataProvider(),
|
|
|
|
$const->value,
|
|
|
|
$this->aliases
|
|
|
|
) ?: Type::getMixed();
|
2019-01-13 16:19:27 +01:00
|
|
|
|
2020-10-24 01:53:04 +02:00
|
|
|
$fq_const_name = Type::getFQCLNFromString($const->name->name, $this->aliases);
|
2020-07-26 20:46:52 +02:00
|
|
|
|
2020-10-24 01:53:04 +02:00
|
|
|
if ($this->codebase->register_stub_files || $this->codebase->register_autoload_files) {
|
|
|
|
$this->codebase->addGlobalConstantType($fq_const_name, $const_type);
|
2017-07-25 22:11:02 +02:00
|
|
|
}
|
|
|
|
|
2020-10-24 01:53:04 +02:00
|
|
|
$this->file_storage->constants[$fq_const_name] = $const_type;
|
|
|
|
$this->file_storage->declaring_constants[$fq_const_name] = $this->file_path;
|
2017-07-25 22:11:02 +02:00
|
|
|
}
|
2020-10-24 01:53:04 +02:00
|
|
|
} elseif ($node instanceof PhpParser\Node\Stmt\If_ && !$this->skip_if_descendants) {
|
|
|
|
if (!$this->fq_classlike_names && !$this->functionlike_node_scanners) {
|
|
|
|
$this->exists_cond_expr = $node->cond;
|
2019-01-28 05:12:40 +01:00
|
|
|
|
2020-10-24 01:53:04 +02:00
|
|
|
if (Reflector\ExpressionResolver::enterConditional(
|
|
|
|
$this->codebase,
|
|
|
|
$this->file_path,
|
|
|
|
$this->exists_cond_expr
|
|
|
|
) === false
|
|
|
|
) {
|
|
|
|
// the else node should terminate the agreement
|
|
|
|
$this->skip_if_descendants = $node->else ? $node->else->getLine() : $node->getLine();
|
2019-01-28 05:12:40 +01:00
|
|
|
}
|
2020-10-24 01:53:04 +02:00
|
|
|
}
|
|
|
|
} elseif ($node instanceof PhpParser\Node\Stmt\Else_) {
|
|
|
|
if ($this->skip_if_descendants === $node->getLine()) {
|
|
|
|
$this->skip_if_descendants = null;
|
|
|
|
$this->exists_cond_expr = null;
|
|
|
|
} elseif (!$this->skip_if_descendants) {
|
|
|
|
if ($this->exists_cond_expr
|
|
|
|
&& Reflector\ExpressionResolver::enterConditional(
|
|
|
|
$this->codebase,
|
|
|
|
$this->file_path,
|
|
|
|
$this->exists_cond_expr
|
|
|
|
) === true
|
2019-01-28 22:56:42 +01:00
|
|
|
) {
|
2020-10-24 01:53:04 +02:00
|
|
|
$this->skip_if_descendants = $node->getLine();
|
2019-01-28 22:56:42 +01:00
|
|
|
}
|
2019-01-28 05:12:40 +01:00
|
|
|
}
|
2020-10-24 01:53:04 +02:00
|
|
|
} elseif ($node instanceof PhpParser\Node\Expr) {
|
|
|
|
$functionlike_storage = null;
|
2017-07-28 16:42:30 +02:00
|
|
|
|
2020-10-24 01:53:04 +02:00
|
|
|
if ($this->functionlike_node_scanners) {
|
|
|
|
$functionlike_node_scanner = end($this->functionlike_node_scanners);
|
|
|
|
$functionlike_storage = $functionlike_node_scanner->storage;
|
|
|
|
}
|
2018-08-08 22:13:37 +02:00
|
|
|
|
2020-10-24 01:53:04 +02:00
|
|
|
Reflector\ExpressionScanner::scan(
|
|
|
|
$this->codebase,
|
|
|
|
$this->file_scanner,
|
|
|
|
$this->file_storage,
|
|
|
|
$this->aliases,
|
|
|
|
$node,
|
|
|
|
$functionlike_storage,
|
|
|
|
$this->skip_if_descendants
|
|
|
|
);
|
|
|
|
}
|
2018-06-29 21:28:45 +02:00
|
|
|
|
2020-10-26 17:35:21 +01:00
|
|
|
if ($doc_comment = $node->getDocComment()) {
|
|
|
|
$var_comments = [];
|
|
|
|
|
|
|
|
$template_types = [];
|
|
|
|
|
|
|
|
if ($this->classlike_node_scanners) {
|
|
|
|
$classlike_node_scanner = end($this->classlike_node_scanners);
|
|
|
|
$classlike_storage = $classlike_node_scanner->storage;
|
|
|
|
$template_types = $classlike_storage->template_types ?? [];
|
|
|
|
}
|
|
|
|
|
2020-10-27 15:01:17 +01:00
|
|
|
foreach ($this->functionlike_node_scanners as $functionlike_node_scanner) {
|
2020-10-26 17:35:21 +01:00
|
|
|
$functionlike_storage = $functionlike_node_scanner->storage;
|
|
|
|
$template_types += $functionlike_storage->template_types ?? [];
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
$var_comments = CommentAnalyzer::getTypeFromComment(
|
|
|
|
$doc_comment,
|
|
|
|
$this->file_scanner,
|
|
|
|
$this->aliases,
|
|
|
|
$template_types,
|
|
|
|
$this->type_aliases
|
|
|
|
);
|
|
|
|
} catch (DocblockParseException $e) {
|
|
|
|
// do nothing
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($var_comments as $var_comment) {
|
|
|
|
if (!$var_comment->type) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$var_type = $var_comment->type;
|
|
|
|
$var_type->queueClassLikesForScanning($this->codebase, $this->file_storage);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($node instanceof PhpParser\Node\Expr\Assign
|
|
|
|
|| $node instanceof PhpParser\Node\Expr\AssignOp
|
|
|
|
|| $node instanceof PhpParser\Node\Expr\AssignRef
|
|
|
|
) {
|
|
|
|
if ($node->var instanceof PhpParser\Node\Expr\PropertyFetch
|
|
|
|
&& $node->var->var instanceof PhpParser\Node\Expr\Variable
|
|
|
|
&& $node->var->var->name === 'this'
|
|
|
|
&& $node->var->name instanceof PhpParser\Node\Identifier
|
|
|
|
) {
|
|
|
|
if ($this->functionlike_node_scanners) {
|
|
|
|
$functionlike_node_scanner = end($this->functionlike_node_scanners);
|
|
|
|
$functionlike_storage = $functionlike_node_scanner->storage;
|
|
|
|
|
|
|
|
if ($functionlike_storage instanceof MethodStorage) {
|
|
|
|
$functionlike_storage->this_property_mutations[$node->var->name->name] = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-24 01:53:04 +02:00
|
|
|
return null;
|
|
|
|
}
|
2019-06-10 05:50:18 +02:00
|
|
|
|
2020-10-24 01:53:04 +02:00
|
|
|
private function handleNamespace(PhpParser\Node\Stmt\Namespace_ $node) : void
|
|
|
|
{
|
|
|
|
$this->file_storage->aliases = $this->aliases;
|
|
|
|
|
|
|
|
$this->namespace_name = $node->name;
|
|
|
|
|
|
|
|
$this->aliases = new Aliases(
|
|
|
|
$node->name ? implode('\\', $node->name->parts) : '',
|
|
|
|
$this->aliases->uses,
|
|
|
|
$this->aliases->functions,
|
|
|
|
$this->aliases->constants,
|
|
|
|
$this->aliases->uses_flipped,
|
|
|
|
$this->aliases->functions_flipped,
|
|
|
|
$this->aliases->constants_flipped
|
|
|
|
);
|
|
|
|
|
|
|
|
$this->file_storage->namespace_aliases[(int) $node->getAttribute('startFilePos')] = $this->aliases;
|
|
|
|
|
|
|
|
if ($node->stmts) {
|
|
|
|
$this->aliases->namespace_first_stmt_start = (int) $node->stmts[0]->getAttribute('startFilePos');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private function handleUse(PhpParser\Node\Stmt\Use_ $node) : void
|
|
|
|
{
|
|
|
|
foreach ($node->uses as $use) {
|
|
|
|
$use_path = implode('\\', $use->name->parts);
|
|
|
|
|
|
|
|
$use_alias = $use->alias ? $use->alias->name : $use->name->getLast();
|
|
|
|
|
|
|
|
switch ($use->type !== PhpParser\Node\Stmt\Use_::TYPE_UNKNOWN ? $use->type : $node->type) {
|
|
|
|
case PhpParser\Node\Stmt\Use_::TYPE_FUNCTION:
|
|
|
|
$this->aliases->functions[strtolower($use_alias)] = $use_path;
|
|
|
|
$this->aliases->functions_flipped[strtolower($use_path)] = $use_alias;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case PhpParser\Node\Stmt\Use_::TYPE_CONSTANT:
|
|
|
|
$this->aliases->constants[$use_alias] = $use_path;
|
|
|
|
$this->aliases->constants_flipped[$use_path] = $use_alias;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case PhpParser\Node\Stmt\Use_::TYPE_NORMAL:
|
|
|
|
$this->aliases->uses[strtolower($use_alias)] = $use_path;
|
|
|
|
$this->aliases->uses_flipped[strtolower($use_path)] = $use_alias;
|
|
|
|
break;
|
2018-04-27 21:00:22 +02:00
|
|
|
}
|
2020-10-24 01:53:04 +02:00
|
|
|
}
|
2018-07-15 23:47:58 +02:00
|
|
|
|
2020-10-24 01:53:04 +02:00
|
|
|
if (!$this->aliases->uses_start) {
|
|
|
|
$this->aliases->uses_start = (int) $node->getAttribute('startFilePos');
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->aliases->uses_end = (int) $node->getAttribute('endFilePos') + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
private function handleGroupUse(PhpParser\Node\Stmt\GroupUse $node) : void
|
|
|
|
{
|
|
|
|
$use_prefix = implode('\\', $node->prefix->parts);
|
|
|
|
|
|
|
|
foreach ($node->uses as $use) {
|
|
|
|
$use_path = $use_prefix . '\\' . implode('\\', $use->name->parts);
|
|
|
|
$use_alias = $use->alias ? $use->alias->name : $use->name->getLast();
|
|
|
|
|
|
|
|
switch ($use->type !== PhpParser\Node\Stmt\Use_::TYPE_UNKNOWN ? $use->type : $node->type) {
|
|
|
|
case PhpParser\Node\Stmt\Use_::TYPE_FUNCTION:
|
|
|
|
$this->aliases->functions[strtolower($use_alias)] = $use_path;
|
|
|
|
$this->aliases->functions_flipped[strtolower($use_path)] = $use_alias;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case PhpParser\Node\Stmt\Use_::TYPE_CONSTANT:
|
|
|
|
$this->aliases->constants[$use_alias] = $use_path;
|
|
|
|
$this->aliases->constants_flipped[$use_path] = $use_alias;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case PhpParser\Node\Stmt\Use_::TYPE_NORMAL:
|
|
|
|
$this->aliases->uses[strtolower($use_alias)] = $use_path;
|
|
|
|
$this->aliases->uses_flipped[strtolower($use_path)] = $use_alias;
|
|
|
|
break;
|
2018-07-15 23:47:58 +02:00
|
|
|
}
|
2017-07-25 22:11:02 +02:00
|
|
|
}
|
2020-09-13 22:50:50 +02:00
|
|
|
|
2020-10-24 01:53:04 +02:00
|
|
|
if (!$this->aliases->uses_start) {
|
|
|
|
$this->aliases->uses_start = (int) $node->getAttribute('startFilePos');
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->aliases->uses_end = (int) $node->getAttribute('endFilePos') + 1;
|
2017-07-25 22:11:02 +02:00
|
|
|
}
|
|
|
|
|
2017-11-26 22:03:17 +01:00
|
|
|
/**
|
2018-03-07 17:16:56 +01:00
|
|
|
* @return null
|
2017-11-26 22:03:17 +01:00
|
|
|
*/
|
2017-07-25 22:11:02 +02:00
|
|
|
public function leaveNode(PhpParser\Node $node)
|
|
|
|
{
|
|
|
|
if ($node instanceof PhpParser\Node\Stmt\Namespace_) {
|
2019-06-30 03:32:26 +02:00
|
|
|
if (!$this->file_storage->aliases) {
|
|
|
|
throw new \UnexpectedValueException('File storage liases should not be null');
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->aliases = $this->file_storage->aliases;
|
2019-02-17 00:50:25 +01:00
|
|
|
|
|
|
|
if ($this->codebase->register_stub_files
|
|
|
|
&& $node->name
|
|
|
|
&& $node->name->parts === ['PHPSTORM_META']
|
|
|
|
) {
|
|
|
|
foreach ($node->stmts as $meta_stmt) {
|
|
|
|
if ($meta_stmt instanceof PhpParser\Node\Stmt\Expression
|
|
|
|
&& $meta_stmt->expr instanceof PhpParser\Node\Expr\FuncCall
|
|
|
|
&& $meta_stmt->expr->name instanceof PhpParser\Node\Name
|
|
|
|
&& $meta_stmt->expr->name->parts === ['override']
|
|
|
|
&& count($meta_stmt->expr->args) > 1
|
|
|
|
) {
|
|
|
|
PhpStormMetaScanner::handleOverride($meta_stmt->expr->args, $this->codebase);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-07-25 22:11:02 +02:00
|
|
|
} elseif ($node instanceof PhpParser\Node\Stmt\ClassLike) {
|
2019-06-28 16:17:59 +02:00
|
|
|
if ($this->skip_if_descendants) {
|
2020-09-13 22:39:06 +02:00
|
|
|
return null;
|
2019-06-28 16:17:59 +02:00
|
|
|
}
|
|
|
|
|
2020-10-01 21:07:25 +02:00
|
|
|
if (isset($this->bad_classes[\spl_object_id($node)])) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2020-10-24 01:53:04 +02:00
|
|
|
if (!$this->classlike_node_scanners) {
|
|
|
|
throw new \UnexpectedValueException('$this->classlike_node_scanners cannot be empty');
|
2020-08-16 18:43:46 +02:00
|
|
|
}
|
|
|
|
|
2020-10-24 01:53:04 +02:00
|
|
|
$classlike_node_scanner = array_pop($this->classlike_node_scanners);
|
2020-08-16 18:43:46 +02:00
|
|
|
|
2020-10-24 01:53:04 +02:00
|
|
|
$classlike_storage = $classlike_node_scanner->finish($node);
|
2017-07-29 21:05:06 +02:00
|
|
|
|
2018-05-29 16:13:26 +02:00
|
|
|
if ($classlike_storage->has_visitor_issues) {
|
|
|
|
$this->file_storage->has_visitor_issues = true;
|
|
|
|
}
|
|
|
|
|
2018-02-12 04:49:19 +01:00
|
|
|
if ($this->after_classlike_check_plugins) {
|
2017-11-07 20:46:53 +01:00
|
|
|
$file_manipulations = [];
|
|
|
|
|
2018-02-12 04:49:19 +01:00
|
|
|
foreach ($this->after_classlike_check_plugins as $plugin_fq_class_name) {
|
2018-11-06 03:57:36 +01:00
|
|
|
$plugin_fq_class_name::afterClassLikeVisit(
|
2017-11-07 20:46:53 +01:00
|
|
|
$node,
|
|
|
|
$classlike_storage,
|
2018-11-13 20:09:37 +01:00
|
|
|
$this,
|
|
|
|
$this->codebase,
|
2017-11-07 20:46:53 +01:00
|
|
|
$file_manipulations
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2018-02-19 06:27:39 +01:00
|
|
|
|
2018-05-29 16:13:26 +02:00
|
|
|
if (!$this->file_storage->has_visitor_issues) {
|
|
|
|
$this->codebase->cacheClassLikeStorage($classlike_storage, $this->file_path);
|
|
|
|
}
|
2017-07-25 22:11:02 +02:00
|
|
|
} elseif ($node instanceof PhpParser\Node\FunctionLike) {
|
2019-06-28 16:17:59 +02:00
|
|
|
if ($this->skip_if_descendants) {
|
2020-09-13 22:39:06 +02:00
|
|
|
return null;
|
2019-06-28 16:17:59 +02:00
|
|
|
}
|
|
|
|
|
2020-10-24 01:53:04 +02:00
|
|
|
if (!$this->functionlike_node_scanners) {
|
2019-11-14 18:54:35 +01:00
|
|
|
if ($this->file_storage->has_visitor_issues) {
|
2020-09-13 22:39:06 +02:00
|
|
|
return null;
|
2019-11-14 18:54:35 +01:00
|
|
|
}
|
|
|
|
|
2019-11-14 17:03:41 +01:00
|
|
|
throw new \UnexpectedValueException(
|
|
|
|
'There should be function storages for line ' . $this->file_path . ':' . $node->getLine()
|
|
|
|
);
|
2018-12-19 22:15:19 +01:00
|
|
|
}
|
|
|
|
|
2020-10-24 01:53:04 +02:00
|
|
|
$functionlike_node_scanner = array_pop($this->functionlike_node_scanners);
|
2018-05-29 16:13:26 +02:00
|
|
|
|
2020-10-24 01:53:04 +02:00
|
|
|
if ($functionlike_node_scanner->storage) {
|
2020-12-16 13:31:28 +01:00
|
|
|
foreach ($functionlike_node_scanner->storage->docblock_issues as $docblock_issue) {
|
|
|
|
if (strpos($docblock_issue->code_location->file_path, 'CoreGenericFunctions.phpstub')
|
|
|
|
|| strpos($docblock_issue->code_location->file_path, 'CoreGenericClasses.phpstub')
|
|
|
|
) {
|
|
|
|
$e = reset($functionlike_node_scanner->storage->docblock_issues);
|
|
|
|
|
|
|
|
$fqcn_parts = explode('\\', get_class($e));
|
|
|
|
$issue_type = array_pop($fqcn_parts);
|
|
|
|
|
|
|
|
$message = $e instanceof \Psalm\Issue\TaintedInput
|
|
|
|
? $e->getJourneyMessage()
|
|
|
|
: $e->message;
|
|
|
|
|
|
|
|
throw new \Psalm\Exception\CodeException(
|
|
|
|
'Error with core stub file docblocks: '
|
|
|
|
. $issue_type
|
|
|
|
. ' - ' . $e->getShortLocationWithPrevious()
|
|
|
|
. ':' . $e->code_location->getColumn()
|
|
|
|
. ' - ' . $message
|
|
|
|
);
|
|
|
|
}
|
2020-10-24 01:53:04 +02:00
|
|
|
}
|
2020-09-05 00:10:14 +02:00
|
|
|
|
2020-10-24 01:53:04 +02:00
|
|
|
if ($functionlike_node_scanner->storage->has_visitor_issues) {
|
|
|
|
$this->file_storage->has_visitor_issues = true;
|
|
|
|
}
|
2018-05-29 16:13:26 +02:00
|
|
|
}
|
2019-07-11 00:58:56 +02:00
|
|
|
} elseif ($node instanceof PhpParser\Node\Stmt\If_ && $node->getLine() === $this->skip_if_descendants) {
|
|
|
|
$this->exists_cond_expr = null;
|
|
|
|
$this->skip_if_descendants = null;
|
|
|
|
} elseif ($node instanceof PhpParser\Node\Stmt\Else_ && $node->getLine() === $this->skip_if_descendants) {
|
2019-06-10 05:50:18 +02:00
|
|
|
$this->exists_cond_expr = null;
|
2019-07-11 00:58:56 +02:00
|
|
|
$this->skip_if_descendants = null;
|
2017-07-25 22:11:02 +02:00
|
|
|
}
|
2017-11-26 22:03:17 +01:00
|
|
|
|
|
|
|
return null;
|
2017-07-25 22:11:02 +02:00
|
|
|
}
|
|
|
|
|
2020-09-04 22:26:33 +02:00
|
|
|
public function getFilePath(): string
|
2018-02-23 21:39:33 +01:00
|
|
|
{
|
|
|
|
return $this->file_path;
|
|
|
|
}
|
|
|
|
|
2020-09-04 22:26:33 +02:00
|
|
|
public function getFileName(): string
|
2018-02-23 21:39:33 +01:00
|
|
|
{
|
|
|
|
return $this->file_scanner->getFileName();
|
|
|
|
}
|
|
|
|
|
2020-09-04 22:26:33 +02:00
|
|
|
public function getRootFilePath(): string
|
2018-02-23 21:39:33 +01:00
|
|
|
{
|
2018-05-30 22:19:18 +02:00
|
|
|
return $this->file_scanner->getRootFilePath();
|
2018-02-23 21:39:33 +01:00
|
|
|
}
|
|
|
|
|
2020-09-04 22:26:33 +02:00
|
|
|
public function getRootFileName(): string
|
2018-02-23 21:39:33 +01:00
|
|
|
{
|
2018-05-30 22:19:18 +02:00
|
|
|
return $this->file_scanner->getRootFileName();
|
2018-02-23 21:39:33 +01:00
|
|
|
}
|
|
|
|
|
2020-09-04 22:26:33 +02:00
|
|
|
public function getAliases(): Aliases
|
2018-02-23 21:39:33 +01:00
|
|
|
{
|
|
|
|
return $this->aliases;
|
|
|
|
}
|
2018-09-24 19:08:23 +02:00
|
|
|
|
2020-09-12 17:24:05 +02:00
|
|
|
public function afterTraverse(array $nodes): void
|
2018-09-24 19:08:23 +02:00
|
|
|
{
|
|
|
|
$this->file_storage->type_aliases = $this->type_aliases;
|
|
|
|
}
|
2017-07-25 22:11:02 +02:00
|
|
|
}
|