Completed
Push — master ( aebb60...558ce3 )
by Todd
12s
created

Compiler::compileComponentBlocks()   B

Complexity

Conditions 3
Paths 4

Size

Total Lines 28
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 3

Importance

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