-
Notifications
You must be signed in to change notification settings - Fork 92
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
14eec8c
commit d0a7ea1
Showing
8 changed files
with
358 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Rules\Symfony; | ||
|
||
use PhpParser\Node; | ||
use PhpParser\Node\Arg; | ||
use PhpParser\Node\Expr\MethodCall; | ||
use PhpParser\Node\Expr\Variable; | ||
use PhpParser\Node\Identifier; | ||
use PhpParser\Node\Scalar\String_; | ||
use PHPStan\Analyser\Scope; | ||
use PHPStan\Rules\Rule; | ||
use PHPStan\Rules\RuleErrorBuilder; | ||
use PHPStan\Type\ObjectType; | ||
use function count; | ||
use function file_exists; | ||
use function in_array; | ||
use function is_string; | ||
use function sprintf; | ||
|
||
/** | ||
* @implements Rule<MethodCall> | ||
*/ | ||
final class TwigTemplateExistsRule implements Rule | ||
{ | ||
|
||
/** @var list<string> */ | ||
private $twigTemplateDirectories; | ||
|
||
/** @param list<string> $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; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Rules\Symfony; | ||
|
||
use Symfony\Bridge\Twig\Mime\TemplatedEmail; | ||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; | ||
use Twig\Environment; | ||
use function rand; | ||
|
||
final class ExampleTwigController extends AbstractController | ||
{ | ||
|
||
public function foo(): void | ||
{ | ||
$this->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'; | ||
} | ||
|
||
} |
37 changes: 37 additions & 0 deletions
37
tests/Rules/Symfony/TwigTemplateExistsRuleMoreTemplatesTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Rules\Symfony; | ||
|
||
use PHPStan\Rules\Rule; | ||
use PHPStan\Testing\RuleTestCase; | ||
|
||
/** | ||
* @extends RuleTestCase<TwigTemplateExistsRule> | ||
*/ | ||
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.', | ||
60, | ||
], | ||
] | ||
); | ||
} | ||
|
||
} |
29 changes: 29 additions & 0 deletions
29
tests/Rules/Symfony/TwigTemplateExistsRuleNoTemplatesTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Rules\Symfony; | ||
|
||
use PHPStan\Rules\Rule; | ||
use PHPStan\Testing\RuleTestCase; | ||
|
||
/** | ||
* @extends RuleTestCase<TwigTemplateExistsRule> | ||
*/ | ||
final class TwigTemplateExistsRuleNoTemplatesTest extends RuleTestCase | ||
{ | ||
|
||
protected function getRule(): Rule | ||
{ | ||
return new TwigTemplateExistsRule([]); | ||
} | ||
|
||
public function testGetArgument(): void | ||
{ | ||
$this->analyse( | ||
[ | ||
__DIR__ . '/ExampleTwigController.php', | ||
], | ||
[] | ||
); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Rules\Symfony; | ||
|
||
use PHPStan\Rules\Rule; | ||
use PHPStan\Testing\RuleTestCase; | ||
|
||
/** | ||
* @extends RuleTestCase<TwigTemplateExistsRule> | ||
*/ | ||
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.', | ||
21, | ||
], | ||
[ | ||
'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.', | ||
34, | ||
], | ||
[ | ||
'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.', | ||
43, | ||
], | ||
[ | ||
'Twig template "bar.html.twig" does not exist.', | ||
44, | ||
], | ||
[ | ||
'Twig template "bar.html.twig" does not exist.', | ||
52, | ||
], | ||
[ | ||
'Twig template "bar.html.twig" does not exist.', | ||
56, | ||
], | ||
[ | ||
'Twig template "bar.html.twig" does not exist.', | ||
60, | ||
], | ||
[ | ||
'Twig template "baz.html.twig" does not exist.', | ||
60, | ||
], | ||
] | ||
); | ||
} | ||
|
||
} |
Empty file.
Empty file.