Passed
Push — master ( c776c7...570285 )
by Kirill
04:05
created

HTMLSyntax   A

Complexity

Total Complexity 28

Size/Duplication

Total Lines 175
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 102
c 1
b 0
f 0
dl 0
loc 175
rs 10
wmc 28

3 Methods

Rating   Name   Duplication   Size   Complexity  
D handle() 0 100 24
A parseVerbatim() 0 16 3
A flush() 0 5 1
1
<?php
2
3
/**
4
 * Spiral Framework.
5
 *
6
 * @license   MIT
7
 * @author    Anton Titov (Wolfy-J)
8
 */
9
10
declare(strict_types=1);
11
12
namespace Spiral\Stempler\Parser\Syntax;
13
14
use Spiral\Stempler\Exception\SyntaxException;
15
use Spiral\Stempler\Lexer\Grammar\HTMLGrammar;
16
use Spiral\Stempler\Lexer\Token;
17
use Spiral\Stempler\Node\HTML\Attr;
18
use Spiral\Stempler\Node\HTML\Nil;
19
use Spiral\Stempler\Node\HTML\Tag;
20
use Spiral\Stempler\Node\HTML\Verbatim;
21
use Spiral\Stempler\Node\Mixin;
22
use Spiral\Stempler\Node\Raw;
23
use Spiral\Stempler\Parser;
24
use Spiral\Stempler\Parser\Assembler;
25
use Spiral\Stempler\Parser\SyntaxInterface;
26
27
/**
28
 * HTML tags and attributes.
29
 */
30
final class HTMLSyntax implements SyntaxInterface
31
{
32
    use Parser\Syntax\Traits\MixinTrait;
0 ignored issues
show
introduced by
The trait Spiral\Stempler\Parser\Syntax\Traits\MixinTrait requires some properties which are not provided by Spiral\Stempler\Parser\Syntax\HTMLSyntax: $tokens, $type, $content
Loading history...
33
34
    // list of tags which are closed automatically (http://xahlee.info/js/html5_non-closing_tag.html)
35
    private const VOID_TAGS = [
36
        'area',
37
        'base',
38
        'br',
39
        'col',
40
        'embed',
41
        'hr',
42
        'img',
43
        'input',
44
        'link',
45
        'meta',
46
        'param',
47
        'source',
48
        'track',
49
        'wbr'
50
    ];
51
52
    // list of attributes which define verbatim blocks (https://www.w3schools.com/tags/ref_eventattributes.asp)
53
    private const VERBATIM_ATTRIBUTES = [
54
        'style',
55
        'on*'
56
    ];
57
58
    /** @var Tag|null */
59
    private $node;
60
61
    /** @var Token|null */
62
    private $token;
63
64
    /** @var Attr|null */
65
    private $attr;
66
67
    /**
68
     * @inheritDoc
69
     */
70
    public function handle(Parser $parser, Assembler $asm, Token $token): void
71
    {
72
        switch ($token->type) {
73
            case HTMLGrammar::TYPE_OPEN:
74
            case HTMLGrammar::TYPE_OPEN_SHORT:
75
                $this->node = new Tag(new Parser\Context($token, $parser->getPath()));
76
                $this->token = $token;
77
78
                break;
79
80
            case HTMLGrammar::TYPE_KEYWORD:
81
                if ($this->node->name === null) {
82
                    $this->node->name = $this->parseToken($parser, $token);
83
                    return;
84
                }
85
86
                if ($this->attr !== null && !$this->attr->value instanceof Nil) {
87
                    $this->attr->value = $this->parseToken($parser, $token);
88
                    $this->attr = null;
89
                    break;
90
                }
91
92
                $this->attr = new Attr(
93
                    $this->parseToken($parser, $token),
94
                    new Nil(),
95
                    new Parser\Context($token, $parser->getPath())
96
                );
97
98
                $this->node->attrs[] = $this->attr;
99
                break;
100
101
            case HTMLGrammar::TYPE_EQUAL:
102
                if ($this->attr === null) {
103
                    throw new SyntaxException('unexpected attribute token', $token);
104
                }
105
106
                // expect the value
107
                $this->attr->value = null;
108
                break;
109
110
            case HTMLGrammar::TYPE_ATTRIBUTE:
111
                if ($this->attr === null) {
112
                    throw new SyntaxException('unexpected attribute token', $token);
113
                }
114
115
                if (
116
                    is_string($this->attr->name)
117
                    && (
118
                        strpos($this->attr->name, 'on') === 0
119
                        || in_array($this->attr->name, self::VERBATIM_ATTRIBUTES, true)
120
                    )
121
                ) {
122
                    $this->attr->value = $this->parseVerbatim($parser, $token);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->parseVerbatim($parser, $token) of type Spiral\Stempler\Node\HTML\Verbatim is incompatible with the declared type Spiral\Stempler\Node\HTM...mpler\Node\Mixin|string of property $value.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
123
                } else {
124
                    $this->attr->value = $this->parseToken($parser, $token);
125
                }
126
127
                $this->attr = null;
128
                break;
129
130
            case HTMLGrammar::TYPE_CLOSE_SHORT:
131
                $this->node->void = true;
132
                $asm->push($this->node);
0 ignored issues
show
Bug introduced by
It seems like $this->node can also be of type null; however, parameter $node of Spiral\Stempler\Parser\Assembler::push() does only seem to accept Spiral\Stempler\Node\NodeInterface, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

132
                $asm->push(/** @scrutinizer ignore-type */ $this->node);
Loading history...
133
                $this->flush();
134
                break;
135
136
            case HTMLGrammar::TYPE_CLOSE:
137
                if ($this->token->type == HTMLGrammar::TYPE_OPEN_SHORT) {
138
                    if (!$asm->getNode() instanceof Tag || $asm->getNode()->name !== $this->node->name) {
139
                        throw new SyntaxException(
140
                            "Invalid closing tag `{$this->node->name}`, expected `{$asm->getNode()->name}`",
141
                            $this->token
0 ignored issues
show
Bug introduced by
It seems like $this->token can also be of type null; however, parameter $context of Spiral\Stempler\Exceptio...xception::__construct() does only seem to accept Spiral\Stempler\Lexer\Token, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

141
                            /** @scrutinizer ignore-type */ $this->token
Loading history...
142
                        );
143
                    }
144
145
                    $asm->close();
146
                } else {
147
                    if (in_array($this->node->name, self::VOID_TAGS)) {
148
                        $this->node->void = true;
149
                        $asm->push($this->node);
150
                    } else {
151
                        $asm->open($this->node, 'nodes');
0 ignored issues
show
Bug introduced by
It seems like $this->node can also be of type null; however, parameter $node of Spiral\Stempler\Parser\Assembler::open() does only seem to accept Spiral\Stempler\Node\NodeInterface, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

151
                        $asm->open(/** @scrutinizer ignore-type */ $this->node, 'nodes');
Loading history...
152
                    }
153
                }
154
                $this->flush();
155
156
                break;
157
158
            case HTMLGrammar::TYPE_VERBATIM:
159
                $asm->push($this->parseVerbatim($parser, $token));
160
                break;
161
162
            default:
163
                if ($asm->getNode() instanceof Mixin || $asm->getNode() instanceof Verbatim) {
164
                    $node = $this->parseToken($parser, $token);
165
                    if (is_string($node)) {
0 ignored issues
show
introduced by
The condition is_string($node) is always false.
Loading history...
166
                        $node = new Raw($node, new Parser\Context($token, $parser->getPath()));
167
                    }
168
169
                    $asm->push($node);
170
                }
171
        }
172
    }
173
174
    /**
175
     * Flush open nodes and tokens.
176
     */
177
    private function flush(): void
178
    {
179
        $this->node = null;
180
        $this->token = null;
181
        $this->attr = null;
182
    }
183
184
    /**
185
     * @param Parser $parser
186
     * @param Token  $token
187
     * @return Verbatim
188
     */
189
    private function parseVerbatim(Parser $parser, Token $token): Verbatim
190
    {
191
        $verbatim = new Verbatim(new Parser\Context($token, $parser->getPath()));
192
193
        if ($token->tokens === []) {
194
            if ($token->content) {
195
                $verbatim->nodes[] = $token->content;
196
            }
197
        } else {
198
            $parser->parseTokens(
199
                new Assembler($verbatim, 'nodes'),
200
                $token->tokens
201
            );
202
        }
203
204
        return $verbatim;
205
    }
206
}
207