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
103 changes: 93 additions & 10 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ function substr_raw(node, css) {
}

/**
*
* @param {import('css-tree').Rule} node
* @param {number} indent_level
* @param {string} css
Expand All @@ -47,8 +46,12 @@ function substr_raw(node, css) {
function print_rule(node, indent_level, css) {
let buffer = ''

if (node.prelude !== null && node.prelude.type === 'SelectorList') {
buffer += print_selectorlist(node.prelude, indent_level, css)
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.block !== null && node.block.type === 'Block') {
Expand Down Expand Up @@ -86,14 +89,96 @@ function print_selectorlist(node, indent_level, css) {
}

/**
*
* @param {import('css-tree').Selector} node
* @param {string} css
*/
function print_simple_selector(node, css) {
let buffer = ''

if (node.children) {
for (let child of node.children) {
switch (child.type) {
case 'Combinator': {
// putting spaces around `child.name`, unless the combinator is ' '
buffer += ' '
if (child.name !== ' ') {
buffer += child.name + ' '
}
break
}
case 'PseudoClassSelector': {
buffer += ':' + child.name

if (child.children) {
buffer += '(' + print_simple_selector(child, css) + ')'
}
break
}
case 'SelectorList': {
for (let grandchild of child.children) {
buffer += print_simple_selector(grandchild, css)

if (grandchild !== child.children.last) {
buffer += ', '
}
}
break
}
case 'Nth': {
if (child.nth) {
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) {
buffer += a + 'n'
}

if (hasA && hasB) {
buffer += ' '
}

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

buffer += b
}
} else {
// For odd/even or maybe other identifiers later on
buffer += substr(child.nth, css)
}
}

if (child.selector !== null) {
// `of .selector`
buffer += ' of ' + print_simple_selector(child.selector, css)
}
break
}
default: {
buffer += substr(child, css)
break
}
}
}
}

return buffer
}

/**
* @param {import('css-tree').Selector} node
* @param {number} indent_level
* @param {string} css
* @returns {string} A formatted Selector
*/
function print_selector(node, indent_level, css) {
return indent(indent_level) + substr(node, css)
return indent(indent_level) + print_simple_selector(node, css)
}

/**
Expand Down Expand Up @@ -146,8 +231,7 @@ function print_block(node, indent_level, css) {
indent_level--

buffer += '\n'
buffer += indent(indent_level)
buffer += '}'
buffer += indent(indent_level) + '}'

return buffer
}
Expand All @@ -159,8 +243,7 @@ function print_block(node, indent_level, css) {
* @returns {string} A formatted Atrule
*/
function print_atrule(node, indent_level, css) {
let buffer = indent(indent_level)
buffer += '@' + node.name
let buffer = indent(indent_level) + '@' + node.name

// @font-face has no prelude
if (node.prelude) {
Expand Down Expand Up @@ -198,7 +281,7 @@ function print_unknown(node, indent_level, css) {
}

/**
* @param {import('css-tree').Stylesheet} node
* @param {import('css-tree').StyleSheet} node
* @param {number} indent_level
* @param {string} css
* @returns {string} A formatted Stylesheet
Expand Down
101 changes: 89 additions & 12 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,24 +38,24 @@ selector {

test('Atrule blocks are surrounded by {} with correct spacing and indentation', () => {
let actual = format(`
@media (min-width:1000px){selector{property:value}}
@media (min-width:1000px){selector{property:value1}}

@media (min-width:1000px)
{
selector
{
property:value
property:value2
}
}`)
let expected = `@media (min-width:1000px) {
selector {
property: value;
property: value1;
}
}

@media (min-width:1000px) {
selector {
property: value;
property: value2;
}
}`

Expand Down Expand Up @@ -98,19 +98,19 @@ test('Declarations end with a semicolon (;)', () => {
}

css {
property1: value2;
property1: value1;
property2: value2;

& .nested {
property1: value2;
property2: value2
property1: value3;
property2: value4
}
}

@media (min-width: 1000px) {
@layer test {
css {
property1: value1
property1: value5
}
}
}
Expand All @@ -121,19 +121,19 @@ test('Declarations end with a semicolon (;)', () => {
}

css {
property1: value2;
property1: value1;
property2: value2;

& .nested {
property1: value2;
property2: value2;
property1: value3;
property2: value4;
}
}

@media (min-width: 1000px) {
@layer test {
css {
property1: value1;
property1: value5;
}
}
}`
Expand Down Expand Up @@ -452,4 +452,81 @@ a.b .c .d .e .f {
assert.equal(actual, expected)
})

test('formats Raw rule prelude', () => {
let actual = format(`:lang("nl","de"),li:nth-child() {}`)
let expected = `:lang("nl","de"),li:nth-child() {}` // no formatting applied
assert.equal(actual, expected)
})

test('formats simple selector combinators', () => {
let actual = format(`
a>b,
a>b~c d {}
`)
let expected = `a > b,
a > b ~ c d {}`
assert.equal(actual, expected)
})

test('formats nested selector combinators', () => {
let fixtures = [
[`:where(a+b) {}`, `:where(a + b) {}`],
[`:where(:is(ol,ul)) {}`, `:where(:is(ol, ul)) {}`],
[`li:nth-of-type(1) {}`, `li:nth-of-type(1) {}`],
[`li:nth-of-type(2n) {}`, `li:nth-of-type(2n) {}`],
]

for (let [css, expected] of fixtures) {
let actual = format(css)
assert.equal(actual, expected)
}
})

test('formats selectors with Nth', () => {
let fixtures = [
[`li:nth-child(3n-2) {}`, `li:nth-child(3n -2) {}`],
[`li:nth-child(0n+1) {}`, `li:nth-child(0n + 1) {}`],
[`li:nth-child(even of .noted) {}`, `li:nth-child(even of .noted) {}`],
[`li:nth-child(2n of .noted) {}`, `li:nth-child(2n of .noted) {}`],
[`li:nth-child(-n + 3 of .noted) {}`, `li:nth-child(-1n + 3 of .noted) {}`],
[`li:nth-child(-n+3 of li.important) {}`, `li:nth-child(-1n + 3 of li.important) {}`],
[`p:nth-child(n+8):nth-child(-n+15) {}`, `p:nth-child(1n + 8):nth-child(-1n + 15) {}`],
]

for (let [css, expected] of fixtures) {
let actual = format(css)
assert.equal(actual, expected)
}
})

test.skip('formats simple value lists', () => {
let actual = format(`
a {
transition-property: all,opacity;
transition: all 100ms ease,opacity 10ms 20ms linear;
color: rgb(0,0,0);
}
`)
let expected = `a {
transition-property: all, opacity;
transition: all 100ms ease, opacity 10ms 20ms linear;
color: rgb(0, 0, 0);
}`
assert.equal(actual, expected)
})

test.skip('formats nested value lists', () => {
let actual = format(`
a {
background: red,linear-gradient(to bottom,red 10%,green 50%,blue 100%);
color: var(--test1,var(--test2,green));
}
`)
let expected = `a {
background: red, linear-gradient(to bottom, red 10%, green 50%, blue 100%);
color: var(--test1, var(--test2, green));
}`
assert.equal(actual, expected)
})

test.run();