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:
- vendor/bin/phpunit
- bin/psalm
- ./psalm
- vendor/bin/phpcs
after_success:

View File

@ -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"

View File

@ -38,6 +38,7 @@
<exclude>
<directory suffix=".php">./src/Psalm/Stubs/</directory>
<file>./src/Psalm/CallMap.php</file>
<file>./src/psalm.php</file>
<file>./src/Psalm/PropertyMap.php</file>
<directory suffix=".php">./src/Psalm/Issue/</directory>
</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>
<errorLevel type="suppress">
<file name="src/Psalm/Plugin.php" />
<directory name="examples" />
</errorLevel>
</PossiblyUnusedVariable>
<UnusedVariable>
<errorLevel type="suppress">
<directory name="examples" />
</errorLevel>
</UnusedVariable>
<UnusedMethod>
<errorLevel type="suppress">
<directory name="tests" />
<directory name="examples" />
</errorLevel>
</UnusedMethod>
@ -46,6 +54,7 @@
<PossiblyUnusedMethod>
<errorLevel type="suppress">
<directory name="tests" />
<directory name="examples" />
</errorLevel>
</PossiblyUnusedMethod>

View File

@ -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' => [''],

View File

@ -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<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') {
$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

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)
&& strtolower($fq_catch_class) !== 'exception'
&& !(ClassChecker::classExtends($project_checker, $fq_catch_class, 'Exception')

View File

@ -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,

View File

@ -6,11 +6,6 @@ use Psalm\TraitSource;
class TraitChecker extends ClassLikeChecker
{
/**
* @var array<string, string>
*/
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<string, string> $method_map
*
* @return void
*/
public function setMethodMap(array $method_map)
{
$this->method_map = $method_map;
}
/**
* @param string $fq_trait_name
* @param FileChecker $file_checker

View File

@ -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);
}
}
}

View File

@ -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
*/

View File

@ -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<string, Union> $redefined_vars
* @param Context $context

View File

@ -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);
}
}

View File

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

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

@ -1,7 +1,5 @@
#!/usr/bin/env php
<?php
use Psalm\Checker\FileChecker;
use Psalm\Checker\ProjectChecker;
use Psalm\Config;
@ -18,7 +16,7 @@ $options = getopt(
'help', 'debug', 'config:', 'monochrome', 'show-info:', 'diff',
'file:', 'self-check', 'update-docblocks', 'output-format:',
'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;
if (isset($options['r'])) {
if (isset($options['r']) && is_string($options['r'])) {
$root_path = realpath($options['r']);
if (!$root_path) {
@ -165,7 +163,8 @@ foreach ($autoload_roots as $autoload_root) {
$error_message = 'Could not find any composer autoloaders in ' . $autoload_root;
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);
@ -173,10 +172,12 @@ foreach ($autoload_roots as $autoload_root) {
}
foreach ($autoload_files as $file) {
/** @psalm-suppress UnresolvableInclude */
require_once $file;
}
if (array_key_exists('v', $options)) {
/** @var string */
$version = \PackageVersions\Versions::getVersion('vimeo/psalm');
echo 'Psalm ' . $version . PHP_EOL;
exit;
@ -192,6 +193,11 @@ if (isset($options['i'])) {
$args = array_values(array_filter(
array_slice($argv, 2),
/**
* @param string $arg
*
* @return bool
*/
function ($arg) {
return $arg !== '--ansi' && $arg !== '--no-ansi';
}
@ -256,23 +262,26 @@ if (isset($options['f'])) {
$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;
if ($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];
if (realpath($input_path) === __FILE__) {
if (realpath($input_path) === realpath(dirname(__DIR__) . DIRECTORY_SEPARATOR . 'psalm')) {
continue;
}
if ($input_path[0] === '-' && strlen($input_path) === 2) {
if ($input_path[1] === 'c' || $input_path[1] === 'f') {
$i++;
++$i;
}
continue;
}
@ -288,14 +297,12 @@ if ($input_paths) {
$filtered_input_paths[] = $input_path;
}
stream_set_blocking(STDIN, 0);
stream_set_blocking(STDIN, false);
if ($filtered_input_paths === ['-'] && $stdin = fgets(STDIN)) {
$filtered_input_paths = preg_split('/\s+/', trim($stdin));
}
$paths_to_check = [];
foreach ($filtered_input_paths as $i => $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) {