1
0
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:
Matthew Brown 2016-07-25 15:05:58 -04:00
parent 08dd92ac00
commit 1ebe333bb2
6 changed files with 93 additions and 59 deletions

View File

@ -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)
{

View File

@ -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) {

View File

@ -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);

View File

@ -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) {

View File

@ -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.
*

View File

@ -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