diff --git a/extension.neon b/extension.neon index 4e0e06004..37d8f0084 100644 --- a/extension.neon +++ b/extension.neon @@ -5,8 +5,6 @@ parameters: earlyTerminatingFunctionCalls: - abort - dd - excludePaths: - - *.blade.php mixinExcludeClasses: - Eloquent bootstrapFiles: @@ -452,6 +450,10 @@ services: class: NunoMaduro\Larastan\Collectors\UsedEmailViewCollector tags: - phpstan.collector + - + class: NunoMaduro\Larastan\Collectors\UsedViewInAnotherViewCollector + tags: + - phpstan.collector rules: - NunoMaduro\Larastan\Rules\RelationExistenceRule - NunoMaduro\Larastan\Rules\UselessConstructs\NoUselessWithFunctionCallsRule diff --git a/src/Collectors/UsedViewInAnotherViewCollector.php b/src/Collectors/UsedViewInAnotherViewCollector.php new file mode 100644 index 000000000..bad2717ef --- /dev/null +++ b/src/Collectors/UsedViewInAnotherViewCollector.php @@ -0,0 +1,43 @@ + */ +class UsedViewInAnotherViewCollector implements Collector +{ + /** @see https://regex101.com/r/8gosof/1 */ + private const VIEW_NAME_REGEX = '/@(extends|include(If|Unless|When|First)?)(\(.*?\'(.*?)\'(\)|,))/m'; + + public function getNodeType(): string + { + return FileNode::class; + } + + public function processNode(Node $node, Scope $scope): ?array + { + $nodes = array_filter($node->getNodes(), function (Node $node) { + return $node instanceof Node\Stmt\InlineHTML; + }); + + if (count($nodes) === 0) { + return null; + } + + $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[4]; + }, $matches)); + } + + return $usedViews; + } +} diff --git a/src/Rules/UnusedViewsRule.php b/src/Rules/UnusedViewsRule.php index f477c43f6..721098089 100644 --- a/src/Rules/UnusedViewsRule.php +++ b/src/Rules/UnusedViewsRule.php @@ -4,10 +4,12 @@ namespace NunoMaduro\Larastan\Rules; +use function collect; use Illuminate\Support\Facades\File; use Illuminate\View\Factory; use NunoMaduro\Larastan\Collectors\UsedEmailViewCollector; use NunoMaduro\Larastan\Collectors\UsedViewFunctionCollector; +use NunoMaduro\Larastan\Collectors\UsedViewInAnotherViewCollector; use PhpParser\Node; use PHPStan\Analyser\Scope; use PHPStan\Node\CollectedDataNode; @@ -25,12 +27,16 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { - $usedViews = array_unique(array_merge(...array_values($node->get(UsedViewFunctionCollector::class)), ...array_values($node->get(UsedEmailViewCollector::class)))); + $usedViews = collect([ + $node->get(UsedViewFunctionCollector::class), + $node->get(UsedEmailViewCollector::class), + $node->get(UsedViewInAnotherViewCollector::class), + ])->flatten()->unique()->toArray(); $allViews = array_map(function (SplFileInfo $file) { return $file->getPathname(); }, array_filter(File::allFiles(resource_path('views')), function (SplFileInfo $file) { - return ! str_contains($file->getPathname(), 'views/vendor') && $file->getExtension() === 'php' && str_ends_with($file->getFilename(), '.blade.php'); + return ! str_contains($file->getPathname(), 'views/vendor') && str_ends_with($file->getFilename(), '.blade.php'); })); $existingViews = []; 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/index.blade.php b/tests/Application/resources/views/index.blade.php new file mode 100644 index 000000000..b750de39d --- /dev/null +++ b/tests/Application/resources/views/index.blade.php @@ -0,0 +1,9 @@ + + +@extends('base') + +Lorem ipsum + +@include('home') diff --git a/tests/Rules/Data/FooController.php b/tests/Rules/Data/FooController.php index d21e7d02b..dff1914c0 100644 --- a/tests/Rules/Data/FooController.php +++ b/tests/Rules/Data/FooController.php @@ -7,6 +7,11 @@ class FooController { + public function index() + { + return view('index'); + } + public function existing(): View { return view('users.index'); diff --git a/tests/Rules/UnusedViewsRuleTest.php b/tests/Rules/UnusedViewsRuleTest.php index f80a3678a..69d8532c4 100644 --- a/tests/Rules/UnusedViewsRuleTest.php +++ b/tests/Rules/UnusedViewsRuleTest.php @@ -4,6 +4,7 @@ use NunoMaduro\Larastan\Collectors\UsedEmailViewCollector; use NunoMaduro\Larastan\Collectors\UsedViewFunctionCollector; +use NunoMaduro\Larastan\Collectors\UsedViewInAnotherViewCollector; use NunoMaduro\Larastan\Rules\UnusedViewsRule; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; @@ -21,12 +22,13 @@ protected function getCollectors(): array return [ new UsedViewFunctionCollector, new UsedEmailViewCollector, + new UsedViewInAnotherViewCollector, ]; } public function testRule(): void { - $this->analyse([__DIR__.'/Data/FooController.php'], [ + $this->analyse([__DIR__.'/Data/FooController.php', __DIR__.'/../Application/resources/views/index.blade.php', __DIR__.'/../Application/resources/views/base.blade.php'], [ [ 'This view is not used in the project.', 00,