@@ -3,6 +3,63 @@ var objectAssign = require('object-assign');
33var path = require ( 'path' ) ;
44var fs = require ( 'fs' ) ;
55
6+ module . exports = postcss . plugin ( 'postcss-sorting' , function ( opts ) {
7+ return function ( css ) {
8+ plugin ( css , opts ) ;
9+ } ;
10+ } ) ;
11+
12+ function plugin ( css , opts ) {
13+ // Verify options and use defaults if not specified
14+ opts = verifyOptions ( opts ) ;
15+
16+ var enableSorting = true ;
17+
18+ css . walk ( function ( node ) {
19+ if ( node . type === 'comment' && node . parent . type === 'root' ) {
20+ if ( node . text === 'postcss-sorting: on' ) {
21+ enableSorting = true ;
22+ } else if ( node . text === 'postcss-sorting: off' ) {
23+ enableSorting = false ;
24+ }
25+ }
26+
27+ if ( ! enableSorting ) {
28+ return ;
29+ }
30+
31+ // Process only rules and atrules with nodes
32+ if ( ( node . type === 'rule' || node . type === 'atrule' ) && node . nodes && node . nodes . length ) {
33+ // Nodes for sorting
34+ var processed = [ ] ;
35+
36+ // Add indexes to nodes
37+ node . each ( function ( childNode , index ) {
38+ processed = processMostNodes ( childNode , index , opts , processed ) ;
39+ } ) ;
40+
41+ // Add last comments in the rule. Need this because last comments are not belonging to anything
42+ node . each ( function ( childNode , index ) {
43+ processed = processLastComments ( childNode , index , processed ) ;
44+ } ) ;
45+
46+ // Sort declarations saved for sorting
47+ processed . sort ( sortByIndexes ) ;
48+
49+ // Replace rule content with sorted one
50+ if ( processed . length ) {
51+ node . removeAll ( ) ;
52+ node . append ( processed ) ;
53+ }
54+
55+ // Taking care of empty lines
56+ node . each ( function ( childNode ) {
57+ formatNodes ( childNode , opts ) ;
58+ } ) ;
59+ }
60+ } ) ;
61+ }
62+
663function verifyOptions ( options ) {
764 if ( options === null || typeof options !== 'object' ) {
865 options = { } ;
@@ -226,172 +283,139 @@ function countEmptyLines(str) {
226283 return lineBreaks ;
227284}
228285
229- module . exports = postcss . plugin ( 'postcss-sorting' , function ( opts ) {
230- // Verify options and use defaults if not specified
231- opts = verifyOptions ( opts ) ;
286+ function processMostNodes ( node , index , opts , processedNodes ) {
287+ if ( node . type === 'comment' ) {
288+ if ( index === 0 && node . raws . before . indexOf ( '\n' ) === - 1 ) {
289+ node . ruleComment = true ; // need this flag to not append this comment twice
232290
233- return function ( css ) {
234- var order = getSortOrderFromOptions ( opts ) ;
235- var linesBetweenChildrenRules = getLinesBetweenRulesFromOptions ( 'children' , opts ) ;
236- var linesBetweenMediaRules = getLinesBetweenRulesFromOptions ( 'media' , opts ) ;
237- var preserveLinesBetweenChildren = opts [ 'preserve-empty-lines-between-children-rules' ] ;
238- var linesBeforeComment = opts [ 'empty-lines-before-comment' ] ;
239- var linesAfterComment = opts [ 'empty-lines-after-comment' ] ;
240- var enableSorting = true ;
241-
242- css . walk ( function ( rule ) {
243- if ( rule . type === 'comment' && rule . parent . type === 'root' ) {
244- if ( rule . text === 'postcss-sorting: on' ) {
245- enableSorting = true ;
246- } else if ( rule . text === 'postcss-sorting: off' ) {
247- enableSorting = false ;
291+ return processedNodes . concat ( node ) ;
292+ }
293+
294+ return processedNodes ;
295+ }
296+
297+ var order = getSortOrderFromOptions ( opts ) ;
298+
299+ node = addIndexesToNode ( node , index , order ) ;
300+
301+ // If comment on separate line before node, use node's indexes for comment
302+ var commentsBefore = fetchAllCommentsBeforeNode ( [ ] , node . prev ( ) , node ) ;
303+
304+ // If comment on same line with the node and node, use node's indexes for comment
305+ var commentsAfter = fetchAllCommentsAfterNode ( [ ] , node . next ( ) , node ) ;
306+
307+ return processedNodes . concat ( commentsBefore , node , commentsAfter ) ;
308+ }
309+
310+ function processLastComments ( node , index , processedNodes ) {
311+ if ( node . type === 'comment' && ! node . hasOwnProperty ( 'groupIndex' ) && ! node . ruleComment ) {
312+ node . groupIndex = Infinity ;
313+ node . propertyIndex = Infinity ;
314+ node . initialIndex = index ;
315+
316+ return processedNodes . concat ( node ) ;
317+ }
318+
319+ return processedNodes ;
320+ }
321+
322+ function sortByIndexes ( a , b ) {
323+ // If a's group index is higher than b's group index, in a sorted
324+ // list a appears after b:
325+ if ( a . groupIndex !== b . groupIndex ) {
326+ return a . groupIndex - b . groupIndex ;
327+ }
328+
329+ // If a and b have the same group index, and a's property index is
330+ // higher than b's property index, in a sorted list a appears after
331+ // b:
332+ if ( a . propertyIndex !== b . propertyIndex ) {
333+ return a . propertyIndex - b . propertyIndex ;
334+ }
335+
336+ // If a and b have the same group index and the same property index,
337+ // in a sorted list they appear in the same order they were in
338+ // original array:
339+ return a . initialIndex - b . initialIndex ;
340+ }
341+
342+ function formatNodes ( node , opts ) {
343+ var linesBetweenChildrenRules = getLinesBetweenRulesFromOptions ( 'children' , opts ) ;
344+ var linesBetweenMediaRules = getLinesBetweenRulesFromOptions ( 'media' , opts ) ;
345+ var preserveLinesBetweenChildren = opts [ 'preserve-empty-lines-between-children-rules' ] ;
346+ var linesBeforeComment = opts [ 'empty-lines-before-comment' ] ;
347+ var linesAfterComment = opts [ 'empty-lines-after-comment' ] ;
348+
349+ // don't remove empty lines if they are should be preserved
350+ if (
351+ ! (
352+ preserveLinesBetweenChildren &&
353+ ( node . type === 'rule' || node . type === 'comment' ) &&
354+ node . prev ( ) &&
355+ getApplicableNode ( 'rule' , node )
356+ )
357+ ) {
358+ node = cleanLineBreaks ( node ) ;
359+ }
360+
361+ var prevNode = node . prev ( ) ;
362+
363+ if ( prevNode && node . raws . before ) {
364+ if ( node . groupIndex > prevNode . groupIndex ) {
365+ node . raws . before = createLineBreaks ( 1 ) + node . raws . before ;
366+ }
367+
368+ var applicableNode ;
369+
370+ // Insert empty lines between children classes
371+ if ( node . type === 'rule' && linesBetweenChildrenRules > 0 ) {
372+ // between rules can be comments, so empty lines should be added to first comment between rules, rather than to rule
373+ applicableNode = getApplicableNode ( 'rule' , node ) ;
374+
375+ if ( applicableNode ) {
376+ // add lines only if source empty lines not preserved, or if there are less empty lines then should be
377+ if (
378+ ! preserveLinesBetweenChildren ||
379+ (
380+ preserveLinesBetweenChildren &&
381+ countEmptyLines ( applicableNode . raws . before ) < linesBetweenChildrenRules
382+ )
383+ ) {
384+ applicableNode . raws . before = createLineBreaks ( linesBetweenChildrenRules - countEmptyLines ( applicableNode . raws . before ) ) + applicableNode . raws . before ;
248385 }
249386 }
387+ }
388+
389+ // Insert empty lines between media rules
390+ if ( node . type === 'atrule' && node . name === 'media' && linesBetweenMediaRules > 0 ) {
391+ // between rules can be comments, so empty lines should be added to first comment between rules, rather than to rule
392+ applicableNode = getApplicableNode ( 'atrule' , node ) ;
250393
251- if ( ! enableSorting ) {
252- return ;
394+ if ( applicableNode ) {
395+ applicableNode . raws . before = createLineBreaks ( linesBetweenMediaRules - countEmptyLines ( applicableNode . raws . before ) ) + applicableNode . raws . before ;
253396 }
397+ }
254398
255- // Process only rules and atrules with nodes
256- if ( ( rule . type === 'rule' || rule . type === 'atrule' ) && rule . nodes && rule . nodes . length ) {
257- // Nodes for sorting
258- var processed = [ ] ;
259-
260- rule . each ( function ( node , index ) {
261- if ( node . type === 'comment' ) {
262- if ( index === 0 && node . raws . before . indexOf ( '\n' ) === - 1 ) {
263- node . ruleComment = true ; // need this flag to not append this comment twice
264-
265- processed . push ( node ) ;
266- }
267-
268- return ;
269- }
270-
271- node = addIndexesToNode ( node , index , order ) ;
272-
273- // If comment on separate line before node, use node's indexes for comment
274- var commentsBefore = fetchAllCommentsBeforeNode ( [ ] , node . prev ( ) , node ) ;
275-
276- // If comment on same line with the node and node, use node's indexes for comment
277- var commentsAfter = fetchAllCommentsAfterNode ( [ ] , node . next ( ) , node ) ;
278-
279- processed = processed . concat ( commentsBefore , node , commentsAfter ) ;
280- } ) ;
281-
282- // Add last comments in the rule. Need this because last comments are not belonging to anything
283- rule . each ( function ( node , index ) {
284- if ( node . type === 'comment' && ! node . hasOwnProperty ( 'groupIndex' ) && ! node . ruleComment ) {
285- node . groupIndex = Infinity ;
286- node . propertyIndex = Infinity ;
287- node . initialIndex = index ;
288-
289- processed . push ( node ) ;
290- }
291- } ) ;
292-
293- // Sort declarations saved for sorting:
294- processed . sort ( function ( a , b ) {
295- // If a's group index is higher than b's group index, in a sorted
296- // list a appears after b:
297- if ( a . groupIndex !== b . groupIndex ) {
298- return a . groupIndex - b . groupIndex ;
299- }
300-
301- // If a and b have the same group index, and a's property index is
302- // higher than b's property index, in a sorted list a appears after
303- // b:
304- if ( a . propertyIndex !== b . propertyIndex ) {
305- return a . propertyIndex - b . propertyIndex ;
306- }
307-
308- // If a and b have the same group index and the same property index,
309- // in a sorted list they appear in the same order they were in
310- // original array:
311- return a . initialIndex - b . initialIndex ;
312- } ) ;
313-
314- if ( processed . length ) {
315- rule . removeAll ( ) ;
316- rule . append ( processed ) ;
317- }
399+ // Insert empty lines before comment
400+ if (
401+ linesBeforeComment &&
402+ node . type === 'comment' &&
403+ ( prevNode . type !== 'comment' || prevNode . raws . before . indexOf ( '\n' ) === - 1 ) && // prevNode it's not a comment or it's an inline comment
404+ node . raws . before . indexOf ( '\n' ) >= 0 && // this isn't an inline comment
405+ countEmptyLines ( node . raws . before ) < linesBeforeComment
406+ ) {
407+ node . raws . before = createLineBreaks ( linesBeforeComment - countEmptyLines ( node . raws . before ) ) + node . raws . before ;
408+ }
318409
319- // Remove all empty lines and add empty lines between groups
320- rule . each ( function ( node ) {
321- // don't remove empty lines if they are should be preserved
322- if (
323- ! (
324- preserveLinesBetweenChildren &&
325- ( node . type === 'rule' || node . type === 'comment' ) &&
326- node . prev ( ) &&
327- getApplicableNode ( 'rule' , node )
328- )
329- ) {
330- node = cleanLineBreaks ( node ) ;
331- }
332-
333- var prevNode = node . prev ( ) ;
334-
335- if ( prevNode && node . raws . before ) {
336- if ( node . groupIndex > prevNode . groupIndex ) {
337- node . raws . before = createLineBreaks ( 1 ) + node . raws . before ;
338- }
339-
340- var applicableNode ;
341-
342- // Insert empty lines between children classes
343- if ( node . type === 'rule' && linesBetweenChildrenRules > 0 ) {
344- // between rules can be comments, so empty lines should be added to first comment between rules, rather than to rule
345- applicableNode = getApplicableNode ( 'rule' , node ) ;
346-
347- if ( applicableNode ) {
348- // add lines only if source empty lines not preserved, or if there are less empty lines then should be
349- if (
350- ! preserveLinesBetweenChildren ||
351- (
352- preserveLinesBetweenChildren &&
353- countEmptyLines ( applicableNode . raws . before ) < linesBetweenChildrenRules
354- )
355- ) {
356- applicableNode . raws . before = createLineBreaks ( linesBetweenChildrenRules - countEmptyLines ( applicableNode . raws . before ) ) + applicableNode . raws . before ;
357- }
358- }
359- }
360-
361- // Insert empty lines between media rules
362- if ( node . type === 'atrule' && node . name === 'media' && linesBetweenMediaRules > 0 ) {
363- // between rules can be comments, so empty lines should be added to first comment between rules, rather than to rule
364- applicableNode = getApplicableNode ( 'atrule' , node ) ;
365-
366- if ( applicableNode ) {
367- applicableNode . raws . before = createLineBreaks ( linesBetweenMediaRules - countEmptyLines ( applicableNode . raws . before ) ) + applicableNode . raws . before ;
368- }
369- }
370-
371- // Insert empty lines before comment
372- if (
373- linesBeforeComment &&
374- node . type === 'comment' &&
375- ( prevNode . type !== 'comment' || prevNode . raws . before . indexOf ( '\n' ) === - 1 ) && // prevNode it's not a comment or it's an inline comment
376- node . raws . before . indexOf ( '\n' ) >= 0 && // this isn't an inline comment
377- countEmptyLines ( node . raws . before ) < linesBeforeComment
378- ) {
379- node . raws . before = createLineBreaks ( linesBeforeComment - countEmptyLines ( node . raws . before ) ) + node . raws . before ;
380- }
381-
382- // Insert empty lines after comment
383- if (
384- linesAfterComment &&
385- node . type !== 'comment' &&
386- prevNode . type === 'comment' &&
387- prevNode . raws . before . indexOf ( '\n' ) >= 0 && // this isn't an inline comment
388- countEmptyLines ( node . raws . before ) < linesAfterComment
389- ) {
390- node . raws . before = createLineBreaks ( linesAfterComment - countEmptyLines ( node . raws . before ) ) + node . raws . before ;
391- }
392- }
393- } ) ;
394- }
395- } ) ;
396- } ;
397- } ) ;
410+ // Insert empty lines after comment
411+ if (
412+ linesAfterComment &&
413+ node . type !== 'comment' &&
414+ prevNode . type === 'comment' &&
415+ prevNode . raws . before . indexOf ( '\n' ) >= 0 && // this isn't an inline comment
416+ countEmptyLines ( node . raws . before ) < linesAfterComment
417+ ) {
418+ node . raws . before = createLineBreaks ( linesAfterComment - countEmptyLines ( node . raws . before ) ) + node . raws . before ;
419+ }
420+ }
421+ }
0 commit comments