| var List = require('css-tree').List; | 
| var generate = require('css-tree').generate; | 
| var walk = require('css-tree').walk; | 
|   | 
| var REPLACE = 1; | 
| var REMOVE = 2; | 
| var TOP = 0; | 
| var RIGHT = 1; | 
| var BOTTOM = 2; | 
| var LEFT = 3; | 
| var SIDES = ['top', 'right', 'bottom', 'left']; | 
| var SIDE = { | 
|     'margin-top': 'top', | 
|     'margin-right': 'right', | 
|     'margin-bottom': 'bottom', | 
|     'margin-left': 'left', | 
|   | 
|     'padding-top': 'top', | 
|     'padding-right': 'right', | 
|     'padding-bottom': 'bottom', | 
|     'padding-left': 'left', | 
|   | 
|     'border-top-color': 'top', | 
|     'border-right-color': 'right', | 
|     'border-bottom-color': 'bottom', | 
|     'border-left-color': 'left', | 
|     'border-top-width': 'top', | 
|     'border-right-width': 'right', | 
|     'border-bottom-width': 'bottom', | 
|     'border-left-width': 'left', | 
|     'border-top-style': 'top', | 
|     'border-right-style': 'right', | 
|     'border-bottom-style': 'bottom', | 
|     'border-left-style': 'left' | 
| }; | 
| var MAIN_PROPERTY = { | 
|     'margin': 'margin', | 
|     'margin-top': 'margin', | 
|     'margin-right': 'margin', | 
|     'margin-bottom': 'margin', | 
|     'margin-left': 'margin', | 
|   | 
|     'padding': 'padding', | 
|     'padding-top': 'padding', | 
|     'padding-right': 'padding', | 
|     'padding-bottom': 'padding', | 
|     'padding-left': 'padding', | 
|   | 
|     'border-color': 'border-color', | 
|     'border-top-color': 'border-color', | 
|     'border-right-color': 'border-color', | 
|     'border-bottom-color': 'border-color', | 
|     'border-left-color': 'border-color', | 
|     'border-width': 'border-width', | 
|     'border-top-width': 'border-width', | 
|     'border-right-width': 'border-width', | 
|     'border-bottom-width': 'border-width', | 
|     'border-left-width': 'border-width', | 
|     'border-style': 'border-style', | 
|     'border-top-style': 'border-style', | 
|     'border-right-style': 'border-style', | 
|     'border-bottom-style': 'border-style', | 
|     'border-left-style': 'border-style' | 
| }; | 
|   | 
| function TRBL(name) { | 
|     this.name = name; | 
|     this.loc = null; | 
|     this.iehack = undefined; | 
|     this.sides = { | 
|         'top': null, | 
|         'right': null, | 
|         'bottom': null, | 
|         'left': null | 
|     }; | 
| } | 
|   | 
| TRBL.prototype.getValueSequence = function(declaration, count) { | 
|     var values = []; | 
|     var iehack = ''; | 
|     var hasBadValues = declaration.value.type !== 'Value' || declaration.value.children.some(function(child) { | 
|         var special = false; | 
|   | 
|         switch (child.type) { | 
|             case 'Identifier': | 
|                 switch (child.name) { | 
|                     case '\\0': | 
|                     case '\\9': | 
|                         iehack = child.name; | 
|                         return; | 
|   | 
|                     case 'inherit': | 
|                     case 'initial': | 
|                     case 'unset': | 
|                     case 'revert': | 
|                         special = child.name; | 
|                         break; | 
|                 } | 
|                 break; | 
|   | 
|             case 'Dimension': | 
|                 switch (child.unit) { | 
|                     // is not supported until IE11 | 
|                     case 'rem': | 
|   | 
|                     // v* units is too buggy across browsers and better | 
|                     // don't merge values with those units | 
|                     case 'vw': | 
|                     case 'vh': | 
|                     case 'vmin': | 
|                     case 'vmax': | 
|                     case 'vm': // IE9 supporting "vm" instead of "vmin". | 
|                         special = child.unit; | 
|                         break; | 
|                 } | 
|                 break; | 
|   | 
|             case 'Hash': // color | 
|             case 'Number': | 
|             case 'Percentage': | 
|                 break; | 
|   | 
|             case 'Function': | 
|                 if (child.name === 'var') { | 
|                     return true; | 
|                 } | 
|   | 
|                 special = child.name; | 
|                 break; | 
|   | 
|             case 'WhiteSpace': | 
|                 return false; // ignore space | 
|   | 
|             default: | 
|                 return true;  // bad value | 
|         } | 
|   | 
|         values.push({ | 
|             node: child, | 
|             special: special, | 
|             important: declaration.important | 
|         }); | 
|     }); | 
|   | 
|     if (hasBadValues || values.length > count) { | 
|         return false; | 
|     } | 
|   | 
|     if (typeof this.iehack === 'string' && this.iehack !== iehack) { | 
|         return false; | 
|     } | 
|   | 
|     this.iehack = iehack; // move outside | 
|   | 
|     return values; | 
| }; | 
|   | 
| TRBL.prototype.canOverride = function(side, value) { | 
|     var currentValue = this.sides[side]; | 
|   | 
|     return !currentValue || (value.important && !currentValue.important); | 
| }; | 
|   | 
| TRBL.prototype.add = function(name, declaration) { | 
|     function attemptToAdd() { | 
|         var sides = this.sides; | 
|         var side = SIDE[name]; | 
|   | 
|         if (side) { | 
|             if (side in sides === false) { | 
|                 return false; | 
|             } | 
|   | 
|             var values = this.getValueSequence(declaration, 1); | 
|   | 
|             if (!values || !values.length) { | 
|                 return false; | 
|             } | 
|   | 
|             // can mix only if specials are equal | 
|             for (var key in sides) { | 
|                 if (sides[key] !== null && sides[key].special !== values[0].special) { | 
|                     return false; | 
|                 } | 
|             } | 
|   | 
|             if (!this.canOverride(side, values[0])) { | 
|                 return true; | 
|             } | 
|   | 
|             sides[side] = values[0]; | 
|             return true; | 
|         } else if (name === this.name) { | 
|             var values = this.getValueSequence(declaration, 4); | 
|   | 
|             if (!values || !values.length) { | 
|                 return false; | 
|             } | 
|   | 
|             switch (values.length) { | 
|                 case 1: | 
|                     values[RIGHT] = values[TOP]; | 
|                     values[BOTTOM] = values[TOP]; | 
|                     values[LEFT] = values[TOP]; | 
|                     break; | 
|   | 
|                 case 2: | 
|                     values[BOTTOM] = values[TOP]; | 
|                     values[LEFT] = values[RIGHT]; | 
|                     break; | 
|   | 
|                 case 3: | 
|                     values[LEFT] = values[RIGHT]; | 
|                     break; | 
|             } | 
|   | 
|             // can mix only if specials are equal | 
|             for (var i = 0; i < 4; i++) { | 
|                 for (var key in sides) { | 
|                     if (sides[key] !== null && sides[key].special !== values[i].special) { | 
|                         return false; | 
|                     } | 
|                 } | 
|             } | 
|   | 
|             for (var i = 0; i < 4; i++) { | 
|                 if (this.canOverride(SIDES[i], values[i])) { | 
|                     sides[SIDES[i]] = values[i]; | 
|                 } | 
|             } | 
|   | 
|             return true; | 
|         } | 
|     } | 
|   | 
|     if (!attemptToAdd.call(this)) { | 
|         return false; | 
|     } | 
|   | 
|     // TODO: use it when we can refer to several points in source | 
|     // if (this.loc) { | 
|     //     this.loc = { | 
|     //         primary: this.loc, | 
|     //         merged: declaration.loc | 
|     //     }; | 
|     // } else { | 
|     //     this.loc = declaration.loc; | 
|     // } | 
|     if (!this.loc) { | 
|         this.loc = declaration.loc; | 
|     } | 
|   | 
|     return true; | 
| }; | 
|   | 
| TRBL.prototype.isOkToMinimize = function() { | 
|     var top = this.sides.top; | 
|     var right = this.sides.right; | 
|     var bottom = this.sides.bottom; | 
|     var left = this.sides.left; | 
|   | 
|     if (top && right && bottom && left) { | 
|         var important = | 
|             top.important + | 
|             right.important + | 
|             bottom.important + | 
|             left.important; | 
|   | 
|         return important === 0 || important === 4; | 
|     } | 
|   | 
|     return false; | 
| }; | 
|   | 
| TRBL.prototype.getValue = function() { | 
|     var result = new List(); | 
|     var sides = this.sides; | 
|     var values = [ | 
|         sides.top, | 
|         sides.right, | 
|         sides.bottom, | 
|         sides.left | 
|     ]; | 
|     var stringValues = [ | 
|         generate(sides.top.node), | 
|         generate(sides.right.node), | 
|         generate(sides.bottom.node), | 
|         generate(sides.left.node) | 
|     ]; | 
|   | 
|     if (stringValues[LEFT] === stringValues[RIGHT]) { | 
|         values.pop(); | 
|         if (stringValues[BOTTOM] === stringValues[TOP]) { | 
|             values.pop(); | 
|             if (stringValues[RIGHT] === stringValues[TOP]) { | 
|                 values.pop(); | 
|             } | 
|         } | 
|     } | 
|   | 
|     for (var i = 0; i < values.length; i++) { | 
|         if (i) { | 
|             result.appendData({ type: 'WhiteSpace', value: ' ' }); | 
|         } | 
|   | 
|         result.appendData(values[i].node); | 
|     } | 
|   | 
|     if (this.iehack) { | 
|         result.appendData({ type: 'WhiteSpace', value: ' ' }); | 
|         result.appendData({ | 
|             type: 'Identifier', | 
|             loc: null, | 
|             name: this.iehack | 
|         }); | 
|     } | 
|   | 
|     return { | 
|         type: 'Value', | 
|         loc: null, | 
|         children: result | 
|     }; | 
| }; | 
|   | 
| TRBL.prototype.getDeclaration = function() { | 
|     return { | 
|         type: 'Declaration', | 
|         loc: this.loc, | 
|         important: this.sides.top.important, | 
|         property: this.name, | 
|         value: this.getValue() | 
|     }; | 
| }; | 
|   | 
| function processRule(rule, shorts, shortDeclarations, lastShortSelector) { | 
|     var declarations = rule.block.children; | 
|     var selector = rule.prelude.children.first().id; | 
|   | 
|     rule.block.children.eachRight(function(declaration, item) { | 
|         var property = declaration.property; | 
|   | 
|         if (!MAIN_PROPERTY.hasOwnProperty(property)) { | 
|             return; | 
|         } | 
|   | 
|         var key = MAIN_PROPERTY[property]; | 
|         var shorthand; | 
|         var operation; | 
|   | 
|         if (!lastShortSelector || selector === lastShortSelector) { | 
|             if (key in shorts) { | 
|                 operation = REMOVE; | 
|                 shorthand = shorts[key]; | 
|             } | 
|         } | 
|   | 
|         if (!shorthand || !shorthand.add(property, declaration)) { | 
|             operation = REPLACE; | 
|             shorthand = new TRBL(key); | 
|   | 
|             // if can't parse value ignore it and break shorthand children | 
|             if (!shorthand.add(property, declaration)) { | 
|                 lastShortSelector = null; | 
|                 return; | 
|             } | 
|         } | 
|   | 
|         shorts[key] = shorthand; | 
|         shortDeclarations.push({ | 
|             operation: operation, | 
|             block: declarations, | 
|             item: item, | 
|             shorthand: shorthand | 
|         }); | 
|   | 
|         lastShortSelector = selector; | 
|     }); | 
|   | 
|     return lastShortSelector; | 
| } | 
|   | 
| function processShorthands(shortDeclarations, markDeclaration) { | 
|     shortDeclarations.forEach(function(item) { | 
|         var shorthand = item.shorthand; | 
|   | 
|         if (!shorthand.isOkToMinimize()) { | 
|             return; | 
|         } | 
|   | 
|         if (item.operation === REPLACE) { | 
|             item.item.data = markDeclaration(shorthand.getDeclaration()); | 
|         } else { | 
|             item.block.remove(item.item); | 
|         } | 
|     }); | 
| } | 
|   | 
| module.exports = function restructBlock(ast, indexer) { | 
|     var stylesheetMap = {}; | 
|     var shortDeclarations = []; | 
|   | 
|     walk(ast, { | 
|         visit: 'Rule', | 
|         reverse: true, | 
|         enter: function(node) { | 
|             var stylesheet = this.block || this.stylesheet; | 
|             var ruleId = (node.pseudoSignature || '') + '|' + node.prelude.children.first().id; | 
|             var ruleMap; | 
|             var shorts; | 
|   | 
|             if (!stylesheetMap.hasOwnProperty(stylesheet.id)) { | 
|                 ruleMap = { | 
|                     lastShortSelector: null | 
|                 }; | 
|                 stylesheetMap[stylesheet.id] = ruleMap; | 
|             } else { | 
|                 ruleMap = stylesheetMap[stylesheet.id]; | 
|             } | 
|   | 
|             if (ruleMap.hasOwnProperty(ruleId)) { | 
|                 shorts = ruleMap[ruleId]; | 
|             } else { | 
|                 shorts = {}; | 
|                 ruleMap[ruleId] = shorts; | 
|             } | 
|   | 
|             ruleMap.lastShortSelector = processRule.call(this, node, shorts, shortDeclarations, ruleMap.lastShortSelector); | 
|         } | 
|     }); | 
|   | 
|     processShorthands(shortDeclarations, indexer.declaration); | 
| }; |