Passed
Push — master ( d4a329...711fef )
by Jeroen De
03:36
created

Formatter   A

Complexity

Total Complexity 34

Size/Duplication

Total Lines 309
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 89
c 1
b 0
f 0
dl 0
loc 309
rs 9.68
wmc 34

10 Methods

Rating   Name   Duplication   Size   Complexity  
A blockLines() 0 9 2
B block() 0 36 9
A customProperty() 0 3 1
A testEmptyChildren() 0 19 6
B write() 0 43 9
A blockChildren() 0 4 2
A blockSelectors() 0 7 1
A format() 0 19 2
A property() 0 3 1
A indentStr() 0 3 1
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;
14
15
use ScssPhp\ScssPhp\Formatter\OutputBlock;
16
use ScssPhp\ScssPhp\SourceMap\SourceMapGenerator;
17
18
/**
19
 * Base formatter
20
 *
21
 * @author Leaf Corcoran <[email protected]>
22
 */
23
abstract class Formatter
24
{
25
    /**
26
     * @var integer
27
     */
28
    public $indentLevel;
29
30
    /**
31
     * @var string
32
     */
33
    public $indentChar;
34
35
    /**
36
     * @var string
37
     */
38
    public $break;
39
40
    /**
41
     * @var string
42
     */
43
    public $open;
44
45
    /**
46
     * @var string
47
     */
48
    public $close;
49
50
    /**
51
     * @var string
52
     */
53
    public $tagSeparator;
54
55
    /**
56
     * @var string
57
     */
58
    public $assignSeparator;
59
60
    /**
61
     * @var boolean
62
     */
63
    public $keepSemicolons;
64
65
    /**
66
     * @var \ScssPhp\ScssPhp\Formatter\OutputBlock
67
     */
68
    protected $currentBlock;
69
70
    /**
71
     * @var integer
72
     */
73
    protected $currentLine;
74
75
    /**
76
     * @var integer
77
     */
78
    protected $currentColumn;
79
80
    /**
81
     * @var \ScssPhp\ScssPhp\SourceMap\SourceMapGenerator
82
     */
83
    protected $sourceMapGenerator;
84
85
    /**
86
     * @var string
87
     */
88
    protected $strippedSemicolon;
89
90
    /**
91
     * Initialize formatter
92
     *
93
     * @api
94
     */
95
    abstract public function __construct();
96
97
    /**
98
     * Return indentation (whitespace)
99
     *
100
     * @return string
101
     */
102
    protected function indentStr()
103
    {
104
        return '';
105
    }
106
107
    /**
108
     * Return property assignment
109
     *
110
     * @api
111
     *
112
     * @param string $name
113
     * @param mixed  $value
114
     *
115
     * @return string
116
     */
117
    public function property($name, $value)
118
    {
119
        return rtrim($name) . $this->assignSeparator . $value . ';';
120
    }
121
122
    /**
123
     * Return custom property assignment
124
     * differs in that you have to keep spaces in the value as is
125
     *
126
     * @api
127
     *
128
     * @param string $name
129
     * @param mixed  $value
130
     *
131
     * @return string
132
     */
133
    public function customProperty($name, $value)
134
    {
135
        return rtrim($name) . trim($this->assignSeparator) . $value . ';';
136
    }
137
138
    /**
139
     * Output lines inside a block
140
     *
141
     * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
142
     */
143
    protected function blockLines(OutputBlock $block)
144
    {
145
        $inner = $this->indentStr();
146
        $glue  = $this->break . $inner;
147
148
        $this->write($inner . implode($glue, $block->lines));
149
150
        if (! empty($block->children)) {
151
            $this->write($this->break);
152
        }
153
    }
154
155
    /**
156
     * Output block selectors
157
     *
158
     * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
159
     */
160
    protected function blockSelectors(OutputBlock $block)
161
    {
162
        $inner = $this->indentStr();
163
164
        $this->write($inner
165
            . implode($this->tagSeparator, $block->selectors)
166
            . $this->open . $this->break);
167
    }
168
169
    /**
170
     * Output block children
171
     *
172
     * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
173
     */
174
    protected function blockChildren(OutputBlock $block)
175
    {
176
        foreach ($block->children as $child) {
177
            $this->block($child);
178
        }
179
    }
180
181
    /**
182
     * Output non-empty block
183
     *
184
     * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
185
     */
186
    protected function block(OutputBlock $block)
187
    {
188
        if (empty($block->lines) && empty($block->children)) {
189
            return;
190
        }
191
192
        $this->currentBlock = $block;
193
194
        $pre = $this->indentStr();
195
196
        if (! empty($block->selectors)) {
197
            $this->blockSelectors($block);
198
199
            $this->indentLevel++;
200
        }
201
202
        if (! empty($block->lines)) {
203
            $this->blockLines($block);
204
        }
205
206
        if (! empty($block->children)) {
207
            $this->blockChildren($block);
208
        }
209
210
        if (! empty($block->selectors)) {
211
            $this->indentLevel--;
212
213
            if (! $this->keepSemicolons) {
214
                $this->strippedSemicolon = '';
215
            }
216
217
            if (empty($block->children)) {
218
                $this->write($this->break);
219
            }
220
221
            $this->write($pre . $this->close . $this->break);
222
        }
223
    }
224
225
    /**
226
     * Test and clean safely empty children
227
     *
228
     * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
229
     *
230
     * @return boolean
231
     */
232
    protected function testEmptyChildren($block)
233
    {
234
        $isEmpty = empty($block->lines);
235
236
        if ($block->children) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $block->children of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
237
            foreach ($block->children as $k => &$child) {
238
                if (! $this->testEmptyChildren($child)) {
239
                    $isEmpty = false;
240
                    continue;
241
                }
242
243
                if ($child->type === Type::T_MEDIA || $child->type === Type::T_DIRECTIVE) {
244
                    $child->children = [];
245
                    $child->selectors = null;
246
                }
247
            }
248
        }
249
250
        return $isEmpty;
251
    }
252
253
    /**
254
     * Entry point to formatting a block
255
     *
256
     * @api
257
     *
258
     * @param \ScssPhp\ScssPhp\Formatter\OutputBlock             $block              An abstract syntax tree
259
     * @param \ScssPhp\ScssPhp\SourceMap\SourceMapGenerator|null $sourceMapGenerator Optional source map generator
260
     *
261
     * @return string
262
     */
263
    public function format(OutputBlock $block, SourceMapGenerator $sourceMapGenerator = null)
264
    {
265
        $this->sourceMapGenerator = null;
266
267
        if ($sourceMapGenerator) {
268
            $this->currentLine        = 1;
269
            $this->currentColumn      = 0;
270
            $this->sourceMapGenerator = $sourceMapGenerator;
271
        }
272
273
        $this->testEmptyChildren($block);
274
275
        ob_start();
276
277
        $this->block($block);
278
279
        $out = ob_get_clean();
280
281
        return $out;
282
    }
283
284
    /**
285
     * Output content
286
     *
287
     * @param string $str
288
     */
289
    protected function write($str)
290
    {
291
        if (! empty($this->strippedSemicolon)) {
292
            echo $this->strippedSemicolon;
293
294
            $this->strippedSemicolon = '';
295
        }
296
297
        /*
298
         * Maybe Strip semi-colon appended by property(); it's a separator, not a terminator
299
         * will be striped for real before a closing, otherwise displayed unchanged starting the next write
300
         */
301
        if (
302
            ! $this->keepSemicolons &&
303
            $str &&
304
            (strpos($str, ';') !== false) &&
305
            (substr($str, -1) === ';')
306
        ) {
307
            $str = substr($str, 0, -1);
308
309
            $this->strippedSemicolon = ';';
310
        }
311
312
        if ($this->sourceMapGenerator) {
313
            $this->sourceMapGenerator->addMapping(
314
                $this->currentLine,
315
                $this->currentColumn,
316
                $this->currentBlock->sourceLine,
317
                //columns from parser are off by one
318
                $this->currentBlock->sourceColumn > 0 ? $this->currentBlock->sourceColumn - 1 : 0,
319
                $this->currentBlock->sourceName
320
            );
321
322
            $lines = explode("\n", $str);
323
            $lineCount = \count($lines);
324
            $this->currentLine += $lineCount - 1;
325
326
            $lastLine = array_pop($lines);
327
328
            $this->currentColumn = ($lineCount === 1 ? $this->currentColumn : 0) + \strlen($lastLine);
329
        }
330
331
        echo $str;
332
    }
333
}
334