mirror of
https://github.com/danog/psalm-plugin-laravel.git
synced 2024-11-30 04:39:01 +01:00
chore: split up appinterfaceprovider and implement the new interfaces
This commit is contained in:
parent
6f1f671329
commit
bd55a5aec3
@ -1,150 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Psalm\LaravelPlugin;
|
||||
|
||||
use Illuminate\Foundation\AliasLoader;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use PhpParser;
|
||||
use Psalm\Context;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\LaravelPlugin\Util\ApplicationProvider;
|
||||
use Psalm\LaravelPlugin\Util\ContainerResolver;
|
||||
use Psalm\Plugin\EventHandler\AfterClassLikeVisitInterface;
|
||||
use Psalm\Plugin\EventHandler\Event\AfterClassLikeVisitEvent;
|
||||
use Psalm\Type;
|
||||
use Psalm\StatementsSource;
|
||||
use function in_array;
|
||||
use function array_merge;
|
||||
use function array_values;
|
||||
use function strtolower;
|
||||
|
||||
class AppInterfaceProvider implements
|
||||
\Psalm\Plugin\Hook\MethodReturnTypeProviderInterface,
|
||||
\Psalm\Plugin\Hook\MethodExistenceProviderInterface,
|
||||
\Psalm\Plugin\Hook\MethodVisibilityProviderInterface,
|
||||
\Psalm\Plugin\Hook\MethodParamsProviderInterface,
|
||||
AfterClassLikeVisitInterface
|
||||
{
|
||||
/** @return array<string> */
|
||||
public static function getClassLikeNames() : array
|
||||
{
|
||||
return [
|
||||
\Illuminate\Contracts\Foundation\Application::class,
|
||||
\Illuminate\Contracts\Container\Container::class
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ?bool
|
||||
*/
|
||||
public static function doesMethodExist(
|
||||
string $fq_classlike_name,
|
||||
string $method_name_lowercase,
|
||||
StatementsSource $source = null,
|
||||
CodeLocation $code_location = null
|
||||
) : ?bool {
|
||||
if ($method_name_lowercase === 'offsetget'
|
||||
|| $method_name_lowercase === 'offsetset'
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ?bool
|
||||
*/
|
||||
public static function isMethodVisible(
|
||||
StatementsSource $source,
|
||||
string $fq_classlike_name,
|
||||
string $method_name_lowercase,
|
||||
Context $context,
|
||||
CodeLocation $code_location = null
|
||||
) : ?bool {
|
||||
if ($method_name_lowercase === 'offsetget'
|
||||
|| $method_name_lowercase === 'offsetset'
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<PhpParser\Node\Arg> $call_args
|
||||
* @return ?array<int, \Psalm\Storage\FunctionLikeParameter>
|
||||
*/
|
||||
public static function getMethodParams(
|
||||
string $fq_classlike_name,
|
||||
string $method_name_lowercase,
|
||||
array $call_args = null,
|
||||
StatementsSource $statements_source = null,
|
||||
Context $context = null,
|
||||
CodeLocation $code_location = null
|
||||
) : ?array {
|
||||
if ($statements_source) {
|
||||
if ($method_name_lowercase === 'offsetget' || $method_name_lowercase === 'offsetset') {
|
||||
return $statements_source->getCodebase()->getMethodParams(
|
||||
ApplicationProvider::getAppFullyQualifiedClassName() . '::' . $method_name_lowercase
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<PhpParser\Node\Arg> $call_args
|
||||
*/
|
||||
public static function getMethodReturnType(
|
||||
StatementsSource $source,
|
||||
string $fq_classlike_name,
|
||||
string $method_name_lowercase,
|
||||
array $call_args,
|
||||
Context $context,
|
||||
CodeLocation $code_location,
|
||||
array $template_type_parameters = null,
|
||||
string $called_fq_classlike_name = null,
|
||||
string $called_method_name_lowercase = null
|
||||
) : ?Type\Union {
|
||||
if ($method_name_lowercase === 'offsetget') {
|
||||
// offsetget is an alias for make
|
||||
return ContainerResolver::resolvePsalmTypeFromApplicationContainerViaArgs($source->getNodeTypeProvider(), $call_args);
|
||||
}
|
||||
|
||||
if ($method_name_lowercase === 'offsetset') {
|
||||
return $source->getCodebase()->getMethodReturnType(
|
||||
ApplicationProvider::getAppFullyQualifiedClassName() . '::' . $method_name_lowercase,
|
||||
$fq_classlike_name
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function afterClassLikeVisit(AfterClassLikeVisitEvent $event)
|
||||
{
|
||||
// @see https://github.com/psalm/psalm-plugin-symfony/issues/25
|
||||
// psalm needs to know about any classes that could be returned before analysis begins. This is a naive first approach
|
||||
if (in_array($event->getStorage()->name, self::getClassLikeNames())) {
|
||||
$appClassName = ApplicationProvider::getAppFullyQualifiedClassName();
|
||||
|
||||
$facades = ApplicationProvider::getApp()->make('config')->get('app.aliases', []);
|
||||
// I'm not sure why this isn't included by default, but this is a hack that fixes the bug
|
||||
$facades['rl'] = RateLimiter::class;
|
||||
|
||||
$classesThatCouldBeReturnedThatArentReferencedAlready = array_merge(
|
||||
[$appClassName],
|
||||
array_values(AliasLoader::getInstance($facades)->getAliases()),
|
||||
);
|
||||
|
||||
foreach ($classesThatCouldBeReturnedThatArentReferencedAlready as $className) {
|
||||
$filePath = $event->getStatementsSource()->getFilePath();
|
||||
$fileStorage = $event->getCodebase()->file_storage_provider->get($filePath);
|
||||
$fileStorage->referenced_classlikes[strtolower($className)] = $className;
|
||||
$event->getCodebase()->queueClassLikeForScanning($className);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
46
src/Handlers/Application/ContainerHandler.php
Normal file
46
src/Handlers/Application/ContainerHandler.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace Psalm\LaravelPlugin\Handlers\Application;
|
||||
|
||||
use Illuminate\Foundation\AliasLoader;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use Psalm\LaravelPlugin\Providers\ApplicationInterfaceProvider;
|
||||
use Psalm\LaravelPlugin\Util\ApplicationProvider;
|
||||
use Psalm\Plugin\EventHandler\AfterClassLikeVisitInterface;
|
||||
use Psalm\Plugin\EventHandler\Event\AfterClassLikeVisitEvent;
|
||||
use function in_array;
|
||||
use function array_merge;
|
||||
use function array_values;
|
||||
use function strtolower;
|
||||
|
||||
/**
|
||||
* @see https://github.com/psalm/psalm-plugin-symfony/issues/25
|
||||
* psalm needs to know about any classes that could be returned before analysis begins. This is a naive first approach
|
||||
*/
|
||||
final class ContainerHandler implements AfterClassLikeVisitInterface
|
||||
{
|
||||
public static function afterClassLikeVisit(AfterClassLikeVisitEvent $event)
|
||||
{
|
||||
if (!in_array($event->getStorage()->name, ApplicationInterfaceProvider::getApplicationInterfaceClassLikes())) {
|
||||
return;
|
||||
}
|
||||
|
||||
$appClassName = ApplicationProvider::getAppFullyQualifiedClassName();
|
||||
|
||||
$facades = ApplicationProvider::getApp()->make('config')->get('app.aliases', []);
|
||||
// I'm not sure why this isn't included by default, but this is a hack that fixes the bug
|
||||
$facades['rl'] = RateLimiter::class;
|
||||
|
||||
$classesThatCouldBeReturnedThatArentReferencedAlready = array_merge(
|
||||
[$appClassName],
|
||||
array_values(AliasLoader::getInstance($facades)->getAliases()),
|
||||
);
|
||||
|
||||
foreach ($classesThatCouldBeReturnedThatArentReferencedAlready as $className) {
|
||||
$filePath = $event->getStatementsSource()->getFilePath();
|
||||
$fileStorage = $event->getCodebase()->file_storage_provider->get($filePath);
|
||||
$fileStorage->referenced_classlikes[strtolower($className)] = $className;
|
||||
$event->getCodebase()->queueClassLikeForScanning($className);
|
||||
}
|
||||
}
|
||||
}
|
90
src/Handlers/Application/OffsetHandler.php
Normal file
90
src/Handlers/Application/OffsetHandler.php
Normal file
@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace Psalm\LaravelPlugin\Handlers\Application;
|
||||
|
||||
use Psalm\LaravelPlugin\Providers\ApplicationInterfaceProvider;
|
||||
use Psalm\LaravelPlugin\Util\ApplicationProvider;
|
||||
use Psalm\LaravelPlugin\Util\ContainerResolver;
|
||||
use Psalm\Plugin\EventHandler\Event\MethodExistenceProviderEvent;
|
||||
use Psalm\Plugin\EventHandler\Event\MethodParamsProviderEvent;
|
||||
use Psalm\Plugin\EventHandler\Event\MethodReturnTypeProviderEvent;
|
||||
use Psalm\Plugin\EventHandler\Event\MethodVisibilityProviderEvent;
|
||||
use Psalm\Plugin\EventHandler\MethodExistenceProviderInterface;
|
||||
use Psalm\Plugin\EventHandler\MethodParamsProviderInterface;
|
||||
use Psalm\Plugin\EventHandler\MethodReturnTypeProviderInterface;
|
||||
use Psalm\Plugin\EventHandler\MethodVisibilityProviderInterface;
|
||||
use Psalm\Type;
|
||||
use function in_array;
|
||||
|
||||
final class OffsetHandler implements
|
||||
MethodReturnTypeProviderInterface,
|
||||
MethodExistenceProviderInterface,
|
||||
MethodVisibilityProviderInterface,
|
||||
MethodParamsProviderInterface
|
||||
{
|
||||
/**
|
||||
* @return array<class-string>
|
||||
*/
|
||||
public static function getClassLikeNames(): array
|
||||
{
|
||||
return ApplicationInterfaceProvider::getApplicationInterfaceClassLikes();
|
||||
}
|
||||
|
||||
public static function getMethodReturnType(MethodReturnTypeProviderEvent $event): ?Type\Union
|
||||
{
|
||||
$method_name_lowercase = $event->getMethodNameLowercase();
|
||||
$source = $event->getSource();
|
||||
|
||||
if ($method_name_lowercase === 'offsetget') {
|
||||
// offsetget is an alias for make
|
||||
return ContainerResolver::resolvePsalmTypeFromApplicationContainerViaArgs(
|
||||
$source->getNodeTypeProvider(),
|
||||
$event->getCallArgs()
|
||||
);
|
||||
}
|
||||
|
||||
if ($method_name_lowercase === 'offsetset') {
|
||||
$fq_classlike_name = $event->getFqClasslikeName();
|
||||
return $source->getCodebase()->getMethodReturnType(
|
||||
ApplicationProvider::getAppFullyQualifiedClassName() . '::' . $method_name_lowercase,
|
||||
$fq_classlike_name
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function doesMethodExist(MethodExistenceProviderEvent $event): ?bool
|
||||
{
|
||||
return self::isOffsetMethod($event->getMethodNameLowercase()) ? true : null;
|
||||
}
|
||||
|
||||
public static function isMethodVisible(MethodVisibilityProviderEvent $event): ?bool
|
||||
{
|
||||
return self::isOffsetMethod($event->getMethodNameLowercase()) ? true : null;
|
||||
}
|
||||
|
||||
public static function getMethodParams(MethodParamsProviderEvent $event): ?array
|
||||
{
|
||||
$source = $event->getStatementsSource();
|
||||
if (!$source) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!self::isOffsetMethod($event->getMethodNameLowercase())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $source->getCodebase()->getMethodParams(
|
||||
ApplicationProvider::getAppFullyQualifiedClassName() . '::' . $event->getMethodNameLowercase()
|
||||
);
|
||||
}
|
||||
|
||||
private static function isOffsetMethod(string $methodName): bool
|
||||
{
|
||||
return in_array($methodName, [
|
||||
'offsetget',
|
||||
'offsetset',
|
||||
]);
|
||||
}
|
||||
}
|
@ -5,6 +5,8 @@ use Illuminate\View\Engines\EngineResolver;
|
||||
use Illuminate\View\Engines\PhpEngine;
|
||||
use Illuminate\View\Factory;
|
||||
use Illuminate\View\FileViewFinder;
|
||||
use Psalm\LaravelPlugin\Handlers\Application\ContainerHandler;
|
||||
use Psalm\LaravelPlugin\Handlers\Application\OffsetHandler;
|
||||
use Psalm\LaravelPlugin\Handlers\Eloquent\Schema\SchemaAggregator;
|
||||
use Psalm\LaravelPlugin\ReturnTypeProvider\ModelReturnTypeProvider;
|
||||
use Psalm\LaravelPlugin\ReturnTypeProvider\PathHelpersReturnTypeProvider;
|
||||
@ -50,8 +52,10 @@ class Plugin implements PluginEntryPointInterface
|
||||
$registration->registerHooksFromClass(ReturnTypeProvider\ViewReturnTypeProvider::class);
|
||||
require_once 'ReturnTypeProvider/AppReturnTypeProvider.php';
|
||||
$registration->registerHooksFromClass(ReturnTypeProvider\AppReturnTypeProvider::class);
|
||||
require_once 'AppInterfaceProvider.php';
|
||||
$registration->registerHooksFromClass(AppInterfaceProvider::class);
|
||||
require_once 'Handlers/Application/ContainerHandler.php';
|
||||
$registration->registerHooksFromClass(ContainerHandler::class);
|
||||
require_once 'Handlers/Application/OffsetHandler.php';
|
||||
$registration->registerHooksFromClass(OffsetHandler::class);
|
||||
require_once 'PropertyProvider/ModelPropertyProvider.php';
|
||||
$registration->registerHooksFromClass(PropertyProvider\ModelPropertyProvider::class);
|
||||
require_once 'ReturnTypeProvider/UrlReturnTypeProvider.php';
|
||||
|
14
src/Providers/ApplicationInterfaceProvider.php
Normal file
14
src/Providers/ApplicationInterfaceProvider.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Psalm\LaravelPlugin\Providers;
|
||||
|
||||
final class ApplicationInterfaceProvider
|
||||
{
|
||||
public static function getApplicationInterfaceClassLikes(): array
|
||||
{
|
||||
return [
|
||||
\Illuminate\Contracts\Foundation\Application::class,
|
||||
\Illuminate\Contracts\Container\Container::class
|
||||
];
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user