From 7699b286b2b4dc19d23df2a292cbdf7191746e17 Mon Sep 17 00:00:00 2001 From: Jakub Pawlowicz Date: Wed, 18 Mar 2015 20:37:01 +0000 Subject: [PATCH] Reworks tokenizer to operate on arrays. * Uses arrays instead of hashes. * Gets rid of cached metadata in favor to live-calculated values. * Adds `removeEmpty` step in advanced optimizer to improve empty elements removal. * Cleans up simple optimizer iteration loop. * Cleans up source map tracking. --- lib/properties/extractor.js | 19 +- lib/properties/optimizer.js | 117 ++-- lib/properties/processable.js | 4 +- lib/properties/reorderable.js | 6 +- lib/properties/token.js | 17 +- lib/selectors/optimizer.js | 2 +- lib/selectors/optimizers/advanced.js | 233 +++---- lib/selectors/optimizers/clean-up.js | 44 +- lib/selectors/optimizers/simple.js | 161 +++-- lib/selectors/source-map-stringifier.js | 92 +-- lib/selectors/stringifier.js | 51 +- lib/selectors/tokenizer.js | 92 ++- lib/utils/extractors.js | 55 +- lib/utils/input-source-map-tracker.js | 12 +- lib/utils/source-maps.js | 153 ++--- lib/utils/stringify-tokens.js | 24 + test/properties/extractor-test.js | 30 +- test/properties/reorderable-test.js | 2 +- test/selectors/optimizers/simple-test.js | 36 +- test/selectors/tokenizer-source-maps-test.js | 624 +++++++++---------- test/selectors/tokenizer-test.js | 336 +++------- test/source-map-test.js | 2 +- 22 files changed, 947 insertions(+), 1165 deletions(-) create mode 100644 lib/utils/stringify-tokens.js diff --git a/lib/properties/extractor.js b/lib/properties/extractor.js index 69156c37..ac131acd 100644 --- a/lib/properties/extractor.js +++ b/lib/properties/extractor.js @@ -5,10 +5,11 @@ function extract(token) { var properties = []; - if (token.kind == 'selector') { - var inSimpleSelector = !/[\.\+#>~\s]/.test(token.metadata.selector); - for (var i = 0, l = token.metadata.bodiesList.length; i < l; i++) { - var property = token.metadata.bodiesList[i]; + if (token[0] == 'selector') { + // TODO: stringifySelector + var inSimpleSelector = !/[\.\+#>~\s]/.test(token[1].join(',')); + for (var i = 0, l = token[2].length; i < l; i++) { + var property = token[2][i][0]; if (property.indexOf('__ESCAPED') === 0) continue; @@ -23,14 +24,14 @@ function extract(token) { name, property.substring(splitAt + 1), nameRoot, - property, - token.metadata.selectorsList, + token[2][i], + token[1], inSimpleSelector ]); } - } else if (token.kind == 'block') { - for (var j = 0, k = token.body.length; j < k; j++) { - properties = properties.concat(extract(token.body[j])); + } else if (token[0] == 'block') { + for (var j = 0, k = token[2].length; j < k; j++) { + properties = properties.concat(extract(token[2][j])); } } diff --git a/lib/properties/optimizer.js b/lib/properties/optimizer.js index 4ffe9166..ce891f9b 100644 --- a/lib/properties/optimizer.js +++ b/lib/properties/optimizer.js @@ -3,8 +3,6 @@ var processableInfo = require('./processable'); var overrideCompactor = require('./override-compactor'); var shorthandCompactor = require('./shorthand-compactor'); -function valueMapper(object) { return object.value; } - module.exports = function Optimizer(options, context) { var overridable = { 'animation-delay': ['animation'], @@ -111,46 +109,46 @@ module.exports = function Optimizer(options, context) { } } - var tokenize = function(body, selector) { - var keyValues = []; + var tokenize = function(properties, selector) { + var tokenized = []; - for (var i = 0, l = body.length; i < l; i++) { - var token = body[i]; - var firstColon = token.value.indexOf(':'); - var property = token.value.substring(0, firstColon); - var value = token.value.substring(firstColon + 1); + for (var i = 0, l = properties.length; i < l; i++) { + var property = properties[i]; + var firstColon = property[0].indexOf(':'); + var name = property[0].substring(0, firstColon); + var value = property[0].substring(firstColon + 1); if (value === '') { - context.warnings.push('Empty property \'' + property + '\' inside \'' + selector.map(valueMapper).join(',') + '\' selector. Ignoring.'); + context.warnings.push('Empty property \'' + name + '\' inside \'' + selector.join(',') + '\' selector. Ignoring.'); continue; } - keyValues.push([ - property, + tokenized.push([ + name, value, - token.value.indexOf('!important') > -1, - token.value.indexOf(IE_BACKSLASH_HACK, firstColon + 1) === token.value.length - IE_BACKSLASH_HACK.length, - token.metadata + value.indexOf('!important') > -1, + property[0].indexOf(IE_BACKSLASH_HACK, firstColon + 1) === property[0].length - IE_BACKSLASH_HACK.length, + property.slice(1) ]); } - return keyValues; + return tokenized; }; - var optimize = function(tokens, allowAdjacent) { + var optimize = function(properties, allowAdjacent) { var merged = []; - var properties = []; - var lastProperty = null; + var names = []; + var lastName = null; var rescanTrigger = {}; var removeOverridenBy = function(property, isImportant) { var overrided = overrides[property]; for (var i = 0, l = overrided.length; i < l; i++) { - for (var j = 0; j < properties.length; j++) { - if (properties[j] != overrided[i] || (merged[j][2] && !isImportant)) + for (var j = 0; j < names.length; j++) { + if (names[j] != overrided[i] || (merged[j][2] && !isImportant)) continue; merged.splice(j, 1); - properties.splice(j, 1); + names.splice(j, 1); j -= 1; } } @@ -163,16 +161,16 @@ module.exports = function Optimizer(options, context) { return allowAdjacent.indexOf(position) > -1; }; - tokensLoop: - for (var i = 0, l = tokens.length; i < l; i++) { - var token = tokens[i]; - var property = token[0]; - var value = token[1]; - var isImportant = token[2]; - var isIEHack = token[3]; - var _property = (property == '-ms-filter' || property == 'filter') ? - (lastProperty == 'background' || lastProperty == 'background-image' ? lastProperty : property) : - property; + propertiesLoop: + for (var i = 0, l = properties.length; i < l; i++) { + var property = properties[i]; + var name = property[0]; + var value = property[1]; + var isImportant = property[2]; + var isIEHack = property[3]; + var _name = (name == '-ms-filter' || name == 'filter') ? + (lastName == 'background' || lastName == 'background-image' ? lastName : name) : + name; var toOverridePosition = 0; if (isIEHack && !compatibility.properties.ieSuffixHack) @@ -183,9 +181,9 @@ module.exports = function Optimizer(options, context) { // e.g. a{display:inline-block;display:-moz-inline-box} // however if `mergeablePosition` yields true then the rule does not apply // (e.g merging two adjacent selectors: `a{display:block}a{display:block}`) - if (aggressiveMerging && property !== '' && _property != lastProperty || mergeablePosition(i)) { + if (aggressiveMerging && name !== '' && _name != lastName || mergeablePosition(i)) { while (true) { - toOverridePosition = properties.indexOf(_property, toOverridePosition); + toOverridePosition = names.indexOf(_name, toOverridePosition); if (toOverridePosition == -1) break; @@ -194,61 +192,60 @@ module.exports = function Optimizer(options, context) { var wasIEHack = lastToken[3]; if (wasImportant && !isImportant) - continue tokensLoop; + continue propertiesLoop; if (compatibility.properties.ieSuffixHack && !wasIEHack && isIEHack) break; - var _info = processable[_property]; - if (!isIEHack && !wasIEHack && _info && _info.canOverride && !_info.canOverride(tokens[toOverridePosition][1], value)) + var _info = processable[_name]; + if (!isIEHack && !wasIEHack && _info && _info.canOverride && !_info.canOverride(properties[toOverridePosition][1], value)) break; merged.splice(toOverridePosition, 1); - properties.splice(toOverridePosition, 1); + names.splice(toOverridePosition, 1); } } - merged.push(token); - properties.push(_property); + merged.push(property); + names.push(_name); // certain properties (see values of `overridable`) should trigger removal of // more granular properties (see keys of `overridable`) - if (rescanTrigger[_property]) - removeOverridenBy(_property, isImportant); + if (rescanTrigger[_name]) + removeOverridenBy(_name, isImportant); // add rescan triggers - if certain property appears later in the list a rescan needs // to be triggered, e.g 'border-top' triggers a rescan after 'border-top-width' and // 'border-top-color' as they can be removed - for (var j = 0, list = overridable[_property] || [], m = list.length; j < m; j++) + for (var j = 0, list = overridable[_name] || [], m = list.length; j < m; j++) rescanTrigger[list[j]] = true; - lastProperty = _property; + lastName = _name; } return merged; }; - var rebuild = function(tokens) { - var tokenized = []; - var list = []; + var rebuild = function(properties) { + var rebuilt = []; var eligibleForCompacting = false; - for (var i = 0, l = tokens.length; i < l; i++) { - if (!eligibleForCompacting && processableInfo.implementedFor.test(tokens[i][0])) + for (var i = 0, l = properties.length; i < l; i++) { + if (!eligibleForCompacting && processableInfo.implementedFor.test(properties[i][0])) eligibleForCompacting = true; // FIXME: the check should be gone with #407 - var property = !tokens[i][0] && tokens[i][1].indexOf('__ESCAPED_') === 0 ? - tokens[i][1] : - tokens[i][0] + ':' + tokens[i][1]; - tokenized.push({ value: property, metadata: tokens[i][4] }); - list.push(property); + var property = !properties[i][0] && properties[i][1].indexOf('__ESCAPED_') === 0 ? + properties[i][1] : + properties[i][0] + ':' + properties[i][1]; + var metadata = properties[i].pop(); + + rebuilt.push([property].concat(metadata)); } return { compactFurther: eligibleForCompacting, - list: list, - tokenized: tokenized + list: rebuilt }; }; @@ -265,14 +262,14 @@ module.exports = function Optimizer(options, context) { }; return { - process: function(selector, body, allowAdjacent, compactProperties) { - var tokenized = tokenize(body, selector); + process: function(selector, properties, allowAdjacent, compactProperties) { + var tokenized = tokenize(properties, selector); var optimized = optimize(tokenized, allowAdjacent); var rebuilt = rebuild(optimized); return shorthandCompacting && compactProperties && rebuilt.compactFurther ? - compact(rebuilt.tokenized) : - rebuilt; + compact(rebuilt.list) : + rebuilt.list; } }; }; diff --git a/lib/properties/processable.js b/lib/properties/processable.js index ea30c79b..60dc250a 100644 --- a/lib/properties/processable.js +++ b/lib/properties/processable.js @@ -90,8 +90,8 @@ module.exports = (function () { return canOverride.sameFunctionOrValue(val1, val2); }, border: function(val1, val2) { - var brokenUp1 = breakUp.border(Token.tokenizeOne({ value: val1 })); - var brokenUp2 = breakUp.border(Token.tokenizeOne({ value: val2 })); + var brokenUp1 = breakUp.border(Token.tokenizeOne([val1])); + var brokenUp2 = breakUp.border(Token.tokenizeOne([val2])); return canOverride.color(brokenUp1[2].value, brokenUp2[2].value); } diff --git a/lib/properties/reorderable.js b/lib/properties/reorderable.js index 7b53cb9d..902d690a 100644 --- a/lib/properties/reorderable.js +++ b/lib/properties/reorderable.js @@ -43,8 +43,10 @@ function canReorderSingle(left, right) { function selectorsDoNotOverlap(s1, s2) { for (var i = 0, l = s1.length; i < l; i++) { - if (s2.indexOf(s1[i]) > -1) - return false; + for (var j = 0, m = s2.length; j < m; j++) { + if (s1[i][0] == s2[j][0]) + return false; + } } return true; diff --git a/lib/properties/token.js b/lib/properties/token.js index 13c5b7bb..2d030e31 100644 --- a/lib/properties/token.js +++ b/lib/properties/token.js @@ -64,16 +64,16 @@ module.exports = (function() { // Parses one CSS property declaration into a token Token.tokenizeOne = function (fullProp) { // Find first colon - var colonPos = fullProp.value.indexOf(':'); + var colonPos = fullProp[0].indexOf(':'); if (colonPos < 0) { // This property doesn't have a colon, it's invalid. Let's keep it intact anyway. - return new Token('', fullProp.value); + return new Token('', fullProp[0]); } // Parse parts of the property - var prop = fullProp.value.substr(0, colonPos).trim(); - var value = fullProp.value.substr(colonPos + 1).trim(); + var prop = fullProp[0].substr(0, colonPos).trim(); + var value = fullProp[0].substr(colonPos + 1).trim(); var isImportant = false; var importantPos = value.indexOf(important); @@ -113,7 +113,6 @@ module.exports = (function() { tokens = [tokens]; } - var tokenized = []; var list = []; // This step takes care of putting together the components of shorthands @@ -136,14 +135,10 @@ module.exports = (function() { // FIXME: to be fixed with #429 property = property.replace(/\) /g, ')'); - tokenized.push({ value: property, metadata: t.metadata || {} }); - list.push(property); + list.push([property]); } - return { - list: list, - tokenized: tokenized - }; + return list; }; // Gets the final (detokenized) length of the given tokens diff --git a/lib/selectors/optimizer.js b/lib/selectors/optimizer.js index 051d5e96..b2a46fa1 100644 --- a/lib/selectors/optimizer.js +++ b/lib/selectors/optimizer.js @@ -8,7 +8,7 @@ function SelectorsOptimizer(options, context) { } SelectorsOptimizer.prototype.process = function (data, stringifier) { - var tokens = new Tokenizer(this.context, this.options.advanced, this.options.sourceMap).toTokens(data); + var tokens = new Tokenizer(this.context, this.options.sourceMap).toTokens(data); new SimpleOptimizer(this.options).optimize(tokens); if (this.options.advanced) diff --git a/lib/selectors/optimizers/advanced.js b/lib/selectors/optimizers/advanced.js index 744b3c31..af9133ea 100644 --- a/lib/selectors/optimizers/advanced.js +++ b/lib/selectors/optimizers/advanced.js @@ -4,25 +4,14 @@ var CleanUp = require('./clean-up'); var extractProperties = require('../../properties/extractor'); var canReorder = require('../../properties/reorderable').canReorder; var canReorderSingle = require('../../properties/reorderable').canReorderSingle; +var stringifyBody = require('../../utils/stringify-tokens').body; +var stringifySelector = require('../../utils/stringify-tokens').selector; function AdvancedOptimizer(options, context) { this.options = options; - this.minificationsMade = []; this.propertyOptimizer = new PropertyOptimizer(this.options, context); } -function changeBodyOf(token, newBody) { - token.body = newBody.tokenized; - token.metadata.body = newBody.list.join(';'); - token.metadata.bodiesList = newBody.list; -} - -function changeSelectorOf(token, newSelectors) { - token.value = newSelectors.tokenized; - token.metadata.selector = newSelectors.list.join(','); - token.metadata.selectorsList = newSelectors.list; -} - function unsafeSelector(value) { return /\.|\*| :/.test(value); } @@ -41,10 +30,10 @@ AdvancedOptimizer.prototype.removeDuplicates = function (tokens) { for (var i = 0, l = tokens.length; i < l; i++) { var token = tokens[i]; - if (token.kind != 'selector') + if (token[0] != 'selector') continue; - var id = token.metadata.body + '@' + token.metadata.selector; + var id = stringifyBody(token[2]) + '@' + stringifySelector(token[1]); var alreadyMatched = matched[id]; if (alreadyMatched) { @@ -62,36 +51,28 @@ AdvancedOptimizer.prototype.removeDuplicates = function (tokens) { for (var j = 0, n = forRemoval.length; j < n; j++) { tokens.splice(forRemoval[j] - j, 1); } - - this.minificationsMade.unshift(forRemoval.length > 0); }; AdvancedOptimizer.prototype.mergeAdjacent = function (tokens) { var forRemoval = []; - var lastToken = { selector: null, body: null }; + var lastToken = [null, [], []]; var adjacentSpace = this.options.compatibility.selectors.adjacentSpace; for (var i = 0, l = tokens.length; i < l; i++) { var token = tokens[i]; - if (token.kind != 'selector') { - lastToken = { selector: null, body: null }; + if (token[0] != 'selector') { + lastToken = [null, [], []]; continue; } - if (lastToken.kind == 'selector' && token.metadata.selector == lastToken.metadata.selector) { - var joinAt = [lastToken.body.length]; - changeBodyOf( - lastToken, - this.propertyOptimizer.process(token.value, lastToken.body.concat(token.body), joinAt, true) - ); + if (lastToken[0] == 'selector' && stringifySelector(token[1]) == stringifySelector(lastToken[1])) { + var joinAt = [lastToken[2].length]; + lastToken[2] = this.propertyOptimizer.process(token[1], lastToken[2].concat(token[2]), joinAt, true); forRemoval.push(i); - } else if (lastToken.body && token.metadata.body == lastToken.metadata.body && - !this.isSpecial(token.metadata.selector) && !this.isSpecial(lastToken.metadata.selector)) { - changeSelectorOf( - lastToken, - CleanUp.selectors(lastToken.value.concat(token.value), false, adjacentSpace) - ); + } else if (lastToken[0] == 'selector' && stringifyBody(token[2]) == stringifyBody(lastToken[2]) && + !this.isSpecial(stringifySelector(token[1])) && !this.isSpecial(stringifySelector(lastToken[1]))) { + lastToken[1] = CleanUp.selectors(lastToken[1].concat(token[1]), false, adjacentSpace); forRemoval.push(i); } else { lastToken = token; @@ -101,8 +82,6 @@ AdvancedOptimizer.prototype.mergeAdjacent = function (tokens) { for (var j = 0, m = forRemoval.length; j < m; j++) { tokens.splice(forRemoval[j] - j, 1); } - - this.minificationsMade.unshift(forRemoval.length > 0); }; AdvancedOptimizer.prototype.reduceNonAdjacent = function (tokens) { @@ -112,13 +91,13 @@ AdvancedOptimizer.prototype.reduceNonAdjacent = function (tokens) { for (var i = tokens.length - 1; i >= 0; i--) { var token = tokens[i]; - if (token.kind != 'selector') + if (token[0] != 'selector') continue; - var isComplexAndNotSpecial = token.value.length > 1 && !this.isSpecial(token.metadata.selector); + var isComplexAndNotSpecial = token[1].length > 1 && !this.isSpecial(stringifySelector(token[1])); var selectors = isComplexAndNotSpecial ? - [token.metadata.selector].concat(token.metadata.selectorsList) : - [token.metadata.selector]; + [stringifySelector(token[1])].concat(token[1]) : + [stringifySelector(token[1])]; for (var j = 0, m = selectors.length; j < m; j++) { var selector = selectors[j]; @@ -130,31 +109,25 @@ AdvancedOptimizer.prototype.reduceNonAdjacent = function (tokens) { candidates[selector].push({ where: i, - list: token.metadata.selectorsList, + list: token[1], isPartial: isComplexAndNotSpecial && j > 0, isComplex: isComplexAndNotSpecial && j === 0 }); } } - var reducedInSimple = this.reduceSimpleNonAdjacentCases(tokens, repeated, candidates); - var reducedInComplex = this.reduceComplexNonAdjacentCases(tokens, candidates); - - this.minificationsMade.unshift(reducedInSimple || reducedInComplex); + this.reduceSimpleNonAdjacentCases(tokens, repeated, candidates); + this.reduceComplexNonAdjacentCases(tokens, candidates); }; AdvancedOptimizer.prototype.reduceSimpleNonAdjacentCases = function (tokens, repeated, candidates) { - var reduced = false; - function filterOut(idx, bodies) { return data[idx].isPartial && bodies.length === 0; } function reduceBody(token, newBody, processedCount, tokenIdx) { - if (!data[processedCount - tokenIdx - 1].isPartial) { - changeBodyOf(token, newBody); - reduced = true; - } + if (!data[processedCount - tokenIdx - 1].isPartial) + token[2] = newBody; } for (var i = 0, l = repeated.length; i < l; i++) { @@ -166,12 +139,9 @@ AdvancedOptimizer.prototype.reduceSimpleNonAdjacentCases = function (tokens, rep callback: reduceBody }); } - - return reduced; }; AdvancedOptimizer.prototype.reduceComplexNonAdjacentCases = function (tokens, candidates) { - var reduced = false; var localContext = {}; function filterOut(idx) { @@ -214,15 +184,12 @@ AdvancedOptimizer.prototype.reduceComplexNonAdjacentCases = function (tokens, ca callback: collectReducedBodies }); - if (reducedBodies[reducedBodies.length - 1].list.join(';') != reducedBodies[0].list.join(';')) + if (stringifyBody(reducedBodies[reducedBodies.length - 1]) != stringifyBody(reducedBodies[0])) continue allSelectors; } - intoToken.body = reducedBodies[0].tokenized; - reduced = true; + intoToken[2] = reducedBodies[0]; } - - return reduced; }; AdvancedOptimizer.prototype.reduceSelector = function (tokens, selector, data, options) { @@ -238,8 +205,8 @@ AdvancedOptimizer.prototype.reduceSelector = function (tokens, selector, data, o var where = data[j].where; var token = tokens[where]; - bodies = bodies.concat(token.body); - bodiesAsList.push(token.metadata.bodiesList); + bodies = bodies.concat(token[2]); + bodiesAsList.push(token[2]); processedTokens.push(where); } @@ -251,19 +218,16 @@ AdvancedOptimizer.prototype.reduceSelector = function (tokens, selector, data, o var optimizedBody = this.propertyOptimizer.process(selector, bodies, joinsAt, false); var processedCount = processedTokens.length; - var propertyIdx = optimizedBody.tokenized.length - 1; + var propertyIdx = optimizedBody.length - 1; var tokenIdx = processedCount - 1; while (tokenIdx >= 0) { - if ((tokenIdx === 0 || (optimizedBody.tokenized[propertyIdx] && bodiesAsList[tokenIdx].indexOf(optimizedBody.tokenized[propertyIdx].value) > -1)) && propertyIdx > -1) { + if ((tokenIdx === 0 || (optimizedBody[propertyIdx] && stringifyBody(bodiesAsList[tokenIdx]).indexOf(optimizedBody[propertyIdx]) > -1)) && propertyIdx > -1) { propertyIdx--; continue; } - var newBody = { - list: optimizedBody.list.splice(propertyIdx + 1), - tokenized: optimizedBody.tokenized.splice(propertyIdx + 1) - }; + var newBody = optimizedBody.splice(propertyIdx + 1); options.callback(tokens[processedTokens[tokenIdx]], newBody, processedCount, tokenIdx); tokenIdx--; @@ -276,12 +240,12 @@ AdvancedOptimizer.prototype.mergeNonAdjacentBySelector = function (tokens) { var i; for (i = tokens.length - 1; i >= 0; i--) { - if (tokens[i].kind != 'selector') + if (tokens[i][0] != 'selector') continue; - if (tokens[i].body.length === 0) + if (tokens[i][2].length === 0) continue; - var selector = tokens[i].metadata.selector; + var selector = stringifySelector(tokens[i][1]); allSelectors[selector] = [i].concat(allSelectors[selector] || []); if (allSelectors[selector].length == 2) @@ -323,11 +287,10 @@ AdvancedOptimizer.prototype.mergeNonAdjacentBySelector = function (tokens) { continue directionIterator; } - var joinAt = topToBottom ? [target.body.length] : [moved.body.length]; - var joinedBodies = topToBottom ? moved.body.concat(target.body) : target.body.concat(moved.body); - var newBody = this.propertyOptimizer.process(target.value, joinedBodies, joinAt, true); - changeBodyOf(target, newBody); - changeBodyOf(moved, { tokenized: [], list: [] }); + var joinAt = topToBottom ? [target[2].length] : [moved[2].length]; + var joinedBodies = topToBottom ? moved[2].concat(target[2]) : target[2].concat(moved[2]); + target[2] = this.propertyOptimizer.process(target[1], joinedBodies, joinAt, true); + moved[2] = []; } } } @@ -339,24 +302,21 @@ AdvancedOptimizer.prototype.mergeNonAdjacentByBody = function (tokens) { for (var i = tokens.length - 1; i >= 0; i--) { var token = tokens[i]; - if (token.kind != 'selector') + if (token[0] != 'selector') continue; - if (token.body.length > 0 && unsafeSelector(token.metadata.selector)) + if (token[2].length > 0 && unsafeSelector(stringifySelector(token[1]))) candidates = {}; - var oldToken = candidates[token.metadata.body]; - if (oldToken && !this.isSpecial(token.metadata.selector) && !this.isSpecial(oldToken.metadata.selector)) { - changeSelectorOf( - token, - CleanUp.selectors(oldToken.value.concat(token.value), false, adjacentSpace) - ); + var oldToken = candidates[stringifyBody(token[2])]; + if (oldToken && !this.isSpecial(stringifySelector(token[1])) && !this.isSpecial(stringifySelector(oldToken[1]))) { + token[1] = CleanUp.selectors(oldToken[1].concat(token[1]), false, adjacentSpace); - changeBodyOf(oldToken, { tokenized: [], list: [] }); - candidates[token.metadata.body] = null; + oldToken[2] = []; + candidates[stringifyBody(token[2])] = null; } - candidates[token.metadata.body] = token; + candidates[stringifyBody(token[2])] = token; } }; @@ -411,7 +371,7 @@ AdvancedOptimizer.prototype.restructure = function (tokens) { function cacheId(cachedTokens) { var id = []; for (var i = 0, l = cachedTokens.length; i < l; i++) { - id.push(cachedTokens[i].metadata.selector); + id.push(stringifySelector(cachedTokens[i][1])); } return id.join(ID_JOIN_CHARACTER); } @@ -421,11 +381,11 @@ AdvancedOptimizer.prototype.restructure = function (tokens) { var mergeableTokens = []; for (var i = sourceTokens.length - 1; i >= 0; i--) { - if (self.isSpecial(sourceTokens[i].metadata.selector)) + if (self.isSpecial(stringifySelector(sourceTokens[i][1]))) continue; mergeableTokens.unshift(sourceTokens[i]); - if (sourceTokens[i].body.length > 0 && uniqueTokensWithBody.indexOf(sourceTokens[i]) == -1) + if (sourceTokens[i][2].length > 0 && uniqueTokensWithBody.indexOf(sourceTokens[i]) == -1) uniqueTokensWithBody.push(sourceTokens[i]); } @@ -437,7 +397,7 @@ AdvancedOptimizer.prototype.restructure = function (tokens) { function shortenIfPossible(position, movedProperty) { var name = movedProperty[0]; var value = movedProperty[1]; - var key = movedProperty[3]; + var key = movedProperty[3][0]; var valueSize = name.length + value.length + 1; var allSelectors = []; var qualifiedTokens = []; @@ -452,7 +412,7 @@ AdvancedOptimizer.prototype.restructure = function (tokens) { return sendToMultiPropertyMoveCache(position, movedProperty, allFits); for (var i = bestFit[0].length - 1; i >=0; i--) { - allSelectors = bestFit[0][i].value.concat(allSelectors); + allSelectors = bestFit[0][i][1].concat(allSelectors); qualifiedTokens.unshift(bestFit[0][i]); } @@ -485,55 +445,41 @@ AdvancedOptimizer.prototype.restructure = function (tokens) { function sizeDifference(tokensVariant, propertySize, propertiesCount) { var allSelectorsSize = 0; for (var i = tokensVariant.length - 1; i >= 0; i--) { - allSelectorsSize += tokensVariant[i].body.length > propertiesCount ? tokensVariant[i].metadata.selector.length : -1; + allSelectorsSize += tokensVariant[i][2].length > propertiesCount ? stringifySelector(tokensVariant[i][1]).length : -1; } return allSelectorsSize - (tokensVariant.length - 1) * propertySize + 1; } function dropAsNewTokenAt(position, properties, allSelectors, mergeableTokens) { - var bodyMetadata = {}; var i, j, k, m; + var allProperties = []; for (i = mergeableTokens.length - 1; i >= 0; i--) { var mergeableToken = mergeableTokens[i]; - for (j = mergeableToken.body.length - 1; j >= 0; j--) { + for (j = mergeableToken[2].length - 1; j >= 0; j--) { for (k = 0, m = properties.length; k < m; k++) { var property = properties[k]; - if (mergeableToken.body[j].value === property[3]) { - bodyMetadata[property[3]] = mergeableToken.body[j].metadata; - - mergeableToken.body.splice(j, 1); - mergeableToken.metadata.bodiesList.splice(j, 1); - mergeableToken.metadata.body = mergeableToken.metadata.bodiesList.join(';'); + if (mergeableToken[2][j][0] === property[3][0]) { + mergeableToken[2].splice(j, 1); break; } } } } - var newToken = { kind: 'selector', metadata: {} }; - var allBodies = { tokenized: [], list: [] }; - for (i = properties.length - 1; i >= 0; i--) { - allBodies.tokenized.push({ value: properties[i][3] }); - allBodies.list.push(properties[i][3]); - } - - changeSelectorOf(newToken, allSelectors); - changeBodyOf(newToken, allBodies); - - for (i = properties.length - 1; i >= 0; i--) { - newToken.body[i].metadata = bodyMetadata[properties[i][3]]; + allProperties.push(properties[i][3]); } + var newToken = ['selector', allSelectors, allProperties]; tokens.splice(position, 0, newToken); } function dropPropertiesAt(position, movedProperty) { - var key = movedProperty[3]; + var key = movedProperty[3][0]; if (movableTokens[key] && movableTokens[key].length > 1) shortenIfPossible(position, movedProperty); @@ -546,7 +492,7 @@ AdvancedOptimizer.prototype.restructure = function (tokens) { for (var i = propertiesAndMergableTokens.length - 1; i >= 0; i--) { property = propertiesAndMergableTokens[i][0]; - var fullValue = property[3]; + var fullValue = property[3][0]; valueSize += fullValue.length + (i > 0 ? 1 : 0); properties.push(property); @@ -560,7 +506,7 @@ AdvancedOptimizer.prototype.restructure = function (tokens) { var allSelectors = []; var qualifiedTokens = []; for (i = bestFit[0].length - 1; i >= 0; i--) { - allSelectors = bestFit[0][i].value.concat(allSelectors); + allSelectors = bestFit[0][i][1].concat(allSelectors); qualifiedTokens.unshift(bestFit[0][i]); } @@ -585,9 +531,9 @@ AdvancedOptimizer.prototype.restructure = function (tokens) { var isSelector; var j, k, m; - if (token.kind == 'selector') { + if (token[0] == 'selector') { isSelector = true; - } else if (token.kind == 'block' && !token.isFlatBlock) { + } else if (token[0] == 'block') { isSelector = false; } else { continue; @@ -619,7 +565,7 @@ AdvancedOptimizer.prototype.restructure = function (tokens) { if (movedToBeDropped.indexOf(k) == -1 && !canReorderSingle(property, movedProperty)) { dropPropertiesAt(i + 1, movedProperty); movedToBeDropped.push(k); - delete movableTokens[movedProperty[3]]; + delete movableTokens[movedProperty[3][0]]; } if (!movedSameProperty) @@ -629,7 +575,7 @@ AdvancedOptimizer.prototype.restructure = function (tokens) { if (!isSelector || unmovableInCurrentToken.indexOf(j) > -1) continue; - var key = property[3]; + var key = property[3][0]; movableTokens[key] = movableTokens[key] || []; movableTokens[key].push(token); @@ -643,10 +589,10 @@ AdvancedOptimizer.prototype.restructure = function (tokens) { } } - var position = tokens[0] && tokens[0].kind == 'at-rule' && tokens[0].value.indexOf('@charset') === 0 ? 1 : 0; + var position = tokens[0] && tokens[0][0] == 'at-rule' && tokens[0][1][0].indexOf('@charset') === 0 ? 1 : 0; for (; position < tokens.length - 1; position++) { - var isImportRule = tokens[position].kind === 'at-rule' && tokens[position].value.indexOf('@import') === 0; - var isEscapedCommentSpecial = tokens[position].kind === 'text' && tokens[position].value.indexOf('__ESCAPED_COMMENT_SPECIAL') === 0; + var isImportRule = tokens[position][0] === 'at-rule' && tokens[position][1][0].indexOf('@import') === 0; + var isEscapedCommentSpecial = tokens[position][0] === 'text' && tokens[position][1][0].indexOf('__ESCAPED_COMMENT_SPECIAL') === 0; if (!(isImportRule || isEscapedCommentSpecial)) break; } @@ -662,13 +608,13 @@ AdvancedOptimizer.prototype.mergeMediaQueries = function (tokens) { for (var i = tokens.length - 1; i >= 0; i--) { var token = tokens[i]; - if (token.kind != 'block' || token.isFlatBlock === true) + if (token[0] != 'block') continue; - var candidate = candidates[token.value]; + var candidate = candidates[token[1][0]]; if (!candidate) { candidate = []; - candidates[token.value] = candidate; + candidates[token[1][0]] = candidate; } candidate.push(i); @@ -691,8 +637,8 @@ AdvancedOptimizer.prototype.mergeMediaQueries = function (tokens) { continue positionLoop; } - target.body = source.body.concat(target.body); - source.body = []; + target[2] = source[2].concat(target[2]); + source[2] = []; reduced.push(target); } @@ -701,17 +647,30 @@ AdvancedOptimizer.prototype.mergeMediaQueries = function (tokens) { return reduced; }; +AdvancedOptimizer.prototype.removeEmpty = function (tokens) { + for (var i = 0, l = tokens.length; i < l; i++) { + var token = tokens[i]; + + if (token[0] == 'selector' && (token[1].length === 0 || token[2].length === 0)) { + tokens.splice(i, 1); + i--; + l--; + } else if (token[1] == 'block') { + this.removeEmpty(token[2]); + } + } +}; + function optimizeProperties(tokens, propertyOptimizer) { for (var i = 0, l = tokens.length; i < l; i++) { var token = tokens[i]; - if (token.kind == 'selector') { - changeBodyOf( - token, - propertyOptimizer.process(token.value, token.body, false, true) - ); - } else if (token.kind == 'block') { - optimizeProperties(token.body, propertyOptimizer); + switch (token[0]) { + case 'selector': + token[2] = propertyOptimizer.process(token[1], token[2], false, true); + break; + case 'block': + optimizeProperties(token[2], propertyOptimizer); } } } @@ -721,9 +680,9 @@ AdvancedOptimizer.prototype.optimize = function (tokens) { function _optimize(tokens, withRestructuring) { tokens.forEach(function (token) { - if (token.kind == 'block') { - var isKeyframes = /@(-moz-|-o-|-webkit-)?keyframes/.test(token.value); - _optimize(token.body, !isKeyframes); + if (token[0] == 'block') { + var isKeyframes = /@(-moz-|-o-|-webkit-)?keyframes/.test(token[1][0]); + _optimize(token[2], !isKeyframes); } }); @@ -744,9 +703,11 @@ AdvancedOptimizer.prototype.optimize = function (tokens) { if (self.options.mediaMerging) { var reduced = self.mergeMediaQueries(tokens); for (var i = reduced.length - 1; i >= 0; i--) { - _optimize(reduced[i].body); + _optimize(reduced[i][2]); } } + + self.removeEmpty(tokens); } _optimize(tokens, true); diff --git a/lib/selectors/optimizers/clean-up.js b/lib/selectors/optimizers/clean-up.js index 878f4b97..ac36a2d4 100644 --- a/lib/selectors/optimizers/clean-up.js +++ b/lib/selectors/optimizers/clean-up.js @@ -3,17 +3,17 @@ function removeWhitespace(match, value) { } function selectorSorter(s1, s2) { - return s1.value > s2.value ? 1 : -1; + return s1[0] > s2[0] ? 1 : -1; } var CleanUp = { selectors: function (selectors, removeUnsupported, adjacentSpace) { - var plain = []; - var tokenized = []; + var list = []; + var repeated = []; for (var i = 0, l = selectors.length; i < l; i++) { var selector = selectors[i]; - var reduced = selector.value + var reduced = selector[0] .replace(/\s+/g, ' ') .replace(/ ?, ?/g, ',') .replace(/\s*([>\+\~])\s*/g, '$1') @@ -34,47 +34,41 @@ var CleanUp = { if (reduced.indexOf('[') > -1) reduced = reduced.replace(/\[([^\]]+)\]/g, removeWhitespace); - if (plain.indexOf(reduced) == -1) { - plain.push(reduced); - selector.value = reduced; - tokenized.push(selector); + if (repeated.indexOf(reduced) == -1) { + selector[0] = reduced; + repeated.push(reduced); + list.push(selector); } } - return { - list: plain.sort(), - tokenized: tokenized.sort(selectorSorter) - }; + return list.sort(selectorSorter); }, selectorDuplicates: function (selectors) { - var plain = []; - var tokenized = []; + var list = []; + var repeated = []; for (var i = 0, l = selectors.length; i < l; i++) { var selector = selectors[i]; - if (plain.indexOf(selector.value) == -1) { - plain.push(selector.value); - tokenized.push(selector); + if (repeated.indexOf(selector[0]) == -1) { + repeated.push(selector[0]); + list.push(selector); } } - return { - list: plain.sort(), - tokenized: tokenized.sort(selectorSorter) - }; + return list.sort(selectorSorter); }, - block: function (block) { - return block + block: function (values) { + values[0] = values[0] .replace(/\s+/g, ' ') .replace(/(,|:|\() /g, '$1') .replace(/ ?\) ?/g, ')'); }, - atRule: function (block) { - return block + atRule: function (values) { + values[0] = values[0] .replace(/\s+/g, ' ') .trim(); } diff --git a/lib/selectors/optimizers/simple.js b/lib/selectors/optimizers/simple.js index 028dbc60..45bf7609 100644 --- a/lib/selectors/optimizers/simple.js +++ b/lib/selectors/optimizers/simple.js @@ -25,8 +25,6 @@ function SimpleOptimizer(options) { options.roundingPrecision; options.precision.multiplier = Math.pow(10, options.precision.value); options.precision.regexp = new RegExp('(\\d*\\.\\d{' + (options.precision.value + 1) + ',})px', 'g'); - - options.updateMetadata = this.options.advanced; } var valueMinifiers = { @@ -127,17 +125,17 @@ function unitMinifier(_, value, unitsRegexp) { return value.replace(unitsRegexp, '$1' + '0'); } -function multipleZerosMinifier(property, value) { +function multipleZerosMinifier(name, value) { if (value.indexOf('0 0 0 0') == -1) return value; - if (property.indexOf('box-shadow') > -1) + if (name.indexOf('box-shadow') > -1) return value == '0 0 0 0' ? '0 0' : value; return value.replace(/^0 0 0 0$/, '0'); } -function colorMininifier(property, value, compatibility) { +function colorMininifier(_, value, compatibility) { if (value.indexOf('#') === -1 && value.indexOf('rgb') == -1 && value.indexOf('hsl') == -1) return HexNameShortener.shorten(value); @@ -179,126 +177,127 @@ function colorMininifier(property, value, compatibility) { return HexNameShortener.shorten(value); } -function spaceMinifier(property, value) { - if (property == 'filter' || value.indexOf(') ') == -1 || processable.implementedFor.test(property)) +function spaceMinifier(name, value) { + if (name == 'filter' || value.indexOf(') ') == -1 || processable.implementedFor.test(name)) return value; return value.replace(/\) ((?![\+\-] )|$)/g, ')$1'); } -function reduce(body, options) { - var reduced = []; - var properties = []; - var newProperty; +function optimizeBody(properties, options) { + var property, firstColon, name, value, important; - for (var i = 0, l = body.length; i < l; i++) { - var token = body[i]; + for (var i = 0, l = properties.length; i < l; i++) { + property = properties[i]; // FIXME: the check should be gone with #407 - if (token.value.indexOf('__ESCAPED_') === 0) { - reduced.push(token); - properties.push(token.value); + if (property[0].indexOf('__ESCAPED_') === 0) continue; - } - var firstColon = token.value.indexOf(':'); - var property = token.value.substring(0, firstColon); - var value = token.value.substring(firstColon + 1); - var important = false; + firstColon = property[0].indexOf(':'); + name = property[0].substring(0, firstColon); + value = property[0].substring(firstColon + 1); + important = false; - if (!options.compatibility.properties.iePrefixHack && (property[0] == '_' || property[0] == '*')) + if ((!options.compatibility.properties.iePrefixHack && (name[0] == '_' || name[0] == '*')) || + (name.indexOf('padding') === 0 && isNegative(value))) { + properties.splice(i, 1); + i--; + l--; continue; + } if (value.indexOf('!important') > 0 || value.indexOf('! important') > 0) { value = value.substring(0, value.indexOf('!')).trim(); important = true; } - if (property.indexOf('padding') === 0 && isNegative(value)) - continue; - - if (property.indexOf('border') === 0 && property.indexOf('radius') > 0) + if (name.indexOf('border') === 0 && name.indexOf('radius') > 0) value = valueMinifiers['border-*-radius'](value); - if (valueMinifiers[property]) - value = valueMinifiers[property](value); + if (valueMinifiers[name]) + value = valueMinifiers[name](value); - value = precisionMinifier(property, value, options.precision); - value = zeroMinifier(property, value); + value = precisionMinifier(name, value, options.precision); + value = zeroMinifier(name, value); if (options.compatibility.properties.zeroUnits) { - value = zeroDegMinifier(property, value); - value = unitMinifier(property, value, options.unitsRegexp); + value = zeroDegMinifier(name, value); + value = unitMinifier(name, value, options.unitsRegexp); } - value = multipleZerosMinifier(property, value); - value = colorMininifier(property, value, options.compatibility); + value = multipleZerosMinifier(name, value); + value = colorMininifier(name, value, options.compatibility); if (!options.compatibility.properties.spaceAfterClosingBrace) - value = spaceMinifier(property, value); + value = spaceMinifier(name, value); - newProperty = property + ':' + value + (important ? '!important' : ''); - reduced.push({ value: newProperty, metadata: token.metadata }); - properties.push(newProperty); + property[0] = name + ':' + value + (important ? '!important' : ''); } - - return { - tokenized: reduced, - list: properties - }; } SimpleOptimizer.prototype.optimize = function(tokens) { var self = this; var hasCharset = false; var options = this.options; + var ie7Hack = options.compatibility.selectors.ie7Hack; + var adjacentSpace = options.compatibility.selectors.adjacentSpace; + var token; - function _optimize(tokens) { + function _cleanupCharsets(tokens) { for (var i = 0, l = tokens.length; i < l; i++) { - var token = tokens[i]; - // FIXME: why it's so? - if (!token) - break; + token = tokens[i]; - if (token.kind == 'selector') { - var newSelectors = CleanUp.selectors(token.value, !options.compatibility.selectors.ie7Hack, options.compatibility.selectors.adjacentSpace); - token.value = newSelectors.tokenized; + if (token[0] != 'at-rule') + continue; - if (token.value.length === 0) { + if (CHARSET_REGEXP.test(token[1][0])) { + if (hasCharset || token[1][0].indexOf(CHARSET_TOKEN) == -1) { tokens.splice(i, 1); i--; - continue; - } - var newBody = reduce(token.body, self.options); - token.body = newBody.tokenized; - - if (options.updateMetadata) { - token.metadata.body = newBody.list.join(';'); - token.metadata.bodiesList = newBody.list; - token.metadata.selector = newSelectors.list.join(','); - token.metadata.selectorsList = newSelectors.list; - } - } else if (token.kind == 'block') { - token.value = CleanUp.block(token.value); - if (token.isFlatBlock) - token.body = reduce(token.body, self.options).tokenized; - else - _optimize(token.body); - } else if (token.kind == 'at-rule') { - token.value = CleanUp.atRule(token.value); - - if (CHARSET_REGEXP.test(token.value)) { - if (hasCharset || token.value.indexOf(CHARSET_TOKEN) == -1) { - tokens.splice(i, 1); - i--; - } else { - hasCharset = true; - tokens.splice(i, 1); - tokens.unshift({ kind: 'at-rule', value: token.value.replace(CHARSET_REGEXP, CHARSET_TOKEN) }); - } + l--; + } else { + hasCharset = true; + tokens.splice(i, 1); + tokens.unshift(['at-rule', [token[1][0].replace(CHARSET_REGEXP, CHARSET_TOKEN)]]); } } } } + function _optimize(tokens) { + var mayHaveCharset = false; + + for (var i = 0, l = tokens.length; i < l; i++) { + token = tokens[i]; + + switch (token[0]) { + case 'selector': + token[1] = CleanUp.selectors(token[1], !ie7Hack, adjacentSpace); + optimizeBody(token[2], self.options); + break; + case 'block': + CleanUp.block(token[1]); + _optimize(token[2]); + break; + case 'flat-block': + CleanUp.block(token[1]); + optimizeBody(token[2], self.options); + break; + case 'at-rule': + CleanUp.atRule(token[1]); + mayHaveCharset = true; + } + + if (token[1].length === 0 || (token[2] && token[2].length === 0)) { + tokens.splice(i, 1); + i--; + l--; + } + } + + if (mayHaveCharset) + _cleanupCharsets(tokens); + } + _optimize(tokens); }; diff --git a/lib/selectors/source-map-stringifier.js b/lib/selectors/source-map-stringifier.js index d09b2b07..f744fda3 100644 --- a/lib/selectors/source-map-stringifier.js +++ b/lib/selectors/source-map-stringifier.js @@ -1,7 +1,7 @@ var SourceMapGenerator = require('source-map').SourceMapGenerator; -var SourceMap = require('../utils/source-maps'); var lineBreak = require('os').EOL; +var unknownSource = '$stdin'; function Rebuilder(options, restoreCallback, inputMapTracker) { this.column = 0; @@ -14,32 +14,34 @@ function Rebuilder(options, restoreCallback, inputMapTracker) { this.outputMap = new SourceMapGenerator(); } -Rebuilder.prototype.rebuildValue = function (list, separator) { +Rebuilder.prototype.rebuildValue = function (elements, separator) { var escaped = 0; - for (var i = 0, l = list.length; i < l; i++) { - var el = list[i]; + for (var i = 0, l = elements.length; i < l; i++) { + var element = elements[i]; - if (el.value.indexOf('__ESCAPED_') === 0) { - this.store(el); + if (element[0].indexOf('__ESCAPED_') === 0) { + this.store(element[0]); escaped++; if (i === l - 1 && escaped > 0) this.output.splice(this.output.length - escaped - 1, 1); } else { - this.store(el); + this.store(element); this.store(i < l - 1 ? separator : ''); escaped = 0; } } }; -Rebuilder.prototype.store = function (token) { - var value = typeof token == 'string' ? - token : - token.value.indexOf('_') > -1 ? this.restore(token.value) : token.value; +Rebuilder.prototype.store = function (element) { + var fromString = typeof element == 'string'; + var value = fromString ? element : element[0]; - this.track(value, token.metadata); + if (value.indexOf('_') > -1) + value = this.restore(value); + + this.track(value, fromString ? null : element); this.output.push(value); }; @@ -49,47 +51,46 @@ Rebuilder.prototype.rebuildList = function (tokens, isFlatBlock) { for (var i = 0, l = tokens.length; i < l; i++) { var token = tokens[i]; - if (token.kind === 'text' || token.kind == 'at-rule') { - this.store(token); - continue; - } - - // FIXME: broken due to joining/splitting - if (token.body && (token.body.length === 0 || (token.body.length == 1 && token.body[0].value === ''))) - continue; - - if (token.kind == 'block') { - if (token.body.length > 0) { - this.rebuildValue([{ value: token.value, metadata: token.metadata }], ''); + switch (token[0]) { + case 'at-rule': + case 'text': + this.store(token[1][0]); + break; + case 'block': + this.rebuildValue([token[1]], ''); this.store('{'); - if (token.isFlatBlock) - this.rebuildValue(token.body, ';'); - else - this.rebuildList(token.body, false); + this.rebuildList(token[2], false); this.store('}'); - } - } else { - this.rebuildValue(token.value, ','); - this.store('{'); - this.rebuildValue(token.body, ';'); - this.store('}'); + this.store(joinCharacter); + break; + case 'flat-block': + this.rebuildValue([token[1]], ''); + this.store('{'); + this.rebuildValue(token[2], ';'); + this.store('}'); + this.store(joinCharacter); + break; + default: + this.rebuildValue(token[1], ','); + this.store('{'); + this.rebuildValue(token[2], ';'); + this.store('}'); + this.store(joinCharacter); } - - this.store(joinCharacter); } }; -Rebuilder.prototype.track = function (value, metadata) { - if (metadata) - this.trackMetadata(metadata); +Rebuilder.prototype.track = function (value, element) { + if (element) + this.trackMetadata(element); var parts = value.split('\n'); this.line += parts.length - 1; this.column = parts.length > 1 ? 0 : (this.column + parts.pop().length); }; -Rebuilder.prototype.trackMetadata = function (metadata) { - var source = metadata.source || SourceMap.unknownSource; +Rebuilder.prototype.trackMetadata = function (element) { + var source = element[3] || unknownSource; this.outputMap.addMapping({ generated: { @@ -97,11 +98,14 @@ Rebuilder.prototype.trackMetadata = function (metadata) { column: this.column }, source: source, - original: metadata.original + original: { + line: element[1], + column: element[2] + } }); - if (metadata.sourcesContent) - this.outputMap.setSourceContent(source, metadata.sourcesContent[metadata.source]); + if (element[4]) + this.outputMap.setSourceContent(source, element[4][element[3]]); }; function SourceMapStringifier(options, restoreCallback, inputMapTracker) { diff --git a/lib/selectors/stringifier.js b/lib/selectors/stringifier.js index 1d9bb461..430fdafa 100644 --- a/lib/selectors/stringifier.js +++ b/lib/selectors/stringifier.js @@ -5,21 +5,22 @@ function Stringifier(options, restoreCallback) { this.restoreCallback = restoreCallback; } -function valueRebuilder(list, separator) { +function valueRebuilder(elements, separator) { var merged = ''; + var element; - for (var i = 0, l = list.length; i < l; i++) { - var el = list[i]; + for (var i = 0, l = elements.length; i < l; i++) { + element = elements[i]; - if (el.value.indexOf('__ESCAPED_') === 0) { - merged += el.value; + if (element[0].indexOf('__ESCAPED_') === 0) { + merged += element[0]; if (i === l - 1) { var lastSemicolonAt = merged.lastIndexOf(';'); merged = merged.substring(0, lastSemicolonAt) + merged.substring(lastSemicolonAt + 1); } } else { - merged += list[i].value + (i < l - 1 ? separator : ''); + merged += element[0] + (i < l - 1 ? separator : ''); } } @@ -35,25 +36,25 @@ function rebuild(tokens, keepBreaks, isFlatBlock) { for (var i = 0, l = tokens.length; i < l; i++) { var token = tokens[i]; - if (token.kind === 'text' || token.kind == 'at-rule') { - parts.push(token.value); - continue; - } - - // FIXME: broken due to joining/splitting - if (token.body && (token.body.length === 0 || (token.body.length == 1 && token.body[0].value === ''))) - continue; - - if (token.kind == 'block') { - body = token.isFlatBlock ? - valueRebuilder(token.body, ';') : - rebuild(token.body, keepBreaks, token.isFlatBlock); - if (body.length > 0) - parts.push(token.value + '{' + body + '}'); - } else { - selector = valueRebuilder(token.value, ','); - body = valueRebuilder(token.body, ';'); - parts.push(selector + '{' + body + '}'); + switch (token[0]) { + case 'at-rule': + case 'text': + parts.push(token[1][0]); + break; + case 'block': + body = rebuild(token[2], keepBreaks, false); + if (body.length > 0) + parts.push(token[1][0] + '{' + body + '}'); + break; + case 'flat-block': + body = valueRebuilder(token[2], ';'); + if (body.length > 0) + parts.push(token[1][0] + '{' + body + '}'); + break; + default: + selector = valueRebuilder(token[1], ','); + body = valueRebuilder(token[2], ';'); + parts.push(selector + '{' + body + '}'); } } diff --git a/lib/selectors/tokenizer.js b/lib/selectors/tokenizer.js index 6fbf4e64..5fa2c5a0 100644 --- a/lib/selectors/tokenizer.js +++ b/lib/selectors/tokenizer.js @@ -1,15 +1,14 @@ var Chunker = require('../utils/chunker'); var Extract = require('../utils/extractors'); -var SourceMaps = require('../utils/source-maps'); +var track = require('../utils/source-maps'); var path = require('path'); var flatBlock = /(^@(font\-face|page|\-ms\-viewport|\-o\-viewport|viewport|counter\-style)|\\@.+?)/; -function Tokenizer(minifyContext, addMetadata, addSourceMap) { +function Tokenizer(minifyContext, sourceMaps) { this.minifyContext = minifyContext; - this.addMetadata = addMetadata; - this.addSourceMap = addSourceMap; + this.sourceMaps = sourceMaps; } Tokenizer.prototype.toTokens = function (data) { @@ -25,8 +24,10 @@ Tokenizer.prototype.toTokens = function (data) { chunker: chunker, chunk: chunker.next(), outer: this.minifyContext, - addMetadata: this.addMetadata, - addSourceMap: this.addSourceMap, + track: this.sourceMaps ? + function (data, snapshotMetadata, fallbacks) { return track(data, context, snapshotMetadata, fallbacks); } : + function () { return []; }, + sourceMaps: this.sourceMaps, state: [], line: 1, column: 0, @@ -102,7 +103,6 @@ function tokenize(context) { var tokenized = []; var newToken; var value; - var addSourceMap = context.addSourceMap; while (true) { var next = whatsNext(context); @@ -112,7 +112,7 @@ function tokenize(context) { if (context.mode == 'body') { context.outer.warnings.push('Missing \'}\' after \'' + whatsLeft + '\'. Ignoring.'); } else { - tokenized.push({ kind: 'text', value: whatsLeft }); + tokenized.push(['text', [whatsLeft]]); } context.cursor += whatsLeft.length; } @@ -132,9 +132,7 @@ function tokenize(context) { if (leadingWhitespace) { context.cursor += leadingWhitespace[0].length; - - if (addSourceMap) - SourceMaps.track(leadingWhitespace[0], context); + context.track(leadingWhitespace[0]); } } @@ -148,14 +146,14 @@ function tokenize(context) { context.cursor = chunk.length; } else if (isSingle) { nextEnd = chunk.indexOf(';', nextSpecial + 1); - value = chunk.substring(context.cursor, nextEnd + 1); - newToken = { kind: 'at-rule', value: value }; - tokenized.push(newToken); - if (addSourceMap) - newToken.metadata = SourceMaps.saveAndTrack(value, context, true); + tokenized.push([ + 'at-rule', + [value].concat(context.track(value, true)) + ]); + context.track(';'); context.cursor = nextEnd + 1; } else { nextEnd = chunk.indexOf('{', nextSpecial + 1); @@ -167,19 +165,19 @@ function tokenize(context) { context.cursor = nextEnd + 1; context.mode = isFlat ? 'body' : 'block'; - newToken = { kind: 'block', value: trimmedValue, isFlatBlock: isFlat }; + newToken = [ + isFlat ? 'flat-block' : 'block' + ]; - if (addSourceMap) - newToken.metadata = SourceMaps.saveAndTrack(value, context, true); + newToken.push([trimmedValue].concat(context.track(value, true))); + context.track('{'); + newToken.push(tokenize(context)); - newToken.body = tokenize(context); - if (typeof newToken.body == 'string') - newToken.body = Extract.properties(newToken.body, context).tokenized; + if (typeof newToken[2] == 'string') + newToken[2] = Extract.properties(newToken[2], context); context.mode = oldMode; - - if (addSourceMap) - SourceMaps.suffix(context); + context.track('}'); tokenized.push(newToken); } @@ -190,9 +188,7 @@ function tokenize(context) { var isEndSourceMarker = !!context.outer.sourceTracker.nextEnd(escaped); if (isStartSourceMarker) { - if (addSourceMap) - SourceMaps.track(escaped, context); - + context.track(escaped); context.state.push({ source: context.source, line: context.line, @@ -206,46 +202,32 @@ function tokenize(context) { context.source = oldState.source; context.line = oldState.line; context.column = oldState.column; - - if (addSourceMap) - SourceMaps.track(escaped, context); + context.track(escaped); } else { if (escaped.indexOf('__ESCAPED_COMMENT_SPECIAL') === 0) - tokenized.push({ kind: 'text', value: escaped }); + tokenized.push(['text', [escaped]]); - if (addSourceMap) - SourceMaps.track(escaped, context); + context.track(escaped); } context.cursor = nextEnd + 2; } else if (what == 'bodyStart') { - var selectorData = Extract.selectors(chunk.substring(context.cursor, nextSpecial), context); + var selectors = Extract.selectors(chunk.substring(context.cursor, nextSpecial), context); oldMode = context.mode; context.cursor = nextSpecial + 1; context.mode = 'body'; - var bodyData = Extract.properties(tokenize(context), context); - - if (addSourceMap) - SourceMaps.suffix(context); + var body = Extract.properties(tokenize(context), context); + context.track('{'); context.mode = oldMode; - newToken = { - kind: 'selector', - value: selectorData.tokenized, - body: bodyData.tokenized - }; - if (context.addMetadata) { - newToken.metadata = { - body: bodyData.list.join(','), - bodiesList: bodyData.list, - selector: selectorData.list.join(','), - selectorsList: selectorData.list - }; - } - tokenized.push(newToken); + tokenized.push([ + 'selector', + selectors, + body + ]); } else if (what == 'bodyEnd') { // extra closing brace at the top level can be safely ignored if (context.mode == 'top') { @@ -259,8 +241,8 @@ function tokenize(context) { continue; } - if (context.mode == 'block' && context.addSourceMap) - SourceMaps.track(chunk.substring(context.cursor, nextSpecial), context); + if (context.mode == 'block') + context.track(chunk.substring(context.cursor, nextSpecial)); if (context.mode != 'block') tokenized = chunk.substring(context.cursor, nextSpecial); diff --git a/lib/utils/extractors.js b/lib/utils/extractors.js index 824f179a..8f9867b6 100644 --- a/lib/utils/extractors.js +++ b/lib/utils/extractors.js @@ -1,9 +1,7 @@ var Splitter = require('./splitter'); -var SourceMaps = require('../utils/source-maps'); var Extractors = { properties: function (string, context) { - var tokenized = []; var list = []; var buffer = []; var all = []; @@ -18,11 +16,10 @@ var Extractors = { var secondToLast; var wasCloseParenthesis; var isEscape; - var token; - var addSourceMap = context.addSourceMap; + var metadata; if (string.replace && string.indexOf(')') > 0) - string = string.replace(/\)([^\s_;:,\)])/g, context.addSourceMap ? ') __ESCAPED_COMMENT_CLEAN_CSS(0,-1)__$1' : ') $1'); + string = string.replace(/\)([^\s_;:,\)])/g, context.sourceMaps ? ') __ESCAPED_COMMENT_CLEAN_CSS(0,-1)__$1' : ') $1'); for (var i = 0, l = string.length; i < l; i++) { current = string[i]; @@ -39,8 +36,7 @@ var Extractors = { i = endOfEscape - 1; if (comment.indexOf('__ESCAPED_COMMENT_SPECIAL') === -1) { - if (addSourceMap) - SourceMaps.track(comment, context, true); + context.track(comment); continue; } else { @@ -55,12 +51,11 @@ var Extractors = { if (buffer.length > 0) { property = buffer.join(''); if (property.indexOf('{') === -1) { - token = { value: property }; - tokenized.push(token); - list.push(property); + metadata = context.track(all.join(''), true); + list.push([property].concat(metadata)); - if (addSourceMap) - token.metadata = SourceMaps.saveAndTrack(all.join(''), context, !isEscape); + if (!isEscape) + context.track(';'); } } buffer = []; @@ -97,44 +92,28 @@ var Extractors = { if (buffer.length > 0) { property = buffer.join(''); if (property.indexOf('{') === -1) { - token = { value: property }; - tokenized.push(token); - list.push(property); - - if (addSourceMap) - token.metadata = SourceMaps.saveAndTrack(all.join(''), context, false); + metadata = context.track(all.join(''), true); + list.push([property].concat(metadata)); } } else if (all.indexOf('\n') > -1) { - SourceMaps.track(all.join(''), context); + context.track(all.join('')); } - return { - list: list, - tokenized: tokenized - }; + return list; }, selectors: function (string, context) { - var tokenized = []; var list = []; + var metadata; var selectors = new Splitter(',').split(string); - var addSourceMap = context.addSourceMap; - for (var i = 0, l = selectors.length; i < l; i++) { - var selector = selectors[i]; - - list.push(selector); - var token = { value: selector }; - tokenized.push(token); - - if (addSourceMap) - token.metadata = SourceMaps.saveAndTrack(selector, context, true, i); + for (var i = 0, l = selectors.length; i < l; i++) { + metadata = context.track(selectors[i], true, i); + context.track(','); + list.push([selectors[i]].concat(metadata)); } - return { - list: list, - tokenized: tokenized - }; + return list; } }; diff --git a/lib/utils/input-source-map-tracker.js b/lib/utils/input-source-map-tracker.js index 3644399c..1ffc70bb 100644 --- a/lib/utils/input-source-map-tracker.js +++ b/lib/utils/input-source-map-tracker.js @@ -127,12 +127,12 @@ function fetch(self, path, onSuccess, onFailure) { .setTimeout(self.timeout); } -function originalPositionIn(trackedSource, sourceInfo, token, allowNFallbacks) { +function originalPositionIn(trackedSource, line, column, token, allowNFallbacks) { var originalPosition; var maxRange = token.length; var position = { - line: sourceInfo.line, - column: sourceInfo.column + maxRange + line: line, + column: column + maxRange }; while (maxRange-- > 0) { @@ -143,8 +143,8 @@ function originalPositionIn(trackedSource, sourceInfo, token, allowNFallbacks) { break; } - if (originalPosition.line === null && sourceInfo.line > 1 && allowNFallbacks > 0) - return originalPositionIn(trackedSource, { line: sourceInfo.line - 1, column: sourceInfo.column }, token, allowNFallbacks - 1); + if (originalPosition.line === null && line > 1 && allowNFallbacks > 0) + return originalPositionIn(trackedSource, line - 1, column, token, allowNFallbacks - 1); if (trackedSource.path) { originalPosition.source = REMOTE_RESOURCE.test(trackedSource.path) ? @@ -237,7 +237,7 @@ InputSourceMapStore.prototype.isTracking = function (source) { }; InputSourceMapStore.prototype.originalPositionFor = function (sourceInfo, token, allowNFallbacks) { - return originalPositionIn(this.maps[sourceInfo.source], sourceInfo.original, token, allowNFallbacks); + return originalPositionIn(this.maps[sourceInfo.source], sourceInfo.line, sourceInfo.column, token, allowNFallbacks); }; InputSourceMapStore.prototype.sourcesContentFor = function (contextSource) { diff --git a/lib/utils/source-maps.js b/lib/utils/source-maps.js index bb2af88c..f9a7d319 100644 --- a/lib/utils/source-maps.js +++ b/lib/utils/source-maps.js @@ -1,14 +1,30 @@ -function trimLeft(value, context) { - var withoutContent; - var total; +var escapePrefix = '__ESCAPED_'; + +function trackPrefix(value, context, interestingContent) { + if (!interestingContent && value.indexOf('\n') == -1) { + if (value.indexOf(escapePrefix) === 0) { + return value; + } else { + context.column += value.length; + return; + } + } + + var withoutContent = 0; var split = value.split('\n'); + var total = split.length; var shift = 0; - for (withoutContent = 0, total = split.length; withoutContent < total; withoutContent++) { + + while (true) { + if (withoutContent == total - 1) + break; + var part = split[withoutContent]; if (/\S/.test(part)) break; shift += part.length + 1; + withoutContent++; } context.line += withoutContent; @@ -27,82 +43,77 @@ function sourceFor(originalMetadata, contextMetadata, context) { return source; } -var SourceMaps = { - unknownSource: '$stdin', - - saveAndTrack: function (data, context, hasSuffix, allowNFallbacks) { - var trimmedValue = trimLeft(data, context); - - var contextMetadata = { - original: { - line: context.line, - column: context.column - }, - source: context.source - }; - var sourceMetadata = context.outer.inputSourceMapTracker.isTracking(contextMetadata.source) ? - context.outer.inputSourceMapTracker.originalPositionFor(contextMetadata, trimmedValue, allowNFallbacks || 0) : - {}; - - contextMetadata.original.line = sourceMetadata.line || contextMetadata.original.line; - contextMetadata.original.column = sourceMetadata.column || contextMetadata.original.column; - contextMetadata.source = sourceMetadata.sourceResolved ? - sourceMetadata.source : - sourceFor(sourceMetadata, contextMetadata, context); - - if (context.outer.options.sourceMapInlineSources) { - var sourceMapSourcesContent = context.outer.inputSourceMapTracker.sourcesContentFor(context.source); - var source = sourceMapSourcesContent && sourceMapSourcesContent[contextMetadata.source] ? - sourceMapSourcesContent : - context.outer.sourceReader.sourceAt(context.source); - - if (source) - contextMetadata.sourcesContent = source; - } - - this.track(trimmedValue, context); +function snapshot(data, context, fallbacks) { + var metadata = { + line: context.line, + column: context.column, + source: context.source + }; + var sourceContent = null; + var sourceMetadata = context.outer.inputSourceMapTracker.isTracking(metadata.source) ? + context.outer.inputSourceMapTracker.originalPositionFor(metadata, data, fallbacks || 0) : + {}; + + metadata.line = sourceMetadata.line || metadata.line; + metadata.column = sourceMetadata.column || metadata.column; + metadata.source = sourceMetadata.sourceResolved ? + sourceMetadata.source : + sourceFor(sourceMetadata, metadata, context); + + if (context.outer.options.sourceMapInlineSources) { + var sourceMapSourcesContent = context.outer.inputSourceMapTracker.sourcesContentFor(context.source); + sourceContent = sourceMapSourcesContent && sourceMapSourcesContent[metadata.source] ? + sourceMapSourcesContent : + context.outer.sourceReader.sourceAt(context.source); + } - if (hasSuffix) - context.column++; + return sourceContent ? + [metadata.line, metadata.column, metadata.source, sourceContent] : + [metadata.line, metadata.column, metadata.source]; +} - return contextMetadata; - }, +function trackSuffix(data, context) { + var parts = data.split('\n'); - suffix: function (context) { - context.column++; - }, + for (var i = 0, l = parts.length; i < l; i++) { + var part = parts[i]; + var cursor = 0; - track: function (data, context) { - var parts = data.split('\n'); + if (i > 0) { + context.line++; + context.column = 0; + } - for (var i = 0, l = parts.length; i < l; i++) { - var part = parts[i]; - var cursor = 0; + while (true) { + var next = part.indexOf(escapePrefix, cursor); - if (i > 0) { - context.line++; - context.column = 0; + if (next == -1) { + context.column += part.substring(cursor).length; + break; } - while (true) { - var next = part.indexOf('__ESCAPED_', cursor); - - if (next == -1) { - context.column += part.substring(cursor).length; - break; - } - - context.column += next - cursor; - cursor += next - cursor; + context.column += next - cursor; + cursor += next - cursor; - var escaped = part.substring(next, part.indexOf('__', next + 1) + 2); - var encodedValues = escaped.substring(escaped.indexOf('(') + 1, escaped.indexOf(')')).split(','); - context.line += ~~encodedValues[0]; - context.column = (~~encodedValues[0] === 0 ? context.column : 0) + ~~encodedValues[1]; - cursor += escaped.length; - } + var escaped = part.substring(next, part.indexOf('__', next + 1) + 2); + var encodedValues = escaped.substring(escaped.indexOf('(') + 1, escaped.indexOf(')')).split(','); + context.line += ~~encodedValues[0]; + context.column = (~~encodedValues[0] === 0 ? context.column : 0) + ~~encodedValues[1]; + cursor += escaped.length; } } -}; +} + +function track(data, context, snapshotMetadata, fallbacks) { + var untracked = trackPrefix(data, context, snapshotMetadata); + var metadata = snapshotMetadata ? + snapshot(untracked, context, fallbacks) : + []; + + if (untracked) + trackSuffix(untracked, context); + + return metadata; +} -module.exports = SourceMaps; +module.exports = track; diff --git a/lib/utils/stringify-tokens.js b/lib/utils/stringify-tokens.js new file mode 100644 index 00000000..0caf9f10 --- /dev/null +++ b/lib/utils/stringify-tokens.js @@ -0,0 +1,24 @@ +function stringify(values, separator) { + var i = 0; + var result = []; + + while (values[i]) { + result.push(values[i][0]); + i++; + } + + return result.join(separator); +} + +function stringifyBody(properties) { + return stringify(properties, ';'); +} + +function stringifySelector(list) { + return stringify(list, ','); +} + +module.exports = { + body: stringifyBody, + selector: stringifySelector +}; diff --git a/test/properties/extractor-test.js b/test/properties/extractor-test.js index cd2db25d..56f21918 100644 --- a/test/properties/extractor-test.js +++ b/test/properties/extractor-test.js @@ -4,7 +4,7 @@ var SelectorTokenizer = require('../../lib/selectors/tokenizer'); var extractor = require('../../lib/properties/extractor'); function buildToken(source) { - return new SelectorTokenizer({ options: {} }, true, false).toTokens(source)[0]; + return new SelectorTokenizer({ options: {} }, false).toTokens(source)[0]; } vows.describe(extractor) @@ -24,21 +24,21 @@ vows.describe(extractor) 'one property': { 'topic': extractor(buildToken('a{color:red}')), 'has no properties': function (tokens) { - assert.deepEqual(tokens, [['color', 'red', 'color', 'color:red', ['a'], true]]); + assert.deepEqual(tokens, [['color', 'red', 'color', ['color:red'], [['a']], true]]); } }, 'one property - complex selector': { 'topic': extractor(buildToken('.one{color:red}')), 'has no properties': function (tokens) { - assert.deepEqual(tokens, [['color', 'red', 'color', 'color:red', ['.one'], false]]); + assert.deepEqual(tokens, [['color', 'red', 'color', ['color:red'], [['.one']], false]]); } }, 'two properties': { 'topic': extractor(buildToken('a{color:red;display:block}')), 'has no properties': function (tokens) { assert.deepEqual(tokens, [ - ['color', 'red', 'color', 'color:red', ['a'], true], - ['display', 'block', 'display', 'display:block', ['a'], true] + ['color', 'red', 'color', ['color:red'], [['a']], true], + ['display', 'block', 'display', ['display:block'], [['a']], true] ]); } }, @@ -46,9 +46,9 @@ vows.describe(extractor) 'topic': extractor(buildToken('@media{a{color:red;display:block}p{color:red}}')), 'has no properties': function (tokens) { assert.deepEqual(tokens, [ - ['color', 'red', 'color', 'color:red', ['a'], true], - ['display', 'block', 'display', 'display:block', ['a'], true], - ['color', 'red', 'color', 'color:red', ['p'], true] + ['color', 'red', 'color', ['color:red'], [['a']], true], + ['display', 'block', 'display', ['display:block'], [['a']], true], + ['color', 'red', 'color', ['color:red'], [['p']], true] ]); } } @@ -58,43 +58,43 @@ vows.describe(extractor) 'vendor prefix': { 'topic': extractor(buildToken('a{-moz-transform:none}')), 'has no properties': function (tokens) { - assert.deepEqual(tokens, [['-moz-transform', 'none', 'transform', '-moz-transform:none', ['a'], true]]); + assert.deepEqual(tokens, [['-moz-transform', 'none', 'transform', ['-moz-transform:none'], [['a']], true]]); } }, 'list-style': { 'topic': extractor(buildToken('a{list-style:none}')), 'has no properties': function (tokens) { - assert.deepEqual(tokens, [['list-style', 'none', 'list-style', 'list-style:none', ['a'], true]]); + assert.deepEqual(tokens, [['list-style', 'none', 'list-style', ['list-style:none'], [['a']], true]]); } }, 'border-radius': { 'topic': extractor(buildToken('a{border-top-left-radius:none}')), 'has no properties': function (tokens) { - assert.deepEqual(tokens, [['border-top-left-radius', 'none', 'border-radius', 'border-top-left-radius:none', ['a'], true]]); + assert.deepEqual(tokens, [['border-top-left-radius', 'none', 'border-radius', ['border-top-left-radius:none'], [['a']], true]]); } }, 'vendor prefixed border-radius': { 'topic': extractor(buildToken('a{-webkit-border-top-left-radius:none}')), 'has no properties': function (tokens) { - assert.deepEqual(tokens, [['-webkit-border-top-left-radius', 'none', 'border-radius', '-webkit-border-top-left-radius:none', ['a'], true]]); + assert.deepEqual(tokens, [['-webkit-border-top-left-radius', 'none', 'border-radius', ['-webkit-border-top-left-radius:none'], [['a']], true]]); } }, 'border-image': { 'topic': extractor(buildToken('a{border-image-width:2px}')), 'has no properties': function (tokens) { - assert.deepEqual(tokens, [['border-image-width', '2px', 'border-image', 'border-image-width:2px', ['a'], true]]); + assert.deepEqual(tokens, [['border-image-width', '2px', 'border-image', ['border-image-width:2px'], [['a']], true]]); } }, 'border-top': { 'topic': extractor(buildToken('a{border-top-style:none}')), 'has no properties': function (tokens) { - assert.deepEqual(tokens, [['border-top-style', 'none', 'border-top', 'border-top-style:none', ['a'], true]]); + assert.deepEqual(tokens, [['border-top-style', 'none', 'border-top', ['border-top-style:none'], [['a']], true]]); } }, 'text-shadow': { 'topic': extractor(buildToken('a{text-shadow:none}')), 'has no properties': function (tokens) { - assert.deepEqual(tokens, [['text-shadow', 'none', 'text-shadow', 'text-shadow:none', ['a'], true]]); + assert.deepEqual(tokens, [['text-shadow', 'none', 'text-shadow', ['text-shadow:none'], [['a']], true]]); } } } diff --git a/test/properties/reorderable-test.js b/test/properties/reorderable-test.js index 6b472551..4ee87874 100644 --- a/test/properties/reorderable-test.js +++ b/test/properties/reorderable-test.js @@ -7,7 +7,7 @@ var canReorder = require('../../lib/properties/reorderable').canReorder; var canReorderSingle = require('../../lib/properties/reorderable').canReorderSingle; function propertiesIn(source) { - return extractProperties(new SelectorTokenizer({ options: {} }, true, false).toTokens(source)[0]); + return extractProperties(new SelectorTokenizer({ options: {} }, false).toTokens(source)[0]); } vows.describe(canReorder) diff --git a/test/selectors/optimizers/simple-test.js b/test/selectors/optimizers/simple-test.js index b7f03b8a..19548fa8 100644 --- a/test/selectors/optimizers/simple-test.js +++ b/test/selectors/optimizers/simple-test.js @@ -15,7 +15,7 @@ function selectorContext(group, specs, options) { var tokens = new Tokenizer({ options: {} }).toTokens(source); new SimpleOptimizer(options).optimize(tokens); - assert.deepEqual(tokens[0] ? tokens[0].value : null, selectors); + assert.deepEqual(tokens[0] ? tokens[0][1] : null, selectors); }; } @@ -38,7 +38,7 @@ function propertyContext(group, specs, options) { return function (source) { var tokens = new Tokenizer({ options: {} }).toTokens(source); new SimpleOptimizer(options).optimize(tokens); - var value = tokens[0].body.map(function (property) { return property.value; }); + var value = tokens[0] ? tokens[0][2].map(function (property) { return property[0]; }) : null; assert.deepEqual(value, selectors); }; @@ -59,23 +59,23 @@ vows.describe(SimpleOptimizer) selectorContext('default', { 'optimized': [ 'a{}', - [{ value: 'a' }] + null ], 'whitespace': [ - ' div > span{}', - [{ value: 'div>span' }] + ' div > span{color:red}', + [['div>span']] ], 'line breaks': [ - ' div >\n\r\n span{}', - [{ value: 'div>span' }] + ' div >\n\r\n span{color:red}', + [['div>span']] ], '+html': [ '*+html .foo{display:inline}', null ], 'adjacent nav': [ - 'div + nav{}', - [{ value: 'div+nav' }] + 'div + nav{color:red}', + [['div+nav']] ] }) ) @@ -91,7 +91,7 @@ vows.describe(SimpleOptimizer) ], '+html - complex': [ '*+html .foo,.bar{display:inline}', - [{ value: '.bar' }] + [['.bar']] ] }, { compatibility: 'ie8' }) ) @@ -99,23 +99,23 @@ vows.describe(SimpleOptimizer) selectorContext('ie7', { '+html': [ '*+html .foo{display:inline}', - [{ value: '*+html .foo' }] + [['*+html .foo']] ], '+html - complex': [ '*+html .foo,.bar{display:inline}', - [{ value: '*+html .foo' }, { value: '.bar' }] + [['*+html .foo'], ['.bar']] ] }, { compatibility: 'ie7' }) ) .addBatch( selectorContext('+adjacentSpace', { 'with whitespace': [ - 'div + nav{}', - [{ value: 'div+ nav' }] + 'div + nav{color:red}', + [['div+ nav']] ], 'without whitespace': [ - 'div+nav{}', - [{ value: 'div+ nav' }] + 'div+nav{color:red}', + [['div+ nav']] ] }, { compatibility: { selectors: { adjacentSpace: true } } }) ) @@ -307,11 +307,11 @@ vows.describe(SimpleOptimizer) propertyContext('ie hacks', { 'underscore': [ 'a{_width:100px}', - [] + null ], 'star': [ 'a{*width:100px}', - [] + null ] }) ) diff --git a/test/selectors/tokenizer-source-maps-test.js b/test/selectors/tokenizer-source-maps-test.js index 95f41e9a..d0bbee80 100644 --- a/test/selectors/tokenizer-source-maps-test.js +++ b/test/selectors/tokenizer-source-maps-test.js @@ -13,35 +13,39 @@ var inputMap = fs.readFileSync(inputMapPath, 'utf-8'); function sourceMapContext(group, specs) { var ctx = {}; - function tokenizedContext(target, index) { + function tokenizedContext(target) { return function (tokenized) { - assert.deepEqual(tokenized[index], target); + assert.deepEqual(tokenized, target); + }; + } + + function toTokens(source) { + return function () { + return new Tokenizer({ + sourceTracker: sourceTracker, + sourceReader: sourceReader, + inputSourceMapTracker: inputSourceMapTracker, + options: {} + }, true).toTokens(source); }; } for (var test in specs) { - for (var i = 0; i < specs[test][1].length; i++) { - var target = specs[test][1][i]; - var sourceTracker = new SourceTracker(); - var sourceReader = new SourceReader(); - var inputSourceMapTracker = new InputSourceMapTracker({ - options: { inliner: {} }, - errors: {}, - sourceTracker: sourceTracker - }); + var target = specs[test][1]; + var sourceTracker = new SourceTracker(); + var sourceReader = new SourceReader(); + var inputSourceMapTracker = new InputSourceMapTracker({ + options: { inliner: {} }, + errors: {}, + sourceTracker: sourceTracker + }); - ctx[group + ' ' + test + ' - #' + (i + 1)] = { - topic: typeof specs[test][0] == 'function' ? - specs[test][0]() : - new Tokenizer({ - sourceTracker: sourceTracker, - sourceReader: sourceReader, - inputSourceMapTracker: inputSourceMapTracker, - options: {} - }, false, true).toTokens(specs[test][0]), - tokenized: tokenizedContext(target, i) - }; - } + ctx[group + ' ' + test] = { + topic: typeof specs[test][0] == 'function' ? + specs[test][0] : + toTokens(specs[test][0]), + tokenized: tokenizedContext(target) + }; } return ctx; @@ -52,91 +56,90 @@ vows.describe('source-maps/analyzer') sourceMapContext('selectors', { 'single': [ 'a{}', - [{ - kind: 'selector', - value: [{ value: 'a', metadata: { original: { line: 1, column: 0 }, source: undefined } }], - body: [] - }] + [ + [ + 'selector', + [['a', 1, 0, undefined]], + [] + ] + ] ], 'double': [ 'a,div{}', - [{ - kind: 'selector', - value: [ - { value: 'a', metadata: { original: { line: 1, column: 0 }, source: undefined } }, - { value: 'div', metadata: { original: { line: 1, column: 2 }, source: undefined } } - ], - body: [] - }] + [ + [ + 'selector', + [ + ['a', 1, 0, undefined], + ['div', 1, 2, undefined] + ], + [] + ] + ] ], 'double with whitespace': [ ' a,\n\ndiv{}', - [{ - kind: 'selector', - value: [ - { value: 'a', metadata: { original: { line: 1, column: 1 }, source: undefined } }, - { value: '\n\ndiv', metadata: { original: { line: 3, column: 0 }, source: undefined } } - ], - body: [] - }] + [ + [ + 'selector', + [['a', 1, 1, undefined], ['\n\ndiv', 3, 0, undefined]], + [] + ] + ] ], 'triple': [ 'a,div,p{}', - [{ - kind: 'selector', - value: [ - { value: 'a', metadata: { original: { line: 1, column: 0 }, source: undefined } }, - { value: 'div', metadata: { original: { line: 1, column: 2 }, source: undefined } }, - { value: 'p', metadata: { original: { line: 1, column: 6 }, source: undefined } } - ], - body: [] - }] + [ + [ + 'selector', + [['a', 1, 0, undefined], ['div', 1, 2, undefined], ['p', 1, 6, undefined]], + [] + ] + ] ], 'triple with whitespace': [ ' a,\n\ndiv\na,\n p{}', - [{ - kind: 'selector', - value: [ - { value: 'a', metadata: { original: { line: 1, column: 1 }, source: undefined } }, - { value: '\n\ndiv\na', metadata: { original: { line: 3, column: 0 }, source: undefined } }, - { value: '\n p', metadata: { original: { line: 5, column: 1 }, source: undefined } } - ], - body: [] - }] + [ + [ + 'selector', + [['a', 1, 1, undefined], ['\n\ndiv\na', 3, 0, undefined], ['\n p', 5, 1, undefined]], + [] + ] + ] ], 'two': [ 'a{}div{}', [ - { - kind: 'selector', - value: [{ value: 'a', metadata: { original: { line: 1, column: 0 }, source: undefined } }], - body: [] - }, - { - kind: 'selector', - value: [{ value: 'div', metadata: { original: { line: 1, column: 3 }, source: undefined } }], - body: [] - } + [ + 'selector', + [['a', 1, 0, undefined]], + [] + ], + [ + 'selector', + [['div', 1, 3, undefined]], + [] + ] ] ], 'three with whitespace and breaks': [ 'a {}\n\ndiv{}\n \n p{}', [ - { - kind: 'selector', - value: [{ value: 'a ', metadata: { original: { line: 1, column: 0 }, source: undefined } }], - body: [] - }, - { - kind: 'selector', - value: [{ value: 'div', metadata: { original: { line: 3, column: 0 }, source: undefined } }], - body: [] - }, - { - kind: 'selector', - value: [{ value: 'p', metadata: { original: { line: 5, column: 2 }, source: undefined } }], - body: [] - } + [ + 'selector', + [['a ', 1, 0, undefined]], + [] + ], + [ + 'selector', + [['div', 3, 0, undefined]], + [] + ], + [ + 'selector', + [['p', 5, 2, undefined]], + [] + ] ] ] }) @@ -145,78 +148,84 @@ vows.describe('source-maps/analyzer') sourceMapContext('properties', { 'single': [ 'a{color:red}', - [{ - kind: 'selector', - value: [{ value: 'a', metadata: { original: { line: 1, column: 0 }, source: undefined } }], - body: [{ value: 'color:red', metadata: { original: { line: 1, column: 2 }, source: undefined } }] - }] + [ + [ + 'selector', + [['a', 1, 0, undefined]], + [['color:red', 1, 2, undefined]] + ] + ] ], 'double': [ 'a{color:red;border:none}', - [{ - kind: 'selector', - value: [{ value: 'a', metadata: { original: { line: 1, column: 0 }, source: undefined } }], - body: [ - { value: 'color:red', metadata: { original: { line: 1, column: 2 }, source: undefined } }, - { value: 'border:none', metadata: { original: { line: 1, column: 12 }, source: undefined } } + [ + [ + 'selector', + [['a', 1, 0, undefined]], + [ + ['color:red', 1, 2, undefined], + ['border:none', 1, 12, undefined] + ] ] - }] + ] ], 'triple with whitespace': [ 'a{color:red;\nborder:\nnone;\n\n display:block}', - [{ - kind: 'selector', - value: [{ value: 'a', metadata: { original: { line: 1, column: 0 }, source: undefined } }], - body: [ - { value: 'color:red', metadata: { original: { line: 1, column: 2 }, source: undefined } }, - { value: 'border:none', metadata: { original: { line: 2, column: 0 }, source: undefined } }, - { value: 'display:block', metadata: { original: { line: 5, column: 2 }, source: undefined } } + [ + [ + 'selector', + [['a', 1, 0, undefined]], + [ + ['color:red', 1, 2, undefined], + ['border:none', 2, 0, undefined], + ['display:block', 5, 2, undefined] + ] ] - }] + ] ], 'two declarations': [ 'a{color:red}div{color:blue}', [ - { - kind: 'selector', - value: [{ value: 'a', metadata: { original: { line: 1, column: 0 }, source: undefined } }], - body: [{ value: 'color:red', metadata: { original: { line: 1, column: 2 }, source: undefined } }] - }, - { - kind: 'selector', - value: [{ value: 'div', metadata: { original: { line: 1, column: 12 }, source: undefined } }], - body: [{ value: 'color:blue', metadata: { original: { line: 1, column: 16 }, source: undefined } }] - } + [ + 'selector', + [['a', 1, 0, undefined]], + [['color:red', 1, 2, undefined]] + ], + [ + 'selector', + [['div', 1, 12, undefined]], + [['color:blue', 1, 16, undefined]] + ] ] ], 'two declarations with whitespace': [ 'a{color:red}\n div{color:blue}', [ - { - kind: 'selector', - value: [{ value: 'a', metadata: { original: { line: 1, column: 0 }, source: undefined } }], - body: [{ value: 'color:red', metadata: { original: { line: 1, column: 2 }, source: undefined } }] - }, - { - kind: 'selector', - value: [{ value: 'div', metadata: { original: { line: 2, column: 1 }, source: undefined } }], - body: [{ value: 'color:blue', metadata: { original: { line: 2, column: 5 }, source: undefined } }] - } + [ + 'selector', + [['a', 1, 0, undefined]], + [['color:red', 1, 2, undefined]] + ], + [ + 'selector', + [['div', 2, 1, undefined]], + [['color:blue', 2, 5, undefined]] + ] ] ], 'two declarations with whitespace and ending semicolon': [ 'a{color:red;\n}\n div{color:blue}', [ - { - kind: 'selector', - value: [{ value: 'a', metadata: { original: { line: 1, column: 0 }, source: undefined } }], - body: [{ value: 'color:red', metadata: { original: { line: 1, column: 2 }, source: undefined } }] - }, - { - kind: 'selector', - value: [{ value: 'div', metadata: { original: { line: 3, column: 1 }, source: undefined } }], - body: [{ value: 'color:blue', metadata: { original: { line: 3, column: 5 }, source: undefined } }] - } + [ + 'selector', + [['a', 1, 0, undefined]], + [['color:red', 1, 2, undefined]] + ], + [ + 'selector', + [['div', 3, 1, undefined]], + [['color:blue', 3, 5, undefined]] + ] ] ] }) @@ -226,36 +235,34 @@ vows.describe('source-maps/analyzer') '@import': [ 'a{}@import \n"test.css";\n\na{color:red}', [ - { - kind: 'selector', - value: [{ value: 'a', metadata: { original: { line: 1, column: 0 }, source: undefined } }], - body: [] - }, - { - kind: 'at-rule', - value: '@import \n"test.css";', - metadata: { original: { line: 1, column: 3 }, source: undefined } - }, - { - kind: 'selector', - value: [{ value: 'a', metadata: { original: { line: 4, column: 0 }, source: undefined } }], - body: [{ value: 'color:red', metadata: { original: { line: 4, column: 2 }, source: undefined } }] - } + [ + 'selector', + [['a', 1, 0, undefined]], + [] + ], + [ + 'at-rule', + ['@import \n"test.css";', 1, 3, undefined] + ], + [ + 'selector', + [['a', 4, 0, undefined]], + [['color:red', 4, 2, undefined]] + ] ] ], '@charset': [ '@charset "utf-8";a{color:red}', [ - { - kind: 'at-rule', - value: '@charset "utf-8";', - metadata: { original: { line: 1, column: 0 }, source: undefined } - }, - { - kind: 'selector', - value: [{ value: 'a', metadata: { original: { line: 1, column: 18 }, source: undefined } }], - body: [{ value: 'color:red', metadata: { original: { line: 1, column: 20 }, source: undefined } }] - } + [ + 'at-rule', + ['@charset "utf-8";', 1, 0, undefined] + ], + [ + 'selector', + [['a', 1, 18, undefined]], + [['color:red', 1, 20, undefined]] + ] ] ] }) @@ -265,99 +272,91 @@ vows.describe('source-maps/analyzer') '@media - simple': [ '@media (min-width:980px){a{color:red}}', [ - { - kind: 'block', - value: '@media (min-width:980px)', - metadata: { original: { line: 1, column: 0 }, source: undefined }, - isFlatBlock: false, - body: [{ - kind: 'selector', - value: [{ value: 'a', metadata: { original: { line: 1, column: 25 }, source: undefined } }], - body: [{ value: 'color:red', metadata: { original: { line: 1, column: 27 }, source: undefined } }] - }] - } + [ + 'block', + ['@media (min-width:980px)', 1, 0, undefined], + [ + [ + 'selector', + [['a', 1, 25, undefined]], + [['color:red', 1, 27, undefined]] + ] + ] + ] ] ], '@media - with whitespace': [ '@media (\nmin-width:980px)\n{\na{\ncolor:\nred}p{}}', [ - { - kind: 'block', - value: '@media (\nmin-width:980px)', - metadata: { original: { line: 1, column: 0 }, source: undefined }, - isFlatBlock: false, - body: [ - { - kind: 'selector', - value: [{ value: 'a', metadata: { original: { line: 4, column: 0 }, source: undefined } }], - body: [{ value: 'color:red', metadata: { original: { line: 5, column: 0 }, source: undefined } }] - }, - { - kind: 'selector', - value: [{ value: 'p', metadata: { original: { line: 6, column: 4 }, source: undefined } }], - body: [] - } + [ + 'block', + ['@media (\nmin-width:980px)', 1, 0, undefined], + [ + [ + 'selector', + [['a', 4, 0, undefined]], + [['color:red', 5, 0, undefined]] + ], + [ + 'selector', + [['p', 6, 4, undefined]], + [] + ] ] - } + ] ] ], '@media - stray whitespace at end': [ '@media (min-width:980px){a{color:red} }p{color:red}', [ - { - kind: 'block', - value: '@media (min-width:980px)', - metadata: { original: { line: 1, column: 0 }, source: undefined }, - isFlatBlock: false, - body: [ - { - kind: 'selector', - value: [{ value: 'a', metadata: { original: { line: 1, column: 25 }, source: undefined } }], - body: [{ value: 'color:red', metadata: { original: { line: 1, column: 27 }, source: undefined } }] - } + [ + 'block', + ['@media (min-width:980px)', 1, 0, undefined], + [ + [ + 'selector', + [['a', 1, 25, undefined]], + [['color:red', 1, 27, undefined]] + ] ] - }, - { - kind: 'selector', - value: [{ value: 'p', metadata: { original: { line: 1, column: 39 }, source: undefined } }], - body: [{ value: 'color:red', metadata: { original: { line: 1, column: 41 }, source: undefined } }] - } + ], + [ + 'selector', + [['p', 1, 39, undefined]], + [['color:red', 1, 41, undefined]] + ] ] ], '@font-face': [ '@font-face{font-family: "Font";\nsrc: url("font.ttf");\nfont-weight: normal;font-style: normal}a{}', [ - { - kind: 'block', - value: '@font-face', - metadata: { original: { line: 1, column: 0 }, source: undefined }, - isFlatBlock: true, - body: [ - { value: 'font-family:"Font"', metadata: { original: { line: 1, column: 11 }, source: undefined } }, - { value: 'src:url("font.ttf")', metadata: { original: { line: 2, column: 0 }, source: undefined } }, - { value: 'font-weight:normal', metadata: { original: { line: 3, column: 0 }, source: undefined } }, - { value: 'font-style:normal', metadata: { original: { line: 3, column: 20 }, source: undefined } } + [ + 'flat-block', + ['@font-face', 1, 0, undefined], + [ + ['font-family:"Font"', 1, 11, undefined], + ['src:url("font.ttf")', 2, 0, undefined], + ['font-weight:normal', 3, 0, undefined], + ['font-style:normal', 3, 20, undefined] ] - }, - { - kind: 'selector', - value: [{ value: 'a', metadata: { original: { line: 3, column: 39 }, source: undefined } }], - body: [] - } + ], + [ + 'selector', + [['a', 3, 39, undefined]], + [] + ] ] ], '@font-face with breaks': [ '\n@font-face\n{font-family: "Font"}', [ - { - kind: 'block', - value: '@font-face', - metadata: { original: { line: 2, column: 0 }, source: undefined }, - isFlatBlock: true, - body: [ - { value: 'font-family:"Font"', metadata: { original: { line: 3, column: 1 }, source: undefined } } + [ + 'flat-block', + ['@font-face', 2, 0, undefined], + [ + ['font-family:"Font"', 3, 1, undefined] ] - } + ] ] ] }) @@ -367,85 +366,86 @@ vows.describe('source-maps/analyzer') 'top-level': [ '__ESCAPED_COMMENT_CLEAN_CSS0(0, 5)__a{}', [ - { - kind: 'selector', - value: [{ value: 'a', metadata: { original: { line: 1, column: 5 }, source: undefined } }], - body: [] - } + [ + 'selector', + [['a', 1, 5, undefined]], + [] + ] ] ], 'top-level with line breaks': [ '__ESCAPED_COMMENT_CLEAN_CSS0(2, 5)__a{}', [ - { - kind: 'selector', - value: [{ value: 'a', metadata: { original: { line: 3, column: 5 }, source: undefined } }], - body: [] - } + [ + 'selector', + [['a', 3, 5, undefined]], + [] + ] ] ], 'in selectors': [ 'div[data-type=__ESCAPED_FREE_TEXT_CLEAN_CSS0(1,3)__],div[data-id=__ESCAPED_FREE_TEXT_CLEAN_CSS1(0,7)__]{color:red}', - [{ - kind: 'selector', - value: [ - { value: 'div[data-type=__ESCAPED_FREE_TEXT_CLEAN_CSS0(1,3)__]', metadata: { original: { line: 1, column: 0 }, source: undefined } }, - { value: 'div[data-id=__ESCAPED_FREE_TEXT_CLEAN_CSS1(0,7)__]', metadata: { original: { line: 2, column: 5 }, source: undefined } } - ], - body: [{ value: 'color:red', metadata: { original: { line: 2, column: 26 }, source: undefined } }] - }] + [ + [ + 'selector', + [ + ['div[data-type=__ESCAPED_FREE_TEXT_CLEAN_CSS0(1,3)__]', 1, 0, undefined], + ['div[data-id=__ESCAPED_FREE_TEXT_CLEAN_CSS1(0,7)__]', 2, 5, undefined] + ], + [['color:red', 2, 26, undefined]] + ] + ] ], 'in properties': [ 'div{__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS0(2,5)__background:url(__ESCAPED_URL_CLEAN_CSS0(0,20)__);color:blue}a{font-family:__ESCAPED_FREE_TEXT_CLEAN_CSS0(1,3)__;color:red}', [ - { - kind: 'selector', - value: [{ value: 'div', metadata: { original: { line: 1, column: 0 }, source: undefined } }], - body: [ - { value: '__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS0(2,5)__', metadata: { original: { line: 1, column: 4 }, source: undefined }}, - { value: 'background:url(__ESCAPED_URL_CLEAN_CSS0(0,20)__)', metadata: { original: { line: 3, column: 5 }, source: undefined } }, - { value: 'color:blue', metadata: { original: { line: 3, column: 42 }, source: undefined } } + [ + 'selector', + [['div', 1, 0, undefined]], + [ + ['__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS0(2,5)__', 1, 4, undefined], + ['background:url(__ESCAPED_URL_CLEAN_CSS0(0,20)__)', 3, 5, undefined], + ['color:blue', 3, 42, undefined] ] - }, - { - kind: 'selector', - value: [{ value: 'a', metadata: { original: { line: 3, column: 53 }, source: undefined } }], - body: [ - { value: 'font-family:__ESCAPED_FREE_TEXT_CLEAN_CSS0(1,3)__', metadata: { original: { line: 3, column: 55 }, source: undefined } }, - { value: 'color:red', metadata: { original: { line: 4, column: 4 }, source: undefined } } + ], + [ + 'selector', + [['a', 3, 53, undefined]], + [ + ['font-family:__ESCAPED_FREE_TEXT_CLEAN_CSS0(1,3)__', 3, 55, undefined], + ['color:red', 4, 4, undefined] ] - } + ] ] ], 'in at-rules': [ '@charset __ESCAPED_FREE_TEXT_CLEAN_CSS0(1, 5)__;div{}', [ - { - kind: 'at-rule', - value: '@charset __ESCAPED_FREE_TEXT_CLEAN_CSS0(1, 5)__;', - metadata: { original: { line: 1, column: 0 }, source: undefined } - }, - { - kind: 'selector', - value: [{ value: 'div', metadata: { original: { line: 2, column: 7 }, source: undefined } }], - body: [] - } + [ + 'at-rule', + ['@charset __ESCAPED_FREE_TEXT_CLEAN_CSS0(1, 5)__;', 1, 0, undefined] + ], + [ + 'selector', + [['div', 2, 7, undefined]], + [] + ] ] ], 'in blocks': [ '@media (__ESCAPED_COMMENT_CLEAN_CSS0(2, 1)__min-width:980px){a{color:red}}', [ - { - kind: 'block', - value: '@media (__ESCAPED_COMMENT_CLEAN_CSS0(2, 1)__min-width:980px)', - metadata: { original: { line: 1, column: 0 }, source: undefined }, - isFlatBlock: false, - body: [{ - kind: 'selector', - value: [{ value: 'a', metadata: { original: { line: 3, column: 18 }, source: undefined } }], - body: [{ value: 'color:red', metadata: { original: { line: 3, column: 20 }, source: undefined } }] - }] - } + [ + 'block', + ['@media (__ESCAPED_COMMENT_CLEAN_CSS0(2, 1)__min-width:980px)', 1, 0, undefined], + [ + [ + 'selector', + [['a', 3, 18, undefined]], + [['color:red', 3, 20, undefined]] + ] + ] + ] ] ] }) @@ -457,43 +457,41 @@ vows.describe('source-maps/analyzer') var tracker = new SourceTracker(); var reader = new SourceReader(); var inputTracker = new InputSourceMapTracker({ options: { inliner: {} }, errors: {}, sourceTracker: tracker }); - var tokenizer = new Tokenizer({ sourceTracker: tracker, sourceReader: reader, inputSourceMapTracker: inputTracker, options: {} }, false, true); + var tokenizer = new Tokenizer({ sourceTracker: tracker, sourceReader: reader, inputSourceMapTracker: inputTracker, options: {} }, true); var data = tracker.store('one.css', 'a{}'); return tokenizer.toTokens(data); }, - [{ - kind: 'selector', - value: [{ value: 'a', metadata: { original: { line: 1, column: 0 }, source: 'one.css' } }], - body: [] - }] + [ + [ + 'selector', + [['a', 1, 0, 'one.css']], + [] + ] + ] ], 'two': [ function () { var tracker = new SourceTracker(); var reader = new SourceReader(); var inputTracker = new InputSourceMapTracker({ options: { inliner: {} }, errors: {}, sourceTracker: tracker }); - var tokenizer = new Tokenizer({ sourceTracker: tracker, sourceReader: reader, inputSourceMapTracker: inputTracker, options: {} }, false, true); + var tokenizer = new Tokenizer({ sourceTracker: tracker, sourceReader: reader, inputSourceMapTracker: inputTracker, options: {} }, true); var data1 = tracker.store('one.css', 'a{}'); var data2 = tracker.store('two.css', '\na{color:red}'); return tokenizer.toTokens(data1 + data2); }, [ - { - kind: 'selector', - value: [ - { value: 'a', metadata: { original: { line: 1, column: 0 }, source: 'one.css' } } - ], - body: [] - }, - { - kind: 'selector', - value: [ - { value: 'a', metadata: { original: { line: 2, column: 0 }, source: 'two.css' } } - ], - body: [{ value: 'color:red', metadata: { original: { line: 2, column: 2 }, source: 'two.css' } }] - } + [ + 'selector', + [['a', 1, 0, 'one.css']], + [] + ], + [ + 'selector', + [['a', 2, 0, 'two.css']], + [['color:red', 2, 2, 'two.css']] + ] ] ] }) @@ -507,14 +505,16 @@ vows.describe('source-maps/analyzer') var inputTracker = new InputSourceMapTracker({ options: { inliner: {}, sourceMap: inputMap, options: {} }, errors: {}, sourceTracker: tracker }); inputTracker.track('', function () {}); - var tokenizer = new Tokenizer({ sourceTracker: tracker, sourceReader: reader, inputSourceMapTracker: inputTracker, options: {} }, false, true); + var tokenizer = new Tokenizer({ sourceTracker: tracker, sourceReader: reader, inputSourceMapTracker: inputTracker, options: {} }, true); return tokenizer.toTokens('div > a {\n color: red;\n}'); }, - [{ - kind: 'selector', - value: [{ value: 'div > a ', metadata: { original: { line: 1, column: 4 }, source: 'styles.less' } }], - body: [{ value: 'color:red', metadata: { original: { line: 2, column: 2 }, source: 'styles.less' } }] - }] + [ + [ + 'selector', + [['div > a ', 1, 4, 'styles.less']], + [['color:red', 2, 2, 'styles.less']] + ] + ] ] }) ) diff --git a/test/selectors/tokenizer-test.js b/test/selectors/tokenizer-test.js index cc044611..1111958b 100644 --- a/test/selectors/tokenizer-test.js +++ b/test/selectors/tokenizer-test.js @@ -3,13 +3,18 @@ var assert = require('assert'); var Tokenizer = require('../../lib/selectors/tokenizer'); var SourceTracker = require('../../lib/utils/source-tracker'); -function tokenizerContext(name, specs, addMetadata) { +function tokenizerContext(name, specs) { var ctx = {}; function tokenized(target) { return function (source) { - var tokenized = new Tokenizer({ options: {}, sourceTracker: new SourceTracker(), warnings: [] }, addMetadata).toTokens(source); - assert.deepEqual(target, tokenized); + var tokenized = new Tokenizer({ + options: {}, + sourceTracker: new SourceTracker(), + warnings: [] + }).toTokens(source); + + assert.deepEqual(tokenized, target); }; } @@ -40,209 +45,145 @@ vows.describe(Tokenizer) ], 'an empty selector': [ 'a{}', - [{ - kind: 'selector', - value: [{ value: 'a' }], - body: [] - }] + [ + ['selector', [['a']], []] + ] ], 'an empty selector with whitespace': [ 'a{ \n }', - [{ - kind: 'selector', - value: [{ value: 'a' }], - body: [] - }] + [ + ['selector', [['a']], []] + ] ], 'a selector': [ 'a{color:red}', - [{ - kind: 'selector', - value: [{ value: 'a' }], - body: [{ value: 'color:red' }] - }] + [ + ['selector', [['a']], [['color:red']]] + ] ], 'a selector with whitespace': [ 'a {color:red;\n\ndisplay :\r\n block }', - [{ - kind: 'selector', - value: [{ value: 'a ' }], - body: [ - { value: 'color:red' }, - { value: 'display:block' - }] - }] + [ + ['selector', [['a ']], [['color:red'], ['display:block']]] + ] ], 'a selector with suffix whitespace': [ 'div a{color:red\r\n}', - [{ kind: 'selector', value: [{ value: 'div a' }], body: [{ value: 'color:red' }] }] + [ + ['selector', [['div a']], [['color:red']]] + ] ], 'a selector with whitespace in functions': [ 'a{color:rgba( 255, 255, 0, 0.5 )}', - [{ - kind: 'selector', - value: [{ value: 'a' }], - body: [{ value: 'color:rgba(255,255,0,0.5)' }] - }] + [ + ['selector', [['a']], [['color:rgba(255,255,0,0.5)']]] + ] ], 'a selector with empty properties': [ 'a{color:red; ; ; ;}', - [{ - kind: 'selector', - value: [{ value: 'a' }], - body: [{ value: 'color:red' }] - }] + [ + ['selector', [['a']], [['color:red']]] + ] ], 'a selector with quoted attribute': [ 'a[data-kind=__ESCAPED_FREE_TEXT_CLEAN_CSS0__]{color:red}', - [{ - kind: 'selector', - value: [{ value: 'a[data-kind=__ESCAPED_FREE_TEXT_CLEAN_CSS0__]' }], - body: [{ value: 'color:red' }] - }] + [ + ['selector', [['a[data-kind=__ESCAPED_FREE_TEXT_CLEAN_CSS0__]']], [['color:red']]] + ] ], 'a selector with escaped quote': [ '.this-class\\\'s-got-an-apostrophe{}', - [{ - kind: 'selector', - value: [{ value: '.this-class\\\'s-got-an-apostrophe' }], - body: [] - }] + [ + ['selector', [['.this-class\\\'s-got-an-apostrophe']], []] + ] ], 'a double selector': [ 'a,\n\ndiv.class > p {color:red}', - [{ - kind: 'selector', - value: [ - { value: 'a' }, - { value: '\n\ndiv.class > p ' } - ], - body: [{ value: 'color:red' }] - }] + [ + ['selector', [['a'], ['\n\ndiv.class > p ']], [['color:red']]] + ] ], 'two selectors': [ 'a{color:red}div{color:blue}', [ - { - kind: 'selector', - value: [{ value: 'a' }], - body: [{ value: 'color:red' }] - }, - { - kind: 'selector', - value: [{ value: 'div' }], - body: [{ value: 'color:blue' }] - } + ['selector', [['a']], [['color:red']]], + ['selector', [['div']], [['color:blue']]], ] ], 'two comments and a selector separated by newline': [ '__ESCAPED_COMMENT_CLEAN_CSS0__\n__ESCAPED_COMMENT_CLEAN_CSS1__\ndiv{}', [ - { - kind: 'selector', - value: [{ value: 'div' }], - body: [] - } + ['selector', [['div']], []] ] ], 'two properties wrapped between comments': [ 'div{__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS0__color:red__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS1__}', - [{ - kind: 'selector', - value: [{ value: 'div' }], - body: [ - { value: '__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS0__' }, - { value: 'color:red' }, - { value: '__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS1__' } - ] - }] + [ + ['selector', [['div']], [['__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS0__'], ['color:red'], ['__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS1__']]] + ] ], 'pseudoselector after an argument one': [ 'div:nth-child(2n):not(.test){}', - [{ - kind: 'selector', - value: [{ value: 'div:nth-child(2n):not(.test)' }], - body: [] - }] + [ + ['selector', [['div:nth-child(2n):not(.test)']], []] + ] ], 'media query': [ '@media (min-width:980px){}', - [{ - kind: 'block', - value: '@media (min-width:980px)', - body: [], - isFlatBlock: false - }] + [ + ['block', ['@media (min-width:980px)'], []] + ] ], 'media query with selectors': [ '@media (min-width:980px){a{color:red}}', - [{ - kind: 'block', - value: '@media (min-width:980px)', - body: [{ - kind: 'selector', - value: [{ value: 'a' }], - body: [{ value: 'color:red' }] - }], - isFlatBlock: false - }] + [ + ['block', ['@media (min-width:980px)'], [ + ['selector', [['a']], [['color:red']]] + ]] + ] ], 'media query spanning more than one chunk': [ '@media only screen and (max-width:1319px) and (min--moz-device-pixel-ratio:1.5),only screen and (max-width:1319px) and (-moz-min-device-pixel-ratio:1.5){a{color:#000}}', - [{ - kind: 'block', - value: '@media only screen and (max-width:1319px) and (min--moz-device-pixel-ratio:1.5),only screen and (max-width:1319px) and (-moz-min-device-pixel-ratio:1.5)', - body: [{ - kind: 'selector', - value: [{ value: 'a' }], - body: [{ value: 'color:#000' }] - }], - isFlatBlock: false - }] + [ + ['block', ['@media only screen and (max-width:1319px) and (min--moz-device-pixel-ratio:1.5),only screen and (max-width:1319px) and (-moz-min-device-pixel-ratio:1.5)'], [ + ['selector', [['a']], [['color:#000']]] + ]] + ] ], 'font-face': [ '@font-face{font-family: fontName;font-size:12px}', - [{ - kind: 'block', - value: '@font-face', - body: [ - { value: 'font-family:fontName' }, - { value: 'font-size:12px' } - ], - isFlatBlock: true - }] + [ + ['flat-block', ['@font-face'], [['font-family:fontName'], ['font-size:12px']]] + ] ], 'charset': [ '@charset \'utf-8\';a{color:red}', [ - { - kind: 'at-rule', - value: '@charset \'utf-8\';' - }, - { - kind: 'selector', - value: [{ value: 'a' }], - body: [{ value: 'color:red' }] - } + ['at-rule', ['@charset \'utf-8\';']], + ['selector', [['a']], [['color:red']]] ] ], 'charset after a line break': [ '\n@charset \n\'utf-8\';', [ - { - kind: 'at-rule', - value: '@charset \n\'utf-8\';' - } + ['at-rule', ['@charset \n\'utf-8\';']] ] ], 'keyframes with quoted attribute': [ '@keyframes __ESCAPED_FREE_TEXT_CLEAN_CSS0__{}', - [{ - kind: 'block', - value: '@keyframes __ESCAPED_FREE_TEXT_CLEAN_CSS0__', - body: [], - isFlatBlock: false - }] + [ + ['block', ['@keyframes __ESCAPED_FREE_TEXT_CLEAN_CSS0__'], []] + ] + ], + 'variables': [ + 'a{border:var(--width)var(--style)var(--color)}', + [ + [ + 'selector', + [[ 'a' ]], + [['border:var(--width) var(--style) var(--color)']] + ] + ] ] }) ) @@ -250,125 +191,16 @@ vows.describe(Tokenizer) tokenizerContext('broken', { 'missing end brace': [ 'a{display:block', - [{ - kind: 'selector', - value: [{ value: 'a' }], - body: [] - }] + [ + ['selector', [['a']], []] + ] ], 'missing end brace in the middle': [ 'body{color:red;a{color:blue;}', - [{ - kind: 'selector', - value: [{ value: 'body' }], - body: [{ value: 'color:red' }] - }] - ] - }) - ) - .addBatch( - tokenizerContext('metadata', { - 'no content': [ - '', - [] - ], - 'an escaped comment': [ - '__ESCAPED_COMMENT_CLEAN_CSS0__', - [] - ], - 'an escaped special comment': [ - '__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS0__', - [{ kind: 'text', value: '__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS0__' }] - ], - 'an empty selector': [ - 'a{}', - [{ - kind: 'selector', - value: [{ value: 'a' }], - body: [], - metadata: { - body: '', - bodiesList: [], - selector: 'a', - selectorsList: ['a'] - } - }] - ], - 'a double selector': [ - 'a,\n\ndiv.class > p {color:red}', - [{ - kind: 'selector', - value: [{ value: 'a' }, { value: '\n\ndiv.class > p ' }], - body: [{ value: 'color:red' }], - metadata: { - body: 'color:red', - bodiesList: ['color:red'], - selector: 'a,\n\ndiv.class > p ', - selectorsList: ['a', '\n\ndiv.class > p '] - } - }] - ], - 'two selectors': [ - 'a{color:red}div{color:blue}', [ - { - kind: 'selector', - value: [{ value: 'a' }], - body: [{ value: 'color:red' }], - metadata: { - body: 'color:red', - bodiesList: ['color:red'], - selector: 'a', - selectorsList: ['a'] - } - }, - { - kind: 'selector', - value: [{ value: 'div' }], - body: [{ value: 'color:blue' }], - metadata: { - body: 'color:blue', - bodiesList: ['color:blue'], - selector: 'div', - selectorsList: ['div'] - } - } + ['selector', [['body']], [['color:red']]] ] - ], - 'two properties wrapped between comments': [ - 'div{__ESCAPED_COMMENT_CLEAN_CSS0(0, 5)__color:red__ESCAPED_COMMENT_CLEAN_CSS1(0, 5)__}', - [{ - kind: 'selector', - value: [{ value: 'div' }], - body: [ - { value: 'color:red' } - ], - metadata: { - body: 'color:red', - bodiesList: ['color:red'], - selector: 'div', - selectorsList: ['div'] - } - }] - ], - 'two properties wrapped between special comments': [ - 'div{__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS0(0, 5)__color:red__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS1(0, 5)__}', - [{ - kind: 'selector', - value: [{ value: 'div' }], - body: [ - { value: '__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS0(0, 5)__' }, - { value: 'color:red' }, - { value: '__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS1(0, 5)__' } - ], - metadata: { - body: '__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS0(0, 5)__,color:red,__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS1(0, 5)__', - bodiesList: ['__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS0(0, 5)__', 'color:red', '__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS1(0, 5)__'], - selector: 'div', - selectorsList: ['div'] - } - }] ] - }, true) + }) ) .export(module); diff --git a/test/source-map-test.js b/test/source-map-test.js index 37f3e9ba..86e031dd 100644 --- a/test/source-map-test.js +++ b/test/source-map-test.js @@ -1489,7 +1489,7 @@ vows.describe('source-map') }) .addBatch({ 'advanced optimizations': { - 'new property in smart sort': { + 'new property in restructuring': { 'topic': function () { return new CleanCSS({ sourceMap: true }).minify('a{color:#000}div{color:red}.one{display:block}.two{display:inline;color:red}'); }, -- 2.34.1