1var populateComponents = require('./properties/populate-components'); 2 3var wrapForOptimizing = require('../wrap-for-optimizing').single; 4var restoreFromOptimizing = require('../restore-from-optimizing'); 5 6var Token = require('../../tokenizer/token'); 7 8var animationNameRegex = /^(\-moz\-|\-o\-|\-webkit\-)?animation-name$/; 9var animationRegex = /^(\-moz\-|\-o\-|\-webkit\-)?animation$/; 10var keyframeRegex = /^@(\-moz\-|\-o\-|\-webkit\-)?keyframes /; 11var importantRegex = /\s{0,31}!important$/; 12var optionalMatchingQuotesRegex = /^(['"]?)(.*)\1$/; 13 14function normalize(value) { 15 return value 16 .replace(optionalMatchingQuotesRegex, '$2') 17 .replace(importantRegex, ''); 18} 19 20function removeUnusedAtRules(tokens, context) { 21 removeUnusedAtRule(tokens, matchCounterStyle, markCounterStylesAsUsed, context); 22 removeUnusedAtRule(tokens, matchFontFace, markFontFacesAsUsed, context); 23 removeUnusedAtRule(tokens, matchKeyframe, markKeyframesAsUsed, context); 24 removeUnusedAtRule(tokens, matchNamespace, markNamespacesAsUsed, context); 25} 26 27function removeUnusedAtRule(tokens, matchCallback, markCallback, context) { 28 var atRules = {}; 29 var atRule; 30 var atRuleTokens; 31 var atRuleToken; 32 var zeroAt; 33 var i, l; 34 35 for (i = 0, l = tokens.length; i < l; i++) { 36 matchCallback(tokens[i], atRules); 37 } 38 39 if (Object.keys(atRules).length === 0) { 40 return; 41 } 42 43 markUsedAtRules(tokens, markCallback, atRules, context); 44 45 for (atRule in atRules) { 46 atRuleTokens = atRules[atRule]; 47 48 for (i = 0, l = atRuleTokens.length; i < l; i++) { 49 atRuleToken = atRuleTokens[i]; 50 zeroAt = atRuleToken[0] == Token.AT_RULE ? 1 : 2; 51 atRuleToken[zeroAt] = []; 52 } 53 } 54} 55 56function markUsedAtRules(tokens, markCallback, atRules, context) { 57 var boundMarkCallback = markCallback(atRules); 58 var i, l; 59 60 for (i = 0, l = tokens.length; i < l; i++) { 61 switch (tokens[i][0]) { 62 case Token.RULE: 63 boundMarkCallback(tokens[i], context); 64 break; 65 case Token.NESTED_BLOCK: 66 markUsedAtRules(tokens[i][2], markCallback, atRules, context); 67 } 68 } 69} 70 71function matchCounterStyle(token, atRules) { 72 var match; 73 74 if (token[0] == Token.AT_RULE_BLOCK && token[1][0][1].indexOf('@counter-style') === 0) { 75 match = token[1][0][1].split(' ')[1]; 76 atRules[match] = atRules[match] || []; 77 atRules[match].push(token); 78 } 79} 80 81function markCounterStylesAsUsed(atRules) { 82 return function (token, context) { 83 var property; 84 var wrappedProperty; 85 var i, l; 86 87 for (i = 0, l = token[2].length; i < l; i++) { 88 property = token[2][i]; 89 90 if (property[1][1] == 'list-style') { 91 wrappedProperty = wrapForOptimizing(property); 92 populateComponents([wrappedProperty], context.validator, context.warnings); 93 94 if (wrappedProperty.components[0].value[0][1] in atRules) { 95 delete atRules[property[2][1]]; 96 } 97 98 restoreFromOptimizing([wrappedProperty]); 99 } 100 101 if (property[1][1] == 'list-style-type' && property[2][1] in atRules) { 102 delete atRules[property[2][1]]; 103 } 104 } 105 }; 106} 107 108function matchFontFace(token, atRules) { 109 var property; 110 var match; 111 var i, l; 112 113 if (token[0] == Token.AT_RULE_BLOCK && token[1][0][1] == '@font-face') { 114 for (i = 0, l = token[2].length; i < l; i++) { 115 property = token[2][i]; 116 117 if (property[1][1] == 'font-family') { 118 match = normalize(property[2][1].toLowerCase()); 119 atRules[match] = atRules[match] || []; 120 atRules[match].push(token); 121 break; 122 } 123 } 124 } 125} 126 127function markFontFacesAsUsed(atRules) { 128 return function (token, context) { 129 var property; 130 var wrappedProperty; 131 var component; 132 var normalizedMatch; 133 var i, l; 134 var j, m; 135 136 for (i = 0, l = token[2].length; i < l; i++) { 137 property = token[2][i]; 138 139 if (property[1][1] == 'font') { 140 wrappedProperty = wrapForOptimizing(property); 141 populateComponents([wrappedProperty], context.validator, context.warnings); 142 component = wrappedProperty.components[6]; 143 144 for (j = 0, m = component.value.length; j < m; j++) { 145 normalizedMatch = normalize(component.value[j][1].toLowerCase()); 146 147 if (normalizedMatch in atRules) { 148 delete atRules[normalizedMatch]; 149 } 150 } 151 152 restoreFromOptimizing([wrappedProperty]); 153 } 154 155 if (property[1][1] == 'font-family') { 156 for (j = 2, m = property.length; j < m; j++) { 157 normalizedMatch = normalize(property[j][1].toLowerCase()); 158 159 if (normalizedMatch in atRules) { 160 delete atRules[normalizedMatch]; 161 } 162 } 163 } 164 } 165 }; 166} 167 168function matchKeyframe(token, atRules) { 169 var match; 170 171 if (token[0] == Token.NESTED_BLOCK && keyframeRegex.test(token[1][0][1])) { 172 match = token[1][0][1].split(' ')[1]; 173 atRules[match] = atRules[match] || []; 174 atRules[match].push(token); 175 } 176} 177 178function markKeyframesAsUsed(atRules) { 179 return function (token, context) { 180 var property; 181 var wrappedProperty; 182 var component; 183 var i, l; 184 var j, m; 185 186 for (i = 0, l = token[2].length; i < l; i++) { 187 property = token[2][i]; 188 189 if (animationRegex.test(property[1][1])) { 190 wrappedProperty = wrapForOptimizing(property); 191 populateComponents([wrappedProperty], context.validator, context.warnings); 192 component = wrappedProperty.components[7]; 193 194 for (j = 0, m = component.value.length; j < m; j++) { 195 if (component.value[j][1] in atRules) { 196 delete atRules[component.value[j][1]]; 197 } 198 } 199 200 restoreFromOptimizing([wrappedProperty]); 201 } 202 203 if (animationNameRegex.test(property[1][1])) { 204 for (j = 2, m = property.length; j < m; j++) { 205 if (property[j][1] in atRules) { 206 delete atRules[property[j][1]]; 207 } 208 } 209 } 210 } 211 }; 212} 213 214function matchNamespace(token, atRules) { 215 var match; 216 217 if (token[0] == Token.AT_RULE && token[1].indexOf('@namespace') === 0) { 218 match = token[1].split(' ')[1]; 219 atRules[match] = atRules[match] || []; 220 atRules[match].push(token); 221 } 222} 223 224function markNamespacesAsUsed(atRules) { 225 var namespaceRegex = new RegExp(Object.keys(atRules).join('\\\||') + '\\\|', 'g'); 226 227 return function (token) { 228 var match; 229 var scope; 230 var normalizedMatch; 231 var i, l; 232 var j, m; 233 234 for (i = 0, l = token[1].length; i < l; i++) { 235 scope = token[1][i]; 236 match = scope[1].match(namespaceRegex); 237 238 for (j = 0, m = match.length; j < m; j++) { 239 normalizedMatch = match[j].substring(0, match[j].length - 1); 240 241 if (normalizedMatch in atRules) { 242 delete atRules[normalizedMatch]; 243 } 244 } 245 } 246 }; 247} 248 249module.exports = removeUnusedAtRules; 250