2018-01-07 06:11:23 +01:00
|
|
|
|
<?php
|
|
|
|
|
|
2020-07-22 21:57:24 +02:00
|
|
|
|
namespace Psalm;
|
|
|
|
|
|
2019-07-07 14:55:53 +02:00
|
|
|
|
use Composer\Autoload\ClassLoader;
|
2020-07-22 21:57:24 +02:00
|
|
|
|
use Phar;
|
2020-10-03 08:26:37 +02:00
|
|
|
|
use Psalm\Internal\Composer;
|
2020-07-22 21:57:24 +02:00
|
|
|
|
use function dirname;
|
|
|
|
|
use function strpos;
|
|
|
|
|
use function realpath;
|
|
|
|
|
use const DIRECTORY_SEPARATOR;
|
|
|
|
|
use function file_exists;
|
|
|
|
|
use function in_array;
|
|
|
|
|
use const PHP_EOL;
|
|
|
|
|
use function fwrite;
|
|
|
|
|
use const STDERR;
|
|
|
|
|
use function implode;
|
|
|
|
|
use function define;
|
|
|
|
|
use function json_decode;
|
|
|
|
|
use function file_get_contents;
|
|
|
|
|
use function is_array;
|
|
|
|
|
use function is_string;
|
|
|
|
|
use function count;
|
|
|
|
|
use function strlen;
|
|
|
|
|
use function substr;
|
|
|
|
|
use function stream_get_meta_data;
|
|
|
|
|
use const STDIN;
|
|
|
|
|
use function stream_set_blocking;
|
|
|
|
|
use function fgets;
|
|
|
|
|
use function preg_split;
|
|
|
|
|
use function trim;
|
|
|
|
|
use function is_dir;
|
|
|
|
|
use function preg_replace;
|
|
|
|
|
use function substr_replace;
|
|
|
|
|
use function file_put_contents;
|
|
|
|
|
use function ini_get;
|
|
|
|
|
use function preg_match;
|
|
|
|
|
use function strtoupper;
|
2019-07-07 14:55:53 +02:00
|
|
|
|
|
2020-09-07 01:36:47 +02:00
|
|
|
|
function requireAutoloaders(string $current_dir, bool $has_explicit_root, string $vendor_dir): ?ClassLoader
|
2018-01-07 06:11:23 +01:00
|
|
|
|
{
|
|
|
|
|
$autoload_roots = [$current_dir];
|
|
|
|
|
|
|
|
|
|
$psalm_dir = dirname(__DIR__);
|
|
|
|
|
|
2019-03-27 16:55:10 +01:00
|
|
|
|
/** @psalm-suppress UndefinedConstant */
|
2019-06-26 22:52:29 +02:00
|
|
|
|
$in_phar = Phar::running() || strpos(__NAMESPACE__, 'HumbugBox');
|
2019-03-27 16:55:10 +01:00
|
|
|
|
|
|
|
|
|
if ($in_phar) {
|
|
|
|
|
require_once(__DIR__ . '/../vendor/autoload.php');
|
|
|
|
|
|
|
|
|
|
// hack required for JsonMapper
|
|
|
|
|
require_once __DIR__ . '/../vendor/netresearch/jsonmapper/src/JsonMapper.php';
|
|
|
|
|
require_once __DIR__ . '/../vendor/netresearch/jsonmapper/src/JsonMapper/Exception.php';
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-26 22:52:29 +02:00
|
|
|
|
if (realpath($psalm_dir) !== realpath($current_dir) && !$in_phar) {
|
2018-01-07 06:11:23 +01:00
|
|
|
|
$autoload_roots[] = $psalm_dir;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$autoload_files = [];
|
|
|
|
|
|
|
|
|
|
foreach ($autoload_roots as $autoload_root) {
|
|
|
|
|
$has_autoloader = false;
|
|
|
|
|
|
2020-09-20 14:55:28 +02:00
|
|
|
|
$nested_autoload_file = dirname($autoload_root, 2). DIRECTORY_SEPARATOR . 'autoload.php';
|
2018-01-07 06:11:23 +01:00
|
|
|
|
|
2018-03-08 23:56:52 +01:00
|
|
|
|
// note: don't realpath $nested_autoload_file, or phar version will fail
|
2018-01-07 06:11:23 +01:00
|
|
|
|
if (file_exists($nested_autoload_file)) {
|
2018-03-08 23:56:52 +01:00
|
|
|
|
if (!in_array($nested_autoload_file, $autoload_files, false)) {
|
|
|
|
|
$autoload_files[] = $nested_autoload_file;
|
2018-03-03 21:19:05 +01:00
|
|
|
|
}
|
2018-01-07 06:11:23 +01:00
|
|
|
|
$has_autoloader = true;
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-25 19:07:36 +01:00
|
|
|
|
$vendor_autoload_file =
|
|
|
|
|
$autoload_root . DIRECTORY_SEPARATOR . $vendor_dir . DIRECTORY_SEPARATOR . 'autoload.php';
|
2018-01-07 06:11:23 +01:00
|
|
|
|
|
2018-03-08 23:56:52 +01:00
|
|
|
|
// note: don't realpath $vendor_autoload_file, or phar version will fail
|
2018-01-07 06:11:23 +01:00
|
|
|
|
if (file_exists($vendor_autoload_file)) {
|
2018-03-08 23:56:52 +01:00
|
|
|
|
if (!in_array($vendor_autoload_file, $autoload_files, false)) {
|
|
|
|
|
$autoload_files[] = $vendor_autoload_file;
|
2018-03-03 21:19:05 +01:00
|
|
|
|
}
|
2018-01-07 06:11:23 +01:00
|
|
|
|
$has_autoloader = true;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-03 08:26:37 +02:00
|
|
|
|
$composer_json_file = Composer::getJsonFilePath($autoload_root);
|
|
|
|
|
if (!$has_autoloader && file_exists($composer_json_file)) {
|
2018-01-07 06:11:23 +01:00
|
|
|
|
$error_message = 'Could not find any composer autoloaders in ' . $autoload_root;
|
|
|
|
|
|
2018-01-10 01:33:39 +01:00
|
|
|
|
if (!$has_explicit_root) {
|
2018-01-07 06:11:23 +01:00
|
|
|
|
$error_message .= PHP_EOL . 'Add a --root=[your/project/directory] flag '
|
|
|
|
|
. 'to specify a particular project to run Psalm on.';
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-30 16:30:41 +02:00
|
|
|
|
fwrite(STDERR, $error_message . PHP_EOL);
|
2018-04-17 17:48:29 +02:00
|
|
|
|
exit(1);
|
2018-01-07 06:11:23 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-03 21:19:05 +01:00
|
|
|
|
$first_autoloader = null;
|
|
|
|
|
|
2018-01-07 06:11:23 +01:00
|
|
|
|
foreach ($autoload_files as $file) {
|
2018-03-03 21:25:35 +01:00
|
|
|
|
/**
|
|
|
|
|
* @psalm-suppress UnresolvableInclude
|
2019-07-05 22:24:00 +02:00
|
|
|
|
*
|
2019-03-18 12:47:57 +01:00
|
|
|
|
* @var mixed
|
2018-03-03 21:25:35 +01:00
|
|
|
|
*/
|
2018-03-03 21:19:05 +01:00
|
|
|
|
$autoloader = require_once $file;
|
|
|
|
|
|
2019-03-18 12:47:57 +01:00
|
|
|
|
if (!$first_autoloader
|
2019-07-07 14:55:53 +02:00
|
|
|
|
&& $autoloader instanceof ClassLoader
|
2019-03-18 12:47:57 +01:00
|
|
|
|
) {
|
2018-03-03 21:19:05 +01:00
|
|
|
|
$first_autoloader = $autoloader;
|
|
|
|
|
}
|
2018-01-07 06:11:23 +01:00
|
|
|
|
}
|
2018-02-25 18:14:35 +01:00
|
|
|
|
|
2019-04-20 22:37:27 +02:00
|
|
|
|
if ($first_autoloader === null && !$in_phar) {
|
2019-04-18 18:12:43 +02:00
|
|
|
|
if (!$autoload_files) {
|
2019-05-30 16:30:41 +02:00
|
|
|
|
fwrite(STDERR, 'Failed to find a valid Composer autoloader' . "\n");
|
2019-04-18 18:12:43 +02:00
|
|
|
|
} else {
|
2019-05-30 16:30:41 +02:00
|
|
|
|
fwrite(STDERR, 'Failed to find a valid Composer autoloader in ' . implode(', ', $autoload_files) . "\n");
|
2019-04-18 18:12:43 +02:00
|
|
|
|
}
|
|
|
|
|
|
2019-05-30 16:30:41 +02:00
|
|
|
|
fwrite(
|
|
|
|
|
STDERR,
|
|
|
|
|
'Please make sure you’ve run `composer install` in the current directory before using Psalm.' . "\n"
|
|
|
|
|
);
|
2019-03-18 12:47:57 +01:00
|
|
|
|
exit(1);
|
2018-03-03 21:25:35 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-07-11 23:17:22 +02:00
|
|
|
|
define('PSALM_VERSION', (string)\PackageVersions\Versions::getVersion('vimeo/psalm'));
|
2019-06-29 18:32:44 +02:00
|
|
|
|
define('PHP_PARSER_VERSION', \PackageVersions\Versions::getVersion('nikic/php-parser'));
|
2018-03-03 21:19:05 +01:00
|
|
|
|
|
|
|
|
|
return $first_autoloader;
|
2018-01-07 06:11:23 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2018-01-25 17:32:54 +01:00
|
|
|
|
* @psalm-suppress MixedArrayAccess
|
|
|
|
|
* @psalm-suppress MixedAssignment
|
2020-04-12 20:01:17 +02:00
|
|
|
|
* @psalm-suppress PossiblyUndefinedStringArrayOffset
|
2018-01-25 17:32:54 +01:00
|
|
|
|
*/
|
2020-09-07 01:36:47 +02:00
|
|
|
|
function getVendorDir(string $current_dir): string
|
2018-01-25 17:32:54 +01:00
|
|
|
|
{
|
2020-10-03 08:26:37 +02:00
|
|
|
|
$composer_json_path = Composer::getJsonFilePath($current_dir);
|
2018-01-25 17:32:54 +01:00
|
|
|
|
|
|
|
|
|
if (!file_exists($composer_json_path)) {
|
|
|
|
|
return 'vendor';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!$composer_json = json_decode(file_get_contents($composer_json_path), true)) {
|
2019-07-06 18:20:20 +02:00
|
|
|
|
fwrite(
|
|
|
|
|
STDERR,
|
|
|
|
|
'Invalid composer.json at ' . $composer_json_path . "\n"
|
|
|
|
|
);
|
|
|
|
|
exit(1);
|
2018-01-25 17:32:54 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-04-12 20:01:17 +02:00
|
|
|
|
if (isset($composer_json['config'])
|
|
|
|
|
&& is_array($composer_json['config'])
|
|
|
|
|
&& isset($composer_json['config']['vendor-dir'])
|
2020-04-04 17:18:24 +02:00
|
|
|
|
&& is_string($composer_json['config']['vendor-dir'])
|
|
|
|
|
) {
|
|
|
|
|
return $composer_json['config']['vendor-dir'];
|
2018-01-25 17:32:54 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 'vendor';
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-02 07:10:50 +02:00
|
|
|
|
/**
|
|
|
|
|
* @return string[]
|
|
|
|
|
*/
|
|
|
|
|
function getArguments() : array
|
|
|
|
|
{
|
|
|
|
|
global $argv;
|
|
|
|
|
|
|
|
|
|
if (!$argv) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$filtered_input_paths = [];
|
|
|
|
|
|
2020-10-15 19:23:35 +02:00
|
|
|
|
for ($i = 0, $iMax = count($argv); $i < $iMax; ++$i) {
|
2019-06-02 07:10:50 +02:00
|
|
|
|
$input_path = $argv[$i];
|
|
|
|
|
|
|
|
|
|
if (realpath($input_path) !== false) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($input_path[0] === '-' && strlen($input_path) === 2) {
|
|
|
|
|
if ($input_path[1] === 'c' || $input_path[1] === 'f') {
|
|
|
|
|
++$i;
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($input_path[0] === '-' && $input_path[2] === '=') {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$filtered_input_paths[] = $input_path;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $filtered_input_paths;
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-25 17:32:54 +01:00
|
|
|
|
/**
|
|
|
|
|
* @param string|array|null|false $f_paths
|
2018-01-07 06:11:23 +01:00
|
|
|
|
*
|
|
|
|
|
* @return string[]|null
|
|
|
|
|
*/
|
2020-09-04 22:26:33 +02:00
|
|
|
|
function getPathsToCheck($f_paths): ?array
|
2018-01-07 06:11:23 +01:00
|
|
|
|
{
|
|
|
|
|
global $argv;
|
|
|
|
|
|
|
|
|
|
$paths_to_check = [];
|
|
|
|
|
|
|
|
|
|
if ($f_paths) {
|
|
|
|
|
$input_paths = is_array($f_paths) ? $f_paths : [$f_paths];
|
|
|
|
|
} else {
|
|
|
|
|
$input_paths = $argv ? $argv : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($input_paths) {
|
|
|
|
|
$filtered_input_paths = [];
|
|
|
|
|
|
2020-10-15 19:23:35 +02:00
|
|
|
|
for ($i = 0, $iMax = count($input_paths); $i < $iMax; ++$i) {
|
2018-01-07 06:11:23 +01:00
|
|
|
|
/** @var string */
|
|
|
|
|
$input_path = $input_paths[$i];
|
|
|
|
|
|
|
|
|
|
if (realpath($input_path) === realpath(dirname(__DIR__) . DIRECTORY_SEPARATOR . 'psalm')
|
|
|
|
|
|| realpath($input_path) === realpath(dirname(__DIR__) . DIRECTORY_SEPARATOR . 'psalter')
|
2019-06-26 22:52:29 +02:00
|
|
|
|
|| realpath($input_path) === realpath(Phar::running(false))
|
2018-01-07 06:11:23 +01:00
|
|
|
|
) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($input_path[0] === '-' && strlen($input_path) === 2) {
|
|
|
|
|
if ($input_path[1] === 'c' || $input_path[1] === 'f') {
|
|
|
|
|
++$i;
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($input_path[0] === '-' && $input_path[2] === '=') {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (substr($input_path, 0, 2) === '--' && strlen($input_path) > 2) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$filtered_input_paths[] = $input_path;
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-18 12:23:15 +01:00
|
|
|
|
if ($filtered_input_paths === ['-']) {
|
2018-01-18 12:48:57 +01:00
|
|
|
|
$meta = stream_get_meta_data(STDIN);
|
|
|
|
|
stream_set_blocking(STDIN, false);
|
2018-01-18 14:35:25 +01:00
|
|
|
|
if ($stdin = fgets(STDIN)) {
|
2018-01-18 12:48:57 +01:00
|
|
|
|
$filtered_input_paths = preg_split('/\s+/', trim($stdin));
|
|
|
|
|
}
|
|
|
|
|
$blocked = $meta['blocked'];
|
|
|
|
|
stream_set_blocking(STDIN, $blocked);
|
2018-01-07 06:11:23 +01:00
|
|
|
|
}
|
|
|
|
|
|
2018-06-17 15:35:08 +02:00
|
|
|
|
foreach ($filtered_input_paths as $path_to_check) {
|
2018-01-07 06:11:23 +01:00
|
|
|
|
if ($path_to_check[0] === '-') {
|
2019-05-30 16:30:41 +02:00
|
|
|
|
fwrite(STDERR, 'Invalid usage, expecting psalm [options] [file...]' . PHP_EOL);
|
2018-04-17 17:48:29 +02:00
|
|
|
|
exit(1);
|
2018-01-07 06:11:23 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!file_exists($path_to_check)) {
|
2019-05-30 16:30:41 +02:00
|
|
|
|
fwrite(STDERR, 'Cannot locate ' . $path_to_check . PHP_EOL);
|
2018-04-17 17:48:29 +02:00
|
|
|
|
exit(1);
|
2018-01-07 06:11:23 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$path_to_check = realpath($path_to_check);
|
|
|
|
|
|
|
|
|
|
if (!$path_to_check) {
|
2019-05-30 16:30:41 +02:00
|
|
|
|
fwrite(STDERR, 'Error getting realpath for file' . PHP_EOL);
|
2018-04-17 17:48:29 +02:00
|
|
|
|
exit(1);
|
2018-01-07 06:11:23 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$paths_to_check[] = $path_to_check;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!$paths_to_check) {
|
|
|
|
|
$paths_to_check = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $paths_to_check;
|
|
|
|
|
}
|
2019-07-07 14:55:53 +02:00
|
|
|
|
|
2020-08-23 19:52:31 +02:00
|
|
|
|
/**
|
|
|
|
|
* @psalm-pure
|
|
|
|
|
*/
|
2019-07-07 14:55:53 +02:00
|
|
|
|
function getPsalmHelpText(): string
|
|
|
|
|
{
|
|
|
|
|
return <<<HELP
|
|
|
|
|
Usage:
|
|
|
|
|
psalm [options] [file...]
|
|
|
|
|
|
2019-12-04 22:16:03 +01:00
|
|
|
|
Basic configuration:
|
2019-07-07 14:55:53 +02:00
|
|
|
|
-c, --config=psalm.xml
|
|
|
|
|
Path to a psalm.xml configuration file. Run psalm --init to create one.
|
|
|
|
|
|
2019-12-04 22:16:03 +01:00
|
|
|
|
--use-ini-defaults
|
|
|
|
|
Use PHP-provided ini defaults for memory and error display
|
2019-07-07 14:55:53 +02:00
|
|
|
|
|
2020-08-06 14:51:57 +02:00
|
|
|
|
--memory-limit=LIMIT
|
|
|
|
|
Use a specific memory limit. Cannot be combined with --use-ini-defaults
|
|
|
|
|
|
2019-12-04 22:16:03 +01:00
|
|
|
|
--disable-extension=[extension]
|
|
|
|
|
Used to disable certain extensions while Psalm is running.
|
2019-07-07 14:55:53 +02:00
|
|
|
|
|
2019-12-04 22:16:03 +01:00
|
|
|
|
--threads=INT
|
|
|
|
|
If greater than one, Psalm will run analysis on multiple threads, speeding things up.
|
2019-07-07 14:55:53 +02:00
|
|
|
|
|
|
|
|
|
--diff
|
2020-01-25 16:50:11 +01:00
|
|
|
|
Runs Psalm in diff mode, only checking files that have changed since last run (and their dependents)
|
2019-07-07 14:55:53 +02:00
|
|
|
|
|
|
|
|
|
--diff-methods
|
2020-01-25 16:50:11 +01:00
|
|
|
|
Only checks methods that have changed since last run (and their dependents)
|
2019-07-07 14:55:53 +02:00
|
|
|
|
|
2019-12-04 22:16:03 +01:00
|
|
|
|
Surfacing issues:
|
|
|
|
|
--show-info[=BOOLEAN]
|
2020-02-17 22:49:54 +01:00
|
|
|
|
Show non-exception parser findings (defaults to false).
|
2019-12-04 22:16:03 +01:00
|
|
|
|
|
|
|
|
|
--show-snippet[=true]
|
|
|
|
|
Show code snippets with errors. Options are 'true' or 'false'
|
2019-07-07 14:55:53 +02:00
|
|
|
|
|
|
|
|
|
--find-dead-code[=auto]
|
|
|
|
|
--find-unused-code[=auto]
|
|
|
|
|
Look for unused code. Options are 'auto' or 'always'. If no value is specified, default is 'auto'
|
|
|
|
|
|
2019-08-18 20:27:50 +02:00
|
|
|
|
--find-unused-psalm-suppress
|
|
|
|
|
Finds all @psalm-suppress annotations that aren’t used
|
|
|
|
|
|
2019-07-07 14:55:53 +02:00
|
|
|
|
--find-references-to=[class|method|property]
|
|
|
|
|
Searches the codebase for references to the given fully-qualified class or method,
|
|
|
|
|
where method is in the format class::methodName
|
|
|
|
|
|
2019-12-04 22:16:03 +01:00
|
|
|
|
--no-suggestions
|
|
|
|
|
Hide suggestions
|
|
|
|
|
|
2020-06-22 17:39:46 +02:00
|
|
|
|
--taint-analysis
|
|
|
|
|
Run Psalm in taint analysis mode – see https://psalm.dev/docs/security_analysis for more info
|
|
|
|
|
|
2019-12-04 22:16:03 +01:00
|
|
|
|
Issue baselines:
|
|
|
|
|
--set-baseline=PATH
|
|
|
|
|
Save all current error level issues to a file, to mark them as info in subsequent runs
|
|
|
|
|
|
|
|
|
|
Add --include-php-versions to also include a list of PHP extension versions
|
|
|
|
|
|
2020-06-25 23:31:03 +02:00
|
|
|
|
--use-baseline=PATH
|
|
|
|
|
Allows you to use a baseline other than the default baseline provided in your config
|
|
|
|
|
|
2019-12-04 22:16:03 +01:00
|
|
|
|
--ignore-baseline
|
|
|
|
|
Ignore the error baseline
|
|
|
|
|
|
|
|
|
|
--update-baseline
|
|
|
|
|
Update the baseline by removing fixed issues. This will not add new issues to the baseline
|
|
|
|
|
|
|
|
|
|
Add --include-php-versions to also include a list of PHP extension versions
|
|
|
|
|
|
|
|
|
|
Plugins:
|
|
|
|
|
--plugin=PATH
|
|
|
|
|
Executes a plugin, an alternative to using the Psalm config
|
|
|
|
|
|
|
|
|
|
Output:
|
|
|
|
|
-m, --monochrome
|
|
|
|
|
Enable monochrome output
|
|
|
|
|
|
|
|
|
|
--output-format=console
|
2019-12-19 21:18:09 +01:00
|
|
|
|
Changes the output format.
|
2020-09-04 22:24:14 +02:00
|
|
|
|
Available formats: compact, console, text, emacs, json, pylint, xml, checkstyle, junit, sonarqube, github,
|
|
|
|
|
phpstorm
|
2019-12-04 22:16:03 +01:00
|
|
|
|
|
|
|
|
|
--no-progress
|
|
|
|
|
Disable the progress indicator
|
|
|
|
|
|
|
|
|
|
--long-progress
|
|
|
|
|
Use a progress indicator suitable for Continuous Integration logs
|
|
|
|
|
|
|
|
|
|
--stats
|
|
|
|
|
Shows a breakdown of Psalm's ability to infer types in the codebase
|
2019-07-07 14:55:53 +02:00
|
|
|
|
|
2019-12-04 22:16:03 +01:00
|
|
|
|
Reports:
|
2019-07-07 14:55:53 +02:00
|
|
|
|
--report=PATH
|
|
|
|
|
The path where to output report file. The output format is based on the file extension.
|
2020-07-08 21:09:31 +02:00
|
|
|
|
(Currently supported formats: ".json", ".xml", ".txt", ".emacs", ".pylint", ".console",
|
|
|
|
|
"checkstyle.xml", "sonarqube.json", "summary.json", "junit.xml")
|
2019-07-07 14:55:53 +02:00
|
|
|
|
|
|
|
|
|
--report-show-info[=BOOLEAN]
|
|
|
|
|
Whether the report should include non-errors in its output (defaults to true)
|
|
|
|
|
|
2019-12-04 22:16:03 +01:00
|
|
|
|
Caching:
|
2019-07-07 14:55:53 +02:00
|
|
|
|
--clear-cache
|
|
|
|
|
Clears all cache files that Psalm uses for this specific project
|
|
|
|
|
|
|
|
|
|
--clear-global-cache
|
|
|
|
|
Clears all cache files that Psalm uses for all projects
|
|
|
|
|
|
|
|
|
|
--no-cache
|
|
|
|
|
Runs Psalm without using cache
|
|
|
|
|
|
|
|
|
|
--no-reflection-cache
|
|
|
|
|
Runs Psalm without using cached representations of unchanged classes and files.
|
|
|
|
|
Useful if you want the afterClassLikeVisit plugin hook to run every time you visit a file.
|
|
|
|
|
|
2020-04-12 14:34:57 +02:00
|
|
|
|
--no-file-cache
|
|
|
|
|
Runs Psalm without using caching every single file for later diffing.
|
|
|
|
|
This reduces the space Psalm uses on disk and file I/O.
|
|
|
|
|
|
2019-12-04 22:16:03 +01:00
|
|
|
|
Miscellaneous:
|
|
|
|
|
-h, --help
|
|
|
|
|
Display this help message
|
2019-07-07 14:55:53 +02:00
|
|
|
|
|
2019-12-04 22:16:03 +01:00
|
|
|
|
-v, --version
|
|
|
|
|
Display the Psalm version
|
2019-07-07 14:55:53 +02:00
|
|
|
|
|
2019-12-04 22:16:03 +01:00
|
|
|
|
-i, --init [source_dir=src] [level=3]
|
|
|
|
|
Create a psalm config file in the current directory that points to [source_dir]
|
|
|
|
|
at the required level, from 1, most strict, to 8, most permissive.
|
2019-07-11 16:41:44 +02:00
|
|
|
|
|
2019-12-04 22:16:03 +01:00
|
|
|
|
--debug
|
|
|
|
|
Debug information
|
2019-07-07 14:55:53 +02:00
|
|
|
|
|
2019-12-04 22:16:03 +01:00
|
|
|
|
--debug-by-line
|
|
|
|
|
Debug information on a line-by-line level
|
2019-07-07 14:55:53 +02:00
|
|
|
|
|
2020-06-23 00:16:47 +02:00
|
|
|
|
--debug-emitted-issues
|
|
|
|
|
Print a php backtrace to stderr when emitting issues.
|
|
|
|
|
|
2019-12-04 22:16:03 +01:00
|
|
|
|
-r, --root
|
|
|
|
|
If running Psalm globally you'll need to specify a project root. Defaults to cwd
|
2019-07-11 16:41:44 +02:00
|
|
|
|
|
2019-07-07 14:55:53 +02:00
|
|
|
|
--generate-json-map=PATH
|
|
|
|
|
Generate a map of node references and types in JSON format, saved to the given path.
|
|
|
|
|
|
2020-01-08 23:23:40 +01:00
|
|
|
|
--generate-stubs=PATH
|
|
|
|
|
Generate stubs for the project and dump the file in the given path
|
|
|
|
|
|
2019-12-03 08:18:44 +01:00
|
|
|
|
--shepherd[=host]
|
|
|
|
|
Send data to Shepherd, Psalm's GitHub integration tool.
|
|
|
|
|
|
2019-07-07 14:55:53 +02:00
|
|
|
|
--alter
|
|
|
|
|
Run Psalter
|
|
|
|
|
|
|
|
|
|
--language-server
|
|
|
|
|
Run Psalm Language Server
|
|
|
|
|
|
|
|
|
|
HELP;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function initialiseConfig(
|
|
|
|
|
?string $path_to_config,
|
|
|
|
|
string $current_dir,
|
|
|
|
|
string $output_format,
|
|
|
|
|
?ClassLoader $first_autoloader
|
|
|
|
|
): Config {
|
|
|
|
|
try {
|
|
|
|
|
if ($path_to_config) {
|
|
|
|
|
$config = Config::loadFromXMLFile($path_to_config, $current_dir);
|
|
|
|
|
} else {
|
|
|
|
|
$config = Config::getConfigForPath($current_dir, $current_dir, $output_format);
|
|
|
|
|
}
|
2020-07-22 21:57:24 +02:00
|
|
|
|
} catch (\Psalm\Exception\ConfigException $e) {
|
2019-07-07 14:55:53 +02:00
|
|
|
|
fwrite(STDERR, $e->getMessage() . PHP_EOL);
|
|
|
|
|
exit(1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$config->setComposerClassLoader($first_autoloader);
|
|
|
|
|
|
|
|
|
|
return $config;
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-09 23:03:23 +02:00
|
|
|
|
function update_config_file(Config $config, string $config_file_path, string $baseline_path) : void
|
|
|
|
|
{
|
|
|
|
|
if ($config->error_baseline === $baseline_path) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-09 15:18:52 +02:00
|
|
|
|
$configFile = $config_file_path;
|
|
|
|
|
|
|
|
|
|
if (is_dir($config_file_path)) {
|
|
|
|
|
$configFile = Config::locateConfigFile($config_file_path);
|
|
|
|
|
}
|
2019-09-09 23:03:23 +02:00
|
|
|
|
|
|
|
|
|
if (!$configFile) {
|
|
|
|
|
fwrite(STDERR, "Don't forget to set errorBaseline=\"{$baseline_path}\" to your config.");
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$configFileContents = file_get_contents($configFile);
|
|
|
|
|
|
|
|
|
|
if ($config->error_baseline) {
|
|
|
|
|
$amendedConfigFileContents = preg_replace(
|
|
|
|
|
'/errorBaseline=".*?"/',
|
|
|
|
|
"errorBaseline=\"{$baseline_path}\"",
|
|
|
|
|
$configFileContents
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
$endPsalmOpenTag = strpos($configFileContents, '>', (int)strpos($configFileContents, '<psalm'));
|
|
|
|
|
|
|
|
|
|
if (!$endPsalmOpenTag) {
|
|
|
|
|
fwrite(STDERR, " Don't forget to set errorBaseline=\"{$baseline_path}\" in your config.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($configFileContents[$endPsalmOpenTag - 1] === "\n") {
|
|
|
|
|
$amendedConfigFileContents = substr_replace(
|
|
|
|
|
$configFileContents,
|
|
|
|
|
" errorBaseline=\"{$baseline_path}\"\n>",
|
|
|
|
|
$endPsalmOpenTag,
|
|
|
|
|
1
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
$amendedConfigFileContents = substr_replace(
|
|
|
|
|
$configFileContents,
|
|
|
|
|
" errorBaseline=\"{$baseline_path}\">",
|
|
|
|
|
$endPsalmOpenTag,
|
|
|
|
|
1
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
file_put_contents($configFile, $amendedConfigFileContents);
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-07 14:55:53 +02:00
|
|
|
|
function get_path_to_config(array $options): ?string
|
|
|
|
|
{
|
|
|
|
|
$path_to_config = isset($options['c']) && is_string($options['c']) ? realpath($options['c']) : null;
|
|
|
|
|
|
|
|
|
|
if ($path_to_config === false) {
|
2019-10-01 21:44:43 +02:00
|
|
|
|
fwrite(STDERR, 'Could not resolve path to config ' . (string) ($options['c'] ?? '') . PHP_EOL);
|
2019-07-07 14:55:53 +02:00
|
|
|
|
exit(1);
|
|
|
|
|
}
|
|
|
|
|
return $path_to_config;
|
|
|
|
|
}
|
2020-02-24 22:18:46 +01:00
|
|
|
|
|
2020-08-23 19:52:31 +02:00
|
|
|
|
/**
|
|
|
|
|
* @psalm-pure
|
|
|
|
|
*/
|
2020-02-24 22:18:46 +01:00
|
|
|
|
function getMemoryLimitInBytes(): int
|
|
|
|
|
{
|
|
|
|
|
$limit = ini_get('memory_limit');
|
|
|
|
|
// for unlimited = -1
|
|
|
|
|
if ($limit < 0) {
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (preg_match('/^(\d+)(\D?)$/', $limit, $matches)) {
|
|
|
|
|
$limit = (int)$matches[1];
|
|
|
|
|
switch (strtoupper($matches[2] ?? '')) {
|
|
|
|
|
case 'G':
|
|
|
|
|
$limit *= 1024 * 1024 * 1024;
|
|
|
|
|
break;
|
|
|
|
|
case 'M':
|
|
|
|
|
$limit *= 1024 * 1024;
|
|
|
|
|
break;
|
|
|
|
|
case 'K':
|
|
|
|
|
$limit *= 1024;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (int)$limit;
|
|
|
|
|
}
|