From b6298a41e215b5f7baf70f623104238f297ca757 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Timur=20Krist=C3=B3f?= Date: Tue, 4 Mar 2014 10:36:47 +0100 Subject: [PATCH] Implemented stylistic changes as asked --- lib/properties/optimizer.js | 22 ++++--- lib/properties/override-compactor.js | 22 +++---- lib/properties/processable.js | 82 ++++++++++++--------------- lib/properties/shorthand-compactor.js | 39 ++++++------- lib/properties/validator.js | 40 ++++++++----- 5 files changed, 105 insertions(+), 100 deletions(-) diff --git a/lib/properties/optimizer.js b/lib/properties/optimizer.js index 4fa83c27..b2e30b51 100644 --- a/lib/properties/optimizer.js +++ b/lib/properties/optimizer.js @@ -1,3 +1,8 @@ + +var processableInfo = require('./processable'); +var overrideCompactor = require('./override-compactor'); +var shorthandCompactor = require('./shorthand-compactor'); + module.exports = function Optimizer(compatibility) { var overridable = { 'animation-delay': ['animation'], @@ -233,18 +238,17 @@ module.exports = function Optimizer(compatibility) { return flat.join(';'); }; - var p = require('./processable'); - var overrideCompactor = require('./override-compactor'); - var shorthandCompactor = require('./shorthand-compactor'); - var compact = function (input) { - var tokens = p.Token.tokenize(input); + var processable = processableInfo.processable; + var Token = processableInfo.Token; + + var tokens = Token.tokenize(input); - tokens = overrideCompactor.compactOverrides(tokens, p.processable); - tokens = shorthandCompactor.compactShorthands(tokens, false, p.processable, p.Token); - tokens = shorthandCompactor.compactShorthands(tokens, true, p.processable, p.Token); + tokens = overrideCompactor.compactOverrides(tokens, processable); + tokens = shorthandCompactor.compactShorthands(tokens, false, processable, Token); + tokens = shorthandCompactor.compactShorthands(tokens, true, processable, Token); - return p.Token.detokenize(tokens); + return Token.detokenize(tokens); }; return { diff --git a/lib/properties/override-compactor.js b/lib/properties/override-compactor.js index b51f7b2e..418cad2d 100644 --- a/lib/properties/override-compactor.js +++ b/lib/properties/override-compactor.js @@ -2,7 +2,10 @@ // Compacts the given tokens according to their ability to override each other. module.exports = (function () { - var sameValue = function (val1, val2) { return val1 === val2; }; + // Default override function: only allow overrides when the two values are the same + var sameValue = function (val1, val2) { + return val1 === val2; + }; var compactOverrides = function (tokens, processable) { var result, can, token, t, i, ii, oldResult, matchingComponent; @@ -17,17 +20,18 @@ module.exports = (function () { }; // Go from the end and always take what the current token can't override as the new result set + // NOTE: can't cache result.length here because it will change with every iteration for (result = tokens, i = 0; (ii = result.length - 1 - i) >= 0; i++) { token = result[ii]; - //console.log(i, ii, token.prop); can = (processable[token.prop] && processable[token.prop].canOverride) || sameValue; oldResult = result; result = []; // Special flag which indicates that the current token should be removed var removeSelf = false; + var oldResultLength = oldResult.length; - for (var iii = 0; iii < oldResult.length; iii++) { + for (var iii = 0; iii < oldResultLength; iii++) { t = oldResult[iii]; // A token can't override itself (checked by reference, not by value) @@ -59,8 +63,7 @@ module.exports = (function () { // The shorthand can't override the component result.push(t); } - } - else if (t.isShorthand && !token.isShorthand && token.isComponentOf(t)) { + } else if (t.isShorthand && !token.isShorthand && token.isComponentOf(t)) { // token (a component) is trying to override a component of t (a shorthand) // Find the matching component in the shorthand @@ -74,8 +77,7 @@ module.exports = (function () { matchingComponent.value = token.value; // We use the special flag to get rid of the component removeSelf = true; - } - else { + } else { // The overriding component is important; sadly we can't get rid of it, // but we can still mark the matching component in the shorthand as irrelevant matchingComponent.isIrrelevant = true; @@ -83,8 +85,7 @@ module.exports = (function () { t.isDirty = true; } result.push(t); - } - else if (token.isShorthand && t.isShorthand && token.prop === t.prop) { + } else if (token.isShorthand && t.isShorthand && token.prop === t.prop) { // token is a shorthand and is trying to override another instance of the same shorthand // Can only override other shorthand when each of its components can override each of the other's components @@ -95,8 +96,7 @@ module.exports = (function () { break; } } - } - else if (t.prop !== token.prop || !can(t.value, token.value)) { + } else if (t.prop !== token.prop || !can(t.value, token.value)) { // in every other case, use the override mechanism result.push(t); } diff --git a/lib/properties/processable.js b/lib/properties/processable.js index 8c9a7ac2..988bf381 100644 --- a/lib/properties/processable.js +++ b/lib/properties/processable.js @@ -1,8 +1,10 @@ // Contains the interpretation of CSS properties, as used by the property optimizer + module.exports = (function () { + var tokenModule = require('./token'); var validator = require('./validator'); // Functions that decide what value can override what. @@ -138,22 +140,21 @@ module.exports = (function () { var result = []; var curr = ''; var parenthesisLevel = 0; + var valLength = val.length; - for (var i = 0; i < val.length; i++) { + for (var i = 0; i < valLength; i++) { var c = val[i]; curr += c; if (c === '(') { parenthesisLevel++; - } - else if (c === ')') { + } else if (c === ')') { parenthesisLevel--; if (parenthesisLevel === 0) { result.push(curr.trim()); curr = ''; } - } - else if (c === ' ' && parenthesisLevel === 0) { + } else if (c === ' ' && parenthesisLevel === 0) { result.push(curr.trim()); curr = ''; } @@ -181,9 +182,8 @@ module.exports = (function () { // Break the background up into parts var parts = token.value.split(' '); - if (parts.length === 0) { + if (parts.length === 0) return result; - } // The trick here is that we start going through the parts from the end, then stop after background repeat, // then start from the from the beginning until we find a valid color value. What remains will be treated as background-image. @@ -204,8 +204,7 @@ module.exports = (function () { position.value = pos; currentIndex -= 2; current = parts[currentIndex]; - } - else if (currentIndex >= 0 && validator.isValidBackgroundPosition(current)) { + } else if (currentIndex >= 0 && validator.isValidBackgroundPosition(current)) { // Found position (containing just one part) position.value = current; currentIndex--; @@ -318,33 +317,26 @@ module.exports = (function () { fourUnits: function (prop, tokens, isImportant) { // See about irrelevant tokens // NOTE: This will enable some crazy optimalizations for us. - if (tokens[0].isIrrelevant) { + if (tokens[0].isIrrelevant) tokens[0].value = tokens[2].value; - } - if (tokens[2].isIrrelevant) { + if (tokens[2].isIrrelevant) tokens[2].value = tokens[0].value; - } - if (tokens[1].isIrrelevant) { + if (tokens[1].isIrrelevant) tokens[1].value = tokens[3].value; - } - if (tokens[3].isIrrelevant) { + if (tokens[3].isIrrelevant) tokens[3].value = tokens[1].value; - } + if (tokens[0].isIrrelevant && tokens[2].isIrrelevant) { - if (tokens[1].value === tokens[3].value) { + if (tokens[1].value === tokens[3].value) tokens[0].value = tokens[2].value = tokens[1].value; - } - else { + else tokens[0].value = tokens[2].value = '0'; - } } if (tokens[1].isIrrelevant && tokens[3].isIrrelevant) { - if (tokens[0].value === tokens[2].value) { + if (tokens[0].value === tokens[2].value) tokens[1].value = tokens[3].value = tokens[0].value; - } - else { + else tokens[1].value = tokens[3].value = '0'; - } } var result = new Token(prop, tokens[0].value, isImportant); @@ -443,19 +435,18 @@ module.exports = (function () { var r2s = new Token(tokens[i].prop, tokens[i].isImportant); r2s.isIrrelevant = true; result2Shorthandable.push(r2s); - } - else { + } else { nonInheritingTokens.push(tokens[i]); result2Shorthandable.push(tokens[i]); } } - // When all the tokens are 'inherit' if (nonInheritingTokens.length === 0) { + // When all the tokens are 'inherit' return new Token(prop, 'inherit', isImportant); - } - // When some (but not all) of the tokens are 'inherit' - else if (inheritingTokens.length > 0) { + } else if (inheritingTokens.length > 0) { + // When some (but not all) of the tokens are 'inherit' + // Result 1. Shorthand just the inherit values and have it overridden with the non-inheriting ones var result1 = [new Token(prop, 'inherit', isImportant)].concat(nonInheritingTokens); @@ -467,9 +458,8 @@ module.exports = (function () { var dl2 = Token.getDetokenizedLength(result2); return dl1 < dl2 ? result1 : result2; - } - // When none of tokens are 'inherit' - else { + } else { + // When none of tokens are 'inherit' return innerFunc(prop, tokens, isImportant); } }; @@ -684,23 +674,25 @@ module.exports = (function () { // Set some stuff iteratively for (var proc in processable) { - if (processable.hasOwnProperty(proc)) { - var currDesc = processable[proc]; + if (!processable.hasOwnProperty(proc)) + continue; - if (currDesc.components instanceof Array && currDesc.components.length) { - currDesc.isShorthand = true; + var currDesc = processable[proc]; - for (var cI = 0; cI < currDesc.components.length; cI++) { - if (!processable[currDesc.components[cI]]) { - throw new Error('"' + currDesc.components[cI] + '" is defined as a component of "' + proc + '" but isn\'t defined in processable.'); - } - processable[currDesc.components[cI]].componentOf = proc; - } + if (!(currDesc.components instanceof Array) || currDesc.components.length === 0) + continue; + + currDesc.isShorthand = true; + + for (var cI = 0; cI < currDesc.components.length; cI++) { + if (!processable[currDesc.components[cI]]) { + throw new Error('"' + currDesc.components[cI] + '" is defined as a component of "' + proc + '" but isn\'t defined in processable.'); } + processable[currDesc.components[cI]].componentOf = proc; } } - var Token = require('./token').createTokenPrototype(processable); + var Token = tokenModule.createTokenPrototype(processable); return { processable: processable, diff --git a/lib/properties/shorthand-compactor.js b/lib/properties/shorthand-compactor.js index 177d7899..12f7c985 100644 --- a/lib/properties/shorthand-compactor.js +++ b/lib/properties/shorthand-compactor.js @@ -18,17 +18,19 @@ module.exports = (function () { var prop = processable[shprop].components[i]; found[prop] = []; - if (componentsSoFar[shprop].found[prop]) { - for (var ii = 0; ii < componentsSoFar[shprop].found[prop].length; ii++) { - var comp = componentsSoFar[shprop].found[prop][ii]; - - if (!comp.isMarkedForDeletion) { - found[prop].push(comp); - if (comp.position && (!shorthandPosition || comp.position < shorthandPosition)) { - shorthandPosition = comp.position; - } - } - } + 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.isMarkedForDeletion) + continue; + + found[prop].push(comp); + + if (comp.position && (!shorthandPosition || comp.position < shorthandPosition)) + shorthandPosition = comp.position; } } } @@ -78,13 +80,11 @@ module.exports = (function () { if (foundRealComp.isReal !== false) { realComponents.push(foundRealComp); } - } - else if (componentsSoFar[prop].lastShorthand) { + } 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 { + } else { // Couldn't find this component at all return false; } @@ -192,15 +192,13 @@ module.exports = (function () { // 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.prop].componentOf) { + } else if (processable[token.prop].componentOf) { // Found a component of a shorthand if (token.isImportant === isImportant) { // Same importantness token.position = i; addComponentSoFar(token, i); - } - else if (!isImportant && token.isImportant) { + } else if (!isImportant && token.isImportant) { // Use importants for optimalization opportunities // https://github.com/GoalSmashers/clean-css/issues/184 var importantTrickComp = new Token(token.prop, token.value, isImportant); @@ -208,8 +206,7 @@ module.exports = (function () { importantTrickComp.isReal = false; addComponentSoFar(importantTrickComp); } - } - else { + } else { // This is not a shorthand and not a component, don't care about it continue; } diff --git a/lib/properties/validator.js b/lib/properties/validator.js index 5ffce7f3..6e12905c 100644 --- a/lib/properties/validator.js +++ b/lib/properties/validator.js @@ -3,11 +3,19 @@ module.exports = (function () { // Regexes used for stuff - var cssUnitRegexStr = '(\\-?\\.?\\d+\\.?\\d*(px|%|em|rem|in|cm|mm|ex|pt|pc|)|auto|inherit)'; + var cssUnitRegexStr = '(\\-?\\.?\\d+\\.?\\d*(px|%|em|rem|in|cm|mm|ex|pt|pc|)|auto|inherit)'; var cssFunctionNoVendorRegexStr = '[A-Z]+(\\-|[A-Z]|[0-9])+\\(([A-Z]|[0-9]|\\ |\\,|\\#|\\+|\\-|\\%|\\.)*\\)'; - var cssFunctionVendorRegexStr = '\\-(\\-|[A-Z]|[0-9])+\\(([A-Z]|[0-9]|\\ |\\,|\\#|\\+|\\-|\\%|\\.)*\\)'; - var cssFunctionAnyRegexStr = '(' + cssFunctionNoVendorRegexStr + '|' + cssFunctionVendorRegexStr + ')'; - var cssUnitAnyRegexStr = '(' + cssUnitRegexStr + '|' + cssFunctionNoVendorRegexStr + '|' + cssFunctionVendorRegexStr + ')'; + var cssFunctionVendorRegexStr = '\\-(\\-|[A-Z]|[0-9])+\\(([A-Z]|[0-9]|\\ |\\,|\\#|\\+|\\-|\\%|\\.)*\\)'; + var cssFunctionAnyRegexStr = '(' + cssFunctionNoVendorRegexStr + '|' + cssFunctionVendorRegexStr + ')'; + var cssUnitAnyRegexStr = '(' + cssUnitRegexStr + '|' + cssFunctionNoVendorRegexStr + '|' + cssFunctionVendorRegexStr + ')'; + + var backgroundRepeatKeywords = ['repeat', 'no-repeat', 'repeat-x', 'repeat-y', 'inherit']; + var backgroundAttachmentKeywords = ['inherit', 'scroll', 'fixed', 'local']; + var backgroundPositionKeywords = ['center', 'top', 'bottom', 'left', 'right']; + var listStyleTypeKeywords = ['armenian', 'circle', 'cjk-ideographic', 'decimal', 'decimal-leading-zero', 'disc', 'georgian', 'hebrew', 'hiragana', 'hiragana-iroha', 'inherit', 'katakana', 'katakana-iroha', 'lower-alpha', 'lower-greek', 'lower-latin', 'lower-roman', 'none', 'square', 'upper-alpha', 'upper-latin', 'upper-roman']; + var listStylePositionKeywords = ['inside', 'outside', 'inherit']; + var outlineStyleKeywords = ['inherit', 'hidden', 'none', 'dotted', 'dashed', 'solid', 'double', 'groove', 'ridge', 'inset', 'outset']; + var outlineWidthKeywords = ['thin', 'thick', 'medium', 'inherit']; var validator = { isValidHexColor: function (s) { @@ -48,37 +56,41 @@ module.exports = (function () { return new RegExp('^' + cssFunctionAnyRegexStr + '$', 'gi').test(s); }, isValidBackgroundRepeat: function (s) { - return s === 'repeat' || s === 'no-repeat' || s === 'repeat-x' || s === 'repeat-y' || s === 'inherit'; + return backgroundRepeatKeywords.indexOf(s) >= 0; }, isValidBackgroundAttachment: function (s) { - return s === 'inherit' || s === 'scroll' || s === 'fixed' || s === 'local'; + return backgroundAttachmentKeywords.indexOf(s) >= 0; }, isValidBackgroundPositionPart: function (s) { - if (s === 'center' || s === 'top' || s === 'bottom' || s === 'left' || s === 'right') + if (backgroundPositionKeywords.indexOf(s) >= 0) return true; - // LIMITATION: currently we don't support functions in here because otherwise we'd confuse things like linear-gradient() - // we need to figure out the complete list of functions that are allowed for units and then we can use isValidUnit here. + return new RegExp('^' + cssUnitRegexStr + '$', 'gi').test(s); }, isValidBackgroundPosition: function (s) { if (s === 'inherit') return true; - return s.split(' ').every(function(p) { return validator.isValidBackgroundPositionPart(p); }); + + return s.split(' ').filter(function (p) { + return p !== ''; + }).every(function(p) { + return validator.isValidBackgroundPositionPart(p); + }); }, isValidListStyleType: function (s) { - return s === 'armenian' || s === 'circle' || s === 'cjk-ideographic' || s === 'decimal' || s === 'decimal-leading-zero' || s === 'disc' || s === 'georgian' || s === 'hebrew' || s === 'hiragana' || s === 'hiragana-iroha' || s === 'inherit' || s === 'katakana' || s === 'katakana-iroha' || s === 'lower-alpha' || s === 'lower-greek' || s === 'lower-latin' || s === 'lower-roman' || s === 'none' || s === 'square' || s === 'upper-alpha' || s === 'upper-latin' || s === 'upper-roman'; + return listStyleTypeKeywords.indexOf(s) >= 0; }, isValidListStylePosition: function (s) { - return s === 'inside' || s === 'outside' || s === 'inherit'; + return listStylePositionKeywords.indexOf(s) >= 0; }, isValidOutlineColor: function (s) { return s === 'invert' || validator.isValidColor(s) || validator.isValidVendorPrefixedValue(s); }, isValidOutlineStyle: function (s) { - return s === 'inherit' || s === 'hidden' || s === 'none' || s === 'dotted' || s === 'dashed' || s === 'solid' || s === 'double' || s === 'groove' || s === 'ridge' || s === 'inset' || s === 'outset'; + return outlineStyleKeywords.indexOf(s) >= 0; }, isValidOutlineWidth: function (s) { - return validator.isValidUnit(s) || s === 'thin' || s === 'thick' || s === 'medium' || s === 'inherit'; + return validator.isValidUnit(s) || outlineWidthKeywords.indexOf(s) >= 0; }, isValidVendorPrefixedValue: function (s) { return /^-([A-Za-z0-9]|-)*$/gi.test(s); -- 2.34.1