var List = require('../../utils/list.js'); var utils = require('./utils.js'); var walkRulesRight = require('../../utils/walk.js').rulesRight; function calcSelectorLength(list) { var length = 0; list.each(function(data) { length += data.id.length + 1; }); return length - 1; } function calcDeclarationsLength(tokens) { var length = 0; for (var i = 0; i < tokens.length; i++) { length += tokens[i].length; } return ( length + // declarations tokens.length - 1 // delimeters ); } function processRuleset(node, item, list) { var avoidRulesMerge = this.stylesheet.avoidRulesMerge; var selectors = node.selector.selectors; var block = node.block; var disallowDownMarkers = Object.create(null); var allowMergeUp = true; var allowMergeDown = true; list.prevUntil(item.prev, function(prev, prevItem) { // skip non-ruleset node if safe if (prev.type !== 'Ruleset') { return utils.unsafeToSkipNode.call(selectors, prev); } var prevSelectors = prev.selector.selectors; var prevBlock = prev.block; if (node.pseudoSignature !== prev.pseudoSignature) { return true; } allowMergeDown = !prevSelectors.some(function(selector) { return selector.compareMarker in disallowDownMarkers; }); // try prev ruleset if simpleselectors has no equal specifity and element selector if (!allowMergeDown && !allowMergeUp) { return true; } // try to join by selectors if (allowMergeUp && utils.isEqualLists(prevSelectors, selectors)) { prevBlock.declarations.appendList(block.declarations); list.remove(item); return true; } // try to join by properties var diff = utils.compareDeclarations(block.declarations, prevBlock.declarations); // console.log(diff.eq, diff.ne1, diff.ne2); if (diff.eq.length) { if (!diff.ne1.length && !diff.ne2.length) { // equal blocks if (allowMergeDown) { utils.addSelectors(selectors, prevSelectors); list.remove(prevItem); } return true; } else if (!avoidRulesMerge) { /* probably we don't need to prevent those merges for @keyframes TODO: need to be checked */ if (diff.ne1.length && !diff.ne2.length) { // prevBlock is subset block var selectorLength = calcSelectorLength(selectors); var blockLength = calcDeclarationsLength(diff.eq); // declarations length if (allowMergeUp && selectorLength < blockLength) { utils.addSelectors(prevSelectors, selectors); block.declarations = new List(diff.ne1); } } else if (!diff.ne1.length && diff.ne2.length) { // node is subset of prevBlock var selectorLength = calcSelectorLength(prevSelectors); var blockLength = calcDeclarationsLength(diff.eq); // declarations length if (allowMergeDown && selectorLength < blockLength) { utils.addSelectors(selectors, prevSelectors); prevBlock.declarations = new List(diff.ne2); } } else { // diff.ne1.length && diff.ne2.length // extract equal block var newSelector = { type: 'Selector', info: {}, selectors: utils.addSelectors(prevSelectors.copy(), selectors) }; var newBlockLength = calcSelectorLength(newSelector.selectors) + 2; // selectors length + curly braces length var blockLength = calcDeclarationsLength(diff.eq); // declarations length // create new ruleset if declarations length greater than // ruleset description overhead if (allowMergeDown && blockLength >= newBlockLength) { var newRuleset = { type: 'Ruleset', info: {}, pseudoSignature: node.pseudoSignature, selector: newSelector, block: { type: 'Block', info: {}, declarations: new List(diff.eq) } }; block.declarations = new List(diff.ne1); prevBlock.declarations = new List(diff.ne2.concat(diff.ne2overrided)); list.insert(list.createItem(newRuleset), prevItem); return true; } } } } if (allowMergeUp) { // TODO: disallow up merge only if any property interception only (i.e. diff.ne2overrided.length > 0); // await property families to find property interception correctly allowMergeUp = !prevSelectors.some(function(prevSelector) { return selectors.some(function(selector) { return selector.compareMarker === prevSelector.compareMarker; }); }); } prevSelectors.each(function(data) { disallowDownMarkers[data.compareMarker] = true; }); }); }; module.exports = function restructRuleset(ast) { walkRulesRight(ast, function(node, item, list) { if (node.type === 'Ruleset') { processRuleset.call(this, node, item, list); } }); };