Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show errors when template resolving fails #81

Merged
merged 1 commit into from
Dec 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
67 changes: 60 additions & 7 deletions src/Collector/Finder/TemplatePathFinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Efabrica\PHPStanLatte\Collector\ValueObject\CollectedTemplatePath;
use Nette\Utils\Finder;
use Nette\Utils\Strings;
use PHPStan\BetterReflection\BetterReflection;
use PHPStan\BetterReflection\Reflection\ReflectionMethod;
use PHPStan\Node\CollectedDataNode;

Expand All @@ -17,12 +18,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 @@ -31,7 +36,7 @@ public function __construct(CollectedDataNode $collectedDataNode)
$this->collectedTemplatePaths[$className][$methodName] = [];
}
$templatePath = $collectedTemplatePath->getTemplatePath();
if (strpos($templatePath, '*') !== false) {
if ($templatePath !== null && strpos($templatePath, '*') !== false) {
$dirWithoutWildcards = (string)Strings::before((string)Strings::before($templatePath, '*'), '/', -1);
$pattern = substr($templatePath, strlen($dirWithoutWildcards) + 1);
/** @var string $file */
Expand All @@ -45,21 +50,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
5 changes: 2 additions & 3 deletions src/Collector/TemplatePathCollector.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,8 @@ public function processNode(Node $node, Scope $scope): ?array
return null;
}

$path = $this->pathResolver->resolve($arg->value, $scope->getFile());
if ($path === null || $path[0] !== '/') {
return null;
$path = $this->pathResolver->resolve($arg->value, $scope->getFile()); if ($path === 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
2 changes: 1 addition & 1 deletion src/Compiler/Compiler/CompilerInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ interface CompilerInterface
* @param string $templateContent latte content
* @return string php content
*/
public function compile(string $templateContent, ?string $actualClass): string;
public function compile(string $templateContent, ?string $actualClass, string $context = ''): string;

/**
* @return array<string, string|array{string, string}>
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