| var hasOwnProperty = Object.prototype.hasOwnProperty; |
| |
| function isEqualSelectors(a, b) { |
| var cursor1 = a.head; |
| var cursor2 = b.head; |
| |
| while (cursor1 !== null && cursor2 !== null && cursor1.data.id === cursor2.data.id) { |
| cursor1 = cursor1.next; |
| cursor2 = cursor2.next; |
| } |
| |
| return cursor1 === null && cursor2 === null; |
| } |
| |
| function isEqualDeclarations(a, b) { |
| var cursor1 = a.head; |
| var cursor2 = b.head; |
| |
| while (cursor1 !== null && cursor2 !== null && cursor1.data.id === cursor2.data.id) { |
| cursor1 = cursor1.next; |
| cursor2 = cursor2.next; |
| } |
| |
| return cursor1 === null && cursor2 === null; |
| } |
| |
| function compareDeclarations(declarations1, declarations2) { |
| var result = { |
| eq: [], |
| ne1: [], |
| ne2: [], |
| ne2overrided: [] |
| }; |
| |
| var fingerprints = Object.create(null); |
| var declarations2hash = Object.create(null); |
| |
| for (var cursor = declarations2.head; cursor; cursor = cursor.next) { |
| declarations2hash[cursor.data.id] = true; |
| } |
| |
| for (var cursor = declarations1.head; cursor; cursor = cursor.next) { |
| var data = cursor.data; |
| |
| if (data.fingerprint) { |
| fingerprints[data.fingerprint] = data.important; |
| } |
| |
| if (declarations2hash[data.id]) { |
| declarations2hash[data.id] = false; |
| result.eq.push(data); |
| } else { |
| result.ne1.push(data); |
| } |
| } |
| |
| for (var cursor = declarations2.head; cursor; cursor = cursor.next) { |
| var data = cursor.data; |
| |
| if (declarations2hash[data.id]) { |
| // when declarations1 has an overriding declaration, this is not a difference |
| // unless no !important is used on prev and !important is used on the following |
| if (!hasOwnProperty.call(fingerprints, data.fingerprint) || |
| (!fingerprints[data.fingerprint] && data.important)) { |
| result.ne2.push(data); |
| } |
| |
| result.ne2overrided.push(data); |
| } |
| } |
| |
| return result; |
| } |
| |
| function addSelectors(dest, source) { |
| source.each(function(sourceData) { |
| var newStr = sourceData.id; |
| var cursor = dest.head; |
| |
| while (cursor) { |
| var nextStr = cursor.data.id; |
| |
| if (nextStr === newStr) { |
| return; |
| } |
| |
| if (nextStr > newStr) { |
| break; |
| } |
| |
| cursor = cursor.next; |
| } |
| |
| dest.insert(dest.createItem(sourceData), cursor); |
| }); |
| |
| return dest; |
| } |
| |
| // check if simpleselectors has no equal specificity and element selector |
| function hasSimilarSelectors(selectors1, selectors2) { |
| var cursor1 = selectors1.head; |
| |
| while (cursor1 !== null) { |
| var cursor2 = selectors2.head; |
| |
| while (cursor2 !== null) { |
| if (cursor1.data.compareMarker === cursor2.data.compareMarker) { |
| return true; |
| } |
| |
| cursor2 = cursor2.next; |
| } |
| |
| cursor1 = cursor1.next; |
| } |
| |
| return false; |
| } |
| |
| // test node can't to be skipped |
| function unsafeToSkipNode(node) { |
| switch (node.type) { |
| case 'Rule': |
| // unsafe skip ruleset with selector similarities |
| return hasSimilarSelectors(node.prelude.children, this); |
| |
| case 'Atrule': |
| // can skip at-rules with blocks |
| if (node.block) { |
| // unsafe skip at-rule if block contains something unsafe to skip |
| return node.block.children.some(unsafeToSkipNode, this); |
| } |
| break; |
| |
| case 'Declaration': |
| return false; |
| } |
| |
| // unsafe by default |
| return true; |
| } |
| |
| module.exports = { |
| isEqualSelectors: isEqualSelectors, |
| isEqualDeclarations: isEqualDeclarations, |
| compareDeclarations: compareDeclarations, |
| addSelectors: addSelectors, |
| hasSimilarSelectors: hasSimilarSelectors, |
| unsafeToSkipNode: unsafeToSkipNode |
| }; |