merge-into-shorthands.js 14 KB

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