From f736fcec12598d7dc1b68163266a4f98cf8c47dc Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 28 Apr 2022 18:55:44 +0200 Subject: [PATCH 1/4] phpstan.neon skips TagParser.php WIP --- phpstan.neon | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/phpstan.neon b/phpstan.neon index 7013f6993..5db9012da 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,4 +1,8 @@ parameters: + excludePaths: + analyse: + - src/Latte/Compiler/TagParser.php + level: 5 paths: From 379921eece47c9120e94f61c3476646a83eb7742 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 18 May 2022 03:06:35 +0200 Subject: [PATCH 2/4] Propagate variable types to code to allow statical analysis [WIP] Thanks to @MartinMystikJonas --- src/Latte/Compiler/Block.php | 1 + src/Latte/Compiler/PrintContext.php | 23 +++++++++ src/Latte/Compiler/TemplateGenerator.php | 33 ++++++++---- src/Latte/Compiler/VariableScope.php | 51 +++++++++++++++++++ src/Latte/Essential/Nodes/BlockNode.php | 23 ++++++--- src/Latte/Essential/Nodes/ForeachNode.php | 6 +++ .../Essential/Nodes/TemplateTypeNode.php | 28 +++++++++- src/Latte/Essential/Nodes/VarNode.php | 38 ++++++++------ src/Latte/Essential/Nodes/VarTypeNode.php | 29 +++++++++-- tests/tags/templateType.phpt | 22 +++++++- tests/tags/var.default.phpt | 2 +- tests/tags/varType.phpt | 22 ++++++-- 12 files changed, 233 insertions(+), 45 deletions(-) create mode 100644 src/Latte/Compiler/VariableScope.php diff --git a/src/Latte/Compiler/Block.php b/src/Latte/Compiler/Block.php index 6dc9a61cb..39995882d 100644 --- a/src/Latte/Compiler/Block.php +++ b/src/Latte/Compiler/Block.php @@ -26,6 +26,7 @@ final class Block /** @var ParameterNode[] */ public array $parameters = []; + public VariableScope $variables; public function __construct( diff --git a/src/Latte/Compiler/PrintContext.php b/src/Latte/Compiler/PrintContext.php index e204f72ae..e5001b762 100644 --- a/src/Latte/Compiler/PrintContext.php +++ b/src/Latte/Compiler/PrintContext.php @@ -77,10 +77,14 @@ final class PrintContext /** @var Escaper[] */ private array $escaperStack = []; + /** @var VariableScope[] */ + private array $scopeStack = []; + public function __construct(string $contentType = ContentType::Html) { $this->escaperStack[] = new Escaper($contentType); + $this->scopeStack[] = new VariableScope; } @@ -160,9 +164,28 @@ public function getEscaper(): Escaper } + public function beginVariableScope(): VariableScope + { + return $this->scopeStack[] = clone end($this->scopeStack); + } + + + public function restoreVariableScope(): void + { + array_pop($this->scopeStack); + } + + + public function getVariableScope(): VariableScope + { + return end($this->scopeStack); + } + + public function addBlock(Block $block): void { $block->escaping = $this->getEscaper()->export(); + $block->variables = clone $this->getVariableScope(); $block->method = 'block' . ucfirst(trim(preg_replace('#\W+#', '_', $block->name->print($this)), '_')); $lower = strtolower($block->method); $used = $this->blocks + ['block' => 1]; diff --git a/src/Latte/Compiler/TemplateGenerator.php b/src/Latte/Compiler/TemplateGenerator.php index 11a319fa6..4f754892c 100644 --- a/src/Latte/Compiler/TemplateGenerator.php +++ b/src/Latte/Compiler/TemplateGenerator.php @@ -40,18 +40,21 @@ public function generate( bool $strictMode = false, ): string { $context = new PrintContext($node->contentType); - $code = $node->main->print($context); - $code = self::buildParams($code, [], '$ʟ_args', $context); - $this->addMethod('main', $code, 'array $ʟ_args'); + $scope = $context->getVariableScope(); + $this->addMethod('main', ''); $head = (new NodeTraverser)->traverse($node->head, fn(Node $node) => $node instanceof Nodes\TextNode ? new Nodes\NopNode : $node); $code = $head->print($context); if ($code || $context->paramsExtraction) { $code .= 'return get_defined_vars();'; - $code = self::buildParams($code, $context->paramsExtraction, '$this->params', $context); + $code = self::buildParams($code, $context->paramsExtraction, '$this->params', $context, $scope); $this->addMethod('prepare', $code, '', 'array'); } + $code = $node->main->print($context); + $code = self::buildParams($code, [], '$ʟ_args', $context, $context->getVariableScope()); + $this->addMethod('main', $code, 'array $ʟ_args'); + if ($node->contentType !== ContentType::Html) { $this->addConstant('ContentType', $node->contentType); } @@ -100,7 +103,7 @@ private function generateBlocks(array $blocks, PrintContext $context): void : [$block->method, $block->escaping]; } - $body = $this->buildParams($block->content, $block->parameters, '$ʟ_args', $context); + $body = self::buildParams($block->content, $block->parameters, '$ʟ_args', $context, $block->variables); if (!$block->isDynamic() && str_contains($body, '$')) { $embedded = $block->tag->name === 'block' && is_int($block->layer) && $block->layer; $body = 'extract(' . ($embedded ? 'end($this->varStack)' : '$this->params') . ');' . $body; @@ -121,8 +124,16 @@ private function generateBlocks(array $blocks, PrintContext $context): void } - private function buildParams(string $body, array $params, string $cont, PrintContext $context): string - { + /** + * @param Nodes\Php\ParameterNode[] $params + */ + private static function buildParams( + string $body, + array $params, + string $cont, + PrintContext $context, + VariableScope $scope, + ): string { if (!str_contains($body, '$') && !str_contains($body, 'get_defined_vars()')) { return $body; } @@ -130,7 +141,8 @@ private function buildParams(string $body, array $params, string $cont, PrintCon $res = []; foreach ($params as $i => $param) { $res[] = $context->format( - '%node = %raw[%dump] ?? %raw[%dump] ?? %node;', + '%raw%node = %raw[%dump] ?? %raw[%dump] ?? %node;', + $param->type ? VariableScope::printComment($param->var->name, $param->type->type) . ' ' : '', $param->var, $cont, $i, @@ -143,7 +155,10 @@ private function buildParams(string $body, array $params, string $cont, PrintCon $extract = $params ? implode('', $res) . 'unset($ʟ_args);' : "extract($cont);" . (str_contains($cont, '$this') ? '' : "unset($cont);"); - return $extract . "\n\n" . $body; + + return $extract . "\n" + . $scope->extractTypes() . "\n\n" + . $body; } diff --git a/src/Latte/Compiler/VariableScope.php b/src/Latte/Compiler/VariableScope.php new file mode 100644 index 000000000..0d77ec94c --- /dev/null +++ b/src/Latte/Compiler/VariableScope.php @@ -0,0 +1,51 @@ +types[$name] = $this->printComment($name, $type); + } + + + public function addExpression(Nodes\Php\ExpressionNode $expr, ?Nodes\Php\SuperiorTypeNode $type): string + { + return $expr instanceof Nodes\Php\Expression\VariableNode && is_string($expr->name) + ? $this->addVariable($expr->name, $type?->type) + : ''; + } + + + public static function printComment(string $name, ?string $type): string + { + if (!$type) { + return ''; + } + $str = '@var ' . $type . ' $' . $name; + return '/** ' . str_replace('*/', '* /', $str) . ' */'; + } + + + public function extractTypes(): string + { + return implode('', $this->types) . "\n"; + } +} diff --git a/src/Latte/Essential/Nodes/BlockNode.php b/src/Latte/Essential/Nodes/BlockNode.php index d1a982cc3..acfea4c43 100644 --- a/src/Latte/Essential/Nodes/BlockNode.php +++ b/src/Latte/Essential/Nodes/BlockNode.php @@ -74,14 +74,20 @@ public static function create(Tag $tag, TemplateParser $parser): \Generator public function print(PrintContext $context): string { - if (!$this->block) { - return $this->printFilter($context); + $context->beginVariableScope(); + try { + if (!$this->block) { + return $this->printFilter($context); - } elseif ($this->block->isDynamic()) { - return $this->printDynamic($context); - } + } elseif ($this->block->isDynamic()) { + return $this->printDynamic($context); - return $this->printStatic($context); + } else { + return $this->printStatic($context); + } + } finally { + $context->restoreVariableScope(); + } } @@ -91,7 +97,9 @@ private function printFilter(PrintContext $context): string <<<'XX' ob_start(fn() => '') %line; try { - (function () { extract(func_get_arg(0)); + (function () { + extract(func_get_arg(0)); + %raw %node })(get_defined_vars()); } finally { @@ -101,6 +109,7 @@ private function printFilter(PrintContext $context): string XX, $this->position, + $context->getVariableScope()->extractTypes(), $this->content, $context->getEscaper()->export(), $this->modifier, diff --git a/src/Latte/Essential/Nodes/ForeachNode.php b/src/Latte/Essential/Nodes/ForeachNode.php index 338a89646..b3c3f888e 100644 --- a/src/Latte/Essential/Nodes/ForeachNode.php +++ b/src/Latte/Essential/Nodes/ForeachNode.php @@ -88,6 +88,12 @@ private static function parseArguments(TagParser $parser, self $node): void public function print(PrintContext $context): string { + $scope = $context->getVariableScope(); + if ($this->key) { + $scope->addExpression($this->key, null); + } + $scope->addExpression($this->value, null); + $content = $this->content->print($context); $iterator = $this->else || ($this->iterator ?? preg_match('#\$iterator\W|\Wget_defined_vars\W#', $content)); diff --git a/src/Latte/Essential/Nodes/TemplateTypeNode.php b/src/Latte/Essential/Nodes/TemplateTypeNode.php index aa3fb4678..03298864b 100644 --- a/src/Latte/Essential/Nodes/TemplateTypeNode.php +++ b/src/Latte/Essential/Nodes/TemplateTypeNode.php @@ -13,6 +13,7 @@ use Latte\Compiler\Nodes\StatementNode; use Latte\Compiler\PrintContext; use Latte\Compiler\Tag; +use Latte\Compiler\Token; /** @@ -20,19 +21,42 @@ */ class TemplateTypeNode extends StatementNode { + public string $class; + + public static function create(Tag $tag): static { if (!$tag->isInHead()) { throw new CompileException('{templateType} is allowed only in template header.', $tag->position); } $tag->expectArguments('class name'); - $tag->parser->parseExpression(); - return new static; + $token = $tag->parser->stream->consume(Token::Php_Identifier, Token::Php_NameQualified, Token::Php_NameFullyQualified); + if (!class_exists($token->text)) { + throw new CompileException("Class '$token->text' used in {templateType} doesn't exist.", $token->position); + } + + $node = new static; + $node->class = $token->text; + return $node; } public function print(PrintContext $context): string { + $scope = $context->getVariableScope(); + $rc = new \ReflectionClass($this->class); + foreach ($rc->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) { + $type = $this->parseAnnotation($property->getDocComment() ?: '') ?: (string) $property->getType(); + $scope->addVariable($property->getName(), $type); + } + return ''; } + + + private function parseAnnotation(string $comment): ?string + { + $comment = trim($comment, '/*'); + return preg_match('#@var ([^$]+)#', $comment, $m) ? trim($m[1]) : null; + } } diff --git a/src/Latte/Essential/Nodes/VarNode.php b/src/Latte/Essential/Nodes/VarNode.php index c3eea61f3..8b7561a37 100644 --- a/src/Latte/Essential/Nodes/VarNode.php +++ b/src/Latte/Essential/Nodes/VarNode.php @@ -13,6 +13,7 @@ use Latte\Compiler\Nodes\Php\Expression\VariableNode; use Latte\Compiler\Nodes\Php\ExpressionNode; use Latte\Compiler\Nodes\Php\Scalar\NullNode; +use Latte\Compiler\Nodes\Php\SuperiorTypeNode; use Latte\Compiler\Nodes\StatementNode; use Latte\Compiler\PrintContext; use Latte\Compiler\Tag; @@ -27,7 +28,7 @@ class VarNode extends StatementNode { public bool $default; - /** @var AssignNode[] */ + /** @var array{AssignNode, ?SuperiorTypeNode}[] */ public array $assignments = []; @@ -46,14 +47,14 @@ private static function parseAssignments(Tag $tag, bool $default): array $stream = $tag->parser->stream; $res = []; do { - $tag->parser->parseType(); + $type = $tag->parser->parseType(); $save = $stream->getIndex(); $expr = $stream->is(Token::Php_Variable) ? $tag->parser->parseExpression() : null; if ($expr instanceof VariableNode) { - $res[] = new AssignNode($expr, new NullNode); + $res[] = [new AssignNode($expr, new NullNode), $type]; } elseif ($expr instanceof AssignNode && (!$default || $expr->var instanceof VariableNode)) { - $res[] = $expr; + $res[] = [$expr, $type]; } else { $stream->seek($save); $stream->throwUnexpectedException(addendum: ' in ' . $tag->getNotation()); @@ -66,27 +67,29 @@ private static function parseAssignments(Tag $tag, bool $default): array public function print(PrintContext $context): string { - $res = []; + $scope = $context->getVariableScope(); + $res = $types = []; + if ($this->default) { - foreach ($this->assignments as $assign) { - assert($assign->var instanceof VariableNode); - if ($assign->var->name instanceof ExpressionNode) { - $var = $assign->var->name->print($context); - } else { - $var = $context->encodeString($assign->var->name); - } + foreach ($this->assignments as [$assign, $type]) { + $var = $assign->var->name instanceof ExpressionNode + ? $assign->var->name->print($context) + : $context->encodeString($assign->var->name); $res[] = $var . ' => ' . $assign->expr->print($context); + $types[] = $scope->addExpression($var, $type); } return $context->format( - 'extract([%raw], EXTR_SKIP) %line;', + 'extract([%raw], EXTR_SKIP) %line;%raw ', implode(', ', $res), $this->position, + implode('', $types), ); } - foreach ($this->assignments as $assign) { - $res[] = $assign->print($context); + foreach ($this->assignments as [$assign, $type]) { + $comment = $scope->addExpression($assign->var, $type); + $res[] = $comment . $assign->print($context); } return $context->format( @@ -99,8 +102,11 @@ public function print(PrintContext $context): string public function &getIterator(): \Generator { - foreach ($this->assignments as &$assign) { + foreach ($this->assignments as [&$assign, &$type]) { yield $assign; + if ($type) { + yield $type; + } } } } diff --git a/src/Latte/Essential/Nodes/VarTypeNode.php b/src/Latte/Essential/Nodes/VarTypeNode.php index 2585a513b..dde40d006 100644 --- a/src/Latte/Essential/Nodes/VarTypeNode.php +++ b/src/Latte/Essential/Nodes/VarTypeNode.php @@ -9,6 +9,8 @@ namespace Latte\Essential\Nodes; +use Latte\Compiler\Nodes\Php\Expression\VariableNode; +use Latte\Compiler\Nodes\Php\SuperiorTypeNode; use Latte\Compiler\Nodes\StatementNode; use Latte\Compiler\PrintContext; use Latte\Compiler\Tag; @@ -20,17 +22,36 @@ */ class VarTypeNode extends StatementNode { + public VariableNode $variable; + public SuperiorTypeNode $type; + + public static function create(Tag $tag): static { $tag->expectArguments(); - $tag->parser->parseType(); - $tag->parser->stream->consume(Token::Php_Variable); - return new static; + $type = $tag->parser->parseType(); + if (!$type) { + $tag->parser->stream->throwUnexpectedException(); + } + $token = $tag->parser->stream->consume(Token::Php_Variable); + + $node = new static; + $node->type = $type; + $node->variable = new VariableNode(substr($token->text, 1)); + return $node; } public function print(PrintContext $context): string { - return ''; + $scope = $context->getVariableScope(); + return $scope->addExpression($this->variable, $this->type) . "\n"; + } + + + public function &getIterator(): \Generator + { + yield $this->variable; + yield $this->type; } } diff --git a/tests/tags/templateType.phpt b/tests/tags/templateType.phpt index 847cb8e79..5b7789ab3 100644 --- a/tests/tags/templateType.phpt +++ b/tests/tags/templateType.phpt @@ -11,6 +11,17 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; +class TemplateClass +{ + public $noType; + public int $intType; + public int|bool $intBoolType; + /** @var array */ + public array $arrayType; + private int $private; +} + + $latte = new Latte\Engine; $latte->setLoader(new Latte\Loaders\StringLoader); @@ -20,10 +31,19 @@ Assert::exception( 'Missing class name in {templateType} (at column 1)', ); +Assert::exception( + fn() => $latte->compile('{templateType AA\BBB}'), + Latte\CompileException::class, + "Class 'AA\BBB' used in {templateType} doesn't exist (at column 15)", +); + Assert::exception( fn() => $latte->compile('{if true}{templateType stdClass}{/if}'), Latte\CompileException::class, '{templateType} is allowed only in template header (at column 10)', ); -Assert::noError(fn() => $latte->compile('{templateType stdClass}')); +Assert::contains( + '/** @var int $intType *//** @var int|bool $intBoolType *//** @var array $arrayType */', + $latte->compile('{templateType TemplateClass}'), +); diff --git a/tests/tags/var.default.phpt b/tests/tags/var.default.phpt index 627dd6392..648813f01 100644 --- a/tests/tags/var.default.phpt +++ b/tests/tags/var.default.phpt @@ -25,7 +25,7 @@ test('{var ...}', function () use ($latte) { // types Assert::contains('$temp->var1 = 123 /*', $latte->compile('{var int $temp->var1 = 123}')); Assert::contains('$temp->var1 = 123 /*', $latte->compile('{var null|int|string[] $temp->var1 = 123}')); - Assert::contains('$var1 = 123; $var2 = \'nette framework\' /*', ws($latte->compile('{var int|string[] $var1 = 123, ?class $var2 = "nette framework"}'))); + Assert::contains('/** @var int|string[] $var1 */ $var1 = 123; /** @var ?class $var2 */ $var2 = \'nette framework\' /* line 1 */;', ws($latte->compile('{var int|string[] $var1 = 123, ?class $var2 = "nette framework"}'))); Assert::contains('$var1 = 123; $var2 = 456 /*', ws($latte->compile('{var A\B $var1 = 123, ?A\B $var2 = 456}'))); Assert::contains('$var1 = 123; $var2 = 456 /*', ws($latte->compile('{var \A\B $var1 = 123, ?\A\B $var2 = 456}'))); diff --git a/tests/tags/varType.phpt b/tests/tags/varType.phpt index a3306d9c2..8c32741cc 100644 --- a/tests/tags/varType.phpt +++ b/tests/tags/varType.phpt @@ -35,13 +35,25 @@ Assert::exception( Assert::exception( fn() => $latte->compile('{varType $var type}'), Latte\CompileException::class, - "Unexpected 'type', expecting end of tag in {varType} (at column 15)", + "Unexpected '\$vartype' (at column 10)", ); -Assert::noError(fn() => $latte->compile('{varType type $var}')); +Assert::contains( + '/** @var type $var */', + $latte->compile('{varType type $var}'), +); -Assert::noError(fn() => $latte->compile('{varType ?\Nm\Class $var}')); +Assert::contains( + '/** @var ?\Nm\Class $var */', + $latte->compile('{varType ?\Nm\Class $var}'), +); -Assert::noError(fn() => $latte->compile('{varType int|null $var}')); +Assert::contains( + '/** @var int|null $var */', + $latte->compile('{varType int|null $var}'), +); -Assert::noError(fn() => $latte->compile('{varType array{0: int, 1: int} $var}')); +Assert::contains( + '/** @var array{0:int,1:int} $var */', + $latte->compile('{varType array{0: int, 1: int} $var}'), +); From 46fb48ebe29165e756156d0e6c2820f660105806 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 18 May 2022 18:26:23 +0200 Subject: [PATCH 3/4] Add filter parameter types to code to allow statical analysis [WIP] --- src/Latte/Compiler/TemplateGenerator.php | 45 ++++++++++++++++++++++-- src/Latte/Engine.php | 1 + src/Latte/Essential/Blueprint.php | 4 +-- 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/src/Latte/Compiler/TemplateGenerator.php b/src/Latte/Compiler/TemplateGenerator.php index 4f754892c..c522437b3 100644 --- a/src/Latte/Compiler/TemplateGenerator.php +++ b/src/Latte/Compiler/TemplateGenerator.php @@ -11,6 +11,8 @@ use Latte; use Latte\ContentType; +use Latte\Essential\Blueprint; +use Nette\PhpGenerator as Php; /** @@ -38,6 +40,7 @@ public function generate( string $className, ?string $comment = null, bool $strictMode = false, + array $filters = [], ): string { $context = new PrintContext($node->contentType); $scope = $context->getVariableScope(); @@ -78,13 +81,18 @@ public function generate( . ($method['body'] ? "\t\t$method[body]\n" : '') . "\t}"; } + $comment .= "\n@property Filters$className \$filters"; + $comment = str_replace('*/', '* /', $comment); + $comment = str_replace("\n", "\n * ", "/**\n" . trim($comment)) . "\n */\n"; + $code = "generateStub($node, 'Filters' . $className, $filters); $code = PhpHelpers::optimizeEcho($code); $code = PhpHelpers::reformatCode($code); @@ -124,6 +132,39 @@ private function generateBlocks(array $blocks, PrintContext $context): void } + private function generateStub(Node $node, string $className, $filters): string + { + if (!class_exists(Php\ClassType::class)) { + return ''; + } + + $used = []; + (new NodeTraverser)->traverse($node, function (Node $node) use (&$used) { + if ($node instanceof Nodes\Php\FilterNode) { + $used[$node->name->name] = true; + } + }); + + $class = new Php\ClassType($className); + $filters = array_intersect_key($filters, $used); + foreach ($filters as $name => $callback) { + $func = (new Php\Factory)->fromCallable($callback); + $type = Blueprint::printType($func->getReturnType(), $func->isReturnNullable(), null) ?: 'mixed'; + $params = []; + $list = $func->getParameters(); + foreach ($list as $param) { + $variadic = $func->isVariadic() && $param === end($list); + $params[] = (Blueprint::printType($param->getType(), $param->isNullable(), null) ?: 'mixed') + . ($variadic ? '...' : ''); + } + + $class->addComment('@property callable(' . implode(', ', $params) . "): $type \$$name"); + } + + return (string) $class; + } + + /** * @param Nodes\Php\ParameterNode[] $params */ diff --git a/src/Latte/Engine.php b/src/Latte/Engine.php index af8621efd..f04b43132 100644 --- a/src/Latte/Engine.php +++ b/src/Latte/Engine.php @@ -191,6 +191,7 @@ public function generate(TemplateNode $node, string $name): string $this->getTemplateClass($name), $comment, $this->strictTypes, + $this->getFilters(), ); } diff --git a/src/Latte/Essential/Blueprint.php b/src/Latte/Essential/Blueprint.php index 51622e7a0..19914417b 100644 --- a/src/Latte/Essential/Blueprint.php +++ b/src/Latte/Essential/Blueprint.php @@ -95,7 +95,7 @@ public function addFunctions(Php\ClassType $class, array $funcs): void } - private function printType(?string $type, bool $nullable, ?Php\PhpNamespace $namespace): string + public static function printType(?string $type, bool $nullable, ?Php\PhpNamespace $namespace): string { if ($type === null) { return ''; @@ -123,7 +123,7 @@ public function printParameters( $list = $function->getParameters(); foreach ($list as $param) { $variadic = $function->isVariadic() && $param === end($list); - $params[] = ltrim($this->printType($param->getType(), $param->isNullable(), $namespace) . ' ') + $params[] = ltrim(self::printType($param->getType(), $param->isNullable(), $namespace) . ' ') . ($param->isReference() ? '&' : '') . ($variadic ? '...' : '') . '$' . $param->getName() From fd8c39b79ce9bd0c9a70cea454dc224dbdddefc5 Mon Sep 17 00:00:00 2001 From: Martin Jonas Date: Mon, 11 Jul 2022 19:05:40 +0200 Subject: [PATCH 4/4] Propagate variable types to generated code to allow statical analysis --- src/Latte/Compiler/TemplateGenerator.php | 20 ++++++--- src/Latte/Compiler/VariableScope.php | 2 +- src/Latte/Essential/Nodes/VarNode.php | 3 +- src/Latte/Essential/Nodes/VarTypeNode.php | 13 +++++- .../expected/Compiler.unquoted.attrs.phtml | 1 + tests/common/expected/contentType.ical.phtml | 1 + tests/common/expected/contentType.xml.phtml | 1 + tests/filters/expected/general.phtml | 1 + tests/tags/define.args.phpt | 23 ++++++++++ tests/tags/expected/block.dynamic.phtml | 1 + tests/tags/expected/block.local.dynamic.phtml | 1 + tests/tags/expected/define.args1.phtml | 1 + tests/tags/expected/define.args2.phtml | 1 + tests/tags/expected/define.args3.phtml | 1 + tests/tags/expected/define.args4.phtml | 1 + tests/tags/expected/define.args5.phtml | 1 + tests/tags/expected/define.args6.html | 7 +++ tests/tags/expected/define.args6.phtml | 45 +++++++++++++++++++ tests/tags/expected/define.phtml | 1 + tests/tags/expected/define.typehints.phtml | 1 + tests/tags/expected/embed.block.phtml | 1 + tests/tags/expected/embed.file.phtml | 1 + tests/tags/expected/extends.1.phtml | 1 + tests/tags/expected/extends.4.phtml | 1 + .../tags/expected/general.n-attributes.phtml | 1 + tests/tags/expected/general.phtml | 6 ++- tests/tags/expected/import.phtml | 5 +++ tests/tags/expected/include.block.from.phtml | 1 + tests/tags/expected/include.inc1.phtml | 1 + tests/tags/expected/include.inc2.phtml | 1 + tests/tags/expected/include.inc3.phtml | 1 + tests/tags/expected/include.phtml | 1 + .../expected/include.with-blocks.inc.phtml | 1 + tests/tags/expected/include.with-blocks.phtml | 1 + .../tags/expected/inheritance.1.parent.phtml | 1 + tests/tags/expected/inheritance.1.phtml | 1 + tests/tags/expected/inheritance.2.phtml | 1 + tests/tags/expected/parameters.inc1.phtml | 9 ++++ tests/tags/expected/parameters.inc2.phtml | 16 +++++++ tests/tags/expected/parameters.inc3.phtml | 16 +++++++ tests/tags/expected/parameters.inc4.phtml | 17 +++++++ tests/tags/expected/parameters.inc5.phtml | 16 +++++++ tests/tags/expected/parameters.inc6.phtml | 16 +++++++ tests/tags/expected/parameters.inc7.phtml | 27 +++++++++++ tests/tags/expected/parameters.inc8.phtml | 27 +++++++++++ tests/tags/expected/templateType.phtml | 19 ++++++++ tests/tags/expected/varType.phtml | 20 +++++++++ tests/tags/parameters.phpt | 17 +++++++ tests/tags/templateType.phpt | 13 ++++-- tests/tags/var.default.nodes.phpt | 2 + tests/tags/var.default.phpt | 6 +-- tests/tags/varType.nodes.phpt | 4 ++ tests/tags/varType.phpt | 24 ++++++++++ 53 files changed, 385 insertions(+), 16 deletions(-) create mode 100644 tests/tags/expected/define.args6.html create mode 100644 tests/tags/expected/define.args6.phtml create mode 100644 tests/tags/expected/parameters.inc1.phtml create mode 100644 tests/tags/expected/parameters.inc2.phtml create mode 100644 tests/tags/expected/parameters.inc3.phtml create mode 100644 tests/tags/expected/parameters.inc4.phtml create mode 100644 tests/tags/expected/parameters.inc5.phtml create mode 100644 tests/tags/expected/parameters.inc6.phtml create mode 100644 tests/tags/expected/parameters.inc7.phtml create mode 100644 tests/tags/expected/parameters.inc8.phtml create mode 100644 tests/tags/expected/templateType.phtml create mode 100644 tests/tags/expected/varType.phtml diff --git a/src/Latte/Compiler/TemplateGenerator.php b/src/Latte/Compiler/TemplateGenerator.php index c522437b3..ae5e29b77 100644 --- a/src/Latte/Compiler/TemplateGenerator.php +++ b/src/Latte/Compiler/TemplateGenerator.php @@ -114,7 +114,18 @@ private function generateBlocks(array $blocks, PrintContext $context): void $body = self::buildParams($block->content, $block->parameters, '$ʟ_args', $context, $block->variables); if (!$block->isDynamic() && str_contains($body, '$')) { $embedded = $block->tag->name === 'block' && is_int($block->layer) && $block->layer; - $body = 'extract(' . ($embedded ? 'end($this->varStack)' : '$this->params') . ');' . $body; + if($context->paramsExtraction) { + $paramTypes = ''; + foreach ($context->paramsExtraction as $param) { + $paramTypes .= $param->type ? + VariableScope::printComment($param->var->name, $param->type->type) . "\n" : + ''; + } + $body = 'extract($this->prepare());' . "\n" . $paramTypes . $body; + } else { + $body = 'extract(' . ($embedded ? 'end($this->varStack)' : '$this->params') . ');' . $body; + } + } $this->addMethod( @@ -193,11 +204,10 @@ private static function buildParams( ); } - $extract = $params - ? implode('', $res) . 'unset($ʟ_args);' - : "extract($cont);" . (str_contains($cont, '$this') ? '' : "unset($cont);"); + $extract = $params ? implode('', $res) : "extract($cont);"; + $extract .= (str_contains($cont, '$this') ? '' : "unset($cont);"); - return $extract . "\n" + return $extract . $scope->extractTypes() . "\n\n" . $body; } diff --git a/src/Latte/Compiler/VariableScope.php b/src/Latte/Compiler/VariableScope.php index 0d77ec94c..a6c1fd84c 100644 --- a/src/Latte/Compiler/VariableScope.php +++ b/src/Latte/Compiler/VariableScope.php @@ -46,6 +46,6 @@ public static function printComment(string $name, ?string $type): string public function extractTypes(): string { - return implode('', $this->types) . "\n"; + return implode("\n", array_filter($this->types)); } } diff --git a/src/Latte/Essential/Nodes/VarNode.php b/src/Latte/Essential/Nodes/VarNode.php index 8b7561a37..3d7513002 100644 --- a/src/Latte/Essential/Nodes/VarNode.php +++ b/src/Latte/Essential/Nodes/VarNode.php @@ -18,6 +18,7 @@ use Latte\Compiler\PrintContext; use Latte\Compiler\Tag; use Latte\Compiler\Token; +use Latte\Compiler\VariableScope; /** @@ -76,7 +77,7 @@ public function print(PrintContext $context): string ? $assign->var->name->print($context) : $context->encodeString($assign->var->name); $res[] = $var . ' => ' . $assign->expr->print($context); - $types[] = $scope->addExpression($var, $type); + $types[] = VariableScope::printComment($var, $type?->type); } return $context->format( diff --git a/src/Latte/Essential/Nodes/VarTypeNode.php b/src/Latte/Essential/Nodes/VarTypeNode.php index dde40d006..839c75139 100644 --- a/src/Latte/Essential/Nodes/VarTypeNode.php +++ b/src/Latte/Essential/Nodes/VarTypeNode.php @@ -15,6 +15,7 @@ use Latte\Compiler\PrintContext; use Latte\Compiler\Tag; use Latte\Compiler\Token; +use Latte\Compiler\VariableScope; /** @@ -24,6 +25,7 @@ class VarTypeNode extends StatementNode { public VariableNode $variable; public SuperiorTypeNode $type; + public bool $isParameterType = false; public static function create(Tag $tag): static @@ -38,14 +40,21 @@ public static function create(Tag $tag): static $node = new static; $node->type = $type; $node->variable = new VariableNode(substr($token->text, 1)); + $node->isParameterType = $tag->isInHead(); return $node; } public function print(PrintContext $context): string { - $scope = $context->getVariableScope(); - return $scope->addExpression($this->variable, $this->type) . "\n"; + if ($this->isParameterType) { + $scope = $context->getVariableScope(); + return $scope->addExpression($this->variable, $this->type) . "\n"; + } elseif (is_string($this->variable->name)) { + return VariableScope::printComment($this->variable->name, $this->type?->type) . "\n"; + } else { + return ''; + } } diff --git a/tests/common/expected/Compiler.unquoted.attrs.phtml b/tests/common/expected/Compiler.unquoted.attrs.phtml index 7896aa032..e0f285d76 100644 --- a/tests/common/expected/Compiler.unquoted.attrs.phtml +++ b/tests/common/expected/Compiler.unquoted.attrs.phtml @@ -40,3 +40,4 @@ final class Template%a% extends Latte\Runtime\Template '; } } +%A% \ No newline at end of file diff --git a/tests/common/expected/contentType.ical.phtml b/tests/common/expected/contentType.ical.phtml index 47fc9154d..b45c44ddc 100644 --- a/tests/common/expected/contentType.ical.phtml +++ b/tests/common/expected/contentType.ical.phtml @@ -49,3 +49,4 @@ World' /* line %d% */; return get_defined_vars(); } } +%A% \ No newline at end of file diff --git a/tests/common/expected/contentType.xml.phtml b/tests/common/expected/contentType.xml.phtml index 656b9de6b..498ec5293 100644 --- a/tests/common/expected/contentType.xml.phtml +++ b/tests/common/expected/contentType.xml.phtml @@ -147,3 +147,4 @@ var html = '; return get_defined_vars(); } } +%A% \ No newline at end of file diff --git a/tests/filters/expected/general.phtml b/tests/filters/expected/general.phtml index 9c18078b9..23a7c060f 100644 --- a/tests/filters/expected/general.phtml +++ b/tests/filters/expected/general.phtml @@ -95,3 +95,4 @@ bar')) /* line %d% */; echo "\n"; } } +%A% \ No newline at end of file diff --git a/tests/tags/define.args.phpt b/tests/tags/define.args.phpt index 47ce35e72..eaaafa34b 100644 --- a/tests/tags/define.args.phpt +++ b/tests/tags/define.args.phpt @@ -160,3 +160,26 @@ Assert::matchFile( __DIR__ . '/expected/define.args5.html', $latte->renderToString($template), ); + +// types +$latte->setLoader(new Latte\Loaders\StringLoader); +$template = <<<'XX' +default values + +{define test $var1 = 0, array $var2 = [1, 2, 3], int $var3 = 10} + Variables {$var1}, {$var2|implode}, {$var3} +{/define} + +a) {include test, 1} + +b) {include test, var1 => 1} +XX; + +Assert::matchFile( + __DIR__ . '/expected/define.args6.phtml', + $latte->compile($template) +); +Assert::matchFile( + __DIR__ . '/expected/define.args6.html', + $latte->renderToString($template) +); diff --git a/tests/tags/expected/block.dynamic.phtml b/tests/tags/expected/block.dynamic.phtml index 80ead1d29..6cf2e82a9 100644 --- a/tests/tags/expected/block.dynamic.phtml +++ b/tests/tags/expected/block.dynamic.phtml @@ -109,3 +109,4 @@ final class Template%a% extends Latte\Runtime\Template echo ' expression '; } } +%A% \ No newline at end of file diff --git a/tests/tags/expected/block.local.dynamic.phtml b/tests/tags/expected/block.local.dynamic.phtml index 19c44503b..940c90c45 100644 --- a/tests/tags/expected/block.local.dynamic.phtml +++ b/tests/tags/expected/block.local.dynamic.phtml @@ -98,3 +98,4 @@ final class Template%a% extends Latte\Runtime\Template echo 'hello'; } } +%A% \ No newline at end of file diff --git a/tests/tags/expected/define.args1.phtml b/tests/tags/expected/define.args1.phtml index 669589658..4e62bdad5 100644 --- a/tests/tags/expected/define.args1.phtml +++ b/tests/tags/expected/define.args1.phtml @@ -62,3 +62,4 @@ d) '; $this->renderBlock('test', ['hello'] + [], 'html') /* line %d% */; } } +%A% \ No newline at end of file diff --git a/tests/tags/expected/define.args2.phtml b/tests/tags/expected/define.args2.phtml index e623f7f98..64778be3c 100644 --- a/tests/tags/expected/define.args2.phtml +++ b/tests/tags/expected/define.args2.phtml @@ -51,3 +51,4 @@ d) '; echo "\n"; } } +%A% \ No newline at end of file diff --git a/tests/tags/expected/define.args3.phtml b/tests/tags/expected/define.args3.phtml index 7a91196fe..a2b0882da 100644 --- a/tests/tags/expected/define.args3.phtml +++ b/tests/tags/expected/define.args3.phtml @@ -48,3 +48,4 @@ c) '; echo "\n"; } } +%A% \ No newline at end of file diff --git a/tests/tags/expected/define.args4.phtml b/tests/tags/expected/define.args4.phtml index ab1fc96a7..02e4d2ae5 100644 --- a/tests/tags/expected/define.args4.phtml +++ b/tests/tags/expected/define.args4.phtml @@ -26,3 +26,4 @@ c) '; $this->renderBlock('test', ['hello' => 1] + [], 'html') /* line %d% */; } } +%A% diff --git a/tests/tags/expected/define.args5.phtml b/tests/tags/expected/define.args5.phtml index abeded288..6177bb489 100644 --- a/tests/tags/expected/define.args5.phtml +++ b/tests/tags/expected/define.args5.phtml @@ -42,3 +42,4 @@ b) '; echo "\n"; } } +%A% \ No newline at end of file diff --git a/tests/tags/expected/define.args6.html b/tests/tags/expected/define.args6.html new file mode 100644 index 000000000..128d3f783 --- /dev/null +++ b/tests/tags/expected/define.args6.html @@ -0,0 +1,7 @@ +default values + + +a) Variables 1, 123, 10 + + +b) Variables 1, 123, 10 diff --git a/tests/tags/expected/define.args6.phtml b/tests/tags/expected/define.args6.phtml new file mode 100644 index 000000000..f96c1c593 --- /dev/null +++ b/tests/tags/expected/define.args6.phtml @@ -0,0 +1,45 @@ + 'blockTest'], + ]; + + + public function main(array $ʟ_args): void + { + extract($ʟ_args); + unset($ʟ_args); + + echo 'default values + + +a) '; + $this->renderBlock('test', [1] + [], 'html') /* line %d% */; + echo ' + +b) '; + $this->renderBlock('test', ['var1' => 1] + [], 'html') /* line %d% */; + } + + + /** {define test $var1 = 0, array $var2 = [1, 2, 3], int $var3 = 10} on line %d% */ + public function blockTest(array $ʟ_args): void + { + extract($this->params); + $var1 = $ʟ_args[0] ?? $ʟ_args['var1'] ?? 0; + /** @var array $var2 */ $var2 = $ʟ_args[1] ?? $ʟ_args['var2'] ?? [1, 2, 3]; + /** @var int $var3 */ $var3 = $ʟ_args[2] ?? $ʟ_args['var3'] ?? 10; + unset($ʟ_args); + + echo ' Variables '; + echo LR\Filters::escapeHtmlText($var1) /* line %d% */; + echo ', '; + echo LR\Filters::escapeHtmlText(($this->filters->implode)($var2)) /* line %d% */; + echo ', '; + echo LR\Filters::escapeHtmlText($var3) /* line %d% */; + echo "\n"; + } +} +%A% \ No newline at end of file diff --git a/tests/tags/expected/define.phtml b/tests/tags/expected/define.phtml index 51d63a484..050802021 100644 --- a/tests/tags/expected/define.phtml +++ b/tests/tags/expected/define.phtml @@ -51,3 +51,4 @@ final class Template%a% extends Latte\Runtime\Template echo 'true'; } } +%A% \ No newline at end of file diff --git a/tests/tags/expected/define.typehints.phtml b/tests/tags/expected/define.typehints.phtml index 49c699d6f..ed93986ac 100644 --- a/tests/tags/expected/define.typehints.phtml +++ b/tests/tags/expected/define.typehints.phtml @@ -22,3 +22,4 @@ final class Template%a% extends Latte\Runtime\Template { } } +%A% \ No newline at end of file diff --git a/tests/tags/expected/embed.block.phtml b/tests/tags/expected/embed.block.phtml index 974eaee47..bbb491ff4 100644 --- a/tests/tags/expected/embed.block.phtml +++ b/tests/tags/expected/embed.block.phtml @@ -72,3 +72,4 @@ final class Template%a% extends Latte\Runtime\Template echo 'embed1-A'; } } +%A% \ No newline at end of file diff --git a/tests/tags/expected/embed.file.phtml b/tests/tags/expected/embed.file.phtml index d2c655a8e..2ce330386 100644 --- a/tests/tags/expected/embed.file.phtml +++ b/tests/tags/expected/embed.file.phtml @@ -46,3 +46,4 @@ final class Template%a% extends Latte\Runtime\Template echo 'nested embeds A'; } } +%A% \ No newline at end of file diff --git a/tests/tags/expected/extends.1.phtml b/tests/tags/expected/extends.1.phtml index 1c29e833d..1677c9454 100644 --- a/tests/tags/expected/extends.1.phtml +++ b/tests/tags/expected/extends.1.phtml @@ -19,3 +19,4 @@ final class Template%a% extends Latte\Runtime\Template return get_defined_vars(); } } +%A% \ No newline at end of file diff --git a/tests/tags/expected/extends.4.phtml b/tests/tags/expected/extends.4.phtml index 6e7e07890..0df86d5f4 100644 --- a/tests/tags/expected/extends.4.phtml +++ b/tests/tags/expected/extends.4.phtml @@ -30,3 +30,4 @@ final class Template%a% extends Latte\Runtime\Template '; } } +%A% \ No newline at end of file diff --git a/tests/tags/expected/general.n-attributes.phtml b/tests/tags/expected/general.n-attributes.phtml index b0484ae2e..d63e3a59e 100644 --- a/tests/tags/expected/general.n-attributes.phtml +++ b/tests/tags/expected/general.n-attributes.phtml @@ -497,3 +497,4 @@ final class Template%a% extends Latte\Runtime\Template '; } } +%A% \ No newline at end of file diff --git a/tests/tags/expected/general.phtml b/tests/tags/expected/general.phtml index 0ba353307..df0b87e77 100644 --- a/tests/tags/expected/general.phtml +++ b/tests/tags/expected/general.phtml @@ -2,7 +2,10 @@ use Latte\Runtime as LR; -/** source: %A% */ +/** +* source: %a% +* @property FiltersTemplate%a% +*/ final class Template%a% extends Latte\Runtime\Template { public const Blocks = [ @@ -108,3 +111,4 @@ final class Template%a% extends Latte\Runtime\Template '; } } +%A% \ No newline at end of file diff --git a/tests/tags/expected/import.phtml b/tests/tags/expected/import.phtml index efbfc743c..5dd111702 100644 --- a/tests/tags/expected/import.phtml +++ b/tests/tags/expected/import.phtml @@ -18,3 +18,8 @@ final class Template%a% extends Latte\Runtime\Template return get_defined_vars(); } } + + +class FiltersTemplate%a% +{ +} diff --git a/tests/tags/expected/include.block.from.phtml b/tests/tags/expected/include.block.from.phtml index a335ee03e..043fab4d4 100644 --- a/tests/tags/expected/include.block.from.phtml +++ b/tests/tags/expected/include.block.from.phtml @@ -8,3 +8,4 @@ final class Template%a% extends Latte\Runtime\Template echo ' after'; %A% } +%A% \ No newline at end of file diff --git a/tests/tags/expected/include.inc1.phtml b/tests/tags/expected/include.inc1.phtml index 68d3d47b6..118de1249 100644 --- a/tests/tags/expected/include.inc1.phtml +++ b/tests/tags/expected/include.inc1.phtml @@ -24,3 +24,4 @@ Parent: '; echo "\n"; } } +%A% \ No newline at end of file diff --git a/tests/tags/expected/include.inc2.phtml b/tests/tags/expected/include.inc2.phtml index 5810d2df6..18dcffbd2 100644 --- a/tests/tags/expected/include.inc2.phtml +++ b/tests/tags/expected/include.inc2.phtml @@ -19,3 +19,4 @@ Parent: '; echo "\n"; } } +%A% \ No newline at end of file diff --git a/tests/tags/expected/include.inc3.phtml b/tests/tags/expected/include.inc3.phtml index 24290bf94..7ac596aa6 100644 --- a/tests/tags/expected/include.inc3.phtml +++ b/tests/tags/expected/include.inc3.phtml @@ -14,3 +14,4 @@ final class Template%a% extends Latte\Runtime\Template '; } } +%A% \ No newline at end of file diff --git a/tests/tags/expected/include.phtml b/tests/tags/expected/include.phtml index a71b9f829..083614c90 100644 --- a/tests/tags/expected/include.phtml +++ b/tests/tags/expected/include.phtml @@ -12,3 +12,4 @@ final class Template%a% extends Latte\Runtime\Template }) /* line %d% */; } } +%A% \ No newline at end of file diff --git a/tests/tags/expected/include.with-blocks.inc.phtml b/tests/tags/expected/include.with-blocks.inc.phtml index c8e566e3a..a578ba461 100644 --- a/tests/tags/expected/include.with-blocks.inc.phtml +++ b/tests/tags/expected/include.with-blocks.inc.phtml @@ -27,3 +27,4 @@ final class Template%a% extends Latte\Runtime\Template echo "\n"; } } +%A% \ No newline at end of file diff --git a/tests/tags/expected/include.with-blocks.phtml b/tests/tags/expected/include.with-blocks.phtml index f10a80e17..a06e8f645 100644 --- a/tests/tags/expected/include.with-blocks.phtml +++ b/tests/tags/expected/include.with-blocks.phtml @@ -11,3 +11,4 @@ final class Template%a% extends Latte\Runtime\Template $this->renderBlock('test', [], 'html') /* line %d% */; } } +%A% \ No newline at end of file diff --git a/tests/tags/expected/inheritance.1.parent.phtml b/tests/tags/expected/inheritance.1.parent.phtml index 4478de062..58b23e3d6 100644 --- a/tests/tags/expected/inheritance.1.parent.phtml +++ b/tests/tags/expected/inheritance.1.parent.phtml @@ -72,3 +72,4 @@ Parent: '; '; } } +%A% \ No newline at end of file diff --git a/tests/tags/expected/inheritance.1.phtml b/tests/tags/expected/inheritance.1.phtml index 3d113a0b7..e791014a8 100644 --- a/tests/tags/expected/inheritance.1.phtml +++ b/tests/tags/expected/inheritance.1.phtml @@ -71,3 +71,4 @@ final class Template%a% extends Latte\Runtime\Template echo "\n"; } } +%A% \ No newline at end of file diff --git a/tests/tags/expected/inheritance.2.phtml b/tests/tags/expected/inheritance.2.phtml index 2da627331..85fc8ae5b 100644 --- a/tests/tags/expected/inheritance.2.phtml +++ b/tests/tags/expected/inheritance.2.phtml @@ -69,3 +69,4 @@ final class Template%a% extends Latte\Runtime\Template { } } +%A% \ No newline at end of file diff --git a/tests/tags/expected/parameters.inc1.phtml b/tests/tags/expected/parameters.inc1.phtml new file mode 100644 index 000000000..c7deafae6 --- /dev/null +++ b/tests/tags/expected/parameters.inc1.phtml @@ -0,0 +1,9 @@ +%A% + public function main(array $ʟ_args): void + { + extract($ʟ_args); + unset($ʟ_args); + +%A% + } +%A% \ No newline at end of file diff --git a/tests/tags/expected/parameters.inc2.phtml b/tests/tags/expected/parameters.inc2.phtml new file mode 100644 index 000000000..2ade69aeb --- /dev/null +++ b/tests/tags/expected/parameters.inc2.phtml @@ -0,0 +1,16 @@ +%A% + public function main(array $ʟ_args): void + { + extract($ʟ_args); + unset($ʟ_args); + +%A% + } +%A% + public function prepare(): array + { + $a = $this->params[0] ?? $this->params['a'] ?? null; + + return get_defined_vars(); + } +%A% \ No newline at end of file diff --git a/tests/tags/expected/parameters.inc3.phtml b/tests/tags/expected/parameters.inc3.phtml new file mode 100644 index 000000000..ad93a72aa --- /dev/null +++ b/tests/tags/expected/parameters.inc3.phtml @@ -0,0 +1,16 @@ +%A% + public function main(array $ʟ_args): void + { + extract($ʟ_args); + unset($ʟ_args); + +%A% + } +%A% + public function prepare(): array + { + /** @var int $a */ $a = $this->params[0] ?? $this->params['a'] ?? 5; + + return get_defined_vars(); + } +%A% \ No newline at end of file diff --git a/tests/tags/expected/parameters.inc4.phtml b/tests/tags/expected/parameters.inc4.phtml new file mode 100644 index 000000000..11ab9dc95 --- /dev/null +++ b/tests/tags/expected/parameters.inc4.phtml @@ -0,0 +1,17 @@ +%A% + public function main(array $ʟ_args): void + { + extract($ʟ_args); + unset($ʟ_args); + +%A% + } +%A% + public function prepare(): array + { + $a = $this->params[0] ?? $this->params['a'] ?? null; + /** @var int $b */ $b = $this->params[1] ?? $this->params['b'] ?? 5; + + return get_defined_vars(); + } +%A% \ No newline at end of file diff --git a/tests/tags/expected/parameters.inc5.phtml b/tests/tags/expected/parameters.inc5.phtml new file mode 100644 index 000000000..ad60d8440 --- /dev/null +++ b/tests/tags/expected/parameters.inc5.phtml @@ -0,0 +1,16 @@ +%A% + public function main(array $ʟ_args): void + { + extract($ʟ_args); + unset($ʟ_args); + +%A% + } +%A% + public function prepare(): array + { + $glob = $this->params[0] ?? $this->params['glob'] ?? null; + + return get_defined_vars(); + } +%A% \ No newline at end of file diff --git a/tests/tags/expected/parameters.inc6.phtml b/tests/tags/expected/parameters.inc6.phtml new file mode 100644 index 000000000..7ca2227b9 --- /dev/null +++ b/tests/tags/expected/parameters.inc6.phtml @@ -0,0 +1,16 @@ +%A% + public function main(array $ʟ_args): void + { + extract($ʟ_args); + unset($ʟ_args); + +%A% + } +%A% + public function prepare(): array + { + /** @var ?\Exception $glob */ $glob = $this->params[0] ?? $this->params['glob'] ?? null; + + return get_defined_vars(); + } +%A% \ No newline at end of file diff --git a/tests/tags/expected/parameters.inc7.phtml b/tests/tags/expected/parameters.inc7.phtml new file mode 100644 index 000000000..9c94579a8 --- /dev/null +++ b/tests/tags/expected/parameters.inc7.phtml @@ -0,0 +1,27 @@ +%A% + public function main(array $ʟ_args): void + { + extract($ʟ_args); + unset($ʟ_args); + +%A% + } +%A% + public function prepare(): array + { + $a = $this->params[0] ?? $this->params['a'] ?? null; + /** @var int $b */ $b = $this->params[1] ?? $this->params['b'] ?? 5; + + return get_defined_vars(); + } +%A% + public function blockX(array $ʟ_args): void + { + extract($this->prepare()); + /** @var int $b */ + extract($ʟ_args); + unset($ʟ_args); + +%A% + } +%A% \ No newline at end of file diff --git a/tests/tags/expected/parameters.inc8.phtml b/tests/tags/expected/parameters.inc8.phtml new file mode 100644 index 000000000..9c94579a8 --- /dev/null +++ b/tests/tags/expected/parameters.inc8.phtml @@ -0,0 +1,27 @@ +%A% + public function main(array $ʟ_args): void + { + extract($ʟ_args); + unset($ʟ_args); + +%A% + } +%A% + public function prepare(): array + { + $a = $this->params[0] ?? $this->params['a'] ?? null; + /** @var int $b */ $b = $this->params[1] ?? $this->params['b'] ?? 5; + + return get_defined_vars(); + } +%A% + public function blockX(array $ʟ_args): void + { + extract($this->prepare()); + /** @var int $b */ + extract($ʟ_args); + unset($ʟ_args); + +%A% + } +%A% \ No newline at end of file diff --git a/tests/tags/expected/templateType.phtml b/tests/tags/expected/templateType.phtml new file mode 100644 index 000000000..303e4bbf4 --- /dev/null +++ b/tests/tags/expected/templateType.phtml @@ -0,0 +1,19 @@ + $arrayType */ +%A% + public function blockTest(array $ʟ_args): void + { + extract($this->params); + extract($ʟ_args); + unset($ʟ_args); + /** @var int $intType */ + /** @var int|bool $intBoolType */ + /** @var array $arrayType */ +%A% \ No newline at end of file diff --git a/tests/tags/expected/varType.phtml b/tests/tags/expected/varType.phtml new file mode 100644 index 000000000..38e95e03d --- /dev/null +++ b/tests/tags/expected/varType.phtml @@ -0,0 +1,20 @@ +params); + extract($ʟ_args); + unset($ʟ_args); + /** @var string $a */ +%A?% + /** @var int $b */ + $b = 5%a%; +%A% +%A% \ No newline at end of file diff --git a/tests/tags/parameters.phpt b/tests/tags/parameters.phpt index e193eebec..44786ba95 100644 --- a/tests/tags/parameters.phpt +++ b/tests/tags/parameters.phpt @@ -18,17 +18,34 @@ $latte->setLoader(new Latte\Loaders\StringLoader([ 'main3' => '{include inc3.latte, a: 10}', 'main4' => '{include inc4.latte, a: 10}', 'main5' => '{include inc5.latte, a: 10}', + 'main6' => '{include inc6.latte, a: 10}', + 'main7' => '{include inc7.latte, a: 10}', + 'main8' => '{include inc8.latte, a: 10}', 'inc1.latte' => '{$a ?? "-"} {$b ?? "-"} {$glob ?? "-"}', 'inc2.latte' => '{parameters $a} {$a ?? "-"} {$b ?? "-"} {$glob ?? "-"}', 'inc3.latte' => '{parameters int $a = 5} {$a ?? "-"} {$b ?? "-"} {$glob ?? "-"}', 'inc4.latte' => '{parameters $a, int $b = 5} {$a ?? "-"} {$b ?? "-"} {$glob ?? "-"}', 'inc5.latte' => '{parameters $glob} {$a ?? "-"} {$b ?? "-"} {$glob ?? "-"}', + 'inc6.latte' => '{parameters ?\Exception $glob} {$a ?? "-"} {$b ?? "-"} {$glob->getMessage() ?? "-"}', + 'inc7.latte' => '{parameters $a, int $b = 5} {block x}{$a ?? "-"} {$b ?? "-"} {$glob ?? "-"}{/block}', + 'inc8.latte' => '{parameters $a, int $b = 5} {define x}{$a ?? "-"} {$b ?? "-"} {$glob ?? "-"}{/define}{include x}', ])); +Assert::matchFile(__DIR__ . '/expected/parameters.inc1.phtml', $latte->compile('inc1.latte')); +Assert::matchFile(__DIR__ . '/expected/parameters.inc2.phtml', $latte->compile('inc2.latte')); +Assert::matchFile(__DIR__ . '/expected/parameters.inc3.phtml', $latte->compile('inc3.latte')); +Assert::matchFile(__DIR__ . '/expected/parameters.inc4.phtml', $latte->compile('inc4.latte')); +Assert::matchFile(__DIR__ . '/expected/parameters.inc5.phtml', $latte->compile('inc5.latte')); +Assert::matchFile(__DIR__ . '/expected/parameters.inc6.phtml', $latte->compile('inc6.latte')); +Assert::matchFile(__DIR__ . '/expected/parameters.inc7.phtml', $latte->compile('inc7.latte')); +Assert::matchFile(__DIR__ . '/expected/parameters.inc8.phtml', $latte->compile('inc8.latte')); Assert::same('10 - 123', $latte->renderToString('main1', ['glob' => 123])); Assert::same(' 10 - -', $latte->renderToString('main2', ['glob' => 123])); Assert::same(' 10 - -', $latte->renderToString('main3', ['glob' => 123])); Assert::same(' 10 5 -', $latte->renderToString('main4', ['glob' => 123])); Assert::same(' - - 123', $latte->renderToString('main5', ['glob' => 123])); +Assert::same(' - - 123', $latte->renderToString('main6', ['glob' => new \Exception("123")])); +Assert::same(' 10 5 -', $latte->renderToString('main7', ['glob' => 123])); +Assert::same(' 10 5 -', $latte->renderToString('main8', ['glob' => 123])); diff --git a/tests/tags/templateType.phpt b/tests/tags/templateType.phpt index 5b7789ab3..902ae51f4 100644 --- a/tests/tags/templateType.phpt +++ b/tests/tags/templateType.phpt @@ -34,7 +34,7 @@ Assert::exception( Assert::exception( fn() => $latte->compile('{templateType AA\BBB}'), Latte\CompileException::class, - "Class 'AA\BBB' used in {templateType} doesn't exist (at column 15)", + "Class 'AA\\BBB' used in {templateType} doesn't exist (at column 15)", ); Assert::exception( @@ -44,6 +44,13 @@ Assert::exception( ); Assert::contains( - '/** @var int $intType *//** @var int|bool $intBoolType *//** @var array $arrayType */', - $latte->compile('{templateType TemplateClass}'), + '/** @var int $intType */' . "\n\t\t" . + '/** @var int|bool $intBoolType */'. "\n\t\t" . + '/** @var array $arrayType */'. "\n\n", + $latte->compile('{templateType TemplateClass}{$intBoolType}'), +); + +Assert::matchFile( + __DIR__ . '/expected/templateType.phtml', + $latte->compile('{templateType TemplateClass}{$intBoolType}{define test}{$intBoolType}{/define}') ); diff --git a/tests/tags/var.default.nodes.phpt b/tests/tags/var.default.nodes.phpt index 6f1794a97..911c420bc 100644 --- a/tests/tags/var.default.nodes.phpt +++ b/tests/tags/var.default.nodes.phpt @@ -20,5 +20,7 @@ Assert::match(<<<'XX' name: var2 Integer: value: 3 + SuperiorType: + 'int|array' Fragment: XX, exportTraversing('{var $var, int|array $var2 = 3}')); diff --git a/tests/tags/var.default.phpt b/tests/tags/var.default.phpt index 648813f01..42566d7c4 100644 --- a/tests/tags/var.default.phpt +++ b/tests/tags/var.default.phpt @@ -25,9 +25,9 @@ test('{var ...}', function () use ($latte) { // types Assert::contains('$temp->var1 = 123 /*', $latte->compile('{var int $temp->var1 = 123}')); Assert::contains('$temp->var1 = 123 /*', $latte->compile('{var null|int|string[] $temp->var1 = 123}')); - Assert::contains('/** @var int|string[] $var1 */ $var1 = 123; /** @var ?class $var2 */ $var2 = \'nette framework\' /* line 1 */;', ws($latte->compile('{var int|string[] $var1 = 123, ?class $var2 = "nette framework"}'))); - Assert::contains('$var1 = 123; $var2 = 456 /*', ws($latte->compile('{var A\B $var1 = 123, ?A\B $var2 = 456}'))); - Assert::contains('$var1 = 123; $var2 = 456 /*', ws($latte->compile('{var \A\B $var1 = 123, ?\A\B $var2 = 456}'))); + Assert::contains('/** @var int|string[] $var1 */$var1 = 123; /** @var ?class $var2 */$var2 = \'nette framework\' /* line 1 */;', ws($latte->compile('{var int|string[] $var1 = 123, ?class $var2 = "nette framework"}'))); + Assert::contains('/** @var A\B $var1 */$var1 = 123; /** @var ?A\B $var2 */$var2 = 456 /*', ws($latte->compile('{var A\B $var1 = 123, ?A\B $var2 = 456}'))); + Assert::contains('/** @var \A\B $var1 */$var1 = 123; /** @var ?\A\B $var2 */$var2 = 456 /*', ws($latte->compile('{var \A\B $var1 = 123, ?\A\B $var2 = 456}'))); // errors Assert::exception( diff --git a/tests/tags/varType.nodes.phpt b/tests/tags/varType.nodes.phpt index e20e9d6b3..8db160990 100644 --- a/tests/tags/varType.nodes.phpt +++ b/tests/tags/varType.nodes.phpt @@ -15,5 +15,9 @@ Assert::match(<<<'XX' Template: Fragment: VarType: + Variable: + name: int + SuperiorType: + 'int' Fragment: XX, exportTraversing('{varType int $int}')); diff --git a/tests/tags/varType.phpt b/tests/tags/varType.phpt index 8c32741cc..66878d560 100644 --- a/tests/tags/varType.phpt +++ b/tests/tags/varType.phpt @@ -57,3 +57,27 @@ Assert::contains( '/** @var array{0:int,1:int} $var */', $latte->compile('{varType array{0: int, 1: int} $var}'), ); + +$template = <<<'XX' + +{varType string $a} + +{$a} + +{varType string $c} +{var $c = 10} + +{include test} + +{define test} + {varType int $b} + {var $b = 5} + {$a}{$b} +{/define} + +XX; + +Assert::matchFile( + __DIR__ . '/expected/varType.phtml', + $latte->compile($template), +);