Skip to content

Commit 8f6ab68

Browse files
authored
Preserve comments (#106)
closes #26
1 parent 50f42e4 commit 8f6ab68

File tree

3 files changed

+590
-42
lines changed

3 files changed

+590
-42
lines changed

index.js

Lines changed: 121 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ const OPEN_PARENTHESES = '('
1010
const CLOSE_PARENTHESES = ')'
1111
const OPEN_BRACKET = '['
1212
const CLOSE_BRACKET = ']'
13+
const OPEN_BRACE = '{'
14+
const CLOSE_BRACE = '}'
15+
const EMPTY_BLOCK = '{}'
1316
const TYPE_ATRULE = 'Atrule'
1417
const TYPE_RULE = 'Rule'
1518
const TYPE_BLOCK = 'Block'
@@ -37,12 +40,18 @@ function lowercase(str) {
3740
* @returns {string} The formatted CSS
3841
*/
3942
export 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

Comments
 (0)