diff --git a/package-lock.json b/package-lock.json index 857cec4..b9216fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -520,16 +520,6 @@ "@octokit/openapi-types": "^24.2.0" } }, - "node_modules/@oxc-project/types": { - "version": "0.115.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.115.0.tgz", - "integrity": "sha512-4n91DKnebUS4yjUHl2g3/b2T+IUdCfmoZGhmwsovZCDaJSs+QkVAM+0AqqTxHSsHfeiMuueT75cZaZcT/m0pSw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" - } - }, "node_modules/@oxfmt/binding-android-arm-eabi": { "version": "0.42.0", "resolved": "https://registry.npmjs.org/@oxfmt/binding-android-arm-eabi/-/binding-android-arm-eabi-0.42.0.tgz", @@ -1235,268 +1225,6 @@ ], "license": "MIT" }, - "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.9.tgz", - "integrity": "sha512-lcJL0bN5hpgJfSIz/8PIf02irmyL43P+j1pTCfbD1DbLkmGRuFIA4DD3B3ZOvGqG0XiVvRznbKtN0COQVaKUTg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.9.tgz", - "integrity": "sha512-J7Zk3kLYFsLtuH6U+F4pS2sYVzac0qkjcO5QxHS7OS7yZu2LRs+IXo+uvJ/mvpyUljDJ3LROZPoQfgBIpCMhdQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.9.tgz", - "integrity": "sha512-iwtmmghy8nhfRGeNAIltcNXzD0QMNaaA5U/NyZc1Ia4bxrzFByNMDoppoC+hl7cDiUq5/1CnFthpT9n+UtfFyg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.9.tgz", - "integrity": "sha512-DLFYI78SCiZr5VvdEplsVC2Vx53lnA4/Ga5C65iyldMVaErr86aiqCoNBLl92PXPfDtUYjUh+xFFor40ueNs4Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.9.tgz", - "integrity": "sha512-CsjTmTwd0Hri6iTw/DRMK7kOZ7FwAkrO4h8YWKoX/kcj833e4coqo2wzIFywtch/8Eb5enQ/lwLM7w6JX1W5RQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.9.tgz", - "integrity": "sha512-2x9O2JbSPxpxMDhP9Z74mahAStibTlrBMW0520+epJH5sac7/LwZW5Bmg/E6CXuEF53JJFW509uP+lSedaUNxg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.9.tgz", - "integrity": "sha512-JA1QRW31ogheAIRhIg9tjMfsYbglXXYGNPLdPEYrwFxdbkQCAzvpSCSHCDWNl4hTtrol8WeboCSEpjdZK8qrCg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.9.tgz", - "integrity": "sha512-aOKU9dJheda8Kj8Y3w9gnt9QFOO+qKPAl8SWd7JPHP+Cu0EuDAE5wokQubLzIDQWg2myXq2XhTpOVS07qqvT+w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.9.tgz", - "integrity": "sha512-OalO94fqj7IWRn3VdXWty75jC5dk4C197AWEuMhIpvVv2lw9fiPhud0+bW2ctCxb3YoBZor71QHbY+9/WToadA==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.9.tgz", - "integrity": "sha512-cVEl1vZtBsBZna3YMjGXNvnYYrOJ7RzuWvZU0ffvJUexWkukMaDuGhUXn0rjnV0ptzGVkvc+vW9Yqy6h8YX4pg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.9.tgz", - "integrity": "sha512-UzYnKCIIc4heAKgI4PZ3dfBGUZefGCJ1TPDuLHoCzgrMYPb5Rv6TLFuYtyM4rWyHM7hymNdsg5ik2C+UD9VDbA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.9.tgz", - "integrity": "sha512-+6zoiF+RRyf5cdlFQP7nm58mq7+/2PFaY2DNQeD4B87N36JzfF/l9mdBkkmTvSYcYPE8tMh/o3cRlsx1ldLfog==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.9.tgz", - "integrity": "sha512-rgFN6sA/dyebil3YTlL2evvi/M+ivhfnyxec7AccTpRPccno/rPoNlqybEZQBkcbZu8Hy+eqNJCqfBR8P7Pg8g==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@napi-rs/wasm-runtime": "^1.1.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.9.tgz", - "integrity": "sha512-lHVNUG/8nlF1IQk1C0Ci574qKYyty2goMiPlRqkC5R+3LkXDkL5Dhx8ytbxq35m+pkHVIvIxviD+TWLdfeuadA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.9.tgz", - "integrity": "sha512-G0oA4+w1iY5AGi5HcDTxWsoxF509hrFIPB2rduV5aDqS9FtDg1CAfa7V34qImbjfhIcA8C+RekocJZA96EarwQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.9.tgz", - "integrity": "sha512-w6oiRWgEBl04QkFZgmW+jnU1EC9b57Oihi2ot3HNWIQRqgHp5PnYDia5iZ5FF7rpa4EQdiqMDXjlqKGXBhsoXw==", - "dev": true, - "license": "MIT" - }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.59.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", @@ -3039,40 +2767,6 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, - "node_modules/rolldown": { - "version": "1.0.0-rc.9", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.9.tgz", - "integrity": "sha512-9EbgWge7ZH+yqb4d2EnELAntgPTWbfL8ajiTW+SyhJEC4qhBbkCKbqFV4Ge4zmu5ziQuVbWxb/XwLZ+RIO7E8Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@oxc-project/types": "=0.115.0", - "@rolldown/pluginutils": "1.0.0-rc.9" - }, - "bin": { - "rolldown": "bin/cli.mjs" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-rc.9", - "@rolldown/binding-darwin-arm64": "1.0.0-rc.9", - "@rolldown/binding-darwin-x64": "1.0.0-rc.9", - "@rolldown/binding-freebsd-x64": "1.0.0-rc.9", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.9", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.9", - "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.9", - "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.9", - "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.9", - "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.9", - "@rolldown/binding-linux-x64-musl": "1.0.0-rc.9", - "@rolldown/binding-openharmony-arm64": "1.0.0-rc.9", - "@rolldown/binding-wasm32-wasi": "1.0.0-rc.9", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.9", - "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.9" - } - }, "node_modules/rollup": { "version": "4.59.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", diff --git a/src/lib/index.ts b/src/lib/index.ts index 7e7ad9e..18ba5cd 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -26,78 +26,28 @@ export type FormatOptions = { tab_size?: number } -/** - * Format a string of CSS using some simple rules - */ -export function format( - css: string, - { minify = false, tab_size = undefined }: FormatOptions = Object.create(null), -): string { - if (tab_size !== undefined && Number(tab_size) < 1) { - throw new TypeError('tab_size must be a number greater than 0') - } - - const NEWLINE = minify ? EMPTY_STRING : '\n' - const OPTIONAL_SPACE = minify ? EMPTY_STRING : SPACE - const LAST_SEMICOLON = minify ? EMPTY_STRING : SEMICOLON +export function unquote(str: string): string { + return str.replace(/(?:^['"])|(?:['"]$)/g, EMPTY_STRING) +} - // First pass: collect all comments - let comments: number[] = [] - let ast = parse(css, { - parse_atrule_preludes: false, - on_comment: minify - ? undefined - : ({ start, end }) => { - comments.push(start, end) - }, - }) +export function print_string(str: string | number | null): string { + str = str?.toString() || '' + return QUOTE + unquote(str) + QUOTE +} +function create_printers( + optional_space: string, + newline: string, + last_semicolon: string, + minify: boolean, + make_indent: (size: number) => string, + lookup_comment: (after: number, before: number, level: number) => string, +) { let depth = 0 - function indent(size: number) { - if (minify === true) return EMPTY_STRING - - if (tab_size !== undefined) { - return SPACE.repeat(tab_size * size) - } - - return '\t'.repeat(size) - } - - /** - * Get and format comments from the CSS string within a range - * @param after After which offset to look for comments - * @param before Before which offset to look for comments - * @param level Indentation level (uses current depth if not specified) - * @returns The formatted comment string, or empty string if no comment found - */ function get_comment(after?: number, before?: number, level: number = depth): string { - if (minify || after === undefined || before === undefined) { - return EMPTY_STRING - } - - let buffer = EMPTY_STRING - for (let i = 0; i < comments.length; i += 2) { - let start = comments[i] - if (start === undefined || start < after) continue - let end = comments[i + 1] - if (end === undefined || end > before) break - - if (buffer.length > 0) { - buffer += NEWLINE + indent(level) - } - buffer += css.slice(start, end) - } - return buffer - } - - function unquote(str: string): string { - return str.replace(/(?:^['"])|(?:['"]$)/g, EMPTY_STRING) - } - - function print_string(str: string | number | null): string { - str = str?.toString() || '' - return QUOTE + unquote(str) + QUOTE + if (after === undefined || before === undefined) return EMPTY_STRING + return lookup_comment(after, before, level) } function print_operator(node: CSSNode): string { @@ -107,7 +57,7 @@ export function format( let operator = node.text let code = operator.charCodeAt(0) // + or - require spaces; comma has no leading space; others use optional space - let space = code === 43 || code === 45 ? SPACE : OPTIONAL_SPACE + let space = code === 43 || code === 45 ? SPACE : optional_space return (code === 44 ? EMPTY_STRING : space) + operator + space } @@ -164,7 +114,7 @@ export function format( let text = node.text let start = text.lastIndexOf('!') important = - OPTIONAL_SPACE + text.slice(start, text.endsWith(SEMICOLON) ? -1 : undefined).toLowerCase() + optional_space + text.slice(start, text.endsWith(SEMICOLON) ? -1 : undefined).toLowerCase() } let value = print_value(node.value as CSSNode[] | null) let property = node.property! @@ -182,7 +132,7 @@ export function format( if (!property.startsWith('--')) { property = property.toLowerCase() } - return property + COLON + OPTIONAL_SPACE + value + important + return property + COLON + optional_space + value + important } function print_nth(node: CSSNode): string { @@ -191,8 +141,8 @@ export function format( let result = a ? `${a}` : EMPTY_STRING if (b) { if (a) { - result += OPTIONAL_SPACE - if (!b.startsWith('-')) result += '+' + OPTIONAL_SPACE + result += optional_space + if (!b.startsWith('-')) result += '+' + optional_space } result += parseFloat(b) } @@ -224,8 +174,8 @@ export function format( return SPACE } // Skip leading space if this is the first node in the selector - let leading_space = is_first ? EMPTY_STRING : OPTIONAL_SPACE - return leading_space + text + OPTIONAL_SPACE + let leading_space = is_first ? EMPTY_STRING : optional_space + return leading_space + text + optional_space } case NODE.PSEUDO_ELEMENT_SELECTOR: @@ -306,7 +256,7 @@ export function format( for (let selector of node) { parts.push(print_selector(selector)) if (selector.has_next) { - parts.push(COMMA, OPTIONAL_SPACE) + parts.push(COMMA, optional_space) } } return parts.join(EMPTY_STRING) @@ -319,7 +269,7 @@ export function format( if (prev_end !== undefined) { let comment = get_comment(prev_end, selector.start) if (comment) { - lines.push(indent(depth) + comment) + lines.push(make_indent(depth) + comment) } } @@ -327,10 +277,10 @@ export function format( if (selector.has_next) { printed += COMMA } - lines.push(indent(depth) + printed) + lines.push(make_indent(depth) + printed) prev_end = selector.end } - return lines.join(NEWLINE) + return lines.join(newline) } function print_block(node: CSSNode): string { @@ -341,17 +291,17 @@ export function format( if (children.length === 0) { let comment = get_comment(node.start, node.end) if (comment) { - lines.push(indent(depth) + comment) + lines.push(make_indent(depth) + comment) depth-- - lines.push(indent(depth) + CLOSE_BRACE) - return lines.join(NEWLINE) + lines.push(make_indent(depth) + CLOSE_BRACE) + return lines.join(newline) } } let first_child = children[0] let comment_before_first = get_comment(node.start, first_child?.start) if (comment_before_first) { - lines.push(indent(depth) + comment_before_first) + lines.push(make_indent(depth) + comment_before_first) } let prev_end: number | undefined @@ -360,7 +310,7 @@ export function format( if (prev_end !== undefined) { let comment = get_comment(prev_end, child.start) if (comment) { - lines.push(indent(depth) + comment) + lines.push(make_indent(depth) + comment) } } @@ -368,8 +318,8 @@ export function format( if (child.type === NODE.DECLARATION) { let declaration = print_declaration(child) - let semi = is_last ? LAST_SEMICOLON : SEMICOLON - lines.push(indent(depth) + declaration + semi) + let semi = is_last ? last_semicolon : SEMICOLON + lines.push(make_indent(depth) + declaration + semi) } else if (child.type === NODE.STYLE_RULE) { if (prev_end !== undefined && lines.length !== 0) { lines.push(EMPTY_STRING) @@ -379,7 +329,7 @@ export function format( if (prev_end !== undefined && lines.length !== 0) { lines.push(EMPTY_STRING) } - lines.push(indent(depth) + print_atrule(child)) + lines.push(make_indent(depth) + print_atrule(child)) } prev_end = child.end @@ -387,12 +337,12 @@ export function format( let comment_after_last = get_comment(prev_end, node.end) if (comment_after_last) { - lines.push(indent(depth) + comment_after_last) + lines.push(make_indent(depth) + comment_after_last) } depth-- - lines.push(indent(depth) + CLOSE_BRACE) - return lines.join(NEWLINE) + lines.push(make_indent(depth) + CLOSE_BRACE) + return lines.join(newline) } function print_rule(node: CSSNode): string { @@ -405,10 +355,10 @@ export function format( let comment = get_comment(node.first_child.end, node.block?.start) if (comment) { - list += NEWLINE + indent(depth) + comment + list += newline + make_indent(depth) + comment } - list += OPTIONAL_SPACE + OPEN_BRACE + list += optional_space + OPEN_BRACE if (!block_has_content) { list += CLOSE_BRACE } @@ -419,7 +369,7 @@ export function format( lines.push(print_block(node.block!)) } - return lines.join(NEWLINE) + return lines.join(newline) } /** @@ -433,14 +383,14 @@ export function format( return prelude .replace(/\s*([:,])/g, prelude.toLowerCase().includes('selector(') ? '$1' : '$1 ') // force whitespace after colon or comma, except inside `selector()` .replace(/\)([a-zA-Z])/g, ') $1') // force whitespace between closing parenthesis and following text (usually and|or) - .replace(/\s*(=>|>=|<=)\s*/g, `${OPTIONAL_SPACE}$1${OPTIONAL_SPACE}`) // add optional spacing 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(/\s*(=>|>=|<=)\s*/g, `${optional_space}$1${optional_space}`) // add optional spacing 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 + let space = operator === '+' || operator === '-' ? SPACE : optional_space return `calc(${left.trim()}${space}${operator}${space}${right.trim()})` }, ) @@ -459,14 +409,14 @@ export function format( if (node.block === null) { name += SEMICOLON } else { - name += OPTIONAL_SPACE + OPEN_BRACE + name += optional_space + OPEN_BRACE if (!block_has_content) { name += CLOSE_BRACE } } if (block_has_content) { - return name + NEWLINE + print_block(node.block!) + return name + newline + print_block(node.block!) } return name } @@ -519,9 +469,112 @@ export function format( lines.push(comment_after_last) } - return lines.join(NEWLINE) + return lines.join(newline) + } + + return { + print_operator, + print_list, + print_value, + print_declaration, + print_nth, + print_nth_of, + print_simple_selector, + print_selector, + print_inline_selector_list, + print_selector_list, + print_block, + print_rule, + print_atrule_prelude, + print_atrule, + print_stylesheet, + } +} + +// Default (pretty-print) exports — these accept just CSSNode or CSSNode[] +// and use non-minified formatting with tab indentation +export const { + print_operator, + print_list, + print_value, + print_declaration, + print_nth, + print_nth_of, + print_simple_selector, + print_selector, + print_inline_selector_list, + print_selector_list, + print_block, + print_rule, + print_atrule_prelude, + print_atrule, + print_stylesheet, +} = create_printers( + SPACE, // optional_space + '\n', // newline + SEMICOLON, // last_semicolon + false, // minify + (size) => '\t'.repeat(size), // make_indent + () => EMPTY_STRING, // lookup_comment — no source CSS available, comments are omitted +) + +/** + * Format a string of CSS using some simple rules + */ +export function format( + css: string, + { minify = false, tab_size = undefined }: FormatOptions = Object.create(null), +): string { + if (tab_size !== undefined && Number(tab_size) < 1) { + throw new TypeError('tab_size must be a number greater than 0') + } + + const optional_space = minify ? EMPTY_STRING : SPACE + const newline = minify ? EMPTY_STRING : '\n' + const last_semicolon = minify ? EMPTY_STRING : SEMICOLON + + // First pass: collect all comments + let comments: number[] = [] + let ast = parse(css, { + parse_atrule_preludes: false, + on_comment: minify + ? undefined + : ({ start, end }) => { + comments.push(start, end) + }, + }) + + function make_indent(size: number): string { + if (minify === true) return EMPTY_STRING + if (typeof tab_size === 'number') return SPACE.repeat(tab_size * size) + return '\t'.repeat(size) + } + + function lookup_comment(after: number, before: number, level: number): string { + let buffer = EMPTY_STRING + for (let i = 0; i < comments.length; i += 2) { + let start = comments[i] + if (start === undefined || start < after) continue + let end = comments[i + 1] + if (end === undefined || end > before) break + + if (buffer.length > 0) { + buffer += newline + make_indent(level) + } + buffer += css.slice(start, end) + } + return buffer } + const { print_stylesheet } = create_printers( + optional_space, + newline, + last_semicolon, + minify, + make_indent, + lookup_comment, + ) + return print_stylesheet(ast).trimEnd() }