@vituum/vite-plugin-latte 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/index.js +2 -2
  2. package/package.json +10 -11
  3. package/vendor/autoload.php +19 -1
  4. package/vendor/bin/latte-lint +16 -4
  5. package/vendor/composer/ClassLoader.php +72 -65
  6. package/vendor/composer/InstalledVersions.php +21 -12
  7. package/vendor/composer/autoload_classmap.php +12 -8
  8. package/vendor/composer/autoload_namespaces.php +1 -1
  9. package/vendor/composer/autoload_psr4.php +1 -1
  10. package/vendor/composer/autoload_real.php +7 -26
  11. package/vendor/composer/autoload_static.php +13 -9
  12. package/vendor/composer/installed.json +8 -8
  13. package/vendor/composer/installed.php +10 -10
  14. package/vendor/latte/latte/bin/latte-lint +8 -2
  15. package/vendor/latte/latte/composer.json +1 -1
  16. package/vendor/latte/latte/readme.md +6 -6
  17. package/vendor/latte/latte/src/Bridges/Tracy/BlueScreenPanel.php +1 -0
  18. package/vendor/latte/latte/src/Bridges/Tracy/LattePanel.php +3 -2
  19. package/vendor/latte/latte/src/Latte/Compiler/Block.php +0 -3
  20. package/vendor/latte/latte/src/Latte/Compiler/Escaper.php +103 -88
  21. package/vendor/latte/latte/src/Latte/Compiler/ExpressionBuilder.php +2 -1
  22. package/vendor/latte/latte/src/Latte/Compiler/Node.php +0 -4
  23. package/vendor/latte/latte/src/Latte/Compiler/NodeHelpers.php +0 -4
  24. package/vendor/latte/latte/src/Latte/Compiler/NodeTraverser.php +0 -4
  25. package/vendor/latte/latte/src/Latte/Compiler/Nodes/AuxiliaryNode.php +11 -3
  26. package/vendor/latte/latte/src/Latte/Compiler/Nodes/FragmentNode.php +10 -0
  27. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Html/AttributeNode.php +22 -4
  28. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Html/ElementNode.php +35 -12
  29. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/ArgumentNode.php +6 -0
  30. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/ArrayItemNode.php +12 -0
  31. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/ArrayNode.php +4 -31
  32. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/AssignNode.php +15 -1
  33. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/AssignOpNode.php +11 -0
  34. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/AuxiliaryNode.php +42 -0
  35. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/ClassConstantFetchNode.php +2 -2
  36. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/ClosureNode.php +1 -1
  37. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/ConstantFetchNode.php +8 -0
  38. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/IssetNode.php +15 -1
  39. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/PostOpNode.php +11 -0
  40. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/PreOpNode.php +11 -0
  41. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/{StaticCallNode.php → StaticMethodCallNode.php} +11 -1
  42. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/{StaticCallableNode.php → StaticMethodCallableNode.php} +11 -1
  43. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/TemporaryNode.php +38 -0
  44. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/ExpressionNode.php +24 -0
  45. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/ListItemNode.php +48 -0
  46. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/ListNode.php +56 -0
  47. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/ModifierNode.php +3 -3
  48. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Scalar/InterpolatedStringNode.php +1 -8
  49. package/vendor/latte/latte/src/Latte/Compiler/PhpHelpers.php +30 -0
  50. package/vendor/latte/latte/src/Latte/Compiler/Position.php +4 -8
  51. package/vendor/latte/latte/src/Latte/Compiler/PrintContext.php +14 -7
  52. package/vendor/latte/latte/src/Latte/Compiler/Tag.php +13 -14
  53. package/vendor/latte/latte/src/Latte/Compiler/TagLexer.php +3 -7
  54. package/vendor/latte/latte/src/Latte/Compiler/TagParser.php +52 -3
  55. package/vendor/latte/latte/src/Latte/Compiler/TagParserData.php +354 -322
  56. package/vendor/latte/latte/src/Latte/Compiler/TemplateGenerator.php +6 -5
  57. package/vendor/latte/latte/src/Latte/Compiler/TemplateLexer.php +95 -176
  58. package/vendor/latte/latte/src/Latte/Compiler/TemplateParser.php +40 -33
  59. package/vendor/latte/latte/src/Latte/Compiler/TemplateParserHtml.php +182 -126
  60. package/vendor/latte/latte/src/Latte/Compiler/Token.php +5 -9
  61. package/vendor/latte/latte/src/Latte/Compiler/TokenStream.php +6 -22
  62. package/vendor/latte/latte/src/Latte/Engine.php +37 -8
  63. package/vendor/latte/latte/src/Latte/Essential/Blueprint.php +0 -2
  64. package/vendor/latte/latte/src/Latte/Essential/CachingIterator.php +0 -4
  65. package/vendor/latte/latte/src/Latte/Essential/CoreExtension.php +14 -3
  66. package/vendor/latte/latte/src/Latte/Essential/Filters.php +8 -6
  67. package/vendor/latte/latte/src/Latte/Essential/Nodes/BlockNode.php +1 -2
  68. package/vendor/latte/latte/src/Latte/Essential/Nodes/CaptureNode.php +2 -13
  69. package/vendor/latte/latte/src/Latte/Essential/Nodes/ContentTypeNode.php +1 -1
  70. package/vendor/latte/latte/src/Latte/Essential/Nodes/DefineNode.php +1 -2
  71. package/vendor/latte/latte/src/Latte/Essential/Nodes/EmbedNode.php +1 -1
  72. package/vendor/latte/latte/src/Latte/Essential/Nodes/ExtendsNode.php +0 -3
  73. package/vendor/latte/latte/src/Latte/Essential/Nodes/FirstLastSepNode.php +1 -1
  74. package/vendor/latte/latte/src/Latte/Essential/Nodes/ForNode.php +1 -1
  75. package/vendor/latte/latte/src/Latte/Essential/Nodes/ForeachNode.php +4 -13
  76. package/vendor/latte/latte/src/Latte/Essential/Nodes/IfChangedNode.php +1 -1
  77. package/vendor/latte/latte/src/Latte/Essential/Nodes/IfContentNode.php +4 -1
  78. package/vendor/latte/latte/src/Latte/Essential/Nodes/IfNode.php +5 -3
  79. package/vendor/latte/latte/src/Latte/Essential/Nodes/IncludeBlockNode.php +5 -2
  80. package/vendor/latte/latte/src/Latte/Essential/Nodes/IterateWhileNode.php +6 -4
  81. package/vendor/latte/latte/src/Latte/Essential/Nodes/JumpNode.php +26 -23
  82. package/vendor/latte/latte/src/Latte/Essential/Nodes/NElseNode.php +88 -0
  83. package/vendor/latte/latte/src/Latte/Essential/Nodes/NTagNode.php +20 -28
  84. package/vendor/latte/latte/src/Latte/Essential/Nodes/PrintNode.php +7 -12
  85. package/vendor/latte/latte/src/Latte/Essential/Nodes/RollbackNode.php +1 -1
  86. package/vendor/latte/latte/src/Latte/Essential/Nodes/SpacelessNode.php +1 -1
  87. package/vendor/latte/latte/src/Latte/Essential/Nodes/SwitchNode.php +1 -1
  88. package/vendor/latte/latte/src/Latte/Essential/Nodes/TranslateNode.php +1 -1
  89. package/vendor/latte/latte/src/Latte/Essential/Nodes/TryNode.php +3 -4
  90. package/vendor/latte/latte/src/Latte/Essential/Nodes/WhileNode.php +1 -1
  91. package/vendor/latte/latte/src/Latte/Essential/Passes.php +9 -11
  92. package/vendor/latte/latte/src/Latte/Essential/RawPhpExtension.php +0 -2
  93. package/vendor/latte/latte/src/Latte/Essential/TranslatorExtension.php +6 -1
  94. package/vendor/latte/latte/src/Latte/Loaders/FileLoader.php +0 -2
  95. package/vendor/latte/latte/src/Latte/Loaders/StringLoader.php +0 -2
  96. package/vendor/latte/latte/src/Latte/PositionAwareException.php +1 -1
  97. package/vendor/latte/latte/src/Latte/Runtime/Block.php +0 -4
  98. package/vendor/latte/latte/src/Latte/Runtime/FilterExecutor.php +43 -51
  99. package/vendor/latte/latte/src/Latte/Runtime/FilterInfo.php +0 -2
  100. package/vendor/latte/latte/src/Latte/Runtime/Filters.php +64 -30
  101. package/vendor/latte/latte/src/Latte/Runtime/Html.php +0 -4
  102. package/vendor/latte/latte/src/Latte/Runtime/Template.php +2 -2
  103. package/vendor/latte/latte/src/Latte/Sandbox/Nodes/FunctionCallNode.php +2 -1
  104. package/vendor/latte/latte/src/Latte/Sandbox/Nodes/MethodCallNode.php +1 -1
  105. package/vendor/latte/latte/src/Latte/Sandbox/Nodes/SandboxNode.php +3 -3
  106. package/vendor/latte/latte/src/Latte/Sandbox/Nodes/{StaticCallNode.php → StaticMethodCallNode.php} +3 -3
  107. package/vendor/latte/latte/src/Latte/Sandbox/Nodes/{StaticCallableNode.php → StaticMethodCallableNode.php} +2 -2
  108. package/vendor/latte/latte/src/Latte/Sandbox/RuntimeChecker.php +0 -2
  109. package/vendor/latte/latte/src/Latte/Sandbox/SandboxExtension.php +11 -9
  110. package/vendor/latte/latte/src/Latte/Sandbox/SecurityPolicy.php +0 -2
  111. package/vendor/latte/latte/src/Latte/exceptions.php +2 -11
  112. package/vendor/latte/latte/src/Tools/Linter.php +13 -37
  113. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Html/QuotedValue.php +0 -53
  114. package/vendor/latte/latte/src/Latte/Strict.php +0 -101
@@ -9,8 +9,6 @@ declare(strict_types=1);
9
9
 
10
10
  namespace Latte\Essential;
11
11
 
12
- use Latte;
13
-
14
12
 
15
13
  /**
16
14
  * Smarter caching iterator.
@@ -29,8 +27,6 @@ use Latte;
29
27
  */
30
28
  class CachingIterator extends \CachingIterator implements \Countable
31
29
  {
32
- use Latte\Strict;
33
-
34
30
  private int $counter = 0;
35
31
  private ?self $parent = null;
36
32
 
@@ -15,6 +15,7 @@ use Latte\Compiler\Nodes\TemplateNode;
15
15
  use Latte\Compiler\Nodes\TextNode;
16
16
  use Latte\Compiler\Tag;
17
17
  use Latte\Compiler\TemplateParser;
18
+ use Latte\Runtime;
18
19
  use Latte\RuntimeException;
19
20
  use Nette;
20
21
 
@@ -24,14 +25,21 @@ use Nette;
24
25
  */
25
26
  final class CoreExtension extends Latte\Extension
26
27
  {
27
- use Latte\Strict;
28
-
29
28
  private array $functions;
29
+ private bool $strict;
30
+ private Runtime\Template $template;
30
31
 
31
32
 
32
33
  public function beforeCompile(Latte\Engine $engine): void
33
34
  {
34
35
  $this->functions = $engine->getFunctions();
36
+ $this->strict = $engine->isStrictParsing();
37
+ }
38
+
39
+
40
+ public function beforeRender(Runtime\Template $template): void
41
+ {
42
+ $this->template = $template;
35
43
  }
36
44
 
37
45
 
@@ -92,6 +100,7 @@ final class CoreExtension extends Latte\Extension
92
100
  'ifset' => [Nodes\IfNode::class, 'create'],
93
101
  'ifchanged' => [Nodes\IfChangedNode::class, 'create'],
94
102
  'n:ifcontent' => [Nodes\IfContentNode::class, 'create'],
103
+ 'n:else' => [Nodes\NElseNode::class, 'create'],
95
104
  'switch' => [Nodes\SwitchNode::class, 'create'],
96
105
  ];
97
106
  }
@@ -178,6 +187,7 @@ final class CoreExtension extends Latte\Extension
178
187
  'last' => [Filters::class, 'last'],
179
188
  'odd' => [Filters::class, 'odd'],
180
189
  'slice' => [Filters::class, 'slice'],
190
+ 'hasBlock' => fn(string $name): bool => $this->template->hasBlock($name),
181
191
  ];
182
192
  }
183
193
 
@@ -185,10 +195,11 @@ final class CoreExtension extends Latte\Extension
185
195
  public function getPasses(): array
186
196
  {
187
197
  return [
188
- 'internalVariables' => [Passes::class, 'internalVariablesPass'],
198
+ 'internalVariables' => fn(TemplateNode $node) => Passes::internalVariablesPass($node, $this->strict),
189
199
  'overwrittenVariables' => [Passes::class, 'overwrittenVariablesPass'],
190
200
  'customFunctions' => fn(TemplateNode $node) => Passes::customFunctionsPass($node, $this->functions),
191
201
  'moveTemplatePrintToHead' => [Passes::class, 'moveTemplatePrintToHeadPass'],
202
+ 'nElse' => [Nodes\NElseNode::class, 'processPass'],
192
203
  ];
193
204
  }
194
205
 
@@ -120,7 +120,7 @@ final class Filters
120
120
  } elseif ($info->contentType === ContentType::Html) {
121
121
  $s = preg_replace_callback('#<(textarea|pre).*?</\1#si', fn($m) => strtr($m[0], " \t\r\n", "\x1F\x1E\x1D\x1A"), $s);
122
122
  if (preg_last_error()) {
123
- throw new Latte\RegexpException;
123
+ throw new Latte\RuntimeException(preg_last_error_msg());
124
124
  }
125
125
 
126
126
  $s = preg_replace('#(?:^|[\r\n]+)(?=[^\r\n])#', '$0' . str_repeat($chars, $level), $s);
@@ -250,7 +250,7 @@ final class Filters
250
250
  {
251
251
  $res = preg_replace($pattern, $replacement, $subject);
252
252
  if (preg_last_error()) {
253
- throw new Latte\RegexpException;
253
+ throw new Latte\RuntimeException(preg_last_error_msg());
254
254
  }
255
255
 
256
256
  return $res;
@@ -372,9 +372,11 @@ final class Filters
372
372
 
373
373
  private static function strLength(string $s): int
374
374
  {
375
- return function_exists('mb_strlen')
376
- ? mb_strlen($s, 'UTF-8')
377
- : strlen(utf8_decode($s));
375
+ return match (true) {
376
+ extension_loaded('mbstring') => mb_strlen($s, 'UTF-8'),
377
+ extension_loaded('iconv') => iconv_strlen($s, 'UTF-8'),
378
+ default => strlen(@utf8_decode($s)), // deprecated
379
+ };
378
380
  }
379
381
 
380
382
 
@@ -386,7 +388,7 @@ final class Filters
386
388
  $charlist = preg_quote($charlist, '#');
387
389
  $s = preg_replace('#^[' . $charlist . ']+|[' . $charlist . ']+$#Du', '', (string) $s);
388
390
  if (preg_last_error()) {
389
- throw new Latte\RegexpException;
391
+ throw new Latte\RuntimeException(preg_last_error_msg());
390
392
  }
391
393
 
392
394
  return $s;
@@ -40,7 +40,7 @@ class BlockNode extends StatementNode
40
40
  {
41
41
  $tag->outputMode = $tag::OutputRemoveIndentation;
42
42
  $stream = $tag->parser->stream;
43
- $node = new static;
43
+ $node = $tag->node = new static;
44
44
 
45
45
  if (!$stream->is('|', Token::End)) {
46
46
  $layer = $tag->parser->tryConsumeTokenBeforeUnquotedString('local')
@@ -52,7 +52,6 @@ class BlockNode extends StatementNode
52
52
 
53
53
  if (!$node->block->isDynamic()) {
54
54
  $parser->checkBlockIsUnique($node->block);
55
- $tag->data->block = $node->block; // for {include}
56
55
  }
57
56
  }
58
57
 
@@ -11,9 +11,7 @@ namespace Latte\Essential\Nodes;
11
11
 
12
12
  use Latte\CompileException;
13
13
  use Latte\Compiler\Escaper;
14
- use Latte\Compiler\Node;
15
14
  use Latte\Compiler\Nodes\AreaNode;
16
- use Latte\Compiler\Nodes\Php\Expression;
17
15
  use Latte\Compiler\Nodes\Php\ExpressionNode;
18
16
  use Latte\Compiler\Nodes\Php\ModifierNode;
19
17
  use Latte\Compiler\Nodes\StatementNode;
@@ -36,7 +34,7 @@ class CaptureNode extends StatementNode
36
34
  {
37
35
  $tag->expectArguments();
38
36
  $variable = $tag->parser->parseExpression();
39
- if (!self::canBeAssignedTo($variable)) {
37
+ if (!$variable->isWritable()) {
40
38
  $text = '';
41
39
  $i = 0;
42
40
  while ($token = $tag->parser->stream->peek(--$i)) {
@@ -45,7 +43,7 @@ class CaptureNode extends StatementNode
45
43
 
46
44
  throw new CompileException("It is not possible to write into '$text' in " . $tag->getNotation(), $tag->position);
47
45
  }
48
- $node = new static;
46
+ $node = $tag->node = new static;
49
47
  $node->variable = $variable;
50
48
  $node->modifier = $tag->parser->parseModifier();
51
49
  [$node->content] = yield;
@@ -80,15 +78,6 @@ class CaptureNode extends StatementNode
80
78
  }
81
79
 
82
80
 
83
- private static function canBeAssignedTo(Node $node): bool
84
- {
85
- return $node instanceof Expression\VariableNode
86
- || $node instanceof Expression\ArrayAccessNode
87
- || $node instanceof Expression\PropertyFetchNode
88
- || $node instanceof Expression\StaticPropertyFetchNode;
89
- }
90
-
91
-
92
81
  public function &getIterator(): \Generator
93
82
  {
94
83
  yield $this->variable;
@@ -32,7 +32,7 @@ class ContentTypeNode extends StatementNode
32
32
  while (!$tag->parser->stream->consume()->isEnd());
33
33
  $type = trim($tag->parser->text);
34
34
 
35
- if (!$tag->isInHead() && !($tag->htmlElement?->name === 'script' && str_contains($type, 'html'))) {
35
+ if (!$tag->isInHead() && !($tag->htmlElement?->is('script') && str_contains($type, 'html'))) {
36
36
  throw new CompileException('{contentType} is allowed only in template header.', $tag->position);
37
37
  }
38
38
 
@@ -42,11 +42,10 @@ class DefineNode extends StatementNode
42
42
  $tag->parser->stream->tryConsume('#');
43
43
  $name = $tag->parser->parseUnquotedStringOrExpression();
44
44
 
45
- $node = new static;
45
+ $node = $tag->node = new static;
46
46
  $node->block = new Block($name, $layer, $tag);
47
47
  if (!$node->block->isDynamic()) {
48
48
  $parser->checkBlockIsUnique($node->block);
49
- $tag->data->block = $node->block; // for {include}
50
49
  $tag->parser->stream->tryConsume(',');
51
50
  $node->block->parameters = self::parseParameters($tag);
52
51
  }
@@ -43,7 +43,7 @@ class EmbedNode extends StatementNode
43
43
  $tag->outputMode = $tag::OutputRemoveIndentation;
44
44
  $tag->expectArguments();
45
45
 
46
- $node = new static;
46
+ $node = $tag->node = new static;
47
47
  $mode = $tag->parser->tryConsumeTokenBeforeUnquotedString('block', 'file')?->text;
48
48
  $node->name = $tag->parser->parseUnquotedStringOrExpression();
49
49
  $node->mode = $mode ?? ($node->name instanceof StringNode && preg_match('~[\w-]+$~DA', $node->name->value) ? 'block' : 'file');
@@ -33,8 +33,6 @@ class ExtendsNode extends StatementNode
33
33
  $node = new static;
34
34
  if (!$tag->isInHead()) {
35
35
  throw new CompileException("{{$tag->name}} must be placed in template head.", $tag->position);
36
- } elseif (isset($tag->data->extends)) {
37
- throw new CompileException("Multiple {{$tag->name}} declarations are not allowed.", $tag->position);
38
36
  } elseif ($tag->parser->stream->tryConsume('auto')) {
39
37
  $node->extends = new NullNode;
40
38
  } elseif ($tag->parser->stream->tryConsume('none')) {
@@ -42,7 +40,6 @@ class ExtendsNode extends StatementNode
42
40
  } else {
43
41
  $node->extends = $tag->parser->parseUnquotedStringOrExpression();
44
42
  }
45
- $tag->data->extends = true;
46
43
  return $node;
47
44
  }
48
45
 
@@ -34,7 +34,7 @@ class FirstLastSepNode extends StatementNode
34
34
  /** @return \Generator<int, ?array, array{AreaNode, ?Tag}, static> */
35
35
  public static function create(Tag $tag): \Generator
36
36
  {
37
- $node = new static;
37
+ $node = $tag->node = new static;
38
38
  $node->name = $tag->name;
39
39
  $node->width = $tag->parser->isEnd() ? null : $tag->parser->parseExpression();
40
40
 
@@ -35,7 +35,7 @@ class ForNode extends StatementNode
35
35
  {
36
36
  $tag->expectArguments();
37
37
  $stream = $tag->parser->stream;
38
- $node = new static;
38
+ $node = $tag->node = new static;
39
39
  while (!$stream->is(';')) {
40
40
  $node->init[] = $tag->parser->parseExpression();
41
41
  $stream->tryConsume(',');
@@ -13,6 +13,7 @@ use Latte\CompileException;
13
13
  use Latte\Compiler\Nodes\AreaNode;
14
14
  use Latte\Compiler\Nodes\NopNode;
15
15
  use Latte\Compiler\Nodes\Php\ExpressionNode;
16
+ use Latte\Compiler\Nodes\Php\ListNode;
16
17
  use Latte\Compiler\Nodes\StatementNode;
17
18
  use Latte\Compiler\Position;
18
19
  use Latte\Compiler\PrintContext;
@@ -28,7 +29,7 @@ class ForeachNode extends StatementNode
28
29
  public ExpressionNode $expression;
29
30
  public ?ExpressionNode $key = null;
30
31
  public bool $byRef = false;
31
- public ExpressionNode $value;
32
+ public ExpressionNode|ListNode $value;
32
33
  public AreaNode $content;
33
34
  public ?AreaNode $else = null;
34
35
  public ?Position $elseLine = null;
@@ -40,9 +41,8 @@ class ForeachNode extends StatementNode
40
41
  public static function create(Tag $tag): \Generator
41
42
  {
42
43
  $tag->expectArguments();
43
- $node = new static;
44
+ $node = $tag->node = new static;
44
45
  self::parseArguments($tag->parser, $node);
45
- $tag->data->iterateWhile = [$node->key, $node->value];
46
46
 
47
47
  $modifier = $tag->parser->parseModifier();
48
48
  foreach ($modifier->filters as $filter) {
@@ -73,16 +73,7 @@ class ForeachNode extends StatementNode
73
73
  $stream = $parser->stream;
74
74
  $node->expression = $parser->parseExpression();
75
75
  $stream->consume('as');
76
- if (!$stream->is('&')) {
77
- $node->value = $parser->parseExpression();
78
- if (!$stream->tryConsume('=>')) {
79
- return;
80
- }
81
- $node->key = $node->value;
82
- }
83
-
84
- $node->byRef = (bool) $stream->tryConsume('&');
85
- $node->value = $parser->parseExpression();
76
+ [$node->key, $node->value, $node->byRef] = $parser->parseForeach();
86
77
  }
87
78
 
88
79
 
@@ -31,7 +31,7 @@ class IfChangedNode extends StatementNode
31
31
  /** @return \Generator<int, ?array, array{AreaNode, ?Tag}, static> */
32
32
  public static function create(Tag $tag): \Generator
33
33
  {
34
- $node = new static;
34
+ $node = $tag->node = new static;
35
35
  $node->conditions = $tag->parser->parseArguments();
36
36
 
37
37
  [$node->then, $nextTag] = yield ['else'];
@@ -27,12 +27,13 @@ class IfContentNode extends StatementNode
27
27
  public AreaNode $content;
28
28
  public int $id;
29
29
  public ElementNode $htmlElement;
30
+ public ?AreaNode $else = null;
30
31
 
31
32
 
32
33
  /** @return \Generator<int, ?array, array{AreaNode, ?Tag}, static> */
33
34
  public static function create(Tag $tag, TemplateParser $parser): \Generator
34
35
  {
35
- $node = new static;
36
+ $node = $tag->node = new static;
36
37
  $node->id = $parser->generateId();
37
38
  [$node->content] = yield;
38
39
  $node->htmlElement = $tag->htmlElement;
@@ -47,6 +48,7 @@ class IfContentNode extends StatementNode
47
48
  {
48
49
  try {
49
50
  $saved = $this->htmlElement->content;
51
+ $else = $this->else ?? new AuxiliaryNode(fn() => '');
50
52
  $this->htmlElement->content = new AuxiliaryNode(fn() => <<<XX
51
53
  ob_start();
52
54
  try {
@@ -63,6 +65,7 @@ class IfContentNode extends StatementNode
63
65
  } finally {
64
66
  if (\$ʟ_ifc[$this->id] ?? null) {
65
67
  ob_end_clean();
68
+ {$else->print($context)}
66
69
  } else {
67
70
  echo ob_get_clean();
68
71
  }
@@ -10,7 +10,6 @@ declare(strict_types=1);
10
10
  namespace Latte\Essential\Nodes;
11
11
 
12
12
  use Latte\CompileException;
13
- use Latte\Compiler\ExpressionBuilder;
14
13
  use Latte\Compiler\Nodes\AreaNode;
15
14
  use Latte\Compiler\Nodes\Php\Expression;
16
15
  use Latte\Compiler\Nodes\Php\ExpressionNode;
@@ -42,7 +41,7 @@ class IfNode extends StatementNode
42
41
  /** @return \Generator<int, ?array, array{AreaNode, ?Tag}, static> */
43
42
  public static function create(Tag $tag, TemplateParser $parser): \Generator
44
43
  {
45
- $node = new static;
44
+ $node = $tag->node = new static;
46
45
  $node->ifset = in_array($tag->name, ['ifset', 'elseifset'], true);
47
46
  $node->capture = !$tag->isNAttribute() && $tag->name === 'if' && $tag->parser->isEnd();
48
47
  $node->position = $tag->position;
@@ -83,7 +82,10 @@ class IfNode extends StatementNode
83
82
  $block = $parser->tryConsumeTokenBeforeUnquotedString('block') ?? $parser->stream->tryConsume('#');
84
83
  $name = $parser->parseUnquotedStringOrExpression();
85
84
  $list[] = $block || $name instanceof StringNode
86
- ? ExpressionBuilder::variable('$this')->method('hasBlock', [$name])->build()
85
+ ? new Expression\AuxiliaryNode(
86
+ fn(PrintContext $context, ExpressionNode $name) => '$this->hasBlock(' . $name->print($context) . ')',
87
+ [$name],
88
+ )
87
89
  : new Expression\IssetNode([$name]);
88
90
  } while ($parser->stream->tryConsume(','));
89
91
 
@@ -65,12 +65,15 @@ class IncludeBlockNode extends StatementNode
65
65
  throw new CompileException('Filters are not allowed in {include parent}', $tag->position);
66
66
 
67
67
  } elseif ($node->parent || $tokenName->is('this')) {
68
- $item = $tag->closestTag(['block', 'define'], fn($item) => isset($item->data->block) && $item->data->block->name !== '');
68
+ $item = $tag->closestTag(
69
+ [BlockNode::class, DefineNode::class],
70
+ fn($item) => $item->node?->block && !$item->node->block->isDynamic() && $item->node->block->name !== ''
71
+ );
69
72
  if (!$item) {
70
73
  throw new CompileException("Cannot include $tokenName->text block outside of any block.", $tag->position);
71
74
  }
72
75
 
73
- $node->name = $item->data->block->name;
76
+ $node->name = $item->node->block->name;
74
77
  }
75
78
 
76
79
  $node->blocks = &$parser->blocks;
@@ -12,6 +12,7 @@ namespace Latte\Essential\Nodes;
12
12
  use Latte\CompileException;
13
13
  use Latte\Compiler\Nodes\AreaNode;
14
14
  use Latte\Compiler\Nodes\Php\ExpressionNode;
15
+ use Latte\Compiler\Nodes\Php\ListNode;
15
16
  use Latte\Compiler\Nodes\StatementNode;
16
17
  use Latte\Compiler\PrintContext;
17
18
  use Latte\Compiler\Tag;
@@ -25,25 +26,26 @@ class IterateWhileNode extends StatementNode
25
26
  public ExpressionNode $condition;
26
27
  public AreaNode $content;
27
28
  public ?ExpressionNode $key;
28
- public ExpressionNode $value;
29
+ public ExpressionNode|ListNode $value;
29
30
  public bool $postTest;
30
31
 
31
32
 
32
33
  /** @return \Generator<int, ?array, array{AreaNode, ?Tag}, static> */
33
34
  public static function create(Tag $tag): \Generator
34
35
  {
35
- $foreach = $tag->closestTag(['foreach']);
36
+ $foreach = $tag->closestTag([ForeachNode::class])?->node;
36
37
  if (!$foreach) {
37
38
  throw new CompileException("Tag {{$tag->name}} must be inside {foreach} ... {/foreach}.", $tag->position);
38
39
  }
39
40
 
40
- $node = new static;
41
+ $node = $tag->node = new static;
41
42
  $node->postTest = $tag->parser->isEnd();
42
43
  if (!$node->postTest) {
43
44
  $node->condition = $tag->parser->parseExpression();
44
45
  }
45
46
 
46
- [$node->key, $node->value] = $foreach->data->iterateWhile;
47
+ $node->key = $foreach->key;
48
+ $node->value = $foreach->value;
47
49
  [$node->content, $nextTag] = yield;
48
50
  if ($node->postTest) {
49
51
  $nextTag->expectArguments();
@@ -26,54 +26,57 @@ class JumpNode extends StatementNode
26
26
  {
27
27
  public string $type;
28
28
  public ExpressionNode $condition;
29
- public ?string $endTag = null;
30
29
 
31
30
 
32
31
  public static function create(Tag $tag): static
33
32
  {
34
33
  $tag->expectArguments();
35
- $allowed = match ($tag->name) {
36
- 'breakIf', 'continueIf' => ['for', 'foreach', 'while'],
37
- 'skipIf' => ['foreach'],
38
- 'exitIf' => ['block', null],
39
- };
34
+ $tag->outputMode = $tag->name === 'exitIf' // to not be in prepare()
35
+ ? $tag::OutputRemoveIndentation
36
+ : $tag::OutputNone;
37
+
40
38
  for (
41
39
  $parent = $tag->parent;
42
- in_array($parent?->name, ['if', 'ifset', 'ifcontent'], true);
40
+ $parent?->node instanceof IfNode || $parent?->node instanceof IfContentNode;
43
41
  $parent = $parent->parent
44
42
  );
45
- if (!in_array($parent?->name, $allowed, true)) {
43
+ $pnode = $parent?->node;
44
+ if (!match ($tag->name) {
45
+ 'breakIf', 'continueIf' => $pnode instanceof ForNode || $pnode instanceof ForeachNode || $pnode instanceof WhileNode,
46
+ 'skipIf' => $pnode instanceof ForeachNode,
47
+ 'exitIf' => !$pnode || $pnode instanceof BlockNode || $pnode instanceof DefineNode,
48
+ }) {
46
49
  throw new CompileException("Tag {{$tag->name}} is unexpected here.", $tag->position);
47
50
  }
48
51
 
52
+ $last = $parent?->prefix === Tag::PrefixNone
53
+ ? $parent->htmlElement->parent
54
+ : $parent?->htmlElement;
55
+ $el = $tag->htmlElement;
56
+ while ($el && $el !== $last) {
57
+ $el->breakable = true;
58
+ $el = $el->parent;
59
+ }
60
+
49
61
  $node = new static;
50
62
  $node->type = $tag->name;
51
63
  $node->condition = $tag->parser->parseExpression();
52
- if (isset($tag->htmlElement->nAttributes['foreach'])) {
53
- $node->endTag = $tag->htmlElement->name;
54
- }
55
64
  return $node;
56
65
  }
57
66
 
58
67
 
59
68
  public function print(PrintContext $context): string
60
69
  {
61
- $cmd = match ($this->type) {
62
- 'breakIf' => 'break;',
63
- 'continueIf' => 'continue;',
64
- 'skipIf' => '{ $iterator->skipRound(); continue; }',
65
- 'exitIf' => 'return;',
66
- };
67
-
68
- if ($this->endTag) {
69
- $cmd = "{ echo \"</$this->endTag>\\n\"; $cmd; } ";
70
- }
71
-
72
70
  return $context->format(
73
71
  "if (%node) %line %raw\n",
74
72
  $this->condition,
75
73
  $this->position,
76
- $cmd,
74
+ match ($this->type) {
75
+ 'breakIf' => 'break;',
76
+ 'continueIf' => 'continue;',
77
+ 'skipIf' => '{ $iterator->skipRound(); continue; }',
78
+ 'exitIf' => 'return;',
79
+ },
77
80
  );
78
81
  }
79
82
 
@@ -0,0 +1,88 @@
1
+ <?php
2
+
3
+ /**
4
+ * This file is part of the Latte (https://latte.nette.org)
5
+ * Copyright (c) 2008 David Grudl (https://davidgrudl.com)
6
+ */
7
+
8
+ declare(strict_types=1);
9
+
10
+ namespace Latte\Essential\Nodes;
11
+
12
+ use Latte\CompileException;
13
+ use Latte\Compiler\Node;
14
+ use Latte\Compiler\Nodes;
15
+ use Latte\Compiler\Nodes\AreaNode;
16
+ use Latte\Compiler\Nodes\StatementNode;
17
+ use Latte\Compiler\NodeTraverser;
18
+ use Latte\Compiler\PrintContext;
19
+ use Latte\Compiler\Tag;
20
+
21
+
22
+ /**
23
+ * n:else
24
+ */
25
+ final class NElseNode extends StatementNode
26
+ {
27
+ public AreaNode $content;
28
+
29
+
30
+ /** @return \Generator<int, ?array, array{AreaNode, ?Tag}, static> */
31
+ public static function create(Tag $tag): \Generator
32
+ {
33
+ $node = $tag->node = new static;
34
+ [$node->content] = yield;
35
+ return $node;
36
+ }
37
+
38
+
39
+ public function print(PrintContext $context): string
40
+ {
41
+ throw new \LogicException('Cannot directly print');
42
+ }
43
+
44
+
45
+ public function &getIterator(): \Generator
46
+ {
47
+ yield $this->content;
48
+ }
49
+
50
+
51
+ public static function processPass(Node $node): void
52
+ {
53
+ (new NodeTraverser)->traverse($node, function (Node $node) {
54
+ if ($node instanceof Nodes\FragmentNode) {
55
+ for ($i = count($node->children) - 1; $i >= 0; $i--) {
56
+ $nElse = $node->children[$i];
57
+ if (!$nElse instanceof self) {
58
+ continue;
59
+ }
60
+
61
+ array_splice($node->children, $i, 1);
62
+ $prev = $node->children[--$i] ?? null;
63
+ if ($prev instanceof Nodes\TextNode && trim($prev->content) === '') {
64
+ array_splice($node->children, $i, 1);
65
+ $prev = $node->children[--$i] ?? null;
66
+ }
67
+
68
+ if (
69
+ $prev instanceof IfNode
70
+ || $prev instanceof ForeachNode
71
+ || $prev instanceof TryNode
72
+ || $prev instanceof IfChangedNode
73
+ || $prev instanceof IfContentNode
74
+ ) {
75
+ if ($prev->else) {
76
+ throw new CompileException('Multiple "else" found.', $nElse->position);
77
+ }
78
+ $prev->else = $nElse->content;
79
+ } else {
80
+ throw new CompileException('n:else must be immediately after n:if, n:foreach etc', $nElse->position);
81
+ }
82
+ }
83
+ } elseif ($node instanceof self) {
84
+ throw new CompileException('n:else must be immediately after n:if, n:foreach etc', $node->position);
85
+ }
86
+ });
87
+ }
88
+ }