Skip to content

Commit fdcc036

Browse files
committed
Sort CSS declarations
1 parent 1e86e8a commit fdcc036

1 file changed

Lines changed: 40 additions & 23 deletions

File tree

src/utils/format/css.ts

Lines changed: 40 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,12 @@
11
import {parseCSS} from '../parse/css';
2-
import type {ParsedAtRule, ParsedStyleRule} from '../parse/css';
2+
import type {ParsedAtRule, ParsedDeclaration, ParsedStyleRule} from '../parse/css';
33

44
export function formatCSS(cssText: string): string {
55
const parsed = parseCSS(cssText);
66

77
const lines: string[] = [];
88
const tab = ' ';
99

10-
function isStyleRule(rule: ParsedAtRule | ParsedStyleRule): rule is ParsedStyleRule {
11-
return 'selectors' in rule;
12-
}
13-
14-
function clearEmpty(rules: Array<ParsedAtRule | ParsedStyleRule>) {
15-
for (let i = rules.length - 1; i >= 0; i--) {
16-
const rule = rules[i];
17-
if (isStyleRule(rule)) {
18-
if (rule.declarations.length === 0) {
19-
rules.splice(i, 1);
20-
}
21-
} else {
22-
clearEmpty(rule.rules);
23-
if (rule.rules.length === 0) {
24-
rules.splice(i, 1);
25-
}
26-
}
27-
}
28-
}
29-
3010
function formatRule(rule: ParsedAtRule | ParsedStyleRule, indent: string) {
3111
if (isStyleRule(rule)) {
3212
formatStyleRule(rule as ParsedStyleRule, indent);
@@ -46,13 +26,50 @@ export function formatCSS(cssText: string): string {
4626
selectors.forEach((selector, i) => {
4727
lines.push(`${indent}${selector}${i < lastSelectorIndex ? ',' : ' {'}`);
4828
});
49-
declarations.forEach(({property, value, important}) => {
29+
const sorted = sortDeclarations(declarations);
30+
sorted.forEach(({property, value, important}) => {
5031
lines.push(`${indent}${tab}${property}: ${value}${important ? ' !important' : ''};`);
5132
});
5233
lines.push(`${indent}}`);
5334
}
5435

55-
clearEmpty(parsed);
36+
clearEmptyRules(parsed);
5637
parsed.forEach((rule) => formatRule(rule, ''));
5738
return lines.join('\n');
5839
}
40+
41+
function sortDeclarations(declarations: ParsedDeclaration[]) {
42+
const prefixRegex = /^-[a-z]-/;
43+
return [...declarations].sort((a, b) => {
44+
const aProp = a.property;
45+
const bProp = b.property;
46+
const aPrefix = aProp.match(prefixRegex)?.[0] ?? '';
47+
const bPrefix = bProp.match(prefixRegex)?.[0] ?? '';
48+
const aNorm = aPrefix ? aProp.replace(prefixRegex, '') : aProp;
49+
const bNorm = bPrefix ? bProp.replace(prefixRegex, '') : bProp;
50+
if (aNorm === bNorm) {
51+
return aPrefix.localeCompare(bPrefix);
52+
}
53+
return aNorm.localeCompare(bNorm);
54+
});
55+
}
56+
57+
function isStyleRule(rule: ParsedAtRule | ParsedStyleRule): rule is ParsedStyleRule {
58+
return 'selectors' in rule;
59+
}
60+
61+
function clearEmptyRules(rules: Array<ParsedAtRule | ParsedStyleRule>) {
62+
for (let i = rules.length - 1; i >= 0; i--) {
63+
const rule = rules[i];
64+
if (isStyleRule(rule)) {
65+
if (rule.declarations.length === 0) {
66+
rules.splice(i, 1);
67+
}
68+
} else {
69+
clearEmptyRules(rule.rules);
70+
if (rule.rules.length === 0) {
71+
rules.splice(i, 1);
72+
}
73+
}
74+
}
75+
}

0 commit comments

Comments
 (0)