"psalm-language-server" repeats infinite startup attempts in case of "Failed to load plugin Psalm\LaravelPlugin\Plugin"
yaegassy opened this issue · 2 comments
Describe the bug
The problem occurs when psalm/plugin-laravel
is "enabled" and there is code that causes a PHP Parse error.
Is there any way to solve this problem?
Impacted Versions
barryvdh/laravel-ide-helper v2.9.1 Laravel IDE Helper, generates correct PHPDocs for all Facade classes, to improve auto-completion.
fruitcake/laravel-cors v2.0.3 Adds CORS (Cross-Origin Resource Sharing) headers support in your Laravel application
laravel/framework v8.36.2 The Laravel Framework.
laravel/sail v1.4.11 Docker files for running a basic Laravel application.
laravel/tinker v2.6.1 Powerful REPL for the Laravel framework.
psalm/plugin-laravel v1.4.3 A Laravel plugin for Psalm
spatie/laravel-ray 1.17.2 Easily debug Laravel apps
vimeo/psalm 4.7.0 A static analysis tool for finding errors in PHP applications
Additional context
Reproduction Procedure
laravel new sample
cd sample
composer require --dev vimeo/psalm
./vendor/bin/psalm --init
composer require --dev psalm/plugin-laravel
./vendor/bin/psalm-plugin enable psalm/plugin-laravel
Add code that will intentionally cause a PHP Parse error.
e.g. app/Models/User.php
<?php
dummy // PHP Parse error
namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
...snip
If you run psalm in this state, it will output "Failed to load plugin Psalm\LaravelPlugin\Plugin"
sample$ ./vendor/bin/psalm
Scanning files...
Psalm\Exception\ConfigException
Failed to load plugin Psalm\LaravelPlugin\Plugin
at vendor/vimeo/psalm/src/Psalm/Config.php:1158
1154▕ */
1155▕ $plugin_object = new $plugin_class_name;
1156▕ $plugin_object($socket, $plugin_config);
1157▕ } catch (\Throwable $e) {
➜ 1158▕ throw new ConfigException('Failed to load plugin ' . $plugin_class_name, 0, $e);
1159▕ }
1160▕
1161▕ $project_analyzer->progress->debug('Loaded plugin ' . $plugin_class_name . ' successfully' . PHP_EOL);
1162▕ }
1 app/Models/User.php:4
ParseError::("syntax error, unexpected token "namespace"")
+1 vendor frames
3 [internal]:0
Composer\Autoload\ClassLoader::loadClass("App\Models\User")
Furthermore, when I start VSCode (using psalm-vscode-plugin extension) in this state, the language server does not start properly. Furthermore, it is trying to start infinitely...
(DEMO)Editor
More
If you disable psalm/plugin-laravel, psalm will also run correctly. ./vendor/bin/psalm-plugin disable psalm/plugin-laravel
Also, "psalm-language-server" will be started correctly.
sample$ ./vendor/bin/psalm
Scanning files...
Analyzing files...
░░░░░░░░░░░░░░░░░░░E
ERROR: UndefinedConstant - app/Models/User.php:2:1 - Const dummy is not defined (see https://psalm.dev/020)
dummy // dummy
ERROR: ParseError - app/Models/User.php:4:1 - Syntax error, unexpected T_NAMESPACE on line 4 (see https://psalm.dev/173)
namespace App\Models;
------------------------------
2 errors found
------------------------------
8 other issues found.
You can display them with --show-info=true
------------------------------
Checks took 7.18 seconds and used 514.328MB of memory
Psalm was able to infer types for 93.9394% of the codebase
Thanks for the great writeup! I'd love to encourage you to PR yourself, as I don't really work with Laravel anymore unfortunately.
I bet the problem is something to do with us booting up laravel ide helper. I bet a type error is being emitted here: https://github.com/psalm/psalm-plugin-laravel/blob/master/src/Plugin.php#L36-L38
Running psalm with xdebug enabled and setting up some breakpoints is probably the next step here?
@mr-feek Thanks reply.
If there is a similar error in "app/Console/Kernel.php", it will also occur in $app = ApplicationHelper::bootApp();
.
For example, what do you think about using try/catch
to handle "early returns"?
<?php
namespace Psalm\LaravelPlugin;
// ... snip
class Plugin implements PluginEntryPointInterface
{
// ...snip
public function __invoke(RegistrationInterface $registration, ?SimpleXMLElement $config = null): void
{
try {
$app = ApplicationHelper::bootApp();
$fake_filesystem = new FakeFilesystem();
$view_factory = $this->getViewFactory($app, $fake_filesystem);
$cache_dir = __DIR__ . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR;
$this->ingestFacadeStubs($registration, $app, $fake_filesystem, $view_factory, $cache_dir);
$this->ingestMetaStubs($registration, $app, $fake_filesystem, $view_factory, $cache_dir);
$this->ingestModelStubs($registration, $app, $fake_filesystem, $cache_dir);
} catch (\Error $e) {
return;
}
// ...snip
$this->addOurStubs($registration);
}
// ...snip
}