Completed
Pull Request — master (#22)
by Todd
02:46
created

Compiler::expr()   C

Complexity

Conditions 7
Paths 4

Size

Total Lines 27
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 7.3229

Importance

Changes 0
Metric Value
dl 0
loc 27
rs 6.7272
c 0
b 0
f 0
ccs 13
cts 16
cp 0.8125
cc 7
eloc 18
nc 4
nop 3
crap 7.3229
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 63
    public function __construct() {
241 63
        $this->expressions = new ExpressionLanguage();
242 63
        $this->expressions->setNamePattern('/[@a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A');
243 63
        $this->expressions->register(
244 63
            'hasChildren',
245
            function ($name = null) {
246 1
                return empty($name) ? 'isset($children[0])' : "isset(\$children[$name ?: 0])";
247 63
            },
248
            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 63
            });
251 63
    }
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 61
    public function defineFunction($name, $function = null) {
260 61
        if ($function === null) {
261 1
            $function = $name;
262 1
        }
263
264 61
        $this->expressions->register(
265 61
            $name,
266 61
            $this->getFunctionCompiler($name, $function),
267 61
            $this->getFunctionEvaluator($function)
268 61
        );
269 61
    }
270
271 61
    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 61
        if ($function === 'empty') {
273
            return function ($expr) {
274
                return empty($expr);
275 61
            };
276 60
        } elseif ($function === 'isset') {
277
            return function ($expr) {
278
                return isset($expr);
279
            };
280
        }
281
282 60
        return $function;
283
    }
284
285 61
    private function getFunctionCompiler($name, $function) {
286 61
        $var = var_export(strtolower($name), true);
287
        $fn = function ($expr) use ($var) {
288 1
            return "\$this->call($var, $expr)";
289 61
        };
290
291 61
        if (is_string($function)) {
292
            $fn = function (...$args) use ($function) {
293 14
                return $function.'('.implode(', ', $args).')';
294 61
            };
295 61
        } elseif (is_array($function)) {
296 60
            if (is_string($function[0])) {
297
                $fn = function (...$args) use ($function) {
298
                    return "$function[0]::$function[1](".implode(', ', $args).')';
299
                };
300 60
            } elseif ($function[0] instanceof Ebi) {
301
                $fn = function (...$args) use ($function) {
302 7
                    return "\$this->$function[1](".implode(', ', $args).')';
303 60
                };
304 60
            }
305 60
        }
306
307 61
        return $fn;
308
    }
309
310 54
    public function compile($src, array $options = []) {
311 54
        $options += ['basename' => '', 'path' => '', 'runtime' => true];
312
313 54
        $src = trim($src);
314
315 54
        $out = new CompilerBuffer();
316
317 54
        $out->setBasename($options['basename']);
318 54
        $out->setSource($src);
319 54
        $out->setPath($options['path']);
320
321 54
        $dom = new \DOMDocument();
322
323 54
        $fragment = false;
324 54
        if (strpos($src, '<html') === false) {
325 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...
326 53
            $fragment = true;
327 53
        }
328
329 54
        libxml_use_internal_errors(true);
330 54
        $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 54
        if ($options['runtime']) {
334 53
            $name = var_export($options['basename'], true);
335 53
            $out->appendCode("\$this->defineComponent($name, function (\$props = [], \$children = []) {\n");
336 53
        } else {
337 1
            $out->appendCode("function (\$props = [], \$children = []) {\n");
338
        }
339
340 54
        $out->pushScope(['this' => 'props']);
341 54
        $out->indent(+1);
342
343 54
        $parent = $fragment ? $dom->firstChild->nextSibling->firstChild : $dom;
344
345 54
        foreach ($parent->childNodes as $node) {
346 54
            $this->compileNode($node, $out);
347 54
        }
348
349 54
        $out->indent(-1);
350 54
        $out->popScope();
351
352 54
        if ($options['runtime']) {
353 53
            $out->appendCode("});");
354 53
        } else {
355 1
            $out->appendCode("};");
356
        }
357
358 54
        $r = $out->flush();
359
360 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...
361
362 54
        return $r;
363
    }
364
365 44
    protected function isComponent($tag) {
366 44
        return !isset(static::$htmlTags[$tag]);
367
    }
368
369 54
    protected function compileNode(DOMNode $node, CompilerBuffer $out) {
370 54
        if ($out->getNodeProp($node, 'skip')) {
371 4
            return;
372
        }
373
374 54
        switch ($node->nodeType) {
375 54
            case XML_TEXT_NODE:
376 48
                $this->compileTextNode($node, $out);
377 48
                break;
378 51
            case XML_ELEMENT_NODE:
379
                /* @var \DOMElement $node */
380 51
                $this->compileElementNode($node, $out);
381 51
                break;
382 3
            case XML_COMMENT_NODE:
383
                /* @var \DOMComment $node */
384 1
                $this->compileCommentNode($node, $out);
385 1
                break;
386 2
            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 1
            case XML_CDATA_SECTION_NODE:
390 1
                $this->compileTextNode($node, $out);
391 1
                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 54
        }
396 54
    }
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 1
        }
449 1
    }
450
451 49
    protected function compileTextNode(DOMNode $node, CompilerBuffer $out) {
452 49
        $text = $this->ltrim($this->rtrim($node->nodeValue, $node, $out), $node, $out);
453
454 49
        $items = $this->splitExpressions($text);
455
456 49
        foreach ($items as $i => list($text, $offset)) {
457 49
            if (preg_match('`^{\S`', $text)) {
458 28
                if (preg_match('`^{\s*unescape\((.+)\)\s*}$`', $text, $m)) {
459 3
                    $out->echoCode($this->expr($m[1], $out));
460 3
                } else {
461 26
                    $out->echoCode('htmlspecialchars('.$this->expr(substr($text, 1, -1), $out).')');
462
                }
463 28
            } else {
464
//                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...
465
//                    $text = $this->ltrim($text, $node, $out);
466
//                }
467
//                if ($i === count($items) - 1) {
468
//                    $text = $this->rtrim($text, $node, $out);
469
//                }
470
471 49
                $out->echoLiteral($text);
472
            }
473 49
        }
474 49
    }
475
476 51
    protected function compileElementNode(DOMElement $node, CompilerBuffer $out) {
477 51
        list($attributes, $special) = $this->splitAttributes($node);
478
479 51
        if ($node->tagName === 'script' && ((isset($attributes['type']) && $attributes['type']->value === self::T_EBI) || !empty($special[self::T_AS]) || !empty($special[self::T_UNESCAPE]))) {
480 6
            $this->compileExpressionNode($node, $attributes, $special, $out);
481 51
        } elseif (!empty($special) || $this->isComponent($node->tagName)) {
482 33
            $this->compileSpecialNode($node, $attributes, $special, $out);
483 33
        } else {
484 26
            $this->compileBasicElement($node, $attributes, $special, $out);
485
        }
486 51
    }
487
488 49
    protected function splitExpressions($value) {
489 49
        $values = preg_split('`({\S[^}]*?})`', $value, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE);
490 49
        return $values;
491
    }
492
493
    /**
494
     * Compile the PHP for an expression
495
     *
496
     * @param string $expr The expression to compile.
497
     * @param CompilerBuffer $out The current output buffer.
498
     * @param DOMAttr|null $attr The owner of the expression.
499
     * @return string Returns a string of PHP code.
500
     */
501 49
    protected function expr($expr, CompilerBuffer $out, DOMAttr $attr = null) {
502 49
        $names = $out->getScopeVariables();
503
504
        try {
505
            $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...
506 47
                if (isset($names[$name])) {
507 29
                    return $names[$name];
508 31
                } elseif ($name[0] === '@') {
509 1
                    return 'this->meta['.var_export(substr($name, 1), true).']';
510
                } else {
511 30
                    return $names['this'].'['.var_export($name, true).']';
512
                }
513 49
            });
514 49
        } catch (SyntaxError $ex) {
515
            if ($attr !== null) {
516
                throw $out->createCompilerException($attr, $ex);
517
            } else {
518
                throw $ex;
519
            }
520
        }
521
522 49
        if ($attr !== null && null !== $fn = $this->getAttributeFunction($attr)) {
523 4
            $compiled = call_user_func($fn, $compiled);
524 4
        }
525
526 49
        return $compiled;
527
    }
528
529
    /**
530
     * Get the compiler function to wrap an attribute.
531
     *
532
     * Attribute functions are regular expression functions, but with a special naming convention. The following naming
533
     * conventions are supported:
534
     *
535
     * - **@tag:attribute**: Applies to an attribute only on a specific tag.
536
     * - **@attribute**: Applies to all attributes with a given name.
537
     *
538
     * @param DOMAttr $attr The attribute to look at.
539
     * @return callable|null A function or **null** if the attribute doesn't have a function.
540
     */
541 29
    private function getAttributeFunction(DOMAttr $attr) {
542 29
        $keys = ['@'.$attr->ownerElement->tagName.':'.$attr->name, '@'.$attr->name];
543
544 29
        foreach ($keys as $key) {
545 29
            if (null !== $fn = $this->expressions->getFunctionCompiler($key)) {
546 7
                return $fn;
547
            }
548 29
        }
549 24
    }
550
551
    /**
552
     * @param DOMElement $node
553
     */
554 51
    protected function splitAttributes(DOMElement $node) {
555 51
        $attributes = [];
556 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...
557
558 51
        foreach ($node->attributes as $name => $attribute) {
559 49
            if (isset(static::$special[$name])) {
560 36
                $special[$name] = $attribute;
561 36
            } else {
562 20
                $attributes[$name] = $attribute;
563
            }
564 51
        }
565
566 51
        uksort($special, function ($a, $b) {
567 9
            return strnatcmp(static::$special[$a], static::$special[$b]);
568 51
        });
569
570 51
        return [$attributes, $special];
571
    }
572
573 33
    protected function compileSpecialNode(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
574 33
        $specialName = key($special);
575
576
        switch ($specialName) {
577 33
            case self::T_COMPONENT:
578 9
                $this->compileComponentRegister($node, $attributes, $special, $out);
579 9
                break;
580 33
            case self::T_IF:
581 9
                $this->compileIf($node, $attributes, $special, $out);
582 9
                break;
583 33
            case self::T_EACH:
584 13
                $this->compileEach($node, $attributes, $special, $out);
585 13
                break;
586 24
            case self::T_BLOCK:
587 1
                $this->compileBlock($node, $attributes, $special, $out);
588 1
                break;
589 24
            case self::T_CHILDREN:
590 3
                $this->compileChildBlock($node, $attributes, $special, $out);
591 3
                break;
592 24
            case self::T_INCLUDE:
593 1
                $this->compileComponentInclude($node, $attributes, $special, $out);
594 1
                break;
595 24
            case self::T_WITH:
596 4
                if ($this->isComponent($node->tagName)) {
597
                    // With has a special meaning in components.
598 3
                    $this->compileComponentInclude($node, $attributes, $special, $out);
599 3
                } else {
600 1
                    $this->compileWith($node, $attributes, $special, $out);
601
                }
602 4
                break;
603 23
            case self::T_LITERAL:
604 2
                $this->compileLiteral($node, $attributes, $special, $out);
605 2
                break;
606 21
            case '':
607 21
                if ($this->isComponent($node->tagName)) {
608 7
                    $this->compileComponentInclude($node, $attributes, $special, $out);
609 7
                } else {
610 20
                    $this->compileElement($node, $attributes, $special, $out);
611
                }
612 21
                break;
613 1
            case self::T_TAG:
614 1
            default:
615
                // This is only a tag node so it just gets compiled as an element.
616 1
                $this->compileBasicElement($node, $attributes, $special, $out);
617 1
                break;
618
        }
619 33
    }
620
621
    /**
622
     * Compile component registering.
623
     *
624
     * @param DOMElement $node
625
     * @param $attributes
626
     * @param $special
627
     * @param CompilerBuffer $out
628
     */
629 9
    public function compileComponentRegister(DOMElement $node, $attributes, $special, CompilerBuffer $out) {
630 9
        $name = strtolower($special[self::T_COMPONENT]->value);
631 9
        unset($special[self::T_COMPONENT]);
632
633 9
        $prev = $out->select($name);
634
635 9
        $varName = var_export($name, true);
636 9
        $out->appendCode("\$this->defineComponent($varName, function (\$props = [], \$children = []) {\n");
637 9
        $out->pushScope(['this' => 'props']);
638 9
        $out->indent(+1);
639
640
        try {
641 9
            $this->compileSpecialNode($node, $attributes, $special, $out);
642 9
        } finally {
643 9
            $out->popScope();
644 9
            $out->indent(-1);
645 9
            $out->appendCode("});");
646 9
            $out->select($prev);
647 9
        }
648 9
    }
649
650 1
    private function compileBlock(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
651 1
        $name = strtolower($special[self::T_BLOCK]->value);
652 1
        unset($special[self::T_BLOCK]);
653
654 1
        $prev = $out->select($name);
655
656 1
        $use = '$'.implode(', $', array_unique($out->getScopeVariables())).', $children';
657
658 1
        $out->appendCode("function () use ($use) {\n");
659 1
        $out->pushScope(['this' => 'props']);
660 1
        $out->indent(+1);
661
662
        try {
663 1
            $this->compileSpecialNode($node, $attributes, $special, $out);
664 1
        } finally {
665 1
            $out->indent(-1);
666 1
            $out->popScope();
667 1
            $out->appendCode("}");
668 1
            $out->select($prev);
669 1
        }
670
671 1
        return $out;
672
    }
673
674
    /**
675
     * Compile component inclusion and rendering.
676
     *
677
     * @param DOMElement $node
678
     * @param DOMAttr[] $attributes
679
     * @param DOMAttr[] $special
680
     * @param CompilerBuffer $out
681
     */
682 11
    protected function compileComponentInclude(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
683
        // Generate the attributes into a property array.
684 11
        $props = [];
685 11
        foreach ($attributes as $name => $attribute) {
686
            /* @var DOMAttr $attr */
687 5
            if ($this->isExpression($attribute->value)) {
688 4
                $expr = $this->expr(substr($attribute->value, 1, -1), $out, $attribute);
689 4
            } else {
690 1
                $expr = var_export($attribute->value, true);
691
            }
692
693 5
            $props[] = var_export($name, true).' => '.$expr;
694 11
        }
695 11
        $propsStr = '['.implode(', ', $props).']';
696
697 11
        if (isset($special[self::T_WITH])) {
698 3
            $withExpr = $this->expr($special[self::T_WITH]->value, $out, $special[self::T_WITH]);
699 3
            unset($special[self::T_WITH]);
700
701 3
            $propsStr = empty($props) ? $withExpr : $propsStr.' + (array)'.$withExpr;
702 11
        } elseif (empty($props)) {
703
            // By default the current context is passed to components.
704 4
            $propsStr = $this->expr('this', $out);
705 4
        }
706
707
        // Compile the children blocks.
708 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...
709 11
        $blocksStr = $blocks->flush();
710
711 11
        if (isset($special[self::T_INCLUDE])) {
712 1
            $name = $this->expr($special[self::T_INCLUDE]->value, $out, $special[self::T_INCLUDE]);
713 1
        } else {
714 10
            $name = var_export($node->tagName, true);
715
        }
716
717 11
        $out->appendCode("\$this->write($name, $propsStr, $blocksStr);\n");
718 11
    }
719
720
    /**
721
     * @param DOMElement $parent
722
     * @return CompilerBuffer
723
     */
724 11
    protected function compileComponentBlocks(DOMElement $parent, CompilerBuffer $out) {
725 11
        $blocksOut = new CompilerBuffer(CompilerBuffer::STYLE_ARRAY, [
726 11
            'baseIndent' => $out->getIndent(),
727 11
            'indent' => $out->getIndent() + 1,
728 11
            'depth' => $out->getDepth(),
729 11
            'scopes' => $out->getAllScopes()
730 11
        ]);
731
732 11
        if ($this->isEmptyNode($parent)) {
733 9
            return $blocksOut;
734
        }
735
736 3
        $use = '$'.implode(', $', $blocksOut->getScopeVariables()).', $children';
737
738 3
        $blocksOut->appendCode("function () use ($use) {\n");
739 3
        $blocksOut->indent(+1);
740
741
        try {
742 3
            foreach ($parent->childNodes as $node) {
743 3
                $this->compileNode($node, $blocksOut);
744 3
            }
745 3
        } finally {
746 3
            $blocksOut->indent(-1);
747 3
            $blocksOut->appendCode("}");
748 3
        }
749
750 3
        return $blocksOut;
751
    }
752
753 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...
754
        // Don't double up comments.
755 24
        if ($node->previousSibling && $node->previousSibling->nodeType === XML_COMMENT_NODE) {
756
            return;
757
        }
758
759 24
        $str = '<'.$node->tagName;
760 24
        foreach ($special as $attr) {
761
            /* @var DOMAttr $attr */
762 24
            $str .= ' '.$attr->name.(empty($attr->value) ? '' : '="'.htmlspecialchars($attr->value).'"');
763 24
        }
764 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...
765 24
        $comments = explode("\n", $str);
766 24
        foreach ($comments as $comment) {
767 24
            $out->appendCode("// $comment\n");
768 24
        }
769 24
    }
770
771
    /**
772
     * @param DOMElement $node
773
     * @param DOMAttr[] $attributes
774
     * @param DOMAttr[] $special
775
     * @param CompilerBuffer $out
776
     * @param bool $force
777
     */
778 45
    protected function compileOpenTag(DOMElement $node, $attributes, $special, CompilerBuffer $out, $force = false) {
779 45
        $tagNameExpr = !empty($special[self::T_TAG]) ? $special[self::T_TAG]->value : '';
780
781 45
        if ($node->tagName === self::T_X && empty($tagNameExpr)) {
782 4
            return;
783
        }
784
785 44
        if (!empty($tagNameExpr)) {
786 1
            $tagNameExpr = $this->expr($tagNameExpr, $out, $special[self::T_TAG]);
787 1
            $out->echoLiteral('<');
788 1
            $out->echoCode($tagNameExpr);
789 1
        } else {
790 44
            $out->echoLiteral('<'.$node->tagName);
791
        }
792
793
        /* @var DOMAttr $attribute */
794 44
        foreach ($attributes as $name => $attribute) {
795
            // Check for an attribute expression.
796 16
            if ($this->isExpression($attribute->value)) {
797 13
                $out->echoCode(
798 13
                    '$this->attribute('.var_export($name, true).', '.
799 13
                    $this->expr(substr($attribute->value, 1, -1), $out, $attribute).
800 13
                    ')');
801 16
            } elseif (null !== $fn = $this->getAttributeFunction($attribute)) {
802 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...
803
804 3
                $out->echoCode('$this->attribute('.var_export($name, true).', '.$value.')');
805 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...
806 1
                $out->echoLiteral(' '.$name);
807 1
            } else {
808 3
                $out->echoLiteral(' '.$name.'="');
809 3
                $out->echoLiteral(htmlspecialchars($attribute->value));
810 3
                $out->echoLiteral('"');
811
            }
812 44
        }
813
814 44
        if ($node->hasChildNodes() || $force) {
815 43
            $out->echoLiteral('>');
816 43
        } else {
817 2
            $out->echoLiteral(" />");
818
        }
819 44
    }
820
821 19
    private function isExpression($value) {
822 19
        return preg_match('`^{\S.*}$`', $value);
823
    }
824
825 45
    protected function compileCloseTag(DOMElement $node, $special, CompilerBuffer $out, $force = false) {
826 45
        if (!$force && !$node->hasChildNodes()) {
827 2
            return;
828
        }
829
830 44
        $tagNameExpr = !empty($special[self::T_TAG]) ? $special[self::T_TAG]->value : '';
831 44
        if (!empty($tagNameExpr)) {
832 1
            $tagNameExpr = $this->expr($tagNameExpr, $out, $special[self::T_TAG]);
833 1
            $out->echoLiteral('</');
834 1
            $out->echoCode($tagNameExpr);
835 1
            $out->echoLiteral('>');
836 44
        } elseif ($node->tagName !== self::T_X) {
837 43
            $out->echoLiteral("</{$node->tagName}>");
838 43
        }
839 44
    }
840
841 4
    protected function isEmptyText(DOMNode $node) {
842 4
        return $node instanceof \DOMText && empty(trim($node->data));
843
    }
844
845 11
    protected function isEmptyNode(DOMNode $node) {
846 11
        if (!$node->hasChildNodes()) {
847 9
            return true;
848
        }
849
850 3
        foreach ($node->childNodes as $childNode) {
851 3
            if ($childNode instanceof DOMElement) {
852 1
                return false;
853
            }
854 2
            if ($childNode instanceof \DOMText && !$this->isEmptyText($childNode)) {
855 2
                return false;
856
            }
857
        }
858
859
        return true;
860
    }
861
862 9
    protected function compileIf(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
863 9
        $this->compileTagComment($node, $attributes, $special, $out);
864 9
        $expr = $this->expr($special[self::T_IF]->value, $out, $special[self::T_IF]);
865 9
        unset($special[self::T_IF]);
866
867 9
        $elseNode = $this->findSpecialNode($node, self::T_ELSE, self::T_IF);
868 9
        $out->setNodeProp($elseNode, 'skip', true);
869
870 9
        $out->appendCode('if ('.$expr.") {\n");
871 9
        $out->indent(+1);
872
873 9
        $this->compileSpecialNode($node, $attributes, $special, $out);
874
875 9
        $out->indent(-1);
876
877 9
        if ($elseNode) {
878 2
            list($attributes, $special) = $this->splitAttributes($elseNode);
879 2
            unset($special[self::T_ELSE]);
880
881 2
            $out->appendCode("} else {\n");
882
883 2
            $out->indent(+1);
884 2
            $this->compileSpecialNode($elseNode, $attributes, $special, $out);
885 2
            $out->indent(-1);
886 2
        }
887
888 9
        $out->appendCode("}\n");
889 9
    }
890
891 13
    protected function compileEach(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
892 13
        $this->compileTagComment($node, $attributes, $special, $out);
893 13
        $this->compileOpenTag($node, $attributes, $special, $out);
894
895 13
        $emptyNode = $this->findSpecialNode($node, self::T_EMPTY, self::T_ELSE);
896 13
        $out->setNodeProp($emptyNode, 'skip', true);
897
898 13
        if ($emptyNode === null) {
899 11
            $this->compileEachLoop($node, $attributes, $special, $out);
900 11
        } else {
901 2
            $expr = $this->expr("empty({$special[self::T_EACH]->value})", $out);
902
903 2
            list ($emptyAttributes, $emptySpecial) = $this->splitAttributes($emptyNode);
904 2
            unset($emptySpecial[self::T_EMPTY]);
905
906 2
            $out->appendCode('if ('.$expr.") {\n");
907
908 2
            $out->indent(+1);
909 2
            $this->compileSpecialNode($emptyNode, $emptyAttributes, $emptySpecial, $out);
910 2
            $out->indent(-1);
911
912 2
            $out->appendCode("} else {\n");
913
914 2
            $out->indent(+1);
915 2
            $this->compileEachLoop($node, $attributes, $special, $out);
916 2
            $out->indent(-1);
917
918 2
            $out->appendCode("}\n");
919
        }
920
921 13
        $this->compileCloseTag($node, $special, $out);
922 13
    }
923
924 1
    protected function compileWith(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
925 1
        $this->compileTagComment($node, $attributes, $special, $out);
926 1
        $with = $this->expr($special[self::T_WITH]->value, $out);
927
928 1
        $out->depth(+1);
929 1
        $scope = ['this' => $out->depthName('props')];
930 1
        if (!empty($special[self::T_AS]) && preg_match(self::IDENT_REGEX, $special[self::T_AS]->value, $m)) {
931
            // The template specified an x-as attribute to alias the with expression.
932 1
            $scope = [$m[1] => $out->depthName('props')];
933 1
        }
934 1
        unset($special[self::T_WITH], $special[self::T_AS]);
935
936 1
        $out->pushScope($scope);
937 1
        $out->appendCode('$'.$out->depthName('props')." = $with;\n");
938
939 1
        $this->compileSpecialNode($node, $attributes, $special, $out);
940
941 1
        $out->depth(-1);
942 1
        $out->popScope();
943 1
    }
944
945 2
    protected function compileLiteral(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
946 2
        $this->compileTagComment($node, $attributes, $special, $out);
947 2
        unset($special[self::T_LITERAL]);
948
949 2
        $this->compileOpenTag($node, $attributes, $special, $out);
950
951 2
        foreach ($node->childNodes as $childNode) {
952 2
            $html = $childNode->ownerDocument->saveHTML($childNode);
953 2
            $out->echoLiteral($html);
954 2
        }
955
956 2
        $this->compileCloseTag($node, $special, $out);
957 2
    }
958
959 20
    protected function compileElement(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
960 20
        $this->compileOpenTag($node, $attributes, $special, $out);
961
962 20
        foreach ($node->childNodes as $childNode) {
963 20
            $this->compileNode($childNode, $out);
964 20
        }
965
966 20
        $this->compileCloseTag($node, $special, $out);
967 20
    }
968
969
    /**
970
     * Find a special node in relation to another node.
971
     *
972
     * This method is used to find things such as x-empty and x-else elements.
973
     *
974
     * @param DOMElement $node The node to search in relation to.
975
     * @param string $attribute The name of the attribute to search for.
976
     * @param string $parentAttribute The name of the parent attribute to resolve conflicts.
977
     * @return DOMElement|null Returns the found element node or **null** if not found.
978
     */
979 21
    protected function findSpecialNode(DOMElement $node, $attribute, $parentAttribute) {
980
        // First look for a sibling after the node.
981 21
        for ($sibNode = $node->nextSibling; $sibNode !== null; $sibNode = $sibNode->nextSibling) {
982 2
            if ($sibNode instanceof DOMElement && $sibNode->hasAttribute($attribute)) {
983 2
                return $sibNode;
984
            }
985
986
            // Stop searching if we encounter another node.
987 2
            if (!$this->isEmptyText($sibNode)) {
988
                break;
989
            }
990 2
        }
991
992
        // Next look inside the node.
993 19
        $parentFound = false;
994 19
        foreach ($node->childNodes as $childNode) {
995 18
            if (!$parentFound && $childNode instanceof DOMElement && $childNode->hasAttribute($attribute)) {
996 2
                return $childNode;
997
            }
998
999 18
            if ($childNode instanceof DOMElement) {
1000 13
                $parentFound = $childNode->hasAttribute($parentAttribute);
1001 18
            } elseif ($childNode instanceof \DOMText && !empty(trim($childNode->data))) {
1002 5
                $parentFound = false;
1003 5
            }
1004 19
        }
1005
1006 17
        return null;
1007
    }
1008
1009
    /**
1010
     * @param DOMElement $node
1011
     * @param array $attributes
1012
     * @param array $special
1013
     * @param CompilerBuffer $out
1014
     */
1015 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...
1016 13
        $each = $this->expr($special[self::T_EACH]->value, $out);
1017 13
        unset($special[self::T_EACH]);
1018
1019 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...
1020 13
        $scope = ['this' => $as[1]];
1021 13
        if (!empty($special[self::T_AS])) {
1022 7
            if (preg_match('`(?:([a-z0-9]+)\s+)?([a-z0-9]+)`i', $special[self::T_AS]->value, $m)) {
1023 7
                $scope = [$m[2] => $as[1]];
1024 7
                if (!empty($m[1])) {
1025 4
                    $scope[$m[1]] = $as[0] = $out->depthName('i', 1);
1026 4
                }
1027 7
            }
1028 7
        }
1029 13
        unset($special[self::T_AS]);
1030 13
        if (empty($as[0])) {
1031 9
            $out->appendCode("foreach ($each as \${$as[1]}) {\n");
1032 9
        } else {
1033 4
            $out->appendCode("foreach ($each as \${$as[0]} => \${$as[1]}) {\n");
1034
        }
1035 13
        $out->depth(+1);
1036 13
        $out->indent(+1);
1037 13
        $out->pushScope($scope);
1038
1039 13
        foreach ($node->childNodes as $childNode) {
1040 13
            $this->compileNode($childNode, $out);
1041 13
        }
1042
1043 13
        $out->indent(-1);
1044 13
        $out->depth(-1);
1045 13
        $out->popScope();
1046 13
        $out->appendCode("}\n");
1047 13
    }
1048
1049 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...
1050 49
        if ($this->inPre($node)) {
1051
            return $text;
1052
        }
1053
1054 49
        $sib = $node->previousSibling ?: $node->parentNode;
1055 49
        if ($sib === null || !$sib instanceof \DOMElement || $out->getNodeProp($sib, 'skip') || $sib->tagName === self::T_X) {
1056 7
            return ltrim($text);
1057
        }
1058
1059 47
        $text = preg_replace('`^\s*\n\s*`', "\n", $text, -1, $count);
1060 47
        if ($count === 0) {
1061 46
            $text = preg_replace('`^\s+`', ' ', $text);
1062 46
        }
1063
1064
//        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...
1065
//            return ltrim($text);
1066
//        }
1067 47
        return $text;
1068
    }
1069
1070 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...
1071 49
        if ($this->inPre($node)) {
1072
            return $text;
1073
        }
1074
1075 49
        $sib = $node->nextSibling ?: $node->parentNode;
1076
1077 49
        if ($sib === null || !$sib instanceof \DOMElement || $out->getNodeProp($sib, 'skip') || $sib->tagName === self::T_X) {
1078 6
            return rtrim($text);
1079
        }
1080
1081 47
        $text = preg_replace('`\s*\n\s*$`', "\n", $text, -1, $count);
1082 47
        if ($count === 0) {
1083 47
            $text = preg_replace('`\s+$`', ' ', $text);
1084 47
        }
1085
1086
//        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...
1087
//            return rtrim($text);
1088
//        }
1089 47
        return $text;
1090
    }
1091
1092 49
    protected function inPre(\DOMNode $node) {
1093 49
        for ($node = $node->parentNode; $node !== null; $node = $node->parentNode) {
1094 49
            if (in_array($node->nodeType, ['code', 'pre'], true)) {
1095
                return true;
1096
            }
1097 49
        }
1098 49
        return false;
1099
    }
1100
1101 3
    private function compileChildBlock(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
1102
        /* @var DOMAttr $child */
1103 3
        $child = $special[self::T_CHILDREN];
1104 3
        unset($special[self::T_CHILDREN]);
1105
1106 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...
1107 3
        $keyStr = var_export($key, true);
1108
1109 3
        $this->compileOpenTag($node, $attributes, $special, $out, true);
1110
1111 3
        $out->appendCode("if (isset(\$children[{$keyStr}])) {\n");
1112 3
        $out->indent(+1);
1113 3
        $out->appendCode("\$children[{$keyStr}]();\n");
1114 3
        $out->indent(-1);
1115 3
        $out->appendCode("}\n");
1116
1117 3
        $this->compileCloseTag($node, $special, $out, true);
1118 3
    }
1119
1120
    /**
1121
     * Compile an x-expr node.
1122
     *
1123
     * @param DOMElement $node The node to compile.
1124
     * @param DOMAttr[] $attributes The node's attributes.
1125
     * @param DOMAttr[] $special An array of special attributes.
1126
     * @param CompilerBuffer $out The compiler output.
1127
     */
1128 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...
1129 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...
1130 6
        $expr = $this->expr($str, $out);
1131
1132 6
        if (!empty($special[self::T_AS])) {
1133 4
            if (preg_match(self::IDENT_REGEX, $special[self::T_AS]->value, $m)) {
1134
            // The template specified an x-as attribute to alias the with expression.
1135 4
            $out->depth(+1);
1136 4
            $scope = [$m[1] => $out->depthName('expr')];
1137 4
            $out->pushScope($scope);
1138 4
            $out->appendCode('$'.$out->depthName('expr')." = $expr;\n");
1139 4
            } else {
0 ignored issues
show
Unused Code introduced by
This else statement is empty and can be removed.

This check looks for the else branches of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These else branches can be removed.

if (rand(1, 6) > 3) {
print "Check failed";
} else {
    //print "Check succeeded";
}

could be turned into

if (rand(1, 6) > 3) {
    print "Check failed";
}

This is much more concise to read.

Loading history...
1140
1141
            }
1142 6
        } elseif (!empty($special[self::T_UNESCAPE])) {
1143 1
            $out->echoCode($expr);
1144 1
        } else {
1145 1
            $out->echoCode('htmlspecialchars('.$expr.')');
1146
        }
1147 6
    }
1148
1149
    /**
1150
     * @param DOMElement $node
1151
     * @param $attributes
1152
     * @param $special
1153
     * @param CompilerBuffer $out
1154
     */
1155 26
    protected function compileBasicElement(DOMElement $node, $attributes, $special, CompilerBuffer $out) {
1156 26
        $this->compileOpenTag($node, $attributes, $special, $out);
1157
1158 26
        foreach ($node->childNodes as $childNode) {
1159 25
            $this->compileNode($childNode, $out);
1160 26
        }
1161
1162 26
        $this->compileCloseTag($node, $special, $out);
1163 26
    }
1164
1165
    private function compileCompilerException(CompileException $ex, CompilerBuffer $out) {
0 ignored issues
show
Unused Code introduced by
The parameter $ex 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...
Unused Code introduced by
The parameter $out 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...
1166
    }
1167
}
1168