Completed
Pull Request — master (#20)
by Todd
03:42
created

Compiler::compileEach()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 32
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 32
ccs 22
cts 22
cp 1
rs 8.8571
c 0
b 0
f 0
cc 2
eloc 21
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_EBI = 'ebi';
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 7 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
    const T_TAG = 'x-tag';
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 7 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...
30
31
    const IDENT_REGEX = '`^([a-z0-9-]+)$`i';
32
33
    protected static $special = [
34
        self::T_COMPONENT => 1,
35
        self::T_IF => 2,
36
        self::T_ELSE => 3,
37
        self::T_EACH => 4,
38
        self::T_EMPTY => 5,
39
        self::T_CHILDREN => 6,
40
        self::T_INCLUDE => 7,
41
        self::T_WITH => 8,
42
        self::T_BLOCK => 9,
43
        self::T_LITERAL => 10,
44
        self::T_AS => 11,
45
        self::T_UNESCAPE => 12,
46
        self::T_TAG => 13
47
    ];
48
49
    protected static $htmlTags = [
50
        'a' => 'i',
51
        'abbr' => 'i',
52
        'acronym' => 'i', // deprecated
53
        'address' => 'b',
54
//        '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...
55
        'area' => 'i',
56
        'article' => 'b',
57
        'aside' => 'b',
58
        'audio' => 'i',
59
        'b' => 'i',
60
        'base' => 'i',
61
//        '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...
62
        'bdi' => 'i',
63
        'bdo' => 'i',
64
//        '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...
65
//        'big' => 'i',
66
        'x' => 'i',
67
//        '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...
68
        'blockquote' => 'b',
69
        'body' => 'b',
70
        'br' => 'i',
71
        'button' => 'i',
72
        'canvas' => 'b',
73
        'caption' => 'i',
74
//        '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...
75
        'cite' => 'i',
76
        'code' => 'i',
77
        'col' => 'i',
78
        'colgroup' => 'i',
79
//        '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...
80
        'content' => 'i',
81
        'data' => 'i',
82
        'datalist' => 'i',
83
        'dd' => 'b',
84
        'del' => 'i',
85
        'details' => 'i',
86
        'dfn' => 'i',
87
        'dialog' => 'i',
88
//        '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...
89
        'div' => 'i',
90
        'dl' => 'b',
91
        'dt' => 'b',
92
//        '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...
93
        'em' => 'i',
94
        'embed' => 'i',
95
        'fieldset' => 'b',
96
        'figcaption' => 'b',
97
        'figure' => 'b',
98
//        '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...
99
        'footer' => 'b',
100
        'form' => 'b',
101
        'frame' => 'i',
102
        'frameset' => 'i',
103
        'h1' => 'b',
104
        'h2' => 'b',
105
        'h3' => 'b',
106
        'h4' => 'b',
107
        'h5' => 'b',
108
        'h6' => 'b',
109
        'head' => 'b',
110
        'header' => 'b',
111
        'hgroup' => 'b',
112
        'hr' => 'b',
113
        'html' => 'b',
114
        'i' => 'i',
115
        'iframe' => 'i',
116
        'image' => 'i',
117
        'img' => 'i',
118
        'input' => 'i',
119
        'ins' => 'i',
120
        'isindex' => 'i',
121
        'kbd' => 'i',
122
        'keygen' => 'i',
123
        'label' => 'i',
124
        'legend' => 'i',
125
        'li' => 'i',
126
        'link' => 'i',
127
//        '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...
128
        'main' => 'b',
129
        'map' => 'i',
130
        'mark' => 'i',
131
//        '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...
132
        'menu' => 'i',
133
        'menuitem' => 'i',
134
        'meta' => 'i',
135
        'meter' => 'i',
136
        'multicol' => 'i',
137
        'nav' => 'b',
138
        'nobr' => 'i',
139
        'noembed' => 'i',
140
        'noframes' => 'i',
141
        'noscript' => 'b',
142
        'object' => 'i',
143
        'ol' => 'b',
144
        'optgroup' => 'i',
145
        'option' => 'b',
146
        'output' => 'i',
147
        'p' => 'b',
148
        'param' => 'i',
149
        'picture' => 'i',
150
//        '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...
151
        'pre' => 'b',
152
        'progress' => 'i',
153
        'q' => 'i',
154
        'rp' => 'i',
155
        'rt' => 'i',
156
        'rtc' => 'i',
157
        'ruby' => 'i',
158
        's' => 'i',
159
        'samp' => 'i',
160
        'script' => 'i',
161
        'section' => 'b',
162
        'select' => 'i',
163
//        '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...
164
        'slot' => 'i',
165
        'small' => 'i',
166
        'source' => 'i',
167
//        '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...
168
        'span' => 'i',
169
//        '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...
170
        'strong' => 'i',
171
        'style' => 'i',
172
        'sub' => 'i',
173
        'summary' => 'i',
174
        'sup' => 'i',
175
        'table' => 'b',
176
        'tbody' => 'i',
177
        'td' => 'i',
178
        'template' => 'i',
179
        'textarea' => 'i',
180
        'tfoot' => 'b',
181
        'th' => 'i',
182
        'thead' => 'i',
183
        'time' => 'i',
184
        'title' => 'i',
185
        'tr' => 'i',
186
        'track' => 'i',
187
//        '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...
188
        'u' => 'i',
189
        'ul' => 'b',
190
        'var' => 'i',
191
        'video' => 'b',
192
        'wbr' => 'i',
193
194
        /// SVG ///
195
        'animate' => 's',
196
        'animateColor' => 's',
197
        'animateMotion' => 's',
198
        'animateTransform' => 's',
199
//        '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...
200
        'circle' => 's',
201
        'desc' => 's',
202
        'defs' => 's',
203
        'discard' => 's',
204
        'ellipse' => 's',
205
        'g' => 's',
206
//        '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...
207
        'line' => 's',
208
        'marker' => 's',
209
        'mask' => 's',
210
        'missing-glyph' => 's',
211
        'mpath' => 's',
212
        'metadata' => 's',
213
        'path' => 's',
214
        'pattern' => 's',
215
        'polygon' => 's',
216
        'polyline' => 's',
217
        'rect' => 's',
218
        'set' => 's',
219
        'svg' => 's',
220
        'switch' => 's',
221
        'symbol' => 's',
222
        'text' => 's',
223
//        '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...
224
        'use' => 's',
225
    ];
226
227
    protected static $boolAttributes = [
228
        'checked' => 1,
229
        'itemscope' => 1,
230
        'required' => 1,
231
        'selected' => 1,
232
    ];
233
234
    /**
235
     * @var ExpressionLanguage
236
     */
237
    protected $expressions;
238
239 62
    public function __construct() {
240 62
        $this->expressions = new ExpressionLanguage();
241 62
        $this->expressions->setNamePattern('/[@a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A');
242 62
        $this->expressions->register(
243 62
            'hasChildren',
244
            function ($name = null) {
245 1
                return empty($name) ? 'isset($children[0])' : "isset(\$children[$name ?: 0])";
246 62
            },
247
            function ($name = null) {
0 ignored issues
show
Unused Code introduced by
The parameter $name 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...
248
                return false;
249 62
            });
250 62
    }
251
252
    /**
253
     * Register a runtime function.
254
     *
255
     * @param string $name The name of the function.
256
     * @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...
257
     */
258 60
    public function defineFunction($name, $function = null) {
259 60
        if ($function === null) {
260 1
            $function = $name;
261 1
        }
262
263 60
        $this->expressions->register(
264 60
            $name,
265 60
            $this->getFunctionCompiler($name, $function),
266 60
            $this->getFunctionEvaluator($function)
267 60
        );
268 60
    }
269
270 60
    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...
271 60
        if ($function === 'empty') {
272
            return function ($expr) {
273
                return empty($expr);
274 60
            };
275 59
        } elseif ($function === 'isset') {
276
            return function ($expr) {
277
                return isset($expr);
278
            };
279
        }
280
281 59
        return $function;
282
    }
283
284 60
    private function getFunctionCompiler($name, $function) {
285 60
        $var = var_export(strtolower($name), true);
286
        $fn = function ($expr) use ($var) {
287 1
            return "\$this->call($var, $expr)";
288 60
        };
289
290 60
        if (is_string($function)) {
291
            $fn = function (...$args) use ($function) {
292 14
                return $function.'('.implode(', ', $args).')';
293 60
            };
294 60
        } elseif (is_array($function)) {
295 59
            if (is_string($function[0])) {
296
                $fn = function (...$args) use ($function) {
297
                    return "$function[0]::$function[1](".implode(', ', $args).')';
298
                };
299 59
            } elseif ($function[0] instanceof Ebi) {
300
                $fn = function (...$args) use ($function) {
301 7
                    return "\$this->$function[1](".implode(', ', $args).')';
302 59
                };
303 59
            }
304 59
        }
305
306 60
        return $fn;
307
    }
308
309 54
    public function compile($src, array $options = []) {
310 54
        $options += ['basename' => '', 'runtime' => true];
311
312 54
        $src = trim($src);
313
314 54
        $dom = new \DOMDocument();
315 54
        libxml_use_internal_errors(true);
316
317 54
        $fragment = false;
318 54
        if (strpos($src, '<html') === false) {
319 53
            $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...
320 53
            $fragment = true;
321 53
        }
322
323 54
        $dom->loadHTML($src, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD | LIBXML_NOCDATA | LIBXML_NOXMLDECL);
324
//        $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...
325
326 54
        $out = new CompilerBuffer();
327
328 54
        $out->setBasename($options['basename']);
329
330 54
        if ($options['runtime']) {
331 53
            $name = var_export($options['basename'], true);
332 53
            $out->appendCode("\$this->defineComponent($name, function (\$props = [], \$children = []) {\n");
333 53
        } else {
334 1
            $out->appendCode("function (\$props = [], \$children = []) {\n");
335
        }
336
337 54
        $out->pushScope(['this' => 'props']);
338 54
        $out->indent(+1);
339
340 54
        $parent = $fragment ? $dom->firstChild->nextSibling->firstChild : $dom;
341
342 54
        foreach ($parent->childNodes as $node) {
343 54
            $this->compileNode($node, $out);
344 54
        }
345
346 54
        $out->indent(-1);
347 54
        $out->popScope();
348
349 54
        if ($options['runtime']) {
350 53
            $out->appendCode("});");
351 53
        } else {
352 1
            $out->appendCode("};");
353
        }
354
355 54
        $r = $out->flush();
356
357 54
        $errs = libxml_get_errors();
0 ignored issues
show
Unused Code introduced by
$errs 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...
358
359 54
        return $r;
360
    }
361
362 44
    protected function isComponent($tag) {
363 44
        return !isset(static::$htmlTags[$tag]);
364
    }
365
366 54
    protected function compileNode(DOMNode $node, CompilerBuffer $out) {
367 54
        if ($out->getNodeProp($node, 'skip')) {
368 4
            return;
369
        }
370
371 54
        switch ($node->nodeType) {
372 54
            case XML_TEXT_NODE:
373 48
                $this->compileTextNode($node, $out);
374 48
                break;
375 51
            case XML_ELEMENT_NODE:
376
                /* @var \DOMElement $node */
377 51
                $this->compileElementNode($node, $out);
378 51
                break;
379 3
            case XML_COMMENT_NODE:
380
                /* @var \DOMComment $node */
381 1
                $this->compileCommentNode($node, $out);
382 1
                break;
383 2
            case XML_DOCUMENT_TYPE_NODE:
384 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...
385 1
                break;
386 1
            case XML_CDATA_SECTION_NODE:
387 1
                $this->compileTextNode($node, $out);
388 1
                break;
389
            default:
390
                $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...
391
                    '// '.str_replace("\n", "\n// ", $node->ownerDocument->saveHTML($node));
392 54
        }
393 54
    }
394
395
    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...
396
        $result = [];
397
398
        if ($root->hasAttributes()) {
399
            $attrs = $root->attributes;
400
            foreach ($attrs as $attr) {
401
                $result['@attributes'][$attr->name] = $attr->value;
402
            }
403
        }
404
405
        if ($root->hasChildNodes()) {
406
            $children = $root->childNodes;
407
            if ($children->length == 1) {
408
                $child = $children->item(0);
409
                if ($child->nodeType == XML_TEXT_NODE) {
410
                    $result['_value'] = $child->nodeValue;
411
                    return count($result) == 1
412
                        ? $result['_value']
413
                        : $result;
414
                }
415
            }
416
            $groups = [];
417
            foreach ($children as $child) {
418
                if (!isset($result[$child->nodeName])) {
419
                    $result[$child->nodeName] = $this->domToArray($child);
420
                } else {
421
                    if (!isset($groups[$child->nodeName])) {
422
                        $result[$child->nodeName] = [$result[$child->nodeName]];
423
                        $groups[$child->nodeName] = 1;
424
                    }
425
                    $result[$child->nodeName][] = $this->domToArray($child);
426
                }
427
            }
428
        }
429
430
        return $result;
431
    }
432
433 1
    protected function newline(DOMNode $node, CompilerBuffer $out) {
434 1
        if ($node->previousSibling && $node->previousSibling->nodeType !== XML_COMMENT_NODE) {
435
            $out->appendCode("\n");
436
        }
437 1
    }
438
439 1
    protected function compileCommentNode(\DOMComment $node, CompilerBuffer $out) {
440 1
        $comments = explode("\n", trim($node->nodeValue));
441
442 1
        $this->newline($node, $out);
443 1
        foreach ($comments as $comment) {
444 1
            $out->appendCode("// $comment\n");
445 1
        }
446 1
    }
447
448 49
    protected function compileTextNode(DOMNode $node, CompilerBuffer $out) {
449 49
        $text = $this->ltrim($this->rtrim($node->nodeValue, $node, $out), $node, $out);
450
451 49
        $items = $this->splitExpressions($text);
452
453 49
        foreach ($items as $i => list($text, $offset)) {
454 49
            if (preg_match('`^{\S`', $text)) {
455 28
                if (preg_match('`^{\s*unescape\((.+)\)\s*}$`', $text, $m)) {
456 3
                    $out->echoCode($this->expr($m[1], $out));
457 3
                } else {
458 26
                    $out->echoCode('htmlspecialchars('.$this->expr(substr($text, 1, -1), $out).')');
459
                }
460 28
            } else {
461
//                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...
462
//                    $text = $this->ltrim($text, $node, $out);
463
//                }
464
//                if ($i === count($items) - 1) {
465
//                    $text = $this->rtrim($text, $node, $out);
466
//                }
467
468 49
                $out->echoLiteral($text);
469
            }
470 49
        }
471 49
    }
472
473 51
    protected function compileElementNode(DOMElement $node, CompilerBuffer $out) {
474 51
        list($attributes, $special) = $this->splitAttributes($node);
475
476 51
        if ($node->tagName === 'script' && ((isset($attributes['type']) && $attributes['type']->value === self::T_EBI) || !empty($special[self::T_AS]) || !empty($special[self::T_UNESCAPE]))) {
477 6
            $this->compileExpressionNode($node, $attributes, $special, $out);
478 51
        } elseif (!empty($special) || $this->isComponent($node->tagName)) {
479 33
            $this->compileSpecialNode($node, $attributes, $special, $out);
480 33
        } else {
481 26
            $this->compileBasicElement($node, $attributes, $special, $out);
482
        }
483 51
    }
484
485 49
    protected function splitExpressions($value) {
486 49
        $values = preg_split('`({\S[^}]*?})`', $value, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE);
487 49
        return $values;
488
    }
489
490 49
    protected function expr($expr, CompilerBuffer $output, DOMAttr $attr = null) {
491 49
        $names = $output->getScopeVariables();
492
493
        $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...
494 47
            if (isset($names[$name])) {
495 29
                return $names[$name];
496 31
            } elseif ($name[0] === '@') {
497 1
                return 'this->meta['.var_export(substr($name, 1), true).']';
498
            } else {
499 30
                return $names['this'].'['.var_export($name, true).']';
500
            }
501 49
        });
502
503 49
        if ($attr !== null && null !== $fn = $this->getAttributeFunction($attr)) {
504 4
            $compiled = call_user_func($fn, $compiled);
505 4
        }
506
507 49
        return $compiled;
508
    }
509
510
    /**
511
     * Get the compiler function to wrap an attribute.
512
     *
513
     * Attribute functions are regular expression functions, but with a special naming convention. The following naming
514
     * conventions are supported:
515
     *
516
     * - **@tag:attribute**: Applies to an attribute only on a specific tag.
517
     * - **@attribute**: Applies to all attributes with a given name.
518
     *
519
     * @param DOMAttr $attr The attribute to look at.
520
     * @return callable|null A function or **null** if the attribute doesn't have a function.
521
     */
522 21
    private function getAttributeFunction(DOMAttr $attr) {
523 21
        $keys = ['@'.$attr->ownerElement->tagName.':'.$attr->name, '@'.$attr->name];
524
525 21
        foreach ($keys as $key) {
526 21
            if (null !== $fn = $this->expressions->getFunctionCompiler($key)) {
527 7
                return $fn;
528
            }
529 21
        }
530 16
    }
531
532
    /**
533
     * @param DOMElement $node
534
     */
535 51
    protected function splitAttributes(DOMElement $node) {
536 51
        $attributes = [];
537 51
        $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...
538
539 51
        foreach ($node->attributes as $name => $attribute) {
540 49
            if (isset(static::$special[$name])) {
541 36
                $special[$name] = $attribute;
542 36
            } else {
543 20
                $attributes[$name] = $attribute;
544
            }
545 51
        }
546
547 51
        uksort($special, function ($a, $b) {
548 9
            return strnatcmp(static::$special[$a], static::$special[$b]);
549 51
        });
550
551 51
        return [$attributes, $special];
552
    }
553
554 33
    protected function compileSpecialNode(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
555 33
        $specialName = key($special);
556
557
        switch ($specialName) {
558 33
            case self::T_COMPONENT:
559 9
                $this->compileComponentRegister($node, $attributes, $special, $out);
560 9
                break;
561 33
            case self::T_IF:
562 9
                $this->compileIf($node, $attributes, $special, $out);
563 9
                break;
564 33
            case self::T_EACH:
565 13
                $this->compileEach($node, $attributes, $special, $out);
566 13
                break;
567 24
            case self::T_BLOCK:
568 1
                $this->compileBlock($node, $attributes, $special, $out);
569 1
                break;
570 24
            case self::T_CHILDREN:
571 3
                $this->compileChildBlock($node, $attributes, $special, $out);
572 3
                break;
573 24
            case self::T_INCLUDE:
574 1
                $this->compileComponentInclude($node, $attributes, $special, $out);
575 1
                break;
576 24
            case self::T_WITH:
577 4
                if ($this->isComponent($node->tagName)) {
578
                    // With has a special meaning in components.
579 3
                    $this->compileComponentInclude($node, $attributes, $special, $out);
580 3
                } else {
581 1
                    $this->compileWith($node, $attributes, $special, $out);
582
                }
583 4
                break;
584 23
            case self::T_LITERAL:
585 2
                $this->compileLiteral($node, $attributes, $special, $out);
586 2
                break;
587 21
            case '':
588 21
                if ($this->isComponent($node->tagName)) {
589 7
                    $this->compileComponentInclude($node, $attributes, $special, $out);
590 7
                } else {
591 20
                    $this->compileElement($node, $attributes, $special, $out);
592
                }
593 21
                break;
594 1
            case self::T_TAG:
595 1
            default:
596
                // This is only a tag node so it just gets compiled as an element.
597 1
                $this->compileBasicElement($node, $attributes, $special, $out);
598 1
                break;
599
        }
600 33
    }
601
602
    /**
603
     * Compile component registering.
604
     *
605
     * @param DOMElement $node
606
     * @param $attributes
607
     * @param $special
608
     * @param CompilerBuffer $out
609
     */
610 9
    public function compileComponentRegister(DOMElement $node, $attributes, $special, CompilerBuffer $out) {
611 9
        $name = strtolower($special[self::T_COMPONENT]->value);
612 9
        unset($special[self::T_COMPONENT]);
613
614 9
        $prev = $out->select($name);
615
616 9
        $varName = var_export($name, true);
617 9
        $out->appendCode("\$this->defineComponent($varName, function (\$props = [], \$children = []) {\n");
618 9
        $out->pushScope(['this' => 'props']);
619 9
        $out->indent(+1);
620
621
        try {
622 9
            $this->compileSpecialNode($node, $attributes, $special, $out);
623 9
        } finally {
624 9
            $out->popScope();
625 9
            $out->indent(-1);
626 9
            $out->appendCode("});");
627 9
            $out->select($prev);
628 9
        }
629 9
    }
630
631 1
    private function compileBlock(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
632 1
        $name = strtolower($special[self::T_BLOCK]->value);
633 1
        unset($special[self::T_BLOCK]);
634
635 1
        $prev = $out->select($name);
636
637 1
        $use = '$'.implode(', $', $out->getScopeVariables()).', $children';
638
639 1
        $out->appendCode("function () use ($use) {\n");
640 1
        $out->pushScope(['this' => 'props']);
641 1
        $out->indent(+1);
642
643
        try {
644 1
            $this->compileSpecialNode($node, $attributes, $special, $out);
645 1
        } finally {
646 1
            $out->indent(-1);
647 1
            $out->popScope();
648 1
            $out->appendCode("}");
649 1
            $out->select($prev);
650 1
        }
651
652 1
        return $out;
653
    }
654
655
    /**
656
     * Compile component inclusion and rendering.
657
     *
658
     * @param DOMElement $node
659
     * @param DOMAttr[] $attributes
660
     * @param DOMAttr[] $special
661
     * @param CompilerBuffer $out
662
     */
663 11
    protected function compileComponentInclude(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
664
        // Generate the attributes into a property array.
665 11
        $props = [];
666 11
        foreach ($attributes as $name => $attribute) {
667
            /* @var DOMAttr $attr */
668 5
            if ($this->isExpression($attribute->value)) {
669 4
                $expr = $this->expr(substr($attribute->value, 1, -1), $out, $attribute);
670 4
            } else {
671 1
                $expr = var_export($attribute->value, true);
672
            }
673
674 5
            $props[] = var_export($name, true).' => '.$expr;
675 11
        }
676 11
        $propsStr = '['.implode(', ', $props).']';
677
678 11
        if (isset($special[self::T_WITH])) {
679 3
            $withExpr = $this->expr($special[self::T_WITH]->value, $out, $special[self::T_WITH]);
680 3
            unset($special[self::T_WITH]);
681
682 3
            $propsStr = empty($props) ? $withExpr : $propsStr.' + (array)'.$withExpr;
683 11
        } elseif (empty($props)) {
684
            // By default the current context is passed to components.
685 4
            $propsStr = $this->expr('this', $out);
686 4
        }
687
688
        // Compile the children blocks.
689 11
        $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...
690 11
        $blocksStr = $blocks->flush();
691
692 11
        if (isset($special[self::T_INCLUDE])) {
693 1
            $name = $this->expr($special[self::T_INCLUDE]->value, $out, $special[self::T_INCLUDE]);
694 1
        } else {
695 10
            $name = var_export($node->tagName, true);
696
        }
697
698 11
        $out->appendCode("\$this->write($name, $propsStr, $blocksStr);\n");
699 11
    }
700
701
    /**
702
     * @param DOMElement $parent
703
     * @return CompilerBuffer
704
     */
705 11
    protected function compileComponentBlocks(DOMElement $parent, CompilerBuffer $out) {
706 11
        $blocksOut = new CompilerBuffer(CompilerBuffer::STYLE_ARRAY, [
707 11
            'baseIndent' => $out->getIndent(),
708 11
            'indent' => $out->getIndent() + 1,
709 11
            'depth' => $out->getDepth(),
710 11
            'scopes' => $out->getAllScopes()
711 11
        ]);
712
713 11
        if ($this->isEmptyNode($parent)) {
714 9
            return $blocksOut;
715
        }
716
717 3
        $use = '$'.implode(', $', $blocksOut->getScopeVariables()).', $children';
718
719 3
        $blocksOut->appendCode("function () use ($use) {\n");
720 3
        $blocksOut->indent(+1);
721
722
        try {
723 3
            foreach ($parent->childNodes as $node) {
724 3
                $this->compileNode($node, $blocksOut);
725 3
            }
726 3
        } finally {
727 3
            $blocksOut->indent(-1);
728 3
            $blocksOut->appendCode("}");
729 3
        }
730
731 3
        return $blocksOut;
732
    }
733
734 24
    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...
735
        // Don't double up comments.
736 24
        if ($node->previousSibling && $node->previousSibling->nodeType === XML_COMMENT_NODE) {
737
            return;
738
        }
739
740 24
        $str = '<'.$node->tagName;
741 24
        foreach ($special as $attr) {
742
            /* @var DOMAttr $attr */
743 24
            $str .= ' '.$attr->name.(empty($attr->value) ? '' : '="'.htmlspecialchars($attr->value).'"');
744 24
        }
745 24
        $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...
746 24
        $comments = explode("\n", $str);
747 24
        foreach ($comments as $comment) {
748 24
            $out->appendCode("// $comment\n");
749 24
        }
750 24
    }
751
752
    /**
753
     * @param DOMElement $node
754
     * @param DOMAttr[] $attributes
755
     * @param DOMAttr[] $special
756
     * @param CompilerBuffer $out
757
     * @param bool $force
758
     */
759 45
    protected function compileOpenTag(DOMElement $node, $attributes, $special, CompilerBuffer $out, $force = false) {
760 45
        $tagNameExpr = !empty($special[self::T_TAG]) ? $special[self::T_TAG]->value : '';
761
762 45
        if ($node->tagName === self::T_X && empty($tagNameExpr)) {
763 4
            return;
764
        }
765
766 44
        if (!empty($tagNameExpr)) {
767 1
            $tagNameExpr = $this->expr($tagNameExpr, $out, $special[self::T_TAG]);
768 1
            $out->echoLiteral('<');
769 1
            $out->echoCode($tagNameExpr);
770 1
        } else {
771 44
            $out->echoLiteral('<'.$node->tagName);
772
        }
773
774
        /* @var DOMAttr $attribute */
775 44
        foreach ($attributes as $name => $attribute) {
776
            // Check for an attribute expression.
777 16
            if ($this->isExpression($attribute->value)) {
778 13
                $out->echoCode(
779 13
                    '$this->attribute('.var_export($name, true).', '.
780 13
                    $this->expr(substr($attribute->value, 1, -1), $out, $attribute).
781 13
                    ')');
782 16
            } elseif (null !== $fn = $this->getAttributeFunction($attribute)) {
783 3
                $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...
784
785 3
                $out->echoCode('$this->attribute('.var_export($name, true).', '.$value.')');
786 4
            } elseif ((empty($attribute->value) || $attribute->value === $name) && isset(self::$boolAttributes[$name])) {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $attribute->value (string) and $name (integer) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
787 1
                $out->echoLiteral(' '.$name);
788 1
            } else {
789 3
                $out->echoLiteral(' '.$name.'="');
790 3
                $out->echoLiteral(htmlspecialchars($attribute->value));
791 3
                $out->echoLiteral('"');
792
            }
793 44
        }
794
795 44
        if ($node->hasChildNodes() || $force) {
796 43
            $out->echoLiteral('>');
797 43
        } else {
798 2
            $out->echoLiteral(" />");
799
        }
800 44
    }
801
802 19
    private function isExpression($value) {
803 19
        return preg_match('`^{\S.*}$`', $value);
804
    }
805
806 45
    protected function compileCloseTag(DOMElement $node, $special, CompilerBuffer $out, $force = false) {
807 45
        if (!$force && !$node->hasChildNodes()) {
808 2
            return;
809
        }
810
811 44
        $tagNameExpr = !empty($special[self::T_TAG]) ? $special[self::T_TAG]->value : '';
812 44
        if (!empty($tagNameExpr)) {
813 1
            $tagNameExpr = $this->expr($tagNameExpr, $out, $special[self::T_TAG]);
814 1
            $out->echoLiteral('</');
815 1
            $out->echoCode($tagNameExpr);
816 1
            $out->echoLiteral('>');
817 44
        } elseif ($node->tagName !== self::T_X) {
818 43
            $out->echoLiteral("</{$node->tagName}>");
819 43
        }
820 44
    }
821
822 4
    protected function isEmptyText(DOMNode $node) {
823 4
        return $node instanceof \DOMText && empty(trim($node->data));
824
    }
825
826 11
    protected function isEmptyNode(DOMNode $node) {
827 11
        if (!$node->hasChildNodes()) {
828 9
            return true;
829
        }
830
831 3
        foreach ($node->childNodes as $childNode) {
832 3
            if ($childNode instanceof DOMElement) {
833 1
                return false;
834
            }
835 2
            if ($childNode instanceof \DOMText && !$this->isEmptyText($childNode)) {
836 2
                return false;
837
            }
838
        }
839
840
        return true;
841
    }
842
843 9
    protected function compileIf(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
844 9
        $this->compileTagComment($node, $attributes, $special, $out);
845 9
        $expr = $this->expr($special[self::T_IF]->value, $out);
846 9
        unset($special[self::T_IF]);
847
848 9
        $elseNode = $this->findSpecialNode($node, self::T_ELSE, self::T_IF);
849 9
        $out->setNodeProp($elseNode, 'skip', true);
850
851 9
        $out->appendCode('if ('.$expr.") {\n");
852 9
        $out->indent(+1);
853
854 9
        $this->compileSpecialNode($node, $attributes, $special, $out);
855
856 9
        $out->indent(-1);
857
858 9
        if ($elseNode) {
859 2
            list($attributes, $special) = $this->splitAttributes($elseNode);
860 2
            unset($special[self::T_ELSE]);
861
862 2
            $out->appendCode("} else {\n");
863
864 2
            $out->indent(+1);
865 2
            $this->compileSpecialNode($elseNode, $attributes, $special, $out);
866 2
            $out->indent(-1);
867 2
        }
868
869 9
        $out->appendCode("}\n");
870 9
    }
871
872 13
    protected function compileEach(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
873 13
        $this->compileTagComment($node, $attributes, $special, $out);
874 13
        $this->compileOpenTag($node, $attributes, $special, $out);
875
876 13
        $emptyNode = $this->findSpecialNode($node, self::T_EMPTY, self::T_ELSE);
877 13
        $out->setNodeProp($emptyNode, 'skip', true);
878
879 13
        if ($emptyNode === null) {
880 11
            $this->compileEachLoop($node, $attributes, $special, $out);
881 11
        } else {
882 2
            $expr = $this->expr("empty({$special[self::T_EACH]->value})", $out);
883
884 2
            list ($emptyAttributes, $emptySpecial) = $this->splitAttributes($emptyNode);
885 2
            unset($emptySpecial[self::T_EMPTY]);
886
887 2
            $out->appendCode('if ('.$expr.") {\n");
888
889 2
            $out->indent(+1);
890 2
            $this->compileSpecialNode($emptyNode, $emptyAttributes, $emptySpecial, $out);
891 2
            $out->indent(-1);
892
893 2
            $out->appendCode("} else {\n");
894
895 2
            $out->indent(+1);
896 2
            $this->compileEachLoop($node, $attributes, $special, $out);
897 2
            $out->indent(-1);
898
899 2
            $out->appendCode("}\n");
900
        }
901
902 13
        $this->compileCloseTag($node, $special, $out);
903 13
    }
904
905 1
    protected function compileWith(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
906 1
        $this->compileTagComment($node, $attributes, $special, $out);
907 1
        $with = $this->expr($special[self::T_WITH]->value, $out);
908
909 1
        $out->depth(+1);
910 1
        $scope = ['this' => $out->depthName('props')];
911 1
        if (!empty($special[self::T_AS]) && preg_match(self::IDENT_REGEX, $special[self::T_AS]->value, $m)) {
912
            // The template specified an x-as attribute to alias the with expression.
913 1
            $scope = [$m[1] => $out->depthName('props')];
914 1
        }
915 1
        unset($special[self::T_WITH], $special[self::T_AS]);
916
917 1
        $out->pushScope($scope);
918 1
        $out->appendCode('$'.$out->depthName('props')." = $with;\n");
919
920 1
        $this->compileSpecialNode($node, $attributes, $special, $out);
921
922 1
        $out->depth(-1);
923 1
        $out->popScope();
924 1
    }
925
926 2
    protected function compileLiteral(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
927 2
        $this->compileTagComment($node, $attributes, $special, $out);
928 2
        unset($special[self::T_LITERAL]);
929
930 2
        $this->compileOpenTag($node, $attributes, $special, $out);
931
932 2
        foreach ($node->childNodes as $childNode) {
933 2
            $html = $childNode->ownerDocument->saveHTML($childNode);
934 2
            $out->echoLiteral($html);
935 2
        }
936
937 2
        $this->compileCloseTag($node, $special, $out);
938 2
    }
939
940 20
    protected function compileElement(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
941 20
        $this->compileOpenTag($node, $attributes, $special, $out);
942
943 20
        foreach ($node->childNodes as $childNode) {
944 20
            $this->compileNode($childNode, $out);
945 20
        }
946
947 20
        $this->compileCloseTag($node, $special, $out);
948 20
    }
949
950
    /**
951
     * Find a special node in relation to another node.
952
     *
953
     * This method is used to find things such as x-empty and x-else elements.
954
     *
955
     * @param DOMElement $node The node to search in relation to.
956
     * @param string $attribute The name of the attribute to search for.
957
     * @param string $parentAttribute The name of the parent attribute to resolve conflicts.
958
     * @return DOMElement|null Returns the found element node or **null** if not found.
959
     */
960 21
    protected function findSpecialNode(DOMElement $node, $attribute, $parentAttribute) {
961
        // First look for a sibling after the node.
962 21
        for ($sibNode = $node->nextSibling; $sibNode !== null; $sibNode = $sibNode->nextSibling) {
963 2
            if ($sibNode instanceof DOMElement && $sibNode->hasAttribute($attribute)) {
964 2
                return $sibNode;
965
            }
966
967
            // Stop searching if we encounter another node.
968 2
            if (!$this->isEmptyText($sibNode)) {
969
                break;
970
            }
971 2
        }
972
973
        // Next look inside the node.
974 19
        $parentFound = false;
975 19
        foreach ($node->childNodes as $childNode) {
976 18
            if (!$parentFound && $childNode instanceof DOMElement && $childNode->hasAttribute($attribute)) {
977 2
                return $childNode;
978
            }
979
980 18
            if ($childNode instanceof DOMElement) {
981 13
                $parentFound = $childNode->hasAttribute($parentAttribute);
982 18
            } elseif ($childNode instanceof \DOMText && !empty(trim($childNode->data))) {
983 5
                $parentFound = false;
984 5
            }
985 19
        }
986
987 17
        return null;
988
    }
989
990
    /**
991
     * @param DOMElement $node
992
     * @param array $attributes
993
     * @param array $special
994
     * @param CompilerBuffer $out
995
     */
996 13
    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...
997 13
        $each = $this->expr($special[self::T_EACH]->value, $out);
998 13
        unset($special[self::T_EACH]);
999
1000 13
        $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...
1001 13
        $scope = ['this' => $as[1]];
1002 13
        if (!empty($special[self::T_AS])) {
1003 7
            if (preg_match('`(?:([a-z0-9]+)\s+)?([a-z0-9]+)`i', $special[self::T_AS]->value, $m)) {
1004 7
                $scope = [$m[2] => $as[1]];
1005 7
                if (!empty($m[1])) {
1006 4
                    $scope[$m[1]] = $as[0] = $out->depthName('i', 1);
1007 4
                }
1008 7
            }
1009 7
        }
1010 13
        unset($special[self::T_AS]);
1011 13
        if (empty($as[0])) {
1012 9
            $out->appendCode("foreach ($each as \${$as[1]}) {\n");
1013 9
        } else {
1014 4
            $out->appendCode("foreach ($each as \${$as[0]} => \${$as[1]}) {\n");
1015
        }
1016 13
        $out->depth(+1);
1017 13
        $out->indent(+1);
1018 13
        $out->pushScope($scope);
1019
1020 13
        foreach ($node->childNodes as $childNode) {
1021 13
            $this->compileNode($childNode, $out);
1022 13
        }
1023
1024 13
        $out->indent(-1);
1025 13
        $out->depth(-1);
1026 13
        $out->popScope();
1027 13
        $out->appendCode("}\n");
1028 13
    }
1029
1030 49
    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...
1031 49
        if ($this->inPre($node)) {
1032
            return $text;
1033
        }
1034
1035 49
        $sib = $node->previousSibling ?: $node->parentNode;
1036 49
        if ($sib === null || !$sib instanceof \DOMElement || $out->getNodeProp($sib, 'skip') || $sib->tagName === self::T_X) {
1037 7
            return ltrim($text);
1038
        }
1039
1040 47
        $text = preg_replace('`^\s*\n\s*`', "\n", $text, -1, $count);
1041 47
        if ($count === 0) {
1042 46
            $text = preg_replace('`^\s+`', ' ', $text);
1043 46
        }
1044
1045
//        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...
1046
//            return ltrim($text);
1047
//        }
1048 47
        return $text;
1049
    }
1050
1051 49
    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...
1052 49
        if ($this->inPre($node)) {
1053
            return $text;
1054
        }
1055
1056 49
        $sib = $node->nextSibling ?: $node->parentNode;
1057
1058 49
        if ($sib === null || !$sib instanceof \DOMElement || $out->getNodeProp($sib, 'skip') || $sib->tagName === self::T_X) {
1059 6
            return rtrim($text);
1060
        }
1061
1062 47
        $text = preg_replace('`\s*\n\s*$`', "\n", $text, -1, $count);
1063 47
        if ($count === 0) {
1064 47
            $text = preg_replace('`\s+$`', ' ', $text);
1065 47
        }
1066
1067
//        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...
1068
//            return rtrim($text);
1069
//        }
1070 47
        return $text;
1071
    }
1072
1073 49
    protected function inPre(\DOMNode $node) {
1074 49
        for ($node = $node->parentNode; $node !== null; $node = $node->parentNode) {
1075 49
            if (in_array($node->nodeType, ['code', 'pre'], true)) {
1076
                return true;
1077
            }
1078 49
        }
1079 49
        return false;
1080
    }
1081
1082 3
    private function compileChildBlock(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
1083
        /* @var DOMAttr $child */
1084 3
        $child = $special[self::T_CHILDREN];
1085 3
        unset($special[self::T_CHILDREN]);
1086
1087 3
        $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...
1088 3
        $keyStr = var_export($key, true);
1089
1090 3
        $this->compileOpenTag($node, $attributes, $special, $out, true);
1091
1092 3
        $out->appendCode("if (isset(\$children[{$keyStr}])) {\n");
1093 3
        $out->indent(+1);
1094 3
        $out->appendCode("\$children[{$keyStr}]();\n");
1095 3
        $out->indent(-1);
1096 3
        $out->appendCode("}\n");
1097
1098 3
        $this->compileCloseTag($node, $special, $out, true);
1099 3
    }
1100
1101
    /**
1102
     * Compile an x-expr node.
1103
     *
1104
     * @param DOMElement $node The node to compile.
1105
     * @param DOMAttr[] $attributes The node's attributes.
1106
     * @param DOMAttr[] $special An array of special attributes.
1107
     * @param CompilerBuffer $out The compiler output.
1108
     */
1109 6
    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...
1110 6
        $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...
1111 6
        $expr = $this->expr($str, $out);
1112
1113 6
        if (!empty($special[self::T_AS]) && preg_match(self::IDENT_REGEX, $special[self::T_AS]->value, $m)) {
1114
            // The template specified an x-as attribute to alias the with expression.
1115 4
            $out->depth(+1);
1116 4
            $scope = [$m[1] => $out->depthName('props')];
1117 4
            $out->pushScope($scope);
1118 4
            $out->appendCode('$'.$out->depthName('props')." = $expr;\n");
1119 6
        } elseif (!empty($special[self::T_UNESCAPE])) {
1120 1
            $out->echoCode($expr);
1121 1
        } else {
1122 1
            $out->echoCode('htmlspecialchars('.$expr.')');
1123
        }
1124 6
    }
1125
1126
    /**
1127
     * @param DOMElement $node
1128
     * @param $attributes
1129
     * @param $special
1130
     * @param CompilerBuffer $out
1131
     */
1132 26
    protected function compileBasicElement(DOMElement $node, $attributes, $special, CompilerBuffer $out) {
1133 26
        $this->compileOpenTag($node, $attributes, $special, $out);
1134
1135 26
        foreach ($node->childNodes as $childNode) {
1136 25
            $this->compileNode($childNode, $out);
1137 26
        }
1138
1139 26
        $this->compileCloseTag($node, $special, $out);
1140 26
    }
1141
}
1142