psalm/psalm-plugin-laravel

"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

psalm-plugin-laravel-issue

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
}