mirror of
https://github.com/danog/psalm.git
synced 2025-01-21 21:31:13 +01:00
Fix self-checking errors
This commit is contained in:
parent
08dd92ac00
commit
1ebe333bb2
@ -137,7 +137,7 @@ class ClassChecker implements StatementsSource
|
||||
$comment = $stmt->getDocComment();
|
||||
$type_in_comment = null;
|
||||
if ($comment && $config->use_docblock_types) {
|
||||
$type_in_comment = CommentChecker::getTypeFromComment($comment, null, $this);
|
||||
$type_in_comment = CommentChecker::getTypeFromComment((string) $comment, null, $this);
|
||||
}
|
||||
|
||||
$property_type = $type_in_comment ? Type::parseString($type_in_comment) : Type::getMixed();
|
||||
@ -213,7 +213,7 @@ class ClassChecker implements StatementsSource
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @return bool|null
|
||||
*/
|
||||
public static function checkClassName(PhpParser\Node\Name $class_name, $namespace, array $aliased_classes, $file_name, array $suppressed_issues)
|
||||
{
|
||||
|
@ -26,12 +26,12 @@ class Context
|
||||
* @param bool $has_leaving_statements whether or not the parent scope is abandoned between $start_context and $end_context
|
||||
* @return void
|
||||
*/
|
||||
public function update(Context $start_context, Context $end_context, $has_leaving_statments, array &$updated_vars)
|
||||
public function update(Context $start_context, Context $end_context, $has_leaving_statements, array &$updated_vars)
|
||||
{
|
||||
foreach ($this->vars_in_scope as $var => &$context_type) {
|
||||
$old_type = $start_context->vars_in_scope[$var];
|
||||
// if we're leaving, we're effectively deleting the possibility of the if types
|
||||
$new_type = !$has_leaving_statments ? $end_context->vars_in_scope[$var] : null;
|
||||
$new_type = !$has_leaving_statements ? $end_context->vars_in_scope[$var] : null;
|
||||
|
||||
// this is only true if there was some sort of type negation
|
||||
if ((string)$context_type !== (string)$old_type) {
|
||||
|
@ -50,7 +50,7 @@ class IssueBuffer
|
||||
throw new Exception\CodeException($error_message);
|
||||
}
|
||||
|
||||
echo "\033[0;31m" . 'ERROR: ' . "\033[0m" . $error_message . PHP_EOL;
|
||||
echo (ProjectChecker::$use_color ? "\033[0;31m" : '') . 'ERROR: ' . (ProjectChecker::$use_color ? "\033[0m" : '') . $error_message . PHP_EOL;
|
||||
|
||||
if ($config->stop_on_first_error) {
|
||||
exit(1);
|
||||
|
@ -13,6 +13,12 @@ class ProjectChecker
|
||||
*/
|
||||
protected static $config;
|
||||
|
||||
/**
|
||||
* Whether or not to use colors in error output
|
||||
* @var boolean
|
||||
*/
|
||||
public static $use_color = true;
|
||||
|
||||
public static function check($debug = false)
|
||||
{
|
||||
if (!self::$config) {
|
||||
|
@ -280,7 +280,7 @@ class StatementsChecker
|
||||
$if_context = clone $context;
|
||||
|
||||
// we need to clone the current context so our ongoing updates to $context don't mess with elseif/else blocks
|
||||
$original_context = ($stmt->elseifs || $stmt->else) ? clone $context : null;
|
||||
$original_context = clone $context;
|
||||
|
||||
if ($this->_checkCondition($stmt->cond, $if_context) === false) {
|
||||
return false;
|
||||
@ -514,6 +514,7 @@ class StatementsChecker
|
||||
|
||||
// if it doesn't end in a return
|
||||
if (!$has_leaving_statements) {
|
||||
/** @var Context $original_context */
|
||||
$else_redefined_vars = Context::getRedefinedVars($original_context, $else_context);
|
||||
|
||||
if ($redefined_vars === null) {
|
||||
@ -850,53 +851,7 @@ class StatementsChecker
|
||||
// do nothing
|
||||
|
||||
} elseif ($stmt instanceof PhpParser\Node\Expr\Include_) {
|
||||
if ($this->_checkExpression($stmt->expr, $context) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$path_to_file = null;
|
||||
|
||||
if ($stmt->expr instanceof PhpParser\Node\Scalar\String_) {
|
||||
$path_to_file = $stmt->expr->value;
|
||||
|
||||
// attempts to resolve using get_include_path dirs
|
||||
$include_path = self::_resolveIncludePath($path_to_file, dirname($this->_file_name));
|
||||
$path_to_file = $include_path ? $include_path : $path_to_file;
|
||||
|
||||
if ($path_to_file[0] !== '/') {
|
||||
$path_to_file = getcwd() . '/' . $path_to_file;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$path_to_file = self::_getPathTo($stmt->expr, $this->_file_name);
|
||||
}
|
||||
|
||||
if ($path_to_file) {
|
||||
$reduce_pattern = '/\/[^\/]+\/\.\.\//';
|
||||
|
||||
while (preg_match($reduce_pattern, $path_to_file)) {
|
||||
$path_to_file = preg_replace($reduce_pattern, '/', $path_to_file);
|
||||
}
|
||||
|
||||
// if the file is already included, we can't check much more
|
||||
if (in_array($path_to_file, get_included_files())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (in_array($path_to_file, FileChecker::getIncludesToIgnore())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (file_exists($path_to_file)) {
|
||||
$file_checker = new FileChecker($path_to_file, []);
|
||||
$file_checker->check(true, true, $context);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$this->_check_classes = false;
|
||||
$this->_check_variables = false;
|
||||
$this->_checkInclude($stmt, $context);
|
||||
|
||||
} elseif ($stmt instanceof PhpParser\Node\Expr\Eval_) {
|
||||
$this->_check_classes = false;
|
||||
@ -1399,7 +1354,7 @@ class StatementsChecker
|
||||
$this->registerVariable($stmt->valueVar->name, $stmt->getLine());
|
||||
}
|
||||
|
||||
CommentChecker::getTypeFromComment($stmt->getDocComment(), $foreach_context, $this->_source, null);
|
||||
CommentChecker::getTypeFromComment((string) $stmt->getDocComment(), $foreach_context, $this->_source, null);
|
||||
|
||||
$this->check($stmt->stmts, $foreach_context, $foreach_context->vars_possibly_in_scope);
|
||||
|
||||
@ -1591,7 +1546,7 @@ class StatementsChecker
|
||||
return false;
|
||||
}
|
||||
|
||||
$type_in_comments = CommentChecker::getTypeFromComment($stmt->getDocComment(), $context, $this->_source, $var_id);
|
||||
$type_in_comments = CommentChecker::getTypeFromComment((string) $stmt->getDocComment(), $context, $this->_source, $var_id);
|
||||
|
||||
if ($type_in_comments) {
|
||||
$return_type = Type::parseString($type_in_comments);
|
||||
@ -2412,7 +2367,7 @@ class StatementsChecker
|
||||
|
||||
protected function _checkReturn(PhpParser\Node\Stmt\Return_ $stmt, Context $context)
|
||||
{
|
||||
$type_in_comments = CommentChecker::getTypeFromComment($stmt->getDocComment(), $context, $this->_source);
|
||||
$type_in_comments = CommentChecker::getTypeFromComment((string) $stmt->getDocComment(), $context, $this->_source);
|
||||
|
||||
if ($stmt->expr) {
|
||||
if ($this->_checkExpression($stmt->expr, $context) === false) {
|
||||
@ -2502,7 +2457,7 @@ class StatementsChecker
|
||||
}
|
||||
elseif ($stmt->cond) {
|
||||
if (isset($stmt->cond->inferredType)) {
|
||||
$if_return_type_reconciled = TypeChecker::reconcileTypes('!empty', $stmt->cond->inferredType, $this->_file_name, $stmt->getLine());
|
||||
$if_return_type_reconciled = TypeChecker::reconcileTypes('!empty', $stmt->cond->inferredType, '', $this->_file_name, $stmt->getLine());
|
||||
|
||||
if ($if_return_type_reconciled === false) {
|
||||
return false;
|
||||
@ -3044,6 +2999,59 @@ class StatementsChecker
|
||||
return isset(self::$_existing_static_vars[$var_id]);
|
||||
}
|
||||
|
||||
protected function _checkInclude(PhpParser\Node\Expr\Include_ $stmt, Context $context)
|
||||
{
|
||||
if ($this->_checkExpression($stmt->expr, $context) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$path_to_file = null;
|
||||
|
||||
if ($stmt->expr instanceof PhpParser\Node\Scalar\String_) {
|
||||
$path_to_file = $stmt->expr->value;
|
||||
|
||||
// attempts to resolve using get_include_path dirs
|
||||
$include_path = self::_resolveIncludePath($path_to_file, dirname($this->_file_name));
|
||||
$path_to_file = $include_path ? $include_path : $path_to_file;
|
||||
|
||||
if ($path_to_file[0] !== '/') {
|
||||
$path_to_file = getcwd() . '/' . $path_to_file;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$path_to_file = self::_getPathTo($stmt->expr, $this->_file_name);
|
||||
}
|
||||
|
||||
if ($path_to_file) {
|
||||
$reduce_pattern = '/\/[^\/]+\/\.\.\//';
|
||||
|
||||
while (preg_match($reduce_pattern, $path_to_file)) {
|
||||
$path_to_file = preg_replace($reduce_pattern, '/', $path_to_file);
|
||||
}
|
||||
|
||||
// if the file is already included, we can't check much more
|
||||
if (in_array($path_to_file, get_included_files())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (in_array($path_to_file, FileChecker::getIncludesToIgnore())) {
|
||||
$this->_check_classes = false;
|
||||
$this->_check_variables = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (file_exists($path_to_file)) {
|
||||
$file_checker = new FileChecker($path_to_file, []);
|
||||
$file_checker->check(true, true, $context);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->_check_classes = false;
|
||||
$this->_check_variables = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a docblock comment into its parts.
|
||||
*
|
||||
|
@ -104,7 +104,7 @@ class TypeChecker
|
||||
/**
|
||||
* Gets all the type assertions in a conditional
|
||||
*
|
||||
* @param PhpParser\Node\Expr $stmt
|
||||
* @param PhpParser\Node\Expr $conditional
|
||||
* @return array
|
||||
*/
|
||||
public function getTypeAssertions(PhpParser\Node\Expr $conditional)
|
||||
@ -258,6 +258,10 @@ class TypeChecker
|
||||
$var_name = StatementsChecker::getVarId($conditional->expr->args[0]->value);
|
||||
$if_types[$var_name] = '!array';
|
||||
}
|
||||
else if (self::_hasObjectCheck($conditional->expr)) {
|
||||
$var_name = StatementsChecker::getVarId($conditional->expr->args[0]->value);
|
||||
$if_types[$var_name] = '!object';
|
||||
}
|
||||
else if ($conditional->expr instanceof PhpParser\Node\Expr\Isset_) {
|
||||
foreach ($conditional->expr->vars as $isset_var) {
|
||||
$var_name = StatementsChecker::getVarId($isset_var);
|
||||
@ -369,6 +373,10 @@ class TypeChecker
|
||||
$var_name = StatementsChecker::getVarId($conditional->args[0]->value);
|
||||
$if_types[$var_name] = 'array';
|
||||
}
|
||||
else if (self::_hasObjectCheck($conditional)) {
|
||||
$var_name = StatementsChecker::getVarId($conditional->args[0]->value);
|
||||
$if_types[$var_name] = 'object';
|
||||
}
|
||||
else if ($conditional instanceof PhpParser\Node\Expr\Empty_) {
|
||||
$var_name = StatementsChecker::getVarId($conditional->expr);
|
||||
if ($var_name) {
|
||||
@ -474,6 +482,18 @@ class TypeChecker
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
protected static function _hasObjectCheck(PhpParser\Node\Expr $stmt)
|
||||
{
|
||||
if ($stmt instanceof PhpParser\Node\Expr\FuncCall && $stmt->name instanceof PhpParser\Node\Name && $stmt->name->parts === ['is_object']) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes two arrays and consolidates them, removing null values from existing types where applicable
|
||||
*
|
||||
@ -526,7 +546,7 @@ class TypeChecker
|
||||
* notEmpty(Object|false) => Object
|
||||
*
|
||||
* @param string $new_var_type
|
||||
* @param Type\Union $existing_var_types
|
||||
* @param Type\Union $existing_var_type
|
||||
* @param string $key
|
||||
* @param string $file_name
|
||||
* @param int $line_number
|
||||
|
Loading…
x
Reference in New Issue
Block a user