diff --git a/CHANGELOG.md b/CHANGELOG.md index 28ab918db7..1cef72c162 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +- feat: Add return-type extension for Application / Container `make` `makeWith` and `resolve` methods by @mad-briller. - feat: freed `joinSub` to allow models to join other models by @harmenjanssen in https://github.com/nunomaduro/larastan/pull/1352 - feat: updated return type of the Request::header method by @mad-briller - feat: Added stub for `optional()` helper and class by @mad-briller in https://github.com/nunomaduro/larastan/pull/1344 diff --git a/extension.neon b/extension.neon index b32cb5fe39..3654be8787 100644 --- a/extension.neon +++ b/extension.neon @@ -479,6 +479,19 @@ services: class: NunoMaduro\Larastan\Support\ViewFileHelper arguments: viewDirectories: %viewDirectories% + + - + class: NunoMaduro\Larastan\ReturnTypes\ApplicationMakeDynamicReturnTypeExtension + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + + - + class: NunoMaduro\Larastan\ReturnTypes\ContainerMakeDynamicReturnTypeExtension + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + + - NunoMaduro\Larastan\ReturnTypes\AppMakeHelper + rules: - NunoMaduro\Larastan\Rules\RelationExistenceRule - NunoMaduro\Larastan\Rules\UselessConstructs\NoUselessWithFunctionCallsRule diff --git a/src/ReturnTypes/AppMakeDynamicReturnTypeExtension.php b/src/ReturnTypes/AppMakeDynamicReturnTypeExtension.php index a7c4218f4f..640d650f64 100644 --- a/src/ReturnTypes/AppMakeDynamicReturnTypeExtension.php +++ b/src/ReturnTypes/AppMakeDynamicReturnTypeExtension.php @@ -5,23 +5,18 @@ namespace NunoMaduro\Larastan\ReturnTypes; use Illuminate\Support\Facades\App; -use NunoMaduro\Larastan\Concerns\HasContainer; -use PhpParser\Node\Expr\ClassConstFetch; use PhpParser\Node\Expr\StaticCall; -use PhpParser\Node\Name\FullyQualified; -use PhpParser\Node\Scalar\String_; use PHPStan\Analyser\Scope; use PHPStan\Reflection\MethodReflection; use PHPStan\Type\DynamicStaticMethodReturnTypeExtension; -use PHPStan\Type\ErrorType; -use PHPStan\Type\NeverType; -use PHPStan\Type\ObjectType; use PHPStan\Type\Type; -use Throwable; final class AppMakeDynamicReturnTypeExtension implements DynamicStaticMethodReturnTypeExtension { - use HasContainer; + public function __construct( + private AppMakeHelper $appMakeHelper + ) { + } public function getClass(): string { @@ -35,30 +30,6 @@ public function isStaticMethodSupported(MethodReflection $methodReflection): boo public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): ?Type { - if (count($methodCall->getArgs()) === 0) { - return new ErrorType(); - } - - $expr = $methodCall->getArgs()[0]->value; - if ($expr instanceof String_) { - try { - /** @var object|null $resolved */ - $resolved = $this->resolve($expr->value); - - if ($resolved === null) { - return new ErrorType(); - } - - return new ObjectType(get_class($resolved)); - } catch (Throwable $exception) { - return new ErrorType(); - } - } - - if ($expr instanceof ClassConstFetch && $expr->class instanceof FullyQualified) { - return new ObjectType($expr->class->toString()); - } - - return new NeverType(); + return $this->appMakeHelper->resolveTypeFromCall($methodCall); } } diff --git a/src/ReturnTypes/AppMakeHelper.php b/src/ReturnTypes/AppMakeHelper.php new file mode 100644 index 0000000000..29b48200c6 --- /dev/null +++ b/src/ReturnTypes/AppMakeHelper.php @@ -0,0 +1,50 @@ +getArgs()) === 0) { + return new ErrorType(); + } + + $expr = $call->getArgs()[0]->value; + if ($expr instanceof String_) { + try { + /** @var object|null $resolved */ + $resolved = $this->resolve($expr->value); + + if ($resolved === null) { + return new ErrorType(); + } + + return new ObjectType(get_class($resolved)); + } catch (Throwable $exception) { + return new ErrorType(); + } + } + + if ($expr instanceof ClassConstFetch && $expr->class instanceof FullyQualified) { + return new ObjectType($expr->class->toString()); + } + + return new NeverType(); + } +} diff --git a/src/ReturnTypes/ApplicationMakeDynamicReturnTypeExtension.php b/src/ReturnTypes/ApplicationMakeDynamicReturnTypeExtension.php new file mode 100644 index 0000000000..e9e6223313 --- /dev/null +++ b/src/ReturnTypes/ApplicationMakeDynamicReturnTypeExtension.php @@ -0,0 +1,35 @@ +getName(), ['make', 'makeWith', 'resolve'], true); + } + + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type + { + return $this->appMakeHelper->resolveTypeFromCall($methodCall); + } +} diff --git a/src/ReturnTypes/ContainerMakeDynamicReturnTypeExtension.php b/src/ReturnTypes/ContainerMakeDynamicReturnTypeExtension.php new file mode 100644 index 0000000000..d0037b52dc --- /dev/null +++ b/src/ReturnTypes/ContainerMakeDynamicReturnTypeExtension.php @@ -0,0 +1,35 @@ +getName(), ['make', 'makeWith', 'resolve'], true); + } + + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type + { + return $this->appMakeHelper->resolveTypeFromCall($methodCall); + } +} diff --git a/src/ReturnTypes/Helpers/AppExtension.php b/src/ReturnTypes/Helpers/AppExtension.php index ef09164b77..f0722372bf 100644 --- a/src/ReturnTypes/Helpers/AppExtension.php +++ b/src/ReturnTypes/Helpers/AppExtension.php @@ -5,24 +5,20 @@ namespace NunoMaduro\Larastan\ReturnTypes\Helpers; use Illuminate\Foundation\Application; -use NunoMaduro\Larastan\Concerns\HasContainer; -use PhpParser\Node\Expr; -use PhpParser\Node\Expr\ClassConstFetch; +use NunoMaduro\Larastan\ReturnTypes\AppMakeHelper; use PhpParser\Node\Expr\FuncCall; -use PhpParser\Node\Name\FullyQualified; -use PhpParser\Node\Scalar\String_; use PHPStan\Analyser\Scope; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\DynamicFunctionReturnTypeExtension; -use PHPStan\Type\ErrorType; -use PHPStan\Type\NeverType; use PHPStan\Type\ObjectType; use PHPStan\Type\Type; -use Throwable; class AppExtension implements DynamicFunctionReturnTypeExtension { - use HasContainer; + public function __construct( + private AppMakeHelper $appMakeHelper + ) { + } public function isFunctionSupported(FunctionReflection $functionReflection): bool { @@ -38,28 +34,6 @@ public function getTypeFromFunctionCall( return new ObjectType(Application::class); } - /** @var Expr $expr */ - $expr = $functionCall->getArgs()[0]->value; - - if ($expr instanceof String_) { - try { - /** @var object|null $resolved */ - $resolved = $this->resolve($expr->value); - - if ($resolved === null) { - return new ErrorType(); - } - - return new ObjectType(get_class($resolved)); - } catch (Throwable $exception) { - return new ErrorType(); - } - } - - if ($expr instanceof ClassConstFetch && $expr->class instanceof FullyQualified) { - return new ObjectType($expr->class->toString()); - } - - return new NeverType(); + return $this->appMakeHelper->resolveTypeFromCall($functionCall); } } diff --git a/tests/Type/GeneralTypeTest.php b/tests/Type/GeneralTypeTest.php index 7bfd20d885..3cf2adc1bb 100644 --- a/tests/Type/GeneralTypeTest.php +++ b/tests/Type/GeneralTypeTest.php @@ -33,6 +33,8 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__.'/data/validator.php'); yield from $this->gatherAssertTypes(__DIR__.'/data/form-request.php'); yield from $this->gatherAssertTypes(__DIR__.'/data/database-transaction.php'); + yield from $this->gatherAssertTypes(__DIR__.'/data/application-make.php'); + yield from $this->gatherAssertTypes(__DIR__.'/data/container-make.php'); } /** diff --git a/tests/Type/data/application-make.php b/tests/Type/data/application-make.php new file mode 100644 index 0000000000..05b22ecc63 --- /dev/null +++ b/tests/Type/data/application-make.php @@ -0,0 +1,16 @@ +make(Repository::class)); +assertType('Illuminate\Contracts\Config\Repository', $app->makeWith(Repository::class)); +assertType('Illuminate\Contracts\Config\Repository', $app->resolve(Repository::class)); + +/** @var \Illuminate\Contracts\Foundation\Application $app */ +assertType('Illuminate\Contracts\Config\Repository', $app->make(Repository::class)); +assertType('Illuminate\Contracts\Config\Repository', $app->makeWith(Repository::class)); +assertType('Illuminate\Contracts\Config\Repository', $app->resolve(Repository::class)); diff --git a/tests/Type/data/container-make.php b/tests/Type/data/container-make.php new file mode 100644 index 0000000000..deff1292e3 --- /dev/null +++ b/tests/Type/data/container-make.php @@ -0,0 +1,16 @@ +make(Repository::class)); +assertType('Illuminate\Contracts\Config\Repository', $container->makeWith(Repository::class)); +assertType('Illuminate\Contracts\Config\Repository', $container->resolve(Repository::class)); + +/** @var \Illuminate\Contracts\Container\Container $container */ +assertType('Illuminate\Contracts\Config\Repository', $container->make(Repository::class)); +assertType('Illuminate\Contracts\Config\Repository', $container->makeWith(Repository::class)); +assertType('Illuminate\Contracts\Config\Repository', $container->resolve(Repository::class));