• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nette / latte / 20837225455

09 Jan 2026 12:47AM UTC coverage: 95.017%. Remained the same
20837225455

push

github

dg
Helpers::sortBeforeAfter() reimplemented using Kahn's algorithm for topological sorting

38 of 38 new or added lines in 1 file covered. (100.0%)

23 existing lines in 7 files now uncovered.

5549 of 5840 relevant lines covered (95.02%)

0.95 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.62
/src/Latte/Compiler/PrintContext.php
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\Compiler;
11

12
use Latte\Compiler\Nodes\Php as Nodes;
13
use Latte\Compiler\Nodes\Php\Expression;
14
use Latte\Compiler\Nodes\Php\OperatorNode;
15
use Latte\Compiler\Nodes\Php\Scalar;
16
use Latte\ContentType;
17
use function addcslashes, array_map, array_pop, end, implode, preg_replace, preg_replace_callback, strtolower, substr, trim, ucfirst;
18

19

20
/**
21
 * PHP printing helpers and context.
22
 */
23
final class PrintContext
24
{
25
        /** @var string[] */
26
        public array $paramsExtraction = [];
27

28
        /** @var array<string, Block> */
29
        public array $blocks = [];
30
        private int $counter = 0;
31

32
        /** @var Escaper[] */
33
        private array $escaperStack = [];
34

35

36
        public function __construct(
1✔
37
                string $contentType = ContentType::Html,
38
                public bool $migrationWarnings = false,
39
        ) {
40
                $this->escaperStack[] = new Escaper($contentType);
1✔
41
        }
1✔
42

43

44
        /**
45
         * Expands %node, %dump, %raw, %args, %line, %escape(), %modify(), %modifyContent() in code.
46
         */
47
        public function format(string $mask, mixed ...$args): string
1✔
48
        {
49
                $pos = 0; // enumerate arguments except for %escape
1✔
50
                $mask = preg_replace_callback(
1✔
51
                        '#%([a-z]{3,})#i',
1✔
52
                        function ($m) use (&$pos) {
1✔
53
                                return $m[1] === 'escape'
1✔
54
                                        ? '%0.escape'
1✔
55
                                        : '%' . ($pos++) . '.' . $m[1];
1✔
56
                        },
1✔
57
                        $mask,
1✔
58
                );
59

60
                $mask = preg_replace_callback(
1✔
61
                        '#% (\d+) \. (escape|modify(?:Content)?) ( \( ([^()]*+|(?-2))+ \) )#xi',
1✔
62
                        function ($m) use ($args) {
1✔
63
                                [, $pos, $fn, $var] = $m;
1✔
64
                                $var = substr($var, 1, -1);
1✔
65
                                /** @var Nodes\ModifierNode[] $args */
66
                                return match ($fn) {
1✔
67
                                        'modify' => $args[$pos]->printSimple($this, $var),
1✔
68
                                        'modifyContent' => $args[$pos]->printContentAware($this, $var),
1✔
69
                                        'escape' => end($this->escaperStack)->escape($var),
1✔
70
                                };
71
                        },
1✔
72
                        $mask,
1✔
73
                );
74

75
                return preg_replace_callback(
1✔
76
                        '#([,+]?\s*)? % (\d+) \. ([a-z]{3,}) (\?)? (\s*\+\s*)? ()#xi',
1✔
77
                        function ($m) use ($args) {
1✔
78
                                [, $left, $pos, $format, $cond, $right] = $m;
1✔
79
                                $arg = $args[$pos];
1✔
80

81
                                $code = match ($format) {
1✔
82
                                        'dump' => PhpHelpers::dump($arg),
1✔
83
                                        'node' => match (true) {
84
                                                !$arg => '',
1✔
85
                                                $arg instanceof OperatorNode && $arg->getOperatorPrecedence() < Expression\AssignNode::Precedence => '(' . $arg->print($this) . ')',
1✔
86
                                                default => $arg->print($this),
1✔
87
                                        },
88
                                        'raw' => (string) $arg,
1✔
89
                                        'args' => $this->implode($arg instanceof Expression\ArrayNode ? $arg->toArguments() : $arg),
1✔
90
                                        'line' => $arg?->line ? "/* pos $arg->line" . ($arg->column ? ":$arg->column" : '') . ' */' : '',
1✔
91
                                };
92

93
                                if ($cond && ($code === '[]' || $code === '' || $code === 'null')) {
1✔
94
                                        return $right ? $left : $right;
1✔
95
                                }
96

97
                                return $code === ''
1✔
98
                                        ? trim($left) . $right
1✔
99
                                        : $left . $code . $right;
1✔
100
                        },
1✔
101
                        $mask,
1✔
102
                );
103
        }
104

105

106
        public function beginEscape(): Escaper
107
        {
108
                return $this->escaperStack[] = $this->getEscaper();
1✔
109
        }
110

111

112
        public function restoreEscape(): void
113
        {
114
                array_pop($this->escaperStack);
1✔
115
        }
1✔
116

117

118
        public function getEscaper(): Escaper
119
        {
120
                return clone end($this->escaperStack);
1✔
121
        }
122

123

124
        public function addBlock(Block $block): void
1✔
125
        {
126
                $block->escaping = $this->getEscaper()->export();
1✔
127
                $block->method = 'block' . ucfirst(trim(preg_replace('#\W+#', '_', $block->name->print($this)), '_'));
1✔
128
                $lower = strtolower($block->method);
1✔
129
                $used = $this->blocks + ['block' => 1];
1✔
130
                $counter = null;
1✔
131
                while (isset($used[$lower . $counter])) {
1✔
132
                        $counter++;
1✔
133
                }
134

135
                $block->method .= $counter;
1✔
136
                $this->blocks[$lower . $counter] = $block;
1✔
137
        }
1✔
138

139

140
        public function generateId(): int
141
        {
142
                return $this->counter++;
1✔
143
        }
144

145

146
        // PHP helpers
147

148

149
        public function encodeString(string $str, string $quote = "'"): string
1✔
150
        {
151
                return $quote === "'"
1✔
152
                        ? "'" . addcslashes($str, "'\\") . "'"
1✔
153
                        : '"' . addcslashes($str, "\n\r\t\f\v$\"\\") . '"';
1✔
154
        }
155

156

157
        #[\Deprecated]
158
        public function infixOp(Node $node, Node $leftNode, string $operatorString, Node $rightNode): string
159
        {
UNCOV
160
                return $this->parenthesize($node, $leftNode, OperatorNode::AssocLeft)
×
UNCOV
161
                        . $operatorString
×
UNCOV
162
                        . $this->parenthesize($node, $rightNode, OperatorNode::AssocRight);
×
163
        }
164

165

166
        #[\Deprecated]
167
        public function prefixOp(Node $node, string $operatorString, Node $expr): string
168
        {
UNCOV
169
                return $operatorString . $this->parenthesize($node, $expr, OperatorNode::AssocRight);
×
170
        }
171

172

173
        #[\Deprecated]
174
        public function postfixOp(Node $node, Node $var, string $operatorString): string
175
        {
UNCOV
176
                return $this->parenthesize($node, $var, OperatorNode::AssocLeft) . $operatorString;
×
177
        }
178

179

180
        /**
181
         * Prints an expression node with the least amount of parentheses necessary to preserve the meaning.
182
         */
183
        public function parenthesize(OperatorNode $parentNode, Node $childNode, int $childPosition): string
1✔
184
        {
185
                [$parentPrec, $parentAssoc] = $parentNode->getOperatorPrecedence();
1✔
186
                [$childPrec] = $childNode instanceof OperatorNode ? $childNode->getOperatorPrecedence() : null;
1✔
187
                return $childPrec && ($childPrec < $parentPrec || ($parentPrec === $childPrec && $parentAssoc !== $childPosition))
1✔
188
                        ? '(' . $childNode->print($this) . ')'
1✔
189
                        : $childNode->print($this);
1✔
190
        }
191

192

193
        /**
194
         * Prints an array of nodes and implodes the printed values with $glue
195
         * @param  (?Node)[]  $nodes
196
         */
197
        public function implode(array $nodes, string $glue = ', '): string
1✔
198
        {
199
                $pNodes = [];
1✔
200
                foreach ($nodes as $node) {
1✔
201
                        if ($node === null) {
1✔
202
                                $pNodes[] = '';
1✔
203
                        } else {
204
                                $pNodes[] = $node->print($this);
1✔
205
                        }
206
                }
207

208
                return implode($glue, $pNodes);
1✔
209
        }
210

211

212
        public function objectProperty(Node $node): string
1✔
213
        {
214
                return $node instanceof Nodes\NameNode || $node instanceof Nodes\IdentifierNode
1✔
215
                        ? (string) $node
1✔
216
                        : '{' . $node->print($this) . '}';
1✔
217
        }
218

219

220
        public function memberAsString(Node $node): string
1✔
221
        {
222
                return $node instanceof Nodes\NameNode || $node instanceof Nodes\IdentifierNode
1✔
223
                        ? $this->encodeString((string) $node)
1✔
224
                        : $node->print($this);
1✔
225
        }
226

227

228
        /**
229
         * Wraps the LHS of a call in parentheses if needed.
230
         */
231
        public function callExpr(Node $expr): string
1✔
232
        {
233
                return $expr instanceof Nodes\NameNode
1✔
234
                        || $expr instanceof Expression\VariableNode
1✔
235
                        || $expr instanceof Expression\ArrayAccessNode
1✔
236
                        || $expr instanceof Expression\FunctionCallNode
1✔
237
                        || $expr instanceof Expression\MethodCallNode
1✔
238
                        || $expr instanceof Expression\StaticMethodCallNode
1✔
239
                        || $expr instanceof Expression\ArrayNode
1✔
240
                        ? $expr->print($this)
1✔
241
                        : '(' . $expr->print($this) . ')';
1✔
242
        }
243

244

245
        /**
246
         * Wraps the LHS of a dereferencing operation in parentheses if needed.
247
         */
248
        public function dereferenceExpr(Node $expr): string
1✔
249
        {
250
                return $expr instanceof Expression\VariableNode
1✔
251
                        || $expr instanceof Nodes\NameNode
1✔
252
                        || $expr instanceof Expression\ArrayAccessNode
1✔
253
                        || $expr instanceof Expression\PropertyFetchNode
1✔
254
                        || $expr instanceof Expression\StaticPropertyFetchNode
1✔
255
                        || $expr instanceof Expression\FunctionCallNode
1✔
256
                        || $expr instanceof Expression\MethodCallNode
1✔
257
                        || $expr instanceof Expression\StaticMethodCallNode
1✔
258
                        || $expr instanceof Expression\ArrayNode
1✔
259
                        || $expr instanceof Scalar\StringNode
1✔
260
                        || $expr instanceof Scalar\BooleanNode
1✔
261
                        || $expr instanceof Scalar\NullNode
1✔
262
                        || $expr instanceof Expression\ConstantFetchNode
1✔
263
                        || $expr instanceof Expression\ClassConstantFetchNode
1✔
264
                        ? $expr->print($this)
1✔
265
                        : '(' . $expr->print($this) . ')';
1✔
266
        }
267

268

269
        /** @param  array<Nodes\ArgumentNode|Nodes\VariadicPlaceholderNode|Nodes\ArgumentPlaceholderNode>  $args */
270
        public function callPartial(string $expr, array $args): string
1✔
271
        {
272
                $params = $pArgs = [];
1✔
273
                foreach ($args as $arg) {
1✔
274
                        if ($arg instanceof Nodes\ArgumentNode) {
1✔
275
                                $pArgs[] = $arg->print($this);
1✔
276
                        } elseif ($arg instanceof Nodes\VariadicPlaceholderNode) {
1✔
277
                                if (!$pArgs) {
1✔
278
                                        $pArgs[] = '...';
1✔
279
                                } else {
280
                                        $params[] = $pArgs[] = '...$__' . count($params);
1✔
281
                                }
282
                        } elseif ($arg instanceof Nodes\ArgumentPlaceholderNode) {
1✔
283
                                $params[] = $var = '$__' . count($params);
1✔
284
                                $pArgs[] = ($arg->name ? $arg->name . ': ' : '') . $var;
1✔
285
                        }
286
                }
287

288
                $expr .= '(' . implode(', ', $pArgs) . ')';
1✔
289
                return $params ? '(static fn(' . implode(', ', $params) . ') => ' . $expr . ')' : $expr;
1✔
290
        }
291

292

293
        /**
294
         * @param  array<Nodes\ArgumentNode|Nodes\VariadicPlaceholderNode|Nodes\ArgumentPlaceholderNode>  $args
295
         * @return array{string, string}  [array code, params for closure]
296
         */
297
        public function argumentsAsArray(array $args): array
1✔
298
        {
299
                $params = $items = [];
1✔
300
                foreach ($args as $arg) {
1✔
301
                        if ($arg instanceof Nodes\ArgumentNode) {
1✔
302
                                $items[] = $arg->toArrayItem()->print($this);
1✔
303
                        } elseif ($arg instanceof Nodes\VariadicPlaceholderNode) {
1✔
304
                                $params[] = $items[] = '...$__' . count($params);
1✔
305
                        } elseif ($arg instanceof Nodes\ArgumentPlaceholderNode) {
1✔
306
                                $params[] = $var = '$__' . count($params);
1✔
307
                                $items[] = ($arg->name ? var_export((string) $arg->name, true) . ' => ' : '') . $var;
1✔
308
                        }
309
                }
310

311
                return ['[' . implode(', ', $items) . ']', implode(', ', $params)];
1✔
312
        }
313

314

315
        /**
316
         * Ensures that expression evaluates to string or throws exception.
317
         */
318
        public function ensureString(Nodes\ExpressionNode $name, string $entity): string
1✔
319
        {
320
                return $name instanceof Scalar\StringNode
1✔
321
                        ? $name->print($this)
1✔
322
                        : $this->format(
1✔
323
                                '(LR\Helpers::stringOrNull($ʟ_tmp = %node) ?? throw new InvalidArgumentException(sprintf(%dump, get_debug_type($ʟ_tmp))))',
1✔
324
                                $name,
325
                                $entity . ' must be a string, %s given.',
1✔
326
                        );
327
        }
328
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc