11import { parseCSS } from '../parse/css' ;
2- import type { ParsedAtRule , ParsedStyleRule } from '../parse/css' ;
2+ import type { ParsedAtRule , ParsedDeclaration , ParsedStyleRule } from '../parse/css' ;
33
44export 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