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