@@ -10,6 +10,9 @@ const OPEN_PARENTHESES = '('
1010const CLOSE_PARENTHESES = ')'
1111const OPEN_BRACKET = '['
1212const CLOSE_BRACKET = ']'
13+ const OPEN_BRACE = '{'
14+ const CLOSE_BRACE = '}'
15+ const EMPTY_BLOCK = '{}'
1316const TYPE_ATRULE = 'Atrule'
1417const TYPE_RULE = 'Rule'
1518const TYPE_BLOCK = 'Block'
@@ -37,12 +40,18 @@ function lowercase(str) {
3740 * @returns {string } The formatted CSS
3841 */
3942export function format ( css , { minify = false } = { } ) {
43+ /** @type {number[] } */
44+ let comments = [ ]
45+
4046 /** @type {import('css-tree').CssNode } */
4147 let ast = parse ( css , {
4248 positions : true ,
4349 parseAtrulePrelude : false ,
4450 parseCustomProperty : true ,
4551 parseValue : true ,
52+ onComment : ( /** @type {string } */ _ , /** @type {import('css-tree').CssLocation } */ position ) => {
53+ comments . push ( position . start . offset , position . end . offset )
54+ }
4655 } )
4756
4857 const NEWLINE = minify ? EMPTY_STRING : '\n'
@@ -63,10 +72,52 @@ export function format(css, { minify = false } = {}) {
6372 /** @param {import('css-tree').CssNode } node */
6473 function substr ( node ) {
6574 let loc = node . loc
75+ // If the node has no location, return an empty string
76+ // This is necessary for space toggles
6677 if ( ! loc ) return EMPTY_STRING
6778 return css . slice ( loc . start . offset , loc . end . offset )
6879 }
6980
81+ /** @param {import('css-tree').CssNode } node */
82+ function start_offset ( node ) {
83+ let loc = /** @type {import('css-tree').CssLocation } */ ( node . loc )
84+ return loc . start . offset
85+ }
86+
87+ /** @param {import('css-tree').CssNode } node */
88+ function end_offset ( node ) {
89+ let loc = /** @type {import('css-tree').CssLocation } */ ( node . loc )
90+ return loc . end . offset
91+ }
92+
93+ /**
94+ * Get a comment from the CSS string after the first offset and before the second offset
95+ * @param {number | undefined } after After which offset to look for comments
96+ * @param {number | undefined } before Before which offset to look for comments
97+ * @returns {string | undefined } The comment string, if found
98+ */
99+ function print_comment ( after , before ) {
100+ if ( minify || after === undefined || before === undefined ) {
101+ return EMPTY_STRING
102+ }
103+
104+ let buffer = ''
105+ for ( let i = 0 ; i < comments . length ; i += 2 ) {
106+ // Check that the comment is within the range
107+ let start = comments [ i ]
108+ if ( start === undefined || start < after ) continue
109+ let end = comments [ i + 1 ]
110+ if ( end === undefined || end > before ) break
111+
112+ // Special case for comments that follow another comment:
113+ if ( buffer . length > 0 ) {
114+ buffer += NEWLINE + indent ( indent_level )
115+ }
116+ buffer += css . slice ( start , end )
117+ }
118+ return buffer
119+ }
120+
70121 /** @param {import('css-tree').Rule } node */
71122 function print_rule ( node ) {
72123 let buffer
@@ -77,6 +128,11 @@ export function format(css, { minify = false } = {}) {
77128 buffer = print_selectorlist ( prelude )
78129 }
79130
131+ let comment = print_comment ( end_offset ( prelude ) , start_offset ( block ) )
132+ if ( comment ) {
133+ buffer += NEWLINE + indent ( indent_level ) + comment
134+ }
135+
80136 if ( block . type === TYPE_BLOCK ) {
81137 buffer += print_block ( block )
82138 }
@@ -96,6 +152,12 @@ export function format(css, { minify = false } = {}) {
96152 if ( item . next !== null ) {
97153 buffer += `,` + NEWLINE
98154 }
155+
156+ let end = item . next !== null ? start_offset ( item . next . data ) : end_offset ( node )
157+ let comment = print_comment ( end_offset ( selector ) , end )
158+ if ( comment ) {
159+ buffer += indent ( indent_level ) + comment + NEWLINE
160+ }
99161 } )
100162
101163 return buffer
@@ -231,14 +293,34 @@ export function format(css, { minify = false } = {}) {
231293 let buffer = OPTIONAL_SPACE
232294
233295 if ( children . isEmpty ) {
234- return buffer + '{}'
296+ // Check if the block maybe contains comments
297+ let comment = print_comment ( start_offset ( node ) , end_offset ( node ) )
298+ if ( comment ) {
299+ buffer += OPEN_BRACE + NEWLINE
300+ buffer += indent ( indent_level + 1 ) + comment
301+ buffer += NEWLINE + indent ( indent_level ) + CLOSE_BRACE
302+ return buffer
303+ }
304+ return buffer + EMPTY_BLOCK
235305 }
236306
237- buffer += '{' + NEWLINE
307+ buffer += OPEN_BRACE + NEWLINE
238308
239309 indent_level ++
240310
311+ let opening_comment = print_comment ( start_offset ( node ) , start_offset ( /** @type {import('css-tree').CssNode } */ ( children . first ) ) )
312+ if ( opening_comment ) {
313+ buffer += indent ( indent_level ) + opening_comment + NEWLINE
314+ }
315+
241316 children . forEach ( ( child , item ) => {
317+ if ( item . prev !== null ) {
318+ let comment = print_comment ( end_offset ( item . prev . data ) , start_offset ( child ) )
319+ if ( comment ) {
320+ buffer += indent ( indent_level ) + comment + NEWLINE
321+ }
322+ }
323+
242324 if ( child . type === TYPE_DECLARATION ) {
243325 buffer += print_declaration ( child )
244326
@@ -270,10 +352,13 @@ export function format(css, { minify = false } = {}) {
270352 }
271353 } )
272354
273- indent_level --
355+ let closing_comment = print_comment ( end_offset ( /** @type {import('css-tree').CssNode } */ ( children . last ) ) , end_offset ( node ) )
356+ if ( closing_comment ) {
357+ buffer += NEWLINE + indent ( indent_level ) + closing_comment
358+ }
274359
275- buffer += NEWLINE
276- buffer += indent ( indent_level ) + '}'
360+ indent_level --
361+ buffer += NEWLINE + indent ( indent_level ) + CLOSE_BRACE
277362
278363 return buffer
279364 }
@@ -443,19 +528,40 @@ export function format(css, { minify = false } = {}) {
443528 let children = ast . children
444529 let buffer = EMPTY_STRING
445530
446- children . forEach ( ( child , item ) => {
447- if ( child . type === TYPE_RULE ) {
448- buffer += print_rule ( child )
449- } else if ( child . type === TYPE_ATRULE ) {
450- buffer += print_atrule ( child )
451- } else {
452- buffer += print_unknown ( child , indent_level )
531+ if ( children . first ) {
532+ let opening_comment = print_comment ( 0 , start_offset ( children . first ) )
533+ if ( opening_comment ) {
534+ buffer += opening_comment + NEWLINE
453535 }
454536
455- if ( item . next !== null ) {
456- buffer += NEWLINE + NEWLINE
537+ children . forEach ( ( child , item ) => {
538+ if ( child . type === TYPE_RULE ) {
539+ buffer += print_rule ( child )
540+ } else if ( child . type === TYPE_ATRULE ) {
541+ buffer += print_atrule ( child )
542+ } else {
543+ buffer += print_unknown ( child , indent_level )
544+ }
545+
546+ if ( item . next !== null ) {
547+ buffer += NEWLINE
548+
549+ let comment = print_comment ( end_offset ( child ) , start_offset ( item . next . data ) )
550+ if ( comment ) {
551+ buffer += indent ( indent_level ) + comment
552+ }
553+
554+ buffer += NEWLINE
555+ }
556+ } )
557+
558+ let closing_comment = print_comment ( end_offset ( /** @type {import('css-tree').CssNode } */ ( children . last ) ) , end_offset ( ast ) )
559+ if ( closing_comment ) {
560+ buffer += NEWLINE + closing_comment
457561 }
458- } )
562+ } else {
563+ buffer += print_comment ( 0 , end_offset ( ast ) )
564+ }
459565
460566 return buffer
461567}
0 commit comments