161 lines
6.4 KiB
JavaScript
161 lines
6.4 KiB
JavaScript
|
|
// Compacts the given tokens according to their ability to override each other.
|
|
|
|
var validator = require('./validator');
|
|
|
|
module.exports = (function () {
|
|
// Default override function: only allow overrides when the two values are the same
|
|
var sameValue = function (val1, val2) {
|
|
return val1 === val2;
|
|
};
|
|
|
|
var compactOverrides = function (tokens, processable, Token, compatibility) {
|
|
var result, can, token, t, i, ii, iiii, oldResult, matchingComponent;
|
|
|
|
// Used when searching for a component that matches token
|
|
var nameMatchFilter1 = function (x) {
|
|
return x.prop === token.prop;
|
|
};
|
|
// Used when searching for a component that matches t
|
|
var nameMatchFilter2 = function (x) {
|
|
return x.prop === t.prop;
|
|
};
|
|
|
|
function willResultInShorterValue (shorthand, token) {
|
|
var shorthandCopy = shorthand.clone();
|
|
shorthandCopy.isDirty = true;
|
|
shorthandCopy.isShorthand = true;
|
|
shorthandCopy.components = [];
|
|
|
|
shorthand.components.forEach(function (component) {
|
|
var componentCopy = component.clone();
|
|
if (component.prop == token.prop)
|
|
componentCopy.value = token.value;
|
|
|
|
shorthandCopy.components.push(componentCopy);
|
|
});
|
|
|
|
return Token.getDetokenizedLength([shorthand, token]) >= Token.getDetokenizedLength([shorthandCopy]);
|
|
}
|
|
|
|
// Go from the end and always take what the current token can't override as the new result set
|
|
// NOTE: can't cache result.length here because it will change with every iteration
|
|
for (result = tokens, i = 0; (ii = result.length - 1 - i) >= 0; i++) {
|
|
token = result[ii];
|
|
can = (processable[token.prop] && processable[token.prop].canOverride) || sameValue;
|
|
oldResult = result;
|
|
result = [];
|
|
|
|
// Special flag which indicates that the current token should be removed
|
|
var removeSelf = false;
|
|
var oldResultLength = oldResult.length;
|
|
|
|
for (var iii = 0; iii < oldResultLength; iii++) {
|
|
t = oldResult[iii];
|
|
|
|
// A token can't override itself (checked by reference, not by value)
|
|
// NOTE: except when we explicitly tell it to remove itself
|
|
if (t === token && !removeSelf) {
|
|
result.push(t);
|
|
continue;
|
|
}
|
|
|
|
// Only an important token can even try to override tokens that come after it
|
|
if (iii > ii && !token.isImportant) {
|
|
result.push(t);
|
|
continue;
|
|
}
|
|
|
|
// A nonimportant token can never override an important one
|
|
if (t.isImportant && !token.isImportant) {
|
|
result.push(t);
|
|
continue;
|
|
}
|
|
|
|
if (token.isShorthand && !t.isShorthand && t.isComponentOf(token)) {
|
|
// token (a shorthand) is trying to override t (a component)
|
|
|
|
// Find the matching component in the shorthand
|
|
matchingComponent = token.components.filter(nameMatchFilter2)[0];
|
|
can = (processable[t.prop] && processable[t.prop].canOverride) || sameValue;
|
|
if (!can(t.value, matchingComponent.value)) {
|
|
// The shorthand can't override the component
|
|
result.push(t);
|
|
}
|
|
} else if (t.isShorthand && !token.isShorthand && token.isComponentOf(t)) {
|
|
// token (a component) is trying to override a component of t (a shorthand)
|
|
|
|
// Find the matching component in the shorthand
|
|
matchingComponent = t.components.filter(nameMatchFilter1)[0];
|
|
if (can(matchingComponent.value, token.value)) {
|
|
// The component can override the matching component in the shorthand
|
|
|
|
if (compatibility) {
|
|
// in compatibility mode check if shorthand in not less understandable than merged-in value
|
|
var wouldBreakCompatibility = false;
|
|
for (iiii = 0; iiii < t.components.length; iiii++) {
|
|
var o = processable[t.components[iiii].prop];
|
|
can = (o && o.canOverride) || sameValue;
|
|
|
|
if (!can(o.defaultValue, t.components[iiii].value)) {
|
|
wouldBreakCompatibility = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (wouldBreakCompatibility) {
|
|
result.push(t);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ((!token.isImportant || token.isImportant && matchingComponent.isImportant) && willResultInShorterValue(t, token)) {
|
|
// The overriding component is non-important which means we can simply include it into the shorthand
|
|
// NOTE: stuff that can't really be included, like inherit, is taken care of at the final step, not here
|
|
matchingComponent.value = token.value;
|
|
// We use the special flag to get rid of the component
|
|
removeSelf = true;
|
|
} else {
|
|
// The overriding component is important; sadly we can't get rid of it,
|
|
// but we can still mark the matching component in the shorthand as irrelevant
|
|
matchingComponent.isIrrelevant = true;
|
|
}
|
|
t.isDirty = true;
|
|
}
|
|
result.push(t);
|
|
} else if (token.isShorthand && t.isShorthand && token.prop === t.prop) {
|
|
// token is a shorthand and is trying to override another instance of the same shorthand
|
|
|
|
// Can only override other shorthand when each of its components can override each of the other's components
|
|
for (iiii = 0; iiii < t.components.length; iiii++) {
|
|
can = (processable[t.components[iiii].prop] && processable[t.components[iiii].prop].canOverride) || sameValue;
|
|
if (!can(t.components[iiii].value, token.components[iiii].value)) {
|
|
result.push(t);
|
|
break;
|
|
}
|
|
if (t.components[iiii].isImportant && token.components[iiii].isImportant && (validator.isValidFunction(t.components[iiii].value) ^ validator.isValidFunction(token.components[iiii].value))) {
|
|
result.push(t);
|
|
break;
|
|
}
|
|
}
|
|
} else if (t.prop !== token.prop || !can(t.value, token.value)) {
|
|
// in every other case, use the override mechanism
|
|
result.push(t);
|
|
} else if (t.isImportant && token.isImportant && (validator.isValidFunction(t.value) ^ validator.isValidFunction(token.value))) {
|
|
result.push(t);
|
|
}
|
|
}
|
|
if (removeSelf) {
|
|
i--;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
return {
|
|
compactOverrides: compactOverrides
|
|
};
|
|
|
|
})();
|