Completed
Pull Request — master (#35)
by Todd
03:39
created

Compiler::inPre()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3.072

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 4
cts 5
cp 0.8
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 5
nc 3
nop 1
crap 3.072
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 81
    public function __construct() {
241 81
        $this->expressions = new ExpressionLanguage();
242 81
        $this->expressions->setNamePattern('/[@a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A');
243 81
        $this->expressions->register(
244 81
            'hasChildren',
245 81
            function ($name = null) {
246 1
                return empty($name) ? 'isset($children[0])' : "isset(\$children[$name ?: 0])";
247 81
            },
248 81
            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 81
            });
251 81
    }
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 79
    public function defineFunction($name, $function = null) {
260 79
        if ($function === null) {
261 1
            $function = $name;
262
        }
263
264 79
        $this->expressions->register(
265 79
            $name,
266 79
            $this->getFunctionCompiler($name, $function),
267 79
            $this->getFunctionEvaluator($function)
268
        );
269 79
    }
270
271 79
    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 79
        if ($function === 'empty') {
273 79
            return function ($expr) {
274
                return empty($expr);
275 79
            };
276 78
        } elseif ($function === 'isset') {
277
            return function ($expr) {
278
                return isset($expr);
279
            };
280
        }
281
282 78
        return $function;
283
    }
284
285 79
    private function getFunctionCompiler($name, $function) {
286 79
        $var = var_export(strtolower($name), true);
287 79
        $fn = function (...$args) use ($var) {
288 2
            return "\$this->call($var, ".implode(', ', $args).')';
289 79
        };
290
291 79
        if (is_string($function)) {
292 79
            $fn = function (...$args) use ($function) {
293 15
                return $function.'('.implode(', ', $args).')';
294 79
            };
295 78
        } elseif (is_array($function)) {
296 78
            if (is_string($function[0])) {
297
                $fn = function (...$args) use ($function) {
298
                    return "$function[0]::$function[1](".implode(', ', $args).')';
299
                };
300 78
            } elseif ($function[0] instanceof Ebi) {
301 78
                $fn = function (...$args) use ($function) {
302 13
                    return "\$this->$function[1](".implode(', ', $args).')';
303 78
                };
304
            }
305
        }
306
307 79
        return $fn;
308
    }
309
310 73
    public function compile($src, array $options = []) {
311 73
        $options += ['basename' => '', 'path' => '', 'runtime' => true];
312
313 73
        $src = trim($src);
314
315 73
        $out = new CompilerBuffer();
316
317 73
        $out->setBasename($options['basename']);
318 73
        $out->setSource($src);
319 73
        $out->setPath($options['path']);
320
321 73
        $dom = new \DOMDocument();
322
323 73
        $fragment = false;
324 73
        if (strpos($src, '<html') === false) {
325 71
            $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 71
            $fragment = true;
327
        }
328
329 73
        libxml_use_internal_errors(true);
330 73
        $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 73
        if ($options['runtime']) {
334 72
            $name = var_export($options['basename'], true);
335 72
            $out->appendCode("\$this->defineComponent($name, function (\$props = [], \$children = []) {\n");
336
        } else {
337 1
            $out->appendCode("function (\$props = [], \$children = []) {\n");
338
        }
339
340 73
        $out->pushScope(['this' => 'props']);
341 73
        $out->indent(+1);
342
343 73
        $parent = $fragment ? $dom->firstChild->nextSibling->firstChild : $dom;
344
345 73
        foreach ($parent->childNodes as $node) {
346 73
            $this->compileNode($node, $out);
347
        }
348
349 65
        $out->indent(-1);
350 65
        $out->popScope();
351
352 65
        if ($options['runtime']) {
353 64
            $out->appendCode("});");
354
        } else {
355 1
            $out->appendCode("};");
356
        }
357
358 65
        $r = $out->flush();
359
360 65
        $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 65
        return $r;
363
    }
364
365 52
    protected function isComponent($tag) {
366 52
        return !isset(static::$htmlTags[$tag]);
367
    }
368
369 73
    protected function compileNode(DOMNode $node, CompilerBuffer $out) {
370 73
        if ($out->getNodeProp($node, 'skip')) {
371 4
            return;
372
        }
373
374 73
        switch ($node->nodeType) {
375 73
            case XML_TEXT_NODE:
376 59
                $this->compileTextNode($node, $out);
377 59
                break;
378 68
            case XML_ELEMENT_NODE:
379
                /* @var \DOMElement $node */
380 68
                $this->compileElementNode($node, $out);
381 61
                break;
382 4
            case XML_COMMENT_NODE:
383
                /* @var \DOMComment $node */
384 1
                $this->compileCommentNode($node, $out);
385 1
                break;
386 3
            case XML_DOCUMENT_TYPE_NODE:
387 1
                $out->echoLiteral("<!DOCTYPE {$node->name}>\n");
0 ignored issues
show
Bug introduced by
The property name does not seem to exist in DOMNode.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
388 1
                break;
389 2
            case XML_CDATA_SECTION_NODE:
390 2
                $this->compileTextNode($node, $out);
391 2
                break;
392
            default:
393
                $r = "// Unknown node\n".
0 ignored issues
show
Unused Code introduced by
$r is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
394
                    '// '.str_replace("\n", "\n// ", $node->ownerDocument->saveHTML($node));
395
        }
396 67
    }
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 61
    protected function compileTextNode(DOMNode $node, CompilerBuffer $out) {
452 61
        $nodeText = $node->nodeValue;
453
454 61
        $items = $this->splitExpressions($nodeText);
455 61
        foreach ($items as $i => list($text, $offset)) {
456 61
            if (preg_match('`^{\S`', $text)) {
457 33
                if (preg_match('`^{\s*unescape\((.+)\)\s*}$`', $text, $m)) {
458 3
                    $out->echoCode($this->expr($m[1], $out));
459
                } else {
460
                    try {
461 31
                        $expr = substr($text, 1, -1);
462 31
                        $out->echoCode($this->compileEscape($this->expr($expr, $out)));
463 1
                    } catch (SyntaxError $ex) {
464 1
                        $nodeLineCount = substr_count($nodeText, "\n");
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
465 1
                        $offsetLineCount = substr_count($nodeText, "\n", 0, $offset);
466 1
                        $line = $node->getLineNo() - $nodeLineCount + $offsetLineCount;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 12 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
467 33
                        throw $out->createCompilerException($node, $ex, ['source' => $expr, 'line' => $line]);
468
                    }
469
                }
470
            } else {
471 61
                if ($i === 0) {
472 61
                    $text = $this->ltrim($text, $node, $out);
473
                }
474 61
                if ($i === count($items) - 1) {
475 61
                    $text = $this->rtrim($text, $node, $out);
476
                }
477
478 61
                $out->echoLiteral($text);
479
            }
480
        }
481 61
    }
482
483 68
    protected function compileElementNode(DOMElement $node, CompilerBuffer $out) {
484 68
        list($attributes, $special) = $this->splitAttributes($node);
485
486 68
        if ($node->tagName === 'script' && ((isset($attributes['type']) && $attributes['type']->value === self::T_EBI) || !empty($special[self::T_AS]) || !empty($special[self::T_UNESCAPE]))) {
487 9
            $this->compileExpressionNode($node, $attributes, $special, $out);
488 61
        } elseif (!empty($special) || $this->isComponent($node->tagName)) {
489 45
            $this->compileSpecialNode($node, $attributes, $special, $out);
490
        } else {
491 31
            $this->compileBasicElement($node, $attributes, $special, $out);
492
        }
493 61
    }
494
495 61
    protected function splitExpressions($value) {
496 61
        $values = preg_split('`({\S[^}]*?})`', $value, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE);
497 61
        return $values;
498
    }
499
500
    /**
501
     * Compile the PHP for an expression
502
     *
503
     * @param string $expr The expression to compile.
504
     * @param CompilerBuffer $out The current output buffer.
505
     * @param DOMAttr|null $attr The owner of the expression.
506
     * @return string Returns a string of PHP code.
507
     */
508 65
    protected function expr($expr, CompilerBuffer $out, DOMAttr $attr = null) {
509 65
        $names = $out->getScopeVariables();
510
511
        try {
512 65
            $compiled = $this->expressions->compile($expr, function ($name) use ($names) {
0 ignored issues
show
Documentation introduced by
function ($name) use($na...e, true) . ']'; } } is of type object<Closure>, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
513 58
                if (isset($names[$name])) {
514 33
                    return $names[$name];
515 38
                } elseif ($name[0] === '@') {
516 1
                    return 'this->meta['.var_export(substr($name, 1), true).']';
517
                } else {
518 37
                    return $names['this'].'['.var_export($name, true).']';
519
                }
520 65
            });
521 3
        } catch (SyntaxError $ex) {
522 3
            if ($attr !== null) {
523 1
                throw $out->createCompilerException($attr, $ex);
524
            } else {
525 2
                throw $ex;
526
            }
527
        }
528
529 62
        if ($attr !== null && null !== $fn = $this->getAttributeFunction($attr)) {
530 6
            $compiled = call_user_func($fn, $compiled);
531
        }
532
533 62
        return $compiled;
534
    }
535
536
    /**
537
     * Get the compiler function to wrap an attribute.
538
     *
539
     * Attribute functions are regular expression functions, but with a special naming convention. The following naming
540
     * conventions are supported:
541
     *
542
     * - **@tag:attribute**: Applies to an attribute only on a specific tag.
543
     * - **@attribute**: Applies to all attributes with a given name.
544
     *
545
     * @param DOMAttr $attr The attribute to look at.
546
     * @return callable|null A function or **null** if the attribute doesn't have a function.
547
     */
548 36
    private function getAttributeFunction(DOMAttr $attr) {
549 36
        $keys = ['@'.$attr->ownerElement->tagName.':'.$attr->name, '@'.$attr->name];
550
551 36
        foreach ($keys as $key) {
552 36
            if (null !== $fn = $this->expressions->getFunctionCompiler($key)) {
553 36
                return $fn;
554
            }
555
        }
556 30
    }
557
558
    /**
559
     * @param DOMElement $node
560
     */
561 68
    protected function splitAttributes(DOMElement $node) {
562 68
        $attributes = [];
563 68
        $special = [];
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
564
565 68
        foreach ($node->attributes as $name => $attribute) {
566 64
            if (isset(static::$special[$name])) {
567 49
                $special[$name] = $attribute;
568
            } else {
569 64
                $attributes[$name] = $attribute;
570
            }
571
        }
572
573 68
        uksort($special, function ($a, $b) {
574 13
            return strnatcmp(static::$special[$a], static::$special[$b]);
575 68
        });
576
577 68
        return [$attributes, $special];
578
    }
579
580 45
    protected function compileSpecialNode(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
581 45
        $specialName = key($special);
582
583
        switch ($specialName) {
584 45
            case self::T_COMPONENT:
585 10
                $this->compileComponentRegister($node, $attributes, $special, $out);
586 10
                break;
587 45
            case self::T_IF:
588 10
                $this->compileIf($node, $attributes, $special, $out);
589 9
                break;
590 44
            case self::T_EACH:
591 16
                $this->compileEach($node, $attributes, $special, $out);
592 15
                break;
593 32
            case self::T_BLOCK:
594 3
                $this->compileBlock($node, $attributes, $special, $out);
595 2
                break;
596 31
            case self::T_CHILDREN:
597 4
                $this->compileChildBlock($node, $attributes, $special, $out);
598 4
                break;
599 31
            case self::T_INCLUDE:
600 1
                $this->compileComponentInclude($node, $attributes, $special, $out);
601 1
                break;
602 31
            case self::T_WITH:
603 5
                if ($this->isComponent($node->tagName)) {
604
                    // With has a special meaning in components.
605 3
                    $this->compileComponentInclude($node, $attributes, $special, $out);
606
                } else {
607 2
                    $this->compileWith($node, $attributes, $special, $out);
608
                }
609 4
                break;
610 29
            case self::T_LITERAL:
611 2
                $this->compileLiteral($node, $attributes, $special, $out);
612 2
                break;
613 27
            case '':
614 23
                if ($this->isComponent($node->tagName)) {
615 9
                    $this->compileComponentInclude($node, $attributes, $special, $out);
616
                } else {
617 21
                    $this->compileElement($node, $attributes, $special, $out);
618
                }
619 22
                break;
620 5
            case self::T_TAG:
621
            default:
622
                // This is only a tag node so it just gets compiled as an element.
623 5
                $this->compileBasicElement($node, $attributes, $special, $out);
624 5
                break;
625
        }
626 40
    }
627
628
    /**
629
     * Compile component registering.
630
     *
631
     * @param DOMElement $node
632
     * @param $attributes
633
     * @param $special
634
     * @param CompilerBuffer $out
635
     */
636 10
    public function compileComponentRegister(DOMElement $node, $attributes, $special, CompilerBuffer $out) {
637 10
        $name = strtolower($special[self::T_COMPONENT]->value);
638 10
        unset($special[self::T_COMPONENT]);
639
640 10
        $prev = $out->select($name);
641
642 10
        $varName = var_export($name, true);
643 10
        $out->appendCode("\$this->defineComponent($varName, function (\$props = [], \$children = []) {\n");
644 10
        $out->pushScope(['this' => 'props']);
645 10
        $out->indent(+1);
646
647
        try {
648 10
            $this->compileSpecialNode($node, $attributes, $special, $out);
649 10
        } finally {
650 10
            $out->popScope();
651 10
            $out->indent(-1);
652 10
            $out->appendCode("});");
653 10
            $out->select($prev);
654
        }
655 10
    }
656
657 3
    private function compileBlock(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
658
        // Blocks must be direct descendants of component includes.
659 3
        if (!$out->getNodeProp($node->parentNode, self::T_INCLUDE)) {
660 1
            throw $out->createCompilerException($node, new \Exception("Blocks must be direct descendants of component includes."));
661
        }
662
663 2
        $name = strtolower($special[self::T_BLOCK]->value);
664 2
        if (empty($name)) {
665
            throw $out->createCompilerException($special[self::T_BLOCK], new \Exception("Block names cannot be empty."));
666
        }
667 2
        if (!preg_match(self::IDENT_REGEX, $name)) {
668
            throw $out->createCompilerException($special[self::T_BLOCK], new \Exception("The block name isn't a valid identifier."));
669
        }
670
671 2
        unset($special[self::T_BLOCK]);
672
673 2
        $prev = $out->select($name, true);
674
675 2
        $vars = array_filter(array_unique($out->getScopeVariables()));
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
676 2
        $vars[] = 'children';
677 2
        $use = '$'.implode(', $', array_unique($vars));
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...
678
679 2
        $out->appendCode("function () use ($use) {\n");
680 2
        $out->pushScope(['this' => 'props']);
681 2
        $out->indent(+1);
682
683
        try {
684 2
            $this->compileSpecialNode($node, $attributes, $special, $out);
685 2
        } finally {
686 2
            $out->indent(-1);
687 2
            $out->popScope();
688 2
            $out->appendCode("}");
689 2
            $out->select($prev);
690
        }
691
692 2
        return $out;
693
    }
694
695
    /**
696
     * Compile component inclusion and rendering.
697
     *
698
     * @param DOMElement $node
699
     * @param DOMAttr[] $attributes
700
     * @param DOMAttr[] $special
701
     * @param CompilerBuffer $out
702
     */
703 13
    protected function compileComponentInclude(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
704
        // Mark the node as a component include.
705 13
        $out->setNodeProp($node, self::T_INCLUDE, true);
706
707
        // Generate the attributes into a property array.
708 13
        $props = [];
709 13
        foreach ($attributes as $name => $attribute) {
710
            /* @var DOMAttr $attr */
711 5
            if ($this->isExpression($attribute->value)) {
712 4
                $expr = $this->expr(substr($attribute->value, 1, -1), $out, $attribute);
713
            } else {
714 1
                $expr = var_export($attribute->value, true);
715
            }
716
717 5
            $props[] = var_export($name, true).' => '.$expr;
718
        }
719 13
        $propsStr = '['.implode(', ', $props).']';
720
721 13
        if (isset($special[self::T_WITH])) {
722 3
            $withExpr = $this->expr($special[self::T_WITH]->value, $out, $special[self::T_WITH]);
723 3
            unset($special[self::T_WITH]);
724
725 3
            $propsStr = empty($props) ? $withExpr : $propsStr.' + (array)'.$withExpr;
726
        } elseif (empty($props)) {
727
            // By default the current context is passed to components.
728 6
            $propsStr = $this->expr('this', $out);
729
        }
730
731
        // Compile the children blocks.
732 13
        $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...
733 12
        $blocksStr = $blocks->flush();
734
735 12
        if (isset($special[self::T_INCLUDE])) {
736 1
            $name = $this->expr($special[self::T_INCLUDE]->value, $out, $special[self::T_INCLUDE]);
737
        } else {
738 11
            $name = var_export($node->tagName, true);
739
        }
740
741 12
        $out->appendCode("\$this->write($name, $propsStr, $blocksStr);\n");
742 12
    }
743
744
    /**
745
     * @param DOMElement $parent
746
     * @return CompilerBuffer
747
     */
748 13
    protected function compileComponentBlocks(DOMElement $parent, CompilerBuffer $out) {
749 13
        $blocksOut = new CompilerBuffer(CompilerBuffer::STYLE_ARRAY, [
750 13
            'baseIndent' => $out->getIndent(),
751 13
            'indent' => $out->getIndent() + 1,
752 13
            'depth' => $out->getDepth(),
753 13
            'scopes' => $out->getAllScopes(),
754 13
            'nodeProps' => $out->getNodePropArray()
755
        ]);
756 13
        $blocksOut->setSource($out->getSource());
757
758 13
        if ($this->isEmptyNode($parent)) {
759 9
            return $blocksOut;
760
        }
761
762 5
        $use = '$'.implode(', $', $blocksOut->getScopeVariables()).', $children';
763
764 5
        $blocksOut->appendCode("function () use ($use) {\n");
765 5
        $blocksOut->indent(+1);
766
767
        try {
768 5
            foreach ($parent->childNodes as $node) {
769 5
                $this->compileNode($node, $blocksOut);
770
            }
771 4
        } finally {
772 5
            $blocksOut->indent(-1);
773 5
            $blocksOut->appendCode("}");
774
        }
775
776 4
        return $blocksOut;
777
    }
778
779
    /**
780
     * Output the source of a node as a PHP comment.
781
     *
782
     * @param DOMElement $node The node to output.
783
     * @param DOMAttr[] $attributes Regular attributes.
784
     * @param DOMAttr[] $special Special attributes.
785
     * @param CompilerBuffer $out The output buffer for the results.
786
     */
787 33
    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...
788
        // Don't double up comments.
789 33
        if ($node->previousSibling && $node->previousSibling->nodeType === XML_COMMENT_NODE) {
790
            return;
791
        }
792
793 33
        $str = '<'.$node->tagName;
794 33
        foreach ($special as $attr) {
795
            /* @var DOMAttr $attr */
796 33
            $str .= ' '.$attr->name.(empty($attr->value) ? '' : '="'.str_replace('"', '&quot;', $attr->value).'"');
797
        }
798 33
        $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...
799 33
        $comments = explode("\n", $str);
800 33
        foreach ($comments as $comment) {
801 33
            $out->appendCode("// $comment\n");
802
        }
803 33
    }
804
805
    /**
806
     * @param DOMElement $node
807
     * @param DOMAttr[] $attributes
808
     * @param DOMAttr[] $special
809
     * @param CompilerBuffer $out
810
     * @param bool $force
811
     */
812 57
    protected function compileOpenTag(DOMElement $node, $attributes, $special, CompilerBuffer $out, $force = false) {
813 57
        $tagNameExpr = !empty($special[self::T_TAG]) ? $special[self::T_TAG]->value : '';
814
815 57
        if ($node->tagName === self::T_X && empty($tagNameExpr)) {
816 5
            return;
817
        }
818
819 55
        if (!empty($tagNameExpr)) {
820 5
            $tagNameExpr = $this->expr($tagNameExpr, $out, $special[self::T_TAG]);
821 5
            $tagName = $node->tagName === 'x' ? "''" : var_export($node->tagName, true);
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...
822
823 5
            $tagVar = $out->depthName('$tag', 1);
824 5
            if ($node->hasChildNodes() || $force) {
825 5
                $out->setNodeProp($node, self::T_TAG, $tagVar);
826 5
                $out->depth(+1);
827
            }
828
829 5
            $out->appendCode("\n");
830 5
            $this->compileTagComment($node, $attributes, $special, $out);
831 5
            $out->appendCode("$tagVar = \$this->tagName($tagNameExpr, $tagName);\n");
832 5
            $out->appendCode("if ($tagVar) {\n");
833 5
            $out->indent(+1);
834 5
            $out->echoLiteral('<');
835 5
            $out->echoCode($tagVar);
836
        } else {
837 51
            $out->echoLiteral('<'.$node->tagName);
838
        }
839
840
        /* @var DOMAttr $attribute */
841 55
        foreach ($attributes as $name => $attribute) {
842
            // Check for an attribute expression.
843 23
            if ($this->isExpression($attribute->value)) {
844 15
                $out->echoCode(
845 15
                    '$this->attribute('.var_export($name, true).', '.
846 15
                    $this->expr(substr($attribute->value, 1, -1), $out, $attribute).
847 15
                    ')');
848 9
            } elseif (null !== $fn = $this->getAttributeFunction($attribute)) {
849 7
                $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...
850
851 7
                $out->echoCode('$this->attribute('.var_export($name, true).', '.$value.')');
852 4
            } elseif ((empty($attribute->value) || $attribute->value === $name) && isset(self::$boolAttributes[$name])) {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $attribute->value (string) and $name (integer) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
853 2
                $out->echoLiteral(' '.$name);
854
            } else {
855 4
                $out->echoLiteral(' '.$name.'="');
856 4
                $out->echoLiteral(htmlspecialchars($attribute->value));
857 23
                $out->echoLiteral('"');
858
            }
859
        }
860
861 55
        if ($node->hasChildNodes() || $force) {
862 53
            $out->echoLiteral('>');
863
        } else {
864 3
            $out->echoLiteral(" />");
865
        }
866
867 55
        if (!empty($tagNameExpr)) {
868 5
            $out->indent(-1);
869 5
            $out->appendCode("}\n\n");
870
        }
871 55
    }
872
873 26
    private function isExpression($value) {
874 26
        return preg_match('`^{\S.*}$`', $value);
875
    }
876
877 55
    protected function compileCloseTag(DOMElement $node, $special, CompilerBuffer $out, $force = false) {
0 ignored issues
show
Unused Code introduced by
The parameter $special 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...
878 55
        if (!$force && !$node->hasChildNodes()) {
879 3
            return;
880
        }
881
882 53
        $tagNameExpr = $out->getNodeProp($node, self::T_TAG); //!empty($special[self::T_TAG]) ? $special[self::T_TAG]->value : '';
0 ignored issues
show
Unused Code Comprehensibility introduced by
74% 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...
883 53
        if (!empty($tagNameExpr)) {
884 5
            $out->appendCode("\n");
885 5
            $out->appendCode("if ($tagNameExpr) {\n");
886 5
            $out->indent(+1);
887
888 5
            $out->echoLiteral('</');
889 5
            $out->echoCode($tagNameExpr);
890 5
            $out->echoLiteral('>');
891 5
            $out->indent(-1);
892 5
            $out->appendCode("}\n");
893 5
            $out->depth(-1);
894 49
        } elseif ($node->tagName !== self::T_X) {
895 47
            $out->echoLiteral("</{$node->tagName}>");
896
        }
897 53
    }
898
899 4
    protected function isEmptyText(DOMNode $node) {
900 4
        return $node instanceof \DOMText && empty(trim($node->data));
901
    }
902
903 13
    protected function isEmptyNode(DOMNode $node) {
904 13
        if (!$node->hasChildNodes()) {
905 9
            return true;
906
        }
907
908 5
        foreach ($node->childNodes as $childNode) {
909 5
            if ($childNode instanceof DOMElement) {
910 3
                return false;
911
            }
912 2
            if ($childNode instanceof \DOMText && !$this->isEmptyText($childNode)) {
913 2
                return false;
914
            }
915
        }
916
917
        return true;
918
    }
919
920 10
    protected function compileIf(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
921 10
        $this->compileTagComment($node, $attributes, $special, $out);
922 10
        $expr = $this->expr($special[self::T_IF]->value, $out, $special[self::T_IF]);
923 9
        unset($special[self::T_IF]);
924
925 9
        $elseNode = $this->findSpecialNode($node, self::T_ELSE, self::T_IF);
926 9
        $out->setNodeProp($elseNode, 'skip', true);
927
928 9
        $out->appendCode('if ('.$expr.") {\n");
929 9
        $out->indent(+1);
930
931 9
        $this->compileSpecialNode($node, $attributes, $special, $out);
932
933 9
        $out->indent(-1);
934
935 9
        if ($elseNode) {
936 2
            list($attributes, $special) = $this->splitAttributes($elseNode);
937 2
            unset($special[self::T_ELSE]);
938
939 2
            $out->appendCode("} else {\n");
940
941 2
            $out->indent(+1);
942 2
            $this->compileSpecialNode($elseNode, $attributes, $special, $out);
943 2
            $out->indent(-1);
944
        }
945
946 9
        $out->appendCode("}\n");
947 9
    }
948
949 16
    protected function compileEach(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
950 16
        $this->compileTagComment($node, $attributes, $special, $out);
951 16
        $this->compileOpenTag($node, $attributes, $special, $out);
952
953 16
        $emptyNode = $this->findSpecialNode($node, self::T_EMPTY, self::T_ELSE);
954 16
        $out->setNodeProp($emptyNode, 'skip', true);
955
956 16
        if ($emptyNode === null) {
957 14
            $this->compileEachLoop($node, $attributes, $special, $out);
958
        } else {
959 2
            $expr = $this->expr("empty({$special[self::T_EACH]->value})", $out);
960
961 2
            list ($emptyAttributes, $emptySpecial) = $this->splitAttributes($emptyNode);
962 2
            unset($emptySpecial[self::T_EMPTY]);
963
964 2
            $out->appendCode('if ('.$expr.") {\n");
965
966 2
            $out->indent(+1);
967 2
            $this->compileSpecialNode($emptyNode, $emptyAttributes, $emptySpecial, $out);
968 2
            $out->indent(-1);
969
970 2
            $out->appendCode("} else {\n");
971
972 2
            $out->indent(+1);
973 2
            $this->compileEachLoop($node, $attributes, $special, $out);
974 2
            $out->indent(-1);
975
976 2
            $out->appendCode("}\n");
977
        }
978
979 15
        $this->compileCloseTag($node, $special, $out);
980 15
    }
981
982 2
    protected function compileWith(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
983 2
        $this->compileTagComment($node, $attributes, $special, $out);
984
985 2
        $out->depth(+1);
986 2
        $scope = ['this' => $out->depthName('props')];
987 2
        if (!empty($special[self::T_AS])) {
988 2
            if (preg_match(self::IDENT_REGEX, $special[self::T_AS]->value, $m)) {
989
                // The template specified an x-as attribute to alias the with expression.
990 1
                $scope = [$m[1] => $out->depthName('props')];
991
            } else {
992 1
                throw $out->createCompilerException(
993 1
                    $special[self::T_AS],
994 1
                    new \Exception("Invalid identifier in x-as attribute.")
995
                );
996
            }
997
        }
998 1
        $with = $this->expr($special[self::T_WITH]->value, $out);
999
1000 1
        unset($special[self::T_WITH], $special[self::T_AS]);
1001
1002 1
        $out->pushScope($scope);
1003 1
        $out->appendCode('$'.$out->depthName('props')." = $with;\n");
1004
1005 1
        $this->compileSpecialNode($node, $attributes, $special, $out);
1006
1007 1
        $out->depth(-1);
1008 1
        $out->popScope();
1009 1
    }
1010
1011 2
    protected function compileLiteral(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
1012 2
        $this->compileTagComment($node, $attributes, $special, $out);
1013 2
        unset($special[self::T_LITERAL]);
1014
1015 2
        $this->compileOpenTag($node, $attributes, $special, $out);
1016
1017 2
        foreach ($node->childNodes as $childNode) {
1018 2
            $html = $childNode->ownerDocument->saveHTML($childNode);
1019 2
            $out->echoLiteral($html);
1020
        }
1021
1022 2
        $this->compileCloseTag($node, $special, $out);
1023 2
    }
1024
1025 21
    protected function compileElement(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
1026 21
        $this->compileOpenTag($node, $attributes, $special, $out);
1027
1028 21
        foreach ($node->childNodes as $childNode) {
1029 21
            $this->compileNode($childNode, $out);
1030
        }
1031
1032 21
        $this->compileCloseTag($node, $special, $out);
1033 21
    }
1034
1035
    /**
1036
     * Find a special node in relation to another node.
1037
     *
1038
     * This method is used to find things such as x-empty and x-else elements.
1039
     *
1040
     * @param DOMElement $node The node to search in relation to.
1041
     * @param string $attribute The name of the attribute to search for.
1042
     * @param string $parentAttribute The name of the parent attribute to resolve conflicts.
1043
     * @return DOMElement|null Returns the found element node or **null** if not found.
1044
     */
1045 24
    protected function findSpecialNode(DOMElement $node, $attribute, $parentAttribute) {
1046
        // First look for a sibling after the node.
1047 24
        for ($sibNode = $node->nextSibling; $sibNode !== null; $sibNode = $sibNode->nextSibling) {
1048 2
            if ($sibNode instanceof DOMElement && $sibNode->hasAttribute($attribute)) {
1049 2
                return $sibNode;
1050
            }
1051
1052
            // Stop searching if we encounter another node.
1053 2
            if (!$this->isEmptyText($sibNode)) {
1054
                break;
1055
            }
1056
        }
1057
1058
        // Next look inside the node.
1059 22
        $parentFound = false;
1060 22
        foreach ($node->childNodes as $childNode) {
1061 21
            if (!$parentFound && $childNode instanceof DOMElement && $childNode->hasAttribute($attribute)) {
1062 2
                return $childNode;
1063
            }
1064
1065 21
            if ($childNode instanceof DOMElement) {
1066 15
                $parentFound = $childNode->hasAttribute($parentAttribute);
1067 10
            } elseif ($childNode instanceof \DOMText && !empty(trim($childNode->data))) {
1068 21
                $parentFound = false;
1069
            }
1070
        }
1071
1072 20
        return null;
1073
    }
1074
1075
    /**
1076
     * @param DOMElement $node
1077
     * @param array $attributes
1078
     * @param array $special
1079
     * @param CompilerBuffer $out
1080
     */
1081 16
    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...
1082 16
        $each = $this->expr($special[self::T_EACH]->value, $out);
1083 16
        unset($special[self::T_EACH]);
1084
1085 16
        $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...
1086 16
        $scope = ['this' => $as[1]];
1087 16
        if (!empty($special[self::T_AS])) {
1088 9
            if (preg_match('`^(?:([a-z0-9]+)\s+)?([a-z0-9]+)$`i', $special[self::T_AS]->value, $m)) {
1089 8
                $scope = [$m[2] => $as[1]];
1090 8
                if (!empty($m[1])) {
1091 5
                    $scope[$m[1]] = $as[0] = $out->depthName('i', 1);
1092
1093
                    // Add loop tracking variables.
1094 8
                    $d = $out->depthName('', 1);
1095
                }
1096
            } else {
1097 1
                throw $out->createCompilerException(
1098 1
                    $special[self::T_AS],
1099 1
                    new \Exception("Invalid identifier in x-as attribute.")
1100
                );
1101
            }
1102
        }
1103 15
        unset($special[self::T_AS]);
1104
1105 15
        if (isset($d)) {
1106 5
            $out->appendCode("\$count$d = count($each);\n");
1107 5
            $out->appendCode("\$index$d = -1;\n");
1108
        }
1109
1110 15
        if (empty($as[0])) {
1111 10
            $out->appendCode("foreach ($each as \${$as[1]}) {\n");
1112
        } else {
1113 5
            $out->appendCode("foreach ($each as \${$as[0]} => \${$as[1]}) {\n");
1114
        }
1115 15
        $out->depth(+1);
1116 15
        $out->indent(+1);
1117 15
        $out->pushScope($scope);
1118
1119 15
        if (isset($d)) {
1120 5
            $out->appendCode("\$index$d++;\n");
1121 5
            $out->appendCode("\$first$d = \$index$d === 0;\n");
1122 5
            $out->appendCode("\$last$d = \$index$d === \$count$d - 1;\n");
1123
        }
1124
1125 15
        foreach ($node->childNodes as $childNode) {
1126 15
            $this->compileNode($childNode, $out);
1127
        }
1128
1129 15
        $out->indent(-1);
1130 15
        $out->depth(-1);
1131 15
        $out->popScope();
1132 15
        $out->appendCode("}\n");
1133 15
    }
1134
1135 61
    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...
1136 61
        if ($this->inPre($node)) {
1137
            return $text;
1138
        }
1139
1140
1141 61
        for ($sib = $node->previousSibling; $sib !== null && $this->canSkip($sib, $out); $sib = $sib->previousSibling) {
0 ignored issues
show
Unused Code introduced by
This for loop is empty and can be removed.

This check looks for for loops 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.

Consider removing the loop.

Loading history...
1142
            //
1143
        }
1144 61
        if ($sib === null) {
1145 61
            return ltrim($text);
1146
        }
1147
//        if ($this->canSkip($sib, $out)) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
61% 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...
1148
//            return ltrim($text);
1149
//        }
1150
1151 7
        $text = preg_replace('`^\s*\n\s*`', "\n", $text, -1, $count);
1152 7
        if ($count === 0) {
1153
            $text = preg_replace('`^\s+`', ' ', $text);
1154
        }
1155
1156
//        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...
1157
//            return ltrim($text);
1158
//        }
1159 7
        return $text;
1160
    }
1161
1162
    /**
1163
     * Whether or not a node can be skipped for the purposes of trimming whitespace.
1164
     *
1165
     * @param DOMNode|null $node The node to test.
1166
     * @param CompilerBuffer|null $out The compiler information.
1167
     * @return bool Returns **true** if whitespace can be trimmed right up to the node or **false** otherwise.
1168
     */
1169 13
    private function canSkip(\DOMNode $node, CompilerBuffer $out) {
1170 13
        if ($out->getNodeProp($node, 'skip')) {
1171 2
            return true;
1172
        }
1173
1174 13
        switch ($node->nodeType) {
1175 13
            case XML_TEXT_NODE:
1176 2
                return false;
1177 13
            case XML_COMMENT_NODE:
1178 1
                return true;
1179 13
            case XML_ELEMENT_NODE:
1180
                /* @var \DOMElement $node */
1181 13
                if ($node->tagName === self::T_X
1182 12
                    || ($node->tagName === 'script' && $node->hasAttribute(self::T_AS)) // expression assignment
1183 9
                    || ($node->hasAttribute(self::T_WITH) && $node->hasAttribute(self::T_AS)) // with assignment
1184 13
                    || ($node->hasAttribute(self::T_BLOCK) || $node->hasAttribute(self::T_COMPONENT))
1185
                ) {
1186 6
                    return true;
1187
                }
1188
        }
1189
1190 8
        return false;
1191
    }
1192
1193 61
    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...
1194 61
        if ($this->inPre($node)) {
1195
            return $text;
1196
        }
1197
1198 61
        for ($sib = $node->nextSibling; $sib !== null && $this->canSkip($sib, $out); $sib = $sib->nextSibling) {
0 ignored issues
show
Unused Code introduced by
This for loop is empty and can be removed.

This check looks for for loops 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.

Consider removing the loop.

Loading history...
1199
            //
1200
        }
1201 61
        if ($sib === null) {
1202 60
            return rtrim($text);
1203
        }
1204
1205 5
        $text = preg_replace('`\s*\n\s*$`', "\n", $text, -1, $count);
1206 5
        if ($count === 0) {
1207 5
            $text = preg_replace('`\s+$`', ' ', $text);
1208
        }
1209
1210
//        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...
1211
//            return rtrim($text);
1212
//        }
1213 5
        return $text;
1214
    }
1215
1216 61
    protected function inPre(\DOMNode $node) {
1217 61
        for ($node = $node->parentNode; $node !== null; $node = $node->parentNode) {
1218 61
            if (in_array($node->nodeType, ['code', 'pre'], true)) {
1219
                return true;
1220
            }
1221
        }
1222 61
        return false;
1223
    }
1224
1225 4
    private function compileChildBlock(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
1226
        /* @var DOMAttr $child */
1227 4
        $child = $special[self::T_CHILDREN];
1228 4
        unset($special[self::T_CHILDREN]);
1229
1230 4
        $name = $child->value === '' ? 0 : strtolower($child->value);
1231 4
        if ($name !== 0) {
1232 2
            if (empty($name)) {
1233
                throw $out->createCompilerException($special[self::T_BLOCK], new \Exception("Block names cannot be empty."));
1234
            }
1235 2
            if (!preg_match(self::IDENT_REGEX, $name)) {
1236
                throw $out->createCompilerException($special[self::T_BLOCK], new \Exception("The block name isn't a valid identifier."));
1237
            }
1238
        }
1239
1240 4
        $keyStr = var_export($name, true);
1241
1242 4
        $this->compileOpenTag($node, $attributes, $special, $out, true);
1243
1244 4
        $out->appendCode("\$this->writeChildren(\$children[{$keyStr}]);\n");
1245
1246 4
        $this->compileCloseTag($node, $special, $out, true);
1247 4
    }
1248
1249
    /**
1250
     * Compile an `<script type="ebi">` node.
1251
     *
1252
     * @param DOMElement $node The node to compile.
1253
     * @param DOMAttr[] $attributes The node's attributes.
1254
     * @param DOMAttr[] $special An array of special attributes.
1255
     * @param CompilerBuffer $out The compiler output.
1256
     */
1257 9
    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...
1258 9
        $str = $node->nodeValue;
1259
1260
        try {
1261 9
            $expr = $this->expr($str, $out);
1262 1
        } catch (SyntaxError $ex) {
1263 1
            $context = [];
1264 1
            if (preg_match('`^(.*) around position (\d*)\.$`', $ex->getMessage(), $m)) {
1265 1
                $add = substr_count($str, "\n", 0, $m[2]);
1266
1267 1
                $context['line'] = $node->getLineNo() + $add;
1268
            }
1269
1270 1
            throw $out->createCompilerException($node, $ex, $context);
1271
        }
1272
1273 8
        if (isset($special[self::T_AS])) {
1274 6
            if (null !== $this->closest($node, function (\DOMNode $n) use ($out) {
1275 6
                return $out->getNodeProp($n, self::T_INCLUDE);
1276 6
            })) {
1277 1
                throw $out->createCompilerException(
1278 1
                    $node,
1279 1
                    new \Exception("Expressions with x-as assignments cannot be declared inside child blocks.")
1280
                );
1281
            }
1282
1283 5
            if (preg_match(self::IDENT_REGEX, $special[self::T_AS]->value, $m)) {
1284
                // The template specified an x-as attribute to alias the with expression.
1285 4
                $out->depth(+1);
1286 4
                $scope = [$m[1] => $out->depthName('expr')];
1287 4
                $out->pushScope($scope);
1288 4
                $out->appendCode('$'.$out->depthName('expr')." = $expr;\n");
1289
            } else {
1290 1
                throw $out->createCompilerException(
1291 1
                    $special[self::T_AS],
1292 5
                    new \Exception("Invalid identifier in x-as attribute.")
1293
                );
1294
            }
1295 2
        } elseif (!empty($special[self::T_UNESCAPE])) {
1296 1
            $out->echoCode($expr);
1297
        } else {
1298 1
            $out->echoCode($this->compileEscape($expr));
1299
        }
1300 6
    }
1301
1302
    /**
1303
     * Similar to jQuery's closest method.
1304
     *
1305
     * @param DOMNode $node
1306
     * @param callable $test
1307
     * @return DOMNode
1308
     */
1309 6
    private function closest(\DOMNode $node, callable $test) {
1310 6
        for ($visitor = $node; $visitor !== null && !$test($visitor); $visitor = $visitor->parentNode) {
0 ignored issues
show
Unused Code introduced by
This for loop is empty and can be removed.

This check looks for for loops 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.

Consider removing the loop.

Loading history...
1311
            // Do nothing. The logic is all in the loop.
1312
        }
1313 6
        return $visitor;
1314
    }
1315
1316
    /**
1317
     * @param DOMElement $node
1318
     * @param $attributes
1319
     * @param $special
1320
     * @param CompilerBuffer $out
1321
     */
1322 35
    protected function compileBasicElement(DOMElement $node, $attributes, $special, CompilerBuffer $out) {
1323 35
        $this->compileOpenTag($node, $attributes, $special, $out);
1324
1325 35
        foreach ($node->childNodes as $childNode) {
1326 33
            $this->compileNode($childNode, $out);
1327
        }
1328
1329 34
        $this->compileCloseTag($node, $special, $out);
1330 34
    }
1331
1332 31
    protected function compileEscape($php) {
1333
//        return 'htmlspecialchars('.$php.')';
0 ignored issues
show
Unused Code Comprehensibility introduced by
56% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1334 31
        return '$this->escape('.$php.')';
1335
    }
1336
}
1337