2018-10-18 20:34:46 +02:00
|
|
|
<?php
|
|
|
|
|
2020-07-23 00:03:17 +02:00
|
|
|
namespace Psalm;
|
2020-07-22 21:57:24 +02:00
|
|
|
|
2020-07-22 22:04:46 +02:00
|
|
|
use Psalm\Internal\Analyzer\ProjectAnalyzer;
|
2020-10-03 08:26:37 +02:00
|
|
|
use Psalm\Internal\Composer;
|
2021-02-22 15:19:34 +01:00
|
|
|
use Psalm\Internal\ErrorHandler;
|
2020-07-22 22:04:46 +02:00
|
|
|
use Psalm\Internal\IncludeCollector;
|
2021-02-22 15:19:34 +01:00
|
|
|
|
|
|
|
use function array_key_exists;
|
2020-07-23 00:03:17 +02:00
|
|
|
use function array_map;
|
2021-02-22 15:19:34 +01:00
|
|
|
use function array_search;
|
|
|
|
use function array_slice;
|
|
|
|
use function chdir;
|
2020-07-23 00:03:17 +02:00
|
|
|
use function error_log;
|
2021-02-22 15:19:34 +01:00
|
|
|
use function fwrite;
|
|
|
|
use function gc_disable;
|
|
|
|
use function getcwd;
|
2020-07-23 00:03:17 +02:00
|
|
|
use function getopt;
|
|
|
|
use function implode;
|
2021-02-22 15:19:34 +01:00
|
|
|
use function in_array;
|
2020-07-23 00:03:17 +02:00
|
|
|
use function ini_set;
|
|
|
|
use function is_array;
|
|
|
|
use function is_string;
|
2021-02-22 15:19:34 +01:00
|
|
|
use function preg_replace;
|
2020-07-23 00:03:17 +02:00
|
|
|
use function realpath;
|
|
|
|
use function setlocale;
|
|
|
|
use function strtolower;
|
2021-02-22 15:19:34 +01:00
|
|
|
use function substr;
|
2020-07-22 22:04:46 +02:00
|
|
|
|
2021-02-22 15:19:34 +01:00
|
|
|
use const DIRECTORY_SEPARATOR;
|
|
|
|
use const LC_CTYPE;
|
|
|
|
use const PHP_EOL;
|
|
|
|
use const STDERR;
|
2018-10-18 20:34:46 +02:00
|
|
|
|
2018-11-18 17:39:14 +01:00
|
|
|
gc_disable();
|
|
|
|
|
2021-02-22 15:19:34 +01:00
|
|
|
require_once __DIR__ . '/Psalm/Internal/ErrorHandler.php';
|
|
|
|
ErrorHandler::install();
|
2018-10-18 20:34:46 +02:00
|
|
|
|
2021-02-22 15:19:34 +01:00
|
|
|
require_once('command_functions.php');
|
|
|
|
require_once __DIR__ . '/Psalm/Internal/Composer.php';
|
2019-12-05 05:17:08 +01:00
|
|
|
|
2018-10-18 20:34:46 +02:00
|
|
|
$valid_short_options = [
|
|
|
|
'h',
|
|
|
|
'v',
|
|
|
|
'c:',
|
|
|
|
'r:',
|
|
|
|
];
|
|
|
|
|
|
|
|
$valid_long_options = [
|
|
|
|
'clear-cache',
|
|
|
|
'config:',
|
|
|
|
'find-dead-code',
|
|
|
|
'help',
|
|
|
|
'root:',
|
|
|
|
'use-ini-defaults',
|
|
|
|
'version',
|
|
|
|
'tcp:',
|
2018-11-20 04:57:59 +01:00
|
|
|
'tcp-server',
|
2019-05-09 17:20:13 +02:00
|
|
|
'disable-on-change::',
|
|
|
|
'enable-autocomplete',
|
2020-04-17 06:47:18 +02:00
|
|
|
'use-extended-diagnostic-codes',
|
|
|
|
'verbose'
|
2018-10-18 20:34:46 +02:00
|
|
|
];
|
|
|
|
|
|
|
|
$args = array_slice($argv, 1);
|
|
|
|
|
2019-07-05 22:24:00 +02:00
|
|
|
$psalm_proxy = array_search('--language-server', $args, true);
|
2019-03-27 16:55:10 +01:00
|
|
|
|
|
|
|
if ($psalm_proxy !== false) {
|
|
|
|
unset($args[$psalm_proxy]);
|
|
|
|
}
|
|
|
|
|
2018-10-18 20:34:46 +02:00
|
|
|
array_map(
|
|
|
|
/**
|
|
|
|
* @param string $arg
|
|
|
|
*/
|
2020-11-13 19:16:39 +01:00
|
|
|
function ($arg) use ($valid_long_options): void {
|
2018-10-18 20:34:46 +02:00
|
|
|
if (substr($arg, 0, 2) === '--' && $arg !== '--') {
|
|
|
|
$arg_name = preg_replace('/=.*$/', '', substr($arg, 2));
|
|
|
|
|
2019-07-05 22:24:00 +02:00
|
|
|
if (!in_array($arg_name, $valid_long_options, true)
|
|
|
|
&& !in_array($arg_name . ':', $valid_long_options, true)
|
|
|
|
&& !in_array($arg_name . '::', $valid_long_options, true)
|
2018-10-30 23:58:22 +01:00
|
|
|
) {
|
2019-05-30 16:30:41 +02:00
|
|
|
fwrite(
|
|
|
|
STDERR,
|
|
|
|
'Unrecognised argument "--' . $arg_name . '"' . PHP_EOL
|
2019-07-05 22:24:00 +02:00
|
|
|
. 'Type --help to see a list of supported arguments' . PHP_EOL
|
2019-05-30 16:30:41 +02:00
|
|
|
);
|
2019-05-09 17:20:13 +02:00
|
|
|
error_log('Bad argument');
|
2018-10-18 20:34:46 +02:00
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
$args
|
|
|
|
);
|
|
|
|
|
|
|
|
// get options from command line
|
|
|
|
$options = getopt(implode('', $valid_short_options), $valid_long_options);
|
|
|
|
|
|
|
|
if (!array_key_exists('use-ini-defaults', $options)) {
|
|
|
|
ini_set('display_errors', '1');
|
|
|
|
ini_set('display_startup_errors', '1');
|
|
|
|
ini_set('memory_limit', (string) (4 * 1024 * 1024 * 1024));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (array_key_exists('help', $options)) {
|
|
|
|
$options['h'] = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (array_key_exists('version', $options)) {
|
|
|
|
$options['v'] = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isset($options['config'])) {
|
|
|
|
$options['c'] = $options['config'];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isset($options['c']) && is_array($options['c'])) {
|
2019-05-30 16:30:41 +02:00
|
|
|
fwrite(STDERR, 'Too many config files provided' . PHP_EOL);
|
2018-10-18 20:34:46 +02:00
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (array_key_exists('h', $options)) {
|
|
|
|
echo <<<HELP
|
|
|
|
Usage:
|
|
|
|
psalm-language-server [options]
|
|
|
|
|
|
|
|
Options:
|
|
|
|
-h, --help
|
|
|
|
Display this help message
|
|
|
|
|
|
|
|
-v, --version
|
|
|
|
Display the Psalm version
|
|
|
|
|
|
|
|
-c, --config=psalm.xml
|
|
|
|
Path to a psalm.xml configuration file. Run psalm --init to create one.
|
|
|
|
|
|
|
|
-r, --root
|
|
|
|
If running Psalm globally you'll need to specify a project root. Defaults to cwd
|
|
|
|
|
|
|
|
--find-dead-code
|
|
|
|
Look for dead code
|
|
|
|
|
|
|
|
--clear-cache
|
|
|
|
Clears all cache files that the language server uses for this specific project
|
|
|
|
|
|
|
|
--use-ini-defaults
|
|
|
|
Use PHP-provided ini defaults for memory and error display
|
|
|
|
|
|
|
|
--tcp=url
|
|
|
|
Use TCP mode (by default Psalm uses STDIO)
|
|
|
|
|
2018-11-20 04:57:59 +01:00
|
|
|
--tcp-server
|
|
|
|
Use TCP in server mode (default is client)
|
|
|
|
|
2018-10-30 23:58:22 +01:00
|
|
|
--disable-on-change[=line-number-threshold]
|
|
|
|
If added, the language server will not respond to onChange events.
|
|
|
|
You can also specify a line count over which Psalm will not run on-change events.
|
2019-05-09 17:20:13 +02:00
|
|
|
|
2019-06-13 22:58:17 +02:00
|
|
|
--enable-autocomplete[=BOOL]
|
|
|
|
Enables or disables autocomplete on methods and properties. Default is true.
|
2020-04-17 06:47:18 +02:00
|
|
|
|
|
|
|
--use-extended-diagnostic-codes
|
|
|
|
Enables sending help uri links with the code in diagnostic messages.
|
|
|
|
|
|
|
|
--verbose
|
|
|
|
Will send log messages to the client with information.
|
2020-11-21 23:37:50 +01:00
|
|
|
|
2018-10-18 20:34:46 +02:00
|
|
|
HELP;
|
|
|
|
|
|
|
|
exit;
|
|
|
|
}
|
|
|
|
|
2020-04-17 06:47:18 +02:00
|
|
|
if (array_key_exists('v', $options)) {
|
|
|
|
echo 'Psalm ' . PSALM_VERSION . PHP_EOL;
|
|
|
|
exit;
|
|
|
|
}
|
|
|
|
|
2018-10-18 20:34:46 +02:00
|
|
|
if (getcwd() === false) {
|
2019-05-30 16:30:41 +02:00
|
|
|
fwrite(STDERR, 'Cannot get current working directory' . PHP_EOL);
|
2018-10-18 20:34:46 +02:00
|
|
|
exit(1);
|
|
|
|
}
|
2019-06-27 16:51:13 +02:00
|
|
|
|
2018-10-18 20:34:46 +02:00
|
|
|
if (isset($options['root'])) {
|
|
|
|
$options['r'] = $options['root'];
|
|
|
|
}
|
|
|
|
|
|
|
|
$current_dir = (string)getcwd() . DIRECTORY_SEPARATOR;
|
|
|
|
|
|
|
|
if (isset($options['r']) && is_string($options['r'])) {
|
|
|
|
$root_path = realpath($options['r']);
|
|
|
|
|
|
|
|
if (!$root_path) {
|
2019-05-30 16:30:41 +02:00
|
|
|
fwrite(
|
|
|
|
STDERR,
|
|
|
|
'Could not locate root directory ' . $current_dir . DIRECTORY_SEPARATOR . $options['r'] . PHP_EOL
|
|
|
|
);
|
2018-10-18 20:34:46 +02:00
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
$current_dir = $root_path . DIRECTORY_SEPARATOR;
|
|
|
|
}
|
|
|
|
|
2020-07-22 21:57:24 +02:00
|
|
|
$vendor_dir = \Psalm\getVendorDir($current_dir);
|
2018-10-18 20:34:46 +02:00
|
|
|
|
2020-07-11 23:17:22 +02:00
|
|
|
require_once __DIR__ . '/Psalm/Internal/IncludeCollector.php';
|
|
|
|
$include_collector = new IncludeCollector();
|
|
|
|
|
|
|
|
$first_autoloader = $include_collector->runAndCollect(
|
2020-09-12 17:24:05 +02:00
|
|
|
function () use ($current_dir, $options, $vendor_dir): ?\Composer\Autoload\ClassLoader {
|
2020-07-22 21:57:24 +02:00
|
|
|
return \Psalm\requireAutoloaders($current_dir, isset($options['r']), $vendor_dir);
|
2020-07-11 23:17:22 +02:00
|
|
|
}
|
|
|
|
);
|
2018-10-18 20:34:46 +02:00
|
|
|
|
2018-11-06 03:57:36 +01:00
|
|
|
$ini_handler = new \Psalm\Internal\Fork\PsalmRestarter('PSALM');
|
2018-10-18 20:34:46 +02:00
|
|
|
|
|
|
|
$ini_handler->disableExtension('grpc');
|
|
|
|
|
2019-09-14 16:13:39 +02:00
|
|
|
// If Xdebug is enabled, restart without it
|
2018-10-18 20:34:46 +02:00
|
|
|
$ini_handler->check();
|
|
|
|
|
|
|
|
setlocale(LC_CTYPE, 'C');
|
|
|
|
|
2020-07-22 21:57:24 +02:00
|
|
|
$path_to_config = \Psalm\get_path_to_config($options);
|
2018-10-18 20:34:46 +02:00
|
|
|
|
|
|
|
if (isset($options['tcp'])) {
|
|
|
|
if (!is_string($options['tcp'])) {
|
2019-05-30 16:30:41 +02:00
|
|
|
fwrite(STDERR, 'tcp url should be a string' . PHP_EOL);
|
2018-10-18 20:34:46 +02:00
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-12 22:29:15 +02:00
|
|
|
$find_unused_code = isset($options['find-dead-code']) ? 'auto' : null;
|
2018-10-18 20:34:46 +02:00
|
|
|
|
2020-07-22 21:57:24 +02:00
|
|
|
$config = \Psalm\initialiseConfig($path_to_config, $current_dir, \Psalm\Report::TYPE_CONSOLE, $first_autoloader);
|
2020-07-11 23:17:22 +02:00
|
|
|
$config->setIncludeCollector($include_collector);
|
2019-07-07 14:55:53 +02:00
|
|
|
|
|
|
|
if ($config->resolve_from_config_file) {
|
|
|
|
$current_dir = $config->base_dir;
|
|
|
|
chdir($current_dir);
|
2018-10-18 20:34:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$config->setServerMode();
|
|
|
|
|
|
|
|
if (isset($options['clear-cache'])) {
|
|
|
|
$cache_directory = $config->getCacheDirectory();
|
|
|
|
|
2020-09-04 22:26:33 +02:00
|
|
|
if ($cache_directory !== null) {
|
|
|
|
Config::removeCacheDirectory($cache_directory);
|
|
|
|
}
|
2018-10-18 20:34:46 +02:00
|
|
|
echo 'Cache directory deleted' . PHP_EOL;
|
|
|
|
exit;
|
|
|
|
}
|
|
|
|
|
2020-07-22 21:57:24 +02:00
|
|
|
$providers = new \Psalm\Internal\Provider\Providers(
|
|
|
|
new \Psalm\Internal\Provider\FileProvider,
|
|
|
|
new \Psalm\Internal\Provider\ParserCacheProvider($config),
|
|
|
|
new \Psalm\Internal\Provider\FileStorageCacheProvider($config),
|
|
|
|
new \Psalm\Internal\Provider\ClassLikeStorageCacheProvider($config),
|
|
|
|
new \Psalm\Internal\Provider\FileReferenceCacheProvider($config),
|
2020-10-03 08:26:37 +02:00
|
|
|
new \Psalm\Internal\Provider\ProjectCacheProvider(Composer::getLockFilePath($current_dir))
|
2018-10-18 20:34:46 +02:00
|
|
|
);
|
|
|
|
|
2018-11-11 18:01:14 +01:00
|
|
|
$project_analyzer = new ProjectAnalyzer(
|
2018-10-18 20:34:46 +02:00
|
|
|
$config,
|
|
|
|
$providers
|
|
|
|
);
|
|
|
|
|
2020-08-12 22:29:15 +02:00
|
|
|
if ($config->find_unused_variables) {
|
|
|
|
$project_analyzer->getCodebase()->reportUnusedVariables();
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($config->find_unused_code) {
|
|
|
|
$find_unused_code = 'auto';
|
|
|
|
}
|
|
|
|
|
2018-10-30 23:58:22 +01:00
|
|
|
if (isset($options['disable-on-change'])) {
|
2018-11-11 18:01:14 +01:00
|
|
|
$project_analyzer->onchange_line_limit = (int) $options['disable-on-change'];
|
2018-10-30 23:58:22 +01:00
|
|
|
}
|
|
|
|
|
2019-06-13 22:58:17 +02:00
|
|
|
$project_analyzer->provide_completion = !isset($options['enable-autocomplete'])
|
|
|
|
|| !is_string($options['enable-autocomplete'])
|
|
|
|
|| strtolower($options['enable-autocomplete']) !== 'false';
|
2019-05-09 17:20:13 +02:00
|
|
|
|
2018-11-11 18:01:14 +01:00
|
|
|
$config->visitComposerAutoloadFiles($project_analyzer);
|
2018-10-18 20:34:46 +02:00
|
|
|
|
2020-08-12 22:29:15 +02:00
|
|
|
if ($find_unused_code) {
|
|
|
|
$project_analyzer->getCodebase()->reportUnusedCode($find_unused_code);
|
2018-10-18 20:34:46 +02:00
|
|
|
}
|
|
|
|
|
2020-04-17 06:47:18 +02:00
|
|
|
if (isset($options['use-extended-diagnostic-codes'])) {
|
|
|
|
$project_analyzer->language_server_use_extended_diagnostic_codes = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isset($options['verbose'])) {
|
|
|
|
$project_analyzer->language_server_verbose = true;
|
|
|
|
}
|
|
|
|
|
2018-11-20 04:57:59 +01:00
|
|
|
$project_analyzer->server($options['tcp'] ?? null, isset($options['tcp-server']) ? true : false);
|