Completed
Pull Request — master (#24)
by Todd
12:34 queued 06:00
created

Compiler::compileWith()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 28
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 28
ccs 21
cts 21
cp 1
rs 8.8571
c 0
b 0
f 0
cc 3
eloc 18
nc 3
nop 4
crap 3
1
<?php
2
/**
3
 * @author Todd Burry <[email protected]>
4
 * @copyright 2009-2017 Vanilla Forums Inc.
5
 * @license MIT
6
 */
7
8
namespace Ebi;
9
10
use DOMAttr;
11
use DOMElement;
12
use DOMNode;
13
use Symfony\Component\ExpressionLanguage\SyntaxError;
14
15
class Compiler {
16
    const T_IF = 'x-if';
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 8 spaces but found 1 space

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

To visualize

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

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

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

will produce no issues.

Loading history...
17
    const T_EACH = 'x-each';
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 6 spaces but found 1 space

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

To visualize

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

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

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

will produce no issues.

Loading history...
18
    const T_WITH = 'x-with';
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 6 spaces but found 1 space

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

To visualize

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

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

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

will produce no issues.

Loading history...
19
    const T_LITERAL = 'x-literal';
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

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

To visualize

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

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

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

will produce no issues.

Loading history...
20
    const T_AS = 'x-as';
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 8 spaces but found 1 space

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

To visualize

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

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

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

will produce no issues.

Loading history...
21
    const T_COMPONENT = 'x-component';
22
    const T_CHILDREN = 'x-children';
23
    const T_BLOCK = 'x-block';
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 5 spaces but found 1 space

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

To visualize

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

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

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

will produce no issues.

Loading history...
24
    const T_ELSE = 'x-else';
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 6 spaces but found 1 space

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

To visualize

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

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

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

will produce no issues.

Loading history...
25
    const T_EMPTY = 'x-empty';
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 5 spaces but found 1 space

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

To visualize

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

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

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

will produce no issues.

Loading history...
26
    const T_X = 'x';
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 9 spaces but found 1 space

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

To visualize

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

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

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

will produce no issues.

Loading history...
27
    const T_INCLUDE = 'x-include';
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

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

To visualize

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

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

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

will produce no issues.

Loading history...
28
    const T_EBI = 'ebi';
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 7 spaces but found 1 space

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

To visualize

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

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

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

will produce no issues.

Loading history...
29
    const T_UNESCAPE = 'x-unescape';
30
    const T_TAG = 'x-tag';
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 7 spaces but found 1 space

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

To visualize

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

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

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

will produce no issues.

Loading history...
31
32
    const IDENT_REGEX = '`^([a-z0-9-]+)$`i';
33
34
    protected static $special = [
35
        self::T_COMPONENT => 1,
36
        self::T_IF => 2,
37
        self::T_ELSE => 3,
38
        self::T_EACH => 4,
39
        self::T_EMPTY => 5,
40
        self::T_CHILDREN => 6,
41
        self::T_INCLUDE => 7,
42
        self::T_WITH => 8,
43
        self::T_BLOCK => 9,
44
        self::T_LITERAL => 10,
45
        self::T_AS => 11,
46
        self::T_UNESCAPE => 12,
47
        self::T_TAG => 13
48
    ];
49
50
    protected static $htmlTags = [
51
        'a' => 'i',
52
        'abbr' => 'i',
53
        'acronym' => 'i', // deprecated
54
        'address' => 'b',
55
//        'applet' => 'i', // deprecated
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
56
        'area' => 'i',
57
        'article' => 'b',
58
        'aside' => 'b',
59
        'audio' => 'i',
60
        'b' => 'i',
61
        'base' => 'i',
62
//        'basefont' => 'i',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
63
        'bdi' => 'i',
64
        'bdo' => 'i',
65
//        'bgsound' => 'i',
0 ignored issues
show
Unused Code Comprehensibility introduced by
54% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
66
//        'big' => 'i',
67
        'x' => 'i',
68
//        'blink' => 'i',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
69
        'blockquote' => 'b',
70
        'body' => 'b',
71
        'br' => 'i',
72
        'button' => 'i',
73
        'canvas' => 'b',
74
        'caption' => 'i',
75
//        'center' => 'b',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
76
        'cite' => 'i',
77
        'code' => 'i',
78
        'col' => 'i',
79
        'colgroup' => 'i',
80
//        'command' => 'i',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
81
        'content' => 'i',
82
        'data' => 'i',
83
        'datalist' => 'i',
84
        'dd' => 'b',
85
        'del' => 'i',
86
        'details' => 'i',
87
        'dfn' => 'i',
88
        'dialog' => 'i',
89
//        'dir' => 'i',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
90
        'div' => 'i',
91
        'dl' => 'b',
92
        'dt' => 'b',
93
//        'element' => 'i',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
94
        'em' => 'i',
95
        'embed' => 'i',
96
        'fieldset' => 'b',
97
        'figcaption' => 'b',
98
        'figure' => 'b',
99
//        'font' => 'i',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
100
        'footer' => 'b',
101
        'form' => 'b',
102
        'frame' => 'i',
103
        'frameset' => 'i',
104
        'h1' => 'b',
105
        'h2' => 'b',
106
        'h3' => 'b',
107
        'h4' => 'b',
108
        'h5' => 'b',
109
        'h6' => 'b',
110
        'head' => 'b',
111
        'header' => 'b',
112
        'hgroup' => 'b',
113
        'hr' => 'b',
114
        'html' => 'b',
115
        'i' => 'i',
116
        'iframe' => 'i',
117
        'image' => 'i',
118
        'img' => 'i',
119
        'input' => 'i',
120
        'ins' => 'i',
121
        'isindex' => 'i',
122
        'kbd' => 'i',
123
        'keygen' => 'i',
124
        'label' => 'i',
125
        'legend' => 'i',
126
        'li' => 'i',
127
        'link' => 'i',
128
//        'listing' => 'i',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
129
        'main' => 'b',
130
        'map' => 'i',
131
        'mark' => 'i',
132
//        'marquee' => 'i',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
133
        'menu' => 'i',
134
        'menuitem' => 'i',
135
        'meta' => 'i',
136
        'meter' => 'i',
137
        'multicol' => 'i',
138
        'nav' => 'b',
139
        'nobr' => 'i',
140
        'noembed' => 'i',
141
        'noframes' => 'i',
142
        'noscript' => 'b',
143
        'object' => 'i',
144
        'ol' => 'b',
145
        'optgroup' => 'i',
146
        'option' => 'b',
147
        'output' => 'i',
148
        'p' => 'b',
149
        'param' => 'i',
150
        'picture' => 'i',
151
//        'plaintext' => 'i',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
152
        'pre' => 'b',
153
        'progress' => 'i',
154
        'q' => 'i',
155
        'rp' => 'i',
156
        'rt' => 'i',
157
        'rtc' => 'i',
158
        'ruby' => 'i',
159
        's' => 'i',
160
        'samp' => 'i',
161
        'script' => 'i',
162
        'section' => 'b',
163
        'select' => 'i',
164
//        'shadow' => 'i',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
165
        'slot' => 'i',
166
        'small' => 'i',
167
        'source' => 'i',
168
//        'spacer' => 'i',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
169
        'span' => 'i',
170
//        'strike' => 'i',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
171
        'strong' => 'i',
172
        'style' => 'i',
173
        'sub' => 'i',
174
        'summary' => 'i',
175
        'sup' => 'i',
176
        'table' => 'b',
177
        'tbody' => 'i',
178
        'td' => 'i',
179
        'template' => 'i',
180
        'textarea' => 'i',
181
        'tfoot' => 'b',
182
        'th' => 'i',
183
        'thead' => 'i',
184
        'time' => 'i',
185
        'title' => 'i',
186
        'tr' => 'i',
187
        'track' => 'i',
188
//        'tt' => 'i',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
189
        'u' => 'i',
190
        'ul' => 'b',
191
        'var' => 'i',
192
        'video' => 'b',
193
        'wbr' => 'i',
194
195
        /// SVG ///
196
        'animate' => 's',
197
        'animateColor' => 's',
198
        'animateMotion' => 's',
199
        'animateTransform' => 's',
200
//        'canvas' => 's',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
201
        'circle' => 's',
202
        'desc' => 's',
203
        'defs' => 's',
204
        'discard' => 's',
205
        'ellipse' => 's',
206
        'g' => 's',
207
//        'image' => 's',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
208
        'line' => 's',
209
        'marker' => 's',
210
        'mask' => 's',
211
        'missing-glyph' => 's',
212
        'mpath' => 's',
213
        'metadata' => 's',
214
        'path' => 's',
215
        'pattern' => 's',
216
        'polygon' => 's',
217
        'polyline' => 's',
218
        'rect' => 's',
219
        'set' => 's',
220
        'svg' => 's',
221
        'switch' => 's',
222
        'symbol' => 's',
223
        'text' => 's',
224
//        'unknown' => 's',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
225
        'use' => 's',
226
    ];
227
228
    protected static $boolAttributes = [
229
        'checked' => 1,
230
        'itemscope' => 1,
231
        'required' => 1,
232
        'selected' => 1,
233
    ];
234
235
    /**
236
     * @var ExpressionLanguage
237
     */
238
    protected $expressions;
239
240 70
    public function __construct() {
241 70
        $this->expressions = new ExpressionLanguage();
242 70
        $this->expressions->setNamePattern('/[@a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A');
243 70
        $this->expressions->register(
244 70
            'hasChildren',
245
            function ($name = null) {
246 1
                return empty($name) ? 'isset($children[0])' : "isset(\$children[$name ?: 0])";
247 70
            },
248
            function ($name = null) {
0 ignored issues
show
Unused Code introduced by
The parameter $name is not used and could be removed.

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

Loading history...
249
                return false;
250 70
            });
251 70
    }
252
253
    /**
254
     * Register a runtime function.
255
     *
256
     * @param string $name The name of the function.
257
     * @param callable $function The function callback.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $function not be callable|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
258
     */
259 68
    public function defineFunction($name, $function = null) {
260 68
        if ($function === null) {
261 1
            $function = $name;
262 1
        }
263
264 68
        $this->expressions->register(
265 68
            $name,
266 68
            $this->getFunctionCompiler($name, $function),
267 68
            $this->getFunctionEvaluator($function)
268 68
        );
269 68
    }
270
271 68
    private function getFunctionEvaluator($function) {
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

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

Loading history...
272 68
        if ($function === 'empty') {
273
            return function ($expr) {
274
                return empty($expr);
275 68
            };
276 67
        } elseif ($function === 'isset') {
277
            return function ($expr) {
278
                return isset($expr);
279
            };
280
        }
281
282 67
        return $function;
283
    }
284
285 68
    private function getFunctionCompiler($name, $function) {
286 68
        $var = var_export(strtolower($name), true);
287
        $fn = function ($expr) use ($var) {
288 1
            return "\$this->call($var, $expr)";
289 68
        };
290
291 68
        if (is_string($function)) {
292
            $fn = function (...$args) use ($function) {
293 14
                return $function.'('.implode(', ', $args).')';
294 68
            };
295 68
        } elseif (is_array($function)) {
296 67
            if (is_string($function[0])) {
297
                $fn = function (...$args) use ($function) {
298
                    return "$function[0]::$function[1](".implode(', ', $args).')';
299
                };
300 67
            } elseif ($function[0] instanceof Ebi) {
301
                $fn = function (...$args) use ($function) {
302 7
                    return "\$this->$function[1](".implode(', ', $args).')';
303 67
                };
304 67
            }
305 67
        }
306
307 68
        return $fn;
308
    }
309
310 62
    public function compile($src, array $options = []) {
311 62
        $options += ['basename' => '', 'path' => '', 'runtime' => true];
312
313 62
        $src = trim($src);
314
315 62
        $out = new CompilerBuffer();
316
317 62
        $out->setBasename($options['basename']);
318 62
        $out->setSource($src);
319 62
        $out->setPath($options['path']);
320
321 62
        $dom = new \DOMDocument();
322
323 62
        $fragment = false;
324 62
        if (strpos($src, '<html') === false) {
325 60
            $src = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><html><body>$src</body></html>";
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 6 spaces but found 1 space

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

To visualize

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

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

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

will produce no issues.

Loading history...
326 60
            $fragment = true;
327 60
        }
328
329 62
        libxml_use_internal_errors(true);
330 62
        $dom->loadHTML($src, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD | LIBXML_NOCDATA | LIBXML_NOXMLDECL);
331
//        $arr = $this->domToArray($dom);
0 ignored issues
show
Unused Code Comprehensibility introduced by
59% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
332
333 62
        if ($options['runtime']) {
334 61
            $name = var_export($options['basename'], true);
335 61
            $out->appendCode("\$this->defineComponent($name, function (\$props = [], \$children = []) {\n");
336 61
        } else {
337 1
            $out->appendCode("function (\$props = [], \$children = []) {\n");
338
        }
339
340 62
        $out->pushScope(['this' => 'props']);
341 62
        $out->indent(+1);
342
343 62
        $parent = $fragment ? $dom->firstChild->nextSibling->firstChild : $dom;
344
345 62
        foreach ($parent->childNodes as $node) {
346 62
            $this->compileNode($node, $out);
347 56
        }
348
349 56
        $out->indent(-1);
350 56
        $out->popScope();
351
352 56
        if ($options['runtime']) {
353 55
            $out->appendCode("});");
354 55
        } else {
355 1
            $out->appendCode("};");
356
        }
357
358 56
        $r = $out->flush();
359
360 56
        $errs = libxml_get_errors();
0 ignored issues
show
Unused Code introduced by
$errs is not used, you could remove the assignment.

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

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

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

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

Loading history...
361
362 56
        return $r;
363
    }
364
365 48
    protected function isComponent($tag) {
366 48
        return !isset(static::$htmlTags[$tag]);
367
    }
368
369 62
    protected function compileNode(DOMNode $node, CompilerBuffer $out) {
370 62
        if ($out->getNodeProp($node, 'skip')) {
371 4
            return;
372
        }
373
374 62
        switch ($node->nodeType) {
375 62
            case XML_TEXT_NODE:
376 49
                $this->compileTextNode($node, $out);
377 49
                break;
378 59
            case XML_ELEMENT_NODE:
379
                /* @var \DOMElement $node */
380 59
                $this->compileElementNode($node, $out);
381 54
                break;
382 4
            case XML_COMMENT_NODE:
383
                /* @var \DOMComment $node */
384 1
                $this->compileCommentNode($node, $out);
385 1
                break;
386 3
            case XML_DOCUMENT_TYPE_NODE:
387 1
                $out->echoLiteral("<!DOCTYPE {$node->name}>\n");
0 ignored issues
show
Bug introduced by
The property name does not seem to exist in DOMNode.

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

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

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

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

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

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

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

Loading history...
394
                    '// '.str_replace("\n", "\n// ", $node->ownerDocument->saveHTML($node));
395 57
        }
396 57
    }
397
398
    protected function domToArray(DOMNode $root) {
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

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

Loading history...
399
        $result = [];
400
401
        if ($root->hasAttributes()) {
402
            $attrs = $root->attributes;
403
            foreach ($attrs as $attr) {
404
                $result['@attributes'][$attr->name] = $attr->value;
405
            }
406
        }
407
408
        if ($root->hasChildNodes()) {
409
            $children = $root->childNodes;
410
            if ($children->length == 1) {
411
                $child = $children->item(0);
412
                if ($child->nodeType == XML_TEXT_NODE) {
413
                    $result['_value'] = $child->nodeValue;
414
                    return count($result) == 1
415
                        ? $result['_value']
416
                        : $result;
417
                }
418
            }
419
            $groups = [];
420
            foreach ($children as $child) {
421
                if (!isset($result[$child->nodeName])) {
422
                    $result[$child->nodeName] = $this->domToArray($child);
423
                } else {
424
                    if (!isset($groups[$child->nodeName])) {
425
                        $result[$child->nodeName] = [$result[$child->nodeName]];
426
                        $groups[$child->nodeName] = 1;
427
                    }
428
                    $result[$child->nodeName][] = $this->domToArray($child);
429
                }
430
            }
431
        }
432
433
        return $result;
434
    }
435
436 1
    protected function newline(DOMNode $node, CompilerBuffer $out) {
437 1
        if ($node->previousSibling && $node->previousSibling->nodeType !== XML_COMMENT_NODE) {
438
            $out->appendCode("\n");
439
        }
440 1
    }
441
442 1
    protected function compileCommentNode(\DOMComment $node, CompilerBuffer $out) {
443 1
        $comments = explode("\n", trim($node->nodeValue));
444
445 1
        $this->newline($node, $out);
446 1
        foreach ($comments as $comment) {
447 1
            $out->appendCode("// $comment\n");
448 1
        }
449 1
    }
450
451 51
    protected function compileTextNode(DOMNode $node, CompilerBuffer $out) {
452 51
        $nodeText = $node->nodeValue;
453
454 51
        $items = $this->splitExpressions($nodeText);
455 51
        foreach ($items as $i => list($text, $offset)) {
456 51
            if (preg_match('`^{\S`', $text)) {
457 29
                if (preg_match('`^{\s*unescape\((.+)\)\s*}$`', $text, $m)) {
458 3
                    $out->echoCode($this->expr($m[1], $out));
459 3
                } else {
460
                    try {
461 27
                        $expr = substr($text, 1, -1);
462 27
                        $out->echoCode('htmlspecialchars('.$this->expr($expr, $out).')');
463 27
                    } catch (SyntaxError $ex) {
464 1
                        $nodeLineCount = substr_count($nodeText, "\n");
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

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

To visualize

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

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

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

will produce no issues.

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

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

To visualize

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

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

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

will produce no issues.

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

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

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

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

To visualize

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

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

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

will produce no issues.

Loading history...
564
565 59
        foreach ($node->attributes as $name => $attribute) {
566 55
            if (isset(static::$special[$name])) {
567 40
                $special[$name] = $attribute;
568 40
            } else {
569 22
                $attributes[$name] = $attribute;
570
            }
571 59
        }
572
573 59
        uksort($special, function ($a, $b) {
574 11
            return strnatcmp(static::$special[$a], static::$special[$b]);
575 59
        });
576
577 59
        return [$attributes, $special];
578
    }
579
580 36
    protected function compileSpecialNode(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
581 36
        $specialName = key($special);
582
583
        switch ($specialName) {
584 36
            case self::T_COMPONENT:
585 9
                $this->compileComponentRegister($node, $attributes, $special, $out);
586 9
                break;
587 36
            case self::T_IF:
588 10
                $this->compileIf($node, $attributes, $special, $out);
589 9
                break;
590 35
            case self::T_EACH:
591 14
                $this->compileEach($node, $attributes, $special, $out);
592 13
                break;
593 25
            case self::T_BLOCK:
594 1
                $this->compileBlock($node, $attributes, $special, $out);
595 1
                break;
596 25
            case self::T_CHILDREN:
597 3
                $this->compileChildBlock($node, $attributes, $special, $out);
598 3
                break;
599 25
            case self::T_INCLUDE:
600 1
                $this->compileComponentInclude($node, $attributes, $special, $out);
601 1
                break;
602 25
            case self::T_WITH:
603 5
                if ($this->isComponent($node->tagName)) {
604
                    // With has a special meaning in components.
605 3
                    $this->compileComponentInclude($node, $attributes, $special, $out);
606 3
                } else {
607 2
                    $this->compileWith($node, $attributes, $special, $out);
608
                }
609 4
                break;
610 23
            case self::T_LITERAL:
611 2
                $this->compileLiteral($node, $attributes, $special, $out);
612 2
                break;
613 21
            case '':
614 21
                if ($this->isComponent($node->tagName)) {
615 7
                    $this->compileComponentInclude($node, $attributes, $special, $out);
616 7
                } else {
617 20
                    $this->compileElement($node, $attributes, $special, $out);
618
                }
619 21
                break;
620 1
            case self::T_TAG:
621 1
            default:
622
                // This is only a tag node so it just gets compiled as an element.
623 1
                $this->compileBasicElement($node, $attributes, $special, $out);
624 1
                break;
625
        }
626 33
    }
627
628
    /**
629
     * Compile component registering.
630
     *
631
     * @param DOMElement $node
632
     * @param $attributes
633
     * @param $special
634
     * @param CompilerBuffer $out
635
     */
636 9
    public function compileComponentRegister(DOMElement $node, $attributes, $special, CompilerBuffer $out) {
637 9
        $name = strtolower($special[self::T_COMPONENT]->value);
638 9
        unset($special[self::T_COMPONENT]);
639
640 9
        $prev = $out->select($name);
641
642 9
        $varName = var_export($name, true);
643 9
        $out->appendCode("\$this->defineComponent($varName, function (\$props = [], \$children = []) {\n");
644 9
        $out->pushScope(['this' => 'props']);
645 9
        $out->indent(+1);
646
647
        try {
648 9
            $this->compileSpecialNode($node, $attributes, $special, $out);
649 9
        } finally {
650 9
            $out->popScope();
651 9
            $out->indent(-1);
652 9
            $out->appendCode("});");
653 9
            $out->select($prev);
654 9
        }
655 9
    }
656
657 1
    private function compileBlock(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
658 1
        $name = strtolower($special[self::T_BLOCK]->value);
659 1
        unset($special[self::T_BLOCK]);
660
661 1
        $prev = $out->select($name);
662
663 1
        $use = '$'.implode(', $', array_unique($out->getScopeVariables())).', $children';
664
665 1
        $out->appendCode("function () use ($use) {\n");
666 1
        $out->pushScope(['this' => 'props']);
667 1
        $out->indent(+1);
668
669
        try {
670 1
            $this->compileSpecialNode($node, $attributes, $special, $out);
671 1
        } finally {
672 1
            $out->indent(-1);
673 1
            $out->popScope();
674 1
            $out->appendCode("}");
675 1
            $out->select($prev);
676 1
        }
677
678 1
        return $out;
679
    }
680
681
    /**
682
     * Compile component inclusion and rendering.
683
     *
684
     * @param DOMElement $node
685
     * @param DOMAttr[] $attributes
686
     * @param DOMAttr[] $special
687
     * @param CompilerBuffer $out
688
     */
689 11
    protected function compileComponentInclude(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
690
        // Generate the attributes into a property array.
691 11
        $props = [];
692 11
        foreach ($attributes as $name => $attribute) {
693
            /* @var DOMAttr $attr */
694 5
            if ($this->isExpression($attribute->value)) {
695 4
                $expr = $this->expr(substr($attribute->value, 1, -1), $out, $attribute);
696 4
            } else {
697 1
                $expr = var_export($attribute->value, true);
698
            }
699
700 5
            $props[] = var_export($name, true).' => '.$expr;
701 11
        }
702 11
        $propsStr = '['.implode(', ', $props).']';
703
704 11
        if (isset($special[self::T_WITH])) {
705 3
            $withExpr = $this->expr($special[self::T_WITH]->value, $out, $special[self::T_WITH]);
706 3
            unset($special[self::T_WITH]);
707
708 3
            $propsStr = empty($props) ? $withExpr : $propsStr.' + (array)'.$withExpr;
709 11
        } elseif (empty($props)) {
710
            // By default the current context is passed to components.
711 4
            $propsStr = $this->expr('this', $out);
712 4
        }
713
714
        // Compile the children blocks.
715 11
        $blocks = $this->compileComponentBlocks($node, $out);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

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

To visualize

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

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

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

will produce no issues.

Loading history...
716 11
        $blocksStr = $blocks->flush();
717
718 11
        if (isset($special[self::T_INCLUDE])) {
719 1
            $name = $this->expr($special[self::T_INCLUDE]->value, $out, $special[self::T_INCLUDE]);
720 1
        } else {
721 10
            $name = var_export($node->tagName, true);
722
        }
723
724 11
        $out->appendCode("\$this->write($name, $propsStr, $blocksStr);\n");
725 11
    }
726
727
    /**
728
     * @param DOMElement $parent
729
     * @return CompilerBuffer
730
     */
731 11
    protected function compileComponentBlocks(DOMElement $parent, CompilerBuffer $out) {
732 11
        $blocksOut = new CompilerBuffer(CompilerBuffer::STYLE_ARRAY, [
733 11
            'baseIndent' => $out->getIndent(),
734 11
            'indent' => $out->getIndent() + 1,
735 11
            'depth' => $out->getDepth(),
736 11
            'scopes' => $out->getAllScopes()
737 11
        ]);
738
739 11
        if ($this->isEmptyNode($parent)) {
740 9
            return $blocksOut;
741
        }
742
743 3
        $use = '$'.implode(', $', $blocksOut->getScopeVariables()).', $children';
744
745 3
        $blocksOut->appendCode("function () use ($use) {\n");
746 3
        $blocksOut->indent(+1);
747
748
        try {
749 3
            foreach ($parent->childNodes as $node) {
750 3
                $this->compileNode($node, $blocksOut);
751 3
            }
752 3
        } finally {
753 3
            $blocksOut->indent(-1);
754 3
            $blocksOut->appendCode("}");
755 3
        }
756
757 3
        return $blocksOut;
758
    }
759
760 27
    protected function compileTagComment(DOMElement $node, $attributes, $special, CompilerBuffer $out) {
0 ignored issues
show
Unused Code introduced by
The parameter $attributes is not used and could be removed.

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

Loading history...
761
        // Don't double up comments.
762 27
        if ($node->previousSibling && $node->previousSibling->nodeType === XML_COMMENT_NODE) {
763
            return;
764
        }
765
766 27
        $str = '<'.$node->tagName;
767 27
        foreach ($special as $attr) {
768
            /* @var DOMAttr $attr */
769 27
            $str .= ' '.$attr->name.(empty($attr->value) ? '' : '="'.htmlspecialchars($attr->value).'"');
770 27
        }
771 27
        $str .= '>';
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 5 spaces but found 1 space

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

To visualize

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

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

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

will produce no issues.

Loading history...
772 27
        $comments = explode("\n", $str);
773 27
        foreach ($comments as $comment) {
774 27
            $out->appendCode("// $comment\n");
775 27
        }
776 27
    }
777
778
    /**
779
     * @param DOMElement $node
780
     * @param DOMAttr[] $attributes
781
     * @param DOMAttr[] $special
782
     * @param CompilerBuffer $out
783
     * @param bool $force
784
     */
785 49
    protected function compileOpenTag(DOMElement $node, $attributes, $special, CompilerBuffer $out, $force = false) {
786 49
        $tagNameExpr = !empty($special[self::T_TAG]) ? $special[self::T_TAG]->value : '';
787
788 49
        if ($node->tagName === self::T_X && empty($tagNameExpr)) {
789 4
            return;
790
        }
791
792 48
        if (!empty($tagNameExpr)) {
793 1
            $tagNameExpr = $this->expr($tagNameExpr, $out, $special[self::T_TAG]);
794 1
            $out->echoLiteral('<');
795 1
            $out->echoCode($tagNameExpr);
796 1
        } else {
797 48
            $out->echoLiteral('<'.$node->tagName);
798
        }
799
800
        /* @var DOMAttr $attribute */
801 48
        foreach ($attributes as $name => $attribute) {
802
            // Check for an attribute expression.
803 17
            if ($this->isExpression($attribute->value)) {
804 13
                $out->echoCode(
805 13
                    '$this->attribute('.var_export($name, true).', '.
806 13
                    $this->expr(substr($attribute->value, 1, -1), $out, $attribute).
807 13
                    ')');
808 17
            } elseif (null !== $fn = $this->getAttributeFunction($attribute)) {
809 3
                $value  = call_user_func($fn, var_export($attribute->value, true));
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned correctly; expected 1 space but found 2 spaces

This check looks for improperly formatted assignments.

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

To illustrate:

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

will have no issues, while

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

will report issues in lines 1 and 2.

Loading history...
810
811 3
                $out->echoCode('$this->attribute('.var_export($name, true).', '.$value.')');
812 5
            } elseif ((empty($attribute->value) || $attribute->value === $name) && isset(self::$boolAttributes[$name])) {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $attribute->value (string) and $name (integer) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
813 2
                $out->echoLiteral(' '.$name);
814 2
            } else {
815 4
                $out->echoLiteral(' '.$name.'="');
816 4
                $out->echoLiteral(htmlspecialchars($attribute->value));
817 4
                $out->echoLiteral('"');
818
            }
819 48
        }
820
821 48
        if ($node->hasChildNodes() || $force) {
822 46
            $out->echoLiteral('>');
823 46
        } else {
824 3
            $out->echoLiteral(" />");
825
        }
826 48
    }
827
828 20
    private function isExpression($value) {
829 20
        return preg_match('`^{\S.*}$`', $value);
830
    }
831
832 48
    protected function compileCloseTag(DOMElement $node, $special, CompilerBuffer $out, $force = false) {
833 48
        if (!$force && !$node->hasChildNodes()) {
834 3
            return;
835
        }
836
837 46
        $tagNameExpr = !empty($special[self::T_TAG]) ? $special[self::T_TAG]->value : '';
838 46
        if (!empty($tagNameExpr)) {
839 1
            $tagNameExpr = $this->expr($tagNameExpr, $out, $special[self::T_TAG]);
840 1
            $out->echoLiteral('</');
841 1
            $out->echoCode($tagNameExpr);
842 1
            $out->echoLiteral('>');
843 46
        } elseif ($node->tagName !== self::T_X) {
844 45
            $out->echoLiteral("</{$node->tagName}>");
845 45
        }
846 46
    }
847
848 4
    protected function isEmptyText(DOMNode $node) {
849 4
        return $node instanceof \DOMText && empty(trim($node->data));
850
    }
851
852 11
    protected function isEmptyNode(DOMNode $node) {
853 11
        if (!$node->hasChildNodes()) {
854 9
            return true;
855
        }
856
857 3
        foreach ($node->childNodes as $childNode) {
858 3
            if ($childNode instanceof DOMElement) {
859 1
                return false;
860
            }
861 2
            if ($childNode instanceof \DOMText && !$this->isEmptyText($childNode)) {
862 2
                return false;
863
            }
864
        }
865
866
        return true;
867
    }
868
869 10
    protected function compileIf(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
870 10
        $this->compileTagComment($node, $attributes, $special, $out);
871 10
        $expr = $this->expr($special[self::T_IF]->value, $out, $special[self::T_IF]);
872 9
        unset($special[self::T_IF]);
873
874 9
        $elseNode = $this->findSpecialNode($node, self::T_ELSE, self::T_IF);
875 9
        $out->setNodeProp($elseNode, 'skip', true);
876
877 9
        $out->appendCode('if ('.$expr.") {\n");
878 9
        $out->indent(+1);
879
880 9
        $this->compileSpecialNode($node, $attributes, $special, $out);
881
882 9
        $out->indent(-1);
883
884 9
        if ($elseNode) {
885 2
            list($attributes, $special) = $this->splitAttributes($elseNode);
886 2
            unset($special[self::T_ELSE]);
887
888 2
            $out->appendCode("} else {\n");
889
890 2
            $out->indent(+1);
891 2
            $this->compileSpecialNode($elseNode, $attributes, $special, $out);
892 2
            $out->indent(-1);
893 2
        }
894
895 9
        $out->appendCode("}\n");
896 9
    }
897
898 14
    protected function compileEach(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
899 14
        $this->compileTagComment($node, $attributes, $special, $out);
900 14
        $this->compileOpenTag($node, $attributes, $special, $out);
901
902 14
        $emptyNode = $this->findSpecialNode($node, self::T_EMPTY, self::T_ELSE);
903 14
        $out->setNodeProp($emptyNode, 'skip', true);
904
905 14
        if ($emptyNode === null) {
906 12
            $this->compileEachLoop($node, $attributes, $special, $out);
907 11
        } else {
908 2
            $expr = $this->expr("empty({$special[self::T_EACH]->value})", $out);
909
910 2
            list ($emptyAttributes, $emptySpecial) = $this->splitAttributes($emptyNode);
911 2
            unset($emptySpecial[self::T_EMPTY]);
912
913 2
            $out->appendCode('if ('.$expr.") {\n");
914
915 2
            $out->indent(+1);
916 2
            $this->compileSpecialNode($emptyNode, $emptyAttributes, $emptySpecial, $out);
917 2
            $out->indent(-1);
918
919 2
            $out->appendCode("} else {\n");
920
921 2
            $out->indent(+1);
922 2
            $this->compileEachLoop($node, $attributes, $special, $out);
923 2
            $out->indent(-1);
924
925 2
            $out->appendCode("}\n");
926
        }
927
928 13
        $this->compileCloseTag($node, $special, $out);
929 13
    }
930
931 2
    protected function compileWith(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
932 2
        $this->compileTagComment($node, $attributes, $special, $out);
933
934 2
        $out->depth(+1);
935 2
        $scope = ['this' => $out->depthName('props')];
936 2
        if (!empty($special[self::T_AS])) {
937 2
            if (preg_match(self::IDENT_REGEX, $special[self::T_AS]->value, $m)) {
938
                // The template specified an x-as attribute to alias the with expression.
939 1
                $scope = [$m[1] => $out->depthName('props')];
940 1
            } else {
941 1
                throw $out->createCompilerException(
942 1
                    $special[self::T_AS],
943 1
                    new \Exception("Invalid identifier \"{$special[self::T_AS]->value}\" in x-as attribute.")
944 1
                );
945
            }
946 1
        }
947 1
        $with = $this->expr($special[self::T_WITH]->value, $out);
948
949 1
        unset($special[self::T_WITH], $special[self::T_AS]);
950
951 1
        $out->pushScope($scope);
952 1
        $out->appendCode('$'.$out->depthName('props')." = $with;\n");
953
954 1
        $this->compileSpecialNode($node, $attributes, $special, $out);
955
956 1
        $out->depth(-1);
957 1
        $out->popScope();
958 1
    }
959
960 2
    protected function compileLiteral(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
961 2
        $this->compileTagComment($node, $attributes, $special, $out);
962 2
        unset($special[self::T_LITERAL]);
963
964 2
        $this->compileOpenTag($node, $attributes, $special, $out);
965
966 2
        foreach ($node->childNodes as $childNode) {
967 2
            $html = $childNode->ownerDocument->saveHTML($childNode);
968 2
            $out->echoLiteral($html);
969 2
        }
970
971 2
        $this->compileCloseTag($node, $special, $out);
972 2
    }
973
974 20
    protected function compileElement(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
975 20
        $this->compileOpenTag($node, $attributes, $special, $out);
976
977 20
        foreach ($node->childNodes as $childNode) {
978 20
            $this->compileNode($childNode, $out);
979 20
        }
980
981 20
        $this->compileCloseTag($node, $special, $out);
982 20
    }
983
984
    /**
985
     * Find a special node in relation to another node.
986
     *
987
     * This method is used to find things such as x-empty and x-else elements.
988
     *
989
     * @param DOMElement $node The node to search in relation to.
990
     * @param string $attribute The name of the attribute to search for.
991
     * @param string $parentAttribute The name of the parent attribute to resolve conflicts.
992
     * @return DOMElement|null Returns the found element node or **null** if not found.
993
     */
994 22
    protected function findSpecialNode(DOMElement $node, $attribute, $parentAttribute) {
995
        // First look for a sibling after the node.
996 22
        for ($sibNode = $node->nextSibling; $sibNode !== null; $sibNode = $sibNode->nextSibling) {
997 2
            if ($sibNode instanceof DOMElement && $sibNode->hasAttribute($attribute)) {
998 2
                return $sibNode;
999
            }
1000
1001
            // Stop searching if we encounter another node.
1002 2
            if (!$this->isEmptyText($sibNode)) {
1003
                break;
1004
            }
1005 2
        }
1006
1007
        // Next look inside the node.
1008 20
        $parentFound = false;
1009 20
        foreach ($node->childNodes as $childNode) {
1010 19
            if (!$parentFound && $childNode instanceof DOMElement && $childNode->hasAttribute($attribute)) {
1011 2
                return $childNode;
1012
            }
1013
1014 19
            if ($childNode instanceof DOMElement) {
1015 14
                $parentFound = $childNode->hasAttribute($parentAttribute);
1016 19
            } elseif ($childNode instanceof \DOMText && !empty(trim($childNode->data))) {
1017 5
                $parentFound = false;
1018 5
            }
1019 20
        }
1020
1021 18
        return null;
1022
    }
1023
1024
    /**
1025
     * @param DOMElement $node
1026
     * @param array $attributes
1027
     * @param array $special
1028
     * @param CompilerBuffer $out
1029
     */
1030 14
    private function compileEachLoop(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
0 ignored issues
show
Unused Code introduced by
The parameter $attributes is not used and could be removed.

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

Loading history...
1031 14
        $each = $this->expr($special[self::T_EACH]->value, $out);
1032 14
        unset($special[self::T_EACH]);
1033
1034 14
        $as = ['', $out->depthName('props', 1)];
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

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

To visualize

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

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

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

will produce no issues.

Loading history...
1035 14
        $scope = ['this' => $as[1]];
1036 14
        if (!empty($special[self::T_AS])) {
1037 8
            if (preg_match('`^(?:([a-z0-9]+)\s+)?([a-z0-9]+)$`i', $special[self::T_AS]->value, $m)) {
1038 7
                $scope = [$m[2] => $as[1]];
1039 7
                if (!empty($m[1])) {
1040 4
                    $scope[$m[1]] = $as[0] = $out->depthName('i', 1);
1041 4
                }
1042 7
            } else {
1043 1
                throw $out->createCompilerException(
1044 1
                    $special[self::T_AS],
1045 1
                    new \Exception("Invalid identifier \"{$special[self::T_AS]->value}\" in x-as attribute.")
1046 1
                );
1047
            }
1048 7
        }
1049 13
        unset($special[self::T_AS]);
1050 13
        if (empty($as[0])) {
1051 9
            $out->appendCode("foreach ($each as \${$as[1]}) {\n");
1052 9
        } else {
1053 4
            $out->appendCode("foreach ($each as \${$as[0]} => \${$as[1]}) {\n");
1054
        }
1055 13
        $out->depth(+1);
1056 13
        $out->indent(+1);
1057 13
        $out->pushScope($scope);
1058
1059 13
        foreach ($node->childNodes as $childNode) {
1060 13
            $this->compileNode($childNode, $out);
1061 13
        }
1062
1063 13
        $out->indent(-1);
1064 13
        $out->depth(-1);
1065 13
        $out->popScope();
1066 13
        $out->appendCode("}\n");
1067 13
    }
1068
1069 51
    protected function ltrim($text, \DOMNode $node, CompilerBuffer $out) {
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

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

Loading history...
1070 51
        if ($this->inPre($node)) {
1071
            return $text;
1072
        }
1073
1074 51
        $sib = $node->previousSibling ?: $node->parentNode;
1075 51
        if ($sib === null || !$sib instanceof \DOMElement || $out->getNodeProp($sib, 'skip') || $sib->tagName === self::T_X) {
1076 7
            return ltrim($text);
1077
        }
1078
1079 49
        $text = preg_replace('`^\s*\n\s*`', "\n", $text, -1, $count);
1080 49
        if ($count === 0) {
1081 47
            $text = preg_replace('`^\s+`', ' ', $text);
1082 47
        }
1083
1084
//        if ($sib !== null && ($sib->nodeType === XML_COMMENT_NODE || in_array($sib->tagName, static::$blocks))) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
53% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
1085
//            return ltrim($text);
1086
//        }
1087 49
        return $text;
1088
    }
1089
1090 51
    protected function rtrim($text, \DOMNode $node, CompilerBuffer $out) {
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

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

Loading history...
1091 51
        if ($this->inPre($node)) {
1092
            return $text;
1093
        }
1094
1095 51
        $sib = $node->nextSibling ?: $node->parentNode;
1096
1097 51
        if ($sib === null || !$sib instanceof \DOMElement || $out->getNodeProp($sib, 'skip') || $sib->tagName === self::T_X) {
1098 6
            return rtrim($text);
1099
        }
1100
1101 49
        $text = preg_replace('`\s*\n\s*$`', "\n", $text, -1, $count);
1102 49
        if ($count === 0) {
1103 48
            $text = preg_replace('`\s+$`', ' ', $text);
1104 48
        }
1105
1106
//        if ($sib !== null && ($sib->nodeType === XML_COMMENT_NODE || in_array($sib->tagName, static::$blocks))) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
53% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
1107
//            return rtrim($text);
1108
//        }
1109 49
        return $text;
1110
    }
1111
1112 51
    protected function inPre(\DOMNode $node) {
1113 51
        for ($node = $node->parentNode; $node !== null; $node = $node->parentNode) {
1114 51
            if (in_array($node->nodeType, ['code', 'pre'], true)) {
1115
                return true;
1116
            }
1117 51
        }
1118 51
        return false;
1119
    }
1120
1121 3
    private function compileChildBlock(DOMElement $node, array $attributes, array $special, CompilerBuffer $out) {
1122
        /* @var DOMAttr $child */
1123 3
        $child = $special[self::T_CHILDREN];
1124 3
        unset($special[self::T_CHILDREN]);
1125
1126 3
        $key = $child->value === '' ? 0 : $child->value;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

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

To visualize

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

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

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

will produce no issues.

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

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

Loading history...
1149 8
        $str = $raw = $node->nodeValue;
0 ignored issues
show
Unused Code introduced by
$raw is not used, you could remove the assignment.

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

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

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

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

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