index.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. 'use strict';
  2. const fs = require('fs');
  3. const path = require('path');
  4. const postcss = require('postcss');
  5. const timsort = require('timsort').sort;
  6. module.exports = postcss.plugin('css-declaration-sorter', function (options) {
  7. return function (css) {
  8. let sortOrderPath;
  9. options = options || {};
  10. // Use included sorting order if order is passed and not alphabetically
  11. if (options.order && options.order !== 'alphabetically') {
  12. sortOrderPath = path.join(__dirname, '../orders/', options.order) + '.json';
  13. } else if (options.customOrder) {
  14. sortOrderPath = options.customOrder;
  15. } else {
  16. // Fallback to the default sorting order
  17. return processCss(css, 'alphabetically');
  18. }
  19. // Load in the array containing the order from a JSON file
  20. return new Promise(function (resolve, reject) {
  21. fs.readFile(sortOrderPath, function (error, data) {
  22. if (error) return reject(error);
  23. resolve(data);
  24. });
  25. }).then(function (data) {
  26. return processCss(css, JSON.parse(data));
  27. });
  28. };
  29. });
  30. function processCss (css, sortOrder) {
  31. const comments = [];
  32. const rulesCache = [];
  33. css.walk(function (node) {
  34. const nodes = node.nodes;
  35. const type = node.type;
  36. if (type === 'comment') {
  37. // Don't do anything to root comments or the last newline comment
  38. const isNewlineNode = ~node.raws.before.indexOf('\n');
  39. const lastNewlineNode = isNewlineNode && !node.next();
  40. const onlyNode = !node.prev() && !node.next();
  41. if (lastNewlineNode || onlyNode || node.parent.type === 'root') {
  42. return;
  43. }
  44. if (isNewlineNode) {
  45. const pairedNode = node.next() ? node.next() : node.prev().prev();
  46. if (pairedNode) {
  47. comments.unshift({
  48. 'comment': node,
  49. 'pairedNode': pairedNode,
  50. 'insertPosition': node.next() ? 'Before' : 'After',
  51. });
  52. node.remove();
  53. }
  54. } else {
  55. const pairedNode = node.prev() ? node.prev() : node.next().next();
  56. if (pairedNode) {
  57. comments.push({
  58. 'comment': node,
  59. 'pairedNode': pairedNode,
  60. 'insertPosition': 'After',
  61. });
  62. node.remove();
  63. }
  64. }
  65. return;
  66. }
  67. // Add rule-like nodes to a cache so that we can remove all
  68. // comment nodes before we start sorting.
  69. const isRule = type === 'rule' || type === 'atrule';
  70. if (isRule && nodes && nodes.length > 1) {
  71. rulesCache.push(nodes);
  72. }
  73. });
  74. // Perform a sort once all comment nodes are removed
  75. rulesCache.forEach(function (nodes) {
  76. sortCssDecls(nodes, sortOrder);
  77. });
  78. // Add comments back to the nodes they are paired with
  79. comments.forEach(function (node) {
  80. const pairedNode = node.pairedNode;
  81. node.comment.remove();
  82. pairedNode.parent['insert' + node.insertPosition](pairedNode, node.comment);
  83. });
  84. }
  85. // Sort CSS declarations alphabetically or using the set sorting order
  86. function sortCssDecls (cssDecls, sortOrder) {
  87. if (sortOrder === 'alphabetically') {
  88. timsort(cssDecls, function (a, b) {
  89. if (a.type === 'decl' && b.type === 'decl') {
  90. return comparator(a.prop, b.prop);
  91. } else {
  92. return compareDifferentType(a, b);
  93. }
  94. });
  95. } else {
  96. timsort(cssDecls, function (a, b) {
  97. if (a.type === 'decl' && b.type === 'decl') {
  98. const aIndex = sortOrder.indexOf(a.prop);
  99. const bIndex = sortOrder.indexOf(b.prop);
  100. return comparator(aIndex, bIndex);
  101. } else {
  102. return compareDifferentType(a, b);
  103. }
  104. });
  105. }
  106. }
  107. function comparator (a, b) {
  108. return a === b ? 0 : a < b ? -1 : 1;
  109. }
  110. function compareDifferentType (a, b) {
  111. if (b.type === 'atrule') { return 0; }
  112. return (a.type === 'decl') ? -1 : (b.type === 'decl') ? 1 : 0;
  113. }