| 1 | var canReorderSingle = require('./reorderable').canReorderSingle;
|
|---|
| 2 | var extractProperties = require('./extract-properties');
|
|---|
| 3 | var isMergeable = require('./is-mergeable');
|
|---|
| 4 | var tidyRuleDuplicates = require('./tidy-rule-duplicates');
|
|---|
| 5 |
|
|---|
| 6 | var Token = require('../../tokenizer/token');
|
|---|
| 7 |
|
|---|
| 8 | var cloneArray = require('../../utils/clone-array');
|
|---|
| 9 |
|
|---|
| 10 | var serializeBody = require('../../writer/one-time').body;
|
|---|
| 11 | var serializeRules = require('../../writer/one-time').rules;
|
|---|
| 12 |
|
|---|
| 13 | function naturalSorter(a, b) {
|
|---|
| 14 | return a > b ? 1 : -1;
|
|---|
| 15 | }
|
|---|
| 16 |
|
|---|
| 17 | function cloneAndMergeSelectors(propertyA, propertyB) {
|
|---|
| 18 | var cloned = cloneArray(propertyA);
|
|---|
| 19 | cloned[5] = cloned[5].concat(propertyB[5]);
|
|---|
| 20 |
|
|---|
| 21 | return cloned;
|
|---|
| 22 | }
|
|---|
| 23 |
|
|---|
| 24 | function restructure(tokens, context) {
|
|---|
| 25 | var options = context.options;
|
|---|
| 26 | var mergeablePseudoClasses = options.compatibility.selectors.mergeablePseudoClasses;
|
|---|
| 27 | var mergeablePseudoElements = options.compatibility.selectors.mergeablePseudoElements;
|
|---|
| 28 | var mergeLimit = options.compatibility.selectors.mergeLimit;
|
|---|
| 29 | var multiplePseudoMerging = options.compatibility.selectors.multiplePseudoMerging;
|
|---|
| 30 | var specificityCache = context.cache.specificity;
|
|---|
| 31 | var movableTokens = {};
|
|---|
| 32 | var movedProperties = [];
|
|---|
| 33 | var multiPropertyMoveCache = {};
|
|---|
| 34 | var movedToBeDropped = [];
|
|---|
| 35 | var maxCombinationsLevel = 2;
|
|---|
| 36 | var ID_JOIN_CHARACTER = '%';
|
|---|
| 37 |
|
|---|
| 38 | function sendToMultiPropertyMoveCache(position, movedProperty, allFits) {
|
|---|
| 39 | for (var i = allFits.length - 1; i >= 0; i--) {
|
|---|
| 40 | var fit = allFits[i][0];
|
|---|
| 41 | var id = addToCache(movedProperty, fit);
|
|---|
| 42 |
|
|---|
| 43 | if (multiPropertyMoveCache[id].length > 1 && processMultiPropertyMove(position, multiPropertyMoveCache[id])) {
|
|---|
| 44 | removeAllMatchingFromCache(id);
|
|---|
| 45 | break;
|
|---|
| 46 | }
|
|---|
| 47 | }
|
|---|
| 48 | }
|
|---|
| 49 |
|
|---|
| 50 | function addToCache(movedProperty, fit) {
|
|---|
| 51 | var id = cacheId(fit);
|
|---|
| 52 | multiPropertyMoveCache[id] = multiPropertyMoveCache[id] || [];
|
|---|
| 53 | multiPropertyMoveCache[id].push([movedProperty, fit]);
|
|---|
| 54 | return id;
|
|---|
| 55 | }
|
|---|
| 56 |
|
|---|
| 57 | function removeAllMatchingFromCache(matchId) {
|
|---|
| 58 | var matchSelectors = matchId.split(ID_JOIN_CHARACTER);
|
|---|
| 59 | var forRemoval = [];
|
|---|
| 60 | var i;
|
|---|
| 61 |
|
|---|
| 62 | for (var id in multiPropertyMoveCache) {
|
|---|
| 63 | var selectors = id.split(ID_JOIN_CHARACTER);
|
|---|
| 64 | for (i = selectors.length - 1; i >= 0; i--) {
|
|---|
| 65 | if (matchSelectors.indexOf(selectors[i]) > -1) {
|
|---|
| 66 | forRemoval.push(id);
|
|---|
| 67 | break;
|
|---|
| 68 | }
|
|---|
| 69 | }
|
|---|
| 70 | }
|
|---|
| 71 |
|
|---|
| 72 | for (i = forRemoval.length - 1; i >= 0; i--) {
|
|---|
| 73 | delete multiPropertyMoveCache[forRemoval[i]];
|
|---|
| 74 | }
|
|---|
| 75 | }
|
|---|
| 76 |
|
|---|
| 77 | function cacheId(cachedTokens) {
|
|---|
| 78 | var id = [];
|
|---|
| 79 | for (var i = 0, l = cachedTokens.length; i < l; i++) {
|
|---|
| 80 | id.push(serializeRules(cachedTokens[i][1]));
|
|---|
| 81 | }
|
|---|
| 82 | return id.join(ID_JOIN_CHARACTER);
|
|---|
| 83 | }
|
|---|
| 84 |
|
|---|
| 85 | function tokensToMerge(sourceTokens) {
|
|---|
| 86 | var uniqueTokensWithBody = [];
|
|---|
| 87 | var mergeableTokens = [];
|
|---|
| 88 |
|
|---|
| 89 | for (var i = sourceTokens.length - 1; i >= 0; i--) {
|
|---|
| 90 | if (!isMergeable(
|
|---|
| 91 | serializeRules(sourceTokens[i][1]),
|
|---|
| 92 | mergeablePseudoClasses,
|
|---|
| 93 | mergeablePseudoElements,
|
|---|
| 94 | multiplePseudoMerging
|
|---|
| 95 | )) {
|
|---|
| 96 | continue;
|
|---|
| 97 | }
|
|---|
| 98 |
|
|---|
| 99 | mergeableTokens.unshift(sourceTokens[i]);
|
|---|
| 100 | if (sourceTokens[i][2].length > 0
|
|---|
| 101 | && uniqueTokensWithBody.indexOf(sourceTokens[i]) == -1) {
|
|---|
| 102 | uniqueTokensWithBody.push(sourceTokens[i]);
|
|---|
| 103 | }
|
|---|
| 104 | }
|
|---|
| 105 |
|
|---|
| 106 | return uniqueTokensWithBody.length > 1
|
|---|
| 107 | ? mergeableTokens
|
|---|
| 108 | : [];
|
|---|
| 109 | }
|
|---|
| 110 |
|
|---|
| 111 | function shortenIfPossible(position, movedProperty) {
|
|---|
| 112 | var name = movedProperty[0];
|
|---|
| 113 | var value = movedProperty[1];
|
|---|
| 114 | var key = movedProperty[4];
|
|---|
| 115 | var valueSize = name.length + value.length + 1;
|
|---|
| 116 | var allSelectors = [];
|
|---|
| 117 | var qualifiedTokens = [];
|
|---|
| 118 |
|
|---|
| 119 | var mergeableTokens = tokensToMerge(movableTokens[key]);
|
|---|
| 120 | if (mergeableTokens.length < 2) { return; }
|
|---|
| 121 |
|
|---|
| 122 | var allFits = findAllFits(mergeableTokens, valueSize, 1);
|
|---|
| 123 | var bestFit = allFits[0];
|
|---|
| 124 | if (bestFit[1] > 0) { return sendToMultiPropertyMoveCache(position, movedProperty, allFits); }
|
|---|
| 125 |
|
|---|
| 126 | for (var i = bestFit[0].length - 1; i >= 0; i--) {
|
|---|
| 127 | allSelectors = bestFit[0][i][1].concat(allSelectors);
|
|---|
| 128 | qualifiedTokens.unshift(bestFit[0][i]);
|
|---|
| 129 | }
|
|---|
| 130 |
|
|---|
| 131 | allSelectors = tidyRuleDuplicates(allSelectors);
|
|---|
| 132 | dropAsNewTokenAt(position, [movedProperty], allSelectors, qualifiedTokens);
|
|---|
| 133 | }
|
|---|
| 134 |
|
|---|
| 135 | function fitSorter(fit1, fit2) {
|
|---|
| 136 | return fit1[1] > fit2[1] ? 1 : (fit1[1] == fit2[1] ? 0 : -1);
|
|---|
| 137 | }
|
|---|
| 138 |
|
|---|
| 139 | function findAllFits(mergeableTokens, propertySize, propertiesCount) {
|
|---|
| 140 | var combinations = allCombinations(mergeableTokens, propertySize, propertiesCount, maxCombinationsLevel - 1);
|
|---|
| 141 | return combinations.sort(fitSorter);
|
|---|
| 142 | }
|
|---|
| 143 |
|
|---|
| 144 | function allCombinations(tokensVariant, propertySize, propertiesCount, level) {
|
|---|
| 145 | var differenceVariants = [[tokensVariant, sizeDifference(tokensVariant, propertySize, propertiesCount)]];
|
|---|
| 146 | if (tokensVariant.length > 2 && level > 0) {
|
|---|
| 147 | for (var i = tokensVariant.length - 1; i >= 0; i--) {
|
|---|
| 148 | var subVariant = Array.prototype.slice.call(tokensVariant, 0);
|
|---|
| 149 | subVariant.splice(i, 1);
|
|---|
| 150 | differenceVariants = differenceVariants.concat(
|
|---|
| 151 | allCombinations(subVariant, propertySize, propertiesCount, level - 1)
|
|---|
| 152 | );
|
|---|
| 153 | }
|
|---|
| 154 | }
|
|---|
| 155 |
|
|---|
| 156 | return differenceVariants;
|
|---|
| 157 | }
|
|---|
| 158 |
|
|---|
| 159 | function sizeDifference(tokensVariant, propertySize, propertiesCount) {
|
|---|
| 160 | var allSelectorsSize = 0;
|
|---|
| 161 | for (var i = tokensVariant.length - 1; i >= 0; i--) {
|
|---|
| 162 | allSelectorsSize += tokensVariant[i][2].length > propertiesCount
|
|---|
| 163 | ? serializeRules(tokensVariant[i][1]).length
|
|---|
| 164 | : -1;
|
|---|
| 165 | }
|
|---|
| 166 | return allSelectorsSize - (tokensVariant.length - 1) * propertySize + 1;
|
|---|
| 167 | }
|
|---|
| 168 |
|
|---|
| 169 | function dropAsNewTokenAt(position, properties, allSelectors, mergeableTokens) {
|
|---|
| 170 | var i, j, k, m;
|
|---|
| 171 | var allProperties = [];
|
|---|
| 172 |
|
|---|
| 173 | for (i = mergeableTokens.length - 1; i >= 0; i--) {
|
|---|
| 174 | var mergeableToken = mergeableTokens[i];
|
|---|
| 175 |
|
|---|
| 176 | for (j = mergeableToken[2].length - 1; j >= 0; j--) {
|
|---|
| 177 | var mergeableProperty = mergeableToken[2][j];
|
|---|
| 178 |
|
|---|
| 179 | for (k = 0, m = properties.length; k < m; k++) {
|
|---|
| 180 | var property = properties[k];
|
|---|
| 181 |
|
|---|
| 182 | var mergeablePropertyName = mergeableProperty[1][1];
|
|---|
| 183 | var propertyName = property[0];
|
|---|
| 184 | var propertyBody = property[4];
|
|---|
| 185 | if (mergeablePropertyName == propertyName && serializeBody([mergeableProperty]) == propertyBody) {
|
|---|
| 186 | mergeableToken[2].splice(j, 1);
|
|---|
| 187 | break;
|
|---|
| 188 | }
|
|---|
| 189 | }
|
|---|
| 190 | }
|
|---|
| 191 | }
|
|---|
| 192 |
|
|---|
| 193 | for (i = properties.length - 1; i >= 0; i--) {
|
|---|
| 194 | allProperties.unshift(properties[i][3]);
|
|---|
| 195 | }
|
|---|
| 196 |
|
|---|
| 197 | var newToken = [Token.RULE, allSelectors, allProperties];
|
|---|
| 198 | tokens.splice(position, 0, newToken);
|
|---|
| 199 | }
|
|---|
| 200 |
|
|---|
| 201 | function dropPropertiesAt(position, movedProperty) {
|
|---|
| 202 | var key = movedProperty[4];
|
|---|
| 203 | var toMove = movableTokens[key];
|
|---|
| 204 |
|
|---|
| 205 | if (toMove && toMove.length > 1) {
|
|---|
| 206 | if (!shortenMultiMovesIfPossible(position, movedProperty)) { shortenIfPossible(position, movedProperty); }
|
|---|
| 207 | }
|
|---|
| 208 | }
|
|---|
| 209 |
|
|---|
| 210 | function shortenMultiMovesIfPossible(position, movedProperty) {
|
|---|
| 211 | var candidates = [];
|
|---|
| 212 | var propertiesAndMergableTokens = [];
|
|---|
| 213 | var key = movedProperty[4];
|
|---|
| 214 | var j, k;
|
|---|
| 215 |
|
|---|
| 216 | var mergeableTokens = tokensToMerge(movableTokens[key]);
|
|---|
| 217 | if (mergeableTokens.length < 2) { return; }
|
|---|
| 218 |
|
|---|
| 219 | movableLoop:
|
|---|
| 220 | for (var value in movableTokens) {
|
|---|
| 221 | var tokensList = movableTokens[value];
|
|---|
| 222 |
|
|---|
| 223 | for (j = mergeableTokens.length - 1; j >= 0; j--) {
|
|---|
| 224 | if (tokensList.indexOf(mergeableTokens[j]) == -1) { continue movableLoop; }
|
|---|
| 225 | }
|
|---|
| 226 |
|
|---|
| 227 | candidates.push(value);
|
|---|
| 228 | }
|
|---|
| 229 |
|
|---|
| 230 | if (candidates.length < 2) { return false; }
|
|---|
| 231 |
|
|---|
| 232 | for (j = candidates.length - 1; j >= 0; j--) {
|
|---|
| 233 | for (k = movedProperties.length - 1; k >= 0; k--) {
|
|---|
| 234 | if (movedProperties[k][4] == candidates[j]) {
|
|---|
| 235 | propertiesAndMergableTokens.unshift([movedProperties[k], mergeableTokens]);
|
|---|
| 236 | break;
|
|---|
| 237 | }
|
|---|
| 238 | }
|
|---|
| 239 | }
|
|---|
| 240 |
|
|---|
| 241 | return processMultiPropertyMove(position, propertiesAndMergableTokens);
|
|---|
| 242 | }
|
|---|
| 243 |
|
|---|
| 244 | function processMultiPropertyMove(position, propertiesAndMergableTokens) {
|
|---|
| 245 | var valueSize = 0;
|
|---|
| 246 | var properties = [];
|
|---|
| 247 | var property;
|
|---|
| 248 |
|
|---|
| 249 | for (var i = propertiesAndMergableTokens.length - 1; i >= 0; i--) {
|
|---|
| 250 | property = propertiesAndMergableTokens[i][0];
|
|---|
| 251 | var fullValue = property[4];
|
|---|
| 252 | valueSize += fullValue.length + (i > 0 ? 1 : 0);
|
|---|
| 253 |
|
|---|
| 254 | properties.push(property);
|
|---|
| 255 | }
|
|---|
| 256 |
|
|---|
| 257 | var mergeableTokens = propertiesAndMergableTokens[0][1];
|
|---|
| 258 | var bestFit = findAllFits(mergeableTokens, valueSize, properties.length)[0];
|
|---|
| 259 | if (bestFit[1] > 0) { return false; }
|
|---|
| 260 |
|
|---|
| 261 | var allSelectors = [];
|
|---|
| 262 | var qualifiedTokens = [];
|
|---|
| 263 | for (i = bestFit[0].length - 1; i >= 0; i--) {
|
|---|
| 264 | allSelectors = bestFit[0][i][1].concat(allSelectors);
|
|---|
| 265 | qualifiedTokens.unshift(bestFit[0][i]);
|
|---|
| 266 | }
|
|---|
| 267 |
|
|---|
| 268 | allSelectors = tidyRuleDuplicates(allSelectors);
|
|---|
| 269 | dropAsNewTokenAt(position, properties, allSelectors, qualifiedTokens);
|
|---|
| 270 |
|
|---|
| 271 | for (i = properties.length - 1; i >= 0; i--) {
|
|---|
| 272 | property = properties[i];
|
|---|
| 273 | var index = movedProperties.indexOf(property);
|
|---|
| 274 |
|
|---|
| 275 | delete movableTokens[property[4]];
|
|---|
| 276 |
|
|---|
| 277 | if (index > -1 && movedToBeDropped.indexOf(index) == -1) { movedToBeDropped.push(index); }
|
|---|
| 278 | }
|
|---|
| 279 |
|
|---|
| 280 | return true;
|
|---|
| 281 | }
|
|---|
| 282 |
|
|---|
| 283 | function boundToAnotherPropertyInCurrrentToken(property, movedProperty, token) {
|
|---|
| 284 | var propertyName = property[0];
|
|---|
| 285 | var movedPropertyName = movedProperty[0];
|
|---|
| 286 | if (propertyName != movedPropertyName) { return false; }
|
|---|
| 287 |
|
|---|
| 288 | var key = movedProperty[4];
|
|---|
| 289 | var toMove = movableTokens[key];
|
|---|
| 290 | return toMove && toMove.indexOf(token) > -1;
|
|---|
| 291 | }
|
|---|
| 292 |
|
|---|
| 293 | for (var i = tokens.length - 1; i >= 0; i--) {
|
|---|
| 294 | var token = tokens[i];
|
|---|
| 295 | var isRule;
|
|---|
| 296 | var j, k, m;
|
|---|
| 297 | var samePropertyAt;
|
|---|
| 298 |
|
|---|
| 299 | if (token[0] == Token.RULE) {
|
|---|
| 300 | isRule = true;
|
|---|
| 301 | } else if (token[0] == Token.NESTED_BLOCK) {
|
|---|
| 302 | isRule = false;
|
|---|
| 303 | } else {
|
|---|
| 304 | continue;
|
|---|
| 305 | }
|
|---|
| 306 |
|
|---|
| 307 | // We cache movedProperties.length as it may change in the loop
|
|---|
| 308 | var movedCount = movedProperties.length;
|
|---|
| 309 |
|
|---|
| 310 | var properties = extractProperties(token);
|
|---|
| 311 | movedToBeDropped = [];
|
|---|
| 312 |
|
|---|
| 313 | var unmovableInCurrentToken = [];
|
|---|
| 314 | for (j = properties.length - 1; j >= 0; j--) {
|
|---|
| 315 | for (k = j - 1; k >= 0; k--) {
|
|---|
| 316 | if (!canReorderSingle(properties[j], properties[k], specificityCache)) {
|
|---|
| 317 | unmovableInCurrentToken.push(j);
|
|---|
| 318 | break;
|
|---|
| 319 | }
|
|---|
| 320 | }
|
|---|
| 321 | }
|
|---|
| 322 |
|
|---|
| 323 | for (j = properties.length - 1; j >= 0; j--) {
|
|---|
| 324 | var property = properties[j];
|
|---|
| 325 | var movedSameProperty = false;
|
|---|
| 326 |
|
|---|
| 327 | for (k = 0; k < movedCount; k++) {
|
|---|
| 328 | var movedProperty = movedProperties[k];
|
|---|
| 329 |
|
|---|
| 330 | if (movedToBeDropped.indexOf(k) == -1 && (
|
|---|
| 331 | !canReorderSingle(property, movedProperty, specificityCache)
|
|---|
| 332 | && !boundToAnotherPropertyInCurrrentToken(property, movedProperty, token)
|
|---|
| 333 | || movableTokens[movedProperty[4]] && movableTokens[movedProperty[4]].length === mergeLimit)
|
|---|
| 334 | ) {
|
|---|
| 335 | dropPropertiesAt(i + 1, movedProperty);
|
|---|
| 336 |
|
|---|
| 337 | if (movedToBeDropped.indexOf(k) == -1) {
|
|---|
| 338 | movedToBeDropped.push(k);
|
|---|
| 339 | delete movableTokens[movedProperty[4]];
|
|---|
| 340 | }
|
|---|
| 341 | }
|
|---|
| 342 |
|
|---|
| 343 | if (!movedSameProperty) {
|
|---|
| 344 | movedSameProperty = property[0] == movedProperty[0] && property[1] == movedProperty[1];
|
|---|
| 345 |
|
|---|
| 346 | if (movedSameProperty) {
|
|---|
| 347 | samePropertyAt = k;
|
|---|
| 348 | }
|
|---|
| 349 | }
|
|---|
| 350 | }
|
|---|
| 351 |
|
|---|
| 352 | if (!isRule || unmovableInCurrentToken.indexOf(j) > -1) { continue; }
|
|---|
| 353 |
|
|---|
| 354 | var key = property[4];
|
|---|
| 355 |
|
|---|
| 356 | if (movedSameProperty && movedProperties[samePropertyAt][5].length + property[5].length > mergeLimit) {
|
|---|
| 357 | dropPropertiesAt(i + 1, movedProperties[samePropertyAt]);
|
|---|
| 358 | movedProperties.splice(samePropertyAt, 1);
|
|---|
| 359 | movableTokens[key] = [token];
|
|---|
| 360 | movedSameProperty = false;
|
|---|
| 361 | } else {
|
|---|
| 362 | movableTokens[key] = movableTokens[key] || [];
|
|---|
| 363 | movableTokens[key].push(token);
|
|---|
| 364 | }
|
|---|
| 365 |
|
|---|
| 366 | if (movedSameProperty) {
|
|---|
| 367 | movedProperties[samePropertyAt] = cloneAndMergeSelectors(movedProperties[samePropertyAt], property);
|
|---|
| 368 | } else {
|
|---|
| 369 | movedProperties.push(property);
|
|---|
| 370 | }
|
|---|
| 371 | }
|
|---|
| 372 |
|
|---|
| 373 | movedToBeDropped = movedToBeDropped.sort(naturalSorter);
|
|---|
| 374 | for (j = 0, m = movedToBeDropped.length; j < m; j++) {
|
|---|
| 375 | var dropAt = movedToBeDropped[j] - j;
|
|---|
| 376 | movedProperties.splice(dropAt, 1);
|
|---|
| 377 | }
|
|---|
| 378 | }
|
|---|
| 379 |
|
|---|
| 380 | var position = tokens[0] && tokens[0][0] == Token.AT_RULE && tokens[0][1].indexOf('@charset') === 0 ? 1 : 0;
|
|---|
| 381 | for (; position < tokens.length - 1; position++) {
|
|---|
| 382 | var isImportRule = tokens[position][0] === Token.AT_RULE && tokens[position][1].indexOf('@import') === 0;
|
|---|
| 383 | var isComment = tokens[position][0] === Token.COMMENT;
|
|---|
| 384 | if (!(isImportRule || isComment)) { break; }
|
|---|
| 385 | }
|
|---|
| 386 |
|
|---|
| 387 | for (i = 0; i < movedProperties.length; i++) {
|
|---|
| 388 | dropPropertiesAt(position, movedProperties[i]);
|
|---|
| 389 | }
|
|---|
| 390 | }
|
|---|
| 391 |
|
|---|
| 392 | module.exports = restructure;
|
|---|