refactor: move app return type provider into container handler

This commit is contained in:
fiachra mcdermott 2021-06-21 18:33:28 -07:00
parent 29f88e6e9a
commit d92e310bdd
3 changed files with 67 additions and 81 deletions

View File

@ -4,21 +4,83 @@ namespace Psalm\LaravelPlugin\Handlers\Application;
use Illuminate\Foundation\AliasLoader;
use Illuminate\Support\Facades\RateLimiter;
use Psalm\CodeLocation;
use Psalm\Context;
use Psalm\Internal\MethodIdentifier;
use Psalm\LaravelPlugin\Providers\ApplicationInterfaceProvider;
use Psalm\LaravelPlugin\Providers\ApplicationProvider;
use Psalm\LaravelPlugin\Util\ContainerResolver;
use Psalm\Plugin\EventHandler\AfterClassLikeVisitInterface;
use Psalm\Plugin\EventHandler\Event\AfterClassLikeVisitEvent;
use Psalm\Plugin\Hook\FunctionReturnTypeProviderInterface;
use Psalm\Plugin\Hook\MethodReturnTypeProviderInterface;
use Psalm\StatementsSource;
use Psalm\Type;
use Psalm\Type\Atomic\TNamedObject;
use Psalm\Type\Union;
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
final class ContainerHandler implements AfterClassLikeVisitInterface, FunctionReturnTypeProviderInterface, MethodReturnTypeProviderInterface
{
/**
* @return array<array-key, lowercase-string>
*/
public static function getFunctionIds(): array
{
return ['app', 'resolve'];
}
/**
* @param array<\PhpParser\Node\Arg> $call_args
*/
public static function getFunctionReturnType(StatementsSource $statements_source, string $function_id, array $call_args, Context $context, CodeLocation $code_location): ?Union
{
if (!$call_args) {
return new Union([
new TNamedObject(get_class(ApplicationProvider::getApp())),
]);
}
return ContainerResolver::resolvePsalmTypeFromApplicationContainerViaArgs($statements_source->getNodeTypeProvider(), $call_args) ?? Type::getMixed();
}
public static function getClassLikeNames(): array
{
return [get_class(ApplicationProvider::getApp())];
}
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 {
// lumen doesn't have the likes of makeWith, so we will ensure these methods actually exist on the underlying
// app contract
$methods = array_filter(['make', 'makewith'], function (string $methodName) use ($source, $fq_classlike_name) {
$methodId = new MethodIdentifier($fq_classlike_name, $methodName);
return $source->getCodebase()->methodExists($methodId);
});
if (!in_array($method_name_lowercase, $methods)) {
return null;
}
return ContainerResolver::resolvePsalmTypeFromApplicationContainerViaArgs($source->getNodeTypeProvider(), $call_args);
}
/**
* @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
*/
public static function afterClassLikeVisit(AfterClassLikeVisitEvent $event)
{
if (!in_array($event->getStorage()->name, ApplicationInterfaceProvider::getApplicationInterfaceClassLikes())) {

View File

@ -61,8 +61,6 @@ class Plugin implements PluginEntryPointInterface
$registration->registerHooksFromClass(ReturnTypeProvider\RedirectReturnTypeProvider::class);
require_once 'ReturnTypeProvider/ViewReturnTypeProvider.php';
$registration->registerHooksFromClass(ReturnTypeProvider\ViewReturnTypeProvider::class);
require_once 'ReturnTypeProvider/AppReturnTypeProvider.php';
$registration->registerHooksFromClass(ReturnTypeProvider\AppReturnTypeProvider::class);
require_once 'Handlers/Application/ContainerHandler.php';
$registration->registerHooksFromClass(ContainerHandler::class);
require_once 'Handlers/Application/OffsetHandler.php';

View File

@ -1,74 +0,0 @@
<?php declare(strict_types=1);
namespace Psalm\LaravelPlugin\ReturnTypeProvider;
use Psalm\CodeLocation;
use Psalm\Context;
use Psalm\Internal\MethodIdentifier;
use Psalm\LaravelPlugin\Providers\ApplicationProvider;
use Psalm\LaravelPlugin\Util\ContainerResolver;
use Psalm\Plugin\Hook\FunctionReturnTypeProviderInterface;
use Psalm\Plugin\Hook\MethodReturnTypeProviderInterface;
use Psalm\StatementsSource;
use Psalm\Type;
use Psalm\Type\Atomic\TNamedObject;
use Psalm\Type\Union;
use function get_class;
use function array_filter;
use function in_array;
final class AppReturnTypeProvider implements FunctionReturnTypeProviderInterface, MethodReturnTypeProviderInterface
{
/**
* @return array<array-key, lowercase-string>
*/
public static function getFunctionIds(): array
{
return ['app', 'resolve'];
}
/**
* @param array<\PhpParser\Node\Arg> $call_args
*/
public static function getFunctionReturnType(StatementsSource $statements_source, string $function_id, array $call_args, Context $context, CodeLocation $code_location): ?Union
{
if (!$call_args) {
return new Union([
new TNamedObject(get_class(ApplicationProvider::getApp())),
]);
}
return ContainerResolver::resolvePsalmTypeFromApplicationContainerViaArgs($statements_source->getNodeTypeProvider(), $call_args) ?? Type::getMixed();
}
public static function getClassLikeNames(): array
{
return [get_class(ApplicationProvider::getApp())];
}
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 {
// lumen doesn't have the likes of makeWith, so we will ensure these methods actually exist on the underlying
// app contract
$methods = array_filter(['make', 'makewith'], function (string $methodName) use ($source, $fq_classlike_name) {
$methodId = new MethodIdentifier($fq_classlike_name, $methodName);
return $source->getCodebase()->methodExists($methodId);
});
if (!in_array($method_name_lowercase, $methods)) {
return null;
}
return ContainerResolver::resolvePsalmTypeFromApplicationContainerViaArgs($source->getNodeTypeProvider(), $call_args);
}
}