diff --git a/src/Latte/Compiler/Compiler.php b/src/Latte/Compiler/Compiler.php index 889acb3e6..734d35f29 100644 --- a/src/Latte/Compiler/Compiler.php +++ b/src/Latte/Compiler/Compiler.php @@ -57,9 +57,12 @@ class Compiler /** @var string[] @internal */ public $placeholders = []; - /** @var string|null */ + /** @var string */ public $paramsExtraction; + /** @var string */ + private $defaultParamsExtraction = 'extract($this->params);'; + /** @var Token[] */ private $tokens; @@ -174,7 +177,8 @@ private function buildClassBody(array $tokens): string $this->output = &$output; $this->inHead = true; $this->macroNode = new RootNode; - $this->htmlNode = $this->context = $this->paramsExtraction = null; + $this->htmlNode = $this->context = null; + $this->paramsExtraction = $this->defaultParamsExtraction; $this->placeholders = $this->properties = $this->constants = []; $this->methods = ['main' => null, 'prepare' => null]; @@ -223,7 +227,7 @@ private function buildClassBody(array $tokens): string $epilogs = (empty($res[1]) ? '' : "") . $epilogs; } - $extractParams = $this->paramsExtraction ?? 'extract($this->params);'; + $extractParams = $this->paramsExtraction; $this->addMethod('main', $this->expandTokens($extractParams . "?>\n$output$epilogstokenizer; $params = []; while ($tokens->isNext()) { - if ($tokens->nextToken($tokens::T_SYMBOL, '?', 'null', '\\')) { // type - $tokens->nextAll($tokens::T_SYMBOL, '\\', '|', '[', ']', 'null'); + $type = $tokens->nextValue($tokens::T_SYMBOL, '?', 'null', '\\'); + if ($type) { + $type .= $tokens->joinAll($tokens::T_SYMBOL, '\\', '|', '[', ']', 'null'); } $param = $tokens->consumeValue($tokens::T_VARIABLE); $default = $tokens->nextToken('=') ? $tokens->joinUntilSameDepth(',') : 'null'; + $mask ='%raw = $ÊŸ_args[%var] ?? $ÊŸ_args[%var] ?? %raw;'; + if($type) { + $mask = "/** @var $type $param */\n" . $mask; + } $params[] = $writer->write( - '%raw = $ÊŸ_args[%var] ?? $ÊŸ_args[%var] ?? %raw;', + $mask, $param, count($params), substr($param, 1), @@ -547,7 +552,7 @@ private function addBlock(MacroNode $node, string $layer = null): Block private function extractMethod(MacroNode $node, Block $block, string $params = null): void { if (preg_match('#\$|n:#', $node->content)) { - $node->content = 'name === 'block' && $node->closest(['embed']) ? 'end($this->varStack)' : '$this->params') . ');' + $node->content = 'name === 'block' && $node->closest(['embed']) ? 'extract(end($this->varStack));' : $this->getCompiler()->paramsExtraction) . ($params ?? 'extract($ÊŸ_args);') . 'unset($ÊŸ_args);?>' . $node->content; diff --git a/src/Latte/Macros/CoreMacros.php b/src/Latte/Macros/CoreMacros.php index 1d66dbcf4..2b76ff88d 100644 --- a/src/Latte/Macros/CoreMacros.php +++ b/src/Latte/Macros/CoreMacros.php @@ -819,15 +819,20 @@ public function macroParameters(MacroNode $node, PhpWriter $writer): void $tokens = $node->tokenizer; $params = []; while ($tokens->isNext()) { - if ($tokens->nextToken($tokens::T_SYMBOL, '?', 'null', '\\')) { // type - $tokens->nextAll($tokens::T_SYMBOL, '\\', '|', '[', ']', 'null'); + $type = $tokens->nextValue($tokens::T_SYMBOL, '?', 'null', '\\'); + if ($type) { + $type .= $tokens->joinAll($tokens::T_SYMBOL, '\\', '|', '[', ']', 'null'); } $param = $tokens->consumeValue($tokens::T_VARIABLE); $default = $tokens->nextToken('=') ? $tokens->joinUntilSameDepth(',') : 'null'; + $mask ='%raw = $this->params[%var] ?? $this->params[%var] ?? %raw;'; + if($type) { + $mask = "/** @var $type $param */\n" . $mask; + } $params[] = $writer->write( - '%raw = $this->params[%var] ?? $this->params[%var] ?? %raw;', + $mask, $param, count($params), substr($param, 1), @@ -844,7 +849,7 @@ public function macroParameters(MacroNode $node, PhpWriter $writer): void /** * {varType type $var} */ - public function macroVarType(MacroNode $node): void + public function macroVarType(MacroNode $node, PhpWriter $writer): string { if ($node->modifiers) { $node->setArgs($node->args . $node->modifiers); @@ -853,10 +858,17 @@ public function macroVarType(MacroNode $node): void $node->validate(true); $type = trim($node->tokenizer->joinUntil($node->tokenizer::T_VARIABLE)); - $variable = $node->tokenizer->nextToken($node->tokenizer::T_VARIABLE); + $variable = $node->tokenizer->nextValue($node->tokenizer::T_VARIABLE); if (!$type || !$variable) { throw new CompileException('Unexpected content, expecting {varType type $var}.'); } + $comment = "/** @var $type $variable */\n"; + if ($this->getCompiler()->isInHead()) { + $this->getCompiler()->paramsExtraction = $comment . $this->getCompiler()->paramsExtraction; + return ""; + } else { + return $writer->write($comment); + } } diff --git a/src/Latte/Runtime/Filters.php b/src/Latte/Runtime/Filters.php index 676b864d2..df65085f4 100644 --- a/src/Latte/Runtime/Filters.php +++ b/src/Latte/Runtime/Filters.php @@ -22,7 +22,7 @@ class Filters { /** @var string @deprecated */ - public static $dateFormat = 'j. n. Y'; + public static $dateFormat = 'j. n. Y'; /** @var bool @internal use XHTML syntax? */ public static $xhtml = false; diff --git a/tests/Latte/BlockMacros.define.args.phpt b/tests/Latte/BlockMacros.define.args.phpt index bbf1f37b0..13765c83f 100644 --- a/tests/Latte/BlockMacros.define.args.phpt +++ b/tests/Latte/BlockMacros.define.args.phpt @@ -157,3 +157,26 @@ Assert::matchFile( __DIR__ . '/expected/BlockMacros.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/BlockMacros.define.args6.phtml', + $latte->compile($template) +); +Assert::matchFile( + __DIR__ . '/expected/BlockMacros.define.args6.html', + $latte->renderToString($template) +); diff --git a/tests/Latte/CoreMacros.parameters.phpt b/tests/Latte/CoreMacros.parameters.phpt index d30ec5381..787936950 100644 --- a/tests/Latte/CoreMacros.parameters.phpt +++ b/tests/Latte/CoreMacros.parameters.phpt @@ -19,17 +19,28 @@ $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::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])); + +Assert::contains('/** @var int $a */', $latte->compile('inc3.latte')); +Assert::contains('/** @var ?\Exception $glob */', $latte->compile('inc6.latte')); diff --git a/tests/Latte/CoreMacros.varType.phpt b/tests/Latte/CoreMacros.varType.phpt index d9b0614e9..e477fa7ef 100644 --- a/tests/Latte/CoreMacros.varType.phpt +++ b/tests/Latte/CoreMacros.varType.phpt @@ -46,3 +46,25 @@ Assert::noError(function () use ($latte) { Assert::noError(function () use ($latte) { $latte->compile('{varType array{0: int, 1: int} $var}'); }); + +Assert::contains('/** @var int|null $var */', $latte->compile('{varType int|null $var}')); + +$template = <<<'XX' +{varType string $a} + +{$a} + +{include test} + +{define test} + {varType int $b} + {var $b = 5} + {$a}{$b} +{/define} + +XX; + +Assert::matchFile( + __DIR__ . '/expected/CoreMacros.varType.phtml', + $latte->compile($template) +); \ No newline at end of file diff --git a/tests/Latte/expected/BlockMacros.define.args6.html b/tests/Latte/expected/BlockMacros.define.args6.html new file mode 100644 index 000000000..128d3f783 --- /dev/null +++ b/tests/Latte/expected/BlockMacros.define.args6.html @@ -0,0 +1,7 @@ +default values + + +a) Variables 1, 123, 10 + + +b) Variables 1, 123, 10 diff --git a/tests/Latte/expected/BlockMacros.define.args6.phtml b/tests/Latte/expected/BlockMacros.define.args6.phtml new file mode 100644 index 000000000..fb4fee684 --- /dev/null +++ b/tests/Latte/expected/BlockMacros.define.args6.phtml @@ -0,0 +1,49 @@ + 'blockTest'], + ]; + + + public function main(): array + { + extract($this->params); + echo 'default values + +'; + if ($this->getParentName()) { + return get_defined_vars(); + } + echo ' +a) '; + $this->renderBlock('test', [1] + [], 'html') /* line %d% */; + echo ' + +b) '; + $this->renderBlock('test', ['var1' => 1] + [], 'html') /* line %d% */; + return get_defined_vars(); + } + + + /** {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"; + } + +} diff --git a/tests/Latte/expected/CoreMacros.varType.phtml b/tests/Latte/expected/CoreMacros.varType.phtml new file mode 100644 index 000000000..e1faf10b6 --- /dev/null +++ b/tests/Latte/expected/CoreMacros.varType.phtml @@ -0,0 +1,15 @@ +params); +%A% + public function blockTest(array $ÊŸ_args): void + { + /** @var string $a */ + extract($this->params); +%A% + /** @var int $b */ + $b = 5%a%; +%A% \ No newline at end of file