Completed
Pull Request — master (#24)
by Todd
02:49
created

Compiler::compileTextNode()   C

Complexity

Conditions 7
Paths 9

Size

Total Lines 31
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 7

Importance

Changes 0
Metric Value
dl 0
loc 31
ccs 20
cts 20
cp 1
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 22
nc 9
nop 2
crap 7
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 71
    public function __construct() {
241 71
        $this->expressions = new ExpressionLanguage();
242 71
        $this->expressions->setNamePattern('/[@a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A');
243 71
        $this->expressions->register(
244 71
            'hasChildren',
245 71
            function ($name = null) {
246 1
                return empty($name) ? 'isset($children[0])' : "isset(\$children[$name ?: 0])";
247 71
            },
248 71
            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 71
            });
251 71
    }
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 69
    public function defineFunction($name, $function = null) {
260 69
        if ($function === null) {
261 1
            $function = $name;
262
        }
263
264 69
        $this->expressions->register(
265 69
            $name,
266 69
            $this->getFunctionCompiler($name, $function),
267 69
            $this->getFunctionEvaluator($function)
268
        );
269 69
    }
270
271 69
    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 69
        if ($function === 'empty') {
273 69
            return function ($expr) {
274
                return empty($expr);
275 69
            };
276 68
        } elseif ($function === 'isset') {
277
            return function ($expr) {
278
                return isset($expr);
279
            };
280
        }
281
282 68
        return $function;
283
    }
284
285 69
    private function getFunctionCompiler($name, $function) {
286 69
        $var = var_export(strtolower($name), true);
287 69
        $fn = function ($expr) use ($var) {
288 1
            return "\$this->call($var, $expr)";
289 69
        };
290
291 69
        if (is_string($function)) {
292 69
            $fn = function (...$args) use ($function) {
293 14
                return $function.'('.implode(', ', $args).')';
294 69
            };
295 68
        } elseif (is_array($function)) {
296 68
            if (is_string($function[0])) {
297
                $fn = function (...$args) use ($function) {
298
                    return "$function[0]::$function[1](".implode(', ', $args).')';
299
                };
300 68
            } elseif ($function[0] instanceof Ebi) {
301 68
                $fn = function (...$args) use ($function) {
302 7
                    return "\$this->$function[1](".implode(', ', $args).')';
303 68
                };
304
            }
305
        }
306
307 69
        return $fn;
308
    }
309
310 63
    public function compile($src, array $options = []) {
311 63
        $options += ['basename' => '', 'path' => '', 'runtime' => true];
312
313 63
        $src = trim($src);
314
315 63
        $out = new CompilerBuffer();
316
317 63
        $out->setBasename($options['basename']);
318 63
        $out->setSource($src);
319 63
        $out->setPath($options['path']);
320
321 63
        $dom = new \DOMDocument();
322
323 63
        $fragment = false;
324 63
        if (strpos($src, '<html') === false) {
325 61
            $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 61
            $fragment = true;
327
        }
328
329 63
        libxml_use_internal_errors(true);
330 63
        $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 63
        if ($options['runtime']) {
334 62
            $name = var_export($options['basename'], true);
335 62
            $out->appendCode("\$this->defineComponent($name, function (\$props = [], \$children = []) {\n");
336
        } else {
337 1
            $out->appendCode("function (\$props = [], \$children = []) {\n");
338
        }
339
340 63
        $out->pushScope(['this' => 'props']);
341 63
        $out->indent(+1);
342
343 63
        $parent = $fragment ? $dom->firstChild->nextSibling->firstChild : $dom;
344
345 63
        foreach ($parent->childNodes as $node) {
346 63
            $this->compileNode($node, $out);
347
        }
348
349 57
        $out->indent(-1);
350 57
        $out->popScope();
351
352 57
        if ($options['runtime']) {
353 56
            $out->appendCode("});");
354
        } else {
355 1
            $out->appendCode("};");
356
        }
357
358 57
        $r = $out->flush();
359
360 57
        $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 57
        return $r;
363
    }
364
365 48
    protected function isComponent($tag) {
366 48
        return !isset(static::$htmlTags[$tag]);
367
    }
368
369 63
    protected function compileNode(DOMNode $node, CompilerBuffer $out) {
370 63
        if ($out->getNodeProp($node, 'skip')) {
371 4
            return;
372
        }
373
374 63
        switch ($node->nodeType) {
375 63
            case XML_TEXT_NODE:
376 50
                $this->compileTextNode($node, $out);
377 50
                break;
378 60
            case XML_ELEMENT_NODE:
379
                /* @var \DOMElement $node */
380 60
                $this->compileElementNode($node, $out);
381 55
                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 58
    }
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 52
    protected function compileTextNode(DOMNode $node, CompilerBuffer $out) {
452 52
        $nodeText = $node->nodeValue;
453
454 52
        $items = $this->splitExpressions($nodeText);
455 52
        foreach ($items as $i => list($text, $offset)) {
456 52
            if (preg_match('`^{\S`', $text)) {
457 30
                if (preg_match('`^{\s*unescape\((.+)\)\s*}$`', $text, $m)) {
458 3
                    $out->echoCode($this->expr($m[1], $out));
459
                } else {
460
                    try {
461 28
                        $expr = substr($text, 1, -1);
462 28
                        $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 30
                        throw $out->createCompilerException($node, $ex, ['source' => $expr, 'line' => $line]);
468
                    }
469
                }
470
            } else {
471 52
                if ($i === 0) {
472 52
                    $text = $this->ltrim($text, $node, $out);
473
                }
474 52
                if ($i === count($items) - 1) {
475 52
                    $text = $this->rtrim($text, $node, $out);
476
                }
477
478 52
                $out->echoLiteral($text);
479
            }
480
        }
481 52
    }
482
483 60
    protected function compileElementNode(DOMElement $node, CompilerBuffer $out) {
484 60
        list($attributes, $special) = $this->splitAttributes($node);
485
486 60
        if ($node->tagName === 'script' && ((isset($attributes['type']) && $attributes['type']->value === self::T_EBI) || !empty($special[self::T_AS]) || !empty($special[self::T_UNESCAPE]))) {
487 8
            $this->compileExpressionNode($node, $attributes, $special, $out);
488 53
        } elseif (!empty($special) || $this->isComponent($node->tagName)) {
489 37
            $this->compileSpecialNode($node, $attributes, $special, $out);
490
        } else {
491 29
            $this->compileBasicElement($node, $attributes, $special, $out);
492
        }
493 55
    }
494
495 52
    protected function splitExpressions($value) {
496 52
        $values = preg_split('`({\S[^}]*?})`', $value, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE);
497 52
        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 55
    protected function expr($expr, CompilerBuffer $out, DOMAttr $attr = null) {
509 55
        $names = $out->getScopeVariables();
510
511
        try {
512 55
            $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 49
                if (isset($names[$name])) {
514 30
                    return $names[$name];
515 32
                } elseif ($name[0] === '@') {
516 1
                    return 'this->meta['.var_export(substr($name, 1), true).']';
517
                } else {
518 31
                    return $names['this'].'['.var_export($name, true).']';
519
                }
520 55
            });
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 52
        if ($attr !== null && null !== $fn = $this->getAttributeFunction($attr)) {
530 4
            $compiled = call_user_func($fn, $compiled);
531
        }
532
533 52
        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 30
    private function getAttributeFunction(DOMAttr $attr) {
549 30
        $keys = ['@'.$attr->ownerElement->tagName.':'.$attr->name, '@'.$attr->name];
550
551 30
        foreach ($keys as $key) {
552 30
            if (null !== $fn = $this->expressions->getFunctionCompiler($key)) {
553 30
                return $fn;
554
            }
555
        }
556 25
    }
557
558
    /**
559
     * @param DOMElement $node
560
     */
561 60
    protected function splitAttributes(DOMElement $node) {
562 60
        $attributes = [];
563 60
        $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 60
        foreach ($node->attributes as $name => $attribute) {
566 56
            if (isset(static::$special[$name])) {
567 41
                $special[$name] = $attribute;
568
            } else {
569 56
                $attributes[$name] = $attribute;
570
            }
571
        }
572
573 60
        uksort($special, function ($a, $b) {
574 11
            return strnatcmp(static::$special[$a], static::$special[$b]);
575 60
        });
576
577 60
        return [$attributes, $special];
578
    }
579
580 37
    protected function compileSpecialNode(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
581 37
        $specialName = key($special);
582
583
        switch ($specialName) {
584 37
            case self::T_COMPONENT:
585 9
                $this->compileComponentRegister($node, $attributes, $special, $out);
586 9
                break;
587 37
            case self::T_IF:
588 10
                $this->compileIf($node, $attributes, $special, $out);
589 9
                break;
590 36
            case self::T_EACH:
591 15
                $this->compileEach($node, $attributes, $special, $out);
592 14
                break;
593 25
            case self::T_BLOCK:
594 1
                $this->compileBlock($node, $attributes, $special, $out);
595 1
                break;
596 25
            case self::T_CHILDREN:
597 3
                $this->compileChildBlock($node, $attributes, $special, $out);
598 3
                break;
599 25
            case self::T_INCLUDE:
600 1
                $this->compileComponentInclude($node, $attributes, $special, $out);
601 1
                break;
602 25
            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 23
            case self::T_LITERAL:
611 2
                $this->compileLiteral($node, $attributes, $special, $out);
612 2
                break;
613 21
            case '':
614 21
                if ($this->isComponent($node->tagName)) {
615 7
                    $this->compileComponentInclude($node, $attributes, $special, $out);
616
                } else {
617 20
                    $this->compileElement($node, $attributes, $special, $out);
618
                }
619 21
                break;
620 1
            case self::T_TAG:
621
            default:
622
                // This is only a tag node so it just gets compiled as an element.
623 1
                $this->compileBasicElement($node, $attributes, $special, $out);
624 1
                break;
625
        }
626 34
    }
627
628
    /**
629
     * Compile component registering.
630
     *
631
     * @param DOMElement $node
632
     * @param $attributes
633
     * @param $special
634
     * @param CompilerBuffer $out
635
     */
636 9
    public function compileComponentRegister(DOMElement $node, $attributes, $special, CompilerBuffer $out) {
637 9
        $name = strtolower($special[self::T_COMPONENT]->value);
638 9
        unset($special[self::T_COMPONENT]);
639
640 9
        $prev = $out->select($name);
641
642 9
        $varName = var_export($name, true);
643 9
        $out->appendCode("\$this->defineComponent($varName, function (\$props = [], \$children = []) {\n");
644 9
        $out->pushScope(['this' => 'props']);
645 9
        $out->indent(+1);
646
647
        try {
648 9
            $this->compileSpecialNode($node, $attributes, $special, $out);
649 9
        } finally {
650 9
            $out->popScope();
651 9
            $out->indent(-1);
652 9
            $out->appendCode("});");
653 9
            $out->select($prev);
654
        }
655 9
    }
656
657 1
    private function compileBlock(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
658 1
        $name = strtolower($special[self::T_BLOCK]->value);
659 1
        unset($special[self::T_BLOCK]);
660
661 1
        $prev = $out->select($name);
662
663 1
        $use = '$'.implode(', $', array_unique($out->getScopeVariables())).', $children';
664
665 1
        $out->appendCode("function () use ($use) {\n");
666 1
        $out->pushScope(['this' => 'props']);
667 1
        $out->indent(+1);
668
669
        try {
670 1
            $this->compileSpecialNode($node, $attributes, $special, $out);
671 1
        } finally {
672 1
            $out->indent(-1);
673 1
            $out->popScope();
674 1
            $out->appendCode("}");
675 1
            $out->select($prev);
676
        }
677
678 1
        return $out;
679
    }
680
681
    /**
682
     * Compile component inclusion and rendering.
683
     *
684
     * @param DOMElement $node
685
     * @param DOMAttr[] $attributes
686
     * @param DOMAttr[] $special
687
     * @param CompilerBuffer $out
688
     */
689 11
    protected function compileComponentInclude(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
690
        // Generate the attributes into a property array.
691 11
        $props = [];
692 11
        foreach ($attributes as $name => $attribute) {
693
            /* @var DOMAttr $attr */
694 5
            if ($this->isExpression($attribute->value)) {
695 4
                $expr = $this->expr(substr($attribute->value, 1, -1), $out, $attribute);
696
            } else {
697 1
                $expr = var_export($attribute->value, true);
698
            }
699
700 5
            $props[] = var_export($name, true).' => '.$expr;
701
        }
702 11
        $propsStr = '['.implode(', ', $props).']';
703
704 11
        if (isset($special[self::T_WITH])) {
705 3
            $withExpr = $this->expr($special[self::T_WITH]->value, $out, $special[self::T_WITH]);
706 3
            unset($special[self::T_WITH]);
707
708 3
            $propsStr = empty($props) ? $withExpr : $propsStr.' + (array)'.$withExpr;
709
        } elseif (empty($props)) {
710
            // By default the current context is passed to components.
711 4
            $propsStr = $this->expr('this', $out);
712
        }
713
714
        // Compile the children blocks.
715 11
        $blocks = $this->compileComponentBlocks($node, $out);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

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

To visualize

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

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

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

will produce no issues.

Loading history...
716 11
        $blocksStr = $blocks->flush();
717
718 11
        if (isset($special[self::T_INCLUDE])) {
719 1
            $name = $this->expr($special[self::T_INCLUDE]->value, $out, $special[self::T_INCLUDE]);
720
        } else {
721 10
            $name = var_export($node->tagName, true);
722
        }
723
724 11
        $out->appendCode("\$this->write($name, $propsStr, $blocksStr);\n");
725 11
    }
726
727
    /**
728
     * @param DOMElement $parent
729
     * @return CompilerBuffer
730
     */
731 11
    protected function compileComponentBlocks(DOMElement $parent, CompilerBuffer $out) {
732 11
        $blocksOut = new CompilerBuffer(CompilerBuffer::STYLE_ARRAY, [
733 11
            'baseIndent' => $out->getIndent(),
734 11
            'indent' => $out->getIndent() + 1,
735 11
            'depth' => $out->getDepth(),
736 11
            'scopes' => $out->getAllScopes()
737
        ]);
738
739 11
        if ($this->isEmptyNode($parent)) {
740 9
            return $blocksOut;
741
        }
742
743 3
        $use = '$'.implode(', $', $blocksOut->getScopeVariables()).', $children';
744
745 3
        $blocksOut->appendCode("function () use ($use) {\n");
746 3
        $blocksOut->indent(+1);
747
748
        try {
749 3
            foreach ($parent->childNodes as $node) {
750 3
                $this->compileNode($node, $blocksOut);
751
            }
752 3
        } finally {
753 3
            $blocksOut->indent(-1);
754 3
            $blocksOut->appendCode("}");
755
        }
756
757 3
        return $blocksOut;
758
    }
759
760 28
    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...
761
        // Don't double up comments.
762 28
        if ($node->previousSibling && $node->previousSibling->nodeType === XML_COMMENT_NODE) {
763
            return;
764
        }
765
766 28
        $str = '<'.$node->tagName;
767 28
        foreach ($special as $attr) {
768
            /* @var DOMAttr $attr */
769 28
            $str .= ' '.$attr->name.(empty($attr->value) ? '' : '="'.htmlspecialchars($attr->value).'"');
770
        }
771 28
        $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...
772 28
        $comments = explode("\n", $str);
773 28
        foreach ($comments as $comment) {
774 28
            $out->appendCode("// $comment\n");
775
        }
776 28
    }
777
778
    /**
779
     * @param DOMElement $node
780
     * @param DOMAttr[] $attributes
781
     * @param DOMAttr[] $special
782
     * @param CompilerBuffer $out
783
     * @param bool $force
784
     */
785 50
    protected function compileOpenTag(DOMElement $node, $attributes, $special, CompilerBuffer $out, $force = false) {
786 50
        $tagNameExpr = !empty($special[self::T_TAG]) ? $special[self::T_TAG]->value : '';
787
788 50
        if ($node->tagName === self::T_X && empty($tagNameExpr)) {
789 5
            return;
790
        }
791
792 48
        if (!empty($tagNameExpr)) {
793 1
            $tagNameExpr = $this->expr($tagNameExpr, $out, $special[self::T_TAG]);
794 1
            $out->echoLiteral('<');
795 1
            $out->echoCode($tagNameExpr);
796
        } else {
797 48
            $out->echoLiteral('<'.$node->tagName);
798
        }
799
800
        /* @var DOMAttr $attribute */
801 48
        foreach ($attributes as $name => $attribute) {
802
            // Check for an attribute expression.
803 17
            if ($this->isExpression($attribute->value)) {
804 13
                $out->echoCode(
805 13
                    '$this->attribute('.var_export($name, true).', '.
806 13
                    $this->expr(substr($attribute->value, 1, -1), $out, $attribute).
807 13
                    ')');
808 5
            } elseif (null !== $fn = $this->getAttributeFunction($attribute)) {
809 3
                $value  = call_user_func($fn, var_export($attribute->value, true));
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned correctly; expected 1 space but found 2 spaces

This check looks for improperly formatted assignments.

Every assignment must have exactly one space before and one space after the equals operator.

To illustrate:

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

will have no issues, while

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

will report issues in lines 1 and 2.

Loading history...
810
811 3
                $out->echoCode('$this->attribute('.var_export($name, true).', '.$value.')');
812 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...
813 2
                $out->echoLiteral(' '.$name);
814
            } else {
815 4
                $out->echoLiteral(' '.$name.'="');
816 4
                $out->echoLiteral(htmlspecialchars($attribute->value));
817 17
                $out->echoLiteral('"');
818
            }
819
        }
820
821 48
        if ($node->hasChildNodes() || $force) {
822 46
            $out->echoLiteral('>');
823
        } else {
824 3
            $out->echoLiteral(" />");
825
        }
826 48
    }
827
828 20
    private function isExpression($value) {
829 20
        return preg_match('`^{\S.*}$`', $value);
830
    }
831
832 49
    protected function compileCloseTag(DOMElement $node, $special, CompilerBuffer $out, $force = false) {
833 49
        if (!$force && !$node->hasChildNodes()) {
834 3
            return;
835
        }
836
837 47
        $tagNameExpr = !empty($special[self::T_TAG]) ? $special[self::T_TAG]->value : '';
838 47
        if (!empty($tagNameExpr)) {
839 1
            $tagNameExpr = $this->expr($tagNameExpr, $out, $special[self::T_TAG]);
840 1
            $out->echoLiteral('</');
841 1
            $out->echoCode($tagNameExpr);
842 1
            $out->echoLiteral('>');
843 47
        } elseif ($node->tagName !== self::T_X) {
844 45
            $out->echoLiteral("</{$node->tagName}>");
845
        }
846 47
    }
847
848 4
    protected function isEmptyText(DOMNode $node) {
849 4
        return $node instanceof \DOMText && empty(trim($node->data));
850
    }
851
852 11
    protected function isEmptyNode(DOMNode $node) {
853 11
        if (!$node->hasChildNodes()) {
854 9
            return true;
855
        }
856
857 3
        foreach ($node->childNodes as $childNode) {
858 3
            if ($childNode instanceof DOMElement) {
859 1
                return false;
860
            }
861 2
            if ($childNode instanceof \DOMText && !$this->isEmptyText($childNode)) {
862 2
                return false;
863
            }
864
        }
865
866
        return true;
867
    }
868
869 10
    protected function compileIf(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
870 10
        $this->compileTagComment($node, $attributes, $special, $out);
871 10
        $expr = $this->expr($special[self::T_IF]->value, $out, $special[self::T_IF]);
872 9
        unset($special[self::T_IF]);
873
874 9
        $elseNode = $this->findSpecialNode($node, self::T_ELSE, self::T_IF);
875 9
        $out->setNodeProp($elseNode, 'skip', true);
876
877 9
        $out->appendCode('if ('.$expr.") {\n");
878 9
        $out->indent(+1);
879
880 9
        $this->compileSpecialNode($node, $attributes, $special, $out);
881
882 9
        $out->indent(-1);
883
884 9
        if ($elseNode) {
885 2
            list($attributes, $special) = $this->splitAttributes($elseNode);
886 2
            unset($special[self::T_ELSE]);
887
888 2
            $out->appendCode("} else {\n");
889
890 2
            $out->indent(+1);
891 2
            $this->compileSpecialNode($elseNode, $attributes, $special, $out);
892 2
            $out->indent(-1);
893
        }
894
895 9
        $out->appendCode("}\n");
896 9
    }
897
898 15
    protected function compileEach(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
899 15
        $this->compileTagComment($node, $attributes, $special, $out);
900 15
        $this->compileOpenTag($node, $attributes, $special, $out);
901
902 15
        $emptyNode = $this->findSpecialNode($node, self::T_EMPTY, self::T_ELSE);
903 15
        $out->setNodeProp($emptyNode, 'skip', true);
904
905 15
        if ($emptyNode === null) {
906 13
            $this->compileEachLoop($node, $attributes, $special, $out);
907
        } else {
908 2
            $expr = $this->expr("empty({$special[self::T_EACH]->value})", $out);
909
910 2
            list ($emptyAttributes, $emptySpecial) = $this->splitAttributes($emptyNode);
911 2
            unset($emptySpecial[self::T_EMPTY]);
912
913 2
            $out->appendCode('if ('.$expr.") {\n");
914
915 2
            $out->indent(+1);
916 2
            $this->compileSpecialNode($emptyNode, $emptyAttributes, $emptySpecial, $out);
917 2
            $out->indent(-1);
918
919 2
            $out->appendCode("} else {\n");
920
921 2
            $out->indent(+1);
922 2
            $this->compileEachLoop($node, $attributes, $special, $out);
923 2
            $out->indent(-1);
924
925 2
            $out->appendCode("}\n");
926
        }
927
928 14
        $this->compileCloseTag($node, $special, $out);
929 14
    }
930
931 2
    protected function compileWith(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
932 2
        $this->compileTagComment($node, $attributes, $special, $out);
933
934 2
        $out->depth(+1);
935 2
        $scope = ['this' => $out->depthName('props')];
936 2
        if (!empty($special[self::T_AS])) {
937 2
            if (preg_match(self::IDENT_REGEX, $special[self::T_AS]->value, $m)) {
938
                // The template specified an x-as attribute to alias the with expression.
939 1
                $scope = [$m[1] => $out->depthName('props')];
940
            } else {
941 1
                throw $out->createCompilerException(
942 1
                    $special[self::T_AS],
943 1
                    new \Exception("Invalid identifier \"{$special[self::T_AS]->value}\" in x-as attribute.")
944
                );
945
            }
946
        }
947 1
        $with = $this->expr($special[self::T_WITH]->value, $out);
948
949 1
        unset($special[self::T_WITH], $special[self::T_AS]);
950
951 1
        $out->pushScope($scope);
952 1
        $out->appendCode('$'.$out->depthName('props')." = $with;\n");
953
954 1
        $this->compileSpecialNode($node, $attributes, $special, $out);
955
956 1
        $out->depth(-1);
957 1
        $out->popScope();
958 1
    }
959
960 2
    protected function compileLiteral(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
961 2
        $this->compileTagComment($node, $attributes, $special, $out);
962 2
        unset($special[self::T_LITERAL]);
963
964 2
        $this->compileOpenTag($node, $attributes, $special, $out);
965
966 2
        foreach ($node->childNodes as $childNode) {
967 2
            $html = $childNode->ownerDocument->saveHTML($childNode);
968 2
            $out->echoLiteral($html);
969
        }
970
971 2
        $this->compileCloseTag($node, $special, $out);
972 2
    }
973
974 20
    protected function compileElement(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
975 20
        $this->compileOpenTag($node, $attributes, $special, $out);
976
977 20
        foreach ($node->childNodes as $childNode) {
978 20
            $this->compileNode($childNode, $out);
979
        }
980
981 20
        $this->compileCloseTag($node, $special, $out);
982 20
    }
983
984
    /**
985
     * Find a special node in relation to another node.
986
     *
987
     * This method is used to find things such as x-empty and x-else elements.
988
     *
989
     * @param DOMElement $node The node to search in relation to.
990
     * @param string $attribute The name of the attribute to search for.
991
     * @param string $parentAttribute The name of the parent attribute to resolve conflicts.
992
     * @return DOMElement|null Returns the found element node or **null** if not found.
993
     */
994 23
    protected function findSpecialNode(DOMElement $node, $attribute, $parentAttribute) {
995
        // First look for a sibling after the node.
996 23
        for ($sibNode = $node->nextSibling; $sibNode !== null; $sibNode = $sibNode->nextSibling) {
997 2
            if ($sibNode instanceof DOMElement && $sibNode->hasAttribute($attribute)) {
998 2
                return $sibNode;
999
            }
1000
1001
            // Stop searching if we encounter another node.
1002 2
            if (!$this->isEmptyText($sibNode)) {
1003
                break;
1004
            }
1005
        }
1006
1007
        // Next look inside the node.
1008 21
        $parentFound = false;
1009 21
        foreach ($node->childNodes as $childNode) {
1010 20
            if (!$parentFound && $childNode instanceof DOMElement && $childNode->hasAttribute($attribute)) {
1011 2
                return $childNode;
1012
            }
1013
1014 20
            if ($childNode instanceof DOMElement) {
1015 14
                $parentFound = $childNode->hasAttribute($parentAttribute);
1016 9
            } elseif ($childNode instanceof \DOMText && !empty(trim($childNode->data))) {
1017 20
                $parentFound = false;
1018
            }
1019
        }
1020
1021 19
        return null;
1022
    }
1023
1024
    /**
1025
     * @param DOMElement $node
1026
     * @param array $attributes
1027
     * @param array $special
1028
     * @param CompilerBuffer $out
1029
     */
1030 15
    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...
1031 15
        $each = $this->expr($special[self::T_EACH]->value, $out);
1032 15
        unset($special[self::T_EACH]);
1033
1034 15
        $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...
1035 15
        $scope = ['this' => $as[1]];
1036 15
        if (!empty($special[self::T_AS])) {
1037 8
            if (preg_match('`^(?:([a-z0-9]+)\s+)?([a-z0-9]+)$`i', $special[self::T_AS]->value, $m)) {
1038 7
                $scope = [$m[2] => $as[1]];
1039 7
                if (!empty($m[1])) {
1040 7
                    $scope[$m[1]] = $as[0] = $out->depthName('i', 1);
1041
                }
1042
            } else {
1043 1
                throw $out->createCompilerException(
1044 1
                    $special[self::T_AS],
1045 1
                    new \Exception("Invalid identifier \"{$special[self::T_AS]->value}\" in x-as attribute.")
1046
                );
1047
            }
1048
        }
1049 14
        unset($special[self::T_AS]);
1050 14
        if (empty($as[0])) {
1051 10
            $out->appendCode("foreach ($each as \${$as[1]}) {\n");
1052
        } else {
1053 4
            $out->appendCode("foreach ($each as \${$as[0]} => \${$as[1]}) {\n");
1054
        }
1055 14
        $out->depth(+1);
1056 14
        $out->indent(+1);
1057 14
        $out->pushScope($scope);
1058
1059 14
        foreach ($node->childNodes as $childNode) {
1060 14
            $this->compileNode($childNode, $out);
1061
        }
1062
1063 14
        $out->indent(-1);
1064 14
        $out->depth(-1);
1065 14
        $out->popScope();
1066 14
        $out->appendCode("}\n");
1067 14
    }
1068
1069 52
    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...
1070 52
        if ($this->inPre($node)) {
1071
            return $text;
1072
        }
1073
1074 52
        $sib = $node->previousSibling ?: $node->parentNode;
1075 52
        if ($sib === null || !$sib instanceof \DOMElement || $out->getNodeProp($sib, 'skip') || $sib->tagName === self::T_X) {
1076 8
            return ltrim($text);
1077
        }
1078
1079 50
        $text = preg_replace('`^\s*\n\s*`', "\n", $text, -1, $count);
1080 50
        if ($count === 0) {
1081 48
            $text = preg_replace('`^\s+`', ' ', $text);
1082
        }
1083
1084
//        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...
1085
//            return ltrim($text);
1086
//        }
1087 50
        return $text;
1088
    }
1089
1090 52
    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...
1091 52
        if ($this->inPre($node)) {
1092
            return $text;
1093
        }
1094
1095 52
        $sib = $node->nextSibling ?: $node->parentNode;
1096
1097 52
        if ($sib === null || !$sib instanceof \DOMElement || $out->getNodeProp($sib, 'skip') || $sib->tagName === self::T_X) {
1098 7
            return rtrim($text);
1099
        }
1100
1101 49
        $text = preg_replace('`\s*\n\s*$`', "\n", $text, -1, $count);
1102 49
        if ($count === 0) {
1103 48
            $text = preg_replace('`\s+$`', ' ', $text);
1104
        }
1105
1106
//        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...
1107
//            return rtrim($text);
1108
//        }
1109 49
        return $text;
1110
    }
1111
1112 52
    protected function inPre(\DOMNode $node) {
1113 52
        for ($node = $node->parentNode; $node !== null; $node = $node->parentNode) {
1114 52
            if (in_array($node->nodeType, ['code', 'pre'], true)) {
1115
                return true;
1116
            }
1117
        }
1118 52
        return false;
1119
    }
1120
1121 3
    private function compileChildBlock(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
1122
        /* @var DOMAttr $child */
1123 3
        $child = $special[self::T_CHILDREN];
1124 3
        unset($special[self::T_CHILDREN]);
1125
1126 3
        $key = $child->value === '' ? 0 : $child->value;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

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

To visualize

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

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

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

will produce no issues.

Loading history...
1127 3
        $keyStr = var_export($key, true);
1128
1129 3
        $this->compileOpenTag($node, $attributes, $special, $out, true);
1130
1131 3
        $out->appendCode("if (isset(\$children[{$keyStr}])) {\n");
1132 3
        $out->indent(+1);
1133 3
        $out->appendCode("\$children[{$keyStr}]();\n");
1134 3
        $out->indent(-1);
1135 3
        $out->appendCode("}\n");
1136
1137 3
        $this->compileCloseTag($node, $special, $out, true);
1138 3
    }
1139
1140
    /**
1141
     * Compile an x-expr node.
1142
     *
1143
     * @param DOMElement $node The node to compile.
1144
     * @param DOMAttr[] $attributes The node's attributes.
1145
     * @param DOMAttr[] $special An array of special attributes.
1146
     * @param CompilerBuffer $out The compiler output.
1147
     */
1148 8
    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...
1149 8
        $str = $raw = $node->nodeValue;
0 ignored issues
show
Unused Code introduced by
$raw is not used, you could remove the assignment.

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

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

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

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

Loading history...
1150
1151
        try {
1152 8
            $expr = $this->expr($str, $out);
1153 1
        } catch (SyntaxError $ex) {
1154 1
            throw $out->createCompilerException($node, $ex);
1155
        }
1156
1157 7
        if (!empty($special[self::T_AS])) {
1158 5
            if (preg_match(self::IDENT_REGEX, $special[self::T_AS]->value, $m)) {
1159
                // The template specified an x-as attribute to alias the with expression.
1160 4
                $out->depth(+1);
1161 4
                $scope = [$m[1] => $out->depthName('expr')];
1162 4
                $out->pushScope($scope);
1163 4
                $out->appendCode('$'.$out->depthName('expr')." = $expr;\n");
1164
            } else {
1165 1
                throw $out->createCompilerException(
1166 1
                    $special[self::T_AS],
1167 5
                    new \Exception("Invalid identifier \"{$special[self::T_AS]->value}\" in x-as attribute.")
1168
                );
1169
            }
1170 2
        } elseif (!empty($special[self::T_UNESCAPE])) {
1171 1
            $out->echoCode($expr);
1172
        } else {
1173 1
            $out->echoCode($this->compileEscape($expr));
1174
        }
1175 6
    }
1176
1177
    /**
1178
     * @param DOMElement $node
1179
     * @param $attributes
1180
     * @param $special
1181
     * @param CompilerBuffer $out
1182
     */
1183 29
    protected function compileBasicElement(DOMElement $node, $attributes, $special, CompilerBuffer $out) {
1184 29
        $this->compileOpenTag($node, $attributes, $special, $out);
1185
1186 29
        foreach ($node->childNodes as $childNode) {
1187 27
            $this->compileNode($childNode, $out);
1188
        }
1189
1190 29
        $this->compileCloseTag($node, $special, $out);
1191 29
    }
1192
1193 28
    protected function compileEscape($php) {
1194
//        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...
1195 28
        return '$this->escape('.$php.')';
1196
    }
1197
}
1198