mirror of
https://github.com/danog/psalm.git
synced 2024-11-30 04:39:00 +01:00
Merge branch 'amp_v3' into strict_types
This commit is contained in:
commit
dea38564a9
@ -1,6 +1,8 @@
|
||||
# Upgrading from Psalm 5 to Psalm 6
|
||||
## Changed
|
||||
|
||||
- The minimum PHP version was raised to PHP 8.1.17.
|
||||
|
||||
- [BC] Switched the internal representation of `list<T>` and `non-empty-list<T>` from the TList and TNonEmptyList classes to an unsealed list shape: the TList, TNonEmptyList and TCallableList classes were removed.
|
||||
Nothing will change for users: the `list<T>` and `non-empty-list<T>` syntax will remain supported and its semantics unchanged.
|
||||
Psalm 5 already deprecates the `TList`, `TNonEmptyList` and `TCallableList` classes: use `\Psalm\Type::getListAtomic`, `\Psalm\Type::getNonEmptyListAtomic` and `\Psalm\Type::getCallableListAtomic` to instantiate list atomics, or directly instantiate TKeyedArray objects with `is_list=true` where appropriate.
|
||||
@ -9,6 +11,8 @@
|
||||
|
||||
- [BC] The `TDependentListKey` type was removed and replaced with an optional property of the `TIntRange` type.
|
||||
|
||||
- [BC] The return type of `Psalm\Internal\LanguageServer\ProtocolWriter#write() changed from `Amp\Promise` to `void` due to the switch to Amp v3
|
||||
|
||||
# Upgrading from Psalm 4 to Psalm 5
|
||||
## Changed
|
||||
|
||||
|
@ -15,7 +15,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "~8.1.0 || ~8.2.0",
|
||||
"php": "~8.1.17 || ~8.2.4",
|
||||
"ext-SimpleXML": "*",
|
||||
"ext-ctype": "*",
|
||||
"ext-dom": "*",
|
||||
|
@ -6,7 +6,9 @@ It currently supports diagnostics (i.e. finding errors and warnings), go-to-defi
|
||||
|
||||
It works well in a variety of editors (listed alphabetically):
|
||||
|
||||
## Emacs
|
||||
## Client configuration
|
||||
|
||||
### Emacs
|
||||
|
||||
I got it working with [eglot](https://github.com/joaotavora/eglot)
|
||||
|
||||
@ -27,13 +29,13 @@ This is the config I used:
|
||||
)
|
||||
```
|
||||
|
||||
## PhpStorm
|
||||
### PhpStorm
|
||||
|
||||
### Native Support
|
||||
#### Native Support
|
||||
|
||||
As of PhpStorm 2020.3 support for psalm is supported and on by default, you can read more about that [here](https://www.jetbrains.com/help/phpstorm/using-psalm.html)
|
||||
|
||||
### With LSP
|
||||
#### With LSP
|
||||
|
||||
Alternatively, psalm works with `gtache/intellij-lsp` plugin ([Jetbrains-approved version](https://plugins.jetbrains.com/plugin/10209-lsp-support), [latest version](https://github.com/gtache/intellij-lsp/releases/tag/v1.6.0)).
|
||||
|
||||
@ -51,7 +53,7 @@ In the "Server definitions" tab you should add a definition for Psalm:
|
||||
|
||||
In the "Timeouts" tab you can adjust the initialization timeout. This is important if you have a large project. You should set the "Init" value to the number of milliseconds you allow Psalm to scan your entire project and your project's dependencies. For opening a couple of projects that use large PHP frameworks, on a high-end business laptop, try `240000` milliseconds for Init.
|
||||
|
||||
## Sublime Text
|
||||
### Sublime Text
|
||||
|
||||
I use the excellent Sublime [LSP plugin](https://github.com/tomv564/LSP) with the following config(Package Settings > LSP > Settings):
|
||||
```json
|
||||
@ -64,7 +66,7 @@ I use the excellent Sublime [LSP plugin](https://github.com/tomv564/LSP) with th
|
||||
}
|
||||
```
|
||||
|
||||
## Vim & Neovim
|
||||
### Vim & Neovim
|
||||
|
||||
**ALE**
|
||||
|
||||
@ -105,6 +107,15 @@ Add settings to `coc-settings.json`:
|
||||
}
|
||||
```
|
||||
|
||||
## VS Code
|
||||
### VS Code
|
||||
|
||||
[Get the Psalm plugin here](https://marketplace.visualstudio.com/items?itemName=getpsalm.psalm-vscode-plugin) (Requires VS Code 1.26+):
|
||||
|
||||
## Running the server in a docker container
|
||||
|
||||
Make sure you use `--map-folder` option. Using it without argument will map the server's CWD to the host's project root folder. You can also specify a custom mapping. For example:
|
||||
```bash
|
||||
docker-compose exec php /usr/share/php/psalm/psalm-language-server \
|
||||
-r=/var/www/html \
|
||||
--map-folder=/var/www/html:$PWD
|
||||
```
|
||||
|
@ -181,9 +181,6 @@ class ConcatAnalyzer
|
||||
}
|
||||
|
||||
if ($literal_concat) {
|
||||
// Bypass opcache bug: https://github.com/php/php-src/issues/10635
|
||||
(function (int $_): void {
|
||||
})($combinations);
|
||||
if (count($result_type_parts) === 0) {
|
||||
throw new AssertionError("The number of parts cannot be 0!");
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ use Psalm\Internal\Fork\PsalmRestarter;
|
||||
use Psalm\Internal\IncludeCollector;
|
||||
use Psalm\Internal\LanguageServer\ClientConfiguration;
|
||||
use Psalm\Internal\LanguageServer\LanguageServer as LanguageServerLanguageServer;
|
||||
use Psalm\Internal\LanguageServer\PathMapper;
|
||||
use Psalm\Report;
|
||||
|
||||
use function array_key_exists;
|
||||
@ -20,6 +21,7 @@ use function array_search;
|
||||
use function array_slice;
|
||||
use function chdir;
|
||||
use function error_log;
|
||||
use function explode;
|
||||
use function fwrite;
|
||||
use function gc_disable;
|
||||
use function getcwd;
|
||||
@ -33,6 +35,7 @@ use function is_string;
|
||||
use function preg_replace;
|
||||
use function realpath;
|
||||
use function setlocale;
|
||||
use function strlen;
|
||||
use function strpos;
|
||||
use function strtolower;
|
||||
use function substr;
|
||||
@ -77,6 +80,7 @@ final class LanguageServer
|
||||
'find-dead-code',
|
||||
'help',
|
||||
'root:',
|
||||
'map-folder::',
|
||||
'use-ini-defaults',
|
||||
'version',
|
||||
'tcp:',
|
||||
@ -129,6 +133,14 @@ final class LanguageServer
|
||||
|
||||
// get options from command line
|
||||
$options = getopt(implode('', $valid_short_options), $valid_long_options);
|
||||
if ($options === false) {
|
||||
// shouldn't really happen, but just in case
|
||||
fwrite(
|
||||
STDERR,
|
||||
'Failed to get CLI args' . PHP_EOL,
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!array_key_exists('use-ini-defaults', $options)) {
|
||||
ini_set('display_errors', '1');
|
||||
@ -171,6 +183,14 @@ final class LanguageServer
|
||||
-r, --root
|
||||
If running Psalm globally you'll need to specify a project root. Defaults to cwd
|
||||
|
||||
--map-folder[=SERVER_FOLDER:CLIENT_FOLDER]
|
||||
Specify folder to map between the client and the server. Use this when the client
|
||||
and server have different views of the filesystem (e.g. in a docker container).
|
||||
Defaults to mapping the rootUri provided by the client to the server's cwd,
|
||||
or `-r` if provided.
|
||||
|
||||
No mapping is done when this option is not specified.
|
||||
|
||||
--find-dead-code
|
||||
Look for dead code
|
||||
|
||||
@ -293,6 +313,8 @@ final class LanguageServer
|
||||
|
||||
setlocale(LC_CTYPE, 'C');
|
||||
|
||||
$path_mapper = self::createPathMapper($options, $current_dir);
|
||||
|
||||
$path_to_config = CliUtils::getPathToConfig($options);
|
||||
|
||||
if (isset($options['tcp'])) {
|
||||
@ -396,6 +418,49 @@ final class LanguageServer
|
||||
$clientConfiguration->TCPServerAddress = $options['tcp'] ?? null;
|
||||
$clientConfiguration->TCPServerMode = isset($options['tcp-server']);
|
||||
|
||||
LanguageServerLanguageServer::run($config, $clientConfiguration, $current_dir, $inMemory);
|
||||
LanguageServerLanguageServer::run($config, $clientConfiguration, $current_dir, $path_mapper, $inMemory);
|
||||
}
|
||||
|
||||
/** @param array<string,string|false|list<string|false>> $options */
|
||||
private static function createPathMapper(array $options, string $server_start_dir): PathMapper
|
||||
{
|
||||
if (!isset($options['map-folder'])) {
|
||||
// dummy no-op mapper
|
||||
return new PathMapper('/', '/');
|
||||
}
|
||||
|
||||
$map_folder = $options['map-folder'];
|
||||
|
||||
if ($map_folder === false) {
|
||||
// autoconfigured mapper
|
||||
return new PathMapper($server_start_dir, null);
|
||||
}
|
||||
|
||||
if (is_string($map_folder)) {
|
||||
if (strpos($map_folder, ':') === false) {
|
||||
fwrite(
|
||||
STDERR,
|
||||
'invalid format for --map-folder option' . PHP_EOL,
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
/** @psalm-suppress PossiblyUndefinedArrayOffset we just checked that we have the separator*/
|
||||
[$server_dir, $client_dir] = explode(':', $map_folder, 2);
|
||||
if (!strlen($server_dir) || !strlen($client_dir)) {
|
||||
fwrite(
|
||||
STDERR,
|
||||
'invalid format for --map-folder option, '
|
||||
. 'neither SERVER_FOLDER nor CLIENT_FOLDER can be empty' . PHP_EOL,
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
return new PathMapper($server_dir, $client_dir);
|
||||
}
|
||||
|
||||
fwrite(
|
||||
STDERR,
|
||||
'--map-folder option can only be specified once' . PHP_EOL,
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
@ -137,6 +137,8 @@ class LanguageServer extends Dispatcher
|
||||
*/
|
||||
protected JsonMapper $mapper;
|
||||
|
||||
protected PathMapper $path_mapper;
|
||||
|
||||
public function __construct(
|
||||
ProtocolReader $reader,
|
||||
ProtocolWriter $writer,
|
||||
@ -144,6 +146,7 @@ class LanguageServer extends Dispatcher
|
||||
Codebase $codebase,
|
||||
ClientConfiguration $clientConfiguration,
|
||||
Progress $progress,
|
||||
PathMapper $path_mapper
|
||||
) {
|
||||
parent::__construct($this, '/');
|
||||
|
||||
@ -153,6 +156,8 @@ class LanguageServer extends Dispatcher
|
||||
|
||||
$this->codebase = $codebase;
|
||||
|
||||
$this->path_mapper = $path_mapper;
|
||||
|
||||
$this->protocolWriter = $writer;
|
||||
|
||||
$this->protocolReader = $reader;
|
||||
@ -222,6 +227,7 @@ class LanguageServer extends Dispatcher
|
||||
|
||||
$this->client = new LanguageClient($reader, $writer, $this, $clientConfiguration);
|
||||
|
||||
|
||||
$this->logInfo("Psalm Language Server ".PSALM_VERSION." has started.");
|
||||
}
|
||||
|
||||
@ -232,7 +238,8 @@ class LanguageServer extends Dispatcher
|
||||
Config $config,
|
||||
ClientConfiguration $clientConfiguration,
|
||||
string $base_dir,
|
||||
bool $inMemory = false,
|
||||
PathMapper $path_mapper,
|
||||
bool $inMemory = false
|
||||
): void {
|
||||
$progress = new Progress();
|
||||
|
||||
@ -304,6 +311,7 @@ class LanguageServer extends Dispatcher
|
||||
$codebase,
|
||||
$clientConfiguration,
|
||||
$progress,
|
||||
$path_mapper,
|
||||
);
|
||||
EventLoop::run();
|
||||
} elseif ($clientConfiguration->TCPServerMode && $clientConfiguration->TCPServerAddress) {
|
||||
@ -327,6 +335,7 @@ class LanguageServer extends Dispatcher
|
||||
$codebase,
|
||||
$clientConfiguration,
|
||||
$progress,
|
||||
$path_mapper,
|
||||
);
|
||||
EventLoop::run();
|
||||
}
|
||||
@ -340,6 +349,7 @@ class LanguageServer extends Dispatcher
|
||||
$codebase,
|
||||
$clientConfiguration,
|
||||
$progress,
|
||||
$path_mapper,
|
||||
);
|
||||
EventLoop::run();
|
||||
}
|
||||
@ -375,6 +385,10 @@ class LanguageServer extends Dispatcher
|
||||
$this->clientCapabilities = $capabilities;
|
||||
$this->trace = $trace;
|
||||
|
||||
if ($rootUri !== null) {
|
||||
$this->path_mapper->configureClientRoot($this->getPathPart($rootUri));
|
||||
}
|
||||
|
||||
$this->logInfo("Initializing...");
|
||||
$this->clientStatus('initializing');
|
||||
|
||||
@ -917,12 +931,15 @@ class LanguageServer extends Dispatcher
|
||||
|
||||
/**
|
||||
* Transforms an absolute file path into a URI as used by the language server protocol.
|
||||
*
|
||||
* @psalm-pure
|
||||
*/
|
||||
public static function pathToUri(string $filepath): string
|
||||
public function pathToUri(string $filepath): string
|
||||
{
|
||||
$filepath = trim(str_replace('\\', '/', $filepath), '/');
|
||||
$filepath = str_replace('\\', '/', $filepath);
|
||||
|
||||
$filepath = $this->path_mapper->mapServerToClient($oldpath = $filepath);
|
||||
$this->logDebug('Translated path to URI', ['from' => $oldpath, 'to' => $filepath]);
|
||||
|
||||
$filepath = trim($filepath, '/');
|
||||
$parts = explode('/', $filepath);
|
||||
// Don't %-encode the colon after a Windows drive letter
|
||||
$first = array_shift($parts);
|
||||
@ -939,7 +956,29 @@ class LanguageServer extends Dispatcher
|
||||
/**
|
||||
* Transforms URI into file path
|
||||
*/
|
||||
public static function uriToPath(string $uri): string
|
||||
public function uriToPath(string $uri): string
|
||||
{
|
||||
$filepath = urldecode($this->getPathPart($uri));
|
||||
|
||||
if (strpos($filepath, ':') !== false) {
|
||||
if ($filepath[0] === '/') {
|
||||
$filepath = substr($filepath, 1);
|
||||
}
|
||||
$filepath = str_replace('/', '\\', $filepath);
|
||||
}
|
||||
|
||||
$filepath = $this->path_mapper->mapClientToServer($oldpath = $filepath);
|
||||
$this->logDebug('Translated URI to path', ['from' => $oldpath, 'to' => $filepath]);
|
||||
|
||||
$realpath = realpath($filepath);
|
||||
if ($realpath !== false) {
|
||||
return $realpath;
|
||||
}
|
||||
|
||||
return $filepath;
|
||||
}
|
||||
|
||||
private function getPathPart(string $uri): string
|
||||
{
|
||||
$fragments = parse_url($uri);
|
||||
if ($fragments === false
|
||||
@ -949,21 +988,21 @@ class LanguageServer extends Dispatcher
|
||||
) {
|
||||
throw new InvalidArgumentException("Not a valid file URI: $uri");
|
||||
}
|
||||
return $fragments['path'];
|
||||
}
|
||||
|
||||
$filepath = urldecode($fragments['path']);
|
||||
// the methods below forward special paths
|
||||
// like `$/cancelRequest` to `$this->cancelRequest()`
|
||||
// and `$/a/b/c` to `$this->a->b->c()`
|
||||
|
||||
if (strpos($filepath, ':') !== false) {
|
||||
if ($filepath[0] === '/') {
|
||||
$filepath = substr($filepath, 1);
|
||||
}
|
||||
$filepath = str_replace('/', '\\', $filepath);
|
||||
}
|
||||
public function __isset(string $prop_name): bool
|
||||
{
|
||||
return $prop_name === '$';
|
||||
}
|
||||
|
||||
$realpath = realpath($filepath);
|
||||
if ($realpath !== false) {
|
||||
return $realpath;
|
||||
}
|
||||
|
||||
return $filepath;
|
||||
/** @return static */
|
||||
public function __get(string $_prop_name): self
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
61
src/Psalm/Internal/LanguageServer/PathMapper.php
Normal file
61
src/Psalm/Internal/LanguageServer/PathMapper.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace Psalm\Internal\LanguageServer;
|
||||
|
||||
use function rtrim;
|
||||
use function strlen;
|
||||
use function substr;
|
||||
|
||||
/** @internal */
|
||||
final class PathMapper
|
||||
{
|
||||
private string $server_root;
|
||||
private ?string $client_root;
|
||||
|
||||
public function __construct(string $server_root, ?string $client_root = null)
|
||||
{
|
||||
$this->server_root = $this->sanitizeFolderPath($server_root);
|
||||
$this->client_root = $this->sanitizeFolderPath($client_root);
|
||||
}
|
||||
|
||||
public function configureClientRoot(string $client_root): void
|
||||
{
|
||||
// ignore if preconfigured
|
||||
if ($this->client_root === null) {
|
||||
$this->client_root = $this->sanitizeFolderPath($client_root);
|
||||
}
|
||||
}
|
||||
|
||||
public function mapClientToServer(string $client_path): string
|
||||
{
|
||||
if ($this->client_root === null) {
|
||||
return $client_path;
|
||||
}
|
||||
|
||||
if (substr($client_path, 0, strlen($this->client_root)) === $this->client_root) {
|
||||
return $this->server_root . substr($client_path, strlen($this->client_root));
|
||||
}
|
||||
|
||||
return $client_path;
|
||||
}
|
||||
|
||||
public function mapServerToClient(string $server_path): string
|
||||
{
|
||||
if ($this->client_root === null) {
|
||||
return $server_path;
|
||||
}
|
||||
if (substr($server_path, 0, strlen($this->server_root)) === $this->server_root) {
|
||||
return $this->client_root . substr($server_path, strlen($this->server_root));
|
||||
}
|
||||
return $server_path;
|
||||
}
|
||||
|
||||
/** @return ($path is null ? null : string) */
|
||||
private function sanitizeFolderPath(?string $path): ?string
|
||||
{
|
||||
if ($path === null) {
|
||||
return $path;
|
||||
}
|
||||
return rtrim($path, '/');
|
||||
}
|
||||
}
|
@ -72,7 +72,7 @@ class TextDocument
|
||||
['version' => $textDocument->version, 'uri' => $textDocument->uri],
|
||||
);
|
||||
|
||||
$file_path = LanguageServer::uriToPath($textDocument->uri);
|
||||
$file_path = $this->server->uriToPath($textDocument->uri);
|
||||
|
||||
$this->codebase->removeTemporaryFileChanges($file_path);
|
||||
$this->codebase->file_provider->openFile($file_path);
|
||||
@ -95,7 +95,7 @@ class TextDocument
|
||||
['uri' => (array) $textDocument],
|
||||
);
|
||||
|
||||
$file_path = LanguageServer::uriToPath($textDocument->uri);
|
||||
$file_path = $this->server->uriToPath($textDocument->uri);
|
||||
|
||||
// reopen file
|
||||
$this->codebase->removeTemporaryFileChanges($file_path);
|
||||
@ -117,7 +117,7 @@ class TextDocument
|
||||
['version' => $textDocument->version, 'uri' => $textDocument->uri],
|
||||
);
|
||||
|
||||
$file_path = LanguageServer::uriToPath($textDocument->uri);
|
||||
$file_path = $this->server->uriToPath($textDocument->uri);
|
||||
|
||||
if (count($contentChanges) === 1 && isset($contentChanges[0]) && $contentChanges[0]->range === null) {
|
||||
$new_content = $contentChanges[0]->text;
|
||||
@ -152,7 +152,7 @@ class TextDocument
|
||||
['uri' => $textDocument->uri],
|
||||
);
|
||||
|
||||
$file_path = LanguageServer::uriToPath($textDocument->uri);
|
||||
$file_path = $this->server->uriToPath($textDocument->uri);
|
||||
|
||||
$this->codebase->file_provider->closeFile($file_path);
|
||||
$this->server->client->textDocument->publishDiagnostics($textDocument->uri, []);
|
||||
@ -175,7 +175,7 @@ class TextDocument
|
||||
'textDocument/definition',
|
||||
);
|
||||
|
||||
$file_path = LanguageServer::uriToPath($textDocument->uri);
|
||||
$file_path = $this->server->uriToPath($textDocument->uri);
|
||||
|
||||
//This currently doesnt work right with out of project files
|
||||
if (!$this->codebase->config->isInProjectDirs($file_path)) {
|
||||
@ -201,7 +201,7 @@ class TextDocument
|
||||
}
|
||||
|
||||
return new Location(
|
||||
LanguageServer::pathToUri($code_location->file_path),
|
||||
$this->server->pathToUri($code_location->file_path),
|
||||
new Range(
|
||||
new Position($code_location->getLineNumber() - 1, $code_location->getColumn() - 1),
|
||||
new Position($code_location->getEndLineNumber() - 1, $code_location->getEndColumn() - 1),
|
||||
@ -226,7 +226,7 @@ class TextDocument
|
||||
'textDocument/hover',
|
||||
);
|
||||
|
||||
$file_path = LanguageServer::uriToPath($textDocument->uri);
|
||||
$file_path = $this->server->uriToPath($textDocument->uri);
|
||||
|
||||
//This currently doesnt work right with out of project files
|
||||
if (!$this->codebase->config->isInProjectDirs($file_path)) {
|
||||
@ -281,7 +281,7 @@ class TextDocument
|
||||
'textDocument/completion',
|
||||
);
|
||||
|
||||
$file_path = LanguageServer::uriToPath($textDocument->uri);
|
||||
$file_path = $this->server->uriToPath($textDocument->uri);
|
||||
|
||||
//This currently doesnt work right with out of project files
|
||||
if (!$this->codebase->config->isInProjectDirs($file_path)) {
|
||||
@ -349,7 +349,7 @@ class TextDocument
|
||||
'textDocument/signatureHelp',
|
||||
);
|
||||
|
||||
$file_path = LanguageServer::uriToPath($textDocument->uri);
|
||||
$file_path = $this->server->uriToPath($textDocument->uri);
|
||||
|
||||
//This currently doesnt work right with out of project files
|
||||
if (!$this->codebase->config->isInProjectDirs($file_path)) {
|
||||
@ -402,7 +402,7 @@ class TextDocument
|
||||
'textDocument/codeAction',
|
||||
);
|
||||
|
||||
$file_path = LanguageServer::uriToPath($textDocument->uri);
|
||||
$file_path = $this->server->uriToPath($textDocument->uri);
|
||||
|
||||
//Don't report code actions for files we arent watching
|
||||
if (!$this->codebase->config->isInProjectDirs($file_path)) {
|
||||
@ -418,7 +418,7 @@ class TextDocument
|
||||
/** @var array{type: string, snippet: string, line_from: int, line_to: int} */
|
||||
$data = (array)$diagnostic->data;
|
||||
|
||||
//$file_path = LanguageServer::uriToPath($textDocument->uri);
|
||||
//$file_path = $this->server->uriToPath($textDocument->uri);
|
||||
//$contents = $this->codebase->file_provider->getContents($file_path);
|
||||
|
||||
$snippetRange = new Range(
|
||||
|
@ -61,7 +61,7 @@ class Workspace
|
||||
$realFiles = array_filter(
|
||||
array_map(function (FileEvent $change) {
|
||||
try {
|
||||
return LanguageServer::uriToPath($change->uri);
|
||||
return $this->server->uriToPath($change->uri);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
return null;
|
||||
}
|
||||
@ -77,7 +77,7 @@ class Workspace
|
||||
}
|
||||
|
||||
foreach ($changes as $change) {
|
||||
$file_path = LanguageServer::uriToPath($change->uri);
|
||||
$file_path = $this->server->uriToPath($change->uri);
|
||||
|
||||
if ($composerLockFile === $file_path) {
|
||||
continue;
|
||||
@ -136,7 +136,7 @@ class Workspace
|
||||
case 'psalm.analyze.uri':
|
||||
/** @var array{uri: string} */
|
||||
$arguments = (array) $arguments;
|
||||
$file = LanguageServer::uriToPath($arguments['uri']);
|
||||
$file = $this->server->uriToPath($arguments['uri']);
|
||||
$this->codebase->reloadFiles(
|
||||
$this->project_analyzer,
|
||||
[$file],
|
||||
|
@ -9,6 +9,7 @@ use Psalm\Internal\Analyzer\ProjectAnalyzer;
|
||||
use Psalm\Internal\LanguageServer\ClientConfiguration;
|
||||
use Psalm\Internal\LanguageServer\LanguageServer;
|
||||
use Psalm\Internal\LanguageServer\Message;
|
||||
use Psalm\Internal\LanguageServer\PathMapper;
|
||||
use Psalm\Internal\LanguageServer\Progress;
|
||||
use Psalm\Internal\Provider\FakeFileProvider;
|
||||
use Psalm\Internal\Provider\Providers;
|
||||
@ -21,6 +22,7 @@ use Psalm\Tests\LanguageServer\Message as MessageBody;
|
||||
use Psalm\Tests\LanguageServer\MockProtocolStream;
|
||||
use Psalm\Tests\TestConfig;
|
||||
|
||||
use function getcwd;
|
||||
use function rand;
|
||||
|
||||
class DiagnosticTest extends AsyncTestCase
|
||||
@ -84,6 +86,7 @@ class DiagnosticTest extends AsyncTestCase
|
||||
$this->codebase,
|
||||
$clientConfiguration,
|
||||
new Progress,
|
||||
new PathMapper(getcwd(), getcwd()),
|
||||
);
|
||||
|
||||
$write->on('message', function (Message $message) use ($deferred, $server): void {
|
||||
|
75
tests/LanguageServer/PathMapperTest.php
Normal file
75
tests/LanguageServer/PathMapperTest.php
Normal file
@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace Psalm\Tests\LanguageServer;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psalm\Internal\LanguageServer\PathMapper;
|
||||
|
||||
final class PathMapperTest extends TestCase
|
||||
{
|
||||
public function testUsesUpdatedClientRoot(): void
|
||||
{
|
||||
$mapper = new PathMapper('/var/www');
|
||||
$mapper->configureClientRoot('/home/user/src/project');
|
||||
$this->assertSame(
|
||||
'/home/user/src/project/filename.php',
|
||||
$mapper->mapServerToClient('/var/www/filename.php'),
|
||||
);
|
||||
}
|
||||
|
||||
public function testIgnoresClientRootIfItWasPreconfigures(): void
|
||||
{
|
||||
$mapper = new PathMapper('/var/www', '/home/user/src/project');
|
||||
// this will be ignored
|
||||
$mapper->configureClientRoot('/home/anotheruser/Projects/project');
|
||||
|
||||
$this->assertSame(
|
||||
'/home/user/src/project/filename.php',
|
||||
$mapper->mapServerToClient('/var/www/filename.php'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider mappingProvider
|
||||
*/
|
||||
public function testMapsClientToServer(
|
||||
string $server_root,
|
||||
?string $client_root_reconfigured,
|
||||
string $client_root_provided_later,
|
||||
string $client_path,
|
||||
string $server_ath
|
||||
): void {
|
||||
$mapper = new PathMapper($server_root, $client_root_reconfigured);
|
||||
$mapper->configureClientRoot($client_root_provided_later);
|
||||
$this->assertSame(
|
||||
$server_ath,
|
||||
$mapper->mapClientToServer($client_path),
|
||||
);
|
||||
}
|
||||
|
||||
/** @dataProvider mappingProvider */
|
||||
public function testMapsServerToClient(
|
||||
string $server_root,
|
||||
?string $client_root_preconfigured,
|
||||
string $client_root_provided_later,
|
||||
string $client_path,
|
||||
string $server_path
|
||||
): void {
|
||||
$mapper = new PathMapper($server_root, $client_root_preconfigured);
|
||||
$mapper->configureClientRoot($client_root_provided_later);
|
||||
$this->assertSame(
|
||||
$client_path,
|
||||
$mapper->mapServerToClient($server_path),
|
||||
);
|
||||
}
|
||||
|
||||
/** @return iterable<int, array{string, string|null, string, string, string}> */
|
||||
public static function mappingProvider(): iterable
|
||||
{
|
||||
yield ["/var/a", null, "/user/project", "/user/project/filename.php", "/var/a/filename.php"];
|
||||
yield ["/var/a", "/user/project", "/whatever", "/user/project/filename.php", "/var/a/filename.php"];
|
||||
yield ["/var/a/", "/user/project", "/whatever", "/user/project/filename.php", "/var/a/filename.php"];
|
||||
yield ["/var/a", "/user/project/", "/whatever", "/user/project/filename.php", "/var/a/filename.php"];
|
||||
yield ["/var/a/", "/user/project/", "/whatever", "/user/project/filename.php", "/var/a/filename.php"];
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user