From 396dc78c5dda9c6d362477d0c9f392a68a43d902 Mon Sep 17 00:00:00 2001 From: Zachary Lund Date: Mon, 12 Aug 2024 10:33:05 -0500 Subject: [PATCH 1/3] Add Twig template exists rule --- extension.neon | 9 ++ src/Rules/Symfony/TwigTemplateExistsRule.php | 122 ++++++++++++++++++ tests/Rules/Symfony/ExampleTwigController.php | 71 ++++++++++ ...wigTemplateExistsRuleMoreTemplatesTest.php | 37 ++++++ .../TwigTemplateExistsRuleNoTemplatesTest.php | 29 +++++ .../Symfony/TwigTemplateExistsRuleTest.php | 90 +++++++++++++ tests/Rules/Symfony/data/bar.html.twig | 0 tests/Rules/Symfony/templates/foo.html.twig | 0 8 files changed, 358 insertions(+) create mode 100644 src/Rules/Symfony/TwigTemplateExistsRule.php create mode 100644 tests/Rules/Symfony/ExampleTwigController.php create mode 100644 tests/Rules/Symfony/TwigTemplateExistsRuleMoreTemplatesTest.php create mode 100644 tests/Rules/Symfony/TwigTemplateExistsRuleNoTemplatesTest.php create mode 100644 tests/Rules/Symfony/TwigTemplateExistsRuleTest.php create mode 100644 tests/Rules/Symfony/data/bar.html.twig create mode 100644 tests/Rules/Symfony/templates/foo.html.twig diff --git a/extension.neon b/extension.neon index 512f9908..629bd28a 100644 --- a/extension.neon +++ b/extension.neon @@ -11,6 +11,7 @@ parameters: constantHassers: true console_application_loader: null consoleApplicationLoader: null + twigTemplateDirectories: [] featureToggles: skipCheckGenericClasses: - Symfony\Component\Form\AbstractType @@ -115,6 +116,7 @@ parametersSchema: constantHassers: bool() console_application_loader: schema(string(), nullable()) consoleApplicationLoader: schema(string(), nullable()) + twigTemplateDirectories: listOf(string()) ]) services: @@ -365,3 +367,10 @@ services: - factory: PHPStan\Type\Symfony\ExtensionGetConfigurationReturnTypeExtension tags: [phpstan.broker.dynamicMethodReturnTypeExtension] + + - + class: PHPStan\Rules\Symfony\TwigTemplateExistsRule + arguments: + twigTemplateDirectories: %symfony.twigTemplateDirectories% + tags: + - phpstan.rules.rule diff --git a/src/Rules/Symfony/TwigTemplateExistsRule.php b/src/Rules/Symfony/TwigTemplateExistsRule.php new file mode 100644 index 00000000..64a1c0d6 --- /dev/null +++ b/src/Rules/Symfony/TwigTemplateExistsRule.php @@ -0,0 +1,122 @@ + + */ +final class TwigTemplateExistsRule implements Rule +{ + + /** @var list */ + private $twigTemplateDirectories; + + /** @param list $twigTemplateDirectories */ + public function __construct(array $twigTemplateDirectories) + { + $this->twigTemplateDirectories = $twigTemplateDirectories; + } + + public function getNodeType(): string + { + return MethodCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (count($this->twigTemplateDirectories) === 0) { + return []; + } + + $templateArg = $this->getTwigTemplateArg($node, $scope); + + if ($templateArg === null) { + return []; + } + + $templateNames = []; + + if ($templateArg->value instanceof Variable && is_string($templateArg->value->name)) { + $varType = $scope->getVariableType($templateArg->value->name); + + foreach ($varType->getConstantStrings() as $constantString) { + $templateNames[] = $constantString->getValue(); + } + } elseif ($templateArg->value instanceof String_) { + $templateNames[] = $templateArg->value->value; + } + + if (count($templateNames) === 0) { + return []; + } + + $errors = []; + + foreach ($templateNames as $templateName) { + if ($this->twigTemplateExists($templateName)) { + continue; + } + + $errors[] = RuleErrorBuilder::message(sprintf( + 'Twig template "%s" does not exist.', + $templateName + ))->line($templateArg->getStartLine())->identifier('twig.templateNotFound')->build(); + } + + return $errors; + } + + private function getTwigTemplateArg(MethodCall $node, Scope $scope): ?Arg + { + if (!$node->name instanceof Identifier) { + return null; + } + + $argType = $scope->getType($node->var); + $methodName = $node->name->name; + + if ((new ObjectType('Symfony\Bundle\FrameworkBundle\Controller\AbstractController'))->isSuperTypeOf($argType)->yes() && in_array($methodName, ['render', 'renderView', 'renderBlockView', 'renderBlock', 'renderForm', 'stream'], true)) { + return $node->getArgs()[0] ?? null; + } + + if ((new ObjectType('Twig\Environment'))->isSuperTypeOf($argType)->yes() && in_array($methodName, ['render', 'display', 'load'], true)) { + return $node->getArgs()[0] ?? null; + } + + if ((new ObjectType('Symfony\Bridge\Twig\Mime\TemplatedEmail'))->isSuperTypeOf($argType)->yes() && in_array($methodName, ['htmlTemplate', 'textTemplate'], true)) { + return $node->getArgs()[0] ?? null; + } + + return null; + } + + private function twigTemplateExists(string $templateName): bool + { + foreach ($this->twigTemplateDirectories as $twigTemplateDirectory) { + $templatePath = $twigTemplateDirectory . '/' . $templateName; + + if (file_exists($templatePath)) { + return true; + } + } + + return false; + } + +} diff --git a/tests/Rules/Symfony/ExampleTwigController.php b/tests/Rules/Symfony/ExampleTwigController.php new file mode 100644 index 00000000..c434d180 --- /dev/null +++ b/tests/Rules/Symfony/ExampleTwigController.php @@ -0,0 +1,71 @@ +render('foo.html.twig'); + $this->renderBlock('foo.html.twig'); + $this->renderBlockView('foo.html.twig'); + $this->renderForm('foo.html.twig'); + $this->renderView('foo.html.twig'); + $this->stream('foo.html.twig'); + + $this->render('bar.html.twig'); + $this->renderBlock('bar.html.twig'); + $this->renderBlockView('bar.html.twig'); + $this->renderForm('bar.html.twig'); + $this->renderView('bar.html.twig'); + $this->stream('bar.html.twig'); + + $twig = new Environment(); + + $twig->render('foo.html.twig'); + $twig->display('foo.html.twig'); + $twig->load('foo.html.twig'); + + $twig->render('bar.html.twig'); + $twig->display('bar.html.twig'); + $twig->load('bar.html.twig'); + + $templatedEmail = new TemplatedEmail(); + + $templatedEmail->htmlTemplate('foo.html.twig'); + $templatedEmail->textTemplate('foo.html.twig'); + + $templatedEmail->textTemplate('bar.html.twig'); + $templatedEmail->textTemplate('bar.html.twig'); + + $name = 'foo.html.twig'; + + $this->render($name); + + $name = 'bar.html.twig'; + + $this->render($name); + + $name = rand(0, 1) ? 'foo.html.twig' : 'bar.html.twig'; + + $this->render($name); + + $name = rand(0, 1) ? 'bar.html.twig' : 'baz.html.twig'; + + $this->render($name); + + $this->render($this->getName()); + } + + private function getName(): string + { + return 'baz.html.twig'; + } + +} diff --git a/tests/Rules/Symfony/TwigTemplateExistsRuleMoreTemplatesTest.php b/tests/Rules/Symfony/TwigTemplateExistsRuleMoreTemplatesTest.php new file mode 100644 index 00000000..e43e0199 --- /dev/null +++ b/tests/Rules/Symfony/TwigTemplateExistsRuleMoreTemplatesTest.php @@ -0,0 +1,37 @@ + + */ +final class TwigTemplateExistsRuleMoreTemplatesTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new TwigTemplateExistsRule([ + __DIR__ . '/data', + __DIR__ . '/templates', + ]); + } + + public function testGetArgument(): void + { + $this->analyse( + [ + __DIR__ . '/ExampleTwigController.php', + ], + [ + [ + 'Twig template "baz.html.twig" does not exist.', + 61, + ], + ] + ); + } + +} diff --git a/tests/Rules/Symfony/TwigTemplateExistsRuleNoTemplatesTest.php b/tests/Rules/Symfony/TwigTemplateExistsRuleNoTemplatesTest.php new file mode 100644 index 00000000..8d9e685b --- /dev/null +++ b/tests/Rules/Symfony/TwigTemplateExistsRuleNoTemplatesTest.php @@ -0,0 +1,29 @@ + + */ +final class TwigTemplateExistsRuleNoTemplatesTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new TwigTemplateExistsRule([]); + } + + public function testGetArgument(): void + { + $this->analyse( + [ + __DIR__ . '/ExampleTwigController.php', + ], + [] + ); + } + +} diff --git a/tests/Rules/Symfony/TwigTemplateExistsRuleTest.php b/tests/Rules/Symfony/TwigTemplateExistsRuleTest.php new file mode 100644 index 00000000..927026ce --- /dev/null +++ b/tests/Rules/Symfony/TwigTemplateExistsRuleTest.php @@ -0,0 +1,90 @@ + + */ +final class TwigTemplateExistsRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new TwigTemplateExistsRule([__DIR__ . '/templates']); + } + + public function testGetArgument(): void + { + $this->analyse( + [ + __DIR__ . '/ExampleTwigController.php', + ], + [ + [ + 'Twig template "bar.html.twig" does not exist.', + 22, + ], + [ + 'Twig template "bar.html.twig" does not exist.', + 23, + ], + [ + 'Twig template "bar.html.twig" does not exist.', + 24, + ], + [ + 'Twig template "bar.html.twig" does not exist.', + 25, + ], + [ + 'Twig template "bar.html.twig" does not exist.', + 26, + ], + [ + 'Twig template "bar.html.twig" does not exist.', + 27, + ], + [ + 'Twig template "bar.html.twig" does not exist.', + 35, + ], + [ + 'Twig template "bar.html.twig" does not exist.', + 36, + ], + [ + 'Twig template "bar.html.twig" does not exist.', + 37, + ], + [ + 'Twig template "bar.html.twig" does not exist.', + 44, + ], + [ + 'Twig template "bar.html.twig" does not exist.', + 45, + ], + [ + 'Twig template "bar.html.twig" does not exist.', + 53, + ], + [ + 'Twig template "bar.html.twig" does not exist.', + 57, + ], + [ + 'Twig template "bar.html.twig" does not exist.', + 61, + ], + [ + 'Twig template "baz.html.twig" does not exist.', + 61, + ], + ] + ); + } + +} diff --git a/tests/Rules/Symfony/data/bar.html.twig b/tests/Rules/Symfony/data/bar.html.twig new file mode 100644 index 00000000..e69de29b diff --git a/tests/Rules/Symfony/templates/foo.html.twig b/tests/Rules/Symfony/templates/foo.html.twig new file mode 100644 index 00000000..e69de29b From d366e518512e09ecca2c4081fb0eeb34e7aa0800 Mon Sep 17 00:00:00 2001 From: Zachary Lund Date: Wed, 21 Aug 2024 09:25:55 -0500 Subject: [PATCH 2/3] Handle Twig namespaces --- extension.neon | 2 +- src/Rules/Symfony/TwigTemplateExistsRule.php | 18 +++++++++++++++--- tests/Rules/Symfony/ExampleTwigController.php | 4 ++++ ...TwigTemplateExistsRuleMoreTemplatesTest.php | 13 +++++++++++-- .../Symfony/TwigTemplateExistsRuleTest.php | 13 ++++++++++++- .../admin/backend.html.twig} | 0 .../Symfony/{ => twig}/templates/foo.html.twig | 0 tests/Rules/Symfony/twig/user/bar.html.twig | 0 8 files changed, 43 insertions(+), 7 deletions(-) rename tests/Rules/Symfony/{data/bar.html.twig => twig/admin/backend.html.twig} (100%) rename tests/Rules/Symfony/{ => twig}/templates/foo.html.twig (100%) create mode 100644 tests/Rules/Symfony/twig/user/bar.html.twig diff --git a/extension.neon b/extension.neon index 629bd28a..fd2cddde 100644 --- a/extension.neon +++ b/extension.neon @@ -116,7 +116,7 @@ parametersSchema: constantHassers: bool() console_application_loader: schema(string(), nullable()) consoleApplicationLoader: schema(string(), nullable()) - twigTemplateDirectories: listOf(string()) + twigTemplateDirectories: arrayOf(schema(string(), nullable())) ]) services: diff --git a/src/Rules/Symfony/TwigTemplateExistsRule.php b/src/Rules/Symfony/TwigTemplateExistsRule.php index 64a1c0d6..489a9a1c 100644 --- a/src/Rules/Symfony/TwigTemplateExistsRule.php +++ b/src/Rules/Symfony/TwigTemplateExistsRule.php @@ -16,6 +16,7 @@ use function file_exists; use function in_array; use function is_string; +use function preg_match; use function sprintf; /** @@ -24,10 +25,10 @@ final class TwigTemplateExistsRule implements Rule { - /** @var list */ + /** @var array */ private $twigTemplateDirectories; - /** @param list $twigTemplateDirectories */ + /** @param array $twigTemplateDirectories */ public function __construct(array $twigTemplateDirectories) { $this->twigTemplateDirectories = $twigTemplateDirectories; @@ -108,7 +109,18 @@ private function getTwigTemplateArg(MethodCall $node, Scope $scope): ?Arg private function twigTemplateExists(string $templateName): bool { - foreach ($this->twigTemplateDirectories as $twigTemplateDirectory) { + if (preg_match('#^@(.+)\/(.+)$#', $templateName, $matches) === 1) { + $templateNamespace = $matches[1]; + $templateName = $matches[2]; + } else { + $templateNamespace = null; + } + + foreach ($this->twigTemplateDirectories as $twigTemplateDirectory => $namespace) { + if ($namespace !== $templateNamespace) { + continue; + } + $templatePath = $twigTemplateDirectory . '/' . $templateName; if (file_exists($templatePath)) { diff --git a/tests/Rules/Symfony/ExampleTwigController.php b/tests/Rules/Symfony/ExampleTwigController.php index c434d180..cc362f5b 100644 --- a/tests/Rules/Symfony/ExampleTwigController.php +++ b/tests/Rules/Symfony/ExampleTwigController.php @@ -61,6 +61,10 @@ public function foo(): void $this->render($name); $this->render($this->getName()); + + $this->render('@admin/backend.html.twig'); + $this->render('@admin/foo.html.twig'); + $this->render('backend.html.twig'); } private function getName(): string diff --git a/tests/Rules/Symfony/TwigTemplateExistsRuleMoreTemplatesTest.php b/tests/Rules/Symfony/TwigTemplateExistsRuleMoreTemplatesTest.php index e43e0199..fd5b6112 100644 --- a/tests/Rules/Symfony/TwigTemplateExistsRuleMoreTemplatesTest.php +++ b/tests/Rules/Symfony/TwigTemplateExistsRuleMoreTemplatesTest.php @@ -14,8 +14,9 @@ final class TwigTemplateExistsRuleMoreTemplatesTest extends RuleTestCase protected function getRule(): Rule { return new TwigTemplateExistsRule([ - __DIR__ . '/data', - __DIR__ . '/templates', + __DIR__ . '/twig/templates' => null, + __DIR__ . '/twig/admin' => 'admin', + __DIR__ . '/twig/user' => null, ]); } @@ -30,6 +31,14 @@ public function testGetArgument(): void 'Twig template "baz.html.twig" does not exist.', 61, ], + [ + 'Twig template "@admin/foo.html.twig" does not exist.', + 66, + ], + [ + 'Twig template "backend.html.twig" does not exist.', + 67, + ], ] ); } diff --git a/tests/Rules/Symfony/TwigTemplateExistsRuleTest.php b/tests/Rules/Symfony/TwigTemplateExistsRuleTest.php index 927026ce..099079d1 100644 --- a/tests/Rules/Symfony/TwigTemplateExistsRuleTest.php +++ b/tests/Rules/Symfony/TwigTemplateExistsRuleTest.php @@ -13,7 +13,10 @@ final class TwigTemplateExistsRuleTest extends RuleTestCase protected function getRule(): Rule { - return new TwigTemplateExistsRule([__DIR__ . '/templates']); + return new TwigTemplateExistsRule([ + __DIR__ . '/twig/templates' => null, + __DIR__ . '/twig/admin' => 'admin', + ]); } public function testGetArgument(): void @@ -83,6 +86,14 @@ public function testGetArgument(): void 'Twig template "baz.html.twig" does not exist.', 61, ], + [ + 'Twig template "@admin/foo.html.twig" does not exist.', + 66, + ], + [ + 'Twig template "backend.html.twig" does not exist.', + 67, + ], ] ); } diff --git a/tests/Rules/Symfony/data/bar.html.twig b/tests/Rules/Symfony/twig/admin/backend.html.twig similarity index 100% rename from tests/Rules/Symfony/data/bar.html.twig rename to tests/Rules/Symfony/twig/admin/backend.html.twig diff --git a/tests/Rules/Symfony/templates/foo.html.twig b/tests/Rules/Symfony/twig/templates/foo.html.twig similarity index 100% rename from tests/Rules/Symfony/templates/foo.html.twig rename to tests/Rules/Symfony/twig/templates/foo.html.twig diff --git a/tests/Rules/Symfony/twig/user/bar.html.twig b/tests/Rules/Symfony/twig/user/bar.html.twig new file mode 100644 index 00000000..e69de29b From 9b97b95daf155526e834dfb225fdeca3a1030f40 Mon Sep 17 00:00:00 2001 From: Zachary Lund Date: Sun, 25 Aug 2024 15:05:32 -0500 Subject: [PATCH 3/3] Use Twig environment loader --- composer.json | 3 +- extension.neon | 10 ++-- rules.neon | 1 + src/Rules/Symfony/TwigTemplateExistsRule.php | 42 ++------------ src/Symfony/TwigEnvironmentResolver.php | 55 +++++++++++++++++++ tests/Rules/Symfony/ExampleTwigController.php | 4 -- ...wigTemplateExistsRuleMoreTemplatesTest.php | 46 ---------------- .../TwigTemplateExistsRuleNoTemplatesTest.php | 3 +- .../Symfony/TwigTemplateExistsRuleTest.php | 14 +---- .../Symfony/twig/admin/backend.html.twig | 0 .../Symfony/twig/templates/foo.html.twig | 0 tests/Rules/Symfony/twig/user/bar.html.twig | 0 .../Rules/Symfony/twig_environment_loader.php | 10 ++++ 13 files changed, 82 insertions(+), 106 deletions(-) create mode 100644 src/Symfony/TwigEnvironmentResolver.php delete mode 100644 tests/Rules/Symfony/TwigTemplateExistsRuleMoreTemplatesTest.php delete mode 100644 tests/Rules/Symfony/twig/admin/backend.html.twig delete mode 100644 tests/Rules/Symfony/twig/templates/foo.html.twig delete mode 100644 tests/Rules/Symfony/twig/user/bar.html.twig create mode 100644 tests/Rules/Symfony/twig_environment_loader.php diff --git a/composer.json b/composer.json index f132544d..44e9701c 100644 --- a/composer.json +++ b/composer.json @@ -36,7 +36,8 @@ "symfony/messenger": "^5.4", "symfony/polyfill-php80": "^1.24", "symfony/serializer": "^5.4", - "symfony/service-contracts": "^2.2.0" + "symfony/service-contracts": "^2.2.0", + "twig/twig": "^3.0" }, "config": { "sort-packages": true diff --git a/extension.neon b/extension.neon index fd2cddde..567ec50d 100644 --- a/extension.neon +++ b/extension.neon @@ -11,7 +11,7 @@ parameters: constantHassers: true console_application_loader: null consoleApplicationLoader: null - twigTemplateDirectories: [] + twigEnvironmentLoader: null featureToggles: skipCheckGenericClasses: - Symfony\Component\Form\AbstractType @@ -116,7 +116,7 @@ parametersSchema: constantHassers: bool() console_application_loader: schema(string(), nullable()) consoleApplicationLoader: schema(string(), nullable()) - twigTemplateDirectories: arrayOf(schema(string(), nullable())) + twigEnvironmentLoader: schema(string(), nullable()) ]) services: @@ -369,8 +369,6 @@ services: tags: [phpstan.broker.dynamicMethodReturnTypeExtension] - - class: PHPStan\Rules\Symfony\TwigTemplateExistsRule + class: PHPStan\Symfony\TwigEnvironmentResolver arguments: - twigTemplateDirectories: %symfony.twigTemplateDirectories% - tags: - - phpstan.rules.rule + twigEnvironmentLoader: %symfony.twigEnvironmentLoader% diff --git a/rules.neon b/rules.neon index cedcea7a..baaa6e1f 100644 --- a/rules.neon +++ b/rules.neon @@ -5,4 +5,5 @@ rules: - PHPStan\Rules\Symfony\InvalidArgumentDefaultValueRule - PHPStan\Rules\Symfony\UndefinedOptionRule - PHPStan\Rules\Symfony\InvalidOptionDefaultValueRule + - PHPStan\Rules\Symfony\TwigTemplateExistsRule diff --git a/src/Rules/Symfony/TwigTemplateExistsRule.php b/src/Rules/Symfony/TwigTemplateExistsRule.php index 489a9a1c..e40cc184 100644 --- a/src/Rules/Symfony/TwigTemplateExistsRule.php +++ b/src/Rules/Symfony/TwigTemplateExistsRule.php @@ -11,12 +11,11 @@ use PHPStan\Analyser\Scope; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; +use PHPStan\Symfony\TwigEnvironmentResolver; use PHPStan\Type\ObjectType; use function count; -use function file_exists; use function in_array; use function is_string; -use function preg_match; use function sprintf; /** @@ -25,13 +24,12 @@ final class TwigTemplateExistsRule implements Rule { - /** @var array */ - private $twigTemplateDirectories; + /** @var TwigEnvironmentResolver */ + private $twigEnvironmentResolver; - /** @param array $twigTemplateDirectories */ - public function __construct(array $twigTemplateDirectories) + public function __construct(TwigEnvironmentResolver $twigEnvironmentResolver) { - $this->twigTemplateDirectories = $twigTemplateDirectories; + $this->twigEnvironmentResolver = $twigEnvironmentResolver; } public function getNodeType(): string @@ -41,10 +39,6 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { - if (count($this->twigTemplateDirectories) === 0) { - return []; - } - $templateArg = $this->getTwigTemplateArg($node, $scope); if ($templateArg === null) { @@ -70,7 +64,7 @@ public function processNode(Node $node, Scope $scope): array $errors = []; foreach ($templateNames as $templateName) { - if ($this->twigTemplateExists($templateName)) { + if ($this->twigEnvironmentResolver->templateExists($templateName)) { continue; } @@ -107,28 +101,4 @@ private function getTwigTemplateArg(MethodCall $node, Scope $scope): ?Arg return null; } - private function twigTemplateExists(string $templateName): bool - { - if (preg_match('#^@(.+)\/(.+)$#', $templateName, $matches) === 1) { - $templateNamespace = $matches[1]; - $templateName = $matches[2]; - } else { - $templateNamespace = null; - } - - foreach ($this->twigTemplateDirectories as $twigTemplateDirectory => $namespace) { - if ($namespace !== $templateNamespace) { - continue; - } - - $templatePath = $twigTemplateDirectory . '/' . $templateName; - - if (file_exists($templatePath)) { - return true; - } - } - - return false; - } - } diff --git a/src/Symfony/TwigEnvironmentResolver.php b/src/Symfony/TwigEnvironmentResolver.php new file mode 100644 index 00000000..9ffe0245 --- /dev/null +++ b/src/Symfony/TwigEnvironmentResolver.php @@ -0,0 +1,55 @@ +twigEnvironmentLoader = $twigEnvironmentLoader; + } + + private function getTwigEnvironment(): ?Environment + { + if ($this->twigEnvironmentLoader === null) { + return null; + } + + if ($this->twigEnvironment !== null) { + return $this->twigEnvironment; + } + + if (!file_exists($this->twigEnvironmentLoader) + || !is_readable($this->twigEnvironmentLoader) + ) { + throw new ShouldNotHappenException(sprintf('Cannot load Twig environment. Check the parameters.symfony.twigEnvironmentLoader setting in PHPStan\'s config. The offending value is "%s".', $this->twigEnvironmentLoader)); + } + + return $this->twigEnvironment = require $this->twigEnvironmentLoader; + } + + public function templateExists(string $name): bool + { + $twigEnvironment = $this->getTwigEnvironment(); + + if ($twigEnvironment === null) { + return true; + } + + return $twigEnvironment->getLoader()->exists($name); + } + +} diff --git a/tests/Rules/Symfony/ExampleTwigController.php b/tests/Rules/Symfony/ExampleTwigController.php index cc362f5b..c434d180 100644 --- a/tests/Rules/Symfony/ExampleTwigController.php +++ b/tests/Rules/Symfony/ExampleTwigController.php @@ -61,10 +61,6 @@ public function foo(): void $this->render($name); $this->render($this->getName()); - - $this->render('@admin/backend.html.twig'); - $this->render('@admin/foo.html.twig'); - $this->render('backend.html.twig'); } private function getName(): string diff --git a/tests/Rules/Symfony/TwigTemplateExistsRuleMoreTemplatesTest.php b/tests/Rules/Symfony/TwigTemplateExistsRuleMoreTemplatesTest.php deleted file mode 100644 index fd5b6112..00000000 --- a/tests/Rules/Symfony/TwigTemplateExistsRuleMoreTemplatesTest.php +++ /dev/null @@ -1,46 +0,0 @@ - - */ -final class TwigTemplateExistsRuleMoreTemplatesTest extends RuleTestCase -{ - - protected function getRule(): Rule - { - return new TwigTemplateExistsRule([ - __DIR__ . '/twig/templates' => null, - __DIR__ . '/twig/admin' => 'admin', - __DIR__ . '/twig/user' => null, - ]); - } - - public function testGetArgument(): void - { - $this->analyse( - [ - __DIR__ . '/ExampleTwigController.php', - ], - [ - [ - 'Twig template "baz.html.twig" does not exist.', - 61, - ], - [ - 'Twig template "@admin/foo.html.twig" does not exist.', - 66, - ], - [ - 'Twig template "backend.html.twig" does not exist.', - 67, - ], - ] - ); - } - -} diff --git a/tests/Rules/Symfony/TwigTemplateExistsRuleNoTemplatesTest.php b/tests/Rules/Symfony/TwigTemplateExistsRuleNoTemplatesTest.php index 8d9e685b..7c65fe8b 100644 --- a/tests/Rules/Symfony/TwigTemplateExistsRuleNoTemplatesTest.php +++ b/tests/Rules/Symfony/TwigTemplateExistsRuleNoTemplatesTest.php @@ -3,6 +3,7 @@ namespace PHPStan\Rules\Symfony; use PHPStan\Rules\Rule; +use PHPStan\Symfony\TwigEnvironmentResolver; use PHPStan\Testing\RuleTestCase; /** @@ -13,7 +14,7 @@ final class TwigTemplateExistsRuleNoTemplatesTest extends RuleTestCase protected function getRule(): Rule { - return new TwigTemplateExistsRule([]); + return new TwigTemplateExistsRule(new TwigEnvironmentResolver(null)); } public function testGetArgument(): void diff --git a/tests/Rules/Symfony/TwigTemplateExistsRuleTest.php b/tests/Rules/Symfony/TwigTemplateExistsRuleTest.php index 099079d1..c3f96195 100644 --- a/tests/Rules/Symfony/TwigTemplateExistsRuleTest.php +++ b/tests/Rules/Symfony/TwigTemplateExistsRuleTest.php @@ -3,6 +3,7 @@ namespace PHPStan\Rules\Symfony; use PHPStan\Rules\Rule; +use PHPStan\Symfony\TwigEnvironmentResolver; use PHPStan\Testing\RuleTestCase; /** @@ -13,10 +14,7 @@ final class TwigTemplateExistsRuleTest extends RuleTestCase protected function getRule(): Rule { - return new TwigTemplateExistsRule([ - __DIR__ . '/twig/templates' => null, - __DIR__ . '/twig/admin' => 'admin', - ]); + return new TwigTemplateExistsRule(new TwigEnvironmentResolver(__DIR__ . '/twig_environment_loader.php')); } public function testGetArgument(): void @@ -86,14 +84,6 @@ public function testGetArgument(): void 'Twig template "baz.html.twig" does not exist.', 61, ], - [ - 'Twig template "@admin/foo.html.twig" does not exist.', - 66, - ], - [ - 'Twig template "backend.html.twig" does not exist.', - 67, - ], ] ); } diff --git a/tests/Rules/Symfony/twig/admin/backend.html.twig b/tests/Rules/Symfony/twig/admin/backend.html.twig deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/Rules/Symfony/twig/templates/foo.html.twig b/tests/Rules/Symfony/twig/templates/foo.html.twig deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/Rules/Symfony/twig/user/bar.html.twig b/tests/Rules/Symfony/twig/user/bar.html.twig deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/Rules/Symfony/twig_environment_loader.php b/tests/Rules/Symfony/twig_environment_loader.php new file mode 100644 index 00000000..822a8e8a --- /dev/null +++ b/tests/Rules/Symfony/twig_environment_loader.php @@ -0,0 +1,10 @@ + 'foo']); + +return new Environment($loader);