From 33bcc0b08b56477cd81f0b6049f3c5184af857d7 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 16 Jul 2019 16:47:16 +0200 Subject: [PATCH 01/20] opened 3.0-dev --- composer.json | 2 +- src/Latte/Engine.php | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index 3d09a1fcf..999ccdd02 100644 --- a/composer.json +++ b/composer.json @@ -46,7 +46,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.10-dev" + "dev-master": "3.0-dev" } } } diff --git a/src/Latte/Engine.php b/src/Latte/Engine.php index 0eab37236..e79f135d6 100644 --- a/src/Latte/Engine.php +++ b/src/Latte/Engine.php @@ -17,8 +17,8 @@ class Engine { use Strict; - public const VERSION = '2.10.3'; - public const VERSION_ID = 21003; + public const VERSION = '3.0.0-dev'; + public const VERSION_ID = 30000; /** Content types */ public const @@ -290,7 +290,7 @@ public function addFilter(?string $name, callable $callback) /** - * Registers filter loader. + * Registers run-time filter loader. * @return static */ public function addFilterLoader(callable $callback) From 80980c7aadaf32348cb13d39e19c020c72e8deb8 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 18 Feb 2021 16:02:10 +0100 Subject: [PATCH 02/20] anonymous {block} create variable scope (possible BC break) --- src/Latte/Macros/BlockMacros.php | 20 +++++++++-------- tests/Latte/expected/BlockMacros.vars.html | 7 +++--- tests/Latte/expected/BlockMacros.vars.phtml | 25 ++++++++++++--------- 3 files changed, 29 insertions(+), 23 deletions(-) diff --git a/src/Latte/Macros/BlockMacros.php b/src/Latte/Macros/BlockMacros.php index 06c726315..f0fcc46fe 100644 --- a/src/Latte/Macros/BlockMacros.php +++ b/src/Latte/Macros/BlockMacros.php @@ -249,7 +249,7 @@ public function macroExtends(MacroNode $node, PhpWriter $writer): void /** * {block [local] [name]} */ - public function macroBlock(MacroNode $node, PhpWriter $writer): string + public function macroBlock(MacroNode $node, PhpWriter $writer) { [$name, $local] = $node->tokenizer->fetchWordWithModifier('local'); $layer = $local ? Template::LAYER_LOCAL : null; @@ -258,15 +258,17 @@ public function macroBlock(MacroNode $node, PhpWriter $writer): string $this->checkExtraArgs($node); if ($data->name === '') { - if ($node->modifiers === '') { - return ''; + $node->openingCode = ''; + $node->closingCode = ''; + if ($node->modifiers !== '') { + $node->modifiers .= '|escape'; + $node->openingCode = $writer->write('') . $node->openingCode; + $node->closingCode .= $writer->write( + '', + implode($node->context) + ); } - $node->modifiers .= '|escape'; - $node->closingCode = $writer->write( - '', - implode($node->context) - ); - return $writer->write('ob_start(function () {}) %node.line;'); + return; } if (Helpers::startsWith((string) $node->context[1], Latte\Compiler::CONTEXT_HTML_ATTRIBUTE)) { diff --git a/tests/Latte/expected/BlockMacros.vars.html b/tests/Latte/expected/BlockMacros.vars.html index 89c493497..0fa087c6a 100644 --- a/tests/Latte/expected/BlockMacros.vars.html +++ b/tests/Latte/expected/BlockMacros.vars.html @@ -11,10 +11,9 @@ a -blockmod - +a - blockmod + a -block +a diff --git a/tests/Latte/expected/BlockMacros.vars.phtml b/tests/Latte/expected/BlockMacros.vars.phtml index 10d48b456..8a0c967f6 100644 --- a/tests/Latte/expected/BlockMacros.vars.phtml +++ b/tests/Latte/expected/BlockMacros.vars.phtml @@ -21,10 +21,13 @@ '; ob_start(function () {}) /* line %d% */; - echo ' '; - echo LR\Filters::escapeHtmlText($var) /* line %d% */; - echo "\n"; - $var = 'blockmod' /* line %d% */; + (function () { + extract(func_get_arg(0)); + echo ' '; + echo LR\Filters::escapeHtmlText($var) /* line %d% */; + echo "\n"; + $var = 'blockmod' /* line %d% */; + })(get_defined_vars()); $ʟ_fi = new LR\FilterInfo('html'); echo LR\Filters::convertTo($ʟ_fi, 'html', $this->filters->filterContent('trim', $ʟ_fi, ob_get_clean())); echo ' @@ -34,13 +37,15 @@ echo ' - '; - echo LR\Filters::escapeHtmlText($var) /* line %d% */; - echo "\n"; - $var = 'block' /* line %d% */; - echo ' - '; + (function () { + extract(func_get_arg(0)); + echo ' '; + echo LR\Filters::escapeHtmlText($var) /* line %d% */; + echo "\n"; + $var = 'block' /* line %d% */; + })(get_defined_vars()); + echo "\n"; echo LR\Filters::escapeHtmlText($var) /* line %d% */; return get_defined_vars(); } From 90950cdfaafb86fca6052d4d808cdbdedae2a402 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 12 Jan 2020 04:44:15 +0100 Subject: [PATCH 03/20] added macro n:nonce --- src/Latte/Macros/CoreMacros.php | 2 ++ tests/Latte/CoreMacros.nonce.phpt | 30 ++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 tests/Latte/CoreMacros.nonce.phpt diff --git a/src/Latte/Macros/CoreMacros.php b/src/Latte/Macros/CoreMacros.php index b0e588319..a356f23dc 100644 --- a/src/Latte/Macros/CoreMacros.php +++ b/src/Latte/Macros/CoreMacros.php @@ -90,6 +90,8 @@ public static function install(Latte\Compiler $compiler): void $me->addMacro('varPrint', [$me, 'macroVarPrint'], null, null, self::ALLOWED_IN_HEAD); $me->addMacro('templateType', [$me, 'macroTemplateType'], null, null, self::ALLOWED_IN_HEAD); $me->addMacro('templatePrint', [$me, 'macroTemplatePrint'], null, null, self::ALLOWED_IN_HEAD); + + $me->addMacro('nonce', null, null, 'echo $this->global->coreNonce ? " nonce=\"" . htmlspecialchars($this->global->coreNonce) . "\"" : "";'); } diff --git a/tests/Latte/CoreMacros.nonce.phpt b/tests/Latte/CoreMacros.nonce.phpt new file mode 100644 index 000000000..57f85a55f --- /dev/null +++ b/tests/Latte/CoreMacros.nonce.phpt @@ -0,0 +1,30 @@ +setLoader(new Latte\Loaders\StringLoader); +$latte->addProvider('coreNonce', null); + +Assert::match( + '', + $latte->renderToString('') +); + + +$latte->addProvider('coreNonce', 'djsdgidk'); + +Assert::match( + '', + $latte->renderToString('') +); From b0a436b82c1e99956b91db0777a0c24850162efc Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 4 Nov 2020 23:07:05 +0100 Subject: [PATCH 04/20] added {cycle} --- src/Latte/Macros/CoreMacros.php | 1 + tests/Latte/CoreMacros.cycle.phpt | 49 +++++++++++++++++++++ tests/Latte/expected/CoreMacros.cycle.phtml | 35 +++++++++++++++ 3 files changed, 85 insertions(+) create mode 100644 tests/Latte/CoreMacros.cycle.phpt create mode 100644 tests/Latte/expected/CoreMacros.cycle.phtml diff --git a/src/Latte/Macros/CoreMacros.php b/src/Latte/Macros/CoreMacros.php index a356f23dc..fcb7012a5 100644 --- a/src/Latte/Macros/CoreMacros.php +++ b/src/Latte/Macros/CoreMacros.php @@ -58,6 +58,7 @@ public static function install(Latte\Compiler $compiler): void $me->addMacro('first', 'if ($iterator->isFirst(%node.args)) %node.line {', '}'); $me->addMacro('last', 'if ($iterator->isLast(%node.args)) %node.line {', '}'); $me->addMacro('sep', 'if (!$iterator->isLast(%node.args)) %node.line {', '}'); + $me->addMacro('cycle', '$_tmp = %node.array; echo %escape($_tmp[$iterator->getCounter0() % count($_tmp)]) %node.line;'); $me->addMacro('try', [$me, 'macroTry'], '}'); $me->addMacro('rollback', [$me, 'macroRollback']); diff --git a/tests/Latte/CoreMacros.cycle.phpt b/tests/Latte/CoreMacros.cycle.phpt new file mode 100644 index 000000000..ad2fa21a3 --- /dev/null +++ b/tests/Latte/CoreMacros.cycle.phpt @@ -0,0 +1,49 @@ +setLoader(new Latte\Loaders\StringLoader); + +$template = <<<'EOD' + +{foreach range(1, 5) as $i} {cycle a, b, c} {/foreach} + + +{foreach range(1, 5) as $i} + {continueIf $i % 2} + {cycle a, b, c} +{/foreach} + + +{foreach range(1, 5) as $i} {cycle $i, b, c} {/foreach} + +EOD; + +/*Assert::matchFile( + __DIR__ . '/expected/CoreMacros.cycle.phtml', + $latte->compile($template) +);*/ + +Assert::match( + ' + a b c a b + + + b + a + + + 1 b c 4 b', + $latte->renderToString($template) +); diff --git a/tests/Latte/expected/CoreMacros.cycle.phtml b/tests/Latte/expected/CoreMacros.cycle.phtml new file mode 100644 index 000000000..549587891 --- /dev/null +++ b/tests/Latte/expected/CoreMacros.cycle.phtml @@ -0,0 +1,35 @@ + global->ifchanged[1] ?? null) !== ($_tmp = [$i])) { + $this->global->ifchanged[1] = $_tmp; + ?> + + +-- + + global->ifchanged[2] ?? null) !== ($_tmp = [$i])) { + $this->global->ifchanged[2] = $_tmp; + ?> same + + global->ifchanged[3] ?? null) !== ($_tmp = ['a', 'b'])) { + $this->global->ifchanged[3] = $_tmp; + ?> const Date: Wed, 20 Feb 2019 11:12:13 +0100 Subject: [PATCH 05/20] SnippetBridge: added typehints --- composer.json | 2 +- src/Latte/Runtime/SnippetBridge.php | 53 ++++++------------------- tests/Latte/mocks/SnippetBridgeMock.php | 10 ++--- 3 files changed, 19 insertions(+), 46 deletions(-) diff --git a/composer.json b/composer.json index 999ccdd02..c1b10fe56 100644 --- a/composer.json +++ b/composer.json @@ -34,7 +34,7 @@ "nette/php-generator": "to use tag {templatePrint}" }, "conflict": { - "nette/application": "<2.4.1" + "nette/application": "<3.0" }, "autoload": { "classmap": ["src/"] diff --git a/src/Latte/Runtime/SnippetBridge.php b/src/Latte/Runtime/SnippetBridge.php index e8e7b8c2c..eeace1822 100644 --- a/src/Latte/Runtime/SnippetBridge.php +++ b/src/Latte/Runtime/SnippetBridge.php @@ -16,46 +16,19 @@ */ interface SnippetBridge { - /** - * @return bool - */ - function isSnippetMode(); - - /** - * @param bool $snippetMode - * @return void - */ - function setSnippetMode($snippetMode); - - /** - * @param string $name - * @return bool - */ - function needsRedraw($name); - - /** - * @param string $name - * @return void - */ - function markRedrawn($name); - - /** - * @param string $name - * @return string - */ - function getHtmlId($name); - - /** - * @param string $name - * @param string $content - * @return void - */ - function addSnippet($name, $content); - - /** - * @return void - */ - function renderChildren(); + function isSnippetMode(): bool; + + function setSnippetMode(bool $snippetMode); + + function needsRedraw(string $name): bool; + + function markRedrawn(string $name): void; + + function getHtmlId(string $name): string; + + function addSnippet(string $name, string $content): void; + + function renderChildren(): void; } diff --git a/tests/Latte/mocks/SnippetBridgeMock.php b/tests/Latte/mocks/SnippetBridgeMock.php index 3f2f431a9..232a5b2d1 100644 --- a/tests/Latte/mocks/SnippetBridgeMock.php +++ b/tests/Latte/mocks/SnippetBridgeMock.php @@ -18,19 +18,19 @@ public function isSnippetMode(): bool } - public function setSnippetMode($snippetMode) + public function setSnippetMode(bool $snippetMode) { $this->snippetMode = $snippetMode; } - public function needsRedraw($name): bool + public function needsRedraw(string $name): bool { return $this->invalid === true || isset($this->invalid[$name]); } - public function markRedrawn($name): void + public function markRedrawn(string $name): void { if ($this->invalid !== true) { unset($this->invalid[$name]); @@ -38,13 +38,13 @@ public function markRedrawn($name): void } - public function getHtmlId($name): string + public function getHtmlId(string $name): string { return $name; } - public function addSnippet($name, $content): void + public function addSnippet(string $name, string $content): void { $this->payload[$name] = $content; } From 163fa75ee6697ac156f788438ecc302c6a1547d9 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 18 Feb 2021 16:02:45 +0100 Subject: [PATCH 06/20] Template: removed old accumulators $_l, $_g (deprecated in 2.4, BC break) --- src/Latte/Runtime/Blueprint.php | 2 +- src/Latte/Runtime/Template.php | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Latte/Runtime/Blueprint.php b/src/Latte/Runtime/Blueprint.php index eb81e2352..f98bb1193 100644 --- a/src/Latte/Runtime/Blueprint.php +++ b/src/Latte/Runtime/Blueprint.php @@ -58,7 +58,7 @@ public function printVars(array $vars): void $res = ''; foreach ($vars as $name => $value) { - if (Latte\Helpers::startsWith($name, 'ʟ_') || $name === '_l' || $name === '_g') { + if (Latte\Helpers::startsWith($name, 'ʟ_')) { continue; } $type = Php\Type::getType($value) ?: 'mixed'; diff --git a/src/Latte/Runtime/Template.php b/src/Latte/Runtime/Template.php index b8a3c1c9f..326aa75f8 100644 --- a/src/Latte/Runtime/Template.php +++ b/src/Latte/Runtime/Template.php @@ -191,8 +191,6 @@ private function doRender(string $block = null): bool $this->global->snippetDriver = new SnippetDriver($this->global->snippetBridge); } Filters::$xhtml = (bool) preg_match('#xml|xhtml#', static::CONTENT_TYPE); - $this->params['_l'] = new \stdClass; // old accumulators for back compatibility - $this->params['_g'] = $this->global; if ($this->referenceType === 'import') { if ($this->parentName) { From 0c208d5b51d02c703296444515cd12ae14023856 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 23 Nov 2020 00:19:39 +0100 Subject: [PATCH 07/20] removed aliases for old classes ILoader, IMacro, IHtmlString, IISnippetBridge --- src/Latte/Loader.php | 3 -- src/Latte/Macro.php | 3 -- src/Latte/Runtime/HtmlStringable.php | 3 -- src/Latte/Runtime/SnippetBridge.php | 3 -- src/compatibility.php | 48 ---------------------------- 5 files changed, 60 deletions(-) delete mode 100644 src/compatibility.php diff --git a/src/Latte/Loader.php b/src/Latte/Loader.php index 7b103877f..12ecdb363 100644 --- a/src/Latte/Loader.php +++ b/src/Latte/Loader.php @@ -45,6 +45,3 @@ function getReferredName($name, $referringName); */ function getUniqueId($name); } - - -interface_exists(ILoader::class); diff --git a/src/Latte/Macro.php b/src/Latte/Macro.php index 844d74863..2513356e0 100644 --- a/src/Latte/Macro.php +++ b/src/Latte/Macro.php @@ -45,6 +45,3 @@ function nodeOpened(MacroNode $node); */ function nodeClosed(MacroNode $node); } - - -interface_exists(IMacro::class); diff --git a/src/Latte/Runtime/HtmlStringable.php b/src/Latte/Runtime/HtmlStringable.php index 7eed26af6..fc21794a2 100644 --- a/src/Latte/Runtime/HtmlStringable.php +++ b/src/Latte/Runtime/HtmlStringable.php @@ -15,6 +15,3 @@ interface HtmlStringable /** @return string in HTML format */ function __toString(): string; } - - -interface_exists(IHtmlString::class); diff --git a/src/Latte/Runtime/SnippetBridge.php b/src/Latte/Runtime/SnippetBridge.php index eeace1822..8d3428d06 100644 --- a/src/Latte/Runtime/SnippetBridge.php +++ b/src/Latte/Runtime/SnippetBridge.php @@ -30,6 +30,3 @@ function addSnippet(string $name, string $content): void; function renderChildren(): void; } - - -interface_exists(ISnippetBridge::class); diff --git a/src/compatibility.php b/src/compatibility.php deleted file mode 100644 index cabfad5c3..000000000 --- a/src/compatibility.php +++ /dev/null @@ -1,48 +0,0 @@ - Date: Sat, 21 Nov 2020 04:06:16 +0100 Subject: [PATCH 08/20] Filters::escapeJS: invalid UTF-8 is replaced with Unicode Replacement Character U+FFFD (instead of throwing exception) --- src/Latte/Runtime/Filters.php | 2 +- tests/Latte/Filters.escapeJs().phpt | 19 ++++++++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/Latte/Runtime/Filters.php b/src/Latte/Runtime/Filters.php index 3df4b8552..e39f6a345 100644 --- a/src/Latte/Runtime/Filters.php +++ b/src/Latte/Runtime/Filters.php @@ -167,7 +167,7 @@ public static function escapeJs($s): string $s = $s->__toString(true); } - $json = json_encode($s, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); + $json = json_encode($s, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | (PHP_VERSION_ID >= 70200 ? JSON_INVALID_UTF8_SUBSTITUTE : 0)); if ($error = json_last_error()) { throw new Latte\RuntimeException(json_last_error_msg(), $error); } diff --git a/tests/Latte/Filters.escapeJs().phpt b/tests/Latte/Filters.escapeJs().phpt index 68ac81d8f..9432a6d9e 100644 --- a/tests/Latte/Filters.escapeJs().phpt +++ b/tests/Latte/Filters.escapeJs().phpt @@ -31,10 +31,15 @@ Assert::same('"
"', Filters::escapeJs(new Test)); Assert::same('"
"', Filters::escapeJs(new Latte\Runtime\Html('
'))); // invalid UTF-8 -Assert::exception(function () { - Filters::escapeJs("foo \u{D800} bar"); // invalid codepoint high surrogates -}, Latte\RuntimeException::class, 'Malformed UTF-8 characters, possibly incorrectly encoded'); - -Assert::exception(function () { - Filters::escapeJs("foo \xE3\x80\x22 bar"); // stripped UTF -}, Latte\RuntimeException::class, 'Malformed UTF-8 characters, possibly incorrectly encoded'); +if (PHP_VERSION_ID >= 70200) { + Assert::same("\"foo \u{FFFD} bar\"", Filters::escapeJs("foo \u{D800} bar")); // invalid codepoint high surrogates + Assert::same("\"foo \u{FFFD}\\\" bar\"", Filters::escapeJs("foo \xE3\x80\x22 bar")); // stripped UTF +} else { + Assert::exception(function () { + Filters::escapeJs("foo \u{D800} bar"); // invalid codepoint high surrogates + }, Latte\RuntimeException::class, 'Malformed UTF-8 characters, possibly incorrectly encoded'); + + Assert::exception(function () { + Filters::escapeJs("foo \xE3\x80\x22 bar"); // stripped UTF + }, Latte\RuntimeException::class, 'Malformed UTF-8 characters, possibly incorrectly encoded'); +} From 24d33f3bc4835b9aff16c2469b1d1cdb2979b1da Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 19 Mar 2020 01:10:00 +0100 Subject: [PATCH 09/20] MacroTokens: correctly parses UTF-8 Combining character (TODO: normalization) --- src/Latte/Compiler/MacroTokens.php | 6 ++--- tests/Latte/MacroTokens.parse.phpt | 41 ++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 tests/Latte/MacroTokens.parse.phpt diff --git a/src/Latte/Compiler/MacroTokens.php b/src/Latte/Compiler/MacroTokens.php index 42b5c93df..2ad25f906 100644 --- a/src/Latte/Compiler/MacroTokens.php +++ b/src/Latte/Compiler/MacroTokens.php @@ -54,10 +54,10 @@ public function parse(string $s): array self::T_STRING => Parser::RE_STRING, self::T_KEYWORD => '(?:true|false|null|TRUE|FALSE|NULL|INF|NAN|and|or|xor|AND|OR|XOR|clone|new|instanceof|return|continue|break)(?!\w)', // keyword self::T_CAST => '\((?:expand|string|array|int|integer|float|bool|boolean|object)\)', // type casting - self::T_VARIABLE => '\$\w+', + self::T_VARIABLE => '\$(?>\w\pM*)+', self::T_NUMBER => '[+-]?[0-9]+(?:\.[0-9]+)?(?:e[0-9]+)?', - self::T_SYMBOL => '\w+(?:-+\w+)*', - self::T_CHAR => '::|=>|->|\?->|\?\?->|\+\+|--|<<|>>|<=>|<=|>=|===|!==|==|!=|<>|&&|\|\||\?\?|\?>|\*\*|\.\.\.|[^"\']', // =>, any char except quotes + self::T_SYMBOL => '(?>\w\pM*)+(?:-+(?>\w\pM*)+)*', + self::T_CHAR => '::|=>|->|\?->|\?\?->|\+\+|--|<<|>>|<=>|<=|>=|===|!==|==|!=|<>|&&|\|\||\?\?|\?>|\*\*|\.\.\.|(?>[^"\']\pM*)', // =>, any char except quotes ], 'u'); return self::$tokenizer->tokenize($s); } diff --git a/tests/Latte/MacroTokens.parse.phpt b/tests/Latte/MacroTokens.parse.phpt new file mode 100644 index 000000000..3d2fcb208 --- /dev/null +++ b/tests/Latte/MacroTokens.parse.phpt @@ -0,0 +1,41 @@ +tokens); +}); + + +test('', function () { // UTF-8 Combining character + $tokenizer = new MacroTokens("\u{0061}\u{0300} $\u{0061}\u{0300}"); + Assert::same([ + ["\u{0061}\u{0300}", 0, MacroTokens::T_SYMBOL], + [' ', 3, MacroTokens::T_WHITESPACE], + ["$\u{0061}\u{0300}", 4, MacroTokens::T_VARIABLE], + ], $tokenizer->tokens); +}); + + +test('', function () { // UTF-8 emoji + $tokenizer = new MacroTokens("\u{1F6E1}\u{FE0F}"); + Assert::same([ + ["\u{1F6E1}\u{FE0F}", 0, MacroTokens::T_CHAR], + ], $tokenizer->tokens); +}); From 6a4e283a3421410f456ee0e948022ac8b594ad04 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 3 Dec 2020 15:02:54 +0100 Subject: [PATCH 10/20] optional chaining: removed support for deprecated syntax --- src/Latte/Compiler/PhpWriter.php | 29 +------ tests/Latte/PhpWriter.formatArgs().phpt | 24 +----- tests/Latte/PhpWriter.formatModifiers().phpt | 15 ---- .../PhpWriter.optionalChainingPass().80.phpt | 78 ------------------- .../PhpWriter.optionalChainingPass().phpt | 67 +--------------- tests/Latte/Policy.properties.operators.phpt | 6 +- 6 files changed, 9 insertions(+), 210 deletions(-) diff --git a/src/Latte/Compiler/PhpWriter.php b/src/Latte/Compiler/PhpWriter.php index a9b78c2f2..7efd79657 100644 --- a/src/Latte/Compiler/PhpWriter.php +++ b/src/Latte/Compiler/PhpWriter.php @@ -353,36 +353,9 @@ public function optionalChainingPass(MacroTokens $tokens): MacroTokens $addBraces = ''; $expr = new MacroTokens([$tokens->currentToken()]); - $var = $tokens->currentValue(); do { - if ($tokens->nextToken('?')) { - if ( // is it ternary operator? - $tokens->isNext() - && ( - !$tokens->isNext($tokens::T_CHAR) - || $tokens->isNext('(', '[', '{', ':', '!', '@', '\\') - ) - ) { - $expr->append($addBraces . ' ?'); - break; - } - - if (!$tokens->isNext('::')) { - $expr->prepend('('); - $expr->append(' ?? null)' . $addBraces); - trigger_error("Syntax '$var?' is deprecated, use '$var ?? null' instead.", E_USER_DEPRECATED); - break; - } - - trigger_error("Syntax '$var?::' is deprecated.", E_USER_DEPRECATED); - $expr->prepend('(($ʟ_tmp = '); - $expr->append(' ?? null) === null ? null : '); - $res->tokens = array_merge($res->tokens, $expr->tokens); - $expr = new MacroTokens('$ʟ_tmp'); - $addBraces .= ')'; - - } elseif ($tokens->nextToken('?->')) { + if ($tokens->nextToken('?->')) { if (PHP_VERSION_ID >= 80000) { $expr->append($tokens->currentToken()); $expr->append($tokens->nextToken()); diff --git a/tests/Latte/PhpWriter.formatArgs().phpt b/tests/Latte/PhpWriter.formatArgs().phpt index d3bce9a58..6cfef9ae1 100644 --- a/tests/Latte/PhpWriter.formatArgs().phpt +++ b/tests/Latte/PhpWriter.formatArgs().phpt @@ -61,11 +61,11 @@ test('short ternary operators', function () { Assert::same("fce() ? 'a' : null, fce() ? 'b' : null", formatArgs('fce() ? a, fce() ? b')); Assert::same("fce() ?? 'a'", formatArgs('fce() ?? a')); // null coalesce is ignored Assert::same("'a'?", formatArgs('a?')); // value must exists - Assert::same('$a ?(1) : null', formatArgs('$a?(1)')); // with braces + Assert::same('$a?(1) : null', formatArgs('$a?(1)')); // with braces Assert::same('$a ? \Foo::BAR : null', formatArgs('$a ? \Foo::BAR')); Assert::same('$c ?: ($a ?: $b)', formatArgs('$c ?: ($a ?: $b)')); Assert::same('$c ? ($a ?: $b) : null', formatArgs('$c ? ($a ?: $b)')); - Assert::same('$a ?(1) : null', formatArgs('$a?(1)')); // with braces + Assert::same('$a?(1) : null', formatArgs('$a?(1)')); // with braces }); @@ -84,9 +84,6 @@ test('special', function () { Assert::same("'symbol' => Namespace \\ Class :: method ()", formatArgs('symbol => Namespace \ Class :: method ()')); Assert::same("'symbol' => \$this->var, ", formatArgs('symbol => $this->var, ')); Assert::same("'symbol' => \$this->VAR, ", formatArgs('symbol => $this->VAR, ')); - Assert::same("'symbol' => \$this->var, ", formatArgs('symbol => $this -> var, ')); - Assert::same("'symbol' => \$this->VAR, ", formatArgs('symbol => $this -> VAR, ')); - Assert::same("'symbol' => \$this->var", formatArgs('symbol => $this -> var')); Assert::same("'symbol1' => 'value'", formatArgs('symbol1 => /*value,* /symbol2=>*/value/**/')); Assert::same('(array)', formatArgs('(array)')); Assert::same('func()[1]', formatArgs('func()[1]')); @@ -139,9 +136,6 @@ test('in operator', function () { test('optionalChainingPass', function () { - Assert::same('$a', formatArgs('$a')); - Assert::same('($a ?? null)', @formatArgs('$a?')); // deprecated - Assert::same('(($a ?? null))', @formatArgs('($a?)')); // deprecated Assert::same('$var->prop->elem[1]->call(2)->item', formatArgs('$var->prop->elem[1]->call(2)->item')); Assert::same( PHP_VERSION_ID >= 80000 @@ -156,20 +150,6 @@ test('optionalChainingPass', function () { }); -test('optionalChainingPass + ternary', function () { - Assert::same('$a ?:$b', formatArgs('$a?:$b')); - Assert::same('$a ? : $b', formatArgs('$a ? : $b')); - Assert::same('$a ?? $b', formatArgs('$a ?? $b')); - Assert::same('$a ? [1, 2, ([3 ? 2 : 1])]: $b', formatArgs('$a ? [1, 2, ([3 ? 2 : 1])]: $b')); - Assert::same( - PHP_VERSION_ID >= 80000 - ? '($a?->foo ?? null) ? [1, 2, ([3 ? 2 : 1])] : $b' - : '(($ʟ_tmp = $a) === null ? null : ($ʟ_tmp->foo ?? null)) ? [1, 2, ([3 ? 2 : 1])] : $b', - @formatArgs('$a?->foo? ? [1, 2, ([3 ? 2 : 1])] : $b') // deprecated - ); -}); - - test('named arguments', function () { Assert::same('func(a: 1, b: 2)', formatArgs('func(a: 1, b: 2)')); Assert::same("func(a: 1, ('a' ?'b': 2))", formatArgs('func(a: 1, (a ?b: 2))')); // ternary diff --git a/tests/Latte/PhpWriter.formatModifiers().phpt b/tests/Latte/PhpWriter.formatModifiers().phpt index 7b40cd4ee..5985d17ae 100644 --- a/tests/Latte/PhpWriter.formatModifiers().phpt +++ b/tests/Latte/PhpWriter.formatModifiers().phpt @@ -69,8 +69,6 @@ test('depth', function () { test('optionalChainingPass', function () { - Assert::same('($this->filters->mod)(@, ($a ?? null))', @formatModifiers('@', 'mod:$a?')); // deprecated - Assert::same('($this->filters->mod)(@, (($a ?? null)))', @formatModifiers('@', 'mod:($a?)')); // deprecated Assert::same( PHP_VERSION_ID >= 80000 ? '($this->filters->mod)(@, $var?->prop?->elem[1]?->call(2)?->item)' @@ -82,16 +80,3 @@ test('optionalChainingPass', function () { formatModifiers('@', 'mod:$var??->prop??->elem[1]??->call(2)??->item') ); }); - - -test('optionalChainingPass + ternary', function () { - Assert::same('($this->filters->mod)(@, $a ?, $b)', formatModifiers('@', 'mod:$a?:$b')); - Assert::same('($this->filters->mod)(@, $a ? , $b)', formatModifiers('@', 'mod:$a ? : $b')); - Assert::same('($this->filters->mod)(@, $a ?? $b)', formatModifiers('@', 'mod:$a ?? $b')); - Assert::same( - PHP_VERSION_ID >= 80000 - ? '($this->filters->mod)(@, ($a?->foo ?? null) ? [1, 2, ([3 ? 2 : 1])] , $b)' - : '($this->filters->mod)(@, (($ʟ_tmp = $a) === null ? null : ($ʟ_tmp->foo ?? null)) ? [1, 2, ([3 ? 2 : 1])] , $b)', - @formatModifiers('@', 'mod:$a?->foo? ? [1, 2, ([3 ? 2 : 1])] : $b') // deprecated - ); -}); diff --git a/tests/Latte/PhpWriter.optionalChainingPass().80.phpt b/tests/Latte/PhpWriter.optionalChainingPass().80.phpt index 1b008acd2..caaaed1c2 100644 --- a/tests/Latte/PhpWriter.optionalChainingPass().80.phpt +++ b/tests/Latte/PhpWriter.optionalChainingPass().80.phpt @@ -19,88 +19,10 @@ function optionalChaining($code) } -test('vars', function () { // deprecated - Assert::same('$a', optionalChaining('$a')); - Assert::same('($a ?? null)', @optionalChaining('$a?')); - Assert::same('(($a ?? null))', @optionalChaining('($a?)')); - Assert::same('a?', optionalChaining('a?')); -}); - - -test('indexes', function () { // deprecated - Assert::same('($foo[1] ?? null)', @optionalChaining('$foo[1]?')); - Assert::same('(($foo[1] ?? null))', @optionalChaining('($foo[1]?)')); -}); - - -test('properties', function () { - Assert::same('$foo?->prop', optionalChaining('$foo?->prop')); - Assert::same('($foo->prop ?? null)', @optionalChaining('$foo->prop?')); // deprecated - Assert::same('($foo?->prop ?? null)', @optionalChaining('$foo?->prop?')); // deprecated - Assert::same('($foo?->prop ?? null) + 10', @optionalChaining('$foo?->prop? + 10')); // deprecated - Assert::same('($foo->prop ?? null) + 10', @optionalChaining('$foo->prop? + 10')); // deprecated - Assert::same('(($foo->prop ?? null))', @optionalChaining('($foo->prop?)')); // deprecated - Assert::same('($foo?->prop)', optionalChaining('($foo?->prop)')); - Assert::same('[($foo?->prop ?? null)]', @optionalChaining('[$foo?->prop?]')); // deprecated - - // variable - Assert::same('$foo?->$prop', optionalChaining('$foo?->$prop')); - Assert::same('($foo->$prop ?? null)', @optionalChaining('$foo->$prop?')); // deprecated - - // static - Assert::same('(($ʟ_tmp = $foo ?? null) === null ? null : $ʟ_tmp::$prop)', @optionalChaining('$foo?::$prop')); // deprecated - Assert::same('($foo::$prop ?? null)', @optionalChaining('$foo::$prop?')); // deprecated -}); - - -test('calling', function () { - Assert::same('$foo?->call()', optionalChaining('$foo?->call()')); - Assert::same('($foo->call() ?? null)', @optionalChaining('$foo->call()?')); // deprecated - Assert::same('($foo?->call() ?? null)', @optionalChaining('$foo?->call()?')); // deprecated - Assert::same('($foo?->call() ?? null) + 10', @optionalChaining('$foo?->call()? + 10')); // deprecated - Assert::same('($foo->call() ?? null) + 10', @optionalChaining('$foo->call()? + 10')); // deprecated - Assert::same('(($foo->call() ?? null))', @optionalChaining('($foo->call()?)')); // deprecated - Assert::same('($foo?->call())', optionalChaining('($foo?->call())')); - Assert::same('(($foo?->call() ?? null))', @optionalChaining('($foo?->call()?)')); // deprecated - Assert::same('($foo?->call( ($a ?? null) ) ?? null)', @optionalChaining('$foo?->call( $a? )?')); // deprecated - Assert::same('($foo?->call( $a?->call() ) ?? null)', @optionalChaining('$foo?->call( $a?->call() )?')); // deprecated -}); - - test('mixed', function () { - Assert::same('($foo->prop ?? null) + ($foo?->prop ?? null)', @optionalChaining('$foo->prop? + $foo?->prop?')); // deprecated - Assert::same('$var->prop->elem[1]->call(2)->item', optionalChaining('$var->prop->elem[1]->call(2)->item')); Assert::same('$var?->prop->elem[1]->call(2)->item', optionalChaining('$var?->prop->elem[1]->call(2)->item')); Assert::same('$var->prop?->elem[1]->call(2)->item', optionalChaining('$var->prop?->elem[1]->call(2)->item')); Assert::same('$var->prop->elem[1]?->call(2)->item', optionalChaining('$var->prop->elem[1]?->call(2)->item')); Assert::same('$var->prop->elem[1]->call(2)?->item', optionalChaining('$var->prop->elem[1]->call(2)?->item')); - Assert::same('($var->prop->elem[1]->call(2)->item ?? null)', @optionalChaining('$var->prop->elem[1]->call(2)->item?')); // deprecated -}); - - -test('not allowed', function () { - Assert::same('$foo ?(hello)', optionalChaining('$foo?(hello)')); - Assert::same('$foo->foo ?(hello)', optionalChaining('$foo->foo?(hello)')); - - Assert::same('$foo ?[1]', optionalChaining('$foo?[1]')); // not allowed due to collision with short ternary - - Assert::same('Class::$prop?', optionalChaining('Class::$prop?')); - Assert::same('$$var?', optionalChaining('$$var?')); -}); - - -test('ternary', function () { - Assert::same('$a ?:$b', optionalChaining('$a?:$b')); - Assert::same('$a ? : $b', optionalChaining('$a ? : $b')); - Assert::same('$a ?? $b', optionalChaining('$a ?? $b')); - Assert::same('$a ? $a->a() : $a', optionalChaining('$a ? $a->a() : $a')); - - Assert::same('$a ? [1, 2, ([3 ? 2 : 1])]: $b', optionalChaining('$a ? [1, 2, ([3 ? 2 : 1])]: $b')); - Assert::same('$a->foo ? [1, 2, ([3 ? 2 : 1])] : $b', optionalChaining('$a->foo ? [1, 2, ([3 ? 2 : 1])] : $b')); - Assert::same('$a?->foo ? [1, 2, ([3 ? 2 : 1])] : $b', optionalChaining('$a?->foo ? [1, 2, ([3 ? 2 : 1])] : $b')); - Assert::same('($a?->foo ?? null) ? [1, 2, ([3 ? 2 : 1])] : $b', @optionalChaining('$a?->foo? ? [1, 2, ([3 ? 2 : 1])] : $b')); // deprecated - Assert::same('($a->foo ?? null) ? [1, 2, ([3 ? 2 : 1])] : $b', @optionalChaining('$a->foo? ? [1, 2, ([3 ? 2 : 1])] : $b')); // deprecated - - Assert::same('$a ? \Foo::BAR : \Foo::BAR', optionalChaining('$a ? \Foo::BAR : \Foo::BAR')); }); diff --git a/tests/Latte/PhpWriter.optionalChainingPass().phpt b/tests/Latte/PhpWriter.optionalChainingPass().phpt index 50e695fee..57c6a72b4 100644 --- a/tests/Latte/PhpWriter.optionalChainingPass().phpt +++ b/tests/Latte/PhpWriter.optionalChainingPass().phpt @@ -19,88 +19,29 @@ function optionalChaining($code) } -test('vars', function () { // deprecated - Assert::same('$a', optionalChaining('$a')); - Assert::same('($a ?? null)', @optionalChaining('$a?')); - Assert::same('(($a ?? null))', @optionalChaining('($a?)')); - Assert::same('a?', optionalChaining('a?')); -}); - - -test('indexes', function () { // deprecated - Assert::same('($foo[1] ?? null)', @optionalChaining('$foo[1]?')); - Assert::same('(($foo[1] ?? null))', @optionalChaining('($foo[1]?)')); -}); - - test('properties', function () { Assert::same('(($ʟ_tmp = $foo) === null ? null : $ʟ_tmp->prop)', optionalChaining('$foo?->prop')); - Assert::same('($foo->prop ?? null)', @optionalChaining('$foo->prop?')); // deprecated - Assert::same('(($ʟ_tmp = $foo) === null ? null : ($ʟ_tmp->prop ?? null))', @optionalChaining('$foo?->prop?')); // deprecated - Assert::same('(($ʟ_tmp = $foo) === null ? null : ($ʟ_tmp->prop ?? null)) + 10', @optionalChaining('$foo?->prop? + 10')); // deprecated - Assert::same('($foo->prop ?? null) + 10', @optionalChaining('$foo->prop? + 10')); // deprecated - Assert::same('(($foo->prop ?? null))', @optionalChaining('($foo->prop?)')); // deprecated + Assert::same('(($ʟ_tmp = $foo) === null ? null : $ʟ_tmp->prop) + 10', optionalChaining('$foo?->prop + 10')); Assert::same('((($ʟ_tmp = $foo) === null ? null : $ʟ_tmp->prop))', optionalChaining('($foo?->prop)')); - Assert::same('[(($ʟ_tmp = $foo) === null ? null : ($ʟ_tmp->prop ?? null))]', @optionalChaining('[$foo?->prop?]')); // deprecated + Assert::same('[(($ʟ_tmp = $foo) === null ? null : $ʟ_tmp->prop)]', optionalChaining('[$foo?->prop]')); // variable Assert::same('(($ʟ_tmp = $foo) === null ? null : $ʟ_tmp->$prop)', optionalChaining('$foo?->$prop')); - Assert::same('($foo->$prop ?? null)', @optionalChaining('$foo->$prop?')); // deprecated - - // static - Assert::same('(($ʟ_tmp = $foo ?? null) === null ? null : $ʟ_tmp::$prop)', @optionalChaining('$foo?::$prop')); // deprecated - Assert::same('($foo::$prop ?? null)', @optionalChaining('$foo::$prop?')); // deprecated }); test('calling', function () { Assert::same('(($ʟ_tmp = $foo) === null ? null : $ʟ_tmp->call())', optionalChaining('$foo?->call()')); - Assert::same('($foo->call() ?? null)', @optionalChaining('$foo->call()?')); // deprecated - Assert::same('(($ʟ_tmp = $foo) === null ? null : ($ʟ_tmp->call() ?? null))', @optionalChaining('$foo?->call()?')); // deprecated - Assert::same('(($ʟ_tmp = $foo) === null ? null : ($ʟ_tmp->call() ?? null)) + 10', @optionalChaining('$foo?->call()? + 10')); // deprecated - Assert::same('($foo->call() ?? null) + 10', @optionalChaining('$foo->call()? + 10')); // deprecated - Assert::same('(($foo->call() ?? null))', @optionalChaining('($foo->call()?)')); // deprecated + Assert::same('(($ʟ_tmp = $foo) === null ? null : $ʟ_tmp->call()) + 10', optionalChaining('$foo?->call() + 10')); Assert::same('((($ʟ_tmp = $foo) === null ? null : $ʟ_tmp->call()))', optionalChaining('($foo?->call())')); - Assert::same('((($ʟ_tmp = $foo) === null ? null : ($ʟ_tmp->call() ?? null)))', @optionalChaining('($foo?->call()?)')); // deprecated - Assert::same('(($ʟ_tmp = $foo) === null ? null : ($ʟ_tmp->call( ($a ?? null) ) ?? null))', @optionalChaining('$foo?->call( $a? )?')); // deprecated - Assert::same('(($ʟ_tmp = $foo) === null ? null : ($ʟ_tmp->call( (($ʟ_tmp = $a) === null ? null : $ʟ_tmp->call()) ) ?? null))', @optionalChaining('$foo?->call( $a?->call() )?')); // deprecated + Assert::same('(($ʟ_tmp = $foo) === null ? null : (($ʟ_tmp = $ʟ_tmp->call( (($ʟ_tmp = $a) === null ? null : $ʟ_tmp->call()) )) === null ? null : $ʟ_tmp->x))', optionalChaining('$foo?->call( $a?->call() )?->x')); }); test('mixed', function () { - Assert::same('($foo->prop ?? null) + (($ʟ_tmp = $foo) === null ? null : ($ʟ_tmp->prop ?? null))', @optionalChaining('$foo->prop? + $foo?->prop?')); // deprecated - Assert::same('$var->prop->elem[1]->call(2)->item', optionalChaining('$var->prop->elem[1]->call(2)->item')); Assert::same('(($ʟ_tmp = $var) === null ? null : $ʟ_tmp->prop->elem[1]->call(2)->item)', optionalChaining('$var?->prop->elem[1]->call(2)->item')); Assert::same('(($ʟ_tmp = $var->prop) === null ? null : $ʟ_tmp->elem[1]->call(2)->item)', optionalChaining('$var->prop?->elem[1]->call(2)->item')); Assert::same('(($ʟ_tmp = $var->prop->elem[1]) === null ? null : $ʟ_tmp->call(2)->item)', optionalChaining('$var->prop->elem[1]?->call(2)->item')); Assert::same('(($ʟ_tmp = $var->prop->elem[1]->call(2)) === null ? null : $ʟ_tmp->item)', optionalChaining('$var->prop->elem[1]->call(2)?->item')); - Assert::same('($var->prop->elem[1]->call(2)->item ?? null)', @optionalChaining('$var->prop->elem[1]->call(2)->item?')); // deprecated -}); - - -test('not allowed', function () { - Assert::same('$foo ?(hello)', optionalChaining('$foo?(hello)')); - Assert::same('$foo->foo ?(hello)', optionalChaining('$foo->foo?(hello)')); - - Assert::same('$foo ?[1]', optionalChaining('$foo?[1]')); // not allowed due to collision with short ternary - - Assert::same('Class::$prop?', optionalChaining('Class::$prop?')); - Assert::same('$$var?', optionalChaining('$$var?')); -}); - - -test('ternary', function () { - Assert::same('$a ?:$b', optionalChaining('$a?:$b')); - Assert::same('$a ? : $b', optionalChaining('$a ? : $b')); - Assert::same('$a ?? $b', optionalChaining('$a ?? $b')); - Assert::same('$a ? $a->a() : $a', optionalChaining('$a ? $a->a() : $a')); - - Assert::same('$a ? [1, 2, ([3 ? 2 : 1])]: $b', optionalChaining('$a ? [1, 2, ([3 ? 2 : 1])]: $b')); - Assert::same('$a->foo ? [1, 2, ([3 ? 2 : 1])] : $b', optionalChaining('$a->foo ? [1, 2, ([3 ? 2 : 1])] : $b')); - Assert::same('(($ʟ_tmp = $a) === null ? null : $ʟ_tmp->foo) ? [1, 2, ([3 ? 2 : 1])] : $b', optionalChaining('$a?->foo ? [1, 2, ([3 ? 2 : 1])] : $b')); - Assert::same('(($ʟ_tmp = $a) === null ? null : ($ʟ_tmp->foo ?? null)) ? [1, 2, ([3 ? 2 : 1])] : $b', @optionalChaining('$a?->foo? ? [1, 2, ([3 ? 2 : 1])] : $b')); // deprecated - Assert::same('($a->foo ?? null) ? [1, 2, ([3 ? 2 : 1])] : $b', @optionalChaining('$a->foo? ? [1, 2, ([3 ? 2 : 1])] : $b')); // deprecated - - Assert::same('$a ? \Foo::BAR : \Foo::BAR', optionalChaining('$a ? \Foo::BAR : \Foo::BAR')); }); diff --git a/tests/Latte/Policy.properties.operators.phpt b/tests/Latte/Policy.properties.operators.phpt index a20dd479c..354f23a49 100644 --- a/tests/Latte/Policy.properties.operators.phpt +++ b/tests/Latte/Policy.properties.operators.phpt @@ -39,11 +39,10 @@ $template = <<<'EOD' {=$obj?->bar} {=$obj?->$prop} -{=$obj?::$static} {=$obj??->bar} EOD; -@$latte->compile($template); +$latte->compile($template); Assert::equal( [ 'macros' => Expect::type('array'), @@ -52,7 +51,7 @@ Assert::equal( ); -@$latte->renderToString($template); +$latte->renderToString($template); Assert::equal( [ 'macros' => Expect::type('array'), @@ -64,7 +63,6 @@ Assert::equal( ['MyClass', 'static'], ['MyClass', 'bar'], ['MyClass', 'bar'], - ['MyClass', 'static'], ['MyClass', 'bar'], ], ], From 29bee5344536a48b34a39209d55c0293e0ff63a4 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 3 Nov 2020 00:38:47 +0100 Subject: [PATCH 11/20] PhpWriter: supports named arguments in modifiers (in PHP 8) --- src/Latte/Compiler/PhpWriter.php | 3 +-- tests/Latte/PhpWriter.formatArgs().phpt | 5 +++-- tests/Latte/PhpWriter.formatModifiers().phpt | 9 +++++++-- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/Latte/Compiler/PhpWriter.php b/src/Latte/Compiler/PhpWriter.php index 7efd79657..f7ab29244 100644 --- a/src/Latte/Compiler/PhpWriter.php +++ b/src/Latte/Compiler/PhpWriter.php @@ -761,9 +761,8 @@ public function modifierPass(MacroTokens $tokens, $var, bool $isContent = false) && $tokens->isPrev(',', ':') && $tokens->isNext(':') ) { - $hint = (clone $tokens)->reset()->joinAll(); - trigger_error("Colon as argument separator is deprecated, use comma in '$hint'.", E_USER_DEPRECATED); $res->append($tokens->currentToken()); + $res->append($tokens->nextToken(':')); } else { $res->append($tokens->currentToken()); diff --git a/tests/Latte/PhpWriter.formatArgs().phpt b/tests/Latte/PhpWriter.formatArgs().phpt index 6cfef9ae1..cc98e8c4c 100644 --- a/tests/Latte/PhpWriter.formatArgs().phpt +++ b/tests/Latte/PhpWriter.formatArgs().phpt @@ -111,8 +111,8 @@ test('inline modifiers', function () { Assert::same("'foo' => (\$this->filters->mod)(\$val, 'param', \"param2)\")", formatArgs('foo => ($val|mod:param,"param2)")')); Assert::same("'foo' => (\$this->filters->mod2)((\$this->filters->mod)(\$val))", formatArgs('foo => ($val|mod|mod2)')); - Assert::same("'foo' => (\$this->filters->mod)(\$val, 'param', (\$this->filters->mod2)(1))", @formatArgs('foo => ($val|mod:param:(1|mod2))')); // deprecated : - Assert::same("'foo' => (\$this->filters->mod)(\$val, 'param', (\$this->filters->mod2)(1, round((\$this->filters->foo)(2))))", @formatArgs('foo => ($val|mod:param:(1|mod2:round((2|foo))))')); // deprecated : + Assert::same("'foo' => (\$this->filters->mod)(\$val, 'param', (\$this->filters->mod2)(1))", formatArgs('foo => ($val|mod:param,(1|mod2))')); + Assert::same("'foo' => (\$this->filters->mod)(\$val, 'param', (\$this->filters->mod2)(1, round((\$this->filters->foo)(2))))", formatArgs('foo => ($val|mod:param,(1|mod2:round((2|foo))))')); Assert::same("'foo' => foo(\$val)", formatArgs('foo => foo($val)')); Assert::same("'foo' => foo((\$this->filters->bar)(\$val))", formatArgs('foo => foo($val|bar)')); Assert::same('foo(($this->filters->bar)($val),($this->filters->lorem)( $val))', formatArgs('foo($val|bar, $val|lorem)')); @@ -154,6 +154,7 @@ test('named arguments', function () { Assert::same('func(a: 1, b: 2)', formatArgs('func(a: 1, b: 2)')); Assert::same("func(a: 1, ('a' ?'b': 2))", formatArgs('func(a: 1, (a ?b: 2))')); // ternary Assert::same("a: 1, 'a' ? 'b': 2", formatArgs('a: 1, a ? b: 2')); + Assert::same('\'foo\' => ($this->filters->mod)($val, param:($this->filters->mod2)(1))', formatArgs('foo => ($val|mod:param:(1|mod2))')); }); diff --git a/tests/Latte/PhpWriter.formatModifiers().phpt b/tests/Latte/PhpWriter.formatModifiers().phpt index 5985d17ae..91a84f5a5 100644 --- a/tests/Latte/PhpWriter.formatModifiers().phpt +++ b/tests/Latte/PhpWriter.formatModifiers().phpt @@ -41,12 +41,11 @@ test('common', function () { test('arguments', function () { Assert::same('($this->filters->mod)(@, $obj->a, 2)', formatModifiers('@', 'mod:$obj->a:2')); - Assert::same('($this->filters->mod)(@, \'arg1\', 2, $var["pocet"])', @formatModifiers('@', 'mod:arg1:2:$var["pocet"]')); // deprecated : Assert::same('($this->filters->mod)(@, \'arg1\', 2, $var["pocet"])', formatModifiers('@', 'mod,arg1,2,$var["pocet"]')); Assert::same('($this->filters->mod)(@, " :a:b:c", "", 3, "")', @formatModifiers('@', 'mod:" :a:b:c":"":3:""')); // deprecated : Assert::same('($this->filters->mod)(@, "\":a:b:c")', formatModifiers('@', 'mod:"\\":a:b:c"')); Assert::same("(\$this->filters->mod)(@, '\\':a:b:c')", formatModifiers('@', "mod:'\\':a:b:c'")); - Assert::same('($this->filters->mod)(@ , \'param\' , \'param\')', @formatModifiers('@', 'mod : param : param')); // deprecated : + Assert::same('($this->filters->mod)(@ , \'param\' , \'param\')', formatModifiers('@', 'mod : param , param')); Assert::same('($this->filters->mod)(@, $var, 0, -0.0, "str", \'str\')', formatModifiers('@', 'mod, $var, 0, -0.0, "str", \'str\'')); Assert::same('($this->filters->mod)(@, true, false, null)', formatModifiers('@', 'mod: true, false, null')); Assert::same('($this->filters->mod)(@, TRUE, FALSE, NULL)', formatModifiers('@', 'mod: TRUE, FALSE, NULL')); @@ -80,3 +79,9 @@ test('optionalChainingPass', function () { formatModifiers('@', 'mod:$var??->prop??->elem[1]??->call(2)??->item') ); }); + + +test('named arguments', function () { + Assert::same('($this->filters->mod)(@, a: 1)', formatModifiers('@', 'mod:a: 1')); + Assert::same('($this->filters->mod)(@, a: 1, b: 2)', formatModifiers('@', 'mod:a: 1, b: 2')); +}); From 9790492b2868f43c3b1d5c00688cdd1b14bb15d4 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 3 Nov 2020 00:50:17 +0100 Subject: [PATCH 12/20] Colons as argument separator in modifiers are deprecated everywhere (BC break) It will be replaced by named arguments --- src/Latte/Compiler/PhpWriter.php | 4 ++++ tests/Latte/PhpWriter.formatArgs().phpt | 1 - tests/Latte/PhpWriter.formatModifiers().phpt | 9 ++++----- tests/Latte/templates/filters.general.latte | 4 ++-- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Latte/Compiler/PhpWriter.php b/src/Latte/Compiler/PhpWriter.php index f7ab29244..5e49dba04 100644 --- a/src/Latte/Compiler/PhpWriter.php +++ b/src/Latte/Compiler/PhpWriter.php @@ -765,6 +765,10 @@ public function modifierPass(MacroTokens $tokens, $var, bool $isContent = false) $res->append($tokens->nextToken(':')); } else { + if ($tokens->isNext(':') && !$tokens->depth) { + $hint = (clone $tokens)->reset()->joinAll(); + trigger_error("Colon as argument separator is deprecated, use comma in '$hint'.", E_USER_DEPRECATED); + } $res->append($tokens->currentToken()); } } elseif ($tokens->isCurrent($tokens::T_SYMBOL)) { diff --git a/tests/Latte/PhpWriter.formatArgs().phpt b/tests/Latte/PhpWriter.formatArgs().phpt index cc98e8c4c..0796d79e5 100644 --- a/tests/Latte/PhpWriter.formatArgs().phpt +++ b/tests/Latte/PhpWriter.formatArgs().phpt @@ -103,7 +103,6 @@ test('inline modifiers', function () { Assert::same('($this->filters->mod)(@)', formatArgs('(@|mod)')); Assert::same('($this->filters->mod3)(($this->filters->mod2)(($this->filters->mod1)(@)))', formatArgs('(@|mod1|mod2|mod3)')); Assert::same('($this->filters->mod3)(($this->filters->mod2)(($this->filters->mod1)(@)))', formatArgs('((@|mod1)|mod2|mod3)')); - Assert::same('($this->filters->mod)(@, 1, 2, $var["pocet"])', formatArgs('(@|mod:1:2:$var["pocet"])')); Assert::same('($this->filters->mod)(@, 1, 2, $var["pocet"])', formatArgs('(@|mod,1,2,$var["pocet"])')); Assert::same('($this->filters->mod)(@, $var, 0, -0.0, "s\"\'tr", \'s"\\\'tr\')', formatArgs('(@|mod, $var, 0, -0.0, "s\"\'tr", \'s"\\\'tr\')')); Assert::same('($this->filters->mod)(@, array(1))', formatArgs('(@|mod: array(1))')); diff --git a/tests/Latte/PhpWriter.formatModifiers().phpt b/tests/Latte/PhpWriter.formatModifiers().phpt index 91a84f5a5..4550f5c60 100644 --- a/tests/Latte/PhpWriter.formatModifiers().phpt +++ b/tests/Latte/PhpWriter.formatModifiers().phpt @@ -40,12 +40,11 @@ test('common', function () { test('arguments', function () { - Assert::same('($this->filters->mod)(@, $obj->a, 2)', formatModifiers('@', 'mod:$obj->a:2')); Assert::same('($this->filters->mod)(@, \'arg1\', 2, $var["pocet"])', formatModifiers('@', 'mod,arg1,2,$var["pocet"]')); - Assert::same('($this->filters->mod)(@, " :a:b:c", "", 3, "")', @formatModifiers('@', 'mod:" :a:b:c":"":3:""')); // deprecated : - Assert::same('($this->filters->mod)(@, "\":a:b:c")', formatModifiers('@', 'mod:"\\":a:b:c"')); - Assert::same("(\$this->filters->mod)(@, '\\':a:b:c')", formatModifiers('@', "mod:'\\':a:b:c'")); - Assert::same('($this->filters->mod)(@ , \'param\' , \'param\')', formatModifiers('@', 'mod : param , param')); + Assert::same('($this->filters->mod)(@, " ,a,b,c", "", 3, "")', @formatModifiers('@', 'mod:" ,a,b,c","",3,""')); + Assert::same('($this->filters->mod)(@, "\",a,b,c")', formatModifiers('@', 'mod:"\\",a,b,c"')); + Assert::same("(\$this->filters->mod)(@, '\\',a,b,c')", formatModifiers('@', "mod:'\\',a,b,c'")); + Assert::same('($this->filters->mod)(@ , \'param\' , \'param\')', @formatModifiers('@', 'mod , param , param')); Assert::same('($this->filters->mod)(@, $var, 0, -0.0, "str", \'str\')', formatModifiers('@', 'mod, $var, 0, -0.0, "str", \'str\'')); Assert::same('($this->filters->mod)(@, true, false, null)', formatModifiers('@', 'mod: true, false, null')); Assert::same('($this->filters->mod)(@, TRUE, FALSE, NULL)', formatModifiers('@', 'mod: TRUE, FALSE, NULL')); diff --git a/tests/Latte/templates/filters.general.latte b/tests/Latte/templates/filters.general.latte index ee2a7ba0c..a228dc804 100644 --- a/tests/Latte/templates/filters.general.latte +++ b/tests/Latte/templates/filters.general.latte @@ -23,7 +23,7 @@
  • {(int)$hello*0 |types:0,0.0,"0"}
  • -
  • {(int)$hello*1 |types:1:"1"}
  • +
  • {(int)$hello*1 |types:1,"1"}
  • {$hello |types:true,null,false}
  • {$hello |types:TRUE,NULL,FALSE}
  • {$hello |types:'',"","$hello"}
  • @@ -73,5 +73,5 @@ Nested blocks: {block|striphtml|truncate:20} Outer {block|striphtml|upper} Inn
    • padLeft: {$hello |padleft:20}
    • -
    • padRight: {$hello |padright:20:'*'}
    • +
    • padRight: {$hello |padright:20,'*'}
    From 1dabdcf84720e354160f44401afe03e9df56c8f6 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 25 May 2020 12:59:59 +0200 Subject: [PATCH 13/20] Auto-empty is deprecated --- src/Latte/Compiler/Compiler.php | 4 ++++ src/Latte/Macro.php | 2 +- tests/Latte/Compiler.flag.AUTO_EMPTY.phpt | 14 +++++++++----- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/Latte/Compiler/Compiler.php b/src/Latte/Compiler/Compiler.php index b8a43ea78..a0dea8573 100644 --- a/src/Latte/Compiler/Compiler.php +++ b/src/Latte/Compiler/Compiler.php @@ -416,6 +416,10 @@ private function processMacroTag(Token $token): void && ($t->type !== Token::MACRO_TAG || $t->name !== $token->name) && ($t->type !== Token::HTML_ATTRIBUTE_BEGIN || $t->name !== Parser::N_PREFIX . $token->name)); $token->empty = $t ? !$t->closing : true; + if ($token->empty) { + $tmp = substr($token->text, 0, -1) . ' /}'; + trigger_error("Auto-empty behaviour is deprecated, replace {$token->text} with $tmp in line " . $this->getLine(), E_USER_DEPRECATED); + } } $node = $this->openMacro($token->name, $token->value, $token->modifiers, $isRightmost); if ($token->empty) { diff --git a/src/Latte/Macro.php b/src/Latte/Macro.php index 2513356e0..d9b2562e2 100644 --- a/src/Latte/Macro.php +++ b/src/Latte/Macro.php @@ -16,7 +16,7 @@ interface Macro { const - AUTO_EMPTY = 4, + AUTO_EMPTY = 4, // deprecated AUTO_CLOSE = 64, ALLOWED_IN_HEAD = 128, DEFAULT_FLAGS = 0; diff --git a/tests/Latte/Compiler.flag.AUTO_EMPTY.phpt b/tests/Latte/Compiler.flag.AUTO_EMPTY.phpt index 50e679388..6bd515f2c 100644 --- a/tests/Latte/Compiler.flag.AUTO_EMPTY.phpt +++ b/tests/Latte/Compiler.flag.AUTO_EMPTY.phpt @@ -47,24 +47,28 @@ $latte->getCompiler()->addMacro('test_auto', $macro, Macro::AUTO_EMPTY); $latte->compile('{test_auto} ... {/test_auto}'); Assert::same(' ... ', $macro->nodes[0]->content); -$latte->compile('{test_auto}'); +Assert::error(function () use ($latte) { + $latte->compile('{test_auto}'); +}, E_USER_DEPRECATED, 'Auto-empty behaviour is deprecated, replace {test_auto} with {test_auto /} in line 1'); + +@$latte->compile('{test_auto}'); // deprecated Assert::same('', $macro->nodes[0]->content); $latte->compile('{test_auto} {if true} {/if} {/test_auto}'); Assert::match('%A% if (true) %A%', $macro->nodes[0]->content); -$latte->compile('{test_auto} {test_auto} ... {/test_auto}'); +@$latte->compile('{test_auto} {test_auto} ... {/test_auto}'); // deprecated Assert::same('', $macro->nodes[0]->content); Assert::same(' ... ', $macro->nodes[1]->content); -$latte->compile('{test_auto}
    '); +@$latte->compile('{test_auto}
    '); // deprecated Assert::same('', $macro->nodes[0]->content); Assert::match('', $macro->nodes[1]->content); Assert::exception(function () use ($latte) { - $latte->compile('{test_auto}
    '); + @$latte->compile('{test_auto}
    '); // deprecated }, Latte\CompileException::class, 'Missing
    for n:test_auto'); Assert::exception(function () use ($latte) { - $latte->compile('{test_auto}
    {/test_auto}'); + @$latte->compile('{test_auto}
    {/test_auto}'); // deprecated }, Latte\CompileException::class, 'Unexpected {/test_auto}'); From 32790007415808a2c10494d6fb36aa1b49a0fdaa Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 15 Jun 2020 15:41:35 +0200 Subject: [PATCH 14/20] Empty closing macro {/} is deprecated --- src/Latte/Compiler/Compiler.php | 4 ++++ tests/Latte/BlockMacros.include.block.phpt | 16 ++++++++-------- tests/Latte/Compiler.macro.lines.phpt | 2 +- tests/Latte/templates/filters.general.latte | 2 +- tests/Latte/templates/general.latte | 2 +- 5 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/Latte/Compiler/Compiler.php b/src/Latte/Compiler/Compiler.php index a0dea8573..da202aeca 100644 --- a/src/Latte/Compiler/Compiler.php +++ b/src/Latte/Compiler/Compiler.php @@ -653,6 +653,10 @@ public function closeMacro( throw new CompileException("Unexpected $name" . ($node ? ', expecting ' . self::printEndTag($node->prefix ? $this->htmlNode : $node) : '')); } + if ($name === '') { + trigger_error("Empty closing tag {/} is deprecated, use {/$node->name} on line " . $this->getLine(), E_USER_DEPRECATED); + } + $this->macroNode = $node->parentNode; if ($node->args === '') { $node->setArgs($args); diff --git a/tests/Latte/BlockMacros.include.block.phpt b/tests/Latte/BlockMacros.include.block.phpt index a680cc13c..ca4819929 100644 --- a/tests/Latte/BlockMacros.include.block.phpt +++ b/tests/Latte/BlockMacros.include.block.phpt @@ -14,16 +14,16 @@ require __DIR__ . '/../bootstrap.php'; $latte = new Latte\Engine; $latte->setLoader(new Latte\Loaders\StringLoader([ - 'main1' => '{define block}[block {$var}]{/} before {include block, var => 1} after', - 'main2' => '{define block}[block {$var}]{/} before {include #block, var => 1} after', - 'main3' => '{define block-2}[block {$var}]{/} before {include block-2, var => 1} after', - 'main4' => '{define block.2}[block {$var}]{/} before {include block.2, var => 1} after', - 'main5' => '{define block.2}[block {$var}]{/} before {include #block.2, var => 1} after', - 'main6' => '{define block.2}[block {$var}]{/} before {include block block.2, var => 1} after', + 'main1' => '{define block}[block {$var}]{/define} before {include block, var => 1} after', + 'main2' => '{define block}[block {$var}]{/define} before {include #block, var => 1} after', + 'main3' => '{define block-2}[block {$var}]{/define} before {include block-2, var => 1} after', + 'main4' => '{define block.2}[block {$var}]{/define} before {include block.2, var => 1} after', + 'main5' => '{define block.2}[block {$var}]{/define} before {include #block.2, var => 1} after', + 'main6' => '{define block.2}[block {$var}]{/define} before {include block block.2, var => 1} after', 'main7' => '{define block}[block {$var}]{/define} {var $name = block} before {include block $name, var => 1} after', - 'main8' => '{define block}block {$var}{/} before {include block, var => 1|striptags} after', - 'main9' => '{define block}block {$var}{/} before {include block true ? "block", var => 2} after', + 'main8' => '{define block}block {$var}{/define} before {include block, var => 1|striptags} after', + 'main9' => '{define block}block {$var}{/define} before {include block true ? "block", var => 2} after', ])); Assert::match( diff --git a/tests/Latte/Compiler.macro.lines.phpt b/tests/Latte/Compiler.macro.lines.phpt index 8d31374f5..055103d6f 100644 --- a/tests/Latte/Compiler.macro.lines.phpt +++ b/tests/Latte/Compiler.macro.lines.phpt @@ -80,7 +80,7 @@ Assert::match( $latte->compile(' {one} - {/ + {/one }') ); diff --git a/tests/Latte/templates/filters.general.latte b/tests/Latte/templates/filters.general.latte index a228dc804..c27e5f1fa 100644 --- a/tests/Latte/templates/filters.general.latte +++ b/tests/Latte/templates/filters.general.latte @@ -8,7 +8,7 @@
  • translated: {_$hello|truncate:3}
  • Translated HTML: {_}{$hello}{/_}
  • Translated HTML: {_}ahoj{/_}
  • -
  • Translated HTML: {_}ahoj{/}
  • +
  • Translated HTML: {_}ahoj{/_}
  • Translated HTML: {_'ahoj|ahojahojahojahoj'}
  • spaces: {$hello |types , '' , "" , "$hello" }
  • dynamic: {$hello |dynamic} {$hello |dynamic2}
  • diff --git a/tests/Latte/templates/general.latte b/tests/Latte/templates/general.latte index 011627b84..9a1145ab9 100644 --- a/tests/Latte/templates/general.latte +++ b/tests/Latte/templates/general.latte @@ -40,7 +40,7 @@ {$any} {else} none -{/} +{/if} {foreach array(true) as $foo} From 814db4169c632e8c476f5f29dbc865190642afa0 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 15 Nov 2020 21:23:35 +0100 Subject: [PATCH 15/20] phpdoc wip --- phpstan.neon | 2 +- src/Latte/Macros/CoreMacros.php | 3 +- src/Latte/Runtime/Filters.php | 95 ++++++++--------------- src/Latte/Runtime/Html.php | 3 + src/Latte/Runtime/SnippetBridge.php | 1 + tests/Latte/Filters.htmlAttributes().phpt | 2 - 6 files changed, 40 insertions(+), 66 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index d7357c01a..0dfd65548 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,5 +1,5 @@ parameters: - level: 5 + level: 6 paths: - src diff --git a/src/Latte/Macros/CoreMacros.php b/src/Latte/Macros/CoreMacros.php index fcb7012a5..3a0abbf13 100644 --- a/src/Latte/Macros/CoreMacros.php +++ b/src/Latte/Macros/CoreMacros.php @@ -98,9 +98,8 @@ public static function install(Latte\Compiler $compiler): void /** * Initializes before template parsing. - * @return void */ - public function initialize() + public function initialize(): void { $this->overwrittenVars = []; $this->idCounter = 0; diff --git a/src/Latte/Runtime/Filters.php b/src/Latte/Runtime/Filters.php index e39f6a345..9eb4ae5c2 100644 --- a/src/Latte/Runtime/Filters.php +++ b/src/Latte/Runtime/Filters.php @@ -21,17 +21,16 @@ */ class Filters { - /** @deprecated */ + /** @var string @deprecated */ public static $dateFormat = '%x'; - /** @internal @var bool use XHTML syntax? */ + /** @var bool @internal use XHTML syntax? */ public static $xhtml = false; /** * Escapes string for use everywhere inside HTML (except for comments). - * @param mixed $s plain text - * @return string HTML + * @param string|int|float $s */ public static function escapeHtml($s): string { @@ -41,8 +40,7 @@ public static function escapeHtml($s): string /** * Escapes string for use inside HTML text. - * @param mixed $s plain text or HtmlStringable - * @return string HTML + * @param string|int|float|HtmlStringable|\Nette\Utils\IHtmlString $s */ public static function escapeHtmlText($s): string { @@ -57,8 +55,7 @@ public static function escapeHtmlText($s): string /** * Escapes string for use inside HTML attribute value. - * @param mixed $s plain text - * @return string HTML + * @param string|int|float|HtmlStringable|\Nette\Utils\IHtmlString $s */ public static function escapeHtmlAttr($s, bool $double = true): string { @@ -73,8 +70,7 @@ public static function escapeHtmlAttr($s, bool $double = true): string /** * Escapes HTML for use inside HTML attribute. - * @param mixed $s HTML text - * @return string HTML + * @param string|int|float $s HTML text */ public static function escapeHtmlAttrConv($s): string { @@ -84,8 +80,7 @@ public static function escapeHtmlAttrConv($s): string /** * Escapes string for use inside HTML attribute name. - * @param string $s plain text - * @return string HTML + * @param string|int|float $s */ public static function escapeHtmlAttrUnquoted($s): string { @@ -98,8 +93,7 @@ public static function escapeHtmlAttrUnquoted($s): string /** * Escapes string for use inside HTML/XML comments. - * @param string $s plain text - * @return string HTML + * @param string|int|float $s */ public static function escapeHtmlComment($s): string { @@ -117,8 +111,7 @@ public static function escapeHtmlComment($s): string /** * Escapes string for use everywhere inside XML (except for comments). - * @param string $s plain text - * @return string XML + * @param string|int|float $s */ public static function escapeXml($s): string { @@ -132,8 +125,7 @@ public static function escapeXml($s): string /** * Escapes string for use inside XML attribute name. - * @param string $s plain text - * @return string XML + * @param string|int|float $s */ public static function escapeXmlAttrUnquoted($s): string { @@ -146,8 +138,7 @@ public static function escapeXmlAttrUnquoted($s): string /** * Escapes string for use inside CSS template. - * @param string $s plain text - * @return string CSS + * @param string|int|float $s */ public static function escapeCss($s): string { @@ -158,8 +149,7 @@ public static function escapeCss($s): string /** * Escapes variables for use inside