[6a3a178] | 1 | var List = require('css-tree').List;
|
---|
| 2 | var generate = require('css-tree').generate;
|
---|
| 3 | var walk = require('css-tree').walk;
|
---|
| 4 |
|
---|
| 5 | var REPLACE = 1;
|
---|
| 6 | var REMOVE = 2;
|
---|
| 7 | var TOP = 0;
|
---|
| 8 | var RIGHT = 1;
|
---|
| 9 | var BOTTOM = 2;
|
---|
| 10 | var LEFT = 3;
|
---|
| 11 | var SIDES = ['top', 'right', 'bottom', 'left'];
|
---|
| 12 | var SIDE = {
|
---|
| 13 | 'margin-top': 'top',
|
---|
| 14 | 'margin-right': 'right',
|
---|
| 15 | 'margin-bottom': 'bottom',
|
---|
| 16 | 'margin-left': 'left',
|
---|
| 17 |
|
---|
| 18 | 'padding-top': 'top',
|
---|
| 19 | 'padding-right': 'right',
|
---|
| 20 | 'padding-bottom': 'bottom',
|
---|
| 21 | 'padding-left': 'left',
|
---|
| 22 |
|
---|
| 23 | 'border-top-color': 'top',
|
---|
| 24 | 'border-right-color': 'right',
|
---|
| 25 | 'border-bottom-color': 'bottom',
|
---|
| 26 | 'border-left-color': 'left',
|
---|
| 27 | 'border-top-width': 'top',
|
---|
| 28 | 'border-right-width': 'right',
|
---|
| 29 | 'border-bottom-width': 'bottom',
|
---|
| 30 | 'border-left-width': 'left',
|
---|
| 31 | 'border-top-style': 'top',
|
---|
| 32 | 'border-right-style': 'right',
|
---|
| 33 | 'border-bottom-style': 'bottom',
|
---|
| 34 | 'border-left-style': 'left'
|
---|
| 35 | };
|
---|
| 36 | var MAIN_PROPERTY = {
|
---|
| 37 | 'margin': 'margin',
|
---|
| 38 | 'margin-top': 'margin',
|
---|
| 39 | 'margin-right': 'margin',
|
---|
| 40 | 'margin-bottom': 'margin',
|
---|
| 41 | 'margin-left': 'margin',
|
---|
| 42 |
|
---|
| 43 | 'padding': 'padding',
|
---|
| 44 | 'padding-top': 'padding',
|
---|
| 45 | 'padding-right': 'padding',
|
---|
| 46 | 'padding-bottom': 'padding',
|
---|
| 47 | 'padding-left': 'padding',
|
---|
| 48 |
|
---|
| 49 | 'border-color': 'border-color',
|
---|
| 50 | 'border-top-color': 'border-color',
|
---|
| 51 | 'border-right-color': 'border-color',
|
---|
| 52 | 'border-bottom-color': 'border-color',
|
---|
| 53 | 'border-left-color': 'border-color',
|
---|
| 54 | 'border-width': 'border-width',
|
---|
| 55 | 'border-top-width': 'border-width',
|
---|
| 56 | 'border-right-width': 'border-width',
|
---|
| 57 | 'border-bottom-width': 'border-width',
|
---|
| 58 | 'border-left-width': 'border-width',
|
---|
| 59 | 'border-style': 'border-style',
|
---|
| 60 | 'border-top-style': 'border-style',
|
---|
| 61 | 'border-right-style': 'border-style',
|
---|
| 62 | 'border-bottom-style': 'border-style',
|
---|
| 63 | 'border-left-style': 'border-style'
|
---|
| 64 | };
|
---|
| 65 |
|
---|
| 66 | function TRBL(name) {
|
---|
| 67 | this.name = name;
|
---|
| 68 | this.loc = null;
|
---|
| 69 | this.iehack = undefined;
|
---|
| 70 | this.sides = {
|
---|
| 71 | 'top': null,
|
---|
| 72 | 'right': null,
|
---|
| 73 | 'bottom': null,
|
---|
| 74 | 'left': null
|
---|
| 75 | };
|
---|
| 76 | }
|
---|
| 77 |
|
---|
| 78 | TRBL.prototype.getValueSequence = function(declaration, count) {
|
---|
| 79 | var values = [];
|
---|
| 80 | var iehack = '';
|
---|
| 81 | var hasBadValues = declaration.value.type !== 'Value' || declaration.value.children.some(function(child) {
|
---|
| 82 | var special = false;
|
---|
| 83 |
|
---|
| 84 | switch (child.type) {
|
---|
| 85 | case 'Identifier':
|
---|
| 86 | switch (child.name) {
|
---|
| 87 | case '\\0':
|
---|
| 88 | case '\\9':
|
---|
| 89 | iehack = child.name;
|
---|
| 90 | return;
|
---|
| 91 |
|
---|
| 92 | case 'inherit':
|
---|
| 93 | case 'initial':
|
---|
| 94 | case 'unset':
|
---|
| 95 | case 'revert':
|
---|
| 96 | special = child.name;
|
---|
| 97 | break;
|
---|
| 98 | }
|
---|
| 99 | break;
|
---|
| 100 |
|
---|
| 101 | case 'Dimension':
|
---|
| 102 | switch (child.unit) {
|
---|
| 103 | // is not supported until IE11
|
---|
| 104 | case 'rem':
|
---|
| 105 |
|
---|
| 106 | // v* units is too buggy across browsers and better
|
---|
| 107 | // don't merge values with those units
|
---|
| 108 | case 'vw':
|
---|
| 109 | case 'vh':
|
---|
| 110 | case 'vmin':
|
---|
| 111 | case 'vmax':
|
---|
| 112 | case 'vm': // IE9 supporting "vm" instead of "vmin".
|
---|
| 113 | special = child.unit;
|
---|
| 114 | break;
|
---|
| 115 | }
|
---|
| 116 | break;
|
---|
| 117 |
|
---|
| 118 | case 'Hash': // color
|
---|
| 119 | case 'Number':
|
---|
| 120 | case 'Percentage':
|
---|
| 121 | break;
|
---|
| 122 |
|
---|
| 123 | case 'Function':
|
---|
| 124 | if (child.name === 'var') {
|
---|
| 125 | return true;
|
---|
| 126 | }
|
---|
| 127 |
|
---|
| 128 | special = child.name;
|
---|
| 129 | break;
|
---|
| 130 |
|
---|
| 131 | case 'WhiteSpace':
|
---|
| 132 | return false; // ignore space
|
---|
| 133 |
|
---|
| 134 | default:
|
---|
| 135 | return true; // bad value
|
---|
| 136 | }
|
---|
| 137 |
|
---|
| 138 | values.push({
|
---|
| 139 | node: child,
|
---|
| 140 | special: special,
|
---|
| 141 | important: declaration.important
|
---|
| 142 | });
|
---|
| 143 | });
|
---|
| 144 |
|
---|
| 145 | if (hasBadValues || values.length > count) {
|
---|
| 146 | return false;
|
---|
| 147 | }
|
---|
| 148 |
|
---|
| 149 | if (typeof this.iehack === 'string' && this.iehack !== iehack) {
|
---|
| 150 | return false;
|
---|
| 151 | }
|
---|
| 152 |
|
---|
| 153 | this.iehack = iehack; // move outside
|
---|
| 154 |
|
---|
| 155 | return values;
|
---|
| 156 | };
|
---|
| 157 |
|
---|
| 158 | TRBL.prototype.canOverride = function(side, value) {
|
---|
| 159 | var currentValue = this.sides[side];
|
---|
| 160 |
|
---|
| 161 | return !currentValue || (value.important && !currentValue.important);
|
---|
| 162 | };
|
---|
| 163 |
|
---|
| 164 | TRBL.prototype.add = function(name, declaration) {
|
---|
| 165 | function attemptToAdd() {
|
---|
| 166 | var sides = this.sides;
|
---|
| 167 | var side = SIDE[name];
|
---|
| 168 |
|
---|
| 169 | if (side) {
|
---|
| 170 | if (side in sides === false) {
|
---|
| 171 | return false;
|
---|
| 172 | }
|
---|
| 173 |
|
---|
| 174 | var values = this.getValueSequence(declaration, 1);
|
---|
| 175 |
|
---|
| 176 | if (!values || !values.length) {
|
---|
| 177 | return false;
|
---|
| 178 | }
|
---|
| 179 |
|
---|
| 180 | // can mix only if specials are equal
|
---|
| 181 | for (var key in sides) {
|
---|
| 182 | if (sides[key] !== null && sides[key].special !== values[0].special) {
|
---|
| 183 | return false;
|
---|
| 184 | }
|
---|
| 185 | }
|
---|
| 186 |
|
---|
| 187 | if (!this.canOverride(side, values[0])) {
|
---|
| 188 | return true;
|
---|
| 189 | }
|
---|
| 190 |
|
---|
| 191 | sides[side] = values[0];
|
---|
| 192 | return true;
|
---|
| 193 | } else if (name === this.name) {
|
---|
| 194 | var values = this.getValueSequence(declaration, 4);
|
---|
| 195 |
|
---|
| 196 | if (!values || !values.length) {
|
---|
| 197 | return false;
|
---|
| 198 | }
|
---|
| 199 |
|
---|
| 200 | switch (values.length) {
|
---|
| 201 | case 1:
|
---|
| 202 | values[RIGHT] = values[TOP];
|
---|
| 203 | values[BOTTOM] = values[TOP];
|
---|
| 204 | values[LEFT] = values[TOP];
|
---|
| 205 | break;
|
---|
| 206 |
|
---|
| 207 | case 2:
|
---|
| 208 | values[BOTTOM] = values[TOP];
|
---|
| 209 | values[LEFT] = values[RIGHT];
|
---|
| 210 | break;
|
---|
| 211 |
|
---|
| 212 | case 3:
|
---|
| 213 | values[LEFT] = values[RIGHT];
|
---|
| 214 | break;
|
---|
| 215 | }
|
---|
| 216 |
|
---|
| 217 | // can mix only if specials are equal
|
---|
| 218 | for (var i = 0; i < 4; i++) {
|
---|
| 219 | for (var key in sides) {
|
---|
| 220 | if (sides[key] !== null && sides[key].special !== values[i].special) {
|
---|
| 221 | return false;
|
---|
| 222 | }
|
---|
| 223 | }
|
---|
| 224 | }
|
---|
| 225 |
|
---|
| 226 | for (var i = 0; i < 4; i++) {
|
---|
| 227 | if (this.canOverride(SIDES[i], values[i])) {
|
---|
| 228 | sides[SIDES[i]] = values[i];
|
---|
| 229 | }
|
---|
| 230 | }
|
---|
| 231 |
|
---|
| 232 | return true;
|
---|
| 233 | }
|
---|
| 234 | }
|
---|
| 235 |
|
---|
| 236 | if (!attemptToAdd.call(this)) {
|
---|
| 237 | return false;
|
---|
| 238 | }
|
---|
| 239 |
|
---|
| 240 | // TODO: use it when we can refer to several points in source
|
---|
| 241 | // if (this.loc) {
|
---|
| 242 | // this.loc = {
|
---|
| 243 | // primary: this.loc,
|
---|
| 244 | // merged: declaration.loc
|
---|
| 245 | // };
|
---|
| 246 | // } else {
|
---|
| 247 | // this.loc = declaration.loc;
|
---|
| 248 | // }
|
---|
| 249 | if (!this.loc) {
|
---|
| 250 | this.loc = declaration.loc;
|
---|
| 251 | }
|
---|
| 252 |
|
---|
| 253 | return true;
|
---|
| 254 | };
|
---|
| 255 |
|
---|
| 256 | TRBL.prototype.isOkToMinimize = function() {
|
---|
| 257 | var top = this.sides.top;
|
---|
| 258 | var right = this.sides.right;
|
---|
| 259 | var bottom = this.sides.bottom;
|
---|
| 260 | var left = this.sides.left;
|
---|
| 261 |
|
---|
| 262 | if (top && right && bottom && left) {
|
---|
| 263 | var important =
|
---|
| 264 | top.important +
|
---|
| 265 | right.important +
|
---|
| 266 | bottom.important +
|
---|
| 267 | left.important;
|
---|
| 268 |
|
---|
| 269 | return important === 0 || important === 4;
|
---|
| 270 | }
|
---|
| 271 |
|
---|
| 272 | return false;
|
---|
| 273 | };
|
---|
| 274 |
|
---|
| 275 | TRBL.prototype.getValue = function() {
|
---|
| 276 | var result = new List();
|
---|
| 277 | var sides = this.sides;
|
---|
| 278 | var values = [
|
---|
| 279 | sides.top,
|
---|
| 280 | sides.right,
|
---|
| 281 | sides.bottom,
|
---|
| 282 | sides.left
|
---|
| 283 | ];
|
---|
| 284 | var stringValues = [
|
---|
| 285 | generate(sides.top.node),
|
---|
| 286 | generate(sides.right.node),
|
---|
| 287 | generate(sides.bottom.node),
|
---|
| 288 | generate(sides.left.node)
|
---|
| 289 | ];
|
---|
| 290 |
|
---|
| 291 | if (stringValues[LEFT] === stringValues[RIGHT]) {
|
---|
| 292 | values.pop();
|
---|
| 293 | if (stringValues[BOTTOM] === stringValues[TOP]) {
|
---|
| 294 | values.pop();
|
---|
| 295 | if (stringValues[RIGHT] === stringValues[TOP]) {
|
---|
| 296 | values.pop();
|
---|
| 297 | }
|
---|
| 298 | }
|
---|
| 299 | }
|
---|
| 300 |
|
---|
| 301 | for (var i = 0; i < values.length; i++) {
|
---|
| 302 | if (i) {
|
---|
| 303 | result.appendData({ type: 'WhiteSpace', value: ' ' });
|
---|
| 304 | }
|
---|
| 305 |
|
---|
| 306 | result.appendData(values[i].node);
|
---|
| 307 | }
|
---|
| 308 |
|
---|
| 309 | if (this.iehack) {
|
---|
| 310 | result.appendData({ type: 'WhiteSpace', value: ' ' });
|
---|
| 311 | result.appendData({
|
---|
| 312 | type: 'Identifier',
|
---|
| 313 | loc: null,
|
---|
| 314 | name: this.iehack
|
---|
| 315 | });
|
---|
| 316 | }
|
---|
| 317 |
|
---|
| 318 | return {
|
---|
| 319 | type: 'Value',
|
---|
| 320 | loc: null,
|
---|
| 321 | children: result
|
---|
| 322 | };
|
---|
| 323 | };
|
---|
| 324 |
|
---|
| 325 | TRBL.prototype.getDeclaration = function() {
|
---|
| 326 | return {
|
---|
| 327 | type: 'Declaration',
|
---|
| 328 | loc: this.loc,
|
---|
| 329 | important: this.sides.top.important,
|
---|
| 330 | property: this.name,
|
---|
| 331 | value: this.getValue()
|
---|
| 332 | };
|
---|
| 333 | };
|
---|
| 334 |
|
---|
| 335 | function processRule(rule, shorts, shortDeclarations, lastShortSelector) {
|
---|
| 336 | var declarations = rule.block.children;
|
---|
| 337 | var selector = rule.prelude.children.first().id;
|
---|
| 338 |
|
---|
| 339 | rule.block.children.eachRight(function(declaration, item) {
|
---|
| 340 | var property = declaration.property;
|
---|
| 341 |
|
---|
| 342 | if (!MAIN_PROPERTY.hasOwnProperty(property)) {
|
---|
| 343 | return;
|
---|
| 344 | }
|
---|
| 345 |
|
---|
| 346 | var key = MAIN_PROPERTY[property];
|
---|
| 347 | var shorthand;
|
---|
| 348 | var operation;
|
---|
| 349 |
|
---|
| 350 | if (!lastShortSelector || selector === lastShortSelector) {
|
---|
| 351 | if (key in shorts) {
|
---|
| 352 | operation = REMOVE;
|
---|
| 353 | shorthand = shorts[key];
|
---|
| 354 | }
|
---|
| 355 | }
|
---|
| 356 |
|
---|
| 357 | if (!shorthand || !shorthand.add(property, declaration)) {
|
---|
| 358 | operation = REPLACE;
|
---|
| 359 | shorthand = new TRBL(key);
|
---|
| 360 |
|
---|
| 361 | // if can't parse value ignore it and break shorthand children
|
---|
| 362 | if (!shorthand.add(property, declaration)) {
|
---|
| 363 | lastShortSelector = null;
|
---|
| 364 | return;
|
---|
| 365 | }
|
---|
| 366 | }
|
---|
| 367 |
|
---|
| 368 | shorts[key] = shorthand;
|
---|
| 369 | shortDeclarations.push({
|
---|
| 370 | operation: operation,
|
---|
| 371 | block: declarations,
|
---|
| 372 | item: item,
|
---|
| 373 | shorthand: shorthand
|
---|
| 374 | });
|
---|
| 375 |
|
---|
| 376 | lastShortSelector = selector;
|
---|
| 377 | });
|
---|
| 378 |
|
---|
| 379 | return lastShortSelector;
|
---|
| 380 | }
|
---|
| 381 |
|
---|
| 382 | function processShorthands(shortDeclarations, markDeclaration) {
|
---|
| 383 | shortDeclarations.forEach(function(item) {
|
---|
| 384 | var shorthand = item.shorthand;
|
---|
| 385 |
|
---|
| 386 | if (!shorthand.isOkToMinimize()) {
|
---|
| 387 | return;
|
---|
| 388 | }
|
---|
| 389 |
|
---|
| 390 | if (item.operation === REPLACE) {
|
---|
| 391 | item.item.data = markDeclaration(shorthand.getDeclaration());
|
---|
| 392 | } else {
|
---|
| 393 | item.block.remove(item.item);
|
---|
| 394 | }
|
---|
| 395 | });
|
---|
| 396 | }
|
---|
| 397 |
|
---|
| 398 | module.exports = function restructBlock(ast, indexer) {
|
---|
| 399 | var stylesheetMap = {};
|
---|
| 400 | var shortDeclarations = [];
|
---|
| 401 |
|
---|
| 402 | walk(ast, {
|
---|
| 403 | visit: 'Rule',
|
---|
| 404 | reverse: true,
|
---|
| 405 | enter: function(node) {
|
---|
| 406 | var stylesheet = this.block || this.stylesheet;
|
---|
| 407 | var ruleId = (node.pseudoSignature || '') + '|' + node.prelude.children.first().id;
|
---|
| 408 | var ruleMap;
|
---|
| 409 | var shorts;
|
---|
| 410 |
|
---|
| 411 | if (!stylesheetMap.hasOwnProperty(stylesheet.id)) {
|
---|
| 412 | ruleMap = {
|
---|
| 413 | lastShortSelector: null
|
---|
| 414 | };
|
---|
| 415 | stylesheetMap[stylesheet.id] = ruleMap;
|
---|
| 416 | } else {
|
---|
| 417 | ruleMap = stylesheetMap[stylesheet.id];
|
---|
| 418 | }
|
---|
| 419 |
|
---|
| 420 | if (ruleMap.hasOwnProperty(ruleId)) {
|
---|
| 421 | shorts = ruleMap[ruleId];
|
---|
| 422 | } else {
|
---|
| 423 | shorts = {};
|
---|
| 424 | ruleMap[ruleId] = shorts;
|
---|
| 425 | }
|
---|
| 426 |
|
---|
| 427 | ruleMap.lastShortSelector = processRule.call(this, node, shorts, shortDeclarations, ruleMap.lastShortSelector);
|
---|
| 428 | }
|
---|
| 429 | });
|
---|
| 430 |
|
---|
| 431 | processShorthands(shortDeclarations, indexer.declaration);
|
---|
| 432 | };
|
---|