From fadcb9189da63cadc337d9222f19144662d4b1f3 Mon Sep 17 00:00:00 2001 From: Jakub Pawlowicz Date: Tue, 21 Oct 2014 09:16:06 +0100 Subject: [PATCH] Adds more advanced tokenizing. * We'll be able to hold more information about tokens now. --- lib/properties/optimizer.js | 23 ++-- lib/properties/processable.js | 4 +- lib/properties/token.js | 10 +- lib/selectors/optimizer.js | 22 ++-- lib/selectors/optimizers/advanced.js | 46 ++++---- lib/selectors/optimizers/clean-up.js | 6 +- lib/selectors/optimizers/simple.js | 30 ++--- lib/selectors/tokenizer.js | 16 +-- test/selectors/optimizers/simple-test.js | 17 +-- test/selectors/tokenizer-test.js | 144 +++++++++++++++++++---- 10 files changed, 221 insertions(+), 97 deletions(-) diff --git a/lib/properties/optimizer.js b/lib/properties/optimizer.js index 83609927..7f617619 100644 --- a/lib/properties/optimizer.js +++ b/lib/properties/optimizer.js @@ -3,6 +3,8 @@ var processableInfo = require('./processable'); var overrideCompactor = require('./override-compactor'); var shorthandCompactor = require('./shorthand-compactor'); +function valueMapper(object) { return object.value; } + module.exports = function Optimizer(compatibility, aggressiveMerging, context) { var overridable = { 'animation-delay': ['animation'], @@ -121,13 +123,13 @@ module.exports = function Optimizer(compatibility, aggressiveMerging, context) { var keyValues = []; for (var i = 0, l = body.length; i < l; i++) { - var token = body[i]; + var token = body[i].value; var firstColon = token.indexOf(':'); var property = token.substring(0, firstColon); var value = token.substring(firstColon + 1); if (value === '') { - context.warnings.push('Empty property \'' + property + '\' inside \'' + selector + '\' selector. Ignoring.'); + context.warnings.push('Empty property \'' + property + '\' inside \'' + selector.map(valueMapper).join(',') + '\' selector. Ignoring.'); continue; } @@ -236,12 +238,19 @@ module.exports = function Optimizer(compatibility, aggressiveMerging, context) { var rebuild = function(tokens) { var flat = []; + var eligibleForCompacting = false; for (var i = 0, l = tokens.length; i < l; i++) { - flat.push(tokens[i][0] + ':' + tokens[i][1]); + if (!eligibleForCompacting && processableInfo.implementedFor.test(tokens[i][0])) + eligibleForCompacting = true; + + flat.push({ value: tokens[i][0] + ':' + tokens[i][1] }); } - return flat; + return { + value: flat, + compactFurther: eligibleForCompacting + }; }; var compact = function (input) { @@ -262,9 +271,9 @@ module.exports = function Optimizer(compatibility, aggressiveMerging, context) { var optimized = optimize(tokenized, allowAdjacent); var rebuilt = rebuild(optimized); - return compactProperties && processableInfo.implementedFor.test(rebuilt) ? - compact(rebuilt) : - rebuilt; + return compactProperties && rebuilt.compactFurther ? + compact(rebuilt.value) : + rebuilt.value; } }; }; diff --git a/lib/properties/processable.js b/lib/properties/processable.js index bc6358c8..913108a4 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(val1)); - var brokenUp2 = breakUp.border(Token.tokenizeOne(val2)); + var brokenUp1 = breakUp.border(Token.tokenizeOne({ value: val1 })); + var brokenUp2 = breakUp.border(Token.tokenizeOne({ value: val2 })); return canOverride.color(brokenUp1[2].value, brokenUp2[2].value); } diff --git a/lib/properties/token.js b/lib/properties/token.js index ff4e514b..bb4b8c71 100644 --- a/lib/properties/token.js +++ b/lib/properties/token.js @@ -63,16 +63,16 @@ module.exports = (function() { // Parses one CSS property declaration into a token Token.tokenizeOne = function (fullProp) { // Find first colon - var colonPos = fullProp.indexOf(':'); + var colonPos = fullProp.value.indexOf(':'); if (colonPos < 0) { // This property doesn't have a colon, it's invalid. Let's keep it intact anyway. - return new Token('', fullProp); + return new Token('', fullProp.value); } // Parse parts of the property - var prop = fullProp.substr(0, colonPos).trim(); - var value = fullProp.substr(colonPos + 1).trim(); + var prop = fullProp.value.substr(0, colonPos).trim(); + var value = fullProp.value.substr(colonPos + 1).trim(); var isImportant = false; var importantPos = value.indexOf(important); @@ -124,7 +124,7 @@ module.exports = (function() { continue; } - result.push(t.prop + ':' + t.value + (t.isImportant ? important : '')); + result.push({ value: t.prop + ':' + t.value + (t.isImportant ? important : '') }); } return result; diff --git a/lib/selectors/optimizer.js b/lib/selectors/optimizer.js index e33c87b4..129c3a43 100644 --- a/lib/selectors/optimizer.js +++ b/lib/selectors/optimizer.js @@ -9,28 +9,36 @@ function SelectorsOptimizer(options, context) { this.context = context || {}; } +function valueMapper (object) { return object.value; } + function rebuild(tokens, keepBreaks, isFlatBlock) { var joinCharacter = isFlatBlock ? ';' : (keepBreaks ? lineBreak : ''); var parts = []; + var body; + var selector; for (var i = 0, l = tokens.length; i < l; i++) { var token = tokens[i]; - if (typeof token === 'string') { - parts.push(token); + if (token.kind === 'text') { + parts.push(token.value); continue; } // TODO: broken due to joining/splitting - if (token.body.length === 0 || (token.body.length == 1 && token.body[0] === '')) + if (token.body && (token.body.length === 0 || (token.body.length == 1 && token.body[0].value === ''))) continue; - if (token.block) { - var body = rebuild(token.body, keepBreaks, token.isFlatBlock); + if (token.kind == 'block') { + body = token.isFlatBlock ? + token.body.map(valueMapper).join(';') : + rebuild(token.body, keepBreaks, token.isFlatBlock); if (body.length > 0) - parts.push(token.block + '{' + body + '}'); + parts.push(token.value + '{' + body + '}'); } else { - parts.push(token.selector.join(',') + '{' + token.body.join(';') + '}'); + selector = token.value.map(valueMapper).join(','); + body = token.body.map(valueMapper).join(';'); + parts.push(selector + '{' + body + '}'); } } diff --git a/lib/selectors/optimizers/advanced.js b/lib/selectors/optimizers/advanced.js index cb3d3516..142b1ed2 100644 --- a/lib/selectors/optimizers/advanced.js +++ b/lib/selectors/optimizers/advanced.js @@ -8,6 +8,8 @@ function AdvancedOptimizer(options, context) { this.propertyOptimizer = new PropertyOptimizer(this.options.compatibility, this.options.aggressiveMerging, context); } +function valueMapper(object) { return object.value; } + AdvancedOptimizer.prototype.isSpecial = function (selector) { return this.options.compatibility.selectors.special.test(selector); }; @@ -18,10 +20,10 @@ AdvancedOptimizer.prototype.removeDuplicates = function (tokens) { for (var i = 0, l = tokens.length; i < l; i++) { var token = tokens[i]; - if (typeof token == 'string' || token.block) + if (token.kind != 'selector') continue; - var id = token.body.join(';') + '@' + token.selector.join(','); + var id = token.body.map(valueMapper).join(';') + '@' + token.value.map(valueMapper).join(','); var alreadyMatched = matched[id]; if (alreadyMatched) { @@ -50,17 +52,17 @@ AdvancedOptimizer.prototype.mergeAdjacent = function (tokens) { for (var i = 0, l = tokens.length; i < l; i++) { var token = tokens[i]; - if (typeof token == 'string' || token.block) + if (token.kind != 'selector') continue; // TODO: broken due to joining/splitting - if (lastToken.selector && token.selector.join(',') == lastToken.selector.join(',')) { + if (lastToken.kind == 'selector' && token.value.map(valueMapper).join(',') == lastToken.value.map(valueMapper).join(',')) { var joinAt = [lastToken.body.length]; - lastToken.body = this.propertyOptimizer.process(token.selector, lastToken.body.concat(token.body), joinAt, true); + lastToken.body = this.propertyOptimizer.process(token.value, lastToken.body.concat(token.body), joinAt, true); forRemoval.push(i); // TODO: broken due to joining/splitting - } else if (lastToken.body && token.body.join(';') == lastToken.body.join(';') && !this.isSpecial(token.selector.join(',')) && !this.isSpecial(lastToken.selector.join(','), this.options)) { - lastToken.selector = CleanUp.selectors(lastToken.selector.concat(token.selector)); + } else if (lastToken.body && token.body.map(valueMapper).join(';') == lastToken.body.map(valueMapper).join(';') && !this.isSpecial(token.value.map(valueMapper).join(',')) && !this.isSpecial(lastToken.value.map(valueMapper).join(','), this.options)) { + lastToken.value = CleanUp.selectors(lastToken.value.concat(token.value)); forRemoval.push(i); } else { lastToken = token; @@ -81,13 +83,13 @@ AdvancedOptimizer.prototype.reduceNonAdjacent = function (tokens) { for (var i = tokens.length - 1; i >= 0; i--) { var token = tokens[i]; - if (typeof token == 'string' || token.block) + if (token.kind != 'selector') continue; - var complexSelector = token.selector; - var selectors = complexSelector.length > 1 && !this.isSpecial(complexSelector.join(','), this.options) ? - [complexSelector.join(',')].concat(complexSelector) : - [complexSelector.join(',')]; + var complexSelector = token.value; + var selectors = complexSelector.length > 1 && !this.isSpecial(complexSelector.map(valueMapper).join(','), this.options) ? + [complexSelector.map(valueMapper).join(',')].concat(complexSelector.map(valueMapper)) : + [complexSelector.map(valueMapper).join(',')]; for (var j = 0, m = selectors.length; j < m; j++) { var selector = selectors[j]; @@ -100,7 +102,7 @@ AdvancedOptimizer.prototype.reduceNonAdjacent = function (tokens) { // TODO: broken due to joining/splitting candidates[selector].push({ where: i, - partial: selector != complexSelector.join(',') + partial: selector != complexSelector.map(valueMapper).join(',') }); } } @@ -170,7 +172,7 @@ AdvancedOptimizer.prototype.reduceComplexNonAdjacentCases = function (tokens, po }, callback: function (token, newBody, processedCount, tokenIdx) { if (tokenIdx === 0) - reducedBodies.push(newBody.join(';')); + reducedBodies.push(newBody.map(valueMapper).join(';')); } }); @@ -178,7 +180,9 @@ AdvancedOptimizer.prototype.reduceComplexNonAdjacentCases = function (tokens, po continue allSelectors; } - intoToken.body = reducedBodies[0].split(';'); + intoToken.body = reducedBodies[0].split(';').map(function (property) { + return { value: property }; + }); reduced = true; } @@ -200,7 +204,7 @@ AdvancedOptimizer.prototype.reduceSelector = function (tokens, selector, data, o var body = token.body; bodies = bodies.concat(body); - splitBodies.push(body); + splitBodies.push(body.map(valueMapper)); processedTokens.push(where); } @@ -217,7 +221,7 @@ AdvancedOptimizer.prototype.reduceSelector = function (tokens, selector, data, o var tokenIdx = processedCount - 1; while (tokenIdx >= 0) { - if ((tokenIdx === 0 || splitBodies[tokenIdx].indexOf(optimizedProperties[propertyIdx]) > -1) && propertyIdx > -1) { + if ((tokenIdx === 0 || (optimizedProperties[propertyIdx] && splitBodies[tokenIdx].indexOf(optimizedProperties[propertyIdx].value) > -1)) && propertyIdx > -1) { propertyIdx--; continue; } @@ -233,9 +237,9 @@ function optimizeProperties(tokens, propertyOptimizer) { for (var i = 0, l = tokens.length; i < l; i++) { var token = tokens[i]; - if (token.selector) { - token.body = propertyOptimizer.process(token.selector, token.body, false, true); - } else if (token.block) { + if (token.kind == 'selector') { + token.body = propertyOptimizer.process(token.value, token.body, false, true); + } else if (token.kind == 'block') { optimizeProperties(token.body, propertyOptimizer); } } @@ -246,7 +250,7 @@ AdvancedOptimizer.prototype.optimize = function (tokens) { function _optimize(tokens) { tokens.forEach(function (token) { - if (token.block) + if (token.kind == 'block') _optimize(token.body); }); diff --git a/lib/selectors/optimizers/clean-up.js b/lib/selectors/optimizers/clean-up.js index d1a8771c..dbe5fb02 100644 --- a/lib/selectors/optimizers/clean-up.js +++ b/lib/selectors/optimizers/clean-up.js @@ -7,7 +7,7 @@ var CleanUp = { var plain = []; for (var i = 0, l = selectors.length; i < l; i++) { - var selector = selectors[i]; + var selector = selectors[i].value; var reduced = selector.replace(/\s*([>\+\~])\s*/g, '$1'); if (selector.indexOf('*') > -1) { @@ -23,7 +23,9 @@ var CleanUp = { plain.push(reduced); } - return plain.sort(); + return plain.sort().map(function (selector) { + return { value: selector }; + }); }, block: function (block) { diff --git a/lib/selectors/optimizers/simple.js b/lib/selectors/optimizers/simple.js index 8fd988ea..28e33202 100644 --- a/lib/selectors/optimizers/simple.js +++ b/lib/selectors/optimizers/simple.js @@ -30,14 +30,14 @@ function removeUnsupported(token, compatibility) { return; var supported = []; - for (var i = 0, l = token.selector.length; i < l; i++) { - var selector = token.selector[i]; + for (var i = 0, l = token.value.length; i < l; i++) { + var selector = token.value[i]; - if (selector.indexOf('*+html ') === -1 && selector.indexOf('*:first-child+html ') === -1) + if (selector.value.indexOf('*+html ') === -1 && selector.value.indexOf('*:first-child+html ') === -1) supported.push(selector); } - token.selector = supported; + token.value = supported; } var valueMinifiers = { @@ -173,7 +173,7 @@ function reduce(body, options) { var reduced = []; for (var i = 0, l = body.length; i < l; i++) { - var token = body[i]; + var token = body[i].value; var firstColon = token.indexOf(':'); var property = token.substring(0, firstColon); var value = token.substring(firstColon + 1); @@ -199,7 +199,7 @@ function reduce(body, options) { value = multipleZerosMinifier(property, value); value = colorMininifier(property, value, options.compatibility); - reduced.push(property + ':' + value + (important ? '!important' : '')); + reduced.push({ value: property + ':' + value + (important ? '!important' : '') }); } return reduced; @@ -216,32 +216,32 @@ SimpleOptimizer.prototype.optimize = function(tokens) { if (!token) break; - if (token.selector) { - token.selector = CleanUp.selectors(token.selector); + if (token.kind == 'selector') { + token.value = CleanUp.selectors(token.value); removeUnsupported(token, self.options.compatibility); - if (token.selector.length === 0) { + if (token.value.length === 0) { tokens.splice(i, 1); i--; continue; } token.body = reduce(token.body, self.options); - } else if (token.block) { - token.block = CleanUp.block(token.block); + } else if (token.kind == 'block') { + token.value = CleanUp.block(token.value); if (token.isFlatBlock) token.body = reduce(token.body, self.options); else _optimize(token.body); - } else if (typeof token === 'string') { - if (CHARSET_REGEXP.test(token)) { - if (hasCharset || token.indexOf(CHARSET_TOKEN) == -1) { + } else if (token.kind == 'text') { + 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(token.replace(CHARSET_REGEXP, CHARSET_TOKEN)); + tokens.unshift({ kind: 'text', value: token.value.replace(CHARSET_REGEXP, CHARSET_TOKEN) }); } } } diff --git a/lib/selectors/tokenizer.js b/lib/selectors/tokenizer.js index b44af56d..8717d31c 100644 --- a/lib/selectors/tokenizer.js +++ b/lib/selectors/tokenizer.js @@ -45,7 +45,7 @@ function extractProperties(string) { .replace(TRAILING_SEMICOLON, ''); return extracted.length > 0 ? - extracted.split(';') : + extracted.split(';').map(function (property) { return { value: property }; }) : []; } @@ -56,7 +56,9 @@ function extractSelectors(string) { .replace(WHITESPACE_COMMA, ',') .trim(); - return new Splitter(',').split(extracted); + return new Splitter(',').split(extracted).map(function (selector) { + return { value: selector }; + }); } function extractBlock(string) { @@ -123,7 +125,7 @@ function tokenize(context) { if (!next) { var whatsLeft = context.chunk.substring(context.cursor); if (whatsLeft.length > 0) { - tokenized.push(whatsLeft); + tokenized.push({ kind: 'text', value: whatsLeft }); context.cursor += whatsLeft.length; } break; @@ -148,7 +150,7 @@ function tokenize(context) { nextEnd = chunk.indexOf(';', nextSpecial + 1); var single = extractBlock(chunk.substring(context.cursor, nextEnd + 1)); - tokenized.push(single); + tokenized.push({ kind: 'text', value: single }); context.cursor = nextEnd + 1; } else { @@ -166,12 +168,12 @@ function tokenize(context) { context.mode = oldMode; - tokenized.push({ block: block, body: specialBody, isFlatBlock: isFlat }); + tokenized.push({ kind: 'block', value: block, body: specialBody, isFlatBlock: isFlat }); } } else if (what == 'escape') { nextEnd = chunk.indexOf('__', nextSpecial + 1); var escaped = chunk.substring(context.cursor, nextEnd + 2); - tokenized.push(escaped); + tokenized.push({ kind: 'text', value: escaped }); context.cursor = nextEnd + 2; } else if (what == 'bodyStart') { @@ -184,7 +186,7 @@ function tokenize(context) { context.mode = oldMode; - tokenized.push({ selector: selector, body: body }); + tokenized.push({ kind: 'selector', value: selector, body: body }); } else if (what == 'bodyEnd') { // extra closing brace at the top level can be safely ignored if (context.mode == 'top') { diff --git a/test/selectors/optimizers/simple-test.js b/test/selectors/optimizers/simple-test.js index f12ed648..fceefdba 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().toTokens(source); new SimpleOptimizer(options).optimize(tokens); - assert.deepEqual(tokens[0] ? tokens[0].selector : null, selectors); + assert.deepEqual(tokens[0] ? tokens[0].value : null, selectors); }; } @@ -38,8 +38,9 @@ function propertyContext(group, specs, options) { return function (source) { var tokens = new Tokenizer().toTokens(source); new SimpleOptimizer(options).optimize(tokens); + var value = tokens[0].body.map(function (property) { return property.value; }); - assert.deepEqual(tokens[0].body, selectors); + assert.deepEqual(value, selectors); }; } @@ -58,15 +59,15 @@ vows.describe(SimpleOptimizer) selectorContext('default', { 'optimized': [ 'a{}', - ['a'] + [{ value: 'a' }] ], 'whitespace': [ ' div > span{}', - ['div>span'] + [{ value: 'div>span' }] ], 'line breaks': [ ' div >\n\r\n span{}', - ['div>span'] + [{ value: 'div>span' }] ], '+html': [ '*+html .foo{display:inline}', @@ -86,7 +87,7 @@ vows.describe(SimpleOptimizer) ], '+html - complex': [ '*+html .foo,.bar{display:inline}', - ['.bar'] + [{ value: '.bar' }] ] }, { compatibility: 'ie8' }) ) @@ -94,11 +95,11 @@ vows.describe(SimpleOptimizer) selectorContext('ie7', { '+html': [ '*+html .foo{display:inline}', - ['*+html .foo'] + [{ value: '*+html .foo' }] ], '+html - complex': [ '*+html .foo,.bar{display:inline}', - ['*+html .foo', '.bar'] + [{ value: '*+html .foo' }, { value: '.bar' }] ] }, { compatibility: 'ie7' }) ) diff --git a/test/selectors/tokenizer-test.js b/test/selectors/tokenizer-test.js index 5c08c7ce..cdcacdc2 100644 --- a/test/selectors/tokenizer-test.js +++ b/test/selectors/tokenizer-test.js @@ -2,7 +2,7 @@ var vows = require('vows'); var assert = require('assert'); var Tokenizer = require('../../lib/selectors/tokenizer'); -function tokenizerContext(config) { +function tokenizerContext(name, specs) { var ctx = {}; function tokenized(target) { @@ -12,10 +12,10 @@ function tokenizerContext(config) { }; } - for (var test in config) { + for (var test in specs) { ctx[test] = { - topic: config[test][0], - tokenized: tokenized(config[test][1]) + topic: specs[test][0], + tokenized: tokenized(specs[test][1]) }; } @@ -24,81 +24,179 @@ function tokenizerContext(config) { vows.describe(Tokenizer) .addBatch( - tokenizerContext({ + tokenizerContext('basic', { 'no content': [ '', [] ], 'an escaped content': [ '__ESCAPED_COMMENT_CLEAN_CSS0__', - ['__ESCAPED_COMMENT_CLEAN_CSS0__'] + [{ + kind: 'text', + value: '__ESCAPED_COMMENT_CLEAN_CSS0__' + }] ], 'an empty selector': [ 'a{}', - [{ selector: ['a'], body: [] }] + [{ + kind: 'selector', + value: [{ value: 'a' }], + body: [] + }] ], 'an empty selector with whitespace': [ 'a{ \n }', - [{ selector: ['a'], body: [] }] + [{ + kind: 'selector', + value: [{ value: 'a' }], + body: [] + }] ], 'a selector': [ 'a{color:red}', - [{ selector: ['a'], body: ['color:red'] }] + [{ + kind: 'selector', + value: [{ value: 'a' }], + body: [{ value: 'color:red' }] + }] ], 'a selector with whitespace': [ 'a {color:red;\n\ndisplay : block }', - [{ selector: ['a'], body: ['color:red', 'display:block'] }] + [{ + kind: 'selector', + value: [{ value: 'a' }], + body: [ + { value: 'color:red' }, + { value: 'display:block' + }] + }] ], 'a selector with whitespace in functions': [ 'a{color:rgba( 255, 255, 0, 0.5 )}', - [{ selector: ['a'], body: ['color:rgba(255,255,0,0.5)'] }] + [{ + kind: 'selector', + value: [{ value: 'a' }], + body: [{ value: 'color:rgba(255,255,0,0.5)' }] + }] ], 'a selector with empty properties': [ 'a{color:red; ; ; ;}', - [{ selector: ['a'], body: ['color:red'] }] + [{ + kind: 'selector', + value: [{ value: 'a' }], + body: [{ value: 'color:red' }] + }] ], 'a selector with quoted attribute': [ 'a[data-kind=__ESCAPED_FREE_TEXT_CLEAN_CSS0__]{color:red}', - [{ selector: ['a[data-kind=__ESCAPED_FREE_TEXT_CLEAN_CSS0__]'], body: ['color:red'] }] + [{ + kind: 'selector', + value: [{ value: 'a[data-kind=__ESCAPED_FREE_TEXT_CLEAN_CSS0__]' }], + body: [{ value: 'color:red' }] + }] ], 'a double selector': [ 'a,\n\ndiv.class > p {color:red}', - [{ selector: ['a', 'div.class > p'], body: ['color:red'] }] + [{ + kind: 'selector', + value: [ + { value: 'a' }, + { value: 'div.class > p' } + ], + body: [{ value: 'color:red' }] + }] ], 'two selectors': [ 'a{color:red}div{color:blue}', [ - { selector: ['a'], body: ['color:red'] }, - { selector: ['div'], body: ['color:blue'] } + { + kind: 'selector', + value: [{ value: 'a' }], + body: [{ value: 'color:red' }] + }, + { + kind: 'selector', + value: [{ value: 'div' }], + body: [{ value: 'color:blue' }] + } ] ], 'media query': [ '@media (min-width:980px){}', - [{ block: '@media (min-width:980px)', body: [], isFlatBlock: false }] + [{ + kind: 'block', + value: '@media (min-width:980px)', + body: [], + isFlatBlock: false + }] ], 'media query with selectors': [ '@media (min-width:980px){a{color:red}}', - [{ block: '@media (min-width:980px)', body: [{ selector: ['a'], body: ['color:red'] }], isFlatBlock: false }] + [{ + kind: 'block', + value: '@media (min-width:980px)', + body: [{ + kind: 'selector', + value: [{ value: 'a' }], + body: [{ value: 'color:red' }] + }], + isFlatBlock: false + }] ], '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}}', - [{ 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)', body: [{ selector: ['a'], body: ['color:#000'] }], isFlatBlock: false }] + [{ + 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 + }] ], 'font-face': [ '@font-face{font-family: fontName;font-size:12px}', - [{ block: '@font-face', body: ['font-family:fontName', 'font-size:12px'], isFlatBlock: true }] + [{ + kind: 'block', + value: '@font-face', + body: [ + { value: 'font-family:fontName' }, + { value: 'font-size:12px' } + ], + isFlatBlock: true + }] ], 'charset': [ '@charset \'utf-8\';a{color:red}', - ['@charset \'utf-8\';', { selector: ['a'], body: ['color:red'] }] + [ + { + kind: 'text', + value: '@charset \'utf-8\';' + }, + { + kind: 'selector', + value: [{ value: 'a' }], + body: [{ value: 'color:red' }] + } + ] ], 'charset after a line break': [ '\n@charset \n\'utf-8\';', - ['@charset \'utf-8\';'] + [{ + kind: 'text', + value: '@charset \'utf-8\';' + }] ], 'keyframes with quoted attribute': [ '@keyframes __ESCAPED_FREE_TEXT_CLEAN_CSS0__{}', - [{ block: '@keyframes __ESCAPED_FREE_TEXT_CLEAN_CSS0__', body: [], isFlatBlock: false }] + [{ + kind: 'block', + value: '@keyframes __ESCAPED_FREE_TEXT_CLEAN_CSS0__', + body: [], + isFlatBlock: false + }] ] }) ) -- 2.34.1