Completed
Push — master ( 9ad54b...895d73 )
by Federico
03:21
created

Parser::tag()   F

Complexity

Conditions 27
Paths 1280

Size

Total Lines 115
Code Lines 76

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 27
eloc 76
c 1
b 0
f 1
nc 1280
nop 1
dl 0
loc 115
rs 2

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
namespace Jade;
4
5
use Jade\Parser\Exception as ParserException;
6
use Jade\Parser\ExtensionsHelper;
7
8
class Parser
9
{
10
    public static $includeNotFound = ".alert.alert-danger.\n\tPage not found.";
11
12
    protected $allowMixedIndent;
13
    protected $basedir;
14
    protected $extending;
15
    protected $extension;
16
    protected $filename;
17
    protected $input;
18
    protected $lexer;
19
    protected $notFound;
20
    protected $options = array();
21
    protected $preRender;
22
23
    protected $blocks = array();
24
    protected $mixins = array();
25
    protected $contexts = array();
26
27
    public function __construct($input, $filename = null, array $options = array())
28
    {
29
        $defaultOptions = array(
30
            'allowMixedIndent' => true,
31
            'basedir' => null,
32
            'customKeywords' => array(),
33
            'extension' => array('.pug', '.jade'),
34
            'notFound' => null,
35
            'preRender' => null,
36
        );
37
        foreach ($defaultOptions as $key => $default) {
38
            $this->$key = isset($options[$key]) ? $options[$key] : $default;
39
            $this->options[$key] = $this->$key;
40
        }
41
42
        $this->setInput($filename, $input);
43
44
        if ($this->input && $this->input[0] === "\xef" && $this->input[1] === "\xbb" && $this->input[2] === "\xbf") {
45
            $this->input = substr($this->input, 3);
46
        }
47
48
        $this->lexer = new Lexer($this->input, $this->options);
49
        array_push($this->contexts, $this);
50
    }
51
52
    protected function getExtensions()
53
    {
54
        $extensions = new ExtensionsHelper($this->extension);
55
56
        return $extensions->getExtensions();
57
    }
58
59
    protected function hasValidTemplateExtension($path)
60
    {
61
        $extensions = new ExtensionsHelper($this->extension);
62
63
        return $extensions->hasValidTemplateExtension($path);
64
    }
65
66
    protected function getTemplatePath($path)
67
    {
68
        $isAbsolutePath = substr($path, 0, 1) === '/';
69
        if ($isAbsolutePath && !isset($this->options['basedir'])) {
70
            throw new \ErrorException("The 'basedir' option need to be set to use absolute path like $path", 29);
71
        }
72
73
        $path = ($isAbsolutePath
74
            ? rtrim($this->options['basedir'], '/\\')
75
            : dirname($this->filename)
76
        ) . DIRECTORY_SEPARATOR . $path;
77
        $extensions = new ExtensionsHelper($this->extension);
78
79
        return $extensions->findValidTemplatePath($path, '');
80
    }
81
82
    protected function getTemplateContents($path, $value = null)
83
    {
84
        if ($path !== null) {
85
            $contents = file_get_contents($path);
86
            if (is_callable($this->preRender)) {
87
                $contents = call_user_func($this->preRender, $contents);
88
            }
89
90
            return $contents;
91
        }
92
93
        $notFound = isset($this->options['notFound'])
94
            ? $this->options['notFound']
95
            : static::$includeNotFound;
96
97
        if ($notFound !== false) {
98
            return $notFound;
99
        }
100
101
        $value = $value ?: $path;
102
        throw new \InvalidArgumentException("The included file '$value' does not exists.", 22);
103
    }
104
105
    protected function setInput($filename, $input)
106
    {
107
        if ($filename === null && file_exists($input)) {
108
            $filename = $input;
109
            $input = file_get_contents($input);
110
        }
111
112
        $this->input = preg_replace('`\r\n|\r`', "\n", $input);
113
        if (is_callable($this->preRender)) {
114
            $this->input = call_user_func($this->preRender, $this->input);
115
        }
116
        $this->filename = $filename;
117
    }
118
119
    public function getFilename()
120
    {
121
        return $this->filename;
122
    }
123
124
    /**
125
     * Get a parser with the same settings.
126
     *
127
     * @return Parser
128
     */
129
    public function subParser($input)
130
    {
131
        return new static($input, $this->filename, $this->options);
132
    }
133
134
    public function context($parser = null)
135
    {
136
        if ($parser === null) {
137
            return array_pop($this->contexts);
138
        }
139
        array_push($this->contexts, $parser);
140
    }
141
142
    public function advance()
143
    {
144
        return $this->lexer->advance();
145
    }
146
147
    public function skip($n)
148
    {
149
        while ($n--) {
150
            $this->advance();
151
        }
152
    }
153
154
    public function peek()
155
    {
156
        return $this->lookahead(1);
157
    }
158
159
    public function line()
160
    {
161
        return $this->lexer->lineno;
162
    }
163
164
    public function lookahead($n = 1)
165
    {
166
        return $this->lexer->lookahead($n);
167
    }
168
169
    public function parse()
170
    {
171
        $block = new Nodes\Block();
172
        $block->line = $this->line();
0 ignored issues
show
Bug introduced by
The property line does not seem to exist in Jade\Nodes\Block.

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...
173
174
        while ($this->peekType() !== 'eos') {
175
            if ($this->peekType() === 'newline') {
176
                $this->advance();
177
                continue;
178
            }
179
            $block->push($this->parseExpression());
180
        }
181
182
        if ($parser = $this->extending) {
183
            $this->context($parser);
184
            // $parser->blocks = $this->blocks;
0 ignored issues
show
Unused Code Comprehensibility introduced by
46% 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...
185
            try {
186
                $ast = $parser->parse();
187
            } catch (\Exception $e) {
188
                throw new ParserException($parser->getFilename() . ' (' . $block->line . ') : ' . $e->getMessage(), 23, $e);
189
            }
190
            $this->context();
191
192
            foreach ($this->mixins as $name => $v) {
193
                $ast->unshift($this->mixins[$name]);
194
            }
195
196
            return $ast;
197
        }
198
199
        return $block;
200
    }
201
202
    protected function expect($type)
203
    {
204
        if ($this->peekType() === $type) {
205
            return $this->lexer->advance();
206
        }
207
208
        $lineNumber = $this->line();
209
        $lines = explode("\n", $this->input);
210
        $lineString = isset($lines[$lineNumber]) ? $lines[$lineNumber] : '';
211
        throw new \ErrorException("\n" . sprintf('Expected %s, but got %s in %dth line : %s', $type, $this->peekType(), $lineNumber, $lineString) . "\n", 24);
212
    }
213
214
    protected function accept($type)
215
    {
216
        if ($this->peekType() === $type) {
217
            return $this->advance();
218
        }
219
    }
220
221
    protected function parseExpression()
222
    {
223
        $_types = array('tag', 'mixin', 'block', 'case', 'when', 'default', 'extends', 'include', 'doctype', 'filter', 'comment', 'text', 'each', 'customKeyword', 'code', 'call', 'interpolation');
224
225
        if (in_array($this->peekType(), $_types)) {
226
            $_method = 'parse' . ucfirst($this->peekType());
227
228
            return $this->$_method();
229
        }
230
231
        switch ($this->peekType()) {
232
            case 'yield':
233
                $this->advance();
234
                $block = new Nodes\Block();
235
                $block->yield = true;
0 ignored issues
show
Bug introduced by
The property yield does not seem to exist in Jade\Nodes\Block.

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...
236
237
                return $block;
238
239
            case 'id':
240
            case 'class':
241
                $token = $this->advance();
242
                $this->lexer->defer($this->lexer->token('tag', 'div'));
243
                $this->lexer->defer($token);
0 ignored issues
show
Documentation introduced by
$token is of type object|boolean, but the function expects a object<stdClass>.

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...
244
245
                return $this->parseExpression();
246
247
            default:
248
                throw new \ErrorException($this->filename . ' (' . $this->line() . ') : Unexpected token "' . $this->peekType() . '"', 25);
249
        }
250
    }
251
252
    protected function parseText()
253
    {
254
        $token = $this->expect('text');
255
        if (preg_match('/^(.*?)#\[([^\]\n]+)\]/', $token->value)) {
256
            $block = new Nodes\Block();
257
            $this->parseInlineTags($block, $token->value);
258
259
            return $block;
260
        }
261
        $node = new Nodes\Text($token->value);
262
        $node->line = $this->line();
0 ignored issues
show
Bug introduced by
The property line does not seem to exist in Jade\Nodes\Text.

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...
263
264
        return $node;
265
    }
266
267
    protected function parseBlockExpansion()
268
    {
269
        if (':' === $this->peekType()) {
270
            $this->advance();
271
272
            return new Nodes\Block($this->parseExpression());
273
        }
274
275
        return $this->block();
276
    }
277
278 View Code Duplication
    protected function parseCase()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
279
    {
280
        $value = $this->expect('case')->value;
281
        $node = new Nodes\CaseNode($value);
282
        $node->line = $this->line();
0 ignored issues
show
Bug introduced by
The property line does not seem to exist in Jade\Nodes\CaseNode.

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...
283
        $node->block = $this->block();
284
285
        return $node;
286
    }
287
288
    protected function parseWhen()
289
    {
290
        $value = $this->expect('when')->value;
291
292
        return new Nodes\When($value, $this->parseBlockExpansion());
293
    }
294
295
    protected function parseDefault()
296
    {
297
        $this->expect('default');
298
299
        return new Nodes\When('default', $this->parseBlockExpansion());
300
    }
301
302
    protected function parseCode()
303
    {
304
        $token = $this->expect('code');
305
        $buffer = isset($token->buffer) ? $token->buffer : false;
306
        $escape = isset($token->escape) ? $token->escape : true;
307
        $node = new Nodes\Code($token->value, $buffer, $escape);
308
        $node->line = $this->line();
0 ignored issues
show
Bug introduced by
The property line does not seem to exist in Jade\Nodes\Code.

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...
309
310
        $i = 1;
311
        while ($this->lookahead($i)->type === 'newline') {
312
            $i++;
313
        }
314
315
        if ($this->lookahead($i)->type === 'indent') {
316
            $this->skip($i - 1);
317
            $node->block = $this->block();
0 ignored issues
show
Bug introduced by
The property block does not seem to exist in Jade\Nodes\Code.

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...
318
        }
319
320
        return $node;
321
    }
322
323
    protected function parseComment()
324
    {
325
        $token = $this->expect('comment');
326
        $node = new Nodes\Comment($token->value, $token->buffer);
327
        $node->line = $this->line();
0 ignored issues
show
Bug introduced by
The property line does not seem to exist in Jade\Nodes\Comment.

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...
328
329
        return $node;
330
    }
331
332
    protected function parseDoctype()
333
    {
334
        $token = $this->expect('doctype');
335
        $node = new Nodes\Doctype($token->value);
336
        $node->line = $this->line();
0 ignored issues
show
Bug introduced by
The property line does not seem to exist in Jade\Nodes\Doctype.

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...
337
338
        return $node;
339
    }
340
341
    protected function parseFilter()
342
    {
343
        $token = $this->expect('filter');
344
        $attributes = $this->accept('attributes');
345
346
        $this->lexer->pipeless = true;
347
        $block = $this->parseTextBlock();
348
        $this->lexer->pipeless = false;
349
350
        $node = new Nodes\Filter($token->value, $block, $attributes);
351
        $node->line = $this->line();
0 ignored issues
show
Bug introduced by
The property line does not seem to exist in Jade\Nodes\Filter.

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...
352
353
        return $node;
354
    }
355
356
    protected function parseEach()
357
    {
358
        $token = $this->expect('each');
359
        $node = new Nodes\Each($token->code, $token->value, $token->key);
360
        $node->line = $this->line();
0 ignored issues
show
Bug introduced by
The property line does not seem to exist in Jade\Nodes\Each.

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...
361
        $node->block = $this->block();
362
        if ($this->peekType() === 'code' && $this->peek()->value === 'else') {
363
            $this->advance();
364
            $node->alternative = $this->block();
0 ignored issues
show
Bug introduced by
The property alternative does not seem to exist in Jade\Nodes\Each.

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...
365
        }
366
367
        return $node;
368
    }
369
370 View Code Duplication
    protected function parseCustomKeyword()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
371
    {
372
        $token = $this->expect('customKeyword');
373
        $node = new Nodes\CustomKeyword($token->value, $token->args);
374
        $node->line = $this->line();
0 ignored issues
show
Bug introduced by
The property line does not seem to exist in Jade\Nodes\CustomKeyword.

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...
375
        if ('indent' === $this->peekType()) {
376
            $node->block = $this->block();
377
        }
378
379
        return $node;
380
    }
381
382
    protected function parseExtends()
383
    {
384
        $extendValue = $this->expect('extends')->value;
385
        $path = $this->getTemplatePath($extendValue);
386
387
        $string = $this->getTemplateContents($path, $extendValue);
388
        $parser = new static($string, $path, $this->options);
389
        // need to be a reference, or be seted after the parse loop
390
        $parser->blocks = &$this->blocks;
391
        $parser->contexts = $this->contexts;
392
        $this->extending = $parser;
393
394
        return new Nodes\Literal('');
395
    }
396
397
    protected function parseBlock()
398
    {
399
        $block = $this->expect('block');
400
        $mode = $block->mode;
401
        $name = trim($block->value);
402
403
        $block = 'indent' === $this->peekType()
404
            ? $this->block()
405
            : new Nodes\Block(empty($name)
406
                ? new Nodes\MixinBlock()
407
                : new Nodes\Literal('')
408
            );
409
410
        if (isset($this->blocks[$name])) {
411
            $prev = &$this->blocks[$name];
412
413
            switch ($prev->mode) {
414
                case 'append':
415
                    $block->nodes = array_merge($block->nodes, $prev->nodes);
416
                    $prev = $block;
417
                    break;
418
419
                case 'prepend':
420
                    $block->nodes = array_merge($prev->nodes, $block->nodes);
421
                    $prev = $block;
422
                    break;
423
424
                case 'replace':
425
                default:
426
                    break;
427
            }
428
429
            return $this->blocks[$name];
430
        }
431
432
        $block->mode = $mode;
0 ignored issues
show
Bug introduced by
The property mode does not seem to exist in Jade\Nodes\Block.

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...
433
        $this->blocks[$name] = $block;
434
435
        return $block;
436
    }
437
438
    protected function parseInclude()
439
    {
440
        $token = $this->expect('include');
441
        $includeValue = trim($token->value);
442
        $path = $this->getTemplatePath($includeValue);
443
444
        if ($path && !$this->hasValidTemplateExtension($path)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $path of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
445
            return new Nodes\Text(file_get_contents($path));
446
        }
447
448
        $string = $this->getTemplateContents($path, $includeValue);
449
450
        $parser = new static($string, $path, $this->options);
451
        $parser->blocks = $this->blocks;
452
        $parser->mixins = $this->mixins;
453
454
        $this->context($parser);
455
        try {
456
            $ast = $parser->parse();
457
        } catch (\Exception $e) {
458
            throw new \ErrorException($path . ' (' . $parser->lexer->lineno . ') : ' . $e->getMessage(), 27);
459
        }
460
        $this->context();
461
        $ast->filename = $path;
462
463
        if ('indent' === $this->peekType() && method_exists($ast, 'includeBlock')) {
464
            $block = $ast->includeBlock();
465
            if (is_object($block)) {
466
                $handler = count($block->nodes) === 1 && isset($block->nodes[0]->block)
467
                    ? $block->nodes[0]->block
468
                    : $block;
469
                $handler->push($this->block());
470
            }
471
        }
472
473
        return $ast;
474
    }
475
476
    protected function parseCall()
477
    {
478
        $token = $this->expect('call');
479
        $name = $token->value;
480
481
        $arguments = isset($token->arguments)
482
            ? $token->arguments
483
            : null;
484
485
        $mixin = new Nodes\Mixin($name, $arguments, new Nodes\Block(), true);
486
487
        $this->tag($mixin);
488
489
        if ($mixin->block->isEmpty()) {
490
            $mixin->block = null;
491
        }
492
493
        return $mixin;
494
    }
495
496
    protected function parseMixin()
497
    {
498
        $token = $this->expect('mixin');
499
        $name = $token->value;
500
        $arguments = $token->arguments;
501
502
        // definition
503
        if ('indent' === $this->peekType()) {
504
            $mixin = new Nodes\Mixin($name, $arguments, $this->block(), false);
505
            $this->mixins[$name] = $mixin;
506
507
            return $mixin;
508
        }
509
510
        // call
511
        return new Nodes\Mixin($name, $arguments, null, true);
512
    }
513
514
    protected function parseTextBlock()
515
    {
516
        $block = new Nodes\Block();
517
        $block->line = $this->line();
0 ignored issues
show
Bug introduced by
The property line does not seem to exist in Jade\Nodes\Block.

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...
518
        $spaces = $this->expect('indent')->value;
519
520
        if (!isset($this->_spaces)) {
521
            $this->_spaces = $spaces;
0 ignored issues
show
Bug introduced by
The property _spaces does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
522
        }
523
524
        $indent = str_repeat(' ', $spaces - $this->_spaces + 1);
525
526
        while ($this->peekType() != 'outdent') {
527
            switch ($this->peekType()) {
528
                case 'newline':
529
                    $this->lexer->advance();
530
                    break;
531
532
                case 'indent':
533
                    foreach ($this->parseTextBlock()->nodes as $n) {
534
                        $block->push($n);
535
                    }
536
                    break;
537
538
                default:
539
                    $this->parseInlineTags($block, $indent . $this->advance()->value);
540
            }
541
        }
542
543
        if (isset($this->_spaces) && $spaces === $this->_spaces) {
544
            unset($this->_spaces);
545
        }
546
547
        $this->expect('outdent');
548
549
        return $block;
550
    }
551
552
    protected function block()
553
    {
554
        $block = new Nodes\Block();
555
        $block->line = $this->line();
0 ignored issues
show
Bug introduced by
The property line does not seem to exist in Jade\Nodes\Block.

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...
556
        $this->expect('indent');
557
558
        while ($this->peekType() !== 'outdent') {
559
            if ($this->peekType() === 'newline') {
560
                $this->lexer->advance();
561
                continue;
562
            }
563
564
            $block->push($this->parseExpression());
565
        }
566
567
        $this->expect('outdent');
568
569
        return $block;
570
    }
571
572
    protected function parseInterpolation()
573
    {
574
        $token = $this->advance();
575
        $tag = new Nodes\Tag($token->value);
576
        $tag->buffer = true;
0 ignored issues
show
Bug introduced by
The property buffer does not seem to exist in Jade\Nodes\Tag.

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...
577
578
        return $this->tag($tag);
579
    }
580
581
    protected function parseASTFilter()
582
    {
583
        $token = $this->expect('tag');
584
        $attributes = $this->accept('attributes');
585
        $this->expect(':');
586
        $block = $this->block();
587
        $node = new Nodes\Filter($token->value, $block, $attributes);
588
        $node->line = $this->line();
0 ignored issues
show
Bug introduced by
The property line does not seem to exist in Jade\Nodes\Filter.

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...
589
590
        return $node;
591
    }
592
593
    protected function parseTag()
594
    {
595
        $i = 2;
596
597
        if ('attributes' === $this->lookahead($i)->type) {
598
            $i++;
599
        }
600
601
        if (':' === $this->lookahead($i)->type) {
602
            $i++;
603
604
            if ('indent' === $this->lookahead($i)->type) {
605
                return $this->parseASTFilter();
606
            }
607
        }
608
609
        $token = $this->advance();
610
        $tag = new Nodes\Tag($token->value);
611
612
        $tag->selfClosing = isset($token->selfClosing)
613
            ? $token->selfClosing
614
            : false;
615
616
        return $this->tag($tag);
617
    }
618
619
    public function parseInlineTags($block, $str)
620
    {
621
        while (preg_match('/^(.*?)#\[([^\]\n]+)\]/', $str, $matches)) {
622
            if (!empty($matches[1])) {
623
                $text = new Nodes\Text($matches[1]);
624
                $text->line = $this->line();
0 ignored issues
show
Bug introduced by
The property line does not seem to exist in Jade\Nodes\Text.

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...
625
                $block->push($text);
626
            }
627
            $parser = $this->subParser($matches[2]);
628
            $tag = $parser->parse();
629
            $tag->line = $this->line();
630
            $block->push($tag);
631
            $str = substr($str, strlen($matches[0]));
632
        }
633
        if (substr($str, 0, 1) === ' ') {
634
            $str = substr($str, 1);
635
        }
636
        $text = new Nodes\Text($str);
637
        $text->line = $this->line();
638
        $block->push($text);
639
    }
640
641
    protected function peekType()
642
    {
643
        return ($peek = $this->peek())
644
            ? $peek->type
645
            : null;
646
    }
647
648
    protected function tag($tag)
649
    {
650
        $tag->line = $this->line();
651
652
        while (true) {
653
            switch ($type = $this->peekType()) {
654
                case 'id':
655
                    $token = $this->advance();
656
                    $peek = $this->peek();
657
                    $escaped = isset($peek->escaped, $peek->escaped[$type]) && $peek->escaped[$type];
658
                    $value = $escaped || !isset($peek->attributes, $peek->attributes[$type])
659
                        ? "'" . $token->value . "'"
660
                        : $peek->attributes[$type];
661
                    $tag->setAttribute($token->type, $value, $escaped);
662
                    unset($peek->attributes[$type]);
663
                    continue;
664
665
                case 'class':
666
                    $token = $this->advance();
667
                    $tag->setAttribute($token->type, "'" . $token->value . "'");
668
                    continue;
669
670
                case 'attributes':
671
                    $token = $this->advance();
672
                    $obj = $token->attributes;
673
                    $escaped = $token->escaped;
674
                    $nameList = array_keys($obj);
675
676
                    if ($token->selfClosing) {
677
                        $tag->selfClosing = true;
678
                    }
679
680
                    foreach ($nameList as $name) {
681
                        $value = $obj[$name];
682
                        $normalizedValue = strtolower($value);
683
                        if ($normalizedValue === 'true' || $normalizedValue === 'false') {
684
                            $value = $normalizedValue === 'true';
685
                        }
686
                        $tag->setAttribute($name, $value, $escaped[$name]);
687
                    }
688
                    continue;
689
690
                case '&attributes':
691
                    $token = $this->advance();
692
                    $tag->setAttribute('&attributes', $token->value);
693
                    continue;
694
695
                default:
696
                    break 2;
697
            }
698
        }
699
700
        $dot = false;
701
        $tag->textOnly = false;
702
        if ('.' === $this->peek()->value) {
703
            $dot = $tag->textOnly = true;
704
            $this->advance();
705
        }
706
707
        switch ($this->peekType()) {
708
            case 'text':
709
                $this->parseInlineTags($tag->block, $this->expect('text')->value);
710
                break;
711
712
            case 'code':
713
                $tag->code = $this->parseCode();
714
                break;
715
716
            case ':':
717
                $this->advance();
718
                $tag->block = new Nodes\Block();
719
                $tag->block->push($this->parseExpression());
720
                break;
721
        }
722
723
        while ('newline' === $this->peekType()) {
724
            $this->advance();
725
        }
726
727
        if ('script' === $tag->name) {
728
            $type = $tag->getAttribute('type');
729
730
            if ($type !== null) {
731
                $type = preg_replace('/^[\'\"]|[\'\"]$/', '', $type);
732
733
                if (!$dot && 'text/javascript' != $type['value']) {
734
                    $tag->textOnly = false;
735
                }
736
            }
737
        }
738
739
        if ('indent' === $this->peekType()) {
740
            if ($tag->textOnly) {
741
                $this->lexer->pipeless = true;
742
                $tag->block = $this->parseTextBlock();
743
                $this->lexer->pipeless = false;
744
745
                return $tag;
746
            }
747
748
            $block = $this->block();
749
750
            if ($tag->block && !$tag->block->isEmpty()) {
751
                foreach ($block->nodes as $n) {
752
                    $tag->block->push($n);
753
                }
754
755
                return $tag;
756
            }
757
758
            $tag->block = $block;
759
        }
760
761
        return $tag;
762
    }
763
}
764