diff --git a/src/Psalm/Checker/ProjectChecker.php b/src/Psalm/Checker/ProjectChecker.php index ba6aada8766..da0eee00aae 100644 --- a/src/Psalm/Checker/ProjectChecker.php +++ b/src/Psalm/Checker/ProjectChecker.php @@ -231,9 +231,6 @@ public function __construct( self::$instance = $this; $this->cache_provider->use_igbinary = $config->use_igbinary; - - $config->visitStubFiles($this->codebase); - $config->initializePlugins($this); } /** @@ -283,6 +280,8 @@ public function check($base_dir, $is_diff = false) $this->codebase->addFilesToAnalyze([$file_path => $file_path]); } + $this->config->initializePlugins($this); + $this->codebase->scanFiles(); } else { if ($this->debug_output) { @@ -297,6 +296,8 @@ public function check($base_dir, $is_diff = false) $this->checkDiffFilesWithConfig($this->config, $file_list); + $this->config->initializePlugins($this); + $this->codebase->scanFiles(); } } @@ -305,6 +306,8 @@ public function check($base_dir, $is_diff = false) echo 'Analyzing files...' . "\n"; } + $this->config->visitStubFiles($this->codebase, $this->debug_output); + $this->codebase->analyzer->analyzeFiles($this, $this->threads, $this->alter_code); $removed_parser_files = $this->cache_provider->deleteOldParserCaches( @@ -386,8 +389,12 @@ public function checkDir($dir_name) echo 'Scanning files...' . "\n"; } + $this->config->initializePlugins($this); + $this->codebase->scanFiles(); + $this->config->visitStubFiles($this->codebase, $this->debug_output); + if ($this->output_format === self::TYPE_CONSOLE) { echo 'Analyzing files...' . "\n"; } @@ -547,8 +554,12 @@ public function checkFile($file_path) echo 'Scanning files...' . "\n"; } + $this->config->initializePlugins($this); + $this->codebase->scanFiles(); + $this->config->visitStubFiles($this->codebase, $this->debug_output); + if ($this->output_format === self::TYPE_CONSOLE) { echo 'Analyzing files...' . "\n"; } diff --git a/src/Psalm/Checker/Statements/Expression/Fetch/ArrayFetchChecker.php b/src/Psalm/Checker/Statements/Expression/Fetch/ArrayFetchChecker.php index b840de04640..72efd970266 100644 --- a/src/Psalm/Checker/Statements/Expression/Fetch/ArrayFetchChecker.php +++ b/src/Psalm/Checker/Statements/Expression/Fetch/ArrayFetchChecker.php @@ -635,8 +635,12 @@ public static function getArrayAccessTypeGivenOffset( if ($type instanceof TNamedObject) { if (strtolower($type->value) !== 'simplexmlelement' - && $codebase->classExists($type->value) - && !$codebase->classImplements($type->value, 'ArrayAccess') + && strtolower($type->value) !== 'arrayaccess' + && (($codebase->classExists($type->value) + && !$codebase->classImplements($type->value, 'ArrayAccess')) + || ($codebase->interfaceExists($type->value) + && !$codebase->interfaceExtends($type->value, 'ArrayAccess')) + ) ) { $non_array_types[] = (string)$type; } else { diff --git a/src/Psalm/Checker/StatementsChecker.php b/src/Psalm/Checker/StatementsChecker.php index 144275e1452..854fa7c955f 100644 --- a/src/Psalm/Checker/StatementsChecker.php +++ b/src/Psalm/Checker/StatementsChecker.php @@ -450,7 +450,9 @@ function ($line) { } } - if (!$project_checker->codebase->register_global_functions) { + if (!$project_checker->codebase->register_stub_files + && !$project_checker->codebase->register_autoload_files + ) { $function_id = strtolower($stmt->name->name); $function_context = new Context($context->self); $config = Config::getInstance(); diff --git a/src/Psalm/Codebase.php b/src/Psalm/Codebase.php index b391e350220..0b90497e376 100644 --- a/src/Psalm/Codebase.php +++ b/src/Psalm/Codebase.php @@ -68,12 +68,19 @@ class Codebase */ private static $stubbed_constants = []; + /** + * Whether to register autoloaded information + * + * @var bool + */ + public $register_autoload_files = false; + /** * Whether to log functions just at the file level or globally (for stubs) * * @var bool */ - public $register_global_functions = false; + public $register_stub_files = false; /** * @var bool @@ -404,7 +411,7 @@ public function getClosureStorage($file_path, $closure_id) * * @return void */ - public function addStubbedConstantType($const_id, $type) + public function addGlobalConstantType($const_id, Type\Union $type) { self::$stubbed_constants[$const_id] = $type; } diff --git a/src/Psalm/Codebase/Functions.php b/src/Psalm/Codebase/Functions.php index a9d752f2508..6471c21599f 100644 --- a/src/Psalm/Codebase/Functions.php +++ b/src/Psalm/Codebase/Functions.php @@ -104,7 +104,7 @@ public function getStorage($statements_checker, $function_id) * * @return void */ - public function addStubbedFunction($function_id, FunctionLikeStorage $storage) + public function addGlobalFunction($function_id, FunctionLikeStorage $storage) { self::$stubbed_functions[strtolower($function_id)] = $storage; } diff --git a/src/Psalm/Codebase/Scanner.php b/src/Psalm/Codebase/Scanner.php index 8c8028438e1..90c0e8991a0 100644 --- a/src/Psalm/Codebase/Scanner.php +++ b/src/Psalm/Codebase/Scanner.php @@ -344,9 +344,13 @@ private function scanFile( $this->queueClassLikeForScanning($fq_classlike_name, $file_path, false, false); } - if ($this->codebase->register_global_functions) { + if ($this->codebase->register_autoload_files) { foreach ($file_storage->functions as $function_storage) { - $this->codebase->functions->addStubbedFunction($function_storage->cased_name, $function_storage); + $this->codebase->functions->addGlobalFunction($function_storage->cased_name, $function_storage); + } + + foreach ($file_storage->constants as $name => $type) { + $this->codebase->addGlobalConstantType($name, $type); } } } diff --git a/src/Psalm/Config.php b/src/Psalm/Config.php index 3368a9d0fc5..eb3983d4bb6 100644 --- a/src/Psalm/Config.php +++ b/src/Psalm/Config.php @@ -1010,11 +1010,13 @@ public function getMockClasses() } /** + * @param bool $debug + * * @return void */ - public function visitStubFiles(Codebase $codebase) + public function visitStubFiles(Codebase $codebase, $debug = false) { - $codebase->register_global_functions = true; + $codebase->register_stub_files = true; // note: don't realpath $generic_stubs_path, or phar version will fail $generic_stubs_path = __DIR__ . '/Stubs/CoreGenericFunctions.php'; @@ -1032,16 +1034,21 @@ public function visitStubFiles(Codebase $codebase) $stub_files = array_merge([$generic_stubs_path, $generic_classes_path], $this->stub_files); - foreach ($stub_files as $stub_file_path) { - $file_storage = $codebase->createFileStorageForPath($stub_file_path); - $file_to_scan = new FileScanner($stub_file_path, $this->shortenFileName($stub_file_path), false); - $file_to_scan->scan( - $codebase, - $file_storage - ); + foreach ($stub_files as $file_path) { + $codebase->scanner->addFileToShallowScan($file_path); + } + + if ($debug) { + echo 'Registering stub files' . "\n"; + } + + $codebase->scanFiles(); + + if ($debug) { + echo 'Finished registering stub files' . "\n"; } - $codebase->register_global_functions = false; + $codebase->register_stub_files = false; } /** @@ -1101,12 +1108,14 @@ public function collectPredefinedFunctions() } /** + * @param bool $debug + * * @return void * * @psalm-suppress MixedAssignment * @psalm-suppress MixedArrayAccess */ - public function visitComposerAutoloadFiles(ProjectChecker $project_checker) + public function visitComposerAutoloadFiles(ProjectChecker $project_checker, $debug = false) { $composer_json_path = $this->base_dir . 'composer.json'; // this should ideally not be hardcoded @@ -1152,15 +1161,23 @@ public function visitComposerAutoloadFiles(ProjectChecker $project_checker) if ($autoload_files_files) { $codebase = $project_checker->codebase; - $codebase->register_global_functions = true; + $codebase->register_autoload_files = true; foreach ($autoload_files_files as $file_path) { - $codebase->scanner->addFileToShallowScan($file_path); + $codebase->scanner->addFileToDeepScan($file_path); + } + + if ($debug) { + echo 'Registering autoloaded files' . "\n"; } $codebase->scanner->scanFiles($codebase->classlikes); - $project_checker->codebase->register_global_functions = false; + if ($debug) { + echo 'Finished registering autoloaded files' . "\n"; + } + + $project_checker->codebase->register_autoload_files = false; } } diff --git a/src/Psalm/Scanner/FileScanner.php b/src/Psalm/Scanner/FileScanner.php index e3848155b14..859e2d0fdf8 100644 --- a/src/Psalm/Scanner/FileScanner.php +++ b/src/Psalm/Scanner/FileScanner.php @@ -53,6 +53,7 @@ public function scan( if ((!$this->will_analyze || $file_storage->deep_scan) && $storage_from_cache && !$file_storage->has_trait + && !$codebase->register_stub_files ) { return; } diff --git a/src/Psalm/Visitor/DependencyFinderVisitor.php b/src/Psalm/Visitor/DependencyFinderVisitor.php index 4e9399aaae3..69a795c2c70 100644 --- a/src/Psalm/Visitor/DependencyFinderVisitor.php +++ b/src/Psalm/Visitor/DependencyFinderVisitor.php @@ -157,6 +157,8 @@ public function enterNode(PhpParser\Node $node) } elseif ($node instanceof PhpParser\Node\Stmt\ClassLike) { $class_location = new CodeLocation($this->file_scanner, $node, null, true); + $storage = null; + if ($node->name === null) { if (!$node instanceof PhpParser\Node\Stmt\Class_) { throw new \LogicException('Anonymous classes are always classes'); @@ -167,34 +169,34 @@ public function enterNode(PhpParser\Node $node) $fq_classlike_name = ($this->aliases->namespace ? $this->aliases->namespace . '\\' : '') . $node->name->name; - if ($this->codebase->classlike_storage_provider->has($fq_classlike_name) - && !$this->codebase->register_global_functions - ) { - $other_file_path = null; - - try { - $other_file_path = $this->codebase->scanner->getClassLikeFilePath($fq_classlike_name); - } catch (\UnexpectedValueException $e) { - // do nothing - } - - $duplicate_class_storage = $this->codebase->classlike_storage_provider->get($fq_classlike_name); + if ($this->codebase->classlike_storage_provider->has($fq_classlike_name)) { + $duplicate_storage = $this->codebase->classlike_storage_provider->get($fq_classlike_name); + + if (!$this->codebase->register_stub_files) { + if (!$duplicate_storage->location + || $duplicate_storage->location->file_path !== $this->file_path + || $class_location->getHash() !== $duplicate_storage->location->getHash() + ) { + if (IssueBuffer::accepts( + new DuplicateClass( + 'Class ' . $fq_classlike_name . ' has already been defined' + . ($duplicate_storage->location + ? ' in ' . $duplicate_storage->location->file_path + : ''), + new CodeLocation($this->file_scanner, $node, null, true) + ) + )) { + $this->file_storage->has_visitor_issues = true; + } - if (!$duplicate_class_storage->location - || $duplicate_class_storage->location->file_path !== $this->file_path - || $class_location->getHash() !== $duplicate_class_storage->location->getHash() - ) { - if (IssueBuffer::accepts( - new DuplicateClass( - 'Class ' . $fq_classlike_name . ' has already been defined' - . ($other_file_path ? ' in ' . $other_file_path : ''), - new CodeLocation($this->file_scanner, $node, null, true) - ) - )) { - $this->file_storage->has_visitor_issues = true; + return PhpParser\NodeTraverser::STOP_TRAVERSAL; } - - return PhpParser\NodeTraverser::STOP_TRAVERSAL; + } elseif ($duplicate_storage->location + && ($duplicate_storage->location->file_path !== $this->file_path + || $class_location->getHash() !== $duplicate_storage->location->getHash()) + ) { + // we're overwriting some methods + $storage = $duplicate_storage; } } } @@ -205,11 +207,13 @@ public function enterNode(PhpParser\Node $node) $this->fq_classlike_names[] = $fq_classlike_name; - $storage = $this->codebase->createClassLikeStorage($fq_classlike_name); + if (!$storage) { + $storage = $this->codebase->createClassLikeStorage($fq_classlike_name); + } $storage->location = $class_location; - $storage->user_defined = !$this->codebase->register_global_functions; - $storage->stubbed = $this->codebase->register_global_functions; + $storage->user_defined = !$this->codebase->register_stub_files; + $storage->stubbed = $this->codebase->register_stub_files; $doc_comment = $node->getDocComment(); @@ -553,14 +557,14 @@ public function enterNode(PhpParser\Node $node) $const_type = StatementsChecker::getSimpleType($this->codebase, $const->value, $this->aliases) ?: Type::getMixed(); - if ($this->codebase->register_global_functions) { - $this->codebase->addStubbedConstantType($const->name->name, $const_type); + if ($this->codebase->register_stub_files || $this->codebase->register_autoload_files) { + $this->codebase->addGlobalConstantType($const->name->name, $const_type); } $this->file_storage->constants[$const->name->name] = $const_type; $this->file_storage->declaring_constants[$const->name->name] = $this->file_path; } - } elseif ($this->codebase->register_global_functions && $node instanceof PhpParser\Node\Stmt\If_) { + } elseif ($this->codebase->register_autoload_files && $node instanceof PhpParser\Node\Stmt\If_) { if ($node->cond instanceof PhpParser\Node\Expr\BooleanNot) { if ($node->cond->expr instanceof PhpParser\Node\Expr\FuncCall && $node->cond->expr->name instanceof PhpParser\Node\Name @@ -689,13 +693,20 @@ private function registerFunctionLike(PhpParser\Node\FunctionLike $stmt, $fake_m $function_id = strtolower($cased_function_id); if (isset($this->file_storage->functions[$function_id])) { + if ($this->codebase->register_stub_files || $this->codebase->register_autoload_files) { + $this->codebase->functions->addGlobalFunction( + $function_id, + $this->file_storage->functions[$function_id] + ); + } + return $this->file_storage->functions[$function_id]; } $storage = new FunctionLikeStorage(); - if ($this->codebase->register_global_functions) { - $this->codebase->functions->addStubbedFunction($function_id, $storage); + if ($this->codebase->register_stub_files || $this->codebase->register_autoload_files) { + $this->codebase->functions->addGlobalFunction($function_id, $storage); } $this->file_storage->functions[$function_id] = $storage; @@ -716,11 +727,19 @@ private function registerFunctionLike(PhpParser\Node\FunctionLike $stmt, $fake_m $class_storage = $this->classlike_storages[count($this->classlike_storages) - 1]; + $storage = null; + if (isset($class_storage->methods[strtolower($stmt->name->name)])) { - throw new \InvalidArgumentException('Cannot re-register ' . $function_id); + if (!$this->codebase->register_stub_files) { + throw new \InvalidArgumentException('Cannot re-register ' . $function_id); + } + + $storage = $class_storage->methods[strtolower($stmt->name->name)]; } - $storage = $class_storage->methods[strtolower($stmt->name->name)] = new MethodStorage(); + if (!$storage) { + $storage = $class_storage->methods[strtolower($stmt->name->name)] = new MethodStorage(); + } $class_name_parts = explode('\\', $fq_classlike_name); $class_name = array_pop($class_name_parts); @@ -786,6 +805,7 @@ private function registerFunctionLike(PhpParser\Node\FunctionLike $stmt, $fake_m $has_optional_param = false; $existing_params = []; + $storage->params = []; /** @var PhpParser\Node\Param $param */ foreach ($stmt->getParams() as $param) { diff --git a/src/psalm.php b/src/psalm.php index 65ee32a9056..b4f5e818871 100644 --- a/src/psalm.php +++ b/src/psalm.php @@ -315,6 +315,8 @@ function ($arg) { exit; } +$debug = array_key_exists('debug', $options) || array_key_exists('debug-by-line', $options); + $project_checker = new ProjectChecker( $config, new Psalm\Provider\FileProvider(), @@ -325,12 +327,12 @@ function ($arg) { $show_info, $output_format, $threads, - array_key_exists('debug', $options) || array_key_exists('debug-by-line', $options), + $debug, isset($options['report']) && is_string($options['report']) ? $options['report'] : null, !isset($options['show-snippet']) || $options['show-snippet'] !== "false" ); -$config->visitComposerAutoloadFiles($project_checker); +$config->visitComposerAutoloadFiles($project_checker, $debug); if (array_key_exists('debug-by-line', $options)) { $project_checker->debug_lines = true; diff --git a/tests/ArrayAccessTest.php b/tests/ArrayAccessTest.php index fdbaaff50d3..c114468344c 100644 --- a/tests/ArrayAccessTest.php +++ b/tests/ArrayAccessTest.php @@ -141,6 +141,14 @@ function takesInt(int $i) : void {} $doc->loadXML(""); $doc->getElementsByTagName("node")[0];' ], + 'getOnArrayAcccess' => [ + ' [], + 'error_levels' => ['MixedArgument'], + ], ]; } diff --git a/tests/ConfigTest.php b/tests/ConfigTest.php index 71d2fec44c9..9869910b008 100644 --- a/tests/ConfigTest.php +++ b/tests/ConfigTest.php @@ -386,361 +386,6 @@ public function testImpossibleIssue() ); } - /** - * @expectedException \Psalm\Exception\ConfigException - * @expectedExceptionMessage Cannot resolve stubfile path - * - * @return void - */ - public function testNonexistentStubFile() - { - $this->project_checker = $this->getProjectCheckerWithConfig( - Config::loadFromXML( - dirname(__DIR__), - ' - - - - - - - - - ' - ) - ); - } - - /** - * @return void - */ - public function testStubFile() - { - $this->project_checker = $this->getProjectCheckerWithConfig( - TestConfig::loadFromXML( - dirname(__DIR__), - ' - - - - - - - - - ' - ) - ); - - $file_path = getcwd() . '/src/somefile.php'; - - $this->addFile( - $file_path, - 'foo(5, "hello"); - $c = SystemClass::bar(5, "hello");' - ); - - $this->analyzeFile($file_path, new Context()); - } - - /** - * @return void - */ - public function testNamespacedStubClass() - { - $this->project_checker = $this->getProjectCheckerWithConfig( - TestConfig::loadFromXML( - dirname(__DIR__), - ' - - - - - - - - - ' - ) - ); - - $file_path = getcwd() . '/src/somefile.php'; - - $this->addFile( - $file_path, - 'foo(5, "hello"); - $c = Foo\SystemClass::bar(5, "hello");' - ); - - $this->analyzeFile($file_path, new Context()); - } - - /** - * @return void - */ - public function testStubFunction() - { - $this->project_checker = $this->getProjectCheckerWithConfig( - TestConfig::loadFromXML( - dirname(__DIR__), - ' - - - - - - - - - ' - ) - ); - - $file_path = getcwd() . '/src/somefile.php'; - - $this->addFile( - $file_path, - 'analyzeFile($file_path, new Context()); - } - - /** - * @return void - */ - public function testPolyfilledFunction() - { - $this->project_checker = $this->getProjectCheckerWithConfig( - TestConfig::loadFromXML( - dirname(__DIR__), - ' - - - - - - - - - ' - ) - ); - - $file_path = getcwd() . '/src/somefile.php'; - - $this->addFile( - $file_path, - 'analyzeFile($file_path, new Context()); - } - - /** - * @return void - */ - public function testStubFunctionWithFunctionExists() - { - $this->project_checker = $this->getProjectCheckerWithConfig( - TestConfig::loadFromXML( - dirname(__DIR__), - ' - - - - - - - - - ' - ) - ); - - $file_path = getcwd() . '/src/somefile.php'; - - $this->addFile( - $file_path, - 'analyzeFile($file_path, new Context()); - } - - /** - * @return void - */ - public function testNamespacedStubFunctionWithFunctionExists() - { - $this->project_checker = $this->getProjectCheckerWithConfig( - TestConfig::loadFromXML( - dirname(__DIR__), - ' - - - - - - - - - ' - ) - ); - - $file_path = getcwd() . '/src/somefile.php'; - - $this->addFile( - $file_path, - '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( - dirname(__DIR__), - ' - - - - - ' - ) - ); - - $file_path = getcwd() . '/src/somefile.php'; - - $this->addFile( - $file_path, - 'analyzeFile($file_path, new Context()); - } - - /** - * @return void - */ - public function testNamespacedStubFunction() - { - $this->project_checker = $this->getProjectCheckerWithConfig( - TestConfig::loadFromXML( - dirname(__DIR__), - ' - - - - - - - - - ' - ) - ); - - $file_path = getcwd() . '/src/somefile.php'; - - $this->addFile( - $file_path, - 'analyzeFile($file_path, new Context()); - } - - /** - * @return void - */ - public function testConditionalNamespacedStubFunction() - { - $this->project_checker = $this->getProjectCheckerWithConfig( - TestConfig::loadFromXML( - dirname(__DIR__), - ' - - - - - - - - - ' - ) - ); - - $file_path = getcwd() . '/src/somefile.php'; - - $this->addFile( - $file_path, - 'analyzeFile($file_path, new Context()); - } - - /** - * @return void - */ - public function testStubFileWithExistingClassDefinition() - { - $this->project_checker = $this->getProjectCheckerWithConfig( - TestConfig::loadFromXML( - dirname(__DIR__), - ' - - - - - - - - - ' - ) - ); - - $file_path = getcwd() . '/src/somefile.php'; - - $this->addFile( - $file_path, - 'analyzeFile($file_path, new Context()); - } - /** * @expectedException \Psalm\Exception\CodeException * @expectedExceptionMessage MissingReturnType diff --git a/tests/FileManipulationTest.php b/tests/FileManipulationTest.php index c0ec92d697d..efe0cddd71b 100644 --- a/tests/FileManipulationTest.php +++ b/tests/FileManipulationTest.php @@ -46,10 +46,6 @@ public function testValidCode($input_code, $output_code, $php_version, array $is $config = new TestConfig(); - if (empty($issues_to_fix)) { - $config->addPluginPath('examples/ClassUnqualifier.php'); - } - $this->project_checker = new \Psalm\Checker\ProjectChecker( $config, $this->file_provider, @@ -58,6 +54,11 @@ public function testValidCode($input_code, $output_code, $php_version, array $is new \Psalm\Provider\NoCache\NoClassLikeStorageCacheProvider() ); + if (empty($issues_to_fix)) { + $config->addPluginPath('examples/ClassUnqualifier.php'); + $config->initializePlugins($this->project_checker); + } + $context = new Context(); $file_path = self::$src_dir_path . 'somefile.php'; diff --git a/tests/StubTest.php b/tests/StubTest.php new file mode 100644 index 00000000000..df502e22ee4 --- /dev/null +++ b/tests/StubTest.php @@ -0,0 +1,550 @@ +file_provider = new Provider\FakeFileProvider(); + } + + /** + * @param Config $config + * + * @return \Psalm\Checker\ProjectChecker + */ + private function getProjectCheckerWithConfig(Config $config) + { + return new \Psalm\Checker\ProjectChecker( + $config, + $this->file_provider, + new Provider\FakeParserCacheProvider(), + new \Psalm\Provider\NoCache\NoFileStorageCacheProvider(), + new \Psalm\Provider\NoCache\NoClassLikeStorageCacheProvider() + ); + } + + /** + * @expectedException \Psalm\Exception\ConfigException + * @expectedExceptionMessage Cannot resolve stubfile path + * + * @return void + */ + public function testNonexistentStubFile() + { + $this->project_checker = $this->getProjectCheckerWithConfig( + Config::loadFromXML( + dirname(__DIR__), + ' + + + + + + + + + ' + ) + ); + } + + /** + * @return void + */ + public function testStubFile() + { + $this->project_checker = $this->getProjectCheckerWithConfig( + TestConfig::loadFromXML( + dirname(__DIR__), + ' + + + + + + + + + ' + ) + ); + + $file_path = getcwd() . '/src/somefile.php'; + + $this->addFile( + $file_path, + 'foo(5, "hello"); + $c = SystemClass::bar(5, "hello");' + ); + + $this->analyzeFile($file_path, new Context()); + } + + /** + * @return void + */ + public function testNamespacedStubClass() + { + $this->project_checker = $this->getProjectCheckerWithConfig( + TestConfig::loadFromXML( + dirname(__DIR__), + ' + + + + + + + + + ' + ) + ); + + $file_path = getcwd() . '/src/somefile.php'; + + $this->addFile( + $file_path, + 'foo(5, "hello"); + $c = Foo\SystemClass::bar(5, "hello");' + ); + + $this->analyzeFile($file_path, new Context()); + } + + /** + * @return void + */ + public function testStubFunction() + { + $this->project_checker = $this->getProjectCheckerWithConfig( + TestConfig::loadFromXML( + dirname(__DIR__), + ' + + + + + + + + + ' + ) + ); + + $file_path = getcwd() . '/src/somefile.php'; + + $this->addFile( + $file_path, + 'analyzeFile($file_path, new Context()); + } + + /** + * @return void + */ + public function testPolyfilledFunction() + { + $this->project_checker = $this->getProjectCheckerWithConfig( + TestConfig::loadFromXML( + dirname(__DIR__), + ' + + + + + ' + ) + ); + + $file_path = getcwd() . '/src/somefile.php'; + + $this->addFile( + $file_path, + 'analyzeFile($file_path, new Context()); + } + + /** + * @return void + */ + public function testStubFunctionWithFunctionExists() + { + $this->project_checker = $this->getProjectCheckerWithConfig( + TestConfig::loadFromXML( + dirname(__DIR__), + ' + + + + + + + + + ' + ) + ); + + $file_path = getcwd() . '/src/somefile.php'; + + $this->addFile( + $file_path, + 'analyzeFile($file_path, new Context()); + } + + /** + * @return void + */ + public function testNamespacedStubFunctionWithFunctionExists() + { + $this->project_checker = $this->getProjectCheckerWithConfig( + TestConfig::loadFromXML( + dirname(__DIR__), + ' + + + + + + + + + ' + ) + ); + + $file_path = getcwd() . '/src/somefile.php'; + + $this->addFile( + $file_path, + '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( + dirname(__DIR__), + ' + + + + + ' + ) + ); + + $file_path = getcwd() . '/src/somefile.php'; + + $this->addFile( + $file_path, + 'analyzeFile($file_path, new Context()); + } + + /** + * @return void + */ + public function testNamespacedStubFunction() + { + $this->project_checker = $this->getProjectCheckerWithConfig( + TestConfig::loadFromXML( + dirname(__DIR__), + ' + + + + + + + + + ' + ) + ); + + $file_path = getcwd() . '/src/somefile.php'; + + $this->addFile( + $file_path, + 'analyzeFile($file_path, new Context()); + } + + /** + * @return void + */ + public function testConditionalNamespacedStubFunction() + { + $this->project_checker = $this->getProjectCheckerWithConfig( + TestConfig::loadFromXML( + dirname(__DIR__), + ' + + + + + + + + + ' + ) + ); + + $file_path = getcwd() . '/src/somefile.php'; + + $this->addFile( + $file_path, + 'analyzeFile($file_path, new Context()); + } + + /** + * @return void + */ + public function testStubFileWithExistingClassDefinition() + { + $this->project_checker = $this->getProjectCheckerWithConfig( + TestConfig::loadFromXML( + dirname(__DIR__), + ' + + + + + + + + + ' + ) + ); + + $file_path = getcwd() . '/src/somefile.php'; + + $this->addFile( + $file_path, + 'analyzeFile($file_path, new Context()); + } + + /** + * @return void + */ + public function testStubFileWithPartialClassDefinitionWithMoreMethods() + { + $this->project_checker = $this->getProjectCheckerWithConfig( + TestConfig::loadFromXML( + dirname(__DIR__), + ' + + + + + + + + + ' + ) + ); + + $file_path = getcwd() . '/src/somefile.php'; + + $this->addFile( + $file_path, + 'foo(A::class); + (new PartiallyStubbedClass())->bar(5);' + ); + + $this->analyzeFile($file_path, new Context()); + } + + /** + * @expectedException \Psalm\Exception\CodeException + * @expectedExceptionMessage TypeCoercion + * + * @return void + */ + public function testStubFileWithPartialClassDefinitionWithCoercion() + { + $this->project_checker = $this->getProjectCheckerWithConfig( + TestConfig::loadFromXML( + dirname(__DIR__), + ' + + + + + + + + + ' + ) + ); + + $file_path = getcwd() . '/src/somefile.php'; + + $this->addFile( + $file_path, + 'foo("dasda");' + ); + + $this->analyzeFile($file_path, new Context()); + } + + /** + * @expectedException \Psalm\Exception\CodeException + * @expectedExceptionMessage InvalidReturnStatement + * + * @return void + */ + public function testStubFileWithPartialClassDefinitionGeneralReturnType() + { + $this->project_checker = $this->getProjectCheckerWithConfig( + TestConfig::loadFromXML( + dirname(__DIR__), + ' + + + + + + + + + ' + ) + ); + + $file_path = getcwd() . '/src/somefile.php'; + + $this->addFile( + $file_path, + 'analyzeFile($file_path, new Context()); + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php index c020886bb46..986ec2adc07 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -90,6 +90,8 @@ public function analyzeFile($file_path, \Psalm\Context $context) $codebase->scanFiles(); + $codebase->config->visitStubFiles($codebase); + $file_checker = new FileChecker( $this->project_checker, $file_path, diff --git a/tests/stubs/partial_class.php b/tests/stubs/partial_class.php new file mode 100644 index 00000000000..16049b67dd6 --- /dev/null +++ b/tests/stubs/partial_class.php @@ -0,0 +1,11 @@ +