@@ -15,16 +15,21 @@ function indent(size) {
1515 * @returns A portion of the CSS
1616 */
1717function substr ( node , css ) {
18- if ( node . loc === null ) return ''
19- let str = css . substring ( node . loc . start . offset , node . loc . end . offset )
18+ let loc = node . loc
19+
20+ if ( ! loc ) return ''
21+
22+ let start = loc . start
23+ let end = loc . end
24+ let str = css . substring ( start . offset , end . offset )
2025
2126 // Single-line node, most common case
22- if ( node . loc . start . line === node . loc . end . line ) {
27+ if ( start . line === end . line ) {
2328 return str
2429 }
2530
2631 // Multi-line nodes, not common
27- return str . split ( '\n' ) . map ( part => part . trim ( ) ) . join ( ' ' )
32+ return str . replace ( / \s + / g , ' ' )
2833}
2934
3035/**
@@ -33,7 +38,7 @@ function substr(node, css) {
3338 * @returns A portion of the CSS
3439 */
3540function substr_raw ( node , css ) {
36- if ( node . loc === null ) return ''
41+ if ( ! node . loc ) return ''
3742 return css . substring ( node . loc . start . offset , node . loc . end . offset )
3843}
3944
@@ -44,14 +49,12 @@ function substr_raw(node, css) {
4449 * @returns {string } A formatted Rule
4550 */
4651function print_rule ( node , indent_level , css ) {
47- let buffer = ''
52+ let buffer
4853
49- if ( node . prelude !== null ) {
50- if ( node . prelude . type === 'SelectorList' ) {
51- buffer += print_selectorlist ( node . prelude , indent_level , css )
52- } else {
53- buffer += print_unknown ( node . prelude , indent_level , css )
54- }
54+ if ( node . prelude . type === 'SelectorList' ) {
55+ buffer = print_selectorlist ( node . prelude , indent_level , css )
56+ } else {
57+ buffer = print_unknown ( node . prelude , indent_level , css )
5558 }
5659
5760 if ( node . block !== null && node . block . type === 'Block' ) {
@@ -71,25 +74,21 @@ function print_selectorlist(node, indent_level, css) {
7174 let buffer = ''
7275
7376 for ( let selector of node . children ) {
74- if ( selector !== node . children . first ) {
75- buffer += '\n'
76- }
77-
7877 if ( selector . type === 'Selector' ) {
7978 buffer += print_selector ( selector , indent_level , css )
8079 } else {
8180 buffer += print_unknown ( selector , indent_level , css )
8281 }
8382
8483 if ( selector !== node . children . last ) {
85- buffer += ','
84+ buffer += `,\n`
8685 }
8786 }
8887 return buffer
8988}
9089
9190/**
92- * @param {import('css-tree').Selector } node
91+ * @param {import('css-tree').Selector|import('css-tree').PseudoClassSelector } node
9392 * @param {string } css
9493 */
9594function print_simple_selector ( node , css ) {
@@ -129,20 +128,18 @@ function print_simple_selector(node, css) {
129128 if ( child . nth . type === 'AnPlusB' ) {
130129 let a = child . nth . a
131130 let b = child . nth . b
132- let hasA = a !== null
133- let hasB = b !== null
134131
135- if ( hasA ) {
132+ if ( a !== null ) {
136133 buffer += a + 'n'
137134 }
138135
139- if ( hasA && hasB ) {
136+ if ( a !== null && b !== null ) {
140137 buffer += ' '
141138 }
142139
143- if ( hasB ) {
140+ if ( b !== null ) {
144141 // When (1n + x) but not (1n - x)
145- if ( hasA && ! b . startsWith ( '-' ) ) {
142+ if ( a !== null && ! b . startsWith ( '-' ) ) {
146143 buffer += '+ '
147144 }
148145
@@ -202,14 +199,14 @@ function print_block(node, indent_level, css) {
202199
203200 for ( let child of children ) {
204201 if ( child . type === 'Declaration' ) {
205- buffer += print_declaration ( child , indent_level , css )
202+ buffer += print_declaration ( child , indent_level , css ) + ';'
206203 } else if ( child . type === 'Rule' ) {
207- if ( prev_type !== undefined && prev_type === 'Declaration' ) {
204+ if ( prev_type === 'Declaration' ) {
208205 buffer += '\n'
209206 }
210207 buffer += print_rule ( child , indent_level , css )
211208 } else if ( child . type === 'Atrule' ) {
212- if ( prev_type !== undefined && prev_type === 'Declaration' ) {
209+ if ( prev_type === 'Declaration' ) {
213210 buffer += '\n'
214211 }
215212 buffer += print_atrule ( child , indent_level , css )
@@ -218,10 +215,10 @@ function print_block(node, indent_level, css) {
218215 }
219216
220217 if ( child !== children . last ) {
221- if ( child . type === 'Declaration' ) {
218+ buffer += '\n'
219+
220+ if ( child . type !== 'Declaration' ) {
222221 buffer += '\n'
223- } else {
224- buffer += '\n\n'
225222 }
226223 }
227224
@@ -243,31 +240,58 @@ function print_block(node, indent_level, css) {
243240 * @returns {string } A formatted Atrule
244241 */
245242function print_atrule ( node , indent_level , css ) {
246- let buffer = indent ( indent_level ) + '@' + node . name
243+ let buffer = indent ( indent_level ) + '@' + node . name . toLowerCase ( )
247244
248245 // @font -face has no prelude
249246 if ( node . prelude !== null ) {
250- buffer += ' ' + substr ( node . prelude , css )
247+ buffer += ' ' + print_prelude ( node . prelude , 0 , css )
251248 }
252249
253- if ( node . block && node . block . type === 'Block' ) {
254- buffer += print_block ( node . block , indent_level , css )
255- } else {
250+ if ( node . block === null ) {
256251 // `@import url(style.css);` has no block, neither does `@layer layer1;`
257252 buffer += ';'
253+ } else if ( node . block . type === 'Block' ) {
254+ buffer += print_block ( node . block , indent_level , css )
258255 }
259256
260257 return buffer
261258}
262259
263260/**
264- * @param {import('css-tree').Declation } node
261+ * Pretty-printing atrule preludes takes an insane amount of rules,
262+ * so we're opting for a couple of 'good-enough' string replacements
263+ * here to force some nice formatting.
264+ * Should be OK perf-wise, since the amount of atrules in most
265+ * stylesheets are limited, so this won't be called too often.
266+ * @param {import('css-tree').AtrulePrelude | import('css-tree').Raw } node
267+ * @param {number } indent_level
268+ * @param {string } css
269+ */
270+ function print_prelude ( node , indent_level , css ) {
271+ let buffer = substr ( node , css )
272+ return buffer
273+ . replace ( / ( [: ,] ) / g, '$1 ' ) // force whitespace after colon or comma
274+ . replace ( / \( \s + / g, '(' ) // remove whitespace after (
275+ . replace ( / \s + \) / g, ')' ) // remove whitespace before )
276+ . replace ( / \s + / g, ' ' ) // collapse multiple whitespaces into one
277+ }
278+
279+ /**
280+ * @param {import('css-tree').Declaration } node
265281 * @param {number } indent_level
266282 * @param {string } css
267283 * @returns {string } A formatted Declaration
268284 */
269285function print_declaration ( node , indent_level , css ) {
270- return indent ( indent_level ) + node . property . toLowerCase ( ) + ': ' + print_value ( node . value , indent_level , css ) . trim ( ) + ';'
286+ let property = node . property . toLowerCase ( )
287+ let value = print_value ( node . value , indent_level , css ) . trim ( )
288+
289+ // Special case for `font` shorthand: remove whitespace around /
290+ if ( property === 'font' ) {
291+ value = value . replace ( / \s * \/ \s * / , '/' )
292+ }
293+
294+ return indent ( indent_level ) + property + ': ' + value
271295}
272296
273297/**
@@ -289,18 +313,34 @@ function print_list(children, indent_level, css) {
289313 } else if ( node . type === 'Function' ) {
290314 buffer += print_function ( node , 0 , css )
291315 } else if ( node . type === 'Dimension' ) {
292- buffer += node . value + node . unit . toLowerCase ( )
316+ buffer += print_dimension ( node , 0 , css )
293317 } else if ( node . type === 'Value' ) {
294318 // Values can be inside var() as fallback
295319 // var(--prop, VALUE)
296320 buffer += print_value ( node , 0 , css )
321+ } else if ( node . type === 'Operator' ) {
322+ // Put extra spacing around + - / *
323+ // but not before a comma
324+ if ( node . value !== ',' ) {
325+ buffer += ' '
326+ }
327+ buffer += substr ( node , css )
297328 } else {
298- buffer += print_unknown ( node , 0 , css )
329+ buffer += substr ( node , css )
299330 }
300331 }
301332 return buffer
302333}
303334
335+ /**
336+ * @param {import('css-tree').Dimension } node
337+ * @param {number } indent_level
338+ * @param {string } css
339+ */
340+ function print_dimension ( node , indent_level , css ) {
341+ return node . value + node . unit . toLowerCase ( )
342+ }
343+
304344/**
305345 * @param {import('css-tree').Value | import('css-tree').Raw } node
306346 * @param {number } indent_level
0 commit comments