From: Jakub Pawlowicz Date: Wed, 15 Apr 2015 07:25:02 +0000 (+0100) Subject: Fixes #528 - better support for IE<9 hacks. X-Git-Url: https://git.ndcode.org/public/gitweb.cgi?a=commitdiff_plain;h=d6b356d439de83905c1a2051120e7bd0d355c646;p=clean-css.git Fixes #528 - better support for IE<9 hacks. Adds better hack handling in property overriding and compacting too. Also normalizes all suffix hacks to '\0' as IE treats them equally. --- diff --git a/History.md b/History.md index cec96cc3..1b4fd7ce 100644 --- a/History.md +++ b/History.md @@ -19,6 +19,7 @@ * Fixed issue [#508](https://github.com/jakubpawlowicz/clean-css/issues/508) - removing duplicate media queries. * Fixed issue [#521](https://github.com/jakubpawlowicz/clean-css/issues/521) - unit optimizations inside `calc()`. * Fixed issue [#526](https://github.com/jakubpawlowicz/clean-css/issues/526) - shorthand overriding into a function. +* Fixed issue [#528](https://github.com/jakubpawlowicz/clean-css/issues/528) - better support for IE<9 hacks. [3.1.9 / 2015-04-04](https://github.com/jakubpawlowicz/clean-css/compare/v3.1.8...v3.1.9) ================== diff --git a/lib/properties/optimizer.js b/lib/properties/optimizer.js index f777a520..55cb3878 100644 --- a/lib/properties/optimizer.js +++ b/lib/properties/optimizer.js @@ -143,6 +143,9 @@ function _optimize(properties, mergeAdjacent, aggressiveMerging, validator) { if (!wasImportant && (wasHack && !isHack || !wasHack && isHack)) continue; + if (wasImportant && (isHack == 'star' || isHack == 'underscore')) + continue; + if (!wasHack && !isHack && !longhandToShorthand && canOverride && !canOverride(toRemove, property, validator)) continue; diff --git a/lib/properties/override-compactor.js b/lib/properties/override-compactor.js index 0942dc05..8a7e07df 100644 --- a/lib/properties/override-compactor.js +++ b/lib/properties/override-compactor.js @@ -200,6 +200,9 @@ function compactOverrides(properties, compatibility, validator) { if (left.unused || right.unused) continue; + if (left.hack && !right.hack || !left.hack && right.hack) + continue; + if (hasInherit(right)) continue; diff --git a/lib/properties/shorthand-compactor.js b/lib/properties/shorthand-compactor.js index 59637cbe..be22088e 100644 --- a/lib/properties/shorthand-compactor.js +++ b/lib/properties/shorthand-compactor.js @@ -110,6 +110,9 @@ function compactShortands(properties, sourceMaps, validator) { if (property.unused) continue; + if (property.hack) + continue; + var descriptor = compactable[property.name]; if (!descriptor || !descriptor.componentOf) continue; diff --git a/lib/selectors/optimization-metadata.js b/lib/selectors/optimization-metadata.js index b05296c4..d5b469eb 100644 --- a/lib/selectors/optimization-metadata.js +++ b/lib/selectors/optimization-metadata.js @@ -1,6 +1,6 @@ // TODO: we should wrap it under `wrap for optimizing` -var BACKSLASH_HACK = '\\9'; +var BACKSLASH_HACK = '\\'; var IMPORTANT = '!important'; var STAR_HACK = '*'; var UNDERSCORE_HACK = '_'; @@ -31,16 +31,25 @@ function addToProperty(property) { var name = property[0][0]; var lastValue = property[property.length - 1]; var isImportant = lastValue[0].indexOf(IMPORTANT) > 0; - var isHack = name[0] == UNDERSCORE_HACK || - name[0] == STAR_HACK || - lastValue[0].indexOf(BACKSLASH_HACK) > 0 && lastValue[0].indexOf(BACKSLASH_HACK) == lastValue[0].length - BACKSLASH_HACK.length; + var hackType = false; + + if (name[0] == UNDERSCORE_HACK) { + property[0][0] = name.substring(1); + hackType = 'underscore'; + } else if (name[0] == STAR_HACK) { + property[0][0] = name.substring(1); + hackType = 'star'; + } else if (lastValue[0].indexOf(BACKSLASH_HACK) > 0 && lastValue[0].indexOf(BACKSLASH_HACK) == lastValue[0].length - BACKSLASH_HACK.length - 1) { + lastValue[0] = lastValue[0].substring(0, lastValue[0].length - BACKSLASH_HACK.length - 1); + hackType = 'suffix'; + } // TODO: this should be done at tokenization step // with adding importance info if (isImportant) lastValue[0] = lastValue[0].substring(0, lastValue[0].length - IMPORTANT.length); - property[0].splice(1, 0, isImportant, isHack); + property[0].splice(1, 0, isImportant, hackType); } module.exports = addOptimizationMetadata; diff --git a/lib/selectors/optimizers/simple.js b/lib/selectors/optimizers/simple.js index f4f47bbd..342113f4 100644 --- a/lib/selectors/optimizers/simple.js +++ b/lib/selectors/optimizers/simple.js @@ -198,9 +198,9 @@ function optimizeBody(properties, options) { name = property[0][0]; - if (property[0][2]) { - var isPrefixHack = name[0] == '_'; - if (isPrefixHack && !options.compatibility.properties.iePrefixHack || !isPrefixHack && !options.compatibility.properties.ieSuffixHack) + var hackType = property[0][2]; + if (hackType) { + if ((hackType == 'star' || hackType == 'underscore') && !options.compatibility.properties.iePrefixHack || hackType == 'suffix' && !options.compatibility.properties.ieSuffixHack) unused = true; } diff --git a/lib/stringifier/helpers.js b/lib/stringifier/helpers.js index b8b3dc81..ffb6b816 100644 --- a/lib/stringifier/helpers.js +++ b/lib/stringifier/helpers.js @@ -1,5 +1,9 @@ var lineBreak = require('os').EOL; +var STAR_HACK = '*'; +var SUFFIX_HACK = '\\0'; +var UNDERSCORE_HACK = '_'; + function hasMoreProperties(tokens, index) { for (var i = index, l = tokens.length; i < l; i++) { if (typeof tokens[i] != 'string') @@ -41,6 +45,14 @@ function inSpecialContext(token, valueIndex, context) { afterComma(token, valueIndex); } +function storePrefixHack(name, context) { + var hackType = name[2]; + if (hackType == 'underscore') + context.store(UNDERSCORE_HACK, context); + else if (hackType == 'star') + context.store(STAR_HACK, context); +} + function selectors(tokens, context) { var store = context.store; @@ -65,6 +77,7 @@ function property(tokens, position, isLast, context) { if (typeof token == 'string') { store(token, context); } else { + storePrefixHack(token[0], context); store(token[0], context); store(':', context); value(tokens, position, isLast, context); @@ -75,10 +88,13 @@ function value(tokens, position, isLast, context) { var store = context.store; var token = tokens[position]; var isImportant = token[0][1]; + var hackType = token[0][2]; for (var j = 1, m = token.length; j < m; j++) { store(token[j], context); + if (j == m - 1 && hackType == 'suffix') + store(SUFFIX_HACK, context); if (j == m - 1 && isImportant) store('!important', context); diff --git a/test/integration-test.js b/test/integration-test.js index ba9ef9da..ffe86894 100644 --- a/test/integration-test.js +++ b/test/integration-test.js @@ -1121,16 +1121,10 @@ path")}', 'IE hacks': cssContext({ 'star': 'a{*color:#fff}', 'unserscore': 'a{_color:#fff}', - 'backslash': 'a{color:#fff\\9}', + 'backslash': 'a{color:#fff\\0}', 'overriding by a star': 'a{color:red;display:block;*color:#fff}', 'overriding by a unserscore': 'a{color:red;display:block;_color:#fff}', - 'overriding by a backslash': 'a{color:red;display:block;color:#fff\\9}', - 'overriding !important by a star': 'a{color:red!important;display:block;*color:#fff}', - 'overriding !important by a unserscore': 'a{color:red!important;display:block;_color:#fff}', - 'overriding !important by a backslash': [ - 'a{color:red!important;display:block;color:#fff\\9}', - 'a{color:red!important;display:block}' - ], + 'overriding by a backslash': 'a{color:red;display:block;color:#fff\\0}', 'overriding a star': [ 'a{*color:red;display:block;*color:#fff}', 'a{display:block;*color:#fff}' @@ -1141,11 +1135,12 @@ path")}', ], 'overriding a backslash': [ 'a{color:red\\9;display:block;color:#fff\\9}', - 'a{display:block;color:#fff\\9}' + 'a{display:block;color:#fff\\0}' ], 'overriding a star by a non-ajacent selector': 'a{color:red}.one{color:#000}a{*color:#fff}', 'overriding an underscore by a non-ajacent selector': 'a{color:red}.one{color:#000}a{_color:#fff}', - 'overriding a backslash by a non-ajacent selector': 'a{color:red}.one{color:#fff}a{color:#fff\\9}', + 'overriding a backslash by a non-ajacent selector': 'a{color:red}.one{color:#fff}a{color:#fff\\0}', + 'preserving backslash in overriddable': 'a{border:1px solid #ccc\\0}', 'keeps rgba(0,0,0,0)': 'a{color:rgba(0,0,0,0)}', 'keeps rgba(255,255,255,0)': 'a{color:rgba(255,255,255,0)}', 'keeps hsla(120,100%,50%,0)': 'a{color:hsla(120,100%,50%,0)}' diff --git a/test/properties/optimizer-test.js b/test/properties/optimizer-test.js index bc102161..fbff5b7f 100644 --- a/test/properties/optimizer-test.js +++ b/test/properties/optimizer-test.js @@ -180,7 +180,7 @@ vows.describe(optimize) assert.deepEqual(_optimize(topic, false, true), [ [['color', false , false], ['red']], [['display', false , false], ['none']], - [['color', false , true], ['#fff\\9']] + [['color', false , 'suffix'], ['#fff']] ]); } }, @@ -188,7 +188,7 @@ vows.describe(optimize) 'topic': 'p{color:red\\9;display:none;color:#fff}', 'into': function (topic) { assert.deepEqual(_optimize(topic, false, true), [ - [['color', false , true], ['red\\9']], + [['color', false , 'suffix'], ['red']], [['display', false , false], ['none']], [['color', false , false], ['#fff']] ]); @@ -199,7 +199,7 @@ vows.describe(optimize) 'into': function (topic) { assert.deepEqual(_optimize(topic, false, true), [ [['display', false , false], ['none']], - [['color', false , true], ['#fff\\9']] + [['color', false , 'suffix'], ['#fff']] ]); } } diff --git a/test/properties/override-compacting-test.js b/test/properties/override-compacting-test.js index e643b9de..4268195e 100644 --- a/test/properties/override-compacting-test.js +++ b/test/properties/override-compacting-test.js @@ -524,4 +524,35 @@ vows.describe(optimize) } } }) + .addBatch({ + 'overriding !important by a star hack': { + 'topic': 'a{color:red!important;display:block;*color:#fff}', + 'into': function (topic) { + assert.deepEqual(_optimize(topic), [ + [['color', true , false], ['red']], + [['display', false , false], ['block']], + [['color', false , 'star'], ['#fff']] + ]); + } + }, + 'overriding !important by an underscore hack': { + 'topic': 'a{color:red!important;display:block;_color:#fff}', + 'into': function (topic) { + assert.deepEqual(_optimize(topic), [ + [['color', true , false], ['red']], + [['display', false , false], ['block']], + [['color', false , 'underscore'], ['#fff']] + ]); + } + }, + 'overriding !important by an backslash hack': { + 'topic': 'a{color:red!important;display:block;color:#fff\\0}', + 'into': function (topic) { + assert.deepEqual(_optimize(topic), [ + [['color', true , false], ['red']], + [['display', false , false], ['block']] + ]); + } + } + }) .export(module); diff --git a/test/properties/shorthand-compacting-test.js b/test/properties/shorthand-compacting-test.js index 42441589..b3bf0cbd 100644 --- a/test/properties/shorthand-compacting-test.js +++ b/test/properties/shorthand-compacting-test.js @@ -137,6 +137,17 @@ vows.describe(optimize) [['padding', false, false], ['10px'], ['2px'], ['3px'], ['5px']] ]); } + }, + 'with hacks': { + 'topic': 'a{padding-top:10px;padding-left:5px;padding-bottom:3px;_padding-right:2px}', + '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, 'underscore'], ['2px']] + ]); + } } }) .addBatch({ diff --git a/test/properties/wrap-for-optimizing-test.js b/test/properties/wrap-for-optimizing-test.js index e6d76b52..9a16af77 100644 --- a/test/properties/wrap-for-optimizing-test.js +++ b/test/properties/wrap-for-optimizing-test.js @@ -98,6 +98,17 @@ vows.describe(wrapForOptimizing) 'is unused': function (wrapped) { assert.isTrue(wrapped[0].unused); } + }, + 'hack': { + 'topic': function () { + return wrapForOptimizing([[['margin', false, 'suffix']]]); + }, + 'has one wrap': function (wrapped) { + assert.lengthOf(wrapped, 1); + }, + 'is a hack': function (wrapped) { + assert.equal(wrapped[0].hack, 'suffix'); + } } }) .export(module); diff --git a/test/selectors/optimization-metadata-test.js b/test/selectors/optimization-metadata-test.js index 1fb9c61a..15907b3a 100644 --- a/test/selectors/optimization-metadata-test.js +++ b/test/selectors/optimization-metadata-test.js @@ -30,21 +30,21 @@ vows.describe(addOptimizationMetadata) 'topic': [['selector', ['a'], [[['_color'], ['red']]] ]], 'metadata': function (tokens) { addOptimizationMetadata(tokens); - assert.deepEqual(tokens, [['selector', ['a'], [[['_color', false, true], ['red']]] ]]); + assert.deepEqual(tokens, [['selector', ['a'], [[['color', false, 'underscore'], ['red']]] ]]); } }, 'star hack': { - 'topic': [['selector', ['a'], [[['_color'], ['red']]] ]], + 'topic': [['selector', ['a'], [[['*color'], ['red']]] ]], 'metadata': function (tokens) { addOptimizationMetadata(tokens); - assert.deepEqual(tokens, [['selector', ['a'], [[['_color', false, true], ['red']]] ]]); + assert.deepEqual(tokens, [['selector', ['a'], [[['color', false, 'star'], ['red']]] ]]); } }, 'backslash hack': { 'topic': [['selector', ['a'], [[['color'], ['red\\9']]] ]], 'metadata': function (tokens) { addOptimizationMetadata(tokens); - assert.deepEqual(tokens, [['selector', ['a'], [[['color', false, true], ['red\\9']]] ]]); + assert.deepEqual(tokens, [['selector', ['a'], [[['color', false, 'suffix'], ['red']]] ]]); } }, 'backslash hack - value of length 1': { diff --git a/test/selectors/optimizers/simple-test.js b/test/selectors/optimizers/simple-test.js index 5bc0fc62..d2e4ead6 100644 --- a/test/selectors/optimizers/simple-test.js +++ b/test/selectors/optimizers/simple-test.js @@ -332,15 +332,15 @@ vows.describe(SimpleOptimizer) propertyContext('ie hacks in compatibility mode', { 'underscore': [ 'a{_width:100px}', - [['_width', '100px']] + [['width', '100px']] ], 'star': [ 'a{*width:100px}', - [['*width', '100px']] + [['width', '100px']] ], 'backslash': [ 'a{width:100px\\9}', - [['width', '100px\\9',]] + [['width', '100px']] ] }, { compatibility: 'ie8' }) )