chore: split up appinterfaceprovider and implement the new interfaces

This commit is contained in:
fiachra mcdermott 2021-06-21 17:33:05 -07:00
parent 6f1f671329
commit bd55a5aec3
5 changed files with 156 additions and 152 deletions

View File

@ -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);
}
}
}
}

View 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);
}
}
}

View 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',
]);
}
}

View File

@ -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';

View 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
];
}
}