Completed
Push — master ( b2d265...a57e80 )
by Todd
07:20
created

Compiler::findSpecialNode()   C

Complexity

Conditions 12
Paths 16

Size

Total Lines 29
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 12.0247

Importance

Changes 0
Metric Value
dl 0
loc 29
ccs 17
cts 18
cp 0.9444
rs 5.1612
c 0
b 0
f 0
cc 12
eloc 15
nc 16
nop 3
crap 12.0247

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 85
    public function __construct() {
241 85
        $this->expressions = new ExpressionLanguage();
242 85
        $this->expressions->setNamePattern('/[@a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A');
243 85
        $this->expressions->register(
244 85
            'hasChildren',
245
            function ($name = null) {
246 1
                return empty($name) ? 'isset($children[0])' : "isset(\$children[$name ?: 0])";
247 85
            },
248
            function ($name = null) {
0 ignored issues
show
Unused Code introduced by
The parameter $name is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
249
                return false;
250 85
            });
251 85
    }
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 83
    public function defineFunction($name, $function = null) {
260 83
        if ($function === null) {
261 1
            $function = $name;
262 1
        }
263
264 83
        $this->expressions->register(
265 83
            $name,
266 83
            $this->getFunctionCompiler($name, $function),
267 83
            $this->getFunctionEvaluator($function)
268 83
        );
269 83
    }
270
271 83
    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 83
        if ($function === 'empty') {
273
            return function ($expr) {
274
                return empty($expr);
275 83
            };
276 82
        } elseif ($function === 'isset') {
277
            return function ($expr) {
278
                return isset($expr);
279
            };
280
        }
281
282 82
        return $function;
283
    }
284
285 83
    private function getFunctionCompiler($name, $function) {
286 83
        $var = var_export(strtolower($name), true);
287
        $fn = function (...$args) use ($var) {
288 2
            return "\$this->call($var, ".implode(', ', $args).')';
289 83
        };
290
291 83
        if (is_string($function)) {
292
            $fn = function (...$args) use ($function) {
293 15
                return $function.'('.implode(', ', $args).')';
294 83
            };
295 83
        } elseif (is_array($function)) {
296 82
            if (is_string($function[0])) {
297
                $fn = function (...$args) use ($function) {
298
                    return "$function[0]::$function[1](".implode(', ', $args).')';
299
                };
300 82
            } elseif ($function[0] instanceof Ebi) {
301
                $fn = function (...$args) use ($function) {
302 17
                    return "\$this->$function[1](".implode(', ', $args).')';
303 82
                };
304 82
            }
305 82
        }
306
307 83
        return $fn;
308 1
    }
309
310 77
    public function compile($src, array $options = []) {
311 77
        $options += ['basename' => '', 'path' => '', 'runtime' => true];
312
313 77
        $src = trim($src);
314
315 77
        $out = new CompilerBuffer();
316
317 77
        $out->setBasename($options['basename']);
318 77
        $out->setSource($src);
319 77
        $out->setPath($options['path']);
320
321 77
        $dom = new \DOMDocument();
322
323 77
        $fragment = false;
324 77
        if (strpos($src, '<html') === false) {
325 75
            $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 75
            $fragment = true;
327 75
        }
328
329 77
        libxml_use_internal_errors(true);
330 77
        $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 77
        if ($options['runtime']) {
334 76
            $name = var_export($options['basename'], true);
335 76
            $out->appendCode("\$this->defineComponent($name, function (\$props = [], \$children = []) {\n");
336 76
        } else {
337 1
            $out->appendCode("function (\$props = [], \$children = []) {\n");
338
        }
339
340 77
        $out->pushScope(['this' => 'props']);
341 77
        $out->indent(+1);
342
343 77
        $parent = $fragment ? $dom->firstChild->nextSibling->firstChild : $dom;
344
345 77
        foreach ($parent->childNodes as $node) {
346 77
            $this->compileNode($node, $out);
347 69
        }
348
349 69
        $out->indent(-1);
350 69
        $out->popScope();
351
352 69
        if ($options['runtime']) {
353 68
            $out->appendCode("});");
354 68
        } else {
355 1
            $out->appendCode("};");
356
        }
357
358 69
        $r = $out->flush();
359
360 69
        $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 69
        return $r;
363
    }
364
365 56
    protected function isComponent($tag) {
366 56
        return !isset(static::$htmlTags[$tag]);
367
    }
368
369 77
    protected function compileNode(DOMNode $node, CompilerBuffer $out) {
370 77
        if ($out->getNodeProp($node, 'skip')) {
371 4
            return;
372
        }
373
374 77
        switch ($node->nodeType) {
375 77
            case XML_TEXT_NODE:
376 63
                $this->compileTextNode($node, $out);
377 63
                break;
378 72
            case XML_ELEMENT_NODE:
379
                /* @var \DOMElement $node */
380 72
                $this->compileElementNode($node, $out);
381 65
                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 71
        }
396 71
    }
397
398
    protected function domToArray(DOMNode $root) {
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
399
        $result = [];
400
401
        if ($root->hasAttributes()) {
402
            $attrs = $root->attributes;
403
            foreach ($attrs as $attr) {
404
                $result['@attributes'][$attr->name] = $attr->value;
405
            }
406
        }
407
408
        if ($root->hasChildNodes()) {
409
            $children = $root->childNodes;
410
            if ($children->length == 1) {
411
                $child = $children->item(0);
412
                if ($child->nodeType == XML_TEXT_NODE) {
413
                    $result['_value'] = $child->nodeValue;
414
                    return count($result) == 1
415
                        ? $result['_value']
416
                        : $result;
417
                }
418
            }
419
            $groups = [];
420
            foreach ($children as $child) {
421
                if (!isset($result[$child->nodeName])) {
422
                    $result[$child->nodeName] = $this->domToArray($child);
423
                } else {
424
                    if (!isset($groups[$child->nodeName])) {
425
                        $result[$child->nodeName] = [$result[$child->nodeName]];
426
                        $groups[$child->nodeName] = 1;
427
                    }
428
                    $result[$child->nodeName][] = $this->domToArray($child);
429
                }
430
            }
431
        }
432
433
        return $result;
434
    }
435
436 1
    protected function newline(DOMNode $node, CompilerBuffer $out) {
437 1
        if ($node->previousSibling && $node->previousSibling->nodeType !== XML_COMMENT_NODE) {
438
            $out->appendCode("\n");
439
        }
440 1
    }
441
442 1
    protected function compileCommentNode(\DOMComment $node, CompilerBuffer $out) {
443 1
        $comments = explode("\n", trim($node->nodeValue));
444
445 1
        $this->newline($node, $out);
446 1
        foreach ($comments as $comment) {
447 1
            $out->appendCode("// $comment\n");
448 1
        }
449 1
    }
450
451 65
    protected function compileTextNode(DOMNode $node, CompilerBuffer $out) {
452 65
        $nodeText = $node->nodeValue;
453
454 65
        $items = $this->splitExpressions($nodeText);
455 65
        foreach ($items as $i => list($text, $offset)) {
456 65
            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 3
                } else {
460
                    try {
461 31
                        $expr = substr($text, 1, -1);
462 31
                        $out->echoCode($this->compileEscape($this->expr($expr, $out)));
463 31
                    } 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 1
                        throw $out->createCompilerException($node, $ex, ['source' => $expr, 'line' => $line]);
468
                    }
469
                }
470 32
            } else {
471 65
                if ($i === 0) {
472 65
                    $text = $this->ltrim($text, $node, $out);
473 65
                }
474 65
                if ($i === count($items) - 1) {
475 65
                    $text = $this->rtrim($text, $node, $out);
476 65
                }
477
478 65
                $out->echoLiteral($text);
479
            }
480 65
        }
481 65
    }
482
483 72
    protected function compileElementNode(DOMElement $node, CompilerBuffer $out) {
484 72
        list($attributes, $special) = $this->splitAttributes($node);
485
486 72
        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 70
        } elseif (!empty($special) || $this->isComponent($node->tagName)) {
489 45
            $this->compileSpecialNode($node, $attributes, $special, $out);
490 40
        } else {
491 35
            $this->compileBasicElement($node, $attributes, $special, $out);
492
        }
493 65
    }
494
495 65
    protected function splitExpressions($value) {
496 65
        $values = preg_split('`({\S[^}]*?})`', $value, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE);
497 65
        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 69
    protected function expr($expr, CompilerBuffer $out, DOMAttr $attr = null) {
509 69
        $names = $out->getScopeVariables();
510
511
        try {
512
            $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 62
                if (isset($names[$name])) {
514 37
                    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 69
            });
521 69
        } 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 66
        if ($attr !== null && null !== $fn = $this->getAttributeFunction($attr)) {
530 10
            $compiled = call_user_func($fn, $compiled);
531 10
        }
532
533 66
        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 40
    private function getAttributeFunction(DOMAttr $attr) {
549 40
        $keys = ['@'.$attr->ownerElement->tagName.':'.$attr->name, '@'.$attr->name];
550
551 40
        foreach ($keys as $key) {
552 40
            if (null !== $fn = $this->expressions->getFunctionCompiler($key)) {
553 17
                return $fn;
554
            }
555 40
        }
556 30
    }
557
558
    /**
559
     * @param DOMElement $node
560
     */
561 72
    protected function splitAttributes(DOMElement $node) {
562 72
        $attributes = [];
563 72
        $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 72
        foreach ($node->attributes as $name => $attribute) {
566 68
            if (isset(static::$special[$name])) {
567 49
                $special[$name] = $attribute;
568 49
            } else {
569 32
                $attributes[$name] = $attribute;
570
            }
571 72
        }
572
573
        uksort($special, function ($a, $b) {
574 13
            return strnatcmp(static::$special[$a], static::$special[$b]);
575 72
        });
576
577 72
        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 3
                } 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 8
                } else {
617 21
                    $this->compileElement($node, $attributes, $special, $out);
618
                }
619 22
                break;
620 5
            case self::T_TAG:
621 5
            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 10
        }
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 2
        }
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 4
            } else {
714 1
                $expr = var_export($attribute->value, true);
715
            }
716
717 5
            $props[] = var_export($name, true).' => '.$expr;
718 13
        }
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 13
        } elseif (empty($props)) {
727
            // By default the current context is passed to components.
728 6
            $propsStr = $this->expr('this', $out);
729 6
        }
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 1
        } 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 13
        ]);
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 4
            }
771 4
        } finally {
772 5
            $blocksOut->indent(-1);
773 5
            $blocksOut->appendCode("}");
774 5
        }
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 33
        }
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 33
        }
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 61
    protected function compileOpenTag(DOMElement $node, $attributes, $special, CompilerBuffer $out, $force = false) {
813 61
        $tagNameExpr = !empty($special[self::T_TAG]) ? $special[self::T_TAG]->value : '';
814
815 61
        if ($node->tagName === self::T_X && empty($tagNameExpr)) {
816 5
            return;
817
        }
818
819 59
        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 5
            }
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 5
        } else {
837 55
            $out->echoLiteral('<'.$node->tagName);
838
        }
839
840
        /* @var DOMAttr $attribute */
841 59
        foreach ($attributes as $name => $attribute) {
842
            // Check for an attribute expression.
843 27
            if ($this->isExpression($attribute->value)) {
844 19
                $out->echoCode(
845 19
                    '$this->attribute('.var_export($name, true).', '.
846 19
                    $this->expr(substr($attribute->value, 1, -1), $out, $attribute).
847 19
                    ')');
848 27
            } 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 9
            } 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 2
            } else {
855 4
                $out->echoLiteral(' '.$name.'="');
856 4
                $out->echoLiteral(htmlspecialchars($attribute->value));
857 4
                $out->echoLiteral('"');
858
            }
859 59
        }
860
861 59
        if ($node->hasChildNodes() || $force) {
862 57
            $out->echoLiteral('>');
863 57
        } else {
864 3
            $out->echoLiteral(" />");
865
        }
866
867 59
        if (!empty($tagNameExpr)) {
868 5
            $out->indent(-1);
869 5
            $out->appendCode("}\n\n");
870 5
        }
871 59
    }
872
873 30
    private function isExpression($value) {
874 30
        return preg_match('`^{\S.*}$`', $value);
875
    }
876
877 59
    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 59
        if (!$force && !$node->hasChildNodes()) {
879 3
            return;
880
        }
881
882 57
        $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 57
        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 57
        } elseif ($node->tagName !== self::T_X) {
895 51
            $out->echoLiteral("</{$node->tagName}>");
896 51
        }
897 57
    }
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 2
        }
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 13
        } 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 1
            } else {
992 1
                throw $out->createCompilerException(
993 1
                    $special[self::T_AS],
994 1
                    new \Exception("Invalid identifier in x-as attribute.")
995 1
                );
996
            }
997 1
        }
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 2
        }
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 21
        }
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 2
        }
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 21
            } elseif ($childNode instanceof \DOMText && !empty(trim($childNode->data))) {
1068 6
                $parentFound = false;
1069 6
            }
1070 22
        }
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 5
                    $d = $out->depthName('', 1);
1095 5
                }
1096 8
            } else {
1097 1
                throw $out->createCompilerException(
1098 1
                    $special[self::T_AS],
1099 1
                    new \Exception("Invalid identifier in x-as attribute.")
1100 1
                );
1101
            }
1102 8
        }
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 5
        }
1109
1110 15
        if (empty($as[0])) {
1111 10
            $out->appendCode("foreach ($each as \${$as[1]}) {\n");
1112 10
        } 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 5
        }
1124
1125 15
        foreach ($node->childNodes as $childNode) {
1126 15
            $this->compileNode($childNode, $out);
1127 15
        }
1128
1129 15
        $out->indent(-1);
1130 15
        $out->depth(-1);
1131 15
        $out->popScope();
1132 15
        $out->appendCode("}\n");
1133 15
    }
1134
1135 65
    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 65
        if ($this->inPre($node)) {
1137
            return $text;
1138
        }
1139
1140
1141 65
        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 5
        }
1144 65
        if ($sib === null) {
1145 65
            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 13
                    || ($node->tagName === 'script' && $node->hasAttribute(self::T_AS)) // expression assignment
1183 12
                    || ($node->hasAttribute(self::T_WITH) && $node->hasAttribute(self::T_AS)) // with assignment
1184 9
                    || ($node->hasAttribute(self::T_BLOCK) || $node->hasAttribute(self::T_COMPONENT))
1185 13
                ) {
1186 6
                    return true;
1187
                }
1188 8
        }
1189
1190 8
        return false;
1191
    }
1192
1193 65
    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 65
        if ($this->inPre($node)) {
1195
            return $text;
1196
        }
1197
1198 65
        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 5
        }
1201 65
        if ($sib === null) {
1202 64
            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 5
        }
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 65
    protected function inPre(\DOMNode $node) {
1217 65
        for ($node = $node->parentNode; $node !== null; $node = $node->parentNode) {
1218 65
            if (in_array($node->nodeType, ['code', 'pre'], true)) {
1219
                return true;
1220
            }
1221 65
        }
1222 65
        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 2
        }
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 9
        } 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 1
            }
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 1
                );
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 4
            } else {
1290 1
                throw $out->createCompilerException(
1291 1
                    $special[self::T_AS],
1292 1
                    new \Exception("Invalid identifier in x-as attribute.")
1293 1
                );
1294
            }
1295 6
        } elseif (!empty($special[self::T_UNESCAPE])) {
1296 1
            $out->echoCode($expr);
1297 1
        } 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 6
        }
1313 6
        return $visitor;
1314
    }
1315
1316
    /**
1317
     * @param DOMElement $node
1318
     * @param $attributes
1319
     * @param $special
1320
     * @param CompilerBuffer $out
1321
     */
1322 39
    protected function compileBasicElement(DOMElement $node, $attributes, $special, CompilerBuffer $out) {
1323 39
        $this->compileOpenTag($node, $attributes, $special, $out);
1324
1325 39
        foreach ($node->childNodes as $childNode) {
1326 37
            $this->compileNode($childNode, $out);
1327 39
        }
1328
1329 38
        $this->compileCloseTag($node, $special, $out);
1330 38
    }
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