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 2, 2022
1 parent 82997aa commit a74cc76
Show file tree
Hide file tree
Showing 39 changed files with 369 additions and 104 deletions.
36 changes: 28 additions & 8 deletions src/Collector/Finder/ComponentFinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Efabrica\PHPStanLatte\Collector\ComponentCollector;
use Efabrica\PHPStanLatte\Collector\ValueObject\CollectedComponent;
use Efabrica\PHPStanLatte\Template\Component;
use PHPStan\BetterReflection\BetterReflection;
use PHPStan\BetterReflection\Reflection\ReflectionMethod;
use PHPStan\Node\CollectedDataNode;
use PHPStan\PhpDoc\TypeStringResolver;
Expand All @@ -19,7 +20,7 @@ final class ComponentFinder
/**
* @var array<string, array<string, Component[]>>
*/
private array $collectedComponents;
private array $collectedComponents = [];

private MethodCallFinder $methodCallFinder;

Expand Down Expand Up @@ -59,40 +60,59 @@ public function __construct(CollectedDataNode $collectedDataNode, MethodCallFind
*/
public function find(string $className, string $methodName): array
{
return $this->findMethodCalls($className, $methodName);
return array_merge(
$this->collectedComponents[$className][''] ?? [],
$this->findInParents($className),
$this->findInMethodCalls($className, '__construct'),
$this->findInMethodCalls($className, $methodName),
);
}

/**
* @return Component[]
*/
public function findByMethod(ReflectionMethod $method): array
{
return $this->findMethodCalls($method->getDeclaringClass()->getName(), $method->getName());
return $this->find($method->getDeclaringClass()->getName(), $method->getName());
}

/**
* @return Component[]
*/
private function findInParents(string $className)
{
$classReflection = (new BetterReflection())->reflector()->reflectClass($className);

$collectedComponents = [];
foreach ($classReflection->getParentClassNames() as $parentClass) {
$collectedComponents = array_merge(
$this->collectedComponents[$parentClass][''] ?? [],
$collectedComponents
);
}
return $collectedComponents;
}

/**
* @param array<string, array<string, true>> $alreadyFound
* @return Component[]
*/
private function findMethodCalls(string $className, string $methodName, array &$alreadyFound = []): array
private function findInMethodCalls(string $className, string $methodName, array &$alreadyFound = []): array
{
if (isset($alreadyFound[$className][$methodName])) {
return []; // stop recursion
} else {
$alreadyFound[$className][$methodName] = true;
}

// TODO check not only called method but also all parents

$collectedComponents = [
$this->collectedComponents[$className][$methodName] ?? [],
];

$methodCalls = $this->methodCallFinder->findCalled($className, $methodName);
foreach ($methodCalls as $calledClassName => $calledMethods) {
$collectedComponents[] = $this->findMethodCalls($calledClassName, '', $alreadyFound);
foreach ($calledMethods as $calledMethod) {
$collectedComponents[] = $this->findMethodCalls($calledClassName, $calledMethod, $alreadyFound);
$collectedComponents[] = $this->findInMethodCalls($calledClassName, $calledMethod, $alreadyFound);
}
}

Expand Down
65 changes: 59 additions & 6 deletions src/Collector/Finder/TemplatePathFinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Efabrica\PHPStanLatte\Collector\TemplatePathCollector;
use Efabrica\PHPStanLatte\Collector\ValueObject\CollectedTemplatePath;
use PHPStan\BetterReflection\BetterReflection;
use PHPStan\BetterReflection\Reflection\ReflectionMethod;
use PHPStan\Node\CollectedDataNode;

Expand All @@ -15,12 +16,16 @@
final class TemplatePathFinder
{
/**
* @var array<string, array<string, string[]>>
* @var array<string, array<string, array<?string>>>
*/
private array $collectedTemplatePaths;
private array $collectedTemplatePaths = [];

public function __construct(CollectedDataNode $collectedDataNode)
private MethodCallFinder $methodCallFinder;

public function __construct(CollectedDataNode $collectedDataNode, MethodCallFinder $methodCallFinder)
{
$this->methodCallFinder = $methodCallFinder;

$collectedTemplatePaths = $this->buildData(array_filter(array_merge(...array_values($collectedDataNode->get(TemplatePathCollector::class)))));
foreach ($collectedTemplatePaths as $collectedTemplatePath) {
$className = $collectedTemplatePath->getClassName();
Expand All @@ -33,21 +38,69 @@ public function __construct(CollectedDataNode $collectedDataNode)
}

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

/**
* @return string[]
* @return array<?string>
*/
public function findByMethod(ReflectionMethod $method): array
{
return $this->find($method->getDeclaringClass()->getName(), $method->getName());
}

/**
* @return array<?string>
*/
private function findInParents(string $className)
{
$classReflection = (new BetterReflection())->reflector()->reflectClass($className);

$collectedTemplatePaths = [];
foreach ($classReflection->getParentClassNames() as $parentClass) {
$collectedTemplatePaths = array_merge(
$this->collectedTemplatePaths[$parentClass][''] ?? [],
$collectedTemplatePaths
);
}
return $collectedTemplatePaths;
}

/**
* @param array<string, array<string, true>> $alreadyFound
* @return array<?string>
*/
private function findInMethodCalls(string $className, string $methodName, array &$alreadyFound = []): array
{
if (isset($alreadyFound[$className][$methodName])) {
return []; // stop recursion
} else {
$alreadyFound[$className][$methodName] = true;
}

$collectedTemplatePaths = [
$this->collectedTemplatePaths[$className][$methodName] ?? [],
];

$methodCalls = $this->methodCallFinder->findCalled($className, $methodName);
foreach ($methodCalls as $calledClassName => $calledMethods) {
foreach ($calledMethods as $calledMethod) {
$collectedTemplatePaths[] = $this->findInMethodCalls($calledClassName, $calledMethod, $alreadyFound);
}
}

return array_merge(...$collectedTemplatePaths);
}

/**
* @phpstan-param array<CollectedTemplatePathArray> $data
* @return CollectedTemplatePath[]
Expand Down
36 changes: 28 additions & 8 deletions src/Collector/Finder/VariableFinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Efabrica\PHPStanLatte\Collector\ValueObject\CollectedVariable;
use Efabrica\PHPStanLatte\Collector\VariableCollector;
use Efabrica\PHPStanLatte\Template\Variable;
use PHPStan\BetterReflection\BetterReflection;
use PHPStan\BetterReflection\Reflection\ReflectionMethod;
use PHPStan\Node\CollectedDataNode;
use PHPStan\PhpDoc\TypeStringResolver;
Expand All @@ -19,7 +20,7 @@ final class VariableFinder
/**
* @var array<string, array<string, Variable[]>>
*/
private array $collectedVariables;
private array $collectedVariables = [];

private MethodCallFinder $methodCallFinder;

Expand All @@ -46,40 +47,59 @@ public function __construct(CollectedDataNode $collectedDataNode, MethodCallFind
*/
public function find(string $className, string $methodName): array
{
return $this->findMethodCalls($className, $methodName);
return array_merge(
$this->collectedVariables[$className][''] ?? [],
$this->findInParents($className),
$this->findInMethodCalls($className, '__construct'),
$this->findInMethodCalls($className, $methodName),
);
}

/**
* @return Variable[]
*/
public function findByMethod(ReflectionMethod $method): array
{
return $this->findMethodCalls($method->getDeclaringClass()->getName(), $method->getName());
return $this->find($method->getDeclaringClass()->getName(), $method->getName());
}

/**
* @return Variable[]
*/
private function findInParents(string $className)
{
$classReflection = (new BetterReflection())->reflector()->reflectClass($className);

$collectedVariables = [];
foreach ($classReflection->getParentClassNames() as $parentClass) {
$collectedVariables = array_merge(
$this->collectedVariables[$parentClass][''] ?? [],
$collectedVariables
);
}
return $collectedVariables;
}

/**
* @param array<string, array<string, true>> $alreadyFound
* @return Variable[]
*/
private function findMethodCalls(string $className, string $methodName, array &$alreadyFound = []): array
private function findInMethodCalls(string $className, string $methodName, array &$alreadyFound = []): array
{
if (isset($alreadyFound[$className][$methodName])) {
return []; // stop recursion
} else {
$alreadyFound[$className][$methodName] = true;
}

// TODO check not only called method but also all parents

$collectedVariables = [
$this->collectedVariables[$className][$methodName] ?? [],
];

$methodCalls = $this->methodCallFinder->findCalled($className, $methodName);
foreach ($methodCalls as $calledClassName => $calledMethods) {
$collectedVariables[] = $this->findMethodCalls($calledClassName, '', $alreadyFound);
foreach ($calledMethods as $calledMethod) {
$collectedVariables[] = $this->findMethodCalls($calledClassName, $calledMethod, $alreadyFound);
$collectedVariables[] = $this->findInMethodCalls($calledClassName, $calledMethod, $alreadyFound);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/Collector/TemplatePathCollector.php
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
4 changes: 2 additions & 2 deletions src/Compiler/Compiler/AbstractCompiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ public function generateClassName(): string
return 'PHPStanLatteTemplate_' . md5(uniqid());
}

public function generateClassComment(string $className): string
public function generateClassComment(string $className, string $context): string
{
$comment = "\n";
$comment = "\n* $context\n";
$comment .= "* @property {$className}_global \$global\n";
$comment .= "\n";
return $comment;
Expand Down
4 changes: 2 additions & 2 deletions src/Compiler/Compiler/Latte2Compiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,14 @@ protected function createDefaultEngine(): Engine
return $engine;
}

public function compile(string $templateContent, ?string $actualClass): string
public function compile(string $templateContent, ?string $actualClass, string $context = ''): string
{
$latteTokens = $this->engine->getParser()->parse($templateContent);
$className = $this->generateClassName();
$phpContent = $this->engine->getCompiler()->compile(
$latteTokens,
$className,
$this->generateClassComment($className),
$this->generateClassComment($className, $context),
$this->strictMode
);
$phpContent = $this->fixLines($phpContent);
Expand Down
4 changes: 2 additions & 2 deletions src/Compiler/Compiler/Latte3Compiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ protected function createDefaultEngine(): Engine
return $engine;
}

public function compile(string $templateContent, ?string $actualClass): string
public function compile(string $templateContent, ?string $actualClass, string $context = ''): string
{
$templateNode = $this->engine->parse($templateContent);
$this->engine->applyPasses($templateNode);
Expand All @@ -49,7 +49,7 @@ public function compile(string $templateContent, ?string $actualClass): string
$phpContent = $templateGenerator->generate(
$templateNode,
$className,
$this->generateClassComment($className),
$this->generateClassComment($className, $context),
$this->strictMode
);
$phpContent = $this->fixLines($phpContent);
Expand Down
11 changes: 6 additions & 5 deletions src/Compiler/LatteToPhpCompiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ public function __construct(
* @param Variable[] $variables
* @param Component[] $components
*/
public function compile(?string $actualClass, string $templateContent, array $variables, array $components): string
public function compile(?string $actualClass, string $templateContent, array $variables, array $components, string $context = ''): string
{
$phpContent = $this->compiler->compile($templateContent, $actualClass);
$phpContent = $this->compiler->compile($templateContent, $actualClass, $context);
$phpContent = $this->explicitCalls($actualClass, $phpContent, $variables, $components);
$phpContent = $this->addExtractParams($phpContent);
return $this->remapLines($phpContent);
Expand All @@ -64,19 +64,20 @@ public function compile(?string $actualClass, string $templateContent, array $va
* @param Variable[] $variables
* @param Component[] $components
*/
public function compileFile(?string $actualClass, string $templatePath, array $variables, array $components): string
public function compileFile(?string $actualClass, string $templatePath, array $variables, array $components, string $context = ''): string
{
if (!file_exists($templatePath)) {
throw new InvalidArgumentException('Template file "' . $templatePath . '" doesn\'t exist.');
}
$templateContent = file_get_contents($templatePath) ?: '';
$phpContent = $this->compile($actualClass, $templateContent, $variables, $components);
$phpContent = $this->compile($actualClass, $templateContent, $variables, $components, $context);
$templateDir = pathinfo($templatePath, PATHINFO_DIRNAME);
$templateFileName = pathinfo($templatePath, PATHINFO_BASENAME);
$contextHash = md5(
$actualClass .
json_encode($variables) .
json_encode($components)
json_encode($components) .
$context
);

$replacedPath = getcwd() ?: '';
Expand Down

0 comments on commit a74cc76

Please sign in to comment.