1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-26 12:24:49 +01:00

Move Psalm execution code into PHP file that Psalm can analyse

This commit is contained in:
Matthew Brown 2017-12-29 12:29:36 -05:00
parent 0b58ee425d
commit 8efc939a5f
17 changed files with 98 additions and 92 deletions

View File

@ -14,7 +14,7 @@ install:
script: script:
- vendor/bin/phpunit - vendor/bin/phpunit
- bin/psalm - ./psalm
- vendor/bin/phpcs - vendor/bin/phpcs
after_success: after_success:

View File

@ -16,7 +16,7 @@
"openlss/lib-array2xml": "^0.0.10||^0.5.1", "openlss/lib-array2xml": "^0.0.10||^0.5.1",
"muglug/package-versions": "^1.2.1" "muglug/package-versions": "^1.2.1"
}, },
"bin": ["bin/psalm"], "bin": ["psalm"],
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"Psalm\\": "src/Psalm" "Psalm\\": "src/Psalm"

View File

@ -38,6 +38,7 @@
<exclude> <exclude>
<directory suffix=".php">./src/Psalm/Stubs/</directory> <directory suffix=".php">./src/Psalm/Stubs/</directory>
<file>./src/Psalm/CallMap.php</file> <file>./src/Psalm/CallMap.php</file>
<file>./src/psalm.php</file>
<file>./src/Psalm/PropertyMap.php</file> <file>./src/Psalm/PropertyMap.php</file>
<directory suffix=".php">./src/Psalm/Issue/</directory> <directory suffix=".php">./src/Psalm/Issue/</directory>
</exclude> </exclude>

2
psalm Executable file
View File

@ -0,0 +1,2 @@
#!/usr/bin/env php
<?php require_once __DIR__ . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'psalm.php';

View File

@ -28,12 +28,20 @@
<PossiblyUnusedVariable> <PossiblyUnusedVariable>
<errorLevel type="suppress"> <errorLevel type="suppress">
<file name="src/Psalm/Plugin.php" /> <file name="src/Psalm/Plugin.php" />
<directory name="examples" />
</errorLevel> </errorLevel>
</PossiblyUnusedVariable> </PossiblyUnusedVariable>
<UnusedVariable>
<errorLevel type="suppress">
<directory name="examples" />
</errorLevel>
</UnusedVariable>
<UnusedMethod> <UnusedMethod>
<errorLevel type="suppress"> <errorLevel type="suppress">
<directory name="tests" /> <directory name="tests" />
<directory name="examples" />
</errorLevel> </errorLevel>
</UnusedMethod> </UnusedMethod>
@ -46,6 +54,7 @@
<PossiblyUnusedMethod> <PossiblyUnusedMethod>
<errorLevel type="suppress"> <errorLevel type="suppress">
<directory name="tests" /> <directory name="tests" />
<directory name="examples" />
</errorLevel> </errorLevel>
</PossiblyUnusedMethod> </PossiblyUnusedMethod>

View File

@ -2507,7 +2507,7 @@ return [
'getmypid' => ['int'], 'getmypid' => ['int'],
'getmyuid' => ['int'], 'getmyuid' => ['int'],
'get_object_vars' => ['array', 'obj'=>'object'], '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='=>''], 'get_parent_class' => ['', 'object='=>''],
'getprotobyname' => ['int|false', 'name'=>'string'], 'getprotobyname' => ['int|false', 'name'=>'string'],
'getprotobynumber' => ['string', 'proto'=>'int'], 'getprotobynumber' => ['string', 'proto'=>'int'],
@ -7922,7 +7922,7 @@ return [
'SCA::createDataObject' => ['SDO_DataObject', 'type_namespace_uri'=>'string', 'type_name'=>'string'], 'SCA::createDataObject' => ['SDO_DataObject', 'type_namespace_uri'=>'string', 'type_name'=>'string'],
'SCA::getService' => ['', 'target'=>'string', 'binding='=>'string', 'config='=>'array'], 'SCA::getService' => ['', 'target'=>'string', 'binding='=>'string', 'config='=>'array'],
'SCA_LocalProxy::createDataObject' => ['SDO_DataObject', 'type_namespace_uri'=>'string', 'type_name'=>'string'], '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'], 'SCA_SoapProxy::createDataObject' => ['SDO_DataObject', 'type_namespace_uri'=>'string', 'type_name'=>'string'],
'SDO_DAS_ChangeSummary::beginLogging' => [''], 'SDO_DAS_ChangeSummary::beginLogging' => [''],
'SDO_DAS_ChangeSummary::endLogging' => [''], 'SDO_DAS_ChangeSummary::endLogging' => [''],

View File

@ -387,18 +387,6 @@ class FileChecker extends SourceChecker implements StatementsSource
$this->analyze($file_context); $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<int, \PhpParser\Node\Stmt> * @return array<int, \PhpParser\Node\Stmt>
*/ */

View File

@ -179,8 +179,6 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
} }
if ($overridden_method_ids && $this->function->name !== '__construct') { if ($overridden_method_ids && $this->function->name !== '__construct') {
$have_emitted = false;
foreach ($overridden_method_ids as $overridden_method_id) { foreach ($overridden_method_ids as $overridden_method_id) {
$parent_method_storage = MethodChecker::getStorage($project_checker, $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 . '::' $implementer_method_id = $implementer_classlike_storage->name . '::'
. strtolower($guide_method_storage->cased_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( $implementer_declaring_method_id = MethodChecker::getDeclaringMethodId(
$project_checker, $project_checker,
$implementer_method_id $implementer_method_id

View File

@ -115,8 +115,6 @@ class TryChecker
} }
} }
$exception_type = new Union([new TNamedObject('Exception'), new TNamedObject('Throwable')]);
if ((ClassChecker::classExists($project_checker, $fq_catch_class) if ((ClassChecker::classExists($project_checker, $fq_catch_class)
&& strtolower($fq_catch_class) !== 'exception' && strtolower($fq_catch_class) !== 'exception'
&& !(ClassChecker::classExtends($project_checker, $fq_catch_class, 'Exception') && !(ClassChecker::classExtends($project_checker, $fq_catch_class, 'Exception')

View File

@ -438,6 +438,10 @@ class FetchChecker
$stmt->inferredType = Type::getTrue(); $stmt->inferredType = Type::getTrue();
break; break;
case 'stdin':
$stmt->inferredType = Type::getResource();
break;
default: default:
$const_type = $statements_checker->getConstType( $const_type = $statements_checker->getConstType(
$statements_checker, $statements_checker,
@ -509,15 +513,6 @@ class FetchChecker
$statements_checker->getAliases() $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( if (ClassLikeChecker::checkFullyQualifiedClassLikeName(
$statements_checker->getFileChecker()->project_checker, $statements_checker->getFileChecker()->project_checker,
$fq_class_name, $fq_class_name,

View File

@ -6,11 +6,6 @@ use Psalm\TraitSource;
class TraitChecker extends ClassLikeChecker class TraitChecker extends ClassLikeChecker
{ {
/**
* @var array<string, string>
*/
private $method_map = [];
/** /**
* @param PhpParser\Node\Stmt\Trait_ $class * @param PhpParser\Node\Stmt\Trait_ $class
* @param TraitSource $trait_source * @param TraitSource $trait_source
@ -27,16 +22,6 @@ class TraitChecker extends ClassLikeChecker
self::$trait_checkers[strtolower($fq_class_name)] = $this; self::$trait_checkers[strtolower($fq_class_name)] = $this;
} }
/**
* @param array<string, string> $method_map
*
* @return void
*/
public function setMethodMap(array $method_map)
{
$this->method_map = $method_map;
}
/** /**
* @param string $fq_trait_name * @param string $fq_trait_name
* @param FileChecker $file_checker * @param FileChecker $file_checker

View File

@ -754,4 +754,33 @@ class Config
return $composer_classmap; 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);
}
}
} }

View File

@ -35,7 +35,7 @@ class IssueBuffer
protected static $recorded_issues = []; protected static $recorded_issues = [];
/** /**
* @var int * @var float
*/ */
protected static $start_time = 0; protected static $start_time = 0;
@ -301,7 +301,7 @@ class IssueBuffer
} }
/** /**
* @param int $time * @param float $time
* *
* @return void * @return void
*/ */

View File

@ -16,6 +16,7 @@ use Psalm\Type\Atomic\TMixed;
use Psalm\Type\Atomic\TNamedObject; use Psalm\Type\Atomic\TNamedObject;
use Psalm\Type\Atomic\TNull; use Psalm\Type\Atomic\TNull;
use Psalm\Type\Atomic\TObject; use Psalm\Type\Atomic\TObject;
use Psalm\Type\Atomic\TResource;
use Psalm\Type\Atomic\TString; use Psalm\Type\Atomic\TString;
use Psalm\Type\Atomic\TTrue; use Psalm\Type\Atomic\TTrue;
use Psalm\Type\Atomic\TVoid; use Psalm\Type\Atomic\TVoid;
@ -426,6 +427,14 @@ abstract class Type
return new Union([$type]); return new Union([$type]);
} }
/**
* @return Type\Union
*/
public static function getResource()
{
return new Union([new TResource]);
}
/** /**
* @param array<string, Union> $redefined_vars * @param array<string, Union> $redefined_vars
* @param Context $context * @param Context $context

View File

@ -128,7 +128,7 @@ trait GenericTrait
*/ */
public function replaceTemplateTypesWithArgTypes(array $template_types) 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); $type_param->replaceTemplateTypesWithArgTypes($template_types);
} }
} }

View File

@ -149,7 +149,6 @@ class ParseTree
array_pop($current_parent->children); array_pop($current_parent->children);
$current_parent->children[] = $new_parent_leaf; $current_parent->children[] = $new_parent_leaf;
$has_object_key = true;
break; break;

80
bin/psalm → src/psalm.php Executable file → Normal file
View File

@ -1,7 +1,5 @@
#!/usr/bin/env php
<?php <?php
use Psalm\Checker\FileChecker;
use Psalm\Checker\ProjectChecker; use Psalm\Checker\ProjectChecker;
use Psalm\Config; use Psalm\Config;
@ -18,7 +16,7 @@ $options = getopt(
'help', 'debug', 'config:', 'monochrome', 'show-info:', 'diff', 'help', 'debug', 'config:', 'monochrome', 'show-info:', 'diff',
'file:', 'self-check', 'update-docblocks', 'output-format:', 'file:', 'self-check', 'update-docblocks', 'output-format:',
'find-dead-code', 'init', 'find-references-to:', 'root:', 'threads:', 'find-dead-code', 'init', 'find-references-to:', 'root:', 'threads:',
'report:', 'clear-cache', 'no-cache', 'version' 'report:', 'clear-cache', 'no-cache', 'version',
] ]
); );
@ -124,7 +122,7 @@ if (isset($options['root'])) {
$current_dir = (string)getcwd() . DIRECTORY_SEPARATOR; $current_dir = (string)getcwd() . DIRECTORY_SEPARATOR;
if (isset($options['r'])) { if (isset($options['r']) && is_string($options['r'])) {
$root_path = realpath($options['r']); $root_path = realpath($options['r']);
if (!$root_path) { if (!$root_path) {
@ -165,7 +163,8 @@ foreach ($autoload_roots as $autoload_root) {
$error_message = 'Could not find any composer autoloaders in ' . $autoload_root; $error_message = 'Could not find any composer autoloaders in ' . $autoload_root;
if (!isset($options['r'])) { if (!isset($options['r'])) {
$error_message .= PHP_EOL . 'Add a --root=[your/project/directory] flag to specify a particular project to run Psalm on.'; $error_message .=
PHP_EOL . 'Add a --root=[your/project/directory] flag to specify a particular project to run Psalm on.';
} }
die($error_message . PHP_EOL); die($error_message . PHP_EOL);
@ -173,10 +172,12 @@ foreach ($autoload_roots as $autoload_root) {
} }
foreach ($autoload_files as $file) { foreach ($autoload_files as $file) {
/** @psalm-suppress UnresolvableInclude */
require_once $file; require_once $file;
} }
if (array_key_exists('v', $options)) { if (array_key_exists('v', $options)) {
/** @var string */
$version = \PackageVersions\Versions::getVersion('vimeo/psalm'); $version = \PackageVersions\Versions::getVersion('vimeo/psalm');
echo 'Psalm ' . $version . PHP_EOL; echo 'Psalm ' . $version . PHP_EOL;
exit; exit;
@ -192,6 +193,11 @@ if (isset($options['i'])) {
$args = array_values(array_filter( $args = array_values(array_filter(
array_slice($argv, 2), array_slice($argv, 2),
/**
* @param string $arg
*
* @return bool
*/
function ($arg) { function ($arg) {
return $arg !== '--ansi' && $arg !== '--no-ansi'; return $arg !== '--ansi' && $arg !== '--no-ansi';
} }
@ -256,23 +262,26 @@ if (isset($options['f'])) {
$input_paths = $argv ? $argv : null; $input_paths = $argv ? $argv : null;
} }
$output_format = isset($options['output-format']) ? $options['output-format'] : ProjectChecker::TYPE_CONSOLE; $output_format = isset($options['output-format']) && is_string($options['output-format'])
? $options['output-format']
: ProjectChecker::TYPE_CONSOLE;
$paths_to_check = null; $paths_to_check = null;
if ($input_paths) { if ($input_paths) {
$filtered_input_paths = []; $filtered_input_paths = [];
for ($i = 0; $i < count($input_paths); $i++) { for ($i = 0; $i < count($input_paths); ++$i) {
/** @var string */
$input_path = $input_paths[$i]; $input_path = $input_paths[$i];
if (realpath($input_path) === __FILE__) { if (realpath($input_path) === realpath(dirname(__DIR__) . DIRECTORY_SEPARATOR . 'psalm')) {
continue; continue;
} }
if ($input_path[0] === '-' && strlen($input_path) === 2) { if ($input_path[0] === '-' && strlen($input_path) === 2) {
if ($input_path[1] === 'c' || $input_path[1] === 'f') { if ($input_path[1] === 'c' || $input_path[1] === 'f') {
$i++; ++$i;
} }
continue; continue;
} }
@ -288,14 +297,12 @@ if ($input_paths) {
$filtered_input_paths[] = $input_path; $filtered_input_paths[] = $input_path;
} }
stream_set_blocking(STDIN, 0); stream_set_blocking(STDIN, false);
if ($filtered_input_paths === ['-'] && $stdin = fgets(STDIN)) { if ($filtered_input_paths === ['-'] && $stdin = fgets(STDIN)) {
$filtered_input_paths = preg_split('/\s+/', trim($stdin)); $filtered_input_paths = preg_split('/\s+/', trim($stdin));
} }
$paths_to_check = [];
foreach ($filtered_input_paths as $i => $path_to_check) { foreach ($filtered_input_paths as $i => $path_to_check) {
if ($path_to_check[0] === '-') { if ($path_to_check[0] === '-') {
die('Invalid usage, expecting psalm [options] [file...]' . PHP_EOL); die('Invalid usage, expecting psalm [options] [file...]' . PHP_EOL);
@ -305,7 +312,13 @@ if ($input_paths) {
die('Cannot locate ' . $path_to_check . PHP_EOL); 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) { 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) { 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); $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_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']); $update_docblocks = isset($options['update-docblocks']);
@ -350,7 +366,7 @@ $project_checker = new ProjectChecker(
$update_docblocks, $update_docblocks,
$find_dead_code || $find_references_to !== null, $find_dead_code || $find_references_to !== null,
$find_references_to, $find_references_to,
isset($options['report'])?$options['report']:null isset($options['report']) && is_string($options['report']) ? $options['report'] : null
); );
// initialise custom config, if passed // initialise custom config, if passed
@ -358,46 +374,24 @@ if ($path_to_config) {
$project_checker->setConfigXML($path_to_config, $current_dir); $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'])) { if (isset($options['clear-cache'])) {
// initialise config if it hasn't already been // initialise config if it hasn't already been
if (!$path_to_config) { if (!$path_to_config) {
$project_checker->getConfigForPath($current_dir, $current_dir); $project_checker->getConfigForPath($current_dir, $current_dir);
} }
$cache_directory = Config::getInstance()->getCacheDirectory(); $cache_directory = Config::getInstance()->getCacheDirectory();
rrmdir($cache_directory); Config::removeCacheDirectory($cache_directory);
echo 'Cache directory deleted' . PHP_EOL; echo 'Cache directory deleted' . PHP_EOL;
exit; exit;
} }
/** @psalm-suppress MixedArgument */
\Psalm\IssueBuffer::setStartTime(microtime(true)); \Psalm\IssueBuffer::setStartTime(microtime(true));
if (array_key_exists('self-check', $options)) { 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) { } elseif ($paths_to_check === null) {
$project_checker->check($current_dir, $is_diff); $project_checker->check($current_dir, $is_diff);
} elseif ($paths_to_check) { } elseif ($paths_to_check) {