Completed
Push — master ( 4b74f6...76160a )
by Todd
12s
created

Compiler::compileBlock()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 30
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 2

Importance

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