@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,7 +9,6 @@ declare(strict_types=1);
9
9
 
10
10
  namespace Latte\Compiler;
11
11
 
12
- use Latte;
13
12
  use Latte\Compiler\Nodes\Html\ElementNode;
14
13
  use Latte\ContentType;
15
14
  use Latte\Runtime\Filters;
@@ -20,8 +19,6 @@ use Latte\Runtime\Filters;
20
19
  */
21
20
  final class Escaper
22
21
  {
23
- use Latte\Strict;
24
-
25
22
  public const
26
23
  Text = 'text',
27
24
  JavaScript = 'js',
@@ -33,14 +30,54 @@ final class Escaper
33
30
  HtmlText = 'html',
34
31
  HtmlComment = 'html/comment',
35
32
  HtmlBogusTag = 'html/bogus',
36
- HtmlCss = 'html/css',
37
- HtmlJavaScript = 'html/js',
33
+ HtmlRawText = 'html/raw',
38
34
  HtmlTag = 'html/tag',
39
35
  HtmlAttribute = 'html/attr';
40
36
 
37
+ private const Convertors = [
38
+ self::Text => [
39
+ self::HtmlText => 'escapeHtmlText',
40
+ self::HtmlAttribute => 'escapeHtmlAttr',
41
+ self::HtmlAttribute . '/' . self::JavaScript => 'escapeHtmlAttr',
42
+ self::HtmlAttribute . '/' . self::Css => 'escapeHtmlAttr',
43
+ self::HtmlAttribute . '/' . self::Url => 'escapeHtmlAttr',
44
+ self::HtmlComment => 'escapeHtmlComment',
45
+ 'xml' => 'escapeXml',
46
+ 'xml/attr' => 'escapeXml',
47
+ ],
48
+ self::JavaScript => [
49
+ self::HtmlText => 'escapeHtmlText',
50
+ self::HtmlAttribute => 'escapeHtmlAttr',
51
+ self::HtmlAttribute . '/' . self::JavaScript => 'escapeHtmlAttr',
52
+ self::HtmlRawText . '/' . self::JavaScript => 'convertJSToHtmlRawText',
53
+ self::HtmlComment => 'escapeHtmlComment',
54
+ ],
55
+ self::Css => [
56
+ self::HtmlText => 'escapeHtmlText',
57
+ self::HtmlAttribute => 'escapeHtmlAttr',
58
+ self::HtmlAttribute . '/' . self::Css => 'escapeHtmlAttr',
59
+ self::HtmlRawText . '/' . self::Css => 'convertJSToHtmlRawText',
60
+ self::HtmlComment => 'escapeHtmlComment',
61
+ ],
62
+ self::HtmlText => [
63
+ self::HtmlAttribute => 'convertHtmlToHtmlAttr',
64
+ self::HtmlAttribute . '/' . self::JavaScript => 'convertHtmlToHtmlAttr',
65
+ self::HtmlAttribute . '/' . self::Css => 'convertHtmlToHtmlAttr',
66
+ self::HtmlAttribute . '/' . self::Url => 'convertHtmlToHtmlAttr',
67
+ self::HtmlComment => 'escapeHtmlComment',
68
+ self::HtmlRawText . '/' . self::HtmlText => 'convertHtmlToHtmlRawText',
69
+ ],
70
+ self::HtmlAttribute => [
71
+ self::HtmlText => 'convertHtmlToHtmlAttr',
72
+ ],
73
+ self::HtmlAttribute . '/' . self::Url => [
74
+ self::HtmlText => 'convertHtmlToHtmlAttr',
75
+ self::HtmlAttribute => 'nop',
76
+ ],
77
+ ];
78
+
41
79
  private string $state = '';
42
80
  private string $tag = '';
43
- private string $quote = '';
44
81
  private string $subType = '';
45
82
 
46
83
 
@@ -65,8 +102,7 @@ final class Escaper
65
102
 
66
103
  public function export(): string
67
104
  {
68
- return ($this->state === self::HtmlAttribute && $this->quote === '' ? 'html/unquoted-attr' : $this->state)
69
- . ($this->subType ? '/' . $this->subType : '');
105
+ return $this->state . ($this->subType ? '/' . $this->subType : '');
70
106
  }
71
107
 
72
108
 
@@ -76,20 +112,29 @@ final class Escaper
76
112
  }
77
113
 
78
114
 
79
- public function enterHtmlText(?ElementNode $node): void
115
+ public function enterHtmlText(ElementNode $el): void
80
116
  {
81
- $this->state = self::HtmlText;
82
- if ($this->contentType === ContentType::Html && $node) {
83
- $name = strtolower($node->name);
84
- if (
85
- ($name === 'script' || $name === 'style')
86
- && is_string($attr = $node->getAttribute('type') ?? 'css')
87
- && preg_match('#(java|j|ecma|live)script|module|json|css|plain#i', $attr)
88
- ) {
89
- $this->state = $name === 'script'
90
- ? self::HtmlJavaScript
91
- : self::HtmlCss;
117
+ if ($el->isRawText()) {
118
+ $this->state = self::HtmlRawText;
119
+ $this->subType = self::Text;
120
+ if ($el->is('script')) {
121
+ $type = $el->getAttribute('type');
122
+ if ($type === true || $type === null
123
+ || is_string($type) && preg_match('#((application|text)/(((x-)?java|ecma|j|live)script|json)|text/plain|module|importmap|)$#Ai', $type)
124
+ ) {
125
+ $this->subType = self::JavaScript;
126
+
127
+ } elseif (is_string($type) && preg_match('#text/((x-)?template|html)$#Ai', $type)) {
128
+ $this->subType = self::HtmlText;
129
+ }
130
+
131
+ } elseif ($el->is('style')) {
132
+ $this->subType = self::Css;
92
133
  }
134
+
135
+ } else {
136
+ $this->state = self::HtmlText;
137
+ $this->subType = '';
93
138
  }
94
139
  }
95
140
 
@@ -101,10 +146,9 @@ final class Escaper
101
146
  }
102
147
 
103
148
 
104
- public function enterHtmlAttribute(?string $name = null, string $quote = ''): void
149
+ public function enterHtmlAttribute(?string $name = null): void
105
150
  {
106
151
  $this->state = self::HtmlAttribute;
107
- $this->quote = $quote;
108
152
  $this->subType = '';
109
153
 
110
154
  if ($this->contentType === ContentType::Html && is_string($name)) {
@@ -122,12 +166,6 @@ final class Escaper
122
166
  }
123
167
 
124
168
 
125
- public function enterHtmlAttributeQuote(string $quote = '"'): void
126
- {
127
- $this->quote = $quote;
128
- }
129
-
130
-
131
169
  public function enterHtmlBogusTag(): void
132
170
  {
133
171
  $this->state = self::HtmlBogusTag;
@@ -142,27 +180,30 @@ final class Escaper
142
180
 
143
181
  public function escape(string $str): string
144
182
  {
145
- [$lq, $rq] = $this->state === self::HtmlAttribute && !$this->quote ? ["'\"' . ", " . '\"'"] : ['', ''];
146
183
  return match ($this->contentType) {
147
184
  ContentType::Html => match ($this->state) {
148
185
  self::HtmlText => 'LR\Filters::escapeHtmlText(' . $str . ')',
149
186
  self::HtmlTag => 'LR\Filters::escapeHtmlTag(' . $str . ')',
150
187
  self::HtmlAttribute => match ($this->subType) {
151
188
  '',
152
- self::Url => $lq . 'LR\Filters::escapeHtmlAttr(' . $str . ')' . $rq,
153
- self::JavaScript => $lq . 'LR\Filters::escapeHtmlAttr(LR\Filters::escapeJs(' . $str . '))' . $rq,
154
- self::Css => $lq . 'LR\Filters::escapeHtmlAttr(LR\Filters::escapeCss(' . $str . '))' . $rq,
189
+ self::Url => 'LR\Filters::escapeHtmlAttr(' . $str . ')',
190
+ self::JavaScript => 'LR\Filters::escapeHtmlAttr(LR\Filters::escapeJs(' . $str . '))',
191
+ self::Css => 'LR\Filters::escapeHtmlAttr(LR\Filters::escapeCss(' . $str . '))',
155
192
  },
156
193
  self::HtmlComment => 'LR\Filters::escapeHtmlComment(' . $str . ')',
157
194
  self::HtmlBogusTag => 'LR\Filters::escapeHtml(' . $str . ')',
158
- self::HtmlJavaScript => 'LR\Filters::escapeJs(' . $str . ')',
159
- self::HtmlCss => 'LR\Filters::escapeCss(' . $str . ')',
195
+ self::HtmlRawText => match ($this->subType) {
196
+ self::Text => 'LR\Filters::convertJSToHtmlRawText(' . $str . ')', // sanitization, escaping is not possible
197
+ self::HtmlText => 'LR\Filters::escapeHtmlRawTextHtml(' . $str . ')',
198
+ self::JavaScript => 'LR\Filters::escapeJs(' . $str . ')',
199
+ self::Css => 'LR\Filters::escapeCss(' . $str . ')',
200
+ },
160
201
  default => throw new \LogicException("Unknown context $this->contentType, $this->state."),
161
202
  },
162
203
  ContentType::Xml => match ($this->state) {
163
204
  self::HtmlText,
164
205
  self::HtmlBogusTag => 'LR\Filters::escapeXml(' . $str . ')',
165
- self::HtmlAttribute => $lq . 'LR\Filters::escapeXml(' . $str . ')' . $rq,
206
+ self::HtmlAttribute => 'LR\Filters::escapeXml(' . $str . ')',
166
207
  self::HtmlComment => 'LR\Filters::escapeHtmlComment(' . $str . ')',
167
208
  self::HtmlTag => 'LR\Filters::escapeXmlTag(' . $str . ')',
168
209
  default => throw new \LogicException("Unknown context $this->contentType, $this->state."),
@@ -176,6 +217,28 @@ final class Escaper
176
217
  }
177
218
 
178
219
 
220
+ public function escapeMandatory(string $str): string
221
+ {
222
+ return match ($this->contentType) {
223
+ ContentType::Html => match ($this->state) {
224
+ self::HtmlAttribute => "LR\\Filters::escapeHtmlQuotes($str)",
225
+ self::HtmlRawText => match ($this->subType) {
226
+ self::HtmlText => 'LR\Filters::convertHtmlToHtmlRawText(' . $str . ')',
227
+ default => "LR\\Filters::convertJSToHtmlRawText($str)",
228
+ },
229
+ self::HtmlComment => 'LR\Filters::escapeHtmlComment(' . $str . ')',
230
+ default => $str,
231
+ },
232
+ ContentType::Xml => match ($this->state) {
233
+ self::HtmlAttribute => "LR\\Filters::escapeHtmlQuotes($str)",
234
+ self::HtmlComment => 'LR\Filters::escapeHtmlComment(' . $str . ')',
235
+ default => $str,
236
+ },
237
+ default => $str,
238
+ };
239
+ }
240
+
241
+
179
242
  public function check(string $str): string
180
243
  {
181
244
  if ($this->state === self::HtmlAttribute && $this->subType === self::Url) {
@@ -187,58 +250,10 @@ final class Escaper
187
250
 
188
251
  public static function getConvertor(string $source, string $dest): ?callable
189
252
  {
190
- $table = [
191
- self::Text => [
192
- 'html' => 'escapeHtmlText',
193
- 'html/attr' => 'escapeHtmlAttr',
194
- 'html/attr/js' => 'escapeHtmlAttr',
195
- 'html/attr/css' => 'escapeHtmlAttr',
196
- 'html/attr/url' => 'escapeHtmlAttr',
197
- 'html/comment' => 'escapeHtmlComment',
198
- 'xml' => 'escapeXml',
199
- 'xml/attr' => 'escapeXml',
200
- ],
201
- self::JavaScript => [
202
- 'html' => 'escapeHtmlText',
203
- 'html/attr' => 'escapeHtmlAttr',
204
- 'html/attr/js' => 'escapeHtmlAttr',
205
- 'html/js' => 'convertJSToHtmlRawText',
206
- 'html/comment' => 'escapeHtmlComment',
207
- ],
208
- self::Css => [
209
- 'html' => 'escapeHtmlText',
210
- 'html/attr' => 'escapeHtmlAttr',
211
- 'html/attr/css' => 'escapeHtmlAttr',
212
- 'html/css' => 'convertJSToHtmlRawText',
213
- 'html/comment' => 'escapeHtmlComment',
214
- ],
215
- 'html' => [
216
- 'html/attr' => 'convertHtmlToHtmlAttr',
217
- 'html/attr/js' => 'convertHtmlToHtmlAttr',
218
- 'html/attr/css' => 'convertHtmlToHtmlAttr',
219
- 'html/attr/url' => 'convertHtmlToHtmlAttr',
220
- 'html/comment' => 'escapeHtmlComment',
221
- 'html/unquoted-attr' => 'convertHtmlToUnquotedAttr',
222
- ],
223
- 'html/attr' => [
224
- 'html' => 'convertHtmlToHtmlAttr',
225
- 'html/unquoted-attr' => 'convertHtmlAttrToUnquotedAttr',
226
- ],
227
- 'html/attr/url' => [
228
- 'html' => 'convertHtmlToHtmlAttr',
229
- 'html/attr' => 'nop',
230
- ],
231
- 'html/unquoted-attr' => [
232
- 'html' => 'convertHtmlToHtmlAttr',
233
- ],
234
- ];
235
-
236
- if ($source === $dest) {
237
- return [Filters::class, 'nop'];
238
- }
239
-
240
- return isset($table[$source][$dest])
241
- ? [Filters::class, $table[$source][$dest]]
242
- : null;
253
+ return match (true) {
254
+ $source === $dest => [Filters::class, 'nop'],
255
+ isset(self::Convertors[$source][$dest]) => [Filters::class, self::Convertors[$source][$dest]],
256
+ default => null,
257
+ };
243
258
  }
244
259
  }
@@ -17,6 +17,7 @@ use Latte\Compiler\Nodes\Php\NameNode;
17
17
  use Latte\Compiler\Nodes\Php\Scalar;
18
18
 
19
19
 
20
+ /** @deprecated */
20
21
  final class ExpressionBuilder
21
22
  {
22
23
  public function __construct(
@@ -69,7 +70,7 @@ final class ExpressionBuilder
69
70
  $name = is_string($name)
70
71
  ? new IdentifierNode($name)
71
72
  : ($name instanceof self ? $name->expr : $name);
72
- return new self(new Expression\StaticCallNode($this->expr, $name, self::arrayToArgs($args)));
73
+ return new self(new Expression\StaticMethodCallNode($this->expr, $name, self::arrayToArgs($args)));
73
74
  }
74
75
 
75
76
 
@@ -4,16 +4,12 @@ declare(strict_types=1);
4
4
 
5
5
  namespace Latte\Compiler;
6
6
 
7
- use Latte;
8
-
9
7
 
10
8
  /**
11
9
  * @implements \IteratorAggregate<Node>
12
10
  */
13
11
  abstract class Node implements \IteratorAggregate
14
12
  {
15
- use Latte\Strict;
16
-
17
13
  public ?Position $position = null;
18
14
 
19
15
 
@@ -9,7 +9,6 @@ declare(strict_types=1);
9
9
 
10
10
  namespace Latte\Compiler;
11
11
 
12
- use Latte;
13
12
  use Latte\Compiler\Nodes\Php;
14
13
  use Latte\Compiler\Nodes\Php\Expression;
15
14
  use Latte\Compiler\Nodes\Php\ExpressionNode;
@@ -18,8 +17,6 @@ use Latte\Compiler\Nodes\Php\Scalar;
18
17
 
19
18
  final class NodeHelpers
20
19
  {
21
- use Latte\Strict;
22
-
23
20
  /** @return Node[] */
24
21
  public static function find(Node $node, callable $filter): array
25
22
  {
@@ -124,7 +121,6 @@ final class NodeHelpers
124
121
 
125
122
  return match (true) {
126
123
  $node instanceof Nodes\TextNode => $node->content,
127
- $node instanceof Nodes\Html\QuotedValue => self::toText($node->value),
128
124
  $node instanceof Nodes\NopNode => '',
129
125
  default => null,
130
126
  };
@@ -9,13 +9,9 @@ declare(strict_types=1);
9
9
 
10
10
  namespace Latte\Compiler;
11
11
 
12
- use Latte;
13
-
14
12
 
15
13
  final class NodeTraverser
16
14
  {
17
- use Latte\Strict;
18
-
19
15
  public const DontTraverseChildren = 1;
20
16
  public const StopTraversal = 2;
21
17
 
@@ -9,25 +9,33 @@ declare(strict_types=1);
9
9
 
10
10
  namespace Latte\Compiler\Nodes;
11
11
 
12
+ use Latte\Compiler\Node;
12
13
  use Latte\Compiler\PrintContext;
13
14
 
14
15
 
15
16
  class AuxiliaryNode extends AreaNode
16
17
  {
17
18
  public function __construct(
18
- public /*readonly*/ \Closure $callable,
19
+ public /*readonly*/ \Closure $print,
20
+ /** @var (?Node)[] */
21
+ public array $nodes = [],
19
22
  ) {
23
+ (function (?Node ...$nodes) {})(...$nodes);
20
24
  }
21
25
 
22
26
 
23
27
  public function print(PrintContext $context): string
24
28
  {
25
- return ($this->callable)($context);
29
+ return ($this->print)($context, ...$this->nodes);
26
30
  }
27
31
 
28
32
 
29
33
  public function &getIterator(): \Generator
30
34
  {
31
- false && yield;
35
+ foreach ($this->nodes as &$node) {
36
+ if ($node) {
37
+ yield $node;
38
+ }
39
+ }
32
40
  }
33
41
  }
@@ -39,6 +39,16 @@ final class FragmentNode extends AreaNode
39
39
  }
40
40
 
41
41
 
42
+ public function simplify(bool $allowsNull = true): ?AreaNode
43
+ {
44
+ return match (true) {
45
+ !$this->children => $allowsNull ? null : $this,
46
+ count($this->children) === 1 => $this->children[0],
47
+ default => $this,
48
+ };
49
+ }
50
+
51
+
42
52
  public function print(PrintContext $context): string
43
53
  {
44
54
  $res = '';
@@ -11,6 +11,8 @@ namespace Latte\Compiler\Nodes\Html;
11
11
 
12
12
  use Latte\Compiler\NodeHelpers;
13
13
  use Latte\Compiler\Nodes\AreaNode;
14
+ use Latte\Compiler\Nodes\FragmentNode;
15
+ use Latte\Compiler\Nodes\TextNode;
14
16
  use Latte\Compiler\Position;
15
17
  use Latte\Compiler\PrintContext;
16
18
 
@@ -20,6 +22,7 @@ class AttributeNode extends AreaNode
20
22
  public function __construct(
21
23
  public AreaNode $name,
22
24
  public ?AreaNode $value = null,
25
+ public ?string $quote = null,
23
26
  public ?Position $position = null,
24
27
  ) {
25
28
  }
@@ -28,12 +31,27 @@ class AttributeNode extends AreaNode
28
31
  public function print(PrintContext $context): string
29
32
  {
30
33
  $res = $this->name->print($context);
31
- if ($this->value) {
32
- $context->beginEscape()->enterHtmlAttribute(NodeHelpers::toText($this->name));
33
- $res .= "echo '=';";
34
+ if (!$this->value) {
35
+ return $res;
36
+ }
37
+
38
+ $res .= "echo '=';";
39
+ $quote = $this->quote ?? ($this->value instanceof TextNode ? null : '"');
40
+ $res .= $quote ? 'echo ' . var_export($quote, true) . ';' : '';
41
+
42
+ $escaper = $context->beginEscape();
43
+ $escaper->enterHtmlAttribute(NodeHelpers::toText($this->name));
44
+ if ($this->value instanceof FragmentNode && $escaper->export() === 'html/attr/url') {
45
+ foreach ($this->value->children as $child) {
46
+ $res .= $child->print($context);
47
+ $escaper->enterHtmlAttribute(null);
48
+ }
49
+ } else {
34
50
  $res .= $this->value->print($context);
35
- $context->restoreEscape();
36
51
  }
52
+
53
+ $context->restoreEscape();
54
+ $res .= $quote ? 'echo ' . var_export($quote, true) . ';' : '';
37
55
  return $res;
38
56
  }
39
57
 
@@ -18,6 +18,7 @@ use Latte\Compiler\Nodes\FragmentNode;
18
18
  use Latte\Compiler\Position;
19
19
  use Latte\Compiler\PrintContext;
20
20
  use Latte\Compiler\Tag;
21
+ use Latte\ContentType;
21
22
 
22
23
 
23
24
  /**
@@ -25,7 +26,7 @@ use Latte\Compiler\Tag;
25
26
  */
26
27
  class ElementNode extends AreaNode
27
28
  {
28
- public ?Node $customName = null;
29
+ public ?Nodes\Php\ExpressionNode $variableName = null;
29
30
  public ?FragmentNode $attributes = null;
30
31
  public bool $selfClosing = false;
31
32
  public ?AreaNode $content = null;
@@ -36,6 +37,7 @@ class ElementNode extends AreaNode
36
37
  /** n:tag & n:tag- support */
37
38
  public AreaNode $tagNode;
38
39
  public bool $captureTagName = false;
40
+ public bool $breakable = false;
39
41
  private ?string $endTagVar;
40
42
 
41
43
 
@@ -44,6 +46,7 @@ class ElementNode extends AreaNode
44
46
  public ?Position $position = null,
45
47
  public /*readonly*/ ?self $parent = null,
46
48
  public ?\stdClass $data = null,
49
+ public string $contentType = ContentType::Html,
47
50
  ) {
48
51
  $this->data ??= new \stdClass;
49
52
  $this->tagNode = new AuxiliaryNode(\Closure::fromCallable([$this, 'printStartTag']));
@@ -65,10 +68,25 @@ class ElementNode extends AreaNode
65
68
  }
66
69
 
67
70
 
71
+ public function is(string $name): bool
72
+ {
73
+ return $this->contentType === ContentType::Html
74
+ ? strcasecmp($this->name, $name) === 0
75
+ : $this->name === $name;
76
+ }
77
+
78
+
79
+ public function isRawText(): bool
80
+ {
81
+ return $this->contentType === ContentType::Html
82
+ && ($this->is('script') || $this->is('style'));
83
+ }
84
+
85
+
68
86
  public function print(PrintContext $context): string
69
87
  {
70
88
  $res = $this->endTagVar = null;
71
- if ($this->captureTagName || $this->customName) {
89
+ if ($this->captureTagName || $this->variableName) {
72
90
  $endTag = $this->endTagVar = '$ʟ_tag[' . $context->generateId() . ']';
73
91
  $res = "$this->endTagVar = '';";
74
92
  } else {
@@ -79,9 +97,11 @@ class ElementNode extends AreaNode
79
97
 
80
98
  if ($this->content) {
81
99
  $context->beginEscape()->enterHtmlText($this);
82
- $res .= $this->content->print($context);
100
+ $content = $this->content->print($context);
83
101
  $context->restoreEscape();
84
- $res .= 'echo ' . $endTag . ';';
102
+ $res .= $this->breakable
103
+ ? 'try { ' . $content . ' } finally { echo ' . $endTag . '; } '
104
+ : $content . 'echo ' . $endTag . ';';
85
105
  }
86
106
 
87
107
  return $res;
@@ -93,14 +113,17 @@ class ElementNode extends AreaNode
93
113
  $context->beginEscape()->enterHtmlTag($this->name);
94
114
  $res = "echo '<';";
95
115
 
96
- $namePhp = var_export($this->name, true);
97
116
  if ($this->endTagVar) {
98
- $res .= 'echo $ʟ_tmp = (' . ($this->customName ? $this->customName->print($context) : $namePhp) . ');';
99
- $res .= $this->endTagVar . ' = '
100
- . "'</' . \$ʟ_tmp . '>'"
101
- . ' . ' . $this->endTagVar . ';';
117
+ $expr = $this->variableName
118
+ ? 'LR\Filters::safeTag('
119
+ . $this->variableName->print($context)
120
+ . ($this->contentType === ContentType::Xml ? ', true' : '')
121
+ . ')'
122
+ : var_export($this->name, true);
123
+ $res .= "echo \$ʟ_tmp = $expr /* line {$this->position->line} */;"
124
+ . "{$this->endTagVar} = '</' . \$ʟ_tmp . '>' . {$this->endTagVar};";
102
125
  } else {
103
- $res .= 'echo ' . $namePhp . ';';
126
+ $res .= 'echo ' . var_export($this->name, true) . ';';
104
127
  }
105
128
 
106
129
  foreach ($this->attributes?->children ?? [] as $attr) {
@@ -116,8 +139,8 @@ class ElementNode extends AreaNode
116
139
  public function &getIterator(): \Generator
117
140
  {
118
141
  yield $this->tagNode;
119
- if ($this->customName) {
120
- yield $this->customName;
142
+ if ($this->variableName) {
143
+ yield $this->variableName;
121
144
  }
122
145
  if ($this->attributes) {
123
146
  yield $this->attributes;
@@ -35,6 +35,12 @@ class ArgumentNode extends Node
35
35
  }
36
36
 
37
37
 
38
+ public function toArrayItem(): ArrayItemNode
39
+ {
40
+ return new ArrayItemNode($this->value, $this->name, $this->byRef, $this->unpack, $this->position);
41
+ }
42
+
43
+
38
44
  public function &getIterator(): \Generator
39
45
  {
40
46
  if ($this->name) {
@@ -40,6 +40,18 @@ class ArrayItemNode extends Node
40
40
  }
41
41
 
42
42
 
43
+ public function toArgument(): ArgumentNode
44
+ {
45
+ $key = match (true) {
46
+ $this->key instanceof Scalar\StringNode => new IdentifierNode($this->key->value),
47
+ $this->key instanceof IdentifierNode => $this->key,
48
+ $this->key === null => null,
49
+ default => throw new \InvalidArgumentException('The expression used in the key cannot be converted to an argument.'),
50
+ };
51
+ return new ArgumentNode($this->value, $this->byRef, $this->unpack, $key, $this->position);
52
+ }
53
+
54
+
43
55
  public function &getIterator(): \Generator
44
56
  {
45
57
  if ($this->key) {
@@ -12,8 +12,6 @@ namespace Latte\Compiler\Nodes\Php\Expression;
12
12
  use Latte\Compiler\Nodes\Php\ArgumentNode;
13
13
  use Latte\Compiler\Nodes\Php\ArrayItemNode;
14
14
  use Latte\Compiler\Nodes\Php\ExpressionNode;
15
- use Latte\Compiler\Nodes\Php\IdentifierNode;
16
- use Latte\Compiler\Nodes\Php\Scalar;
17
15
  use Latte\Compiler\Position;
18
16
  use Latte\Compiler\PrintContext;
19
17
 
@@ -21,41 +19,18 @@ use Latte\Compiler\PrintContext;
21
19
  class ArrayNode extends ExpressionNode
22
20
  {
23
21
  public function __construct(
24
- /** @var array<ArrayItemNode|null> */
22
+ /** @var array<ArrayItemNode> */
25
23
  public array $items = [],
26
24
  public ?Position $position = null,
27
25
  ) {
28
- (function (?ArrayItemNode ...$args) {})(...$items);
29
- }
30
-
31
-
32
- /** @param ArgumentNode[] $args */
33
- public static function fromArguments(array $args): self
34
- {
35
- $node = new self;
36
- foreach ($args as $arg) {
37
- $node->items[] = new ArrayItemNode($arg->value, $arg->name, $arg->byRef, $arg->unpack, $arg->position);
38
- }
39
-
40
- return $node;
26
+ (function (ArrayItemNode ...$args) {})(...$items);
41
27
  }
42
28
 
43
29
 
44
30
  /** @return ArgumentNode[] */
45
31
  public function toArguments(): array
46
32
  {
47
- $args = [];
48
- foreach ($this->items as $item) {
49
- $key = match (true) {
50
- $item->key instanceof Scalar\StringNode => new IdentifierNode($item->key->value),
51
- $item->key instanceof IdentifierNode => $item->key,
52
- $item->key === null => null,
53
- default => throw new \InvalidArgumentException('The expression used in the key cannot be converted to an argument.'),
54
- };
55
- $args[] = new ArgumentNode($item->value, $item->byRef, $item->unpack, $key, $item->position);
56
- }
57
-
58
- return $args;
33
+ return array_map(fn(ArrayItemNode $item) => $item->toArgument(), $this->items);
59
34
  }
60
35
 
61
36
 
@@ -87,9 +62,7 @@ class ArrayNode extends ExpressionNode
87
62
  public function &getIterator(): \Generator
88
63
  {
89
64
  foreach ($this->items as &$item) {
90
- if ($item) {
91
- yield $item;
92
- }
65
+ yield $item;
93
66
  }
94
67
  }
95
68
  }