* Removes tokenization from all steps in favour to single at the beginning.
* Adds tokenization of properties value by value instead of all in one piece, e.g.
`margin:0px 1px` gets tokenized into `margin`, `0px, and `1px`.
* Full tokenization means more detailed source maps too.
* Reworks override compactor and associated classes to use single tokenization.
* Simplifies override compactor code so it's much easier to understand.
* Adds loads of tests to override compactor and family.
Caveats / in progress:
* Shorthand compactor is turned off.
* Inherit compacting is gone (but will be back).
* Some multivalue background tests are failing.
* There's no way to turn off compacting (possible performance regression).
--- /dev/null
+var wrapSingle = require('./wrap-for-optimizing').single;
+var validator = require('./validator');
+
+var Splitter = require('../utils/splitter');
+
+function _colorFilter(value) {
+ return value[0] == 'invert' || validator.isValidColor(value[0]);
+}
+
+function _styleFilter(value) {
+ return value[0] != 'inherit' && validator.isValidStyle(value[0]);
+}
+
+function _wrapDefault(name, property, compactable) {
+ var descriptor = compactable[name];
+ if (descriptor.doubleValues && descriptor.defaultValue.length == 2)
+ return wrapSingle([[name, property.important], [descriptor.defaultValue[0]], [descriptor.defaultValue[1]]]);
+ else if (descriptor.doubleValues && descriptor.defaultValue.length == 1)
+ return wrapSingle([[name, property.important], [descriptor.defaultValue[0]]]);
+ else
+ return wrapSingle([[name, property.important], [descriptor.defaultValue]]);
+}
+
+function _widthFilter(value) {
+ return value[0] != 'inherit' && validator.isValidWidth(value[0]);
+}
+
+function background(property, compactable) {
+ var image = _wrapDefault('background-image', property, compactable);
+ var position = _wrapDefault('background-position', property, compactable);
+ var size = _wrapDefault('background-size', property, compactable);
+ var repeat = _wrapDefault('background-repeat', property, compactable);
+ var attachment = _wrapDefault('background-attachment', property, compactable);
+ var origin = _wrapDefault('background-origin', property, compactable);
+ var clip = _wrapDefault('background-clip', property, compactable);
+ var color = _wrapDefault('background-color', property, compactable);
+ var components = [image, position, size, repeat, attachment, origin, clip, color];
+ var values = property.value;
+
+ var positionSet = false;
+ var clipSet = false;
+ var originSet = false;
+ var repeatSet = false;
+
+ if (property.value.length == 1 && property.value[0][0] == 'inherit') {
+ // NOTE: 'inherit' is not a valid value for background-attachment
+ color.value = image.value = repeat.value = position.value = size.value = origin.value = clip.value = property.value;
+ return components;
+ }
+
+ for (var i = values.length - 1; i >= 0; i--) {
+ var value = values[i];
+
+ if (validator.isValidBackgroundAttachment(value[0])) {
+ attachment.value = [value];
+ } else if (validator.isValidBackgroundBox(value[0])) {
+ if (clipSet) {
+ origin.value = [value];
+ originSet = true;
+ } else {
+ clip.value = [value];
+ clipSet = true;
+ }
+ } else if (validator.isValidBackgroundRepeat(value[0])) {
+ if (repeatSet) {
+ repeat.value.unshift(value);
+ } else {
+ repeat.value = [value];
+ repeatSet = true;
+ }
+ } else if (validator.isValidBackgroundPositionPart(value[0]) || validator.isValidBackgroundSizePart(value[0])) {
+ if (i > 0) {
+ var previousValue = values[i - 1];
+
+ if (previousValue[0].indexOf('/') > 0) {
+ var twoParts = new Splitter('/').split(previousValue[0]);
+ // NOTE: we do this slicing as value may contain metadata too, like for source maps
+ size.value = [[twoParts.pop()].concat(previousValue.slice(1)), value];
+ values[i - 1] = [twoParts.pop()].concat(previousValue.slice(1));
+ } else if (i > 1 && values[i - 2] == '/') {
+ size.value = [previousValue, value];
+ i -= 2;
+ } else if (values[i - 1] == '/') {
+ size.value = [value];
+ } else {
+ if (!positionSet)
+ position.value = [];
+
+ position.value.unshift(value);
+ positionSet = true;
+ }
+ } else {
+ if (!positionSet)
+ position.value = [];
+
+ position.value.unshift(value);
+ positionSet = true;
+ }
+ } else if (validator.isValidBackgroundPositionAndSize(value[0])) {
+ var sizeValue = new Splitter('/').split(value[0]);
+ // NOTE: we do this slicing as value may contain metadata too, like for source maps
+ size.value = [[sizeValue.pop()].concat(value.slice(1))];
+ position.value = [[sizeValue.pop()].concat(value.slice(1))];
+ } else if ((color.value == compactable[color.name].defaultValue || color.value == 'none') && validator.isValidColor(value[0])) {
+ color.value = [value];
+ } else if (validator.isValidUrl(value[0]) || validator.isValidFunction(value[0])) {
+ image.value = [value];
+ }
+ }
+
+ if (clipSet && !originSet)
+ origin.value = clip.value;
+
+ return components;
+}
+
+function borderRadius(property, compactable) {
+ var values = property.value;
+ var splitAt = -1;
+
+ for (var i = 0, l = values.length; i < l; i++) {
+ if (values[i][0] == '/') {
+ splitAt = i;
+ break;
+ }
+ }
+
+ if (splitAt == -1)
+ return fourValues(property, compactable);
+
+ var target = _wrapDefault(property.name, property, compactable);
+ target.value = values.slice(0, splitAt);
+ target.components = fourValues(target, compactable);
+
+ var remainder = _wrapDefault(property.name, property, compactable);
+ remainder.value = values.slice(splitAt + 1);
+ remainder.components = fourValues(remainder, compactable);
+
+ for (var j = 0; j < 4; j++) {
+ target.components[j].value = [target.components[j].value, remainder.components[j].value];
+ }
+
+ return target.components;
+}
+
+function fourValues(property, compactable) {
+ var componentNames = compactable[property.name].components;
+ var components = [];
+ var value = property.value;
+
+ if (value.length < 1)
+ return [];
+
+ if (value.length < 2)
+ value[1] = value[0];
+ if (value.length < 3)
+ value[2] = value[0];
+ if (value.length < 4)
+ value[3] = value[1];
+
+ for (var i = componentNames.length - 1; i >= 0; i--) {
+ var component = wrapSingle([[componentNames[i], property.important]]);
+ component.value = [value[i]];
+ components.unshift(component);
+ }
+
+ return components;
+}
+
+function multipleValues(splitWith) {
+ return function (property, compactable) {
+ var splitsAt = [];
+ var values = property.value;
+ var i, j, l, m;
+
+ // find split commas
+ for (i = 0, l = values.length; i < l; i++) {
+ if (values[i][0] == ',')
+ splitsAt.push(i);
+ }
+
+ if (splitsAt.length === 0)
+ return splitWith(property, compactable);
+
+ var splitComponents = [];
+
+ // split over commas, and into components
+ for (i = 0, l = splitsAt.length; i <= l; i++) {
+ var from = i === 0 ? 0 : splitsAt[i - 1] + 1;
+ var to = i < l ? splitsAt[i] : values.length;
+
+ var _property = _wrapDefault(property.name, property, compactable);
+ _property.value = values.slice(from, to);
+
+ splitComponents.push(splitWith(_property, compactable));
+ }
+
+ var components = splitComponents[0];
+
+ // group component values from each split
+ for (i = 0, l = components.length; i < l; i++) {
+ components[i].value = [components[i].value];
+ components[i].multiplex = true;
+
+ for (j = 1, m = splitComponents.length; j < m; j++) {
+ components[i].value.push(splitComponents[j][i].value);
+ }
+ }
+
+ return components;
+ };
+}
+
+function listStyle(property, compactable) {
+ var type = _wrapDefault('list-style-type', property, compactable);
+ var position = _wrapDefault('list-style-position', property, compactable);
+ var image = _wrapDefault('list-style-image', property, compactable);
+ var components = [type, position, image];
+
+ if (property.value.length == 1 && property.value[0][0] == 'inherit') {
+ type.value = position.value = image.value = [property.value[0]];
+ return components;
+ }
+
+ var values = property.value;
+ var index = 0;
+
+ if (index < values.length && validator.isValidListStyleType(values[index][0]))
+ type.value = [values[index++]];
+ if (index < values.length && validator.isValidListStylePosition(values[index][0]))
+ position.value = [values[index++]];
+ if (index < values.length)
+ image.value = [values[index]];
+
+ return components;
+}
+
+function widthStyleColor(property, compactable) {
+ var descriptor = compactable[property.name];
+ var components = [
+ _wrapDefault(descriptor.components[0], property, compactable),
+ _wrapDefault(descriptor.components[1], property, compactable),
+ _wrapDefault(descriptor.components[2], property, compactable)
+ ];
+ var color, style, width;
+
+ for (var i = 0; i < 3; i++) {
+ var component = components[i];
+
+ if (component.name.indexOf('color') > 0)
+ color = component;
+ else if (component.name.indexOf('style') > 0)
+ style = component;
+ else
+ width = component;
+ }
+
+ if ((property.value.length == 1 && property.value[0][0] == 'inherit') ||
+ (property.value.length == 3 && property.value[0][0] == 'inherit' && property.value[1][0] == 'inherit' && property.value[2][0] == 'inherit')) {
+ color.value = style.value = width.value = [property.value[0]];
+ return components;
+ }
+
+ var values = property.value.slice(0);
+ var match, matches;
+
+ // NOTE: usually users don't follow the required order of parts in this shorthand,
+ // so we'll try to parse it caring as little about order as possible
+
+ if (values.length > 0) {
+ matches = values.filter(_widthFilter);
+ match = matches.length > 1 && matches[0] == 'none' ? matches[1] : matches[0];
+ if (match) {
+ width.value = [match];
+ values.splice(values.indexOf(match), 1);
+ }
+ }
+
+ if (values.length > 0) {
+ match = values.filter(_styleFilter)[0];
+ if (match) {
+ style.value = [match];
+ values.splice(values.indexOf(match), 1);
+ }
+ }
+
+ if (values.length > 0) {
+ match = values.filter(_colorFilter)[0];
+ if (match) {
+ color.value = [match];
+ values.splice(values.indexOf(match), 1);
+ }
+ }
+
+ return components;
+}
+
+module.exports = {
+ background: background,
+ border: widthStyleColor,
+ borderRadius: borderRadius,
+ fourValues: fourValues,
+ listStyle: listStyle,
+ multipleValues: multipleValues,
+ outline: widthStyleColor
+};
--- /dev/null
+var validator = require('./validator');
+
+// Functions that decide what value can override what.
+// The main purpose is to disallow removing CSS fallbacks.
+// A separate implementation is needed for every different kind of CSS property.
+// -----
+// The generic idea is that properties that have wider browser support are 'more understandable'
+// than others and that 'less understandable' values can't override more understandable ones.
+
+function _valueOf(property) {
+ return Array.isArray(property.value[0]) ? property.value[0][0] : property.value[0];
+}
+
+// Use when two tokens of the same property can always be merged
+function always() {
+ return true;
+}
+
+function backgroundImage(property1, property2) {
+ // The idea here is that 'more understandable' values override 'less understandable' values, but not vice versa
+ // Understandability: (none | url | inherit) > (same function) > (same value)
+
+ // (none | url)
+ var image1 = _valueOf(property1);
+ var image2 = _valueOf(property2);
+
+ if (image2 == 'none' || image2 == 'inherit' || validator.isValidUrl(image2))
+ return true;
+ if (image1 == 'none' || image1 == 'inherit' || validator.isValidUrl(image1))
+ return false;
+
+ // Functions with the same name can override each other; same values can override each other
+ return sameFunctionOrValue(property1, property2);
+}
+
+function border(property1, property2) {
+ return color(property1.components[2], property2.components[2]);
+}
+
+// Use for color properties (color, background-color, border-color, etc.)
+function color(property1, property2) {
+ // The idea here is that 'more understandable' values override 'less understandable' values, but not vice versa
+ // Understandability: (hex | named) > (rgba | hsla) > (same function name) > anything else
+ // NOTE: at this point rgb and hsl are replaced by hex values by clean-css
+
+ var color1 = _valueOf(property1);
+ var color2 = _valueOf(property2);
+
+ // (hex | named)
+ if (validator.isValidNamedColor(color2) || validator.isValidHexColor(color2))
+ return true;
+ if (validator.isValidNamedColor(color1) || validator.isValidHexColor(color1))
+ return false;
+
+ // (rgba|hsla)
+ if (validator.isValidRgbaColor(color2) || validator.isValidHslaColor(color2))
+ return true;
+ if (validator.isValidRgbaColor(color1) || validator.isValidHslaColor(color1))
+ return false;
+
+ // Functions with the same name can override each other; same values can override each other
+ return sameFunctionOrValue(property1, property2);
+}
+
+function twoOptionalFunctions(property1, property2) {
+ var value1 = _valueOf(property1);
+ var value2 = _valueOf(property2);
+
+ return !(validator.isValidFunction(value1) ^ validator.isValidFunction(value2));
+}
+
+function sameValue(property1, property2) {
+ var value1 = _valueOf(property1);
+ var value2 = _valueOf(property2);
+
+ return value1 === value2;
+}
+
+function sameFunctionOrValue(property1, property2) {
+ var value1 = _valueOf(property1);
+ var value2 = _valueOf(property2);
+
+ // Functions with the same name can override each other
+ if (validator.areSameFunction(value1, value2))
+ return true;
+
+ return value1 === value2;
+}
+
+// Use for properties containing CSS units (margin-top, padding-left, etc.)
+function unit(property1, property2) {
+ // The idea here is that 'more understandable' values override 'less understandable' values, but not vice versa
+ // Understandability: (unit without functions) > (same functions | standard functions) > anything else
+ // NOTE: there is no point in having different vendor-specific functions override each other or standard functions,
+ // or having standard functions override vendor-specific functions, but standard functions can override each other
+ // NOTE: vendor-specific property values are not taken into consideration here at the moment
+ var value1 = _valueOf(property1);
+ var value2 = _valueOf(property2);
+
+ if (validator.isValidAndCompatibleUnitWithoutFunction(value1) && !validator.isValidAndCompatibleUnitWithoutFunction(value2))
+ return false;
+
+ if (validator.isValidUnitWithoutFunction(value2))
+ return true;
+ if (validator.isValidUnitWithoutFunction(value1))
+ return false;
+
+ // Standard non-vendor-prefixed functions can override each other
+ if (validator.isValidFunctionWithoutVendorPrefix(value2) && validator.isValidFunctionWithoutVendorPrefix(value1)) {
+ return true;
+ }
+
+ // Functions with the same name can override each other; same values can override each other
+ return sameFunctionOrValue(property1, property2);
+}
+
+module.exports = {
+ always: always,
+ backgroundImage: backgroundImage,
+ border: border,
+ color: color,
+ sameValue: sameValue,
+ sameFunctionOrValue: sameFunctionOrValue,
+ twoOptionalFunctions: twoOptionalFunctions,
+ unit: unit
+};
--- /dev/null
+// Contains the interpretation of CSS properties, as used by the property optimizer
+
+var breakUp = require('./break-up');
+var canOverride = require('./can-override');
+var restore = require('./restore');
+
+// Properties to process
+// Extend this object in order to add support for more properties in the optimizer.
+//
+// Each key in this object represents a CSS property and should be an object.
+// Such an object contains properties that describe how the represented CSS property should be handled.
+// Possible options:
+//
+// * components: array (Only specify for shorthand properties.)
+// Contains the names of the granular properties this shorthand compacts.
+//
+// * canOverride: function (Default is canOverride.sameValue - meaning that they'll only be merged if they have the same value.)
+// Returns whether two tokens of this property can be merged with each other.
+// This property has no meaning for shorthands.
+//
+// * defaultValue: string
+// Specifies the default value of the property according to the CSS standard.
+// For shorthand, this is used when every component is set to its default value, therefore it should be the shortest possible default value of all the components.
+//
+// * shortestValue: string
+// Specifies the shortest possible value the property can possibly have.
+// (Falls back to defaultValue if unspecified.)
+//
+// * breakUp: function (Only specify for shorthand properties.)
+// Breaks the shorthand up to its components.
+//
+// * restore: function (Only specify for shorthand properties.)
+// Puts the shorthand together from its components.
+//
+var compactable = {
+ 'color': {
+ canOverride: canOverride.color,
+ defaultValue: 'transparent',
+ shortestValue: 'red'
+ },
+ 'background': {
+ components: [
+ 'background-image',
+ 'background-position',
+ 'background-size',
+ 'background-repeat',
+ 'background-attachment',
+ 'background-origin',
+ 'background-clip',
+ 'background-color'
+ ],
+ breakUp: breakUp.multipleValues(breakUp.background),
+ defaultValue: '0 0',
+ restore: restore.multipleValues(restore.background),
+ shortestValue: '0',
+ shorthand: true
+ },
+ 'background-clip': {
+ canOverride: canOverride.always,
+ defaultValue: 'border-box',
+ shortestValue: 'border-box'
+ },
+ 'background-color': {
+ canOverride: canOverride.color,
+ defaultValue: 'transparent',
+ multiplexLastOnly: true,
+ nonMergeableValue: 'none',
+ shortestValue: 'red'
+ },
+ 'background-image': {
+ canOverride: canOverride.backgroundImage,
+ defaultValue: 'none'
+ },
+ 'background-origin': {
+ canOverride: canOverride.always,
+ defaultValue: 'padding-box',
+ shortestValue: 'border-box'
+ },
+ 'background-repeat': {
+ canOverride: canOverride.always,
+ defaultValue: ['repeat'],
+ doubleValues: true
+ },
+ 'background-position': {
+ canOverride: canOverride.always,
+ defaultValue: ['0', '0'],
+ doubleValues: true,
+ shortestValue: '0'
+ },
+ 'background-size': {
+ canOverride: canOverride.always,
+ defaultValue: ['auto'],
+ doubleValues: true,
+ shortestValue: '0 0'
+ },
+ 'background-attachment': {
+ canOverride: canOverride.always,
+ defaultValue: 'scroll'
+ },
+ 'border': {
+ breakUp: breakUp.border,
+ canOverride: canOverride.border,
+ components: [
+ 'border-width',
+ 'border-style',
+ 'border-color'
+ ],
+ defaultValue: 'none',
+ restore: restore.withoutDefaults,
+ shorthand: true
+ },
+ 'border-color': {
+ canOverride: canOverride.color,
+ defaultValue: 'none',
+ shorthand: true
+ },
+ 'border-style': {
+ canOverride: canOverride.always,
+ defaultValue: 'none',
+ shorthand: true
+ },
+ 'border-width': {
+ canOverride: canOverride.unit,
+ defaultValue: 'medium',
+ shortestValue: '0',
+ shorthand: true
+ },
+ 'list-style': {
+ components: [
+ 'list-style-type',
+ 'list-style-position',
+ 'list-style-image'
+ ],
+ canOverride: canOverride.always,
+ breakUp: breakUp.listStyle,
+ restore: restore.withoutDefaults,
+ defaultValue: 'outside', // can't use 'disc' because that'd override default 'decimal' for <ol>
+ shortestValue: 'none',
+ shorthand: true
+ },
+ 'list-style-type' : {
+ canOverride: canOverride.always,
+ defaultValue: '__hack',
+ // NOTE: we can't tell the real default value here, it's 'disc' for <ul> and 'decimal' for <ol>
+ // -- this is a hack, but it doesn't matter because this value will be either overridden or it will disappear at the final step anyway
+ shortestValue: 'none'
+ },
+ 'list-style-position' : {
+ canOverride: canOverride.always,
+ defaultValue: 'outside',
+ shortestValue: 'inside'
+ },
+ 'list-style-image' : {
+ canOverride: canOverride.always,
+ defaultValue: 'none'
+ },
+ 'outline': {
+ components: [
+ 'outline-color',
+ 'outline-style',
+ 'outline-width'
+ ],
+ breakUp: breakUp.outline,
+ restore: restore.withoutDefaults,
+ defaultValue: '0',
+ shorthand: true
+ },
+ 'outline-color': {
+ canOverride: canOverride.color,
+ defaultValue: 'invert',
+ shortestValue: 'red'
+ },
+ 'outline-style': {
+ canOverride: canOverride.always,
+ defaultValue: 'none'
+ },
+ 'outline-width': {
+ canOverride: canOverride.unit,
+ defaultValue: 'medium',
+ shortestValue: '0'
+ },
+ '-moz-transform': {
+ canOverride: canOverride.sameFunctionOrValue
+ },
+ '-ms-transform': {
+ canOverride: canOverride.sameFunctionOrValue
+ },
+ '-webkit-transform': {
+ canOverride: canOverride.sameFunctionOrValue
+ },
+ 'transform': {
+ canOverride: canOverride.sameFunctionOrValue
+ }
+};
+
+var addFourValueShorthand = function (prop, components, options) {
+ options = options || {};
+ compactable[prop] = {
+ components: components,
+ breakUp: options.breakUp || breakUp.fourValues,
+ defaultValue: options.defaultValue || '0',
+ restore: options.restore || restore.fourValues,
+ shortestValue: options.shortestValue,
+ shorthand: true
+ };
+ for (var i = 0; i < components.length; i++) {
+ compactable[components[i]] = {
+ breakUp: options.breakUp || breakUp.fourValues,
+ canOverride: options.canOverride || canOverride.unit,
+ defaultValue: options.defaultValue || '0',
+ shortestValue: options.shortestValue
+ };
+ }
+};
+
+['', '-moz-', '-o-', '-webkit-'].forEach(function (prefix) {
+ addFourValueShorthand(prefix + 'border-radius', [
+ prefix + 'border-top-left-radius',
+ prefix + 'border-top-right-radius',
+ prefix + 'border-bottom-right-radius',
+ prefix + 'border-bottom-left-radius'
+ ], {
+ breakUp: breakUp.borderRadius,
+ restore: restore.borderRadius
+ });
+});
+
+addFourValueShorthand('border-color', [
+ 'border-top-color',
+ 'border-right-color',
+ 'border-bottom-color',
+ 'border-left-color'
+], {
+ breakUp: breakUp.fourValues,
+ canOverride: canOverride.color,
+ defaultValue: 'none',
+ shortestValue: 'red'
+});
+
+addFourValueShorthand('border-style', [
+ 'border-top-style',
+ 'border-right-style',
+ 'border-bottom-style',
+ 'border-left-style'
+], {
+ breakUp: breakUp.fourValues,
+ canOverride: canOverride.always,
+ defaultValue: 'none'
+});
+
+addFourValueShorthand('border-width', [
+ 'border-top-width',
+ 'border-right-width',
+ 'border-bottom-width',
+ 'border-left-width'
+], {
+ defaultValue: 'medium',
+ shortestValue: '0'
+});
+
+addFourValueShorthand('padding', [
+ 'padding-top',
+ 'padding-right',
+ 'padding-bottom',
+ 'padding-left'
+]);
+
+addFourValueShorthand('margin', [
+ 'margin-top',
+ 'margin-right',
+ 'margin-bottom',
+ 'margin-left'
+]);
+
+module.exports = compactable;
+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 removeUnused = require('./remove-unused');
+var restoreShorthands = require('./restore-shorthands');
+
+var shorthands = {
+ 'animation-delay': ['animation'],
+ 'animation-direction': ['animation'],
+ 'animation-duration': ['animation'],
+ 'animation-fill-mode': ['animation'],
+ 'animation-iteration-count': ['animation'],
+ 'animation-name': ['animation'],
+ 'animation-play-state': ['animation'],
+ 'animation-timing-function': ['animation'],
+ '-moz-animation-delay': ['-moz-animation'],
+ '-moz-animation-direction': ['-moz-animation'],
+ '-moz-animation-duration': ['-moz-animation'],
+ '-moz-animation-fill-mode': ['-moz-animation'],
+ '-moz-animation-iteration-count': ['-moz-animation'],
+ '-moz-animation-name': ['-moz-animation'],
+ '-moz-animation-play-state': ['-moz-animation'],
+ '-moz-animation-timing-function': ['-moz-animation'],
+ '-o-animation-delay': ['-o-animation'],
+ '-o-animation-direction': ['-o-animation'],
+ '-o-animation-duration': ['-o-animation'],
+ '-o-animation-fill-mode': ['-o-animation'],
+ '-o-animation-iteration-count': ['-o-animation'],
+ '-o-animation-name': ['-o-animation'],
+ '-o-animation-play-state': ['-o-animation'],
+ '-o-animation-timing-function': ['-o-animation'],
+ '-webkit-animation-delay': ['-webkit-animation'],
+ '-webkit-animation-direction': ['-webkit-animation'],
+ '-webkit-animation-duration': ['-webkit-animation'],
+ '-webkit-animation-fill-mode': ['-webkit-animation'],
+ '-webkit-animation-iteration-count': ['-webkit-animation'],
+ '-webkit-animation-name': ['-webkit-animation'],
+ '-webkit-animation-play-state': ['-webkit-animation'],
+ '-webkit-animation-timing-function': ['-webkit-animation'],
+ 'border-color': ['border'],
+ 'border-style': ['border'],
+ 'border-width': ['border'],
+ 'border-bottom': ['border'],
+ 'border-bottom-color': ['border-bottom', 'border-color', 'border'],
+ 'border-bottom-style': ['border-bottom', 'border-style', 'border'],
+ 'border-bottom-width': ['border-bottom', 'border-width', 'border'],
+ 'border-left': ['border'],
+ 'border-left-color': ['border-left', 'border-color', 'border'],
+ 'border-left-style': ['border-left', 'border-style', 'border'],
+ 'border-left-width': ['border-left', 'border-width', 'border'],
+ 'border-right': ['border'],
+ 'border-right-color': ['border-right', 'border-color', 'border'],
+ 'border-right-style': ['border-right', 'border-style', 'border'],
+ 'border-right-width': ['border-right', 'border-width', 'border'],
+ 'border-top': ['border'],
+ 'border-top-color': ['border-top', 'border-color', 'border'],
+ 'border-top-style': ['border-top', 'border-style', 'border'],
+ 'border-top-width': ['border-top', 'border-width', 'border'],
+ 'font-family': ['font'],
+ 'font-size': ['font'],
+ 'font-style': ['font'],
+ 'font-variant': ['font'],
+ 'font-weight': ['font'],
+ 'transition-delay': ['transition'],
+ 'transition-duration': ['transition'],
+ 'transition-property': ['transition'],
+ 'transition-timing-function': ['transition'],
+ '-moz-transition-delay': ['-moz-transition'],
+ '-moz-transition-duration': ['-moz-transition'],
+ '-moz-transition-property': ['-moz-transition'],
+ '-moz-transition-timing-function': ['-moz-transition'],
+ '-o-transition-delay': ['-o-transition'],
+ '-o-transition-duration': ['-o-transition'],
+ '-o-transition-property': ['-o-transition'],
+ '-o-transition-timing-function': ['-o-transition'],
+ '-webkit-transition-delay': ['-webkit-transition'],
+ '-webkit-transition-duration': ['-webkit-transition'],
+ '-webkit-transition-property': ['-webkit-transition'],
+ '-webkit-transition-timing-function': ['-webkit-transition']
+};
-var processableInfo = require('./processable');
-var overrideCompactor = require('./override-compactor');
-var shorthandCompactor = require('./shorthand-compactor');
-
-module.exports = function Optimizer(options, context) {
- var overridable = {
- 'animation-delay': ['animation'],
- 'animation-direction': ['animation'],
- 'animation-duration': ['animation'],
- 'animation-fill-mode': ['animation'],
- 'animation-iteration-count': ['animation'],
- 'animation-name': ['animation'],
- 'animation-play-state': ['animation'],
- 'animation-timing-function': ['animation'],
- '-moz-animation-delay': ['-moz-animation'],
- '-moz-animation-direction': ['-moz-animation'],
- '-moz-animation-duration': ['-moz-animation'],
- '-moz-animation-fill-mode': ['-moz-animation'],
- '-moz-animation-iteration-count': ['-moz-animation'],
- '-moz-animation-name': ['-moz-animation'],
- '-moz-animation-play-state': ['-moz-animation'],
- '-moz-animation-timing-function': ['-moz-animation'],
- '-o-animation-delay': ['-o-animation'],
- '-o-animation-direction': ['-o-animation'],
- '-o-animation-duration': ['-o-animation'],
- '-o-animation-fill-mode': ['-o-animation'],
- '-o-animation-iteration-count': ['-o-animation'],
- '-o-animation-name': ['-o-animation'],
- '-o-animation-play-state': ['-o-animation'],
- '-o-animation-timing-function': ['-o-animation'],
- '-webkit-animation-delay': ['-webkit-animation'],
- '-webkit-animation-direction': ['-webkit-animation'],
- '-webkit-animation-duration': ['-webkit-animation'],
- '-webkit-animation-fill-mode': ['-webkit-animation'],
- '-webkit-animation-iteration-count': ['-webkit-animation'],
- '-webkit-animation-name': ['-webkit-animation'],
- '-webkit-animation-play-state': ['-webkit-animation'],
- '-webkit-animation-timing-function': ['-webkit-animation'],
- 'background-clip': ['background'],
- 'background-origin': ['background'],
- 'border-color': ['border'],
- 'border-style': ['border'],
- 'border-width': ['border'],
- 'border-bottom': ['border'],
- 'border-bottom-color': ['border-bottom', 'border-color', 'border'],
- 'border-bottom-style': ['border-bottom', 'border-style', 'border'],
- 'border-bottom-width': ['border-bottom', 'border-width', 'border'],
- 'border-left': ['border'],
- 'border-left-color': ['border-left', 'border-color', 'border'],
- 'border-left-style': ['border-left', 'border-style', 'border'],
- 'border-left-width': ['border-left', 'border-width', 'border'],
- 'border-right': ['border'],
- 'border-right-color': ['border-right', 'border-color', 'border'],
- 'border-right-style': ['border-right', 'border-style', 'border'],
- 'border-right-width': ['border-right', 'border-width', 'border'],
- 'border-top': ['border'],
- 'border-top-color': ['border-top', 'border-color', 'border'],
- 'border-top-style': ['border-top', 'border-style', 'border'],
- 'border-top-width': ['border-top', 'border-width', 'border'],
- 'font-family': ['font'],
- 'font-size': ['font'],
- 'font-style': ['font'],
- 'font-variant': ['font'],
- 'font-weight': ['font'],
- 'margin-bottom': ['margin'],
- 'margin-left': ['margin'],
- 'margin-right': ['margin'],
- 'margin-top': ['margin'],
- 'padding-bottom': ['padding'],
- 'padding-left': ['padding'],
- 'padding-right': ['padding'],
- 'padding-top': ['padding'],
- 'transition-delay': ['transition'],
- 'transition-duration': ['transition'],
- 'transition-property': ['transition'],
- 'transition-timing-function': ['transition'],
- '-moz-transition-delay': ['-moz-transition'],
- '-moz-transition-duration': ['-moz-transition'],
- '-moz-transition-property': ['-moz-transition'],
- '-moz-transition-timing-function': ['-moz-transition'],
- '-o-transition-delay': ['-o-transition'],
- '-o-transition-duration': ['-o-transition'],
- '-o-transition-property': ['-o-transition'],
- '-o-transition-timing-function': ['-o-transition'],
- '-webkit-transition-delay': ['-webkit-transition'],
- '-webkit-transition-duration': ['-webkit-transition'],
- '-webkit-transition-property': ['-webkit-transition'],
- '-webkit-transition-timing-function': ['-webkit-transition']
- };
-
- var compatibility = options.compatibility;
- var aggressiveMerging = options.aggressiveMerging;
- var shorthandCompacting = options.shorthandCompacting;
-
- var IE_BACKSLASH_HACK = '\\9';
- var processable = processableInfo.processable(compatibility);
+function _optimize(properties, mergeAdjacent, aggressiveMerging) {
+ var overrideMapping = {};
+ var lastName = null;
+ var j;
- var overrides = {};
- for (var granular in overridable) {
- for (var i = 0; i < overridable[granular].length; i++) {
- var coarse = overridable[granular][i];
- var list = overrides[coarse];
+ function mergeablePosition(position) {
+ if (mergeAdjacent === false || mergeAdjacent === true)
+ return mergeAdjacent;
- if (list)
- list.push(granular);
- else
- overrides[coarse] = [granular];
- }
+ return mergeAdjacent.indexOf(position) > -1;
}
- var tokenize = function(properties, selector) {
- var tokenized = [];
-
- for (var i = 0, l = properties.length; i < l; i++) {
- var property = properties[i];
- var firstColon = property[0].indexOf(':');
- var name = property[0].substring(0, firstColon);
- var value = property[0].substring(firstColon + 1);
- if (value === '') {
- context.warnings.push('Empty property \'' + name + '\' inside \'' + selector.join(',') + '\' selector. Ignoring.');
- continue;
- }
-
- tokenized.push([
- name,
- value,
- value.indexOf('!important') > -1,
- property[0].indexOf(IE_BACKSLASH_HACK, firstColon + 1) === property[0].length - IE_BACKSLASH_HACK.length,
- property.slice(1)
- ]);
- }
-
- return tokenized;
- };
-
- var optimize = function(properties, allowAdjacent) {
- var merged = [];
- var names = [];
- var lastName = null;
- var rescanTrigger = {};
-
- var removeOverridenBy = function(property, isImportant) {
- var overrided = overrides[property];
- for (var i = 0, l = overrided.length; i < l; i++) {
- for (var j = 0; j < names.length; j++) {
- if (names[j] != overrided[i] || (merged[j][2] && !isImportant))
- continue;
-
- merged.splice(j, 1);
- names.splice(j, 1);
- j -= 1;
+ propertyLoop:
+ for (var position = 0, total = properties.length; position < total; position++) {
+ var property = properties[position];
+ var _name = (property.name == '-ms-filter' || property.name == 'filter') ?
+ (lastName == 'background' || lastName == 'background-image' ? lastName : property.name) :
+ property.name;
+ var isImportant = property.important;
+ var isHack = property.hack;
+
+ if (property.unused)
+ continue;
+
+ // comment is necessary - we assume that if two properties are one after another
+ // then it is intentional way of redefining property which may not be widely supported
+ // e.g. a{display:inline-block;display:-moz-inline-box}
+ // however if `mergeablePosition` yields true then the rule does not apply
+ // (e.g merging two adjacent selectors: `a{display:block}a{display:block}`)
+ if ((aggressiveMerging && _name != lastName || mergeablePosition(position)) && _name in overrideMapping) {
+ var toOverridePositions = overrideMapping[_name];
+ var canOverride = compactable[_name] && compactable[_name].canOverride;
+ var anyRemoved = false;
+
+ for (j = toOverridePositions.length - 1; j >= 0; j--) {
+ var toRemove = properties[toOverridePositions[j]];
+ var longhandToShorthand = toRemove.name != _name;
+ var wasImportant = toRemove.important;
+ var wasHack = toRemove.hack;
+
+ if (toRemove.unused)
+ continue;
+
+ if (longhandToShorthand && wasImportant)
+ continue;
+
+ if (!wasImportant && (wasHack && !isHack || !wasHack && isHack))
+ continue;
+
+ if (!wasHack && !isHack && !longhandToShorthand && canOverride && !canOverride(toRemove, property))
+ continue;
+
+ if (wasImportant && !isImportant || wasImportant && isHack) {
+ property.unused = true;
+ continue propertyLoop;
+ } else {
+ anyRemoved = true;
+ toRemove.unused = true;
}
}
- };
-
- var mergeablePosition = function(position) {
- if (allowAdjacent === false || allowAdjacent === true)
- return allowAdjacent;
-
- return allowAdjacent.indexOf(position) > -1;
- };
-
- propertiesLoop:
- for (var i = 0, l = properties.length; i < l; i++) {
- var property = properties[i];
- var name = property[0];
- var value = property[1];
- var isImportant = property[2];
- var isIEHack = property[3];
- var _name = (name == '-ms-filter' || name == 'filter') ?
- (lastName == 'background' || lastName == 'background-image' ? lastName : name) :
- name;
- var toOverridePosition = 0;
-
- if (isIEHack && !compatibility.properties.ieSuffixHack)
- continue;
-
- // comment is necessary - we assume that if two properties are one after another
- // then it is intentional way of redefining property which may not be widely supported
- // e.g. a{display:inline-block;display:-moz-inline-box}
- // however if `mergeablePosition` yields true then the rule does not apply
- // (e.g merging two adjacent selectors: `a{display:block}a{display:block}`)
- if (aggressiveMerging && name !== '' && _name != lastName || mergeablePosition(i)) {
- while (true) {
- toOverridePosition = names.indexOf(_name, toOverridePosition);
- if (toOverridePosition == -1)
- break;
-
- var lastToken = merged[toOverridePosition];
- var wasImportant = lastToken[2];
- var wasIEHack = lastToken[3];
-
- if (wasImportant && !isImportant)
- continue propertiesLoop;
-
- if (compatibility.properties.ieSuffixHack && !wasIEHack && isIEHack)
- break;
- var _info = processable[_name];
- if (!isIEHack && !wasIEHack && _info && _info.canOverride && !_info.canOverride(properties[toOverridePosition][1], value))
- break;
-
- merged.splice(toOverridePosition, 1);
- names.splice(toOverridePosition, 1);
+ if (anyRemoved) {
+ position = -1;
+ overrideMapping = {};
+ }
+ } else {
+ overrideMapping[_name] = overrideMapping[_name] || [];
+ overrideMapping[_name].push(position);
+
+ // TODO: to be removed with
+ // certain shorthand (see values of `shorthands`) should trigger removal of
+ // longhand properties (see keys of `shorthands`)
+ var _shorthands = shorthands[_name];
+ if (_shorthands) {
+ for (j = _shorthands.length - 1; j >= 0; j--) {
+ var shorthand = _shorthands[j];
+ overrideMapping[shorthand] = overrideMapping[shorthand] || [];
+ overrideMapping[shorthand].push(position);
}
}
-
- merged.push(property);
- names.push(_name);
-
- // certain properties (see values of `overridable`) should trigger removal of
- // more granular properties (see keys of `overridable`)
- if (rescanTrigger[_name])
- removeOverridenBy(_name, isImportant);
-
- // add rescan triggers - if certain property appears later in the list a rescan needs
- // to be triggered, e.g 'border-top' triggers a rescan after 'border-top-width' and
- // 'border-top-color' as they can be removed
- for (var j = 0, list = overridable[_name] || [], m = list.length; j < m; j++)
- rescanTrigger[list[j]] = true;
-
- lastName = _name;
- }
-
- return merged;
- };
-
- var rebuild = function(properties) {
- var rebuilt = [];
- var eligibleForCompacting = false;
-
- for (var i = 0, l = properties.length; i < l; i++) {
- if (!eligibleForCompacting && processableInfo.implementedFor.test(properties[i][0]))
- eligibleForCompacting = true;
-
- // FIXME: the check should be gone with #407
- var property = !properties[i][0] && properties[i][1].indexOf('__ESCAPED_') === 0 ?
- properties[i][1] :
- properties[i][0] + ':' + properties[i][1];
- var metadata = properties[i].pop();
-
- rebuilt.push([property].concat(metadata));
}
- return {
- compactFurther: eligibleForCompacting,
- list: rebuilt
- };
- };
-
- var compact = function (input) {
- var Token = processableInfo.Token;
-
- var tokens = Token.tokenize(input);
-
- tokens = overrideCompactor.compactOverrides(tokens, processable, Token, compatibility);
- tokens = shorthandCompactor.compactShorthands(tokens, false, processable, Token);
- tokens = shorthandCompactor.compactShorthands(tokens, true, processable, Token);
-
- return Token.detokenize(tokens);
- };
+ lastName = _name;
+ }
+}
+
+function optimize(selector, properties, mergeAdjacent, options) {
+ var _properties = wrapForOptimizing(properties);
+ populateComponents(_properties);
+ _optimize(_properties, mergeAdjacent, options.aggressiveMerging);
+
+ // TODO: we removed option to manually turn compacting off
+ // may result in performance regression
+ if (options.shorthandCompacting && !options.sourceMap) {
+ compactOverrides(_properties, options.compatibility);
+ // compactShorthands(_properties, false, options.compatibility);
+ // compactShorthands(_properties, true, options.compatibility);
+ }
- return {
- process: function(selector, properties, allowAdjacent, compactProperties) {
- var tokenized = tokenize(properties, selector);
- var optimized = optimize(tokenized, allowAdjacent);
- var rebuilt = rebuild(optimized);
+ restoreShorthands(_properties);
+ removeUnused(_properties);
+}
- return shorthandCompacting && compactProperties && rebuilt.compactFurther ?
- compact(rebuilt.list) :
- rebuilt.list;
- }
- };
-};
+module.exports = optimize;
-
-// Compacts the given tokens according to their ability to override each other.
-
-var validator = require('./validator');
-
-module.exports = (function () {
- // Default override function: only allow overrides when the two values are the same
- var sameValue = function (val1, val2) {
- return val1 === val2;
+var canOverride = require('./can-override');
+var compactable = require('./compactable');
+var shallowClone = require('./shallow-clone');
+
+// Used when searching for a component that matches property
+function nameMatchFilter(to) {
+ return function (property) {
+ return to.name === property.name;
};
+}
+
+function wouldBreakCompatibility(property) {
+ for (var i = 0; i < property.components.length; i++) {
+ var component = property.components[i];
+ var descriptor = compactable[component.name];
+ var canOverride = descriptor && descriptor.canOverride || canOverride.sameValue;
+
+ var _component = shallowClone(component);
+ _component.value = [[descriptor.defaultValue]];
+
+ if (!canOverride(_component, component))
+ return true;
+ }
+
+ return false;
+}
+
+function isComponentOf(shorthand, longhand) {
+ return compactable[shorthand.name].components.indexOf(longhand.name) > -1;
+}
+
+function overrideSimple(property, by) {
+ by.unused = true;
+ property.value = by.value;
+}
+
+function overrideMultiplex(property, by) {
+ by.unused = true;
+
+ for (var i = 0; i < property.value.length; i++) {
+ property.value[i] = by.value;
+ }
+}
+
+function override(property, by) {
+ if (property.multiplex)
+ overrideMultiplex(property, by);
+ else
+ overrideSimple(property, by);
+}
+
+function overrideShorthand(property, by) {
+ by.unused = true;
+
+ for (var i = 0, l = property.components.length; i < l; i++) {
+ override(property.components[i], by.components[i]);
+ }
+}
+
+function hasInherits(property) {
+ for (var i = property.value.length - 1; i >= 0; i--) {
+ if (property.value[i] == 'inherit')
+ return true;
+ }
+
+ return false;
+}
+
+function compactOverrides(properties, compatibility) {
+ var mayOverride, right, left, component;
+ var i, j, k;
+
+ propertyLoop:
+ for (i = properties.length - 1; i >= 0; i--) {
+ right = properties[i];
+ mayOverride = (compactable[right.name] && compactable[right.name].canOverride) || canOverride.sameValue;
+
+ for (j = i - 1; j >= 0; j--) {
+ left = properties[j];
+
+ if (left.unused || right.unused)
+ continue;
+
+ if (!left.shorthand && right.shorthand && isComponentOf(right, left)) {
+ // maybe `left` can be overridden by `right` which is a shorthand?
+ // TODO: this is actually more complex, as in some cases it's better to incorporate the value, e.g.
+ // background:url(...); background-repeat:no-repeat,no-repeat;
+ // background:url(...) no-repeat,no-repeat;
+ if (!right.multiplex && left.multiplex)
+ continue;
- var compactOverrides = function (tokens, processable, Token, compatibility) {
- var result, can, token, t, i, ii, iiii, oldResult, matchingComponent;
-
- // Used when searching for a component that matches token
- var nameMatchFilter1 = function (x) {
- return x.prop === token.prop;
- };
- // Used when searching for a component that matches t
- var nameMatchFilter2 = function (x) {
- return x.prop === t.prop;
- };
-
- function willResultInShorterValue (shorthand, token) {
- var shorthandCopy = shorthand.clone();
- shorthandCopy.isDirty = true;
- shorthandCopy.isShorthand = true;
- shorthandCopy.components = [];
-
- shorthand.components.forEach(function (component) {
- var componentCopy = component.clone();
- if (component.prop == token.prop)
- componentCopy.value = token.value;
-
- shorthandCopy.components.push(componentCopy);
- });
-
- return Token.getDetokenizedLength([shorthand, token]) >= Token.getDetokenizedLength([shorthandCopy]);
- }
-
- // 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];
- 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 < oldResultLength; iii++) {
- t = oldResult[iii];
-
- // A token can't override itself (checked by reference, not by value)
- // NOTE: except when we explicitly tell it to remove itself
- if (t === token && !removeSelf) {
- result.push(t);
+ if (!right.important && left.important)
continue;
+
+ component = right.components.filter(nameMatchFilter(left))[0];
+ mayOverride = (compactable[left.name] && compactable[left.name].canOverride) || canOverride.sameValue;
+ if (mayOverride(left, component)) {
+ left.unused = true;
}
+ } else if (left.shorthand && !right.shorthand && isComponentOf(left, right)) {
+ // maybe `right` can be pulled into `left` which is a shorthand?
+ // TODO - see above
+ if (right.multiplex && !left.multiplex)
+ continue;
- // Only an important token can even try to override tokens that come after it
- if (iii > ii && !token.isImportant) {
- result.push(t);
+ if (right.important && !left.important)
continue;
+
+ component = left.components.filter(nameMatchFilter(right))[0];
+ if (mayOverride(component, right)) {
+ var disabledBackgroundSizeMerging = !compatibility.properties.backgroundSizeMerging && component.name.indexOf('background-size') > -1;
+ var nonMergeableValue = compactable[right.name].nonMergeableValue === right.value[0][0];
+
+ if (disabledBackgroundSizeMerging || nonMergeableValue)
+ continue;
+
+ if (!compatibility.properties.merging && wouldBreakCompatibility(left))
+ continue;
+
+ if (component.value[0][0] != right.value[0][0] && (hasInherits(left) || hasInherits(right)))
+ continue;
+
+ override(component, right);
+ left.dirty = true;
}
+ } else if (left.shorthand && right.shorthand && left.name == right.name) {
+ // merge if all components can be merged
- // If an important component tries to override an important shorthand and it is not yet merged
- // just make sure it is not lost
- if (iii > ii && t.isImportant && token.isImportant && t.prop != token.prop && t.isComponentOf(token)) {
- result.push(t);
- continue;
+ if (!right.important && left.important) {
+ right.unused = true;
+ continue propertyLoop;
}
- // A nonimportant token can never override an important one
- if (t.isImportant && !token.isImportant) {
- result.push(t);
+ if (right.important && !left.important) {
+ left.unused = true;
continue;
}
- if (token.isShorthand && !t.isShorthand && t.isComponentOf(token)) {
- // token (a shorthand) is trying to override t (a component)
-
- // Find the matching component in the shorthand
- matchingComponent = token.components.filter(nameMatchFilter2)[0];
- can = (processable[t.prop] && processable[t.prop].canOverride) || sameValue;
- if (!can(t.value, matchingComponent.value)) {
- // The shorthand can't override the component
- result.push(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
- matchingComponent = t.components.filter(nameMatchFilter1)[0];
- if (can(matchingComponent.value, token.value)) {
- // The component can override the matching component in the shorthand
- var disabledForToken = !compatibility.properties.backgroundSizeMerging && token.prop.indexOf('background-size') > -1 ||
- processable[token.prop].nonMergeableValue && processable[token.prop].nonMergeableValue == token.value;
-
- if (disabledForToken) {
- result.push(t);
- continue;
- }
-
- if (!compatibility.properties.merging) {
- // in compatibility mode check if shorthand in not less understandable than merged-in value
- var wouldBreakCompatibility = false;
- for (iiii = 0; iiii < t.components.length; iiii++) {
- var o = processable[t.components[iiii].prop];
- can = (o && o.canOverride) || sameValue;
-
- if (!can(o.defaultValue, t.components[iiii].value)) {
- wouldBreakCompatibility = true;
- break;
- }
- }
-
- if (wouldBreakCompatibility) {
- result.push(t);
- continue;
- }
- }
-
- if ((!token.isImportant || token.isImportant && matchingComponent.isImportant) && willResultInShorterValue(t, token)) {
- // The overriding component is non-important which means we can simply include it into the shorthand
- // NOTE: stuff that can't really be included, like inherit, is taken care of at the final step, not here
- matchingComponent.value = token.value;
- // We use the special flag to get rid of the component
- removeSelf = true;
- } 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;
- }
- t.isDirty = true;
- }
- result.push(t);
- } 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
- for (iiii = 0; iiii < t.components.length; iiii++) {
- can = (processable[t.components[iiii].prop] && processable[t.components[iiii].prop].canOverride) || sameValue;
- if (!can(t.components[iiii].value, token.components[iiii].value)) {
- result.push(t);
- break;
- }
- if (t.components[iiii].isImportant && token.components[iiii].isImportant && (validator.isValidFunction(t.components[iiii].value) ^ validator.isValidFunction(token.components[iiii].value))) {
- result.push(t);
- break;
- }
- }
- } else if (t.prop !== token.prop || !can(t.value, token.value)) {
- // in every other case, use the override mechanism
- result.push(t);
- } else if (t.isImportant && token.isImportant && (validator.isValidFunction(t.value) ^ validator.isValidFunction(token.value))) {
- result.push(t);
+ for (k = left.components.length - 1; k >= 0; k--) {
+ var leftComponent = left.components[k];
+ var rightComponent = right.components[k];
+
+ mayOverride = compactable[leftComponent.name].canOverride || canOverride.sameValue;
+ if (!mayOverride(leftComponent, rightComponent) || !canOverride.twoOptionalFunctions(leftComponent, rightComponent))
+ continue propertyLoop;
}
- }
- if (removeSelf) {
- i--;
+
+ overrideShorthand(left, right);
+ left.dirty = true;
}
}
+ }
+}
- return result;
- };
-
- return {
- compactOverrides: compactOverrides
- };
-
-})();
+module.exports = compactOverrides;
--- /dev/null
+var compactable = require('./compactable');
+
+function populateComponents(properties) {
+ for (var i = properties.length - 1; i >= 0; i--) {
+ var property = properties[i];
+ var descriptor = compactable[property.name];
+
+ if (descriptor && descriptor.shorthand) {
+ property.shorthand = true;
+ property.dirty = true;
+ property.components = descriptor.breakUp(property, compactable);
+
+ if (property.components.length > 0)
+ property.multiplex = Array.isArray(property.components[0].value[0][0]);
+ else
+ property.unused = true;
+ }
+ }
+}
+
+module.exports = populateComponents;
+++ /dev/null
-
-// Contains the interpretation of CSS properties, as used by the property optimizer
-
-module.exports = (function () {
-
- var tokenModule = require('./token');
- var validator = require('./validator');
- var Splitter = require('../utils/splitter');
-
- // Functions that decide what value can override what.
- // The main purpose is to disallow removing CSS fallbacks.
- // A separate implementation is needed for every different kind of CSS property.
- // -----
- // The generic idea is that properties that have wider browser support are 'more understandable'
- // than others and that 'less understandable' values can't override more understandable ones.
- var canOverride = {
- // Use when two tokens of the same property can always be merged
- always: function () {
- // NOTE: We could have (val1, val2) parameters here but jshint complains because we don't use them
- return true;
- },
- // Use when two tokens of the same property can only be merged if they have the same value
- sameValue: function(val1, val2) {
- return val1 === val2;
- },
- sameFunctionOrValue: function(val1, val2) {
- // Functions with the same name can override each other
- if (validator.areSameFunction(val1, val2)) {
- return true;
- }
-
- return val1 === val2;
- },
- // Use for properties containing CSS units (margin-top, padding-left, etc.)
- unit: function(val1, val2) {
- // The idea here is that 'more understandable' values override 'less understandable' values, but not vice versa
- // Understandability: (unit without functions) > (same functions | standard functions) > anything else
- // NOTE: there is no point in having different vendor-specific functions override each other or standard functions,
- // or having standard functions override vendor-specific functions, but standard functions can override each other
- // NOTE: vendor-specific property values are not taken into consideration here at the moment
- if (validator.isValidAndCompatibleUnitWithoutFunction(val1) && !validator.isValidAndCompatibleUnitWithoutFunction(val2))
- return false;
-
- if (validator.isValidUnitWithoutFunction(val2))
- return true;
- if (validator.isValidUnitWithoutFunction(val1))
- return false;
-
- // Standard non-vendor-prefixed functions can override each other
- if (validator.isValidFunctionWithoutVendorPrefix(val2) && validator.isValidFunctionWithoutVendorPrefix(val1)) {
- return true;
- }
-
- // Functions with the same name can override each other; same values can override each other
- return canOverride.sameFunctionOrValue(val1, val2);
- },
- // Use for color properties (color, background-color, border-color, etc.)
- color: function(val1, val2) {
- // The idea here is that 'more understandable' values override 'less understandable' values, but not vice versa
- // Understandability: (hex | named) > (rgba | hsla) > (same function name) > anything else
- // NOTE: at this point rgb and hsl are replaced by hex values by clean-css
-
- // (hex | named)
- if (validator.isValidNamedColor(val2) || validator.isValidHexColor(val2))
- return true;
- if (validator.isValidNamedColor(val1) || validator.isValidHexColor(val1))
- return false;
-
- // (rgba|hsla)
- if (validator.isValidRgbaColor(val2) || validator.isValidHslaColor(val2))
- return true;
- if (validator.isValidRgbaColor(val1) || validator.isValidHslaColor(val1))
- return false;
-
- // Functions with the same name can override each other; same values can override each other
- return canOverride.sameFunctionOrValue(val1, val2);
- },
- // Use for background-image
- backgroundImage: function(val1, val2) {
- // The idea here is that 'more understandable' values override 'less understandable' values, but not vice versa
- // Understandability: (none | url | inherit) > (same function) > (same value)
-
- // (none | url)
- if (val2 === 'none' || val2 === 'inherit' || validator.isValidUrl(val2))
- return true;
- if (val1 === 'none' || val1 === 'inherit' || validator.isValidUrl(val1))
- return false;
-
- // Functions with the same name can override each other; same values can override each other
- return canOverride.sameFunctionOrValue(val1, val2);
- },
- border: function(val1, val2) {
- var brokenUp1 = breakUp.border(Token.tokenizeOne([val1]));
- var brokenUp2 = breakUp.border(Token.tokenizeOne([val2]));
-
- return canOverride.color(brokenUp1[2].value, brokenUp2[2].value);
- }
- };
- canOverride = Object.freeze(canOverride);
-
- // Functions for breaking up shorthands to components
- var breakUp = {};
- breakUp.takeCareOfFourValues = function (splitfunc) {
- return function (token) {
- var descriptor = processable[token.prop];
- var result = [];
- var splitval = splitfunc(token.value);
-
- if (splitval.length === 0 || (splitval.length < descriptor.components.length && descriptor.components.length > 4)) {
- // This token is malformed and we have no idea how to fix it. So let's just keep it intact
- return [token];
- }
-
- // Fix those that we do know how to fix
- if (splitval.length < descriptor.components.length && splitval.length < 2) {
- // foo{margin:1px} -> foo{margin:1px 1px}
- splitval[1] = splitval[0];
- }
- if (splitval.length < descriptor.components.length && splitval.length < 3) {
- // foo{margin:1px 2px} -> foo{margin:1px 2px 1px}
- splitval[2] = splitval[0];
- }
- if (splitval.length < descriptor.components.length && splitval.length < 4) {
- // foo{margin:1px 2px 3px} -> foo{margin:1px 2px 3px 2px}
- splitval[3] = splitval[1];
- }
-
- // Now break it up to its components
- for (var i = 0; i < descriptor.components.length; i++) {
- var t = new Token(descriptor.components[i], splitval[i], token.isImportant);
- result.push(t);
- }
-
- return result;
- };
- };
- // Use this when you simply want to break up four values along spaces
- breakUp.fourBySpaces = breakUp.takeCareOfFourValues(function (val) {
- return new Splitter(' ').split(val).filter(function (v) { return v; });
- });
- // Breaks up a background property value
- breakUp.commaSeparatedMulitpleValues = function (splitfunc) {
- return function (token) {
- if (token.value.indexOf(',') === -1)
- return splitfunc(token);
-
- var values = new Splitter(',').split(token.value);
- var components = [];
-
- // TODO: we should be rather clonging elements than reusing them!
- for (var i = 0, l = values.length; i < l; i++) {
- token.value = values[i];
- components.push(splitfunc(token));
- }
-
- token.value = values.join(',');
-
- for (var j = 0, m = components[0].length; j < m; j++) {
- for (var k = 0, n = components.length, newValues = []; k < n; k++) {
- newValues.push(components[k][j].value);
- }
-
- components[0][j].value = newValues.join(',');
- }
-
- return components[0];
- };
- };
- breakUp.background = function (token) {
- // Default values
- var result = Token.makeDefaults(['background-image', 'background-position', 'background-size', 'background-repeat', 'background-attachment', 'background-origin', 'background-clip', 'background-color'], token.isImportant);
- var image = result[0];
- var position = result[1];
- var size = result[2];
- var repeat = result[3];
- var attachment = result[4];
- var origin = result[5];
- var clip = result[6];
- var color = result[7];
- var positionSet = false;
- var clipSet = false;
- var originSet = false;
- var repeatSet = false;
-
- // Take care of inherit
- if (token.value === 'inherit') {
- // NOTE: 'inherit' is not a valid value for background-attachment so there we'll leave the default value
- color.value = image.value = repeat.value = position.value = size.value = attachment.value = origin.value = clip.value = 'inherit';
- return result;
- }
-
- // Break the background up into parts
- var parts = new Splitter(' ').split(token.value);
- if (parts.length === 0)
- return result;
-
- // Iterate over all parts and try to fit them into positions
- for (var i = parts.length - 1; i >= 0; i--) {
- var currentPart = parts[i];
-
- if (validator.isValidBackgroundAttachment(currentPart)) {
- attachment.value = currentPart;
- } else if (validator.isValidBackgroundBox(currentPart)) {
- if (clipSet) {
- origin.value = currentPart;
- originSet = true;
- } else {
- clip.value = currentPart;
- clipSet = true;
- }
- } else if (validator.isValidBackgroundRepeat(currentPart)) {
- if (repeatSet) {
- repeat.value = currentPart + ' ' + repeat.value;
- } else {
- repeat.value = currentPart;
- repeatSet = true;
- }
- } else if (validator.isValidBackgroundPositionPart(currentPart) || validator.isValidBackgroundSizePart(currentPart)) {
- if (i > 0) {
- var previousPart = parts[i - 1];
-
- if (previousPart.indexOf('/') > 0) {
- var twoParts = new Splitter('/').split(previousPart);
- size.value = twoParts.pop() + ' ' + currentPart;
- parts[i - 1] = twoParts.pop();
- } else if (i > 1 && parts[i - 2] == '/') {
- size.value = previousPart + ' ' + currentPart;
- i -= 2;
- } else if (parts[i - 1] == '/') {
- size.value = currentPart;
- } else {
- position.value = currentPart + (positionSet ? ' ' + position.value : '');
- positionSet = true;
- }
- } else {
- position.value = currentPart + (positionSet ? ' ' + position.value : '');
- positionSet = true;
- }
- } else if (validator.isValidBackgroundPositionAndSize(currentPart)) {
- var sizeValue = new Splitter('/').split(currentPart);
- size.value = sizeValue.pop();
- position.value = sizeValue.pop();
- } else if ((color.value == processable[color.prop].defaultValue || color.value == 'none') && validator.isValidColor(currentPart)) {
- color.value = currentPart;
- } else if (validator.isValidUrl(currentPart) || validator.isValidFunction(currentPart)) {
- image.value = currentPart;
- }
- }
-
- if (clipSet && !originSet)
- origin.value = clip.value;
-
- return result;
- };
- // Breaks up a list-style property value
- breakUp.listStyle = function (token) {
- // Default values
- var result = Token.makeDefaults(['list-style-type', 'list-style-position', 'list-style-image'], token.isImportant);
- var type = result[0], position = result[1], image = result[2];
-
- if (token.value === 'inherit') {
- type.value = position.value = image.value = 'inherit';
- return result;
- }
-
- var parts = new Splitter(' ').split(token.value);
- var ci = 0;
-
- // Type
- if (ci < parts.length && validator.isValidListStyleType(parts[ci])) {
- type.value = parts[ci];
- ci++;
- }
- // Position
- if (ci < parts.length && validator.isValidListStylePosition(parts[ci])) {
- position.value = parts[ci];
- ci++;
- }
- // Image
- if (ci < parts.length) {
- image.value = parts.splice(ci, parts.length - ci + 1).join(' ');
- }
-
- return result;
- };
-
- breakUp._widthStyleColor = function(token, prefix, order) {
- // Default values
- var components = order.map(function(prop) {
- return prefix + '-' + prop;
- });
- var result = Token.makeDefaults(components, token.isImportant);
- var color = result[order.indexOf('color')];
- var style = result[order.indexOf('style')];
- var width = result[order.indexOf('width')];
-
- // Take care of inherit
- if (token.value === 'inherit' || token.value === 'inherit inherit inherit') {
- color.value = style.value = width.value = 'inherit';
- return result;
- }
-
- // NOTE: usually users don't follow the required order of parts in this shorthand,
- // so we'll try to parse it caring as little about order as possible
-
- var parts = new Splitter(' ').split(token.value), w;
-
- if (parts.length === 0) {
- return result;
- }
-
- if (parts.length >= 1) {
- // Try to find -width, excluding inherit because that can be anything
- w = parts.filter(function(p) { return p !== 'inherit' && validator.isValidOutlineWidth(p); });
- if (w.length) {
- width.value = w[0];
- parts.splice(parts.indexOf(w[0]), 1);
- }
- }
- if (parts.length >= 1) {
- // Try to find -style, excluding inherit because that can be anything
- w = parts.filter(function(p) { return p !== 'inherit' && validator.isValidOutlineStyle(p); });
- if (w.length) {
- style.value = w[0];
- parts.splice(parts.indexOf(w[0]), 1);
- }
- }
- if (parts.length >= 1) {
- // Find -color but this time can catch inherit
- w = parts.filter(function(p) { return validator.isValidOutlineColor(p); });
- if (w.length) {
- color.value = w[0];
- parts.splice(parts.indexOf(w[0]), 1);
- }
- }
-
- return result;
- };
-
- breakUp.outline = function(token) {
- return breakUp._widthStyleColor(token, 'outline', ['color', 'style', 'width']);
- };
-
- breakUp.border = function(token) {
- return breakUp._widthStyleColor(token, 'border', ['width', 'style', 'color']);
- };
-
- breakUp.borderRadius = function(token) {
- var parts = token.value.split('/');
- if (parts.length == 1)
- return breakUp.fourBySpaces(token);
-
- var horizontalPart = token.clone();
- var verticalPart = token.clone();
-
- horizontalPart.value = parts[0];
- verticalPart.value = parts[1];
-
- var horizontalBreakUp = breakUp.fourBySpaces(horizontalPart);
- var verticalBreakUp = breakUp.fourBySpaces(verticalPart);
-
- for (var i = 0; i < 4; i++) {
- horizontalBreakUp[i].value = [horizontalBreakUp[i].value, verticalBreakUp[i].value];
- }
-
- return horizontalBreakUp;
- };
-
- // Contains functions that can put together shorthands from their components
- // NOTE: correct order of tokens is assumed inside these functions!
- var putTogether = {
- // Use this for properties which have four unit values (margin, padding, etc.)
- // NOTE: optimizes to shorter forms too (that only specify 1, 2, or 3 values)
- fourUnits: function (prop, tokens, isImportant) {
- // See about irrelevant tokens
- // NOTE: This will enable some crazy optimalizations for us.
- if (tokens[0].isIrrelevant)
- tokens[0].value = tokens[2].value;
- if (tokens[2].isIrrelevant)
- tokens[2].value = tokens[0].value;
- if (tokens[1].isIrrelevant)
- tokens[1].value = tokens[3].value;
- if (tokens[3].isIrrelevant)
- tokens[3].value = tokens[1].value;
-
- if (tokens[0].isIrrelevant && tokens[2].isIrrelevant) {
- if (tokens[1].value === tokens[3].value)
- tokens[0].value = tokens[2].value = tokens[1].value;
- else
- tokens[0].value = tokens[2].value = '0';
- }
- if (tokens[1].isIrrelevant && tokens[3].isIrrelevant) {
- if (tokens[0].value === tokens[2].value)
- tokens[1].value = tokens[3].value = tokens[0].value;
- else
- tokens[1].value = tokens[3].value = '0';
- }
-
- var result = new Token(prop, tokens[0].value, isImportant);
- result.granularValues = [];
- result.granularValues[tokens[0].prop] = tokens[0].value;
- result.granularValues[tokens[1].prop] = tokens[1].value;
- result.granularValues[tokens[2].prop] = tokens[2].value;
- result.granularValues[tokens[3].prop] = tokens[3].value;
-
- // If all of them are irrelevant
- if (tokens[0].isIrrelevant && tokens[1].isIrrelevant && tokens[2].isIrrelevant && tokens[3].isIrrelevant) {
- result.value = processable[prop].shortestValue || processable[prop].defaultValue;
- return result;
- }
-
- // 1-value short form: all four components are equal
- if (tokens[0].value === tokens[1].value && tokens[0].value === tokens[2].value && tokens[0].value === tokens[3].value) {
- return result;
- }
- result.value += ' ' + tokens[1].value;
- // 2-value short form: first and third; second and fourth values are equal
- if (tokens[0].value === tokens[2].value && tokens[1].value === tokens[3].value) {
- return result;
- }
- result.value += ' ' + tokens[2].value;
- // 3-value short form: second and fourth values are equal
- if (tokens[1].value === tokens[3].value) {
- return result;
- }
- // 4-value form (none of the above optimalizations could be accomplished)
- result.value += ' ' + tokens[3].value;
- return result;
- },
- // Puts together the components by spaces and omits default values (this is the case for most shorthands)
- bySpacesOmitDefaults: function (prop, tokens, isImportant, meta) {
- var result = new Token(prop, '', isImportant);
-
- // Get irrelevant tokens
- var irrelevantTokens = tokens.filter(function (t) { return t.isIrrelevant; });
-
- // If every token is irrelevant, return shortest possible value, fallback to default value
- if (irrelevantTokens.length === tokens.length) {
- result.isIrrelevant = true;
- result.value = processable[prop].shortestValue || processable[prop].defaultValue;
- return result;
- }
-
- // This will be the value of the shorthand if all the components are default
- var valueIfAllDefault = processable[prop].defaultValue;
-
- // Go through all tokens and concatenate their values as necessary
- for (var i = 0; i < tokens.length; i++) {
- var token = tokens[i];
- var definition = processable[token.prop] && processable[token.prop];
-
- // Set granular value so that other parts of the code can use this for optimalization opportunities
- result.granularValues = result.granularValues || { };
- result.granularValues[token.prop] = token.value;
-
- // Use irrelevant tokens for optimalization opportunity
- if (token.isIrrelevant) {
- // Get shortest possible value, fallback to default value
- var tokenShortest = processable[token.prop].shortestValue || processable[token.prop].defaultValue;
- // If the shortest possible value of this token is shorter than the default value of the shorthand, use it instead
- if (tokenShortest.length < valueIfAllDefault.length) {
- valueIfAllDefault = tokenShortest;
- }
- }
-
- // merge with previous if possible
- if (definition.mergeWithPrevious && token.value === tokens[i - 1].value)
- continue;
-
- // omit irrelevant value
- if (token.isIrrelevant)
- continue;
-
- // omit default value unless mergable with previous and it wasn't default
- if (definition.defaultValue === token.value)
- if (!definition.mergeWithPrevious || tokens[i - 1].value === processable[tokens[i - 1].prop].defaultValue)
- continue;
-
- if (meta && meta.partsCount && meta.position < meta.partsCount - 1 && definition.multiValueLastOnly)
- continue;
-
- var requiresPreceeding = definition.shorthandFollows;
- if (requiresPreceeding && (tokens[i - 1].value == processable[requiresPreceeding].defaultValue)) {
- result.value += ' ' + tokens[i - 1].value;
- }
-
- result.value += (definition.prefixShorthandValueWith || ' ') + token.value;
- }
-
- result.value = result.value.trim();
- if (!result.value) {
- result.value = valueIfAllDefault;
- }
-
- return result;
- },
- commaSeparatedMulitpleValues: function (assembleFunction) {
- return function(prop, tokens, isImportant) {
- var tokenSplitLengths = tokens.map(function (token) {
- return new Splitter(',').split(token.value).length;
- });
- var partsCount = Math.max.apply(Math, tokenSplitLengths);
-
- if (partsCount == 1)
- return assembleFunction(prop, tokens, isImportant);
-
- var merged = [];
-
- for (var i = 0; i < partsCount; i++) {
- merged.push([]);
-
- for (var j = 0; j < tokens.length; j++) {
- var split = new Splitter(',').split(tokens[j].value);
- merged[i].push(split[i] || split[0]);
- }
- }
-
- var mergedValues = [];
- var firstProcessed;
- for (i = 0; i < partsCount; i++) {
- var newTokens = [];
- for (var k = 0, n = merged[i].length; k < n; k++) {
- var newToken = tokens[k].clone();
- newToken.value = merged[i][k];
- newTokens.push(newToken);
- }
-
- var meta = {
- partsCount: partsCount,
- position: i
- };
- var processed = assembleFunction(prop, newTokens, isImportant, meta);
- mergedValues.push(processed.value);
-
- if (!firstProcessed)
- firstProcessed = processed;
- }
-
- firstProcessed.value = mergedValues.join(',');
- return firstProcessed;
- };
- },
- // Handles the cases when some or all the fine-grained properties are set to inherit
- takeCareOfInherit: function (innerFunc) {
- return function (prop, tokens, isImportant, meta) {
- // Filter out the inheriting and non-inheriting tokens in one iteration
- var inheritingTokens = [];
- var nonInheritingTokens = [];
- var result2Shorthandable = [];
- var i;
- for (i = 0; i < tokens.length; i++) {
- if (tokens[i].value === 'inherit') {
- inheritingTokens.push(tokens[i]);
-
- // Indicate that this property is irrelevant and its value can safely be set to anything else
- var r2s = new Token(tokens[i].prop, tokens[i].isImportant);
- r2s.isIrrelevant = true;
- result2Shorthandable.push(r2s);
- } else {
- nonInheritingTokens.push(tokens[i]);
- result2Shorthandable.push(tokens[i]);
- }
- }
-
- if (nonInheritingTokens.length === 0) {
- // When all the tokens are 'inherit'
- return new Token(prop, 'inherit', isImportant);
- } 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);
-
- // Result 2. Shorthand every non-inherit value and then have it overridden with the inheriting ones
- var result2 = [innerFunc(prop, result2Shorthandable, isImportant, meta)].concat(inheritingTokens);
-
- // Return whichever is shorter
- var dl1 = Token.getDetokenizedLength(result1);
- var dl2 = Token.getDetokenizedLength(result2);
-
- return dl1 < dl2 ? result1 : result2;
- } else {
- // When none of tokens are 'inherit'
- return innerFunc(prop, tokens, isImportant, meta);
- }
- };
- },
- borderRadius: function (prop, tokens, isImportant) {
- var verticalTokens = [];
- var newTokens = [];
-
- for (var i = 0, l = tokens.length; i < l; i++) {
- var token = tokens[i];
- var newToken = token.clone();
- newTokens.push(newToken);
- if (!Array.isArray(token.value))
- continue;
-
- if (token.value.length > 1) {
- verticalTokens.push({
- prop: token.prop,
- value: token.value[1],
- isImportant: token.isImportant
- });
- }
-
- newToken.value = token.value[0];
- }
-
- var result = putTogether.takeCareOfInherit(putTogether.fourUnits)(prop, newTokens, isImportant);
- if (verticalTokens.length > 0) {
- var verticalResult = putTogether.takeCareOfInherit(putTogether.fourUnits)(prop, verticalTokens, isImportant);
- if (result.value != verticalResult.value)
- result.value += '/' + verticalResult.value;
- }
-
- return result;
- }
- };
-
- // Properties to process
- // Extend this object in order to add support for more properties in the optimizer.
- //
- // Each key in this object represents a CSS property and should be an object.
- // Such an object contains properties that describe how the represented CSS property should be handled.
- // Possible options:
- //
- // * components: array (Only specify for shorthand properties.)
- // Contains the names of the granular properties this shorthand compacts.
- //
- // * canOverride: function (Default is canOverride.sameValue - meaning that they'll only be merged if they have the same value.)
- // Returns whether two tokens of this property can be merged with each other.
- // This property has no meaning for shorthands.
- //
- // * defaultValue: string
- // Specifies the default value of the property according to the CSS standard.
- // For shorthand, this is used when every component is set to its default value, therefore it should be the shortest possible default value of all the components.
- //
- // * shortestValue: string
- // Specifies the shortest possible value the property can possibly have.
- // (Falls back to defaultValue if unspecified.)
- //
- // * breakUp: function (Only specify for shorthand properties.)
- // Breaks the shorthand up to its components.
- //
- // * putTogether: function (Only specify for shorthand properties.)
- // Puts the shorthand together from its components.
- //
- var processable = {
- 'color': {
- canOverride: canOverride.color,
- defaultValue: 'transparent',
- shortestValue: 'red'
- },
- // background ------------------------------------------------------------------------------
- 'background': {
- components: [
- 'background-image',
- 'background-position',
- 'background-size',
- 'background-repeat',
- 'background-attachment',
- 'background-origin',
- 'background-clip',
- 'background-color'
- ],
- breakUp: breakUp.commaSeparatedMulitpleValues(breakUp.background),
- putTogether: putTogether.commaSeparatedMulitpleValues(
- putTogether.takeCareOfInherit(putTogether.bySpacesOmitDefaults)
- ),
- defaultValue: '0 0',
- shortestValue: '0'
- },
- 'background-clip': {
- canOverride: canOverride.always,
- defaultValue: 'border-box',
- shortestValue: 'border-box',
- shorthandFollows: 'background-origin',
- mergeWithPrevious: true
- },
- 'background-color': {
- canOverride: canOverride.color,
- defaultValue: 'transparent',
- multiValueLastOnly: true,
- nonMergeableValue: 'none',
- shortestValue: 'red'
- },
- 'background-image': {
- canOverride: canOverride.backgroundImage,
- defaultValue: 'none'
- },
- 'background-origin': {
- canOverride: canOverride.always,
- defaultValue: 'padding-box',
- shortestValue: 'border-box'
- },
- 'background-repeat': {
- canOverride: canOverride.always,
- defaultValue: 'repeat'
- },
- 'background-position': {
- canOverride: canOverride.always,
- defaultValue: '0 0',
- shortestValue: '0'
- },
- 'background-size': {
- canOverride: canOverride.always,
- defaultValue: 'auto',
- shortestValue: '0 0',
- prefixShorthandValueWith: '/',
- shorthandFollows: 'background-position'
- },
- 'background-attachment': {
- canOverride: canOverride.always,
- defaultValue: 'scroll'
- },
- 'border': {
- breakUp: breakUp.border,
- canOverride: canOverride.border,
- components: [
- 'border-width',
- 'border-style',
- 'border-color'
- ],
- defaultValue: 'none',
- putTogether: putTogether.takeCareOfInherit(putTogether.bySpacesOmitDefaults)
- },
- 'border-color': {
- canOverride: canOverride.color,
- defaultValue: 'none'
- },
- 'border-style': {
- canOverride: canOverride.always,
- defaultValue: 'none'
- },
- 'border-width': {
- canOverride: canOverride.unit,
- defaultValue: 'medium',
- shortestValue: '0'
- },
- // list-style ------------------------------------------------------------------------------
- 'list-style': {
- components: [
- 'list-style-type',
- 'list-style-position',
- 'list-style-image'
- ],
- canOverride: canOverride.always,
- breakUp: breakUp.listStyle,
- putTogether: putTogether.takeCareOfInherit(putTogether.bySpacesOmitDefaults),
- defaultValue: 'outside', // can't use 'disc' because that'd override default 'decimal' for <ol>
- shortestValue: 'none'
- },
- 'list-style-type' : {
- canOverride: canOverride.always,
- shortestValue: 'none',
- defaultValue: '__hack'
- // NOTE: we can't tell the real default value here, it's 'disc' for <ul> and 'decimal' for <ol>
- // -- this is a hack, but it doesn't matter because this value will be either overridden or it will disappear at the final step anyway
- },
- 'list-style-position' : {
- canOverride: canOverride.always,
- defaultValue: 'outside',
- shortestValue: 'inside'
- },
- 'list-style-image' : {
- canOverride: canOverride.always,
- defaultValue: 'none'
- },
- // outline ------------------------------------------------------------------------------
- 'outline': {
- components: [
- 'outline-color',
- 'outline-style',
- 'outline-width'
- ],
- breakUp: breakUp.outline,
- putTogether: putTogether.takeCareOfInherit(putTogether.bySpacesOmitDefaults),
- defaultValue: '0'
- },
- 'outline-color': {
- canOverride: canOverride.color,
- defaultValue: 'invert',
- shortestValue: 'red'
- },
- 'outline-style': {
- canOverride: canOverride.always,
- defaultValue: 'none'
- },
- 'outline-width': {
- canOverride: canOverride.unit,
- defaultValue: 'medium',
- shortestValue: '0'
- },
- // transform
- '-moz-transform': {
- canOverride: canOverride.sameFunctionOrValue
- },
- '-ms-transform': {
- canOverride: canOverride.sameFunctionOrValue
- },
- '-webkit-transform': {
- canOverride: canOverride.sameFunctionOrValue
- },
- 'transform': {
- canOverride: canOverride.sameFunctionOrValue
- }
- };
-
- var addFourValueShorthand = function (prop, components, options) {
- options = options || {};
- processable[prop] = {
- components: components,
- breakUp: options.breakUp || breakUp.fourBySpaces,
- putTogether: options.putTogether || putTogether.takeCareOfInherit(putTogether.fourUnits),
- defaultValue: options.defaultValue || '0',
- shortestValue: options.shortestValue
- };
- for (var i = 0; i < components.length; i++) {
- processable[components[i]] = {
- breakUp: options.breakUp || breakUp.fourBySpaces,
- canOverride: options.canOverride || canOverride.unit,
- defaultValue: options.defaultValue || '0',
- shortestValue: options.shortestValue
- };
- }
- };
-
- ['', '-moz-', '-o-', '-webkit-'].forEach(function (prefix) {
- addFourValueShorthand(prefix + 'border-radius', [
- prefix + 'border-top-left-radius',
- prefix + 'border-top-right-radius',
- prefix + 'border-bottom-right-radius',
- prefix + 'border-bottom-left-radius'
- ], {
- breakUp: breakUp.borderRadius,
- putTogether: putTogether.borderRadius
- });
- });
-
- addFourValueShorthand('border-color', [
- 'border-top-color',
- 'border-right-color',
- 'border-bottom-color',
- 'border-left-color'
- ], {
- breakUp: breakUp.fourBySpaces,
- canOverride: canOverride.color,
- defaultValue: 'currentColor',
- shortestValue: 'red'
- });
-
- addFourValueShorthand('border-style', [
- 'border-top-style',
- 'border-right-style',
- 'border-bottom-style',
- 'border-left-style'
- ], {
- breakUp: breakUp.fourBySpaces,
- canOverride: canOverride.always,
- defaultValue: 'none'
- });
-
- addFourValueShorthand('border-width', [
- 'border-top-width',
- 'border-right-width',
- 'border-bottom-width',
- 'border-left-width'
- ], {
- defaultValue: 'medium',
- shortestValue: '0'
- });
-
- addFourValueShorthand('padding', [
- 'padding-top',
- 'padding-right',
- 'padding-bottom',
- 'padding-left'
- ]);
-
- addFourValueShorthand('margin', [
- 'margin-top',
- 'margin-right',
- 'margin-bottom',
- 'margin-left'
- ]);
-
- // Set some stuff iteratively
- for (var proc in processable) {
- if (!processable.hasOwnProperty(proc))
- continue;
-
- var currDesc = processable[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 = tokenModule.createTokenPrototype(processable);
-
- return {
- implementedFor: /background|border|color|list|margin|outline|padding|transform/,
- processable: function (compatibility) {
- // FIXME: we need a proper OO way
- validator.setCompatibility(compatibility);
-
- return processable;
- },
- Token: Token
- };
-})();
--- /dev/null
+function removeUnused(properties) {
+ for (var i = properties.length - 1; i >= 0; i--) {
+ if (properties[i].unused)
+ properties[i].all.splice(i, 1);
+ }
+}
+
+module.exports = removeUnused;
--- /dev/null
+var compactable = require('./compactable');
+
+function restoreShorthands(properties) {
+ for (var i = properties.length - 1; i >= 0; i--) {
+ var property = properties[i];
+ var descriptor = compactable[property.name];
+
+ if (descriptor && descriptor.shorthand && property.dirty && !property.unused) {
+ var restored = descriptor.restore(property, compactable);
+ var current = property.all[property.position];
+ current.splice(1, current.length - 1);
+
+ Array.prototype.push.apply(current, restored);
+ }
+ }
+}
+
+module.exports = restoreShorthands;
--- /dev/null
+var shallowClone = require('./shallow-clone');
+
+function background(property, compactable) {
+ var components = property.components;
+ var restored = [];
+ var needsOne, needsBoth;
+
+ function restoreValue(component) {
+ Array.prototype.unshift.apply(restored, component.value);
+ }
+
+ function isDefaultValue(component) {
+ var descriptor = compactable[component.name];
+ if (descriptor.doubleValues) {
+ if (descriptor.defaultValue.length == 1)
+ return component.value[0][0] == descriptor.defaultValue[0] && (component.value[1] ? component.value[1][0] == descriptor.defaultValue[0] : true);
+ else
+ return component.value[0][0] == descriptor.defaultValue[0] && (component.value[1] ? component.value[1][0] : component.value[0][0]) == descriptor.defaultValue[1];
+ } else {
+ return component.value[0][0] == descriptor.defaultValue;
+ }
+ }
+
+ for (var i = components.length - 1; i >= 0; i--) {
+ var component = components[i];
+ var isDefault = isDefaultValue(component);
+
+ if (component.name == 'background-clip') {
+ var originComponent = components[i - 1];
+ var isOriginDefault = isDefaultValue(originComponent);
+
+ needsOne = component.value[0][0] == originComponent.value[0][0];
+
+ needsBoth = !needsOne && (
+ (isOriginDefault && !isDefault) ||
+ (!isOriginDefault && !isDefault) ||
+ (!isOriginDefault && isDefault && component.value[0][0] != originComponent.value[0][0]));
+
+ if (needsOne) {
+ restoreValue(originComponent);
+ } else if (needsBoth) {
+ restoreValue(component);
+ restoreValue(originComponent);
+ }
+
+ i--;
+ } else if (component.name == 'background-size') {
+ var positionComponent = components[i - 1];
+ var isPositionDefault = isDefaultValue(positionComponent);
+
+ needsOne = !isPositionDefault && isDefault;
+
+ needsBoth = !needsOne &&
+ (isPositionDefault && !isDefault || !isPositionDefault && !isDefault);
+
+ if (needsOne) {
+ restoreValue(positionComponent);
+ } else if (needsBoth) {
+ restoreValue(component);
+ restored.unshift(['/']);
+ restoreValue(positionComponent);
+ } else if (positionComponent.value.length == 1) {
+ restoreValue(positionComponent);
+ }
+
+ i--;
+ } else {
+ if (!isDefault)
+ restoreValue(component);
+ }
+ }
+
+ if (restored.length === 0 && property.value.length == 1 && property.value[0][0] == '0')
+ restored.push(property.value[0]);
+
+ if (restored.length === 0)
+ restored.push([compactable[property.name].defaultValue]);
+
+ return restored;
+}
+
+function borderRadius(property, compactable) {
+ if (property.multiplex) {
+ var horizontal = shallowClone(property);
+ var vertical = shallowClone(property);
+
+ for (var i = 0; i < 4; i++) {
+ var component = property.components[i];
+
+ var horizontalComponent = shallowClone(property);
+ horizontalComponent.value = component.value[0];
+ horizontal.components.push(horizontalComponent);
+
+ var verticalComponent = shallowClone(property);
+ verticalComponent.value = component.value[1];
+ vertical.components.push(verticalComponent);
+ }
+
+ var horizontalValues = fourValues(horizontal, compactable);
+ var verticalValues = fourValues(vertical, compactable);
+
+ if (horizontalValues.length == verticalValues.length &&
+ horizontalValues[0][0] == verticalValues[0][0] &&
+ (horizontalValues.length > 1 ? horizontalValues[1][0] == verticalValues[1][0] : true) &&
+ (horizontalValues.length > 2 ? horizontalValues[2][0] == verticalValues[2][0] : true) &&
+ (horizontalValues.length > 3 ? horizontalValues[3][0] == verticalValues[3][0] : true)) {
+ return horizontalValues;
+ } else {
+ return horizontalValues.concat([['/']]).concat(verticalValues);
+ }
+ } else {
+ return fourValues(property, compactable);
+ }
+}
+
+function fourValues(property) {
+ var components = property.components;
+ var value1 = components[0].value[0];
+ var value2 = components[1].value[0];
+ var value3 = components[2].value[0];
+ var value4 = components[3].value[0];
+
+ if (value1[0] == value2[0] && value1[0] == value3[0] && value1[0] == value4[0]) {
+ return [value1];
+ } else if (value1[0] == value3[0] && value2[0] == value4[0]) {
+ return [value1, value2];
+ } else if (value2[0] == value4[0]) {
+ return [value1, value2, value3];
+ } else {
+ return [value1, value2, value3, value4];
+ }
+}
+
+function multipleValues(restoreWith) {
+ return function (property, compactable) {
+ if (!property.multiplex)
+ return restoreWith(property, compactable);
+
+ var repeatCounts = property.components[0].value.length;
+ var restored = [];
+
+ for (var i = 0; i < repeatCounts; i++) {
+ var _property = shallowClone(property);
+
+ for (var j = 0, m = property.components.length; j < m; j++) {
+ var _component = shallowClone(property.components[j]);
+ _component.value = property.components[j].value[i];
+ _property.components.push(_component);
+ }
+
+ var _restored = restoreWith(_property, compactable);
+ Array.prototype.push.apply(restored, _restored);
+
+ if (i < repeatCounts - 1)
+ restored.push([',']);
+ }
+
+ return restored;
+ };
+}
+
+function withoutDefaults(property, compactable) {
+ var components = property.components;
+ var restored = [];
+
+ for (var i = components.length - 1; i >= 0; i--) {
+ var component = components[i];
+ var descriptor = compactable[component.name];
+
+ if (component.value[0][0] != descriptor.defaultValue)
+ restored.unshift(component.value[0]);
+ }
+
+ if (restored.length === 0)
+ restored.push([compactable[property.name].defaultValue]);
+
+ return restored;
+}
+
+module.exports = {
+ background: background,
+ borderRadius: borderRadius,
+ fourValues: fourValues,
+ multipleValues: multipleValues,
+ withoutDefaults: withoutDefaults
+};
--- /dev/null
+var wrapSingle = require('./wrap-for-optimizing').single;
+
+function shallowClone(property) {
+ var cloned = wrapSingle([[property.name, property.important, property.hack]]);
+ cloned.unused = false;
+ return cloned;
+}
+
+module.exports = shallowClone;
-// 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.isMarkedForDeletion)
- 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.prop].componentOf;
- if (!componentsSoFar[shprop])
- initSoFar(shprop);
- if (!componentsSoFar[shprop].found[token.prop])
- componentsSoFar[shprop].found[token.prop] = [];
-
- // Add the newfound component to componentsSoFar
- componentsSoFar[shprop].found[token.prop].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.isReal !== 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.prop].canOverride(processable[rc.prop].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.isMarkedForDeletion) {
- continue;
- }
- if (!processable[token.prop]) {
- // We don't know what it is, move on
- continue;
- }
- if (processable[token.prop].isShorthand) {
- // Found an instance of a full shorthand
- // NOTE: we should NOT mix together tokens that come before and after the shorthands
-
- if (token.isImportant === isImportant || (token.isImportant && !isImportant)) {
- // Try to compact what we've found so far
- while (compactSoFar(token.prop)) { }
- // Reset
- initSoFar(token.prop, 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.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) {
- // Use importants for optimalization opportunities
- // https://github.com/jakubpawlowicz/clean-css/issues/184
- var importantTrickComp = new Token(token.prop, token.value, isImportant);
- importantTrickComp.isIrrelevant = true;
- importantTrickComp.isReal = 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.isMarkedForDeletion) {
- result.push(token);
- }
-
- token.isMarkedForDeletion = false;
- token.replaceWith = null;
- }
-
- return result;
- };
-
- return {
- compactShorthands: compactShorthands
- };
-
-})();
+// // 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
+// };
+
+// })();
+++ /dev/null
-
-// Helper for tokenizing the contents of a CSS selector block
-
-module.exports = (function() {
- var createTokenPrototype = function (processable) {
- var important = '!important';
-
- // Constructor for tokens
- function Token (prop, p2, p3) {
- this.prop = prop;
- if (typeof(p2) === 'string') {
- this.value = p2;
- this.isImportant = p3;
- }
- else {
- this.value = processable[prop].defaultValue;
- this.isImportant = p2;
- }
- }
-
- Token.prototype.prop = null;
- Token.prototype.value = null;
- Token.prototype.granularValues = null;
- Token.prototype.components = null;
- Token.prototype.position = null;
- Token.prototype.isImportant = false;
- Token.prototype.isDirty = false;
- Token.prototype.isShorthand = false;
- Token.prototype.isIrrelevant = false;
- Token.prototype.isReal = true;
- Token.prototype.isMarkedForDeletion = false;
- Token.prototype.metadata = null;
-
- // Tells if this token is a component of the other one
- Token.prototype.isComponentOf = function (other) {
- if (!processable[this.prop] || !processable[other.prop])
- return false;
- if (!(processable[other.prop].components instanceof Array) || !processable[other.prop].components.length)
- return false;
-
- return processable[other.prop].components.indexOf(this.prop) >= 0;
- };
-
- // Clones a token
- Token.prototype.clone = function (isImportant) {
- var token = new Token(this.prop, this.value, (typeof(isImportant) !== 'undefined' ? isImportant : this.isImportant));
- return token;
- };
-
- // Creates an irrelevant token with the same prop
- Token.prototype.cloneIrrelevant = function (isImportant) {
- var token = Token.makeDefault(this.prop, (typeof(isImportant) !== 'undefined' ? isImportant : this.isImportant));
- token.isIrrelevant = true;
- return token;
- };
-
- // Creates an array of property tokens with their default values
- Token.makeDefaults = function (props, important) {
- return props.map(function(prop) {
- return new Token(prop, important);
- });
- };
-
- // Parses one CSS property declaration into a token
- Token.tokenizeOne = function (fullProp) {
- // Find first colon
- var colonPos = fullProp[0].indexOf(':');
-
- if (colonPos < 0) {
- // This property doesn't have a colon, it's invalid. Let's keep it intact anyway.
- return new Token('', fullProp[0]);
- }
-
- // Parse parts of the property
- var prop = fullProp[0].substr(0, colonPos).trim();
- var value = fullProp[0].substr(colonPos + 1).trim();
- var isImportant = false;
- var importantPos = value.indexOf(important);
-
- // Check if the property is important
- if (importantPos >= 1 && importantPos === value.length - important.length) {
- value = value.substr(0, importantPos).trim();
- isImportant = true;
- }
-
- // Return result
- var result = new Token(prop, value, isImportant);
-
- // If this is a shorthand, break up its values
- // NOTE: we need to do this for all shorthands because otherwise we couldn't remove default values from them
- if (processable[prop] && processable[prop].isShorthand) {
- result.isShorthand = true;
- result.components = processable[prop].breakUp(result);
- result.isDirty = true;
- }
-
- result.metadata = fullProp.metadata;
-
- return result;
- };
-
- // Breaks up a string of CSS property declarations into tokens so that they can be handled more easily
- Token.tokenize = function (input) {
- // Split the input by semicolons and parse the parts
- var tokens = input.map(Token.tokenizeOne);
- return tokens;
- };
-
- // Transforms tokens back into CSS properties
- Token.detokenize = function (tokens) {
- // If by mistake the input is not an array, make it an array
- if (!(tokens instanceof Array)) {
- tokens = [tokens];
- }
-
- var list = [];
-
- // This step takes care of putting together the components of shorthands
- // NOTE: this is necessary to do for every shorthand, otherwise we couldn't remove their default values
- for (var i = 0; i < tokens.length; i++) {
- var t = tokens[i];
- if (t.isShorthand && t.isDirty) {
- var news = processable[t.prop].putTogether(t.prop, t.components, t.isImportant);
- Array.prototype.splice.apply(tokens, [i, 1].concat(news));
- t.isDirty = false;
- i--;
- continue;
- }
-
- // FIXME: the check should be gone with #407
- var property = t.prop === '' && t.value.indexOf('__ESCAPED_') === 0 ?
- t.value :
- t.prop + ':' + t.value + (t.isImportant ? important : '');
-
- // FIXME: to be fixed with #429
- property = property.replace(/\) ([^\+\-\/\*])/g, ')$1');
-
- list.push([property]);
- }
-
- return list;
- };
-
- // Gets the final (detokenized) length of the given tokens
- Token.getDetokenizedLength = function (tokens) {
- // If by mistake the input is not an array, make it an array
- if (!(tokens instanceof Array)) {
- tokens = [tokens];
- }
-
- var result = 0;
-
- // This step takes care of putting together the components of shorthands
- // NOTE: this is necessary to do for every shorthand, otherwise we couldn't remove their default values
- for (var i = 0; i < tokens.length; i++) {
- var t = tokens[i];
- if (t.isShorthand && t.isDirty) {
- var news = processable[t.prop].putTogether(t.prop, t.components, t.isImportant);
- Array.prototype.splice.apply(tokens, [i, 1].concat(news));
- t.isDirty = false;
- i--;
- continue;
- }
-
- if (t.prop) {
- result += t.prop.length + 1;
- }
- if (t.value) {
- result += t.value.length;
- }
- if (t.isImportant) {
- result += important.length;
- }
- }
-
- return result;
- };
-
- return Token;
- };
-
- return {
- createTokenPrototype: createTokenPrototype
- };
-
-})();
-
// Validates various CSS property values
var Splitter = require('../utils/splitter');
-module.exports = (function () {
- // Regexes used for stuff
- var widthKeywords = ['thin', 'thick', 'medium', 'inherit', 'initial'];
- var allUnits = ['px', '%', 'em', 'rem', 'in', 'cm', 'mm', 'ex', 'pt', 'pc', 'vw', 'vh', 'vmin', 'vmax'];
- var cssUnitRegexStr = '(\\-?\\.?\\d+\\.?\\d*(' + allUnits.join('|') + '|)|auto|inherit)';
- var cssCalcRegexStr = '(\\-moz\\-|\\-webkit\\-)?calc\\([^\\)]+\\)';
- var cssFunctionNoVendorRegexStr = '[A-Z]+(\\-|[A-Z]|[0-9])+\\(([A-Z]|[0-9]|\\ |\\,|\\#|\\+|\\-|\\%|\\.|\\(|\\))*\\)';
- var cssFunctionVendorRegexStr = '\\-(\\-|[A-Z]|[0-9])+\\(([A-Z]|[0-9]|\\ |\\,|\\#|\\+|\\-|\\%|\\.|\\(|\\))*\\)';
- var cssVariableRegexStr = 'var\\(\\-\\-[^\\)]+\\)';
- var cssFunctionAnyRegexStr = '(' + cssVariableRegexStr + '|' + cssFunctionNoVendorRegexStr + '|' + cssFunctionVendorRegexStr + ')';
- var cssUnitOrCalcRegexStr = '(' + cssUnitRegexStr + '|' + cssCalcRegexStr + ')';
- var cssUnitAnyRegexStr = '(none|' + widthKeywords.join('|') + '|' + cssUnitRegexStr + '|' + cssVariableRegexStr + '|' + cssFunctionNoVendorRegexStr + '|' + cssFunctionVendorRegexStr + ')';
-
- var cssFunctionNoVendorRegex = new RegExp('^' + cssFunctionNoVendorRegexStr + '$', 'i');
- var cssFunctionVendorRegex = new RegExp('^' + cssFunctionVendorRegexStr + '$', 'i');
- var cssVariableRegex = new RegExp('^' + cssVariableRegexStr + '$', 'i');
- var cssFunctionAnyRegex = new RegExp('^' + cssFunctionAnyRegexStr + '$', 'i');
- var cssUnitRegex = new RegExp('^' + cssUnitRegexStr + '$', 'i');
- var cssUnitOrCalcRegex = new RegExp('^' + cssUnitOrCalcRegexStr + '$', 'i');
- var cssUnitAnyRegex = new RegExp('^' + cssUnitAnyRegexStr + '$', 'i');
-
- var backgroundRepeatKeywords = ['repeat', 'no-repeat', 'repeat-x', 'repeat-y', 'inherit'];
- var backgroundAttachmentKeywords = ['inherit', 'scroll', 'fixed', 'local'];
- var backgroundPositionKeywords = ['center', 'top', 'bottom', 'left', 'right'];
- var backgroundSizeKeywords = ['contain', 'cover'];
- var backgroundBoxKeywords = ['border-box', 'content-box', 'padding-box'];
- 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 = ['auto', 'inherit', 'hidden', 'none', 'dotted', 'dashed', 'solid', 'double', 'groove', 'ridge', 'inset', 'outset'];
-
- var compatibleCssUnitRegex;
- var compatibleCssUnitAnyRegex;
-
- var validator = {
- // FIXME: we need a proper OO here
- setCompatibility: function (compatibility) {
- if (compatibility.units.rem) {
- compatibleCssUnitRegex = cssUnitRegex;
- compatibleCssUnitAnyRegex = cssUnitAnyRegex;
- return;
- }
-
- var validUnits = allUnits.slice(0).filter(function (value) {
- return value != 'rem';
- });
-
- var compatibleCssUnitRegexStr = '(\\-?\\.?\\d+\\.?\\d*(' + validUnits.join('|') + ')|auto|inherit)';
- compatibleCssUnitRegex = new RegExp('^' + compatibleCssUnitRegexStr + '$', 'i');
- compatibleCssUnitAnyRegex = new RegExp('^(none|' + widthKeywords.join('|') + '|' + compatibleCssUnitRegexStr + '|' + cssVariableRegexStr + '|' + cssFunctionNoVendorRegexStr + '|' + cssFunctionVendorRegexStr + ')$', 'i');
- },
-
- isValidHexColor: function (s) {
- return (s.length === 4 || s.length === 7) && s[0] === '#';
- },
- isValidRgbaColor: function (s) {
- s = s.split(' ').join('');
- return s.length > 0 && s.indexOf('rgba(') === 0 && s.indexOf(')') === s.length - 1;
- },
- isValidHslaColor: function (s) {
- s = s.split(' ').join('');
- return s.length > 0 && s.indexOf('hsla(') === 0 && s.indexOf(')') === s.length - 1;
- },
- isValidNamedColor: function (s) {
- // We don't really check if it's a valid color value, but allow any letters in it
- return s !== 'auto' && (s === 'transparent' || s === 'inherit' || /^[a-zA-Z]+$/.test(s));
- },
- isValidVariable: function(s) {
- return cssVariableRegex.test(s);
- },
- isValidColor: function (s) {
- return validator.isValidNamedColor(s) || validator.isValidHexColor(s) || validator.isValidRgbaColor(s) || validator.isValidHslaColor(s) || validator.isValidVariable(s);
- },
- isValidUrl: function (s) {
- // NOTE: at this point all URLs are replaced with placeholders by clean-css, so we check for those placeholders
- return s.indexOf('__ESCAPED_URL_CLEAN_CSS') === 0;
- },
- isValidUnit: function (s) {
- return cssUnitAnyRegex.test(s);
- },
- isValidUnitWithoutFunction: function (s) {
- return cssUnitRegex.test(s);
- },
- isValidAndCompatibleUnit: function (s) {
- return compatibleCssUnitAnyRegex.test(s);
- },
- isValidAndCompatibleUnitWithoutFunction: function (s) {
- return compatibleCssUnitRegex.test(s);
- },
- isValidFunctionWithoutVendorPrefix: function (s) {
- return cssFunctionNoVendorRegex.test(s);
- },
- isValidFunctionWithVendorPrefix: function (s) {
- return cssFunctionVendorRegex.test(s);
- },
- isValidFunction: function (s) {
- return cssFunctionAnyRegex.test(s);
- },
- isValidBackgroundRepeat: function (s) {
- return backgroundRepeatKeywords.indexOf(s) >= 0 || validator.isValidVariable(s);
- },
- isValidBackgroundAttachment: function (s) {
- return backgroundAttachmentKeywords.indexOf(s) >= 0 || validator.isValidVariable(s);
- },
- isValidBackgroundBox: function (s) {
- return backgroundBoxKeywords.indexOf(s) >= 0 || validator.isValidVariable(s);
- },
- isValidBackgroundPositionPart: function (s) {
- return backgroundPositionKeywords.indexOf(s) >= 0 || cssUnitOrCalcRegex.test(s) || validator.isValidVariable(s);
- },
- isValidBackgroundPosition: function (s) {
- if (s === 'inherit')
- return true;
-
- var parts = s.split(' ');
- for (var i = 0, l = parts.length; i < l; i++) {
- if (parts[i] === '')
- continue;
- if (validator.isValidBackgroundPositionPart(parts[i]) || validator.isValidVariable(parts[i]))
- continue;
-
- return false;
- }
-
- return true;
- },
- isValidBackgroundSizePart: function(s) {
- return backgroundSizeKeywords.indexOf(s) >= 0 || cssUnitRegex.test(s) || validator.isValidVariable(s);
- },
- isValidBackgroundPositionAndSize: function(s) {
- if (s.indexOf('/') < 0)
- return false;
-
- var twoParts = new Splitter('/').split(s);
- return validator.isValidBackgroundSizePart(twoParts.pop()) && validator.isValidBackgroundPositionPart(twoParts.pop());
- },
- isValidListStyleType: function (s) {
- return listStyleTypeKeywords.indexOf(s) >= 0 || validator.isValidVariable(s);
- },
- isValidListStylePosition: function (s) {
- return listStylePositionKeywords.indexOf(s) >= 0 || validator.isValidVariable(s);
- },
- isValidOutlineColor: function (s) {
- return s === 'invert' || validator.isValidColor(s) || validator.isValidVendorPrefixedValue(s);
- },
- isValidOutlineStyle: function (s) {
- return outlineStyleKeywords.indexOf(s) >= 0 || validator.isValidVariable(s);
- },
- isValidOutlineWidth: function (s) {
- return validator.isValidUnit(s) || widthKeywords.indexOf(s) >= 0 || validator.isValidVariable(s);
- },
- isValidVendorPrefixedValue: function (s) {
- return /^-([A-Za-z0-9]|-)*$/gi.test(s);
- },
- areSameFunction: function (a, b) {
- if (!validator.isValidFunction(a) || !validator.isValidFunction(b))
- return false;
-
- var f1name = a.substring(0, a.indexOf('('));
- var f2name = b.substring(0, b.indexOf('('));
-
- return f1name === f2name;
- }
- };
-
- return validator;
-})();
+var widthKeywords = ['thin', 'thick', 'medium', 'inherit', 'initial'];
+var allUnits = ['px', '%', 'em', 'rem', 'in', 'cm', 'mm', 'ex', 'pt', 'pc', 'vw', 'vh', 'vmin', 'vmax'];
+var cssUnitRegexStr = '(\\-?\\.?\\d+\\.?\\d*(' + allUnits.join('|') + '|)|auto|inherit)';
+var cssCalcRegexStr = '(\\-moz\\-|\\-webkit\\-)?calc\\([^\\)]+\\)';
+var cssFunctionNoVendorRegexStr = '[A-Z]+(\\-|[A-Z]|[0-9])+\\(([A-Z]|[0-9]|\\ |\\,|\\#|\\+|\\-|\\%|\\.|\\(|\\))*\\)';
+var cssFunctionVendorRegexStr = '\\-(\\-|[A-Z]|[0-9])+\\(([A-Z]|[0-9]|\\ |\\,|\\#|\\+|\\-|\\%|\\.|\\(|\\))*\\)';
+var cssVariableRegexStr = 'var\\(\\-\\-[^\\)]+\\)';
+var cssFunctionAnyRegexStr = '(' + cssVariableRegexStr + '|' + cssFunctionNoVendorRegexStr + '|' + cssFunctionVendorRegexStr + ')';
+var cssUnitOrCalcRegexStr = '(' + cssUnitRegexStr + '|' + cssCalcRegexStr + ')';
+var cssUnitAnyRegexStr = '(none|' + widthKeywords.join('|') + '|' + cssUnitRegexStr + '|' + cssVariableRegexStr + '|' + cssFunctionNoVendorRegexStr + '|' + cssFunctionVendorRegexStr + ')';
+
+var cssFunctionNoVendorRegex = new RegExp('^' + cssFunctionNoVendorRegexStr + '$', 'i');
+var cssFunctionVendorRegex = new RegExp('^' + cssFunctionVendorRegexStr + '$', 'i');
+var cssVariableRegex = new RegExp('^' + cssVariableRegexStr + '$', 'i');
+var cssFunctionAnyRegex = new RegExp('^' + cssFunctionAnyRegexStr + '$', 'i');
+var cssUnitRegex = new RegExp('^' + cssUnitRegexStr + '$', 'i');
+var cssUnitOrCalcRegex = new RegExp('^' + cssUnitOrCalcRegexStr + '$', 'i');
+var cssUnitAnyRegex = new RegExp('^' + cssUnitAnyRegexStr + '$', 'i');
+
+var backgroundRepeatKeywords = ['repeat', 'no-repeat', 'repeat-x', 'repeat-y', 'inherit'];
+var backgroundAttachmentKeywords = ['inherit', 'scroll', 'fixed', 'local'];
+var backgroundPositionKeywords = ['center', 'top', 'bottom', 'left', 'right'];
+var backgroundSizeKeywords = ['contain', 'cover'];
+var backgroundBoxKeywords = ['border-box', 'content-box', 'padding-box'];
+var styleKeywords = ['auto', 'inherit', 'hidden', 'none', 'dotted', 'dashed', 'solid', 'double', 'groove', 'ridge', 'inset', 'outset'];
+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 compatibleCssUnitRegex;
+// var compatibleCssUnitAnyRegex;
+
+// var validator = {
+// // FIXME: we need a proper OO here
+// setCompatibility: function (compatibility) {
+// if (compatibility.units.rem) {
+// compatibleCssUnitRegex = cssUnitRegex;
+// compatibleCssUnitAnyRegex = cssUnitAnyRegex;
+// return;
+// }
+
+// var validUnits = allUnits.slice(0).filter(function (value) {
+// return value != 'rem';
+// });
+
+// var compatibleCssUnitRegexStr = '(\\-?\\.?\\d+\\.?\\d*(' + validUnits.join('|') + ')|auto|inherit)';
+// compatibleCssUnitRegex = new RegExp('^' + compatibleCssUnitRegexStr + '$', 'i');
+// compatibleCssUnitAnyRegex = new RegExp('^(none|' + widthKeywords.join('|') + '|' + compatibleCssUnitRegexStr + '|' + cssVariableRegexStr + '|' + cssFunctionNoVendorRegexStr + '|' + cssFunctionVendorRegexStr + ')$', 'i');
+// }
+
+function isValidHexColor(s) {
+ return (s.length === 4 || s.length === 7) && s[0] === '#';
+}
+function isValidRgbaColor(s) {
+ s = s.split(' ').join('');
+ return s.length > 0 && s.indexOf('rgba(') === 0 && s.indexOf(')') === s.length - 1;
+}
+function isValidHslaColor(s) {
+ s = s.split(' ').join('');
+ return s.length > 0 && s.indexOf('hsla(') === 0 && s.indexOf(')') === s.length - 1;
+}
+function isValidNamedColor(s) {
+ // We don't really check if it's a valid color value, but allow any letters in it
+ return s !== 'auto' && (s === 'transparent' || s === 'inherit' || /^[a-zA-Z]+$/.test(s));
+}
+function isValidVariable(s) {
+ return cssVariableRegex.test(s);
+}
+function isValidColor(s) {
+ return isValidNamedColor(s) || isValidHexColor(s) || isValidRgbaColor(s) || isValidHslaColor(s) || isValidVariable(s) || isValidVendorPrefixedValue(s);
+}
+function isValidUrl(s) {
+ // NOTE: at this point all URLs are replaced with placeholders by clean-css, so we check for those placeholders
+ return s.indexOf('__ESCAPED_URL_CLEAN_CSS') === 0;
+}
+function isValidUnit(s) {
+ return cssUnitAnyRegex.test(s);
+}
+function isValidUnitWithoutFunction(s) {
+ return cssUnitRegex.test(s);
+}
+function isValidAndCompatibleUnit(s) {
+ return cssUnitAnyRegex.test(s);
+}
+function isValidAndCompatibleUnitWithoutFunction(s) {
+ return cssUnitRegex.test(s);
+}
+function isValidFunctionWithoutVendorPrefix(s) {
+ return cssFunctionNoVendorRegex.test(s);
+}
+function isValidFunctionWithVendorPrefix(s) {
+ return cssFunctionVendorRegex.test(s);
+}
+function isValidFunction(s) {
+ return cssFunctionAnyRegex.test(s);
+}
+function isValidBackgroundRepeat(s) {
+ return backgroundRepeatKeywords.indexOf(s) >= 0 || isValidVariable(s);
+}
+function isValidBackgroundAttachment(s) {
+ return backgroundAttachmentKeywords.indexOf(s) >= 0 || isValidVariable(s);
+}
+function isValidBackgroundBox(s) {
+ return backgroundBoxKeywords.indexOf(s) >= 0 || isValidVariable(s);
+}
+function isValidBackgroundPositionPart(s) {
+ return backgroundPositionKeywords.indexOf(s) >= 0 || cssUnitOrCalcRegex.test(s) || isValidVariable(s);
+}
+function isValidBackgroundPosition(s) {
+ if (s === 'inherit')
+ return true;
+
+ var parts = s.split(' ');
+ for (var i = 0, l = parts.length; i < l; i++) {
+ if (parts[i] === '')
+ continue;
+ if (isValidBackgroundPositionPart(parts[i]) || isValidVariable(parts[i]))
+ continue;
+
+ return false;
+ }
+
+ return true;
+}
+function isValidBackgroundSizePart(s) {
+ return backgroundSizeKeywords.indexOf(s) >= 0 || cssUnitRegex.test(s) || isValidVariable(s);
+}
+function isValidBackgroundPositionAndSize(s) {
+ if (s.indexOf('/') < 0)
+ return false;
+
+ var twoParts = new Splitter('/').split(s);
+ return isValidBackgroundSizePart(twoParts.pop()) && isValidBackgroundPositionPart(twoParts.pop());
+}
+function isValidListStyleType(s) {
+ return listStyleTypeKeywords.indexOf(s) >= 0 || isValidVariable(s);
+}
+function isValidListStylePosition(s) {
+ return listStylePositionKeywords.indexOf(s) >= 0 || isValidVariable(s);
+}
+function isValidStyle(s) {
+ return styleKeywords.indexOf(s) >= 0 || isValidVariable(s);
+}
+function isValidWidth(s) {
+ return isValidUnit(s) || widthKeywords.indexOf(s) >= 0 || isValidVariable(s);
+}
+function isValidVendorPrefixedValue(s) {
+ return /^-([A-Za-z0-9]|-)*$/gi.test(s);
+}
+function areSameFunction(a, b) {
+ if (!isValidFunction(a) || !isValidFunction(b))
+ return false;
+
+ var f1name = a.substring(0, a.indexOf('('));
+ var f2name = b.substring(0, b.indexOf('('));
+
+ return f1name === f2name;
+}
+
+module.exports = {
+ isValidHexColor: isValidHexColor,
+ isValidRgbaColor: isValidRgbaColor,
+ isValidHslaColor: isValidHslaColor,
+ isValidNamedColor: isValidNamedColor,
+ isValidVariable: isValidVariable,
+ isValidColor: isValidColor,
+ isValidUrl: isValidUrl,
+ isValidUnit: isValidUnit,
+ isValidUnitWithoutFunction: isValidUnitWithoutFunction,
+ isValidAndCompatibleUnit: isValidAndCompatibleUnit,
+ isValidAndCompatibleUnitWithoutFunction: isValidAndCompatibleUnitWithoutFunction,
+ isValidFunctionWithoutVendorPrefix: isValidFunctionWithoutVendorPrefix,
+ isValidFunctionWithVendorPrefix: isValidFunctionWithVendorPrefix,
+ isValidFunction: isValidFunction,
+ isValidBackgroundRepeat: isValidBackgroundRepeat,
+ isValidBackgroundAttachment: isValidBackgroundAttachment,
+ isValidBackgroundBox: isValidBackgroundBox,
+ isValidBackgroundPositionPart: isValidBackgroundPositionPart,
+ isValidBackgroundPosition: isValidBackgroundPosition,
+ isValidBackgroundSizePart: isValidBackgroundSizePart,
+ isValidBackgroundPositionAndSize: isValidBackgroundPositionAndSize,
+ isValidListStyleType: isValidListStyleType,
+ isValidListStylePosition: isValidListStylePosition,
+ isValidStyle: isValidStyle,
+ isValidWidth: isValidWidth,
+ isValidVendorPrefixedValue: isValidVendorPrefixedValue,
+ areSameFunction: areSameFunction
+};
--- /dev/null
+function wrapAll(properties) {
+ var wrapped = [];
+
+ for (var i = properties.length - 1; i >= 0; i--) {
+ if (typeof properties[i][0] == 'string')
+ continue;
+
+ var single = wrapSingle(properties[i]);
+ single.all = properties;
+ single.position = i;
+ wrapped.unshift(single);
+ }
+
+ return wrapped;
+}
+
+function isMultiplex(property) {
+ for (var i = 1, l = property.length; i < l; i++) {
+ if (property[i][0] == ',' || property[i][0] == '/')
+ return true;
+ }
+
+ return false;
+}
+
+function wrapSingle(property) {
+ return {
+ components: [],
+ dirty: false,
+ hack: property[0][2],
+ important: property[0][1],
+ irrelevant: false,
+ name: property[0][0],
+ multiplex: property.length > 2 ? isMultiplex(property) : false,
+ position: 0,
+ real: true,
+ shorthand: false,
+ unused: property.length < 2,
+ value: property.slice(1)
+ };
+}
+
+module.exports = {
+ all: wrapAll,
+ single: wrapSingle
+};
// IMPORTANT: Mind Token class and this code is not related!
// Properties will be tokenized in one step, see #429
+var stringifySelector = require('../utils/stringify-tokens').selector;
+var stringifyValue = require('../utils/stringify-tokens').value;
+
function extract(token) {
var properties = [];
if (token[0] == 'selector') {
- // TODO: stringifySelector
- var inSimpleSelector = !/[\.\+#>~\s]/.test(token[1].join(','));
+ var inSimpleSelector = !/[\.\+#>~\s]/.test(stringifySelector(token[1]));
for (var i = 0, l = token[2].length; i < l; i++) {
- var property = token[2][i][0];
+ var property = token[2][i];
+
if (property.indexOf('__ESCAPED') === 0)
continue;
- var splitAt = property.indexOf(':');
- var name = property.substring(0, splitAt);
- if (!name)
+ var name = token[2][i][0][0];
+ if (name.length === 0)
continue;
- var nameRoot = findNameRoot(name);
+ var value = stringifyValue(token[2][i]);
properties.push([
name,
- property.substring(splitAt + 1),
- nameRoot,
+ value,
+ findNameRoot(name),
token[2][i],
+ name + ':' + value,
token[1],
inSimpleSelector
]);
--- /dev/null
+// TODO: we should wrap it under `wrap for optimizing`
+
+var BACKSLASH_HACK = '\\9';
+var IMPORTANT = '!important';
+var STAR_HACK = '*';
+var UNDERSCORE_HACK = '_';
+
+function addOptimizationMetadata(tokens) {
+ for (var i = tokens.length - 1; i >= 0; i--) {
+ var token = tokens[i];
+
+ switch (token[0]) {
+ case 'selector':
+ addToProperties(token[2]);
+ break;
+ case 'block':
+ addOptimizationMetadata(token[2]);
+ break;
+ }
+ }
+}
+
+function addToProperties(properties) {
+ for (var i = properties.length - 1; i >= 0; i--) {
+ if (typeof properties[i] != 'string')
+ addToProperty(properties[i]);
+ }
+}
+
+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;
+
+ // 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);
+}
+
+module.exports = addOptimizationMetadata;
var Tokenizer = require('./tokenizer');
var SimpleOptimizer = require('./optimizers/simple');
var AdvancedOptimizer = require('./optimizers/advanced');
+var addOptimizationMetadata = require('./optimization-metadata');
function SelectorsOptimizer(options, context) {
this.options = options || {};
SelectorsOptimizer.prototype.process = function (data, stringifier) {
var tokens = new Tokenizer(this.context, this.options.sourceMap).toTokens(data);
+ addOptimizationMetadata(tokens);
+
new SimpleOptimizer(this.options).optimize(tokens);
if (this.options.advanced)
new AdvancedOptimizer(this.options, this.context).optimize(tokens);
-var PropertyOptimizer = require('../../properties/optimizer');
+var optimizeProperties = require('../../properties/optimizer');
var CleanUp = require('./clean-up');
-var extractProperties = require('../../properties/extractor');
-var canReorder = require('../../properties/reorderable').canReorder;
-var canReorderSingle = require('../../properties/reorderable').canReorderSingle;
+var extractProperties = require('../extractor');
+var canReorder = require('../reorderable').canReorder;
+var canReorderSingle = require('../reorderable').canReorderSingle;
var stringifyBody = require('../../utils/stringify-tokens').body;
var stringifySelector = require('../../utils/stringify-tokens').selector;
-function AdvancedOptimizer(options, context) {
+function AdvancedOptimizer(options) {
this.options = options;
- this.propertyOptimizer = new PropertyOptimizer(this.options, context);
}
function unsafeSelector(value) {
if (lastToken[0] == 'selector' && stringifySelector(token[1]) == stringifySelector(lastToken[1])) {
var joinAt = [lastToken[2].length];
- lastToken[2] = this.propertyOptimizer.process(token[1], lastToken[2].concat(token[2]), joinAt, true);
+ Array.prototype.push.apply(lastToken[2], token[2]);
+ optimizeProperties(token[1], lastToken[2], joinAt, this.options);
token[2] = [];
} else if (lastToken[0] == 'selector' && stringifyBody(token[2]) == stringifyBody(lastToken[2]) &&
!this.isSpecial(stringifySelector(token[1])) && !this.isSpecial(stringifySelector(lastToken[1]))) {
if (token[0] != 'selector')
continue;
+ if (token[2].length === 0)
+ continue;
var selectorAsString = stringifySelector(token[1]);
var isComplexAndNotSpecial = token[1].length > 1 && !this.isSpecial(selectorAsString);
joinsAt.push((joinsAt[j - 1] || 0) + bodiesAsList[j].length);
}
- var optimizedBody = this.propertyOptimizer.process(selector, bodies, joinsAt, false);
+ optimizeProperties(selector, bodies, joinsAt, this.options);
var processedCount = processedTokens.length;
- var propertyIdx = optimizedBody.length - 1;
+ var propertyIdx = bodies.length - 1;
var tokenIdx = processedCount - 1;
while (tokenIdx >= 0) {
- if ((tokenIdx === 0 || (optimizedBody[propertyIdx] && stringifyBody(bodiesAsList[tokenIdx]).indexOf(optimizedBody[propertyIdx]) > -1)) && propertyIdx > -1) {
+ if ((tokenIdx === 0 || (bodies[propertyIdx] && bodiesAsList[tokenIdx].indexOf(bodies[propertyIdx]) > -1)) && propertyIdx > -1) {
propertyIdx--;
continue;
}
- var newBody = optimizedBody.splice(propertyIdx + 1);
+ var newBody = bodies.splice(propertyIdx + 1);
options.callback(tokens[processedTokens[tokenIdx]], newBody, processedCount, tokenIdx);
tokenIdx--;
}
var joinAt = topToBottom ? [target[2].length] : [moved[2].length];
- var joinedBodies = topToBottom ? moved[2].concat(target[2]) : target[2].concat(moved[2]);
- target[2] = this.propertyOptimizer.process(target[1], joinedBodies, joinAt, true);
+ if (topToBottom) {
+ Array.prototype.push.apply(moved[2], target[2]);
+ target[2] = moved[2];
+ } else {
+ Array.prototype.push.apply(target[2], moved[2]);
+ }
+
+ optimizeProperties(target[1], target[2], joinAt, this.options);
moved[2] = [];
}
}
function shortenIfPossible(position, movedProperty) {
var name = movedProperty[0];
var value = movedProperty[1];
- var key = movedProperty[3][0];
+ var key = movedProperty[4];
var valueSize = name.length + value.length + 1;
var allSelectors = [];
var qualifiedTokens = [];
for (k = 0, m = properties.length; k < m; k++) {
var property = properties[k];
- if (mergeableToken[2][j][0] === property[3][0]) {
+ var mergeablePropertyName = mergeableToken[2][j][0][0];
+ var propertyName = property[3][0][0];
+ if (mergeablePropertyName === propertyName) {
mergeableToken[2].splice(j, 1);
break;
}
}
function dropPropertiesAt(position, movedProperty) {
- var key = movedProperty[3][0];
+ var key = movedProperty[4];
if (movableTokens[key] && movableTokens[key].length > 1)
shortenIfPossible(position, movedProperty);
for (var i = propertiesAndMergableTokens.length - 1; i >= 0; i--) {
property = propertiesAndMergableTokens[i][0];
- var fullValue = property[3][0];
+ var fullValue = property[4];
valueSize += fullValue.length + (i > 0 ? 1 : 0);
properties.push(property);
property = properties[i];
var index = movedProperties.indexOf(property);
- delete movableTokens[property[3]];
+ delete movableTokens[property[4]];
if (index > -1 && movedToBeDropped.indexOf(index) == -1)
movedToBeDropped.push(index);
if (movedToBeDropped.indexOf(k) == -1 && !canReorderSingle(property, movedProperty)) {
dropPropertiesAt(i + 1, movedProperty);
movedToBeDropped.push(k);
- delete movableTokens[movedProperty[3][0]];
+ delete movableTokens[movedProperty[4]];
}
if (!movedSameProperty)
if (!isSelector || unmovableInCurrentToken.indexOf(j) > -1)
continue;
- var key = property[3][0];
+ var key = property[4];
movableTokens[key] = movableTokens[key] || [];
movableTokens[key].push(token);
}
};
-function optimizeProperties(tokens, propertyOptimizer) {
+function recursivelyOptimizeProperties(tokens, options) {
for (var i = 0, l = tokens.length; i < l; i++) {
var token = tokens[i];
switch (token[0]) {
case 'selector':
- token[2] = propertyOptimizer.process(token[1], token[2], false, true);
+ optimizeProperties(token[1], token[2], false, options);
break;
case 'block':
- optimizeProperties(token[2], propertyOptimizer);
+ recursivelyOptimizeProperties(token[2], options);
}
}
}
}
});
- optimizeProperties(tokens, self.propertyOptimizer);
+ recursivelyOptimizeProperties(tokens, self.options);
self.removeDuplicates(tokens);
self.mergeAdjacent(tokens);
var HSL = require('../../colors/hsl');
var HexNameShortener = require('../../colors/hex-name-shortener');
-var processable = require('../../properties/processable');
-
var DEFAULT_ROUNDING_PRECISION = 2;
var CHARSET_TOKEN = '@charset';
var CHARSET_REGEXP = new RegExp('^' + CHARSET_TOKEN, 'i');
'background': function (value) {
return value == 'none' || value == 'transparent' ? '0 0' : value;
},
- 'border-*-radius': function (value) {
- if (value.indexOf('/') == -1)
- return value;
-
- var parts = value.split(/\s*\/\s*/);
- if (parts[0] == parts[1])
- return parts[0];
- else
- return parts[0] + '/' + parts[1];
- },
- 'filter': function (value) {
- if (value.indexOf('DXImageTransform') === value.lastIndexOf('DXImageTransform')) {
- value = value.replace(/progid:DXImageTransform\.Microsoft\.(Alpha|Chroma)(\W)/, function (match, filter, suffix) {
- return filter.toLowerCase() + suffix;
- });
- }
-
- return value
- .replace(/,(\S)/g, ', $1')
- .replace(/ ?= ?/g, '=');
- },
- 'font': function (value) {
- var parts = value.split(' ');
-
- if (parts[1] != 'normal' && parts[1] != 'bold' && !/^[1-9]00/.test(parts[1]))
- parts[0] = this['font-weight'](parts[0]);
-
- return parts.join(' ');
- },
'font-weight': function (value) {
if (value == 'normal')
return '400';
}
};
-function isNegative(value) {
- var parts = new Splitter(',').split(value);
- for (var i = 0, l = parts.length; i < l; i++) {
- if (parts[i][0] == '-' && parseFloat(parts[i]) < 0)
- return true;
- }
-
- return false;
+function isNegative(property, idx) {
+ return property[idx] && property[idx][0][0] == '-' && parseFloat(property[idx][0]) < 0;
}
-function zeroMinifier(_, value) {
+function zeroMinifier(name, value) {
if (value.indexOf('0') == -1)
return value;
return value.replace(/\(0deg\)/g, '(0)');
}
+function whitespaceMinifier(name, value) {
+ if (name.indexOf('filter') > -1 || value.indexOf(' ') == -1)
+ return value;
+
+ value = value.replace(/\s+/g, ' ');
+
+ if (value.indexOf('calc') > -1)
+ value = value.replace(/\) ?\/ ?/g, ')/ ');
+
+ return value
+ .replace(/\( /g, '(')
+ .replace(/ \)/g, ')')
+ .replace(/, /g, ',');
+}
+
function precisionMinifier(_, value, precisionOptions) {
if (precisionOptions.value === -1 || value.indexOf('.') === -1)
return value;
return value.replace(unitsRegexp, '$1' + '0');
}
-function multipleZerosMinifier(name, value) {
- if (value.indexOf('0 0 0 0') == -1)
- return value;
-
- if (name.indexOf('box-shadow') > -1)
- return value == '0 0 0 0' ? '0 0' : value;
-
- return value.replace(/^0 0 0 0$/, '0');
+function multipleZerosMinifier(property) {
+ if (property.length == 5 && property[1][0] === '0' && property[2][0] === '0' && property[3][0] === '0' && property[4][0] === '0') {
+ if (property[0][0].indexOf('box-shadow') > -1)
+ property.splice(3);
+ else
+ property.splice(2);
+ }
}
function colorMininifier(_, value, compatibility) {
return HexNameShortener.shorten(value);
}
-function spaceMinifier(name, value) {
- if (name == 'filter' || value.indexOf(') ') == -1 || processable.implementedFor.test(name))
- return value;
+function minifyBorderRadius(property) {
+ if (property.length == 4 && property[2][0] == '/' && property[1][0] == property[3][0])
+ property.splice(2);
+ else if (property.length == 6 && property[3][0] == '/' && property[1][0] == property[4][0] && property[2][0] == property[5][0])
+ property.splice(3);
+ else if (property.length == 8 && property[4][0] == '/' && property[1][0] == property[5][0] && property[2][0] == property[6][0] && property[3][0] == property[7][0])
+ property.splice(4);
+ else if (property.length == 10 && property[5][0] == '/' && property[1][0] == property[6][0] && property[2][0] == property[7][0] && property[3][0] == property[8][0] && property[4][0] == property[9][0])
+ property.splice(5);
+}
+
+function minifyFilter(property) {
+ if (property.length < 3) {
+ property[1][0] = property[1][0].replace(/progid:DXImageTransform\.Microsoft\.(Alpha|Chroma)(\W)/, function (match, filter, suffix) {
+ return filter.toLowerCase() + suffix;
+ });
+ }
- return value.replace(/\) ((?![\+\-] )|$)/g, ')$1');
+ property[1][0] = property[1][0]
+ .replace(/,(\S)/g, ', $1')
+ .replace(/ ?= ?/g, '=');
+}
+
+function minifyFont(property) {
+ if (property[2] && property[2][0] != 'normal' && property[2][0] != 'bold' && !/^[1-9]00/.test(property[2][0]))
+ property[1][0] = valueMinifiers['font-weight'](property[1][0]);
}
function optimizeBody(properties, options) {
- var property, firstColon, name, value, important;
+ var property, name, value, unused;
for (var i = 0, l = properties.length; i < l; i++) {
+ unused = false;
property = properties[i];
// FIXME: the check should be gone with #407
- if (property[0].indexOf('__ESCAPED_') === 0)
+ if (typeof property == 'string' && property.indexOf('__ESCAPED_') === 0)
continue;
- firstColon = property[0].indexOf(':');
- name = property[0].substring(0, firstColon);
- value = property[0].substring(firstColon + 1);
- important = false;
+ name = property[0][0];
+
+ if (property[0][2]) {
+ var isPrefixHack = name[0] == '_';
+ if (isPrefixHack && !options.compatibility.properties.iePrefixHack || !isPrefixHack && !options.compatibility.properties.ieSuffixHack)
+ unused = true;
+ }
+
+ if (name.indexOf('padding') === 0 && (isNegative(property, 1) || isNegative(property, 2) || isNegative(property, 3) || isNegative(property, 4)))
+ unused = true;
- if ((!options.compatibility.properties.iePrefixHack && (name[0] == '_' || name[0] == '*')) ||
- (name.indexOf('padding') === 0 && isNegative(value))) {
+ if (unused) {
properties.splice(i, 1);
i--;
l--;
continue;
}
- if (value.indexOf('!important') > 0 || value.indexOf('! important') > 0) {
- value = value.substring(0, value.indexOf('!')).trim();
- important = true;
- }
+ for (var j = 1, m = property.length; j < m; j++) {
+ value = property[j][0];
- if (name.indexOf('border') === 0 && name.indexOf('radius') > 0)
- value = valueMinifiers['border-*-radius'](value);
+ if (valueMinifiers[name])
+ value = valueMinifiers[name](value);
- if (valueMinifiers[name])
- value = valueMinifiers[name](value);
+ value = whitespaceMinifier(name, value);
+ value = precisionMinifier(name, value, options.precision);
+ value = zeroMinifier(name, value);
+ if (options.compatibility.properties.zeroUnits) {
+ value = zeroDegMinifier(name, value);
+ value = unitMinifier(name, value, options.unitsRegexp);
+ }
+ value = colorMininifier(name, value, options.compatibility);
- value = precisionMinifier(name, value, options.precision);
- value = zeroMinifier(name, value);
- if (options.compatibility.properties.zeroUnits) {
- value = zeroDegMinifier(name, value);
- value = unitMinifier(name, value, options.unitsRegexp);
+ property[j][0] = value;
}
- value = multipleZerosMinifier(name, value);
- value = colorMininifier(name, value, options.compatibility);
- if (!options.compatibility.properties.spaceAfterClosingBrace)
- value = spaceMinifier(name, value);
+ multipleZerosMinifier(property);
- property[0] = name + ':' + value + (important ? '!important' : '');
+ if (name.indexOf('border') === 0 && name.indexOf('radius') > 0)
+ minifyBorderRadius(property);
+ else if (name == 'filter')
+ minifyFilter(property);
+ else if (name == 'font')
+ minifyFont(property);
}
}
+// TODO: it'd be great to merge it with the other canReorder functionality
+
var FLEX_PROPERTIES = /align\-items|box\-align|box\-pack|flex|justify/;
function canReorder(left, right) {
var leftName = left[0];
var leftValue = left[1];
var leftNameRoot = left[2];
- var leftSelector = left[4];
- var leftInSimpleSelector = left[5];
+ var leftSelector = left[5];
+ var leftInSimpleSelector = left[6];
var rightName = right[0];
var rightValue = right[1];
var rightNameRoot = right[2];
- var rightSelector = right[4];
- var rightInSimpleSelector = right[5];
+ var rightSelector = right[5];
+ var rightInSimpleSelector = right[6];
if (leftName == 'font' && rightName == 'line-height' || rightName == 'font' && leftName == 'line-height')
return false;
var lineBreak = require('os').EOL;
var unknownSource = '$stdin';
+function hasMoreProperties(elements, index) {
+ for (var i = index, l = elements.length; i < l; i++) {
+ if (typeof elements[i] != 'string')
+ return true;
+ }
+
+ return false;
+}
+
function Rebuilder(options, restoreCallback, inputMapTracker) {
this.column = 0;
this.line = 1;
this.outputMap = new SourceMapGenerator();
}
-Rebuilder.prototype.rebuildValue = function (elements, separator) {
- var escaped = 0;
-
+Rebuilder.prototype.rebuildSelectors = function (elements) {
for (var i = 0, l = elements.length; i < l; i++) {
var element = elements[i];
+ this.store(element);
+
+ if (i < l - 1)
+ this.store(',');
+ }
+};
- if (element[0].indexOf('__ESCAPED_') === 0) {
- this.store(element[0]);
- escaped++;
+Rebuilder.prototype.rebuildBody = function (elements) {
+ for (var i = 0, l = elements.length; i < l; i++) {
+ var element = elements[i];
- if (i === l - 1 && escaped > 0)
- this.output.splice(this.output.length - escaped - 1, 1);
- } else {
+ if (typeof element == 'string') {
this.store(element);
- this.store(i < l - 1 ? separator : '');
- escaped = 0;
+ continue;
+ }
+
+ for (var j = 0, m = element.length; j < m; j++) {
+ this.store(element[j]);
+
+ if (j == m - 1 && element[0][1])
+ this.store('!important');
+
+ if (j === 0) {
+ this.store(':');
+ } else if (j < m - 1) {
+ this.store(' ');
+ } else if (i < l - 1 && hasMoreProperties(elements, i + 1)) {
+ this.store(';');
+ }
}
}
};
Rebuilder.prototype.store = function (element) {
+ // handles defaults and values like `,` or `/` which do not have mapping
+ if (Array.isArray(element) && element.length == 1)
+ element = element[0];
+
var fromString = typeof element == 'string';
var value = fromString ? element : element[0];
this.store(token[1][0]);
break;
case 'block':
- this.rebuildValue([token[1]], '');
+ this.rebuildSelectors([token[1]]);
this.store('{');
this.rebuildList(token[2], false);
this.store('}');
this.store(joinCharacter);
break;
case 'flat-block':
- this.rebuildValue([token[1]], '');
+ this.rebuildSelectors([token[1]]);
this.store('{');
- this.rebuildValue(token[2], ';');
+ this.rebuildBody(token[2]);
this.store('}');
this.store(joinCharacter);
break;
default:
- this.rebuildValue(token[1], ',');
+ this.rebuildSelectors(token[1]);
this.store('{');
- this.rebuildValue(token[2], ';');
+ this.rebuildBody(token[2]);
this.store('}');
this.store(joinCharacter);
}
};
Rebuilder.prototype.trackMetadata = function (element) {
- var source = element[3] || unknownSource;
+ var sourceAt = element.length - 1;
+ if (typeof element[sourceAt] == 'object')
+ sourceAt--;
+
+ var source = element[sourceAt] || unknownSource;
this.outputMap.addMapping({
generated: {
},
source: source,
original: {
- line: element[1],
- column: element[2]
+ line: element[sourceAt - 2],
+ column: element[sourceAt - 1]
}
});
- if (element[4])
- this.outputMap.setSourceContent(source, element[4][element[3]]);
+ if (element[sourceAt + 1])
+ this.outputMap.setSourceContent(source, element[sourceAt + 1][element[sourceAt]]);
};
function SourceMapStringifier(options, restoreCallback, inputMapTracker) {
this.restoreCallback = restoreCallback;
}
-function valueRebuilder(elements, separator) {
+function selectorRebuilder(elements) {
var merged = '';
- var element;
+
+ for (var i = 0, l = elements.length; i < l; i++) {
+ merged += elements[i] + (i < l - 1 ? ',' : '');
+ }
+
+ return merged;
+}
+
+function bodyRebuilder(elements) {
+ var merged = '';
+ var element, important, lastSemicolonAt, value, valueLastChar, shouldSkipSpaceAfter;
for (var i = 0, l = elements.length; i < l; i++) {
element = elements[i];
- if (element[0].indexOf('__ESCAPED_') === 0) {
- merged += element[0];
+ if (typeof element == 'string' && element.indexOf('__ESCAPED_') === 0) {
+ merged += element;
if (i === l - 1) {
- var lastSemicolonAt = merged.lastIndexOf(';');
+ lastSemicolonAt = merged.lastIndexOf(';');
merged = merged.substring(0, lastSemicolonAt) + merged.substring(lastSemicolonAt + 1);
}
} else {
- merged += element[0] + (i < l - 1 ? separator : '');
+ important = element[0][1];
+ merged += element[0][0] + ':';
+
+ for (var j = 1, m = element.length; j < m; j++) {
+ value = element[j][0];
+ valueLastChar = value[value.length - 1];
+
+ if (value == ',' || value == '/') {
+ if (merged[merged.length - 1] == ')')
+ merged += value;
+ else
+ merged = merged.substring(0, merged.length - 1) + value;
+ } else {
+ shouldSkipSpaceAfter = j == m - 1 || valueLastChar == ')' && value.indexOf('progid') == -1;
+ merged += value + (shouldSkipSpaceAfter ? '' : ' ');
+ }
+ }
+
+ if (important)
+ merged += '!important';
+
+ merged += (i < l - 1 ? ';' : '');
}
}
parts.push(token[1][0] + '{' + body + '}');
break;
case 'flat-block':
- body = valueRebuilder(token[2], ';');
+ body = bodyRebuilder(token[2]);
if (body.length > 0)
parts.push(token[1][0] + '{' + body + '}');
break;
default:
- selector = valueRebuilder(token[1], ',');
- body = valueRebuilder(token[2], ';');
+ selector = selectorRebuilder(token[1]);
+ body = bodyRebuilder(token[2]);
parts.push(selector + '{' + body + '}');
}
}
var Splitter = require('./splitter');
+var COMMA = ',';
+var FORWARD_SLASH = '/';
+
var Extractors = {
properties: function (string, context) {
var list = [];
- var buffer = [];
- var all = [];
- var property;
- var isPropertyEnd;
- var isWhitespace;
- var wasWhitespace;
- var isSpecial;
- var wasSpecial;
- var current;
- var last;
- var secondToLast;
- var wasCloseParenthesis;
- var isEscape;
- var metadata;
+ var splitter = new Splitter(/[ ,\/]/);
- if (string.replace && string.indexOf(')') > 0)
- string = string.replace(/\)([^\s_;:,\)])/g, context.sourceMaps ? ') __ESCAPED_COMMENT_CLEAN_CSS(0,-1)__$1' : ') $1');
-
- for (var i = 0, l = string.length; i < l; i++) {
- current = string[i];
- isPropertyEnd = current === ';';
-
- isEscape = !isPropertyEnd && current == '_' && string.indexOf('__ESCAPED_COMMENT', i) === i;
- if (isEscape) {
- if (buffer.length > 0) {
- i--;
- isPropertyEnd = true;
- } else {
- var endOfEscape = string.indexOf('__', i + 1) + 2;
- var comment = string.substring(i, endOfEscape);
- i = endOfEscape - 1;
-
- if (comment.indexOf('__ESCAPED_COMMENT_SPECIAL') === -1) {
- context.track(comment);
- continue;
- }
- else {
- buffer = all = [comment];
- }
- }
+ if (typeof string != 'string')
+ return [];
+
+ if (string.indexOf('__ESCAPED_COMMENT_') > -1)
+ string = string.replace(/(__ESCAPED_COMMENT_(SPECIAL_)?CLEAN_CSS[^_]+?__)/g, ';$1;');
+
+ if (string.indexOf(')') > -1)
+ string = string.replace(/\)([^\s_;:,\)])/g, context.sourceMaps ? ') __ESCAPED_COMMENT_CLEAN_CSS(0,-1)__ $1' : ') $1');
+
+ if (string.indexOf('ESCAPED_URL_CLEAN_CSS') > -1)
+ string = string.replace(/(ESCAPED_URL_CLEAN_CSS[^_]+?__)/g, context.sourceMaps ? '$1 __ESCAPED_COMMENT_CLEAN_CSS(0,-1)__ ' : '$1 ');
+
+ var candidates = string.split(';');
+
+ for (var i = 0, l = candidates.length; i < l; i++) {
+ var candidate = candidates[i];
+ var firstColonAt = candidate.indexOf(':');
+
+ if (firstColonAt == -1) {
+ context.track(candidate);
+ if (candidate.indexOf('__ESCAPED_COMMENT_SPECIAL') > -1)
+ list.push(candidate);
+ continue;
}
- if (isPropertyEnd || isEscape) {
- if (wasWhitespace && buffer[buffer.length - 1] === ' ')
- buffer.pop();
- if (buffer.length > 0) {
- property = buffer.join('');
- if (property.indexOf('{') === -1) {
- metadata = context.track(all.join(''), true);
- list.push([property].concat(metadata));
-
- if (!isEscape)
- context.track(';');
- }
+ if (candidate.indexOf('{') > 0) {
+ context.track(candidate);
+ continue;
+ }
+
+ var body = [];
+ var name = candidate.substring(0, firstColonAt);
+ body.push([name.trim()].concat(context.track(name, true)));
+ context.track(':');
+
+ var values = splitter.split(candidate.substring(firstColonAt + 1), true);
+
+ for (var j = 0, m = values.length; j < m; j++) {
+ var value = values[j];
+ var trimmed = value.trim();
+
+ if (trimmed.length === 0)
+ continue;
+
+ var lastCharacter = trimmed[trimmed.length - 1];
+ var endsWithNonSpaceSeparator = trimmed.length > 1 && (lastCharacter == COMMA || lastCharacter == FORWARD_SLASH);
+
+ if (endsWithNonSpaceSeparator)
+ trimmed = trimmed.substring(0, trimmed.length - 1);
+
+ if (trimmed.indexOf('__ESCAPED_COMMENT_CLEAN_CSS(0,-') > -1) {
+ context.track(trimmed);
+ continue;
+ }
+
+ var pos = body.length - 1;
+ if (trimmed == 'important' && body[pos][0] == '!') {
+ context.track(trimmed);
+ body[pos - 1][0] += '!important';
+ body.pop();
+ continue;
}
- buffer = [];
- all = [];
- } else {
- isWhitespace = current === ' ' || current === '\t' || current === '\n';
- isSpecial = current === ':' || current === '[' || current === ']' || current === ',' || current === '(' || current === ')';
-
- if (wasWhitespace && isSpecial) {
- last = buffer[buffer.length - 1];
- secondToLast = buffer[buffer.length - 2];
- if (secondToLast != '+' && secondToLast != '-' && secondToLast != '/' && secondToLast != '*' && last != '(')
- buffer.pop();
- buffer.push(current);
- } else if (isWhitespace && wasSpecial && !wasCloseParenthesis) {
- } else if (isWhitespace && !wasWhitespace && buffer.length > 0) {
- buffer.push(' ');
- } else if (isWhitespace && buffer.length === 0) {
- } else if (isWhitespace && wasWhitespace) {
- } else {
- buffer.push(isWhitespace ? ' ' : current);
+
+ if (trimmed == '!important' || (trimmed == 'important' && body[pos][0][body[pos][0].length - 1] == '!')) {
+ context.track(trimmed);
+ body[pos][0] += trimmed;
+ continue;
}
- all.push(current);
+ body.push([trimmed].concat(context.track(value, true)));
+
+ if (endsWithNonSpaceSeparator) {
+ body.push([lastCharacter]);
+ context.track(lastCharacter);
+ }
}
- wasSpecial = isSpecial;
- wasWhitespace = isWhitespace;
- wasCloseParenthesis = current === ')';
- }
+ if (i < l - 1)
+ context.track(';');
- if (wasWhitespace && buffer[buffer.length - 1] === ' ')
- buffer.pop();
- if (buffer.length > 0) {
- property = buffer.join('');
- if (property.indexOf('{') === -1) {
- metadata = context.track(all.join(''), true);
- list.push([property].concat(metadata));
- }
- } else if (all.indexOf('\n') > -1) {
- context.track(all.join(''));
+ list.push(body);
}
return list;
for (var i = 0, l = selectors.length; i < l; i++) {
metadata = context.track(selectors[i], true, i);
context.track(',');
- list.push([selectors[i]].concat(metadata));
+ list.push([selectors[i].trim()].concat(metadata));
}
return list;
function Splitter(separator) {
this.separator = separator;
+ this.withRegex = typeof separator != 'string';
}
Splitter.prototype.split = function (value, withSeparator) {
- if (value.indexOf(this.separator) === -1)
+ var hasSeparator = this.withRegex ?
+ this.separator.test(value) :
+ value.indexOf(this.separator);
+ if (!hasSeparator)
return [value];
if (value.indexOf('(') === -1 && !withSeparator)
level++;
} else if (value[cursor] == ')') {
level--;
- } else if (value[cursor] == this.separator && level === 0) {
+ } else if ((this.withRegex ? this.separator.test(value[cursor]) : value[cursor] == this.separator) && level === 0) {
tokens.push(value.substring(lastStart, cursor + (withSeparator ? 1 : 0)));
lastStart = cursor + 1;
}
-function stringify(values, separator) {
- var i = 0;
- var result = [];
-
- while (values[i]) {
- result.push(values[i][0]);
- i++;
+function stringifyValue(property) {
+ var result = '';
+ for (var i = 1, l = property.length; i < l; i++) {
+ result += property[i][0] + (i < l - 1 ? ' ' : '');
}
- return result.join(separator);
+ return result;
}
function stringifyBody(properties) {
- return stringify(properties, ';');
+ var result = '';
+ for (var i = 0, l = properties.length; i < l; i++) {
+ var property = properties[i];
+
+ result += property[0][0] + ':';
+ for (var j = 1, m = property.length; j < m; j++) {
+ result += property[j][0] + (j < m - 1 ? ' ' : '');
+ }
+
+ result += (i < l - 1 ? ';' : '');
+ }
+
+ return result;
}
function stringifySelector(list) {
- return stringify(list, ',');
+ var result = '';
+ for (var i = 0, l = list.length; i < l; i++) {
+ result += list[i][0] + (i < l - 1 ? ',' : '');
+ }
+
+ return result;
}
module.exports = {
+ value: stringifyValue,
body: stringifyBody,
selector: stringifySelector
};
}),
'of mergeable properties': pipedContext('a{background:red;display:block;background:white}', '--skip-aggressive-merging', {
'gets right result': function(error, stdout) {
- assert.equal(stdout, 'a{display:block;background:#fff}');
+ assert.equal(stdout, 'a{background:#fff;display:block}');
}
})
},
var count = 0;
sourceMap.eachMapping(function () { count++; });
- assert.equal(count, 4);
+ assert.equal(count, 6);
},
'teardown': function () {
deleteFile('import.min.css');
var count = 0;
sourceMap.eachMapping(function () { count++; });
- assert.equal(count, 4);
+ assert.equal(count, 6);
},
'teardown': function () {
deleteFile('import-inline.min.css');
return context;
};
-var redefineContext = function(redefinitions, options) {
- var context = {};
- var vendorPrefixes = ['', '-moz-', '-o-', '-webkit-']; // there is no -ms-animation nor -ms-transition.
-
- for (var property in redefinitions) {
- for (var i = 0; i < redefinitions[property].length; i++) {
- var by = redefinitions[property][i];
- var prefixes = options.vendorPrefixes.indexOf(by) > -1 ? vendorPrefixes : [''];
-
- for (var j = 0, m = prefixes.length; j < m; j++) {
- var prefixedProperty = prefixes[j] + property;
- var prefixedBy = prefixes[j] + by;
- var zeroValue = options.noneFor.indexOf(prefixedProperty) > -1 ? 'none' : '0';
-
- context['should override ' + prefixedProperty + ' by ' + prefixedBy] = [
- 'a{' + prefixedProperty + ':inherit;' + prefixedBy + ':' + zeroValue + '}',
- 'a{' + prefixedBy + ':' + zeroValue + '}'
- ];
- context['should not override ' + prefixedBy + ' by ' + prefixedProperty] =
- 'a{' + prefixedBy + ':' + zeroValue + ';' + prefixedProperty + ':inherit}';
- }
- }
- }
-
- return cssContext(context);
-};
-
vows.describe('integration tests').addBatch({
'identity': cssContext({
'preserve minified content': 'a{color:#f10}'
],
'not inside calc method with more parentheses': [
'div{height:-moz-calc((10% + 12px)/2 + 10em)}',
- 'div{height:-moz-calc((10% + 12px)/2 + 10em)}'
+ 'div{height:-moz-calc((10% + 12px)/ 2 + 10em)}'
],
'not inside calc method with multiplication': [
'div{height:-moz-calc(3 * 2em + 10px)}',
'@media (min-width:100px){a{color:red}}@media screen{p{width:100px}a{color:red}}'
]
}),
- 'duplicate properties': cssContext({
- 'of two properties one after another': 'a{display:-moz-inline-box;display:inline-block}',
- 'of two properties in one declaration': [
- 'a{display:inline-block;color:red;display:block}',
- 'a{color:red;display:block}'
- ],
- 'of two properties in one declaration with former as !important': [
- 'a{display:inline-block!important;color:red;display:block}',
- 'a{display:inline-block!important;color:red}'
- ],
- 'of two properties in one declaration with latter as !important': [
- 'a{display:inline-block;color:red;display:block!important}',
- 'a{color:red;display:block!important}'
- ],
- 'of two properties in one declaration with both as !important': [
- 'a{display:inline-block!important;color:red;display:block!important}',
- 'a{color:red;display:block!important}'
- ],
- 'of two properties in one declaration with both as !important but 2nd less understandable': 'a{color:red!important;display:block;color:rgba(0,255,0,.5)!important}',
- 'of two properties in one declaration with both as !important but 2nd more understandable': 'a{color:rgba(0,255,0,.5)!important;display:block;color:red!important}',
- 'of two shorthand properties in one declaration with both as !important but 2nd less understandable': 'a{background:red!important;background:rgba(0,255,0,.5)!important}',
- 'of two shorthand properties in one declaration with both as !important but 2nd more understandable': 'a{background:rgba(0,255,0,.5)!important;background:red!important}',
- 'of many properties in one declaration': [
- 'a{display:inline-block;color:red;font-weight:bolder;font-weight:700;display:block!important;color:#fff}',
- 'a{font-weight:bolder;font-weight:700;display:block!important;color:#fff}'
- ],
- 'both redefined and overridden': [
- 'p{display:block;display:-moz-inline-box;color:red;display:table-cell}',
- 'p{color:red;display:table-cell}'
- ],
- 'background redefined with merging': [
- '.one{display:block}.one{background:#fff;background:-webkit-gradient();background:-moz-linear-gradient();filter:progid:DXImageTransform}',
- '.one{display:block;background:#fff;background:-webkit-gradient();background:-moz-linear-gradient();filter:progid:DXImageTransform}'
- ],
- 'filter treated as background': 'p{background:-moz-linear-gradient();background:-webkit-linear-gradient();filter:"progid:DXImageTransform";background:linear-gradient()}',
- 'filter treated as background-image': 'p{background-image:-moz-linear-gradient();background-image:-webkit-linear-gradient();filter:"progid:DXImageTransform";background-image:linear-gradient()}',
- '-ms-filter treated as background': 'p{background:-moz-linear-gradient();background:-webkit-linear-gradient();-ms-filter:"progid:DXImageTransform";background:linear-gradient()}',
- '-ms-filter treated as background-image': 'p{background-image:-moz-linear-gradient();background-image:-webkit-linear-gradient();-ms-filter:"progid:DXImageTransform";background-image:linear-gradient()}',
- '-ms-transform with different values #1': 'div{-ms-transform:translate(0,0);-ms-transform:translate3d(0,0,0)}',
- '-ms-transform with different values #2': 'div{-ms-transform:translate(0,0);-webkit-transform:translate3d(0,0,0);-ms-transform:translate3d(0,0,0)}',
- 'transform with different values #1': 'div{transform:translate(0,0);transform:translate3d(0,0,0)}',
- 'transform with different values #2': 'div{transform:translate(0,0);-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}',
- 'border(hex) with border(rgba)': 'a{border:1px solid #fff;display:none;border:1px solid rgba(1,0,0,.5)}',
- 'border(hex !important) with border(hex)': [
- 'a{border:1px solid #fff!important;display:none;border:1px solid #fff}',
- 'a{border:1px solid #fff!important;display:none}'
- ],
- 'border(hex) with border(hex !important)': [
- 'a{border:1px solid #fff;display:none;border:1px solid #fff!important}',
- 'a{display:none;border:1px solid #fff!important}'
- ]
- }),
'duplicate properties with aggressive merging disabled': cssContext({
'of (yet) unmergeable properties': 'a{display:inline-block;color:red;display:-moz-block}',
'of mergeable properties': [
'a{background:red;display:block;background:white}',
- 'a{display:block;background:#fff}'
+ 'a{background:#fff;display:block}'
]
}, { aggressiveMerging: false }),
'same selectors': cssContext({
'units - IE8 compatibility': cssContext({
'rems': 'div{padding-top:16px;padding-top:1rem}'
}, { compatibility: 'ie8' }),
- 'redefined more granular properties': redefineContext({
- 'animation-delay': ['animation'],
- 'animation-direction': ['animation'],
- 'animation-duration': ['animation'],
- 'animation-fill-mode': ['animation'],
- 'animation-iteration-count': ['animation'],
- 'animation-name': ['animation'],
- 'animation-play-state': ['animation'],
- 'animation-timing-function': ['animation'],
- 'background-attachment': ['background'],
- 'background-clip': ['background'],
- 'background-color': ['background'],
- 'background-image': ['background'],
- 'background-origin': ['background'],
- 'background-position': ['background'],
- 'background-repeat': ['background'],
- 'background-size': ['background'],
- 'border-color': ['border'],
- 'border-style': ['border'],
- 'border-width': ['border'],
- 'border-bottom': ['border'],
- 'border-bottom-color': ['border-bottom', 'border-color', 'border'],
- 'border-bottom-style': ['border-bottom', 'border-style', 'border'],
- 'border-bottom-width': ['border-bottom', 'border-width', 'border'],
- 'border-left': ['border'],
- 'border-left-color': ['border-left', 'border-color', 'border'],
- 'border-left-style': ['border-left', 'border-style', 'border'],
- 'border-left-width': ['border-left', 'border-width', 'border'],
- 'border-right': ['border'],
- 'border-right-color': ['border-right', 'border-color', 'border'],
- 'border-right-style': ['border-right', 'border-style', 'border'],
- 'border-right-width': ['border-right', 'border-width', 'border'],
- 'border-top': ['border'],
- 'border-top-color': ['border-top', 'border-color', 'border'],
- 'border-top-style': ['border-top', 'border-style', 'border'],
- 'border-top-width': ['border-top', 'border-width', 'border'],
- 'font-family': ['font'],
- 'font-size': ['font'],
- 'font-style': ['font'],
- 'font-variant': ['font'],
- 'font-weight': ['font'],
- 'list-style-image': ['list-style'],
- 'list-style-position': ['list-style'],
- 'list-style-type': ['list-style'],
- 'margin-bottom': ['margin'],
- 'margin-left': ['margin'],
- 'margin-right': ['margin'],
- 'margin-top': ['margin'],
- 'outline-color': ['outline'],
- 'outline-style': ['outline'],
- 'outline-width': ['outline'],
- 'padding-bottom': ['padding'],
- 'padding-left': ['padding'],
- 'padding-right': ['padding'],
- 'padding-top': ['padding'],
- 'transition-delay': ['transition'],
- 'transition-duration': ['transition'],
- 'transition-property': ['transition'],
- 'transition-timing-function': ['transition']
- }, { vendorPrefixes: ['animation', 'transition'], noneFor: ['list-style-image'] }),
'redefined more granular properties with property merging': cssContext({
'should merge background with background-attachment': [
'a{background:0;background-attachment:fixed}',
'a{background:0;background-color:inherit}'
],
'should NOT merge background with background-color set to none': [
- 'a{background:url(logo.png)no-repeat center;background-color:none}',
- 'a{background:url(logo.png)no-repeat center;background-color:none}'
+ 'a{background:url(logo.png)center no-repeat;background-color:none}',
+ 'a{background:url(logo.png)center no-repeat;background-color:none}'
],
'should merge background with background-image': [
'a{background:0;background-image:url(hello_world)}',
'li{list-style:inside}'
]
}),
- '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}'
- ]
- }),
- 'remove defaults from shorthands': cssContext({
- 'all-default background should be changed to shortest possible default value': [
- 'div{background:transparent none repeat 0 0 scroll}',
- 'div{background:0 0}'
- ],
- 'default background components should be removed #1': [
- 'body{background:#9fce00 none repeat scroll}',
- 'body{background:#9fce00}'
- ],
- 'default background components should be removed #2': [
- 'body{background:transparent none 1px 5px scroll}',
- 'body{background:1px 5px}'
- ],
- 'default background components should be removed #3': [
- 'body{background:none repeat scroll 0 0 #000}',
- 'body{background:#000}'
- ]
- }),
'merging of rules': cssContext({
'rules without pseudo classes should be merged': [
'a{color:red}b{color:red}',
'a:first{color:red}b{color:red}'
]
}),
- '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}'
- ]
- }),
'grouping with advanced optimizations': cssContext({
'@-moz-document': '@-moz-document domain(mozilla.org){a{color:red}}',
'@media': '@media{a{color:red}}',
'.one{background:50% no-repeat}.one{background-image:url(/img.png)}',
'.one{background:url(/img.png)50% no-repeat}'
],
- 'merging color with backgrounds': [
- 'p{background:red;background-image:url(1.png),url(2.png)}',
- 'p{background:url(1.png),url(2.png)red}'
- ],
+ // TODO: restore multiplex merging
+ // 'merging color with backgrounds': [
+ // 'p{background:red;background-image:url(1.png),url(2.png)}',
+ // 'p{background:url(1.png),url(2.png)red}'
+ // ],
'unknown @ rule': '@unknown "test";h1{color:red}',
'property without a value': [
'a{color:}',
--- /dev/null
+var vows = require('vows');
+var assert = require('assert');
+
+var wrapForOptimizing = require('../../lib/properties/wrap-for-optimizing').all;
+var populateComponents = require('../../lib/properties/populate-components');
+
+var breakUp = require('../../lib/properties/break-up');
+
+function _breakUp(properties) {
+ var wrapped = wrapForOptimizing(properties);
+ populateComponents(wrapped);
+
+ return wrapped[0].components;
+}
+
+vows.describe(breakUp)
+ .addBatch({
+ 'background': {
+ 'inherit': {
+ 'topic': function () {
+ return _breakUp([[['background'], ['inherit']]]);
+ },
+ 'has 8 components': function (components) {
+ assert.lengthOf(components, 8);
+ },
+ 'has background-image': function (components) {
+ assert.deepEqual(components[0].name, 'background-image');
+ assert.deepEqual(components[0].value, [['inherit']]);
+ },
+ 'has background-position': function (components) {
+ assert.deepEqual(components[1].name, 'background-position');
+ assert.deepEqual(components[1].value, [['inherit']]);
+ },
+ 'has background-size': function (components) {
+ assert.deepEqual(components[2].name, 'background-size');
+ assert.deepEqual(components[2].value, [['inherit']]);
+ },
+ 'has background-repeat': function (components) {
+ assert.deepEqual(components[3].name, 'background-repeat');
+ assert.deepEqual(components[3].value, [['inherit']]);
+ },
+ 'has background-attachment': function (components) {
+ assert.deepEqual(components[4].name, 'background-attachment');
+ assert.deepEqual(components[4].value, [['scroll']]);
+ },
+ 'has background-origin': function (components) {
+ assert.deepEqual(components[5].name, 'background-origin');
+ assert.deepEqual(components[5].value, [['inherit']]);
+ },
+ 'has background-clip': function (components) {
+ assert.deepEqual(components[6].name, 'background-clip');
+ assert.deepEqual(components[6].value, [['inherit']]);
+ },
+ 'has background-color': function (components) {
+ assert.deepEqual(components[7].name, 'background-color');
+ assert.deepEqual(components[7].value, [['inherit']]);
+ }
+ },
+ 'all': {
+ 'topic': function () {
+ return _breakUp([[['background'], ['__ESCAPED_URL_CLEAN_CSS0__'], ['repeat'], ['no-repeat'], ['2px'], ['3px'], ['/'], ['50%'], ['60%'], ['fixed'], ['padding-box'], ['border-box'], ['red']]]);
+ },
+ 'has 8 components': function (components) {
+ assert.lengthOf(components, 8);
+ },
+ 'has background-image': function (components) {
+ assert.deepEqual(components[0].name, 'background-image');
+ assert.deepEqual(components[0].value, [['__ESCAPED_URL_CLEAN_CSS0__']]);
+ },
+ 'has background-position': function (components) {
+ assert.deepEqual(components[1].name, 'background-position');
+ assert.deepEqual(components[1].value, [['2px'], ['3px']]);
+ },
+ 'has background-size': function (components) {
+ assert.deepEqual(components[2].name, 'background-size');
+ assert.deepEqual(components[2].value, [['50%'], ['60%']]);
+ },
+ 'has background-repeat': function (components) {
+ assert.deepEqual(components[3].name, 'background-repeat');
+ assert.deepEqual(components[3].value, [['repeat'], ['no-repeat']]);
+ },
+ 'has background-attachment': function (components) {
+ assert.deepEqual(components[4].name, 'background-attachment');
+ assert.deepEqual(components[4].value, [['fixed']]);
+ },
+ 'has background-origin': function (components) {
+ assert.deepEqual(components[5].name, 'background-origin');
+ assert.deepEqual(components[5].value, [['padding-box']]);
+ },
+ 'has background-clip': function (components) {
+ assert.deepEqual(components[6].name, 'background-clip');
+ assert.deepEqual(components[6].value, [['border-box']]);
+ },
+ 'has background-color': function (components) {
+ assert.deepEqual(components[7].name, 'background-color');
+ assert.deepEqual(components[7].value, [['red']]);
+ }
+ },
+ 'no size': {
+ 'topic': function () {
+ return _breakUp([[['background'], ['bottom']]]);
+ },
+ 'has 8 components': function (components) {
+ assert.lengthOf(components, 8);
+ },
+ 'has background-position': function (components) {
+ assert.deepEqual(components[1].name, 'background-position');
+ assert.deepEqual(components[1].value, [['bottom']]);
+ },
+ 'has background-size': function (components) {
+ assert.deepEqual(components[2].name, 'background-size');
+ assert.deepEqual(components[2].value, [['auto']]);
+ }
+ },
+ 'shorthand size & position': {
+ 'topic': function () {
+ return _breakUp([[['background'], ['2px'], ['/'], ['50px']]]);
+ },
+ 'has 8 components': function (components) {
+ assert.lengthOf(components, 8);
+ },
+ 'has background-position': function (components) {
+ assert.deepEqual(components[1].name, 'background-position');
+ assert.deepEqual(components[1].value, [['2px']]);
+ },
+ 'has background-size': function (components) {
+ assert.deepEqual(components[2].name, 'background-size');
+ assert.deepEqual(components[2].value, [['50px']]);
+ }
+ },
+ 'size & position joined together': {
+ 'topic': function () {
+ return _breakUp([[['background'], ['2px/50px']]]);
+ },
+ 'has 8 components': function (components) {
+ assert.lengthOf(components, 8);
+ },
+ 'has background-position': function (components) {
+ assert.deepEqual(components[1].name, 'background-position');
+ assert.deepEqual(components[1].value, [['2px']]);
+ },
+ 'has background-size': function (components) {
+ assert.deepEqual(components[2].name, 'background-size');
+ assert.deepEqual(components[2].value, [['50px']]);
+ }
+ },
+ 'size & position joined together with 4 values': {
+ 'topic': function () {
+ return _breakUp([[['background'], ['5px'], ['2px/50px'], ['30px']]]);
+ },
+ 'has 8 components': function (components) {
+ assert.lengthOf(components, 8);
+ },
+ 'has background-position': function (components) {
+ assert.deepEqual(components[1].name, 'background-position');
+ assert.deepEqual(components[1].value, [['5px'], ['2px']]);
+ },
+ 'has background-size': function (components) {
+ assert.deepEqual(components[2].name, 'background-size');
+ assert.deepEqual(components[2].value, [['50px'], ['30px']]);
+ }
+ },
+ 'clip to origin': {
+ 'topic': function () {
+ return _breakUp([[['background'], ['padding-box']]]);
+ },
+ 'has 8 components': function (components) {
+ assert.lengthOf(components, 8);
+ },
+ 'has background-origin': function (components) {
+ assert.deepEqual(components[5].name, 'background-origin');
+ assert.deepEqual(components[5].value, [['padding-box']]);
+ },
+ 'has background-clip': function (components) {
+ assert.deepEqual(components[6].name, 'background-clip');
+ assert.deepEqual(components[6].value, [['padding-box']]);
+ }
+ }
+ },
+ 'border': {
+ 'inherit': {
+ 'topic': function () {
+ return _breakUp([[['border'], ['inherit']]]);
+ },
+ 'has 3 components': function (components) {
+ assert.lengthOf(components, 3);
+ },
+ 'has border-width': function (components) {
+ assert.deepEqual(components[0].name, 'border-width');
+ assert.deepEqual(components[0].value, [['inherit']]);
+ },
+ 'has border-style': function (components) {
+ assert.deepEqual(components[1].name, 'border-style');
+ assert.deepEqual(components[1].value, [['inherit']]);
+ },
+ 'has border-color': function (components) {
+ assert.deepEqual(components[2].name, 'border-color');
+ assert.deepEqual(components[2].value, [['inherit']]);
+ }
+ },
+ '3 inherits': {
+ 'topic': function () {
+ return _breakUp([[['border'], ['inherit'], ['inherit'], ['inherit']]]);
+ },
+ 'has 3 components': function (components) {
+ assert.lengthOf(components, 3);
+ },
+ 'has border-width': function (components) {
+ assert.deepEqual(components[0].name, 'border-width');
+ assert.deepEqual(components[0].value, [['inherit']]);
+ },
+ 'has border-style': function (components) {
+ assert.deepEqual(components[1].name, 'border-style');
+ assert.deepEqual(components[1].value, [['inherit']]);
+ },
+ 'has border-color': function (components) {
+ assert.deepEqual(components[2].name, 'border-color');
+ assert.deepEqual(components[2].value, [['inherit']]);
+ }
+ },
+ 'all values in correct order': {
+ 'topic': function () {
+ return _breakUp([[['border'], ['1px'], ['solid'], ['red']]]);
+ },
+ 'has 3 components': function (components) {
+ assert.lengthOf(components, 3);
+ },
+ 'has border-width': function (components) {
+ assert.deepEqual(components[0].name, 'border-width');
+ assert.deepEqual(components[0].value, [['1px']]);
+ },
+ 'has border-style': function (components) {
+ assert.deepEqual(components[1].name, 'border-style');
+ assert.deepEqual(components[1].value, [['solid']]);
+ },
+ 'has border-color': function (components) {
+ assert.deepEqual(components[2].name, 'border-color');
+ assert.deepEqual(components[2].value, [['red']]);
+ }
+ },
+ 'all values in wrong order': {
+ 'topic': function () {
+ return _breakUp([[['border'], ['red'], ['solid'], ['1px']]]);
+ },
+ 'has 3 components': function (components) {
+ assert.lengthOf(components, 3);
+ },
+ 'has border-width': function (components) {
+ assert.deepEqual(components[0].name, 'border-width');
+ assert.deepEqual(components[0].value, [['1px']]);
+ },
+ 'has border-style': function (components) {
+ assert.deepEqual(components[1].name, 'border-style');
+ assert.deepEqual(components[1].value, [['solid']]);
+ },
+ 'has border-color': function (components) {
+ assert.deepEqual(components[2].name, 'border-color');
+ assert.deepEqual(components[2].value, [['red']]);
+ }
+ },
+ 'missing values': {
+ 'topic': function () {
+ return _breakUp([[['border'], ['red']]]);
+ },
+ 'has 3 components': function (components) {
+ assert.lengthOf(components, 3);
+ },
+ 'has border-width': function (components) {
+ assert.deepEqual(components[0].name, 'border-width');
+ assert.deepEqual(components[0].value, [['medium']]);
+ },
+ 'has border-style': function (components) {
+ assert.deepEqual(components[1].name, 'border-style');
+ assert.deepEqual(components[1].value, [['none']]);
+ },
+ 'has border-color': function (components) {
+ assert.deepEqual(components[2].name, 'border-color');
+ assert.deepEqual(components[2].value, [['red']]);
+ }
+ }
+ },
+ 'border radius': {
+ 'no horizontal vertical split': {
+ 'topic': function () {
+ return _breakUp([[['border-radius'], ['0px'], ['1px'], ['2px'], ['3px']]]);
+ },
+ 'has 4 components': function (components) {
+ assert.lengthOf(components, 4);
+ },
+ 'has border-top-left-radius': function (components) {
+ assert.equal(components[0].name, 'border-top-left-radius');
+ assert.deepEqual(components[0].value, [['0px']]);
+ },
+ 'has border-top-right-radius': function (components) {
+ assert.equal(components[1].name, 'border-top-right-radius');
+ assert.deepEqual(components[1].value, [['1px']]);
+ },
+ 'has border-bottom-right-radius': function (components) {
+ assert.equal(components[2].name, 'border-bottom-right-radius');
+ assert.deepEqual(components[2].value, [['2px']]);
+ },
+ 'has border-bottom-left': function (components) {
+ assert.equal(components[3].name, 'border-bottom-left-radius');
+ assert.deepEqual(components[3].value, [['3px']]);
+ }
+ },
+ 'horizontal vertical split': {
+ 'topic': function () {
+ return _breakUp([[['border-radius'], ['0px'], ['1px'], ['2px'], ['3px'], ['/'], ['1px'], ['2px'], ['3px'], ['4px']]]);
+ },
+ 'has 4 components': function (components) {
+ assert.lengthOf(components, 4);
+ },
+ 'has border-top-left-radius': function (components) {
+ assert.equal(components[0].name, 'border-top-left-radius');
+ assert.deepEqual(components[0].value, [[['0px']], [['1px']]]);
+ },
+ 'has border-top-right-radius': function (components) {
+ assert.equal(components[1].name, 'border-top-right-radius');
+ assert.deepEqual(components[1].value, [[['1px']], [['2px']]]);
+ },
+ 'has border-bottom-right-radius': function (components) {
+ assert.equal(components[2].name, 'border-bottom-right-radius');
+ assert.deepEqual(components[2].value, [[['2px']], [['3px']]]);
+ },
+ 'has border-bottom-left': function (components) {
+ assert.equal(components[3].name, 'border-bottom-left-radius');
+ assert.deepEqual(components[3].value, [[['3px']], [['4px']]]);
+ }
+ },
+ 'vendor prefix asymetrical horizontal vertical split': {
+ 'topic': function () {
+ return _breakUp([[['-webkit-border-radius'], ['0px'], ['1px'], ['2px'], ['/'], ['1px'], ['4px']]]);
+ },
+ 'has 4 components': function (components) {
+ assert.lengthOf(components, 4);
+ },
+ 'has border-top-left-radius': function (components) {
+ assert.equal(components[0].name, '-webkit-border-top-left-radius');
+ assert.deepEqual(components[0].value, [[['0px']], [['1px']]]);
+ },
+ 'has border-top-right-radius': function (components) {
+ assert.equal(components[1].name, '-webkit-border-top-right-radius');
+ assert.deepEqual(components[1].value, [[['1px']], [['4px']]]);
+ },
+ 'has border-bottom-right-radius': function (components) {
+ assert.equal(components[2].name, '-webkit-border-bottom-right-radius');
+ assert.deepEqual(components[2].value, [[['2px']], [['1px']]]);
+ },
+ 'has border-bottom-left': function (components) {
+ assert.equal(components[3].name, '-webkit-border-bottom-left-radius');
+ assert.deepEqual(components[3].value, [[['1px']], [['4px']]]);
+ }
+ }
+ },
+ 'four values': {
+ 'four given': {
+ 'topic': function () {
+ return _breakUp([[['margin'], ['0px'], ['1px'], ['2px'], ['3px']]]);
+ },
+ 'has 4 components': function (components) {
+ assert.lengthOf(components, 4);
+ },
+ 'has margin-top': function (components) {
+ assert.equal(components[0].name, 'margin-top');
+ assert.deepEqual(components[0].value, [['0px']]);
+ },
+ 'has margin-right': function (components) {
+ assert.equal(components[1].name, 'margin-right');
+ assert.deepEqual(components[1].value, [['1px']]);
+ },
+ 'has margin-bottom': function (components) {
+ assert.equal(components[2].name, 'margin-bottom');
+ assert.deepEqual(components[2].value, [['2px']]);
+ },
+ 'has margin-left': function (components) {
+ assert.equal(components[3].name, 'margin-left');
+ assert.deepEqual(components[3].value, [['3px']]);
+ }
+ },
+ 'three given': {
+ 'topic': function () {
+ return _breakUp([[['padding'], ['0px'], ['1px'], ['2px']]]);
+ },
+ 'has 4 components': function (components) {
+ assert.lengthOf(components, 4);
+ },
+ 'has padding-top': function (components) {
+ assert.equal(components[0].name, 'padding-top');
+ assert.deepEqual(components[0].value, [['0px']]);
+ },
+ 'has padding-right': function (components) {
+ assert.equal(components[1].name, 'padding-right');
+ assert.deepEqual(components[1].value, [['1px']]);
+ },
+ 'has padding-bottom': function (components) {
+ assert.equal(components[2].name, 'padding-bottom');
+ assert.deepEqual(components[2].value, [['2px']]);
+ },
+ 'has padding-left': function (components) {
+ assert.equal(components[3].name, 'padding-left');
+ assert.deepEqual(components[3].value, [['1px']]);
+ }
+ },
+ 'two given': {
+ 'topic': function () {
+ return _breakUp([[['border-color'], ['red'], ['blue']]]);
+ },
+ 'has 4 components': function (components) {
+ assert.lengthOf(components, 4);
+ },
+ 'has border-top-color': function (components) {
+ assert.equal(components[0].name, 'border-top-color');
+ assert.deepEqual(components[0].value, [['red']]);
+ },
+ 'has border-right-color': function (components) {
+ assert.equal(components[1].name, 'border-right-color');
+ assert.deepEqual(components[1].value, [['blue']]);
+ },
+ 'has border-bottom-color': function (components) {
+ assert.equal(components[2].name, 'border-bottom-color');
+ assert.deepEqual(components[2].value, [['red']]);
+ },
+ 'has border-left-color': function (components) {
+ assert.equal(components[3].name, 'border-left-color');
+ assert.deepEqual(components[3].value, [['blue']]);
+ }
+ },
+ 'one given': {
+ 'topic': function () {
+ return _breakUp([[['border-style'], ['solid']]]);
+ },
+ 'has 4 components': function (components) {
+ assert.lengthOf(components, 4);
+ },
+ 'has border-top-style': function (components) {
+ assert.equal(components[0].name, 'border-top-style');
+ assert.deepEqual(components[0].value, [['solid']]);
+ },
+ 'has border-right-style': function (components) {
+ assert.equal(components[1].name, 'border-right-style');
+ assert.deepEqual(components[1].value, [['solid']]);
+ },
+ 'has border-bottom-style': function (components) {
+ assert.equal(components[2].name, 'border-bottom-style');
+ assert.deepEqual(components[2].value, [['solid']]);
+ },
+ 'has border-left-style': function (components) {
+ assert.equal(components[3].name, 'border-left-style');
+ assert.deepEqual(components[3].value, [['solid']]);
+ }
+ },
+ 'none given': {
+ 'topic': function () {
+ return _breakUp([[['border-style']]]);
+ },
+ 'has 0 components': function (components) {
+ assert.lengthOf(components, 0);
+ }
+ }
+ },
+ 'list style': {
+ 'inherit': {
+ 'topic': function () {
+ return _breakUp([[['list-style'], ['inherit']]]);
+ },
+ 'has 3 components': function (components) {
+ assert.lengthOf(components, 3);
+ },
+ 'has border-top-style': function (components) {
+ assert.equal(components[0].name, 'list-style-type');
+ assert.deepEqual(components[0].value, [['inherit']]);
+ },
+ 'has border-right-style': function (components) {
+ assert.equal(components[1].name, 'list-style-position');
+ assert.deepEqual(components[1].value, [['inherit']]);
+ },
+ 'has border-bottom-style': function (components) {
+ assert.equal(components[2].name, 'list-style-image');
+ assert.deepEqual(components[2].value, [['inherit']]);
+ }
+ },
+ 'all values': {
+ 'topic': function () {
+ return _breakUp([[['list-style'], ['circle'], ['inside'], ['__ESCAPED_URL_CLEAN_CSS0__']]]);
+ },
+ 'has 3 components': function (components) {
+ assert.lengthOf(components, 3);
+ },
+ 'has border-top-style': function (components) {
+ assert.equal(components[0].name, 'list-style-type');
+ assert.deepEqual(components[0].value, [['circle']]);
+ },
+ 'has border-right-style': function (components) {
+ assert.equal(components[1].name, 'list-style-position');
+ assert.deepEqual(components[1].value, [['inside']]);
+ },
+ 'has border-bottom-style': function (components) {
+ assert.equal(components[2].name, 'list-style-image');
+ assert.deepEqual(components[2].value, [['__ESCAPED_URL_CLEAN_CSS0__']]);
+ }
+ },
+ 'some missing': {
+ 'topic': function () {
+ return _breakUp([[['list-style'], ['inside']]]);
+ },
+ 'has 3 components': function (components) {
+ assert.lengthOf(components, 3);
+ },
+ 'has border-top-style': function (components) {
+ assert.equal(components[0].name, 'list-style-type');
+ assert.deepEqual(components[0].value, [['__hack']]);
+ },
+ 'has border-right-style': function (components) {
+ assert.equal(components[1].name, 'list-style-position');
+ assert.deepEqual(components[1].value, [['inside']]);
+ },
+ 'has border-bottom-style': function (components) {
+ assert.equal(components[2].name, 'list-style-image');
+ assert.deepEqual(components[2].value, [['none']]);
+ }
+ }
+ },
+ 'multiple values': {
+ 'background': {
+ 'topic': function () {
+ return _breakUp([[['background'], ['__ESCAPED_URL_CLEAN_CSS0__'], ['#fff'], [','], ['url(image2.png)'], ['repeat'], ['no-repeat'], ['2px'], ['3px'], ['/'], ['50%'], ['60%'], ['fixed'], ['content-box'], ['content-box'], ['red']]]);
+ },
+ 'has 8 components': function (components) {
+ assert.lengthOf(components, 8);
+ },
+ 'has background-image': function (components) {
+ assert.deepEqual(components[0].name, 'background-image');
+ assert.deepEqual(components[0].value, [[['__ESCAPED_URL_CLEAN_CSS0__']], [['url(image2.png)']]]);
+ assert.isTrue(components[0].multiplex);
+ },
+ 'has background-position': function (components) {
+ assert.deepEqual(components[1].name, 'background-position');
+ assert.deepEqual(components[1].value, [[['0'], ['0']], [['2px'], ['3px']]]);
+ assert.isTrue(components[0].multiplex);
+ },
+ 'has background-size': function (components) {
+ assert.deepEqual(components[2].name, 'background-size');
+ assert.deepEqual(components[2].value, [[['auto']], [['50%'], ['60%']]]);
+ assert.isTrue(components[0].multiplex);
+ },
+ 'has background-repeat': function (components) {
+ assert.deepEqual(components[3].name, 'background-repeat');
+ assert.deepEqual(components[3].value, [[['repeat']], [['repeat'], ['no-repeat']]]);
+ assert.isTrue(components[0].multiplex);
+ },
+ 'has background-attachment': function (components) {
+ assert.deepEqual(components[4].name, 'background-attachment');
+ assert.deepEqual(components[4].value, [[['scroll']], [['fixed']]]);
+ assert.isTrue(components[0].multiplex);
+ },
+ 'has background-origin': function (components) {
+ assert.deepEqual(components[5].name, 'background-origin');
+ assert.deepEqual(components[5].value, [[['padding-box']], [['content-box']]]);
+ assert.isTrue(components[0].multiplex);
+ },
+ 'has background-clip': function (components) {
+ assert.deepEqual(components[6].name, 'background-clip');
+ assert.deepEqual(components[6].value, [[['border-box']], [['content-box']]]);
+ assert.isTrue(components[0].multiplex);
+ },
+ 'has background-color': function (components) {
+ assert.deepEqual(components[7].name, 'background-color');
+ assert.deepEqual(components[7].value, [[['#fff']], [['red']]]);
+ assert.isTrue(components[0].multiplex);
+ }
+ }
+ },
+ 'outline': {
+ 'inherit': {
+ 'topic': function () {
+ return _breakUp([[['outline'], ['inherit']]]);
+ },
+ 'has 3 components': function (components) {
+ assert.lengthOf(components, 3);
+ },
+ 'has outline-color': function (components) {
+ assert.deepEqual(components[0].name, 'outline-color');
+ assert.deepEqual(components[0].value, [['inherit']]);
+ },
+ 'has outline-style': function (components) {
+ assert.deepEqual(components[1].name, 'outline-style');
+ assert.deepEqual(components[1].value, [['inherit']]);
+ },
+ 'has outline-width': function (components) {
+ assert.deepEqual(components[2].name, 'outline-width');
+ assert.deepEqual(components[2].value, [['inherit']]);
+ }
+ },
+ '3 inherits': {
+ 'topic': function () {
+ return _breakUp([[['outline'], ['inherit'], ['inherit'], ['inherit']]]);
+ },
+ 'has 3 components': function (components) {
+ assert.lengthOf(components, 3);
+ },
+ 'has outline-color': function (components) {
+ assert.deepEqual(components[0].name, 'outline-color');
+ assert.deepEqual(components[0].value, [['inherit']]);
+ },
+ 'has outline-style': function (components) {
+ assert.deepEqual(components[1].name, 'outline-style');
+ assert.deepEqual(components[1].value, [['inherit']]);
+ },
+ 'has outline-width': function (components) {
+ assert.deepEqual(components[2].name, 'outline-width');
+ assert.deepEqual(components[2].value, [['inherit']]);
+ }
+ },
+ 'all values in correct order': {
+ 'topic': function () {
+ return _breakUp([[['outline'], ['red'], ['solid'], ['1px']]]);
+ },
+ 'has 3 components': function (components) {
+ assert.lengthOf(components, 3);
+ },
+ 'has outline-color': function (components) {
+ assert.deepEqual(components[0].name, 'outline-color');
+ assert.deepEqual(components[0].value, [['red']]);
+ },
+ 'has outline-style': function (components) {
+ assert.deepEqual(components[1].name, 'outline-style');
+ assert.deepEqual(components[1].value, [['solid']]);
+ },
+ 'has outline-width': function (components) {
+ assert.deepEqual(components[2].name, 'outline-width');
+ assert.deepEqual(components[2].value, [['1px']]);
+ }
+ },
+ 'all values in wrong order': {
+ 'topic': function () {
+ return _breakUp([[['outline'], ['1px'], ['dotted'], ['#fff']]]);
+ },
+ 'has 3 components': function (components) {
+ assert.lengthOf(components, 3);
+ },
+ 'has outline-color': function (components) {
+ assert.deepEqual(components[0].name, 'outline-color');
+ assert.deepEqual(components[0].value, [['#fff']]);
+ },
+ 'has outline-style': function (components) {
+ assert.deepEqual(components[1].name, 'outline-style');
+ assert.deepEqual(components[1].value, [['dotted']]);
+ },
+ 'has outline-width': function (components) {
+ assert.deepEqual(components[2].name, 'outline-width');
+ assert.deepEqual(components[2].value, [['1px']]);
+ }
+ },
+ 'missing values': {
+ 'topic': function () {
+ return _breakUp([[['outline'], ['solid']]]);
+ },
+ 'has 3 components': function (components) {
+ assert.lengthOf(components, 3);
+ },
+ 'has outline-color': function (components) {
+ assert.deepEqual(components[0].name, 'outline-color');
+ assert.deepEqual(components[0].value, [['invert']]);
+ },
+ 'has outline-style': function (components) {
+ assert.deepEqual(components[1].name, 'outline-style');
+ assert.deepEqual(components[1].value, [['solid']]);
+ },
+ 'has outline-width': function (components) {
+ assert.deepEqual(components[2].name, 'outline-width');
+ assert.deepEqual(components[2].value, [['medium']]);
+ }
+ },
+ 'default values': {
+ 'topic': function () {
+ return _breakUp([[['outline'], ['invert'], ['none'], ['medium']]]);
+ },
+ 'has 3 components': function (components) {
+ assert.lengthOf(components, 3);
+ },
+ 'has outline-color': function (components) {
+ assert.deepEqual(components[0].name, 'outline-color');
+ assert.deepEqual(components[0].value, [['invert']]);
+ },
+ 'has outline-style': function (components) {
+ assert.deepEqual(components[1].name, 'outline-style');
+ assert.deepEqual(components[1].value, [['none']]);
+ },
+ 'has outline-width': function (components) {
+ assert.deepEqual(components[2].name, 'outline-width');
+ assert.deepEqual(components[2].value, [['medium']]);
+ }
+ }
+ }
+ })
+ .export(module);
--- /dev/null
+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 addOptimizationMetadata = require('../../lib/selectors/optimization-metadata');
+
+function _optimize(source) {
+ var tokens = new Tokenizer({
+ options: {},
+ sourceTracker: new SourceTracker(),
+ warnings: []
+ }).toTokens(source);
+
+ addOptimizationMetadata(tokens);
+
+ var compatibility = new Compatibility().toOptions();
+ optimize(tokens[0][1], tokens[0][2], false, { compatibility: compatibility, aggressiveMerging: true, shorthandCompacting: true });
+
+ return tokens[0][2];
+}
+
+function longhandFirst(prefixedLonghand, prefixedShorthand, zeroValue) {
+ return {
+ 'topic': function () {
+ return _optimize('a{' + prefixedLonghand + ':inherit;' + prefixedShorthand + ':' + zeroValue + '}');
+ },
+ 'has one token': function (body) {
+ assert.lengthOf(body, 1);
+ },
+ 'has zero value only': function (body) {
+ assert.deepEqual(body[0][0], [prefixedShorthand, false, false]);
+ assert.deepEqual(body[0][1], [zeroValue]);
+ }
+ };
+}
+
+function shorthandFirst(prefixedLonghand, prefixedShorthand, zeroValue) {
+ return {
+ 'topic': function () {
+ return _optimize('a{' + prefixedShorthand + ':' + zeroValue + ';' + prefixedLonghand + ':inherit}');
+ },
+ 'has two tokens': function (body) {
+ assert.lengthOf(body, 2);
+ },
+ 'first is shorthand': function (body) {
+ assert.deepEqual(body[0][0], [prefixedShorthand, false, false]);
+ assert.deepEqual(body[0][1], [zeroValue]);
+ },
+ 'second is longhand': function (body) {
+ assert.deepEqual(body[1][0], [prefixedLonghand, false, false]);
+ assert.deepEqual(body[1][1], ['inherit']);
+ }
+ };
+}
+
+function overrideContext(longhands) {
+ var context = {};
+ var vendorPrefixes = ['', '-moz-', '-o-', '-webkit-']; // there is no -ms-animation nor -ms-transition.
+ var vendorPrefixesFor = ['animation', 'transition'];
+ var defaultValues = {
+ 'list-style-image': 'none',
+ 'background': '0 0'
+ };
+
+ for (var longhand in longhands) {
+ for (var i = 0; i < longhands[longhand].length; i++) {
+ var shorthand = longhands[longhand][i];
+ var prefixes = vendorPrefixesFor.indexOf(shorthand) > -1 ? vendorPrefixes : [''];
+
+ for (var j = 0, m = prefixes.length; j < m; j++) {
+ var prefixedLonghand = prefixes[j] + longhand;
+ var prefixedShorthand = prefixes[j] + shorthand;
+ var zeroValue = defaultValues[prefixedShorthand] || '0';
+
+ context['should override ' + prefixedLonghand + ' with ' + prefixedShorthand] = longhandFirst(prefixedLonghand, prefixedShorthand, zeroValue);
+ context['should not override ' + prefixedShorthand + ' shorthand with ' + prefixedLonghand] = shorthandFirst(prefixedLonghand, prefixedShorthand, zeroValue);
+ }
+ }
+ }
+
+ return context;
+}
+
+vows.describe(optimize)
+ .addBatch(
+ overrideContext({
+ 'animation-delay': ['animation'],
+ 'animation-direction': ['animation'],
+ 'animation-duration': ['animation'],
+ 'animation-fill-mode': ['animation'],
+ 'animation-iteration-count': ['animation'],
+ 'animation-name': ['animation'],
+ 'animation-play-state': ['animation'],
+ 'animation-timing-function': ['animation'],
+ 'background-attachment': ['background'],
+ 'background-clip': ['background'],
+ 'background-color': ['background'],
+ 'background-image': ['background'],
+ 'background-origin': ['background'],
+ 'background-position': ['background'],
+ 'background-repeat': ['background'],
+ 'background-size': ['background'],
+ 'border-color': ['border'],
+ 'border-style': ['border'],
+ 'border-width': ['border'],
+ 'border-bottom': ['border'],
+ 'border-bottom-color': ['border-bottom', 'border-color', 'border'],
+ 'border-bottom-style': ['border-bottom', 'border-style', 'border'],
+ 'border-bottom-width': ['border-bottom', 'border-width', 'border'],
+ 'border-left': ['border'],
+ 'border-left-color': ['border-left', 'border-color', 'border'],
+ 'border-left-style': ['border-left', 'border-style', 'border'],
+ 'border-left-width': ['border-left', 'border-width', 'border'],
+ 'border-right': ['border'],
+ 'border-right-color': ['border-right', 'border-color', 'border'],
+ 'border-right-style': ['border-right', 'border-style', 'border'],
+ 'border-right-width': ['border-right', 'border-width', 'border'],
+ 'border-top': ['border'],
+ 'border-top-color': ['border-top', 'border-color', 'border'],
+ 'border-top-style': ['border-top', 'border-style', 'border'],
+ 'border-top-width': ['border-top', 'border-width', 'border'],
+ 'font-family': ['font'],
+ 'font-size': ['font'],
+ 'font-style': ['font'],
+ 'font-variant': ['font'],
+ 'font-weight': ['font'],
+ 'list-style-image': ['list-style'],
+ 'list-style-position': ['list-style'],
+ 'list-style-type': ['list-style'],
+ 'margin-bottom': ['margin'],
+ 'margin-left': ['margin'],
+ 'margin-right': ['margin'],
+ 'margin-top': ['margin'],
+ 'outline-color': ['outline'],
+ 'outline-style': ['outline'],
+ 'outline-width': ['outline'],
+ 'padding-bottom': ['padding'],
+ 'padding-left': ['padding'],
+ 'padding-right': ['padding'],
+ 'padding-top': ['padding'],
+ 'transition-delay': ['transition'],
+ 'transition-duration': ['transition'],
+ 'transition-property': ['transition'],
+ 'transition-timing-function': ['transition']
+ })
+ )
+ .export(module);
--- /dev/null
+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 addOptimizationMetadata = require('../../lib/selectors/optimization-metadata');
+
+var compatibility = new Compatibility().toOptions();
+
+function _optimize(source, mergeAdjacent, aggressiveMerging) {
+ var tokens = new Tokenizer({
+ options: {},
+ sourceTracker: new SourceTracker(),
+ warnings: []
+ }).toTokens(source);
+
+ addOptimizationMetadata(tokens);
+ optimize(tokens[0][1], tokens[0][2], mergeAdjacent, { compatibility: compatibility, aggressiveMerging: aggressiveMerging });
+
+ return tokens[0][2];
+}
+
+vows.describe(optimize)
+ .addBatch({
+ 'of two adjacent properties': {
+ 'topic': 'a{display:-moz-inline-box;display:inline-block}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic, false, true), [
+ [['display', false, false], ['-moz-inline-box']],
+ [['display', false, false], ['inline-block']]
+ ]);
+ }
+ },
+ 'of two properties ': {
+ 'topic': 'a{display:inline-block;color:red;display:block}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic, false, true), [
+ [['color', false , false], ['red']],
+ [['display', false , false], ['block']]
+ ]);
+ }
+ },
+ 'of two properties where former is !important': {
+ 'topic': 'a{display:inline-block!important;color:red;display:block}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic, false, true), [
+ [['display', true , false], ['inline-block']],
+ [['color', false , false], ['red']]
+ ]);
+ }
+ },
+ 'of two properties where latter is !important': {
+ 'topic': 'a{display:inline-block;color:red;display:block!important}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic, false, true), [
+ [['color', false , false], ['red']],
+ [['display', true , false], ['block']]
+ ]);
+ }
+ },
+ 'of two properties where both are !important': {
+ 'topic': 'a{display:inline-block!important;color:red;display:block!important}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic, false, true), [
+ [['color', false , false], ['red']],
+ [['display', true , false], ['block']]
+ ]);
+ }
+ },
+ 'of many properties': {
+ 'topic': 'a{display:inline-block;color:red;font-weight:bolder;font-weight:700;display:block!important;color:#fff}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic, false, true), [
+ [['font-weight', false , false], ['bolder']],
+ [['font-weight', false , false], ['700']],
+ [['display', true , false], ['block']],
+ [['color', false , false], ['#fff']]
+ ]);
+ }
+ },
+ 'both redefined': {
+ 'topic': 'p{display:block;display:-moz-inline-box;color:red;display:table-cell}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic, false, true), [
+ [['color', false , false], ['red']],
+ [['display', false , false], ['table-cell']]
+ ]);
+ }
+ },
+ 'filter treated as background': {
+ 'topic': 'p{background:-moz-linear-gradient();background:-webkit-linear-gradient();filter:"progid:DXImageTransform";background:linear-gradient()}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic, false, true), [
+ [['background', false , false], ['-moz-linear-gradient()']],
+ [['background', false , false], ['-webkit-linear-gradient()']],
+ [['filter', false , false], ['"progid:DXImageTransform"']],
+ [['background', false , false], ['linear-gradient()']]
+ ]);
+ }
+ },
+ 'filter treated as background-image': {
+ 'topic': 'p{background-image:-moz-linear-gradient();background-image:-webkit-linear-gradient();filter:"progid:DXImageTransform";background-image:linear-gradient()}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic, false, true), [
+ [['background-image', false , false], ['-moz-linear-gradient()']],
+ [['background-image', false , false], ['-webkit-linear-gradient()']],
+ [['filter', false , false], ['"progid:DXImageTransform"']],
+ [['background-image', false , false], ['linear-gradient()']]
+ ]);
+ }
+ },
+ '-ms-filter treated as background': {
+ 'topic': 'p{background:-moz-linear-gradient();background:-webkit-linear-gradient();-ms-filter:"progid:DXImageTransform";background:linear-gradient()}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic, false, true), [
+ [['background', false , false], ['-moz-linear-gradient()']],
+ [['background', false , false], ['-webkit-linear-gradient()']],
+ [['-ms-filter', false , false], ['"progid:DXImageTransform"']],
+ [['background', false , false], ['linear-gradient()']]
+ ]);
+ }
+ },
+ '-ms-filter treated as background-image': {
+ 'topic': 'p{background-image:-moz-linear-gradient();background-image:-webkit-linear-gradient();-ms-filter:"progid:DXImageTransform";background-image:linear-gradient()}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic, false, true), [
+ [['background-image', false , false], ['-moz-linear-gradient()']],
+ [['background-image', false , false], ['-webkit-linear-gradient()']],
+ [['-ms-filter', false , false], ['"progid:DXImageTransform"']],
+ [['background-image', false , false], ['linear-gradient()']]
+ ]);
+ }
+ },
+ 'longhand then shorthand': {
+ 'topic': 'p{border-left-style:solid;border:1px dotted red}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic, false, true), [
+ [['border', false , false], ['1px'], ['dotted'], ['red']]
+ ]);
+ }
+ },
+ 'longhand then shorthand with important': {
+ 'topic': 'p{border-left-style:solid!important;border:1px dotted red}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic, false, true), [
+ [['border-left-style', true, false], ['solid']],
+ [['border', false , false], ['1px'], ['dotted'], ['red']]
+ ]);
+ }
+ },
+ 'shorthand then longhand': {
+ 'topic': 'p{background:url(image.png);background-image:#fff}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic, false, true), [
+ [['background', false , false], ['url(image.png)']],
+ [['background-image', false , false], ['#fff']]
+ ]);
+ }
+ }
+ })
+ .addBatch({
+ 'ie hacks - normal before hack': {
+ 'topic': 'p{color:red;display:none;color:#fff\\9}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic, false, true), [
+ [['color', false , false], ['red']],
+ [['display', false , false], ['none']],
+ [['color', false , true], ['#fff\\9']]
+ ]);
+ }
+ },
+ 'ie hacks - normal after hack': {
+ 'topic': 'p{color:red\\9;display:none;color:#fff}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic, false, true), [
+ [['color', false , true], ['red\\9']],
+ [['display', false , false], ['none']],
+ [['color', false , false], ['#fff']]
+ ]);
+ }
+ },
+ 'ie hacks - hack after hack': {
+ 'topic': 'p{color:red\\9;display:none;color:#fff\\9}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic, false, true), [
+ [['display', false , false], ['none']],
+ [['color', false , true], ['#fff\\9']]
+ ]);
+ }
+ }
+ })
+ .addBatch({
+ 'mergeAdjacent is true': {
+ 'topic': 'p{display:block;display:inline-block}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic, true, true), [
+ [['display', false , false], ['inline-block']]
+ ]);
+ }
+ },
+ 'mergeAdjacent is false': {
+ 'topic': 'p{display:block;display:inline-block}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic, false, true), [
+ [['display', false , false], ['block']],
+ [['display', false , false], ['inline-block']]
+ ]);
+ }
+ },
+ 'mergeAdjacent is an array with irrelevant join positions': {
+ 'topic': 'p{display:block;display:inline-block;color:red}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic, [2], true), [
+ [['display', false , false], ['block']],
+ [['display', false , false], ['inline-block']],
+ [['color', false , false], ['red']]
+ ]);
+ }
+ },
+ 'mergeAdjacent is an array with relevant join positions': {
+ 'topic': 'p{display:block;display:inline-block;color:red}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic, [1], true), [
+ [['display', false , false], ['inline-block']],
+ [['color', false , false], ['red']]
+ ]);
+ }
+ }
+ })
+ .addBatch({
+ 'aggressive off - (yet) not overriddable': {
+ 'topic': 'a{display:inline-block;color:red;display:-moz-block}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic, false, false), [
+ [['display', false , false], ['inline-block']],
+ [['color', false , false], ['red']],
+ [['display', false , false], ['-moz-block']]
+ ]);
+ }
+ }
+ })
+ .addBatch({
+ 'understandable - 2 properties, both !important, 2nd less understandable': {
+ 'topic': 'a{color:red!important;display:block;color:rgba(0,255,0,.5)!important}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic, false, true), [
+ [['color', true , false], ['red']],
+ [['display', false , false], ['block']],
+ [['color', true , false], ['rgba(0,255,0,.5)']]
+ ]);
+ }
+ },
+ 'understandable - 2 properties, both !important, 2nd more understandable': {
+ 'topic': 'a{color:rgba(0,255,0,.5)!important;display:block;color:red!important}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic, false, true), [
+ [['display', false , false], ['block']],
+ [['color', true , false], ['red']]
+ ]);
+ }
+ },
+ 'understandable - 2 adjacent properties, both !important, 2nd less understandable': {
+ 'topic': 'a{background:red!important;background:rgba(0,255,0,.5)!important}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic, false, true), [
+ [['background', true , false], ['red']],
+ [['background', true , false], ['rgba(0,255,0,.5)']]
+ ]);
+ }
+ },
+ 'understandable - 2 adjacent properties, both !important, 2nd more understandable': {
+ 'topic': 'a{background:rgba(0,255,0,.5)!important;background:red!important}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic, false, true), [
+ [['background', true , false], ['rgba(0,255,0,.5)']],
+ [['background', true , false], ['red']]
+ ]);
+ }
+ },
+ 'understandable - 2 adjacent -ms-transform with different values': {
+ 'topic': 'div{-ms-transform:translate(0,0);-ms-transform:translate3d(0,0,0)}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic, false, true), [
+ [['-ms-transform', false , false], ['translate(0,0)']],
+ [['-ms-transform', false , false], ['translate3d(0,0,0)']]
+ ]);
+ }
+ },
+ 'understandable - 2 non-adjacent -ms-transform with different values': {
+ 'topic': 'div{-ms-transform:translate(0,0);-webkit-transform:translate3d(0,0,0);-ms-transform:translate3d(0,0,0)}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic, false, true), [
+ [['-ms-transform', false , false], ['translate(0,0)']],
+ [['-webkit-transform', false , false], ['translate3d(0,0,0)']],
+ [['-ms-transform', false , false], ['translate3d(0,0,0)']]
+ ]);
+ }
+ },
+ 'understandable - 2 adjacent transform with different values': {
+ 'topic': 'div{transform:translate(0,0);transform:translate3d(0,0,0)}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic, false, true), [
+ [['transform', false , false], ['translate(0,0)']],
+ [['transform', false , false], ['translate3d(0,0,0)']]
+ ]);
+ }
+ },
+ 'understandable - 2 non-adjacent transform with different values': {
+ 'topic': 'div{transform:translate(0,0);-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic, false, true), [
+ [['transform', false , false], ['translate(0,0)']],
+ [['-webkit-transform', false , false], ['translate3d(0,0,0)']],
+ [['transform', false , false], ['translate3d(0,0,0)']]
+ ]);
+ }
+ },
+ 'understandable - border(hex) with border(rgba)': {
+ 'topic': 'a{border:1px solid #fff;border:1px solid rgba(1,0,0,.5)}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic, false, true), [
+ [['border', false , false], ['1px'], ['solid'], ['#fff']],
+ [['border', false , false], ['1px'], ['solid'], ['rgba(1,0,0,.5)']]
+ ]);
+ }
+ },
+ 'understandable - border(hex) with border(rgba !important)': {
+ 'topic': 'a{border:1px solid #fff;border:1px solid rgba(1,0,0,.5)!important}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic, false, true), [
+ [['border', false , false], ['1px'], ['solid'], ['#fff']],
+ [['border', true , false], ['1px'], ['solid'], ['rgba(1,0,0,.5)']]
+ ]);
+ }
+ },
+ 'understandable - border(hex !important) with border(hex)': {
+ 'topic': 'a{border:1px solid #fff!important;display:block;border:1px solid #fff}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic, false, true), [
+ [['border', true , false], ['1px'], ['solid'], ['#fff']],
+ [['display', false , false], ['block']]
+ ]);
+ }
+ },
+ 'understandable - border(hex) with border(hex !important)': {
+ 'topic': 'a{border:1px solid #fff;display:block;border:1px solid #fff!important}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic, false, true), [
+ [['display', false , false], ['block']],
+ [['border', true , false], ['1px'], ['solid'], ['#fff']]
+ ]);
+ }
+ },
+ 'understandable - unit with function with unit without one': {
+ 'topic': 'a{border-top-width:calc(100%);display:block;border-top-width:1px}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic, false, true), [
+ [['display', false , false], ['block']],
+ [['border-top-width', false , false], ['1px']]
+ ]);
+ }
+ },
+ 'understandable - unit without function with unit with one': {
+ 'topic': 'a{border-top-width:1px;display:block;border-top-width:calc(100%)}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic, false, true), [
+ [['border-top-width', false , false], ['1px']],
+ [['display', false , false], ['block']],
+ [['border-top-width', false , false], ['calc(100%)']]
+ ]);
+ }
+ }
+ })
+ .export(module);
--- /dev/null
+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 addOptimizationMetadata = require('../../lib/selectors/optimization-metadata');
+
+function _optimize(source, compatibility) {
+ var tokens = new Tokenizer({
+ options: {},
+ sourceTracker: new SourceTracker(),
+ warnings: []
+ }).toTokens(source);
+ compatibility = new Compatibility(compatibility).toOptions();
+
+ addOptimizationMetadata(tokens);
+ optimize(tokens[0][1], tokens[0][2], false, { compatibility: compatibility, shorthandCompacting: true });
+
+ return tokens[0][2];
+}
+
+vows.describe(optimize)
+ .addBatch({
+ 'longhand then shorthand': {
+ 'topic': 'p{background-image:none;background:__ESCAPED_URL_CLEAN_CSS0__}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic), [
+ [['background', false , false], ['__ESCAPED_URL_CLEAN_CSS0__']]
+ ]);
+ }
+ },
+ 'longhand then shorthand - important then non-important': {
+ 'topic': 'p{background-image:none!important;background:__ESCAPED_URL_CLEAN_CSS0__}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic), [
+ [['background-image', true , false], ['none']],
+ [['background', false , false], ['__ESCAPED_URL_CLEAN_CSS0__']]
+ ]);
+ }
+ },
+ 'longhand then shorthand - multiplex then simple': {
+ 'topic': 'p{background-repeat:no-repeat,no-repeat;background:__ESCAPED_URL_CLEAN_CSS0__}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic), [
+ [['background-repeat', false , false], ['no-repeat'], [','], ['no-repeat']],
+ [['background', false , false], ['__ESCAPED_URL_CLEAN_CSS0__']]
+ ]);
+ }
+ },
+ 'longhand then shorthand - simple then multiplex': {
+ 'topic': 'p{background-repeat:no-repeat;background:__ESCAPED_URL_CLEAN_CSS0__,__ESCAPED_URL_CLEAN_CSS1__}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic), [
+ [['background', false , false], ['__ESCAPED_URL_CLEAN_CSS0__'], [','], ['__ESCAPED_URL_CLEAN_CSS1__']]
+ ]);
+ }
+ },
+ 'shorthand then longhand': {
+ 'topic': 'p{background:__ESCAPED_URL_CLEAN_CSS0__ repeat;background-repeat:no-repeat}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic), [
+ [['background', false , false], ['__ESCAPED_URL_CLEAN_CSS0__'], ['no-repeat']]
+ ]);
+ }
+ },
+ 'shorthand then longhand - important then non-important': {
+ 'topic': 'p{background:__ESCAPED_URL_CLEAN_CSS0__ repeat!important;background-repeat:no-repeat}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic), [
+ [['background', true , false], ['__ESCAPED_URL_CLEAN_CSS0__'], ['no-repeat']]
+ ]);
+ }
+ },
+ 'shorthand then longhand - non-important then important': {
+ 'topic': 'p{background:__ESCAPED_URL_CLEAN_CSS0__ repeat;background-repeat:no-repeat!important}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic), [
+ [['background', false , false], ['__ESCAPED_URL_CLEAN_CSS0__']],
+ [['background-repeat', true , false], ['no-repeat']]
+ ]);
+ }
+ },
+ 'shorthand then longhand - multiple values': {
+ 'topic': 'p{background:__ESCAPED_URL_CLEAN_CSS0__,__ESCAPED_URL_CLEAN_CSS1__;background-repeat:no-repeat}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic), [
+ [['background', false , false], ['__ESCAPED_URL_CLEAN_CSS0__'], ['no-repeat'], [','], ['__ESCAPED_URL_CLEAN_CSS1__'], ['no-repeat']]
+ ]);
+ }
+ },
+ 'shorthand then longhand - single value then multi value': {
+ 'topic': 'p{background:__ESCAPED_URL_CLEAN_CSS0__;background-repeat:no-repeat,no-repeat}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic), [
+ [['background', false , false], ['__ESCAPED_URL_CLEAN_CSS0__']],
+ [['background-repeat', false , false], ['no-repeat'], [','], ['no-repeat']]
+ ]);
+ }
+ },
+ 'shorthand then longhand - disabled background size merging': {
+ 'topic': 'p{background:__ESCAPED_URL_CLEAN_CSS0__;background-size:50%}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic, { properties: { backgroundSizeMerging: false } }), [
+ [['background', false , false], ['__ESCAPED_URL_CLEAN_CSS0__']],
+ [['background-size', false , false], ['50%']]
+ ]);
+ }
+ },
+ 'shorthand then longhand - non mergeable value': {
+ 'topic': 'p{background:__ESCAPED_URL_CLEAN_CSS0__;background-color:none}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic, { properties: { backgroundSizeMerging: false } }), [
+ [['background', false , false], ['__ESCAPED_URL_CLEAN_CSS0__']],
+ [['background-color', false , false], ['none']]
+ ]);
+ }
+ },
+ 'shorthand then longhand - color into a function': {
+ 'topic': 'p{background:linear-gradient();background-color:red}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic, { properties: { backgroundSizeMerging: false } }), [
+ [['background', false , false], ['linear-gradient()'], ['red']]
+ ]);
+ }
+ },
+ 'shorthand then longhand - color into a color - with merging off': {
+ 'topic': 'p{background:white;background-color:red}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic, { properties: { merging: false } }), [
+ [['background', false , false], ['red']]
+ ]);
+ }
+ },
+ 'shorthand then longhand - color into a function - with merging off': {
+ 'topic': 'p{background:linear-gradient();background-color:red}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic, { properties: { merging: false } }), [
+ [['background', false , false], ['linear-gradient()']],
+ [['background-color', false , false], ['red']]
+ ]);
+ }
+ },
+ 'shorthand then shorthand - same values': {
+ 'topic': 'p{background:red;background:red}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic), [
+ [['background', false , false], ['red']]
+ ]);
+ }
+ },
+ 'shorthand then shorthand - same values with defaults': {
+ 'topic': 'p{background:repeat red;background:red}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic), [
+ [['background', false , false], ['red']]
+ ]);
+ }
+ },
+ 'shorthand then shorthand - with different functions': {
+ 'topic': 'p{background:linear-gradient();background:-webkit-gradient()}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic), [
+ [['background', false , false], ['linear-gradient()']],
+ [['background', false , false], ['-webkit-gradient()']]
+ ]);
+ }
+ },
+ 'shorthand then shorthand - with function and url': {
+ 'topic': 'p{background:linear-gradient();background:__ESCAPED_URL_CLEAN_CSS0__}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic), [
+ [['background', false , false], ['linear-gradient()']],
+ [['background', false , false], ['__ESCAPED_URL_CLEAN_CSS0__']]
+ ]);
+ }
+ },
+ 'shorthand then shorthand - important then non-important': {
+ 'topic': 'p{background:__ESCAPED_URL_CLEAN_CSS0__ no-repeat!important;background:__ESCAPED_URL_CLEAN_CSS1__ repeat red}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic), [
+ [['background', true , false], ['__ESCAPED_URL_CLEAN_CSS0__'], ['no-repeat']]
+ ]);
+ }
+ },
+ 'shorthand then shorthand - non-important then important': {
+ 'topic': 'p{background:__ESCAPED_URL_CLEAN_CSS0__ no-repeat;background:__ESCAPED_URL_CLEAN_CSS1__ repeat red!important}',
+ 'into': function (topic) {
+ assert.deepEqual(_optimize(topic), [
+ [['background', true , false], ['__ESCAPED_URL_CLEAN_CSS1__'], ['red']]
+ ]);
+ }
+ }
+
+ // 'aggressive off - overriddable': {
+ // 'topic': 'a{background:white;color:red;background:red}',
+ // 'into': function (topic) {
+ // assert.deepEqual(optimize(topic, false, false), [
+ // [['color', false , false], ['red']],
+ // [['background', false , false], ['red']]
+ // ]);
+ // }
+ // }
+ })
+ .export(module);
--- /dev/null
+var vows = require('vows');
+var assert = require('assert');
+
+var wrapForOptimizing = require('../../lib/properties/wrap-for-optimizing').all;
+var populateComponents = require('../../lib/properties/populate-components');
+
+vows.describe(populateComponents)
+ .addBatch({
+ 'shorthand': {
+ 'topic': function () {
+ var wrapped = wrapForOptimizing([[['margin', false, false], ['0px'], ['1px'], ['2px'], ['3px']]]);
+
+ populateComponents(wrapped);
+ return wrapped;
+ },
+ 'has one': function (wrapped) {
+ assert.lengthOf(wrapped, 1);
+ },
+ 'becomes shorthand': function (wrapped) {
+ assert.isTrue(wrapped[0].shorthand);
+ },
+ 'is dirty': function (wrapped) {
+ assert.isTrue(wrapped[0].dirty);
+ },
+ 'gets 4 components': function (wrapped) {
+ assert.lengthOf(wrapped[0].components, 4);
+ },
+ 'gets a margin-top': function (wrapped) {
+ assert.deepEqual(wrapped[0].components[0].name, 'margin-top');
+ assert.deepEqual(wrapped[0].components[0].value, [['0px']]);
+ },
+ 'gets a margin-right': function (wrapped) {
+ assert.deepEqual(wrapped[0].components[1].name, 'margin-right');
+ assert.deepEqual(wrapped[0].components[1].value, [['1px']]);
+ },
+ 'gets a margin-bottom': function (wrapped) {
+ assert.deepEqual(wrapped[0].components[2].name, 'margin-bottom');
+ assert.deepEqual(wrapped[0].components[2].value, [['2px']]);
+ },
+ 'gets a margin-left': function (wrapped) {
+ assert.deepEqual(wrapped[0].components[3].name, 'margin-left');
+ assert.deepEqual(wrapped[0].components[3].value, [['3px']]);
+ }
+ },
+ 'longhand': {
+ 'topic': function () {
+ var wrapped = wrapForOptimizing([[['margin-top', false, false], ['0px']]]);
+
+ populateComponents(wrapped);
+ return wrapped;
+ },
+ 'has one': function (wrapped) {
+ assert.lengthOf(wrapped, 1);
+ },
+ 'gets no components': function (wrapped) {
+ assert.isEmpty(wrapped[0].components);
+ }
+ },
+ 'no value': {
+ 'topic': function () {
+ var wrapped = wrapForOptimizing([[['margin', false, false]]]);
+
+ populateComponents(wrapped);
+ return wrapped;
+ },
+ 'has one': function (wrapped) {
+ assert.lengthOf(wrapped, 1);
+ },
+ 'is unused': function (wrapped) {
+ assert.isTrue(wrapped[0].unused);
+ }
+ }
+ })
+ .export(module);
--- /dev/null
+var vows = require('vows');
+var assert = require('assert');
+
+var removeUnused = require('../../lib/properties/remove-unused');
+var wrapForOptimizing = require('../../lib/properties/wrap-for-optimizing').all;
+
+vows.describe(removeUnused)
+ .addBatch({
+ 'it removes unused only': {
+ 'topic': function () {
+ var properties = [
+ [['background'], ['none']],
+ [['color'], ['red']]
+ ];
+ var _properties = wrapForOptimizing(properties);
+ _properties[0].unused = true;
+
+ removeUnused(_properties);
+ return properties;
+ },
+ 'it has one property left': function (properties) {
+ assert.lengthOf(properties, 1);
+ assert.equal(properties[0][0], 'color');
+ }
+ }
+ })
+ .export(module);
+++ /dev/null
-var vows = require('vows');
-var assert = require('assert');
-
-var SelectorTokenizer = require('../../lib/selectors/tokenizer');
-var extractProperties = require('../../lib/properties/extractor');
-var canReorder = require('../../lib/properties/reorderable').canReorder;
-var canReorderSingle = require('../../lib/properties/reorderable').canReorderSingle;
-
-function propertiesIn(source) {
- return extractProperties(new SelectorTokenizer({ options: {} }, false).toTokens(source)[0]);
-}
-
-vows.describe(canReorder)
- .addBatch({
- 'empty': {
- 'topic': canReorder(propertiesIn('a{}'), propertiesIn('a{}')),
- 'must be true': function (result) { assert.isTrue(result); }
- },
- 'left empty': {
- 'topic': canReorder(propertiesIn('a{}'), propertiesIn('a{color:red}')),
- 'must be true': function (result) { assert.isTrue(result); }
- },
- 'right empty': {
- 'topic': canReorder(propertiesIn('a{color:red}'), propertiesIn('a{}')),
- 'must be true': function (result) { assert.isTrue(result); }
- },
- 'all reorderable': {
- 'topic': canReorder(propertiesIn('a{color:red;width:100%}'), propertiesIn('a{display:block;height:20px}')),
- 'must be true': function (result) { assert.isTrue(result); }
- },
- 'one not reorderable on the left': {
- 'topic': canReorder(propertiesIn('a{color:red;width:100%;display:inline}'), propertiesIn('a{display:block;height:20px}')),
- 'must be false': function (result) { assert.isFalse(result); }
- },
- 'one not reorderable on the right': {
- 'topic': canReorder(propertiesIn('a{color:red;width:100%}'), propertiesIn('a{display:block;height:20px;width:20px}')),
- 'must be false': function (result) { assert.isFalse(result); }
- }
- })
- .export(module);
-
-vows.describe(canReorderSingle)
- .addBatch({
- 'different properties': {
- 'topic': canReorderSingle(propertiesIn('a{color:red}')[0], propertiesIn('a{display:block}')[0]),
- 'must be true': function (result) { assert.isTrue(result); }
- },
- 'font and line-height': {
- 'topic': canReorderSingle(propertiesIn('a{font:10px}')[0], propertiesIn('a{line-height:12px}')[0]),
- 'must be false': function (result) { assert.isFalse(result); }
- },
- 'same properties with same value': {
- 'topic': canReorderSingle(propertiesIn('a{color:red}')[0], propertiesIn('a{color:red}')[0]),
- 'must be true': function (result) { assert.isTrue(result); }
- },
- 'same properties with same value and different case': {
- 'topic': canReorderSingle(propertiesIn('a{COLOR:red}')[0], propertiesIn('a{color:red}')[0]),
- 'must be true': function (result) { assert.isTrue(result); }
- },
- 'same properties with different value': {
- 'topic': canReorderSingle(propertiesIn('a{color:red}')[0], propertiesIn('a{color:blue}')[0]),
- 'must be false': function (result) { assert.isFalse(result); }
- },
- 'same properties with different value and different case': {
- 'topic': canReorderSingle(propertiesIn('a{color:red}')[0], propertiesIn('a{COLOR:blue}')[0]),
- 'must be false': function (result) { assert.isFalse(result); }
- },
- 'different properties with same root': {
- 'topic': canReorderSingle(propertiesIn('a{text-shadow:none}')[0], propertiesIn('a{text-decoration:underline}')[0]),
- 'must be true': function (result) { assert.isTrue(result); }
- },
- 'different properties with same root when shorthand does not reset': {
- 'topic': canReorderSingle(propertiesIn('a{border:none}')[0], propertiesIn('a{border-spacing:1px}')[0]),
- 'must be true': function (result) { assert.isTrue(result); }
- },
- 'shorhand and longhand with different value': {
- 'topic': canReorderSingle(propertiesIn('a{margin:3px}')[0], propertiesIn('a{margin-bottom:5px}')[0]),
- 'must be false': function (result) { assert.isFalse(result); }
- },
- 'shorhand and longhand with same value': {
- 'topic': canReorderSingle(propertiesIn('a{margin:3px}')[0], propertiesIn('a{margin-bottom:3px}')[0]),
- 'must be false': function (result) { assert.isTrue(result); }
- },
- 'two longhand with different value sharing same shorthand': {
- 'topic': canReorderSingle(propertiesIn('a{margin-top:3px solid red}')[0], propertiesIn('a{margin-bottom:3px solid white}')[0]),
- 'must be true': function (result) { assert.isTrue(result); }
- },
- 'different, non-overlapping simple selectors': {
- 'topic': canReorderSingle(propertiesIn('a{border:none}')[0], propertiesIn('div{border:1px solid #f00}')[0]),
- 'must be true': function (result) { assert.isTrue(result); }
- },
- 'different, non-overlapping complex selectors': {
- 'topic': canReorderSingle(propertiesIn('.one{border:none}')[0], propertiesIn('div{border:1px solid #f00}')[0]),
- 'must be false': function (result) { assert.isFalse(result); }
- },
- 'different, overlapping simple selectors': {
- 'topic': canReorderSingle(propertiesIn('a{border:none}')[0], propertiesIn('a{border:1px solid #f00}')[0]),
- 'must be false': function (result) { assert.isFalse(result); }
- },
- 'align-items': {
- 'topic': canReorderSingle(propertiesIn('a{border:none}')[0], propertiesIn('a{align-items:flex-start}')[0]),
- 'must be true': function (result) { assert.isTrue(result); }
- }
- })
- .addBatch({
- 'flex #1': {
- 'topic': canReorderSingle(propertiesIn('a{-webkit-box-align:flex-start}')[0], propertiesIn('a{align-items:flex-start}')[0]),
- 'must be false': function (result) { assert.isFalse(result); }
- },
- 'flex #2': {
- 'topic': canReorderSingle(propertiesIn('a{-ms-flex-align:start}')[0], propertiesIn('a{align-items:flex-start}')[0]),
- 'must be false': function (result) { assert.isFalse(result); }
- },
- 'flex #3': {
- 'topic': canReorderSingle(propertiesIn('a{flex:none}')[0], propertiesIn('a{align-items:flex-start}')[0]),
- 'must be false': function (result) { assert.isFalse(result); }
- },
- 'flex #4': {
- 'topic': canReorderSingle(propertiesIn('a{justify-content:center}')[0], propertiesIn('a{–ms-flex-pack:center}')[0]),
- 'must be false': function (result) { assert.isFalse(result); }
- },
- 'flex #5': {
- 'topic': canReorderSingle(propertiesIn('a{justify-content:center}')[0], propertiesIn('a{–webkit-box-pack:center}')[0]),
- 'must be false': function (result) { assert.isFalse(result); }
- }
- })
- .export(module);
--- /dev/null
+var vows = require('vows');
+var assert = require('assert');
+
+var wrapForOptimizing = require('../../lib/properties/wrap-for-optimizing').all;
+var populateComponents = require('../../lib/properties/populate-components');
+
+var restoreShorthands = require('../../lib/properties/restore-shorthands');
+
+vows.describe(restoreShorthands)
+ .addBatch({
+ 'longhands': {
+ 'topic': function () {
+ var properties = ['/*comment */', [['margin-top', false, false], ['0']]];
+ var _properties = wrapForOptimizing(properties);
+ populateComponents(_properties);
+ restoreShorthands(_properties);
+
+ return properties;
+ },
+ 'is same as source': function (properties) {
+ assert.deepEqual(properties, ['/*comment */', [['margin-top', false, false], ['0']]]);
+ }
+ },
+ 'shorthands': {
+ 'topic': function () {
+ var properties = ['/*comment */', [['background', false, false], ['url(image.png)']]];
+ var _properties = wrapForOptimizing(properties);
+ populateComponents(_properties);
+
+ properties[1].pop();
+ _properties[0].dirty = true;
+
+ restoreShorthands(_properties);
+ return properties;
+ },
+ 'is same as source': function (properties) {
+ assert.deepEqual(properties, ['/*comment */', [['background', false, false], ['url(image.png)']]]);
+ }
+ }
+ })
+ .export(module);
--- /dev/null
+var vows = require('vows');
+var assert = require('assert');
+
+var wrapForOptimizing = require('../../lib/properties/wrap-for-optimizing').single;
+var compactable = require('../../lib/properties/compactable');
+
+var restore = require('../../lib/properties/restore');
+
+function _breakUp(property) {
+ var descriptor = compactable[property[0][0]];
+ var _property = wrapForOptimizing(property);
+ _property.components = descriptor.breakUp(_property, compactable);
+ _property.multiplex = Array.isArray(_property.components[0].value[0][0]);
+ return _property;
+}
+
+function _restore(_property) {
+ var descriptor = compactable[_property.name];
+ return descriptor.restore(_property, compactable);
+}
+
+vows.describe(restore)
+ .addBatch({
+ 'background': {
+ 'background with some values': {
+ 'topic': function () {
+ return _restore(_breakUp([['background'], ['__ESCAPED_URL_CLEAN_CSS0__'], ['no-repeat'], ['padding-box']]));
+ },
+ 'gives right value back': function (restoredValue) {
+ assert.deepEqual(restoredValue, [['__ESCAPED_URL_CLEAN_CSS0__'], ['no-repeat'], ['padding-box']]);
+ }
+ },
+ 'background with some default values': {
+ 'topic': function () {
+ return _restore(_breakUp([['background'], ['__ESCAPED_URL_CLEAN_CSS0__'], ['repeat'], ['padding-box'], ['border-box']]));
+ },
+ 'gives right value back': function (restoredValue) {
+ assert.deepEqual(restoredValue, [['__ESCAPED_URL_CLEAN_CSS0__']]);
+ }
+ },
+ 'background with all default values': {
+ 'topic': function () {
+ return _restore(_breakUp([['background'], ['transparent'], ['none'], ['repeat'], ['scroll'], ['0'], ['0'], ['padding-box'], ['border-box']]));
+ },
+ 'gives right value back': function (restoredValue) {
+ assert.deepEqual(restoredValue, [['0 0']]);
+ }
+ },
+ 'background with some double values': {
+ 'topic': function () {
+ return _restore(_breakUp([['background'], ['__ESCAPED_URL_CLEAN_CSS0__'], ['repeat'], ['no-repeat'], ['2px'], ['3px'], ['/'], ['auto'], ['padding-box']]));
+ },
+ 'gives right value back': function (restoredValue) {
+ assert.deepEqual(restoredValue, [['__ESCAPED_URL_CLEAN_CSS0__'], ['2px'], ['3px'], ['repeat'], ['no-repeat'], ['padding-box']]);
+ }
+ },
+ 'background with default background origin and background clip': {
+ 'topic': function () {
+ return _restore(_breakUp([['background'], ['__ESCAPED_URL_CLEAN_CSS0__'], ['padding-box'], ['border-box']]));
+ },
+ 'gives right value back': function (restoredValue) {
+ assert.deepEqual(restoredValue, [['__ESCAPED_URL_CLEAN_CSS0__']]);
+ }
+ },
+ 'background with same background origin and background clip': {
+ 'topic': function () {
+ return _restore(_breakUp([['background'], ['__ESCAPED_URL_CLEAN_CSS0__'], ['padding-box'], ['padding-box']]));
+ },
+ 'gives right value back': function (restoredValue) {
+ assert.deepEqual(restoredValue, [['__ESCAPED_URL_CLEAN_CSS0__'], ['padding-box']]);
+ }
+ },
+ 'background with default background position and background size': {
+ 'topic': function () {
+ return _restore(_breakUp([['background'], ['__ESCAPED_URL_CLEAN_CSS0__'], ['0'], ['0'], ['/'], ['50%'], ['25%']]));
+ },
+ 'gives right value back': function (restoredValue) {
+ assert.deepEqual(restoredValue, [['__ESCAPED_URL_CLEAN_CSS0__'], ['0'], ['0'], ['/'], ['50%'], ['25%']]);
+ }
+ },
+ 'background with default background position and single background size': {
+ 'topic': function () {
+ return _restore(_breakUp([['background'], ['__ESCAPED_URL_CLEAN_CSS0__'], ['0'], ['0'], ['/'], ['50%']]));
+ },
+ 'gives right value back': function (restoredValue) {
+ assert.deepEqual(restoredValue, [['__ESCAPED_URL_CLEAN_CSS0__'], ['0'], ['0'], ['/'], ['50%']]);
+ }
+ },
+ 'background with default background position and background size differing by 2nd value': {
+ 'topic': function () {
+ return _restore(_breakUp([['background'], ['__ESCAPED_URL_CLEAN_CSS0__'], ['0'], ['50px'], ['/'], ['0'], ['30px']]));
+ },
+ 'gives right value back': function (restoredValue) {
+ assert.deepEqual(restoredValue, [['__ESCAPED_URL_CLEAN_CSS0__'], ['0'], ['50px'], ['/'], ['0'], ['30px']]);
+ }
+ },
+ 'background 0 to background 0': {
+ 'topic': function () {
+ return _restore(_breakUp([['background'], ['0']]));
+ },
+ 'gives right value back': function (restoredValue) {
+ assert.deepEqual(restoredValue, [['0']]);
+ }
+ },
+ },
+ 'border radius': {
+ '4 values': {
+ 'topic': function () {
+ return _restore(_breakUp([['border-radius'], ['0px'], ['1px'], ['2px'], ['3px']]));
+ },
+ 'gives right value back': function (restoredValue) {
+ assert.deepEqual(restoredValue, [['0px'], ['1px'], ['2px'], ['3px']]);
+ }
+ },
+ '3 values': {
+ 'topic': function () {
+ return _restore(_breakUp([['border-radius'], ['0px'], ['1px'], ['2px'], ['1px']]));
+ },
+ 'gives right value back': function (restoredValue) {
+ assert.deepEqual(restoredValue, [['0px'], ['1px'], ['2px']]);
+ }
+ },
+ '2 values': {
+ 'topic': function () {
+ return _restore(_breakUp([['border-radius'], ['0px'], ['1px'], ['0px'], ['1px']]));
+ },
+ 'gives right value back': function (restoredValue) {
+ assert.deepEqual(restoredValue, [['0px'], ['1px']]);
+ }
+ },
+ '1 value': {
+ 'topic': function () {
+ return _restore(_breakUp([['border-radius'], ['0px'], ['0px'], ['0px'], ['0px']]));
+ },
+ 'gives right value back': function (restoredValue) {
+ assert.deepEqual(restoredValue, [['0px']]);
+ }
+ },
+ 'horizontal + vertical - different values': {
+ 'topic': function () {
+ return _restore(_breakUp([['border-radius'], ['0px'], ['1px'], ['2px'], ['3px'], ['/'], ['2px'], ['1px'], ['2px'], ['1px']]));
+ },
+ 'gives right value back': function (restoredValue) {
+ assert.deepEqual(restoredValue, [['0px'], ['1px'], ['2px'], ['3px'], ['/'], ['2px'], ['1px']]);
+ }
+ },
+ 'horizontal + vertical - same values': {
+ 'topic': function () {
+ return _restore(_breakUp([['border-radius'], ['0px'], ['1px'], ['2px'], ['3px'], ['/'], ['0px'], ['1px'], ['2px'], ['3px']]));
+ },
+ 'gives right value back': function (restoredValue) {
+ assert.deepEqual(restoredValue, [['0px'], ['1px'], ['2px'], ['3px']]);
+ }
+ },
+ 'horizontal + vertical - asymetrical': {
+ 'topic': function () {
+ return _restore(_breakUp([['border-radius'], ['0px'], ['1px'], ['2px'], ['3px'], ['/'], ['0px'], ['1px']]));
+ },
+ 'gives right value back': function (restoredValue) {
+ assert.deepEqual(restoredValue, [['0px'], ['1px'], ['2px'], ['3px'], ['/'], ['0px'], ['1px']]);
+ }
+ }
+ },
+ 'four values': {
+ '4 different': {
+ 'topic': function () {
+ return _restore(_breakUp([['padding'], ['0px'], ['1px'], ['2px'], ['3px']]));
+ },
+ 'gives right value back': function (restoredValue) {
+ assert.deepEqual(restoredValue, [['0px'], ['1px'], ['2px'], ['3px']]);
+ }
+ },
+ '3 different': {
+ 'topic': function () {
+ return _restore(_breakUp([['padding'], ['0px'], ['1px'], ['2px'], ['1px']]));
+ },
+ 'gives right value back': function (restoredValue) {
+ assert.deepEqual(restoredValue, [['0px'], ['1px'], ['2px']]);
+ }
+ },
+ '2 different': {
+ 'topic': function () {
+ return _restore(_breakUp([['padding'], ['0px'], ['1px'], ['0px'], ['1px']]));
+ },
+ 'gives right value back': function (restoredValue) {
+ assert.deepEqual(restoredValue, [['0px'], ['1px']]);
+ }
+ },
+ 'all same': {
+ 'topic': function () {
+ return _restore(_breakUp([['padding'], ['0px'], ['0px'], ['0px'], ['0px']]));
+ },
+ 'gives right value back': function (restoredValue) {
+ assert.deepEqual(restoredValue, [['0px']]);
+ }
+ }
+ },
+ 'repeated values': {
+ 'background with some values': {
+ 'topic': function () {
+ return _restore(_breakUp([['background'], ['__ESCAPED_URL_CLEAN_CSS0__'], ['no-repeat'], ['padding-box'], [','], ['repeat'], ['red']]));
+ },
+ 'gives right value back': function (restoredValue) {
+ assert.deepEqual(restoredValue, [['__ESCAPED_URL_CLEAN_CSS0__'], ['no-repeat'], ['padding-box'], [','], ['red']]);
+ }
+ },
+ 'background with background origin and size': {
+ 'topic': function () {
+ return _restore(_breakUp([['background'], ['no-repeat'], ['padding-box'], [','], ['repeat'], ['10px'], ['10px'], ['/'], ['auto'], ['red'], [','], ['top'], ['left'], ['/'], ['30%']]));
+ },
+ 'gives right value back': function (restoredValue) {
+ assert.deepEqual(restoredValue, [['no-repeat'], ['padding-box'], [','], ['10px'], ['10px'], ['red'], [','], ['top'], ['left'], ['/'], ['30%']]);
+ }
+ }
+ },
+ 'without defaults': {
+ 'border with some values': {
+ 'topic': function () {
+ return _restore(_breakUp([['border'], ['solid']]));
+ },
+ 'gives right value back': function (restoredValue) {
+ assert.deepEqual(restoredValue, [['solid']]);
+ }
+ },
+ 'border with all values': {
+ 'topic': function () {
+ return _restore(_breakUp([['border'], ['1px'], ['solid'], ['red']]));
+ },
+ 'gives right value back': function (restoredValue) {
+ assert.deepEqual(restoredValue, [['1px'], ['solid'], ['red']]);
+ }
+ },
+ 'border with all defaults': {
+ 'topic': function () {
+ return _restore(_breakUp([['border'], ['medium'], ['none'], ['none']]));
+ },
+ 'gives right value back': function (restoredValue) {
+ assert.deepEqual(restoredValue, [['none']]);
+ }
+ },
+ 'list with some values': {
+ 'topic': function () {
+ return _restore(_breakUp([['list-style'], ['circle']]));
+ },
+ 'gives right value back': function (restoredValue) {
+ assert.deepEqual(restoredValue, [['circle']]);
+ }
+ },
+ 'list with all values': {
+ 'topic': function () {
+ return _restore(_breakUp([['list-style'], ['circle'], ['inside'], ['url()']]));
+ },
+ 'gives right value back': function (restoredValue) {
+ assert.deepEqual(restoredValue, [['circle'], ['inside'], ['url()']]);
+ }
+ },
+ 'list with some defaults': {
+ 'topic': function () {
+ return _restore(_breakUp([['list-style'], ['circle'], ['outside'], ['__ESCAPED_URL_CLEAN_CSS0__']]));
+ },
+ 'gives right value back': function (restoredValue) {
+ assert.deepEqual(restoredValue, [['circle'], ['__ESCAPED_URL_CLEAN_CSS0__']]);
+ }
+ },
+ 'outline with some values': {
+ 'topic': function () {
+ return _restore(_breakUp([['outline'], ['dotted']]));
+ },
+ 'gives right value back': function (restoredValue) {
+ assert.deepEqual(restoredValue, [['dotted']]);
+ }
+ },
+ 'outline with all values': {
+ 'topic': function () {
+ return _restore(_breakUp([['outline'], ['#fff'], ['dotted'], ['1px']]));
+ },
+ 'gives right value back': function (restoredValue) {
+ assert.deepEqual(restoredValue, [['#fff'], ['dotted'], ['1px']]);
+ }
+ },
+ 'outline with all defaults': {
+ 'topic': function () {
+ return _restore(_breakUp([['outline'], ['invert'], ['none'], ['medium']]));
+ },
+ 'gives right value back': function (restoredValue) {
+ assert.deepEqual(restoredValue, [['0']]);
+ }
+ }
+ }
+ })
+ .export(module);
--- /dev/null
+// '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}'
+// ]
+// }),
--- /dev/null
+var vows = require('vows');
+var assert = require('assert');
+
+var wrapForOptimizing = require('../../lib/properties/wrap-for-optimizing').all;
+
+vows.describe(wrapForOptimizing)
+ .addBatch({
+ 'one': {
+ 'topic': function () {
+ return wrapForOptimizing([[['margin', false, false], ['0'], ['0']]]);
+ },
+ 'has one wrap': function (wrapped) {
+ assert.lengthOf(wrapped, 1);
+ },
+ 'has name': function (wrapped) {
+ assert.deepEqual(wrapped[0].name, 'margin');
+ },
+ 'has value': function (wrapped) {
+ assert.deepEqual(wrapped[0].value, [['0'], ['0']]);
+ },
+ 'has no components': function (wrapped) {
+ assert.lengthOf(wrapped[0].components, 0);
+ },
+ 'is not important': function (wrapped) {
+ assert.isFalse(wrapped[0].important);
+ },
+ 'is not dirty': function (wrapped) {
+ assert.isFalse(wrapped[0].dirty);
+ },
+ 'is not a shorthand': function (wrapped) {
+ assert.isFalse(wrapped[0].shorthand);
+ },
+ 'is not irrelevant': function (wrapped) {
+ assert.isFalse(wrapped[0].irrelevant);
+ },
+ 'is real': function (wrapped) {
+ assert.isTrue(wrapped[0].real);
+ },
+ 'is unused': function (wrapped) {
+ assert.isFalse(wrapped[0].unused);
+ },
+ 'is hack': function (wrapped) {
+ assert.isFalse(wrapped[0].hack);
+ },
+ 'is multiplex': function (wrapped) {
+ assert.isFalse(wrapped[0].multiplex);
+ }
+ },
+ 'two': {
+ 'topic': function () {
+ return wrapForOptimizing([[['margin', false, false], ['0'], ['0']], [['color', true, true], ['red']]]);
+ },
+ 'has two wraps': function (wrapped) {
+ assert.lengthOf(wrapped, 2);
+ },
+ 'marks second as important': function (wrapped) {
+ assert.isTrue(wrapped[1].important);
+ },
+ 'marks second as a hack': function (wrapped) {
+ assert.isTrue(wrapped[1].hack);
+ }
+ },
+ 'with comments': {
+ 'topic': function () {
+ return wrapForOptimizing([['/* comment */'], [['color', true, true], ['red']]]);
+ },
+ 'has one wrap': function (wrapped) {
+ assert.lengthOf(wrapped, 1);
+ },
+ 'sets position correctly': function (wrapped) {
+ assert.equal(wrapped[0].position, 1);
+ }
+ },
+ 'longhand': {
+ 'topic': function () {
+ return wrapForOptimizing([[['border-radius-top-left', false, false], ['1px'], ['/'], ['2px']]]);
+ },
+ 'has one wrap': function (wrapped) {
+ assert.lengthOf(wrapped, 1);
+ },
+ 'has name': function (wrapped) {
+ assert.deepEqual(wrapped[0].name, 'border-radius-top-left');
+ },
+ 'has value': function (wrapped) {
+ assert.deepEqual(wrapped[0].value, [['1px'], ['/'], ['2px']]);
+ },
+ 'is multiplex': function (wrapped) {
+ assert.isTrue(wrapped[0].multiplex);
+ }
+ },
+ 'without value': {
+ 'topic': function () {
+ return wrapForOptimizing([[['margin', false, false]]]);
+ },
+ 'has one wrap': function (wrapped) {
+ assert.lengthOf(wrapped, 1);
+ },
+ 'is unused': function (wrapped) {
+ assert.isTrue(wrapped[0].unused);
+ }
+ }
+ })
+ .export(module);
var vows = require('vows');
var assert = require('assert');
var SelectorTokenizer = require('../../lib/selectors/tokenizer');
-var extractor = require('../../lib/properties/extractor');
+var extractor = require('../../lib/selectors/extractor');
function buildToken(source) {
- return new SelectorTokenizer({ options: {} }, false).toTokens(source)[0];
+ return new SelectorTokenizer({ options: {} }).toTokens(source)[0];
}
vows.describe(extractor)
'one property': {
'topic': extractor(buildToken('a{color:red}')),
'has no properties': function (tokens) {
- assert.deepEqual(tokens, [['color', 'red', 'color', ['color:red'], [['a']], true]]);
+ assert.deepEqual(tokens, [['color', 'red', 'color', [['color'], ['red']], 'color:red', [['a']], true]]);
+ }
+ },
+ 'one important property': {
+ 'topic': extractor(buildToken('a{color:red!important}')),
+ 'has no properties': function (tokens) {
+ assert.deepEqual(tokens, [['color', 'red!important', 'color', [['color'], ['red!important']], 'color:red!important', [['a']], true]]);
}
},
'one property - complex selector': {
'topic': extractor(buildToken('.one{color:red}')),
'has no properties': function (tokens) {
- assert.deepEqual(tokens, [['color', 'red', 'color', ['color:red'], [['.one']], false]]);
+ assert.deepEqual(tokens, [['color', 'red', 'color', [['color'], ['red']], 'color:red', [['.one']], false]]);
}
},
'two properties': {
'topic': extractor(buildToken('a{color:red;display:block}')),
'has no properties': function (tokens) {
assert.deepEqual(tokens, [
- ['color', 'red', 'color', ['color:red'], [['a']], true],
- ['display', 'block', 'display', ['display:block'], [['a']], true]
+ ['color', 'red', 'color', [['color'], ['red']], 'color:red', [['a']], true],
+ ['display', 'block', 'display', [['display'], ['block']], 'display:block', [['a']], true]
]);
}
},
'topic': extractor(buildToken('@media{a{color:red;display:block}p{color:red}}')),
'has no properties': function (tokens) {
assert.deepEqual(tokens, [
- ['color', 'red', 'color', ['color:red'], [['a']], true],
- ['display', 'block', 'display', ['display:block'], [['a']], true],
- ['color', 'red', 'color', ['color:red'], [['p']], true]
+ ['color', 'red', 'color', [['color'], ['red']], 'color:red', [['a']], true],
+ ['display', 'block', 'display', [['display'], ['block']], 'display:block', [['a']], true],
+ ['color', 'red', 'color', [['color'], ['red']], 'color:red', [['p']], true]
+ ]);
+ }
+ },
+ 'with source map info': {
+ 'topic': extractor(['selector', [['a', 1, 0, undefined]], [[['color', false, false, 1, 3, undefined], ['red', 1, 9, undefined]]]]),
+ 'has one property': function (tokens) {
+ assert.deepEqual(tokens, [
+ ['color', 'red', 'color', [['color', false, false, 1, 3, undefined], ['red', 1, 9, undefined]], 'color:red', [['a', 1, 0, undefined]], true],
]);
}
}
'vendor prefix': {
'topic': extractor(buildToken('a{-moz-transform:none}')),
'has no properties': function (tokens) {
- assert.deepEqual(tokens, [['-moz-transform', 'none', 'transform', ['-moz-transform:none'], [['a']], true]]);
+ assert.deepEqual(tokens, [['-moz-transform', 'none', 'transform', [['-moz-transform'], ['none']], '-moz-transform:none', [['a']], true]]);
}
},
'list-style': {
'topic': extractor(buildToken('a{list-style:none}')),
'has no properties': function (tokens) {
- assert.deepEqual(tokens, [['list-style', 'none', 'list-style', ['list-style:none'], [['a']], true]]);
+ assert.deepEqual(tokens, [['list-style', 'none', 'list-style', [['list-style'], ['none']], 'list-style:none', [['a']], true]]);
}
},
'border-radius': {
'topic': extractor(buildToken('a{border-top-left-radius:none}')),
'has no properties': function (tokens) {
- assert.deepEqual(tokens, [['border-top-left-radius', 'none', 'border-radius', ['border-top-left-radius:none'], [['a']], true]]);
+ assert.deepEqual(tokens, [['border-top-left-radius', 'none', 'border-radius', [['border-top-left-radius'], ['none']], 'border-top-left-radius:none', [['a']], true]]);
}
},
'vendor prefixed border-radius': {
'topic': extractor(buildToken('a{-webkit-border-top-left-radius:none}')),
'has no properties': function (tokens) {
- assert.deepEqual(tokens, [['-webkit-border-top-left-radius', 'none', 'border-radius', ['-webkit-border-top-left-radius:none'], [['a']], true]]);
+ assert.deepEqual(tokens, [['-webkit-border-top-left-radius', 'none', 'border-radius', [['-webkit-border-top-left-radius'], ['none']], '-webkit-border-top-left-radius:none', [['a']], true]]);
}
},
'border-image': {
'topic': extractor(buildToken('a{border-image-width:2px}')),
'has no properties': function (tokens) {
- assert.deepEqual(tokens, [['border-image-width', '2px', 'border-image', ['border-image-width:2px'], [['a']], true]]);
+ assert.deepEqual(tokens, [['border-image-width', '2px', 'border-image', [['border-image-width'], ['2px']], 'border-image-width:2px', [['a']], true]]);
}
},
'border-top': {
'topic': extractor(buildToken('a{border-top-style:none}')),
'has no properties': function (tokens) {
- assert.deepEqual(tokens, [['border-top-style', 'none', 'border-top', ['border-top-style:none'], [['a']], true]]);
+ assert.deepEqual(tokens, [['border-top-style', 'none', 'border-top', [['border-top-style'], ['none']], 'border-top-style:none', [['a']], true]]);
}
},
'text-shadow': {
'topic': extractor(buildToken('a{text-shadow:none}')),
'has no properties': function (tokens) {
- assert.deepEqual(tokens, [['text-shadow', 'none', 'text-shadow', ['text-shadow:none'], [['a']], true]]);
+ assert.deepEqual(tokens, [['text-shadow', 'none', 'text-shadow', [['text-shadow'], ['none']], 'text-shadow:none', [['a']], true]]);
}
}
}
--- /dev/null
+var vows = require('vows');
+var assert = require('assert');
+
+var addOptimizationMetadata = require('../../lib/selectors/optimization-metadata');
+
+vows.describe(addOptimizationMetadata)
+ .addBatch({
+ 'comment': {
+ 'topic': [['selector', ['a'], ['/* comment */']]],
+ 'metadata': function (tokens) {
+ addOptimizationMetadata(tokens);
+ assert.deepEqual(tokens, [['selector', ['a'], ['/* comment */']]]);
+ }
+ },
+ 'normal': {
+ 'topic': [['selector', ['a'], [[['color'], ['red']]] ]],
+ 'metadata': function (tokens) {
+ addOptimizationMetadata(tokens);
+ assert.deepEqual(tokens, [['selector', ['a'], [[['color', false, false], ['red']]] ]]);
+ }
+ },
+ 'important': {
+ 'topic': [['selector', ['a'], [[['color'], ['red!important']]] ]],
+ 'metadata': function (tokens) {
+ addOptimizationMetadata(tokens);
+ assert.deepEqual(tokens, [['selector', ['a'], [[['color', true, false], ['red']]] ]]);
+ }
+ },
+ 'underscore hack': {
+ 'topic': [['selector', ['a'], [[['_color'], ['red']]] ]],
+ 'metadata': function (tokens) {
+ addOptimizationMetadata(tokens);
+ assert.deepEqual(tokens, [['selector', ['a'], [[['_color', false, true], ['red']]] ]]);
+ }
+ },
+ 'star hack': {
+ 'topic': [['selector', ['a'], [[['_color'], ['red']]] ]],
+ 'metadata': function (tokens) {
+ addOptimizationMetadata(tokens);
+ assert.deepEqual(tokens, [['selector', ['a'], [[['_color', false, true], ['red']]] ]]);
+ }
+ },
+ 'backslash hack': {
+ 'topic': [['selector', ['a'], [[['color'], ['red\\9']]] ]],
+ 'metadata': function (tokens) {
+ addOptimizationMetadata(tokens);
+ assert.deepEqual(tokens, [['selector', ['a'], [[['color', false, true], ['red\\9']]] ]]);
+ }
+ },
+ 'backslash hack - value of length 1': {
+ 'topic': [['selector', ['a'], [[['width'], ['0']]] ]],
+ 'metadata': function (tokens) {
+ addOptimizationMetadata(tokens);
+ assert.deepEqual(tokens, [['selector', ['a'], [[['width', false, false], ['0']]] ]]);
+ }
+ }
+ })
+ .addBatch({
+ 'source map': {
+ 'topic': [['selector', ['a', 1, 0, undefined], [[['color', 1, 2, undefined], ['red', 1, 2, undefined]]] ]],
+ 'metadata': function (tokens) {
+ addOptimizationMetadata(tokens);
+ assert.deepEqual(tokens, [['selector', ['a', 1, 0, undefined], [[['color', false, false, 1, 2, undefined], ['red', 1, 2, undefined]]] ]]);
+ }
+ }
+ })
+ .export(module);
],
'non-adjacent with multi selectors': [
'a{padding:10px;margin:0;color:red}.one{color:red}a,p{color:red;padding:0}',
- '.one,a,p{color:red}a{padding:10px;margin:0}a,p{padding:0}'
+ 'a,p{padding:0}.one,a,p{color:red}a{margin:0}'
]
}, { advanced: true, aggressiveMerging: false })
)
var Tokenizer = require('../../../lib/selectors/tokenizer');
var SimpleOptimizer = require('../../../lib/selectors/optimizers/simple');
var Compatibility = require('../../../lib/utils/compatibility');
+var addOptimizationMetadata = require('../../../lib/selectors/optimization-metadata');
function selectorContext(group, specs, options) {
var context = {};
function optimized(selectors) {
return function (source) {
var tokens = new Tokenizer({ options: {} }).toTokens(source);
+ addOptimizationMetadata(tokens);
new SimpleOptimizer(options).optimize(tokens);
- var value = tokens[0] ? tokens[0][2].map(function (property) { return property[0]; }) : null;
+
+ var value = tokens[0] ?
+ tokens[0][2].map(function (property) {
+ return typeof property == 'string' ?
+ property :
+ property.map(function(t) { return t[0]; });
+ }) :
+ null;
assert.deepEqual(value, selectors);
};
propertyContext('@background', {
'none to 0 0': [
'a{background:none}',
- ['background:0 0']
+ [['background', '0 0']]
],
'transparent to 0 0': [
'a{background:transparent}',
- ['background:0 0']
+ [['background', '0 0']]
],
'any other': [
'a{background:red}',
- ['background:red']
+ [['background', 'red']]
]
})
)
propertyContext('@border-*-radius', {
'spaces around /': [
'a{border-top-left-radius:2em / 1em}',
- ['border-top-left-radius:2em/1em']
+ [['border-top-left-radius', '2em', '/', '1em']]
],
'symmetric expanded to shorthand': [
'a{border-top-left-radius:1em 2em 3em 4em / 1em 2em 3em 4em}',
- ['border-top-left-radius:1em 2em 3em 4em']
+ [['border-top-left-radius', '1em', '2em', '3em', '4em']]
]
})
)
propertyContext('@box-shadow', {
'four zeros': [
'a{box-shadow:0 0 0 0}',
- ['box-shadow:0 0']
+ [['box-shadow', '0', '0']]
],
'four zeros in vendor prefixed': [
'a{-webkit-box-shadow:0 0 0 0}',
- ['-webkit-box-shadow:0 0']
+ [['-webkit-box-shadow', '0', '0']]
]
})
)
propertyContext('colors', {
'rgb to hex': [
'a{color:rgb(255,254,253)}',
- ['color:#fffefd']
+ [['color', '#fffefd']]
],
'rgba not to hex': [
'a{color:rgba(255,254,253,.5)}',
- ['color:rgba(255,254,253,.5)']
+ [['color', 'rgba(255,254,253,.5)']]
],
'hsl to hex': [
'a{color:hsl(240,100%,50%)}',
- ['color:#00f']
+ [['color', '#00f']]
],
'hsla not to hex': [
'a{color:hsla(240,100%,50%,.5)}',
- ['color:hsla(240,100%,50%,.5)']
+ [['color', 'hsla(240,100%,50%,.5)']]
],
'long hex to short hex': [
'a{color:#ff00ff}',
- ['color:#f0f']
+ [['color', '#f0f']]
],
'hex to name': [
'a{color:#f00}',
- ['color:red']
+ [['color', 'red']]
],
'name to hex': [
'a{color:white}',
- ['color:#fff']
+ [['color', '#fff']]
],
'transparent black rgba to transparent': [
'a{color:rgba(0,0,0,0)}',
- ['color:transparent']
+ [['color', 'transparent']]
],
'transparent non-black rgba': [
'a{color:rgba(255,0,0,0)}',
- ['color:rgba(255,0,0,0)']
+ [['color', 'rgba(255,0,0,0)']]
],
'transparent black hsla to transparent': [
'a{color:hsla(0,0%,0%,0)}',
- ['color:transparent']
+ [['color', 'transparent']]
],
'transparent non-black hsla': [
'a{color:rgba(240,0,0,0)}',
- ['color:rgba(240,0,0,0)']
+ [['color', 'rgba(240,0,0,0)']]
],
'partial hex to name': [
'a{color:#f00000}',
- ['color:#f00000']
+ [['color', '#f00000']]
],
'partial hex further down to name': [
'a{background:url(test.png) #f00000}',
- ['background:url(test.png) #f00000']
+ [['background', 'url(test.png)', '#f00000']]
],
'partial name to hex': [
'a{color:greyish}',
- ['color:greyish']
+ [['color', 'greyish']]
],
'partial name further down to hex': [
'a{background:url(test.png) blueish}',
- ['background:url(test.png) blueish']
+ [['background', 'url(test.png)', 'blueish']]
],
'partial name as a suffix': [
'a{font-family:alrightsanslp-black}',
- ['font-family:alrightsanslp-black']
+ [['font-family', 'alrightsanslp-black']]
]
})
)
propertyContext('colors - ie8 compatibility', {
'transparent black rgba': [
'a{color:rgba(0,0,0,0)}',
- ['color:rgba(0,0,0,0)']
+ [['color', 'rgba(0,0,0,0)']]
],
'transparent non-black rgba': [
'a{color:rgba(255,0,0,0)}',
- ['color:rgba(255,0,0,0)']
+ [['color', 'rgba(255,0,0,0)']]
],
'transparent black hsla': [
'a{color:hsla(0,0%,0%,0)}',
- ['color:hsla(0,0%,0%,0)']
+ [['color', 'hsla(0,0%,0%,0)']]
],
'transparent non-black hsla': [
'a{color:rgba(240,0,0,0)}',
- ['color:rgba(240,0,0,0)']
+ [['color', 'rgba(240,0,0,0)']]
]
}, { compatibility: 'ie8' })
)
propertyContext('@filter', {
'spaces after comma': [
'a{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=\'#cccccc\',endColorstr=\'#000000\', enabled=true)}',
- ['filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=\'#cccccc\', endColorstr=\'#000000\', enabled=true)']
+ [['filter', 'progid:DXImageTransform.Microsoft.gradient(startColorstr=\'#cccccc\', endColorstr=\'#000000\', enabled=true)']]
],
'single Alpha filter': [
'a{filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80)}',
- ['filter:alpha(Opacity=80)']
+ [['filter', 'alpha(Opacity=80)']]
],
'single Chroma filter': [
'a{filter:progid:DXImageTransform.Microsoft.Chroma(color=#919191)}',
- ['filter:chroma(color=#919191)']
+ [['filter', 'chroma(color=#919191)']]
],
'multiple filters': [
'a{filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80) progid:DXImageTransform.Microsoft.Chroma(color=#919191)}',
- ['filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80) progid:DXImageTransform.Microsoft.Chroma(color=#919191)']
+ [['filter', 'progid:DXImageTransform.Microsoft.Alpha(Opacity=80)', 'progid:DXImageTransform.Microsoft.Chroma(color=#919191)']]
]
})
)
propertyContext('@font', {
'in shorthand': [
'a{font:normal 13px/20px sans-serif}',
- ['font:400 13px/20px sans-serif']
+ [['font', '400', '13px', '/', '20px', 'sans-serif']]
],
'in shorthand with fractions': [
'a{font:bold .9em sans-serif}',
- ['font:700 .9em sans-serif']
+ [['font', '700', '.9em', 'sans-serif']]
],
'with font wariant and style': [
'a{font:normal normal normal 13px/20px sans-serif}',
- ['font:normal normal normal 13px/20px sans-serif']
+ [['font', 'normal', 'normal', 'normal', '13px', '/', '20px', 'sans-serif']]
],
'with mixed order of variant and style': [
'a{font:normal 300 normal 13px/20px sans-serif}',
- ['font:normal 300 normal 13px/20px sans-serif']
+ [['font', 'normal', '300', 'normal', '13px', '/', '20px', 'sans-serif']]
]
})
)
propertyContext('@font-weight', {
'normal to 400': [
'a{font-weight:normal}',
- ['font-weight:400']
+ [['font-weight', '400']]
],
'bold to 700': [
'a{font-weight:bold}',
- ['font-weight:700']
+ [['font-weight', '700']]
],
'any other': [
'a{font-weight:bolder}',
- ['font-weight:bolder']
+ [['font-weight', 'bolder']]
]
})
)
'star': [
'a{*width:100px}',
null
+ ],
+ 'backslash': [
+ 'a{width:100px\\9}',
+ null
]
})
)
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',]]
]
}, { compatibility: 'ie8' })
)
propertyContext('important', {
'minified': [
'a{color:red!important}',
- ['color:red!important']
+ [['color', 'red']]
],
'space before !': [
'a{color:red !important}',
- ['color:red!important']
+ [['color', 'red']]
],
'space after !': [
'a{color:red! important}',
- ['color:red!important']
+ [['color', 'red']]
]
}, { compatibility: 'ie8' })
)
propertyContext('@outline', {
'none to 0': [
'a{outline:none}',
- ['outline:0']
+ [['outline', '0']]
],
'any other': [
'a{outline:10px}',
- ['outline:10px']
+ [['outline', '10px']]
]
})
)
propertyContext('rounding', {
'pixels': [
'a{transform:translateY(123.31135px)}',
- ['transform:translateY(123.311px)']
+ [['transform', 'translateY(123.311px)']]
],
'percents': [
'a{left:20.1231%}',
- ['left:20.1231%']
+ [['left', '20.1231%']]
],
'ems': [
'a{left:1.1231em}',
- ['left:1.1231em']
+ [['left', '1.1231em']]
]
}, { roundingPrecision: 3 })
)
propertyContext('rounding disabled', {
'pixels': [
'a{transform:translateY(123.31135px)}',
- ['transform:translateY(123.31135px)']
+ [['transform', 'translateY(123.31135px)']]
],
'percents': [
'a{left:20.1231%}',
- ['left:20.1231%']
+ [['left', '20.1231%']]
],
'ems': [
'a{left:1.1231em}',
- ['left:1.1231em']
+ [['left', '1.1231em']]
]
}, { roundingPrecision: -1 })
)
propertyContext('units', {
'pixels': [
'a{width:0px}',
- ['width:0']
+ [['width', '0']]
],
'degrees': [
'div{background:linear-gradient(0deg,red,#fff)}',
- ['background:linear-gradient(0deg,red,#fff)']
+ [['background', 'linear-gradient(0deg,red,#fff)']]
],
'degrees when not mixed': [
'div{transform:rotate(0deg) skew(0deg)}',
- ['transform:rotate(0) skew(0)']
+ [['transform', 'rotate(0)', 'skew(0)']]
],
'non-zero degrees when not mixed': [
'div{transform:rotate(10deg) skew(.5deg)}',
- ['transform:rotate(10deg) skew(.5deg)']
+ [['transform', 'rotate(10deg)', 'skew(.5deg)']]
],
'mixed units': [
'a{margin:0em 0rem 0px 0pt}',
- ['margin:0']
+ [['margin', '0']]
],
'mixed vales': [
'a{padding:10px 0em 30% 0rem}',
- ['padding:10px 0 30% 0']
+ [['padding', '10px', '0', '30%', '0']]
]
})
)
propertyContext('units in compatibility mode', {
'pixels': [
'a{width:0px}',
- ['width:0']
+ [['width', '0']]
],
'mixed units': [
'a{margin:0em 0rem 0px 0pt}',
- ['margin:0 0rem 0 0']
+ [['margin', '0', '0rem', '0', '0']]
],
'mixed vales': [
'a{padding:10px 0em 30% 0rem}',
- ['padding:10px 0 30% 0rem']
+ [['padding', '10px', '0', '30%', '0rem']]
]
}, { compatibility: 'ie8' })
)
propertyContext('zeros', {
'-0 to 0': [
'a{margin:-0}',
- ['margin:0']
+ [['margin', '0']]
],
'-0px to 0': [
'a{margin:-0px}',
- ['margin:0']
+ [['margin', '0']]
],
'-0% to 0': [
'a{width:-0%}',
- ['width:0']
+ [['width', '0']]
],
'missing': [
'a{opacity:1.}',
- ['opacity:1']
+ [['opacity', '1']]
],
'multiple': [
'a{margin:-0 -0 -0 -0}',
- ['margin:0']
+ [['margin', '0']]
],
'keeps negative non-zero': [
'a{margin:-0.5em}',
- ['margin:-.5em']
+ [['margin', '-.5em']]
],
'inside names #1': [
'div{animation-name:test-0-bounce}',
- ['animation-name:test-0-bounce']
+ [['animation-name', 'test-0-bounce']]
],
'inside names #2': [
'div{animation-name:test-0bounce}',
- ['animation-name:test-0bounce']
+ [['animation-name', 'test-0bounce']]
],
'inside names #3': [
'div{animation-name:test-0px}',
- ['animation-name:test-0px']
+ [['animation-name', 'test-0px']]
],
'strips leading from value': [
'a{padding:010px 0015px}',
- ['padding:10px 15px']
+ [['padding', '10px', '15px']]
],
'strips leading from fractions': [
'a{margin:-0.5em}',
- ['margin:-.5em']
+ [['margin', '-.5em']]
],
'strips trailing from opacity': [
'a{opacity:1.0}',
- ['opacity:1']
+ [['opacity', '1']]
],
'.0 to 0': [
'a{margin:.0 .0 .0 .0}',
- ['margin:0']
+ [['margin', '0']]
],
'fraction zeros': [
'a{margin:10.0em 15.50em 10.01em 0.0em}',
- ['margin:10em 15.5em 10.01em 0']
+ [['margin', '10em', '15.5em', '10.01em', '0']]
],
'fraction zeros after rounding': [
'a{margin:10.0010px}',
- ['margin:10px']
+ [['margin', '10px']]
],
'four zeros into one': [
'a{margin:0 0 0 0}',
- ['margin:0']
+ [['margin', '0']]
],
'rect zeros': [
'a{clip:rect(0px 0px 0px 0px)}',
- ['clip:rect(0 0 0 0)']
+ [['clip', 'rect(0 0 0 0)']]
],
'rect zeros with non-zero value': [
'a{clip:rect(0.5% 0px 0px 0px)}',
- ['clip:rect(.5% 0 0 0)']
+ [['clip', 'rect(.5% 0 0 0)']]
],
'rect zeros with commas': [
'a{clip:rect(0px, 0px, 0px, 0px)}',
- ['clip:rect(0,0,0,0)']
+ [['clip', 'rect(0,0,0,0)']]
]
})
)
propertyContext('zeros with disabled zeroUnits', {
'10.0em': [
'a{margin:10.0em}',
- ['margin:10em']
+ [['margin', '10em']]
],
'0px': [
'a{margin:0px}',
- ['margin:0px']
+ [['margin', '0px']]
],
'0px 0px': [
'a{margin:0px 0px}',
- ['margin:0px 0px']
+ [['margin', '0px', '0px']]
],
'0deg': [
'div{transform:rotate(0deg) skew(0deg)}',
- ['transform:rotate(0deg) skew(0deg)']
+ [['transform', 'rotate(0deg)', 'skew(0deg)']]
],
'0%': [
'a{height:0%}',
- ['height:0%']
+ [['height', '0%']]
],
'10%': [
'a{width:10%}',
- ['width:10%']
+ [['width', '10%']]
]
}, { compatibility: { properties: { zeroUnits: false } } })
)
propertyContext('comments', {
'comment': [
'a{__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS0__color:red__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS1__}',
- ['__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS0__', 'color:red', '__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS1__']
+ ['__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS0__', ['color', 'red'], '__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS1__']
]
})
)
propertyContext('whitespace', {
'stripped spaces': [
'div{text-shadow:rgba(255,1,1,.5) 1px}',
- ['text-shadow:rgba(255,1,1,.5)1px']
+ [['text-shadow', 'rgba(255,1,1,.5)', '1px']]
]
})
)
- .addBatch(
- propertyContext('whitespace in compatibility mode', {
- 'stripped spaces': [
- 'div{text-shadow:rgba(255,1,1,.5) 1px}',
- ['text-shadow:rgba(255,1,1,.5) 1px']
- ]
- }, { compatibility: 'ie8' })
- )
- .addBatch(
- propertyContext('whitespace in compatibility mode', {
- 'stripped spaces': [
- 'div{text-shadow:rgba(255,1,1,.5) 1px}',
- ['text-shadow:rgba(255,1,1,.5) 1px']
- ]
- }, { compatibility: 'ie8' })
- )
.export(module);
--- /dev/null
+var vows = require('vows');
+var assert = require('assert');
+
+var SelectorTokenizer = require('../../lib/selectors/tokenizer');
+var extractProperties = require('../../lib/selectors/extractor');
+var canReorder = require('../../lib/selectors/reorderable').canReorder;
+var canReorderSingle = require('../../lib/selectors/reorderable').canReorderSingle;
+
+function propertiesIn(source) {
+ return extractProperties(new SelectorTokenizer({ options: {} }, false).toTokens(source)[0]);
+}
+
+vows.describe(canReorder)
+ .addBatch({
+ 'empty': {
+ 'topic': function () {
+ return canReorder(propertiesIn('a{}'), propertiesIn('a{}'));
+ },
+ 'must be true': function (result) {
+ assert.isTrue(result);
+ }
+ },
+ 'left empty': {
+ 'topic': function () {
+ return canReorder(propertiesIn('a{}'), propertiesIn('a{color:red}'));
+ },
+ 'must be true': function (result) {
+ assert.isTrue(result);
+ }
+ },
+ 'right empty': {
+ 'topic': function () {
+ return canReorder(propertiesIn('a{color:red}'), propertiesIn('a{}'));
+ },
+ 'must be true': function (result) {
+ assert.isTrue(result);
+ }
+ },
+ 'all reorderable': {
+ 'topic': function () {
+ return canReorder(propertiesIn('a{color:red;width:100%}'), propertiesIn('a{display:block;height:20px}'));
+ },
+ 'must be true': function (result) {
+ assert.isTrue(result);
+ }
+ },
+ 'one not reorderable on the left': {
+ 'topic': function () {
+ return canReorder(propertiesIn('a{color:red;width:100%;display:inline}'), propertiesIn('a{display:block;height:20px}'));
+ },
+ 'must be false': function (result) {
+ assert.isFalse(result);
+ }
+ },
+ 'one not reorderable on the right': {
+ 'topic': function () {
+ return canReorder(propertiesIn('a{color:red;width:100%}'), propertiesIn('a{display:block;height:20px;width:20px}'));
+ },
+ 'must be false': function (result) {
+ assert.isFalse(result);
+ }
+ }
+ })
+ .export(module);
+
+vows.describe(canReorderSingle)
+ .addBatch({
+ 'different properties': {
+ 'topic': function () {
+ return canReorderSingle(propertiesIn('a{color:red}')[0], propertiesIn('a{display:block}')[0]);
+ },
+ 'must be true': function (result) {
+ assert.isTrue(result);
+ }
+ },
+ 'font and line-height': {
+ 'topic': function () {
+ return canReorderSingle(propertiesIn('a{font:10px}')[0], propertiesIn('a{line-height:12px}')[0]);
+ },
+ 'must be false': function (result) {
+ assert.isFalse(result);
+ }
+ },
+ 'same properties with same value': {
+ 'topic': function () {
+ return canReorderSingle(propertiesIn('a{color:red}')[0], propertiesIn('a{color:red}')[0]);
+ },
+ 'must be true': function (result) {
+ assert.isTrue(result);
+ }
+ },
+ 'same properties with same value and different case': {
+ 'topic': function () {
+ return canReorderSingle(propertiesIn('a{COLOR:red}')[0], propertiesIn('a{color:red}')[0]);
+ },
+ 'must be true': function (result) {
+ assert.isTrue(result);
+ }
+ },
+ 'same properties with different value': {
+ 'topic': function () {
+ return canReorderSingle(propertiesIn('a{color:red}')[0], propertiesIn('a{color:blue}')[0]);
+ },
+ 'must be false': function (result) {
+ assert.isFalse(result);
+ }
+ },
+ 'same properties with different value and different case': {
+ 'topic': function () {
+ return canReorderSingle(propertiesIn('a{color:red}')[0], propertiesIn('a{COLOR:blue}')[0]);
+ },
+ 'must be false': function (result) {
+ assert.isFalse(result);
+ }
+ },
+ 'different properties with same root': {
+ 'topic': function () {
+ return canReorderSingle(propertiesIn('a{text-shadow:none}')[0], propertiesIn('a{text-decoration:underline}')[0]);
+ },
+ 'must be true': function (result) {
+ assert.isTrue(result);
+ }
+ },
+ 'different properties with same root when shorthand does not reset': {
+ 'topic': function () {
+ return canReorderSingle(propertiesIn('a{border:none}')[0], propertiesIn('a{border-spacing:1px}')[0]);
+ },
+ 'must be true': function (result) {
+ assert.isTrue(result);
+ }
+ },
+ 'shorhand and longhand with different value': {
+ 'topic': function () {
+ return canReorderSingle(propertiesIn('a{margin:3px}')[0], propertiesIn('a{margin-bottom:5px}')[0]);
+ },
+ 'must be false': function (result) {
+ assert.isFalse(result);
+ }
+ },
+ 'shorhand and longhand with same value': {
+ 'topic': function () {
+ return canReorderSingle(propertiesIn('a{margin:3px}')[0], propertiesIn('a{margin-bottom:3px}')[0]);
+ },
+ 'must be false': function (result) {
+ assert.isTrue(result);
+ }
+ },
+ 'two longhand with different value sharing same shorthand': {
+ 'topic': function () {
+ return canReorderSingle(propertiesIn('a{margin-top:3px solid red}')[0], propertiesIn('a{margin-bottom:3px solid white}')[0]);
+ },
+ 'must be true': function (result) {
+ assert.isTrue(result);
+ }
+ },
+ 'different, non-overlapping simple selectors': {
+ 'topic': function () {
+ return canReorderSingle(propertiesIn('a{border:none}')[0], propertiesIn('div{border:1px solid #f00}')[0]);
+ },
+ 'must be true': function (result) {
+ assert.isTrue(result);
+ }
+ },
+ 'different, non-overlapping complex selectors': {
+ 'topic': function () {
+ return canReorderSingle(propertiesIn('.one{border:none}')[0], propertiesIn('div{border:1px solid #f00}')[0]);
+ },
+ 'must be false': function (result) {
+ assert.isFalse(result);
+ }
+ },
+ 'different, overlapping simple selectors': {
+ 'topic': function () {
+ return canReorderSingle(propertiesIn('a{border:none}')[0], propertiesIn('a{border:1px solid #f00}')[0]);
+ },
+ 'must be false': function (result) {
+ assert.isFalse(result);
+ }
+ },
+ 'align-items': {
+ 'topic': function () {
+ return canReorderSingle(propertiesIn('a{border:none}')[0], propertiesIn('a{align-items:flex-start}')[0]);
+ },
+ 'must be true': function (result) {
+ assert.isTrue(result);
+ }
+ }
+ })
+ .addBatch({
+ 'flex #1': {
+ 'topic': function () {
+ return canReorderSingle(propertiesIn('a{-webkit-box-align:flex-start}')[0], propertiesIn('a{align-items:flex-start}')[0]);
+ },
+ 'must be false': function (result) {
+ assert.isFalse(result);
+ }
+ },
+ 'flex #2': {
+ 'topic': function () {
+ return canReorderSingle(propertiesIn('a{-ms-flex-align:start}')[0], propertiesIn('a{align-items:flex-start}')[0]);
+ },
+ 'must be false': function (result) {
+ assert.isFalse(result);
+ }
+ },
+ 'flex #3': {
+ 'topic': function () {
+ return canReorderSingle(propertiesIn('a{flex:none}')[0], propertiesIn('a{align-items:flex-start}')[0]);
+ },
+ 'must be false': function (result) {
+ assert.isFalse(result);
+ }
+ },
+ 'flex #4': {
+ 'topic': function () {
+ return canReorderSingle(propertiesIn('a{justify-content:center}')[0], propertiesIn('a{–ms-flex-pack:center}')[0]);
+ },
+ 'must be false': function (result) {
+ assert.isFalse(result);
+ }
+ },
+ 'flex #5': {
+ 'topic': function () {
+ return canReorderSingle(propertiesIn('a{justify-content:center}')[0], propertiesIn('a{–webkit-box-pack:center}')[0]);
+ },
+ 'must be false': function (result) {
+ assert.isFalse(result);
+ }
+ }
+ })
+ .export(module);
[
[
'selector',
- [['a', 1, 1, undefined], ['\n\ndiv', 3, 0, undefined]],
+ [['a', 1, 1, undefined], ['div', 3, 0, undefined]],
[]
]
]
[
[
'selector',
- [['a', 1, 1, undefined], ['\n\ndiv\na', 3, 0, undefined], ['\n p', 5, 1, undefined]],
+ [['a', 1, 1, undefined], ['div\na', 3, 0, undefined], ['p', 5, 1, undefined]],
[]
]
]
[
[
'selector',
- [['a ', 1, 0, undefined]],
+ [['a', 1, 0, undefined]],
[]
],
[
[
'selector',
[['a', 1, 0, undefined]],
- [['color:red', 1, 2, undefined]]
+ [[['color', 1, 2, undefined], ['red', 1, 8, undefined]]]
]
]
],
'selector',
[['a', 1, 0, undefined]],
[
- ['color:red', 1, 2, undefined],
- ['border:none', 1, 12, undefined]
+ [['color', 1, 2, undefined], ['red', 1, 8, undefined]],
+ [['border', 1, 12, undefined], ['none', 1, 19, undefined]]
]
]
]
'selector',
[['a', 1, 0, undefined]],
[
- ['color:red', 1, 2, undefined],
- ['border:none', 2, 0, undefined],
- ['display:block', 5, 2, undefined]
+ [['color', 1, 2, undefined], ['red', 1, 8, undefined]],
+ [['border', 2, 0, undefined], ['none', 3, 0, undefined]],
+ [['display', 5, 2, undefined], ['block', 5, 10, undefined]]
]
]
]
[
'selector',
[['a', 1, 0, undefined]],
- [['color:red', 1, 2, undefined]]
+ [[['color', 1, 2, undefined], ['red', 1, 8, undefined]]]
],
[
'selector',
[['div', 1, 12, undefined]],
- [['color:blue', 1, 16, undefined]]
+ [[['color', 1, 16, undefined], ['blue', 1, 22, undefined]]]
]
]
],
[
'selector',
[['a', 1, 0, undefined]],
- [['color:red', 1, 2, undefined]]
+ [[['color', 1, 2, undefined], ['red', 1, 8, undefined]]]
],
[
'selector',
[['div', 2, 1, undefined]],
- [['color:blue', 2, 5, undefined]]
+ [[['color', 2, 5, undefined], ['blue', 2, 11, undefined]]]
]
]
],
[
'selector',
[['a', 1, 0, undefined]],
- [['color:red', 1, 2, undefined]]
+ [[['color', 1, 2, undefined], ['red', 1, 8, undefined]]]
],
[
'selector',
[['div', 3, 1, undefined]],
- [['color:blue', 3, 5, undefined]]
+ [[['color', 3, 5, undefined], ['blue', 3, 11, undefined]]]
]
]
]
[
'selector',
[['a', 4, 0, undefined]],
- [['color:red', 4, 2, undefined]]
+ [[['color', 4, 2, undefined], ['red', 4, 8, undefined]]]
]
]
],
[
'selector',
[['a', 1, 18, undefined]],
- [['color:red', 1, 20, undefined]]
+ [[['color', 1, 20, undefined], ['red', 1, 26, undefined]]]
]
]
]
[
'selector',
[['a', 1, 25, undefined]],
- [['color:red', 1, 27, undefined]]
+ [[['color', 1, 27, undefined], ['red', 1, 33, undefined]]]
]
]
]
[
'selector',
[['a', 4, 0, undefined]],
- [['color:red', 5, 0, undefined]]
+ [[['color', 5, 0, undefined], ['red', 6, 0, undefined]]]
],
[
'selector',
[
'selector',
[['a', 1, 25, undefined]],
- [['color:red', 1, 27, undefined]]
+ [[['color', 1, 27, undefined], ['red', 1, 33, undefined]]]
]
]
],
[
'selector',
[['p', 1, 39, undefined]],
- [['color:red', 1, 41, undefined]]
+ [[['color', 1, 41, undefined], ['red', 1, 47, undefined]]]
]
]
],
'flat-block',
['@font-face', 1, 0, undefined],
[
- ['font-family:"Font"', 1, 11, undefined],
- ['src:url("font.ttf")', 2, 0, undefined],
- ['font-weight:normal', 3, 0, undefined],
- ['font-style:normal', 3, 20, undefined]
+ [['font-family', 1, 11, undefined], ['"Font"', 1, 24, undefined]],
+ [['src', 2, 0, undefined], ['url("font.ttf")', 2, 5, undefined]],
+ [['font-weight', 3, 0, undefined], ['normal', 3, 13, undefined]],
+ [['font-style', 3, 20, undefined], ['normal', 3, 32, undefined]]
]
],
[
'flat-block',
['@font-face', 2, 0, undefined],
[
- ['font-family:"Font"', 3, 1, undefined]
+ [['font-family', 3, 1, undefined], ['"Font"', 3, 14, undefined]]
]
]
]
['div[data-type=__ESCAPED_FREE_TEXT_CLEAN_CSS0(1,3)__]', 1, 0, undefined],
['div[data-id=__ESCAPED_FREE_TEXT_CLEAN_CSS1(0,7)__]', 2, 5, undefined]
],
- [['color:red', 2, 26, undefined]]
+ [[['color', 2, 26, undefined], ['red', 2, 32, undefined]]]
]
]
],
- 'in properties': [
- 'div{__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS0(2,5)__background:url(__ESCAPED_URL_CLEAN_CSS0(0,20)__);color:blue}a{font-family:__ESCAPED_FREE_TEXT_CLEAN_CSS0(1,3)__;color:red}',
+ 'in properties123': [
+ 'div{__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS0(2,5)__background:__ESCAPED_URL_CLEAN_CSS0(0,20)__;color:blue}a{font-family:__ESCAPED_FREE_TEXT_CLEAN_CSS0(1,3)__;color:red}',
[
[
'selector',
[['div', 1, 0, undefined]],
[
- ['__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS0(2,5)__', 1, 4, undefined],
- ['background:url(__ESCAPED_URL_CLEAN_CSS0(0,20)__)', 3, 5, undefined],
- ['color:blue', 3, 42, undefined]
+ '__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS0(2,5)__',
+ [['background', 3, 5, undefined], ['__ESCAPED_URL_CLEAN_CSS0(0,20)__', 3, 16, undefined]],
+ [['color', 3, 37, undefined], ['blue', 3, 43, undefined]]
]
],
[
'selector',
- [['a', 3, 53, undefined]],
+ [['a', 3, 48, undefined]],
[
- ['font-family:__ESCAPED_FREE_TEXT_CLEAN_CSS0(1,3)__', 3, 55, undefined],
- ['color:red', 4, 4, undefined]
+ [['font-family', 3, 50, undefined], ['__ESCAPED_FREE_TEXT_CLEAN_CSS0(1,3)__', 3, 62, undefined]],
+ [['color', 4, 4, undefined], ['red', 4, 10, undefined]]
]
]
]
],
+ 'in properties generated for closing brace': [
+ 'div{background:url(image.png)no-repeat}',
+ [
+ [
+ 'selector',
+ [['div', 1, 0, undefined]],
+ [[
+ ['background', 1, 4, undefined],
+ ['url(image.png)', 1, 15, undefined],
+ ['no-repeat', 1, 29, undefined]
+ ]]
+ ]
+ ]
+ ],
'in at-rules': [
'@charset __ESCAPED_FREE_TEXT_CLEAN_CSS0(1, 5)__;div{}',
[
[
'selector',
[['a', 3, 18, undefined]],
- [['color:red', 3, 20, undefined]]
+ [[['color', 3, 20, undefined], ['red', 3, 26, undefined]]]
]
]
]
[
'selector',
[['a', 2, 0, 'two.css']],
- [['color:red', 2, 2, 'two.css']]
+ [[['color', 2, 2, 'two.css'], ['red', 2, 8, 'two.css']]]
]
]
]
[
[
'selector',
- [['div > a ', 1, 4, 'styles.less']],
- [['color:red', 2, 2, 'styles.less']]
+ [['div > a', 1, 4, 'styles.less']],
+ [[['color', 2, 2, 'styles.less'], ['red', 2, 2, 'styles.less']]]
+ ]
+ ]
+ ],
+ 'with fallback for properties': [
+ function () {
+ var tracker = new SourceTracker();
+ var reader = new SourceReader();
+ var inputTracker = new InputSourceMapTracker({ options: { inliner: {}, sourceMap: inputMap, options: {} }, errors: {}, sourceTracker: tracker });
+ inputTracker.track('', function () {});
+
+ var tokenizer = new Tokenizer({ sourceTracker: tracker, sourceReader: reader, inputSourceMapTracker: inputTracker, options: {} }, true);
+ return tokenizer.toTokens('div > a {\n color: red red;\n}');
+ },
+ [
+ [
+ 'selector',
+ [['div > a', 1, 4, 'styles.less']],
+ [[['color', 2, 2, 'styles.less'], ['red', 2, 2, 'styles.less'], ['red', 2, 2, 'styles.less']]]
]
]
]
['selector', [['a']], []]
]
],
- 'a selector': [
+ 'a selector with a property': [
'a{color:red}',
[
- ['selector', [['a']], [['color:red']]]
+ ['selector', [['a']], [[['color'], ['red']]]]
+ ]
+ ],
+ 'a selector with a multi value property': [
+ 'a{margin:0px 2px 1px}',
+ [
+ ['selector', [['a']], [[['margin'], ['0px'], ['2px'], ['1px']]]]
]
],
'a selector with whitespace': [
'a {color:red;\n\ndisplay :\r\n block }',
[
- ['selector', [['a ']], [['color:red'], ['display:block']]]
+ ['selector', [['a']], [[['color'], ['red']], [['display'], ['block']]]]
]
],
'a selector with suffix whitespace': [
'div a{color:red\r\n}',
[
- ['selector', [['div a']], [['color:red']]]
+ ['selector', [['div a']], [[['color'], ['red']]]]
]
],
'a selector with whitespace in functions': [
'a{color:rgba( 255, 255, 0, 0.5 )}',
[
- ['selector', [['a']], [['color:rgba(255,255,0,0.5)']]]
+ ['selector', [['a']], [[['color'], ['rgba( 255, 255, 0, 0.5 )']]]]
+ ]
+ ],
+ 'a selector with functions and no whitespace breaks': [
+ 'a{background:rgba(255,255,0,0.5)url(test.png)repeat no-repeat}',
+ [
+ ['selector', [['a']], [[['background'], ['rgba(255,255,0,0.5)'], ['url(test.png)'], ['repeat'], ['no-repeat']]]]
+ ]
+ ],
+ 'a selector with escaped url and no whitespace breaks': [
+ 'a{background:__ESCAPED_URL_CLEAN_CSS0__50px/25%}',
+ [
+ ['selector', [['a']], [[['background'], ['__ESCAPED_URL_CLEAN_CSS0__'], ['50px'], ['/'], ['25%']]]]
]
],
'a selector with empty properties': [
'a{color:red; ; ; ;}',
[
- ['selector', [['a']], [['color:red']]]
+ ['selector', [['a']], [[['color'], ['red']]]]
]
],
'a selector with quoted attribute': [
'a[data-kind=__ESCAPED_FREE_TEXT_CLEAN_CSS0__]{color:red}',
[
- ['selector', [['a[data-kind=__ESCAPED_FREE_TEXT_CLEAN_CSS0__]']], [['color:red']]]
+ ['selector', [['a[data-kind=__ESCAPED_FREE_TEXT_CLEAN_CSS0__]']], [[['color'], ['red']]]]
]
],
'a selector with escaped quote': [
'a double selector': [
'a,\n\ndiv.class > p {color:red}',
[
- ['selector', [['a'], ['\n\ndiv.class > p ']], [['color:red']]]
+ ['selector', [['a'], ['div.class > p']], [[['color'], ['red']]]]
]
],
'two selectors': [
'a{color:red}div{color:blue}',
[
- ['selector', [['a']], [['color:red']]],
- ['selector', [['div']], [['color:blue']]],
+ ['selector', [['a']], [[['color'], ['red']]]],
+ ['selector', [['div']], [[['color'], ['blue']]]],
]
],
'two comments and a selector separated by newline': [
'two properties wrapped between comments': [
'div{__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS0__color:red__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS1__}',
[
- ['selector', [['div']], [['__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS0__'], ['color:red'], ['__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS1__']]]
+ ['selector', [['div']], ['__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS0__', [['color'], ['red']], '__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS1__']]
]
],
'pseudoselector after an argument one': [
'@media (min-width:980px){a{color:red}}',
[
['block', ['@media (min-width:980px)'], [
- ['selector', [['a']], [['color:red']]]
+ ['selector', [['a']], [[['color'], ['red']]]]
]]
]
],
'@media only screen and (max-width:1319px) and (min--moz-device-pixel-ratio:1.5),only screen and (max-width:1319px) and (-moz-min-device-pixel-ratio:1.5){a{color:#000}}',
[
['block', ['@media only screen and (max-width:1319px) and (min--moz-device-pixel-ratio:1.5),only screen and (max-width:1319px) and (-moz-min-device-pixel-ratio:1.5)'], [
- ['selector', [['a']], [['color:#000']]]
+ ['selector', [['a']], [[['color'], ['#000']]]]
]]
]
],
'font-face': [
'@font-face{font-family: fontName;font-size:12px}',
[
- ['flat-block', ['@font-face'], [['font-family:fontName'], ['font-size:12px']]]
+ ['flat-block', ['@font-face'], [[['font-family'], ['fontName']], [['font-size'], ['12px']]]]
]
],
'charset': [
'@charset \'utf-8\';a{color:red}',
[
['at-rule', ['@charset \'utf-8\';']],
- ['selector', [['a']], [['color:red']]]
+ ['selector', [['a']], [[['color'], ['red']]]]
]
],
'charset after a line break': [
[
[
'selector',
- [[ 'a' ]],
- [['border:var(--width) var(--style) var(--color)']]
+ [['a']],
+ [[['border'], ['var(--width)'], ['var(--style)'], ['var(--color)']]]
+ ]
+ ]
+ ],
+ '! important': [
+ 'a{color:red! important}',
+ [
+ [
+ 'selector',
+ [['a']],
+ [[['color'], ['red!important']]]
+ ]
+ ]
+ ],
+ ' !important': [
+ 'a{color:red !important}',
+ [
+ [
+ 'selector',
+ [['a']],
+ [[['color'], ['red!important']]]
+ ]
+ ]
+ ],
+ ' ! important': [
+ 'a{color:red ! important}',
+ [
+ [
+ 'selector',
+ [['a']],
+ [[['color'], ['red!important']]]
]
]
]
})
)
+ .addBatch(
+ tokenizerContext('multiple values', {
+ 'comma - no spaces': [
+ 'a{background:no-repeat,no-repeat}',
+ [
+ ['selector', [['a']], [[['background'], ['no-repeat'], [','], ['no-repeat']]]]
+ ]
+ ],
+ 'comma - one spaces': [
+ 'a{background:no-repeat, no-repeat}',
+ [
+ ['selector', [['a']], [[['background'], ['no-repeat'], [','], ['no-repeat']]]]
+ ]
+ ],
+ 'comma - two spaces': [
+ 'a{background:no-repeat , no-repeat}',
+ [
+ ['selector', [['a']], [[['background'], ['no-repeat'], [','], ['no-repeat']]]]
+ ]
+ ],
+ 'comma - inside function': [
+ 'a{background:rgba(0,0,0,0)}',
+ [
+ ['selector', [['a']], [[['background'], ['rgba(0,0,0,0)']]]]
+ ]
+ ],
+ 'forward slash - no spaces': [
+ 'a{border-radius:5px/4px}',
+ [
+ ['selector', [['a']], [[['border-radius'], ['5px'], ['/'], ['4px']]]]
+ ]
+ ],
+ 'forward slash - one spaces': [
+ 'a{border-radius:5px /4px}',
+ [
+ ['selector', [['a']], [[['border-radius'], ['5px'], ['/'], ['4px']]]]
+ ]
+ ],
+ 'forward slash - two spaces': [
+ 'a{border-radius:5px / 4px}',
+ [
+ ['selector', [['a']], [[['border-radius'], ['5px'], ['/'], ['4px']]]]
+ ]
+ ],
+ 'forward slash - inside function': [
+ 'a{width:calc(5px/4px)}',
+ [
+ ['selector', [['a']], [[['width'], ['calc(5px/4px)']]]]
+ ]
+ ],
+ })
+ )
.addBatch(
tokenizerContext('broken', {
'missing end brace': [
'missing end brace in the middle': [
'body{color:red;a{color:blue;}',
[
- ['selector', [['body']], [['color:red']]]
+ ['selector', [['body']], [[['color'], ['red']]]]
]
]
})
'topic': function () {
return new CleanCSS({ sourceMap: true }).minify('/*! a */div[data-id=" abc "] { color:red; }');
},
- 'should have 2 mappings': function(minified) {
- assert.lengthOf(minified.sourceMap._mappings._array, 2);
+ 'has 3 mappings': function(minified) {
+ assert.lengthOf(minified.sourceMap._mappings._array, 3);
},
- 'should have selector mapping': function (minified) {
+ 'has selector mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 8,
};
assert.deepEqual(minified.sourceMap._mappings._array[0], mapping);
},
- 'should have body mapping': function (minified) {
+ 'has name mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 29,
name: null
};
assert.deepEqual(minified.sourceMap._mappings._array[1], mapping);
+ },
+ 'has value mapping': function (minified) {
+ var mapping = {
+ generatedLine: 1,
+ generatedColumn: 35,
+ originalLine: 1,
+ originalColumn: 37,
+ source: '$stdin',
+ name: null
+ };
+ assert.deepEqual(minified.sourceMap._mappings._array[2], mapping);
}
},
'module #2': {
'topic': function () {
return new CleanCSS({ sourceMap: true }).minify('@media screen {\n@font-face \n{ \nfont-family: test; } }');
},
- 'should have 3 mappings': function(minified) {
- assert.lengthOf(minified.sourceMap._mappings._array, 3);
+ 'has 4 mappings': function(minified) {
+ assert.lengthOf(minified.sourceMap._mappings._array, 4);
},
- 'should have @media mapping': function (minified) {
+ 'has `@media` mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 0,
};
assert.deepEqual(minified.sourceMap._mappings._array[0], mapping);
},
- 'should have @font-face mapping': function (minified) {
+ 'has `@font-face mapping`': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 14,
};
assert.deepEqual(minified.sourceMap._mappings._array[1], mapping);
},
- 'should have font-family mapping': function (minified) {
+ 'has `font-family` mapping': function (minified) {
+ var mapping = {
+ generatedLine: 1,
+ generatedColumn: 25,
+ originalLine: 4,
+ originalColumn: 0,
+ source: '$stdin',
+ name: null
+ };
+ assert.deepEqual(minified.sourceMap._mappings._array[2], mapping);
+ },
+ 'has `test` mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 25,
'topic': function () {
return new CleanCSS({ sourceMap: true, keepBreaks: true }).minify('@media screen { a{color:red} p {color:blue} }div{color:pink}');
},
- 'should have 7 mappings': function(minified) {
- assert.lengthOf(minified.sourceMap._mappings._array, 7);
+ 'has 10 mappings': function(minified) {
+ assert.lengthOf(minified.sourceMap._mappings._array, 10);
},
- 'should have @media mapping': function (minified) {
+ 'has `@media` mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 0,
};
assert.deepEqual(minified.sourceMap._mappings._array[0], mapping);
},
- 'should have _a_ mapping': function (minified) {
+ 'has `a` mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 14,
};
assert.deepEqual(minified.sourceMap._mappings._array[1], mapping);
},
- 'should have _color:red_ mapping': function (minified) {
+ 'has `color` mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 16,
};
assert.deepEqual(minified.sourceMap._mappings._array[2], mapping);
},
- 'should have _p_ mapping': function (minified) {
+ 'has `red` mapping': function (minified) {
+ var mapping = {
+ generatedLine: 1,
+ generatedColumn: 22,
+ originalLine: 1,
+ originalColumn: 24,
+ source: '$stdin',
+ name: null
+ };
+ assert.deepEqual(minified.sourceMap._mappings._array[3], mapping);
+ },
+ 'has `p` mapping': function (minified) {
var mapping = {
generatedLine: 2,
generatedColumn: 0,
source: '$stdin',
name: null
};
- assert.deepEqual(minified.sourceMap._mappings._array[3], mapping);
+ assert.deepEqual(minified.sourceMap._mappings._array[4], mapping);
},
- 'should have _color:blue_ mapping': function (minified) {
+ 'has second `color` mapping': function (minified) {
var mapping = {
generatedLine: 2,
generatedColumn: 2,
source: '$stdin',
name: null
};
- assert.deepEqual(minified.sourceMap._mappings._array[4], mapping);
+ assert.deepEqual(minified.sourceMap._mappings._array[5], mapping);
},
- 'should have _div_ mapping': function (minified) {
+ 'has `blue` mapping': function (minified) {
+ var mapping = {
+ generatedLine: 2,
+ generatedColumn: 8,
+ originalLine: 1,
+ originalColumn: 38,
+ source: '$stdin',
+ name: null
+ };
+ assert.deepEqual(minified.sourceMap._mappings._array[6], mapping);
+ },
+ 'has `div` mapping': function (minified) {
var mapping = {
generatedLine: 4,
generatedColumn: 0,
source: '$stdin',
name: null
};
- assert.deepEqual(minified.sourceMap._mappings._array[5], mapping);
+ assert.deepEqual(minified.sourceMap._mappings._array[7], mapping);
},
- 'should have _color:pink_ mapping': function (minified) {
+ 'has third `color` mapping': function (minified) {
var mapping = {
generatedLine: 4,
generatedColumn: 4,
source: '$stdin',
name: null
};
- assert.deepEqual(minified.sourceMap._mappings._array[6], mapping);
+ assert.deepEqual(minified.sourceMap._mappings._array[8], mapping);
+ },
+ 'has `pink` mapping': function (minified) {
+ var mapping = {
+ generatedLine: 4,
+ generatedColumn: 10,
+ originalLine: 1,
+ originalColumn: 55,
+ source: '$stdin',
+ name: null
+ };
+ assert.deepEqual(minified.sourceMap._mappings._array[9], mapping);
}
},
'shorthands': {
'topic': function () {
return new CleanCSS({ sourceMap: true }).minify('a{background:url(image.png);background-color:red}');
},
- 'should have 3 mappings': function(minified) {
- assert.lengthOf(minified.sourceMap._mappings._array, 3);
+ 'has 3 mappings': function(minified) {
+ assert.lengthOf(minified.sourceMap._mappings._array, 5);
},
- 'should have selector mapping': function (minified) {
+ 'has `a` mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 0,
};
assert.deepEqual(minified.sourceMap._mappings._array[0], mapping);
},
- 'should have _background_ mapping': function (minified) {
+ 'has `background` mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 2,
};
assert.deepEqual(minified.sourceMap._mappings._array[1], mapping);
},
- 'should have _background-color_ mapping': function (minified) {
+ 'has `url(image.png)` mapping': function (minified) {
+ var mapping = {
+ generatedLine: 1,
+ generatedColumn: 13,
+ originalLine: 1,
+ originalColumn: 13,
+ source: '$stdin',
+ name: null
+ };
+ assert.deepEqual(minified.sourceMap._mappings._array[2], mapping);
+ },
+ 'has `background-color` mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 28,
source: '$stdin',
name: null
};
- assert.deepEqual(minified.sourceMap._mappings._array[2], mapping);
+ assert.deepEqual(minified.sourceMap._mappings._array[3], mapping);
+ },
+ 'has `red` mapping': function (minified) {
+ var mapping = {
+ generatedLine: 1,
+ generatedColumn: 45,
+ originalLine: 1,
+ originalColumn: 45,
+ source: '$stdin',
+ name: null
+ };
+ assert.deepEqual(minified.sourceMap._mappings._array[4], mapping);
}
},
'keyframes': {
'topic': function () {
return new CleanCSS({ sourceMap: true }).minify('@-webkit-keyframes frames {\n 0% {\n border: 1px;\n }\n 100% {\n border: 3px;\n }\n}');
},
- 'should have 5 mappings': function(minified) {
- assert.lengthOf(minified.sourceMap._mappings._array, 5);
+ 'has 7 mappings': function(minified) {
+ assert.lengthOf(minified.sourceMap._mappings._array, 7);
},
- 'should have _@keframes_ mapping': function (minified) {
+ 'has `@keframes` mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 0,
};
assert.deepEqual(minified.sourceMap._mappings._array[0], mapping);
},
- 'should have _0%_ mapping': function (minified) {
+ 'has `0%` mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 26,
};
assert.deepEqual(minified.sourceMap._mappings._array[1], mapping);
},
- 'should have _border:1px_ mapping': function (minified) {
+ 'has `border` mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 29,
};
assert.deepEqual(minified.sourceMap._mappings._array[2], mapping);
},
- 'should have _100%_ mapping': function (minified) {
+ 'has `1px` mapping': function (minified) {
+ var mapping = {
+ generatedLine: 1,
+ generatedColumn: 36,
+ originalLine: 3,
+ originalColumn: 12,
+ source: '$stdin',
+ name: null
+ };
+ assert.deepEqual(minified.sourceMap._mappings._array[3], mapping);
+ },
+ 'has `100%` mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 40,
source: '$stdin',
name: null
};
- assert.deepEqual(minified.sourceMap._mappings._array[3], mapping);
+ assert.deepEqual(minified.sourceMap._mappings._array[4], mapping);
},
- 'should have _border:3px_ mapping': function (minified) {
+ 'has second `border` mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 45,
source: '$stdin',
name: null
};
- assert.deepEqual(minified.sourceMap._mappings._array[4], mapping);
+ assert.deepEqual(minified.sourceMap._mappings._array[5], mapping);
+ },
+ 'has `3px` mapping': function (minified) {
+ var mapping = {
+ generatedLine: 1,
+ generatedColumn: 52,
+ originalLine: 6,
+ originalColumn: 12,
+ source: '$stdin',
+ name: null
+ };
+ assert.deepEqual(minified.sourceMap._mappings._array[6], mapping);
}
},
'double comments': {
'topic': function () {
return new CleanCSS({ sourceMap: true }).minify('/* COMMENT 1 */\n/* COMMENT 2 */\ndiv{color:red}');
},
- 'should have 2 mappings': function(minified) {
- assert.lengthOf(minified.sourceMap._mappings._array, 2);
+ 'has 3 mappings': function(minified) {
+ assert.lengthOf(minified.sourceMap._mappings._array, 3);
},
- 'should have _div__ mapping': function (minified) {
+ 'has `div`_ mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 0,
};
assert.deepEqual(minified.sourceMap._mappings._array[0], mapping);
},
- 'should have _color:red_ mapping': function (minified) {
+ 'has `color` mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 4,
name: null
};
assert.deepEqual(minified.sourceMap._mappings._array[1], mapping);
+ },
+ 'has `red` mapping': function (minified) {
+ var mapping = {
+ generatedLine: 1,
+ generatedColumn: 10,
+ originalLine: 3,
+ originalColumn: 10,
+ source: '$stdin',
+ name: null
+ };
+ assert.deepEqual(minified.sourceMap._mappings._array[2], mapping);
}
}
})
'topic': function () {
return new CleanCSS({ sourceMap: inputMap }).minify('div > a {\n color: red;\n}');
},
- 'should have 2 mappings': function (minified) {
- assert.lengthOf(minified.sourceMap._mappings._array, 2);
+ 'has 3 mappings': function (minified) {
+ assert.lengthOf(minified.sourceMap._mappings._array, 3);
},
- 'should have selector mapping': function (minified) {
+ 'has `div > a` mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 0,
};
assert.deepEqual(minified.sourceMap._mappings._array[0], mapping);
},
- 'should have _color:red_ mapping': function (minified) {
+ 'has `color` mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 6,
name: null
};
assert.deepEqual(minified.sourceMap._mappings._array[1], mapping);
+ },
+ 'has `red` mapping': function (minified) {
+ var mapping = {
+ generatedLine: 1,
+ generatedColumn: 12,
+ originalLine: 2,
+ originalColumn: 2,
+ source: 'styles.less',
+ name: null
+ };
+ assert.deepEqual(minified.sourceMap._mappings._array[2], mapping);
}
},
'input map from source': {
'topic': function () {
return new CleanCSS({ sourceMap: true }).minify('div > a {\n color: red;\n}/*# sourceMappingURL=' + inputMapPath + ' */');
},
- 'should have 2 mappings': function (minified) {
- assert.lengthOf(minified.sourceMap._mappings._array, 2);
+ 'has 3 mappings': function (minified) {
+ assert.lengthOf(minified.sourceMap._mappings._array, 3);
},
- 'should have selector mapping': function (minified) {
+ 'has `div > a` mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 0,
};
assert.deepEqual(minified.sourceMap._mappings._array[0], mapping);
},
- 'should have _color:red_ mapping': function (minified) {
+ 'has `color` mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 6,
name: null
};
assert.deepEqual(minified.sourceMap._mappings._array[1], mapping);
+ },
+ 'has second `color` mapping': function (minified) {
+ var mapping = {
+ generatedLine: 1,
+ generatedColumn: 12,
+ originalLine: 2,
+ originalColumn: 2,
+ source: path.join('test', 'fixtures', 'source-maps', 'styles.less'),
+ name: null
+ };
+ assert.deepEqual(minified.sourceMap._mappings._array[2], mapping);
}
},
'input map from source with root': {
'topic': function () {
return new CleanCSS({ sourceMap: true, root: './test/fixtures' }).minify('div > a {\n color: red;\n}/*# sourceMappingURL=source-maps/styles.css.map */');
},
- 'should have 2 mappings': function (minified) {
- assert.lengthOf(minified.sourceMap._mappings._array, 2);
+ 'has 3 mappings': function (minified) {
+ assert.lengthOf(minified.sourceMap._mappings._array, 3);
},
- 'should have selector mapping': function (minified) {
+ 'has `div > a` mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 0,
};
assert.deepEqual(minified.sourceMap._mappings._array[0], mapping);
},
- 'should have _color:red_ mapping': function (minified) {
+ 'has `color` mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 6,
name: null
};
assert.deepEqual(minified.sourceMap._mappings._array[1], mapping);
+ },
+ 'has `red` mapping': function (minified) {
+ var mapping = {
+ generatedLine: 1,
+ generatedColumn: 12,
+ originalLine: 2,
+ originalColumn: 2,
+ source: path.join('source-maps', 'styles.less'),
+ name: null
+ };
+ assert.deepEqual(minified.sourceMap._mappings._array[2], mapping);
}
},
'input map from source with target': {
'topic': function () {
return new CleanCSS({ sourceMap: true, target: './test' }).minify('div > a {\n color: red;\n}/*# sourceMappingURL=' + inputMapPath + ' */');
},
- 'should have 2 mappings': function (minified) {
- assert.lengthOf(minified.sourceMap._mappings._array, 2);
+ 'has 3 mappings': function (minified) {
+ assert.lengthOf(minified.sourceMap._mappings._array, 3);
},
- 'should have selector mapping': function (minified) {
+ 'has `div > a` mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 0,
};
assert.deepEqual(minified.sourceMap._mappings._array[0], mapping);
},
- 'should have _color:red_ mapping': function (minified) {
+ 'has `color` mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 6,
name: null
};
assert.deepEqual(minified.sourceMap._mappings._array[1], mapping);
+ },
+ 'has `red` mapping': function (minified) {
+ var mapping = {
+ generatedLine: 1,
+ generatedColumn: 12,
+ originalLine: 2,
+ originalColumn: 2,
+ source: path.join('fixtures', 'source-maps', 'styles.less'),
+ name: null
+ };
+ assert.deepEqual(minified.sourceMap._mappings._array[2], mapping);
}
},
'complex input map': {
'topic': function () {
return new CleanCSS({ sourceMap: true, root: path.dirname(inputMapPath) }).minify('@import url(import.css);');
},
- 'should have 4 mappings': function (minified) {
- assert.lengthOf(minified.sourceMap._mappings._array, 4);
+ 'has 6 mappings': function (minified) {
+ assert.lengthOf(minified.sourceMap._mappings._array, 6);
},
- 'should have first selector mapping': function (minified) {
+ 'has `div` mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 0,
};
assert.deepEqual(minified.sourceMap._mappings._array[0], mapping);
},
- 'should have _color:red_ mapping': function (minified) {
+ 'has `color` mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 4,
};
assert.deepEqual(minified.sourceMap._mappings._array[1], mapping);
},
- 'should have second selector mapping': function (minified) {
+ 'has `red` mapping': function (minified) {
+ var mapping = {
+ generatedLine: 1,
+ generatedColumn: 10,
+ originalLine: 2,
+ originalColumn: 2,
+ source: 'some.less',
+ name: null
+ };
+ assert.deepEqual(minified.sourceMap._mappings._array[2], mapping);
+ },
+ 'has `div > a` mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 14,
source: 'styles.less',
name: null
};
- assert.deepEqual(minified.sourceMap._mappings._array[2], mapping);
+ assert.deepEqual(minified.sourceMap._mappings._array[3], mapping);
},
- 'should have _color:blue_ mapping': function (minified) {
+ 'has second `color` mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 20,
source: 'styles.less',
name: null
};
- assert.deepEqual(minified.sourceMap._mappings._array[3], mapping);
+ assert.deepEqual(minified.sourceMap._mappings._array[4], mapping);
+ },
+ 'has `blue` mapping': function (minified) {
+ var mapping = {
+ generatedLine: 1,
+ generatedColumn: 26,
+ originalLine: 2,
+ originalColumn: 2,
+ source: 'styles.less',
+ name: null
+ };
+ assert.deepEqual(minified.sourceMap._mappings._array[5], mapping);
}
},
'complex input map referenced by path': {
'topic': function () {
return new CleanCSS({ sourceMap: true }).minify('@import url(test/fixtures/source-maps/import.css);');
},
- 'should have 4 mappings': function (minified) {
- assert.lengthOf(minified.sourceMap._mappings._array, 4);
+ 'has 6 mappings': function (minified) {
+ assert.lengthOf(minified.sourceMap._mappings._array, 6);
}
},
'complex but partial input map referenced by path': {
'topic': function () {
return new CleanCSS({ sourceMap: true }).minify('@import url(test/fixtures/source-maps/no-map-import.css);');
},
- 'should have 4 mappings': function (minified) {
- assert.lengthOf(minified.sourceMap._mappings._array, 4);
+ 'has 6 mappings': function (minified) {
+ assert.lengthOf(minified.sourceMap._mappings._array, 6);
},
- 'should have 2 mappings to .less file': function (minified) {
+ 'has 3 mappings to .less file': function (minified) {
var fromLess = minified.sourceMap._mappings._array.filter(function (mapping) {
return mapping.source == path.join('test', 'fixtures', 'source-maps', 'styles.less');
});
- assert.lengthOf(fromLess, 2);
+ assert.lengthOf(fromLess, 3);
},
- 'should have 2 mappings to .css file': function (minified) {
+ 'has 3 mappings to .css file': function (minified) {
var fromCSS = minified.sourceMap._mappings._array.filter(function (mapping) {
return mapping.source == path.join('test', 'fixtures', 'source-maps', 'no-map.css');
});
- assert.lengthOf(fromCSS, 2);
+ assert.lengthOf(fromCSS, 3);
}
},
'complex input map with an existing file as target': {
'topic': function () {
return new CleanCSS({ sourceMap: true, target: path.join('test', 'fixtures', 'source-maps', 'styles.css') }).minify('@import url(test/fixtures/source-maps/styles.css);');
},
- 'should have 2 mappings': function (minified) {
- assert.lengthOf(minified.sourceMap._mappings._array, 2);
+ 'has 3 mappings': function (minified) {
+ assert.lengthOf(minified.sourceMap._mappings._array, 3);
},
- 'should have 2 mappings to styles.less file': function (minified) {
+ 'has 3 mappings to styles.less file': function (minified) {
var stylesSource = minified.sourceMap._mappings._array.filter(function (mapping) {
return mapping.source == 'styles.less';
});
- assert.lengthOf(stylesSource, 2);
+ assert.lengthOf(stylesSource, 3);
}
},
'nested once': {
'topic': function () {
return new CleanCSS({ sourceMap: true }).minify('@import url(test/fixtures/source-maps/nested/once.css);');
},
- 'should have 2 mappings': function (minified) {
- assert.lengthOf(minified.sourceMap._mappings._array, 2);
+ 'has 3 mappings': function (minified) {
+ assert.lengthOf(minified.sourceMap._mappings._array, 3);
},
- 'should have "section > div a" mapping': function (minified) {
+ 'has `section > div a` mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 0,
};
assert.deepEqual(minified.sourceMap._mappings._array[0], mapping);
},
- 'should have "color:red" mapping': function (minified) {
+ 'has `color` mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 14,
name: null
};
assert.deepEqual(minified.sourceMap._mappings._array[1], mapping);
+ },
+ 'has `red` mapping': function (minified) {
+ var mapping = {
+ generatedLine: 1,
+ generatedColumn: 20,
+ originalLine: 3,
+ originalColumn: 4,
+ source: path.join('test', 'fixtures', 'source-maps', 'nested', 'once.less'),
+ name: null
+ };
+ assert.deepEqual(minified.sourceMap._mappings._array[2], mapping);
}
},
'nested twice': {
'topic': function () {
return new CleanCSS({ sourceMap: true }).minify('@import url(test/fixtures/source-maps/nested/twice.css);');
},
- 'should have 2 mappings': function (minified) {
- assert.lengthOf(minified.sourceMap._mappings._array, 2);
+ 'has 3 mappings': function (minified) {
+ assert.lengthOf(minified.sourceMap._mappings._array, 3);
},
- 'should have "body > nav a" mapping': function (minified) {
+ 'has `body > nav a` mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 0,
};
assert.deepEqual(minified.sourceMap._mappings._array[0], mapping);
},
- 'should have "color:red" mapping': function (minified) {
+ 'has `color` mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 11,
name: null
};
assert.deepEqual(minified.sourceMap._mappings._array[1], mapping);
+ },
+ 'has `red` mapping': function (minified) {
+ var mapping = {
+ generatedLine: 1,
+ generatedColumn: 17,
+ originalLine: 4,
+ originalColumn: 6,
+ source: path.join('test', 'fixtures', 'source-maps', 'nested', 'twice.less'),
+ name: null
+ };
+ assert.deepEqual(minified.sourceMap._mappings._array[2], mapping);
}
},
'input source map with missing mutliselector input': {
'topic': function () {
return new CleanCSS({ sourceMap: '{"version":3,"sources":["source.css"],"names":[],"mappings":"AAAA;;;;IAII,YAAW;EACd"}' }).minify('a,\na:hover,\na:visited\n{\n color: red;\n}');
},
- 'should have 4 mappings': function (minified) {
- assert.lengthOf(minified.sourceMap._mappings._array, 4);
+ 'has 5 mappings': function (minified) {
+ assert.lengthOf(minified.sourceMap._mappings._array, 5);
},
- 'should have "a" mapping': function (minified) {
+ 'has `a` mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 0,
};
assert.deepEqual(minified.sourceMap._mappings._array[0], mapping);
},
- 'should have "a:hover" mapping': function (minified) {
+ 'has `a:hover` mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 2,
};
assert.deepEqual(minified.sourceMap._mappings._array[1], mapping);
},
- 'should have "a:visited" mapping': function (minified) {
+ 'has `a:visited` mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 10,
};
assert.deepEqual(minified.sourceMap._mappings._array[2], mapping);
},
- 'should have "color:red" mapping': function (minified) {
+ 'has `color` mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 20,
name: null
};
assert.deepEqual(minified.sourceMap._mappings._array[3], mapping);
+ },
+ 'has `red` mapping': function (minified) {
+ var mapping = {
+ generatedLine: 1,
+ generatedColumn: 26,
+ originalLine: 5,
+ originalColumn: 4,
+ source: 'source.css',
+ name: null
+ };
+ assert.deepEqual(minified.sourceMap._mappings._array[4], mapping);
}
},
'input source map with missing mutliselector sortable input': {
'topic': function () {
return new CleanCSS({ sourceMap: '{"version":3,"sources":["source.css"],"names":[],"mappings":"AAAA;;;;IAII,YAAW;EACd"}' }).minify('a.button:link,\na.button:visited,\na.button:hover\n{\n color: red;\n}');
},
- 'should have 4 mappings': function (minified) {
- assert.lengthOf(minified.sourceMap._mappings._array, 4);
+ 'has 5 mappings': function (minified) {
+ assert.lengthOf(minified.sourceMap._mappings._array, 5);
},
- 'should have "a.button:hover" mapping': function (minified) {
+ 'has `a.button:hover` mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 0,
};
assert.deepEqual(minified.sourceMap._mappings._array[0], mapping);
},
- 'should have "a.button:link" mapping': function (minified) {
+ 'has `a.button:link` mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 15,
};
assert.deepEqual(minified.sourceMap._mappings._array[1], mapping);
},
- 'should have "a.button:visited" mapping': function (minified) {
+ 'has `a.button:visited` mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 29,
};
assert.deepEqual(minified.sourceMap._mappings._array[2], mapping);
},
- 'should have "color:red" mapping': function (minified) {
+ 'has `color` mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 46,
name: null
};
assert.deepEqual(minified.sourceMap._mappings._array[3], mapping);
+ },
+ 'has `red` mapping': function (minified) {
+ var mapping = {
+ generatedLine: 1,
+ generatedColumn: 52,
+ originalLine: 5,
+ originalColumn: 4,
+ source: 'source.css',
+ name: null
+ };
+ assert.deepEqual(minified.sourceMap._mappings._array[4], mapping);
}
}
})
'has right output': function (errors, minified) {
assert.equal(minified.styles, 'div,section>div a{color:red}div>a{color:#00f}');
},
- 'should have 5 mappings': function (minified) {
- assert.lengthOf(minified.sourceMap._mappings._array, 5);
+ 'has 7 mappings': function (minified) {
+ assert.lengthOf(minified.sourceMap._mappings._array, 7);
},
- 'should have "div" mapping': function (minified) {
+ 'has `div` mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 0,
};
assert.deepEqual(minified.sourceMap._mappings._array[0], mapping);
},
- 'should have "section > div a" mapping': function (minified) {
+ 'has `section > div a` mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 4,
};
assert.deepEqual(minified.sourceMap._mappings._array[1], mapping);
},
- 'should have "color: red" mapping': function (minified) {
+ 'has `color` mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 18,
};
assert.deepEqual(minified.sourceMap._mappings._array[2], mapping);
},
- 'should have "div > a" mapping': function (minified) {
+ 'has `red` mapping': function (minified) {
+ var mapping = {
+ generatedLine: 1,
+ generatedColumn: 24,
+ originalLine: 2,
+ originalColumn: 2,
+ source: path.join('test', 'fixtures', 'source-maps', 'some.less'),
+ name: null
+ };
+ assert.deepEqual(minified.sourceMap._mappings._array[3], mapping);
+ },
+ 'has `div > a` mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 28,
source: path.join('test', 'fixtures', 'source-maps', 'styles.less'),
name: null
};
- assert.deepEqual(minified.sourceMap._mappings._array[3], mapping);
+ assert.deepEqual(minified.sourceMap._mappings._array[4], mapping);
},
- 'should have "color: blue" mapping': function (minified) {
+ 'has second `color` mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 34,
source: path.join('test', 'fixtures', 'source-maps', 'styles.less'),
name: null
};
- assert.deepEqual(minified.sourceMap._mappings._array[4], mapping);
+ assert.deepEqual(minified.sourceMap._mappings._array[5], mapping);
+ },
+ 'has `#00f` mapping': function (minified) {
+ var mapping = {
+ generatedLine: 1,
+ generatedColumn: 40,
+ originalLine: 2,
+ originalColumn: 2,
+ source: path.join('test', 'fixtures', 'source-maps', 'styles.less'),
+ name: null
+ };
+ assert.deepEqual(minified.sourceMap._mappings._array[6], mapping);
}
}
},
}
});
},
- 'should have 5 mappings': function (minified) {
- assert.lengthOf(minified.sourceMap._mappings._array, 5);
+ 'has 7 mappings': function (minified) {
+ assert.lengthOf(minified.sourceMap._mappings._array, 7);
},
- 'should have right sources': function (minified) {
+ 'has right sources': function (minified) {
var sources = [];
minified.sourceMap._mappings._array.forEach(function (m) {
if (sources.indexOf(m.source) === -1)
'topic': function () {
return new CleanCSS({ sourceMap: true }).minify('div > a {\n color: red;\n}');
},
- 'should have 2 mappings': function (minified) {
- assert.lengthOf(minified.sourceMap._mappings._array, 2);
+ 'has 3 mappings': function (minified) {
+ assert.lengthOf(minified.sourceMap._mappings._array, 3);
},
- 'should have embedded sources': function (minified) {
+ 'has embedded sources': function (minified) {
assert.deepEqual(JSON.parse(minified.sourceMap.toString()).sources, ['$stdin']);
},
- 'should have embedded sources content': function (minified) {
+ 'has embedded sources content': function (minified) {
assert.isUndefined(JSON.parse(minified.sourceMap.toString()).sourcesContent);
},
- 'should have selector mapping': function (minified) {
+ 'has selector mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 0,
};
assert.deepEqual(minified.sourceMap._mappings._array[0], mapping);
},
- 'should have _color:red_ mapping': function (minified) {
+ 'has `color` mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 6,
name: null
};
assert.deepEqual(minified.sourceMap._mappings._array[1], mapping);
+ },
+ 'has `red` mapping': function (minified) {
+ var mapping = {
+ generatedLine: 1,
+ generatedColumn: 12,
+ originalLine: 2,
+ originalColumn: 9,
+ source: '$stdin',
+ name: null
+ };
+ assert.deepEqual(minified.sourceMap._mappings._array[2], mapping);
}
},
'from string - on': {
'topic': function () {
return new CleanCSS({ sourceMap: true, sourceMapInlineSources: true }).minify('div > a {\n color: red;\n}');
},
- 'should have 2 mappings': function (minified) {
- assert.lengthOf(minified.sourceMap._mappings._array, 2);
+ 'has 3 mappings': function (minified) {
+ assert.lengthOf(minified.sourceMap._mappings._array, 3);
},
- 'should have embedded sources': function (minified) {
+ 'has embedded sources': function (minified) {
assert.deepEqual(JSON.parse(minified.sourceMap.toString()).sources, ['$stdin']);
},
- 'should have embedded sources content': function (minified) {
+ 'has embedded sources content': function (minified) {
assert.deepEqual(JSON.parse(minified.sourceMap.toString()).sourcesContent, ['div > a {\n color: red;\n}']);
}
},
'test/fixtures/partials/three.css'
]);
},
- 'should have 4 mappings': function (minified) {
- assert.lengthOf(minified.sourceMap._mappings._array, 4);
+ 'has 6 mappings': function (minified) {
+ assert.lengthOf(minified.sourceMap._mappings._array, 6);
},
- 'should have embedded sources': function (minified) {
+ 'has embedded sources': function (minified) {
assert.deepEqual(JSON.parse(minified.sourceMap.toString()).sources, [
path.join('test', 'fixtures', 'partials', 'one.css'),
path.join('test', 'fixtures', 'partials', 'three.css')
]);
},
- 'should have embedded sources content': function (minified) {
+ 'has embedded sources content': function (minified) {
assert.isUndefined(JSON.parse(minified.sourceMap.toString()).sourcesContent);
}
},
'test/fixtures/partials/three.css'
]);
},
- 'should have 4 mappings': function (minified) {
- assert.lengthOf(minified.sourceMap._mappings._array, 4);
+ 'has 6 mappings': function (minified) {
+ assert.lengthOf(minified.sourceMap._mappings._array, 6);
},
- 'should have embedded sources': function (minified) {
+ 'has embedded sources': function (minified) {
assert.deepEqual(JSON.parse(minified.sourceMap.toString()).sources, [
path.join('test', 'fixtures', 'partials', 'one.css'),
path.join('test', 'fixtures', 'partials', 'three.css')
]);
},
- 'should have embedded sources content': function (minified) {
+ 'has embedded sources content': function (minified) {
assert.deepEqual(JSON.parse(minified.sourceMap.toString()).sourcesContent, [
'.one { color:#f00; }' + lineBreak,
'.three {background-image: url(test/fixtures/partials/extra/down.gif);}' + lineBreak
'http://127.0.0.1/some.css'
], this.callback);
},
- 'should have 2 mappings': function (minified) {
- assert.lengthOf(minified.sourceMap._mappings._array, 2);
+ 'has 3 mappings': function (minified) {
+ assert.lengthOf(minified.sourceMap._mappings._array, 3);
},
- 'should have embedded sources': function (minified) {
+ 'has embedded sources': function (minified) {
assert.deepEqual(JSON.parse(minified.sourceMap.toString()).sources, [
'http://127.0.0.1/some.css'
]);
},
- 'should have embedded sources content': function (minified) {
+ 'has embedded sources content': function (minified) {
assert.deepEqual(JSON.parse(minified.sourceMap.toString()).sourcesContent, [
'div{background:url(http://127.0.0.1/image.png)}',
]);
}
});
},
- 'should have 5 mappings': function (minified) {
- assert.lengthOf(minified.sourceMap._mappings._array, 5);
+ 'has 7 mappings': function (minified) {
+ assert.lengthOf(minified.sourceMap._mappings._array, 7);
},
- 'should have embedded sources': function (minified) {
+ 'has embedded sources': function (minified) {
assert.deepEqual(JSON.parse(minified.sourceMap.toString()).sources, [
'test/fixtures/source-maps/some.css',
'test/fixtures/source-maps/nested/once.css',
'test/fixtures/source-maps/styles.css'
]);
},
- 'should have embedded sources content': function (minified) {
+ 'has embedded sources content': function (minified) {
assert.isUndefined(JSON.parse(minified.sourceMap.toString()).sourcesContent);
}
},
}
});
},
- 'should have 5 mappings': function (minified) {
- assert.lengthOf(minified.sourceMap._mappings._array, 5);
+ 'has 7 mappings': function (minified) {
+ assert.lengthOf(minified.sourceMap._mappings._array, 7);
},
- 'should have embedded sources': function (minified) {
+ 'has embedded sources': function (minified) {
assert.deepEqual(JSON.parse(minified.sourceMap.toString()).sources, [
'test/fixtures/source-maps/some.css',
'test/fixtures/source-maps/nested/once.css',
'test/fixtures/source-maps/styles.css'
]);
},
- 'should have embedded sources content': function (minified) {
+ 'has embedded sources content': function (minified) {
assert.deepEqual(JSON.parse(minified.sourceMap.toString()).sourcesContent, [
'div {\n color: red;\n}',
'section > div a {\n color: red;\n}',
sourceMapInlineSources: true
}).minify('div > a {\n color: red;\n}');
},
- 'should have 2 mappings': function (minified) {
- assert.lengthOf(minified.sourceMap._mappings._array, 2);
+ 'has 3 mappings': function (minified) {
+ assert.lengthOf(minified.sourceMap._mappings._array, 3);
},
- 'should have embedded sources': function (minified) {
+ 'has embedded sources': function (minified) {
assert.deepEqual(JSON.parse(minified.sourceMap.toString()).sources, ['styles.less']);
},
- 'should have embedded sources content': function (minified) {
+ 'has embedded sources content': function (minified) {
assert.deepEqual(JSON.parse(minified.sourceMap.toString()).sourcesContent, ['div > a {\n color: blue;\n}\n']);
},
- 'should have selector mapping': function (minified) {
+ 'has selector mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 0,
};
assert.deepEqual(minified.sourceMap._mappings._array[0], mapping);
},
- 'should have _color:red_ mapping': function (minified) {
+ 'has `color` mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 6,
name: null
};
assert.deepEqual(minified.sourceMap._mappings._array[1], mapping);
+ },
+ 'has `red` mapping': function (minified) {
+ var mapping = {
+ generatedLine: 1,
+ generatedColumn: 12,
+ originalLine: 2,
+ originalColumn: 2,
+ source: 'styles.less',
+ name: null
+ };
+ assert.deepEqual(minified.sourceMap._mappings._array[2], mapping);
}
},
'multiple': {
}
});
},
- 'should have 5 mappings': function (minified) {
- assert.lengthOf(minified.sourceMap._mappings._array, 5);
+ 'has 7 mappings': function (minified) {
+ assert.lengthOf(minified.sourceMap._mappings._array, 7);
},
- 'should have embedded sources': function (minified) {
+ 'has embedded sources': function (minified) {
assert.deepEqual(JSON.parse(minified.sourceMap.toString()).sources, [
path.join('test', 'fixtures', 'source-maps', 'some.less'),
path.join('test', 'fixtures', 'source-maps', 'nested', 'once.less'),
path.join('test', 'fixtures', 'source-maps', 'styles.less')
]);
},
- 'should have embedded sources content': function (minified) {
+ 'has embedded sources content': function (minified) {
assert.deepEqual(JSON.parse(minified.sourceMap.toString()).sourcesContent, [
'div {\n color: red;\n}\n',
'section {\n > div a {\n color:red;\n }\n}\n',
}
});
},
- 'should have 5 mappings': function (minified) {
- assert.lengthOf(minified.sourceMap._mappings._array, 5);
+ 'has 7 mappings': function (minified) {
+ assert.lengthOf(minified.sourceMap._mappings._array, 7);
},
- 'should have embedded sources': function (minified) {
+ 'has embedded sources': function (minified) {
assert.deepEqual(JSON.parse(minified.sourceMap.toString()).sources, [
path.join('fixtures', 'source-maps', 'some.less'),
path.join('fixtures', 'source-maps', 'nested', 'once.less'),
path.join('fixtures', 'source-maps', 'styles.less')
]);
},
- 'should have embedded sources content': function (minified) {
+ 'has embedded sources content': function (minified) {
assert.deepEqual(JSON.parse(minified.sourceMap.toString()).sourcesContent, [
'div {\n color: red;\n}\n',
'section {\n > div a {\n color:red;\n }\n}\n',
}
});
},
- 'should have 5 mappings': function (minified) {
- assert.lengthOf(minified.sourceMap._mappings._array, 5);
+ 'has 7 mappings': function (minified) {
+ assert.lengthOf(minified.sourceMap._mappings._array, 7);
},
- 'should have embedded sources': function (minified) {
+ 'has embedded sources': function (minified) {
assert.deepEqual(JSON.parse(minified.sourceMap.toString()).sources, [
path.join('test', 'fixtures', 'source-maps', 'some.less'),
path.join('test', 'fixtures', 'source-maps', 'nested', 'once.less'),
path.join('test', 'fixtures', 'source-maps', 'styles.less')
]);
},
- 'should have embedded sources content': function (minified) {
+ 'has embedded sources content': function (minified) {
assert.deepEqual(JSON.parse(minified.sourceMap.toString()).sourcesContent, [
'div {\n color: red;\n}\n',
'section {\n > div a {\n color:red;\n }\n}\n',
}
});
},
- 'should have 5 mappings': function (minified) {
- assert.lengthOf(minified.sourceMap._mappings._array, 5);
+ 'has 7 mappings': function (minified) {
+ assert.lengthOf(minified.sourceMap._mappings._array, 7);
},
- 'should have embedded sources': function (minified) {
+ 'has embedded sources': function (minified) {
assert.deepEqual(JSON.parse(minified.sourceMap.toString()).sources, [
path.join('test', 'fixtures', 'source-maps', 'some.less'),
path.join('test', 'fixtures', 'source-maps', 'nested', 'once.less'),
path.join('test', 'fixtures', 'source-maps', 'styles.less')
]);
},
- 'should have embedded sources content': function (minified) {
+ 'has embedded sources content': function (minified) {
assert.isUndefined(JSON.parse(minified.sourceMap.toString()).sourcesContent);
}
},
}
}, this.callback);
},
- 'should have 5 mappings': function (minified) {
- assert.lengthOf(minified.sourceMap._mappings._array, 5);
+ 'has 7 mappings': function (minified) {
+ assert.lengthOf(minified.sourceMap._mappings._array, 7);
},
- 'should have embedded sources': function (minified) {
+ 'has embedded sources': function (minified) {
assert.deepEqual(JSON.parse(minified.sourceMap.toString()).sources, [
'http://127.0.0.1/some.less',
path.join('test', 'fixtures', 'source-maps', 'nested', 'once.less'),
'http://127.0.0.1/styles.less'
]);
},
- 'should have embedded sources content': function (minified) {
+ 'has embedded sources content': function (minified) {
assert.deepEqual(JSON.parse(minified.sourceMap.toString()).sourcesContent, [
'div {\n color: red;\n}\n',
'section {' + lineBreak + ' > div a {' + lineBreak + ' color:red;' + lineBreak + ' }' + lineBreak + '}' + lineBreak,
}
}, this.callback);
},
- 'should have 5 mappings': function (minified) {
- assert.lengthOf(minified.sourceMap._mappings._array, 5);
+ 'has 7 mappings': function (minified) {
+ assert.lengthOf(minified.sourceMap._mappings._array, 7);
},
'should warn about some.less': function (minified) {
assert.deepEqual(minified.warnings, ['Broken original source file at "http://127.0.0.1/some.less" - 404']);
},
- 'should have embedded sources': function (minified) {
+ 'has embedded sources': function (minified) {
assert.deepEqual(JSON.parse(minified.sourceMap.toString()).sources, [
'http://127.0.0.1/some.less',
path.join('test', 'fixtures', 'source-maps', 'nested', 'once.less'),
'http://127.0.0.1/styles.less'
]);
},
- 'should have embedded sources content': function (minified) {
+ 'has embedded sources content': function (minified) {
assert.deepEqual(JSON.parse(minified.sourceMap.toString()).sourcesContent, [
null,
'section {' + lineBreak + ' > div a {' + lineBreak + ' color:red;' + lineBreak + ' }' + lineBreak + '}' + lineBreak,
}
});
},
- 'should have 5 mappings': function (minified) {
- assert.lengthOf(minified.sourceMap._mappings._array, 5);
+ 'has 7 mappings': function (minified) {
+ assert.lengthOf(minified.sourceMap._mappings._array, 7);
},
'should warn about some.less and styles.less': function (minified) {
assert.deepEqual(minified.warnings, [
'No callback given to `#minify` method, cannot fetch a remote file from "http://127.0.0.1/styles.less"'
]);
},
- 'should have embedded sources': function (minified) {
+ 'has embedded sources': function (minified) {
assert.deepEqual(JSON.parse(minified.sourceMap.toString()).sources, [
'http://127.0.0.1/some.less',
path.join('test', 'fixtures', 'source-maps', 'nested', 'once.less'),
'http://127.0.0.1/styles.less'
]);
},
- 'should have embedded sources content': function (minified) {
+ 'has embedded sources content': function (minified) {
assert.deepEqual(JSON.parse(minified.sourceMap.toString()).sourcesContent, [
null,
'section {' + lineBreak + ' > div a {' + lineBreak + ' color:red;' + lineBreak + ' }' + lineBreak + '}' + lineBreak,
'topic': function () {
return new CleanCSS({ sourceMap: true }).minify('a{color:#000}div{color:red}.one{display:block}.two{display:inline;color:red}');
},
- 'should have 5 mappings': function (minified) {
- assert.lengthOf(minified.sourceMap._mappings._array, 9);
+ 'has right output': function (minified) {
+ assert.equal(minified.styles, 'a{color:#000}.two,div{color:red}.one{display:block}.two{display:inline}');
},
- 'should have a merged ".two" mapping': function (minified) {
+ 'has 13 mappings': function (minified) {
+ assert.lengthOf(minified.sourceMap._mappings._array, 13);
+ },
+ 'has a merged `.two` mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 13,
source: '$stdin',
name: null
};
- assert.deepEqual(minified.sourceMap._mappings._array[2], mapping);
+ assert.deepEqual(minified.sourceMap._mappings._array[3], mapping);
},
- 'should have a merged "div" mapping': function (minified) {
+ 'has a merged `div` mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 18,
source: '$stdin',
name: null
};
- assert.deepEqual(minified.sourceMap._mappings._array[3], mapping);
+ assert.deepEqual(minified.sourceMap._mappings._array[4], mapping);
},
- 'should have a merged "color:red" mapping': function (minified) {
+ 'has a merged `color` mapping': function (minified) {
var mapping = {
generatedLine: 1,
generatedColumn: 22,
source: '$stdin',
name: null
};
- assert.deepEqual(minified.sourceMap._mappings._array[4], mapping);
+ assert.deepEqual(minified.sourceMap._mappings._array[5], mapping);
+ },
+ 'has a merged `red` mapping': function (minified) {
+ var mapping = {
+ generatedLine: 1,
+ generatedColumn: 28,
+ originalLine: 1,
+ originalColumn: 72,
+ source: '$stdin',
+ name: null
+ };
+ assert.deepEqual(minified.sourceMap._mappings._array[6], mapping);
}
}
}
'comma separated - level 2': split('linear-gradient(0,#fff,rgba(0,0,0)),red', ['linear-gradient(0,#fff,rgba(0,0,0))', 'red'], ','),
'space separated - level 0': split('#000 #fff #0f0', ['#000', '#fff', '#0f0'], ' '),
'space separated - level 1': split('rgb(0, 0, 0) #fff', ['rgb(0, 0, 0)', '#fff'], ' '),
- 'space separated - level 2': split('linear-gradient(0, #fff, rgba(0, 0, 0)) red', ['linear-gradient(0, #fff, rgba(0, 0, 0))', 'red'], ' ')
+ 'space separated - level 2': split('linear-gradient(0, #fff, rgba(0, 0, 0)) red', ['linear-gradient(0, #fff, rgba(0, 0, 0))', 'red'], ' '),
+ 'with regex': split('no-repeat,0/0', ['no-repeat', '0', '0'], /[ ,\/]/)
})
.addBatch({
'leading space and quote with separator': split(' "Font"', [' "Font"'], ' ', true),
'comma separated - level 2 - with separator': split('linear-gradient(0,#fff,rgba(0,0,0)),red', ['linear-gradient(0,#fff,rgba(0,0,0)),', 'red'], ',', true),
- 'space separated - level 2 - with separator': split('linear-gradient(0, #fff, rgba(0, 0, 0)) red', ['linear-gradient(0, #fff, rgba(0, 0, 0)) ', 'red'], ' ', true)
+ 'space separated - level 2 - with separator': split('linear-gradient(0, #fff, rgba(0, 0, 0)) red', ['linear-gradient(0, #fff, rgba(0, 0, 0)) ', 'red'], ' ', true),
+ 'with regex': split('no-repeat,0/0', ['no-repeat,', '0/', '0'], /[ ,\/]/, true)
})
.export(module);