mirror of
https://github.com/danog/psalm.git
synced 2024-11-26 20:34:47 +01:00
Show code snippets when reporting errors
This also introduces a new method of identifying specific code locations when creating issues
This commit is contained in:
parent
1d603b11d0
commit
a1acbfec07
@ -15,12 +15,16 @@ class StringChecker extends \Psalm\Plugin
|
||||
* checks an expression
|
||||
* @param PhpParser\Node\Expr $stmt
|
||||
* @param Context $context
|
||||
* @param string $file_name
|
||||
* @param CodeLocation $file_name
|
||||
* @param array<string> $suppressed_issues
|
||||
* @return null|false
|
||||
*/
|
||||
public function checkExpression(PhpParser\Node\Expr $stmt, \Psalm\Context $context, $file_name, array $suppressed_issues)
|
||||
{
|
||||
public function checkExpression(
|
||||
PhpParser\Node\Expr $stmt,
|
||||
Context $context,
|
||||
CodeLocation $code_location,
|
||||
array $suppressed_issues
|
||||
) {
|
||||
if ($stmt instanceof \PhpParser\Node\Scalar\String_) {
|
||||
$class_or_class_method = '/^\\\?Psalm(\\\[A-Z][A-Za-z0-9]+)+(::[A-Za-z0-9]+)?$/';
|
||||
|
||||
@ -29,9 +33,9 @@ class StringChecker extends \Psalm\Plugin
|
||||
|
||||
if (Checker\ClassChecker::checkFullyQualifiedClassLikeName(
|
||||
$fq_class_name,
|
||||
$file_name,
|
||||
$stmt->getLine(),
|
||||
$suppressed_issues) === false
|
||||
$code_location,
|
||||
$suppressed_issues
|
||||
) === false
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
@ -39,9 +43,9 @@ class StringChecker extends \Psalm\Plugin
|
||||
if ($fq_class_name !== $stmt->value) {
|
||||
if (Checker\MethodChecker::checkMethodExists(
|
||||
$stmt->value,
|
||||
$file_name,
|
||||
$stmt->getLine(),
|
||||
$suppressed_issues)
|
||||
$code_location,
|
||||
$suppressed_issues
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
namespace Psalm\Checker;
|
||||
|
||||
use PhpParser;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\Config;
|
||||
use Psalm\Context;
|
||||
use Psalm\Exception\DocblockParseException;
|
||||
@ -202,7 +203,9 @@ abstract class ClassLikeChecker extends SourceChecker implements StatementsSourc
|
||||
$this->aliased_constants = $source->getAliasedConstants();
|
||||
$this->aliased_functions = $source->getAliasedFunctions();
|
||||
$this->file_name = $source->getFileName();
|
||||
$this->file_path = $source->getFilePath();
|
||||
$this->include_file_name = $source->getIncludeFileName();
|
||||
$this->include_file_path = $source->getIncludeFilePath();
|
||||
$this->fq_class_name = $fq_class_name;
|
||||
|
||||
$this->suppressed_issues = $source->getSuppressedIssues();
|
||||
@ -289,8 +292,7 @@ abstract class ClassLikeChecker extends SourceChecker implements StatementsSourc
|
||||
foreach ($parent_interfaces as $interface_name) {
|
||||
if (self::checkFullyQualifiedClassLikeName(
|
||||
$interface_name,
|
||||
$this->file_name,
|
||||
$this->class->getLine(),
|
||||
new CodeLocation($this, $this->class, true),
|
||||
$this->getSuppressedIssues()
|
||||
) === false) {
|
||||
return false;
|
||||
@ -392,8 +394,7 @@ abstract class ClassLikeChecker extends SourceChecker implements StatementsSourc
|
||||
if (IssueBuffer::accepts(
|
||||
new UnimplementedInterfaceMethod(
|
||||
'Method ' . $method_name . ' is not defined on class ' . $this->fq_class_name,
|
||||
$this->file_name,
|
||||
$this->class->getLine()
|
||||
new CodeLocation($this, $this->class)
|
||||
),
|
||||
$this->suppressed_issues
|
||||
)) {
|
||||
@ -434,10 +435,17 @@ abstract class ClassLikeChecker extends SourceChecker implements StatementsSourc
|
||||
*/
|
||||
protected function registerParentClassProperties($parent_class)
|
||||
{
|
||||
if (!$this->class instanceof PhpParser\Node\Stmt\Class_) {
|
||||
throw new \UnexpectedValueException('Cannot register parent class where none exists');
|
||||
}
|
||||
|
||||
if (!$this->class->extends) {
|
||||
throw new \UnexpectedValueException('Cannot register parent class where none exists');
|
||||
}
|
||||
|
||||
if (self::checkFullyQualifiedClassLikeName(
|
||||
$parent_class,
|
||||
$this->file_name,
|
||||
$this->class->getLine(),
|
||||
new CodeLocation($this, $this->class->extends, true),
|
||||
$this->getSuppressedIssues()
|
||||
) === false
|
||||
) {
|
||||
@ -535,7 +543,10 @@ abstract class ClassLikeChecker extends SourceChecker implements StatementsSourc
|
||||
|
||||
if (!TraitChecker::traitExists($trait_name)) {
|
||||
if (IssueBuffer::accepts(
|
||||
new UndefinedTrait('Trait ' . $trait_name . ' does not exist', $this->file_name, $trait->getLine()),
|
||||
new UndefinedTrait(
|
||||
'Trait ' . $trait_name . ' does not exist',
|
||||
new CodeLocation($this, $trait)
|
||||
),
|
||||
$this->suppressed_issues
|
||||
)) {
|
||||
return false;
|
||||
@ -547,8 +558,7 @@ abstract class ClassLikeChecker extends SourceChecker implements StatementsSourc
|
||||
if (IssueBuffer::accepts(
|
||||
new UndefinedTrait(
|
||||
'Trait ' . $trait_name . ' has wrong casing',
|
||||
$this->file_name,
|
||||
$trait->getLine()
|
||||
new CodeLocation($this, $trait)
|
||||
),
|
||||
$this->suppressed_issues
|
||||
)) {
|
||||
@ -596,13 +606,11 @@ abstract class ClassLikeChecker extends SourceChecker implements StatementsSourc
|
||||
if ($comment && $config->use_docblock_types) {
|
||||
try {
|
||||
$type_in_comment = CommentChecker::getTypeFromComment((string) $comment, null, $this);
|
||||
}
|
||||
catch (DocblockParseException $e) {
|
||||
} catch (DocblockParseException $e) {
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidDocblock(
|
||||
(string)$e->getMessage(),
|
||||
$this->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($this, $this->class, true)
|
||||
)
|
||||
)) {
|
||||
return false;
|
||||
@ -613,8 +621,7 @@ abstract class ClassLikeChecker extends SourceChecker implements StatementsSourc
|
||||
new MissingPropertyType(
|
||||
'Property ' . $this->fq_class_name . '::$' . $stmt->props[0]->name . ' does not have a ' .
|
||||
'declared type',
|
||||
$this->file_name,
|
||||
$stmt->getLine()
|
||||
new CodeLocation($this, $stmt)
|
||||
),
|
||||
$this->suppressed_issues
|
||||
)) {
|
||||
@ -757,15 +764,13 @@ abstract class ClassLikeChecker extends SourceChecker implements StatementsSourc
|
||||
|
||||
/**
|
||||
* @param string $fq_class_name
|
||||
* @param string $file_name
|
||||
* @param int $line_number
|
||||
* @param CodeLocation $code_location
|
||||
* @param array<string> $suppressed_issues
|
||||
* @return bool|null
|
||||
*/
|
||||
public static function checkFullyQualifiedClassLikeName(
|
||||
$fq_class_name,
|
||||
$file_name,
|
||||
$line_number,
|
||||
CodeLocation $code_location,
|
||||
array $suppressed_issues
|
||||
) {
|
||||
if (empty($fq_class_name)) {
|
||||
@ -781,8 +786,7 @@ abstract class ClassLikeChecker extends SourceChecker implements StatementsSourc
|
||||
if (IssueBuffer::accepts(
|
||||
new UndefinedClass(
|
||||
'Class or interface ' . $fq_class_name . ' does not exist',
|
||||
$file_name,
|
||||
$line_number
|
||||
$code_location
|
||||
),
|
||||
$suppressed_issues
|
||||
)) {
|
||||
@ -798,8 +802,7 @@ abstract class ClassLikeChecker extends SourceChecker implements StatementsSourc
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidClass(
|
||||
'Class or interface ' . $fq_class_name . ' has wrong casing',
|
||||
$file_name,
|
||||
$line_number
|
||||
$code_location
|
||||
),
|
||||
$suppressed_issues
|
||||
)) {
|
||||
@ -807,7 +810,7 @@ abstract class ClassLikeChecker extends SourceChecker implements StatementsSourc
|
||||
}
|
||||
}
|
||||
|
||||
FileChecker::addFileReferenceToClass(Config::getInstance()->getBaseDir() . $file_name, $fq_class_name);
|
||||
FileChecker::addFileReferenceToClass(Config::getInstance()->getBaseDir() . $code_location->file_name, $fq_class_name);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -78,23 +78,25 @@ class CommentChecker
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $comment
|
||||
* @param string $comment
|
||||
* @param int $line_number
|
||||
* @return FunctionDocblockComment
|
||||
* @throws DocblockParseException If there was a problem parsing the docblock.
|
||||
* @psalm-suppress MixedArrayAccess
|
||||
*/
|
||||
public static function extractDocblockInfo($comment)
|
||||
public static function extractDocblockInfo($comment, $line_number)
|
||||
{
|
||||
$comments = self::parseDocComment($comment);
|
||||
$comments = self::parseDocComment($comment, $line_number);
|
||||
|
||||
$info = new FunctionDocblockComment();
|
||||
|
||||
if (isset($comments['specials']['return']) || isset($comments['specials']['psalm-return'])) {
|
||||
$return_block = trim(
|
||||
isset($comments['specials']['psalm-return'])
|
||||
? (string)$comments['specials']['psalm-return'][0]
|
||||
: (string)$comments['specials']['return'][0]
|
||||
);
|
||||
/** @var array */
|
||||
$return_specials = isset($comments['specials']['psalm-return'])
|
||||
? $comments['specials']['psalm-return']
|
||||
: $comments['specials']['return'];
|
||||
|
||||
$return_block = trim((string)reset($return_specials));
|
||||
|
||||
try {
|
||||
$line_parts = self::splitDocLine($return_block);
|
||||
@ -107,11 +109,12 @@ class CommentChecker
|
||||
&& !strpos($line_parts[0], '::')
|
||||
) {
|
||||
$info->return_type = $line_parts[0];
|
||||
$info->return_type_line_number = array_keys($return_specials)[0];
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($comments['specials']['param'])) {
|
||||
foreach ($comments['specials']['param'] as $param) {
|
||||
foreach ($comments['specials']['param'] as $line_number => $param) {
|
||||
try {
|
||||
$line_parts = self::splitDocLine((string)$param);
|
||||
} catch (DocblockParseException $e) {
|
||||
@ -128,7 +131,11 @@ class CommentChecker
|
||||
$line_parts[1] = substr($line_parts[1], 1);
|
||||
}
|
||||
|
||||
$info->params[] = ['name' => substr($line_parts[1], 1), 'type' => $line_parts[0]];
|
||||
$info->params[] = [
|
||||
'name' => substr($line_parts[1], 1),
|
||||
'type' => $line_parts[0],
|
||||
'line_number' => $line_number
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -202,9 +209,10 @@ class CommentChecker
|
||||
* https://github.com/facebook/libphutil/blob/master/src/parser/docblock/PhutilDocblockParser.php
|
||||
*
|
||||
* @param string $docblock
|
||||
* @param int $line_number
|
||||
* @return array Array of the main comment and specials
|
||||
*/
|
||||
public static function parseDocComment($docblock)
|
||||
public static function parseDocComment($docblock, $line_number = null)
|
||||
{
|
||||
// Strip off comments.
|
||||
$docblock = trim($docblock);
|
||||
@ -214,6 +222,13 @@ class CommentChecker
|
||||
|
||||
// Normalize multi-line @specials.
|
||||
$lines = explode("\n", $docblock);
|
||||
|
||||
$line_map = [];
|
||||
|
||||
if ($line_number) {
|
||||
$line_number++;
|
||||
}
|
||||
|
||||
$last = false;
|
||||
foreach ($lines as $k => $line) {
|
||||
if (preg_match('/^\s?@\w/i', $line)) {
|
||||
@ -224,6 +239,10 @@ class CommentChecker
|
||||
$lines[$last] = rtrim($lines[$last]).' '.trim($line);
|
||||
unset($lines[$k]);
|
||||
}
|
||||
|
||||
if ($line_number) {
|
||||
$line_map[$line] = $line_number++;
|
||||
}
|
||||
}
|
||||
|
||||
$docblock = implode("\n", $lines);
|
||||
@ -235,14 +254,14 @@ class CommentChecker
|
||||
$have_specials = preg_match_all('/^\s?@([\w\-:]+)\s*([^\n]*)/m', $docblock, $matches, PREG_SET_ORDER);
|
||||
if ($have_specials) {
|
||||
$docblock = preg_replace('/^\s?@([\w\-:]+)\s*([^\n]*)/m', '', $docblock);
|
||||
foreach ($matches as $match) {
|
||||
foreach ($matches as $m => $match) {
|
||||
list($_, $type, $data) = $match;
|
||||
|
||||
if (empty($special[$type])) {
|
||||
$special[$type] = array();
|
||||
$special[$type] = [];
|
||||
}
|
||||
|
||||
$special[$type][] = $data;
|
||||
$special[$type][$line_map && isset($line_map[$_]) ? $line_map[$_] : $m] = $data;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,7 @@ class FileChecker extends SourceChecker implements StatementsSource
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $real_file_name;
|
||||
protected $file_path;
|
||||
|
||||
/**
|
||||
* @var array<string, array<string, string>>
|
||||
@ -117,7 +117,7 @@ class FileChecker extends SourceChecker implements StatementsSource
|
||||
*/
|
||||
public function __construct($file_name, array $preloaded_statements = [])
|
||||
{
|
||||
$this->real_file_name = $file_name;
|
||||
$this->file_path = $file_name;
|
||||
$this->file_name = Config::getInstance()->shortenFileName($file_name);
|
||||
|
||||
self::$file_checkers[$this->file_name] = $this;
|
||||
@ -147,11 +147,11 @@ class FileChecker extends SourceChecker implements StatementsSource
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($cache && $check_classes && !$check_functions && isset(self::$classes_checked[$this->real_file_name])) {
|
||||
if ($cache && $check_classes && !$check_functions && isset(self::$classes_checked[$this->file_path])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($cache && !$check_classes && !$check_functions && isset(self::$files_checked[$this->real_file_name])) {
|
||||
if ($cache && !$check_classes && !$check_functions && isset(self::$files_checked[$this->file_path])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -249,19 +249,19 @@ class FileChecker extends SourceChecker implements StatementsSource
|
||||
}
|
||||
|
||||
if ($check_functions) {
|
||||
self::$functions_checked[$this->real_file_name] = true;
|
||||
self::$functions_checked[$this->file_path] = true;
|
||||
}
|
||||
|
||||
if ($check_classes) {
|
||||
self::$classes_checked[$this->real_file_name] = true;
|
||||
self::$classes_checked[$this->file_path] = true;
|
||||
}
|
||||
|
||||
self::$files_checked[$this->real_file_name] = true;
|
||||
self::$files_checked[$this->file_path] = true;
|
||||
|
||||
if ($update_docblocks && isset(self::$docblock_return_types[$this->file_name])) {
|
||||
$line_upset = 0;
|
||||
|
||||
$file_lines = explode(PHP_EOL, (string)file_get_contents($this->real_file_name));
|
||||
$file_lines = explode(PHP_EOL, (string)file_get_contents($this->file_path));
|
||||
|
||||
$file_docblock_updates = self::$docblock_return_types[$this->file_name];
|
||||
|
||||
@ -269,7 +269,7 @@ class FileChecker extends SourceChecker implements StatementsSource
|
||||
self::updateDocblock($file_lines, $line_number, $line_upset, $type[0], $type[1], $type[2]);
|
||||
}
|
||||
|
||||
file_put_contents($this->real_file_name, implode(PHP_EOL, $file_lines));
|
||||
file_put_contents($this->file_path, implode(PHP_EOL, $file_lines));
|
||||
|
||||
echo 'Added/updated ' . count($file_docblock_updates) . ' docblocks in ' . $this->file_name . PHP_EOL;
|
||||
}
|
||||
@ -321,7 +321,7 @@ class FileChecker extends SourceChecker implements StatementsSource
|
||||
{
|
||||
return $this->preloaded_statements
|
||||
? $this->preloaded_statements
|
||||
: self::getStatementsForFile($this->real_file_name);
|
||||
: self::getStatementsForFile($this->file_path);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -339,8 +339,10 @@ class FileChecker extends SourceChecker implements StatementsSource
|
||||
$cache_location = null;
|
||||
$name_cache_key = null;
|
||||
|
||||
$version = 'parsercache2';
|
||||
|
||||
$file_contents = (string)file_get_contents($file_name);
|
||||
$file_content_hash = md5($file_contents);
|
||||
$file_content_hash = md5($version . $file_contents);
|
||||
$name_cache_key = self::getParserCacheKey($file_name);
|
||||
|
||||
if (self::$file_content_hashes === null) {
|
||||
@ -365,7 +367,13 @@ class FileChecker extends SourceChecker implements StatementsSource
|
||||
}
|
||||
|
||||
if (!$stmts) {
|
||||
$parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
|
||||
$lexer = new PhpParser\Lexer([
|
||||
'usedAttributes' => [
|
||||
'comments', 'startLine', 'startFilePos', 'endFilePos'
|
||||
]
|
||||
]);
|
||||
|
||||
$parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7, $lexer);
|
||||
|
||||
$stmts = $parser->parse($file_contents);
|
||||
}
|
||||
@ -481,14 +489,6 @@ class FileChecker extends SourceChecker implements StatementsSource
|
||||
return $this->aliased_classes_flipped;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getRealFileName()
|
||||
{
|
||||
return $this->real_file_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $file_name
|
||||
* @return mixed
|
||||
|
@ -2,6 +2,7 @@
|
||||
namespace Psalm\Checker;
|
||||
|
||||
use PhpParser;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\Config;
|
||||
use Psalm\EffectsAnalyser;
|
||||
use Psalm\Exception\DocblockParseException;
|
||||
@ -194,9 +195,7 @@ class FunctionChecker extends FunctionLikeChecker
|
||||
foreach ($function->getParams() as $param) {
|
||||
$param_array = self::getTranslatedParam(
|
||||
$param,
|
||||
$this->fq_class_name,
|
||||
$this->namespace,
|
||||
$this->getAliasedClasses()
|
||||
$this
|
||||
);
|
||||
|
||||
self::$file_function_params[$file_name][$function_id][] = $param_array;
|
||||
@ -210,63 +209,69 @@ class FunctionChecker extends FunctionLikeChecker
|
||||
|
||||
$this->suppressed_issues = [];
|
||||
|
||||
try {
|
||||
$docblock_info = CommentChecker::extractDocblockInfo((string)$function->getDocComment());
|
||||
} catch (DocblockParseException $e) {
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidDocblock(
|
||||
'Invalid type passed in docblock for ' . $this->getMethodId(),
|
||||
$this->getCheckedFileName(),
|
||||
$function->getLine()
|
||||
)
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
$doc_comment = $function->getDocComment();
|
||||
|
||||
if ($docblock_info) {
|
||||
if ($docblock_info->deprecated) {
|
||||
self::$deprecated_functions[$file_name][$function_id] = true;
|
||||
}
|
||||
|
||||
if ($docblock_info->variadic) {
|
||||
self::$variadic_functions[$file_name][$function_id] = true;
|
||||
}
|
||||
|
||||
$this->suppressed_issues = $docblock_info->suppress;
|
||||
|
||||
if ($function->returnType) {
|
||||
$return_type = Type::parseString(
|
||||
is_string($function->returnType)
|
||||
? $function->returnType
|
||||
: ClassLikeChecker::getFQCLNFromNameObject(
|
||||
$function->returnType,
|
||||
$this->namespace,
|
||||
$this->getAliasedClasses()
|
||||
)
|
||||
if ($doc_comment) {
|
||||
try {
|
||||
$docblock_info = CommentChecker::extractDocblockInfo(
|
||||
(string)$doc_comment,
|
||||
$doc_comment->getLine()
|
||||
);
|
||||
} catch (DocblockParseException $e) {
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidDocblock(
|
||||
'Invalid type passed in docblock for ' . $this->getMethodId(),
|
||||
new CodeLocation($this, $function, true)
|
||||
)
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($config->use_docblock_types) {
|
||||
if ($docblock_info->return_type) {
|
||||
$return_type =
|
||||
Type::parseString(
|
||||
self::fixUpLocalType(
|
||||
(string)$docblock_info->return_type,
|
||||
null,
|
||||
if ($docblock_info) {
|
||||
if ($docblock_info->deprecated) {
|
||||
self::$deprecated_functions[$file_name][$function_id] = true;
|
||||
}
|
||||
|
||||
if ($docblock_info->variadic) {
|
||||
self::$variadic_functions[$file_name][$function_id] = true;
|
||||
}
|
||||
|
||||
$this->suppressed_issues = $docblock_info->suppress;
|
||||
|
||||
if ($function->returnType) {
|
||||
$return_type = Type::parseString(
|
||||
is_string($function->returnType)
|
||||
? $function->returnType
|
||||
: ClassLikeChecker::getFQCLNFromNameObject(
|
||||
$function->returnType,
|
||||
$this->namespace,
|
||||
$this->getAliasedClasses()
|
||||
)
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
if ($docblock_info->params) {
|
||||
$this->improveParamsFromDocblock(
|
||||
$docblock_info->params,
|
||||
$function_param_names,
|
||||
self::$file_function_params[$file_name][$function_id],
|
||||
$function->getLine()
|
||||
);
|
||||
if ($config->use_docblock_types) {
|
||||
if ($docblock_info->return_type) {
|
||||
$return_type =
|
||||
Type::parseString(
|
||||
self::fixUpLocalType(
|
||||
(string)$docblock_info->return_type,
|
||||
null,
|
||||
$this->namespace,
|
||||
$this->getAliasedClasses()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ($docblock_info->params) {
|
||||
$this->improveParamsFromDocblock(
|
||||
$docblock_info->params,
|
||||
$function_param_names,
|
||||
self::$file_function_params[$file_name][$function_id],
|
||||
new CodeLocation($this, $function, false)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -326,6 +331,7 @@ class FunctionChecker extends FunctionLikeChecker
|
||||
$arg_name,
|
||||
$by_reference,
|
||||
$arg_type ? Type::parseString($arg_type) : Type::getMixed(),
|
||||
null,
|
||||
$optional,
|
||||
false,
|
||||
$arg_name === '...'
|
||||
@ -362,16 +368,14 @@ class FunctionChecker extends FunctionLikeChecker
|
||||
/**
|
||||
* @param string $function_id
|
||||
* @param array<PhpParser\Node\Arg> $call_args
|
||||
* @param string $file_name
|
||||
* @param int $line_number
|
||||
* @param CodeLocation $code_location
|
||||
* @param array $suppressed_issues
|
||||
* @return Type\Union
|
||||
*/
|
||||
public static function getReturnTypeFromCallMapWithArgs(
|
||||
$function_id,
|
||||
array $call_args,
|
||||
$file_name,
|
||||
$line_number,
|
||||
CodeLocation $code_location,
|
||||
array $suppressed_issues
|
||||
) {
|
||||
$call_map_key = strtolower($function_id);
|
||||
@ -409,8 +413,7 @@ class FunctionChecker extends FunctionLikeChecker
|
||||
$array_return_type = self::getArrayReturnType(
|
||||
$call_map_key,
|
||||
$call_args,
|
||||
$file_name,
|
||||
$line_number,
|
||||
$code_location,
|
||||
$suppressed_issues
|
||||
);
|
||||
|
||||
@ -434,20 +437,18 @@ class FunctionChecker extends FunctionLikeChecker
|
||||
/**
|
||||
* @param string $call_map_key
|
||||
* @param array<PhpParser\Node\Arg> $call_args
|
||||
* @param string $file_name
|
||||
* @param int $line_number
|
||||
* @param CodeLocation $code_location
|
||||
* @param array $suppressed_issues
|
||||
* @return Type\Union|null
|
||||
*/
|
||||
protected static function getArrayReturnType(
|
||||
$call_map_key,
|
||||
$call_args,
|
||||
$file_name,
|
||||
$line_number,
|
||||
CodeLocation $code_location,
|
||||
array $suppressed_issues
|
||||
) {
|
||||
if ($call_map_key === 'array_map' || $call_map_key === 'array_filter') {
|
||||
return self::getArrayMapReturnType($call_map_key, $call_args, $file_name, $line_number, $suppressed_issues);
|
||||
return self::getArrayMapReturnType($call_map_key, $call_args, $code_location, $suppressed_issues);
|
||||
}
|
||||
|
||||
$first_arg = isset($call_args[0]->value) ? $call_args[0]->value : null;
|
||||
@ -552,16 +553,14 @@ class FunctionChecker extends FunctionLikeChecker
|
||||
/**
|
||||
* @param string $call_map_key
|
||||
* @param array<PhpParser\Node\Arg> $call_args
|
||||
* @param string $file_name
|
||||
* @param int $line_number
|
||||
* @param CodeLocation $code_location
|
||||
* @param array $suppressed_issues
|
||||
* @return Type\Union
|
||||
*/
|
||||
protected static function getArrayMapReturnType(
|
||||
$call_map_key,
|
||||
$call_args,
|
||||
$file_name,
|
||||
$line_number,
|
||||
CodeLocation $code_location,
|
||||
array $suppressed_issues
|
||||
) {
|
||||
$function_index = $call_map_key === 'array_map' ? 0 : 1;
|
||||
@ -591,8 +590,7 @@ class FunctionChecker extends FunctionLikeChecker
|
||||
IssueBuffer::accepts(
|
||||
new InvalidReturnType(
|
||||
'No return type could be found in the closure passed to ' . $call_map_key,
|
||||
$file_name,
|
||||
$line_number
|
||||
$code_location
|
||||
),
|
||||
$suppressed_issues
|
||||
);
|
||||
|
@ -3,6 +3,7 @@ namespace Psalm\Checker;
|
||||
|
||||
use PhpParser\Node\Expr\Closure;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use Psalm\CodeLocation;
|
||||
use PhpParser\Node\Stmt\Function_;
|
||||
use PhpParser;
|
||||
use Psalm\Checker\Statements\ExpressionChecker;
|
||||
@ -86,7 +87,9 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
|
||||
$this->class_name = $source->getClassName();
|
||||
$this->class_extends = $source->getParentClass();
|
||||
$this->file_name = $source->getFileName();
|
||||
$this->file_path = $source->getFilePath();
|
||||
$this->include_file_name = $source->getIncludeFileName();
|
||||
$this->include_file_path = $source->getIncludeFilePath();
|
||||
$this->fq_class_name = $source->getFQCLN();
|
||||
$this->source = $source;
|
||||
$this->suppressed_issues = $source->getSuppressedIssues();
|
||||
@ -162,8 +165,7 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
|
||||
new MethodSignatureMismatch(
|
||||
'Method ' . $cased_method_id .' has fewer arguments than parent method ' .
|
||||
$parent_method_id,
|
||||
$this->getCheckedFileName(),
|
||||
$this->function->getLine()
|
||||
new CodeLocation($this, $this->function)
|
||||
)
|
||||
)) {
|
||||
return false;
|
||||
@ -185,8 +187,7 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
|
||||
$function_params[$i]->signature_type . '\', expecting \'' .
|
||||
$implemented_param->signature_type . '\' as defined by ' .
|
||||
$parent_method_id,
|
||||
$this->getCheckedFileName(),
|
||||
$this->function->getLine()
|
||||
new CodeLocation($this, $this->function)
|
||||
)
|
||||
)) {
|
||||
return false;
|
||||
@ -206,9 +207,7 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
|
||||
foreach ($this->function->getParams() as $param) {
|
||||
$function_params[] = self::getTranslatedParam(
|
||||
$param,
|
||||
$this->fq_class_name,
|
||||
$this->namespace,
|
||||
$this->getAliasedClasses()
|
||||
$this
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -221,14 +220,17 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
|
||||
$this->getMethodId()
|
||||
);
|
||||
|
||||
if (!$function_param->code_location) {
|
||||
throw new \UnexpectedValueException('We should know where this code is');
|
||||
}
|
||||
|
||||
foreach ($param_type->types as $atomic_type) {
|
||||
if ($atomic_type->isObjectType()
|
||||
&& !$atomic_type->isObject()
|
||||
&& $this->function instanceof PhpParser\Node
|
||||
&& ClassLikeChecker::checkFullyQualifiedClassLikeName(
|
||||
$atomic_type->value,
|
||||
$this->file_name,
|
||||
$this->function->getLine(),
|
||||
$function_param->code_location,
|
||||
$this->suppressed_issues
|
||||
) === false
|
||||
) {
|
||||
@ -238,7 +240,7 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
|
||||
|
||||
$context->vars_in_scope['$' . $function_param->name] = $param_type;
|
||||
|
||||
$statements_checker->registerVariable($function_param->name, $function_param->line);
|
||||
$statements_checker->registerVariable($function_param->name, $function_param->code_location->line_number);
|
||||
}
|
||||
|
||||
$statements_checker->check($function_stmts, $context, null, $global_context);
|
||||
@ -430,8 +432,7 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
|
||||
if (IssueBuffer::accepts(
|
||||
new MissingReturnType(
|
||||
'Method ' . $cased_method_id . ' does not have a return type',
|
||||
$this->file_name,
|
||||
$this->function->getLine()
|
||||
new CodeLocation($this, $this->function, true)
|
||||
),
|
||||
$this->suppressed_issues
|
||||
)) {
|
||||
@ -495,8 +496,7 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
|
||||
new InvalidReturnType(
|
||||
'No return type was found for method ' . $cased_method_id .
|
||||
' but return type \'' . $declared_return_type . '\' was expected',
|
||||
$this->getCheckedFileName(),
|
||||
$this->function->getLine()
|
||||
new CodeLocation($this, $this->function, true)
|
||||
)
|
||||
)) {
|
||||
return false;
|
||||
@ -515,8 +515,7 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
|
||||
new MixedInferredReturnType(
|
||||
'Could not verify return type \'' . $declared_return_type . '\' for ' .
|
||||
$cased_method_id,
|
||||
$this->getCheckedFileName(),
|
||||
$this->function->getLine()
|
||||
new CodeLocation($this, $this->function, true)
|
||||
),
|
||||
$this->getSuppressedIssues()
|
||||
)) {
|
||||
@ -549,8 +548,7 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
|
||||
new InvalidReturnType(
|
||||
'The given return type \'' . $declared_return_type . '\' for ' . $cased_method_id .
|
||||
' is incorrect, got \'' . $inferred_return_type . '\'',
|
||||
$this->getCheckedFileName(),
|
||||
$this->function->getLine()
|
||||
new CodeLocation($this, $this->function, true)
|
||||
),
|
||||
$this->getSuppressedIssues()
|
||||
)) {
|
||||
@ -563,30 +561,31 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<array{type:string,name:string}> $docblock_params
|
||||
* @param array<array{type:string,name:string,line_number:int}> $docblock_params
|
||||
* @param array<string,Type\Union> $function_param_names
|
||||
* @param array<\Psalm\FunctionLikeParameter> &$function_signature
|
||||
* @param int $method_line_number
|
||||
* @param CodeLocation $code_location
|
||||
* @return false|null
|
||||
*/
|
||||
protected function improveParamsFromDocblock(
|
||||
array $docblock_params,
|
||||
array $function_param_names,
|
||||
array &$function_signature,
|
||||
$method_line_number
|
||||
CodeLocation $code_location
|
||||
) {
|
||||
$docblock_param_vars = [];
|
||||
|
||||
foreach ($docblock_params as $docblock_param) {
|
||||
$param_name = $docblock_param['name'];
|
||||
$line_number = $docblock_param['line_number'];
|
||||
|
||||
if (!array_key_exists($param_name, $function_param_names)) {
|
||||
$code_location->setCommentLine($line_number);
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidDocblock(
|
||||
'Parameter $' . $param_name .' does not appear in the argument list for ' .
|
||||
$this->getMethodId(),
|
||||
$this->getCheckedFileName(),
|
||||
$method_line_number
|
||||
$code_location
|
||||
)
|
||||
)) {
|
||||
return false;
|
||||
@ -608,12 +607,12 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
|
||||
|
||||
if ($function_param_names[$param_name] && !$function_param_names[$param_name]->isMixed()) {
|
||||
if (!$new_param_type->isIn($function_param_names[$param_name])) {
|
||||
$code_location->setCommentLine($line_number);
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidDocblock(
|
||||
'Parameter $' . $param_name .' has wrong type \'' . $new_param_type . '\', should be \'' .
|
||||
$function_param_names[$param_name] . '\'',
|
||||
$this->getCheckedFileName(),
|
||||
$method_line_number
|
||||
$code_location
|
||||
)
|
||||
)) {
|
||||
return false;
|
||||
@ -638,14 +637,13 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($function_param_names as $param_name => $_) {
|
||||
if (!isset($docblock_param_vars[$param_name])) {
|
||||
foreach ($function_signature as &$function_signature_param) {
|
||||
if (!isset($docblock_param_vars[$function_signature_param->name]) && $function_signature_param->code_location) {
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidDocblock(
|
||||
'Parameter $' . $param_name .' does not appear in the docbock for ' .
|
||||
'Parameter $' . $function_signature_param->name .' does not appear in the docbock for ' .
|
||||
$this->getMethodId(),
|
||||
$this->getCheckedFileName(),
|
||||
$method_line_number
|
||||
$function_signature_param->code_location
|
||||
)
|
||||
)) {
|
||||
return false;
|
||||
@ -660,16 +658,12 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
|
||||
|
||||
/**
|
||||
* @param PhpParser\Node\Param $param
|
||||
* @param string $fq_class_name
|
||||
* @param string $namespace
|
||||
* @param array<string> $aliased_classes
|
||||
* @param StatementsSource $source
|
||||
* @return FunctionLikeParameter
|
||||
*/
|
||||
public static function getTranslatedParam(
|
||||
PhpParser\Node\Param $param,
|
||||
$fq_class_name,
|
||||
$namespace,
|
||||
array $aliased_classes
|
||||
StatementsSource $source
|
||||
) {
|
||||
$param_type = null;
|
||||
|
||||
@ -684,12 +678,12 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
|
||||
} elseif ($param->type instanceof PhpParser\Node\Name\FullyQualified) {
|
||||
$param_type_string = implode('\\', $param->type->parts);
|
||||
} elseif ($param->type->parts === ['self']) {
|
||||
$param_type_string = $fq_class_name;
|
||||
$param_type_string = $source->getFQCLN();
|
||||
} else {
|
||||
$param_type_string = ClassLikeChecker::getFQCLNFromString(
|
||||
implode('\\', $param->type->parts),
|
||||
$namespace,
|
||||
$aliased_classes
|
||||
$source->getNamespace(),
|
||||
$source->getAliasedClasses()
|
||||
);
|
||||
}
|
||||
|
||||
@ -720,6 +714,7 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
|
||||
$param->name,
|
||||
$param->byRef,
|
||||
$param_type ?: Type::getMixed(),
|
||||
new CodeLocation($source, $param),
|
||||
$is_optional,
|
||||
$is_nullable,
|
||||
$param->variadic
|
||||
@ -772,6 +767,7 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
|
||||
$param_name,
|
||||
(bool)$param->isPassedByReference(),
|
||||
$param_type,
|
||||
null,
|
||||
$is_optional,
|
||||
$is_nullable
|
||||
);
|
||||
|
@ -2,6 +2,7 @@
|
||||
namespace Psalm\Checker;
|
||||
|
||||
use PhpParser;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\Config;
|
||||
use Psalm\Exception\DocblockParseException;
|
||||
use Psalm\Issue\DeprecatedMethod;
|
||||
@ -227,12 +228,11 @@ class MethodChecker extends FunctionLikeChecker
|
||||
* Determines whether a given method is static or not
|
||||
*
|
||||
* @param string $method_id
|
||||
* @param string $file_name
|
||||
* @param int $line_number
|
||||
* @param CodeLocation $code_location
|
||||
* @param array<int, string> $suppressed_issues
|
||||
* @return bool
|
||||
*/
|
||||
public static function checkMethodStatic($method_id, $file_name, $line_number, array $suppressed_issues)
|
||||
public static function checkMethodStatic($method_id, CodeLocation $code_location, array $suppressed_issues)
|
||||
{
|
||||
self::registerClassMethod($method_id);
|
||||
|
||||
@ -243,8 +243,7 @@ class MethodChecker extends FunctionLikeChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidStaticInvocation(
|
||||
'Method ' . MethodChecker::getCasedMethodId($method_id) . ' is not static',
|
||||
$file_name,
|
||||
$line_number
|
||||
$code_location
|
||||
),
|
||||
$suppressed_issues
|
||||
)) {
|
||||
@ -263,8 +262,7 @@ class MethodChecker extends FunctionLikeChecker
|
||||
{
|
||||
if (strtolower($method->name) === strtolower((string)$this->class_name)) {
|
||||
$method_id = $this->fq_class_name . '::__construct';
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
$method_id = $this->fq_class_name . '::' . strtolower($method->name);
|
||||
}
|
||||
|
||||
@ -299,9 +297,7 @@ class MethodChecker extends FunctionLikeChecker
|
||||
foreach ($method->getParams() as $param) {
|
||||
$param_array = $this->getTranslatedParam(
|
||||
$param,
|
||||
$this->fq_class_name,
|
||||
$this->namespace,
|
||||
$this->getAliasedClasses()
|
||||
$this
|
||||
);
|
||||
|
||||
self::$method_params[$method_id][] = $param_array;
|
||||
@ -331,13 +327,12 @@ class MethodChecker extends FunctionLikeChecker
|
||||
$docblock_info = null;
|
||||
|
||||
try {
|
||||
$docblock_info = CommentChecker::extractDocblockInfo((string)$doc_comment);
|
||||
$docblock_info = CommentChecker::extractDocblockInfo((string)$doc_comment, $doc_comment->getLine());
|
||||
} catch (DocblockParseException $e) {
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidDocblock(
|
||||
'Invalid type passed in docblock for ' . $cased_method_id,
|
||||
$this->getCheckedFileName(),
|
||||
$method->getLine()
|
||||
new CodeLocation($this, $method)
|
||||
)
|
||||
)) {
|
||||
return false;
|
||||
@ -373,7 +368,7 @@ class MethodChecker extends FunctionLikeChecker
|
||||
$docblock_info->params,
|
||||
$method_param_names,
|
||||
self::$method_params[$method_id],
|
||||
$method->getLine()
|
||||
new CodeLocation($this, $method, true)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -433,20 +428,19 @@ class MethodChecker extends FunctionLikeChecker
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $method_id
|
||||
* @param string $file_name
|
||||
* @param int $line_number
|
||||
* @param array $suppressed_issues
|
||||
* @param string $method_id
|
||||
* @param CodeLocation $code_location
|
||||
* @param array $suppressed_issues
|
||||
* @return bool|null
|
||||
*/
|
||||
public static function checkMethodExists($method_id, $file_name, $line_number, array $suppressed_issues)
|
||||
public static function checkMethodExists($method_id, CodeLocation $code_location, array $suppressed_issues)
|
||||
{
|
||||
if (self::methodExists($method_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (IssueBuffer::accepts(
|
||||
new UndefinedMethod('Method ' . $method_id . ' does not exist', $file_name, $line_number),
|
||||
new UndefinedMethod('Method ' . $method_id . ' does not exist', $code_location),
|
||||
$suppressed_issues
|
||||
)) {
|
||||
return false;
|
||||
@ -501,13 +495,12 @@ class MethodChecker extends FunctionLikeChecker
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $method_id
|
||||
* @param string $file_name
|
||||
* @param int $line_number
|
||||
* @param array $suppressed_issues
|
||||
* @param string $method_id
|
||||
* @param CodeLocation $code_location
|
||||
* @param array $suppressed_issues
|
||||
* @return false|null
|
||||
*/
|
||||
public static function checkMethodNotDeprecated($method_id, $file_name, $line_number, array $suppressed_issues)
|
||||
public static function checkMethodNotDeprecated($method_id, CodeLocation $code_location, array $suppressed_issues)
|
||||
{
|
||||
self::registerClassMethod($method_id);
|
||||
|
||||
@ -515,8 +508,7 @@ class MethodChecker extends FunctionLikeChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new DeprecatedMethod(
|
||||
'The method ' . MethodChecker::getCasedMethodId($method_id) . ' has been marked as deprecated',
|
||||
$file_name,
|
||||
$line_number
|
||||
$code_location
|
||||
),
|
||||
$suppressed_issues
|
||||
)) {
|
||||
@ -531,7 +523,7 @@ class MethodChecker extends FunctionLikeChecker
|
||||
* @param string $method_id
|
||||
* @param string|null $calling_context
|
||||
* @param StatementsSource $source
|
||||
* @param int $line_number
|
||||
* @param CodeLocation $code_location
|
||||
* @param array $suppressed_issues
|
||||
* @return false|null
|
||||
*/
|
||||
@ -539,7 +531,7 @@ class MethodChecker extends FunctionLikeChecker
|
||||
$method_id,
|
||||
$calling_context,
|
||||
StatementsSource $source,
|
||||
$line_number,
|
||||
CodeLocation $code_location,
|
||||
array $suppressed_issues
|
||||
) {
|
||||
self::registerClassMethod($method_id);
|
||||
@ -551,7 +543,7 @@ class MethodChecker extends FunctionLikeChecker
|
||||
|
||||
if (!isset(self::$method_visibility[$declared_method_id])) {
|
||||
if (IssueBuffer::accepts(
|
||||
new InaccessibleMethod('Cannot access method ' . $method_id, $source->getFileName(), $line_number),
|
||||
new InaccessibleMethod('Cannot access method ' . $method_id, $code_location),
|
||||
$suppressed_issues
|
||||
)) {
|
||||
return false;
|
||||
@ -572,8 +564,7 @@ class MethodChecker extends FunctionLikeChecker
|
||||
new InaccessibleMethod(
|
||||
'Cannot access private method ' . MethodChecker::getCasedMethodId($method_id) .
|
||||
' from context ' . $calling_context,
|
||||
$source->getFileName(),
|
||||
$line_number
|
||||
$code_location
|
||||
),
|
||||
$suppressed_issues
|
||||
)) {
|
||||
@ -592,8 +583,7 @@ class MethodChecker extends FunctionLikeChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new InaccessibleMethod(
|
||||
'Cannot access protected method ' . $method_id,
|
||||
$source->getFileName(),
|
||||
$line_number
|
||||
$code_location
|
||||
),
|
||||
$suppressed_issues
|
||||
)) {
|
||||
@ -614,8 +604,7 @@ class MethodChecker extends FunctionLikeChecker
|
||||
new InaccessibleMethod(
|
||||
'Cannot access protected method ' . MethodChecker::getCasedMethodId($method_id) .
|
||||
' from context ' . $calling_context,
|
||||
$source->getFileName(),
|
||||
$line_number
|
||||
$code_location
|
||||
),
|
||||
$suppressed_issues
|
||||
)) {
|
||||
|
@ -35,7 +35,9 @@ class NamespaceChecker extends SourceChecker implements StatementsSource
|
||||
$this->namespace = $namespace;
|
||||
$this->namespace_name = $this->namespace->name ? implode('\\', $this->namespace->name->parts) : '';
|
||||
$this->file_name = $source->getFileName();
|
||||
$this->file_path = $source->getFilePath();
|
||||
$this->include_file_name = $source->getIncludeFileName();
|
||||
$this->include_file_path = $source->getIncludeFilePath();
|
||||
$this->suppressed_issues = $source->getSuppressedIssues();
|
||||
}
|
||||
|
||||
|
@ -34,11 +34,21 @@ abstract class SourceChecker implements StatementsSource
|
||||
*/
|
||||
protected $file_name;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $file_path;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
protected $include_file_name;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
protected $include_file_path;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
@ -198,6 +208,14 @@ abstract class SourceChecker implements StatementsSource
|
||||
return $this->file_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getFilePath()
|
||||
{
|
||||
return $this->file_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null|string
|
||||
*/
|
||||
@ -206,13 +224,23 @@ abstract class SourceChecker implements StatementsSource
|
||||
return $this->include_file_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null|string
|
||||
*/
|
||||
public function getIncludeFilePath()
|
||||
{
|
||||
return $this->include_file_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $file_name
|
||||
* @param string|null $file_path
|
||||
* @return void
|
||||
*/
|
||||
public function setIncludeFileName($file_name)
|
||||
public function setIncludeFileName($file_name, $file_path)
|
||||
{
|
||||
$this->include_file_name = $file_name;
|
||||
$this->include_file_path = $file_path;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -223,6 +251,14 @@ abstract class SourceChecker implements StatementsSource
|
||||
return $this->include_file_name ?: $this->file_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getCheckedFilePath()
|
||||
{
|
||||
return $this->include_file_path ?: $this->file_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
|
@ -2,6 +2,7 @@
|
||||
namespace Psalm\Checker\Statements\Block;
|
||||
|
||||
use PhpParser;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\Context;
|
||||
use Psalm\IssueBuffer;
|
||||
use Psalm\Checker\ClassChecker;
|
||||
@ -101,8 +102,7 @@ class ForeachChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new NullReference(
|
||||
'Cannot iterate over ' . $return_type->value,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -120,8 +120,7 @@ class ForeachChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidIterator(
|
||||
'Cannot iterate over ' . $return_type->value,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -159,8 +158,7 @@ class ForeachChecker
|
||||
) {
|
||||
if (ClassLikeChecker::checkFullyQualifiedClassLikeName(
|
||||
$return_type->value,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine(),
|
||||
new CodeLocation($statements_checker->getSource(), $stmt),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
) === false) {
|
||||
return false;
|
||||
|
@ -6,6 +6,7 @@ use Psalm\Checker\ScopeChecker;
|
||||
use Psalm\Checker\Statements\ExpressionChecker;
|
||||
use Psalm\Checker\StatementsChecker;
|
||||
use Psalm\Checker\TypeChecker;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\Context;
|
||||
use Psalm\IfScope;
|
||||
use Psalm\Type;
|
||||
@ -111,8 +112,7 @@ class IfChecker
|
||||
TypeChecker::reconcileKeyedTypes(
|
||||
$reconcilable_if_types,
|
||||
$if_context->vars_in_scope,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine(),
|
||||
new CodeLocation($statements_checker->getSource(), $stmt),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
);
|
||||
|
||||
@ -139,8 +139,7 @@ class IfChecker
|
||||
$else_vars_reconciled = TypeChecker::reconcileKeyedTypes(
|
||||
$if_scope->negated_types,
|
||||
$temp_else_context->vars_in_scope,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine(),
|
||||
new CodeLocation($statements_checker->getSource(), $stmt),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
);
|
||||
|
||||
@ -284,8 +283,7 @@ class IfChecker
|
||||
$outer_context_vars_reconciled = TypeChecker::reconcileKeyedTypes(
|
||||
$if_scope->negated_types,
|
||||
$outer_context->vars_in_scope,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine(),
|
||||
new CodeLocation($statements_checker->getSource(), $stmt),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
);
|
||||
|
||||
@ -353,8 +351,7 @@ class IfChecker
|
||||
$elseif_vars_reconciled = TypeChecker::reconcileKeyedTypes(
|
||||
$if_scope->negated_types,
|
||||
$elseif_context->vars_in_scope,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$elseif->getLine(),
|
||||
new CodeLocation($statements_checker->getSource(), $elseif),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
);
|
||||
|
||||
@ -414,8 +411,7 @@ class IfChecker
|
||||
$elseif_vars_reconciled = TypeChecker::reconcileKeyedTypes(
|
||||
$reconcilable_elseif_types,
|
||||
$elseif_context->vars_in_scope,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$elseif->getLine(),
|
||||
new CodeLocation($statements_checker->getSource(), $elseif),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
);
|
||||
|
||||
@ -569,8 +565,7 @@ class IfChecker
|
||||
$else_vars_reconciled = TypeChecker::reconcileKeyedTypes(
|
||||
$if_scope->negated_types,
|
||||
$else_context->vars_in_scope,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$else->getLine(),
|
||||
new CodeLocation($statements_checker->getSource(), $else),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
);
|
||||
|
||||
|
@ -5,6 +5,7 @@ use PhpParser;
|
||||
use Psalm\Checker\ScopeChecker;
|
||||
use Psalm\Checker\StatementsChecker;
|
||||
use Psalm\Checker\Statements\ExpressionChecker;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\Context;
|
||||
use Psalm\Issue\InvalidContinue;
|
||||
use Psalm\IssueBuffer;
|
||||
@ -133,8 +134,7 @@ class SwitchChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidContinue(
|
||||
'Continue called when not in loop',
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$case->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $case)
|
||||
)
|
||||
)) {
|
||||
return false;
|
||||
|
@ -3,6 +3,7 @@ namespace Psalm\Checker\Statements\Block;
|
||||
|
||||
use PhpParser;
|
||||
use Psalm\Checker\ClassLikeChecker;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\Checker\ScopeChecker;
|
||||
use Psalm\Checker\StatementsChecker;
|
||||
use Psalm\Context;
|
||||
@ -43,8 +44,7 @@ class TryChecker
|
||||
|
||||
if (ClassLikeChecker::checkFullyQualifiedClassLikeName(
|
||||
$fq_class_name,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine(),
|
||||
new CodeLocation($statements_checker->getSource(), $stmt),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
) === false) {
|
||||
return false;
|
||||
|
@ -2,6 +2,7 @@
|
||||
namespace Psalm\Checker\Statements\Block;
|
||||
|
||||
use PhpParser;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\Context;
|
||||
use Psalm\Checker\Statements\ExpressionChecker;
|
||||
use Psalm\Checker\StatementsChecker;
|
||||
@ -43,8 +44,7 @@ class WhileChecker
|
||||
$while_vars_in_scope_reconciled = TypeChecker::reconcileKeyedTypes(
|
||||
$while_types,
|
||||
$while_context->vars_in_scope,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine(),
|
||||
new CodeLocation($statements_checker->getSource(), $stmt),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
);
|
||||
|
||||
|
@ -9,6 +9,7 @@ use Psalm\Checker\InterfaceChecker;
|
||||
use Psalm\Checker\MethodChecker;
|
||||
use Psalm\Checker\StatementsChecker;
|
||||
use Psalm\Checker\Statements\ExpressionChecker;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\Context;
|
||||
use Psalm\Issue\FailedTypeResolution;
|
||||
use Psalm\Issue\InvalidArrayAssignment;
|
||||
@ -158,8 +159,7 @@ class AssignmentChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new FailedTypeResolution(
|
||||
'Cannot assign ' . $var_id . ' to type void',
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$assign_var->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $assign_var)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -200,7 +200,7 @@ class AssignmentChecker
|
||||
$expr_type = isset($stmt->expr->inferredType) ? $stmt->expr->inferredType : null;
|
||||
|
||||
if ($stmt instanceof PhpParser\Node\Expr\AssignOp\Plus) {
|
||||
ExpressionChecker::checkPlusOp($statements_checker, $stmt->getLine(), $var_type, $expr_type, $result_type);
|
||||
ExpressionChecker::checkPlusOp($var_type, $expr_type, $result_type);
|
||||
|
||||
if ($result_type && $var_id) {
|
||||
$context->vars_in_scope[$var_id] = $result_type;
|
||||
@ -259,8 +259,7 @@ class AssignmentChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidScope(
|
||||
'Cannot use $this when not inside class',
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -279,8 +278,7 @@ class AssignmentChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new MixedPropertyAssignment(
|
||||
$var_id . ' with mixed type cannot be assigned to',
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -294,8 +292,7 @@ class AssignmentChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new NullPropertyAssignment(
|
||||
$var_id . ' with null type cannot be assigned to',
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -309,8 +306,7 @@ class AssignmentChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new NullPropertyAssignment(
|
||||
$var_id . ' with possibly null type \'' . $lhs_type . '\' cannot be assigned to',
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -336,8 +332,7 @@ class AssignmentChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidPropertyAssignment(
|
||||
$var_id . ' with possible non-object type \'' . $lhs_type_part . '\' cannot be assigned to',
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -382,8 +377,7 @@ class AssignmentChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new NoInterfaceProperties(
|
||||
'Interfaces cannot have properties',
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -396,8 +390,7 @@ class AssignmentChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new UndefinedClass(
|
||||
'Cannot set properties of undefined class ' . $lhs_type_part->value,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -417,8 +410,7 @@ class AssignmentChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new UndefinedThisPropertyAssignment(
|
||||
'Instance property ' . $lhs_type_part->value . '::$' . $prop_name . ' is not defined',
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -428,8 +420,7 @@ class AssignmentChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new UndefinedPropertyAssignment(
|
||||
'Instance property ' . $lhs_type_part->value . '::$' . $prop_name . ' is not defined',
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -447,8 +438,7 @@ class AssignmentChecker
|
||||
new MissingPropertyType(
|
||||
'Property ' . $lhs_type_part->value . '::$' . $stmt->name . ' does not have a declared ' .
|
||||
'type',
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -482,8 +472,7 @@ class AssignmentChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new MissingPropertyDeclaration(
|
||||
'Missing property declaration for ' . $var_id,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -507,8 +496,7 @@ class AssignmentChecker
|
||||
new InvalidPropertyAssignment(
|
||||
$var_id . ' with declared type \'' . $class_property_type . '\' cannot be assigned type \'' .
|
||||
$assignment_type . '\'',
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -580,8 +568,7 @@ class AssignmentChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new InvisibleProperty(
|
||||
'Static property ' . $var_id . ' is not visible in this context',
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -592,8 +579,7 @@ class AssignmentChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new UndefinedThisPropertyAssignment(
|
||||
'Static property ' . $var_id . ' is not defined',
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -603,8 +589,7 @@ class AssignmentChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new UndefinedPropertyAssignment(
|
||||
'Static property ' . $var_id . ' is not defined',
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -624,8 +609,7 @@ class AssignmentChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new MissingPropertyType(
|
||||
'Property ' . $fq_class_name . '::$' . $prop_name . ' does not have a declared type',
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -652,8 +636,7 @@ class AssignmentChecker
|
||||
new InvalidPropertyAssignment(
|
||||
$var_id . ' with declared type \'' . $class_property_type . '\' cannot be assigned type \'' .
|
||||
$assignment_type . '\'',
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -754,8 +737,7 @@ class AssignmentChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new MixedStringOffsetAssignment(
|
||||
'Cannot assign a mixed variable to a string offset for ' . $var_id,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -768,8 +750,7 @@ class AssignmentChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidArrayAssignment(
|
||||
'Cannot assign string offset for ' . $var_id . ' of type ' . $value_type,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
|
@ -10,6 +10,7 @@ use Psalm\Checker\MethodChecker;
|
||||
use Psalm\Checker\StatementsChecker;
|
||||
use Psalm\Checker\Statements\ExpressionChecker;
|
||||
use Psalm\Checker\TraitChecker;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\Context;
|
||||
use Psalm\Issue\ForbiddenCode;
|
||||
use Psalm\Issue\InvalidArgument;
|
||||
@ -65,8 +66,7 @@ class CallChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new ForbiddenCode(
|
||||
'Unsafe ' . implode('', $method->parts),
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -109,8 +109,10 @@ class CallChecker
|
||||
|
||||
$in_call_map = FunctionChecker::inCallMap($method_id);
|
||||
|
||||
$code_location = new CodeLocation($statements_checker->getSource(), $stmt);
|
||||
|
||||
if (!$in_call_map &&
|
||||
self::checkFunctionExists($statements_checker, $method_id, $context, $stmt->getLine()) === false
|
||||
self::checkFunctionExists($statements_checker, $method_id, $context, $code_location) === false
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
@ -120,7 +122,7 @@ class CallChecker
|
||||
$stmt->args,
|
||||
$method_id,
|
||||
$context,
|
||||
$stmt->getLine()
|
||||
$code_location
|
||||
) === false) {
|
||||
return false;
|
||||
}
|
||||
@ -129,8 +131,7 @@ class CallChecker
|
||||
$stmt->inferredType = FunctionChecker::getReturnTypeFromCallMapWithArgs(
|
||||
$method_id,
|
||||
$stmt->args,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine(),
|
||||
$code_location,
|
||||
$statements_checker->getSuppressedIssues()
|
||||
);
|
||||
} else {
|
||||
@ -185,8 +186,7 @@ class CallChecker
|
||||
|
||||
if (ClassLikeChecker::checkFullyQualifiedClassLikeName(
|
||||
$fq_class_name,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine(),
|
||||
new CodeLocation($statements_checker->getSource(), $stmt->class),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
) === false) {
|
||||
return false;
|
||||
@ -226,7 +226,7 @@ class CallChecker
|
||||
$stmt->args,
|
||||
$method_id,
|
||||
$context,
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
) === false) {
|
||||
return false;
|
||||
}
|
||||
@ -305,8 +305,7 @@ class CallChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidScope(
|
||||
'Use of $this in non-class context',
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -377,8 +376,7 @@ class CallChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new NullReference(
|
||||
'Cannot call method ' . $stmt->name . ' on possibly null variable ' . $var_id,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -394,8 +392,7 @@ class CallChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidArgument(
|
||||
'Cannot call method ' . $stmt->name . ' on ' . $class_type . ' variable ' . $var_id,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -408,8 +405,7 @@ class CallChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new MixedMethodCall(
|
||||
'Cannot call method ' . $stmt->name . ' on a mixed variable ' . $var_id,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -432,8 +428,7 @@ class CallChecker
|
||||
|
||||
$does_class_exist = ClassLikeChecker::checkFullyQualifiedClassLikeName(
|
||||
$fq_class_name,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine(),
|
||||
new CodeLocation($statements_checker->getSource(), $stmt),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
);
|
||||
|
||||
@ -446,8 +441,7 @@ class CallChecker
|
||||
|
||||
$does_method_exist = MethodChecker::checkMethodExists(
|
||||
$cased_method_id,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine(),
|
||||
new CodeLocation($statements_checker->getSource(), $stmt),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
);
|
||||
|
||||
@ -462,7 +456,7 @@ class CallChecker
|
||||
$method_id,
|
||||
$context->self,
|
||||
$statements_checker->getSource(),
|
||||
$stmt->getLine(),
|
||||
new CodeLocation($statements_checker->getSource(), $stmt),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
) === false) {
|
||||
return false;
|
||||
@ -470,8 +464,7 @@ class CallChecker
|
||||
|
||||
if (MethodChecker::checkMethodNotDeprecated(
|
||||
$method_id,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine(),
|
||||
new CodeLocation($statements_checker->getSource(), $stmt),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
) === false) {
|
||||
return false;
|
||||
@ -507,7 +500,7 @@ class CallChecker
|
||||
$stmt->args,
|
||||
$method_id,
|
||||
$context,
|
||||
$stmt->getLine(),
|
||||
new CodeLocation($statements_checker->getSource(), $stmt),
|
||||
$has_mock
|
||||
) === false) {
|
||||
return false;
|
||||
@ -551,8 +544,7 @@ class CallChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new ParentNotFound(
|
||||
'Cannot call method on parent as this class does not extend another',
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -585,8 +577,7 @@ class CallChecker
|
||||
|
||||
$does_class_exist = ClassLikeChecker::checkFullyQualifiedClassLikeName(
|
||||
$fq_class_name,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine(),
|
||||
new CodeLocation($statements_checker->getSource(), $stmt),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
);
|
||||
|
||||
@ -637,8 +628,7 @@ class CallChecker
|
||||
|
||||
$does_method_exist = MethodChecker::checkMethodExists(
|
||||
$cased_method_id,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine(),
|
||||
new CodeLocation($statements_checker->getSource(), $stmt),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
);
|
||||
|
||||
@ -650,7 +640,7 @@ class CallChecker
|
||||
$method_id,
|
||||
$context->self,
|
||||
$statements_checker->getSource(),
|
||||
$stmt->getLine(),
|
||||
new CodeLocation($statements_checker->getSource(), $stmt),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
) === false) {
|
||||
return false;
|
||||
@ -663,8 +653,7 @@ class CallChecker
|
||||
) {
|
||||
if (MethodChecker::checkMethodStatic(
|
||||
$method_id,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine(),
|
||||
new CodeLocation($statements_checker->getSource(), $stmt),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
) === false) {
|
||||
return false;
|
||||
@ -673,8 +662,7 @@ class CallChecker
|
||||
|
||||
if (MethodChecker::checkMethodNotDeprecated(
|
||||
$method_id,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine(),
|
||||
new CodeLocation($statements_checker->getSource(), $stmt),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
) === false) {
|
||||
return false;
|
||||
@ -705,7 +693,7 @@ class CallChecker
|
||||
$stmt->args,
|
||||
$method_id,
|
||||
$context,
|
||||
$stmt->getLine(),
|
||||
new CodeLocation($statements_checker->getSource(), $stmt),
|
||||
$has_mock
|
||||
) === false) {
|
||||
return false;
|
||||
@ -720,7 +708,7 @@ class CallChecker
|
||||
* @param array<int, PhpParser\Node\Arg> $args
|
||||
* @param string|null $method_id
|
||||
* @param Context $context
|
||||
* @param int $line_number
|
||||
* @param CodeLocation $code_location
|
||||
* @param boolean $is_mock
|
||||
* @return false|null
|
||||
*/
|
||||
@ -729,7 +717,7 @@ class CallChecker
|
||||
array $args,
|
||||
$method_id,
|
||||
Context $context,
|
||||
$line_number,
|
||||
CodeLocation $code_location,
|
||||
$is_mock = false
|
||||
) {
|
||||
$function_params = null;
|
||||
@ -879,7 +867,7 @@ class CallChecker
|
||||
),
|
||||
$cased_method_id,
|
||||
$argument_offset,
|
||||
$arg->value->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $arg->value)
|
||||
) === false) {
|
||||
return false;
|
||||
}
|
||||
@ -924,8 +912,7 @@ class CallChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new TooManyArguments(
|
||||
'Too many arguments in closure for ' . ($cased_method_id ?: $method_id),
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$closure_arg->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $closure_arg)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -935,8 +922,7 @@ class CallChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new TooFewArguments(
|
||||
'You must supply a param in the closure for ' . ($cased_method_id ?: $method_id),
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$closure_arg->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $closure_arg)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -954,9 +940,7 @@ class CallChecker
|
||||
|
||||
$translated_param = FunctionLikeChecker::getTranslatedParam(
|
||||
$closure_param,
|
||||
$statements_checker->getFQCLN(),
|
||||
$statements_checker->getNamespace(),
|
||||
$statements_checker->getAliasedClasses()
|
||||
$statements_checker->getSource()
|
||||
);
|
||||
|
||||
$param_type = $translated_param->type;
|
||||
@ -978,8 +962,7 @@ class CallChecker
|
||||
new TypeCoercion(
|
||||
'First parameter of closure passed to function ' . $cased_method_id . ' expects ' .
|
||||
$param_type . ', parent type ' . $input_type . ' provided',
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$closure_param->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $closure_param)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -993,8 +976,7 @@ class CallChecker
|
||||
new InvalidScalarArgument(
|
||||
'First parameter of closure passed to function ' . $cased_method_id . ' expects ' .
|
||||
$param_type . ', ' . $input_type . ' provided',
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$closure_param->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $closure_param)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -1004,8 +986,7 @@ class CallChecker
|
||||
new InvalidArgument(
|
||||
'First parameter of closure passed to function ' . $cased_method_id . ' expects ' .
|
||||
$param_type . ', ' . $input_type . ' provided',
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$closure_param->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $closure_param)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -1024,8 +1005,7 @@ class CallChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new TooManyArguments(
|
||||
'Too many arguments for method ' . ($cased_method_id ?: $method_id),
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$line_number
|
||||
$code_location
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -1043,8 +1023,7 @@ class CallChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new TooFewArguments(
|
||||
'Too few arguments for method ' . $cased_method_id,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$line_number
|
||||
$code_location
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -1066,7 +1045,7 @@ class CallChecker
|
||||
* @param Type\Union $param_type
|
||||
* @param string $cased_method_id
|
||||
* @param int $argument_offset
|
||||
* @param int $line_number
|
||||
* @param CodeLocation $code_location
|
||||
* @return null|false
|
||||
*/
|
||||
protected static function checkFunctionArgumentType(
|
||||
@ -1075,7 +1054,7 @@ class CallChecker
|
||||
Type\Union $param_type,
|
||||
$cased_method_id,
|
||||
$argument_offset,
|
||||
$line_number
|
||||
CodeLocation $code_location
|
||||
) {
|
||||
if ($param_type->isMixed()) {
|
||||
return null;
|
||||
@ -1086,8 +1065,7 @@ class CallChecker
|
||||
new MixedArgument(
|
||||
'Argument ' . ($argument_offset + 1) . ' of ' . $cased_method_id . ' cannot be mixed, expecting ' .
|
||||
$param_type,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$line_number
|
||||
$code_location
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -1102,8 +1080,7 @@ class CallChecker
|
||||
new NullReference(
|
||||
'Argument ' . ($argument_offset + 1) . ' of ' . $cased_method_id . ' cannot be null, possibly ' .
|
||||
'null value provided',
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$line_number
|
||||
$code_location
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -1123,8 +1100,7 @@ class CallChecker
|
||||
new TypeCoercion(
|
||||
'Argument ' . ($argument_offset + 1) . ' of ' . $cased_method_id . ' expects ' . $param_type .
|
||||
', parent type ' . $input_type . ' provided',
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$line_number
|
||||
$code_location
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -1138,8 +1114,7 @@ class CallChecker
|
||||
new InvalidScalarArgument(
|
||||
'Argument ' . ($argument_offset + 1) . ' of ' . $cased_method_id . ' expects ' . $param_type .
|
||||
', ' . $input_type . ' provided',
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$line_number
|
||||
$code_location
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -1149,8 +1124,7 @@ class CallChecker
|
||||
new InvalidArgument(
|
||||
'Argument ' . ($argument_offset + 1) . ' of ' . $cased_method_id . ' expects ' . $param_type .
|
||||
', ' . $input_type . ' provided',
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$line_number
|
||||
$code_location
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -1165,14 +1139,14 @@ class CallChecker
|
||||
* @param StatementsChecker $statements_checker
|
||||
* @param string $function_id
|
||||
* @param Context $context
|
||||
* @param int $line_number
|
||||
* @param CodeLocation $code_location
|
||||
* @return bool
|
||||
*/
|
||||
protected static function checkFunctionExists(
|
||||
StatementsChecker $statements_checker,
|
||||
$function_id,
|
||||
Context $context,
|
||||
$line_number
|
||||
CodeLocation $code_location
|
||||
) {
|
||||
$cased_function_id = $function_id;
|
||||
$function_id = strtolower($function_id);
|
||||
@ -1181,8 +1155,7 @@ class CallChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new UndefinedFunction(
|
||||
'Function ' . $cased_function_id . ' does not exist',
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$line_number
|
||||
$code_location
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
|
@ -9,6 +9,7 @@ use Psalm\Checker\MethodChecker;
|
||||
use Psalm\Checker\StatementsChecker;
|
||||
use Psalm\Checker\Statements\ExpressionChecker;
|
||||
use Psalm\Checker\TraitChecker;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\Context;
|
||||
use Psalm\Issue\InvalidArrayAccess;
|
||||
use Psalm\Issue\InvalidArrayAssignment;
|
||||
@ -96,8 +97,7 @@ class FetchChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new NullPropertyFetch(
|
||||
'Cannot get property on null variable ' . $stmt_var_id,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -111,8 +111,7 @@ class FetchChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new MixedPropertyFetch(
|
||||
'Cannot fetch property on empty var ' . $stmt_var_id,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -126,8 +125,7 @@ class FetchChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new MixedPropertyFetch(
|
||||
'Cannot fetch property on mixed var ' . $stmt_var_id,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -141,8 +139,7 @@ class FetchChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new NullPropertyFetch(
|
||||
'Cannot get property on possibly null variable ' . $stmt_var_id,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -172,8 +169,7 @@ class FetchChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidPropertyFetch(
|
||||
'Cannot fetch property on non-object ' . $stmt_var_id . ' of type ' . $lhs_type_part,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -203,8 +199,7 @@ class FetchChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new NoInterfaceProperties(
|
||||
'Interfaces cannot have properties',
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -217,8 +212,7 @@ class FetchChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new UndefinedClass(
|
||||
'Cannot get properties of undefined class ' . $lhs_type_part->value,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -258,8 +252,7 @@ class FetchChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new UndefinedThisPropertyFetch(
|
||||
'Instance property ' . $lhs_type_part->value .'::$' . $stmt->name . ' is not defined',
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -269,8 +262,7 @@ class FetchChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new UndefinedPropertyFetch(
|
||||
'Instance property ' . $lhs_type_part->value .'::$' . $stmt->name . ' is not defined',
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -287,8 +279,7 @@ class FetchChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new MissingPropertyType(
|
||||
'Property ' . $lhs_type_part->value . '::$' . $stmt->name . ' does not have a declared type',
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -350,8 +341,7 @@ class FetchChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new UndefinedConstant(
|
||||
'Const ' . $const_name . ' is not defined',
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -389,8 +379,7 @@ class FetchChecker
|
||||
|
||||
if (ClassLikeChecker::checkFullyQualifiedClassLikeName(
|
||||
$fq_class_name,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine(),
|
||||
new CodeLocation($statements_checker->getSource(), $stmt),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
) === false) {
|
||||
return false;
|
||||
@ -410,8 +399,7 @@ class FetchChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new UndefinedConstant(
|
||||
'Const ' . $const_id . ' is not defined',
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -463,8 +451,7 @@ class FetchChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new ParentNotFound(
|
||||
'Cannot check property fetch on parent as this class does not extend another',
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -495,8 +482,7 @@ class FetchChecker
|
||||
|
||||
if (ClassLikeChecker::checkFullyQualifiedClassLikeName(
|
||||
$fq_class_name,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine(),
|
||||
new CodeLocation($statements_checker->getSource(), $stmt),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
) === false) {
|
||||
return false;
|
||||
@ -556,16 +542,14 @@ class FetchChecker
|
||||
IssueBuffer::add(
|
||||
new InvisibleProperty(
|
||||
'Static property ' . $var_id . ' is not visible in this context',
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
IssueBuffer::add(
|
||||
new UndefinedPropertyFetch(
|
||||
'Static property ' . $var_id . ' does not exist',
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -683,8 +667,7 @@ class FetchChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidArrayAssignment(
|
||||
'Cannot assign value on variable ' . $var_id . ' of scalar type ' . $type->value,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -701,7 +684,7 @@ class FetchChecker
|
||||
$assignment_key_type,
|
||||
$assignment_value_type,
|
||||
$var_id,
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
);
|
||||
|
||||
if ($refined_type === false) {
|
||||
@ -888,8 +871,7 @@ class FetchChecker
|
||||
new InvalidArrayAccess(
|
||||
'Cannot access value on array variable ' . $var_id . ' using int offset - ' .
|
||||
'expecting ' . $expected_keys_string,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -909,8 +891,7 @@ class FetchChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new NullArrayAccess(
|
||||
'Cannot access array value on possibly null variable ' . $array_var_id . ' of type ' . $var_type,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -926,8 +907,7 @@ class FetchChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new MixedArrayAccess(
|
||||
'Cannot access array value on mixed variable ' . $array_var_id,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -940,8 +920,7 @@ class FetchChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidArrayAccess(
|
||||
'Cannot access array value on non-array variable ' . $array_var_id . ' of type ' . $var_type,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -975,8 +954,7 @@ class FetchChecker
|
||||
new MixedArrayOffset(
|
||||
'Cannot access value on variable ' . $var_id . ' using mixed offset - expecting ' .
|
||||
$key_type,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -987,8 +965,7 @@ class FetchChecker
|
||||
new InvalidArrayAccess(
|
||||
'Cannot access value on variable ' . $var_id . ' using ' . $at . ' offset - ' .
|
||||
'expecting ' . $key_type,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -1008,7 +985,7 @@ class FetchChecker
|
||||
* @param Type\Union $assignment_key_type
|
||||
* @param Type\Union $assignment_value_type
|
||||
* @param string|null $var_id
|
||||
* @param int $line_number
|
||||
* @param CodeLocation $code_location
|
||||
* @return Type\Atomic|null|false
|
||||
*/
|
||||
protected static function refineArrayType(
|
||||
@ -1017,14 +994,13 @@ class FetchChecker
|
||||
Type\Union $assignment_key_type,
|
||||
Type\Union $assignment_value_type,
|
||||
$var_id,
|
||||
$line_number
|
||||
CodeLocation $code_location
|
||||
) {
|
||||
if ($type->value === 'null') {
|
||||
if (IssueBuffer::accepts(
|
||||
new NullReference(
|
||||
'Cannot assign value on possibly null array' . ($var_id ? ' ' . $var_id : ''),
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$line_number
|
||||
$code_location
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -1043,8 +1019,7 @@ class FetchChecker
|
||||
new InvalidArrayAssignment(
|
||||
'Cannot assign value on variable' . ($var_id ? ' ' . $var_id : '') . ' of type ' . $type->value . ' that does not ' .
|
||||
'implement ArrayAccess',
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$line_number
|
||||
$code_location
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
|
@ -12,6 +12,7 @@ use Psalm\Checker\Statements\Expression\CallChecker;
|
||||
use Psalm\Checker\Statements\Expression\FetchChecker;
|
||||
use Psalm\Checker\StatementsChecker;
|
||||
use Psalm\Checker\TypeChecker;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\Config;
|
||||
use Psalm\Context;
|
||||
use Psalm\Issue\ForbiddenCode;
|
||||
@ -309,8 +310,7 @@ class ExpressionChecker
|
||||
|
||||
if (ClassLikeChecker::checkFullyQualifiedClassLikeName(
|
||||
$fq_class_name,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine(),
|
||||
new CodeLocation($statements_checker->getSource(), $stmt->class),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
) === false) {
|
||||
return false;
|
||||
@ -354,7 +354,10 @@ class ExpressionChecker
|
||||
// do nothing
|
||||
} elseif ($stmt instanceof PhpParser\Node\Expr\ShellExec) {
|
||||
if (IssueBuffer::accepts(
|
||||
new ForbiddenCode('Use of shell_exec', $statements_checker->getCheckedFileName(), $stmt->getLine()),
|
||||
new ForbiddenCode(
|
||||
'Use of shell_exec',
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
return false;
|
||||
@ -371,8 +374,7 @@ class ExpressionChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new UnrecognizedExpression(
|
||||
'Psalm does not understand ' . get_class($stmt),
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -380,14 +382,20 @@ class ExpressionChecker
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Config::getInstance()->getPlugins() as $plugin) {
|
||||
if ($plugin->checkExpression(
|
||||
$stmt,
|
||||
$context,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
) === false) {
|
||||
return false;
|
||||
$plugins = Config::getInstance()->getPlugins();
|
||||
|
||||
if ($plugins) {
|
||||
$code_location = new CodeLocation($statements_checker->getSource(), $stmt);
|
||||
|
||||
foreach ($plugins as $plugin) {
|
||||
if ($plugin->checkExpression(
|
||||
$stmt,
|
||||
$context,
|
||||
$code_location,
|
||||
$statements_checker->getSuppressedIssues()
|
||||
) === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -416,8 +424,7 @@ class ExpressionChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidStaticVariable(
|
||||
'Invalid reference to $this in a static context',
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -475,8 +482,7 @@ class ExpressionChecker
|
||||
IssueBuffer::add(
|
||||
new UndefinedVariable(
|
||||
'Cannot find referenced variable ' . $var_name,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
)
|
||||
);
|
||||
|
||||
@ -489,8 +495,7 @@ class ExpressionChecker
|
||||
new PossiblyUndefinedVariable(
|
||||
'Possibly undefined variable ' . $var_name .', first seen on line ' .
|
||||
$statements_checker->getFirstAppearance($var_name),
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $stmt)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -647,8 +652,7 @@ class ExpressionChecker
|
||||
$op_vars_in_scope = TypeChecker::reconcileKeyedTypes(
|
||||
$left_type_assertions,
|
||||
$context->vars_in_scope,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine(),
|
||||
new CodeLocation($statements_checker->getSource(), $stmt),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
);
|
||||
|
||||
@ -695,8 +699,7 @@ class ExpressionChecker
|
||||
$op_vars_in_scope = TypeChecker::reconcileKeyedTypes(
|
||||
$negated_type_assertions,
|
||||
$context->vars_in_scope,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine(),
|
||||
new CodeLocation($statements_checker->getSource(), $stmt),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
);
|
||||
|
||||
@ -745,10 +748,8 @@ class ExpressionChecker
|
||||
|
||||
// let's do some fun type assignment
|
||||
if (isset($stmt->left->inferredType) && isset($stmt->right->inferredType)) {
|
||||
if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Plus) {
|
||||
if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Plus || $stmt instanceof PhpParser\Node\Expr\BinaryOp\Minus) {
|
||||
self::checkPlusOp(
|
||||
$statements_checker,
|
||||
$stmt->getLine(),
|
||||
$stmt->left->inferredType,
|
||||
$stmt->right->inferredType,
|
||||
$result_type
|
||||
@ -758,9 +759,7 @@ class ExpressionChecker
|
||||
$stmt->inferredType = $result_type;
|
||||
}
|
||||
|
||||
} elseif ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Mul
|
||||
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\Minus
|
||||
) {
|
||||
} elseif ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Mul) {
|
||||
if ($stmt->left->inferredType->isInt() && $stmt->right->inferredType->isInt()) {
|
||||
$stmt->inferredType = Type::getInt();
|
||||
} elseif ($stmt->left->inferredType->hasNumericType() &&
|
||||
@ -798,16 +797,12 @@ class ExpressionChecker
|
||||
}
|
||||
|
||||
/**
|
||||
* @param StatementsChecker $statements_checker
|
||||
* @param int $line_number
|
||||
* @param Type\Union|null $left_type
|
||||
* @param Type\Union|null $right_type
|
||||
* @param Type\Union|null &$result_type
|
||||
* @return void
|
||||
*/
|
||||
public static function checkPlusOp(
|
||||
StatementsChecker $statements_checker,
|
||||
$line_number,
|
||||
Type\Union $left_type = null,
|
||||
Type\Union $right_type = null,
|
||||
Type\Union &$result_type = null
|
||||
@ -1021,8 +1016,7 @@ class ExpressionChecker
|
||||
IssueBuffer::add(
|
||||
new UndefinedVariable(
|
||||
'Cannot find referenced variable $' . $use->var,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$use->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $use)
|
||||
)
|
||||
);
|
||||
|
||||
@ -1035,8 +1029,7 @@ class ExpressionChecker
|
||||
new PossiblyUndefinedVariable(
|
||||
'Possibly undefined variable $' . $use->var . ', first seen on line ' .
|
||||
$statements_checker->getFirstAppearance('$' . $use->var),
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$use->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $use)
|
||||
),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
)) {
|
||||
@ -1050,8 +1043,7 @@ class ExpressionChecker
|
||||
IssueBuffer::add(
|
||||
new UndefinedVariable(
|
||||
'Cannot find referenced variable $' . $use->var,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$use->getLine()
|
||||
new CodeLocation($statements_checker->getSource(), $use)
|
||||
)
|
||||
);
|
||||
|
||||
@ -1172,8 +1164,7 @@ class ExpressionChecker
|
||||
$t_if_vars_in_scope_reconciled = TypeChecker::reconcileKeyedTypes(
|
||||
$reconcilable_if_types,
|
||||
$t_if_context->vars_in_scope,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine(),
|
||||
new CodeLocation($statements_checker->getSource(), $stmt),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
);
|
||||
|
||||
@ -1197,8 +1188,7 @@ class ExpressionChecker
|
||||
$t_else_vars_in_scope_reconciled = TypeChecker::reconcileKeyedTypes(
|
||||
$negated_if_types,
|
||||
$t_else_context->vars_in_scope,
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine(),
|
||||
new CodeLocation($statements_checker->getSource(), $stmt),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
);
|
||||
|
||||
@ -1225,8 +1215,7 @@ class ExpressionChecker
|
||||
'!empty',
|
||||
$stmt->cond->inferredType,
|
||||
'',
|
||||
$statements_checker->getCheckedFileName(),
|
||||
$stmt->getLine(),
|
||||
new CodeLocation($statements_checker->getSource(), $stmt),
|
||||
$statements_checker->getSuppressedIssues()
|
||||
);
|
||||
|
||||
|
@ -10,6 +10,7 @@ use Psalm\Checker\Statements\Block\TryChecker;
|
||||
use Psalm\Checker\Statements\Block\WhileChecker;
|
||||
use Psalm\Checker\Statements\ExpressionChecker;
|
||||
use Psalm\Checker\Statements\Expression\AssignmentChecker;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\Config;
|
||||
use Psalm\Context;
|
||||
use Psalm\Issue\ContinueOutsideLoop;
|
||||
@ -72,16 +73,31 @@ class StatementsChecker
|
||||
*/
|
||||
protected $file_name;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $file_path;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $checked_file_name;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $checked_file_path;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
protected $include_file_name;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
protected $include_file_path;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
@ -116,7 +132,9 @@ class StatementsChecker
|
||||
{
|
||||
$this->source = $source;
|
||||
$this->file_name = $this->source->getFileName();
|
||||
$this->file_path = $this->source->getFilePath();
|
||||
$this->checked_file_name = $this->source->getCheckedFileName();
|
||||
$this->checked_file_path = $this->source->getCheckedFilePath();
|
||||
$this->namespace = $this->source->getNamespace();
|
||||
$this->is_static = $this->source->isStatic();
|
||||
$this->fq_class_name = $this->source->getFQCLN();
|
||||
@ -157,16 +175,23 @@ class StatementsChecker
|
||||
}
|
||||
|
||||
foreach ($stmts as $stmt) {
|
||||
foreach (Config::getInstance()->getPlugins() as $plugin) {
|
||||
if ($plugin->checkStatement(
|
||||
$stmt,
|
||||
$context,
|
||||
$this->checked_file_name,
|
||||
$this->getSuppressedIssues()
|
||||
) === false) {
|
||||
return false;
|
||||
$plugins = Config::getInstance()->getPlugins();
|
||||
|
||||
if ($plugins) {
|
||||
$code_location = new CodeLocation($this->source, $stmt);
|
||||
|
||||
foreach ($plugins as $plugin) {
|
||||
if ($plugin->checkStatement(
|
||||
$stmt,
|
||||
$context,
|
||||
$code_location,
|
||||
$this->getSuppressedIssues()
|
||||
) === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ($has_returned && !($stmt instanceof PhpParser\Node\Stmt\Nop) &&
|
||||
!($stmt instanceof PhpParser\Node\Stmt\InlineHTML)) {
|
||||
@ -223,8 +248,7 @@ class StatementsChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new ContinueOutsideLoop(
|
||||
'Continue call outside loop context',
|
||||
$this->checked_file_name,
|
||||
$stmt->getLine()
|
||||
new CodeLocation($this->source, $stmt)
|
||||
),
|
||||
$this->suppressed_issues
|
||||
)) {
|
||||
@ -291,15 +315,13 @@ class StatementsChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidGlobal(
|
||||
'Cannot use global scope here',
|
||||
$this->checked_file_name,
|
||||
$stmt->getLine()
|
||||
new CodeLocation($this->source, $stmt)
|
||||
),
|
||||
$this->suppressed_issues
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
foreach ($stmt->vars as $var) {
|
||||
if ($var instanceof PhpParser\Node\Expr\Variable) {
|
||||
if (is_string($var->name)) {
|
||||
@ -361,8 +383,7 @@ class StatementsChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidNamespace(
|
||||
'Cannot redeclare namespace',
|
||||
$this->checked_file_name,
|
||||
$stmt->getLine()
|
||||
new CodeLocation($this->source, $stmt)
|
||||
),
|
||||
$this->suppressed_issues
|
||||
)) {
|
||||
@ -378,8 +399,7 @@ class StatementsChecker
|
||||
if (IssueBuffer::accepts(
|
||||
new UnrecognizedStatement(
|
||||
'Psalm does not understand ' . get_class($stmt),
|
||||
$this->getCheckedFileName(),
|
||||
$stmt->getLine()
|
||||
new CodeLocation($this->source, $stmt)
|
||||
),
|
||||
$this->getSuppressedIssues()
|
||||
)) {
|
||||
@ -713,11 +733,14 @@ class StatementsChecker
|
||||
if (file_exists($path_to_file)) {
|
||||
$include_stmts = FileChecker::getStatementsForFile($path_to_file);
|
||||
$old_include_file_name = $this->include_file_name;
|
||||
$this->include_file_name = Config::getInstance()->shortenFileName($path_to_file);
|
||||
$this->source->setIncludeFileName($this->include_file_name);
|
||||
$old_include_file_path = $this->include_file_path;
|
||||
$this->include_file_path = $path_to_file;
|
||||
$this->include_file_name = Config::getInstance()->shortenFileName($this->include_file_path);
|
||||
$this->source->setIncludeFileName($this->include_file_name, $this->include_file_path);
|
||||
$this->check($include_stmts, $context);
|
||||
$this->include_file_name = $old_include_file_name;
|
||||
$this->source->setIncludeFileName($old_include_file_name);
|
||||
$this->include_file_path = $old_include_file_path;
|
||||
$this->source->setIncludeFileName($old_include_file_name, $old_include_file_path);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -865,6 +888,14 @@ class StatementsChecker
|
||||
return $this->checked_file_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getCheckedFilePath()
|
||||
{
|
||||
return $this->checked_file_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
@ -873,6 +904,14 @@ class StatementsChecker
|
||||
return $this->file_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getFilePath()
|
||||
{
|
||||
return $this->file_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
|
@ -27,6 +27,7 @@ class TraitChecker extends ClassLikeChecker
|
||||
$this->namespace = $source->getNamespace();
|
||||
$this->aliased_classes = $source->getAliasedClasses();
|
||||
$this->file_name = $source->getFileName();
|
||||
$this->file_path = $source->getFilePath();
|
||||
$this->fq_class_name = $fq_class_name;
|
||||
|
||||
$this->parent_class = null;
|
||||
|
@ -3,6 +3,7 @@ namespace Psalm\Checker;
|
||||
|
||||
use PhpParser;
|
||||
use Psalm\Checker\Statements\ExpressionChecker;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\Issue\FailedTypeResolution;
|
||||
use Psalm\IssueBuffer;
|
||||
use Psalm\Type;
|
||||
@ -1049,16 +1050,14 @@ class TypeChecker
|
||||
*
|
||||
* @param array<string,string> $new_types
|
||||
* @param array<string,Type\Union> $existing_types
|
||||
* @param string $file_name
|
||||
* @param int $line_number
|
||||
* @param CodeLocation $code_location
|
||||
* @param array<string> $suppressed_issues
|
||||
* @return array|false
|
||||
*/
|
||||
public static function reconcileKeyedTypes(
|
||||
array $new_types,
|
||||
array $existing_types,
|
||||
$file_name,
|
||||
$line_number,
|
||||
CodeLocation $code_location,
|
||||
array $suppressed_issues = []
|
||||
) {
|
||||
$keys = array_merge(array_keys($new_types), array_keys($existing_types));
|
||||
@ -1087,8 +1086,7 @@ class TypeChecker
|
||||
(string) $new_type_part,
|
||||
$result_type,
|
||||
$key,
|
||||
$file_name,
|
||||
$line_number,
|
||||
$code_location,
|
||||
$suppressed_issues
|
||||
);
|
||||
|
||||
@ -1124,8 +1122,7 @@ class TypeChecker
|
||||
* @param string $new_var_type
|
||||
* @param Type\Union $existing_var_type
|
||||
* @param string $key
|
||||
* @param string $file_name
|
||||
* @param int $line_number
|
||||
* @param CodeLocation $code_location
|
||||
* @param array $suppressed_issues
|
||||
* @return Type\Union|null|false
|
||||
*/
|
||||
@ -1133,8 +1130,7 @@ class TypeChecker
|
||||
$new_var_type,
|
||||
Type\Union $existing_var_type = null,
|
||||
$key = null,
|
||||
$file_name = null,
|
||||
$line_number = null,
|
||||
CodeLocation $code_location = null,
|
||||
array $suppressed_issues = []
|
||||
) {
|
||||
$result_var_types = null;
|
||||
@ -1191,9 +1187,9 @@ class TypeChecker
|
||||
$existing_var_type->removeType($negated_type);
|
||||
|
||||
if (empty($existing_var_type->types)) {
|
||||
if ($key && $file_name && $line_number) {
|
||||
if ($key && $code_location) {
|
||||
if (IssueBuffer::accepts(
|
||||
new FailedTypeResolution('Cannot resolve types for ' . $key, $file_name, $line_number),
|
||||
new FailedTypeResolution('Cannot resolve types for ' . $key, $code_location),
|
||||
$suppressed_issues
|
||||
)) {
|
||||
return false;
|
||||
|
55
src/Psalm/CodeLocation.php
Normal file
55
src/Psalm/CodeLocation.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
namespace Psalm;
|
||||
|
||||
class CodeLocation
|
||||
{
|
||||
/** @var int */
|
||||
public $file_start;
|
||||
|
||||
/** @var int */
|
||||
public $file_end;
|
||||
|
||||
/** @var string */
|
||||
public $file_path;
|
||||
|
||||
/** @var string */
|
||||
public $file_name;
|
||||
|
||||
/** @var int */
|
||||
public $line_number;
|
||||
|
||||
/** @var bool */
|
||||
public $single_line;
|
||||
|
||||
/** @var int */
|
||||
public $preview_start;
|
||||
|
||||
/** @var int|null */
|
||||
public $comment_line_number;
|
||||
|
||||
/**
|
||||
* @param StatementsSource $statements_source
|
||||
* @param \PhpParser\Node $stmt
|
||||
* @param boolean $single_line
|
||||
*/
|
||||
public function __construct(StatementsSource $statements_source, \PhpParser\Node $stmt, $single_line = false)
|
||||
{
|
||||
$this->file_start = (int)$stmt->getAttribute('startFilePos');
|
||||
$this->file_end = (int)$stmt->getAttribute('endFilePos');
|
||||
$this->file_path = $statements_source->getCheckedFilePath();
|
||||
$this->file_name = $statements_source->getCheckedFileName();
|
||||
$this->single_line = $single_line;
|
||||
|
||||
$doc_comment = $stmt->getDocComment();
|
||||
$this->preview_start = $doc_comment ? $doc_comment->getFilePos() : $this->file_start;
|
||||
$this->line_number = $doc_comment ? $doc_comment->getLine() : $stmt->getLine();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $line
|
||||
* @return void
|
||||
*/
|
||||
public function setCommentLine($line) {
|
||||
$this->comment_line_number = $line;
|
||||
}
|
||||
}
|
@ -30,4 +30,7 @@ class FunctionDocblockComment
|
||||
* @var array<string>
|
||||
*/
|
||||
public $suppress = [];
|
||||
|
||||
/** @var int */
|
||||
public $return_type_line_number;
|
||||
}
|
||||
|
@ -34,9 +34,9 @@ class FunctionLikeParameter
|
||||
public $is_nullable;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
* @var CodeLocation|null
|
||||
*/
|
||||
public $line;
|
||||
public $code_location;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
@ -47,6 +47,7 @@ class FunctionLikeParameter
|
||||
* @param string $name
|
||||
* @param boolean $by_ref
|
||||
* @param Type\Union $type
|
||||
* @param CodeLocation $code_location
|
||||
* @param boolean $is_optional
|
||||
* @param boolean $is_nullable
|
||||
* @param boolean $is_variadic
|
||||
@ -55,6 +56,7 @@ class FunctionLikeParameter
|
||||
$name,
|
||||
$by_ref,
|
||||
Type\Union $type,
|
||||
CodeLocation $code_location = null,
|
||||
$is_optional = true,
|
||||
$is_nullable = false,
|
||||
$is_variadic = false
|
||||
@ -66,5 +68,6 @@ class FunctionLikeParameter
|
||||
$this->is_optional = $is_optional;
|
||||
$this->is_nullable = $is_nullable;
|
||||
$this->is_variadic = $is_variadic;
|
||||
$this->code_location = $code_location;
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,16 @@
|
||||
<?php
|
||||
namespace Psalm\Issue;
|
||||
|
||||
use Psalm\CodeLocation;
|
||||
|
||||
abstract class CodeIssue
|
||||
{
|
||||
const CODE_EXCEPTION = 1;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @var CodeLocation
|
||||
*/
|
||||
protected $file_name;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $line_number;
|
||||
protected $code_location;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
@ -21,14 +18,12 @@ abstract class CodeIssue
|
||||
protected $message;
|
||||
|
||||
/**
|
||||
* @param string $message
|
||||
* @param string $file_name
|
||||
* @param int $line_number
|
||||
* @param string $message
|
||||
* @param CodeLocation $code_location
|
||||
*/
|
||||
public function __construct($message, $file_name, $line_number)
|
||||
public function __construct($message, CodeLocation $code_location)
|
||||
{
|
||||
$this->line_number = $line_number;
|
||||
$this->file_name = $file_name;
|
||||
$this->code_location = $code_location;
|
||||
$this->message = $message;
|
||||
}
|
||||
|
||||
@ -37,7 +32,23 @@ abstract class CodeIssue
|
||||
*/
|
||||
public function getLineNumber()
|
||||
{
|
||||
return $this->line_number;
|
||||
return $this->code_location->line_number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int[]
|
||||
*/
|
||||
public function getFileRange()
|
||||
{
|
||||
return [$this->code_location->file_start, $this->code_location->file_end];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getFilePath()
|
||||
{
|
||||
return $this->code_location->file_path;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -45,7 +56,7 @@ abstract class CodeIssue
|
||||
*/
|
||||
public function getFileName()
|
||||
{
|
||||
return $this->file_name;
|
||||
return $this->code_location->file_name;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -53,6 +64,48 @@ abstract class CodeIssue
|
||||
*/
|
||||
public function getMessage()
|
||||
{
|
||||
return $this->file_name . ':' . $this->line_number .' - ' . $this->message;
|
||||
return $this->code_location->file_name . ':' . $this->code_location->line_number .' - ' . $this->message;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getFileSnippet()
|
||||
{
|
||||
$file_start = $this->code_location->file_start;
|
||||
$file_end = $this->code_location->file_end;
|
||||
$preview_start = $this->code_location->preview_start;
|
||||
|
||||
$file_contents = (string)file_get_contents($this->code_location->file_path);
|
||||
|
||||
if ($this->code_location->comment_line_number && $preview_start < $file_start) {
|
||||
$preview_lines = explode("\n", substr($file_contents, $preview_start, $file_start - $preview_start - 1));
|
||||
|
||||
$preview_offset = 0;
|
||||
|
||||
$i = 0;
|
||||
|
||||
while ($i < $this->code_location->comment_line_number - $this->code_location->line_number) {
|
||||
$preview_offset += strlen($preview_lines[$i++]) + 1;
|
||||
}
|
||||
|
||||
$preview_offset += (int)strpos($preview_lines[$i], '@');
|
||||
|
||||
$file_start = $preview_offset + $preview_start;
|
||||
}
|
||||
|
||||
$line_beginning = (int)strrpos(
|
||||
$file_contents,
|
||||
"\n",
|
||||
min($preview_start, $file_start) - strlen($file_contents)
|
||||
) + 1;
|
||||
$line_end = (int)strpos($file_contents, "\n", $this->code_location->single_line ? $file_start : $file_end);
|
||||
|
||||
$code_line = substr($file_contents, $line_beginning, $line_end - $line_beginning);
|
||||
$code_line_error_start = $file_start - $line_beginning;
|
||||
$code_line_error_length = $file_end - $file_start + 1;
|
||||
return substr($code_line, 0, $code_line_error_start) .
|
||||
"\e[97;41m" . substr($code_line, $code_line_error_start, $code_line_error_length) .
|
||||
"\e[0m" . substr($code_line, $code_line_error_length + $code_line_error_start) . PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
@ -54,15 +54,15 @@ class IssueBuffer
|
||||
|
||||
$reporting_level = $config->getReportingLevel($issue_type);
|
||||
|
||||
switch ($reporting_level) {
|
||||
case Config::REPORT_INFO:
|
||||
if (ProjectChecker::$show_info && !self::alreadyEmitted($error_message)) {
|
||||
echo 'INFO: ' . $error_message . PHP_EOL;
|
||||
}
|
||||
return false;
|
||||
if ($reporting_level === Config::REPORT_SUPPRESS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
case Config::REPORT_SUPPRESS:
|
||||
return false;
|
||||
if ($reporting_level === Config::REPORT_INFO) {
|
||||
if (ProjectChecker::$show_info && !self::alreadyEmitted($error_message)) {
|
||||
echo 'INFO: ' . $error_message . PHP_EOL;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($config->throw_exception) {
|
||||
@ -70,8 +70,10 @@ class IssueBuffer
|
||||
}
|
||||
|
||||
if (!self::alreadyEmitted($error_message)) {
|
||||
echo (ProjectChecker::$use_color ? "\033[0;31m" : '') . 'ERROR: ' .
|
||||
(ProjectChecker::$use_color ? "\033[0m" : '') . $error_message . PHP_EOL;
|
||||
echo (ProjectChecker::$use_color ? "\e[0;31m" : '') . 'ERROR: ' .
|
||||
(ProjectChecker::$use_color ? "\e[0m" : '') . $error_message . PHP_EOL;
|
||||
|
||||
echo $e->getFileSnippet() . PHP_EOL;
|
||||
}
|
||||
|
||||
if ($config->stop_on_first_error) {
|
||||
|
@ -10,12 +10,12 @@ abstract class Plugin
|
||||
*
|
||||
* @param PhpParser\Node\Expr $stmt
|
||||
* @param Context $context
|
||||
* @param string $file_name
|
||||
* @param CodeLocation $code_location
|
||||
* @param array $suppressed_issues
|
||||
* @return null|false
|
||||
* @psalm-suppress InvalidReturnType
|
||||
*/
|
||||
public function checkExpression(PhpParser\Node\Expr $stmt, Context $context, $file_name, array $suppressed_issues)
|
||||
public function checkExpression(PhpParser\Node\Expr $stmt, Context $context, CodeLocation $code_location, array $suppressed_issues)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@ -25,12 +25,12 @@ abstract class Plugin
|
||||
*
|
||||
* @param PhpParser\Node $stmt
|
||||
* @param Context $context
|
||||
* @param string $file_name
|
||||
* @param CodeLocation $code_location
|
||||
* @param array $suppressed_issues
|
||||
* @return null|false
|
||||
* @psalm-suppress InvalidReturnType
|
||||
*/
|
||||
public function checkStatement(PhpParser\Node $stmt, Context $context, $file_name, array $suppressed_issues)
|
||||
public function checkStatement(PhpParser\Node $stmt, Context $context, CodeLocation $code_location, array $suppressed_issues)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
@ -57,20 +57,36 @@ interface StatementsSource
|
||||
*/
|
||||
public function getFileName();
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getFilePath();
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getIncludeFileName();
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getIncludeFilePath();
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getCheckedFileName();
|
||||
|
||||
/**
|
||||
* @param string|null $file_name
|
||||
* @return string
|
||||
*/
|
||||
public function setIncludeFileName($file_name);
|
||||
public function getCheckedFilePath();
|
||||
|
||||
/**
|
||||
* @param string|null $file_name
|
||||
* @param string|null $file_path
|
||||
*/
|
||||
public function setIncludeFileName($file_name, $file_path);
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
|
Loading…
Reference in New Issue
Block a user