From 517bae97708b03980c23640705292aafc960128e Mon Sep 17 00:00:00 2001 From: Jakub Pawlowicz Date: Sun, 12 Apr 2015 10:49:26 +0100 Subject: [PATCH] Readds shorthand compacting. Basically tries to compact longhands into shorthands if possible, caring about number of components and their understandability. It DOES NOT re-add `!important`-based compacting which was previously available because: * it's logic is flawed in global context, when introduced important property may override properties appearing later on; * it's very uncommon to occur. --- lib/properties/compactable.js | 9 + lib/properties/optimizer.js | 5 +- lib/properties/shorthand-compactor.js | 342 +++++----------- test/fixtures/big-min.css | 8 +- test/properties/shorthand-compacting-test.js | 401 +++++++++---------- 5 files changed, 305 insertions(+), 460 deletions(-) diff --git a/lib/properties/compactable.js b/lib/properties/compactable.js index 05f5dec8..09b14775 100644 --- a/lib/properties/compactable.js +++ b/lib/properties/compactable.js @@ -272,4 +272,13 @@ addFourValueShorthand('margin', [ 'margin-left' ]); +// Adds `componentOf` field to all longhands +for (var property in compactable) { + if (compactable[property].shorthand) { + for (var i = 0, l = compactable[property].components.length; i < l; i++) { + compactable[compactable[property].components[i]].componentOf = property; + } + } +} + module.exports = compactable; diff --git a/lib/properties/optimizer.js b/lib/properties/optimizer.js index e55c5684..11f6a3e1 100644 --- a/lib/properties/optimizer.js +++ b/lib/properties/optimizer.js @@ -2,7 +2,7 @@ var compactable = require('./compactable'); var wrapForOptimizing = require('./wrap-for-optimizing').all; var populateComponents = require('./populate-components'); var compactOverrides = require('./override-compactor'); -// var compactShorthands = require('./shorthand-compactor'); +var compactShorthands = require('./shorthand-compactor'); var removeUnused = require('./remove-unused'); var restoreShorthands = require('./restore-shorthands'); var stringifyProperty = require('../stringifier/one-time').property; @@ -187,8 +187,7 @@ function optimize(selector, properties, mergeAdjacent, withCompacting, options, if (withCompacting && options.shorthandCompacting && !options.sourceMap) { compactOverrides(_properties, options.compatibility, validator); - // compactShorthands(_properties, false, options.compatibility); - // compactShorthands(_properties, true, options.compatibility); + compactShorthands(_properties, options.compatibility, validator); } restoreShorthands(_properties); diff --git a/lib/properties/shorthand-compactor.js b/lib/properties/shorthand-compactor.js index 0c94f318..2fbe09e8 100644 --- a/lib/properties/shorthand-compactor.js +++ b/lib/properties/shorthand-compactor.js @@ -1,244 +1,100 @@ +var compactable = require('./compactable'); +var deepClone = require('./clone').deep; +var populateComponents = require('./populate-components'); +var wrapSingle = require('./wrap-for-optimizing').single; -// // Compacts the tokens by transforming properties into their shorthand notations when possible - -// module.exports = (function () { -// var isHackValue = function (t) { return t.value === '__hack'; }; - -// var compactShorthands = function(tokens, isImportant, processable, Token) { -// // Contains the components found so far, grouped by shorthand name -// var componentsSoFar = { }; - -// // Initializes a prop in componentsSoFar -// var initSoFar = function (shprop, last, clearAll) { -// var found = {}; -// var shorthandPosition; - -// if (!clearAll && componentsSoFar[shprop]) { -// for (var i = 0; i < processable[shprop].components.length; i++) { -// var prop = processable[shprop].components[i]; -// found[prop] = []; - -// if (!(componentsSoFar[shprop].found[prop])) -// continue; - -// for (var ii = 0; ii < componentsSoFar[shprop].found[prop].length; ii++) { -// var comp = componentsSoFar[shprop].found[prop][ii]; - -// if (comp.trashed) -// continue; - -// found[prop].push(comp); - -// if (comp.position && (!shorthandPosition || comp.position < shorthandPosition)) -// shorthandPosition = comp.position; -// } -// } -// } -// componentsSoFar[shprop] = { -// lastShorthand: last, -// found: found, -// shorthandPosition: shorthandPosition -// }; -// }; - -// // Adds a component to componentsSoFar -// var addComponentSoFar = function (token, index) { -// var shprop = processable[token.name].componentOf; -// if (!componentsSoFar[shprop]) -// initSoFar(shprop); -// if (!componentsSoFar[shprop].found[token.name]) -// componentsSoFar[shprop].found[token.name] = []; - -// // Add the newfound component to componentsSoFar -// componentsSoFar[shprop].found[token.name].push(token); - -// if (!componentsSoFar[shprop].shorthandPosition && index) { -// // If the haven't decided on where the shorthand should go, put it in the place of this component -// componentsSoFar[shprop].shorthandPosition = index; -// } -// }; - -// // Tries to compact a prop in componentsSoFar -// var compactSoFar = function (prop) { -// var i; -// var componentsCount = processable[prop].components.length; - -// // Check basics -// if (!componentsSoFar[prop] || !componentsSoFar[prop].found) -// return false; - -// // Find components for the shorthand -// var components = []; -// var realComponents = []; -// for (i = 0 ; i < componentsCount; i++) { -// // Get property name -// var pp = processable[prop].components[i]; - -// if (componentsSoFar[prop].found[pp] && componentsSoFar[prop].found[pp].length) { -// // We really found it -// var foundRealComp = componentsSoFar[prop].found[pp][0]; -// components.push(foundRealComp); -// if (foundRealComp.real !== false) { -// realComponents.push(foundRealComp); -// } -// } else if (componentsSoFar[prop].lastShorthand) { -// // It's defined in the previous shorthand -// var c = componentsSoFar[prop].lastShorthand.components[i].clone(isImportant); -// components.push(c); -// } else { -// // Couldn't find this component at all -// return false; -// } -// } - -// if (realComponents.length === 0) { -// // Couldn't find enough components, sorry -// return false; -// } - -// if (realComponents.length === componentsCount) { -// // When all the components are from real values, only allow shorthanding if their understandability allows it -// // This is the case when every component can override their default values, or when all of them use the same function - -// var canOverrideDefault = true; -// var functionNameMatches = true; -// var functionName; - -// for (var ci = 0; ci < realComponents.length; ci++) { -// var rc = realComponents[ci]; - -// if (!processable[rc.name].canOverride(processable[rc.name].defaultValue, rc.value)) { -// canOverrideDefault = false; -// } -// var iop = rc.value.indexOf('('); -// if (iop >= 0) { -// var otherFunctionName = rc.value.substring(0, iop); -// if (functionName) -// functionNameMatches = functionNameMatches && otherFunctionName === functionName; -// else -// functionName = otherFunctionName; -// } -// } - -// if (!canOverrideDefault || !functionNameMatches) -// return false; -// } - -// // Compact the components into a shorthand -// var compacted = processable[prop].putTogether(prop, components, isImportant); -// if (!(compacted instanceof Array)) { -// compacted = [compacted]; -// } - -// var compactedLength = Token.getDetokenizedLength(compacted); -// var authenticLength = Token.getDetokenizedLength(realComponents); - -// if (realComponents.length === componentsCount || compactedLength < authenticLength || components.some(isHackValue)) { -// compacted[0].isShorthand = true; -// compacted[0].components = processable[prop].breakUp(compacted[0]); - -// // Mark the granular components for deletion -// for (i = 0; i < realComponents.length; i++) { -// realComponents[i].isMarkedForDeletion = true; -// } - -// // Mark the position of the new shorthand -// tokens[componentsSoFar[prop].shorthandPosition].replaceWith = compacted; - -// // Reinitialize the thing for further compacting -// initSoFar(prop, compacted[0]); -// for (i = 1; i < compacted.length; i++) { -// addComponentSoFar(compacted[i]); -// } - -// // Yes, we can keep the new shorthand! -// return true; -// } - -// return false; -// }; - -// // Tries to compact all properties currently in componentsSoFar -// var compactAllSoFar = function () { -// for (var i in componentsSoFar) { -// if (componentsSoFar.hasOwnProperty(i)) { -// while (compactSoFar(i)) { } -// } -// } -// }; - -// var i, token; - -// // Go through each token and collect components for each shorthand as we go on -// for (i = 0; i < tokens.length; i++) { -// token = tokens[i]; -// if (token.trashed) { -// continue; -// } -// if (!processable[token.name]) { -// // We don't know what it is, move on -// continue; -// } -// if (processable[token.name].isShorthand) { -// // Found an instance of a full shorthand -// // NOTE: we should NOT mix together tokens that come before and after the shorthands - -// if (token.important === isImportant || (token.important && !isImportant)) { -// // Try to compact what we've found so far -// while (compactSoFar(token.name)) { } -// // Reset -// initSoFar(token.name, token, true); -// } - -// // TODO: when the old optimizer is removed, take care of this corner case: -// // div{background-color:#111;background-image:url(aaa);background:linear-gradient(aaa);background-repeat:no-repeat;background-position:1px 2px;background-attachment:scroll} -// // -> should not be shorthanded / minified at all because the result wouldn't be equivalent to the original in any browser -// } else if (processable[token.name].componentOf) { -// // Found a component of a shorthand -// if (token.important === isImportant) { -// // Same importantness -// token.position = i; -// addComponentSoFar(token, i); -// } else if (!isImportant && token.important) { -// // Use importants for optimalization opportunities -// // https://github.com/jakubpawlowicz/clean-css/issues/184 -// var importantTrickComp = new Token(token.name, token.value, isImportant); -// importantTrickComp.irrelevant = true; -// importantTrickComp.real = false; -// addComponentSoFar(importantTrickComp); -// } -// } else { -// // This is not a shorthand and not a component, don't care about it -// continue; -// } -// } - -// // Perform all possible compactions -// compactAllSoFar(); - -// // Process the results - throw away stuff marked for deletion, insert compacted things, etc. -// var result = []; -// for (i = 0; i < tokens.length; i++) { -// token = tokens[i]; - -// if (token.replaceWith) { -// for (var ii = 0; ii < token.replaceWith.length; ii++) { -// result.push(token.replaceWith[ii]); -// } -// } -// if (!token.trashed) { -// result.push(token); -// } - -// token.trashed = false; -// token.replaceWith = null; -// } - -// return result; -// }; - -// return { -// compactShorthands: compactShorthands -// }; - -// })(); +function mixedImportance(components) { + var important; + + for (var name in components) { + if (undefined !== important && components[name].important != important) + return true; + + important = components[name].important; + } + + return false; +} + +function replaceWithShorthand(properties, candidateComponents, name, validator) { + var descriptor = compactable[name]; + var newValuePlaceholder = [[name, false, false], [descriptor.defaultValue]]; + var all; + + var newProperty = wrapSingle(newValuePlaceholder); + newProperty.shorthand = true; + newProperty.dirty = true; + + populateComponents([newProperty], validator); + + for (var i = 0, l = descriptor.components.length; i < l; i++) { + var component = candidateComponents[descriptor.components[i]]; + var canOverride = compactable[component.name].canOverride; + + if (!canOverride(newProperty.components[i], component, validator)) + return; + + newProperty.components[i] = deepClone(component); + newProperty.important = component.important; + + all = component.all; + } + + for (var componentName in candidateComponents) { + candidateComponents[componentName].unused = true; + } + + newProperty.position = all.length; + newProperty.all = all; + newProperty.all.push(newValuePlaceholder); + newValuePlaceholder[0][1] = newProperty.important; + + properties.push(newProperty); +} + +function invalidateOrCompact(properties, position, candidates, validator) { + var property = properties[position]; + + for (var name in candidates) { + if (undefined !== property && name == property.name) + continue; + + var descriptor = compactable[name]; + var candidateComponents = candidates[name]; + if (descriptor.components.length > Object.keys(candidateComponents).length) { + delete candidates[name]; + continue; + } + + if (mixedImportance(candidateComponents)) + continue; + + replaceWithShorthand(properties, candidateComponents, name, validator); + } +} + +function compactShortands(properties, compatibility, validator) { + var candidates = {}; + + for (var i = 0, l = properties.length; i < l; i++) { + var property = properties[i]; + if (property.unused) + continue; + + var descriptor = compactable[property.name]; + if (!descriptor || !descriptor.componentOf) + continue; + + if (property.shorthand) { + invalidateOrCompact(properties, i, candidates, validator); + } else { + var componentOf = descriptor.componentOf; + candidates[componentOf] = candidates[componentOf] || {}; + candidates[componentOf][property.name] = property; + } + } + + invalidateOrCompact(properties, i, candidates, validator); +} + +module.exports = compactShortands; diff --git a/test/fixtures/big-min.css b/test/fixtures/big-min.css index 8577b36d..67a96a7d 100644 --- a/test/fixtures/big-min.css +++ b/test/fixtures/big-min.css @@ -1527,7 +1527,7 @@ label.comparer input{margin-right:8px} .lien_img314x64.explorer_discours_2012{background:url(/medias/web/img/textes/elections/widget_explorer_discours_2012.png);text-indent:-9999px} *{margin:0;padding:0} form,img{border:0} -ul{list-style-image:none;list-style-position:inside;list-style-type:none} +ul{list-style:none inside} #mainContent{background:#fff;font-size:12px;color:#222} body>img{position:absolute} .megaban{margin-right:auto;margin-left:auto} @@ -2125,6 +2125,7 @@ a.god:hover{background:#3c3c3c;color:#fff} #core-liberation .block-search-head form input[type=submit]:hover{background-image:url(http://s0.libe.com/libe/img/common/bg-search-formsubmit-on.png?21388ca68b89)} #core-liberation .block-search-results form input[type=submit]:active{background-image:url(http://s0.libe.com/libe/img/common/bg-search-formsubmit-active.png?16bd2d8fbc96)} #core-liberation .block-search-head .pagination{border-top:0} +#core-liberation .block-search-head .advanced,#core-liberation .pagination{border-top-color:#b7b7b7;border-top:1px dotted} #core-liberation .block-search-head .pagination a,#core-liberation .block-search-results .pagination a{background-color:#fff} #core-liberation .block-search-head .pagination .prev,#core-liberation .block-search-results .pagination .prev{margin-left:0;padding-left:17px} #core-liberation .block-search-head .pagination .next,#core-liberation .block-search-results .pagination .next{margin-right:0;padding-right:17px} @@ -2173,7 +2174,7 @@ a.god:hover{background:#3c3c3c;color:#fff} #core-liberation .block-search-results .block-content .object-picture img{display:block;width:87px} #core-liberation .block-search-results .block-content .article .object-picture,#core-liberation .block-search-results .block-content .article .object-picture img{height:116px} #core-liberation .block-search-results .block-content .object-picture .np{position:relative} -#core-liberation .block-search-results .block-content .object-picture .np .p1,#core-liberation .block-search-results .block-content .object-picture .np .p2{position:absolute;width:79px;height:102px;border-style:solid;border-top-width:7px;border-bottom-width:7px;border-left-width:4px;border-right-width:4px} +#core-liberation .block-search-results .block-content .object-picture .np .p1,#core-liberation .block-search-results .block-content .object-picture .np .p2{position:absolute;width:79px;height:102px;border-style:solid;border-width:7px 4px} #core-liberation .block-search-results .block-content .object-picture .np .p1{z-index:2000} #core-liberation .block-search-results .block-content .object-picture .np .p2{z-index:1000} #core-liberation .block-search-results .block-content .object-picture .np a.date{position:absolute;display:block;width:80px;top:10px;padding:2px 2px 3px 4px;z-index:500} @@ -2783,7 +2784,6 @@ body.slideshow .ad-top .megaban{background:#333} #core-liberation .block-search-head p.opinion span{color:#707070} #core-liberation .block-search-head p.opinion a{color:#e20000} #core-liberation .block-search-head .advanced .note .links,#core-liberation .block-search-head .advanced .note .links a,#core-liberation .block-search-head .advanced .note a.displayer,#core-liberation .block-search-head .results p{color:#87888a} -#core-liberation .block-search-head .advanced{border-top-color:#b7b7b7} #core-liberation .block-search-head .advanced .searchform .between,#core-liberation .block-search-head .advanced .searchform .category,#core-liberation .block-search-head .advanced .searchform .period,#core-liberation .block-search-head .advanced .searchform .source{border-bottom-color:#fff} #core-liberation .block-search-head .results{margin-bottom:15px} #core-liberation .block-search-head .results p.filters strong{color:#2e2e2e} @@ -2826,7 +2826,7 @@ body.slideshow .ad-top .megaban{background:#333} .site-liberation .btn-read-digitalpaper{border-color:#878787} .site-liberation .toolbox,.site-liberation .toolbox li.fold-options ul,.site-liberation .toolbox li.fold-options+li.fold-options{border-color:#d7d7d7} .site-liberation .btn-read-digitalpaper a,.site-liberation .btn-read-digitalpaper span{color:#2e2e2e} -#core-liberation .pagination{background-color:#e7e7e7;border-top-color:#b7b7b7;border-bottom-color:#b7b7b7} +#core-liberation .pagination{background-color:#e7e7e7;border-bottom-color:#b7b7b7} #bar-liberation,#bar-liberation #login-box-content,#bar-liberation .content ul.list li{border-bottom-color:#dadada} #core-liberation .pagination .disabled{background-color:transparent;color:#c8c8c8} #core-liberation .pagination .current{background-color:transparent;color:#e20000} diff --git a/test/properties/shorthand-compacting-test.js b/test/properties/shorthand-compacting-test.js index ed1b6947..245bcb84 100644 --- a/test/properties/shorthand-compacting-test.js +++ b/test/properties/shorthand-compacting-test.js @@ -1,210 +1,191 @@ -// 'shorthand properties': cssContext({ -// 'shorthand background #1' : [ -// 'div{background-color:#111;background-image:url(aaa);background-repeat:repeat;background-position:0 0;background-attachment:scroll;background-size:auto;background-origin:padding-box;background-clip:border-box}', -// 'div{background:url(aaa)#111}' -// ], -// 'shorthand background #2' : [ -// 'div{background-color:#111;background-image:url(aaa);background-repeat:no-repeat;background-position:0 0;background-attachment:scroll;background-size:auto;background-origin:padding-box;background-clip:border-box}', -// 'div{background:url(aaa)no-repeat #111}' -// ], -// 'shorthand important background' : [ -// 'div{background-color:#111!important;background-image:url(aaa)!important;background-repeat:repeat!important;background-position:0 0!important;background-attachment:scroll!important;background-size:auto!important;background-origin:padding-box!important;background-clip:border-box!important}', -// 'div{background:url(aaa)#111!important}' -// ], -// 'shorthand important background overriding': [ -// 'a{background:url(a.jpg) !important; background-color:#fff !important}', -// 'a{background:url(a.jpg)#fff!important}' -// ], -// 'shorthand important background overriding by non-mergeable property': [ -// 'a{background:url(a.jpg) !important; background-color:#fff !important; background-size:10px 10px !important}', -// 'a{background:url(a.jpg)#fff!important;background-size:10px 10px!important}' -// ], -// 'shorthand background-repeat correctly': [ -// 'a{background:url(/image/path.png) no-repeat repeat}', -// 'a{background:url(/image/path.png)no-repeat repeat}' -// ], -// 'shorthand border-width': [ -// '.t{border-top-width:7px;border-bottom-width:7px;border-left-width:4px;border-right-width:4px}', -// '.t{border-width:7px 4px}' -// ], -// 'shorthand border-color #1': [ -// '.t{border-top-color:#9fce00;border-bottom-color:#9fce00;border-left-color:#9fce00;border-right-color:#9fce00}', -// '.t{border-color:#9fce00}' -// ], -// 'shorthand border-color #2': [ -// '.t{border-right-color:#002;border-bottom-color:#003;border-top-color:#001;border-left-color:#004}', -// '.t{border-color:#001 #002 #003 #004}' -// ], -// 'shorthand border-radius': [ -// '.t{border-top-left-radius:7px;border-bottom-right-radius:6px;border-bottom-left-radius:5px;border-top-right-radius:3px}', -// '.t{border-radius:7px 3px 6px 5px}' -// ], -// 'shorthand border-radius none': 'li{border-radius:none}', -// 'shorthand list-style #1': [ -// '.t{list-style-type:circle;list-style-position:outside;list-style-image:url(aaa)}', -// '.t{list-style:circle url(aaa)}' -// ], -// 'shorthand list-style #2': [ -// '.t{list-style-image:url(aaa);list-style-type:circle;list-style-position:inside}', -// '.t{list-style:circle inside url(aaa)}' -// ] -// }), -// 'cares about understandability of shorthand components': cssContext({ -// 'linear-gradient should NOT clear out background with color only' : [ -// 'div{background:#fff;background:linear-gradient(whatever)}', -// 'div{background:#fff;background:linear-gradient(whatever)}' -// ], -// 'linear-gradient should NOT clear out background with color only, even if it has a color' : [ -// 'div{background:#fff;background:linear-gradient(whatever) #222}', -// 'div{background:#fff;background:linear-gradient(whatever)#222}' -// ], -// 'a background-image with just a linear-gradient should not be compacted to a shorthand' : [ -// 'div{background-color:#111;background-image:linear-gradient(aaa);background-repeat:no-repeat;background-position:0 0;background-attachment:scroll}', -// 'div{background-color:#111;background-image:linear-gradient(aaa);background-repeat:no-repeat;background-position:0 0;background-attachment:scroll}' -// ], -// 'a background-image with a none and a linear-gradient should result in two shorthands' : [ -// 'div{background-color:#111;background-image:none;background-image:linear-gradient(aaa);background-repeat:repeat;background-position:0 0;background-attachment:scroll;background-size:auto;background-origin:padding-box;background-clip:border-box}', -// 'div{background:#111;background:linear-gradient(aaa)#111}' -// ] -// }), -// 'cares about understandability of border components': cssContext({ -// 'border(none) with border(rgba)': 'a{border:none;border:1px solid rgba(1,0,0,.5)}', -// 'border(rgba) with border(none)': 'a{border:1px solid rgba(1,0,0,.5);border:none}', -// 'border(hex) with border(rgba)': 'a{border:1px solid #fff;border:1px solid rgba(1,0,0,.5)}' -// }), -// 'merge same properties sensibly': cssContext({ -// 'should merge color values with same understandability #1': [ -// 'p{color:red;color:#fff;color:blue}', -// 'p{color:#00f}' -// ], -// 'should merge color values with same understandability #2': [ -// 'p{color:red;color:#fff;color:blue;color:transparent}', -// 'p{color:transparent}' -// ], -// 'should NOT destroy less understandable values': [ -// 'p{color:red;color:#fff;color:blue;color:rgba(1,2,3,.4)}', -// 'p{color:#00f;color:rgba(1,2,3,.4)}' -// ], -// 'should destroy even less understandable values if a more understandable one comes after them': [ -// 'p{color:red;color:#fff;color:blue;color:rgba(1,2,3,.4);color:#9fce00}', -// 'p{color:#9fce00}' -// ], -// 'should merge functions with the same name but keep different functions intact': [ -// 'p{background:-webkit-linear-gradient(aaa);background:-webkit-linear-gradient(bbb);background:linear-gradient(aaa);}', -// 'p{background:-webkit-linear-gradient(bbb);background:linear-gradient(aaa)}' -// ], -// 'should merge nonimportant + important into one important': [ -// 'a{color:#aaa;color:#bbb!important}', -// 'a{color:#bbb!important}' -// ], -// 'should merge important + nonimportant into one important': [ -// 'a{color:#aaa!important;color:#bbb}', -// 'a{color:#aaa!important}' -// ], -// 'should merge importants just like nonimportants while also overriding them': [ -// 'p{color:red!important;color:#fff!important;color:blue!important;color:rgba(1,2,3,.4)}', -// 'p{color:#00f!important}' -// ] -// }), -// 'shorthand granular properties when other granular properties are already covered by the shorthand': cssContext({ -// 'should consider the already existing margin to shorthand margin-top and margin-bottom': [ -// 'p{margin:5px;margin-top:foo(1);margin-left:foo(2)}', -// 'p{margin:5px;margin:foo(1)5px 5px foo(2)}' -// ], -// 'should merge margin-top and margin-left with shorthand if their understandability is the same': [ -// 'p{margin:5px;margin-top:1px;margin-left:2px}', -// 'p{margin:1px 5px 5px 2px}' -// ], -// 'should NOT shorthand to margin-top if the result would be longer than the input': [ -// 'p{margin:5px;margin-top:foo(1)}', -// 'p{margin:5px;margin-top:foo(1)}' -// ], -// 'should consider the already existing background to shorthand background-color': [ -// 'p{background:#9fce00;background-color:rgba(1,2,3,.4)}', -// 'p{background:#9fce00;background:rgba(1,2,3,.4)}' -// ], -// 'should NOT touch important outline-color but should minify default value of outline to 0': [ -// 'p{outline:medium;outline-color:#9fce00!important}', -// 'p{outline:0;outline-color:#9fce00!important}' -// ] -// }), -// 'take advantage of importants for optimalization opportunities': cssContext({ -// 'should take into account important margin-left to shorthand non-important margin-top, margin-right and margin-bottom': [ -// 'p{margin-top:1px;margin-right:2px;margin-bottom:3px;margin-left:4px !important}', -// 'p{margin:1px 2px 3px;margin-left:4px!important}' -// ], -// 'should take into account important margin-bottom and margin-left to shorten shorthanded non-important margin-top and margin-bottom': [ -// 'p{margin-top:1px;margin-right:2px;margin-bottom:3px!important;margin-left:4px !important}', -// 'p{margin:1px 2px;margin-bottom:3px!important;margin-left:4px!important}' -// ], -// 'should take into account important margin-right and margin-left to shorten shorthanded non-important margin-top and margin-bottom': [ -// 'p{margin-top:1px;margin-bottom:3px;margin-right:2px!important;margin-left:4px !important}', -// 'p{margin:1px 0 3px;margin-right:2px!important;margin-left:4px!important}' -// ], -// 'should take into account important margin-right and margin-left to shorten shorthanded non-important margin-top and margin-bottom #2': [ -// 'p{margin-top:1px;margin-bottom:1px;margin-right:2px!important;margin-left:4px !important}', -// 'p{margin:1px;margin-right:2px!important;margin-left:4px!important}' -// ], -// 'should take into account important background-color and shorthand others into background': [ -// 'p{background-color:#9fce00!important;background-image:url(hello);background-attachment:scroll;background-position:1px 2px;background-repeat:repeat-y;background-size:auto;background-origin:padding-box;background-clip:border-box}', -// 'p{background-color:#9fce00!important;background:url(hello)1px 2px repeat-y}' -// ], -// 'should take into account important outline-color and default value of outline-width': [ -// 'p{outline:inset medium;outline-color:#9fce00!important;outline-style:inset!important}', -// 'p{outline:0;outline-color:#9fce00!important;outline-style:inset!important}' -// ], -// 'should take into account important background-position remove its irrelevant counterpart': [ -// 'p{background:#9fce00 url(hello) 4px 5px;background-position:5px 3px!important}', -// 'p{background:url(hello)#9fce00;background-position:5px 3px!important}' -// ], -// 'should take into account important background-position and assign the shortest possible value for its irrelevant counterpart': [ -// 'p{background:transparent;background-position:5px 3px!important}', -// 'p{background:0;background-position:5px 3px!important}' -// ] -// }), -// 'properly care about inherit': cssContext({ -// 'merge multiple inherited margin granular properties into one inherited shorthand': [ -// 'p{margin-top:inherit;margin-right:inherit;margin-bottom:inherit;margin-left:inherit}', -// 'p{margin:inherit}' -// ], -// 'merge multiple inherited background granular properties into one inherited shorthand': [ -// 'p{background-color:inherit;background-image:inherit;background-attachment:inherit;background-position:inherit;background-repeat:inherit;;background-size:inherit;background-origin:inherit;background-clip:inherit}', -// 'p{background:inherit}' -// ], -// 'when shorter, optimize inherited/non-inherited background granular properties into an inherited shorthand and some non-inherited granular properties': [ -// 'p{background-color:inherit;background-image:inherit;background-attachment:inherit;background-position:inherit;background-repeat:repeat-y;background-size:inherit;background-origin:inherit;background-clip:inherit}', -// 'p{background:inherit;background-repeat:repeat-y}' -// ], -// 'when shorter, optimize inherited/non-inherited background granular properties into a non-inherited shorthand and some inherited granular properties': [ -// 'p{background-color:#9fce00;background-image:inherit;background-attachment:scroll;background-position:1px 2px;background-repeat:repeat-y;background-size:auto;background-clip:inherit;background-origin:padding-box;}', -// 'p{background:1px 2px repeat-y #9fce00;background-image:inherit;background-clip:inherit}' -// ], -// 'put inherit to the place where it consumes the least space': [ -// 'div{padding:0;padding-bottom:inherit;padding-right:inherit}', -// 'div{padding:inherit;padding-top:0;padding-left:0}' -// ] -// }), -// 'complex granular properties': cssContext({ -// 'two granular properties': 'a{border-bottom:1px solid red;border-color:red}', -// 'more understandable granular property should override less understandable': [ -// 'a{border-color:rgba(0,0,0,.5);border-color:red}', -// 'a{border-color:red}' -// ], -// 'less understandable granular property should NOT override more understandable': [ -// 'a{border-color:red;border-color:rgba(0,0,0,.5)}', -// 'a{border-color:red;border-color:rgba(0,0,0,.5)}' -// ], -// 'two same granular properties redefined': [ -// 'a{border-color:rgba(0,0,0,.5);border-color:red;border:0}', -// 'a{border:0}' -// ], -// 'important granular property redefined': 'a{border-color:red!important;border:0}', -// 'important granular property redefined with important': [ -// 'a{border-color:red!important;border:0!important}', -// 'a{border:0!important}' -// ], -// 'mix of border properties': [ -// 'a{border-top:1px solid red;border-top-color:#0f0;color:red;border-top-width:2px;border-bottom-width:1px;border:0;border-left:1px solid red}', -// 'a{color:red;border:0;border-left:1px solid red}' -// ] -// }), +var vows = require('vows'); +var assert = require('assert'); + +var optimize = require('../../lib/properties/optimizer'); + +var Tokenizer = require('../../lib/selectors/tokenizer'); +var SourceTracker = require('../../lib/utils/source-tracker'); +var Compatibility = require('../../lib/utils/compatibility'); +var Validator = require('../../lib/properties/validator'); +var addOptimizationMetadata = require('../../lib/selectors/optimization-metadata'); + +function _optimize(source) { + var tokens = new Tokenizer({ + options: {}, + sourceTracker: new SourceTracker(), + warnings: [] + }).toTokens(source); + + var compatibility = new Compatibility(compatibility).toOptions(); + var validator = new Validator(compatibility); + var options = { + aggressiveMerging: true, + compatibility: compatibility, + shorthandCompacting: true + }; + addOptimizationMetadata(tokens); + optimize(tokens[0][1], tokens[0][2], false, true, options, validator); + + return tokens[0][2]; +} + +vows.describe(optimize) + .addBatch({ + 'shorthand background #1': { + 'topic': 'p{background-color:#111;background-image:__ESCAPED_URL_CLEAN_CSS0__;background-repeat:repeat;background-position:0 0;background-attachment:scroll;background-size:auto;background-origin:padding-box;background-clip:border-box}', + 'into': function (topic) { + assert.deepEqual(_optimize(topic), [ + [['background', false , false], ['__ESCAPED_URL_CLEAN_CSS0__'], ['#111']] + ]); + } + }, + 'shorthand background #2': { + 'topic': 'p{background-color:#111;background-image:__ESCAPED_URL_CLEAN_CSS0__;background-repeat:no-repeat;background-position:0 0;background-attachment:scroll;background-size:auto;background-origin:padding-box;background-clip:border-box}', + 'into': function (topic) { + assert.deepEqual(_optimize(topic), [ + [['background', false , false], ['__ESCAPED_URL_CLEAN_CSS0__'], ['no-repeat'], ['#111']] + ]); + } + }, + 'shorthand important background': { + 'topic': 'p{background-color:#111!important;background-image:__ESCAPED_URL_CLEAN_CSS0__!important;background-repeat:repeat!important;background-position:0 0!important;background-attachment:scroll!important;background-size:auto!important;background-origin:padding-box!important;background-clip:border-box!important}', + 'into': function (topic) { + assert.deepEqual(_optimize(topic), [ + [['background', true , false], ['__ESCAPED_URL_CLEAN_CSS0__'], ['#111']] + ]); + } + }, + 'shorthand border-width': { + 'topic': 'p{border-top-width:7px;border-bottom-width:7px;border-left-width:4px;border-right-width:4px}', + 'into': function (topic) { + assert.deepEqual(_optimize(topic), [ + [['border-width', false , false], ['7px'], ['4px']] + ]); + } + }, + 'shorthand border-color #1': { + 'topic': 'p{border-top-color:#9fce00;border-bottom-color:#9fce00;border-left-color:#9fce00;border-right-color:#9fce00}', + 'into': function (topic) { + assert.deepEqual(_optimize(topic), [ + [['border-color', false , false], ['#9fce00']] + ]); + } + }, + 'shorthand border-color #2': { + 'topic': 'p{border-right-color:#002;border-bottom-color:#003;border-top-color:#001;border-left-color:#004}', + 'into': function (topic) { + assert.deepEqual(_optimize(topic), [ + [['border-color', false , false], ['#001'], ['#002'], ['#003'], ['#004']] + ]); + } + }, + 'shorthand border-radius': { + 'topic': 'p{border-top-left-radius:7px;border-bottom-right-radius:6px;border-bottom-left-radius:5px;border-top-right-radius:3px}', + 'into': function (topic) { + assert.deepEqual(_optimize(topic), [ + [['border-radius', false , false], ['7px'], ['3px'], ['6px'], ['5px']] + ]); + } + }, + 'shorthand list-style #1': { + 'topic': 'a{list-style-type:circle;list-style-position:outside;list-style-image:__ESCAPED_URL_CLEAN_CSS0__}', + 'into': function (topic) { + assert.deepEqual(_optimize(topic), [ + [['list-style', false , false], ['circle'], ['__ESCAPED_URL_CLEAN_CSS0__']] + ]); + } + }, + 'shorthand list-style #2': { + 'topic': 'a{list-style-image:__ESCAPED_URL_CLEAN_CSS0__;list-style-type:circle;list-style-position:inside}', + 'into': function (topic) { + assert.deepEqual(_optimize(topic), [ + [['list-style', false , false], ['circle'], ['inside'], ['__ESCAPED_URL_CLEAN_CSS0__']] + ]); + } + }, + 'shorthand margin': { + 'topic': 'a{margin-top:10px;margin-right:5px;margin-bottom:3px;margin-left:2px}', + 'into': function (topic) { + assert.deepEqual(_optimize(topic), [ + [['margin', false , false], ['10px'], ['5px'], ['3px'], ['2px']] + ]); + } + }, + 'shorthand padding': { + 'topic': 'a{padding-top:10px;padding-left:5px;padding-bottom:3px;padding-right:2px}', + 'into': function (topic) { + assert.deepEqual(_optimize(topic), [ + [['padding', false , false], ['10px'], ['2px'], ['3px'], ['5px']] + ]); + } + }, + 'mixed': { + 'topic': 'a{padding-top:10px;margin-top:3px;padding-left:5px;margin-left:3px;padding-bottom:3px;margin-bottom:3px;padding-right:2px;margin-right:3px}', + 'into': function (topic) { + assert.deepEqual(_optimize(topic), [ + [['padding', false , false], ['10px'], ['2px'], ['3px'], ['5px']], + [['margin', false , false], ['3px']] + ]); + } + }, + 'with other properties': { + 'topic': 'a{padding-top:10px;padding-left:5px;padding-bottom:3px;color:red;padding-right:2px;width:100px}', + 'into': function (topic) { + assert.deepEqual(_optimize(topic), [ + [['color', false , false], ['red']], + [['width', false , false], ['100px']], + [['padding', false , false], ['10px'], ['2px'], ['3px'], ['5px']] + ]); + } + } + }) + .addBatch({ + 'not enough components': { + 'topic': 'a{padding-top:10px;padding-left:5px;padding-bottom:3px}', + 'into': function (topic) { + assert.deepEqual(_optimize(topic), [ + [['padding-top', false , false], ['10px']], + [['padding-left', false , false], ['5px']], + [['padding-bottom', false , false], ['3px']] + ]); + } + }, + 'mixed importance': { + 'topic': 'a{padding-top:10px;padding-left:5px;padding-bottom:3px;padding-right:2px!important}', + 'into': function (topic) { + assert.deepEqual(_optimize(topic), [ + [['padding-top', false , false], ['10px']], + [['padding-left', false , false], ['5px']], + [['padding-bottom', false , false], ['3px']], + [['padding-right', true , false], ['2px']] + ]); + } + }, + 'mixed understandability of units': { + 'topic': 'a{padding-top:10px;padding-left:5px;padding-bottom:3px;padding-right:calc(100% - 20px)}', + 'into': function (topic) { + assert.deepEqual(_optimize(topic), [ + [['padding-top', false , false], ['10px']], + [['padding-left', false , false], ['5px']], + [['padding-bottom', false , false], ['3px']], + [['padding-right', false , false], ['calc(100% - 20px)']] + ]); + } + }, + 'mixed understandability of images': { + 'topic': 'p{background-color:#111;background-image:linear-gradient(sth);background-repeat:repeat;background-position:0 0;background-attachment:scroll;background-size:auto;background-origin:padding-box;background-clip:border-box}', + 'into': function (topic) { + assert.deepEqual(_optimize(topic), [ + [['background-color', false , false], ['#111']], + [['background-image', false , false], ['linear-gradient(sth)']], + [['background-repeat', false , false], ['repeat']], + [['background-position', false , false], ['0'], ['0']], + [['background-attachment', false , false], ['scroll']], + [['background-size', false , false], ['auto']], + [['background-origin', false , false], ['padding-box']], + [['background-clip', false , false], ['border-box']] + ]); + } + } + }) + .export(module); -- 2.34.1