From 44fe71dd37175ba567ee470e8ae4bdeea6d57123 Mon Sep 17 00:00:00 2001 From: Can Vural Date: Wed, 23 Nov 2022 21:12:16 +0100 Subject: [PATCH] feat: unused views collector rule (#1423) --- bootstrap.php | 8 +- extension.neon | 47 +++++++++-- src/Collectors/UsedEmailViewCollector.php | 54 +++++++++++++ .../UsedRouteFacadeViewCollector.php | 59 ++++++++++++++ .../UsedViewFacadeMakeCollector.php | 59 ++++++++++++++ src/Collectors/UsedViewFunctionCollector.php | 49 ++++++++++++ .../UsedViewInAnotherViewCollector.php | 62 +++++++++++++++ src/Collectors/UsedViewMakeCollector.php | 53 +++++++++++++ ...iewWithMethodsClassReflectionExtension.php | 2 +- src/ReturnTypes/Helpers/ViewExtension.php | 42 ---------- src/Rules/UnusedViewsRule.php | 77 +++++++++++++++++++ src/Support/ViewFileHelper.php | 74 ++++++++++++++++++ stubs/Contracts/View.stub | 9 +++ stubs/Helpers.stub | 2 +- .../resources/views/base.blade.php | 3 + .../resources/views/emails/markdown.blade.php | 1 + .../resources/views/emails/view.blade.php | 1 + .../resources/views/index.blade.php | 9 +++ .../resources/views/route-view.blade.php | 1 + .../resources/views/unused.blade.php | 1 + .../views/view-factory-make.blade.php | 1 + .../views/view-helper-make.blade.php | 1 + .../views/view-static-make.blade.php | 1 + .../ReturnTypes/Helpers/ViewExtension.php | 21 ----- ...nseMethodsClassReflectionExtensionTest.php | 2 +- ...CompatibleWithClassConstructorRuleTest.php | 7 -- tests/Rules/Data/FooController.php | 64 +++++++++++++++ ...ServiceProviderMissingProvidesRuleTest.php | 7 -- tests/Rules/UnusedViewsRuleTest.php | 64 +++++++++++++++ tests/Type/data/view.php | 7 +- 30 files changed, 698 insertions(+), 90 deletions(-) create mode 100644 src/Collectors/UsedEmailViewCollector.php create mode 100644 src/Collectors/UsedRouteFacadeViewCollector.php create mode 100644 src/Collectors/UsedViewFacadeMakeCollector.php create mode 100644 src/Collectors/UsedViewFunctionCollector.php create mode 100644 src/Collectors/UsedViewInAnotherViewCollector.php create mode 100644 src/Collectors/UsedViewMakeCollector.php delete mode 100644 src/ReturnTypes/Helpers/ViewExtension.php create mode 100644 src/Rules/UnusedViewsRule.php create mode 100644 src/Support/ViewFileHelper.php create mode 100644 stubs/Contracts/View.stub create mode 100644 tests/Application/resources/views/base.blade.php create mode 100644 tests/Application/resources/views/emails/markdown.blade.php create mode 100644 tests/Application/resources/views/emails/view.blade.php create mode 100644 tests/Application/resources/views/index.blade.php create mode 100644 tests/Application/resources/views/route-view.blade.php create mode 100644 tests/Application/resources/views/unused.blade.php create mode 100644 tests/Application/resources/views/view-factory-make.blade.php create mode 100644 tests/Application/resources/views/view-helper-make.blade.php create mode 100644 tests/Application/resources/views/view-static-make.blade.php delete mode 100644 tests/Features/ReturnTypes/Helpers/ViewExtension.php create mode 100644 tests/Rules/Data/FooController.php create mode 100644 tests/Rules/UnusedViewsRuleTest.php diff --git a/bootstrap.php b/bootstrap.php index 904ad05a2..c4a028e3c 100644 --- a/bootstrap.php +++ b/bootstrap.php @@ -8,7 +8,9 @@ use NunoMaduro\Larastan\ApplicationResolver; use Orchestra\Testbench\Concerns\CreatesApplication; -define('LARAVEL_START', microtime(true)); +if (! defined('LARAVEL_START')) { + define('LARAVEL_START', microtime(true)); +} if (file_exists($applicationPath = getcwd().'/bootstrap/app.php')) { // Applications and Local Dev $app = require $applicationPath; @@ -26,4 +28,6 @@ $app->boot(); } -define('LARAVEL_VERSION', $app->version()); +if (! defined('LARAVEL_VERSION')) { + define('LARAVEL_VERSION', $app->version()); +} diff --git a/extension.neon b/extension.neon index 4ecda48db..b32cb5fe3 100644 --- a/extension.neon +++ b/extension.neon @@ -18,8 +18,10 @@ parameters: noUnnecessaryCollectionCallExcept: [] squashedMigrationsPath: [] databaseMigrationsPath: [] + viewDirectories: [] checkModelProperties: false checkPhpDocMissingReturn: false + checkUnusedViews: false parametersSchema: checkOctaneCompatibility: bool() @@ -28,8 +30,10 @@ parametersSchema: noUnnecessaryCollectionCallOnly: listOf(string()) noUnnecessaryCollectionCallExcept: listOf(string()) databaseMigrationsPath: listOf(string()) + viewDirectories: listOf(string()) squashedMigrationsPath: listOf(string()) checkModelProperties: bool() + checkUnusedViews: bool() conditionalTags: NunoMaduro\Larastan\Rules\NoModelMakeRule: @@ -42,6 +46,8 @@ conditionalTags: phpstan.rules.rule: %checkModelProperties% NunoMaduro\Larastan\Rules\ModelProperties\ModelPropertyStaticCallRule: phpstan.rules.rule: %checkModelProperties% + NunoMaduro\Larastan\Rules\UnusedViewsRule: + phpstan.rules.rule: %checkUnusedViews% services: - @@ -262,11 +268,6 @@ services: tags: - phpstan.broker.dynamicFunctionReturnTypeExtension - - - class: NunoMaduro\Larastan\ReturnTypes\Helpers\ViewExtension - tags: - - phpstan.broker.dynamicFunctionReturnTypeExtension - - class: NunoMaduro\Larastan\ReturnTypes\Helpers\ValidatorExtension tags: @@ -442,6 +443,42 @@ services: - class: NunoMaduro\Larastan\LarastanStubFilesExtension tags: [phpstan.stubFilesExtension] + + - + class: NunoMaduro\Larastan\Rules\UnusedViewsRule + + - + class: NunoMaduro\Larastan\Collectors\UsedViewFunctionCollector + tags: + - phpstan.collector + + - + class: NunoMaduro\Larastan\Collectors\UsedEmailViewCollector + tags: + - phpstan.collector + + - + class: NunoMaduro\Larastan\Collectors\UsedViewMakeCollector + tags: + - phpstan.collector + + - + class: NunoMaduro\Larastan\Collectors\UsedViewFacadeMakeCollector + tags: + - phpstan.collector + + - + class: NunoMaduro\Larastan\Collectors\UsedRouteFacadeViewCollector + tags: + - phpstan.collector + - + class: NunoMaduro\Larastan\Collectors\UsedViewInAnotherViewCollector + arguments: + parser: @currentPhpVersionSimpleDirectParser + - + class: NunoMaduro\Larastan\Support\ViewFileHelper + arguments: + viewDirectories: %viewDirectories% rules: - NunoMaduro\Larastan\Rules\RelationExistenceRule - NunoMaduro\Larastan\Rules\UselessConstructs\NoUselessWithFunctionCallsRule diff --git a/src/Collectors/UsedEmailViewCollector.php b/src/Collectors/UsedEmailViewCollector.php new file mode 100644 index 000000000..a531a4495 --- /dev/null +++ b/src/Collectors/UsedEmailViewCollector.php @@ -0,0 +1,54 @@ + */ +final class UsedEmailViewCollector implements Collector +{ + public function getNodeType(): string + { + return Node\Expr\MethodCall::class; + } + + /** @param Node\Expr\MethodCall $node */ + public function processNode(Node $node, Scope $scope): ?string + { + $name = $node->name; + + if (! $name instanceof Identifier) { + return null; + } + + if (! in_array($name->name, ['markdown', 'view'], true)) { + return null; + } + + if (count($node->getArgs()) === 0) { + return null; + } + + $class = $node->var; + + if (! (new ObjectType(Mailable::class))->isSuperTypeOf($scope->getType($class))->yes()) { + return null; + } + + $template = $node->getArgs()[0]->value; + + if (! $template instanceof Node\Scalar\String_) { + return null; + } + + return ViewName::normalize($template->value); + } +} diff --git a/src/Collectors/UsedRouteFacadeViewCollector.php b/src/Collectors/UsedRouteFacadeViewCollector.php new file mode 100644 index 000000000..2586c9ee2 --- /dev/null +++ b/src/Collectors/UsedRouteFacadeViewCollector.php @@ -0,0 +1,59 @@ + */ +final class UsedRouteFacadeViewCollector implements Collector +{ + public function getNodeType(): string + { + return Node\Expr\StaticCall::class; + } + + /** @param Node\Expr\StaticCall $node */ + public function processNode(Node $node, Scope $scope): ?string + { + $name = $node->name; + + if (! $name instanceof Node\Identifier) { + return null; + } + + if ($name->name !== 'view') { + return null; + } + + if (count($node->getArgs()) < 2) { + return null; + } + + $class = $node->class; + + if (! $class instanceof Node\Name) { + return null; + } + + $class = $scope->resolveName($class); + + if (! (new ObjectType(Route::class))->isSuperTypeOf(new ObjectType($class))->yes()) { + return null; + } + + $template = $node->getArgs()[1]->value; + + if (! $template instanceof Node\Scalar\String_) { + return null; + } + + return ViewName::normalize($template->value); + } +} diff --git a/src/Collectors/UsedViewFacadeMakeCollector.php b/src/Collectors/UsedViewFacadeMakeCollector.php new file mode 100644 index 000000000..2840540db --- /dev/null +++ b/src/Collectors/UsedViewFacadeMakeCollector.php @@ -0,0 +1,59 @@ + */ +final class UsedViewFacadeMakeCollector implements Collector +{ + public function getNodeType(): string + { + return Node\Expr\StaticCall::class; + } + + /** @param Node\Expr\StaticCall $node */ + public function processNode(Node $node, Scope $scope): ?string + { + $name = $node->name; + + if (! $name instanceof Node\Identifier) { + return null; + } + + if ($name->name !== 'make') { + return null; + } + + if (count($node->getArgs()) < 1) { + return null; + } + + $class = $node->class; + + if (! $class instanceof Node\Name) { + return null; + } + + $class = $scope->resolveName($class); + + if (! (new ObjectType(View::class))->isSuperTypeOf(new ObjectType($class))->yes()) { + return null; + } + + $template = $node->getArgs()[0]->value; + + if (! $template instanceof Node\Scalar\String_) { + return null; + } + + return ViewName::normalize($template->value); + } +} diff --git a/src/Collectors/UsedViewFunctionCollector.php b/src/Collectors/UsedViewFunctionCollector.php new file mode 100644 index 000000000..2427ab6d4 --- /dev/null +++ b/src/Collectors/UsedViewFunctionCollector.php @@ -0,0 +1,49 @@ + */ +final class UsedViewFunctionCollector implements Collector +{ + public function getNodeType(): string + { + return Node\Expr\FuncCall::class; + } + + /** @param Node\Expr\FuncCall $node */ + public function processNode(Node $node, Scope $scope): ?string + { + $funcName = $node->name; + + if (! $funcName instanceof Node\Name) { + return null; + } + + $funcName = $scope->resolveName($funcName); + + if ($funcName !== 'view') { + return null; + } + + // TODO: maybe make sure this function is coming from Laravel + + if (count($node->getArgs()) < 1) { + return null; + } + + $template = $node->getArgs()[0]->value; + + if (! $template instanceof Node\Scalar\String_) { + return null; + } + + return ViewName::normalize($template->value); + } +} diff --git a/src/Collectors/UsedViewInAnotherViewCollector.php b/src/Collectors/UsedViewInAnotherViewCollector.php new file mode 100644 index 000000000..bacad6536 --- /dev/null +++ b/src/Collectors/UsedViewInAnotherViewCollector.php @@ -0,0 +1,62 @@ + */ + public function getUsedViews(): array + { + $usedViews = []; + foreach ($this->viewFileHelper->getAllViewFilePaths() as $viewFile) { + try { + $parserNodes = $this->parser->parseFile($viewFile); + + $usedViews = array_merge($usedViews, $this->processNodes($parserNodes)); + } catch (ParserErrorsException $e) { + continue; + } + } + + return $usedViews; + } + + /** + * @param Node\Stmt[] $nodes + * @return list + */ + private function processNodes(array $nodes): array + { + $nodes = array_filter($nodes, function (Node $node) { + return $node instanceof Node\Stmt\InlineHTML; + }); + + if (count($nodes) === 0) { + return []; + } + + $usedViews = []; + + foreach ($nodes as $node) { + preg_match_all(self::VIEW_NAME_REGEX, $node->value, $matches, PREG_SET_ORDER, 0); + + $usedViews = array_merge($usedViews, array_map(function ($match) { + return $match[5]; + }, $matches)); + } + + return $usedViews; + } +} diff --git a/src/Collectors/UsedViewMakeCollector.php b/src/Collectors/UsedViewMakeCollector.php new file mode 100644 index 000000000..48ed14469 --- /dev/null +++ b/src/Collectors/UsedViewMakeCollector.php @@ -0,0 +1,53 @@ + */ +final class UsedViewMakeCollector implements Collector +{ + public function getNodeType(): string + { + return Node\Expr\MethodCall::class; + } + + /** @param Node\Expr\MethodCall $node */ + public function processNode(Node $node, Scope $scope): ?string + { + $name = $node->name; + + if (! $name instanceof Node\Identifier) { + return null; + } + + if ($name->name !== 'make') { + return null; + } + + if (count($node->getArgs()) < 1) { + return null; + } + + $class = $node->var; + + if (! (new ObjectType(Factory::class))->isSuperTypeOf($scope->getType($class))->yes()) { + return null; + } + + $template = $node->getArgs()[0]->value; + + if (! $template instanceof Node\Scalar\String_) { + return null; + } + + return ViewName::normalize($template->value); + } +} diff --git a/src/Methods/ViewWithMethodsClassReflectionExtension.php b/src/Methods/ViewWithMethodsClassReflectionExtension.php index 95000790e..25fddf0ad 100644 --- a/src/Methods/ViewWithMethodsClassReflectionExtension.php +++ b/src/Methods/ViewWithMethodsClassReflectionExtension.php @@ -11,7 +11,7 @@ class ViewWithMethodsClassReflectionExtension implements MethodsClassReflectionE { public function hasMethod(ClassReflection $classReflection, string $methodName): bool { - if ($classReflection->getName() !== 'Illuminate\View\View') { + if (! in_array($classReflection->getName(), ['Illuminate\View\View', 'Illuminate\Contracts\View\View'], true)) { return false; } diff --git a/src/ReturnTypes/Helpers/ViewExtension.php b/src/ReturnTypes/Helpers/ViewExtension.php deleted file mode 100644 index 030271ded..000000000 --- a/src/ReturnTypes/Helpers/ViewExtension.php +++ /dev/null @@ -1,42 +0,0 @@ -getName() === 'view'; - } - - /** - * {@inheritdoc} - */ - public function getTypeFromFunctionCall( - FunctionReflection $functionReflection, - FuncCall $functionCall, - Scope $scope - ): Type { - if (count($functionCall->getArgs()) === 0) { - return new ObjectType(\Illuminate\Contracts\View\Factory::class); - } - - return new ObjectType(\Illuminate\View\View::class); - } -} diff --git a/src/Rules/UnusedViewsRule.php b/src/Rules/UnusedViewsRule.php new file mode 100644 index 000000000..045705c14 --- /dev/null +++ b/src/Rules/UnusedViewsRule.php @@ -0,0 +1,77 @@ + */ +final class UnusedViewsRule implements Rule +{ + /** @var list|null */ + private ?array $viewsUsedInOtherViews = null; + + public function __construct(private UsedViewInAnotherViewCollector $usedViewInAnotherViewCollector, private ViewFileHelper $viewFileHelper) + { + } + + public function getNodeType(): string + { + return CollectedDataNode::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($this->viewsUsedInOtherViews === null) { + $this->viewsUsedInOtherViews = $this->usedViewInAnotherViewCollector->getUsedViews(); + } + + $usedViews = collect([ + $node->get(UsedViewFunctionCollector::class), + $node->get(UsedEmailViewCollector::class), + $node->get(UsedViewMakeCollector::class), + $node->get(UsedViewFacadeMakeCollector::class), + $node->get(UsedRouteFacadeViewCollector::class), + $this->viewsUsedInOtherViews, + ])->flatten()->unique()->toArray(); + + $allViews = iterator_to_array($this->viewFileHelper->getAllViewNames()); + + $existingViews = []; + + /** @var Factory $view */ + $view = view(); + + foreach ($usedViews as $viewName) { + if ($view->exists($viewName)) { + $existingViews[] = $viewName; + } + } + + $unusedViews = array_diff($allViews, array_filter($existingViews)); + + $errors = []; + foreach ($unusedViews as $file) { + $errors[] = RuleErrorBuilder::message('This view is not used in the project.') + ->file($file.'.blade.php') + ->line(0) + ->build(); + } + + return $errors; + } +} diff --git a/src/Support/ViewFileHelper.php b/src/Support/ViewFileHelper.php new file mode 100644 index 000000000..a6ace8480 --- /dev/null +++ b/src/Support/ViewFileHelper.php @@ -0,0 +1,74 @@ + $viewDirectories + */ + public function __construct(private array $viewDirectories, private FileHelper $fileHelper) + { + if (count($viewDirectories) === 0) { + $this->viewDirectories = [resource_path('views')]; // @phpstan-ignore-line + } + } + + public function getAllViewFilePaths(): Generator + { + foreach ($this->viewDirectories as $viewDirectory) { + $absolutePath = $this->fileHelper->absolutizePath($viewDirectory); + + if (! is_dir($absolutePath)) { + continue; + } + + $views = iterator_to_array( + new RegexIterator( + new RecursiveIteratorIterator(new RecursiveDirectoryIterator($absolutePath)), + '/\.blade\.php$/i' + ) + ); + + foreach ($views as $view) { + yield $view->getPathname(); + } + } + } + + public function getAllViewNames(): Generator + { + foreach ($this->viewDirectories as $viewDirectory) { + $absolutePath = $this->fileHelper->absolutizePath($viewDirectory); + + if (! is_dir($absolutePath)) { + continue; + } + + $views = iterator_to_array( + new RegexIterator( + new RecursiveIteratorIterator(new RecursiveDirectoryIterator($absolutePath)), + '/\.blade\.php$/i' + ) + ); + + foreach ($views as $view) { + if (str_contains($view->getPathname(), 'views/vendor')) { + continue; + } + + $viewName = explode($viewDirectory.'/', $view->getPathname()); + + yield str_replace(['/', '.blade.php'], ['.', ''], $viewName[1]); + } + } + } +} diff --git a/stubs/Contracts/View.stub b/stubs/Contracts/View.stub new file mode 100644 index 000000000..c501422a1 --- /dev/null +++ b/stubs/Contracts/View.stub @@ -0,0 +1,9 @@ +|array $data * @param array $mergeData - * @return mixed + * @return ($view is null ? \Illuminate\Contracts\View\Factory : \Illuminate\Contracts\View\View) */ function view($view = null, $data = [], $mergeData = []) { diff --git a/tests/Application/resources/views/base.blade.php b/tests/Application/resources/views/base.blade.php new file mode 100644 index 000000000..871449b81 --- /dev/null +++ b/tests/Application/resources/views/base.blade.php @@ -0,0 +1,3 @@ +

Base view

+ +@include('users.index') diff --git a/tests/Application/resources/views/emails/markdown.blade.php b/tests/Application/resources/views/emails/markdown.blade.php new file mode 100644 index 000000000..95f6db387 --- /dev/null +++ b/tests/Application/resources/views/emails/markdown.blade.php @@ -0,0 +1 @@ +// Used with $this->markdown('emails.markdown') diff --git a/tests/Application/resources/views/emails/view.blade.php b/tests/Application/resources/views/emails/view.blade.php new file mode 100644 index 000000000..43c035322 --- /dev/null +++ b/tests/Application/resources/views/emails/view.blade.php @@ -0,0 +1 @@ +// Used with $this->view('emails.view') diff --git a/tests/Application/resources/views/index.blade.php b/tests/Application/resources/views/index.blade.php new file mode 100644 index 000000000..89451e726 --- /dev/null +++ b/tests/Application/resources/views/index.blade.php @@ -0,0 +1,9 @@ + + +@extends('base') + +Lorem ipsum + +@include("home") diff --git a/tests/Application/resources/views/route-view.blade.php b/tests/Application/resources/views/route-view.blade.php new file mode 100644 index 000000000..0f7910319 --- /dev/null +++ b/tests/Application/resources/views/route-view.blade.php @@ -0,0 +1 @@ +This is used in a Route::view diff --git a/tests/Application/resources/views/unused.blade.php b/tests/Application/resources/views/unused.blade.php new file mode 100644 index 000000000..1c8e4aca5 --- /dev/null +++ b/tests/Application/resources/views/unused.blade.php @@ -0,0 +1 @@ +// Not used in any other file diff --git a/tests/Application/resources/views/view-factory-make.blade.php b/tests/Application/resources/views/view-factory-make.blade.php new file mode 100644 index 000000000..7d3db43f7 --- /dev/null +++ b/tests/Application/resources/views/view-factory-make.blade.php @@ -0,0 +1 @@ +// Used by $viewFactory->make('view-factory-make') diff --git a/tests/Application/resources/views/view-helper-make.blade.php b/tests/Application/resources/views/view-helper-make.blade.php new file mode 100644 index 000000000..59e8c05f8 --- /dev/null +++ b/tests/Application/resources/views/view-helper-make.blade.php @@ -0,0 +1 @@ +// Used by view()->make('view-helper-make') diff --git a/tests/Application/resources/views/view-static-make.blade.php b/tests/Application/resources/views/view-static-make.blade.php new file mode 100644 index 000000000..12be948eb --- /dev/null +++ b/tests/Application/resources/views/view-static-make.blade.php @@ -0,0 +1 @@ +// Used by View::make('view-static-make') diff --git a/tests/Features/ReturnTypes/Helpers/ViewExtension.php b/tests/Features/ReturnTypes/Helpers/ViewExtension.php deleted file mode 100644 index e6c9a1fb7..000000000 --- a/tests/Features/ReturnTypes/Helpers/ViewExtension.php +++ /dev/null @@ -1,21 +0,0 @@ -markdown('emails.markdown'); + } + + public function bar(): self + { + return $this->view('emails.view'); + } +} + +function viewHelper(): View +{ + return view()->make('view-helper-make'); +} + +function viewFactory(Factory $factory): View +{ + return $factory->make('view-factory-make'); +} + +function viewStaticMake(): View +{ + return \Illuminate\Support\Facades\View::make('view-static-make'); +} + +function routeView(): void +{ + Route::view('/welcome', 'route-view'); +} diff --git a/tests/Rules/DeferrableServiceProviderMissingProvidesRuleTest.php b/tests/Rules/DeferrableServiceProviderMissingProvidesRuleTest.php index 9c06cf18c..b1d8b256e 100644 --- a/tests/Rules/DeferrableServiceProviderMissingProvidesRuleTest.php +++ b/tests/Rules/DeferrableServiceProviderMissingProvidesRuleTest.php @@ -47,11 +47,4 @@ protected function getRule(): Rule { return new DeferrableServiceProviderMissingProvidesRule(); } - - public static function getAdditionalConfigFiles(): array - { - return [ - __DIR__.'/phpstan-rules.neon', - ]; - } } diff --git a/tests/Rules/UnusedViewsRuleTest.php b/tests/Rules/UnusedViewsRuleTest.php new file mode 100644 index 000000000..236a13bc6 --- /dev/null +++ b/tests/Rules/UnusedViewsRuleTest.php @@ -0,0 +1,64 @@ + */ +class UnusedViewsRuleTest extends RuleTestCase +{ + protected function getRule(): Rule + { + $viewFileHelper = new ViewFileHelper([__DIR__.'/../Application/resources/views'], $this->getFileHelper()); + + return new UnusedViewsRule(new UsedViewInAnotherViewCollector( + $this->getContainer()->getService('currentPhpVersionSimpleDirectParser'), + $viewFileHelper, + ), $viewFileHelper); + } + + protected function getCollectors(): array + { + return [ + new UsedViewFunctionCollector, + new UsedEmailViewCollector, + new UsedViewMakeCollector, + new UsedViewFacadeMakeCollector, + new UsedRouteFacadeViewCollector, + ]; + } + + protected function setUp(): void + { + parent::setUp(); + + // This is a workaround for a weird PHPStan container cache issue. + require __DIR__.'/../../bootstrap.php'; + } + + public function testRule(): void + { + $this->analyse([__DIR__.'/Data/FooController.php'], [ + [ + 'This view is not used in the project.', + 00, + ], + ]); + } + + public static function getAdditionalConfigFiles(): array + { + return [ + __DIR__.'/../../extension.neon', + ]; + } +} diff --git a/tests/Type/data/view.php b/tests/Type/data/view.php index becf3f9a6..9149137e2 100644 --- a/tests/Type/data/view.php +++ b/tests/Type/data/view.php @@ -4,6 +4,7 @@ use function PHPStan\Testing\assertType; -assertType('Illuminate\View\View', view('foo')); -assertType('Illuminate\View\View', view('foo')->with('bar', 'baz')); -assertType('Illuminate\View\View', view('foo')->withFoo('bar')); +assertType('Illuminate\Contracts\View\Factory', view()); +assertType('Illuminate\Contracts\View\View', view('foo')); +assertType('Illuminate\Contracts\View\View', view('foo')->with('bar', 'baz')); +assertType('Illuminate\Contracts\View\View', view('foo')->withFoo('bar'));