Skip to content

Commit 3e5f067

Browse files
authored
Pretty print preludes (#13)
closes #10 #12
1 parent 228bec6 commit 3e5f067

File tree

10 files changed

+868
-602
lines changed

10 files changed

+868
-602
lines changed

index.js

Lines changed: 79 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,21 @@ function indent(size) {
1515
* @returns A portion of the CSS
1616
*/
1717
function 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
*/
3540
function 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
*/
4651
function 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
*/
9594
function 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
*/
245242
function 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
*/
269285
function 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

package-lock.json

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"test": "uvu"
1717
},
1818
"devDependencies": {
19-
"@types/css-tree": "^2.3.1",
19+
"@types/css-tree": "^2.3.4",
2020
"microbundle": "^0.15.1",
2121
"uvu": "^0.5.6"
2222
},
@@ -35,4 +35,4 @@
3535
"pretty",
3636
"prettier"
3737
]
38-
}
38+
}

0 commit comments

Comments
 (0)