From e05a7c00cc6446a04f913de7eef2e661d6f26d25 Mon Sep 17 00:00:00 2001 From: Matthew Brown Date: Sun, 21 Jan 2018 12:44:46 -0500 Subject: [PATCH] Break FileScanner out from FileChecker --- config.xsd | 3 +- examples/TemplateChecker.php | 46 +----- examples/TemplateScanner.php | 56 +++++++ src/Psalm/Checker/ClassChecker.php | 28 ++-- src/Psalm/Checker/ClassLikeChecker.php | 28 ++-- src/Psalm/Checker/CommentChecker.php | 5 +- src/Psalm/Checker/FileChecker.php | 54 ++----- src/Psalm/Checker/ProjectChecker.php | 97 +++++++++-- .../Statements/Expression/BinaryOpChecker.php | 34 ++-- .../Statements/Expression/IncludeChecker.php | 11 +- src/Psalm/Checker/StatementsChecker.php | 4 - src/Psalm/Checker/TraitChecker.php | 49 ++++-- src/Psalm/CodeLocation.php | 8 +- src/Psalm/Config.php | 53 +++--- src/Psalm/FileSource.php | 25 +++ src/Psalm/Plugin.php | 4 +- .../Provider/ClassLikeStorageProvider.php | 2 +- src/Psalm/Provider/FileStorageProvider.php | 2 +- src/Psalm/Scanner/FileScanner.php | 83 ++++++++++ src/Psalm/StatementsSource.php | 22 +-- src/Psalm/TraitSource.php | 151 ------------------ src/Psalm/Visitor/DependencyFinderVisitor.php | 132 +++++++-------- tests/ConfigTest.php | 32 ++++ tests/IncludeTest.php | 12 +- tests/MethodMutationTest.php | 6 +- tests/Php56Test.php | 7 +- tests/TestCase.php | 14 +- tests/TypeReconciliationTest.php | 2 +- 28 files changed, 512 insertions(+), 458 deletions(-) create mode 100644 examples/TemplateScanner.php create mode 100644 src/Psalm/FileSource.php create mode 100644 src/Psalm/Scanner/FileScanner.php delete mode 100644 src/Psalm/TraitSource.php diff --git a/config.xsd b/config.xsd index 2cfd556e3..108d0135e 100644 --- a/config.xsd +++ b/config.xsd @@ -54,7 +54,8 @@ - + + diff --git a/examples/TemplateChecker.php b/examples/TemplateChecker.php index b474d6263..8bf62afa4 100644 --- a/examples/TemplateChecker.php +++ b/examples/TemplateChecker.php @@ -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 $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) { diff --git a/examples/TemplateScanner.php b/examples/TemplateScanner.php new file mode 100644 index 000000000..3ba1268e7 --- /dev/null +++ b/examples/TemplateScanner.php @@ -0,0 +1,56 @@ + $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); + } +} diff --git a/src/Psalm/Checker/ClassChecker.php b/src/Psalm/Checker/ClassChecker.php index edae301be..225830a6b 100644 --- a/src/Psalm/Checker/ClassChecker.php +++ b/src/Psalm/Checker/ClassChecker.php @@ -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); } diff --git a/src/Psalm/Checker/ClassLikeChecker.php b/src/Psalm/Checker/ClassLikeChecker.php index 55d8bc9c2..e0b98b78f 100644 --- a/src/Psalm/Checker/ClassLikeChecker.php +++ b/src/Psalm/Checker/ClassLikeChecker.php @@ -91,13 +91,6 @@ abstract class ClassLikeChecker extends SourceChecker implements StatementsSourc */ protected static $property_map; - /** - * A lookup table of cached TraitCheckers - * - * @var array - */ - 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_checker = self::$trait_checkers[strtolower($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 + ); 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 = []; } } diff --git a/src/Psalm/Checker/CommentChecker.php b/src/Psalm/Checker/CommentChecker.php index e6cee8402..4650fe667 100644 --- a/src/Psalm/Checker/CommentChecker.php +++ b/src/Psalm/Checker/CommentChecker.php @@ -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|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, diff --git a/src/Psalm/Checker/FileChecker.php b/src/Psalm/Checker/FileChecker.php index 31dbc2f52..4ccaba5a2 100644 --- a/src/Psalm/Checker/FileChecker.php +++ b/src/Psalm/Checker/FileChecker.php @@ -2,7 +2,6 @@ namespace Psalm\Checker; use PhpParser; -use Psalm\Config; use Psalm\Context; use Psalm\FileManipulation\FileManipulationBuffer; use Psalm\IssueBuffer; @@ -74,36 +73,15 @@ class FileChecker extends SourceChecker implements StatementsSource public $project_checker; /** - * @var bool + * @param string $file_path + * @param string $file_name + * @param ProjectChecker $project_checker */ - public $will_analyze; - - /** - * @param string $file_path - * @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 - */ - 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(); } /** diff --git a/src/Psalm/Checker/ProjectChecker.php b/src/Psalm/Checker/ProjectChecker.php index 8de986bc9..58c25c798 100644 --- a/src/Psalm/Checker/ProjectChecker.php +++ b/src/Psalm/Checker/ProjectChecker.php @@ -1,6 +1,8 @@ + */ + private $trait_nodes = []; + + /** + * @var array + */ + 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 + ); + } } diff --git a/src/Psalm/Checker/Statements/Expression/BinaryOpChecker.php b/src/Psalm/Checker/Statements/Expression/BinaryOpChecker.php index cf14093f6..6d54d3f1f 100644 --- a/src/Psalm/Checker/Statements/Expression/BinaryOpChecker.php +++ b/src/Psalm/Checker/Statements/Expression/BinaryOpChecker.php @@ -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) diff --git a/src/Psalm/Checker/Statements/Expression/IncludeChecker.php b/src/Psalm/Checker/Statements/Expression/IncludeChecker.php index 347af8a60..5f86f1db4 100644 --- a/src/Psalm/Checker/Statements/Expression/IncludeChecker.php +++ b/src/Psalm/Checker/Statements/Expression/IncludeChecker.php @@ -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; diff --git a/src/Psalm/Checker/StatementsChecker.php b/src/Psalm/Checker/StatementsChecker.php index c9d67f748..06d713a80 100644 --- a/src/Psalm/Checker/StatementsChecker.php +++ b/src/Psalm/Checker/StatementsChecker.php @@ -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 || diff --git a/src/Psalm/Checker/TraitChecker.php b/src/Psalm/Checker/TraitChecker.php index d77ab5387..bca04ad4e 100644 --- a/src/Psalm/Checker/TraitChecker.php +++ b/src/Psalm/Checker/TraitChecker.php @@ -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 - * @param string $fq_class_name + * @var Aliases */ - public function __construct(PhpParser\Node\Stmt\Trait_ $class, TraitSource $trait_source, $fq_class_name) - { - $this->source = $trait_source; - $this->file_checker = $trait_source->getFileChecker(); + private $aliases; + + /** + * @param string $fq_class_name + */ + 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 + */ + public function getAliasedClassesFlipped() + { + return []; } /** diff --git a/src/Psalm/CodeLocation.php b/src/Psalm/CodeLocation.php index 0ca53b4ef..7debbbe5d 100644 --- a/src/Psalm/CodeLocation.php +++ b/src/Psalm/CodeLocation.php @@ -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; diff --git a/src/Psalm/Config.php b/src/Psalm/Config.php index e2af8bbf0..d55cc2719 100644 --- a/src/Psalm/Config.php +++ b/src/Psalm/Config.php @@ -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 + ); } } diff --git a/src/Psalm/FileSource.php b/src/Psalm/FileSource.php new file mode 100644 index 000000000..7f8d57ebf --- /dev/null +++ b/src/Psalm/FileSource.php @@ -0,0 +1,25 @@ +file_path = $file_path; + $this->file_name = $file_name; + $this->will_analyze = $will_analyze; + } + + /** + * @param array $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; + } +} diff --git a/src/Psalm/StatementsSource.php b/src/Psalm/StatementsSource.php index cc5ea7fdf..27b6bbd97 100644 --- a/src/Psalm/StatementsSource.php +++ b/src/Psalm/StatementsSource.php @@ -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 */ diff --git a/src/Psalm/TraitSource.php b/src/Psalm/TraitSource.php deleted file mode 100644 index 78057e8b1..000000000 --- a/src/Psalm/TraitSource.php +++ /dev/null @@ -1,151 +0,0 @@ -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 - */ - 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 - */ - public function getSuppressedIssues() - { - return []; - } - - /** - * @param array $new_issues - * - * @return void - */ - public function addSuppressedIssues(array $new_issues) - { - } - - /** - * @param array $new_issues - * - * @return void - */ - public function removeSuppressedIssues(array $new_issues) - { - } -} diff --git a/src/Psalm/Visitor/DependencyFinderVisitor.php b/src/Psalm/Visitor/DependencyFinderVisitor.php index b59288480..3fb18897c 100644 --- a/src/Psalm/Visitor/DependencyFinderVisitor.php +++ b/src/Psalm/Visitor/DependencyFinderVisitor.php @@ -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 */ - protected $class_template_types = []; + private $class_template_types = []; /** @var array */ - 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(); diff --git a/tests/ConfigTest.php b/tests/ConfigTest.php index 5577c9ea1..873cbd734 100644 --- a/tests/ConfigTest.php +++ b/tests/ConfigTest.php @@ -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__), + ' + + + + + ' + ) + ); + + $file_path = getcwd() . '/src/somefile.php'; + + $this->addFile( + $file_path, + 'analyzeFile($file_path, new Context()); + } + /** * @return void */ diff --git a/tests/IncludeTest.php b/tests/IncludeTest.php index c5614a67a..6f6152e90 100644 --- a/tests/IncludeTest.php +++ b/tests/IncludeTest.php @@ -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(); } } diff --git a/tests/MethodMutationTest.php b/tests/MethodMutationTest.php index 188151443..59fdb096a 100644 --- a/tests/MethodMutationTest.php +++ b/tests/MethodMutationTest.php @@ -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); diff --git a/tests/Php56Test.php b/tests/Php56Test.php index d8c5ea7cc..3a92300f6 100644 --- a/tests/Php56Test.php +++ b/tests/Php56Test.php @@ -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' => [ diff --git a/tests/TestCase.php b/tests/TestCase.php index 009879c63..3c17f3f52 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -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); diff --git a/tests/TypeReconciliationTest.php b/tests/TypeReconciliationTest.php index ae092fde8..065da97fb 100644 --- a/tests/TypeReconciliationTest.php +++ b/tests/TypeReconciliationTest.php @@ -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); }