Completed
Pull Request — master (#22)
by Todd
03:12
created

Compiler::compileExpressionNode()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 25
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 5

Importance

Changes 0
Metric Value
dl 0
loc 25
ccs 16
cts 16
cp 1
rs 8.439
c 0
b 0
f 0
cc 5
eloc 17
nc 5
nop 4
crap 5
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 64
    public function __construct() {
241 64
        $this->expressions = new ExpressionLanguage();
242 64
        $this->expressions->setNamePattern('/[@a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A');
243 64
        $this->expressions->register(
244 64
            'hasChildren',
245 64
            function ($name = null) {
246 1
                return empty($name) ? 'isset($children[0])' : "isset(\$children[$name ?: 0])";
247 64
            },
248 64
            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 64
            });
251 64
    }
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 62
    public function defineFunction($name, $function = null) {
260 62
        if ($function === null) {
261 1
            $function = $name;
262
        }
263
264 62
        $this->expressions->register(
265 62
            $name,
266 62
            $this->getFunctionCompiler($name, $function),
267 62
            $this->getFunctionEvaluator($function)
268
        );
269 62
    }
270
271 62
    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 62
        if ($function === 'empty') {
273 62
            return function ($expr) {
274
                return empty($expr);
275 62
            };
276 61
        } elseif ($function === 'isset') {
277
            return function ($expr) {
278
                return isset($expr);
279
            };
280
        }
281
282 61
        return $function;
283
    }
284
285 62
    private function getFunctionCompiler($name, $function) {
286 62
        $var = var_export(strtolower($name), true);
287 62
        $fn = function ($expr) use ($var) {
288 1
            return "\$this->call($var, $expr)";
289 62
        };
290
291 62
        if (is_string($function)) {
292 62
            $fn = function (...$args) use ($function) {
293 14
                return $function.'('.implode(', ', $args).')';
294 62
            };
295 61
        } elseif (is_array($function)) {
296 61
            if (is_string($function[0])) {
297
                $fn = function (...$args) use ($function) {
298
                    return "$function[0]::$function[1](".implode(', ', $args).')';
299
                };
300 61
            } elseif ($function[0] instanceof Ebi) {
301 61
                $fn = function (...$args) use ($function) {
302 7
                    return "\$this->$function[1](".implode(', ', $args).')';
303 61
                };
304
            }
305
        }
306
307 62
        return $fn;
308
    }
309
310 55
    public function compile($src, array $options = []) {
311 55
        $options += ['basename' => '', 'path' => '', 'runtime' => true];
312
313 55
        $src = trim($src);
314
315 55
        $out = new CompilerBuffer();
316
317 55
        $out->setBasename($options['basename']);
318 55
        $out->setSource($src);
319 55
        $out->setPath($options['path']);
320
321 55
        $dom = new \DOMDocument();
322
323 55
        $fragment = false;
324 55
        if (strpos($src, '<html') === false) {
325 54
            $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 54
            $fragment = true;
327
        }
328
329 55
        libxml_use_internal_errors(true);
330 55
        $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 55
        if ($options['runtime']) {
334 54
            $name = var_export($options['basename'], true);
335 54
            $out->appendCode("\$this->defineComponent($name, function (\$props = [], \$children = []) {\n");
336
        } else {
337 1
            $out->appendCode("function (\$props = [], \$children = []) {\n");
338
        }
339
340 55
        $out->pushScope(['this' => 'props']);
341 55
        $out->indent(+1);
342
343 55
        $parent = $fragment ? $dom->firstChild->nextSibling->firstChild : $dom;
344
345 55
        foreach ($parent->childNodes as $node) {
346 55
            $this->compileNode($node, $out);
347
        }
348
349 54
        $out->indent(-1);
350 54
        $out->popScope();
351
352 54
        if ($options['runtime']) {
353 53
            $out->appendCode("});");
354
        } 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 55
    protected function compileNode(DOMNode $node, CompilerBuffer $out) {
370 55
        if ($out->getNodeProp($node, 'skip')) {
371 4
            return;
372
        }
373
374 55
        switch ($node->nodeType) {
375 55
            case XML_TEXT_NODE:
376 48
                $this->compileTextNode($node, $out);
377 48
                break;
378 52
            case XML_ELEMENT_NODE:
379
                /* @var \DOMElement $node */
380 52
                $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
        }
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
        }
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
                } else {
461 28
                    $out->echoCode('htmlspecialchars('.$this->expr(substr($text, 1, -1), $out).')');
462
                }
463
            } 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
        }
474 49
    }
475
476 52
    protected function compileElementNode(DOMElement $node, CompilerBuffer $out) {
477 52
        list($attributes, $special) = $this->splitAttributes($node);
478
479 52
        if ($node->tagName === 'script' && ((isset($attributes['type']) && $attributes['type']->value === self::T_EBI) || !empty($special[self::T_AS]) || !empty($special[self::T_UNESCAPE]))) {
480 7
            $this->compileExpressionNode($node, $attributes, $special, $out);
481 46
        } elseif (!empty($special) || $this->isComponent($node->tagName)) {
482 33
            $this->compileSpecialNode($node, $attributes, $special, $out);
483
        } 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 50
    protected function expr($expr, CompilerBuffer $out, DOMAttr $attr = null) {
502 50
        $names = $out->getScopeVariables();
503
504
        try {
505 50
            $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 50
            });
514 1
        } catch (SyntaxError $ex) {
515 1
            if ($attr !== null) {
516
                throw $out->createCompilerException($attr, $ex);
517
            } else {
518 1
                throw $ex;
519
            }
520
        }
521
522 49
        if ($attr !== null && null !== $fn = $this->getAttributeFunction($attr)) {
523 4
            $compiled = call_user_func($fn, $compiled);
524
        }
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 29
                return $fn;
547
            }
548
        }
549 24
    }
550
551
    /**
552
     * @param DOMElement $node
553
     */
554 52
    protected function splitAttributes(DOMElement $node) {
555 52
        $attributes = [];
556 52
        $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 52
        foreach ($node->attributes as $name => $attribute) {
559 50
            if (isset(static::$special[$name])) {
560 36
                $special[$name] = $attribute;
561
            } else {
562 50
                $attributes[$name] = $attribute;
563
            }
564
        }
565
566 52
        uksort($special, function ($a, $b) {
567 9
            return strnatcmp(static::$special[$a], static::$special[$b]);
568 52
        });
569
570 52
        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
                } 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
                } else {
610 20
                    $this->compileElement($node, $attributes, $special, $out);
611
                }
612 21
                break;
613 1
            case self::T_TAG:
614
            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
        }
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
        }
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
            } else {
690 1
                $expr = var_export($attribute->value, true);
691
            }
692
693 5
            $props[] = var_export($name, true).' => '.$expr;
694
        }
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
        } elseif (empty($props)) {
703
            // By default the current context is passed to components.
704 4
            $propsStr = $this->expr('this', $out);
705
        }
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
        } 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
        ]);
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
            }
745 3
        } finally {
746 3
            $blocksOut->indent(-1);
747 3
            $blocksOut->appendCode("}");
748
        }
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
        }
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
        }
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
        } 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 4
            } 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 3
            } 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
            } else {
808 3
                $out->echoLiteral(' '.$name.'="');
809 3
                $out->echoLiteral(htmlspecialchars($attribute->value));
810 16
                $out->echoLiteral('"');
811
            }
812
        }
813
814 44
        if ($node->hasChildNodes() || $force) {
815 43
            $out->echoLiteral('>');
816
        } 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
        }
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
        }
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
        } 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
        }
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
        }
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
        }
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
        }
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 7
            } elseif ($childNode instanceof \DOMText && !empty(trim($childNode->data))) {
1002 18
                $parentFound = false;
1003
            }
1004
        }
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
                }
1027
            }
1028
        }
1029 13
        unset($special[self::T_AS]);
1030 13
        if (empty($as[0])) {
1031 9
            $out->appendCode("foreach ($each as \${$as[1]}) {\n");
1032
        } 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
        }
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
        }
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
        }
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
        }
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 7
    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 7
        $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
1131
        try {
1132 7
            $expr = $this->expr($str, $out);
1133 1
        } catch (SyntaxError $ex) {
1134 1
            throw $out->createCompilerException($node, $ex);
1135
        }
1136
1137 6
        if (!empty($special[self::T_AS])) {
1138 4
            if (preg_match(self::IDENT_REGEX, $special[self::T_AS]->value, $m)) {
1139
            // The template specified an x-as attribute to alias the with expression.
1140 4
            $out->depth(+1);
1141 4
            $scope = [$m[1] => $out->depthName('expr')];
1142 4
            $out->pushScope($scope);
1143 4
            $out->appendCode('$'.$out->depthName('expr')." = $expr;\n");
1144 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...
1145
1146
            }
1147 2
        } elseif (!empty($special[self::T_UNESCAPE])) {
1148 1
            $out->echoCode($expr);
1149
        } else {
1150 1
            $out->echoCode('htmlspecialchars('.$expr.')');
1151
        }
1152 6
    }
1153
1154
    /**
1155
     * @param DOMElement $node
1156
     * @param $attributes
1157
     * @param $special
1158
     * @param CompilerBuffer $out
1159
     */
1160 26
    protected function compileBasicElement(DOMElement $node, $attributes, $special, CompilerBuffer $out) {
1161 26
        $this->compileOpenTag($node, $attributes, $special, $out);
1162
1163 26
        foreach ($node->childNodes as $childNode) {
1164 25
            $this->compileNode($childNode, $out);
1165
        }
1166
1167 26
        $this->compileCloseTag($node, $special, $out);
1168 26
    }
1169
1170
    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...
1171
    }
1172
}
1173