override-properties.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  1. var hasInherit = require('./has-inherit');
  2. var hasUnset = require('./has-unset');
  3. var everyValuesPair = require('./every-values-pair');
  4. var findComponentIn = require('./find-component-in');
  5. var isComponentOf = require('./is-component-of');
  6. var isMergeableShorthand = require('./is-mergeable-shorthand');
  7. var overridesNonComponentShorthand = require('./overrides-non-component-shorthand');
  8. var sameVendorPrefixesIn = require('./../../vendor-prefixes').same;
  9. var configuration = require('../../configuration');
  10. var deepClone = require('../../clone').deep;
  11. var restoreWithComponents = require('../restore-with-components');
  12. var shallowClone = require('../../clone').shallow;
  13. var restoreFromOptimizing = require('../../restore-from-optimizing');
  14. var Token = require('../../../tokenizer/token');
  15. var Marker = require('../../../tokenizer/marker');
  16. var serializeProperty = require('../../../writer/one-time').property;
  17. function sameValue(_validator, value1, value2) {
  18. return value1 === value2;
  19. }
  20. function wouldBreakCompatibility(property, validator) {
  21. for (var i = 0; i < property.components.length; i++) {
  22. var component = property.components[i];
  23. var descriptor = configuration[component.name];
  24. var canOverride = descriptor && descriptor.canOverride || sameValue;
  25. var _component = shallowClone(component);
  26. _component.value = [[Token.PROPERTY_VALUE, descriptor.defaultValue]];
  27. if (!everyValuesPair(canOverride.bind(null, validator), _component, component)) {
  28. return true;
  29. }
  30. }
  31. return false;
  32. }
  33. function overrideIntoMultiplex(property, by) {
  34. by.unused = true;
  35. turnIntoMultiplex(by, multiplexSize(property));
  36. property.value = by.value;
  37. }
  38. function overrideByMultiplex(property, by) {
  39. by.unused = true;
  40. property.multiplex = true;
  41. property.value = by.value;
  42. }
  43. function overrideSimple(property, by) {
  44. by.unused = true;
  45. property.value = by.value;
  46. }
  47. function override(property, by) {
  48. if (by.multiplex) {
  49. overrideByMultiplex(property, by);
  50. } else if (property.multiplex) {
  51. overrideIntoMultiplex(property, by);
  52. } else {
  53. overrideSimple(property, by);
  54. }
  55. }
  56. function overrideShorthand(property, by) {
  57. by.unused = true;
  58. for (var i = 0, l = property.components.length; i < l; i++) {
  59. override(property.components[i], by.components[i]);
  60. }
  61. }
  62. function turnIntoMultiplex(property, size) {
  63. property.multiplex = true;
  64. if (configuration[property.name].shorthand) {
  65. turnShorthandValueIntoMultiplex(property, size);
  66. } else {
  67. turnLonghandValueIntoMultiplex(property, size);
  68. }
  69. }
  70. function turnShorthandValueIntoMultiplex(property, size) {
  71. var component;
  72. var i, l;
  73. for (i = 0, l = property.components.length; i < l; i++) {
  74. component = property.components[i];
  75. if (!component.multiplex) {
  76. turnLonghandValueIntoMultiplex(component, size);
  77. }
  78. }
  79. }
  80. function turnLonghandValueIntoMultiplex(property, size) {
  81. var descriptor = configuration[property.name];
  82. var withRealValue = descriptor.intoMultiplexMode == 'real';
  83. var withValue = descriptor.intoMultiplexMode == 'real'
  84. ? property.value.slice(0)
  85. : (descriptor.intoMultiplexMode == 'placeholder' ? descriptor.placeholderValue : descriptor.defaultValue);
  86. var i = multiplexSize(property);
  87. var j;
  88. var m = withValue.length;
  89. for (; i < size; i++) {
  90. property.value.push([Token.PROPERTY_VALUE, Marker.COMMA]);
  91. if (Array.isArray(withValue)) {
  92. for (j = 0; j < m; j++) {
  93. property.value.push(withRealValue ? withValue[j] : [Token.PROPERTY_VALUE, withValue[j]]);
  94. }
  95. } else {
  96. property.value.push(withRealValue ? withValue : [Token.PROPERTY_VALUE, withValue]);
  97. }
  98. }
  99. }
  100. function multiplexSize(component) {
  101. var size = 0;
  102. for (var i = 0, l = component.value.length; i < l; i++) {
  103. if (component.value[i][1] == Marker.COMMA) { size++; }
  104. }
  105. return size + 1;
  106. }
  107. function lengthOf(property) {
  108. var fakeAsArray = [
  109. Token.PROPERTY,
  110. [Token.PROPERTY_NAME, property.name]
  111. ].concat(property.value);
  112. return serializeProperty([fakeAsArray], 0).length;
  113. }
  114. function moreSameShorthands(properties, startAt, name) {
  115. // Since we run the main loop in `compactOverrides` backwards, at this point some
  116. // properties may not be marked as unused.
  117. // We should consider reverting the order if possible
  118. var count = 0;
  119. for (var i = startAt; i >= 0; i--) {
  120. if (properties[i].name == name && !properties[i].unused) { count++; }
  121. if (count > 1) { break; }
  122. }
  123. return count > 1;
  124. }
  125. function overridingFunction(shorthand, validator) {
  126. for (var i = 0, l = shorthand.components.length; i < l; i++) {
  127. if (!anyValue(validator.isUrl, shorthand.components[i])
  128. && anyValue(validator.isFunction, shorthand.components[i])) { return true; }
  129. }
  130. return false;
  131. }
  132. function anyValue(fn, property) {
  133. for (var i = 0, l = property.value.length; i < l; i++) {
  134. if (property.value[i][1] == Marker.COMMA) { continue; }
  135. if (fn(property.value[i][1])) { return true; }
  136. }
  137. return false;
  138. }
  139. function wouldResultInLongerValue(left, right) {
  140. if (!left.multiplex && !right.multiplex || left.multiplex && right.multiplex) { return false; }
  141. var multiplex = left.multiplex ? left : right;
  142. var simple = left.multiplex ? right : left;
  143. var component;
  144. var multiplexClone = deepClone(multiplex);
  145. restoreFromOptimizing([multiplexClone], restoreWithComponents);
  146. var simpleClone = deepClone(simple);
  147. restoreFromOptimizing([simpleClone], restoreWithComponents);
  148. var lengthBefore = lengthOf(multiplexClone) + 1 + lengthOf(simpleClone);
  149. if (left.multiplex) {
  150. component = findComponentIn(multiplexClone, simpleClone);
  151. overrideIntoMultiplex(component, simpleClone);
  152. } else {
  153. component = findComponentIn(simpleClone, multiplexClone);
  154. turnIntoMultiplex(simpleClone, multiplexSize(multiplexClone));
  155. overrideByMultiplex(component, multiplexClone);
  156. }
  157. restoreFromOptimizing([simpleClone], restoreWithComponents);
  158. var lengthAfter = lengthOf(simpleClone);
  159. return lengthBefore <= lengthAfter;
  160. }
  161. function isCompactable(property) {
  162. return property.name in configuration;
  163. }
  164. function noneOverrideHack(left, right) {
  165. return !left.multiplex
  166. && (left.name == 'background' || left.name == 'background-image')
  167. && right.multiplex
  168. && (right.name == 'background' || right.name == 'background-image')
  169. && anyLayerIsNone(right.value);
  170. }
  171. function anyLayerIsNone(values) {
  172. var layers = intoLayers(values);
  173. for (var i = 0, l = layers.length; i < l; i++) {
  174. if (layers[i].length == 1 && layers[i][0][1] == 'none') { return true; }
  175. }
  176. return false;
  177. }
  178. function intoLayers(values) {
  179. var layers = [];
  180. for (var i = 0, layer = [], l = values.length; i < l; i++) {
  181. var value = values[i];
  182. if (value[1] == Marker.COMMA) {
  183. layers.push(layer);
  184. layer = [];
  185. } else {
  186. layer.push(value);
  187. }
  188. }
  189. layers.push(layer);
  190. return layers;
  191. }
  192. function overrideProperties(properties, withMerging, compatibility, validator) {
  193. var mayOverride, right, left, component;
  194. var overriddenComponents;
  195. var overriddenComponent;
  196. var overridingComponent;
  197. var overridable;
  198. var i, j, k;
  199. propertyLoop:
  200. for (i = properties.length - 1; i >= 0; i--) {
  201. right = properties[i];
  202. if (!isCompactable(right)) { continue; }
  203. if (right.block) { continue; }
  204. mayOverride = configuration[right.name].canOverride || sameValue;
  205. traverseLoop:
  206. for (j = i - 1; j >= 0; j--) {
  207. left = properties[j];
  208. if (!isCompactable(left)) { continue; }
  209. if (left.block) { continue; }
  210. if (left.dynamic || right.dynamic) { continue; }
  211. if (left.unused || right.unused) { continue; }
  212. if (left.hack && !right.hack && !right.important || !left.hack && !left.important && right.hack) { continue; }
  213. if (left.important == right.important && left.hack[0] != right.hack[0]) { continue; }
  214. if (left.important == right.important
  215. && (left.hack[0] != right.hack[0] || (left.hack[1] && left.hack[1] != right.hack[1]))) { continue; }
  216. if (hasInherit(right)) { continue; }
  217. if (noneOverrideHack(left, right)) { continue; }
  218. if (right.shorthand && isComponentOf(right, left)) {
  219. // maybe `left` can be overridden by `right` which is a shorthand?
  220. if (!right.important && left.important) { continue; }
  221. if (!sameVendorPrefixesIn([left], right.components)) { continue; }
  222. if (!anyValue(validator.isFunction, left) && overridingFunction(right, validator)) { continue; }
  223. if (!isMergeableShorthand(right)) {
  224. left.unused = true;
  225. continue;
  226. }
  227. component = findComponentIn(right, left);
  228. mayOverride = configuration[left.name].canOverride || sameValue;
  229. if (everyValuesPair(mayOverride.bind(null, validator), left, component)) {
  230. left.unused = true;
  231. }
  232. } else if (right.shorthand && overridesNonComponentShorthand(right, left)) {
  233. // `right` is a shorthand while `left` can be overriden by it, think `border` and `border-top`
  234. if (!right.important && left.important) {
  235. continue;
  236. }
  237. if (!sameVendorPrefixesIn([left], right.components)) {
  238. continue;
  239. }
  240. if (!anyValue(validator.isFunction, left) && overridingFunction(right, validator)) {
  241. continue;
  242. }
  243. overriddenComponents = left.shorthand
  244. ? left.components
  245. : [left];
  246. for (k = overriddenComponents.length - 1; k >= 0; k--) {
  247. overriddenComponent = overriddenComponents[k];
  248. overridingComponent = findComponentIn(right, overriddenComponent);
  249. mayOverride = configuration[overriddenComponent.name].canOverride || sameValue;
  250. if (!everyValuesPair(mayOverride.bind(null, validator), left, overridingComponent)) {
  251. continue traverseLoop;
  252. }
  253. }
  254. left.unused = true;
  255. } else if (withMerging && left.shorthand && !right.shorthand && isComponentOf(left, right, true)) {
  256. // maybe `right` can be pulled into `left` which is a shorthand?
  257. if (right.important && !left.important) { continue; }
  258. if (!right.important && left.important) {
  259. right.unused = true;
  260. continue;
  261. }
  262. // Pending more clever algorithm in #527
  263. if (moreSameShorthands(properties, i - 1, left.name)) { continue; }
  264. if (overridingFunction(left, validator)) { continue; }
  265. if (!isMergeableShorthand(left)) { continue; }
  266. if (hasUnset(left) || hasUnset(right)) { continue; }
  267. component = findComponentIn(left, right);
  268. if (everyValuesPair(mayOverride.bind(null, validator), component, right)) {
  269. var disabledBackgroundMerging = !compatibility.properties.backgroundClipMerging && component.name.indexOf('background-clip') > -1
  270. || !compatibility.properties.backgroundOriginMerging && component.name.indexOf('background-origin') > -1
  271. || !compatibility.properties.backgroundSizeMerging && component.name.indexOf('background-size') > -1;
  272. var nonMergeableValue = configuration[right.name].nonMergeableValue === right.value[0][1];
  273. if (disabledBackgroundMerging || nonMergeableValue) { continue; }
  274. if (!compatibility.properties.merging && wouldBreakCompatibility(left, validator)) { continue; }
  275. if (component.value[0][1] != right.value[0][1] && (hasInherit(left) || hasInherit(right))) { continue; }
  276. if (wouldResultInLongerValue(left, right)) { continue; }
  277. if (!left.multiplex && right.multiplex) { turnIntoMultiplex(left, multiplexSize(right)); }
  278. override(component, right);
  279. left.dirty = true;
  280. }
  281. } else if (withMerging && left.shorthand && right.shorthand && left.name == right.name) {
  282. // merge if all components can be merged
  283. if (!left.multiplex && right.multiplex) { continue; }
  284. if (!right.important && left.important) {
  285. right.unused = true;
  286. continue propertyLoop;
  287. }
  288. if (right.important && !left.important) {
  289. left.unused = true;
  290. continue;
  291. }
  292. if (!isMergeableShorthand(right)) {
  293. left.unused = true;
  294. continue;
  295. }
  296. for (k = left.components.length - 1; k >= 0; k--) {
  297. var leftComponent = left.components[k];
  298. var rightComponent = right.components[k];
  299. mayOverride = configuration[leftComponent.name].canOverride || sameValue;
  300. if (!everyValuesPair(mayOverride.bind(null, validator), leftComponent, rightComponent)) {
  301. continue propertyLoop;
  302. }
  303. }
  304. overrideShorthand(left, right);
  305. left.dirty = true;
  306. } else if (withMerging && left.shorthand && right.shorthand && isComponentOf(left, right)) {
  307. // border is a shorthand but any of its components is a shorthand too
  308. if (!left.important && right.important) { continue; }
  309. component = findComponentIn(left, right);
  310. mayOverride = configuration[right.name].canOverride || sameValue;
  311. if (!everyValuesPair(mayOverride.bind(null, validator), component, right)) { continue; }
  312. if (left.important && !right.important) {
  313. right.unused = true;
  314. continue;
  315. }
  316. var rightRestored = configuration[right.name].restore(right, configuration);
  317. if (rightRestored.length > 1) { continue; }
  318. component = findComponentIn(left, right);
  319. override(component, right);
  320. right.dirty = true;
  321. } else if (left.name == right.name) {
  322. // two non-shorthands should be merged based on understandability
  323. overridable = true;
  324. if (right.shorthand) {
  325. for (k = right.components.length - 1; k >= 0 && overridable; k--) {
  326. overriddenComponent = left.components[k];
  327. overridingComponent = right.components[k];
  328. mayOverride = configuration[overridingComponent.name].canOverride || sameValue;
  329. overridable = everyValuesPair(mayOverride.bind(null, validator), overriddenComponent, overridingComponent);
  330. }
  331. } else {
  332. mayOverride = configuration[right.name].canOverride || sameValue;
  333. overridable = everyValuesPair(mayOverride.bind(null, validator), left, right);
  334. }
  335. if (left.important && !right.important && overridable) {
  336. right.unused = true;
  337. continue;
  338. }
  339. if (!left.important && right.important && overridable) {
  340. left.unused = true;
  341. continue;
  342. }
  343. if (!overridable) {
  344. continue;
  345. }
  346. left.unused = true;
  347. }
  348. }
  349. }
  350. }
  351. module.exports = overrideProperties;