// 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 }; })();