1var isMergeable = require('./is-mergeable'); 2 3var optimizeProperties = require('./properties/optimize'); 4 5var cloneArray = require('../../utils/clone-array'); 6 7var Token = require('../../tokenizer/token'); 8 9var serializeBody = require('../../writer/one-time').body; 10var serializeRules = require('../../writer/one-time').rules; 11 12function reduceNonAdjacent(tokens, context) { 13 var options = context.options; 14 var mergeablePseudoClasses = options.compatibility.selectors.mergeablePseudoClasses; 15 var mergeablePseudoElements = options.compatibility.selectors.mergeablePseudoElements; 16 var multiplePseudoMerging = options.compatibility.selectors.multiplePseudoMerging; 17 var candidates = {}; 18 var repeated = []; 19 20 for (var i = tokens.length - 1; i >= 0; i--) { 21 var token = tokens[i]; 22 23 if (token[0] != Token.RULE) { 24 continue; 25 } else if (token[2].length === 0) { 26 continue; 27 } 28 29 var selectorAsString = serializeRules(token[1]); 30 var isComplexAndNotSpecial = token[1].length > 1 && 31 isMergeable(selectorAsString, mergeablePseudoClasses, mergeablePseudoElements, multiplePseudoMerging); 32 var wrappedSelectors = wrappedSelectorsFrom(token[1]); 33 var selectors = isComplexAndNotSpecial ? 34 [selectorAsString].concat(wrappedSelectors) : 35 [selectorAsString]; 36 37 for (var j = 0, m = selectors.length; j < m; j++) { 38 var selector = selectors[j]; 39 40 if (!candidates[selector]) 41 candidates[selector] = []; 42 else 43 repeated.push(selector); 44 45 candidates[selector].push({ 46 where: i, 47 list: wrappedSelectors, 48 isPartial: isComplexAndNotSpecial && j > 0, 49 isComplex: isComplexAndNotSpecial && j === 0 50 }); 51 } 52 } 53 54 reduceSimpleNonAdjacentCases(tokens, repeated, candidates, options, context); 55 reduceComplexNonAdjacentCases(tokens, candidates, options, context); 56} 57 58function wrappedSelectorsFrom(list) { 59 var wrapped = []; 60 61 for (var i = 0; i < list.length; i++) { 62 wrapped.push([list[i][1]]); 63 } 64 65 return wrapped; 66} 67 68function reduceSimpleNonAdjacentCases(tokens, repeated, candidates, options, context) { 69 function filterOut(idx, bodies) { 70 return data[idx].isPartial && bodies.length === 0; 71 } 72 73 function reduceBody(token, newBody, processedCount, tokenIdx) { 74 if (!data[processedCount - tokenIdx - 1].isPartial) 75 token[2] = newBody; 76 } 77 78 for (var i = 0, l = repeated.length; i < l; i++) { 79 var selector = repeated[i]; 80 var data = candidates[selector]; 81 82 reduceSelector(tokens, data, { 83 filterOut: filterOut, 84 callback: reduceBody 85 }, options, context); 86 } 87} 88 89function reduceComplexNonAdjacentCases(tokens, candidates, options, context) { 90 var mergeablePseudoClasses = options.compatibility.selectors.mergeablePseudoClasses; 91 var mergeablePseudoElements = options.compatibility.selectors.mergeablePseudoElements; 92 var multiplePseudoMerging = options.compatibility.selectors.multiplePseudoMerging; 93 var localContext = {}; 94 95 function filterOut(idx) { 96 return localContext.data[idx].where < localContext.intoPosition; 97 } 98 99 function collectReducedBodies(token, newBody, processedCount, tokenIdx) { 100 if (tokenIdx === 0) 101 localContext.reducedBodies.push(newBody); 102 } 103 104 allSelectors: 105 for (var complexSelector in candidates) { 106 var into = candidates[complexSelector]; 107 if (!into[0].isComplex) 108 continue; 109 110 var intoPosition = into[into.length - 1].where; 111 var intoToken = tokens[intoPosition]; 112 var reducedBodies = []; 113 114 var selectors = isMergeable(complexSelector, mergeablePseudoClasses, mergeablePseudoElements, multiplePseudoMerging) ? 115 into[0].list : 116 [complexSelector]; 117 118 localContext.intoPosition = intoPosition; 119 localContext.reducedBodies = reducedBodies; 120 121 for (var j = 0, m = selectors.length; j < m; j++) { 122 var selector = selectors[j]; 123 var data = candidates[selector]; 124 125 if (data.length < 2) 126 continue allSelectors; 127 128 localContext.data = data; 129 130 reduceSelector(tokens, data, { 131 filterOut: filterOut, 132 callback: collectReducedBodies 133 }, options, context); 134 135 if (serializeBody(reducedBodies[reducedBodies.length - 1]) != serializeBody(reducedBodies[0])) 136 continue allSelectors; 137 } 138 139 intoToken[2] = reducedBodies[0]; 140 } 141} 142 143function reduceSelector(tokens, data, context, options, outerContext) { 144 var bodies = []; 145 var bodiesAsList = []; 146 var processedTokens = []; 147 148 for (var j = data.length - 1; j >= 0; j--) { 149 if (context.filterOut(j, bodies)) 150 continue; 151 152 var where = data[j].where; 153 var token = tokens[where]; 154 var clonedBody = cloneArray(token[2]); 155 156 bodies = bodies.concat(clonedBody); 157 bodiesAsList.push(clonedBody); 158 processedTokens.push(where); 159 } 160 161 optimizeProperties(bodies, true, false, outerContext); 162 163 var processedCount = processedTokens.length; 164 var propertyIdx = bodies.length - 1; 165 var tokenIdx = processedCount - 1; 166 167 while (tokenIdx >= 0) { 168 if ((tokenIdx === 0 || (bodies[propertyIdx] && bodiesAsList[tokenIdx].indexOf(bodies[propertyIdx]) > -1)) && propertyIdx > -1) { 169 propertyIdx--; 170 continue; 171 } 172 173 var newBody = bodies.splice(propertyIdx + 1); 174 context.callback(tokens[processedTokens[tokenIdx]], newBody, processedCount, tokenIdx); 175 176 tokenIdx--; 177 } 178} 179 180module.exports = reduceNonAdjacent; 181