1
0
mirror of https://github.com/danog/psalm.git synced 2024-12-16 19:36:59 +01:00
psalm/src/psalm-language-server.php
Tyson Andre b713066d32 Add an exception handler that prints full exception trace (#2418)
By default, php will only print the first few thousand bytes of the
exception, for an uncaught exception
(I think that's the default, and not just my configuration)

Instead, print all of the parts of the exception.

For #1083 (this PR does not cover set_error_handler)
2019-12-04 23:17:08 -05:00

261 lines
6.8 KiB
PHP

<?php
require_once('command_functions.php');
use Psalm\Config;
use Psalm\Internal\Analyzer\ProjectAnalyzer;
gc_disable();
// show all errors
error_reporting(-1);
require_once __DIR__ . '/Psalm/Internal/exception_handler.php';
$valid_short_options = [
'h',
'v',
'c:',
'r:',
];
$valid_long_options = [
'clear-cache',
'config:',
'find-dead-code',
'help',
'root:',
'use-ini-defaults',
'version',
'tcp:',
'tcp-server',
'disable-on-change::',
'enable-autocomplete',
];
$args = array_slice($argv, 1);
$psalm_proxy = array_search('--language-server', $args, true);
if ($psalm_proxy !== false) {
unset($args[$psalm_proxy]);
}
array_map(
/**
* @param string $arg
*
* @return void
*/
function ($arg) use ($valid_long_options, $valid_short_options) {
if (substr($arg, 0, 2) === '--' && $arg !== '--') {
$arg_name = preg_replace('/=.*$/', '', substr($arg, 2));
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)
) {
fwrite(
STDERR,
'Unrecognised argument "--' . $arg_name . '"' . PHP_EOL
. 'Type --help to see a list of supported arguments' . PHP_EOL
);
error_log('Bad argument');
exit(1);
}
} elseif (substr($arg, 0, 2) === '-' && $arg !== '-' && $arg !== '--') {
$arg_name = preg_replace('/=.*$/', '', substr($arg, 1));
if (!in_array($arg_name, $valid_short_options, true)
&& !in_array($arg_name . ':', $valid_short_options, true)
) {
fwrite(
STDERR,
'Unrecognised argument "-' . $arg_name . '"' . PHP_EOL
. 'Type --help to see a list of supported arguments' . PHP_EOL
);
error_log('Bad argument');
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'])) {
fwrite(STDERR, 'Too many config files provided' . PHP_EOL);
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)
--tcp-server
Use TCP in server mode (default is client)
--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.
--enable-autocomplete[=BOOL]
Enables or disables autocomplete on methods and properties. Default is true.
HELP;
exit;
}
if (getcwd() === false) {
fwrite(STDERR, 'Cannot get current working directory' . PHP_EOL);
exit(1);
}
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) {
fwrite(
STDERR,
'Could not locate root directory ' . $current_dir . DIRECTORY_SEPARATOR . $options['r'] . PHP_EOL
);
exit(1);
}
$current_dir = $root_path . DIRECTORY_SEPARATOR;
}
$vendor_dir = getVendorDir($current_dir);
$first_autoloader = requireAutoloaders($current_dir, isset($options['r']), $vendor_dir);
if (array_key_exists('v', $options)) {
echo 'Psalm ' . PSALM_VERSION . PHP_EOL;
exit;
}
if (ini_get('pcre.jit') === '1'
&& PHP_OS === 'Darwin'
&& version_compare(PHP_VERSION, '7.3.0') >= 0
&& version_compare(PHP_VERSION, '7.4.0') < 0
) {
die(\Psalm\Internal\Fork\Pool::MAC_PCRE_MESSAGE . PHP_EOL . PHP_EOL);
}
$ini_handler = new \Psalm\Internal\Fork\PsalmRestarter('PSALM');
$ini_handler->disableExtension('grpc');
// If Xdebug is enabled, restart without it
$ini_handler->check();
setlocale(LC_CTYPE, 'C');
$path_to_config = get_path_to_config($options);
if (isset($options['tcp'])) {
if (!is_string($options['tcp'])) {
fwrite(STDERR, 'tcp url should be a string' . PHP_EOL);
exit(1);
}
}
$find_dead_code = isset($options['find-dead-code']);
$config = initialiseConfig($path_to_config, $current_dir, \Psalm\Report::TYPE_CONSOLE, $first_autoloader);
if ($config->resolve_from_config_file) {
$current_dir = $config->base_dir;
chdir($current_dir);
}
$config->setServerMode();
if (isset($options['clear-cache'])) {
$cache_directory = $config->getCacheDirectory();
Config::removeCacheDirectory($cache_directory);
echo 'Cache directory deleted' . PHP_EOL;
exit;
}
$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)
);
$project_analyzer = new ProjectAnalyzer(
$config,
$providers
);
if (isset($options['disable-on-change'])) {
$project_analyzer->onchange_line_limit = (int) $options['disable-on-change'];
}
$project_analyzer->provide_completion = !isset($options['enable-autocomplete'])
|| !is_string($options['enable-autocomplete'])
|| strtolower($options['enable-autocomplete']) !== 'false';
$config->visitComposerAutoloadFiles($project_analyzer);
if ($find_dead_code) {
$project_analyzer->getCodebase()->reportUnusedCode();
}
$project_analyzer->server($options['tcp'] ?? null, isset($options['tcp-server']) ? true : false);