1var everyValuesPair = require('./every-values-pair'); 2var hasInherit = require('./has-inherit'); 3var populateComponents = require('./populate-components'); 4 5var compactable = require('../compactable'); 6var deepClone = require('../clone').deep; 7var restoreWithComponents = require('../restore-with-components'); 8 9var restoreFromOptimizing = require('../../restore-from-optimizing'); 10var wrapSingle = require('../../wrap-for-optimizing').single; 11 12var serializeBody = require('../../../writer/one-time').body; 13var Token = require('../../../tokenizer/token'); 14 15function mergeIntoShorthands(properties, validator) { 16 var candidates = {}; 17 var descriptor; 18 var componentOf; 19 var property; 20 var i, l; 21 var j, m; 22 23 // there is no shorthand property made up of less than 3 longhands 24 if (properties.length < 3) { 25 return; 26 } 27 28 for (i = 0, l = properties.length; i < l; i++) { 29 property = properties[i]; 30 descriptor = compactable[property.name]; 31 32 if (property.unused) { 33 continue; 34 } 35 36 if (property.hack) { 37 continue; 38 } 39 40 if (property.block) { 41 continue; 42 } 43 44 invalidateOrCompact(properties, i, candidates, validator); 45 46 if (descriptor && descriptor.componentOf) { 47 for (j = 0, m = descriptor.componentOf.length; j < m; j++) { 48 componentOf = descriptor.componentOf[j]; 49 50 candidates[componentOf] = candidates[componentOf] || {}; 51 candidates[componentOf][property.name] = property; 52 } 53 } 54 } 55 56 invalidateOrCompact(properties, i, candidates, validator); 57} 58 59function invalidateOrCompact(properties, position, candidates, validator) { 60 var invalidatedBy = properties[position]; 61 var shorthandName; 62 var shorthandDescriptor; 63 var candidateComponents; 64 65 for (shorthandName in candidates) { 66 if (undefined !== invalidatedBy && shorthandName == invalidatedBy.name) { 67 continue; 68 } 69 70 shorthandDescriptor = compactable[shorthandName]; 71 candidateComponents = candidates[shorthandName]; 72 if (invalidatedBy && invalidates(candidates, shorthandName, invalidatedBy)) { 73 delete candidates[shorthandName]; 74 continue; 75 } 76 77 if (shorthandDescriptor.components.length > Object.keys(candidateComponents).length) { 78 continue; 79 } 80 81 if (mixedImportance(candidateComponents)) { 82 continue; 83 } 84 85 if (!overridable(candidateComponents, shorthandName, validator)) { 86 continue; 87 } 88 89 if (!mergeable(candidateComponents)) { 90 continue; 91 } 92 93 if (mixedInherit(candidateComponents)) { 94 replaceWithInheritBestFit(properties, candidateComponents, shorthandName, validator); 95 } else { 96 replaceWithShorthand(properties, candidateComponents, shorthandName, validator); 97 } 98 } 99} 100 101function invalidates(candidates, shorthandName, invalidatedBy) { 102 var shorthandDescriptor = compactable[shorthandName]; 103 var invalidatedByDescriptor = compactable[invalidatedBy.name]; 104 var componentName; 105 106 if ('overridesShorthands' in shorthandDescriptor && shorthandDescriptor.overridesShorthands.indexOf(invalidatedBy.name) > -1) { 107 return true; 108 } 109 110 if (invalidatedByDescriptor && 'componentOf' in invalidatedByDescriptor) { 111 for (componentName in candidates[shorthandName]) { 112 if (invalidatedByDescriptor.componentOf.indexOf(componentName) > -1) { 113 return true; 114 } 115 } 116 } 117 118 return false; 119} 120 121function mixedImportance(components) { 122 var important; 123 var componentName; 124 125 for (componentName in components) { 126 if (undefined !== important && components[componentName].important != important) { 127 return true; 128 } 129 130 important = components[componentName].important; 131 } 132 133 return false; 134} 135 136function overridable(components, shorthandName, validator) { 137 var descriptor = compactable[shorthandName]; 138 var newValuePlaceholder = [ 139 Token.PROPERTY, 140 [Token.PROPERTY_NAME, shorthandName], 141 [Token.PROPERTY_VALUE, descriptor.defaultValue] 142 ]; 143 var newProperty = wrapSingle(newValuePlaceholder); 144 var component; 145 var mayOverride; 146 var i, l; 147 148 populateComponents([newProperty], validator, []); 149 150 for (i = 0, l = descriptor.components.length; i < l; i++) { 151 component = components[descriptor.components[i]]; 152 mayOverride = compactable[component.name].canOverride; 153 154 if (!everyValuesPair(mayOverride.bind(null, validator), newProperty.components[i], component)) { 155 return false; 156 } 157 } 158 159 return true; 160} 161 162function mergeable(components) { 163 var lastCount = null; 164 var currentCount; 165 var componentName; 166 var component; 167 var descriptor; 168 var values; 169 170 for (componentName in components) { 171 component = components[componentName]; 172 descriptor = compactable[componentName]; 173 174 if (!('restore' in descriptor)) { 175 continue; 176 } 177 178 restoreFromOptimizing([component.all[component.position]], restoreWithComponents); 179 values = descriptor.restore(component, compactable); 180 181 currentCount = values.length; 182 183 if (lastCount !== null && currentCount !== lastCount) { 184 return false; 185 } 186 187 lastCount = currentCount; 188 } 189 190 return true; 191} 192 193function mixedInherit(components) { 194 var componentName; 195 var lastValue = null; 196 var currentValue; 197 198 for (componentName in components) { 199 currentValue = hasInherit(components[componentName]); 200 201 if (lastValue !== null && lastValue !== currentValue) { 202 return true; 203 } 204 205 lastValue = currentValue; 206 } 207 208 return false; 209} 210 211function replaceWithInheritBestFit(properties, candidateComponents, shorthandName, validator) { 212 var viaLonghands = buildSequenceWithInheritLonghands(candidateComponents, shorthandName, validator); 213 var viaShorthand = buildSequenceWithInheritShorthand(candidateComponents, shorthandName, validator); 214 var longhandTokensSequence = viaLonghands[0]; 215 var shorthandTokensSequence = viaShorthand[0]; 216 var isLonghandsShorter = serializeBody(longhandTokensSequence).length < serializeBody(shorthandTokensSequence).length; 217 var newTokensSequence = isLonghandsShorter ? longhandTokensSequence : shorthandTokensSequence; 218 var newProperty = isLonghandsShorter ? viaLonghands[1] : viaShorthand[1]; 219 var newComponents = isLonghandsShorter ? viaLonghands[2] : viaShorthand[2]; 220 var all = candidateComponents[Object.keys(candidateComponents)[0]].all; 221 var componentName; 222 var oldComponent; 223 var newComponent; 224 var newToken; 225 226 newProperty.position = all.length; 227 newProperty.shorthand = true; 228 newProperty.dirty = true; 229 newProperty.all = all; 230 newProperty.all.push(newTokensSequence[0]); 231 232 properties.push(newProperty); 233 234 for (componentName in candidateComponents) { 235 oldComponent = candidateComponents[componentName]; 236 oldComponent.unused = true; 237 238 if (oldComponent.name in newComponents) { 239 newComponent = newComponents[oldComponent.name]; 240 newToken = findTokenIn(newTokensSequence, componentName); 241 242 newComponent.position = all.length; 243 newComponent.all = all; 244 newComponent.all.push(newToken); 245 246 properties.push(newComponent); 247 } 248 } 249} 250 251function buildSequenceWithInheritLonghands(components, shorthandName, validator) { 252 var tokensSequence = []; 253 var inheritComponents = {}; 254 var nonInheritComponents = {}; 255 var descriptor = compactable[shorthandName]; 256 var shorthandToken = [ 257 Token.PROPERTY, 258 [Token.PROPERTY_NAME, shorthandName], 259 [Token.PROPERTY_VALUE, descriptor.defaultValue] 260 ]; 261 var newProperty = wrapSingle(shorthandToken); 262 var component; 263 var longhandToken; 264 var newComponent; 265 var nameMetadata; 266 var i, l; 267 268 populateComponents([newProperty], validator, []); 269 270 for (i = 0, l = descriptor.components.length; i < l; i++) { 271 component = components[descriptor.components[i]]; 272 273 if (hasInherit(component)) { 274 longhandToken = component.all[component.position].slice(0, 2); 275 Array.prototype.push.apply(longhandToken, component.value); 276 tokensSequence.push(longhandToken); 277 278 newComponent = deepClone(component); 279 newComponent.value = inferComponentValue(components, newComponent.name); 280 281 newProperty.components[i] = newComponent; 282 inheritComponents[component.name] = deepClone(component); 283 } else { 284 newComponent = deepClone(component); 285 newComponent.all = component.all; 286 newProperty.components[i] = newComponent; 287 288 nonInheritComponents[component.name] = component; 289 } 290 } 291 292 nameMetadata = joinMetadata(nonInheritComponents, 1); 293 shorthandToken[1].push(nameMetadata); 294 295 restoreFromOptimizing([newProperty], restoreWithComponents); 296 297 shorthandToken = shorthandToken.slice(0, 2); 298 Array.prototype.push.apply(shorthandToken, newProperty.value); 299 300 tokensSequence.unshift(shorthandToken); 301 302 return [tokensSequence, newProperty, inheritComponents]; 303} 304 305function inferComponentValue(components, propertyName) { 306 var descriptor = compactable[propertyName]; 307 308 if ('oppositeTo' in descriptor) { 309 return components[descriptor.oppositeTo].value; 310 } else { 311 return [[Token.PROPERTY_VALUE, descriptor.defaultValue]]; 312 } 313} 314 315function joinMetadata(components, at) { 316 var metadata = []; 317 var component; 318 var originalValue; 319 var componentMetadata; 320 var componentName; 321 322 for (componentName in components) { 323 component = components[componentName]; 324 originalValue = component.all[component.position]; 325 componentMetadata = originalValue[at][originalValue[at].length - 1]; 326 327 Array.prototype.push.apply(metadata, componentMetadata); 328 } 329 330 return metadata.sort(metadataSorter); 331} 332 333function metadataSorter(metadata1, metadata2) { 334 var line1 = metadata1[0]; 335 var line2 = metadata2[0]; 336 var column1 = metadata1[1]; 337 var column2 = metadata2[1]; 338 339 if (line1 < line2) { 340 return -1; 341 } else if (line1 === line2) { 342 return column1 < column2 ? -1 : 1; 343 } else { 344 return 1; 345 } 346} 347 348function buildSequenceWithInheritShorthand(components, shorthandName, validator) { 349 var tokensSequence = []; 350 var inheritComponents = {}; 351 var nonInheritComponents = {}; 352 var descriptor = compactable[shorthandName]; 353 var shorthandToken = [ 354 Token.PROPERTY, 355 [Token.PROPERTY_NAME, shorthandName], 356 [Token.PROPERTY_VALUE, 'inherit'] 357 ]; 358 var newProperty = wrapSingle(shorthandToken); 359 var component; 360 var longhandToken; 361 var nameMetadata; 362 var valueMetadata; 363 var i, l; 364 365 populateComponents([newProperty], validator, []); 366 367 for (i = 0, l = descriptor.components.length; i < l; i++) { 368 component = components[descriptor.components[i]]; 369 370 if (hasInherit(component)) { 371 inheritComponents[component.name] = component; 372 } else { 373 longhandToken = component.all[component.position].slice(0, 2); 374 Array.prototype.push.apply(longhandToken, component.value); 375 tokensSequence.push(longhandToken); 376 377 nonInheritComponents[component.name] = deepClone(component); 378 } 379 } 380 381 nameMetadata = joinMetadata(inheritComponents, 1); 382 shorthandToken[1].push(nameMetadata); 383 384 valueMetadata = joinMetadata(inheritComponents, 2); 385 shorthandToken[2].push(valueMetadata); 386 387 tokensSequence.unshift(shorthandToken); 388 389 return [tokensSequence, newProperty, nonInheritComponents]; 390} 391 392function findTokenIn(tokens, componentName) { 393 var i, l; 394 395 for (i = 0, l = tokens.length; i < l; i++) { 396 if (tokens[i][1][1] == componentName) { 397 return tokens[i]; 398 } 399 } 400} 401 402function replaceWithShorthand(properties, candidateComponents, shorthandName, validator) { 403 var descriptor = compactable[shorthandName]; 404 var nameMetadata; 405 var valueMetadata; 406 var newValuePlaceholder = [ 407 Token.PROPERTY, 408 [Token.PROPERTY_NAME, shorthandName], 409 [Token.PROPERTY_VALUE, descriptor.defaultValue] 410 ]; 411 var all; 412 413 var newProperty = wrapSingle(newValuePlaceholder); 414 newProperty.shorthand = true; 415 newProperty.dirty = true; 416 417 populateComponents([newProperty], validator, []); 418 419 for (var i = 0, l = descriptor.components.length; i < l; i++) { 420 var component = candidateComponents[descriptor.components[i]]; 421 422 newProperty.components[i] = deepClone(component); 423 newProperty.important = component.important; 424 425 all = component.all; 426 } 427 428 for (var componentName in candidateComponents) { 429 candidateComponents[componentName].unused = true; 430 } 431 432 nameMetadata = joinMetadata(candidateComponents, 1); 433 newValuePlaceholder[1].push(nameMetadata); 434 435 valueMetadata = joinMetadata(candidateComponents, 2); 436 newValuePlaceholder[2].push(valueMetadata); 437 438 newProperty.position = all.length; 439 newProperty.all = all; 440 newProperty.all.push(newValuePlaceholder); 441 442 properties.push(newProperty); 443} 444 445module.exports = mergeIntoShorthands; 446