@projectwallace/format-css 2.2.5 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -41,10 +41,10 @@ npm install @projectwallace/format-css
41
41
  ## Usage
42
42
 
43
43
  ```js
44
- import { format } from "@projectwallace/format-css";
44
+ import { format } from '@projectwallace/format-css'
45
45
 
46
- let old_css = "/* Your old CSS here */";
47
- let new_css = format(old_css);
46
+ let old_css = '/* Your old CSS here */'
47
+ let new_css = format(old_css)
48
48
  ```
49
49
 
50
50
  Need more examples?
@@ -65,34 +65,64 @@ Need more examples?
65
65
  1. Multiline tokens like **Selectors, Values, etc.** are rendered on a single line
66
66
  1. Unknown syntax is rendered as-is, with multi-line formatting kept intact
67
67
 
68
- ## Minify CSS
68
+ ## Options
69
+
70
+ ### Minify CSS
69
71
 
70
72
  This package also exposes a minifier function since minifying CSS follows many of the same rules as formatting.
71
73
 
72
74
  ```js
73
- import { format, minify } from "@projectwallace/format-css";
75
+ import { format, minify } from '@projectwallace/format-css'
74
76
 
75
- let minified = minify("a {}");
77
+ let minified = minify('a {}')
76
78
 
77
79
  // which is an alias for
78
80
 
79
- let formatted_mini = format("a {}", { minify: true });
81
+ let formatted_mini = format('a {}', { minify: true })
80
82
  ```
81
83
 
82
- ## Tab size
84
+ ### Tab size
83
85
 
84
86
  For cases where you cannot control the tab size with CSS there is an option to override the default tabbed indentation with N spaces.
85
87
 
86
88
  ```js
87
- import { format } from "@projectwallace/format-css";
89
+ import { format } from '@projectwallace/format-css'
90
+
91
+ let formatted = format('a { color: red; }', {
92
+ tab_size: 2,
93
+ })
94
+ ```
95
+
96
+ ## CLI
97
+
98
+ This library also ships a CLI tools that's a small wrapper around the library.
99
+
100
+ ```
101
+ USAGE
102
+ format-css [options] [file...]
103
+ cat styles.css | format-css [options]
104
+
105
+ OPTIONS
106
+ --minify Minify the CSS output
107
+ --tab-size=<n> Use N spaces for indentation instead of tabs
108
+ --help, -h Show this help
109
+
110
+ EXAMPLES
111
+ # Format a file
112
+ format-css styles.css
113
+
114
+ # Format with 2-space indentation
115
+ format-css styles.css --tab-size=2
116
+
117
+ # Minify
118
+ format-css styles.css --minify
88
119
 
89
- let formatted = format("a { color: red; }", {
90
- tab_size: 2
91
- });
120
+ # Via pipe
121
+ cat styles.css | format-css
92
122
  ```
93
123
 
94
124
  ## Related projects
95
125
 
96
- - [Format CSS online](https://www.projectwallace.com/prettify-css?utm_source=github&utm_medium=wallace_format_css_related_projects) - See this formatter in action online!
97
- - [Minify CSS online](https://www.projectwallace.com/minify-css?utm_source=github&utm_medium=wallace_format_css_related_projects) - See this minifier in action online!
98
- - [CSS Analyzer](https://github.com/projectwallace/css-analyzer) - The best CSS analyzer that powers all analysis on [projectwallace.com](https://www.projectwallace.com?utm_source=github&utm_medium=wallace_format_css_related_projects)
126
+ - [Format CSS online](https://www.projectwallace.com/prettify-css) - See this formatter in action online!
127
+ - [Minify CSS online](https://www.projectwallace.com/minify-css) - See this minifier in action online!
128
+ - [CSS Analyzer](https://github.com/projectwallace/css-analyzer) - The best CSS analyzer that powers all analysis on [projectwallace.com](https://www.projectwallace.com)
package/dist/cli.mjs ADDED
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env node
2
+ import { parseArgs, styleText } from "node:util";
3
+ import { readFileSync } from "node:fs";
4
+ import { resolve, sep } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { format } from "@projectwallace/format-css";
7
+ //#region src/cli/cli.ts
8
+ function help() {
9
+ return `
10
+ ${styleText("bold", "USAGE")}
11
+ format-css [options] [file...]
12
+ cat styles.css | format-css [options]
13
+
14
+ ${styleText("bold", "OPTIONS")}
15
+ --minify Minify the CSS output
16
+ --tab-size=<n> Use N spaces for indentation instead of tabs
17
+ --help, -h Show this help
18
+
19
+ ${styleText("bold", "EXAMPLES")}
20
+ ${styleText("dim", "# Format a file")}
21
+ format-css styles.css
22
+
23
+ ${styleText("dim", "# Format with 2-space indentation")}
24
+ format-css styles.css --tab-size=2
25
+
26
+ ${styleText("dim", "# Minify")}
27
+ format-css styles.css --minify
28
+
29
+ ${styleText("dim", "# Via pipe")}
30
+ cat styles.css | format-css
31
+ `.trim();
32
+ }
33
+ function parse_arguments(args) {
34
+ const { values, positionals } = parseArgs({
35
+ args,
36
+ allowPositionals: true,
37
+ options: {
38
+ minify: {
39
+ type: "boolean",
40
+ default: false
41
+ },
42
+ "tab-size": { type: "string" }
43
+ }
44
+ });
45
+ const issues = [];
46
+ let tab_size;
47
+ if (values["tab-size"] !== void 0) {
48
+ tab_size = Number(values["tab-size"]);
49
+ if (isNaN(tab_size) || tab_size < 1) issues.push("--tab-size must be a positive integer");
50
+ }
51
+ const cwd = process.cwd();
52
+ const files = [];
53
+ for (const file of positionals) {
54
+ const resolved = resolve(file);
55
+ if (resolved !== cwd && !resolved.startsWith(cwd + sep)) issues.push(`Invalid path: ${file}`);
56
+ else files.push(resolved);
57
+ }
58
+ if (issues.length > 0) throw new Error(issues.join("\n"));
59
+ return {
60
+ files,
61
+ minify: values.minify ?? false,
62
+ tab_size
63
+ };
64
+ }
65
+ async function run(args, io) {
66
+ if (args.includes("--help") || args.includes("-h")) {
67
+ io.write(help() + "\n");
68
+ return;
69
+ }
70
+ const { files, minify, tab_size } = parse_arguments(args);
71
+ const options = {
72
+ minify,
73
+ tab_size
74
+ };
75
+ if (files.length > 0) for (const file of files) io.write(format(io.readFile(file), options));
76
+ else if (!io.isTTY) io.write(format(await io.readStdin(), options));
77
+ else io.write(help() + "\n");
78
+ }
79
+ async function read_stdin() {
80
+ const chunks = [];
81
+ for await (const chunk of process.stdin) chunks.push(chunk);
82
+ return Buffer.concat(chunks).toString("utf-8");
83
+ }
84
+ if (process.argv[1] === fileURLToPath(import.meta.url)) try {
85
+ await run(process.argv.slice(2), {
86
+ readFile: (path) => readFileSync(path, "utf-8"),
87
+ readStdin: read_stdin,
88
+ write: (output) => process.stdout.write(output),
89
+ isTTY: process.stdin.isTTY === true
90
+ });
91
+ } catch (error) {
92
+ console.error(error instanceof Error ? error.message : String(error));
93
+ process.exit(1);
94
+ }
95
+ //#endregion
96
+ export { parse_arguments, run };
package/dist/index.d.ts CHANGED
@@ -1,16 +1,20 @@
1
- export type FormatOptions = {
2
- /** Whether to minify the CSS or keep it formatted */
3
- minify?: boolean;
4
- /** Tell the formatter to use N spaces instead of tabs */
5
- tab_size?: number;
1
+ //#region src/lib/index.d.ts
2
+ type FormatOptions = {
3
+ /** Whether to minify the CSS or keep it formatted */minify?: boolean; /** Tell the formatter to use N spaces instead of tabs */
4
+ tab_size?: number;
6
5
  };
7
6
  /**
8
7
  * Format a string of CSS using some simple rules
9
8
  */
10
- export declare function format(css: string, { minify, tab_size }?: FormatOptions): string;
9
+ declare function format(css: string, {
10
+ minify,
11
+ tab_size
12
+ }?: FormatOptions): string;
11
13
  /**
12
14
  * Minify a string of CSS
13
15
  * @param {string} css The original CSS
14
16
  * @returns {string} The minified CSS
15
17
  */
16
- export declare function minify(css: string): string;
18
+ declare function minify(css: string): string;
19
+ //#endregion
20
+ export { FormatOptions, format, minify };
package/dist/index.js ADDED
@@ -0,0 +1,335 @@
1
+ import { ATTR_FLAG_NAMES, ATTR_OPERATOR_NAMES, NODE_TYPES, parse } from "@projectwallace/css-parser";
2
+ //#region src/lib/index.ts
3
+ const SPACE = " ";
4
+ const EMPTY_STRING = "";
5
+ const COLON = ":";
6
+ const SEMICOLON = ";";
7
+ const QUOTE = "\"";
8
+ const OPEN_PARENTHESES = "(";
9
+ const CLOSE_PARENTHESES = ")";
10
+ const OPEN_BRACKET = "[";
11
+ const CLOSE_BRACKET = "]";
12
+ const OPEN_BRACE = "{";
13
+ const CLOSE_BRACE = "}";
14
+ const COMMA = ",";
15
+ /**
16
+ * Format a string of CSS using some simple rules
17
+ */
18
+ function format(css, { minify = false, tab_size = void 0 } = Object.create(null)) {
19
+ if (tab_size !== void 0 && Number(tab_size) < 1) throw new TypeError("tab_size must be a number greater than 0");
20
+ const NEWLINE = minify ? EMPTY_STRING : "\n";
21
+ const OPTIONAL_SPACE = minify ? EMPTY_STRING : SPACE;
22
+ const LAST_SEMICOLON = minify ? EMPTY_STRING : SEMICOLON;
23
+ let comments = [];
24
+ let ast = parse(css, {
25
+ parse_atrule_preludes: false,
26
+ on_comment: minify ? void 0 : ({ start, end }) => {
27
+ comments.push(start, end);
28
+ }
29
+ });
30
+ let depth = 0;
31
+ function indent(size) {
32
+ if (minify === true) return EMPTY_STRING;
33
+ if (tab_size !== void 0) return SPACE.repeat(tab_size * size);
34
+ return " ".repeat(size);
35
+ }
36
+ /**
37
+ * Get and format comments from the CSS string within a range
38
+ * @param after After which offset to look for comments
39
+ * @param before Before which offset to look for comments
40
+ * @param level Indentation level (uses current depth if not specified)
41
+ * @returns The formatted comment string, or empty string if no comment found
42
+ */
43
+ function get_comment(after, before, level = depth) {
44
+ if (minify || after === void 0 || before === void 0) return EMPTY_STRING;
45
+ let buffer = EMPTY_STRING;
46
+ for (let i = 0; i < comments.length; i += 2) {
47
+ let start = comments[i];
48
+ if (start === void 0 || start < after) continue;
49
+ let end = comments[i + 1];
50
+ if (end === void 0 || end > before) break;
51
+ if (buffer.length > 0) buffer += NEWLINE + indent(level);
52
+ buffer += css.slice(start, end);
53
+ }
54
+ return buffer;
55
+ }
56
+ function unquote(str) {
57
+ return str.replace(/(?:^['"])|(?:['"]$)/g, EMPTY_STRING);
58
+ }
59
+ function print_string(str) {
60
+ str = str?.toString() || "";
61
+ return QUOTE + unquote(str) + QUOTE;
62
+ }
63
+ function print_operator(node) {
64
+ let parts = [];
65
+ let operator = node.text;
66
+ let code = operator.charCodeAt(0);
67
+ if (code === 43 || code === 45) parts.push(SPACE);
68
+ else if (code !== 44) parts.push(OPTIONAL_SPACE);
69
+ parts.push(operator);
70
+ if (code === 43 || code === 45) parts.push(SPACE);
71
+ else parts.push(OPTIONAL_SPACE);
72
+ return parts.join(EMPTY_STRING);
73
+ }
74
+ function print_list(nodes) {
75
+ let parts = [];
76
+ for (let node of nodes) {
77
+ if (node.type === NODE_TYPES.FUNCTION) {
78
+ let fn = node.name?.toLowerCase();
79
+ parts.push(fn, OPEN_PARENTHESES);
80
+ parts.push(print_list(node.children));
81
+ parts.push(CLOSE_PARENTHESES);
82
+ } else if (node.type === NODE_TYPES.DIMENSION) parts.push(node.value, node.unit?.toLowerCase());
83
+ else if (node.type === NODE_TYPES.STRING) parts.push(print_string(node.text));
84
+ else if (node.type === NODE_TYPES.OPERATOR) parts.push(print_operator(node));
85
+ else if (node.type === NODE_TYPES.PARENTHESIS) parts.push(OPEN_PARENTHESES, print_list(node.children), CLOSE_PARENTHESES);
86
+ else if (node.type === NODE_TYPES.URL && typeof node.value === "string") {
87
+ parts.push("url(");
88
+ let { value } = node;
89
+ if (/^['"]?data:/i.test(value)) parts.push(unquote(value));
90
+ else parts.push(print_string(value));
91
+ parts.push(CLOSE_PARENTHESES);
92
+ } else parts.push(node.text);
93
+ if (node.type !== NODE_TYPES.OPERATOR) {
94
+ if (node.has_next) {
95
+ if (node.next_sibling?.type !== NODE_TYPES.OPERATOR) parts.push(SPACE);
96
+ }
97
+ }
98
+ }
99
+ return parts.join(EMPTY_STRING);
100
+ }
101
+ function print_value(nodes) {
102
+ if (nodes === null) return EMPTY_STRING;
103
+ return print_list(nodes);
104
+ }
105
+ function print_declaration(node) {
106
+ let important = [];
107
+ if (node.is_important) {
108
+ let text = node.text;
109
+ let has_semicolon = text.endsWith(SEMICOLON);
110
+ let start = text.lastIndexOf("!");
111
+ let end = has_semicolon ? -1 : void 0;
112
+ important.push(OPTIONAL_SPACE, text.slice(start, end).toLowerCase());
113
+ }
114
+ let value = print_value(node.value);
115
+ let property = node.property;
116
+ if (property === "font") value = value.replace(/\s*\/\s*/, "/");
117
+ if (value === EMPTY_STRING && minify === true) value += SPACE;
118
+ if (!property.startsWith("--")) property = property.toLowerCase();
119
+ return property + COLON + OPTIONAL_SPACE + value + important.join(EMPTY_STRING);
120
+ }
121
+ function print_nth(node) {
122
+ let parts = [];
123
+ let a = node.nth_a;
124
+ let b = node.nth_b;
125
+ if (a) parts.push(a);
126
+ if (a && b) parts.push(OPTIONAL_SPACE);
127
+ if (b) {
128
+ if (a && !b.startsWith("-")) parts.push("+", OPTIONAL_SPACE);
129
+ parts.push(parseFloat(b));
130
+ }
131
+ return parts.join(EMPTY_STRING);
132
+ }
133
+ function print_nth_of(node) {
134
+ let parts = [];
135
+ if (node.children[0]?.type === NODE_TYPES.NTH_SELECTOR) {
136
+ parts.push(print_nth(node.children[0]));
137
+ parts.push(SPACE, "of", SPACE);
138
+ }
139
+ if (node.children[1]?.type === NODE_TYPES.SELECTOR_LIST) parts.push(print_inline_selector_list(node.children[1]));
140
+ return parts.join(EMPTY_STRING);
141
+ }
142
+ function print_simple_selector(node, is_first = false) {
143
+ let name = node.name ?? "";
144
+ switch (node.type) {
145
+ case NODE_TYPES.TYPE_SELECTOR: return name.toLowerCase() ?? "";
146
+ case NODE_TYPES.COMBINATOR: {
147
+ let text = node.text;
148
+ if (/^\s+$/.test(text)) return SPACE;
149
+ return (is_first ? EMPTY_STRING : OPTIONAL_SPACE) + text + OPTIONAL_SPACE;
150
+ }
151
+ case NODE_TYPES.PSEUDO_ELEMENT_SELECTOR:
152
+ case NODE_TYPES.PSEUDO_CLASS_SELECTOR: {
153
+ let parts = [COLON];
154
+ name = name.toLowerCase();
155
+ if (name === "before" || name === "after" || node.type === NODE_TYPES.PSEUDO_ELEMENT_SELECTOR) parts.push(COLON);
156
+ parts.push(name);
157
+ if (node.has_children) {
158
+ parts.push(OPEN_PARENTHESES);
159
+ if (node.children.length > 0) if (name === "highlight") parts.push(print_list(node.children));
160
+ else parts.push(print_inline_selector_list(node));
161
+ parts.push(CLOSE_PARENTHESES);
162
+ }
163
+ return parts.join(EMPTY_STRING);
164
+ }
165
+ case NODE_TYPES.ATTRIBUTE_SELECTOR: {
166
+ let parts = [OPEN_BRACKET, name.toLowerCase()];
167
+ if (node.attr_operator) {
168
+ parts.push(ATTR_OPERATOR_NAMES[node.attr_operator] ?? "");
169
+ if (typeof node.value === "string") parts.push(print_string(node.value));
170
+ if (node.attr_flags) parts.push(SPACE, ATTR_FLAG_NAMES[node.attr_flags] ?? "");
171
+ }
172
+ parts.push(CLOSE_BRACKET);
173
+ return parts.join(EMPTY_STRING);
174
+ }
175
+ default: return node.text;
176
+ }
177
+ }
178
+ function print_selector(node) {
179
+ if (node.type === NODE_TYPES.NTH_SELECTOR) return print_nth(node);
180
+ if (node.type === NODE_TYPES.NTH_OF_SELECTOR) return print_nth_of(node);
181
+ if (node.type === NODE_TYPES.SELECTOR_LIST) return print_inline_selector_list(node);
182
+ if (node.type === NODE_TYPES.LANG_SELECTOR) return print_string(node.text);
183
+ let parts = [];
184
+ node.children.forEach((child, index) => {
185
+ const part = print_simple_selector(child, index === 0);
186
+ parts.push(part);
187
+ });
188
+ return parts.join(EMPTY_STRING);
189
+ }
190
+ function print_inline_selector_list(node) {
191
+ let parts = [];
192
+ for (let selector of node) {
193
+ parts.push(print_selector(selector));
194
+ if (selector.has_next) parts.push(COMMA, OPTIONAL_SPACE);
195
+ }
196
+ return parts.join(EMPTY_STRING);
197
+ }
198
+ function print_selector_list(node) {
199
+ let lines = [];
200
+ let prev_end;
201
+ for (let selector of node) {
202
+ if (prev_end !== void 0) {
203
+ let comment = get_comment(prev_end, selector.start);
204
+ if (comment) lines.push(indent(depth) + comment);
205
+ }
206
+ let printed = print_selector(selector);
207
+ if (selector.has_next) printed += COMMA;
208
+ lines.push(indent(depth) + printed);
209
+ prev_end = selector.end;
210
+ }
211
+ return lines.join(NEWLINE);
212
+ }
213
+ function print_block(node) {
214
+ let lines = [];
215
+ depth++;
216
+ let children = node.children;
217
+ if (children.length === 0) {
218
+ let comment = get_comment(node.start, node.end);
219
+ if (comment) {
220
+ lines.push(indent(depth) + comment);
221
+ depth--;
222
+ lines.push(indent(depth) + CLOSE_BRACE);
223
+ return lines.join(NEWLINE);
224
+ }
225
+ }
226
+ let first_child = children[0];
227
+ let comment_before_first = get_comment(node.start, first_child?.start);
228
+ if (comment_before_first) lines.push(indent(depth) + comment_before_first);
229
+ let prev_end;
230
+ for (let child of children) {
231
+ if (prev_end !== void 0) {
232
+ let comment = get_comment(prev_end, child.start);
233
+ if (comment) lines.push(indent(depth) + comment);
234
+ }
235
+ let is_last = child.next_sibling?.type !== NODE_TYPES.DECLARATION;
236
+ if (child.type === NODE_TYPES.DECLARATION) {
237
+ let declaration = print_declaration(child);
238
+ let semi = is_last ? LAST_SEMICOLON : SEMICOLON;
239
+ lines.push(indent(depth) + declaration + semi);
240
+ } else if (child.type === NODE_TYPES.STYLE_RULE) {
241
+ if (prev_end !== void 0 && lines.length !== 0) lines.push(EMPTY_STRING);
242
+ lines.push(print_rule(child));
243
+ } else if (child.type === NODE_TYPES.AT_RULE) {
244
+ if (prev_end !== void 0 && lines.length !== 0) lines.push(EMPTY_STRING);
245
+ lines.push(indent(depth) + print_atrule(child));
246
+ }
247
+ prev_end = child.end;
248
+ }
249
+ let comment_after_last = get_comment(prev_end, node.end);
250
+ if (comment_after_last) lines.push(indent(depth) + comment_after_last);
251
+ depth--;
252
+ lines.push(indent(depth) + CLOSE_BRACE);
253
+ return lines.join(NEWLINE);
254
+ }
255
+ function print_rule(node) {
256
+ let lines = [];
257
+ if (node.first_child?.type === NODE_TYPES.SELECTOR_LIST) {
258
+ let list = print_selector_list(node.first_child);
259
+ let comment = get_comment(node.first_child.end, node.block?.start);
260
+ if (comment) list += NEWLINE + indent(depth) + comment;
261
+ list += OPTIONAL_SPACE + OPEN_BRACE;
262
+ if (!(node.block && (node.block.has_children || get_comment(node.block.start, node.block.end)))) list += CLOSE_BRACE;
263
+ lines.push(list);
264
+ }
265
+ if (node.block) {
266
+ if (node.block.has_children || get_comment(node.block.start, node.block.end)) lines.push(print_block(node.block));
267
+ }
268
+ return lines.join(NEWLINE);
269
+ }
270
+ /**
271
+ * Pretty-printing atrule preludes takes an insane amount of rules,
272
+ * so we're opting for a couple of 'good-enough' string replacements
273
+ * here to force some nice formatting.
274
+ * Should be OK perf-wise, since the amount of atrules in most
275
+ * stylesheets are limited, so this won't be called too often.
276
+ */
277
+ function print_atrule_prelude(prelude) {
278
+ return prelude.replace(/\s*([:,])/g, prelude.toLowerCase().includes("selector(") ? "$1" : "$1 ").replace(/\)([a-zA-Z])/g, ") $1").replace(/\s*(=>|<=)\s*/g, " $1 ").replace(/([^<>=\s])([<>])([^<>=\s])/g, `$1${OPTIONAL_SPACE}$2${OPTIONAL_SPACE}$3`).replace(/\s+/g, OPTIONAL_SPACE).replace(/calc\(\s*([^()+\-*/]+)\s*([*/+-])\s*([^()+\-*/]+)\s*\)/g, (_, left, operator, right) => {
279
+ let space = operator === "+" || operator === "-" ? SPACE : OPTIONAL_SPACE;
280
+ return `calc(${left.trim()}${space}${operator}${space}${right.trim()})`;
281
+ }).replace(/selector|url|supports|layer\(/gi, (match) => match.toLowerCase());
282
+ }
283
+ function print_atrule(node) {
284
+ let lines = [];
285
+ let name = [`@`, node.name.toLowerCase()];
286
+ if (node.prelude) name.push(SPACE, print_atrule_prelude(node.prelude.text));
287
+ if (node.block === null) name.push(SEMICOLON);
288
+ else {
289
+ name.push(OPTIONAL_SPACE, OPEN_BRACE);
290
+ if (!(!node.block.is_empty || get_comment(node.block.start, node.block.end))) name.push(CLOSE_BRACE);
291
+ }
292
+ lines.push(name.join(EMPTY_STRING));
293
+ if (node.block !== null) {
294
+ if (!node.block.is_empty || get_comment(node.block.start, node.block.end)) lines.push(print_block(node.block));
295
+ }
296
+ return lines.join(NEWLINE);
297
+ }
298
+ function print_stylesheet(node) {
299
+ let lines = [];
300
+ let children = node.children;
301
+ if (children.length === 0) return get_comment(0, node.end, 0);
302
+ let first_child = children[0];
303
+ if (first_child) {
304
+ let comment_before_first = get_comment(0, first_child.start, 0);
305
+ if (comment_before_first) lines.push(comment_before_first);
306
+ }
307
+ let prev_end;
308
+ for (let child of node) {
309
+ if (prev_end !== void 0) {
310
+ let comment = get_comment(prev_end, child.start, 0);
311
+ if (comment) lines.push(comment);
312
+ }
313
+ if (child.type === NODE_TYPES.STYLE_RULE) lines.push(print_rule(child));
314
+ else if (child.type === NODE_TYPES.AT_RULE) lines.push(print_atrule(child));
315
+ prev_end = child.end;
316
+ if (child.has_next) {
317
+ if (!(child.next_sibling && get_comment(child.end, child.next_sibling.start, 0))) lines.push(EMPTY_STRING);
318
+ }
319
+ }
320
+ let comment_after_last = get_comment(prev_end, node.end, 0);
321
+ if (comment_after_last) lines.push(comment_after_last);
322
+ return lines.join(NEWLINE);
323
+ }
324
+ return print_stylesheet(ast).trimEnd();
325
+ }
326
+ /**
327
+ * Minify a string of CSS
328
+ * @param {string} css The original CSS
329
+ * @returns {string} The minified CSS
330
+ */
331
+ function minify(css) {
332
+ return format(css, { minify: true });
333
+ }
334
+ //#endregion
335
+ export { format, minify };
package/package.json CHANGED
@@ -1,65 +1,61 @@
1
1
  {
2
2
  "name": "@projectwallace/format-css",
3
- "version": "2.2.5",
3
+ "version": "3.0.0",
4
4
  "description": "Fast, small, zero-config library to format or minify CSS with basic rules.",
5
+ "keywords": [
6
+ "css",
7
+ "format",
8
+ "formatter",
9
+ "prettier",
10
+ "prettifier",
11
+ "pretty",
12
+ "print",
13
+ "style",
14
+ "stylesheet"
15
+ ],
16
+ "homepage": "https://github.com/projectwallace/format-css",
17
+ "license": "MIT",
18
+ "author": "Bart Veneman <bart@projectwallace.com>",
5
19
  "repository": {
6
20
  "type": "git",
7
21
  "url": "git+https://github.com/projectwallace/format-css.git"
8
22
  },
9
- "homepage": "https://github.com/projectwallace/format-css",
10
- "issues": "https://github.com/projectwallace/format-css/issues",
11
- "license": "MIT",
12
- "author": "Bart Veneman <bart@projectwallace.com>",
13
- "engines": {
14
- "node": ">=18.0.0"
23
+ "bin": {
24
+ "format-css": "./dist/cli.mjs"
15
25
  },
26
+ "files": [
27
+ "dist"
28
+ ],
16
29
  "type": "module",
17
- "main": "./dist/format-css.js",
30
+ "main": "./dist/index.js",
31
+ "types": "./dist/index.d.ts",
18
32
  "exports": {
19
33
  "types": "./dist/index.d.ts",
20
- "default": "./dist/format-css.js"
34
+ "default": "./dist/index.js"
21
35
  },
22
- "types": "./dist/index.d.ts",
23
36
  "scripts": {
24
- "build": "vite build",
37
+ "build": "tsdown",
25
38
  "test": "vitest --coverage",
26
39
  "check": "tsc",
27
40
  "prettier": "prettier --check index.ts test/**/*.ts",
28
- "lint": "oxlint",
29
- "lint-package": "publint"
41
+ "lint": "oxlint && oxfmt --check"
42
+ },
43
+ "dependencies": {
44
+ "@projectwallace/css-parser": "^0.13.5"
30
45
  },
31
46
  "devDependencies": {
32
- "@codecov/vite-plugin": "^1.9.1",
47
+ "@codecov/rollup-plugin": "^1.9.1",
48
+ "@types/node": "^25.5.0",
33
49
  "@vitest/coverage-v8": "^4.0.3",
34
- "c8": "^10.1.3",
50
+ "oxfmt": "^0.36.0",
35
51
  "oxlint": "^1.24.0",
36
- "prettier": "^3.6.2",
37
52
  "publint": "^0.3.15",
53
+ "tsdown": "^0.21.0",
38
54
  "typescript": "^5.9.3",
39
- "vite": "^6.2.6",
40
- "vite-plugin-dts": "^4.5.4",
41
55
  "vitest": "^4.0.3"
42
56
  },
43
- "files": [
44
- "dist"
45
- ],
46
- "keywords": [
47
- "css",
48
- "stylesheet",
49
- "formatter",
50
- "format",
51
- "print",
52
- "prettifier",
53
- "pretty",
54
- "prettier"
55
- ],
56
- "prettier": {
57
- "semi": false,
58
- "useTabs": true,
59
- "printWidth": 140,
60
- "singleQuote": true
57
+ "engines": {
58
+ "node": ">=20.12.0"
61
59
  },
62
- "dependencies": {
63
- "@projectwallace/css-parser": "^0.13.5"
64
- }
60
+ "issues": "https://github.com/projectwallace/format-css/issues"
65
61
  }
@@ -1,216 +0,0 @@
1
- import { parse as V, NODE_TYPES as u, ATTR_OPERATOR_NAMES as X, ATTR_FLAG_NAMES as z } from "@projectwallace/css-parser";
2
- const _ = " ", p = "", y = ":", A = ";", d = '"', v = "(", g = ")", tt = "[", et = "]", M = "{", N = "}", U = ",";
3
- function rt(L, { minify: O = !1, tab_size: m = void 0 } = /* @__PURE__ */ Object.create(null)) {
4
- if (m !== void 0 && Number(m) < 1)
5
- throw new TypeError("tab_size must be a number greater than 0");
6
- const E = O ? p : `
7
- `, c = O ? p : _, D = O ? p : A;
8
- let S = [], B = V(L, {
9
- parse_atrule_preludes: !1,
10
- on_comment: O ? void 0 : ({ start: t, end: e }) => {
11
- S.push(t, e);
12
- }
13
- }), a = 0;
14
- function o(t) {
15
- return O === !0 ? p : m !== void 0 ? _.repeat(m * t) : " ".repeat(t);
16
- }
17
- function f(t, e, s = a) {
18
- if (O || t === void 0 || e === void 0)
19
- return p;
20
- let r = p;
21
- for (let i = 0; i < S.length; i += 2) {
22
- let l = S[i];
23
- if (l === void 0 || l < t) continue;
24
- let n = S[i + 1];
25
- if (n === void 0 || n > e) break;
26
- r.length > 0 && (r += E + o(s)), r += L.slice(l, n);
27
- }
28
- return r;
29
- }
30
- function x(t) {
31
- return t.replace(/(?:^['"])|(?:['"]$)/g, p);
32
- }
33
- function C(t) {
34
- return t = (t == null ? void 0 : t.toString()) || "", d + x(t) + d;
35
- }
36
- function H(t) {
37
- let e = [], s = t.text, r = s.charCodeAt(0);
38
- return r === 43 || r === 45 ? e.push(_) : r !== 44 && e.push(c), e.push(s), r === 43 || r === 45 ? e.push(_) : e.push(c), e.join(p);
39
- }
40
- function b(t) {
41
- var s, r, i;
42
- let e = [];
43
- for (let l of t) {
44
- if (l.type === u.FUNCTION) {
45
- let n = (s = l.name) == null ? void 0 : s.toLowerCase();
46
- e.push(n, v), e.push(b(l.children)), e.push(g);
47
- } else if (l.type === u.DIMENSION)
48
- e.push(l.value, (r = l.unit) == null ? void 0 : r.toLowerCase());
49
- else if (l.type === u.STRING)
50
- e.push(C(l.text));
51
- else if (l.type === u.OPERATOR)
52
- e.push(H(l));
53
- else if (l.type === u.PARENTHESIS)
54
- e.push(v, b(l.children), g);
55
- else if (l.type === u.URL && typeof l.value == "string") {
56
- e.push("url(");
57
- let { value: n } = l;
58
- /^['"]?data:/i.test(n) ? e.push(x(n)) : e.push(C(n)), e.push(g);
59
- } else
60
- e.push(l.text);
61
- l.type !== u.OPERATOR && l.has_next && ((i = l.next_sibling) == null ? void 0 : i.type) !== u.OPERATOR && e.push(_);
62
- }
63
- return e.join(p);
64
- }
65
- function Y(t) {
66
- return t === null ? p : b(t);
67
- }
68
- function F(t) {
69
- let e = [];
70
- if (t.is_important) {
71
- let i = t.text, l = i.endsWith(A), n = i.lastIndexOf("!"), T = l ? -1 : void 0;
72
- e.push(c, i.slice(n, T).toLowerCase());
73
- }
74
- let s = Y(t.value), r = t.property;
75
- return r === "font" && (s = s.replace(/\s*\/\s*/, "/")), s === p && O === !0 && (s += _), r.startsWith("--") || (r = r.toLowerCase()), r + y + c + s + e.join(p);
76
- }
77
- function P(t) {
78
- let e = [], s = t.nth_a, r = t.nth_b;
79
- return s && e.push(s), s && r && e.push(c), r && (s && !r.startsWith("-") && e.push("+", c), e.push(parseFloat(r))), e.join(p);
80
- }
81
- function G(t) {
82
- var s, r;
83
- let e = [];
84
- return ((s = t.children[0]) == null ? void 0 : s.type) === u.NTH_SELECTOR && (e.push(P(t.children[0])), e.push(_, "of", _)), ((r = t.children[1]) == null ? void 0 : r.type) === u.SELECTOR_LIST && e.push(k(t.children[1])), e.join(p);
85
- }
86
- function W(t, e = !1) {
87
- let s = t.name ?? "";
88
- switch (t.type) {
89
- case u.TYPE_SELECTOR:
90
- return s.toLowerCase() ?? "";
91
- case u.COMBINATOR: {
92
- let r = t.text;
93
- return /^\s+$/.test(r) ? _ : (e ? p : c) + r + c;
94
- }
95
- case u.PSEUDO_ELEMENT_SELECTOR:
96
- case u.PSEUDO_CLASS_SELECTOR: {
97
- let r = [y];
98
- return s = s.toLowerCase(), (s === "before" || s === "after" || t.type === u.PSEUDO_ELEMENT_SELECTOR) && r.push(y), r.push(s), t.has_children && (r.push(v), t.children.length > 0 && (s === "highlight" ? r.push(b(t.children)) : r.push(k(t))), r.push(g)), r.join(p);
99
- }
100
- case u.ATTRIBUTE_SELECTOR: {
101
- let r = [tt, s.toLowerCase()];
102
- return t.attr_operator && (r.push(X[t.attr_operator] ?? ""), typeof t.value == "string" && r.push(C(t.value)), t.attr_flags && r.push(_, z[t.attr_flags] ?? "")), r.push(et), r.join(p);
103
- }
104
- default:
105
- return t.text;
106
- }
107
- }
108
- function I(t) {
109
- if (t.type === u.NTH_SELECTOR)
110
- return P(t);
111
- if (t.type === u.NTH_OF_SELECTOR)
112
- return G(t);
113
- if (t.type === u.SELECTOR_LIST)
114
- return k(t);
115
- if (t.type === u.LANG_SELECTOR)
116
- return C(t.text);
117
- let e = [];
118
- return t.children.forEach((s, r) => {
119
- const i = W(s, r === 0);
120
- e.push(i);
121
- }), e.join(p);
122
- }
123
- function k(t) {
124
- let e = [];
125
- for (let s of t)
126
- e.push(I(s)), s.has_next && e.push(U, c);
127
- return e.join(p);
128
- }
129
- function K(t) {
130
- let e = [], s;
131
- for (let r of t) {
132
- if (s !== void 0) {
133
- let l = f(s, r.start);
134
- l && e.push(o(a) + l);
135
- }
136
- let i = I(r);
137
- r.has_next && (i += U), e.push(o(a) + i), s = r.end;
138
- }
139
- return e.join(E);
140
- }
141
- function j(t) {
142
- var T;
143
- let e = [];
144
- a++;
145
- let s = t.children;
146
- if (s.length === 0) {
147
- let h = f(t.start, t.end);
148
- if (h)
149
- return e.push(o(a) + h), a--, e.push(o(a) + N), e.join(E);
150
- }
151
- let r = s[0], i = f(t.start, r == null ? void 0 : r.start);
152
- i && e.push(o(a) + i);
153
- let l;
154
- for (let h of s) {
155
- if (l !== void 0) {
156
- let R = f(l, h.start);
157
- R && e.push(o(a) + R);
158
- }
159
- let Z = ((T = h.next_sibling) == null ? void 0 : T.type) !== u.DECLARATION;
160
- if (h.type === u.DECLARATION) {
161
- let R = F(h), J = Z ? D : A;
162
- e.push(o(a) + R + J);
163
- } else h.type === u.STYLE_RULE ? (l !== void 0 && e.length !== 0 && e.push(p), e.push($(h))) : h.type === u.AT_RULE && (l !== void 0 && e.length !== 0 && e.push(p), e.push(o(a) + w(h)));
164
- l = h.end;
165
- }
166
- let n = f(l, t.end);
167
- return n && e.push(o(a) + n), a--, e.push(o(a) + N), e.join(E);
168
- }
169
- function $(t) {
170
- var s, r;
171
- let e = [];
172
- if (((s = t.first_child) == null ? void 0 : s.type) === u.SELECTOR_LIST) {
173
- let i = K(t.first_child), l = f(t.first_child.end, (r = t.block) == null ? void 0 : r.start);
174
- l && (i += E + o(a) + l), i += c + M, t.block && (t.block.has_children || f(t.block.start, t.block.end)) || (i += N), e.push(i);
175
- }
176
- return t.block && (t.block.has_children || f(t.block.start, t.block.end)) && e.push(j(t.block)), e.join(E);
177
- }
178
- function q(t) {
179
- return t.replace(/\s*([:,])/g, t.toLowerCase().includes("selector(") ? "$1" : "$1 ").replace(/\)([a-zA-Z])/g, ") $1").replace(/\s*(=>|<=)\s*/g, " $1 ").replace(/([^<>=\s])([<>])([^<>=\s])/g, `$1${c}$2${c}$3`).replace(/\s+/g, c).replace(/calc\(\s*([^()+\-*/]+)\s*([*/+-])\s*([^()+\-*/]+)\s*\)/g, (e, s, r, i) => {
180
- let l = r === "+" || r === "-" ? _ : c;
181
- return `calc(${s.trim()}${l}${r}${l}${i.trim()})`;
182
- }).replace(/selector|url|supports|layer\(/gi, (e) => e.toLowerCase());
183
- }
184
- function w(t) {
185
- let e = [], s = ["@", t.name.toLowerCase()];
186
- return t.prelude && s.push(_, q(t.prelude.text)), t.block === null ? s.push(A) : (s.push(c, M), !t.block.is_empty || f(t.block.start, t.block.end) || s.push(N)), e.push(s.join(p)), t.block !== null && (!t.block.is_empty || f(t.block.start, t.block.end)) && e.push(j(t.block)), e.join(E);
187
- }
188
- function Q(t) {
189
- let e = [], s = t.children;
190
- if (s.length === 0)
191
- return f(0, t.end, 0);
192
- let r = s[0];
193
- if (r) {
194
- let n = f(0, r.start, 0);
195
- n && e.push(n);
196
- }
197
- let i;
198
- for (let n of t) {
199
- if (i !== void 0) {
200
- let T = f(i, n.start, 0);
201
- T && e.push(T);
202
- }
203
- n.type === u.STYLE_RULE ? e.push($(n)) : n.type === u.AT_RULE && e.push(w(n)), i = n.end, n.has_next && (n.next_sibling && f(n.end, n.next_sibling.start, 0) || e.push(p));
204
- }
205
- let l = f(i, t.end, 0);
206
- return l && e.push(l), e.join(E);
207
- }
208
- return Q(B).trimEnd();
209
- }
210
- function lt(L) {
211
- return rt(L, { minify: !0 });
212
- }
213
- export {
214
- rt as format,
215
- lt as minify
216
- };