├── .gitignore ├── .npmignore ├── .travis.yml ├── History.md ├── Makefile ├── Readme.md ├── component.json ├── examples ├── benchmark.css ├── benchmark.js ├── example.css ├── index.js ├── nesting.css └── nesting.js ├── index.js ├── lib ├── compiler.js ├── lexer.js └── parser.js ├── package.json └── test ├── cases ├── charset.css ├── charset.out.css ├── combinations.css ├── combinations.out.css ├── comments.css ├── comments.out.css ├── fontface.css ├── fontface.out.css ├── media.complex.css ├── media.complex.out.css ├── media.css ├── media.many.css ├── media.many.out.css ├── media.out.css ├── multiline.css ├── multiline.out.css ├── nesting.css ├── nesting.out.css ├── parent.css ├── parent.out.css ├── properties.css ├── properties.out.css ├── pseudos.css ├── pseudos.out.css ├── whitespace.css └── whitespace.out.css └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | testing 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | support 2 | test 3 | examples 4 | *.sock 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.8 4 | - 0.10 5 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | 1.1.1 / 2015-02-23 3 | ================== 4 | 5 | * update debug dep 6 | 7 | 1.1.0 / 2013-06-02 8 | ================== 9 | 10 | * add component support 11 | * add support for regular css multi-line comments. Closes #12 12 | * add stripping of blank lines. Closes #14 13 | * remove blank() from lexer 14 | 15 | 1.0.0 / 2013-02-27 16 | ================== 17 | 18 | * fix & support with comma-delimited selectors 19 | 20 | 0.0.6 / 2013-02-20 21 | ================== 22 | 23 | * fix :fullscreen support 24 | 25 | 0.0.5 / 2013-01-21 26 | ================== 27 | 28 | * fix @fontface support 29 | 30 | 0.0.4 / 2012-12-03 31 | ================== 32 | 33 | * fix vendor pseudos 34 | 35 | 0.0.3 / 2012-12-03 36 | ================== 37 | 38 | * fix complex media query support 39 | 40 | 0.0.2 / 2012-12-03 41 | ================== 42 | 43 | * fix :before / :after 44 | 45 | 0.0.1 / 2010-01-03 46 | ================== 47 | 48 | * Initial release 49 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | test: 3 | @./node_modules/.bin/mocha \ 4 | --require should \ 5 | --reporter spec 6 | 7 | .PHONY: test -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # css-whitespace 2 | 3 | Whitespace significant CSS to regular CSS. Typically used for [Rework](https://github.com/visionmedia/rework), 4 | however you may use it on its own if you like. 5 | 6 | ## Installation 7 | 8 | ``` 9 | $ npm install css-whitespace 10 | $ component install visionmedia/css-whitespace 11 | ``` 12 | 13 | ## API 14 | 15 | ```js 16 | var compile = require('css-whitespace'); 17 | var css = compile('body\n color: #888\n'); 18 | ``` 19 | 20 | ## Example 21 | 22 | ```css 23 | 24 | @charset "utf-8" 25 | 26 | @import "foo.css" 27 | 28 | body 29 | padding: 50px 30 | background: black 31 | color: white 32 | 33 | form 34 | button 35 | border-radius: 5px 36 | padding: 5px 10px 37 | 38 | @media print 39 | body 40 | padding: 0 41 | 42 | button 43 | border-radius: 0 44 | width: 100% 45 | ``` 46 | 47 | yields: 48 | 49 | ```css 50 | @charset "utf-8"; 51 | 52 | @import "foo.css"; 53 | 54 | body { 55 | padding: 50px; 56 | background: black; 57 | color: white; 58 | } 59 | 60 | form button { 61 | border-radius: 5px; 62 | padding: 5px 10px; 63 | } 64 | 65 | @media print { 66 | body { 67 | padding: 0; 68 | } 69 | button { 70 | border-radius: 0; 71 | width: 100%; 72 | } 73 | } 74 | ``` 75 | 76 | ## License 77 | 78 | MIT 79 | -------------------------------------------------------------------------------- /component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "css-whitespace", 3 | "repo": "visionmedia/css-whitespace", 4 | "version": "1.1.1", 5 | "description": "Whitespace significant CSS to regular CSS", 6 | "keywords": [ 7 | "css", 8 | "parser", 9 | "rework" 10 | ], 11 | "dependencies": { 12 | "visionmedia/debug": "*" 13 | }, 14 | "scripts": [ 15 | "index.js", 16 | "lib/compiler.js", 17 | "lib/lexer.js", 18 | "lib/parser.js" 19 | ], 20 | "main": "index.js" 21 | } -------------------------------------------------------------------------------- /examples/benchmark.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var fs = require('fs') 7 | , compile = require('..') 8 | , read = fs.readFileSync 9 | 10 | var str = read('examples/benchmark.css', 'utf8'); 11 | var start = new Date; 12 | var css = compile(str); 13 | 14 | console.log(); 15 | console.log(' duration: %dms', new Date - start); 16 | console.log(' lines: %d', str.split('\n').length); 17 | console.log(' size: %dkb', str.length / 1024 | 0); 18 | console.log(); -------------------------------------------------------------------------------- /examples/example.css: -------------------------------------------------------------------------------- 1 | 2 | @charset "utf-8" 3 | @import "foo.css" 4 | @import "bar.css" 5 | 6 | button 7 | &:hover 8 | color: red 9 | &:active 10 | color: blue 11 | 12 | ul 13 | li 14 | a 15 | .something 16 | color: white 17 | 18 | button 19 | // moar comments 20 | // hurrrrrrrr 21 | border-radius: 5px 22 | padding: 5px 10px 23 | box-shadow: 24 | 0 0 3px red, 25 | 0 0 10px green, 26 | inset 0 0 3px blue 27 | 28 | @media print 29 | // some stuff 30 | body 31 | padding: 0 32 | 33 | button 34 | border-radius: 0 35 | width: 100% -------------------------------------------------------------------------------- /examples/index.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var fs = require('fs') 7 | , compile = require('..') 8 | , read = fs.readFileSync 9 | 10 | var str = read('examples/example.css', 'utf8'); 11 | var css = compile(str); 12 | console.log(css); -------------------------------------------------------------------------------- /examples/nesting.css: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | 4 | regular multi-line comment 5 | 6 | */ 7 | 8 | /* regular comment */ 9 | 10 | body 11 | background: #888 12 | color: #eee 13 | 14 | // stripped comment 15 | 16 | ul 17 | margin: 0 18 | li 19 | color: white 20 | a 21 | color: blue 22 | 23 | @media print 24 | body 25 | width: 100px 26 | ul 27 | width: 50px 28 | li 29 | list-style: disc 30 | -------------------------------------------------------------------------------- /examples/nesting.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var fs = require('fs') 7 | , compile = require('..') 8 | , read = fs.readFileSync 9 | 10 | var str = read('examples/nesting.css', 'utf8'); 11 | var css = compile(str); 12 | console.log(css); -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var parse = require('./lib/parser'); 7 | var compile = require('./lib/compiler'); 8 | 9 | /** 10 | * Compile a whitespace significant 11 | * `str` of CSS to the valid CSS 12 | * equivalent. 13 | * 14 | * @param {String} str 15 | * @return {String} 16 | * @api public 17 | */ 18 | 19 | module.exports = function(str){ 20 | return compile(parse(str)); 21 | }; 22 | -------------------------------------------------------------------------------- /lib/compiler.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var debug = require('debug')('css-whitespace:parser'); 7 | 8 | /** 9 | * Compile the given `node`. 10 | * 11 | * @param {Array} node 12 | * @return {String} 13 | * @api private 14 | */ 15 | 16 | module.exports = function(node){ 17 | var indents = 0; 18 | var rules = []; 19 | var stash = []; 20 | var level = 0; 21 | var nest = 0; 22 | 23 | if (debug.enabled) { 24 | var util = require('util'); 25 | console.log(util.inspect(node, false, 12, true)); 26 | } 27 | 28 | return visit(node); 29 | 30 | /** 31 | * Visit `node`. 32 | */ 33 | 34 | function visit(node) { 35 | switch (node[0]) { 36 | case 'root': 37 | return root(node); 38 | case 'rule': 39 | if ('@' == node[1][0][0]) ++nest; 40 | var ret = rule(node); 41 | if ('@' == node[1][0][0]) --nest; 42 | return ret; 43 | case 'block': 44 | ++level; 45 | var ret = block(node); 46 | --level; 47 | return ret; 48 | case 'prop': 49 | return prop(node); 50 | case 'comment': 51 | return comment(node); 52 | default: 53 | throw new Error('invalid node "' + node[0] + '"'); 54 | } 55 | } 56 | 57 | /** 58 | * Visit block. 59 | */ 60 | 61 | function block(node) { 62 | var buf = []; 63 | var nodes = node[1]; 64 | 65 | for (var i = 0; i < nodes.length; ++i) { 66 | buf.push(visit(nodes[i])); 67 | } 68 | 69 | return buf.join(''); 70 | } 71 | 72 | /** 73 | * Visit comment. 74 | */ 75 | 76 | function comment(node) { 77 | return indent() + '/*' + node[1] + '*/\n'; 78 | } 79 | 80 | /** 81 | * Visit prop. 82 | */ 83 | 84 | function prop(node) { 85 | var prop = node[1]; 86 | var val = node[2]; 87 | return indent() + prop + ': ' + val + ';\n'; 88 | } 89 | 90 | /** 91 | * Visit rule. 92 | */ 93 | 94 | function rule(node) { 95 | var font = '@font-face' == node[1][0].trim(); 96 | var rule = node[1]; 97 | var block = node[2]; 98 | var buf = ''; 99 | 100 | if (!block) return rule.join('') + ';'; 101 | 102 | rules.push(node); 103 | 104 | if ('@' == rule[0][0] && !font) { 105 | buf = join(rules) + ' {\n'; 106 | visit(block); 107 | buf += stash.join('\n'); 108 | buf += '\n}'; 109 | stash = []; 110 | } else if (nest && !font) { 111 | indents = 1; 112 | buf = join(rules, 1) + ' {\n'; 113 | indents = 2; 114 | buf += visit(block); 115 | buf += ' }'; 116 | indents = 1; 117 | } else { 118 | indents = 0; 119 | buf = join(rules) + ' {\n' 120 | indents = 1; 121 | buf += visit(block); 122 | indents = 0; 123 | buf += '}'; 124 | if (!hasProperties(block)) buf = ''; 125 | } 126 | 127 | if (rules.length > 1) { 128 | if (hasProperties(block)) stash.push(buf); 129 | buf = ''; 130 | } 131 | 132 | rules.pop(); 133 | 134 | return buf; 135 | } 136 | 137 | /** 138 | * Visit root. 139 | */ 140 | 141 | function root(node) { 142 | var buf = []; 143 | for (var i = 0; i < node[1].length; ++i) { 144 | buf.push(visit(node[1][i])); 145 | if (stash.length) { 146 | buf = buf.concat(stash); 147 | stash = []; 148 | } 149 | } 150 | return buf.join('\n\n'); 151 | } 152 | 153 | /** 154 | * Join the given rules. 155 | * 156 | * @param {Array} rules 157 | * @param {Number} [offset] 158 | * @return {String} 159 | * @api private 160 | */ 161 | 162 | function join(rules, offset) { 163 | offset = offset || 0; 164 | var selectors = []; 165 | var buf = []; 166 | var curr; 167 | var next; 168 | 169 | function compile(rules, i) { 170 | if (offset != i) { 171 | rules[i][1].forEach(function(selector){ 172 | var parent = ~selector.indexOf('&'); 173 | selector = selector.replace('&', ''); 174 | buf.unshift(parent ? selector : ' ' + selector); 175 | compile(rules, i - 1); 176 | buf.shift(); 177 | }); 178 | } else { 179 | rules[i][1].forEach(function(selector){ 180 | var tail = buf.join(''); 181 | selectors.push(indent() + selector + tail); 182 | }); 183 | } 184 | } 185 | 186 | compile(rules, rules.length - 1); 187 | 188 | return selectors.join(',\n'); 189 | } 190 | 191 | /** 192 | * Return indent. 193 | */ 194 | 195 | function indent() { 196 | return Array(indents + 1).join(' '); 197 | } 198 | }; 199 | 200 | /** 201 | * Check if `block` has properties. 202 | * 203 | * @param {Array} block 204 | * @return {Boolean} 205 | * @api private 206 | */ 207 | 208 | function hasProperties(block) { 209 | var nodes = block[1]; 210 | for (var i = 0; i < nodes.length; ++i) { 211 | if ('prop' == nodes[i][0]) return true; 212 | } 213 | return false; 214 | } 215 | 216 | /** 217 | * Blank string filter. 218 | * 219 | * @api private 220 | */ 221 | 222 | function blank(str) { 223 | return '' != str; 224 | } 225 | -------------------------------------------------------------------------------- /lib/lexer.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Pesudo selectors. 4 | */ 5 | 6 | var pseudos = [ 7 | ':selection', 8 | 'fullscreen', 9 | 'nth-child', 10 | 'first-child', 11 | 'last-child', 12 | 'link', 13 | 'visited', 14 | 'hover', 15 | 'active', 16 | 'focus', 17 | 'first-letter', 18 | 'first-line', 19 | 'before', 20 | 'after', 21 | 'lang', 22 | 'enabled', 23 | 'disabled', 24 | 'only-child', 25 | 'only-of-type', 26 | 'first-of-type', 27 | 'last-of-type', 28 | 'nth-last-of-type', 29 | 'nth-of-type', 30 | 'root', 31 | 'empty', 32 | 'target', 33 | 'not', 34 | '-o', 35 | '-ms', 36 | '-moz', 37 | '-webkit' 38 | ] 39 | 40 | /** 41 | * Property regexp. 42 | */ 43 | 44 | pseudos = pseudos.join('|'); 45 | var propre = new RegExp('^ *([-\\w]+):(?!' + pseudos + ') *([^\n]*)'); 46 | 47 | /** 48 | * Scan the given `str` returning tokens. 49 | * 50 | * @param {String} str 51 | * @return {Array} 52 | * @api private 53 | */ 54 | 55 | module.exports = function(str) { 56 | var indents = [0]; 57 | var stash = []; 58 | 59 | // strip blanks 60 | str = str.replace(/\r/g, ''); 61 | str = str.replace(/\n\s*\n/gm, '\n'); 62 | 63 | return scan(); 64 | 65 | /** 66 | * tok+ 67 | */ 68 | 69 | function scan() { 70 | var toks = [] 71 | , curr; 72 | 73 | while (str.length) { 74 | curr = next(); 75 | curr && toks.push(curr); 76 | if (str.length && !curr) { 77 | throw new Error('syntax error near "' + str.slice(0, 10) + '"'); 78 | } 79 | } 80 | 81 | toks = toks.concat(stash); 82 | while (indents.pop()) toks.push(['outdent']); 83 | toks.push(['eos']); 84 | return toks; 85 | } 86 | 87 | /** 88 | * eos 89 | * | indentation 90 | * | rule 91 | */ 92 | 93 | function next() { 94 | return stashed() 95 | || comment() 96 | || csscomment() 97 | || indentation() 98 | || prop() 99 | || rule(); 100 | } 101 | 102 | /** 103 | * Deferred tokens. 104 | */ 105 | 106 | function stashed() { 107 | return stash.shift(); 108 | } 109 | 110 | /** 111 | * Comment. 112 | */ 113 | 114 | function comment() { 115 | var m = str.match(/^\/\/([^\n]*)/); 116 | if (!m) return; 117 | str = str.slice(m[0].length); 118 | return next(); 119 | } 120 | 121 | /** 122 | * Multiline comment. 123 | */ 124 | 125 | function csscomment() { 126 | if ('/' != str[0] || '*' != str[1]) return; 127 | str = str.slice(2); 128 | 129 | var i = 0; 130 | while ('*' != str[i] && '/' != str[i + 1]) ++i; 131 | 132 | var buf = str.slice(0, i); 133 | str = str.slice(buf.length + 2); 134 | 135 | return ['comment', buf]; 136 | } 137 | 138 | /** 139 | * INDENT 140 | * | OUTDENT 141 | */ 142 | 143 | function indentation() { 144 | var spaces = str.match(/^\n( *)/); 145 | if (!spaces) return; 146 | str = str.slice(spaces[0].length); 147 | spaces = spaces[1].length; 148 | var prev = indents[indents.length - 1]; 149 | 150 | // INDENT 151 | if (spaces > prev) return indent(spaces); 152 | 153 | // OUTDENT 154 | if (spaces < prev) return outdent(spaces); 155 | 156 | return next(); 157 | } 158 | 159 | /** 160 | * Indent. 161 | */ 162 | 163 | function indent(spaces) { 164 | indents.push(spaces); 165 | return ['indent']; 166 | } 167 | 168 | /** 169 | * Outdent. 170 | */ 171 | 172 | function outdent(spaces) { 173 | while (indents[indents.length - 1] > spaces) { 174 | indents.pop(); 175 | stash.push(['outdent']); 176 | } 177 | return stashed(); 178 | } 179 | 180 | /** 181 | * Property. 182 | */ 183 | 184 | function prop() { 185 | var m = str.match(propre); 186 | if (!m) return; 187 | str = str.slice(m[0].length); 188 | return ['prop', m[1], m[2]]; 189 | } 190 | 191 | /** 192 | * Rule. 193 | */ 194 | 195 | function rule() { 196 | var m = str.match(/^([^\n,]+, *\n|[^\n]+)+/); 197 | if (!m) return; 198 | str = str.slice(m[0].length); 199 | m = m[0].split(/\s*,\s*/); 200 | return ['rule', m]; 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /lib/parser.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | 5 | var debug = require('debug')('css-whitespace:lexer'); 6 | var scan = require('./lexer'); 7 | 8 | /** 9 | * Parse the given `str`, returning an AST. 10 | * 11 | * @param {String} str 12 | * @return {Array} 13 | * @api private 14 | */ 15 | 16 | module.exports = function(str) { 17 | var toks = scan(str); 18 | 19 | if (debug.enabled) { 20 | var util = require('util'); 21 | console.log(util.inspect(toks, false, 12, true)); 22 | } 23 | 24 | return stmts(); 25 | 26 | /** 27 | * Grab the next token. 28 | */ 29 | 30 | function next() { 31 | return toks.shift(); 32 | } 33 | 34 | /** 35 | * Check if the next token is `type`. 36 | */ 37 | 38 | function is(type) { 39 | if (type == toks[0][0]) return true; 40 | } 41 | 42 | /** 43 | * Expect `type` or throw. 44 | */ 45 | 46 | function expect(type) { 47 | if (is(type)) return next(); 48 | throw new Error('expected "' + type + '", but got "' + toks[0][0] + '"'); 49 | } 50 | 51 | /** 52 | * stmt+ 53 | */ 54 | 55 | function stmts() { 56 | var stmts = []; 57 | while (!is('eos')) stmts.push(stmt()); 58 | return ['root', stmts]; 59 | } 60 | 61 | /** 62 | * INDENT stmt+ OUTDENT 63 | */ 64 | 65 | function block() { 66 | var props = []; 67 | expect('indent'); 68 | while (!is('outdent')) props.push(stmt()); 69 | expect('outdent'); 70 | return ['block', props]; 71 | } 72 | 73 | /** 74 | * rule 75 | * | prop 76 | */ 77 | 78 | function stmt() { 79 | if (is('rule')) return rule(); 80 | if (is('prop')) return prop(); 81 | return next(); 82 | } 83 | 84 | /** 85 | * prop 86 | * | prop INDENT rule* OUTDENT 87 | */ 88 | 89 | function prop() { 90 | var prop = next(); 91 | if (is('indent')) { 92 | next(); 93 | while (!is('outdent')) { 94 | var tok = next(); 95 | prop[2] += ' ' + tok[1].join(', '); 96 | } 97 | expect('outdent'); 98 | } 99 | return prop; 100 | } 101 | 102 | /** 103 | * rule block? 104 | */ 105 | 106 | function rule() { 107 | var rule = next(); 108 | if (is('indent')) rule.push(block()); 109 | return rule; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "css-whitespace", 3 | "version": "1.1.1", 4 | "description": "Whitespace significant CSS to regular CSS", 5 | "keywords": [ 6 | "css", 7 | "parser", 8 | "rework" 9 | ], 10 | "author": "TJ Holowaychuk ", 11 | "dependencies": { 12 | "debug": "2" 13 | }, 14 | "devDependencies": { 15 | "mocha": "*", 16 | "should": "*" 17 | }, 18 | "main": "index", 19 | "scripts": { 20 | "test": "mocha --require should --reporter spec" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "https://github.com/reworkcss/css-whitespace.git" 25 | } 26 | } -------------------------------------------------------------------------------- /test/cases/charset.css: -------------------------------------------------------------------------------- 1 | @charset "ascii" -------------------------------------------------------------------------------- /test/cases/charset.out.css: -------------------------------------------------------------------------------- 1 | @charset "ascii"; -------------------------------------------------------------------------------- /test/cases/combinations.css: -------------------------------------------------------------------------------- 1 | 2 | ol, 3 | ul 4 | margin: 0 5 | li 6 | a 7 | color: blue 8 | 9 | @media print 10 | .content 11 | ul 12 | li.first, 13 | li.last, 14 | li.odd 15 | display: none 16 | body 17 | padding: 0 18 | 19 | ul, 20 | ol 21 | li 22 | a 23 | color: blue -------------------------------------------------------------------------------- /test/cases/combinations.out.css: -------------------------------------------------------------------------------- 1 | ol, 2 | ul { 3 | margin: 0; 4 | } 5 | 6 | ol li a, 7 | ul li a { 8 | color: blue; 9 | } 10 | 11 | @media print { 12 | .content ul li.first, 13 | .content ul li.last, 14 | .content ul li.odd { 15 | display: none; 16 | } 17 | body { 18 | padding: 0; 19 | } 20 | ul li a, 21 | ol li a { 22 | color: blue; 23 | } 24 | } -------------------------------------------------------------------------------- /test/cases/comments.css: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | 4 | this is a button 5 | 6 | */ 7 | 8 | // foo 9 | button 10 | // bar 11 | color: #eee 12 | // bar 13 | 14 | 15 | 16 | // baz 17 | /* css style */ 18 | background: blue 19 | -------------------------------------------------------------------------------- /test/cases/comments.out.css: -------------------------------------------------------------------------------- 1 | /* 2 | this is a button 3 | */ 4 | 5 | 6 | button { 7 | color: #eee; 8 | /* css style */ 9 | background: blue; 10 | } 11 | -------------------------------------------------------------------------------- /test/cases/fontface.css: -------------------------------------------------------------------------------- 1 | 2 | @font-face 3 | font-family: 'icons' 4 | src: url("/icons/font/icons.eot") 5 | src: url("/icons/font/icons.eot?#iefix") format('embedded-opentype'), url("/icons/font/icons.woff") format('woff'), url("/icons/font/icons.ttf") format('truetype'), url("/icons/font/icons.svg#icons") format('svg') 6 | font-weight: normal 7 | font-style: normal 8 | -------------------------------------------------------------------------------- /test/cases/fontface.out.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'icons'; 3 | src: url("/icons/font/icons.eot"); 4 | src: url("/icons/font/icons.eot?#iefix") format('embedded-opentype'), url("/icons/font/icons.woff") format('woff'), url("/icons/font/icons.ttf") format('truetype'), url("/icons/font/icons.svg#icons") format('svg'); 5 | font-weight: normal; 6 | font-style: normal; 7 | } 8 | -------------------------------------------------------------------------------- /test/cases/media.complex.css: -------------------------------------------------------------------------------- 1 | 2 | @media only screen and (min-device-width: 320px) and (max-device-width: 480px) 3 | body 4 | width: 480px 5 | height: 500px -------------------------------------------------------------------------------- /test/cases/media.complex.out.css: -------------------------------------------------------------------------------- 1 | @media only screen and (min-device-width: 320px) and (max-device-width: 480px) { 2 | body { 3 | width: 480px; 4 | height: 500px; 5 | } 6 | } -------------------------------------------------------------------------------- /test/cases/media.css: -------------------------------------------------------------------------------- 1 | 2 | body 3 | padding: 50px 4 | 5 | @media print 6 | body 7 | padding: 0 8 | 9 | ul 10 | li a 11 | color: blue 12 | text-decoration: underline -------------------------------------------------------------------------------- /test/cases/media.many.css: -------------------------------------------------------------------------------- 1 | 2 | @media screen and (max-width: 481px) 3 | .collection.two, 4 | .collection.three 5 | width: 400px 6 | background: red 7 | .item 8 | margin-left: 0 9 | -------------------------------------------------------------------------------- /test/cases/media.many.out.css: -------------------------------------------------------------------------------- 1 | @media screen and (max-width: 481px) { 2 | .collection.two .item, 3 | .collection.three .item { 4 | margin-left: 0; 5 | } 6 | .collection.two, 7 | .collection.three { 8 | width: 400px; 9 | background: red; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/cases/media.out.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | } 4 | 5 | @media print { 6 | body { 7 | padding: 0; 8 | } 9 | ul li a { 10 | color: blue; 11 | text-decoration: underline; 12 | } 13 | } -------------------------------------------------------------------------------- /test/cases/multiline.css: -------------------------------------------------------------------------------- 1 | 2 | button 3 | border-radius: 5px 4 | padding: 5px 10px 5 | box-shadow: 6 | 0 0 3px red, 7 | 0 0 10px green, 8 | inset 0 0 3px blue 9 | color: blue 10 | -------------------------------------------------------------------------------- /test/cases/multiline.out.css: -------------------------------------------------------------------------------- 1 | button { 2 | border-radius: 5px; 3 | padding: 5px 10px; 4 | box-shadow: 0 0 3px red, 0 0 10px green, inset 0 0 3px blue; 5 | color: blue; 6 | } -------------------------------------------------------------------------------- /test/cases/nesting.css: -------------------------------------------------------------------------------- 1 | 2 | body 3 | padding: 50px 4 | ul 5 | margin: 0 6 | padding: 0 7 | li 8 | list-style: none 9 | a 10 | color: blue -------------------------------------------------------------------------------- /test/cases/nesting.out.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | } 4 | 5 | body ul li a { 6 | color: blue; 7 | } 8 | 9 | body ul li { 10 | list-style: none; 11 | } 12 | 13 | body ul { 14 | margin: 0; 15 | padding: 0; 16 | } -------------------------------------------------------------------------------- /test/cases/parent.css: -------------------------------------------------------------------------------- 1 | 2 | ul 3 | li 4 | background: #eee 5 | &:hover 6 | background: white 7 | &:active 8 | background: #ddd 9 | 10 | ul 11 | &:hover 12 | li 13 | a 14 | color: blue 15 | 16 | something 17 | color: black 18 | &:active, 19 | &:hover, 20 | &.hover 21 | color: white 22 | -------------------------------------------------------------------------------- /test/cases/parent.out.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | ul li:hover { 4 | background: white; 5 | } 6 | 7 | ul li:active { 8 | background: #ddd; 9 | } 10 | 11 | ul li { 12 | background: #eee; 13 | } 14 | 15 | 16 | 17 | ul:hover li a { 18 | color: blue; 19 | } 20 | 21 | something { 22 | color: black; 23 | } 24 | 25 | something:active, 26 | something:hover, 27 | something.hover { 28 | color: white; 29 | } 30 | -------------------------------------------------------------------------------- /test/cases/properties.css: -------------------------------------------------------------------------------- 1 | 2 | body 3 | background: #eee 4 | color: white 5 | 6 | ul li 7 | list-style: none 8 | margin: 0 9 | padding: 0 10 | -------------------------------------------------------------------------------- /test/cases/properties.out.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #eee; 3 | color: white; 4 | } 5 | 6 | ul li { 7 | list-style: none; 8 | margin: 0; 9 | padding: 0; 10 | } -------------------------------------------------------------------------------- /test/cases/pseudos.css: -------------------------------------------------------------------------------- 1 | .input 2 | position: relative 3 | text-align: left 4 | 5 | .input-popup 6 | font-size: 90% 7 | position: absolute 8 | background: rgba(255,255,255,0.975) 9 | border: 1px solid #D4D4D4 10 | border-bottom: none 11 | border-radius: 3px 3px 0 0 12 | box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(0, 0, 0, 0.05) 13 | margin: 0 14 | padding: 0 15 | list-style: none 16 | 17 | .input-popup li 18 | margin: 0 19 | padding: 0 8px 20 | height: 28px 21 | line-height: 28px 22 | color: #444 23 | span.status-icon 24 | float: right 25 | position: relative 26 | top: 8px 27 | .avatar 28 | width: 20px 29 | height: 20px 30 | background-size: cover 31 | background-position: top center 32 | float: left 33 | position: relative 34 | top: 3px 35 | border-radius: 10px 36 | margin-right: 10px 37 | box-shadow: 0 0 0 1px rgba(255,255,255,0.5) 38 | 39 | .input-popup li:first-child 40 | border-radius: 2px 2px 0 0 41 | 42 | .input-popup li:nth-child(2) 43 | background: white 44 | 45 | .input-popup li.selected 46 | background: #92CDF3 47 | color: white 48 | 49 | .input .mr-paragraph-render-target .token 50 | display: inline-block 51 | border: 1px solid rgba(0,0,0,0.08) 52 | background: rgba(0,0,0,0.04) 53 | color: #3F78B0 54 | font-weight: bold 55 | text-shadow: 0 1px 0 white 56 | padding: 0 8px 57 | border-radius: 18px 58 | cursor: pointer 59 | font-size: 90% 60 | a:before 61 | color: white 62 | a:after 63 | color: white 64 | 65 | input:-webkit-autofill ~ .error 66 | pointer-events: none 67 | z-index: inherit 68 | 69 | textarea:focus::-webkit-input-placeholder 70 | color: #cdcdcd -------------------------------------------------------------------------------- /test/cases/pseudos.out.css: -------------------------------------------------------------------------------- 1 | .input { 2 | position: relative; 3 | text-align: left; 4 | } 5 | 6 | .input-popup { 7 | font-size: 90%; 8 | position: absolute; 9 | background: rgba(255,255,255,0.975); 10 | border: 1px solid #D4D4D4; 11 | border-bottom: none; 12 | border-radius: 3px 3px 0 0; 13 | box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(0, 0, 0, 0.05); 14 | margin: 0; 15 | padding: 0; 16 | list-style: none; 17 | } 18 | 19 | .input-popup li { 20 | margin: 0; 21 | padding: 0 8px; 22 | height: 28px; 23 | line-height: 28px; 24 | color: #444; 25 | } 26 | 27 | .input-popup li span.status-icon { 28 | float: right; 29 | position: relative; 30 | top: 8px; 31 | } 32 | 33 | .input-popup li .avatar { 34 | width: 20px; 35 | height: 20px; 36 | background-size: cover; 37 | background-position: top center; 38 | float: left; 39 | position: relative; 40 | top: 3px; 41 | border-radius: 10px; 42 | margin-right: 10px; 43 | box-shadow: 0 0 0 1px rgba(255,255,255,0.5); 44 | } 45 | 46 | .input-popup li:first-child { 47 | border-radius: 2px 2px 0 0; 48 | } 49 | 50 | .input-popup li:nth-child(2) { 51 | background: white; 52 | } 53 | 54 | .input-popup li.selected { 55 | background: #92CDF3; 56 | color: white; 57 | } 58 | 59 | .input .mr-paragraph-render-target .token { 60 | display: inline-block; 61 | border: 1px solid rgba(0,0,0,0.08); 62 | background: rgba(0,0,0,0.04); 63 | color: #3F78B0; 64 | font-weight: bold; 65 | text-shadow: 0 1px 0 white; 66 | padding: 0 8px; 67 | border-radius: 18px; 68 | cursor: pointer; 69 | font-size: 90%; 70 | } 71 | 72 | .input .mr-paragraph-render-target .token a:before { 73 | color: white; 74 | } 75 | 76 | .input .mr-paragraph-render-target .token a:after { 77 | color: white; 78 | } 79 | 80 | input:-webkit-autofill ~ .error { 81 | pointer-events: none; 82 | z-index: inherit; 83 | } 84 | 85 | textarea:focus::-webkit-input-placeholder { 86 | color: #cdcdcd; 87 | } -------------------------------------------------------------------------------- /test/cases/whitespace.css: -------------------------------------------------------------------------------- 1 | 2 | body 3 | ul 4 | 5 | li 6 | 7 | 8 | 9 | a 10 | color: blue 11 | 12 | text-align: center 13 | -------------------------------------------------------------------------------- /test/cases/whitespace.out.css: -------------------------------------------------------------------------------- 1 | body ul li a { 2 | color: blue; 3 | text-align: center; 4 | } 5 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 2 | var compile = require('..'); 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | var read = fs.readFileSync; 6 | var readdir = fs.readdirSync; 7 | 8 | describe('should support', function(){ 9 | readdir('test/cases').forEach(function(file){ 10 | if (~file.indexOf('.out')) return; 11 | var base = path.basename(file, '.css'); 12 | var input = read('test/cases/' + file, 'utf8'); 13 | var output = read('test/cases/' + base + '.out.css', 'utf8'); 14 | 15 | it(base, function(){ 16 | var out = compile(input).trim(); 17 | out.should.equal(output.trim()); 18 | }) 19 | }); 20 | }) 21 | --------------------------------------------------------------------------------