@@ -3,6 +3,63 @@ var objectAssign = require('object-assign');
3
3
var path = require ( 'path' ) ;
4
4
var fs = require ( 'fs' ) ;
5
5
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
+
6
63
function verifyOptions ( options ) {
7
64
if ( options === null || typeof options !== 'object' ) {
8
65
options = { } ;
@@ -226,172 +283,139 @@ function countEmptyLines(str) {
226
283
return lineBreaks ;
227
284
}
228
285
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
232
290
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 ;
248
385
}
249
386
}
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 ) ;
250
393
251
- if ( ! enableSorting ) {
252
- return ;
394
+ if ( applicableNode ) {
395
+ applicableNode . raws . before = createLineBreaks ( linesBetweenMediaRules - countEmptyLines ( applicableNode . raws . before ) ) + applicableNode . raws . before ;
253
396
}
397
+ }
254
398
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
+ }
318
409
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