You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							428 lines
						
					
					
						
							11 KiB
						
					
					
				
			
		
		
	
	
							428 lines
						
					
					
						
							11 KiB
						
					
					
				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.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 'HexColor': // color | 
						|
            case 'Number': | 
						|
            case 'Percentage': | 
						|
                break; | 
						|
 | 
						|
            case 'Function': | 
						|
                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); | 
						|
};
 | 
						|
 |