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

nette / latte / 20107372588

10 Dec 2025 05:19PM UTC coverage: 94.875% (-0.09%) from 94.966%
20107372588

push

github

dg
fixed operator ! priority

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

10 existing lines in 3 files now uncovered.

5517 of 5815 relevant lines covered (94.88%)

0.95 hits per line

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

96.21
/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
        public array $paramsExtraction = [];
26
        public array $blocks = [];
27
        private int $counter = 0;
28

29
        /** @var Escaper[] */
30
        private array $escaperStack = [];
31

32

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

40

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

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

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

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

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

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

102

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

108

109
        public function restoreEscape(): void
110
        {
111
                array_pop($this->escaperStack);
1✔
112
        }
1✔
113

114

115
        public function getEscaper(): Escaper
116
        {
117
                return clone end($this->escaperStack);
1✔
118
        }
119

120

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

132
                $block->method .= $counter;
1✔
133
                $this->blocks[$lower . $counter] = $block;
1✔
134
        }
1✔
135

136

137
        public function generateId(): int
138
        {
139
                return $this->counter++;
1✔
140
        }
141

142

143
        // PHP helpers
144

145

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

153

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

162

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

169

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

176

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

189

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

204
                return implode($glue, $pNodes);
1✔
205
        }
206

207

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

215

216
        public function memberAsString(Node $node): string
1✔
217
        {
218
                return $node instanceof Nodes\NameNode || $node instanceof Nodes\IdentifierNode
1✔
219
                        ? $this->encodeString((string) $node)
1✔
220
                        : $node->print($this);
1✔
221
        }
222

223

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

243

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

270

271
        /**
272
         * @param  Nodes\ArgumentNode[]  $args
273
         */
274
        public function argumentsAsArray(array $args): string
1✔
275
        {
276
                $items = array_map(fn(Nodes\ArgumentNode $arg) => $arg->toArrayItem(), $args);
1✔
277
                return '[' . $this->implode($items) . ']';
1✔
278
        }
279

280

281
        /**
282
         * Ensures that expression evaluates to string or throws exception.
283
         */
284
        public function ensureString(Nodes\ExpressionNode $name, string $entity): string
1✔
285
        {
286
                return $name instanceof Scalar\StringNode
1✔
287
                        ? $name->print($this)
1✔
288
                        : $this->format(
1✔
289
                                '(LR\Helpers::stringOrNull($ʟ_tmp = %node) ?? throw new InvalidArgumentException(sprintf(%dump, get_debug_type($ʟ_tmp))))',
1✔
290
                                $name,
291
                                $entity . ' must be a string, %s given.',
1✔
292
                        );
293
        }
294
}
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