@@ -25,126 +25,206 @@ module.exports = {
2525 * @param {node } node
2626 */
2727 process : function ( nodeType , node ) {
28+ var order = this . _order , // sort order of properties
29+ sorted = [ ] , // list of declarations that should be sorted
30+ deleted = [ ] , // list of nodes that should be removed from parent node
31+ p , // property's name
32+ NODES = [ 'declaration' , 's' , 'comment' , 'atruleb' , 'atrules' , 'atruler' ] , // allowed nodes
33+ SC = [ 's' , 'comment' ] , // spaces and comments
34+ i , l , j , nl ; // counters for loops
35+
36+ // TODO: Think it through!
2837 if ( nodeType !== 'block' ) return ;
2938
30- var order = this . _order ;
31- var nodeExt = this . _createNodeExt ( node ) ;
32- var firstSymbols = nodeExt [ 0 ] . decl || nodeExt . shift ( ) ;
33- var lastSymbols = nodeExt [ nodeExt . length - 1 ] . decl || nodeExt . pop ( ) ;
34-
35- nodeExt . sort ( function ( a , b ) {
36- var indexA = order [ a . decl ] || { prop : - 1 } ;
37- var indexB = order [ b . decl ] || { prop : - 1 } ;
38- var groupDelta = indexA . group - indexB . group ;
39- var propDelta = indexA . prop - indexB . prop ;
40-
41- return groupDelta !== 0 ? groupDelta : propDelta ;
42- } ) ;
43-
44- firstSymbols && nodeExt . unshift ( firstSymbols ) ;
45- lastSymbols && nodeExt . push ( lastSymbols ) ;
46-
47- this . _applyNodeExt ( node , nodeExt ) ;
48- } ,
39+ // Check every child node.
40+ // If it is declaration (property-value pair, e.g. `color: tomato`),
41+ // combine it with spaces, semicolon and comments and move them from
42+ // current node to a separate list for further sorting:
43+ for ( i = 0 , l = node . length ; i < l ; i ++ ) {
44+ if ( NODES . indexOf ( node [ i ] [ 0 ] ) === - 1 ) continue ;
45+
46+ // Save preceding spaces and comments, if there are any, and mark
47+ // them for removing from parent node:
48+ var sc0 = _checkSC0 ( i ) ;
49+ if ( ! sc0 ) continue ;
50+
51+ if ( ! node [ i ] ) {
52+ deleted . splice ( deleted . length - sc0 . length , deleted . length + 1 ) ;
53+ break ;
54+ }
4955
50- /**
51- * Internal. Smart split bunch on '\n' symbols;
52- * @param {Array } bunch
53- * @param {Boolean } isPrevDeclExists
54- */
55- _splitBunch : function ( bunch , isPrevDeclExists ) {
56- var nextBunch = [ ] ;
57- var declAlready = false ;
58- var flag = false ;
59- var item ;
60- var indexOf ;
56+ // Check if there is a property (e.g. `color`) inside
57+ // the declaration. If not, proceed with the next node:
58+ p = null ;
59+ for ( j = 1 , nl = node [ i ] . length ; j < nl ; j ++ ) {
60+ if ( node [ i ] [ j ] [ 0 ] === 'property' ) {
61+ if ( node [ i ] [ j ] [ 1 ] [ 0 ] === 'variable' ) {
62+ p = '$variable' ;
63+ } else {
64+ p = node [ i ] [ j ] [ 1 ] [ 1 ] ;
65+ }
66+ break ;
67+ } else if ( node [ i ] [ j ] [ 0 ] === 'atkeyword' ) {
68+ if ( [ 'import' , 'include' , 'extend' ] . indexOf ( node [ i ] [ j ] [ 1 ] [ 1 ] ) > - 1 ) {
69+ p = '$' + node [ i ] [ j ] [ 1 ] [ 1 ] ;
70+ }
71+ break ;
72+ }
73+ }
6174
62- for ( var i = 0 ; i < bunch . length ; ++ i ) {
63- if ( flag ) { nextBunch . push ( bunch [ i ] ) ; continue ; }
64- if ( bunch [ i ] [ 0 ] === 'declaration' ) { declAlready = true ; }
75+ if ( ! p ) {
76+ deleted = [ ] ;
77+ continue ;
78+ }
6579
66- if ( isPrevDeclExists && ! declAlready ) continue ;
67- if ( bunch [ i ] [ 0 ] !== 's' ) continue ;
80+ // Combine declaration node with other relevant information
81+ // (property index, semicolon, spaces and comments):
82+ var n = {
83+ node : node [ i ] ,
84+ sc0 : sc0 ,
85+ delim : [ ] ,
86+ // If the declaration's property is in order's list, save its
87+ // group and property indices. Otherwise set them to 10000, so
88+ // declaration appears at the bottom of a sorted list:
89+ gi : ( order [ p ] && order [ p ] . group > - 1 ? order [ p ] . group + 2 : 10000 ) , // group index
90+ pi : ( order [ p ] && order [ p ] . prop > - 1 ? order [ p ] . prop + 2 : 10000 ) // property index
91+ } ;
92+
93+ // Mark the declaration node to remove it later from parent node:
94+ deleted . push ( i ) ;
95+
96+ // If there is `;` right after the declaration, save it with the
97+ // declaration and mark it for removing from parent node:
98+ if ( node [ i + 1 ] && node [ i + 1 ] [ 0 ] === 'decldelim' ) {
99+ n . delim . push ( node [ i + 1 ] ) ;
100+ deleted . push ( i + 1 ) ;
101+ i ++ ;
102+ }
68103
69- var indexOfNewLine = bunch [ i ] [ 1 ] . indexOf ( '\n' ) ;
70104
71- if ( indexOfNewLine === - 1 ) continue ;
105+ // Save spaces and comments which follow right after the declaration
106+ // and mark them for removing from parent node:
107+ n . sc1 = _checkSC1 ( i ) ;
72108
73- nextBunch . push ( [ 's' , bunch [ i ] [ 1 ] . substr ( indexOfNewLine + 1 ) ] ) ;
74- bunch [ i ] [ 1 ] = bunch [ i ] [ 1 ] . substr ( 0 , indexOfNewLine + 1 ) ;
109+ // Move the declaration node to a separate list for further sorting:
110+ sorted . push ( n ) ;
75111
76- flag = i + 1 ;
77112 }
78113
79- if ( nextBunch . length === 1 && nextBunch [ 0 ] [ 0 ] === 's' ) {
80- item = nextBunch [ 0 ] ;
81- indexOf = item [ 1 ] . lastIndexOf ( '\n' ) ;
82114
83- indexOf !== - 1 && ( item [ 1 ] = item [ 1 ] . substr ( indexOf + 1 ) ) ;
115+
116+ // Remove all nodes, that were moved to a `sorted` list, from parent node:
117+ for ( i = 0 , j = 0 , l = deleted . length ; i < l ; i ++ , j ++ ) {
118+ // Since every time we remove an element from an array, array's
119+ // length reduces by 1, save number of already removed elements (`j`)
120+ // and use it to find the index of a next element to remove:
121+ node . splice ( deleted [ i ] - j , 1 ) ;
84122 }
85123
86- flag && bunch . splice ( flag ) ;
124+ // Sort declarations saved for sorting:
125+ sorted . sort ( function ( a , b ) {
126+ // If a's group index is higher than b's group index, in a sorted
127+ // list a appears after b:
128+ if ( a . gi !== b . gi ) return a . gi - b . gi ;
87129
88- return nextBunch ;
89- } ,
130+ // If a and b have the same group index, and a's property index is
131+ // higher than b's property index, in a sorted list a appears after
132+ // b:
133+ return a . pi - b . pi ;
134+ } ) ;
90135
91- /**
92- * Internal. Create extended node in format of list
93- * {
94- * data:[,,declaration,]
95- * decl: declarationPropertyName
96- * };
97- * @param {node } node
98- */
99- _createNodeExt : function ( node ) {
100- var extNode = [ ] ;
101- var bunch = [ ] ;
102- var nextBunch ;
103- var prevDeclPropertyName ;
104-
105- node . forEach ( function ( node ) {
106- if ( node [ 0 ] === 'declaration' ) {
107- nextBunch = this . _splitBunch ( bunch , prevDeclPropertyName ) ;
108- extNode . push ( { data : bunch , decl : prevDeclPropertyName } ) ;
109- bunch = nextBunch ;
110- prevDeclPropertyName = node [ 1 ] [ 1 ] [ 1 ] ;
136+ // Build all nodes back together. First go sorted declarations, then
137+ // everything else:
138+ if ( sorted . length > 0 ) {
139+ for ( i = sorted . length - 1 , l = - 1 ; i > l ; i -- ) {
140+
141+ // Divide declarations from different groups with an empty line:
142+ if ( sorted [ i - 1 ] && sorted [ i ] . gi > sorted [ i - 1 ] . gi ) {
143+ if ( sorted [ i ] . sc0 [ 0 ] && sorted [ i ] . sc0 [ 0 ] [ 0 ] === 's' &&
144+ sorted [ i ] . sc0 [ 0 ] [ 1 ] . match ( / \n / g) &&
145+ sorted [ i ] . sc0 [ 0 ] [ 1 ] . match ( / \n / g) . length < 2 ) {
146+ sorted [ i ] . sc0 . unshift ( [ 's' , '\n' ] ) ;
147+ }
148+ }
149+
150+ sorted [ i ] . sc0 . reverse ( ) ;
151+ sorted [ i ] . sc1 . reverse ( ) ;
152+
153+ for ( j = 0 , nl = sorted [ i ] . sc1 . length ; j < nl ; j ++ ) {
154+ node . unshift ( sorted [ i ] . sc1 [ j ] ) ;
155+ }
156+ if ( sorted [ i ] . delim . length > 0 ) node . unshift ( [ 'decldelim' ] ) ;
157+ node . unshift ( sorted [ i ] . node ) ;
158+ for ( j = 0 , nl = sorted [ i ] . sc0 . length ; j < nl ; j ++ ) {
159+ node . unshift ( sorted [ i ] . sc0 [ j ] ) ;
160+ }
111161 }
112- bunch . push ( node ) ;
113- } , this ) ;
162+ }
114163
115- nextBunch = this . _splitBunch ( bunch , prevDeclPropertyName ) ;
116- extNode . push ( { data : bunch , decl : prevDeclPropertyName } ) ;
117- nextBunch . length && extNode . push ( { data : nextBunch } ) ;
164+ /**
165+ * Check if there are any comments or spaces before the declaration
166+ * @returns {Array } List of nodes with spaces and comments
167+ * @private
168+ */
169+ function _checkSC0 ( ) {
170+ var sc = [ ] ,
171+ d = [ ] ;
172+
173+ for ( ; i < l ; i ++ ) {
174+ // If there is no node, or it is nor spaces neither comment, stop:
175+ if ( ! node [ i ] ||
176+ NODES . indexOf ( node [ i ] [ 0 ] ) === - 1 ) {
177+ return false ;
178+ }
179+
180+ // If node is declaration or @-rule:
181+ if ( SC . indexOf ( node [ i ] [ 0 ] ) === - 1 ) break ;
182+
183+ sc . push ( node [ i ] ) ;
184+ d . push ( i ) ;
185+ }
118186
119- return extNode ;
120- } ,
187+ deleted = deleted . concat ( d ) ;
121188
122- /**
123- * Internal. Add delimiter at the end of groups of properties
124- * @param {extNode } extNodePrev
125- * @param {extNode } extNode
126- */
127- _addGroupDelimiter : function ( extNodePrev , extNode ) {
128- if ( ! extNodePrev . decl || ! extNode . decl ) return ;
189+ return sc ;
190+ }
129191
130- var indexA = this . _order [ extNodePrev . decl ] ;
131- var indexB = this . _order [ extNode . decl ] ;
132- var isGroupBorder = indexA && indexB && indexA . group !== indexB . group ;
192+ /**
193+ * Check if there are any comments or spaces after the declaration
194+ * @returns {Array } List of nodes with spaces and comments
195+ * @private
196+ */
197+ function _checkSC1 ( ) {
198+ var sc = [ ] , // nodes with spaces and comments
199+ d = [ ] ;
200+
201+ // Check every next node:
202+ for ( ; i < l ; i ++ ) {
203+ // If there is no node, or it is nor spaces neither comment, stop:
204+ if ( ! node [ i + 1 ] || SC . indexOf ( node [ i + 1 ] [ 0 ] ) === - 1 ) break ;
205+
206+ if ( node [ i + 1 ] [ 0 ] === 'comment' ) {
207+ sc . push ( node [ i + 1 ] ) ;
208+ d . push ( i + 1 ) ;
209+ continue ;
210+ }
211+
212+ var lbIndex = node [ i + 1 ] [ 1 ] . indexOf ( '\n' ) ;
213+
214+ if ( lbIndex > - 1 ) {
215+ // TODO: Don't push an empty array
216+ sc . push ( [ 's' , node [ i + 1 ] [ 1 ] . substring ( 0 , lbIndex ) ] ) ;
217+ node [ i + 1 ] [ 1 ] = node [ i + 1 ] [ 1 ] . substring ( lbIndex ) ;
218+ break ;
219+ }
220+
221+ sc . push ( node [ i + 1 ] ) ;
222+ d . push ( i + 1 ) ;
223+ }
133224
134- if ( isGroupBorder ) extNode . data . unshift ( [ 's' , '\n' ] ) ;
135- } ,
225+ deleted = deleted . concat ( d ) ;
136226
137- /**
138- * Internal. apply extNode back at common format node.
139- * @param {node } node
140- * @param {extNode } extNode
141- */
142- _applyNodeExt : function ( node , extNode ) {
143- node . splice ( 0 , node . length ) ;
144- extNode . forEach ( function ( item , i ) {
145- i > 0 && this . _addGroupDelimiter ( extNode [ i - 1 ] , item ) ;
146- node . push . apply ( node , item . data ) ;
147- } , this ) ;
227+ return sc ;
228+ }
148229 }
149-
150230} ;
0 commit comments