Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 79 additions & 39 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,21 @@ function indent(size) {
* @returns A portion of the CSS
*/
function substr(node, css) {
if (node.loc === null) return ''
let str = css.substring(node.loc.start.offset, node.loc.end.offset)
let loc = node.loc

if (!loc) return ''

let start = loc.start
let end = loc.end
let str = css.substring(start.offset, end.offset)

// Single-line node, most common case
if (node.loc.start.line === node.loc.end.line) {
if (start.line === end.line) {
return str
}

// Multi-line nodes, not common
return str.split('\n').map(part => part.trim()).join(' ')
return str.replace(/\s+/g, ' ')
}

/**
Expand All @@ -33,7 +38,7 @@ function substr(node, css) {
* @returns A portion of the CSS
*/
function substr_raw(node, css) {
if (node.loc === null) return ''
if (!node.loc) return ''
return css.substring(node.loc.start.offset, node.loc.end.offset)
}

Expand All @@ -44,14 +49,12 @@ function substr_raw(node, css) {
* @returns {string} A formatted Rule
*/
function print_rule(node, indent_level, css) {
let buffer = ''
let buffer

if (node.prelude !== null) {
if (node.prelude.type === 'SelectorList') {
buffer += print_selectorlist(node.prelude, indent_level, css)
} else {
buffer += print_unknown(node.prelude, indent_level, css)
}
if (node.prelude.type === 'SelectorList') {
buffer = print_selectorlist(node.prelude, indent_level, css)
} else {
buffer = print_unknown(node.prelude, indent_level, css)
}

if (node.block !== null && node.block.type === 'Block') {
Expand All @@ -71,25 +74,21 @@ function print_selectorlist(node, indent_level, css) {
let buffer = ''

for (let selector of node.children) {
if (selector !== node.children.first) {
buffer += '\n'
}

if (selector.type === 'Selector') {
buffer += print_selector(selector, indent_level, css)
} else {
buffer += print_unknown(selector, indent_level, css)
}

if (selector !== node.children.last) {
buffer += ','
buffer += `,\n`
}
}
return buffer
}

/**
* @param {import('css-tree').Selector} node
* @param {import('css-tree').Selector|import('css-tree').PseudoClassSelector} node
* @param {string} css
*/
function print_simple_selector(node, css) {
Expand Down Expand Up @@ -129,20 +128,18 @@ function print_simple_selector(node, css) {
if (child.nth.type === 'AnPlusB') {
let a = child.nth.a
let b = child.nth.b
let hasA = a !== null
let hasB = b !== null

if (hasA) {
if (a !== null) {
buffer += a + 'n'
}

if (hasA && hasB) {
if (a !== null && b !== null) {
buffer += ' '
}

if (hasB) {
if (b !== null) {
// When (1n + x) but not (1n - x)
if (hasA && !b.startsWith('-')) {
if (a !== null && !b.startsWith('-')) {
buffer += '+ '
}

Expand Down Expand Up @@ -202,14 +199,14 @@ function print_block(node, indent_level, css) {

for (let child of children) {
if (child.type === 'Declaration') {
buffer += print_declaration(child, indent_level, css)
buffer += print_declaration(child, indent_level, css) + ';'
} else if (child.type === 'Rule') {
if (prev_type !== undefined && prev_type === 'Declaration') {
if (prev_type === 'Declaration') {
buffer += '\n'
}
buffer += print_rule(child, indent_level, css)
} else if (child.type === 'Atrule') {
if (prev_type !== undefined && prev_type === 'Declaration') {
if (prev_type === 'Declaration') {
buffer += '\n'
}
buffer += print_atrule(child, indent_level, css)
Expand All @@ -218,10 +215,10 @@ function print_block(node, indent_level, css) {
}

if (child !== children.last) {
if (child.type === 'Declaration') {
buffer += '\n'

if (child.type !== 'Declaration') {
buffer += '\n'
} else {
buffer += '\n\n'
}
}

Expand All @@ -243,31 +240,58 @@ function print_block(node, indent_level, css) {
* @returns {string} A formatted Atrule
*/
function print_atrule(node, indent_level, css) {
let buffer = indent(indent_level) + '@' + node.name
let buffer = indent(indent_level) + '@' + node.name.toLowerCase()

// @font-face has no prelude
if (node.prelude !== null) {
buffer += ' ' + substr(node.prelude, css)
buffer += ' ' + print_prelude(node.prelude, 0, css)
}

if (node.block && node.block.type === 'Block') {
buffer += print_block(node.block, indent_level, css)
} else {
if (node.block === null) {
// `@import url(style.css);` has no block, neither does `@layer layer1;`
buffer += ';'
} else if (node.block.type === 'Block') {
buffer += print_block(node.block, indent_level, css)
}

return buffer
}

/**
* @param {import('css-tree').Declation} node
* Pretty-printing atrule preludes takes an insane amount of rules,
* so we're opting for a couple of 'good-enough' string replacements
* here to force some nice formatting.
* Should be OK perf-wise, since the amount of atrules in most
* stylesheets are limited, so this won't be called too often.
* @param {import('css-tree').AtrulePrelude | import('css-tree').Raw} node
* @param {number} indent_level
* @param {string} css
*/
function print_prelude(node, indent_level, css) {
let buffer = substr(node, css)
return buffer
.replace(/([:,])/g, '$1 ') // force whitespace after colon or comma
.replace(/\(\s+/g, '(') // remove whitespace after (
.replace(/\s+\)/g, ')') // remove whitespace before )
.replace(/\s+/g, ' ') // collapse multiple whitespaces into one
}

/**
* @param {import('css-tree').Declaration} node
* @param {number} indent_level
* @param {string} css
* @returns {string} A formatted Declaration
*/
function print_declaration(node, indent_level, css) {
return indent(indent_level) + node.property.toLowerCase() + ': ' + print_value(node.value, indent_level, css).trim() + ';'
let property = node.property.toLowerCase()
let value = print_value(node.value, indent_level, css).trim()

// Special case for `font` shorthand: remove whitespace around /
if (property === 'font') {
value = value.replace(/\s*\/\s*/, '/')
}

return indent(indent_level) + property + ': ' + value
}

/**
Expand All @@ -289,18 +313,34 @@ function print_list(children, indent_level, css) {
} else if (node.type === 'Function') {
buffer += print_function(node, 0, css)
} else if (node.type === 'Dimension') {
buffer += node.value + node.unit.toLowerCase()
buffer += print_dimension(node, 0, css)
} else if (node.type === 'Value') {
// Values can be inside var() as fallback
// var(--prop, VALUE)
buffer += print_value(node, 0, css)
} else if (node.type === 'Operator') {
// Put extra spacing around + - / *
// but not before a comma
if (node.value !== ',') {
buffer += ' '
}
buffer += substr(node, css)
} else {
buffer += print_unknown(node, 0, css)
buffer += substr(node, css)
}
}
return buffer
}

/**
* @param {import('css-tree').Dimension} node
* @param {number} indent_level
* @param {string} css
*/
function print_dimension(node, indent_level, css) {
return node.value + node.unit.toLowerCase()
}

/**
* @param {import('css-tree').Value | import('css-tree').Raw} node
* @param {number} indent_level
Expand Down
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"test": "uvu"
},
"devDependencies": {
"@types/css-tree": "^2.3.1",
"@types/css-tree": "^2.3.4",
"microbundle": "^0.15.1",
"uvu": "^0.5.6"
},
Expand All @@ -35,4 +35,4 @@
"pretty",
"prettier"
]
}
}
Loading