diff --git a/index.js b/index.js index af19928..87325e2 100644 --- a/index.js +++ b/index.js @@ -395,11 +395,15 @@ export function format(css, { minify = false } = {}) { return buffer .replace(/\s*([:,])/g, buffer.toLowerCase().includes('selector(') ? '$1' : '$1 ') // force whitespace after colon or comma, except inside `selector()` - .replace(/\s*(=>|<=)\s*/g, ' $1 ') // force whitespace around => and <= .replace(/\)([a-zA-Z])/g, ') $1') // force whitespace between closing parenthesis and following text (usually and|or) - .replace(/(?)(?])(?![<= ])(?![=> ])(?![ =>])/g, ' $1 ') - .replace(/calc\(([^*]*)\*([^*])/g, 'calc($1 * $2') // force correct whitespace around * in calc() - .replace(/\s+/g, SPACE) // collapse multiple whitespaces into one + .replace(/\s*(=>|<=)\s*/g, ' $1 ') // force whitespace around => and <= + .replace(/([^<>=\s])([<>])([^<>=\s])/g, `$1${OPTIONAL_SPACE}$2${OPTIONAL_SPACE}$3`) // add spacing around < or > except when it's part of <=, >=, => + .replace(/\s+/g, OPTIONAL_SPACE) // collapse multiple whitespaces into one + .replace(/calc\(\s*([^()+\-*/]+)\s*([*/+-])\s*([^()+\-*/]+)\s*\)/g, (_, left, operator, right) => { + // force required or optional whitespace around * and / in calc() + let space = operator === '+' || operator === '-' ? SPACE : OPTIONAL_SPACE + return `calc(${left.trim()}${space}${operator}${space}${right.trim()})` + }) .replace(/selector|url|supports|layer\(/ig, (match) => lowercase(match)) // lowercase function names } diff --git a/test/atrules.test.js b/test/atrules.test.js index 62c2ae3..9980dcc 100644 --- a/test/atrules.test.js +++ b/test/atrules.test.js @@ -1,6 +1,6 @@ import { suite } from 'uvu' import * as assert from 'uvu/assert' -import { format } from '../index.js' +import { format, minify } from '../index.js' let test = suite('Atrules') @@ -166,6 +166,24 @@ test('calc() inside @media', () => { assert.equal(actual, expected) }) +test('minify: calc(*) inside @media', () => { + let actual = minify(`@media (min-width: calc(1px*1)) {}`) + let expected = `@media (min-width:calc(1px*1)){}` + assert.equal(actual, expected) +}) + +test('minify: calc(+) inside @media', () => { + let actual = minify(`@media (min-width: calc(1px + 1em)) {}`) + let expected = `@media (min-width:calc(1px + 1em)){}` + assert.equal(actual, expected) +}) + +test('minify: calc(-) inside @media', () => { + let actual = minify(`@media (min-width: calc(1em - 1px)) {}`) + let expected = `@media (min-width:calc(1em - 1px)){}` + assert.equal(actual, expected) +}) + test('@import prelude formatting', () => { let fixtures = [ ['@import url("fineprint.css") print;', '@import url("fineprint.css") print;'], @@ -204,6 +222,18 @@ test('@layer prelude formatting', () => { } }) +test('minify: @layer prelude formatting', () => { + let fixtures = [ + [`@layer test;`, `@layer test;`], + [`@layer tbody,thead;`, `@layer tbody,thead;`], + ] + + for (let [css, expected] of fixtures) { + let actual = minify(css) + assert.equal(actual, expected) + } +}) + test('single empty line after a rule, before atrule', () => { let actual = format(` rule1 { property: value } @@ -314,6 +344,12 @@ test('new-fangled comparators (width > 1000px)', () => { assert.is(actual, expected) }) +test('minify: new-fangled comparators (width > 1000px)', () => { + let actual = minify(`@container (width>1000px) {}`) + let expected = `@container (width>1000px){}` + assert.is(actual, expected) +}) + test.skip('preserves comments', () => { let actual = format(` @media /* comment */ all {} diff --git a/test/comments.test.js b/test/comments.test.js index f070fda..8b3a7c5 100644 --- a/test/comments.test.js +++ b/test/comments.test.js @@ -558,7 +558,7 @@ test('strips comments in minification mode', () => { } /* comment 5 */ `, { minify: true }) - let expected = `selector{}@media (min-width: 1000px){selector{}}` + let expected = `selector{}@media (min-width:1000px){selector{}}` assert.is(actual, expected) })