You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
177 lines
6.3 KiB
177 lines
6.3 KiB
var List = require('css-tree').List; |
|
var walk = require('css-tree').walk; |
|
var utils = require('./utils'); |
|
|
|
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 processRule(node, item, list) { |
|
var avoidRulesMerge = this.block !== null ? this.block.avoidRulesMerge : false; |
|
var selectors = node.prelude.children; |
|
var block = node.block; |
|
var disallowDownMarkers = Object.create(null); |
|
var allowMergeUp = true; |
|
var allowMergeDown = true; |
|
|
|
list.prevUntil(item.prev, function(prev, prevItem) { |
|
var prevBlock = prev.block; |
|
var prevType = prev.type; |
|
|
|
if (prevType !== 'Rule') { |
|
var unsafe = utils.unsafeToSkipNode.call(selectors, prev); |
|
|
|
if (!unsafe && prevType === 'Atrule' && prevBlock) { |
|
walk(prevBlock, { |
|
visit: 'Rule', |
|
enter: function(node) { |
|
node.prelude.children.each(function(data) { |
|
disallowDownMarkers[data.compareMarker] = true; |
|
}); |
|
} |
|
}); |
|
} |
|
|
|
return unsafe; |
|
} |
|
|
|
var prevSelectors = prev.prelude.children; |
|
|
|
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.isEqualSelectors(prevSelectors, selectors)) { |
|
prevBlock.children.appendList(block.children); |
|
list.remove(item); |
|
return true; |
|
} |
|
|
|
// try to join by properties |
|
var diff = utils.compareDeclarations(block.children, prevBlock.children); |
|
|
|
// 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.children = new List().fromArray(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.children = new List().fromArray(diff.ne2); |
|
} |
|
} else { |
|
// diff.ne1.length && diff.ne2.length |
|
// extract equal block |
|
var newSelector = { |
|
type: 'SelectorList', |
|
loc: null, |
|
children: utils.addSelectors(prevSelectors.copy(), selectors) |
|
}; |
|
var newBlockLength = calcSelectorLength(newSelector.children) + 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 (blockLength >= newBlockLength) { |
|
var newItem = list.createItem({ |
|
type: 'Rule', |
|
loc: null, |
|
prelude: newSelector, |
|
block: { |
|
type: 'Block', |
|
loc: null, |
|
children: new List().fromArray(diff.eq) |
|
}, |
|
pseudoSignature: node.pseudoSignature |
|
}); |
|
|
|
block.children = new List().fromArray(diff.ne1); |
|
prevBlock.children = new List().fromArray(diff.ne2overrided); |
|
|
|
if (allowMergeUp) { |
|
list.insert(newItem, prevItem); |
|
} else { |
|
list.insert(newItem, item); |
|
} |
|
|
|
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 restructRule(ast) { |
|
walk(ast, { |
|
visit: 'Rule', |
|
reverse: true, |
|
enter: processRule |
|
}); |
|
};
|
|
|