Completed
Push — master ( a775b3...d1d7e4 )
by Todd
11s
created

Compiler::compileIf()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 28
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 28
ccs 19
cts 19
cp 1
rs 8.8571
c 0
b 0
f 0
cc 2
eloc 18
nc 2
nop 4
crap 2
1
<?php
2
/**
3
 * @author Todd Burry <[email protected]>
4
 * @copyright 2009-2017 Vanilla Forums Inc.
5
 * @license MIT
6
 */
7
8
namespace Ebi;
9
10
use DOMAttr;
11
use DOMElement;
12
use DOMNode;
13
14
class Compiler {
15
    const T_IF = 'x-if';
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 8 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
16
    const T_EACH = 'x-each';
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 6 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
17
    const T_WITH = 'x-with';
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 6 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
18
    const T_LITERAL = 'x-literal';
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
19
    const T_AS = 'x-as';
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 8 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
20
    const T_COMPONENT = 'x-component';
21
    const T_CHILDREN = 'x-children';
22
    const T_BLOCK = 'x-block';
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 5 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
23
    const T_ELSE = 'x-else';
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 6 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
24
    const T_EMPTY = 'x-empty';
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 5 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
25
    const T_X = 'x';
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 9 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
26
    const T_INCLUDE = 'x-include';
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
27
    const T_EXPR = 'x-expr';
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 6 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
28
    const T_UNESCAPE = 'x-unescape';
29
30
    protected static $special = [
31
        self::T_COMPONENT => 1,
32
        self::T_IF => 2,
33
        self::T_ELSE => 3,
34
        self::T_EACH => 4,
35
        self::T_EMPTY => 5,
36
        self::T_CHILDREN => 6,
37
        self::T_INCLUDE => 7,
38
        self::T_WITH => 8,
39
        self::T_BLOCK => 9,
40
        self::T_LITERAL => 10,
41
        self::T_AS => 11,
42
        self::T_UNESCAPE => 12
43
    ];
44
45
    protected static $htmlTags = [
46
        'a' => 'i',
47
        'abbr' => 'i',
48
        'acronym' => 'i', // deprecated
49
        'address' => 'b',
50
//        'applet' => 'i', // deprecated
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
51
        'area' => 'i',
52
        'article' => 'b',
53
        'aside' => 'b',
54
        'audio' => 'i',
55
        'b' => 'i',
56
        'base' => 'i',
57
//        'basefont' => 'i',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
58
        'bdi' => 'i',
59
        'bdo' => 'i',
60
//        'bgsound' => 'i',
0 ignored issues
show
Unused Code Comprehensibility introduced by
54% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
61
//        'big' => 'i',
62
        'x' => 'i',
63
//        'blink' => 'i',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
64
        'blockquote' => 'b',
65
        'body' => 'b',
66
        'br' => 'i',
67
        'button' => 'i',
68
        'canvas' => 'b',
69
        'caption' => 'i',
70
//        'center' => 'b',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
71
        'cite' => 'i',
72
        'code' => 'i',
73
        'col' => 'i',
74
        'colgroup' => 'i',
75
//        'command' => 'i',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
76
        'content' => 'i',
77
        'data' => 'i',
78
        'datalist' => 'i',
79
        'dd' => 'b',
80
        'del' => 'i',
81
        'details' => 'i',
82
        'dfn' => 'i',
83
        'dialog' => 'i',
84
//        'dir' => 'i',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
85
        'div' => 'i',
86
        'dl' => 'b',
87
        'dt' => 'b',
88
//        'element' => 'i',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
89
        'em' => 'i',
90
        'embed' => 'i',
91
        'fieldset' => 'b',
92
        'figcaption' => 'b',
93
        'figure' => 'b',
94
//        'font' => 'i',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
95
        'footer' => 'b',
96
        'form' => 'b',
97
        'frame' => 'i',
98
        'frameset' => 'i',
99
        'h1' => 'b',
100
        'h2' => 'b',
101
        'h3' => 'b',
102
        'h4' => 'b',
103
        'h5' => 'b',
104
        'h6' => 'b',
105
        'head' => 'b',
106
        'header' => 'b',
107
        'hgroup' => 'b',
108
        'hr' => 'b',
109
        'html' => 'b',
110
        'i' => 'i',
111
        'iframe' => 'i',
112
        'image' => 'i',
113
        'img' => 'i',
114
        'input' => 'i',
115
        'ins' => 'i',
116
        'isindex' => 'i',
117
        'kbd' => 'i',
118
        'keygen' => 'i',
119
        'label' => 'i',
120
        'legend' => 'i',
121
        'li' => 'i',
122
        'link' => 'i',
123
//        'listing' => 'i',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
124
        'main' => 'b',
125
        'map' => 'i',
126
        'mark' => 'i',
127
//        'marquee' => 'i',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
128
        'menu' => 'i',
129
        'menuitem' => 'i',
130
        'meta' => 'i',
131
        'meter' => 'i',
132
        'multicol' => 'i',
133
        'nav' => 'b',
134
        'nobr' => 'i',
135
        'noembed' => 'i',
136
        'noframes' => 'i',
137
        'noscript' => 'b',
138
        'object' => 'i',
139
        'ol' => 'b',
140
        'optgroup' => 'i',
141
        'option' => 'b',
142
        'output' => 'i',
143
        'p' => 'b',
144
        'param' => 'i',
145
        'picture' => 'i',
146
//        'plaintext' => 'i',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
147
        'pre' => 'b',
148
        'progress' => 'i',
149
        'q' => 'i',
150
        'rp' => 'i',
151
        'rt' => 'i',
152
        'rtc' => 'i',
153
        'ruby' => 'i',
154
        's' => 'i',
155
        'samp' => 'i',
156
        'script' => 'i',
157
        'section' => 'b',
158
        'select' => 'i',
159
//        'shadow' => 'i',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
160
        'slot' => 'i',
161
        'small' => 'i',
162
        'source' => 'i',
163
//        'spacer' => 'i',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
164
        'span' => 'i',
165
//        'strike' => 'i',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
166
        'strong' => 'i',
167
        'style' => 'i',
168
        'sub' => 'i',
169
        'summary' => 'i',
170
        'sup' => 'i',
171
        'table' => 'b',
172
        'tbody' => 'i',
173
        'td' => 'i',
174
        'template' => 'i',
175
        'textarea' => 'i',
176
        'tfoot' => 'b',
177
        'th' => 'i',
178
        'thead' => 'i',
179
        'time' => 'i',
180
        'title' => 'i',
181
        'tr' => 'i',
182
        'track' => 'i',
183
//        'tt' => 'i',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
184
        'u' => 'i',
185
        'ul' => 'b',
186
        'var' => 'i',
187
        'video' => 'b',
188
        'wbr' => 'i',
189
190
        /// SVG ///
191
        'animate' => 's',
192
        'animateColor' => 's',
193
        'animateMotion' => 's',
194
        'animateTransform' => 's',
195
//        'canvas' => 's',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
196
        'circle' => 's',
197
        'desc' => 's',
198
        'defs' => 's',
199
        'discard' => 's',
200
        'ellipse' => 's',
201
        'g' => 's',
202
//        'image' => 's',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
203
        'line' => 's',
204
        'marker' => 's',
205
        'mask' => 's',
206
        'missing-glyph' => 's',
207
        'mpath' => 's',
208
        'metadata' => 's',
209
        'path' => 's',
210
        'pattern' => 's',
211
        'polygon' => 's',
212
        'polyline' => 's',
213
        'rect' => 's',
214
        'set' => 's',
215
        'svg' => 's',
216
        'switch' => 's',
217
        'symbol' => 's',
218
        'text' => 's',
219
//        'unknown' => 's',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
220
        'use' => 's',
221
    ];
222
223
    /**
224
     * @var ExpressionLanguage
225
     */
226
    protected $expressions;
227
228 51
    public function __construct() {
229 51
        $this->expressions = new ExpressionLanguage();
230 51
        $this->expressions->setNamePattern('/[@a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A');
231 51
    }
232
233
    /**
234
     * Register a runtime function.
235
     *
236
     * @param string $name The name of the function.
237
     * @param callable $function The function callback.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $function not be callable|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
238
     */
239 49
    public function defineFunction($name, $function = null) {
240 49
        if ($function === null) {
241 1
            $function = $name;
242
        }
243
244 49
        $this->expressions->register(
245 49
            $name,
246 49
            $this->getFunctionCompiler($name, $function),
247 49
            $this->getFunctionEvaluator($function)
248
        );
249 49
    }
250
251 49
    private function getFunctionEvaluator($function) {
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
252 49
        if ($function === 'empty') {
253 49
            return function ($expr) {
254
                return empty($expr);
255 49
            };
256 48
        } elseif ($function === 'isset') {
257
            return function ($expr) {
258
                return isset($expr);
259
            };
260
        }
261
262 48
        return $function;
263
    }
264
265 49
    private function getFunctionCompiler($name, $function) {
266 49
        $var = var_export(strtolower($name), true);
267 49
        $fn = function ($expr) use ($var) {
268 1
            return "\$this->call($var, $expr)";
269 49
        };
270
271 49
        if (is_string($function)) {
272 49
            $fn = function (...$args) use ($function) {
273 11
                return $function.'('.implode(', ', $args).')';
274 49
            };
275 48
        } elseif (is_array($function)) {
276 48
            if (is_string($function[0])) {
277
                $fn = function (...$args) use ($function) {
278
                    return "$function[0]::$function[1](".implode(', ', $args).')';
279
                };
280 48
            } elseif ($function[0] instanceof Ebi) {
281 48
                $fn = function (...$args) use ($function) {
282 6
                    return "\$this->$function[1](".implode(', ', $args).')';
283 48
                };
284
            }
285
        }
286
287 49
        return $fn;
288
    }
289
290 43
    public function compile($src, array $options = []) {
291 43
        $options += ['basename' => '', 'runtime' => true];
292
293 43
        $src = trim($src);
294
295 43
        $dom = new \DOMDocument();
296 43
        libxml_use_internal_errors(true);
297
298 43
        $fragment = false;
299 43
        if (strpos($src, '<html') === false) {
300 42
            $src = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><html><body>$src</body></html>";
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 6 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
301 42
            $fragment = true;
302
        }
303
304 43
        $dom->loadHTML($src, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD | LIBXML_NOCDATA | LIBXML_NOXMLDECL);
305
//        $arr = $this->domToArray($dom);
0 ignored issues
show
Unused Code Comprehensibility introduced by
59% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
306
307 43
        $out = new CompilerBuffer();
308
309 43
        $out->setBasename($options['basename']);
310
311 43
        if ($options['runtime']) {
312 42
            $name = var_export($options['basename'], true);
313 42
            $out->appendCode("\$this->defineComponent($name, function (\$props = [], \$children = []) {\n");
314
        } else {
315 1
            $out->appendCode("function (\$props = [], \$children = []) {\n");
316
        }
317
318 43
        $out->pushScope(['this' => 'props']);
319 43
        $out->indent(+1);
320
321 43
        $parent = $fragment ? $dom->firstChild->nextSibling->firstChild : $dom;
322
323 43
        foreach ($parent->childNodes as $node) {
324 43
            $this->compileNode($node, $out);
325
        }
326
327 43
        $out->indent(-1);
328 43
        $out->popScope();
329
330 43
        if ($options['runtime']) {
331 42
            $out->appendCode("});");
332
        } else {
333 1
            $out->appendCode("};");
334
        }
335
336 43
        $r = $out->flush();
337 43
        return $r;
338
    }
339
340 34
    protected function isComponent($tag) {
341 34
        return !isset(static::$htmlTags[$tag]);
342
    }
343
344 43
    protected function compileNode(DOMNode $node, CompilerBuffer $out) {
345 43
        if ($out->getNodeProp($node, 'skip')) {
346 4
            return;
347
        }
348
349 43
        switch ($node->nodeType) {
350 43
            case XML_TEXT_NODE:
351 38
                $this->compileTextNode($node, $out);
352 38
                break;
353 40
            case XML_ELEMENT_NODE:
354
                /* @var \DOMElement $node */
355 40
                $this->compileElementNode($node, $out);
356 40
                break;
357 2
            case XML_COMMENT_NODE:
358
                /* @var \DOMComment $node */
359 1
                $this->compileCommentNode($node, $out);
360 1
                break;
361 1
            case XML_DOCUMENT_TYPE_NODE:
362 1
                $out->echoLiteral("<!DOCTYPE {$node->name}>\n");
0 ignored issues
show
Bug introduced by
The property name does not seem to exist in DOMNode.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
363 1
                break;
364
            default:
365
                $r = "// Unknown node\n".
0 ignored issues
show
Unused Code introduced by
$r is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
366
                    '// '.str_replace("\n", "\n// ", $node->ownerDocument->saveHTML($node));
367
        }
368 43
    }
369
370
    protected function domToArray(DOMNode $root) {
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
371
        $result = [];
372
373
        if ($root->hasAttributes()) {
374
            $attrs = $root->attributes;
375
            foreach ($attrs as $attr) {
376
                $result['@attributes'][$attr->name] = $attr->value;
377
            }
378
        }
379
380
        if ($root->hasChildNodes()) {
381
            $children = $root->childNodes;
382
            if ($children->length == 1) {
383
                $child = $children->item(0);
384
                if ($child->nodeType == XML_TEXT_NODE) {
385
                    $result['_value'] = $child->nodeValue;
386
                    return count($result) == 1
387
                        ? $result['_value']
388
                        : $result;
389
                }
390
            }
391
            $groups = [];
392
            foreach ($children as $child) {
393
                if (!isset($result[$child->nodeName])) {
394
                    $result[$child->nodeName] = $this->domToArray($child);
395
                } else {
396
                    if (!isset($groups[$child->nodeName])) {
397
                        $result[$child->nodeName] = [$result[$child->nodeName]];
398
                        $groups[$child->nodeName] = 1;
399
                    }
400
                    $result[$child->nodeName][] = $this->domToArray($child);
401
                }
402
            }
403
        }
404
405
        return $result;
406
    }
407
408 1
    protected function newline(DOMNode $node, CompilerBuffer $out) {
409 1
        if ($node->previousSibling && $node->previousSibling->nodeType !== XML_COMMENT_NODE) {
410
            $out->appendCode("\n");
411
        }
412 1
    }
413
414 1
    protected function compileCommentNode(\DOMComment $node, CompilerBuffer $out) {
415 1
        $comments = explode("\n", trim($node->nodeValue));
416
417 1
        $this->newline($node, $out);
418 1
        foreach ($comments as $comment) {
419 1
            $out->appendCode("// $comment\n");
420
        }
421 1
    }
422
423 38
    protected function compileTextNode(DOMNode $node, CompilerBuffer $out) {
424 38
        $text = $this->ltrim($this->rtrim($node->nodeValue, $node, $out), $node, $out);
425
426 38
        $items = $this->splitExpressions($text);
427
428 38
        foreach ($items as $i => list($text, $offset)) {
429 38
            if (preg_match('`^{\S`', $text)) {
430 24
                if (preg_match('`^{\s*unescape\((.+)\)\s*}$`', $text, $m)) {
431 2
                    $out->echoCode($this->expr($m[1], $out));
432
                } else {
433 24
                    $out->echoCode('htmlspecialchars('.$this->expr(substr($text, 1, -1), $out).')');
434
                }
435
            } else {
436
//                if ($i === 0) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
52% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
437
//                    $text = $this->ltrim($text, $node, $out);
438
//                }
439
//                if ($i === count($items) - 1) {
440
//                    $text = $this->rtrim($text, $node, $out);
441
//                }
442
443 38
                $out->echoLiteral($text);
444
            }
445
        }
446 38
    }
447
448 40
    protected function compileElementNode(DOMElement $node, CompilerBuffer $out) {
449 40
        list($attributes, $special) = $this->splitAttributes($node);
450
451 40
        if ($node->tagName === self::T_EXPR) {
452 4
            $this->compileExpressionNode($node, $attributes, $special, $out);
453 36
        } elseif (!empty($special) || $this->isComponent($node->tagName)) {
454 30
            $this->compileSpecialNode($node, $attributes, $special, $out);
455
        } else {
456 18
            $this->compileOpenTag($node, $node->attributes, $out);
457
458 18
            foreach ($node->childNodes as $childNode) {
459 17
                $this->compileNode($childNode, $out);
460
            }
461
462 18
            $this->compileCloseTag($node, $out);
463
        }
464 40
    }
465
466 38
    protected function splitExpressions($value) {
467 38
        $values = preg_split('`({\S[^}]*?})`', $value, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE);
468 38
        return $values;
469
    }
470
471 38
    protected function expr($expr, CompilerBuffer $output, DOMAttr $attr = null) {
472 38
        $names = $output->getScopeVariables();
473
474 38
        $compiled = $this->expressions->compile($expr, function ($name) use ($names) {
0 ignored issues
show
Documentation introduced by
function ($name) use($na...e, true) . ']'; } } is of type object<Closure>, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
475 36
            if (isset($names[$name])) {
476 18
                return $names[$name];
477 30
            } elseif ($name[0] === '@') {
478 1
                return 'this->meta['.var_export(substr($name, 1), true).']';
479
            } else {
480 29
                return $names['this'].'['.var_export($name, true).']';
481
            }
482 38
        });
483
484 38
        if ($attr !== null && null !== $fn = $this->getAttributeFunction($attr)) {
485 4
            $compiled = call_user_func($fn, $compiled);
486
        }
487
488 38
        return $compiled;
489
    }
490
491
    /**
492
     * Get the compiler function to wrap an attribute.
493
     *
494
     * Attribute functions are regular expression functions, but with a special naming convention. The following naming
495
     * conventions are supported:
496
     *
497
     * - **@tag:attribute**: Applies to an attribute only on a specific tag.
498
     * - **@attribute**: Applies to all attributes with a given name.
499
     *
500
     * @param DOMAttr $attr The attribute to look at.
501
     * @return callable|null A function or **null** if the attribute doesn't have a function.
502
     */
503 13
    private function getAttributeFunction(DOMAttr $attr) {
504 13
        $keys = ['@'.$attr->ownerElement->tagName.':'.$attr->name, '@'.$attr->name];
505
506 13
        foreach ($keys as $key) {
507 13
            if (null !== $fn = $this->expressions->getFunctionCompiler($key)) {
508 13
                return $fn;
509
            }
510
        }
511 8
    }
512
513
    /**
514
     * @param DOMElement $node
515
     */
516 40
    protected function splitAttributes(DOMElement $node) {
517 40
        $attributes = [];
518 40
        $special = [];
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
519
520 40
        foreach ($node->attributes as $name => $attribute) {
521 38
            if (isset(static::$special[$name])) {
522 32
                $special[$name] = $attribute;
523
            } else {
524 38
                $attributes[$name] = $attribute;
525
            }
526
        }
527
528 40
        uksort($special, function ($a, $b) {
529 7
            return strnatcmp(static::$special[$a], static::$special[$b]);
530 40
        });
531
532 40
        return [$attributes, $special];
533
    }
534
535 30
    protected function compileSpecialNode(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
536 30
        $specialName = key($special);
537
538
        switch ($specialName) {
539 30
            case self::T_COMPONENT:
540 8
                $this->compileComponentRegister($node, $attributes, $special, $out);
541 8
                break;
542 30
            case self::T_IF:
543 7
                $this->compileIf($node, $attributes, $special, $out);
544 7
                break;
545 30
            case self::T_EACH:
546 12
                $this->compileEach($node, $attributes, $special, $out);
547 12
                break;
548 21
            case self::T_BLOCK:
549 1
                $this->compileBlock($node, $attributes, $special, $out);
550 1
                break;
551 21
            case self::T_CHILDREN:
552 2
                $this->compileChildBlock($node, $attributes, $special, $out);
553 2
                break;
554 21
            case self::T_INCLUDE:
555 1
                $this->compileComponentInclude($node, $attributes, $special, $out);
556 1
                break;
557 21
            case self::T_WITH:
558 3
                if ($this->isComponent($node->tagName)) {
559
                    // With has a special meaning in components.
560 2
                    $this->compileComponentInclude($node, $attributes, $special, $out);
561
                } else {
562 1
                    $this->compileWith($node, $attributes, $special, $out);
563
                }
564 3
                break;
565 21
            case self::T_LITERAL:
566 2
                $this->compileLiteral($node, $attributes, $special, $out);
567 2
                break;
568 19
            case '':
569 19
                if ($this->isComponent($node->tagName)) {
570 6
                    $this->compileComponentInclude($node, $attributes, $special, $out);
571
                } else {
572 18
                    $this->compileElement($node, $attributes, $out);
573
                }
574 19
                break;
575
        }
576 30
    }
577
578
    /**
579
     * Compile component registering.
580
     *
581
     * @param DOMElement $node
582
     * @param $attributes
583
     * @param $special
584
     * @param CompilerBuffer $out
585
     */
586 8
    public function compileComponentRegister(DOMElement $node, $attributes, $special, CompilerBuffer $out) {
587 8
        $name = strtolower($special[self::T_COMPONENT]->value);
588 8
        unset($special[self::T_COMPONENT]);
589
590 8
        $prev = $out->select($name);
591
592 8
        $varName = var_export($name, true);
593 8
        $out->appendCode("\$this->defineComponent($varName, function (\$props = [], \$children = []) {\n");
594 8
        $out->pushScope(['this' => 'props']);
595 8
        $out->indent(+1);
596
597
        try {
598 8
            $this->compileSpecialNode($node, $attributes, $special, $out);
599 8
        } finally {
600 8
            $out->popScope();
601 8
            $out->indent(-1);
602 8
            $out->appendCode("});");
603 8
            $out->select($prev);
604
        }
605 8
    }
606
607 1
    private function compileBlock(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
608 1
        $name = strtolower($special[self::T_BLOCK]->value);
609 1
        unset($special[self::T_BLOCK]);
610
611 1
        $prev = $out->select($name);
612
613 1
        $use = '$'.implode(', $', $out->getScopeVariables()).', $children';
614
615 1
        $out->appendCode("function () use ($use) {\n");
616 1
        $out->pushScope(['this' => 'props']);
617 1
        $out->indent(+1);
618
619
        try {
620 1
            $this->compileSpecialNode($node, $attributes, $special, $out);
621 1
        } finally {
622 1
            $out->indent(-1);
623 1
            $out->popScope();
624 1
            $out->appendCode("}");
625 1
            $out->select($prev);
626
        }
627
628 1
        return $out;
629
    }
630
631
    /**
632
     * Compile component inclusion and rendering.
633
     *
634
     * @param DOMElement $node
635
     * @param DOMAttr[] $attributes
636
     * @param DOMAttr[] $special
637
     * @param CompilerBuffer $out
638
     */
639 9
    protected function compileComponentInclude(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
640
        // Generate the attributes into a property array.
641 9
        $props = [];
642 9
        foreach ($attributes as $name => $attribute) {
643
            /* @var DOMAttr $attr */
644 5
            if ($this->isExpression($attribute->value)) {
645 4
                $expr = $this->expr(substr($attribute->value, 1, -1), $out, $attribute);
646
            } else {
647 1
                $expr = var_export($attribute->value, true);
648
            }
649
650 5
            $props[] = var_export($name, true).' => '.$expr;
651
        }
652 9
        $propsStr = '['.implode(', ', $props).']';
653
654 9
        if (isset($special[self::T_WITH])) {
655 2
            $withExpr = $this->expr($special[self::T_WITH]->value, $out, $special[self::T_WITH]);
656 2
            unset($special[self::T_WITH]);
657
658 2
            $propsStr = empty($props) ? $withExpr : $propsStr.' + (array)'.$withExpr;
659
        } elseif (empty($props)) {
660
            // By default the current context is passed to components.
661 3
            $propsStr = $this->expr('this', $out);
662
        }
663
664
        // Compile the children blocks.
665 9
        $blocks = $this->compileComponentBlocks($node, $out);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
666 9
        $blocksStr = $blocks->flush();
667
668 9
        if (isset($special[self::T_INCLUDE])) {
669 1
            $name = $this->expr($special[self::T_INCLUDE]->value, $out, $special[self::T_INCLUDE]);
670
        } else {
671 8
            $name = var_export($node->tagName, true);
672
        }
673
674 9
        $out->appendCode("\$this->write($name, $propsStr, $blocksStr);\n");
675 9
    }
676
677
    /**
678
     * @param DOMElement $parent
679
     * @return CompilerBuffer
680
     */
681 9
    protected function compileComponentBlocks(DOMElement $parent, CompilerBuffer $out) {
682 9
        $blocksOut = new CompilerBuffer(CompilerBuffer::STYLE_ARRAY, [
683 9
            'baseIndent' => $out->getIndent(),
684 9
            'indent' => $out->getIndent() + 1,
685 9
            'depth' => $out->getDepth(),
686 9
            'scopes' => $out->getAllScopes()
687
        ]);
688
689 9
        if ($this->isEmptyNode($parent)) {
690 7
            return $blocksOut;
691
        }
692
693 2
        $use = '$'.implode(', $', $blocksOut->getScopeVariables()).', $children';
694
695 2
        $blocksOut->appendCode("function () use ($use) {\n");
696 2
        $blocksOut->indent(+1);
697
698
        try {
699 2
            foreach ($parent->childNodes as $node) {
700 2
                $this->compileNode($node, $blocksOut);
701
            }
702 2
        } finally {
703 2
            $blocksOut->indent(-1);
704 2
            $blocksOut->appendCode("}");
705
        }
706
707 2
        return $blocksOut;
708
    }
709
710 22
    protected function compileTagComment(DOMElement $node, $attributes, $special, CompilerBuffer $out) {
0 ignored issues
show
Unused Code introduced by
The parameter $attributes is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
711
        // Don't double up comments.
712 22
        if ($node->previousSibling && $node->previousSibling->nodeType === XML_COMMENT_NODE) {
713
            return;
714
        }
715
716 22
        $str = '<'.$node->tagName;
717 22
        foreach ($special as $attr) {
718
            /* @var DOMAttr $attr */
719 22
            $str .= ' '.$attr->name.(empty($attr->value) ? '' : '="'.htmlspecialchars($attr->value).'"');
720
        }
721 22
        $str .= '>';
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 5 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
722 22
        $comments = explode("\n", $str);
723 22
        foreach ($comments as $comment) {
724 22
            $out->appendCode("// $comment\n");
725
        }
726 22
    }
727
728 36
    protected function compileOpenTag(DOMElement $node, $attributes, CompilerBuffer $out, $force = false) {
729 36
        if ($node->tagName === self::T_X) {
730 3
            return;
731
        }
732
733 35
        $out->echoLiteral('<'.$node->tagName);
734
735 35
        foreach ($attributes as $name => $attribute) {
736
            /* @var DOMAttr $attribute */
737 9
            $out->echoLiteral(' '.$name.'="');
738
739
            // Check for an attribute expression.
740 9
            if ($this->isExpression($attribute->value)) {
741 6
                $out->echoCode('htmlspecialchars('.$this->expr(substr($attribute->value, 1, -1), $out, $attribute).')');
742 3
            } elseif (null !== $fn = $this->getAttributeFunction($attribute)) {
743 2
                $value  = call_user_func($fn, var_export($attribute->value, true));
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned correctly; expected 1 space but found 2 spaces

This check looks for improperly formatted assignments.

Every assignment must have exactly one space before and one space after the equals operator.

To illustrate:

$a = "a";
$ab = "ab";
$abc = "abc";

will have no issues, while

$a   = "a";
$ab  = "ab";
$abc = "abc";

will report issues in lines 1 and 2.

Loading history...
744
745 2
                $out->echoCode("htmlspecialchars($value)");
746
747
            } else {
748 2
                $out->echoLiteral(htmlspecialchars($attribute->value));
749
            }
750
751 9
            $out->echoLiteral('"');
752
        }
753
754 35
        if ($node->hasChildNodes() || $force) {
755 34
            $out->echoLiteral('>');
756
        } else {
757 2
            $out->echoLiteral(" />");
758
        }
759 35
    }
760
761 12
    private function isExpression($value) {
762 12
        return preg_match('`^{\S.*}$`', $value);
763
    }
764
765 36
    protected function compileCloseTag(DOMElement $node, CompilerBuffer $out, $force = false) {
766 36
        if (($force || $node->hasChildNodes()) && $node->tagName !== self::T_X) {
767 34
            $out->echoLiteral("</{$node->tagName}>");
768
        }
769 36
    }
770
771 3
    protected function isEmptyText(DOMNode $node) {
772 3
        return $node instanceof \DOMText && empty(trim($node->data));
773
    }
774
775 9
    protected function isEmptyNode(DOMNode $node) {
776 9
        if (!$node->hasChildNodes()) {
777 7
            return true;
778
        }
779
780 2
        foreach ($node->childNodes as $childNode) {
781 2
            if ($childNode instanceof DOMElement) {
782 1
                return false;
783
            }
784 1
            if ($childNode instanceof \DOMText && !$this->isEmptyText($childNode)) {
785 1
                return false;
786
            }
787
        }
788
789
        return true;
790
    }
791
792 7
    protected function compileIf(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
793 7
        $this->compileTagComment($node, $attributes, $special, $out);
794 7
        $expr = $this->expr($special[self::T_IF]->value, $out);
795 7
        unset($special[self::T_IF]);
796
797 7
        $elseNode = $this->findSpecialNode($node, self::T_ELSE, self::T_IF);
798 7
        $out->setNodeProp($elseNode, 'skip', true);
799
800 7
        $out->appendCode('if ('.$expr.") {\n");
801 7
        $out->indent(+1);
802
803 7
        $this->compileSpecialNode($node, $attributes, $special, $out);
804
805 7
        $out->indent(-1);
806
807 7
        if ($elseNode) {
808 2
            list($attributes, $special) = $this->splitAttributes($elseNode);
809 2
            unset($special[self::T_ELSE]);
810
811 2
            $out->appendCode("} else {\n");
812
813 2
            $out->indent(+1);
814 2
            $this->compileSpecialNode($elseNode, $attributes, $special, $out);
815 2
            $out->indent(-1);
816
        }
817
818 7
        $out->appendCode("}\n");
819 7
    }
820
821 12
    protected function compileEach(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
822 12
        $this->compileTagComment($node, $attributes, $special, $out);
823 12
        $this->compileOpenTag($node, $attributes, $out);
824
825 12
        $emptyNode = $this->findSpecialNode($node, self::T_EMPTY, self::T_ELSE);
826 12
        $out->setNodeProp($emptyNode, 'skip', true);
827
828 12
        if ($emptyNode === null) {
829 10
            $this->compileEachLoop($node, $attributes, $special, $out);
830
        } else {
831 2
            $expr = $this->expr("empty({$special[self::T_EACH]->value})", $out);
832
833 2
            list ($emptyAttributes, $emptySpecial) = $this->splitAttributes($emptyNode);
834 2
            unset($emptySpecial[self::T_EMPTY]);
835
836 2
            $out->appendCode('if ('.$expr.") {\n");
837
838 2
            $out->indent(+1);
839 2
            $this->compileSpecialNode($emptyNode, $emptyAttributes, $emptySpecial, $out);
840 2
            $out->indent(-1);
841
842 2
            $out->appendCode("} else {\n");
843
844 2
            $out->indent(+1);
845 2
            $this->compileEachLoop($node, $attributes, $special, $out);
846 2
            $out->indent(-1);
847
848 2
            $out->appendCode("}\n");
849
        }
850
851 12
        $this->compileCloseTag($node, $out);
852 12
    }
853
854 1
    protected function compileWith(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
855 1
        $this->compileTagComment($node, $attributes, $special, $out);
856 1
        $with = $this->expr($special[self::T_WITH]->value, $out);
857
858 1
        $out->depth(+1);
859 1
        $scope = ['this' => $out->depthName('props')];
860 1
        if (!empty($special[self::T_AS]) && preg_match('`^([a-z0-9]+)$`', $special[self::T_AS]->value, $m)) {
861
            // The template specified an x-as attribute to alias the with expression.
862 1
            $scope = [$m[1] => $out->depthName('props')];
863
        }
864 1
        unset($special[self::T_WITH], $special[self::T_AS]);
865
866 1
        $out->pushScope($scope);
867 1
        $out->appendCode('$'.$out->depthName('props')." = $with;\n");
868
869 1
        $this->compileSpecialNode($node, $attributes, $special, $out);
870
871 1
        $out->depth(-1);
872 1
        $out->popScope();
873 1
    }
874
875 2
    protected function compileLiteral(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
876 2
        $this->compileTagComment($node, $attributes, $special, $out);
877 2
        unset($special[self::T_LITERAL]);
878
879 2
        $this->compileOpenTag($node, $attributes, $out);
880
881 2
        foreach ($node->childNodes as $childNode) {
882 2
            $html = $childNode->ownerDocument->saveHTML($childNode);
883 2
            $out->echoLiteral($html);
884
        }
885
886 2
        $this->compileCloseTag($node, $out);
887 2
    }
888
889 18
    protected function compileElement(DOMElement $node, array $attributes, CompilerBuffer $out) {
890 18
        $this->compileOpenTag($node, $attributes, $out);
891
892 18
        foreach ($node->childNodes as $childNode) {
893 18
            $this->compileNode($childNode, $out);
894
        }
895
896 18
        $this->compileCloseTag($node, $out);
897 18
    }
898
899
    /**
900
     * Find a special node in relation to another node.
901
     *
902
     * This method is used to find things such as x-empty and x-else elements.
903
     *
904
     * @param DOMElement $node The node to search in relation to.
905
     * @param string $attribute The name of the attribute to search for.
906
     * @param string $parentAttribute The name of the parent attribute to resolve conflicts.
907
     * @return DOMElement|null Returns the found element node or **null** if not found.
908
     */
909 19
    protected function findSpecialNode(DOMElement $node, $attribute, $parentAttribute) {
910
        // First look for a sibling after the node.
911 19
        for ($sibNode = $node->nextSibling; $sibNode !== null; $sibNode = $sibNode->nextSibling) {
912 2
            if ($sibNode instanceof DOMElement && $sibNode->hasAttribute($attribute)) {
913 2
                return $sibNode;
914
            }
915
916
            // Stop searching if we encounter another node.
917 2
            if (!$this->isEmptyText($sibNode)) {
918
                break;
919
            }
920
        }
921
922
        // Next look inside the node.
923 17
        $parentFound = false;
924 17
        foreach ($node->childNodes as $childNode) {
925 17
            if (!$parentFound && $childNode instanceof DOMElement && $childNode->hasAttribute($attribute)) {
926 2
                return $childNode;
927
            }
928
929 17
            if ($childNode instanceof DOMElement) {
930 12
                $parentFound = $childNode->hasAttribute($parentAttribute);
931 7
            } elseif ($childNode instanceof \DOMText && !empty(trim($childNode->data))) {
932 17
                $parentFound = false;
933
            }
934
        }
935
936 15
        return null;
937
    }
938
939
    /**
940
     * @param DOMElement $node
941
     * @param array $attributes
942
     * @param array $special
943
     * @param CompilerBuffer $out
944
     */
945 12
    private function compileEachLoop(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
0 ignored issues
show
Unused Code introduced by
The parameter $attributes is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
946 12
        $each = $this->expr($special[self::T_EACH]->value, $out);
947 12
        unset($special[self::T_EACH]);
948
949 12
        $as = ['', $out->depthName('props', 1)];
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
950 12
        $scope = ['this' => $as[1]];
951 12
        if (!empty($special[self::T_AS])) {
952 6
            if (preg_match('`(?:([a-z0-9]+)\s+)?([a-z0-9]+)`', $special[self::T_AS]->value, $m)) {
953 6
                $scope = [$m[2] => $as[1]];
954 6
                if (!empty($m[1])) {
955 3
                    $scope[$m[1]] = $as[0] = $out->depthName('i', 1);
956
                }
957
            }
958
        }
959 12
        unset($special[self::T_AS]);
960 12
        if (empty($as[0])) {
961 9
            $out->appendCode("foreach ($each as \${$as[1]}) {\n");
962
        } else {
963 3
            $out->appendCode("foreach ($each as \${$as[0]} => \${$as[1]}) {\n");
964
        }
965 12
        $out->depth(+1);
966 12
        $out->indent(+1);
967 12
        $out->pushScope($scope);
968
969 12
        foreach ($node->childNodes as $childNode) {
970 12
            $this->compileNode($childNode, $out);
971
        }
972
973 12
        $out->indent(-1);
974 12
        $out->depth(-1);
975 12
        $out->popScope();
976 12
        $out->appendCode("}\n");
977 12
    }
978
979 38
    protected function ltrim($text, \DOMNode $node, CompilerBuffer $out) {
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
980 38
        if ($this->inPre($node)) {
981
            return $text;
982
        }
983
984 38
        $sib = $node->previousSibling ?: $node->parentNode;
985 38
        if ($sib === null || !$sib instanceof \DOMElement || $out->getNodeProp($sib, 'skip') || $sib->tagName === self::T_X) {
986 6
            return ltrim($text);
987
        }
988
989 37
        $text = preg_replace('`^\s*\n\s*`', "\n", $text, -1, $count);
990 37
        if ($count === 0) {
991 37
            $text = preg_replace('`^\s+`', ' ', $text);
992
        }
993
994
//        if ($sib !== null && ($sib->nodeType === XML_COMMENT_NODE || in_array($sib->tagName, static::$blocks))) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
53% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
995
//            return ltrim($text);
996
//        }
997 37
        return $text;
998
    }
999
1000 38
    protected function rtrim($text, \DOMNode $node, CompilerBuffer $out) {
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
1001 38
        if ($this->inPre($node)) {
1002
            return $text;
1003
        }
1004
1005 38
        $sib = $node->nextSibling ?: $node->parentNode;
1006
1007 38
        if ($sib === null || !$sib instanceof \DOMElement || $out->getNodeProp($sib, 'skip') || $sib->tagName === self::T_X) {
1008 5
            return rtrim($text);
1009
        }
1010
1011 37
        $text = preg_replace('`\s*\n\s*$`', "\n", $text, -1, $count);
1012 37
        if ($count === 0) {
1013 37
            $text = preg_replace('`\s+$`', ' ', $text);
1014
        }
1015
1016
//        if ($sib !== null && ($sib->nodeType === XML_COMMENT_NODE || in_array($sib->tagName, static::$blocks))) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
53% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1017
//            return rtrim($text);
1018
//        }
1019 37
        return $text;
1020
    }
1021
1022 38
    protected function inPre(\DOMNode $node) {
1023 38
        for ($node = $node->parentNode; $node !== null; $node = $node->parentNode) {
1024 38
            if (in_array($node->nodeType, ['code', 'pre'], true)) {
1025
                return true;
1026
            }
1027
        }
1028 38
        return false;
1029
    }
1030
1031 2
    private function compileChildBlock(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
1032
        /* @var DOMAttr $child */
1033 2
        $child = $special[self::T_CHILDREN];
1034 2
        unset($special[self::T_CHILDREN]);
1035
1036 2
        $key = $child->value === '' ? 0 : $child->value;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
1037 2
        $keyStr = var_export($key, true);
1038
1039 2
        $this->compileOpenTag($node, $attributes, $out, true);
1040
1041 2
        $out->appendCode("if (isset(\$children[{$keyStr}])) {\n");
1042 2
        $out->indent(+1);
1043 2
        $out->appendCode("\$children[{$keyStr}]();\n");
1044 2
        $out->indent(-1);
1045 2
        $out->appendCode("}\n");
1046
1047 2
        $this->compileCloseTag($node, $out, true);
1048 2
    }
1049
1050
    /**
1051
     * Compile an x-expr node.
1052
     *
1053
     * @param DOMElement $node The node to compile.
1054
     * @param array $attributes The node's attributes.
1055
     * @param array $special An array of special attributes.
1056
     * @param CompilerBuffer $out The compiler output.
1057
     */
1058 4
    private function compileExpressionNode(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
0 ignored issues
show
Unused Code introduced by
The parameter $attributes is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1059 4
        $str = $raw = $node->nodeValue;
0 ignored issues
show
Unused Code introduced by
$raw is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1060 4
        $expr = $this->expr($str, $out);
1061
1062 4
        if (!empty($special[self::T_AS]) && preg_match('`^([a-z0-9]+)$`', $special[self::T_AS]->value, $m)) {
1063
            // The template specified an x-as attribute to alias the with expression.
1064 2
            $scope = [$m[1] => $out->depthName('props', 1)];
1065 2
            $out->pushScope($scope);
1066 2
            $out->appendCode('$'.$out->depthName('props', 1)." = $expr;\n");
1067 2
        } elseif (!empty($special[self::T_UNESCAPE])) {
1068 1
            $out->echoCode($expr);
1069
        } else {
1070 1
            $out->echoCode('htmlspecialchars('.$expr.')');
1071
        }
1072 4
    }
1073
}
1074