8-restructRuleset.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. var List = require('../../utils/list.js');
  2. var utils = require('./utils.js');
  3. var walkRulesRight = require('../../utils/walk.js').rulesRight;
  4. function calcSelectorLength(list) {
  5. var length = 0;
  6. list.each(function(data) {
  7. length += data.id.length + 1;
  8. });
  9. return length - 1;
  10. }
  11. function calcDeclarationsLength(tokens) {
  12. var length = 0;
  13. for (var i = 0; i < tokens.length; i++) {
  14. length += tokens[i].length;
  15. }
  16. return (
  17. length + // declarations
  18. tokens.length - 1 // delimeters
  19. );
  20. }
  21. function processRuleset(node, item, list) {
  22. var avoidRulesMerge = this.stylesheet.avoidRulesMerge;
  23. var selectors = node.selector.selectors;
  24. var block = node.block;
  25. var disallowDownMarkers = Object.create(null);
  26. var allowMergeUp = true;
  27. var allowMergeDown = true;
  28. list.prevUntil(item.prev, function(prev, prevItem) {
  29. // skip non-ruleset node if safe
  30. if (prev.type !== 'Ruleset') {
  31. return utils.unsafeToSkipNode.call(selectors, prev);
  32. }
  33. var prevSelectors = prev.selector.selectors;
  34. var prevBlock = prev.block;
  35. if (node.pseudoSignature !== prev.pseudoSignature) {
  36. return true;
  37. }
  38. allowMergeDown = !prevSelectors.some(function(selector) {
  39. return selector.compareMarker in disallowDownMarkers;
  40. });
  41. // try prev ruleset if simpleselectors has no equal specifity and element selector
  42. if (!allowMergeDown && !allowMergeUp) {
  43. return true;
  44. }
  45. // try to join by selectors
  46. if (allowMergeUp && utils.isEqualLists(prevSelectors, selectors)) {
  47. prevBlock.declarations.appendList(block.declarations);
  48. list.remove(item);
  49. return true;
  50. }
  51. // try to join by properties
  52. var diff = utils.compareDeclarations(block.declarations, prevBlock.declarations);
  53. // console.log(diff.eq, diff.ne1, diff.ne2);
  54. if (diff.eq.length) {
  55. if (!diff.ne1.length && !diff.ne2.length) {
  56. // equal blocks
  57. if (allowMergeDown) {
  58. utils.addSelectors(selectors, prevSelectors);
  59. list.remove(prevItem);
  60. }
  61. return true;
  62. } else if (!avoidRulesMerge) { /* probably we don't need to prevent those merges for @keyframes
  63. TODO: need to be checked */
  64. if (diff.ne1.length && !diff.ne2.length) {
  65. // prevBlock is subset block
  66. var selectorLength = calcSelectorLength(selectors);
  67. var blockLength = calcDeclarationsLength(diff.eq); // declarations length
  68. if (allowMergeUp && selectorLength < blockLength) {
  69. utils.addSelectors(prevSelectors, selectors);
  70. block.declarations = new List(diff.ne1);
  71. }
  72. } else if (!diff.ne1.length && diff.ne2.length) {
  73. // node is subset of prevBlock
  74. var selectorLength = calcSelectorLength(prevSelectors);
  75. var blockLength = calcDeclarationsLength(diff.eq); // declarations length
  76. if (allowMergeDown && selectorLength < blockLength) {
  77. utils.addSelectors(selectors, prevSelectors);
  78. prevBlock.declarations = new List(diff.ne2);
  79. }
  80. } else {
  81. // diff.ne1.length && diff.ne2.length
  82. // extract equal block
  83. var newSelector = {
  84. type: 'Selector',
  85. info: {},
  86. selectors: utils.addSelectors(prevSelectors.copy(), selectors)
  87. };
  88. var newBlockLength = calcSelectorLength(newSelector.selectors) + 2; // selectors length + curly braces length
  89. var blockLength = calcDeclarationsLength(diff.eq); // declarations length
  90. // create new ruleset if declarations length greater than
  91. // ruleset description overhead
  92. if (allowMergeDown && blockLength >= newBlockLength) {
  93. var newRuleset = {
  94. type: 'Ruleset',
  95. info: {},
  96. pseudoSignature: node.pseudoSignature,
  97. selector: newSelector,
  98. block: {
  99. type: 'Block',
  100. info: {},
  101. declarations: new List(diff.eq)
  102. }
  103. };
  104. block.declarations = new List(diff.ne1);
  105. prevBlock.declarations = new List(diff.ne2.concat(diff.ne2overrided));
  106. list.insert(list.createItem(newRuleset), prevItem);
  107. return true;
  108. }
  109. }
  110. }
  111. }
  112. if (allowMergeUp) {
  113. // TODO: disallow up merge only if any property interception only (i.e. diff.ne2overrided.length > 0);
  114. // await property families to find property interception correctly
  115. allowMergeUp = !prevSelectors.some(function(prevSelector) {
  116. return selectors.some(function(selector) {
  117. return selector.compareMarker === prevSelector.compareMarker;
  118. });
  119. });
  120. }
  121. prevSelectors.each(function(data) {
  122. disallowDownMarkers[data.compareMarker] = true;
  123. });
  124. });
  125. };
  126. module.exports = function restructRuleset(ast) {
  127. walkRulesRight(ast, function(node, item, list) {
  128. if (node.type === 'Ruleset') {
  129. processRuleset.call(this, node, item, list);
  130. }
  131. });
  132. };