1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* SCSSPHP |
5
|
|
|
* |
6
|
|
|
* @copyright 2012-2020 Leaf Corcoran |
7
|
|
|
* |
8
|
|
|
* @license http://opensource.org/licenses/MIT MIT |
9
|
|
|
* |
10
|
|
|
* @link http://scssphp.github.io/scssphp |
11
|
|
|
*/ |
12
|
|
|
|
13
|
|
|
namespace ScssPhp\ScssPhp\Formatter; |
14
|
|
|
|
15
|
|
|
use ScssPhp\ScssPhp\Formatter; |
16
|
|
|
use ScssPhp\ScssPhp\Formatter\OutputBlock; |
17
|
|
|
use ScssPhp\ScssPhp\Type; |
18
|
|
|
|
19
|
|
|
/** |
20
|
|
|
* Nested formatter |
21
|
|
|
* |
22
|
|
|
* @author Leaf Corcoran <[email protected]> |
23
|
|
|
*/ |
24
|
|
|
class Nested extends Formatter |
25
|
|
|
{ |
26
|
|
|
/** |
27
|
|
|
* @var integer |
28
|
|
|
*/ |
29
|
|
|
private $depth; |
30
|
|
|
|
31
|
|
|
/** |
32
|
|
|
* {@inheritdoc} |
33
|
|
|
*/ |
34
|
|
|
public function __construct() |
35
|
|
|
{ |
36
|
|
|
$this->indentLevel = 0; |
37
|
|
|
$this->indentChar = ' '; |
38
|
|
|
$this->break = "\n"; |
39
|
|
|
$this->open = ' {'; |
40
|
|
|
$this->close = ' }'; |
41
|
|
|
$this->tagSeparator = ', '; |
42
|
|
|
$this->assignSeparator = ': '; |
43
|
|
|
$this->keepSemicolons = true; |
44
|
|
|
} |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* {@inheritdoc} |
48
|
|
|
*/ |
49
|
|
|
protected function indentStr() |
50
|
|
|
{ |
51
|
|
|
$n = $this->depth - 1; |
52
|
|
|
|
53
|
|
|
return str_repeat($this->indentChar, max($this->indentLevel + $n, 0)); |
54
|
|
|
} |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* {@inheritdoc} |
58
|
|
|
*/ |
59
|
|
|
protected function blockLines(OutputBlock $block) |
60
|
|
|
{ |
61
|
|
|
$inner = $this->indentStr(); |
62
|
|
|
$glue = $this->break . $inner; |
63
|
|
|
|
64
|
|
|
foreach ($block->lines as $index => $line) { |
65
|
|
|
if (substr($line, 0, 2) === '/*') { |
66
|
|
|
$block->lines[$index] = preg_replace('/\r\n?|\n|\f/', $this->break, $line); |
67
|
|
|
} |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
$this->write($inner . implode($glue, $block->lines)); |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* {@inheritdoc} |
75
|
|
|
*/ |
76
|
|
|
protected function block(OutputBlock $block) |
77
|
|
|
{ |
78
|
|
|
static $depths; |
79
|
|
|
static $downLevel; |
80
|
|
|
static $closeBlock; |
81
|
|
|
static $previousEmpty; |
82
|
|
|
static $previousHasSelector; |
83
|
|
|
|
84
|
|
|
if ($block->type === 'root') { |
85
|
|
|
$depths = [ 0 ]; |
86
|
|
|
$downLevel = ''; |
87
|
|
|
$closeBlock = ''; |
88
|
|
|
$this->depth = 0; |
89
|
|
|
$previousEmpty = false; |
90
|
|
|
$previousHasSelector = false; |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
$isMediaOrDirective = \in_array($block->type, [Type::T_DIRECTIVE, Type::T_MEDIA]); |
94
|
|
|
$isSupport = ($block->type === Type::T_DIRECTIVE |
95
|
|
|
&& $block->selectors && strpos(implode('', $block->selectors), '@supports') !== false); |
|
|
|
|
96
|
|
|
|
97
|
|
|
while ($block->depth < end($depths) || ($block->depth == 1 && end($depths) == 1)) { |
98
|
|
|
array_pop($depths); |
99
|
|
|
$this->depth--; |
100
|
|
|
|
101
|
|
|
if ( |
102
|
|
|
! $this->depth && ($block->depth <= 1 || (! $this->indentLevel && $block->type === Type::T_COMMENT)) && |
103
|
|
|
(($block->selectors && ! $isMediaOrDirective) || $previousHasSelector) |
|
|
|
|
104
|
|
|
) { |
105
|
|
|
$downLevel = $this->break; |
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
if (empty($block->lines) && empty($block->children)) { |
109
|
|
|
$previousEmpty = true; |
110
|
|
|
} |
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
if (empty($block->lines) && empty($block->children)) { |
114
|
|
|
return; |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
$this->currentBlock = $block; |
118
|
|
|
|
119
|
|
|
if (! empty($block->lines) || (! empty($block->children) && ($this->depth < 1 || $isSupport))) { |
120
|
|
|
if ($block->depth > end($depths)) { |
121
|
|
|
if (! $previousEmpty || $this->depth < 1) { |
122
|
|
|
$this->depth++; |
123
|
|
|
|
124
|
|
|
$depths[] = $block->depth; |
125
|
|
|
} else { |
126
|
|
|
// keep the current depth unchanged but take the block depth as a new reference for following blocks |
127
|
|
|
array_pop($depths); |
128
|
|
|
|
129
|
|
|
$depths[] = $block->depth; |
130
|
|
|
} |
131
|
|
|
} |
132
|
|
|
} |
133
|
|
|
|
134
|
|
|
$previousEmpty = ($block->type === Type::T_COMMENT); |
135
|
|
|
$previousHasSelector = false; |
136
|
|
|
|
137
|
|
|
if (! empty($block->selectors)) { |
138
|
|
|
if ($closeBlock) { |
139
|
|
|
$this->write($closeBlock); |
140
|
|
|
$closeBlock = ''; |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
if ($downLevel) { |
144
|
|
|
$this->write($downLevel); |
145
|
|
|
$downLevel = ''; |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
$this->blockSelectors($block); |
149
|
|
|
|
150
|
|
|
$this->indentLevel++; |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
if (! empty($block->lines)) { |
154
|
|
|
if ($closeBlock) { |
155
|
|
|
$this->write($closeBlock); |
156
|
|
|
$closeBlock = ''; |
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
if ($downLevel) { |
160
|
|
|
$this->write($downLevel); |
161
|
|
|
$downLevel = ''; |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
$this->blockLines($block); |
165
|
|
|
|
166
|
|
|
$closeBlock = $this->break; |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
if (! empty($block->children)) { |
170
|
|
|
if ($this->depth > 0 && ($isMediaOrDirective || ! $this->hasFlatChild($block))) { |
171
|
|
|
array_pop($depths); |
172
|
|
|
|
173
|
|
|
$this->depth--; |
174
|
|
|
$this->blockChildren($block); |
175
|
|
|
$this->depth++; |
176
|
|
|
|
177
|
|
|
$depths[] = $block->depth; |
178
|
|
|
} else { |
179
|
|
|
$this->blockChildren($block); |
180
|
|
|
} |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
// reclear to not be spoiled by children if T_DIRECTIVE |
184
|
|
|
if ($block->type === Type::T_DIRECTIVE) { |
185
|
|
|
$previousHasSelector = false; |
|
|
|
|
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
if (! empty($block->selectors)) { |
189
|
|
|
$this->indentLevel--; |
190
|
|
|
|
191
|
|
|
if (! $this->keepSemicolons) { |
192
|
|
|
$this->strippedSemicolon = ''; |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
$this->write($this->close); |
196
|
|
|
|
197
|
|
|
$closeBlock = $this->break; |
198
|
|
|
|
199
|
|
|
if ($this->depth > 1 && ! empty($block->children)) { |
200
|
|
|
array_pop($depths); |
201
|
|
|
$this->depth--; |
202
|
|
|
} |
203
|
|
|
|
204
|
|
|
if (! $isMediaOrDirective) { |
205
|
|
|
$previousHasSelector = true; |
206
|
|
|
} |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
if ($block->type === 'root') { |
210
|
|
|
$this->write($this->break); |
211
|
|
|
} |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
/** |
215
|
|
|
* Block has flat child |
216
|
|
|
* |
217
|
|
|
* @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block |
218
|
|
|
* |
219
|
|
|
* @return boolean |
220
|
|
|
*/ |
221
|
|
|
private function hasFlatChild($block) |
222
|
|
|
{ |
223
|
|
|
foreach ($block->children as $child) { |
224
|
|
|
if (empty($child->selectors)) { |
225
|
|
|
return true; |
226
|
|
|
} |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
return false; |
230
|
|
|
} |
231
|
|
|
} |
232
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.