diff --git a/.travis.yml b/.travis.yml index f99927298..1380e6da0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,7 @@ install: script: - vendor/bin/phpunit - - bin/psalm + - ./psalm - vendor/bin/phpcs after_success: diff --git a/composer.json b/composer.json index 5666101c8..bf469f3bd 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,7 @@ "openlss/lib-array2xml": "^0.0.10||^0.5.1", "muglug/package-versions": "^1.2.1" }, - "bin": ["bin/psalm"], + "bin": ["psalm"], "autoload": { "psr-4": { "Psalm\\": "src/Psalm" diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 595700027..2425f21df 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -38,6 +38,7 @@ ./src/Psalm/Stubs/ ./src/Psalm/CallMap.php + ./src/psalm.php ./src/Psalm/PropertyMap.php ./src/Psalm/Issue/ diff --git a/psalm b/psalm new file mode 100755 index 000000000..b6546edd8 --- /dev/null +++ b/psalm @@ -0,0 +1,2 @@ +#!/usr/bin/env php + + + + + + + + + @@ -46,6 +54,7 @@ + diff --git a/src/Psalm/CallMap.php b/src/Psalm/CallMap.php index f26ce97c3..345236a94 100644 --- a/src/Psalm/CallMap.php +++ b/src/Psalm/CallMap.php @@ -2507,7 +2507,7 @@ return [ 'getmypid' => ['int'], 'getmyuid' => ['int'], 'get_object_vars' => ['array', 'obj'=>'object'], -'getopt' => ['array', 'options'=>'string', 'longopts='=>'array', '&w_optind='=>'int'], +'getopt' => ['(string|false|array)[]', 'options'=>'string', 'longopts='=>'array', '&w_optind='=>'int'], 'get_parent_class' => ['', 'object='=>''], 'getprotobyname' => ['int|false', 'name'=>'string'], 'getprotobynumber' => ['string', 'proto'=>'int'], @@ -7922,7 +7922,7 @@ return [ 'SCA::createDataObject' => ['SDO_DataObject', 'type_namespace_uri'=>'string', 'type_name'=>'string'], 'SCA::getService' => ['', 'target'=>'string', 'binding='=>'string', 'config='=>'array'], 'SCA_LocalProxy::createDataObject' => ['SDO_DataObject', 'type_namespace_uri'=>'string', 'type_name'=>'string'], -'scandir' => ['array', 'dir'=>'string', 'sorting_order='=>'int', 'context='=>''], +'scandir' => ['string[]|false', 'dir'=>'string', 'sorting_order='=>'int', 'context='=>''], 'SCA_SoapProxy::createDataObject' => ['SDO_DataObject', 'type_namespace_uri'=>'string', 'type_name'=>'string'], 'SDO_DAS_ChangeSummary::beginLogging' => [''], 'SDO_DAS_ChangeSummary::endLogging' => [''], diff --git a/src/Psalm/Checker/FileChecker.php b/src/Psalm/Checker/FileChecker.php index 47710e984..b232d5422 100644 --- a/src/Psalm/Checker/FileChecker.php +++ b/src/Psalm/Checker/FileChecker.php @@ -387,18 +387,6 @@ class FileChecker extends SourceChecker implements StatementsSource $this->analyze($file_context); } - /** - * Used when checking single files with multiple classlike declarations - * - * @param string $fq_class_name - * - * @return bool - */ - public function containsUnEvaluatedClassLike($fq_class_name) - { - return false; - } - /** * @return array */ diff --git a/src/Psalm/Checker/FunctionLikeChecker.php b/src/Psalm/Checker/FunctionLikeChecker.php index fac0a2914..c95b612c7 100644 --- a/src/Psalm/Checker/FunctionLikeChecker.php +++ b/src/Psalm/Checker/FunctionLikeChecker.php @@ -179,8 +179,6 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo } if ($overridden_method_ids && $this->function->name !== '__construct') { - $have_emitted = false; - foreach ($overridden_method_ids as $overridden_method_id) { $parent_method_storage = MethodChecker::getStorage($project_checker, $overridden_method_id); @@ -564,7 +562,6 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo ) { $implementer_method_id = $implementer_classlike_storage->name . '::' . strtolower($guide_method_storage->cased_name); - $guide_method_id = $guide_classlike_storage->name . '::' . strtolower($guide_method_storage->cased_name); $implementer_declaring_method_id = MethodChecker::getDeclaringMethodId( $project_checker, $implementer_method_id diff --git a/src/Psalm/Checker/Statements/Block/TryChecker.php b/src/Psalm/Checker/Statements/Block/TryChecker.php index 5562e8695..641d5d92b 100644 --- a/src/Psalm/Checker/Statements/Block/TryChecker.php +++ b/src/Psalm/Checker/Statements/Block/TryChecker.php @@ -115,8 +115,6 @@ class TryChecker } } - $exception_type = new Union([new TNamedObject('Exception'), new TNamedObject('Throwable')]); - if ((ClassChecker::classExists($project_checker, $fq_catch_class) && strtolower($fq_catch_class) !== 'exception' && !(ClassChecker::classExtends($project_checker, $fq_catch_class, 'Exception') diff --git a/src/Psalm/Checker/Statements/Expression/FetchChecker.php b/src/Psalm/Checker/Statements/Expression/FetchChecker.php index 861e82a5b..d38b7b65b 100644 --- a/src/Psalm/Checker/Statements/Expression/FetchChecker.php +++ b/src/Psalm/Checker/Statements/Expression/FetchChecker.php @@ -438,6 +438,10 @@ class FetchChecker $stmt->inferredType = Type::getTrue(); break; + case 'stdin': + $stmt->inferredType = Type::getResource(); + break; + default: $const_type = $statements_checker->getConstType( $statements_checker, @@ -509,15 +513,6 @@ class FetchChecker $statements_checker->getAliases() ); - // edge case when evaluating single files - if ($stmt->name === 'class' && - $statements_checker->getFileChecker()->containsUnEvaluatedClassLike($fq_class_name) - ) { - $stmt->inferredType = Type::getString(); - - return null; - } - if (ClassLikeChecker::checkFullyQualifiedClassLikeName( $statements_checker->getFileChecker()->project_checker, $fq_class_name, diff --git a/src/Psalm/Checker/TraitChecker.php b/src/Psalm/Checker/TraitChecker.php index 2d6948fbe..d77ab5387 100644 --- a/src/Psalm/Checker/TraitChecker.php +++ b/src/Psalm/Checker/TraitChecker.php @@ -6,11 +6,6 @@ use Psalm\TraitSource; class TraitChecker extends ClassLikeChecker { - /** - * @var array - */ - private $method_map = []; - /** * @param PhpParser\Node\Stmt\Trait_ $class * @param TraitSource $trait_source @@ -27,16 +22,6 @@ class TraitChecker extends ClassLikeChecker self::$trait_checkers[strtolower($fq_class_name)] = $this; } - /** - * @param array $method_map - * - * @return void - */ - public function setMethodMap(array $method_map) - { - $this->method_map = $method_map; - } - /** * @param string $fq_trait_name * @param FileChecker $file_checker diff --git a/src/Psalm/Config.php b/src/Psalm/Config.php index 1378fc301..8a04f9952 100644 --- a/src/Psalm/Config.php +++ b/src/Psalm/Config.php @@ -754,4 +754,33 @@ class Config return $composer_classmap; } + + /** + * @param string $dir + * + * @return void + */ + public static function removeCacheDirectory($dir) + { + if (is_dir($dir)) { + $objects = scandir($dir); + + if ($objects === false) { + throw new \UnexpectedValueException('Not expecting false here'); + } + + foreach ($objects as $object) { + if ($object != '.' && $object != '..') { + if (filetype($dir . '/' . $object) == 'dir') { + self::removeCacheDirectory($dir . '/' . $object); + } else { + unlink($dir . '/' . $object); + } + } + } + + reset($objects); + rmdir($dir); + } + } } diff --git a/src/Psalm/IssueBuffer.php b/src/Psalm/IssueBuffer.php index 895a522ba..955118984 100644 --- a/src/Psalm/IssueBuffer.php +++ b/src/Psalm/IssueBuffer.php @@ -35,7 +35,7 @@ class IssueBuffer protected static $recorded_issues = []; /** - * @var int + * @var float */ protected static $start_time = 0; @@ -301,7 +301,7 @@ class IssueBuffer } /** - * @param int $time + * @param float $time * * @return void */ diff --git a/src/Psalm/Type.php b/src/Psalm/Type.php index 5472fb609..f38a19e2a 100644 --- a/src/Psalm/Type.php +++ b/src/Psalm/Type.php @@ -16,6 +16,7 @@ use Psalm\Type\Atomic\TMixed; use Psalm\Type\Atomic\TNamedObject; use Psalm\Type\Atomic\TNull; use Psalm\Type\Atomic\TObject; +use Psalm\Type\Atomic\TResource; use Psalm\Type\Atomic\TString; use Psalm\Type\Atomic\TTrue; use Psalm\Type\Atomic\TVoid; @@ -426,6 +427,14 @@ abstract class Type return new Union([$type]); } + /** + * @return Type\Union + */ + public static function getResource() + { + return new Union([new TResource]); + } + /** * @param array $redefined_vars * @param Context $context diff --git a/src/Psalm/Type/Atomic/GenericTrait.php b/src/Psalm/Type/Atomic/GenericTrait.php index 0bd9e1ec0..c8384c272 100644 --- a/src/Psalm/Type/Atomic/GenericTrait.php +++ b/src/Psalm/Type/Atomic/GenericTrait.php @@ -128,7 +128,7 @@ trait GenericTrait */ public function replaceTemplateTypesWithArgTypes(array $template_types) { - foreach ($this->type_params as $offset => $type_param) { + foreach ($this->type_params as $type_param) { $type_param->replaceTemplateTypesWithArgTypes($template_types); } } diff --git a/src/Psalm/Type/ParseTree.php b/src/Psalm/Type/ParseTree.php index 44aeac276..e3aa2d1a8 100644 --- a/src/Psalm/Type/ParseTree.php +++ b/src/Psalm/Type/ParseTree.php @@ -149,7 +149,6 @@ class ParseTree array_pop($current_parent->children); $current_parent->children[] = $new_parent_leaf; - $has_object_key = true; break; diff --git a/bin/psalm b/src/psalm.php old mode 100755 new mode 100644 similarity index 84% rename from bin/psalm rename to src/psalm.php index a0b8a9c28..e652fdf50 --- a/bin/psalm +++ b/src/psalm.php @@ -1,7 +1,5 @@ -#!/usr/bin/env php $path_to_check) { if ($path_to_check[0] === '-') { die('Invalid usage, expecting psalm [options] [file...]' . PHP_EOL); @@ -305,7 +312,13 @@ if ($input_paths) { die('Cannot locate ' . $path_to_check . PHP_EOL); } - $paths_to_check[] = realpath($path_to_check); + $path_to_check = realpath($path_to_check); + + if (!$path_to_check) { + die('Error getting realpath for file' . PHP_EOL); + } + + $paths_to_check[] = $path_to_check; } if (!$paths_to_check) { @@ -313,10 +326,11 @@ if ($input_paths) { } } -$path_to_config = isset($options['c']) ? realpath($options['c']) : null; +$path_to_config = isset($options['c']) && is_string($options['c']) ? realpath($options['c']) : null; if ($path_to_config === false) { - die('Could not resolve path to config ' . $options['c'] . PHP_EOL); + /** @psalm-suppress InvalidCast */ + die('Could not resolve path to config ' . (string)$options['c'] . PHP_EOL); } $use_color = !array_key_exists('m', $options); @@ -329,7 +343,9 @@ $is_diff = isset($options['diff']); $find_dead_code = isset($options['find-dead-code']); -$find_references_to = isset($options['find-references-to']) ? $options['find-references-to'] : null; +$find_references_to = isset($options['find-references-to']) && is_string($options['find-references-to']) + ? $options['find-references-to'] + : null; $update_docblocks = isset($options['update-docblocks']); @@ -350,7 +366,7 @@ $project_checker = new ProjectChecker( $update_docblocks, $find_dead_code || $find_references_to !== null, $find_references_to, - isset($options['report'])?$options['report']:null + isset($options['report']) && is_string($options['report']) ? $options['report'] : null ); // initialise custom config, if passed @@ -358,46 +374,24 @@ if ($path_to_config) { $project_checker->setConfigXML($path_to_config, $current_dir); } -/** - * @param string $dir - * @return void - */ -function rrmdir($dir) -{ - if (is_dir($dir)) { - $objects = scandir($dir); - foreach ($objects as $object) { - if ($object != "." && $object != "..") { - if (filetype($dir."/".$object) == "dir") { - rrmdir($dir."/".$object); - } else { - unlink($dir."/".$object); - } - } - } - - reset($objects); - rmdir($dir); - } -} - if (isset($options['clear-cache'])) { // initialise config if it hasn't already been if (!$path_to_config) { - $project_checker->getConfigForPath($current_dir, $current_dir); + $project_checker->getConfigForPath($current_dir, $current_dir); } $cache_directory = Config::getInstance()->getCacheDirectory(); - rrmdir($cache_directory); + Config::removeCacheDirectory($cache_directory); echo 'Cache directory deleted' . PHP_EOL; exit; } +/** @psalm-suppress MixedArgument */ \Psalm\IssueBuffer::setStartTime(microtime(true)); if (array_key_exists('self-check', $options)) { - $project_checker->checkDir(dirname(__DIR__) . '/src', dirname(__DIR__)); + $project_checker->checkDir(__DIR__, dirname(__DIR__)); } elseif ($paths_to_check === null) { $project_checker->check($current_dir, $is_diff); } elseif ($paths_to_check) {