Completed
Pull Request — master (#16)
by Todd
02:39
created

Compiler::compileCloseTag()   B

Complexity

Conditions 6
Paths 7

Size

Total Lines 15
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 7.3329

Importance

Changes 0
Metric Value
dl 0
loc 15
ccs 8
cts 12
cp 0.6667
rs 8.8571
c 0
b 0
f 0
cc 6
eloc 11
nc 7
nop 4
crap 7.3329
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_EXPR = 'x-expr';
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...
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 54
    public function __construct() {
233 54
        $this->expressions = new ExpressionLanguage();
234 54
        $this->expressions->setNamePattern('/[@a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A');
235 54
        $this->expressions->register(
236 54
            'hasChildren',
237 54
            function ($name = null) {
238 1
                return empty($name) ? 'isset($children[0])' : "isset(\$children[$name ?: 0])";
239 54
            },
240 54
            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 54
            });
243 54
    }
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 52
    public function defineFunction($name, $function = null) {
252 52
        if ($function === null) {
253 1
            $function = $name;
254
        }
255
256 52
        $this->expressions->register(
257 52
            $name,
258 52
            $this->getFunctionCompiler($name, $function),
259 52
            $this->getFunctionEvaluator($function)
260
        );
261 52
    }
262
263 52
    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 52
        if ($function === 'empty') {
265 52
            return function ($expr) {
266
                return empty($expr);
267 52
            };
268 51
        } elseif ($function === 'isset') {
269
            return function ($expr) {
270
                return isset($expr);
271
            };
272
        }
273
274 51
        return $function;
275
    }
276
277 52
    private function getFunctionCompiler($name, $function) {
278 52
        $var = var_export(strtolower($name), true);
279 52
        $fn = function ($expr) use ($var) {
280 1
            return "\$this->call($var, $expr)";
281 52
        };
282
283 52
        if (is_string($function)) {
284 52
            $fn = function (...$args) use ($function) {
285 13
                return $function.'('.implode(', ', $args).')';
286 52
            };
287 51
        } elseif (is_array($function)) {
288 51
            if (is_string($function[0])) {
289
                $fn = function (...$args) use ($function) {
290
                    return "$function[0]::$function[1](".implode(', ', $args).')';
291
                };
292 51
            } elseif ($function[0] instanceof Ebi) {
293 51
                $fn = function (...$args) use ($function) {
294 6
                    return "\$this->$function[1](".implode(', ', $args).')';
295 51
                };
296
            }
297
        }
298
299 52
        return $fn;
300
    }
301
302 46
    public function compile($src, array $options = []) {
303 46
        $options += ['basename' => '', 'runtime' => true];
304
305 46
        $src = trim($src);
306
307 46
        $dom = new \DOMDocument();
308 46
        libxml_use_internal_errors(true);
309
310 46
        $fragment = false;
311 46
        if (strpos($src, '<html') === false) {
312 45
            $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 45
            $fragment = true;
314
        }
315
316 46
        $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 46
        $out = new CompilerBuffer();
320
321 46
        $out->setBasename($options['basename']);
322
323 46
        if ($options['runtime']) {
324 45
            $name = var_export($options['basename'], true);
325 45
            $out->appendCode("\$this->defineComponent($name, function (\$props = [], \$children = []) {\n");
326
        } else {
327 1
            $out->appendCode("function (\$props = [], \$children = []) {\n");
328
        }
329
330 46
        $out->pushScope(['this' => 'props']);
331 46
        $out->indent(+1);
332
333 46
        $parent = $fragment ? $dom->firstChild->nextSibling->firstChild : $dom;
334
335 46
        foreach ($parent->childNodes as $node) {
336 46
            $this->compileNode($node, $out);
337
        }
338
339 46
        $out->indent(-1);
340 46
        $out->popScope();
341
342 46
        if ($options['runtime']) {
343 45
            $out->appendCode("});");
344
        } else {
345 1
            $out->appendCode("};");
346
        }
347
348 46
        $r = $out->flush();
349
350 46
        $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 46
        return $r;
353
    }
354
355 37
    protected function isComponent($tag) {
356 37
        return !isset(static::$htmlTags[$tag]);
357
    }
358
359 46
    protected function compileNode(DOMNode $node, CompilerBuffer $out) {
360 46
        if ($out->getNodeProp($node, 'skip')) {
361 4
            return;
362
        }
363
364 46
        switch ($node->nodeType) {
365 46
            case XML_TEXT_NODE:
366 40
                $this->compileTextNode($node, $out);
367 40
                break;
368 43
            case XML_ELEMENT_NODE:
369
                /* @var \DOMElement $node */
370 43
                $this->compileElementNode($node, $out);
371 43
                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 46
    }
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 41
    protected function compileTextNode(DOMNode $node, CompilerBuffer $out) {
442 41
        $text = $this->ltrim($this->rtrim($node->nodeValue, $node, $out), $node, $out);
443
444 41
        $items = $this->splitExpressions($text);
445
446 41
        foreach ($items as $i => list($text, $offset)) {
447 41
            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 41
                $out->echoLiteral($text);
462
            }
463
        }
464 41
    }
465
466 43
    protected function compileElementNode(DOMElement $node, CompilerBuffer $out) {
467 43
        list($attributes, $special) = $this->splitAttributes($node);
468
469 43
        if ($node->tagName === self::T_EXPR) {
470 5
            $this->compileExpressionNode($node, $attributes, $special, $out);
471 39
        } elseif (!empty($special) || $this->isComponent($node->tagName)) {
472 32
            $this->compileSpecialNode($node, $attributes, $special, $out);
473
        } else {
474 19
            $this->compileOpenTag($node, $node->attributes, $special, $out);
0 ignored issues
show
Documentation introduced by
$node->attributes is of type object<DOMNamedNodeMap>, but the function expects a array<integer,object<DOMAttr>>.

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...
475
476 19
            foreach ($node->childNodes as $childNode) {
477 18
                $this->compileNode($childNode, $out);
478
            }
479
480 19
            $this->compileCloseTag($node, $special, $out);
481
        }
482 43
    }
483
484 41
    protected function splitExpressions($value) {
485 41
        $values = preg_split('`({\S[^}]*?})`', $value, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE);
486 41
        return $values;
487
    }
488
489 41
    protected function expr($expr, CompilerBuffer $output, DOMAttr $attr = null) {
490 41
        $names = $output->getScopeVariables();
491
492 41
        $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 39
            if (isset($names[$name])) {
494 21
                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 41
        });
501
502 41
        if ($attr !== null && null !== $fn = $this->getAttributeFunction($attr)) {
503 4
            $compiled = call_user_func($fn, $compiled);
504
        }
505
506 41
        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 14
    private function getAttributeFunction(DOMAttr $attr) {
522 14
        $keys = ['@'.$attr->ownerElement->tagName.':'.$attr->name, '@'.$attr->name];
523
524 14
        foreach ($keys as $key) {
525 14
            if (null !== $fn = $this->expressions->getFunctionCompiler($key)) {
526 14
                return $fn;
527
            }
528
        }
529 9
    }
530
531
    /**
532
     * @param DOMElement $node
533
     */
534 43
    protected function splitAttributes(DOMElement $node) {
535 43
        $attributes = [];
536 43
        $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 43
        foreach ($node->attributes as $name => $attribute) {
539 40
            if (isset(static::$special[$name])) {
540 34
                $special[$name] = $attribute;
541
            } else {
542 40
                $attributes[$name] = $attribute;
543
            }
544
        }
545
546 43
        uksort($special, function ($a, $b) {
547 8
            return strnatcmp(static::$special[$a], static::$special[$b]);
548 43
        });
549
550 43
        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 38
    protected function compileOpenTag(DOMElement $node, $attributes, $special, CompilerBuffer $out, $force = false) {
754 38
        $tagNameExpr = !empty($special[self::T_TAG]) ? $special[self::T_TAG]->value : '';
755
756 38
        if ($node->tagName === self::T_X && empty($tagNameExpr)) {
757 4
            return;
758
        }
759
760 37
        if (!empty($tagNameExpr)) {
761
            $tagNameExpr = $this->expr($tagNameExpr, $out, $special[self::T_TAG]);
762
            $out->echoLiteral('<');
763
            $out->echoCode($tagNameExpr);
764
        } else {
765 37
            $out->echoLiteral('<'.$node->tagName);
766
        }
767
768 37
        foreach ($attributes as $name => $attribute) {
769
            /* @var DOMAttr $attribute */
770 9
            $out->echoLiteral(' '.$name.'="');
771
772
            // Check for an attribute expression.
773 9
            if ($this->isExpression($attribute->value)) {
774 6
                $out->echoCode('htmlspecialchars('.$this->expr(substr($attribute->value, 1, -1), $out, $attribute).')');
775 3
            } elseif (null !== $fn = $this->getAttributeFunction($attribute)) {
776 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...
777
778 2
                $out->echoCode("htmlspecialchars($value)");
779
780
            } else {
781 2
                $out->echoLiteral(htmlspecialchars($attribute->value));
782
            }
783
784 9
            $out->echoLiteral('"');
785
        }
786
787 37
        if ($node->hasChildNodes() || $force) {
788 36
            $out->echoLiteral('>');
789
        } else {
790 2
            $out->echoLiteral(" />");
791
        }
792 37
    }
793
794 12
    private function isExpression($value) {
795 12
        return preg_match('`^{\S.*}$`', $value);
796
    }
797
798 38
    protected function compileCloseTag(DOMElement $node, $special, CompilerBuffer $out, $force = false) {
799 38
        if (!$force && !$node->hasChildNodes()) {
800 2
            return;
801
        }
802
803 37
        $tagNameExpr = !empty($special[self::T_TAG]) ? $special[self::T_TAG]->value : '';
804 37
        if (!empty($tagNameExpr)) {
805
            $tagNameExpr = $this->expr($tagNameExpr, $out, $special[self::T_TAG]);
806
            $out->echoLiteral('</');
807
            $out->echoCode($tagNameExpr);
808
            $out->echoLiteral('>');
809 37
        } elseif ($node->tagName !== self::T_X) {
810 36
            $out->echoLiteral("</{$node->tagName}>");
811
        }
812 37
    }
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 41
    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 41
        if ($this->inPre($node)) {
1024
            return $text;
1025
        }
1026
1027 41
        $sib = $node->previousSibling ?: $node->parentNode;
1028 41
        if ($sib === null || !$sib instanceof \DOMElement || $out->getNodeProp($sib, 'skip') || $sib->tagName === self::T_X) {
1029 6
            return ltrim($text);
1030
        }
1031
1032 40
        $text = preg_replace('`^\s*\n\s*`', "\n", $text, -1, $count);
1033 40
        if ($count === 0) {
1034 40
            $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 40
        return $text;
1041
    }
1042
1043 41
    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 41
        if ($this->inPre($node)) {
1045
            return $text;
1046
        }
1047
1048 41
        $sib = $node->nextSibling ?: $node->parentNode;
1049
1050 41
        if ($sib === null || !$sib instanceof \DOMElement || $out->getNodeProp($sib, 'skip') || $sib->tagName === self::T_X) {
1051 5
            return rtrim($text);
1052
        }
1053
1054 40
        $text = preg_replace('`\s*\n\s*$`', "\n", $text, -1, $count);
1055 40
        if ($count === 0) {
1056 40
            $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 40
        return $text;
1063
    }
1064
1065 41
    protected function inPre(\DOMNode $node) {
1066 41
        for ($node = $node->parentNode; $node !== null; $node = $node->parentNode) {
1067 41
            if (in_array($node->nodeType, ['code', 'pre'], true)) {
1068
                return true;
1069
            }
1070
        }
1071 41
        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 array $attributes The node's attributes.
1098
     * @param array $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
        $s = '';
1103 5
        foreach ($node->childNodes as $childNode) {
1104 5
            $s .= 'foo';
1105
        }
1106
1107 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...
1108 5
        $expr = $this->expr($str, $out);
1109
1110 5
        if (!empty($special[self::T_AS]) && preg_match(self::IDENT_REGEX, $special[self::T_AS]->value, $m)) {
1111
            // The template specified an x-as attribute to alias the with expression.
1112 3
            $scope = [$m[1] => $out->depthName('props', 1)];
1113 3
            $out->pushScope($scope);
1114 3
            $out->appendCode('$'.$out->depthName('props', 1)." = $expr;\n");
1115 2
        } elseif (!empty($special[self::T_UNESCAPE])) {
1116 1
            $out->echoCode($expr);
1117
        } else {
1118 1
            $out->echoCode('htmlspecialchars('.$expr.')');
1119
        }
1120 5
    }
1121
}
1122