1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-30 04:39:00 +01:00

Break FileScanner out from FileChecker

This commit is contained in:
Matthew Brown 2018-01-21 12:44:46 -05:00
parent f4a9306eee
commit e05a7c00cc
28 changed files with 512 additions and 458 deletions

View File

@ -54,7 +54,8 @@
<xs:element name="extension" maxOccurs="unbounded">
<xs:complexType>
<xs:attribute name="name" type="xs:string" use="required" />
<xs:attribute name="filetypeHandler" type="xs:string" />
<xs:attribute name="scanner" type="xs:string" />
<xs:attribute name="checker" type="xs:string" />
</xs:complexType>
</xs:element>
</xs:sequence>

View File

@ -16,47 +16,10 @@ class TemplateChecker extends Psalm\Checker\FileChecker
{
const VIEW_CLASS = 'Your\\View\\Class';
public function scan()
{
$stmts = $this->getStatements();
if (empty($stmts)) {
return;
}
$first_stmt = $stmts[0];
if (($first_stmt instanceof PhpParser\Node\Stmt\Nop) && ($doc_comment = $first_stmt->getDocComment())) {
$comment_block = CommentChecker::parseDocComment(trim($doc_comment->getText()));
if (isset($comment_block['specials']['variablesfrom'])) {
$variables_from = trim($comment_block['specials']['variablesfrom'][0]);
$first_line_regex = '/([A-Za-z\\\0-9]+::[a-z_A-Z]+)(\s+weak)?/';
$matches = [];
if (!preg_match($first_line_regex, $variables_from, $matches)) {
throw new \InvalidArgumentException('Could not interpret doc comment correctly');
}
/** @psalm-suppress MixedArgument */
list($fq_class_name, $method_name) = explode('::', $matches[1]);
$this->project_checker->queueClassLikeForScanning($fq_class_name, $this->file_path, true);
}
}
$this->project_checker->queueClassLikeForScanning(self::VIEW_CLASS, $this->file_path);
parent::scan();
}
public function analyze(Context $context = null, $update_docblocks = false)
{
$this->project_checker->enableCache();
$stmts = $this->getStatements();
$stmts = $this->project_checker->getStatementsForFile($this->file_path);
if (empty($stmts)) {
return;
@ -102,7 +65,7 @@ class TemplateChecker extends Psalm\Checker\FileChecker
]);
}
$this->checkWithViewClass($this_params);
$this->checkWithViewClass($this_params, $stmts);
$this->project_checker->disableCache();
}
@ -158,13 +121,12 @@ class TemplateChecker extends Psalm\Checker\FileChecker
/**
* @param Context $context
* @param array<PhpParser\Node\Stmt> $stmts
*
* @return void
*/
protected function checkWithViewClass(Context $context)
protected function checkWithViewClass(Context $context, array $stmts)
{
$stmts = $this->getStatements();
$pseudo_method_stmts = [];
foreach ($stmts as $stmt) {

View File

@ -0,0 +1,56 @@
<?php
namespace Psalm\Examples\Template;
use PhpParser;
use Psalm;
use Psalm\Checker\CommentChecker;
use Psalm\Checker\ProjectChecker;
use Psalm\Storage\FileStorage;
class TemplateScanner extends Psalm\Scanner\FileScanner
{
const VIEW_CLASS = 'Your\\View\\Class';
/**
* @param array<mixed, PhpParser\Node> $stmts
*
* @return void
*/
public function scan(ProjectChecker $project_checker, array $stmts, FileStorage $file_storage)
{
if (empty($stmts)) {
return;
}
$first_stmt = $stmts[0];
if (($first_stmt instanceof PhpParser\Node\Stmt\Nop) && ($doc_comment = $first_stmt->getDocComment())) {
$comment_block = CommentChecker::parseDocComment(trim($doc_comment->getText()));
if (isset($comment_block['specials']['variablesfrom'])) {
$variables_from = trim($comment_block['specials']['variablesfrom'][0]);
$first_line_regex = '/([A-Za-z\\\0-9]+::[a-z_A-Z]+)(\s+weak)?/';
$matches = [];
if (!preg_match($first_line_regex, $variables_from, $matches)) {
throw new \InvalidArgumentException('Could not interpret doc comment correctly');
}
/** @psalm-suppress MixedArgument */
list($fq_class_name, $method_name) = explode('::', $matches[1]);
$project_checker->queueClassLikeForScanning(
$fq_class_name,
$this->file_path,
true
);
}
}
$project_checker->queueClassLikeForScanning(self::VIEW_CLASS, $this->file_path);
parent::scan($project_checker, $stmts, $file_storage);
}
}

View File

@ -444,15 +444,17 @@ class ClassChecker extends ClassLikeChecker
continue;
}
if (!isset(self::$trait_checkers[strtolower($fq_trait_name)])) {
throw new \UnexpectedValueException(
'Expecting trait statements to exist for ' . $fq_trait_name
$trait_file_checker = $project_checker->getFileCheckerForClassLike($fq_trait_name);
$trait_node = $project_checker->getTraitNode($fq_trait_name);
$trait_aliases = $project_checker->getTraitAliases($fq_trait_name);
$trait_checker = new TraitChecker(
$trait_node,
$trait_file_checker,
$fq_trait_name,
$trait_aliases
);
}
$trait_checker = self::$trait_checkers[strtolower($fq_trait_name)];
foreach ($trait_checker->class->stmts as $trait_stmt) {
foreach ($trait_node->stmts as $trait_stmt) {
if ($trait_stmt instanceof PhpParser\Node\Stmt\ClassMethod) {
$trait_method_checker = $this->analyzeClassMethod(
$trait_stmt,
@ -664,15 +666,9 @@ class ClassChecker extends ClassLikeChecker
$this->source->getAliases()
);
if (!isset(self::$trait_checkers[strtolower($fq_trait_name)])) {
throw new \UnexpectedValueException(
'Expecting trait statements to exist for ' . $fq_trait_name
);
}
$trait_node = $project_checker->getTraitNode($fq_trait_name);
$trait_checker = self::$trait_checkers[strtolower($fq_trait_name)];
foreach ($trait_checker->class->stmts as $trait_stmt) {
foreach ($trait_node->stmts as $trait_stmt) {
if ($trait_stmt instanceof PhpParser\Node\Stmt\Property) {
$this->checkForMissingPropertyType($project_checker, $trait_stmt);
}

View File

@ -91,13 +91,6 @@ abstract class ClassLikeChecker extends SourceChecker implements StatementsSourc
*/
protected static $property_map;
/**
* A lookup table of cached TraitCheckers
*
* @var array<string, TraitChecker>
*/
public static $trait_checkers;
/**
* A lookup table of cached ClassCheckers
*
@ -173,8 +166,6 @@ abstract class ClassLikeChecker extends SourceChecker implements StatementsSourc
if ($stmt instanceof PhpParser\Node\Stmt\ClassMethod &&
strtolower($stmt->name) === strtolower($method_name)
) {
$project_checker = $this->getFileChecker()->project_checker;
$method_id = $this->fq_class_name . '::' . $stmt->name;
if ($project_checker->canCache() && isset($project_checker->method_checkers[$method_id])) {
@ -195,13 +186,15 @@ abstract class ClassLikeChecker extends SourceChecker implements StatementsSourc
$this->source->getAliases()
);
if (!isset(self::$trait_checkers[strtolower($fq_trait_name)])) {
throw new \UnexpectedValueException(
'Expecting trait statements to exist for ' . $fq_trait_name
$trait_file_checker = $project_checker->getFileCheckerForClassLike($fq_trait_name);
$trait_node = $project_checker->getTraitNode($fq_trait_name);
$trait_aliases = $project_checker->getTraitAliases($fq_trait_name);
$trait_checker = new TraitChecker(
$trait_node,
$trait_file_checker,
$fq_trait_name,
$trait_aliases
);
}
$trait_checker = self::$trait_checkers[strtolower($fq_trait_name)];
foreach ($trait_checker->class->stmts as $trait_stmt) {
if ($trait_stmt instanceof PhpParser\Node\Stmt\ClassMethod &&
@ -1113,9 +1106,6 @@ abstract class ClassLikeChecker extends SourceChecker implements StatementsSourc
public static function clearCache()
{
self::$file_classes = [];
self::$trait_checkers = [];
self::$class_checkers = [];
}
}

View File

@ -6,8 +6,8 @@ use Psalm\ClassLikeDocblockComment;
use Psalm\Exception\DocblockParseException;
use Psalm\Exception\IncorrectDocblockException;
use Psalm\Exception\TypeParseTreeException;
use Psalm\FileSource;
use Psalm\FunctionDocblockComment;
use Psalm\StatementsSource;
use Psalm\Type;
use Psalm\VarDocblockComment;
@ -17,7 +17,6 @@ class CommentChecker
/**
* @param string $comment
* @param StatementsSource $source
* @param Aliases $aliases
* @param array<string, string>|null $template_types
* @param int|null $var_line_number
@ -30,7 +29,7 @@ class CommentChecker
*/
public static function getTypeFromComment(
$comment,
StatementsSource $source,
FileSource $source,
Aliases $aliases,
array $template_types = null,
$var_line_number = null,

View File

@ -2,7 +2,6 @@
namespace Psalm\Checker;
use PhpParser;
use Psalm\Config;
use Psalm\Context;
use Psalm\FileManipulation\FileManipulationBuffer;
use Psalm\IssueBuffer;
@ -73,37 +72,16 @@ class FileChecker extends SourceChecker implements StatementsSource
*/
public $project_checker;
/**
* @var bool
*/
public $will_analyze;
/**
* @param string $file_path
* @param string $file_name
* @param ProjectChecker $project_checker
* @param bool $will_analyze
*/
public function __construct(
$file_path,
ProjectChecker $project_checker,
$will_analyze = true
) {
$this->file_path = $file_path;
$this->file_name = Config::getInstance()->shortenFileName($this->file_path);
$this->project_checker = $project_checker;
$this->will_analyze = $will_analyze;
}
/**
* @return void
*/
public function scan()
public function __construct(ProjectChecker $project_checker, $file_path, $file_name)
{
$stmts = $this->getStatements();
$traverser = new PhpParser\NodeTraverser();
$traverser->addVisitor(new \Psalm\Visitor\DependencyFinderVisitor($this->project_checker, $this));
$traverser->traverse($stmts);
$this->file_path = $file_path;
$this->file_name = $file_name;
$this->project_checker = $project_checker;
}
/**
@ -131,9 +109,9 @@ class FileChecker extends SourceChecker implements StatementsSource
$this->context->is_global = true;
$config = Config::getInstance();
$config = $this->project_checker->getConfig();
$stmts = $this->getStatements();
$stmts = $this->project_checker->getStatementsForFile($this->file_path);
$statements_checker = new StatementsChecker($this);
@ -171,8 +149,6 @@ class FileChecker extends SourceChecker implements StatementsSource
$class_checker->analyze(null, $this->context);
}
$config = Config::getInstance();
foreach ($this->function_checkers as $function_checker) {
$function_context = new Context($this->context->self);
$function_context->collect_references = $this->project_checker->collect_references;
@ -193,7 +169,7 @@ class FileChecker extends SourceChecker implements StatementsSource
$return_type_location = $function_storage->return_type_location;
$function_checker->verifyReturnType(
$statements_checker->getFileChecker()->project_checker,
$this->project_checker,
$return_type,
null,
$return_type_location
@ -356,14 +332,6 @@ class FileChecker extends SourceChecker implements StatementsSource
}
}
/**
* @return array<int, \PhpParser\Node\Stmt>
*/
public function getStatements()
{
return $this->project_checker->getStatementsForFile($this->file_path);
}
/**
* @return ?string
*/
@ -397,6 +365,8 @@ class FileChecker extends SourceChecker implements StatementsSource
IssueBuffer::clearCache();
FileManipulationBuffer::clearCache();
FunctionLikeChecker::clearCache();
\Psalm\Provider\ClassLikeStorageProvider::deleteAll();
\Psalm\Provider\FileStorageProvider::deleteAll();
}
/**

View File

@ -1,6 +1,8 @@
<?php
namespace Psalm\Checker;
use PhpParser;
use Psalm\Aliases;
use Psalm\Config;
use Psalm\Context;
use Psalm\FileManipulation\FileManipulationBuffer;
@ -19,6 +21,7 @@ use Psalm\Provider\FileReferenceProvider;
use Psalm\Provider\FileStorageProvider;
use Psalm\Provider\ParserCacheProvider;
use Psalm\Provider\StatementsProvider;
use Psalm\Scanner\FileScanner;
use Psalm\Storage\ClassLikeStorage;
use Psalm\Storage\FileStorage;
use Psalm\Type;
@ -270,6 +273,16 @@ class ProjectChecker
*/
public $only_replace_php_types_with_non_docblock_types = false;
/**
* @var array<string, PhpParser\Node\Stmt\Trait_>
*/
private $trait_nodes = [];
/**
* @var array<string, Aliases>
*/
private $trait_aliases = [];
const TYPE_CONSOLE = 'console';
const TYPE_JSON = 'json';
const TYPE_EMACS = 'emacs';
@ -951,7 +964,7 @@ class ProjectChecker
* @psalm-suppress UnusedParam
*/
function ($i, $file_path) use ($filetype_handlers) {
$file_checker = $this->getFile($file_path, $filetype_handlers, true);
$file_checker = $this->getFile($file_path, $filetype_handlers);
if ($this->debug_output) {
echo 'Analyzing ' . $file_checker->getFilePath() . PHP_EOL;
@ -1467,15 +1480,17 @@ class ProjectChecker
*
* @return FileChecker
*/
private function getFile($file_path, array $filetype_handlers, $will_analyze = false)
private function getFile($file_path, array $filetype_handlers)
{
$extension = (string)pathinfo($file_path)['extension'];
$file_name = $this->config->shortenFileName($file_path);
if (isset($filetype_handlers[$extension])) {
/** @var FileChecker */
$file_checker = new $filetype_handlers[$extension]($file_path, $this);
$file_checker = new $filetype_handlers[$extension]($this, $file_path, $file_name);
} else {
$file_checker = new FileChecker($file_path, $this, $will_analyze);
$file_checker = new FileChecker($this, $file_path, $file_name);
}
if ($this->debug_output) {
@ -1490,7 +1505,7 @@ class ProjectChecker
* @param array $filetype_handlers
* @param bool $will_analyze
*
* @return FileChecker
* @return FileScanner
*/
private function scanFile($file_path, array $filetype_handlers, $will_analyze = false)
{
@ -1498,11 +1513,13 @@ class ProjectChecker
$file_name_parts = explode('.', array_pop($path_parts));
$extension = count($file_name_parts) > 1 ? array_pop($file_name_parts) : null;
$file_name = $this->config->shortenFileName($file_path);
if (isset($filetype_handlers[$extension])) {
/** @var FileChecker */
$file_checker = new $filetype_handlers[$extension]($file_path, $this);
/** @var FileScanner */
$file_scanner = new $filetype_handlers[$extension]($file_path, $file_name, $will_analyze);
} else {
$file_checker = new FileChecker($file_path, $this, $will_analyze);
$file_scanner = new FileScanner($file_path, $file_name, $will_analyze);
}
if (isset($this->scanned_files[$file_path])) {
@ -1521,9 +1538,13 @@ class ProjectChecker
$this->scanned_files[$file_path] = true;
$file_checker->scan();
$file_scanner->scan(
$this,
$this->getStatementsForFile($file_path),
$this->file_storage_provider->create($file_path)
);
return $file_checker;
return $file_scanner;
}
/**
@ -1664,7 +1685,7 @@ class ProjectChecker
$file_checker = $this->getFileCheckerForClassLike($appearing_fq_class_name);
}
$stmts = $file_checker->getStatements();
$stmts = $this->getStatementsForFile($file_checker->getFilePath());
$file_checker->populateCheckers($stmts);
@ -1681,7 +1702,7 @@ class ProjectChecker
*
* @return FileChecker
*/
private function getFileCheckerForClassLike($fq_class_name)
public function getFileCheckerForClassLike($fq_class_name)
{
$fq_class_name_lc = strtolower($fq_class_name);
@ -1700,7 +1721,9 @@ class ProjectChecker
return $this->file_checkers[$file_path];
}
$file_checker = new FileChecker($file_path, $this, true);
$file_name = $this->config->shortenFileName($file_path);
$file_checker = new FileChecker($this, $file_path, $file_name);
if ($this->cache) {
$this->file_checkers[$file_path] = $file_checker;
@ -2059,4 +2082,52 @@ class ProjectChecker
{
return $this->issues_to_fix;
}
/**
* @param string $fq_trait_name
*
* @return void
*/
public function addTraitNode($fq_trait_name, PhpParser\Node\Stmt\Trait_ $node, Aliases $aliases)
{
$fq_trait_name_lc = strtolower($fq_trait_name);
$this->trait_nodes[$fq_trait_name_lc] = $node;
$this->trait_aliases[$fq_trait_name_lc] = $aliases;
}
/**
* @param string $fq_trait_name
*
* @return PhpParser\Node\Stmt\Trait_
*/
public function getTraitNode($fq_trait_name)
{
$fq_trait_name_lc = strtolower($fq_trait_name);
if (isset($this->trait_nodes[$fq_trait_name_lc])) {
return $this->trait_nodes[$fq_trait_name_lc];
}
throw new \UnexpectedValueException(
'Expecting trait statements to exist for ' . $fq_trait_name
);
}
/**
* @param string $fq_trait_name
*
* @return Aliases
*/
public function getTraitAliases($fq_trait_name)
{
$fq_trait_name_lc = strtolower($fq_trait_name);
if (isset($this->trait_aliases[$fq_trait_name_lc])) {
return $this->trait_aliases[$fq_trait_name_lc];
}
throw new \UnexpectedValueException(
'Expecting trait aliases to exist for ' . $fq_trait_name
);
}
}

View File

@ -449,7 +449,7 @@ class BinaryOpChecker
}
/**
* @param StatementsSource $statements_source
* @param StatementsSource|null $statements_source
* @param PhpParser\Node\Expr $left
* @param PhpParser\Node\Expr $right
* @param PhpParser\Node $parent
@ -458,20 +458,24 @@ class BinaryOpChecker
* @return void
*/
public static function analyzeNonDivArithmenticOp(
StatementsSource $statements_source,
$statements_source,
PhpParser\Node\Expr $left,
PhpParser\Node\Expr $right,
PhpParser\Node $parent,
Type\Union &$result_type = null,
Context $context = null
) {
$project_checker = $statements_source->getFileChecker()->project_checker;
$project_checker = $statements_source
? $statements_source->getFileChecker()->project_checker
: null;
$left_type = isset($left->inferredType) ? $left->inferredType : null;
$right_type = isset($right->inferredType) ? $right->inferredType : null;
$config = Config::getInstance();
if ($project_checker->infer_types_from_usage
if ($project_checker
&& $project_checker->infer_types_from_usage
&& $statements_source
&& $context
&& $left_type
&& $right_type
@ -491,7 +495,7 @@ class BinaryOpChecker
if ($left_type && $right_type) {
if ($left_type->isNullable()) {
if (IssueBuffer::accepts(
if ($statements_source && IssueBuffer::accepts(
new PossiblyNullOperand(
'Left operand cannot be nullable, got ' . $left_type,
new CodeLocation($statements_source, $left)
@ -501,7 +505,7 @@ class BinaryOpChecker
// fall through
}
} elseif ($left_type->isNull()) {
if (IssueBuffer::accepts(
if ($statements_source && IssueBuffer::accepts(
new NullOperand(
'Left operand cannot be null',
new CodeLocation($statements_source, $left)
@ -515,7 +519,7 @@ class BinaryOpChecker
}
if ($right_type->isNullable()) {
if (IssueBuffer::accepts(
if ($statements_source && IssueBuffer::accepts(
new PossiblyNullOperand(
'Right operand cannot be nullable, got ' . $right_type,
new CodeLocation($statements_source, $right)
@ -525,7 +529,7 @@ class BinaryOpChecker
// fall through
}
} elseif ($right_type->isNull()) {
if (IssueBuffer::accepts(
if ($statements_source && IssueBuffer::accepts(
new NullOperand(
'Right operand cannot be null',
new CodeLocation($statements_source, $right)
@ -547,7 +551,7 @@ class BinaryOpChecker
if ($left_type_part instanceof TMixed || $right_type_part instanceof TMixed) {
if ($left_type_part instanceof TMixed) {
if (IssueBuffer::accepts(
if ($statements_source && IssueBuffer::accepts(
new MixedOperand(
'Left operand cannot be mixed',
new CodeLocation($statements_source, $left)
@ -557,7 +561,7 @@ class BinaryOpChecker
// fall through
}
} else {
if (IssueBuffer::accepts(
if ($statements_source && IssueBuffer::accepts(
new MixedOperand(
'Right operand cannot be mixed',
new CodeLocation($statements_source, $right)
@ -582,7 +586,7 @@ class BinaryOpChecker
|| (!$left_type_part instanceof TArray && !$left_type_part instanceof ObjectLike)
) {
if (!$left_type_part instanceof TArray && !$left_type_part instanceof ObjectLike) {
if (IssueBuffer::accepts(
if ($statements_source && IssueBuffer::accepts(
new InvalidOperand(
'Cannot add an array to a non-array ' . $left_type_part,
new CodeLocation($statements_source, $left)
@ -592,7 +596,7 @@ class BinaryOpChecker
// fall through
}
} else {
if (IssueBuffer::accepts(
if ($statements_source && IssueBuffer::accepts(
new InvalidOperand(
'Cannot add an array to a non-array ' . $right_type_part,
new CodeLocation($statements_source, $right)
@ -662,7 +666,7 @@ class BinaryOpChecker
($left_type_part instanceof TInt && $right_type_part instanceof TFloat)
) {
if ($config->strict_binary_operands) {
if (IssueBuffer::accepts(
if ($statements_source && IssueBuffer::accepts(
new InvalidOperand(
'Cannot add ints to floats',
new CodeLocation($statements_source, $parent)
@ -684,7 +688,7 @@ class BinaryOpChecker
if ($left_type_part->isNumericType() && $right_type_part->isNumericType()) {
if ($config->strict_binary_operands) {
if (IssueBuffer::accepts(
if ($statements_source && IssueBuffer::accepts(
new InvalidOperand(
'Cannot add numeric types together, please cast explicitly',
new CodeLocation($statements_source, $parent)
@ -706,7 +710,7 @@ class BinaryOpChecker
$non_numeric_type = $left_type_part->isNumericType() ? $right_type_part : $left_type_part;
if (IssueBuffer::accepts(
if ($statements_source && IssueBuffer::accepts(
new InvalidOperand(
'Cannot add a numeric type to a non-numeric type ' . $non_numeric_type,
new CodeLocation($statements_source, $parent)

View File

@ -2,7 +2,6 @@
namespace Psalm\Checker\Statements\Expression;
use PhpParser;
use Psalm\Checker\FileChecker;
use Psalm\Checker\Statements\ExpressionChecker;
use Psalm\Checker\StatementsChecker;
use Psalm\CodeLocation;
@ -71,12 +70,12 @@ class IncludeChecker
if ($current_file_checker->project_checker->fileExists($path_to_file)) {
if (is_subclass_of($current_file_checker, 'Psalm\\Checker\\FileChecker')) {
$include_file_checker = new FileChecker(
$path_to_file,
$current_file_checker->project_checker,
false
$project_checker = $statements_checker->getFileChecker()->project_checker;
$statements_checker->analyze(
$project_checker->getStatementsForFile($path_to_file),
$context
);
$statements_checker->analyze($include_file_checker->getStatements(), $context);
}
return null;

View File

@ -584,10 +584,6 @@ class StatementsChecker extends SourceChecker implements StatementsSource
return null;
}
if (!$statements_source) {
return null;
}
if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Plus ||
$stmt instanceof PhpParser\Node\Expr\BinaryOp\Minus ||
$stmt instanceof PhpParser\Node\Expr\BinaryOp\Mod ||

View File

@ -2,24 +2,55 @@
namespace Psalm\Checker;
use PhpParser;
use Psalm\TraitSource;
use Psalm\Aliases;
use Psalm\StatementsSource;
class TraitChecker extends ClassLikeChecker
{
/**
* @param PhpParser\Node\Stmt\Trait_ $class
* @param TraitSource $trait_source
* @var Aliases
*/
private $aliases;
/**
* @param string $fq_class_name
*/
public function __construct(PhpParser\Node\Stmt\Trait_ $class, TraitSource $trait_source, $fq_class_name)
{
$this->source = $trait_source;
$this->file_checker = $trait_source->getFileChecker();
public function __construct(
PhpParser\Node\Stmt\Trait_ $class,
StatementsSource $source,
$fq_class_name,
Aliases $aliases
) {
$this->source = $source;
$this->file_checker = $source->getFileChecker();
$this->class = $class;
$this->fq_class_name = $fq_class_name;
$this->storage = $this->file_checker->project_checker->classlike_storage_provider->get($fq_class_name);
$this->aliases = $aliases;
}
self::$trait_checkers[strtolower($fq_class_name)] = $this;
/**
* @return ?string
*/
public function getNamespace()
{
return $this->aliases->namespace;
}
/**
* @return Aliases
*/
public function getAliases()
{
return $this->aliases;
}
/**
* @return array<string, string>
*/
public function getAliasedClassesFlipped()
{
return [];
}
/**

View File

@ -67,15 +67,13 @@ class CodeLocation
const FUNCTION_PARAM_VAR = 5;
/**
* @param StatementsSource $statements_source
* @param \PhpParser\Node $stmt
* @param bool $single_line
* @param ?int $regex_type
* @param ?CodeLocation $previous_location
* @param ?string $selected_text
*/
public function __construct(
StatementsSource $statements_source,
FileSource $file_source,
\PhpParser\Node $stmt,
CodeLocation $previous_location = null,
$single_line = false,
@ -84,8 +82,8 @@ class CodeLocation
) {
$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->file_path = $file_source->getCheckedFilePath();
$this->file_name = $file_source->getCheckedFileName();
$this->single_line = $single_line;
$this->regex_type = $regex_type;
$this->previous_location = $previous_location;

View File

@ -2,11 +2,11 @@
namespace Psalm;
use Psalm\Checker\ClassLikeChecker;
use Psalm\Checker\FileChecker;
use Psalm\Checker\ProjectChecker;
use Psalm\Config\IssueHandler;
use Psalm\Config\ProjectFileFilter;
use Psalm\Exception\ConfigException;
use Psalm\Scanner\FileScanner;
use SimpleXMLElement;
class Config
@ -513,9 +513,13 @@ class Config
public function initializePlugins(ProjectChecker $project_checker)
{
foreach ($this->filetype_handlers as &$path) {
$project_checker->file_storage_provider->create($path);
$plugin_file_checker = new FileChecker($path, $project_checker);
$plugin_file_checker->scan();
$storage = $project_checker->file_storage_provider->create($path);
$plugin_file_scanner = new FileScanner($path, $this->shortenFileName($path), false);
$plugin_file_scanner->scan(
$project_checker,
$project_checker->getStatementsForFile($path),
$storage
);
$declared_classes = ClassLikeChecker::getClassesForFile($project_checker, $path);
@ -651,23 +655,28 @@ class Config
{
$project_checker->register_global_functions = true;
$generic_stubs = realpath(__DIR__ . '/Stubs/CoreGenericFunctions.php');
$generic_stubs_path = realpath(__DIR__ . '/Stubs/CoreGenericFunctions.php');
if ($generic_stubs) {
$generic_stub_checker = new FileChecker(
$generic_stubs,
$project_checker
);
$project_checker->file_storage_provider->create($generic_stubs);
$generic_stub_checker->scan();
} else {
if (!$generic_stubs_path) {
throw new \UnexpectedValueException('Cannot locate core generic stubs');
}
foreach ($this->stub_files as $stub_file) {
$stub_checker = new FileChecker($stub_file, $project_checker);
$project_checker->file_storage_provider->create($stub_file);
$stub_checker->scan();
$file_storage = $project_checker->file_storage_provider->create($generic_stubs_path);
$file_to_scan = new FileScanner($generic_stubs_path, $this->shortenFileName($generic_stubs_path), false);
$file_to_scan->scan(
$project_checker,
$project_checker->getStatementsForFile($generic_stubs_path),
$file_storage
);
foreach ($this->stub_files as $stub_file_path) {
$file_storage = $project_checker->file_storage_provider->create($stub_file_path);
$file_to_scan = new FileScanner($stub_file_path, $this->shortenFileName($stub_file_path), false);
$file_to_scan->scan(
$project_checker,
$project_checker->getStatementsForFile($stub_file_path),
$file_storage
);
}
$project_checker->register_global_functions = false;
@ -771,9 +780,13 @@ class Config
continue;
}
$file_checker = new FileChecker($file_path, $project_checker);
$project_checker->file_storage_provider->create($file_path);
$file_checker->scan();
$file_storage = $project_checker->file_storage_provider->create($file_path);
$file_to_scan = new FileScanner($file_path, $this->shortenFileName($file_path), false);
$file_to_scan->scan(
$project_checker,
$project_checker->getStatementsForFile($file_path),
$file_storage
);
}
}

25
src/Psalm/FileSource.php Normal file
View File

@ -0,0 +1,25 @@
<?php
namespace Psalm;
interface FileSource
{
/**
* @return string
*/
public function getFileName();
/**
* @return string
*/
public function getFilePath();
/**
* @return string
*/
public function getCheckedFileName();
/**
* @return string
*/
public function getCheckedFilePath();
}

View File

@ -2,9 +2,9 @@
namespace Psalm;
use PhpParser;
use Psalm\Checker\FileChecker;
use Psalm\Checker\StatementsChecker;
use Psalm\FileManipulation\FileManipulation;
use Psalm\Scanner\FileScanner;
use Psalm\Storage\ClassLikeStorage;
abstract class Plugin
@ -63,7 +63,7 @@ abstract class Plugin
public function visitClassLike(
PhpParser\Node\Stmt\ClassLike $stmt,
ClassLikeStorage $storage,
FileChecker $file_checker,
FileScanner $file_checker,
Aliases $aliases,
array &$file_replacements = []
) {

View File

@ -65,7 +65,7 @@ class ClassLikeStorageProvider
/**
* @return void
*/
public function deleteAll()
public static function deleteAll()
{
self::$storage = [];
}

View File

@ -56,7 +56,7 @@ class FileStorageProvider
/**
* @return void
*/
public function deleteAll()
public static function deleteAll()
{
self::$storage = [];
}

View File

@ -0,0 +1,83 @@
<?php
namespace Psalm\Scanner;
use PhpParser;
use PhpParser\NodeTraverser;
use Psalm\Checker\ProjectChecker;
use Psalm\FileSource;
use Psalm\Storage\FileStorage;
use Psalm\Visitor\DependencyFinderVisitor;
class FileScanner implements FileSource
{
/**
* @var string
*/
public $file_path;
/**
* @var string
*/
public $file_name;
/**
* @var bool
*/
public $will_analyze;
/**
* @param string $file_path
* @param string $file_name
* @param bool $will_analyze
*/
public function __construct($file_path, $file_name, $will_analyze)
{
$this->file_path = $file_path;
$this->file_name = $file_name;
$this->will_analyze = $will_analyze;
}
/**
* @param array<mixed, PhpParser\Node> $stmts
*
* @return void
*/
public function scan(ProjectChecker $project_checker, array $stmts, FileStorage $file_storage)
{
$traverser = new NodeTraverser();
$traverser->addVisitor(new DependencyFinderVisitor($project_checker, $file_storage, $this));
$traverser->traverse($stmts);
}
/**
* @return string
*/
public function getFilePath()
{
return $this->file_path;
}
/**
* @return string
*/
public function getFileName()
{
return $this->file_name;
}
/**
* @return string
*/
public function getCheckedFilePath()
{
return $this->file_path;
}
/**
* @return string
*/
public function getCheckedFileName()
{
return $this->file_name;
}
}

View File

@ -3,7 +3,7 @@ namespace Psalm;
use Psalm\Checker\FileChecker;
interface StatementsSource
interface StatementsSource extends FileSource
{
/**
* @return ?string
@ -40,26 +40,6 @@ interface StatementsSource
*/
public function getParentFQCLN();
/**
* @return string
*/
public function getFileName();
/**
* @return string
*/
public function getFilePath();
/**
* @return string
*/
public function getCheckedFileName();
/**
* @return string
*/
public function getCheckedFilePath();
/**
* @return bool
*/

View File

@ -1,151 +0,0 @@
<?php
namespace Psalm;
use Psalm\Checker\FileChecker;
class TraitSource implements StatementsSource
{
/** @var Aliases */
private $aliases;
/** @var FileChecker */
private $file_checker;
public function __construct(FileChecker $file_checker, Aliases $aliases)
{
$this->aliases = $aliases;
$this->file_checker = $file_checker;
}
/**
* @return ?string
*/
public function getNamespace()
{
return $this->aliases->namespace;
}
/**
* @return Aliases
*/
public function getAliases()
{
return $this->aliases;
}
/**
* @return array<string, string>
*/
public function getAliasedClassesFlipped()
{
return [];
}
/**
* @return string|null
*/
public function getFQCLN()
{
return null;
}
/**
* @return string|null
*/
public function getClassName()
{
return null;
}
/**
* @return FileChecker
*/
public function getFileChecker()
{
return $this->file_checker;
}
/**
* @return string|null
*/
public function getParentFQCLN()
{
return null;
}
/**
* @return string
*/
public function getFileName()
{
return $this->file_checker->getFileName();
}
/**
* @return string
*/
public function getFilePath()
{
return $this->file_checker->getFilePath();
}
/**
* @return string
*/
public function getCheckedFileName()
{
return $this->file_checker->getCheckedFileName();
}
/**
* @return string
*/
public function getCheckedFilePath()
{
return $this->file_checker->getCheckedFilePath();
}
/**
* @return bool
*/
public function isStatic()
{
return false;
}
/**
* @return FileChecker
*/
public function getSource()
{
return $this->file_checker;
}
/**
* Get a list of suppressed issues
*
* @return array<int, string>
*/
public function getSuppressedIssues()
{
return [];
}
/**
* @param array<int, string> $new_issues
*
* @return void
*/
public function addSuppressedIssues(array $new_issues)
{
}
/**
* @param array<int, string> $new_issues
*
* @return void
*/
public function removeSuppressedIssues(array $new_issues)
{
}
}

View File

@ -6,14 +6,12 @@ use Psalm\Aliases;
use Psalm\Checker\ClassChecker;
use Psalm\Checker\ClassLikeChecker;
use Psalm\Checker\CommentChecker;
use Psalm\Checker\FileChecker;
use Psalm\Checker\FunctionChecker;
use Psalm\Checker\FunctionLikeChecker;
use Psalm\Checker\MethodChecker;
use Psalm\Checker\ProjectChecker;
use Psalm\Checker\Statements\Expression\IncludeChecker;
use Psalm\Checker\StatementsChecker;
use Psalm\Checker\TraitChecker;
use Psalm\CodeLocation;
use Psalm\Config;
use Psalm\Exception\DocblockParseException;
@ -26,76 +24,72 @@ use Psalm\Issue\InvalidDocblock;
use Psalm\Issue\MisplacedRequiredParam;
use Psalm\Issue\MissingDocblockType;
use Psalm\IssueBuffer;
use Psalm\Scanner\FileScanner;
use Psalm\Storage\ClassLikeStorage;
use Psalm\Storage\FileStorage;
use Psalm\Storage\FunctionLikeStorage;
use Psalm\Storage\MethodStorage;
use Psalm\Storage\PropertyStorage;
use Psalm\TraitSource;
use Psalm\Type;
class DependencyFinderVisitor extends PhpParser\NodeVisitorAbstract implements PhpParser\NodeVisitor
{
/** @var Aliases */
protected $aliases;
private $aliases;
/** @var Aliases */
protected $file_aliases;
private $file_aliases;
/**
* @var string[]
*/
protected $fq_classlike_names = [];
private $fq_classlike_names = [];
/** @var ProjectChecker */
protected $project_checker;
private $project_checker;
/** @var FileChecker */
protected $file_checker;
/** @var FileScanner */
private $file_scanner;
/** @var string */
protected $file_path;
private $file_path;
/** @var bool */
protected $scan_deep;
private $scan_deep;
/** @var Config */
protected $config;
private $config;
/** @var bool */
protected $queue_strings_as_possible_type = false;
private $queue_strings_as_possible_type = false;
/** @var array<string, string> */
protected $class_template_types = [];
private $class_template_types = [];
/** @var array<string, string> */
protected $function_template_types = [];
private $function_template_types = [];
/** @var FunctionLikeStorage[] */
protected $functionlike_storages = [];
private $functionlike_storages = [];
/** @var FileStorage */
protected $file_storage;
private $file_storage;
/** @var ClassLikeStorage[] */
protected $classlike_storages = [];
private $classlike_storages = [];
/** @var \Psalm\Plugin[] */
protected $plugins;
private $plugins;
/**
* @param ProjectChecker $project_checker
* @param FileChecker $file_checker
*/
public function __construct(ProjectChecker $project_checker, FileChecker $file_checker)
public function __construct(ProjectChecker $project_checker, FileStorage $file_storage, FileScanner $file_scanner)
{
$this->project_checker = $project_checker;
$this->file_checker = $file_checker;
$this->file_path = $file_checker->getFilePath();
$this->scan_deep = $file_checker->will_analyze;
$this->file_scanner = $file_scanner;
$this->file_path = $file_scanner->getFilePath();
$this->scan_deep = $file_scanner->will_analyze;
$this->config = Config::getInstance();
$this->aliases = $this->file_aliases = new Aliases();
$this->file_storage = $project_checker->file_storage_provider->get($this->file_path);
$this->file_storage = $file_storage;
$this->plugins = $this->config->getPlugins();
}
@ -170,7 +164,7 @@ class DependencyFinderVisitor extends PhpParser\NodeVisitorAbstract implements P
$storage = $this->project_checker->classlike_storage_provider->create($fq_classlike_name);
$storage->location = new CodeLocation($this->file_checker, $node, null, true);
$storage->location = new CodeLocation($this->file_scanner, $node, null, true);
$storage->user_defined = true;
$doc_comment = $node->getDocComment();
@ -188,7 +182,7 @@ class DependencyFinderVisitor extends PhpParser\NodeVisitorAbstract implements P
IssueBuffer::accepts(
new InvalidDocblock(
$e->getMessage() . ' in docblock for ' . implode('.', $this->fq_classlike_names),
new CodeLocation($this->file_checker, $node, null, true)
new CodeLocation($this->file_scanner, $node, null, true)
)
);
}
@ -266,12 +260,24 @@ class DependencyFinderVisitor extends PhpParser\NodeVisitorAbstract implements P
} elseif ($node instanceof PhpParser\Node\Stmt\Trait_) {
$storage->is_trait = true;
$this->project_checker->addFullyQualifiedTraitName($fq_classlike_name, $this->file_path);
ClassLikeChecker::$trait_checkers[$fq_classlike_name_lc] = new TraitChecker(
$this->project_checker->addTraitNode(
$fq_classlike_name,
$node,
new TraitSource($this->file_checker, $this->aliases),
$fq_classlike_name
$this->aliases
);
}
foreach ($node->stmts as $node_stmt) {
if ($node_stmt instanceof PhpParser\Node\Stmt\ClassConst) {
$this->visitClassConstDeclaration($node_stmt, $storage);
}
}
foreach ($node->stmts as $node_stmt) {
if ($node_stmt instanceof PhpParser\Node\Stmt\Property) {
$this->visitPropertyDeclaration($node_stmt, $this->config, $storage);
}
}
} elseif (($node instanceof PhpParser\Node\Expr\New_
|| $node instanceof PhpParser\Node\Expr\Instanceof_
|| $node instanceof PhpParser\Node\Expr\StaticPropertyFetch
@ -379,10 +385,6 @@ class DependencyFinderVisitor extends PhpParser\NodeVisitorAbstract implements P
$this->project_checker->queueClassLikeForScanning($trait_fqcln, $this->file_path, $this->scan_deep);
$storage->used_traits[strtolower($trait_fqcln)] = $trait_fqcln;
}
} elseif ($node instanceof PhpParser\Node\Stmt\Property) {
$this->visitPropertyDeclaration($node, $this->config);
} elseif ($node instanceof PhpParser\Node\Stmt\ClassConst) {
$this->visitClassConstDeclaration($node);
} elseif ($node instanceof PhpParser\Node\Expr\Include_) {
$this->visitInclude($node);
} elseif ($node instanceof PhpParser\Node\Scalar\String_ && $this->queue_strings_as_possible_type) {
@ -399,7 +401,7 @@ class DependencyFinderVisitor extends PhpParser\NodeVisitorAbstract implements P
try {
$var_comment = CommentChecker::getTypeFromComment(
(string)$doc_comment,
$this->file_checker,
$this->file_scanner,
$this->aliases,
null,
null
@ -483,14 +485,11 @@ class DependencyFinderVisitor extends PhpParser\NodeVisitorAbstract implements P
$plugin->visitClassLike(
$node,
$classlike_storage,
$this->file_checker,
$this->file_scanner,
$this->aliases,
$file_manipulations
);
}
if ($file_manipulations) {
}
}
} elseif ($node instanceof PhpParser\Node\Stmt\Function_
|| $node instanceof PhpParser\Node\Stmt\ClassMethod
@ -609,7 +608,7 @@ class DependencyFinderVisitor extends PhpParser\NodeVisitorAbstract implements P
$storage->cased_name = $stmt->name;
}
$storage->location = new CodeLocation($this->file_checker, $stmt, null, true);
$storage->location = new CodeLocation($this->file_scanner, $stmt, null, true);
$required_param_count = 0;
$i = 0;
@ -625,7 +624,7 @@ class DependencyFinderVisitor extends PhpParser\NodeVisitorAbstract implements P
if (IssueBuffer::accepts(
new DuplicateParam(
'Duplicate param $' . $param->name . ' in docblock for ' . $cased_function_id,
new CodeLocation($this->file_checker, $param, null, true)
new CodeLocation($this->file_scanner, $param, null, true)
)
)) {
continue;
@ -644,7 +643,7 @@ class DependencyFinderVisitor extends PhpParser\NodeVisitorAbstract implements P
new MisplacedRequiredParam(
'Required param $' . $param->name . ' should come before any optional params in ' .
$cased_function_id,
new CodeLocation($this->file_checker, $param, null, true)
new CodeLocation($this->file_scanner, $param, null, true)
)
)) {
// fall through
@ -736,7 +735,7 @@ class DependencyFinderVisitor extends PhpParser\NodeVisitorAbstract implements P
$storage->return_type = Type::parseString($return_type_string, true);
$storage->return_type_location = new CodeLocation(
$this->file_checker,
$this->file_scanner,
$stmt,
null,
false,
@ -770,7 +769,7 @@ class DependencyFinderVisitor extends PhpParser\NodeVisitorAbstract implements P
if (IssueBuffer::accepts(
new MissingDocblockType(
$e->getMessage() . ' in docblock for ' . $cased_function_id,
new CodeLocation($this->file_checker, $stmt, null, true)
new CodeLocation($this->file_scanner, $stmt, null, true)
)
)) {
// fall through
@ -781,7 +780,7 @@ class DependencyFinderVisitor extends PhpParser\NodeVisitorAbstract implements P
if (IssueBuffer::accepts(
new InvalidDocblock(
$e->getMessage() . ' in docblock for ' . $cased_function_id,
new CodeLocation($this->file_checker, $stmt, null, true)
new CodeLocation($this->file_scanner, $stmt, null, true)
)
)) {
// fall through
@ -859,7 +858,7 @@ class DependencyFinderVisitor extends PhpParser\NodeVisitorAbstract implements P
if (!$storage->return_type_location) {
$storage->return_type_location = new CodeLocation(
$this->file_checker,
$this->file_scanner,
$stmt,
null,
false,
@ -899,7 +898,7 @@ class DependencyFinderVisitor extends PhpParser\NodeVisitorAbstract implements P
if (IssueBuffer::accepts(
new InvalidDocblock(
$e->getMessage() . ' in docblock for ' . $cased_function_id,
new CodeLocation($this->file_checker, $stmt, null, true)
new CodeLocation($this->file_scanner, $stmt, null, true)
)
)) {
// fall through
@ -998,9 +997,9 @@ class DependencyFinderVisitor extends PhpParser\NodeVisitorAbstract implements P
$param->name,
$param->byRef,
$param_type,
new CodeLocation($this->file_checker, $param, null, false, CodeLocation::FUNCTION_PARAM_VAR),
new CodeLocation($this->file_scanner, $param, null, false, CodeLocation::FUNCTION_PARAM_VAR),
$param_typehint
? new CodeLocation($this->file_checker, $param, null, false, CodeLocation::FUNCTION_PARAM_TYPE)
? new CodeLocation($this->file_scanner, $param, null, false, CodeLocation::FUNCTION_PARAM_TYPE)
: null,
$is_optional,
$is_nullable,
@ -1053,7 +1052,7 @@ class DependencyFinderVisitor extends PhpParser\NodeVisitorAbstract implements P
}
$code_location = new CodeLocation(
$this->file_checker,
$this->file_scanner,
$function,
null,
true,
@ -1134,7 +1133,8 @@ class DependencyFinderVisitor extends PhpParser\NodeVisitorAbstract implements P
*/
private function visitPropertyDeclaration(
PhpParser\Node\Stmt\Property $stmt,
Config $config
Config $config,
ClassLikeStorage $storage
) {
if (!$this->fq_classlike_names) {
throw new \LogicException('$this->fq_classlike_names should not be empty');
@ -1148,10 +1148,12 @@ class DependencyFinderVisitor extends PhpParser\NodeVisitorAbstract implements P
throw new \UnexpectedValueException('$this->classlike_storages should not be empty');
}
$storage = $this->classlike_storages[count($this->classlike_storages) - 1];
$property_is_initialized = false;
$existing_constants = $storage->protected_class_constants
+ $storage->private_class_constants
+ $storage->public_class_constants;
if ($comment && $comment->getText() && ($config->use_docblock_types || $config->use_docblock_property_types)) {
if (preg_match('/[ \t\*]+@psalm-suppress[ \t]+PropertyNotSetInConstructor/', (string)$comment)) {
$property_is_initialized = true;
@ -1161,7 +1163,7 @@ class DependencyFinderVisitor extends PhpParser\NodeVisitorAbstract implements P
$property_type_line_number = $comment->getLine();
$var_comment = CommentChecker::getTypeFromComment(
$comment->getText(),
$this->file_checker,
$this->file_scanner,
$this->aliases,
$this->function_template_types + $this->class_template_types,
$property_type_line_number
@ -1170,7 +1172,7 @@ class DependencyFinderVisitor extends PhpParser\NodeVisitorAbstract implements P
if (IssueBuffer::accepts(
new MissingDocblockType(
$e->getMessage(),
new CodeLocation($this->file_checker, $stmt, null, true)
new CodeLocation($this->file_scanner, $stmt, null, true)
)
)) {
// fall through
@ -1179,7 +1181,7 @@ class DependencyFinderVisitor extends PhpParser\NodeVisitorAbstract implements P
if (IssueBuffer::accepts(
new InvalidDocblock(
$e->getMessage(),
new CodeLocation($this->file_checker, $stmt, null, true)
new CodeLocation($this->file_scanner, $stmt, null, true)
)
)) {
// fall through
@ -1200,14 +1202,14 @@ class DependencyFinderVisitor extends PhpParser\NodeVisitorAbstract implements P
if (!$property_group_type) {
if ($property->default) {
$default_type = StatementsChecker::getSimpleType($property->default);
$default_type = StatementsChecker::getSimpleType($property->default, null, $existing_constants);
}
$property_type = false;
} else {
if ($var_comment && $var_comment->line_number) {
$property_type_location = new CodeLocation(
$this->file_checker,
$this->file_scanner,
$stmt,
null,
false,
@ -1223,7 +1225,7 @@ class DependencyFinderVisitor extends PhpParser\NodeVisitorAbstract implements P
$property_storage = $storage->properties[$property->name] = new PropertyStorage();
$property_storage->is_static = (bool)$stmt->isStatic();
$property_storage->type = $property_type;
$property_storage->location = new CodeLocation($this->file_checker, $property);
$property_storage->location = new CodeLocation($this->file_scanner, $property);
$property_storage->type_location = $property_type_location;
$property_storage->has_default = $property->default ? true : false;
$property_storage->suggested_type = $property_group_type ? null : $default_type;
@ -1259,14 +1261,12 @@ class DependencyFinderVisitor extends PhpParser\NodeVisitorAbstract implements P
*
* @return void
*/
private function visitClassConstDeclaration(PhpParser\Node\Stmt\ClassConst $stmt)
private function visitClassConstDeclaration(PhpParser\Node\Stmt\ClassConst $stmt, ClassLikeStorage $storage)
{
if (!$this->classlike_storages) {
throw new \LogicException('$this->classlike_storages should not be empty');
}
$storage = $this->classlike_storages[count($this->classlike_storages) - 1];
$existing_constants = $storage->protected_class_constants
+ $storage->private_class_constants
+ $storage->public_class_constants;
@ -1274,7 +1274,7 @@ class DependencyFinderVisitor extends PhpParser\NodeVisitorAbstract implements P
foreach ($stmt->consts as $const) {
$const_type = StatementsChecker::getSimpleType(
$const->value,
$this->file_checker,
null,
$existing_constants
) ?: Type::getMixed();

View File

@ -403,6 +403,38 @@ class ConfigTest extends TestCase
$this->analyzeFile($file_path, new Context());
}
/**
* @expectedException \Psalm\Exception\CodeException
* @expectedExceptionMessage UndefinedFunction - /src/somefile.php:2 - Function barBar does not exist
*
* @return void
*/
public function testNoStubFunction()
{
$this->project_checker = $this->getProjectCheckerWithConfig(
TestConfig::loadFromXML(
'psalm.xml',
dirname(__DIR__),
'<?xml version="1.0"?>
<psalm>
<projectFiles>
<directory name="src" />
</projectFiles>
</psalm>'
)
);
$file_path = getcwd() . '/src/somefile.php';
$this->addFile(
$file_path,
'<?php
echo barBar("hello");'
);
$this->analyzeFile($file_path, new Context());
}
/**
* @return void
*/

View File

@ -22,8 +22,10 @@ class IncludeTest extends TestCase
$this->project_checker->scanFiles();
foreach ($files_to_check as $filename) {
$file_checker = new FileChecker($filename, $this->project_checker);
$config = $this->project_checker->getConfig();
foreach ($files_to_check as $file_path) {
$file_checker = new FileChecker($this->project_checker, $file_path, $config->shortenFileName($file_path));
$file_checker->analyze();
}
}
@ -49,8 +51,10 @@ class IncludeTest extends TestCase
$this->expectException('\Psalm\Exception\CodeException');
$this->expectExceptionMessageRegexp('/\b' . preg_quote($error_message, '/') . '\b/');
foreach ($files_to_check as $filename) {
$file_checker = new FileChecker($filename, $this->project_checker);
$config = $this->project_checker->getConfig();
foreach ($files_to_check as $file_path) {
$file_checker = new FileChecker($this->project_checker, $file_path, $config->shortenFileName($file_path));
$file_checker->analyze();
}
}

View File

@ -89,7 +89,7 @@ class MethodMutationTest extends TestCase
}'
);
new FileChecker('somefile.php', $this->project_checker);
new FileChecker($this->project_checker, 'somefile.php', 'somefile.php');
$this->project_checker->scanFiles();
$method_context = new Context();
$this->project_checker->getMethodMutations('FooController::barBar', $method_context);
@ -126,7 +126,7 @@ class MethodMutationTest extends TestCase
}'
);
new FileChecker('somefile.php', $this->project_checker);
new FileChecker($this->project_checker, 'somefile.php', 'somefile.php');
$this->project_checker->scanFiles();
$method_context = new Context();
$this->project_checker->getMethodMutations('FooController::__construct', $method_context);
@ -162,7 +162,7 @@ class MethodMutationTest extends TestCase
}'
);
new FileChecker('somefile.php', $this->project_checker);
new FileChecker($this->project_checker, 'somefile.php', 'somefile.php');
$this->project_checker->scanFiles();
$method_context = new Context();
$this->project_checker->getMethodMutations('FooController::__construct', $method_context);

View File

@ -28,6 +28,9 @@ class Php56Test extends TestCase
const ONE_THIRD = self::ONE / self::THREE;
const SENTENCE = "The value of THREE is " . self::THREE;
/** @var int */
public $four = self::ONE + self::THREE;
/**
* @param int $a
* @return int
@ -42,7 +45,8 @@ class Php56Test extends TestCase
$c3 = C::THREE;
$c1_3rd = C::ONE_THIRD;
$c_sentence = C::SENTENCE;
$cf = (new C)->f();',
$cf = (new C)->f();
$c4 = (new C)->four',
'assertions' => [
'$c1' => 'int',
'$c2' => 'int',
@ -50,6 +54,7 @@ class Php56Test extends TestCase
'$c1_3rd' => 'float|int',
'$c_sentence' => 'string',
'$cf' => 'int',
'$c4' => 'int',
],
],
'constFeatures' => [

View File

@ -49,17 +49,6 @@ class TestCase extends BaseTestCase
$this->project_checker->infer_types_from_usage = true;
}
/**
* @return void
*/
public function tearDown()
{
if ($this->project_checker) {
$this->project_checker->classlike_storage_provider->deleteAll();
$this->project_checker->file_storage_provider->deleteAll();
}
}
/**
* @param string $file_path
* @param string $contents
@ -80,7 +69,8 @@ class TestCase extends BaseTestCase
*/
public function analyzeFile($file_path, Context $context)
{
$file_checker = new FileChecker($file_path, $this->project_checker);
$config = $this->project_checker->getConfig();
$file_checker = new FileChecker($this->project_checker, $file_path, $config->shortenFileName($file_path));
$this->project_checker->registerAnalyzableFile($file_path);
$this->project_checker->scanFiles();
$file_checker->analyze($context);

View File

@ -28,7 +28,7 @@ class TypeReconciliationTest extends TestCase
{
parent::setUp();
$this->file_checker = new FileChecker('somefile.php', $this->project_checker);
$this->file_checker = new FileChecker($this->project_checker, 'somefile.php', 'somefile.php');
$this->file_checker->context = new Context();
$this->statements_checker = new StatementsChecker($this->file_checker);
}