1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/* |
4
|
|
|
* This file is part of the ILess |
5
|
|
|
* |
6
|
|
|
* For the full copyright and license information, please view the LICENSE |
7
|
|
|
* file that was distributed with this source code. |
8
|
|
|
*/ |
9
|
|
|
|
10
|
|
|
namespace ILess\Node; |
11
|
|
|
|
12
|
|
|
use ILess\Context; |
13
|
|
|
use ILess\Exception\Exception; |
14
|
|
|
use ILess\FileInfo; |
15
|
|
|
use ILess\Node; |
16
|
|
|
use ILess\Output\OutputInterface; |
17
|
|
|
use ILess\Output\StandardOutput; |
18
|
|
|
|
19
|
|
|
/** |
20
|
|
|
* Rule. |
21
|
|
|
*/ |
22
|
|
|
class RuleNode extends Node implements MakeableImportantInterface, MarkableAsReferencedInterface |
23
|
|
|
{ |
24
|
|
|
/** |
25
|
|
|
* Node type. |
26
|
|
|
* |
27
|
|
|
* @var string |
28
|
|
|
*/ |
29
|
|
|
protected $type = 'Rule'; |
30
|
|
|
|
31
|
|
|
/** |
32
|
|
|
* The name. |
33
|
|
|
* |
34
|
|
|
* @var string |
35
|
|
|
*/ |
36
|
|
|
public $name; |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* Important keyword ( "!important"). |
40
|
|
|
* |
41
|
|
|
* @var string |
42
|
|
|
*/ |
43
|
|
|
public $important; |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* Current index. |
47
|
|
|
* |
48
|
|
|
* @var int |
49
|
|
|
*/ |
50
|
|
|
public $index = 0; |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* Inline flag. |
54
|
|
|
* |
55
|
|
|
* @var bool |
56
|
|
|
*/ |
57
|
|
|
public $inline = false; |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* Is variable? |
61
|
|
|
* |
62
|
|
|
* @var bool |
63
|
|
|
*/ |
64
|
|
|
public $variable = false; |
65
|
|
|
|
66
|
|
|
/** |
67
|
|
|
* Merge flag. |
68
|
|
|
* |
69
|
|
|
* @var bool |
70
|
|
|
*/ |
71
|
|
|
public $merge = false; |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* Constructor. |
75
|
|
|
* |
76
|
|
|
* @param string|array $name The rule name |
77
|
|
|
* @param string|ValueNode $value The value |
78
|
|
|
* @param string $important Important keyword |
79
|
|
|
* @param string|null $merge Merge? |
80
|
|
|
* @param int $index Current index |
81
|
|
|
* @param FileInfo $currentFileInfo The current file info |
82
|
|
|
* @param bool $inline Inline flag |
83
|
|
|
* @param bool|null $variable |
84
|
|
|
*/ |
85
|
|
|
public function __construct( |
86
|
|
|
$name, |
87
|
|
|
$value, |
88
|
|
|
$important = null, |
89
|
|
|
$merge = null, |
90
|
|
|
$index = 0, |
91
|
|
|
FileInfo $currentFileInfo = null, |
92
|
|
|
$inline = false, |
93
|
|
|
$variable = null |
94
|
|
|
) { |
95
|
|
|
parent::__construct(($value instanceof Node) ? $value : new ValueNode([$value])); |
96
|
|
|
|
97
|
|
|
$this->name = $name; |
|
|
|
|
98
|
|
|
$this->important = $important ? ' ' . trim($important) : ''; |
99
|
|
|
$this->merge = $merge; |
|
|
|
|
100
|
|
|
$this->index = $index; |
101
|
|
|
$this->currentFileInfo = $currentFileInfo; |
102
|
|
|
$this->inline = (boolean) $inline; |
103
|
|
|
$this->variable = null !== $variable ? $variable : (is_string($name) && $name[0] === '@'); |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* Compiles the node. |
108
|
|
|
* |
109
|
|
|
* @param Context $context The context |
110
|
|
|
* @param array|null $arguments Array of arguments |
111
|
|
|
* @param bool|null $important Important flag |
112
|
|
|
* |
113
|
|
|
* @return RuleNode |
114
|
|
|
* |
115
|
|
|
* @throws |
116
|
|
|
*/ |
117
|
|
|
public function compile(Context $context, $arguments = null, $important = null) |
118
|
|
|
{ |
119
|
|
|
$strictMathBypass = false; |
120
|
|
|
$return = null; |
121
|
|
|
$name = $this->name; |
122
|
|
|
$variable = $this->variable; |
123
|
|
|
|
124
|
|
|
if (!is_string($name)) { |
125
|
|
|
// expand 'primitive' name directly to get |
126
|
|
|
// things faster (~10% for benchmark.less): |
127
|
|
|
$name = (count($name) === 1 && $name[0] instanceof KeywordNode) ? $name[0]->value : $this->evalName($context, |
128
|
|
|
$name); |
129
|
|
|
$variable = false; // never treat expanded interpolation as new variable name |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
if ($name === 'font' && !$context->strictMath) { |
133
|
|
|
$strictMathBypass = true; |
134
|
|
|
$context->strictMath = true; |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
$e = null; |
138
|
|
|
try { |
139
|
|
|
array_push($context->importantScope, []); |
140
|
|
|
$compiledValue = $this->value->compile($context); |
141
|
|
|
|
142
|
|
|
if (!$this->variable && $compiledValue instanceof DetachedRulesetNode) { |
143
|
|
|
throw new Exception('Rulesets cannot be evaluated on a property.', $this->index, |
144
|
|
|
$this->currentFileInfo); |
145
|
|
|
} |
146
|
|
|
|
147
|
|
|
$important = $this->important; |
148
|
|
|
$importantResult = array_pop($context->importantScope); |
149
|
|
|
|
150
|
|
|
if (!$important && isset($importantResult['important'])) { |
151
|
|
|
$important = $importantResult['important']; |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
$return = new self($name, |
155
|
|
|
$compiledValue, |
156
|
|
|
$important, $this->merge, |
|
|
|
|
157
|
|
|
$this->index, $this->currentFileInfo, |
158
|
|
|
$this->inline, |
159
|
|
|
$variable |
160
|
|
|
); |
161
|
|
|
} catch (Exception $e) { |
162
|
|
|
if ($e instanceof Exception) { |
163
|
|
|
if ($e->getCurrentFile() === null && $e->getIndex() === null) { |
164
|
|
|
$e->setCurrentFile($this->currentFileInfo, $this->index); |
165
|
|
|
} |
166
|
|
|
} |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
if ($strictMathBypass) { |
170
|
|
|
$context->strictMath = false; |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
if (isset($e)) { |
174
|
|
|
throw $e; |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
return $return; |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
/** |
181
|
|
|
* @param Context $context |
182
|
|
|
* @param array $name |
183
|
|
|
* |
184
|
|
|
* @return string |
185
|
|
|
*/ |
186
|
|
|
private function evalName(Context $context, $name) |
187
|
|
|
{ |
188
|
|
|
$output = new StandardOutput(); |
189
|
|
|
for ($i = 0, $n = count($name); $i < $n; ++$i) { |
190
|
|
|
$name[$i]->compile($context)->generateCss($context, $output); |
191
|
|
|
} |
192
|
|
|
|
193
|
|
|
return $output->toString(); |
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
/** |
197
|
|
|
* {@inheritdoc} |
198
|
|
|
*/ |
199
|
|
|
public function generateCSS(Context $context, OutputInterface $output) |
200
|
|
|
{ |
201
|
|
|
$output->add($this->name . ($context->compress ? ':' : ': '), $this->currentFileInfo, $this->index); |
202
|
|
|
try { |
203
|
|
|
$this->value->generateCSS($context, $output); |
204
|
|
|
} catch (Exception $e) { |
205
|
|
|
if ($this->currentFileInfo) { |
206
|
|
|
$e->setCurrentFile($this->currentFileInfo, $this->index); |
207
|
|
|
} |
208
|
|
|
// rethrow |
209
|
|
|
throw $e; |
210
|
|
|
} |
211
|
|
|
$output->add($this->important . (($this->inline || ($context->lastRule && $context->compress)) ? '' : ';'), |
212
|
|
|
$this->currentFileInfo, $this->index); |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
/** |
216
|
|
|
* Makes the node important. |
217
|
|
|
* |
218
|
|
|
* @return RuleNode |
219
|
|
|
*/ |
220
|
|
|
public function makeImportant() |
221
|
|
|
{ |
222
|
|
|
return new self( |
223
|
|
|
$this->name, |
224
|
|
|
$this->value, |
|
|
|
|
225
|
|
|
'!important', |
226
|
|
|
$this->merge, |
|
|
|
|
227
|
|
|
$this->index, |
228
|
|
|
$this->currentFileInfo, |
229
|
|
|
$this->inline |
230
|
|
|
); |
231
|
|
|
} |
232
|
|
|
|
233
|
|
|
/** |
234
|
|
|
* Marks as referenced. |
235
|
|
|
*/ |
236
|
|
|
public function markReferenced() |
237
|
|
|
{ |
238
|
|
|
if ($this->value) { |
239
|
|
|
$this->markReferencedRecursive($this->value); |
240
|
|
|
} |
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
private function markReferencedRecursive(&$value) |
244
|
|
|
{ |
245
|
|
|
if (!is_array($value)) { |
246
|
|
|
if ($value instanceof MarkableAsReferencedInterface) { |
247
|
|
|
$value->markReferenced(); |
248
|
|
|
} |
249
|
|
|
} else { |
250
|
|
|
foreach ($value as &$v) { |
251
|
|
|
$this->markReferencedRecursive($v); |
252
|
|
|
} |
253
|
|
|
} |
254
|
|
|
} |
255
|
|
|
} |
256
|
|
|
|
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.