Skip to content

Commit

Permalink
Show errors when template resolving fails
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinMystikJonas committed Dec 1, 2022
1 parent 82997aa commit e4ebb7f
Show file tree
Hide file tree
Showing 13 changed files with 138 additions and 48 deletions.
6 changes: 3 additions & 3 deletions src/Collector/Finder/TemplatePathFinder.php
Expand Up @@ -15,7 +15,7 @@
final class TemplatePathFinder
{
/**
* @var array<string, array<string, string[]>>
* @var array<string, array<string, array<?string>>>
*/
private array $collectedTemplatePaths;

Expand All @@ -33,15 +33,15 @@ public function __construct(CollectedDataNode $collectedDataNode)
}

/**
* @return string[]
* @return array<?string>
*/
public function find(string $className, string $methodName): array
{
return $this->collectedTemplatePaths[$className][$methodName] ?? [];
}

/**
* @return string[]
* @return array<?string>
*/
public function findByMethod(ReflectionMethod $method): array
{
Expand Down
2 changes: 1 addition & 1 deletion src/Collector/TemplatePathCollector.php
Expand Up @@ -76,7 +76,7 @@ public function processNode(Node $node, Scope $scope): ?array
/** @var string|null $path */
$path = $this->valueResolver->resolve($arg->value, $scope->getFile());
if ($path === null) {
return null;
return (new CollectedTemplatePath($actualClassName, $functionName, null))->toArray();
}
return (new CollectedTemplatePath($actualClassName, $functionName, $path))->toArray();
}
Expand Down
8 changes: 4 additions & 4 deletions src/Collector/ValueObject/CollectedTemplatePath.php
Expand Up @@ -5,17 +5,17 @@
namespace Efabrica\PHPStanLatte\Collector\ValueObject;

/**
* @phpstan-type CollectedTemplatePathArray array{className: string, methodName: string, templatePath: string}
* @phpstan-type CollectedTemplatePathArray array{className: string, methodName: string, templatePath: ?string}
*/
final class CollectedTemplatePath
{
private string $className;

private string $methodName;

private string $templatePath;
private ?string $templatePath;

public function __construct(string $className, string $methodName, string $templatePath)
public function __construct(string $className, string $methodName, ?string $templatePath)
{
$this->className = $className;
$this->methodName = $methodName;
Expand All @@ -32,7 +32,7 @@ public function getMethodName(): string
return $this->methodName;
}

public function getTemplatePath(): string
public function getTemplatePath(): ?string
{
return $this->templatePath;
}
Expand Down
24 changes: 20 additions & 4 deletions src/LatteTemplateResolver/AbstractClassMethodTemplateResolver.php
Expand Up @@ -7,25 +7,41 @@
use Efabrica\PHPStanLatte\Template\Template;
use PHPStan\BetterReflection\Reflection\ReflectionClass;
use PHPStan\Node\CollectedDataNode;
use PHPStan\Rules\RuleErrorBuilder;

abstract class AbstractClassMethodTemplateResolver extends AbstractClassTemplateResolver
{
protected function getClassTemplates(ReflectionClass $reflectionClass, CollectedDataNode $collectedDataNode): array
protected function getClassResult(ReflectionClass $reflectionClass, CollectedDataNode $collectedDataNode): LatteTemplateResolverResult
{
$className = $reflectionClass->getName();
$globalVariables = $this->getClassGlobalVariables($reflectionClass);
$globalComponents = $this->getClassGlobalComponents($reflectionClass);

$templates = [];
$result = new LatteTemplateResolverResult();
foreach ($this->getMethodsMatching($reflectionClass, $this->getClassMethodPattern() . 'i') as $reflectionMethod) {
$variables = array_merge($globalVariables, $this->variableFinder->findByMethod($reflectionMethod));
$components = array_merge($globalComponents, $this->componentFinder->findByMethod($reflectionMethod));

$templatePaths = $this->templatePathFinder->findByMethod($reflectionMethod);
if (count($templatePaths) === 0) {
$result->addErrorFromBuilder(RuleErrorBuilder::message("Cannot resolve latte template for {$reflectionMethod->getName()} of $className")
->file($reflectionClass->getFileName() ?? 'unknown')
->line($reflectionMethod->getStartLine())
->identifier($reflectionMethod->getName()));
}
foreach ($templatePaths as $templatePath) {
$templates[] = new Template($templatePath, $reflectionClass->getName(), $reflectionMethod->getName(), $variables, $components);
if ($templatePath === null) {
// TODO exact file and line where failed expression is located
$result->addErrorFromBuilder(RuleErrorBuilder::message("Cannot automatically resolve latte template from expression in {$reflectionMethod->getName()} of $className")
->file($reflectionClass->getFileName() ?? 'unknown')
->line($reflectionMethod->getStartLine())
->identifier($reflectionMethod->getName()));
} else {
$result->addTemplate(new Template($templatePath, $className, $reflectionMethod->getName(), $variables, $components));
}
}
}
return $templates;
return $result;
}

abstract protected function getClassMethodPattern(): string;
Expand Down
Expand Up @@ -11,20 +11,20 @@

abstract class AbstractClassStandaloneTemplateResolver extends AbstractClassTemplateResolver
{
protected function getClassTemplates(ReflectionClass $reflectionClass, CollectedDataNode $collectedDataNode): array
protected function getClassResult(ReflectionClass $reflectionClass, CollectedDataNode $collectedDataNode): LatteTemplateResolverResult
{
$templates = [];
$result = new LatteTemplateResolverResult();
$standaloneTemplateFiles = $this->findStandaloneTemplates($reflectionClass);
foreach ($standaloneTemplateFiles as $standaloneTemplateFile) {
$templates[] = new Template(
$result->addTemplate(new Template(
$standaloneTemplateFile,
$reflectionClass->getName(),
null,
$this->getClassGlobalVariables($reflectionClass),
$this->getClassGlobalComponents($reflectionClass)
);
));
}
return $templates;
return $result;
}

/**
Expand Down
13 changes: 6 additions & 7 deletions src/LatteTemplateResolver/AbstractClassTemplateResolver.php
Expand Up @@ -6,7 +6,6 @@

use Efabrica\PHPStanLatte\Collector\ValueObject\CollectedResolvedNode;
use Efabrica\PHPStanLatte\Template\Component;
use Efabrica\PHPStanLatte\Template\Template;
use Efabrica\PHPStanLatte\Template\Variable;
use PhpParser\Node;
use PhpParser\Node\Stmt\Class_;
Expand Down Expand Up @@ -61,19 +60,19 @@ public function collect(Node $node, Scope $scope): ?CollectedResolvedNode
}

/**
* @return Template[]
* @return LatteTemplateResolverResult
*/
protected function getTemplates(CollectedResolvedNode $resolvedNode, CollectedDataNode $collectedDataNode): array
protected function getResult(CollectedResolvedNode $resolvedNode, CollectedDataNode $collectedDataNode): LatteTemplateResolverResult
{
$className = $resolvedNode->getParam(self::PARAM_CLASS_NAME);
$reflectionClass = (new BetterReflection())->reflector()->reflectClass($className);

$fileName = $reflectionClass->getFileName();
if ($fileName === null) {
return [];
return new LatteTemplateResolverResult();
}

return $this->getClassTemplates($reflectionClass, $collectedDataNode);
return $this->getClassResult($reflectionClass, $collectedDataNode);
}

/**
Expand Down Expand Up @@ -123,7 +122,7 @@ abstract protected function getClassGlobalVariables(ReflectionClass $reflectionC
abstract protected function getClassGlobalComponents(ReflectionClass $reflectionClass): array;

/**
* @return Template[]
* @return LatteTemplateResolverResult
*/
abstract protected function getClassTemplates(ReflectionClass $resolveClass, CollectedDataNode $collectedDataNode): array;
abstract protected function getClassResult(ReflectionClass $resolveClass, CollectedDataNode $collectedDataNode): LatteTemplateResolverResult;
}
9 changes: 4 additions & 5 deletions src/LatteTemplateResolver/AbstractTemplateResolver.php
Expand Up @@ -9,7 +9,6 @@
use Efabrica\PHPStanLatte\Collector\Finder\TemplatePathFinder;
use Efabrica\PHPStanLatte\Collector\Finder\VariableFinder;
use Efabrica\PHPStanLatte\Collector\ValueObject\CollectedResolvedNode;
use Efabrica\PHPStanLatte\Template\Template;
use PHPStan\Node\CollectedDataNode;
use PHPStan\PhpDoc\TypeStringResolver;

Expand All @@ -30,19 +29,19 @@ public function __construct(TypeStringResolver $typeStringResolver)
$this->typeStringResolver = $typeStringResolver;
}

public function findTemplates(CollectedResolvedNode $resolvedNode, CollectedDataNode $collectedDataNode): array
public function resolve(CollectedResolvedNode $resolvedNode, CollectedDataNode $collectedDataNode): LatteTemplateResolverResult
{
// TODO create factories?
$this->methodCallFinder = new MethodCallFinder($collectedDataNode);
$this->variableFinder = new VariableFinder($collectedDataNode, $this->methodCallFinder, $this->typeStringResolver);
$this->componentFinder = new ComponentFinder($collectedDataNode, $this->methodCallFinder, $this->typeStringResolver);
$this->templatePathFinder = new TemplatePathFinder($collectedDataNode);

return $this->getTemplates($resolvedNode, $collectedDataNode);
return $this->getResult($resolvedNode, $collectedDataNode);
}

/**
* @return Template[]
* @return LatteTemplateResolverResult
*/
abstract protected function getTemplates(CollectedResolvedNode $resolvedNode, CollectedDataNode $collectedDataNode): array;
abstract protected function getResult(CollectedResolvedNode $resolvedNode, CollectedDataNode $collectedDataNode): LatteTemplateResolverResult;
}
5 changes: 2 additions & 3 deletions src/LatteTemplateResolver/LatteTemplateResolverInterface.php
Expand Up @@ -5,7 +5,6 @@
namespace Efabrica\PHPStanLatte\LatteTemplateResolver;

use Efabrica\PHPStanLatte\Collector\ValueObject\CollectedResolvedNode;
use Efabrica\PHPStanLatte\Template\Template;
use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Node\CollectedDataNode;
Expand All @@ -16,7 +15,7 @@ interface LatteTemplateResolverInterface
public function collect(Node $node, Scope $scope): ?CollectedResolvedNode;

/**
* @return Template[]
* @return LatteTemplateResolverResult
*/
public function findTemplates(CollectedResolvedNode $resolvedNode, CollectedDataNode $collectedDataNode): array;
public function resolve(CollectedResolvedNode $resolvedNode, CollectedDataNode $collectedDataNode): LatteTemplateResolverResult;
}
59 changes: 59 additions & 0 deletions src/LatteTemplateResolver/LatteTemplateResolverResult.php
@@ -0,0 +1,59 @@
<?php

declare(strict_types=1);

namespace Efabrica\PHPStanLatte\LatteTemplateResolver;

use Efabrica\PHPStanLatte\Template\Template;
use PHPStan\Rules\RuleError;
use PHPStan\Rules\RuleErrorBuilder;

final class LatteTemplateResolverResult
{
/** @var Template[] */
private array $templates;

/** @var RuleError[] */
private array $errors;

/**
* @param Template[] $templates
* @param RuleError[] $errors
*/
public function __construct(array $templates = [], array $errors = [])
{
$this->templates = $templates;
$this->errors = $errors;
}

/**
* @return Template[]
*/
public function getTemplates(): array
{
return $this->templates;
}

/**
* @return RuleError[]
*/
public function getErrors(): array
{
return $this->errors;
}

public function addTemplate(Template $template): void
{
$this->templates[] = $template;
}

public function addError(RuleError $error): void
{
$this->errors[] = $error;
}

public function addErrorFromBuilder(RuleErrorBuilder $error): void
{
$this->errors[] = $error->build();
}
}
14 changes: 10 additions & 4 deletions src/LatteTemplateResolver/NetteApplicationUIPresenter.php
Expand Up @@ -7,6 +7,7 @@
use Efabrica\PHPStanLatte\Template\Template;
use PHPStan\BetterReflection\Reflection\ReflectionClass;
use PHPStan\Node\CollectedDataNode;
use PHPStan\Rules\RuleErrorBuilder;

final class NetteApplicationUIPresenter extends AbstractClassTemplateResolver
{
Expand All @@ -17,7 +18,7 @@ public function getSupportedClasses(): array
return ['Nette\Application\UI\Presenter'];
}

protected function getClassTemplates(ReflectionClass $reflectionClass, CollectedDataNode $collectedDataNode): array
protected function getClassResult(ReflectionClass $reflectionClass, CollectedDataNode $collectedDataNode): LatteTemplateResolverResult
{
$actions = [];
foreach ($this->getMethodsMatching($reflectionClass, '/^(action|render).*/') as $reflectionMethod) {
Expand All @@ -27,23 +28,28 @@ protected function getClassTemplates(ReflectionClass $reflectionClass, Collected
$actions[$actionName] = [
'variables' => $this->getClassGlobalVariables($reflectionClass),
'components' => $this->getClassGlobalComponents($reflectionClass),
'line' => $reflectionMethod->getStartLine(),
];
}

$actions[$actionName]['variables'] = array_merge($actions[$actionName]['variables'], $this->variableFinder->findByMethod($reflectionMethod));
$actions[$actionName]['components'] = array_merge($actions[$actionName]['components'], $this->componentFinder->findByMethod($reflectionMethod));
}

$templates = [];
$result = new LatteTemplateResolverResult();
foreach ($actions as $actionName => $actionDefinition) {
$template = $this->findTemplateFilePath($reflectionClass, $actionName);
if ($template === null) {
$result->addErrorFromBuilder(RuleErrorBuilder::message("Cannot resolve latte template for action $actionName of presenter {$reflectionClass->getName()}")
->file($reflectionClass->getFileName() ?? 'unknown')
->line($actionDefinition['line'])
->identifier($actionName));
continue;
}
$templates[] = new Template($template, $reflectionClass->getName(), $actionName, $actionDefinition['variables'], $actionDefinition['components']);
$result->addTemplate(new Template($template, $reflectionClass->getName(), $actionName, $actionDefinition['variables'], $actionDefinition['components']));
}

return $templates;
return $result;
}

private function findTemplateFilePath(ReflectionClass $reflectionClass, string $actionName): ?string
Expand Down
5 changes: 3 additions & 2 deletions src/Rule/LatteTemplatesRule.php
Expand Up @@ -91,8 +91,9 @@ public function processNode(Node $collectedDataNode, Scope $scope): array
$errors = [];
foreach ($this->latteTemplateResolvers as $latteTemplateResolver) {
foreach ($resolvedNodeFinder->find(get_class($latteTemplateResolver)) as $collectedResolvedNode) {
$templates = $latteTemplateResolver->findTemplates($collectedResolvedNode, $collectedDataNode);
$this->analyseTemplates($templates, $scope, $errors);
$result = $latteTemplateResolver->resolve($collectedResolvedNode, $collectedDataNode);
$errors = array_merge($errors, $result->getErrors());
$this->analyseTemplates($result->getTemplates(), $scope, $errors);
}
}

Expand Down
20 changes: 10 additions & 10 deletions tests/Rule/LatteTemplatesRule/CollectorResultRule.php
Expand Up @@ -10,10 +10,11 @@
use Efabrica\PHPStanLatte\Template\Variable;
use Nette\Utils\Strings;
use PhpParser\Node;
use PHPStan\Analyser\Error;
use PHPStan\Analyser\Scope;
use PHPStan\Node\CollectedDataNode;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleError;
use PHPStan\Rules\RuleErrorBuilder;

/**
* @implements Rule<CollectedDataNode>
Expand Down Expand Up @@ -47,12 +48,12 @@ public function processNode(Node $collectedDataNode, Scope $scope): array
foreach ($this->latteTemplateResolvers as $latteTemplateResolver) {
foreach ($resolvedNodeFinder->find(get_class($latteTemplateResolver)) as $collectedResolvedNode) {
$resolver = $this->shortClassName($collectedResolvedNode->getResolver());
$errors[] = new Error(
"NODE $resolver " . $this->dumpValue($collectedResolvedNode->getParams()),
$scope->getFile()
);
$templates = $latteTemplateResolver->findTemplates($collectedResolvedNode, $collectedDataNode);
$errors[] = RuleErrorBuilder::message("NODE $resolver " . $this->dumpValue($collectedResolvedNode->getParams()))->build();
$templates = $latteTemplateResolver->resolve($collectedResolvedNode, $collectedDataNode)->getTemplates();
foreach ($templates as $template) {
if ($template instanceof RuleError) {
continue;
}
$path = pathinfo($template->getPath(), PATHINFO_BASENAME);
$presenter = $this->shortClassName($template->getActualClass());
$variables = array_map(function (Variable $v) {
Expand All @@ -61,12 +62,11 @@ public function processNode(Node $collectedDataNode, Scope $scope): array
$components = array_map(function (Component $v) {
return $v->getName();
}, $template->getComponents());
$errors[] = new Error(
$errors[] = RuleErrorBuilder::message(
"TEMPLATE $path $presenter " .
$this->dumpValue($variables) . ' ' .
$this->dumpValue($components),
$template->getPath()
);
$this->dumpValue($components)
)->build();
}
}
}
Expand Down

0 comments on commit e4ebb7f

Please sign in to comment.