diff --git a/index.js b/index.js index 93945ed..b7673d7 100644 --- a/index.js +++ b/index.js @@ -38,7 +38,6 @@ function substr_raw(node, css) { } /** - * * @param {import('css-tree').Rule} node * @param {number} indent_level * @param {string} css @@ -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') { @@ -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) } /** @@ -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 } @@ -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) { @@ -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 diff --git a/test.js b/test.js index 92172c1..1ad3696 100644 --- a/test.js +++ b/test.js @@ -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; } }` @@ -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 } } } @@ -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; } } }` @@ -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(); \ No newline at end of file