No Description

override-compactor.js 6.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. // Compacts the given tokens according to their ability to override each other.
  2. var validator = require('./validator');
  3. module.exports = (function () {
  4. // Default override function: only allow overrides when the two values are the same
  5. var sameValue = function (val1, val2) {
  6. return val1 === val2;
  7. };
  8. var compactOverrides = function (tokens, processable, Token, compatibility) {
  9. var result, can, token, t, i, ii, iiii, oldResult, matchingComponent;
  10. // Used when searching for a component that matches token
  11. var nameMatchFilter1 = function (x) {
  12. return x.prop === token.prop;
  13. };
  14. // Used when searching for a component that matches t
  15. var nameMatchFilter2 = function (x) {
  16. return x.prop === t.prop;
  17. };
  18. function willResultInShorterValue (shorthand, token) {
  19. var shorthandCopy = shorthand.clone();
  20. shorthandCopy.isDirty = true;
  21. shorthandCopy.isShorthand = true;
  22. shorthandCopy.components = [];
  23. shorthand.components.forEach(function (component) {
  24. var componentCopy = component.clone();
  25. if (component.prop == token.prop)
  26. componentCopy.value = token.value;
  27. shorthandCopy.components.push(componentCopy);
  28. });
  29. return Token.getDetokenizedLength([shorthand, token]) >= Token.getDetokenizedLength([shorthandCopy]);
  30. }
  31. // Go from the end and always take what the current token can't override as the new result set
  32. // NOTE: can't cache result.length here because it will change with every iteration
  33. for (result = tokens, i = 0; (ii = result.length - 1 - i) >= 0; i++) {
  34. token = result[ii];
  35. can = (processable[token.prop] && processable[token.prop].canOverride) || sameValue;
  36. oldResult = result;
  37. result = [];
  38. // Special flag which indicates that the current token should be removed
  39. var removeSelf = false;
  40. var oldResultLength = oldResult.length;
  41. for (var iii = 0; iii < oldResultLength; iii++) {
  42. t = oldResult[iii];
  43. // A token can't override itself (checked by reference, not by value)
  44. // NOTE: except when we explicitly tell it to remove itself
  45. if (t === token && !removeSelf) {
  46. result.push(t);
  47. continue;
  48. }
  49. // Only an important token can even try to override tokens that come after it
  50. if (iii > ii && !token.isImportant) {
  51. result.push(t);
  52. continue;
  53. }
  54. // A nonimportant token can never override an important one
  55. if (t.isImportant && !token.isImportant) {
  56. result.push(t);
  57. continue;
  58. }
  59. if (token.isShorthand && !t.isShorthand && t.isComponentOf(token)) {
  60. // token (a shorthand) is trying to override t (a component)
  61. // Find the matching component in the shorthand
  62. matchingComponent = token.components.filter(nameMatchFilter2)[0];
  63. can = (processable[t.prop] && processable[t.prop].canOverride) || sameValue;
  64. if (!can(t.value, matchingComponent.value)) {
  65. // The shorthand can't override the component
  66. result.push(t);
  67. }
  68. } else if (t.isShorthand && !token.isShorthand && token.isComponentOf(t)) {
  69. // token (a component) is trying to override a component of t (a shorthand)
  70. // Find the matching component in the shorthand
  71. matchingComponent = t.components.filter(nameMatchFilter1)[0];
  72. if (can(matchingComponent.value, token.value)) {
  73. // The component can override the matching component in the shorthand
  74. if (compatibility) {
  75. // in compatibility mode check if shorthand in not less understandable than merged-in value
  76. var wouldBreakCompatibility = false;
  77. for (iiii = 0; iiii < t.components.length; iiii++) {
  78. var o = processable[t.components[iiii].prop];
  79. can = (o && o.canOverride) || sameValue;
  80. if (!can(o.defaultValue, t.components[iiii].value)) {
  81. wouldBreakCompatibility = true;
  82. break;
  83. }
  84. }
  85. if (wouldBreakCompatibility) {
  86. result.push(t);
  87. continue;
  88. }
  89. }
  90. if ((!token.isImportant || token.isImportant && matchingComponent.isImportant) && willResultInShorterValue(t, token)) {
  91. // The overriding component is non-important which means we can simply include it into the shorthand
  92. // NOTE: stuff that can't really be included, like inherit, is taken care of at the final step, not here
  93. matchingComponent.value = token.value;
  94. // We use the special flag to get rid of the component
  95. removeSelf = true;
  96. } else {
  97. // The overriding component is important; sadly we can't get rid of it,
  98. // but we can still mark the matching component in the shorthand as irrelevant
  99. matchingComponent.isIrrelevant = true;
  100. }
  101. t.isDirty = true;
  102. }
  103. result.push(t);
  104. } else if (token.isShorthand && t.isShorthand && token.prop === t.prop) {
  105. // token is a shorthand and is trying to override another instance of the same shorthand
  106. // Can only override other shorthand when each of its components can override each of the other's components
  107. for (iiii = 0; iiii < t.components.length; iiii++) {
  108. can = (processable[t.components[iiii].prop] && processable[t.components[iiii].prop].canOverride) || sameValue;
  109. if (!can(t.components[iiii].value, token.components[iiii].value)) {
  110. result.push(t);
  111. break;
  112. }
  113. if (t.components[iiii].isImportant && token.components[iiii].isImportant && (validator.isValidFunction(t.components[iiii].value) ^ validator.isValidFunction(token.components[iiii].value))) {
  114. result.push(t);
  115. break;
  116. }
  117. }
  118. } else if (t.prop !== token.prop || !can(t.value, token.value)) {
  119. // in every other case, use the override mechanism
  120. result.push(t);
  121. } else if (t.isImportant && token.isImportant && (validator.isValidFunction(t.value) ^ validator.isValidFunction(token.value))) {
  122. result.push(t);
  123. }
  124. }
  125. if (removeSelf) {
  126. i--;
  127. }
  128. }
  129. return result;
  130. };
  131. return {
  132. compactOverrides: compactOverrides
  133. };
  134. })();