Completed
Push — master ( b380ba...d8b52d )
by Todd
11s
created

Compiler::compileOpenTag()   D

Complexity

Conditions 10
Paths 34

Size

Total Lines 40
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 10.1728

Importance

Changes 0
Metric Value
dl 0
loc 40
ccs 22
cts 25
cp 0.88
rs 4.8196
c 0
b 0
f 0
cc 10
eloc 27
nc 34
nop 5
crap 10.1728

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
14
class Compiler {
15
    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...
16
    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...
17
    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...
18
    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...
19
    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...
20
    const T_COMPONENT = 'x-component';
21
    const T_CHILDREN = 'x-children';
22
    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...
23
    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...
24
    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...
25
    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...
26
    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...
27
    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...
28
    const T_UNESCAPE = 'x-unescape';
29
    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...
30
31
    const IDENT_REGEX = '`^([a-z0-9-]+)$`i';
32
33
    protected static $special = [
34
        self::T_COMPONENT => 1,
35
        self::T_IF => 2,
36
        self::T_ELSE => 3,
37
        self::T_EACH => 4,
38
        self::T_EMPTY => 5,
39
        self::T_CHILDREN => 6,
40
        self::T_INCLUDE => 7,
41
        self::T_WITH => 8,
42
        self::T_BLOCK => 9,
43
        self::T_LITERAL => 10,
44
        self::T_AS => 11,
45
        self::T_UNESCAPE => 12,
46
        self::T_TAG => 13
47
    ];
48
49
    protected static $htmlTags = [
50
        'a' => 'i',
51
        'abbr' => 'i',
52
        'acronym' => 'i', // deprecated
53
        'address' => 'b',
54
//        '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...
55
        'area' => 'i',
56
        'article' => 'b',
57
        'aside' => 'b',
58
        'audio' => 'i',
59
        'b' => 'i',
60
        'base' => 'i',
61
//        '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...
62
        'bdi' => 'i',
63
        'bdo' => 'i',
64
//        '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...
65
//        'big' => 'i',
66
        'x' => 'i',
67
//        '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...
68
        'blockquote' => 'b',
69
        'body' => 'b',
70
        'br' => 'i',
71
        'button' => 'i',
72
        'canvas' => 'b',
73
        'caption' => 'i',
74
//        '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...
75
        'cite' => 'i',
76
        'code' => 'i',
77
        'col' => 'i',
78
        'colgroup' => 'i',
79
//        '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...
80
        'content' => 'i',
81
        'data' => 'i',
82
        'datalist' => 'i',
83
        'dd' => 'b',
84
        'del' => 'i',
85
        'details' => 'i',
86
        'dfn' => 'i',
87
        'dialog' => 'i',
88
//        '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...
89
        'div' => 'i',
90
        'dl' => 'b',
91
        'dt' => 'b',
92
//        '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...
93
        'em' => 'i',
94
        'embed' => 'i',
95
        'fieldset' => 'b',
96
        'figcaption' => 'b',
97
        'figure' => 'b',
98
//        '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...
99
        'footer' => 'b',
100
        'form' => 'b',
101
        'frame' => 'i',
102
        'frameset' => 'i',
103
        'h1' => 'b',
104
        'h2' => 'b',
105
        'h3' => 'b',
106
        'h4' => 'b',
107
        'h5' => 'b',
108
        'h6' => 'b',
109
        'head' => 'b',
110
        'header' => 'b',
111
        'hgroup' => 'b',
112
        'hr' => 'b',
113
        'html' => 'b',
114
        'i' => 'i',
115
        'iframe' => 'i',
116
        'image' => 'i',
117
        'img' => 'i',
118
        'input' => 'i',
119
        'ins' => 'i',
120
        'isindex' => 'i',
121
        'kbd' => 'i',
122
        'keygen' => 'i',
123
        'label' => 'i',
124
        'legend' => 'i',
125
        'li' => 'i',
126
        'link' => 'i',
127
//        '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...
128
        'main' => 'b',
129
        'map' => 'i',
130
        'mark' => 'i',
131
//        '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...
132
        'menu' => 'i',
133
        'menuitem' => 'i',
134
        'meta' => 'i',
135
        'meter' => 'i',
136
        'multicol' => 'i',
137
        'nav' => 'b',
138
        'nobr' => 'i',
139
        'noembed' => 'i',
140
        'noframes' => 'i',
141
        'noscript' => 'b',
142
        'object' => 'i',
143
        'ol' => 'b',
144
        'optgroup' => 'i',
145
        'option' => 'b',
146
        'output' => 'i',
147
        'p' => 'b',
148
        'param' => 'i',
149
        'picture' => 'i',
150
//        '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...
151
        'pre' => 'b',
152
        'progress' => 'i',
153
        'q' => 'i',
154
        'rp' => 'i',
155
        'rt' => 'i',
156
        'rtc' => 'i',
157
        'ruby' => 'i',
158
        's' => 'i',
159
        'samp' => 'i',
160
        'script' => 'i',
161
        'section' => 'b',
162
        'select' => 'i',
163
//        '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...
164
        'slot' => 'i',
165
        'small' => 'i',
166
        'source' => 'i',
167
//        '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...
168
        'span' => 'i',
169
//        '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...
170
        'strong' => 'i',
171
        'style' => 'i',
172
        'sub' => 'i',
173
        'summary' => 'i',
174
        'sup' => 'i',
175
        'table' => 'b',
176
        'tbody' => 'i',
177
        'td' => 'i',
178
        'template' => 'i',
179
        'textarea' => 'i',
180
        'tfoot' => 'b',
181
        'th' => 'i',
182
        'thead' => 'i',
183
        'time' => 'i',
184
        'title' => 'i',
185
        'tr' => 'i',
186
        'track' => 'i',
187
//        '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...
188
        'u' => 'i',
189
        'ul' => 'b',
190
        'var' => 'i',
191
        'video' => 'b',
192
        'wbr' => 'i',
193
194
        /// SVG ///
195
        'animate' => 's',
196
        'animateColor' => 's',
197
        'animateMotion' => 's',
198
        'animateTransform' => 's',
199
//        '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...
200
        'circle' => 's',
201
        'desc' => 's',
202
        'defs' => 's',
203
        'discard' => 's',
204
        'ellipse' => 's',
205
        'g' => 's',
206
//        '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...
207
        'line' => 's',
208
        'marker' => 's',
209
        'mask' => 's',
210
        'missing-glyph' => 's',
211
        'mpath' => 's',
212
        'metadata' => 's',
213
        'path' => 's',
214
        'pattern' => 's',
215
        'polygon' => 's',
216
        'polyline' => 's',
217
        'rect' => 's',
218
        'set' => 's',
219
        'svg' => 's',
220
        'switch' => 's',
221
        'symbol' => 's',
222
        'text' => 's',
223
//        '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...
224
        'use' => 's',
225
    ];
226
227
    /**
228
     * @var ExpressionLanguage
229
     */
230
    protected $expressions;
231
232 60
    public function __construct() {
233 60
        $this->expressions = new ExpressionLanguage();
234 60
        $this->expressions->setNamePattern('/[@a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A');
235 60
        $this->expressions->register(
236 60
            'hasChildren',
237 60
            function ($name = null) {
238 1
                return empty($name) ? 'isset($children[0])' : "isset(\$children[$name ?: 0])";
239 60
            },
240 60
            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...
241
                return false;
242 60
            });
243 60
    }
244
245
    /**
246
     * Register a runtime function.
247
     *
248
     * @param string $name The name of the function.
249
     * @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...
250
     */
251 58
    public function defineFunction($name, $function = null) {
252 58
        if ($function === null) {
253 1
            $function = $name;
254
        }
255
256 58
        $this->expressions->register(
257 58
            $name,
258 58
            $this->getFunctionCompiler($name, $function),
259 58
            $this->getFunctionEvaluator($function)
260
        );
261 58
    }
262
263 58
    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...
264 58
        if ($function === 'empty') {
265 58
            return function ($expr) {
266
                return empty($expr);
267 58
            };
268 57
        } elseif ($function === 'isset') {
269
            return function ($expr) {
270
                return isset($expr);
271
            };
272
        }
273
274 57
        return $function;
275
    }
276
277 58
    private function getFunctionCompiler($name, $function) {
278 58
        $var = var_export(strtolower($name), true);
279 58
        $fn = function ($expr) use ($var) {
280 1
            return "\$this->call($var, $expr)";
281 58
        };
282
283 58
        if (is_string($function)) {
284 58
            $fn = function (...$args) use ($function) {
285 13
                return $function.'('.implode(', ', $args).')';
286 58
            };
287 57
        } elseif (is_array($function)) {
288 57
            if (is_string($function[0])) {
289
                $fn = function (...$args) use ($function) {
290
                    return "$function[0]::$function[1](".implode(', ', $args).')';
291
                };
292 57
            } elseif ($function[0] instanceof Ebi) {
293 57
                $fn = function (...$args) use ($function) {
294 6
                    return "\$this->$function[1](".implode(', ', $args).')';
295 57
                };
296
            }
297
        }
298
299 58
        return $fn;
300
    }
301
302 52
    public function compile($src, array $options = []) {
303 52
        $options += ['basename' => '', 'runtime' => true];
304
305 52
        $src = trim($src);
306
307 52
        $dom = new \DOMDocument();
308 52
        libxml_use_internal_errors(true);
309
310 52
        $fragment = false;
311 52
        if (strpos($src, '<html') === false) {
312 51
            $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...
313 51
            $fragment = true;
314
        }
315
316 52
        $dom->loadHTML($src, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD | LIBXML_NOCDATA | LIBXML_NOXMLDECL);
317
//        $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...
318
319 52
        $out = new CompilerBuffer();
320
321 52
        $out->setBasename($options['basename']);
322
323 52
        if ($options['runtime']) {
324 51
            $name = var_export($options['basename'], true);
325 51
            $out->appendCode("\$this->defineComponent($name, function (\$props = [], \$children = []) {\n");
326
        } else {
327 1
            $out->appendCode("function (\$props = [], \$children = []) {\n");
328
        }
329
330 52
        $out->pushScope(['this' => 'props']);
331 52
        $out->indent(+1);
332
333 52
        $parent = $fragment ? $dom->firstChild->nextSibling->firstChild : $dom;
334
335 52
        foreach ($parent->childNodes as $node) {
336 52
            $this->compileNode($node, $out);
337
        }
338
339 52
        $out->indent(-1);
340 52
        $out->popScope();
341
342 52
        if ($options['runtime']) {
343 51
            $out->appendCode("});");
344
        } else {
345 1
            $out->appendCode("};");
346
        }
347
348 52
        $r = $out->flush();
349
350 52
        $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...
351
352 52
        return $r;
353
    }
354
355 43
    protected function isComponent($tag) {
356 43
        return !isset(static::$htmlTags[$tag]);
357
    }
358
359 52
    protected function compileNode(DOMNode $node, CompilerBuffer $out) {
360 52
        if ($out->getNodeProp($node, 'skip')) {
361 4
            return;
362
        }
363
364 52
        switch ($node->nodeType) {
365 52
            case XML_TEXT_NODE:
366 46
                $this->compileTextNode($node, $out);
367 46
                break;
368 49
            case XML_ELEMENT_NODE:
369
                /* @var \DOMElement $node */
370 49
                $this->compileElementNode($node, $out);
371 49
                break;
372 3
            case XML_COMMENT_NODE:
373
                /* @var \DOMComment $node */
374 1
                $this->compileCommentNode($node, $out);
375 1
                break;
376 2
            case XML_DOCUMENT_TYPE_NODE:
377 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...
378 1
                break;
379 1
            case XML_CDATA_SECTION_NODE:
380 1
                $this->compileTextNode($node, $out);
381 1
                break;
382
            default:
383
                $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...
384
                    '// '.str_replace("\n", "\n// ", $node->ownerDocument->saveHTML($node));
385
        }
386 52
    }
387
388
    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...
389
        $result = [];
390
391
        if ($root->hasAttributes()) {
392
            $attrs = $root->attributes;
393
            foreach ($attrs as $attr) {
394
                $result['@attributes'][$attr->name] = $attr->value;
395
            }
396
        }
397
398
        if ($root->hasChildNodes()) {
399
            $children = $root->childNodes;
400
            if ($children->length == 1) {
401
                $child = $children->item(0);
402
                if ($child->nodeType == XML_TEXT_NODE) {
403
                    $result['_value'] = $child->nodeValue;
404
                    return count($result) == 1
405
                        ? $result['_value']
406
                        : $result;
407
                }
408
            }
409
            $groups = [];
410
            foreach ($children as $child) {
411
                if (!isset($result[$child->nodeName])) {
412
                    $result[$child->nodeName] = $this->domToArray($child);
413
                } else {
414
                    if (!isset($groups[$child->nodeName])) {
415
                        $result[$child->nodeName] = [$result[$child->nodeName]];
416
                        $groups[$child->nodeName] = 1;
417
                    }
418
                    $result[$child->nodeName][] = $this->domToArray($child);
419
                }
420
            }
421
        }
422
423
        return $result;
424
    }
425
426 1
    protected function newline(DOMNode $node, CompilerBuffer $out) {
427 1
        if ($node->previousSibling && $node->previousSibling->nodeType !== XML_COMMENT_NODE) {
428
            $out->appendCode("\n");
429
        }
430 1
    }
431
432 1
    protected function compileCommentNode(\DOMComment $node, CompilerBuffer $out) {
433 1
        $comments = explode("\n", trim($node->nodeValue));
434
435 1
        $this->newline($node, $out);
436 1
        foreach ($comments as $comment) {
437 1
            $out->appendCode("// $comment\n");
438
        }
439 1
    }
440
441 47
    protected function compileTextNode(DOMNode $node, CompilerBuffer $out) {
442 47
        $text = $this->ltrim($this->rtrim($node->nodeValue, $node, $out), $node, $out);
443
444 47
        $items = $this->splitExpressions($text);
445
446 47
        foreach ($items as $i => list($text, $offset)) {
447 47
            if (preg_match('`^{\S`', $text)) {
448 26
                if (preg_match('`^{\s*unescape\((.+)\)\s*}$`', $text, $m)) {
449 3
                    $out->echoCode($this->expr($m[1], $out));
450
                } else {
451 26
                    $out->echoCode('htmlspecialchars('.$this->expr(substr($text, 1, -1), $out).')');
452
                }
453
            } else {
454
//                if ($i === 0) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
52% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
455
//                    $text = $this->ltrim($text, $node, $out);
456
//                }
457
//                if ($i === count($items) - 1) {
458
//                    $text = $this->rtrim($text, $node, $out);
459
//                }
460
461 47
                $out->echoLiteral($text);
462
            }
463
        }
464 47
    }
465
466 49
    protected function compileElementNode(DOMElement $node, CompilerBuffer $out) {
467 49
        list($attributes, $special) = $this->splitAttributes($node);
468
469 49
        if ($node->tagName === 'script' && ((isset($attributes['type']) && $attributes['type']->value === self::T_EBI) || !empty($special[self::T_AS]) || !empty($special[self::T_UNESCAPE]))) {
470 5
            $this->compileExpressionNode($node, $attributes, $special, $out);
471 45
        } elseif (!empty($special) || $this->isComponent($node->tagName)) {
472 32
            $this->compileSpecialNode($node, $attributes, $special, $out);
473
        } else {
474 25
            $this->compileOpenTag($node, $attributes, $special, $out);
475
476 25
            foreach ($node->childNodes as $childNode) {
477 24
                $this->compileNode($childNode, $out);
478
            }
479
480 25
            $this->compileCloseTag($node, $special, $out);
481
        }
482 49
    }
483
484 47
    protected function splitExpressions($value) {
485 47
        $values = preg_split('`({\S[^}]*?})`', $value, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE);
486 47
        return $values;
487
    }
488
489 47
    protected function expr($expr, CompilerBuffer $output, DOMAttr $attr = null) {
490 47
        $names = $output->getScopeVariables();
491
492 47
        $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...
493 45
            if (isset($names[$name])) {
494 27
                return $names[$name];
495 31
            } elseif ($name[0] === '@') {
496 1
                return 'this->meta['.var_export(substr($name, 1), true).']';
497
            } else {
498 30
                return $names['this'].'['.var_export($name, true).']';
499
            }
500 47
        });
501
502 47
        if ($attr !== null && null !== $fn = $this->getAttributeFunction($attr)) {
503 4
            $compiled = call_user_func($fn, $compiled);
504
        }
505
506 47
        return $compiled;
507
    }
508
509
    /**
510
     * Get the compiler function to wrap an attribute.
511
     *
512
     * Attribute functions are regular expression functions, but with a special naming convention. The following naming
513
     * conventions are supported:
514
     *
515
     * - **@tag:attribute**: Applies to an attribute only on a specific tag.
516
     * - **@attribute**: Applies to all attributes with a given name.
517
     *
518
     * @param DOMAttr $attr The attribute to look at.
519
     * @return callable|null A function or **null** if the attribute doesn't have a function.
520
     */
521 20
    private function getAttributeFunction(DOMAttr $attr) {
522 20
        $keys = ['@'.$attr->ownerElement->tagName.':'.$attr->name, '@'.$attr->name];
523
524 20
        foreach ($keys as $key) {
525 20
            if (null !== $fn = $this->expressions->getFunctionCompiler($key)) {
526 20
                return $fn;
527
            }
528
        }
529 15
    }
530
531
    /**
532
     * @param DOMElement $node
533
     */
534 49
    protected function splitAttributes(DOMElement $node) {
535 49
        $attributes = [];
536 49
        $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...
537
538 49
        foreach ($node->attributes as $name => $attribute) {
539 47
            if (isset(static::$special[$name])) {
540 34
                $special[$name] = $attribute;
541
            } else {
542 47
                $attributes[$name] = $attribute;
543
            }
544
        }
545
546 49
        uksort($special, function ($a, $b) {
547 8
            return strnatcmp(static::$special[$a], static::$special[$b]);
548 49
        });
549
550 49
        return [$attributes, $special];
551
    }
552
553 32
    protected function compileSpecialNode(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
554 32
        $specialName = key($special);
555
556
        switch ($specialName) {
557 32
            case self::T_COMPONENT:
558 9
                $this->compileComponentRegister($node, $attributes, $special, $out);
559 9
                break;
560 32
            case self::T_IF:
561 8
                $this->compileIf($node, $attributes, $special, $out);
562 8
                break;
563 32
            case self::T_EACH:
564 12
                $this->compileEach($node, $attributes, $special, $out);
565 12
                break;
566 23
            case self::T_BLOCK:
567 1
                $this->compileBlock($node, $attributes, $special, $out);
568 1
                break;
569 23
            case self::T_CHILDREN:
570 3
                $this->compileChildBlock($node, $attributes, $special, $out);
571 3
                break;
572 23
            case self::T_INCLUDE:
573 1
                $this->compileComponentInclude($node, $attributes, $special, $out);
574 1
                break;
575 23
            case self::T_WITH:
576 4
                if ($this->isComponent($node->tagName)) {
577
                    // With has a special meaning in components.
578 3
                    $this->compileComponentInclude($node, $attributes, $special, $out);
579
                } else {
580 1
                    $this->compileWith($node, $attributes, $special, $out);
581
                }
582 4
                break;
583 22
            case self::T_LITERAL:
584 2
                $this->compileLiteral($node, $attributes, $special, $out);
585 2
                break;
586 20
            case '':
587 20
                if ($this->isComponent($node->tagName)) {
588 7
                    $this->compileComponentInclude($node, $attributes, $special, $out);
589
                } else {
590 19
                    $this->compileElement($node, $attributes, $special, $out);
591
                }
592 20
                break;
593
        }
594 32
    }
595
596
    /**
597
     * Compile component registering.
598
     *
599
     * @param DOMElement $node
600
     * @param $attributes
601
     * @param $special
602
     * @param CompilerBuffer $out
603
     */
604 9
    public function compileComponentRegister(DOMElement $node, $attributes, $special, CompilerBuffer $out) {
605 9
        $name = strtolower($special[self::T_COMPONENT]->value);
606 9
        unset($special[self::T_COMPONENT]);
607
608 9
        $prev = $out->select($name);
609
610 9
        $varName = var_export($name, true);
611 9
        $out->appendCode("\$this->defineComponent($varName, function (\$props = [], \$children = []) {\n");
612 9
        $out->pushScope(['this' => 'props']);
613 9
        $out->indent(+1);
614
615
        try {
616 9
            $this->compileSpecialNode($node, $attributes, $special, $out);
617 9
        } finally {
618 9
            $out->popScope();
619 9
            $out->indent(-1);
620 9
            $out->appendCode("});");
621 9
            $out->select($prev);
622
        }
623 9
    }
624
625 1
    private function compileBlock(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
626 1
        $name = strtolower($special[self::T_BLOCK]->value);
627 1
        unset($special[self::T_BLOCK]);
628
629 1
        $prev = $out->select($name);
630
631 1
        $use = '$'.implode(', $', $out->getScopeVariables()).', $children';
632
633 1
        $out->appendCode("function () use ($use) {\n");
634 1
        $out->pushScope(['this' => 'props']);
635 1
        $out->indent(+1);
636
637
        try {
638 1
            $this->compileSpecialNode($node, $attributes, $special, $out);
639 1
        } finally {
640 1
            $out->indent(-1);
641 1
            $out->popScope();
642 1
            $out->appendCode("}");
643 1
            $out->select($prev);
644
        }
645
646 1
        return $out;
647
    }
648
649
    /**
650
     * Compile component inclusion and rendering.
651
     *
652
     * @param DOMElement $node
653
     * @param DOMAttr[] $attributes
654
     * @param DOMAttr[] $special
655
     * @param CompilerBuffer $out
656
     */
657 11
    protected function compileComponentInclude(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
658
        // Generate the attributes into a property array.
659 11
        $props = [];
660 11
        foreach ($attributes as $name => $attribute) {
661
            /* @var DOMAttr $attr */
662 5
            if ($this->isExpression($attribute->value)) {
663 4
                $expr = $this->expr(substr($attribute->value, 1, -1), $out, $attribute);
664
            } else {
665 1
                $expr = var_export($attribute->value, true);
666
            }
667
668 5
            $props[] = var_export($name, true).' => '.$expr;
669
        }
670 11
        $propsStr = '['.implode(', ', $props).']';
671
672 11
        if (isset($special[self::T_WITH])) {
673 3
            $withExpr = $this->expr($special[self::T_WITH]->value, $out, $special[self::T_WITH]);
674 3
            unset($special[self::T_WITH]);
675
676 3
            $propsStr = empty($props) ? $withExpr : $propsStr.' + (array)'.$withExpr;
677
        } elseif (empty($props)) {
678
            // By default the current context is passed to components.
679 4
            $propsStr = $this->expr('this', $out);
680
        }
681
682
        // Compile the children blocks.
683 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...
684 11
        $blocksStr = $blocks->flush();
685
686 11
        if (isset($special[self::T_INCLUDE])) {
687 1
            $name = $this->expr($special[self::T_INCLUDE]->value, $out, $special[self::T_INCLUDE]);
688
        } else {
689 10
            $name = var_export($node->tagName, true);
690
        }
691
692 11
        $out->appendCode("\$this->write($name, $propsStr, $blocksStr);\n");
693 11
    }
694
695
    /**
696
     * @param DOMElement $parent
697
     * @return CompilerBuffer
698
     */
699 11
    protected function compileComponentBlocks(DOMElement $parent, CompilerBuffer $out) {
700 11
        $blocksOut = new CompilerBuffer(CompilerBuffer::STYLE_ARRAY, [
701 11
            'baseIndent' => $out->getIndent(),
702 11
            'indent' => $out->getIndent() + 1,
703 11
            'depth' => $out->getDepth(),
704 11
            'scopes' => $out->getAllScopes()
705
        ]);
706
707 11
        if ($this->isEmptyNode($parent)) {
708 9
            return $blocksOut;
709
        }
710
711 3
        $use = '$'.implode(', $', $blocksOut->getScopeVariables()).', $children';
712
713 3
        $blocksOut->appendCode("function () use ($use) {\n");
714 3
        $blocksOut->indent(+1);
715
716
        try {
717 3
            foreach ($parent->childNodes as $node) {
718 3
                $this->compileNode($node, $blocksOut);
719
            }
720 3
        } finally {
721 3
            $blocksOut->indent(-1);
722 3
            $blocksOut->appendCode("}");
723
        }
724
725 3
        return $blocksOut;
726
    }
727
728 23
    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...
729
        // Don't double up comments.
730 23
        if ($node->previousSibling && $node->previousSibling->nodeType === XML_COMMENT_NODE) {
731
            return;
732
        }
733
734 23
        $str = '<'.$node->tagName;
735 23
        foreach ($special as $attr) {
736
            /* @var DOMAttr $attr */
737 23
            $str .= ' '.$attr->name.(empty($attr->value) ? '' : '="'.htmlspecialchars($attr->value).'"');
738
        }
739 23
        $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...
740 23
        $comments = explode("\n", $str);
741 23
        foreach ($comments as $comment) {
742 23
            $out->appendCode("// $comment\n");
743
        }
744 23
    }
745
746
    /**
747
     * @param DOMElement $node
748
     * @param DOMAttr[] $attributes
749
     * @param DOMAttr[] $special
750
     * @param CompilerBuffer $out
751
     * @param bool $force
752
     */
753 44
    protected function compileOpenTag(DOMElement $node, $attributes, $special, CompilerBuffer $out, $force = false) {
754 44
        $tagNameExpr = !empty($special[self::T_TAG]) ? $special[self::T_TAG]->value : '';
755
756 44
        if ($node->tagName === self::T_X && empty($tagNameExpr)) {
757 4
            return;
758
        }
759
760 43
        if (!empty($tagNameExpr)) {
761
            $tagNameExpr = $this->expr($tagNameExpr, $out, $special[self::T_TAG]);
762
            $out->echoLiteral('<');
763
            $out->echoCode($tagNameExpr);
764
        } else {
765 43
            $out->echoLiteral('<'.$node->tagName);
766
        }
767
768
        /* @var DOMAttr $attribute */
769 43
        foreach ($attributes as $name => $attribute) {
770
            // Check for an attribute expression.
771 15
            if ($this->isExpression($attribute->value)) {
772 12
                $out->echoCode(
773 12
                    '$this->attribute('.var_export($name, true).', '.
774 12
                    $this->expr(substr($attribute->value, 1, -1), $out, $attribute).
775 12
                    ')');
776 3
            } elseif (null !== $fn = $this->getAttributeFunction($attribute)) {
777 2
                $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...
778
779 2
                $out->echoCode('$this->attribute('.var_export($name, true).', '.$value.')');
780
            } else {
781 2
                $out->echoLiteral(' '.$name.'="');
782 2
                $out->echoLiteral(htmlspecialchars($attribute->value));
783 15
                $out->echoLiteral('"');
784
            }
785
        }
786
787 43
        if ($node->hasChildNodes() || $force) {
788 42
            $out->echoLiteral('>');
789
        } else {
790 2
            $out->echoLiteral(" />");
791
        }
792 43
    }
793
794 18
    private function isExpression($value) {
795 18
        return preg_match('`^{\S.*}$`', $value);
796
    }
797
798 44
    protected function compileCloseTag(DOMElement $node, $special, CompilerBuffer $out, $force = false) {
799 44
        if (!$force && !$node->hasChildNodes()) {
800 2
            return;
801
        }
802
803 43
        $tagNameExpr = !empty($special[self::T_TAG]) ? $special[self::T_TAG]->value : '';
804 43
        if (!empty($tagNameExpr)) {
805
            $tagNameExpr = $this->expr($tagNameExpr, $out, $special[self::T_TAG]);
806
            $out->echoLiteral('</');
807
            $out->echoCode($tagNameExpr);
808
            $out->echoLiteral('>');
809 43
        } elseif ($node->tagName !== self::T_X) {
810 42
            $out->echoLiteral("</{$node->tagName}>");
811
        }
812 43
    }
813
814 4
    protected function isEmptyText(DOMNode $node) {
815 4
        return $node instanceof \DOMText && empty(trim($node->data));
816
    }
817
818 11
    protected function isEmptyNode(DOMNode $node) {
819 11
        if (!$node->hasChildNodes()) {
820 9
            return true;
821
        }
822
823 3
        foreach ($node->childNodes as $childNode) {
824 3
            if ($childNode instanceof DOMElement) {
825 1
                return false;
826
            }
827 2
            if ($childNode instanceof \DOMText && !$this->isEmptyText($childNode)) {
828 2
                return false;
829
            }
830
        }
831
832
        return true;
833
    }
834
835 8
    protected function compileIf(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
836 8
        $this->compileTagComment($node, $attributes, $special, $out);
837 8
        $expr = $this->expr($special[self::T_IF]->value, $out);
838 8
        unset($special[self::T_IF]);
839
840 8
        $elseNode = $this->findSpecialNode($node, self::T_ELSE, self::T_IF);
841 8
        $out->setNodeProp($elseNode, 'skip', true);
842
843 8
        $out->appendCode('if ('.$expr.") {\n");
844 8
        $out->indent(+1);
845
846 8
        $this->compileSpecialNode($node, $attributes, $special, $out);
847
848 8
        $out->indent(-1);
849
850 8
        if ($elseNode) {
851 2
            list($attributes, $special) = $this->splitAttributes($elseNode);
852 2
            unset($special[self::T_ELSE]);
853
854 2
            $out->appendCode("} else {\n");
855
856 2
            $out->indent(+1);
857 2
            $this->compileSpecialNode($elseNode, $attributes, $special, $out);
858 2
            $out->indent(-1);
859
        }
860
861 8
        $out->appendCode("}\n");
862 8
    }
863
864 12
    protected function compileEach(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
865 12
        $this->compileTagComment($node, $attributes, $special, $out);
866 12
        $this->compileOpenTag($node, $attributes, $special, $out);
867
868 12
        $emptyNode = $this->findSpecialNode($node, self::T_EMPTY, self::T_ELSE);
869 12
        $out->setNodeProp($emptyNode, 'skip', true);
870
871 12
        if ($emptyNode === null) {
872 10
            $this->compileEachLoop($node, $attributes, $special, $out);
873
        } else {
874 2
            $expr = $this->expr("empty({$special[self::T_EACH]->value})", $out);
875
876 2
            list ($emptyAttributes, $emptySpecial) = $this->splitAttributes($emptyNode);
877 2
            unset($emptySpecial[self::T_EMPTY]);
878
879 2
            $out->appendCode('if ('.$expr.") {\n");
880
881 2
            $out->indent(+1);
882 2
            $this->compileSpecialNode($emptyNode, $emptyAttributes, $emptySpecial, $out);
883 2
            $out->indent(-1);
884
885 2
            $out->appendCode("} else {\n");
886
887 2
            $out->indent(+1);
888 2
            $this->compileEachLoop($node, $attributes, $special, $out);
889 2
            $out->indent(-1);
890
891 2
            $out->appendCode("}\n");
892
        }
893
894 12
        $this->compileCloseTag($node, $special, $out);
895 12
    }
896
897 1
    protected function compileWith(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
898 1
        $this->compileTagComment($node, $attributes, $special, $out);
899 1
        $with = $this->expr($special[self::T_WITH]->value, $out);
900
901 1
        $out->depth(+1);
902 1
        $scope = ['this' => $out->depthName('props')];
903 1
        if (!empty($special[self::T_AS]) && preg_match(self::IDENT_REGEX, $special[self::T_AS]->value, $m)) {
904
            // The template specified an x-as attribute to alias the with expression.
905 1
            $scope = [$m[1] => $out->depthName('props')];
906
        }
907 1
        unset($special[self::T_WITH], $special[self::T_AS]);
908
909 1
        $out->pushScope($scope);
910 1
        $out->appendCode('$'.$out->depthName('props')." = $with;\n");
911
912 1
        $this->compileSpecialNode($node, $attributes, $special, $out);
913
914 1
        $out->depth(-1);
915 1
        $out->popScope();
916 1
    }
917
918 2
    protected function compileLiteral(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
919 2
        $this->compileTagComment($node, $attributes, $special, $out);
920 2
        unset($special[self::T_LITERAL]);
921
922 2
        $this->compileOpenTag($node, $attributes, $special, $out);
923
924 2
        foreach ($node->childNodes as $childNode) {
925 2
            $html = $childNode->ownerDocument->saveHTML($childNode);
926 2
            $out->echoLiteral($html);
927
        }
928
929 2
        $this->compileCloseTag($node, $special, $out);
930 2
    }
931
932 19
    protected function compileElement(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
933 19
        $this->compileOpenTag($node, $attributes, $special, $out);
934
935 19
        foreach ($node->childNodes as $childNode) {
936 19
            $this->compileNode($childNode, $out);
937
        }
938
939 19
        $this->compileCloseTag($node, $special, $out);
940 19
    }
941
942
    /**
943
     * Find a special node in relation to another node.
944
     *
945
     * This method is used to find things such as x-empty and x-else elements.
946
     *
947
     * @param DOMElement $node The node to search in relation to.
948
     * @param string $attribute The name of the attribute to search for.
949
     * @param string $parentAttribute The name of the parent attribute to resolve conflicts.
950
     * @return DOMElement|null Returns the found element node or **null** if not found.
951
     */
952 20
    protected function findSpecialNode(DOMElement $node, $attribute, $parentAttribute) {
953
        // First look for a sibling after the node.
954 20
        for ($sibNode = $node->nextSibling; $sibNode !== null; $sibNode = $sibNode->nextSibling) {
955 2
            if ($sibNode instanceof DOMElement && $sibNode->hasAttribute($attribute)) {
956 2
                return $sibNode;
957
            }
958
959
            // Stop searching if we encounter another node.
960 2
            if (!$this->isEmptyText($sibNode)) {
961
                break;
962
            }
963
        }
964
965
        // Next look inside the node.
966 18
        $parentFound = false;
967 18
        foreach ($node->childNodes as $childNode) {
968 17
            if (!$parentFound && $childNode instanceof DOMElement && $childNode->hasAttribute($attribute)) {
969 2
                return $childNode;
970
            }
971
972 17
            if ($childNode instanceof DOMElement) {
973 12
                $parentFound = $childNode->hasAttribute($parentAttribute);
974 7
            } elseif ($childNode instanceof \DOMText && !empty(trim($childNode->data))) {
975 17
                $parentFound = false;
976
            }
977
        }
978
979 16
        return null;
980
    }
981
982
    /**
983
     * @param DOMElement $node
984
     * @param array $attributes
985
     * @param array $special
986
     * @param CompilerBuffer $out
987
     */
988 12
    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...
989 12
        $each = $this->expr($special[self::T_EACH]->value, $out);
990 12
        unset($special[self::T_EACH]);
991
992 12
        $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...
993 12
        $scope = ['this' => $as[1]];
994 12
        if (!empty($special[self::T_AS])) {
995 6
            if (preg_match('`(?:([a-z0-9]+)\s+)?([a-z0-9]+)`i', $special[self::T_AS]->value, $m)) {
996 6
                $scope = [$m[2] => $as[1]];
997 6
                if (!empty($m[1])) {
998 3
                    $scope[$m[1]] = $as[0] = $out->depthName('i', 1);
999
                }
1000
            }
1001
        }
1002 12
        unset($special[self::T_AS]);
1003 12
        if (empty($as[0])) {
1004 9
            $out->appendCode("foreach ($each as \${$as[1]}) {\n");
1005
        } else {
1006 3
            $out->appendCode("foreach ($each as \${$as[0]} => \${$as[1]}) {\n");
1007
        }
1008 12
        $out->depth(+1);
1009 12
        $out->indent(+1);
1010 12
        $out->pushScope($scope);
1011
1012 12
        foreach ($node->childNodes as $childNode) {
1013 12
            $this->compileNode($childNode, $out);
1014
        }
1015
1016 12
        $out->indent(-1);
1017 12
        $out->depth(-1);
1018 12
        $out->popScope();
1019 12
        $out->appendCode("}\n");
1020 12
    }
1021
1022 47
    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...
1023 47
        if ($this->inPre($node)) {
1024
            return $text;
1025
        }
1026
1027 47
        $sib = $node->previousSibling ?: $node->parentNode;
1028 47
        if ($sib === null || !$sib instanceof \DOMElement || $out->getNodeProp($sib, 'skip') || $sib->tagName === self::T_X) {
1029 6
            return ltrim($text);
1030
        }
1031
1032 46
        $text = preg_replace('`^\s*\n\s*`', "\n", $text, -1, $count);
1033 46
        if ($count === 0) {
1034 46
            $text = preg_replace('`^\s+`', ' ', $text);
1035
        }
1036
1037
//        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...
1038
//            return ltrim($text);
1039
//        }
1040 46
        return $text;
1041
    }
1042
1043 47
    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...
1044 47
        if ($this->inPre($node)) {
1045
            return $text;
1046
        }
1047
1048 47
        $sib = $node->nextSibling ?: $node->parentNode;
1049
1050 47
        if ($sib === null || !$sib instanceof \DOMElement || $out->getNodeProp($sib, 'skip') || $sib->tagName === self::T_X) {
1051 5
            return rtrim($text);
1052
        }
1053
1054 46
        $text = preg_replace('`\s*\n\s*$`', "\n", $text, -1, $count);
1055 46
        if ($count === 0) {
1056 46
            $text = preg_replace('`\s+$`', ' ', $text);
1057
        }
1058
1059
//        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...
1060
//            return rtrim($text);
1061
//        }
1062 46
        return $text;
1063
    }
1064
1065 47
    protected function inPre(\DOMNode $node) {
1066 47
        for ($node = $node->parentNode; $node !== null; $node = $node->parentNode) {
1067 47
            if (in_array($node->nodeType, ['code', 'pre'], true)) {
1068
                return true;
1069
            }
1070
        }
1071 47
        return false;
1072
    }
1073
1074 3
    private function compileChildBlock(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
1075
        /* @var DOMAttr $child */
1076 3
        $child = $special[self::T_CHILDREN];
1077 3
        unset($special[self::T_CHILDREN]);
1078
1079 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...
1080 3
        $keyStr = var_export($key, true);
1081
1082 3
        $this->compileOpenTag($node, $attributes, $special, $out, true);
1083
1084 3
        $out->appendCode("if (isset(\$children[{$keyStr}])) {\n");
1085 3
        $out->indent(+1);
1086 3
        $out->appendCode("\$children[{$keyStr}]();\n");
1087 3
        $out->indent(-1);
1088 3
        $out->appendCode("}\n");
1089
1090 3
        $this->compileCloseTag($node, $special, $out, true);
1091 3
    }
1092
1093
    /**
1094
     * Compile an x-expr node.
1095
     *
1096
     * @param DOMElement $node The node to compile.
1097
     * @param DOMAttr[] $attributes The node's attributes.
1098
     * @param DOMAttr[] $special An array of special attributes.
1099
     * @param CompilerBuffer $out The compiler output.
1100
     */
1101 5
    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...
1102 5
        $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...
1103 5
        $expr = $this->expr($str, $out);
1104
1105 5
        if (!empty($special[self::T_AS]) && preg_match(self::IDENT_REGEX, $special[self::T_AS]->value, $m)) {
1106
            // The template specified an x-as attribute to alias the with expression.
1107 3
            $scope = [$m[1] => $out->depthName('props', 1)];
1108 3
            $out->pushScope($scope);
1109 3
            $out->appendCode('$'.$out->depthName('props', 1)." = $expr;\n");
1110 2
        } elseif (!empty($special[self::T_UNESCAPE])) {
1111 1
            $out->echoCode($expr);
1112
        } else {
1113 1
            $out->echoCode('htmlspecialchars('.$expr.')');
1114
        }
1115 5
    }
1116
}
1117