├── .gitignore ├── test ├── cases │ ├── empty │ │ ├── input.css │ │ ├── output.css │ │ ├── compressed.css │ │ └── ast.json │ ├── rule │ │ ├── compressed.css │ │ ├── input.css │ │ ├── output.css │ │ └── ast.json │ ├── charset-linebreak │ │ ├── output.css │ │ ├── compressed.css │ │ ├── input.css │ │ └── ast.json │ ├── comment-url │ │ ├── compressed.css │ │ ├── input.css │ │ ├── output.css │ │ └── ast.json │ ├── comment │ │ ├── compressed.css │ │ ├── input.css │ │ ├── output.css │ │ └── ast.json │ ├── page-linebreak │ │ ├── compressed.css │ │ ├── output.css │ │ ├── input.css │ │ └── ast.json │ ├── colon-space │ │ ├── compressed.css │ │ ├── output.css │ │ ├── input.css │ │ └── ast.json │ ├── hose-linebreak │ │ ├── compressed.css │ │ ├── output.css │ │ ├── input.css │ │ └── ast.json │ ├── host │ │ ├── compressed.css │ │ ├── input.css │ │ ├── output.css │ │ └── ast.json │ ├── quote-escape │ │ ├── compressed.css │ │ ├── input.css │ │ ├── output.css │ │ └── ast.json │ ├── selectors │ │ ├── compressed.css │ │ ├── input.css │ │ ├── output.css │ │ └── ast.json │ ├── charset │ │ ├── compressed.css │ │ ├── input.css │ │ ├── output.css │ │ └── ast.json │ ├── import-linebreak │ │ ├── output.css │ │ ├── compressed.css │ │ ├── input.css │ │ └── ast.json │ ├── at-namespace │ │ ├── input.css │ │ ├── output.css │ │ ├── compressed.css │ │ └── ast.json │ ├── namespace-linebreak │ │ ├── output.css │ │ ├── compressed.css │ │ ├── input.css │ │ └── ast.json │ ├── custom-media-linebreak │ │ ├── compressed.css │ │ ├── output.css │ │ ├── input.css │ │ └── ast.json │ ├── document-linebreak │ │ ├── compressed.css │ │ ├── output.css │ │ ├── input.css │ │ └── ast.json │ ├── keyframes │ │ ├── compressed.css │ │ ├── input.css │ │ ├── output.css │ │ └── ast.json │ ├── rules │ │ ├── compressed.css │ │ ├── input.css │ │ ├── output.css │ │ └── ast.json │ ├── keyframes-linebreak │ │ ├── compressed.css │ │ ├── input.css │ │ ├── output.css │ │ └── ast.json │ ├── no-semi │ │ ├── compressed.css │ │ ├── output.css │ │ ├── input.css │ │ └── ast.json │ ├── supports-linebreak │ │ ├── compressed.css │ │ ├── output.css │ │ ├── input.css │ │ └── ast.json │ ├── wtf │ │ ├── compressed.css │ │ ├── input.css │ │ ├── output.css │ │ └── ast.json │ ├── keyframes-messed │ │ ├── compressed.css │ │ ├── input.css │ │ ├── output.css │ │ └── ast.json │ ├── quoted │ │ ├── compressed.css │ │ ├── input.css │ │ ├── output.css │ │ └── ast.json │ ├── keyframes-vendor │ │ ├── compressed.css │ │ ├── input.css │ │ ├── output.css │ │ └── ast.json │ ├── media-linebreak │ │ ├── compressed.css │ │ ├── output.css │ │ ├── input.css │ │ └── ast.json │ ├── messed-up │ │ ├── compressed.css │ │ ├── output.css │ │ ├── input.css │ │ └── ast.json │ ├── comment-in │ │ ├── compressed.css │ │ ├── output.css │ │ ├── input.css │ │ └── ast.json │ ├── props │ │ ├── compressed.css │ │ ├── output.css │ │ ├── input.css │ │ └── ast.json │ ├── keyframes-advanced │ │ ├── compressed.css │ │ ├── input.css │ │ ├── output.css │ │ └── ast.json │ ├── namespace │ │ ├── compressed.css │ │ ├── input.css │ │ ├── output.css │ │ └── ast.json │ ├── document │ │ ├── compressed.css │ │ ├── input.css │ │ ├── output.css │ │ └── ast.json │ ├── paged-media │ │ ├── compressed.css │ │ ├── input.css │ │ ├── output.css │ │ └── ast.json │ ├── supports │ │ ├── compressed.css │ │ ├── input.css │ │ ├── output.css │ │ └── ast.json │ ├── custom-media │ │ ├── compressed.css │ │ ├── input.css │ │ ├── output.css │ │ └── ast.json │ ├── keyframes-complex │ │ ├── compressed.css │ │ ├── input.css │ │ ├── output.css │ │ └── ast.json │ ├── font-face │ │ ├── compressed.css │ │ ├── input.css │ │ ├── output.css │ │ └── ast.json │ ├── media │ │ ├── compressed.css │ │ ├── input.css │ │ ├── output.css │ │ └── ast.json │ ├── font-face-linebreak │ │ ├── compressed.css │ │ ├── output.css │ │ ├── input.css │ │ └── ast.json │ ├── media-messed │ │ ├── compressed.css │ │ ├── output.css │ │ ├── input.css │ │ └── ast.json │ ├── import │ │ ├── compressed.css │ │ ├── input.css │ │ ├── output.css │ │ └── ast.json │ ├── import-messed │ │ ├── compressed.css │ │ ├── input.css │ │ ├── output.css │ │ └── ast.json │ ├── comma-selector-function │ │ ├── compressed.css │ │ ├── input.css │ │ ├── output.css │ │ └── ast.json │ ├── comma-attribute │ │ ├── compressed.css │ │ ├── input.css │ │ ├── output.css │ │ └── ast.json │ └── escapes │ │ ├── compressed.css │ │ ├── input.css │ │ └── output.css ├── source-map │ ├── apply.scss │ ├── apply.css │ ├── apply.css.map │ └── test.css ├── cases.js ├── parse.js └── stringify.js ├── .travis.yml ├── index.js ├── benchmark ├── index.js ├── LICENSES └── small.css ├── package.json ├── lib ├── stringify │ ├── compiler.js │ ├── index.js │ ├── source-map-support.js │ ├── compress.js │ └── identity.js └── parse │ └── index.js ├── LICENSE ├── generate-tests.js ├── History.md └── Readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /test/cases/empty/input.css: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/cases/empty/output.css: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/cases/empty/compressed.css: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/cases/rule/compressed.css: -------------------------------------------------------------------------------- 1 | foo{bar:'baz';} 2 | -------------------------------------------------------------------------------- /test/source-map/apply.scss: -------------------------------------------------------------------------------- 1 | tobi { name: 'tobi'; } 2 | -------------------------------------------------------------------------------- /test/cases/charset-linebreak/output.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; -------------------------------------------------------------------------------- /test/cases/comment-url/compressed.css: -------------------------------------------------------------------------------- 1 | foo{bar:baz;} 2 | -------------------------------------------------------------------------------- /test/cases/charset-linebreak/compressed.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; -------------------------------------------------------------------------------- /test/cases/comment/compressed.css: -------------------------------------------------------------------------------- 1 | head,body{foo:'bar';} 2 | -------------------------------------------------------------------------------- /test/cases/page-linebreak/compressed.css: -------------------------------------------------------------------------------- 1 | @page toc{color:black;} -------------------------------------------------------------------------------- /test/cases/rule/input.css: -------------------------------------------------------------------------------- 1 | foo { 2 | bar: 'baz'; 3 | } 4 | -------------------------------------------------------------------------------- /test/cases/rule/output.css: -------------------------------------------------------------------------------- 1 | foo { 2 | bar: 'baz'; 3 | } 4 | -------------------------------------------------------------------------------- /test/cases/colon-space/compressed.css: -------------------------------------------------------------------------------- 1 | a{margin:auto;padding:0;} 2 | -------------------------------------------------------------------------------- /test/cases/hose-linebreak/compressed.css: -------------------------------------------------------------------------------- 1 | @host{:scope{color:white;}} -------------------------------------------------------------------------------- /test/cases/host/compressed.css: -------------------------------------------------------------------------------- 1 | @host{:scope{display:block;}} 2 | -------------------------------------------------------------------------------- /test/cases/quote-escape/compressed.css: -------------------------------------------------------------------------------- 1 | p[qwe="a\",b"]{color:red;} -------------------------------------------------------------------------------- /test/cases/quote-escape/input.css: -------------------------------------------------------------------------------- 1 | p[qwe="a\",b"] { color: red } 2 | -------------------------------------------------------------------------------- /test/cases/selectors/compressed.css: -------------------------------------------------------------------------------- 1 | foo,bar,baz{color:'black';} 2 | -------------------------------------------------------------------------------- /test/cases/page-linebreak/output.css: -------------------------------------------------------------------------------- 1 | @page toc { 2 | color: black; 3 | } -------------------------------------------------------------------------------- /test/cases/charset-linebreak/input.css: -------------------------------------------------------------------------------- 1 | @charset 2 | "UTF-8" 3 | ; 4 | -------------------------------------------------------------------------------- /test/cases/charset/compressed.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8";@charset 'iso-8859-15'; 2 | -------------------------------------------------------------------------------- /test/cases/import-linebreak/output.css: -------------------------------------------------------------------------------- 1 | @import url(test.css) 2 | screen; -------------------------------------------------------------------------------- /test/cases/quote-escape/output.css: -------------------------------------------------------------------------------- 1 | p[qwe="a\",b"] { 2 | color: red; 3 | } -------------------------------------------------------------------------------- /test/cases/at-namespace/input.css: -------------------------------------------------------------------------------- 1 | @namespace svg "http://www.w3.org/2000/svg"; 2 | -------------------------------------------------------------------------------- /test/cases/at-namespace/output.css: -------------------------------------------------------------------------------- 1 | @namespace svg "http://www.w3.org/2000/svg"; 2 | -------------------------------------------------------------------------------- /test/cases/import-linebreak/compressed.css: -------------------------------------------------------------------------------- 1 | @import url(test.css) 2 | screen; -------------------------------------------------------------------------------- /test/cases/namespace-linebreak/output.css: -------------------------------------------------------------------------------- 1 | @namespace "http://www.w3.org/1999/xhtml"; -------------------------------------------------------------------------------- /test/cases/at-namespace/compressed.css: -------------------------------------------------------------------------------- 1 | @namespace svg "http://www.w3.org/2000/svg"; 2 | -------------------------------------------------------------------------------- /test/cases/colon-space/output.css: -------------------------------------------------------------------------------- 1 | a { 2 | margin: auto; 3 | padding: 0; 4 | } 5 | -------------------------------------------------------------------------------- /test/cases/custom-media-linebreak/compressed.css: -------------------------------------------------------------------------------- 1 | @custom-media --test (min-width: 200px); -------------------------------------------------------------------------------- /test/cases/custom-media-linebreak/output.css: -------------------------------------------------------------------------------- 1 | @custom-media --test (min-width: 200px); -------------------------------------------------------------------------------- /test/cases/document-linebreak/compressed.css: -------------------------------------------------------------------------------- 1 | @document url-prefix(){.test{color:blue;}} -------------------------------------------------------------------------------- /test/cases/keyframes/compressed.css: -------------------------------------------------------------------------------- 1 | @keyframes fade{from{opacity:0;}to{opacity:1;}} 2 | -------------------------------------------------------------------------------- /test/cases/namespace-linebreak/compressed.css: -------------------------------------------------------------------------------- 1 | @namespace "http://www.w3.org/1999/xhtml"; -------------------------------------------------------------------------------- /test/cases/rules/compressed.css: -------------------------------------------------------------------------------- 1 | tobi{name:'tobi';age:2;}loki{name:'loki';age:1;} 2 | -------------------------------------------------------------------------------- /test/cases/selectors/input.css: -------------------------------------------------------------------------------- 1 | foo, 2 | bar, 3 | baz { 4 | color: 'black'; 5 | } 6 | -------------------------------------------------------------------------------- /test/cases/selectors/output.css: -------------------------------------------------------------------------------- 1 | foo, 2 | bar, 3 | baz { 4 | color: 'black'; 5 | } 6 | -------------------------------------------------------------------------------- /test/cases/colon-space/input.css: -------------------------------------------------------------------------------- 1 | a { 2 | margin : auto; 3 | padding : 0; 4 | } 5 | -------------------------------------------------------------------------------- /test/cases/host/input.css: -------------------------------------------------------------------------------- 1 | @host { 2 | :scope { 3 | display: block; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/cases/host/output.css: -------------------------------------------------------------------------------- 1 | @host { 2 | :scope { 3 | display: block; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/cases/keyframes-linebreak/compressed.css: -------------------------------------------------------------------------------- 1 | @keyframes test{from{opacity:1;}to{opacity:0;}} -------------------------------------------------------------------------------- /test/cases/no-semi/compressed.css: -------------------------------------------------------------------------------- 1 | tobi loki jane{are:'all';the-species:called "ferrets";} 2 | -------------------------------------------------------------------------------- /test/cases/supports-linebreak/compressed.css: -------------------------------------------------------------------------------- 1 | @supports (display: flex){.test{display:flex;}} -------------------------------------------------------------------------------- /test/cases/wtf/compressed.css: -------------------------------------------------------------------------------- 1 | .wtf{*overflow-x:hidden;//max-height:110px;#height:18px;} 2 | -------------------------------------------------------------------------------- /test/cases/hose-linebreak/output.css: -------------------------------------------------------------------------------- 1 | @host { 2 | :scope { 3 | color: white; 4 | } 5 | } -------------------------------------------------------------------------------- /test/cases/import-linebreak/input.css: -------------------------------------------------------------------------------- 1 | @import 2 | url(test.css) 3 | screen 4 | ; 5 | -------------------------------------------------------------------------------- /test/cases/keyframes-messed/compressed.css: -------------------------------------------------------------------------------- 1 | @keyframes fade{from{opacity:0;}to{opacity:1;}} 2 | -------------------------------------------------------------------------------- /test/cases/quoted/compressed.css: -------------------------------------------------------------------------------- 1 | body{background:url('some;stuff;here') 50% 50% no-repeat;} 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | arch: 2 | - amd64 3 | - ppc64le 4 | language: node_js 5 | node_js: 6 | - "12.18.2" 7 | -------------------------------------------------------------------------------- /test/cases/hose-linebreak/input.css: -------------------------------------------------------------------------------- 1 | @host 2 | { 3 | :scope { color: white; } 4 | } 5 | -------------------------------------------------------------------------------- /test/cases/keyframes-vendor/compressed.css: -------------------------------------------------------------------------------- 1 | @-webkit-keyframes fade{from{opacity:0;}to{opacity:1;}} 2 | -------------------------------------------------------------------------------- /test/cases/media-linebreak/compressed.css: -------------------------------------------------------------------------------- 1 | @media ( 2 | min-width: 300px 3 | ){.test{width:100px;}} -------------------------------------------------------------------------------- /test/cases/messed-up/compressed.css: -------------------------------------------------------------------------------- 1 | body{foo:'bar';}body{foo:bar;bar:baz;}body{foo:bar;bar:baz;} 2 | -------------------------------------------------------------------------------- /test/cases/page-linebreak/input.css: -------------------------------------------------------------------------------- 1 | @page 2 | toc 3 | { 4 | color: black; 5 | } 6 | -------------------------------------------------------------------------------- /test/source-map/apply.css: -------------------------------------------------------------------------------- 1 | tobi { 2 | name: 'tobi'; } 3 | 4 | /*# sourceMappingURL=apply.css.map */ -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | exports.parse = require('./lib/parse'); 2 | exports.stringify = require('./lib/stringify'); 3 | -------------------------------------------------------------------------------- /test/cases/comment-in/compressed.css: -------------------------------------------------------------------------------- 1 | a{color:12px;padding:1px 2px 3px;border:solid;border-top:none\9;} 2 | -------------------------------------------------------------------------------- /test/cases/namespace-linebreak/input.css: -------------------------------------------------------------------------------- 1 | @namespace 2 | "http://www.w3.org/1999/xhtml" 3 | ; 4 | -------------------------------------------------------------------------------- /test/cases/props/compressed.css: -------------------------------------------------------------------------------- 1 | tobi loki jane{are:'all';the-species:called "ferrets";*even:'ie crap';} 2 | -------------------------------------------------------------------------------- /test/cases/quoted/input.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: url('some;stuff;here') 50% 50% no-repeat; 3 | } 4 | -------------------------------------------------------------------------------- /test/cases/quoted/output.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: url('some;stuff;here') 50% 50% no-repeat; 3 | } 4 | -------------------------------------------------------------------------------- /test/cases/custom-media-linebreak/input.css: -------------------------------------------------------------------------------- 1 | @custom-media 2 | --test 3 | (min-width: 200px) 4 | ; 5 | -------------------------------------------------------------------------------- /test/cases/no-semi/output.css: -------------------------------------------------------------------------------- 1 | tobi loki jane { 2 | are: 'all'; 3 | the-species: called "ferrets"; 4 | } 5 | -------------------------------------------------------------------------------- /test/cases/document-linebreak/output.css: -------------------------------------------------------------------------------- 1 | @document url-prefix() { 2 | .test { 3 | color: blue; 4 | } 5 | } -------------------------------------------------------------------------------- /test/cases/empty/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/cases/no-semi/input.css: -------------------------------------------------------------------------------- 1 | 2 | tobi loki jane { 3 | are: 'all'; 4 | the-species: called "ferrets" 5 | } 6 | -------------------------------------------------------------------------------- /test/cases/supports-linebreak/output.css: -------------------------------------------------------------------------------- 1 | @supports (display: flex) { 2 | .test { 3 | display: flex; 4 | } 5 | } -------------------------------------------------------------------------------- /test/cases/wtf/input.css: -------------------------------------------------------------------------------- 1 | .wtf { 2 | *overflow-x: hidden; 3 | //max-height: 110px; 4 | #height: 18px; 5 | } 6 | -------------------------------------------------------------------------------- /test/cases/wtf/output.css: -------------------------------------------------------------------------------- 1 | .wtf { 2 | *overflow-x: hidden; 3 | //max-height: 110px; 4 | #height: 18px; 5 | } 6 | -------------------------------------------------------------------------------- /test/cases/keyframes-vendor/input.css: -------------------------------------------------------------------------------- 1 | @-webkit-keyframes fade { 2 | from { opacity: 0 } 3 | to { opacity: 1 } 4 | } 5 | -------------------------------------------------------------------------------- /test/cases/keyframes-advanced/compressed.css: -------------------------------------------------------------------------------- 1 | @keyframes advanced{top{opacity[sqrt]:0;}100{opacity:0.5;}bottom{opacity:1;}} 2 | -------------------------------------------------------------------------------- /test/cases/media-linebreak/output.css: -------------------------------------------------------------------------------- 1 | @media ( 2 | min-width: 300px 3 | ) { 4 | .test { 5 | width: 100px; 6 | } 7 | } -------------------------------------------------------------------------------- /test/cases/namespace/compressed.css: -------------------------------------------------------------------------------- 1 | @namespace "http://www.w3.org/1999/xhtml";@namespace svg "http://www.w3.org/2000/svg"; 2 | -------------------------------------------------------------------------------- /test/cases/namespace/input.css: -------------------------------------------------------------------------------- 1 | @namespace "http://www.w3.org/1999/xhtml"; 2 | @namespace svg "http://www.w3.org/2000/svg"; 3 | -------------------------------------------------------------------------------- /test/cases/document/compressed.css: -------------------------------------------------------------------------------- 1 | @-moz-document url-prefix(){.ui-select .ui-btn select{opacity:.0001;}.icon-spin{height:.9em;}} 2 | -------------------------------------------------------------------------------- /test/cases/keyframes-messed/input.css: -------------------------------------------------------------------------------- 1 | @keyframes fade {from 2 | {opacity: 0; 3 | } 4 | to 5 | { 6 | opacity: 1;}} 7 | -------------------------------------------------------------------------------- /test/cases/media-linebreak/input.css: -------------------------------------------------------------------------------- 1 | @media 2 | 3 | ( 4 | min-width: 300px 5 | ) 6 | { 7 | .test { width: 100px; } 8 | } 9 | -------------------------------------------------------------------------------- /test/cases/namespace/output.css: -------------------------------------------------------------------------------- 1 | @namespace "http://www.w3.org/1999/xhtml"; 2 | 3 | @namespace svg "http://www.w3.org/2000/svg"; 4 | -------------------------------------------------------------------------------- /test/cases/paged-media/compressed.css: -------------------------------------------------------------------------------- 1 | @page toc, index:blank{color:green;}@page {font-size:16pt;}@page :left{margin-left:5cm;} 2 | -------------------------------------------------------------------------------- /test/cases/props/output.css: -------------------------------------------------------------------------------- 1 | tobi loki jane { 2 | are: 'all'; 3 | the-species: called "ferrets"; 4 | *even: 'ie crap'; 5 | } 6 | -------------------------------------------------------------------------------- /test/cases/props/input.css: -------------------------------------------------------------------------------- 1 | 2 | tobi loki jane { 3 | are: 'all'; 4 | the-species: called "ferrets"; 5 | *even: 'ie crap'; 6 | } 7 | -------------------------------------------------------------------------------- /test/cases/supports-linebreak/input.css: -------------------------------------------------------------------------------- 1 | @supports 2 | (display: flex) 3 | { 4 | .test { display: flex; } 5 | } 6 | -------------------------------------------------------------------------------- /test/cases/supports/compressed.css: -------------------------------------------------------------------------------- 1 | @supports (display: flex) or (display: box){.flex{display:box;display:flex;}div{something:else;}} 2 | -------------------------------------------------------------------------------- /test/cases/comment-in/output.css: -------------------------------------------------------------------------------- 1 | a { 2 | color: 12px; 3 | padding: 1px 2px 3px; 4 | border: solid; 5 | border-top: none\9; 6 | } 7 | -------------------------------------------------------------------------------- /test/cases/rules/input.css: -------------------------------------------------------------------------------- 1 | tobi { 2 | name: 'tobi'; 3 | age: 2; 4 | } 5 | 6 | loki { 7 | name: 'loki'; 8 | age: 1; 9 | } 10 | -------------------------------------------------------------------------------- /test/cases/custom-media/compressed.css: -------------------------------------------------------------------------------- 1 | @custom-media --narrow-window (max-width: 30em);@custom-media --wide-window screen and (min-width: 40em); 2 | -------------------------------------------------------------------------------- /test/cases/custom-media/input.css: -------------------------------------------------------------------------------- 1 | @custom-media --narrow-window (max-width: 30em); 2 | @custom-media --wide-window screen and (min-width: 40em); 3 | -------------------------------------------------------------------------------- /test/cases/keyframes-complex/compressed.css: -------------------------------------------------------------------------------- 1 | @keyframes foo{0%{top:0;left:0;}30.50%{top:50px;}.68%,72%,85%{left:50px;}100%{top:100px;left:100%;}} 2 | -------------------------------------------------------------------------------- /test/cases/rules/output.css: -------------------------------------------------------------------------------- 1 | tobi { 2 | name: 'tobi'; 3 | age: 2; 4 | } 5 | 6 | loki { 7 | name: 'loki'; 8 | age: 1; 9 | } 10 | -------------------------------------------------------------------------------- /test/cases/keyframes-linebreak/input.css: -------------------------------------------------------------------------------- 1 | @keyframes 2 | test 3 | { 4 | from { opacity: 1; } 5 | to { opacity: 0; } 6 | } 7 | -------------------------------------------------------------------------------- /test/cases/keyframes-linebreak/output.css: -------------------------------------------------------------------------------- 1 | @keyframes test { 2 | from { 3 | opacity: 1; 4 | } 5 | 6 | to { 7 | opacity: 0; 8 | } 9 | } -------------------------------------------------------------------------------- /test/cases/custom-media/output.css: -------------------------------------------------------------------------------- 1 | @custom-media --narrow-window (max-width: 30em); 2 | 3 | @custom-media --wide-window screen and (min-width: 40em); 4 | -------------------------------------------------------------------------------- /test/cases/keyframes-messed/output.css: -------------------------------------------------------------------------------- 1 | @keyframes fade { 2 | from { 3 | opacity: 0; 4 | } 5 | 6 | to { 7 | opacity: 1; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/source-map/apply.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "file": "", 4 | "sources": ["apply.scss"], 5 | "names": [], 6 | "mappings": "AAAA;EAAO,MAAM" 7 | } -------------------------------------------------------------------------------- /test/cases/keyframes-vendor/output.css: -------------------------------------------------------------------------------- 1 | @-webkit-keyframes fade { 2 | from { 3 | opacity: 0; 4 | } 5 | 6 | to { 7 | opacity: 1; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/cases/comment-url/input.css: -------------------------------------------------------------------------------- 1 | /* http://foo.com/bar/baz.html */ 2 | /**/ 3 | 4 | foo { /*/*/ 5 | /* something */ 6 | bar: baz; /* http://foo.com/bar/baz.html */ 7 | } 8 | -------------------------------------------------------------------------------- /test/cases/document-linebreak/input.css: -------------------------------------------------------------------------------- 1 | @document 2 | url-prefix() 3 | { 4 | 5 | .test { 6 | color: blue; 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /test/cases/comment-in/input.css: -------------------------------------------------------------------------------- 1 | a { 2 | color/**/: 12px; 3 | padding/*4815162342*/: 1px /**/ 2px /*13*/ 3px; 4 | border/*\**/: solid; border-top/*\**/: none\9; 5 | } 6 | -------------------------------------------------------------------------------- /test/cases/comment/input.css: -------------------------------------------------------------------------------- 1 | /* 1 */ 2 | 3 | head, /* footer, */body/*, nav */ { /* 2 */ 4 | /* 3 */ 5 | /**/foo: 'bar'; 6 | /* 4 */ 7 | } /* 5 */ 8 | 9 | /* 6 */ 10 | -------------------------------------------------------------------------------- /test/cases/messed-up/output.css: -------------------------------------------------------------------------------- 1 | body { 2 | foo: 'bar'; 3 | } 4 | 5 | body { 6 | foo: bar; 7 | bar: baz; 8 | } 9 | 10 | body { 11 | foo: bar; 12 | bar: baz; 13 | } 14 | -------------------------------------------------------------------------------- /test/cases/comment/output.css: -------------------------------------------------------------------------------- 1 | /* 1 */ 2 | 3 | head, 4 | body { 5 | /* 2 */ 6 | /* 3 */ 7 | /**/ 8 | foo: 'bar'; 9 | /* 4 */ 10 | } 11 | 12 | /* 5 */ 13 | 14 | /* 6 */ 15 | -------------------------------------------------------------------------------- /test/cases/comment-url/output.css: -------------------------------------------------------------------------------- 1 | /* http://foo.com/bar/baz.html */ 2 | 3 | /**/ 4 | 5 | foo { 6 | /*/*/ 7 | /* something */ 8 | bar: baz; 9 | /* http://foo.com/bar/baz.html */ 10 | } 11 | -------------------------------------------------------------------------------- /test/cases/keyframes-complex/input.css: -------------------------------------------------------------------------------- 1 | @keyframes foo { 2 | 0% { top: 0; left: 0 } 3 | 30.50% { top: 50px } 4 | .68% , 5 | 72% 6 | , 85% { left: 50px } 7 | 100% { top: 100px; left: 100% } 8 | } 9 | -------------------------------------------------------------------------------- /test/cases/font-face/compressed.css: -------------------------------------------------------------------------------- 1 | @font-face{font-family:"Bitstream Vera Serif Bold";src:url("http://developer.mozilla.org/@api/deki/files/2934/=VeraSeBd.ttf");}body{font-family:"Bitstream Vera Serif Bold", serif;} 2 | -------------------------------------------------------------------------------- /test/cases/media/compressed.css: -------------------------------------------------------------------------------- 1 | @media screen, projection{html{background:#fffef0;color:#300;}body{max-width:35em;margin:0 auto;}}@media print{html{background:#fff;color:#000;}body{padding:1in;border:0.5pt solid #666;}} 2 | -------------------------------------------------------------------------------- /test/cases/keyframes-advanced/input.css: -------------------------------------------------------------------------------- 1 | @keyframes advanced { 2 | top { 3 | opacity[sqrt]: 0; 4 | } 5 | 6 | 100 { 7 | opacity: 0.5; 8 | } 9 | 10 | bottom { 11 | opacity: 1; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/cases/keyframes-advanced/output.css: -------------------------------------------------------------------------------- 1 | @keyframes advanced { 2 | top { 3 | opacity[sqrt]: 0; 4 | } 5 | 6 | 100 { 7 | opacity: 0.5; 8 | } 9 | 10 | bottom { 11 | opacity: 1; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/cases/font-face-linebreak/compressed.css: -------------------------------------------------------------------------------- 1 | @font-face{font-family:"Bitstream Vera Serif Bold";src:url("http://developer.mozilla.org/@api/deki/files/2934/=VeraSeBd.ttf");}body{font-family:"Bitstream Vera Serif Bold", serif;} 2 | -------------------------------------------------------------------------------- /test/cases/media-messed/compressed.css: -------------------------------------------------------------------------------- 1 | @media screen, projection{html{background:#fffef0;color:#300;}body{max-width:35em;margin:0 auto;}}@media print{html{background:#fff;color:#000;}body{padding:1in;border:0.5pt solid #666;}} 2 | -------------------------------------------------------------------------------- /test/cases/import/compressed.css: -------------------------------------------------------------------------------- 1 | @import url("fineprint.css") print;@import url("bluish.css") projection, tv;@import 'custom.css';@import "common.css" screen, projection;@import url('landscape.css') screen and (orientation:landscape); 2 | -------------------------------------------------------------------------------- /test/cases/messed-up/input.css: -------------------------------------------------------------------------------- 1 | body { foo 2 | : 3 | 'bar' } 4 | 5 | body{foo:bar;bar:baz} 6 | body 7 | { 8 | foo 9 | : 10 | bar 11 | ; 12 | bar 13 | : 14 | baz 15 | } 16 | -------------------------------------------------------------------------------- /test/cases/charset/input.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; /* Set the encoding of the style sheet to Unicode UTF-8 */ 2 | @charset 'iso-8859-15'; /* Set the encoding of the style sheet to Latin-9 (Western European languages, with euro sign) */ 3 | -------------------------------------------------------------------------------- /test/cases/paged-media/input.css: -------------------------------------------------------------------------------- 1 | /* toc above */ 2 | @page toc, index:blank { 3 | /* toc inside */ 4 | color: green; 5 | } 6 | 7 | @page { 8 | font-size: 16pt; 9 | } 10 | 11 | @page :left { 12 | margin-left: 5cm; 13 | } 14 | -------------------------------------------------------------------------------- /test/cases/document/input.css: -------------------------------------------------------------------------------- 1 | @-moz-document url-prefix() { 2 | /* ui above */ 3 | .ui-select .ui-btn select { 4 | /* ui inside */ 5 | opacity:.0001 6 | } 7 | 8 | .icon-spin { 9 | height: .9em; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/cases/import-messed/compressed.css: -------------------------------------------------------------------------------- 1 | @import url("fineprint.css") print;@import url("bluish.css") projection, tv;@import 'custom.css';@import "common.css" screen, projection;@import url('landscape.css') screen and (orientation:landscape); 2 | -------------------------------------------------------------------------------- /test/cases/import/input.css: -------------------------------------------------------------------------------- 1 | @import url("fineprint.css") print; 2 | @import url("bluish.css") projection, tv; 3 | @import 'custom.css'; 4 | @import "common.css" screen, projection; 5 | @import url('landscape.css') screen and (orientation:landscape); 6 | -------------------------------------------------------------------------------- /test/cases/keyframes/input.css: -------------------------------------------------------------------------------- 1 | @keyframes fade { 2 | /* from above */ 3 | from { 4 | /* from inside */ 5 | opacity: 0; 6 | } 7 | 8 | /* to above */ 9 | to { 10 | /* to inside */ 11 | opacity: 1; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/cases/keyframes/output.css: -------------------------------------------------------------------------------- 1 | @keyframes fade { 2 | /* from above */ 3 | from { 4 | /* from inside */ 5 | opacity: 0; 6 | } 7 | 8 | /* to above */ 9 | to { 10 | /* to inside */ 11 | opacity: 1; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/cases/paged-media/output.css: -------------------------------------------------------------------------------- 1 | /* toc above */ 2 | 3 | @page toc, index:blank { 4 | /* toc inside */ 5 | color: green; 6 | } 7 | 8 | @page { 9 | font-size: 16pt; 10 | } 11 | 12 | @page :left { 13 | margin-left: 5cm; 14 | } 15 | -------------------------------------------------------------------------------- /test/cases/document/output.css: -------------------------------------------------------------------------------- 1 | @-moz-document url-prefix() { 2 | /* ui above */ 3 | 4 | .ui-select .ui-btn select { 5 | /* ui inside */ 6 | opacity: .0001; 7 | } 8 | 9 | .icon-spin { 10 | height: .9em; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/source-map/test.css: -------------------------------------------------------------------------------- 1 | tobi { 2 | name: 'tobi'; 3 | age: 2; 4 | } 5 | 6 | loki { 7 | name: 'loki'; 8 | age: 1; 9 | } 10 | 11 | @media screen { 12 | screen-only { 13 | display: block; 14 | } 15 | } 16 | 17 | /* comment */ 18 | -------------------------------------------------------------------------------- /test/cases/charset/output.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | /* Set the encoding of the style sheet to Unicode UTF-8 */ 4 | 5 | @charset 'iso-8859-15'; 6 | 7 | /* Set the encoding of the style sheet to Latin-9 (Western European languages, with euro sign) */ 8 | -------------------------------------------------------------------------------- /test/cases/font-face/input.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "Bitstream Vera Serif Bold"; 3 | src: url("http://developer.mozilla.org/@api/deki/files/2934/=VeraSeBd.ttf"); 4 | } 5 | 6 | body { 7 | font-family: "Bitstream Vera Serif Bold", serif; 8 | } 9 | -------------------------------------------------------------------------------- /test/cases/font-face/output.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "Bitstream Vera Serif Bold"; 3 | src: url("http://developer.mozilla.org/@api/deki/files/2934/=VeraSeBd.ttf"); 4 | } 5 | 6 | body { 7 | font-family: "Bitstream Vera Serif Bold", serif; 8 | } 9 | -------------------------------------------------------------------------------- /test/cases/supports/input.css: -------------------------------------------------------------------------------- 1 | @supports (display: flex) or (display: box) { 2 | /* flex above */ 3 | .flex { 4 | /* flex inside */ 5 | display: box; 6 | display: flex; 7 | } 8 | 9 | div { 10 | something: else; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/cases/font-face-linebreak/output.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "Bitstream Vera Serif Bold"; 3 | src: url("http://developer.mozilla.org/@api/deki/files/2934/=VeraSeBd.ttf"); 4 | } 5 | 6 | body { 7 | font-family: "Bitstream Vera Serif Bold", serif; 8 | } 9 | -------------------------------------------------------------------------------- /test/cases/supports/output.css: -------------------------------------------------------------------------------- 1 | @supports (display: flex) or (display: box) { 2 | /* flex above */ 3 | 4 | .flex { 5 | /* flex inside */ 6 | display: box; 7 | display: flex; 8 | } 9 | 10 | div { 11 | something: else; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/cases/comma-selector-function/compressed.css: -------------------------------------------------------------------------------- 1 | .foo:matches(.bar,.baz),.foo:matches(.bar, .baz),.foo:matches(.bar , .baz),.foo:matches(.bar ,.baz){prop:value;}.foo:matches(.bar,.baz,.foobar),.foo:matches(.bar, .baz,),.foo:matches(,.bar , .baz){anotherprop:anothervalue;} 2 | -------------------------------------------------------------------------------- /test/cases/import-messed/input.css: -------------------------------------------------------------------------------- 1 | 2 | @import url("fineprint.css") print; 3 | @import url("bluish.css") projection, tv; 4 | @import 'custom.css'; 5 | @import "common.css" screen, projection ; 6 | 7 | @import url('landscape.css') screen and (orientation:landscape); 8 | -------------------------------------------------------------------------------- /test/cases/import/output.css: -------------------------------------------------------------------------------- 1 | @import url("fineprint.css") print; 2 | 3 | @import url("bluish.css") projection, tv; 4 | 5 | @import 'custom.css'; 6 | 7 | @import "common.css" screen, projection; 8 | 9 | @import url('landscape.css') screen and (orientation:landscape); 10 | -------------------------------------------------------------------------------- /test/cases/import-messed/output.css: -------------------------------------------------------------------------------- 1 | @import url("fineprint.css") print; 2 | 3 | @import url("bluish.css") projection, tv; 4 | 5 | @import 'custom.css'; 6 | 7 | @import "common.css" screen, projection; 8 | 9 | @import url('landscape.css') screen and (orientation:landscape); 10 | -------------------------------------------------------------------------------- /test/cases/font-face-linebreak/input.css: -------------------------------------------------------------------------------- 1 | @font-face 2 | 3 | { 4 | font-family: "Bitstream Vera Serif Bold"; 5 | src: url("http://developer.mozilla.org/@api/deki/files/2934/=VeraSeBd.ttf"); 6 | } 7 | 8 | body { 9 | font-family: "Bitstream Vera Serif Bold", serif; 10 | } 11 | -------------------------------------------------------------------------------- /test/cases/keyframes-complex/output.css: -------------------------------------------------------------------------------- 1 | @keyframes foo { 2 | 0% { 3 | top: 0; 4 | left: 0; 5 | } 6 | 7 | 30.50% { 8 | top: 50px; 9 | } 10 | 11 | .68%, 72%, 85% { 12 | left: 50px; 13 | } 14 | 15 | 100% { 16 | top: 100px; 17 | left: 100%; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/cases/comma-selector-function/input.css: -------------------------------------------------------------------------------- 1 | .foo:matches(.bar,.baz), 2 | .foo:matches(.bar, .baz), 3 | .foo:matches(.bar , .baz), 4 | .foo:matches(.bar ,.baz) { 5 | prop: value; 6 | } 7 | 8 | .foo:matches(.bar,.baz,.foobar), 9 | .foo:matches(.bar, .baz,), 10 | .foo:matches(,.bar , .baz) { 11 | anotherprop: anothervalue; 12 | } 13 | -------------------------------------------------------------------------------- /test/cases/comma-selector-function/output.css: -------------------------------------------------------------------------------- 1 | .foo:matches(.bar,.baz), 2 | .foo:matches(.bar, .baz), 3 | .foo:matches(.bar , .baz), 4 | .foo:matches(.bar ,.baz) { 5 | prop: value; 6 | } 7 | 8 | .foo:matches(.bar,.baz,.foobar), 9 | .foo:matches(.bar, .baz,), 10 | .foo:matches(,.bar , .baz) { 11 | anotherprop: anothervalue; 12 | } 13 | -------------------------------------------------------------------------------- /test/cases/media-messed/output.css: -------------------------------------------------------------------------------- 1 | @media screen, projection { 2 | html { 3 | background: #fffef0; 4 | color: #300; 5 | } 6 | 7 | body { 8 | max-width: 35em; 9 | margin: 0 auto; 10 | } 11 | } 12 | 13 | @media print { 14 | html { 15 | background: #fff; 16 | color: #000; 17 | } 18 | 19 | body { 20 | padding: 1in; 21 | border: 0.5pt solid #666; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/cases/charset-linebreak/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [ 5 | { 6 | "type": "charset", 7 | "charset": "\"UTF-8\"", 8 | "position": { 9 | "start": { 10 | "line": 1, 11 | "column": 1 12 | }, 13 | "end": { 14 | "line": 3, 15 | "column": 6 16 | }, 17 | "source": "input.css" 18 | } 19 | } 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /test/cases/media/input.css: -------------------------------------------------------------------------------- 1 | @media screen, projection { 2 | /* html above */ 3 | html { 4 | /* html inside */ 5 | background: #fffef0; 6 | color: #300; 7 | } 8 | 9 | /* body above */ 10 | body { 11 | /* body inside */ 12 | max-width: 35em; 13 | margin: 0 auto; 14 | } 15 | } 16 | 17 | @media print { 18 | html { 19 | background: #fff; 20 | color: #000; 21 | } 22 | body { 23 | padding: 1in; 24 | border: 0.5pt solid #666; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/cases/import-linebreak/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [ 5 | { 6 | "type": "import", 7 | "import": "url(test.css)\n screen", 8 | "position": { 9 | "start": { 10 | "line": 1, 11 | "column": 1 12 | }, 13 | "end": { 14 | "line": 4, 15 | "column": 6 16 | }, 17 | "source": "input.css" 18 | } 19 | } 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /test/cases/media-messed/input.css: -------------------------------------------------------------------------------- 1 | @media screen, projection{ html 2 | 3 | { 4 | background: #fffef0; 5 | color:#300; 6 | } 7 | body 8 | 9 | { 10 | max-width: 35em; 11 | margin: 0 auto; 12 | 13 | 14 | } 15 | } 16 | 17 | @media print 18 | { 19 | html { 20 | background: #fff; 21 | color: #000; 22 | } 23 | body { 24 | padding: 1in; 25 | border: 0.5pt solid #666; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/cases/comma-attribute/compressed.css: -------------------------------------------------------------------------------- 1 | .foo[bar="baz,quz"]{foobar:123;}.bar,#bar[baz="qux,foo"],#qux{foobar:456;}.baz[qux=",foo"],.baz[qux="foo,"],.baz[qux="foo,bar,baz"],.baz[qux=",foo,bar,baz,"],.baz[qux=" , foo , bar , baz , "]{foobar:789;}.qux[foo='bar,baz'],.qux[bar="baz,foo"],#qux[foo="foobar"],#qux[foo=',bar,baz, ']{foobar:012;}#foo[foo=""],#foo[bar=" "],#foo[bar=","],#foo[bar=", "],#foo[bar=" ,"],#foo[bar=" , "],#foo[baz=''],#foo[qux=' '],#foo[qux=','],#foo[qux=', '],#foo[qux=' ,'],#foo[qux=' , ']{foobar:345;} 2 | -------------------------------------------------------------------------------- /test/cases/media/output.css: -------------------------------------------------------------------------------- 1 | @media screen, projection { 2 | /* html above */ 3 | 4 | html { 5 | /* html inside */ 6 | background: #fffef0; 7 | color: #300; 8 | } 9 | 10 | /* body above */ 11 | 12 | body { 13 | /* body inside */ 14 | max-width: 35em; 15 | margin: 0 auto; 16 | } 17 | } 18 | 19 | @media print { 20 | html { 21 | background: #fff; 22 | color: #000; 23 | } 24 | 25 | body { 26 | padding: 1in; 27 | border: 0.5pt solid #666; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/cases/at-namespace/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [ 5 | { 6 | "type": "namespace", 7 | "namespace": "svg \"http://www.w3.org/2000/svg\"", 8 | "position": { 9 | "start": { 10 | "line": 1, 11 | "column": 1 12 | }, 13 | "end": { 14 | "line": 1, 15 | "column": 45 16 | }, 17 | "source": "input.css" 18 | } 19 | } 20 | ] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/cases/namespace-linebreak/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [ 5 | { 6 | "type": "namespace", 7 | "namespace": "\"http://www.w3.org/1999/xhtml\"", 8 | "position": { 9 | "start": { 10 | "line": 1, 11 | "column": 1 12 | }, 13 | "end": { 14 | "line": 3, 15 | "column": 6 16 | }, 17 | "source": "input.css" 18 | } 19 | } 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /test/cases/custom-media-linebreak/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [ 5 | { 6 | "type": "custom-media", 7 | "name": "--test", 8 | "media": "(min-width: 200px)", 9 | "position": { 10 | "start": { 11 | "line": 1, 12 | "column": 1 13 | }, 14 | "end": { 15 | "line": 4, 16 | "column": 2 17 | }, 18 | "source": "input.css" 19 | } 20 | } 21 | ] 22 | } 23 | } -------------------------------------------------------------------------------- /test/cases/comma-attribute/input.css: -------------------------------------------------------------------------------- 1 | .foo[bar="baz,quz"] { 2 | foobar: 123; 3 | } 4 | 5 | .bar, 6 | #bar[baz="qux,foo"], 7 | #qux { 8 | foobar: 456; 9 | } 10 | 11 | .baz[qux=",foo"], 12 | .baz[qux="foo,"], 13 | .baz[qux="foo,bar,baz"], 14 | .baz[qux=",foo,bar,baz,"], 15 | .baz[qux=" , foo , bar , baz , "] { 16 | foobar: 789; 17 | } 18 | 19 | .qux[foo='bar,baz'], 20 | .qux[bar="baz,foo"], 21 | #qux[foo="foobar"], 22 | #qux[foo=',bar,baz, '] { 23 | foobar: 012; 24 | } 25 | 26 | #foo[foo=""], 27 | #foo[bar=" "], 28 | #foo[bar=","], 29 | #foo[bar=", "], 30 | #foo[bar=" ,"], 31 | #foo[bar=" , "], 32 | #foo[baz=''], 33 | #foo[qux=' '], 34 | #foo[qux=','], 35 | #foo[qux=', '], 36 | #foo[qux=' ,'], 37 | #foo[qux=' , '] { 38 | foobar: 345; 39 | } 40 | -------------------------------------------------------------------------------- /test/cases/comma-attribute/output.css: -------------------------------------------------------------------------------- 1 | .foo[bar="baz,quz"] { 2 | foobar: 123; 3 | } 4 | 5 | .bar, 6 | #bar[baz="qux,foo"], 7 | #qux { 8 | foobar: 456; 9 | } 10 | 11 | .baz[qux=",foo"], 12 | .baz[qux="foo,"], 13 | .baz[qux="foo,bar,baz"], 14 | .baz[qux=",foo,bar,baz,"], 15 | .baz[qux=" , foo , bar , baz , "] { 16 | foobar: 789; 17 | } 18 | 19 | .qux[foo='bar,baz'], 20 | .qux[bar="baz,foo"], 21 | #qux[foo="foobar"], 22 | #qux[foo=',bar,baz, '] { 23 | foobar: 012; 24 | } 25 | 26 | #foo[foo=""], 27 | #foo[bar=" "], 28 | #foo[bar=","], 29 | #foo[bar=", "], 30 | #foo[bar=" ,"], 31 | #foo[bar=" , "], 32 | #foo[baz=''], 33 | #foo[qux=' '], 34 | #foo[qux=','], 35 | #foo[qux=', '], 36 | #foo[qux=' ,'], 37 | #foo[qux=' , '] { 38 | foobar: 345; 39 | } 40 | -------------------------------------------------------------------------------- /test/cases/namespace/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [ 5 | { 6 | "type": "namespace", 7 | "namespace": "\"http://www.w3.org/1999/xhtml\"", 8 | "position": { 9 | "start": { 10 | "line": 1, 11 | "column": 1 12 | }, 13 | "end": { 14 | "line": 1, 15 | "column": 43 16 | }, 17 | "source": "input.css" 18 | } 19 | }, 20 | { 21 | "type": "namespace", 22 | "namespace": "svg \"http://www.w3.org/2000/svg\"", 23 | "position": { 24 | "start": { 25 | "line": 2, 26 | "column": 1 27 | }, 28 | "end": { 29 | "line": 2, 30 | "column": 45 31 | }, 32 | "source": "input.css" 33 | } 34 | } 35 | ] 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /benchmark/index.js: -------------------------------------------------------------------------------- 1 | 2 | var bytes = require('bytes'); 3 | var parse = require('..').parse; 4 | var fs = require('fs'); 5 | 6 | var small = fs.readFileSync('benchmark/small.css', 'utf8'); 7 | var large = fs.readFileSync('benchmark/large.css', 'utf8'); 8 | var huge = Array(8).join(large); 9 | 10 | function lines(str) { 11 | return str.split(/\n/g).length; 12 | } 13 | 14 | console.log(); 15 | console.log(' small : %s : %s lines', bytes(Buffer.byteLength(small)), lines(small)); 16 | console.log(' large : %s : %s lines', bytes(Buffer.byteLength(large)), lines(large)); 17 | console.log(' huge : %s : %s lines', bytes(Buffer.byteLength(huge)), lines(huge)); 18 | 19 | suite('css parse', function(){ 20 | bench('small', function(){ 21 | parse(small); 22 | }); 23 | 24 | bench('large', function(){ 25 | parse(large); 26 | }); 27 | 28 | bench('huge', function(){ 29 | parse(huge); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "css", 3 | "version": "3.0.0", 4 | "description": "CSS parser / stringifier", 5 | "main": "index", 6 | "files": [ 7 | "index.js", 8 | "lib", 9 | "Readme.md" 10 | ], 11 | "dependencies": { 12 | "inherits": "^2.0.4", 13 | "source-map": "^0.6.1", 14 | "source-map-resolve": "^0.6.0" 15 | }, 16 | "devDependencies": { 17 | "mocha": "^8.0.1", 18 | "should": "^13.2.3", 19 | "matcha": "^0.7.0", 20 | "bytes": "^3.1.0" 21 | }, 22 | "scripts": { 23 | "benchmark": "matcha", 24 | "test": "mocha --require should --reporter spec test/*.js" 25 | }, 26 | "author": "TJ Holowaychuk ", 27 | "license": "MIT", 28 | "repository": { 29 | "type": "git", 30 | "url": "https://github.com/reworkcss/css.git" 31 | }, 32 | "keywords": [ 33 | "css", 34 | "parser", 35 | "stringifier", 36 | "stylesheet" 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /test/cases/custom-media/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [ 5 | { 6 | "type": "custom-media", 7 | "name": "--narrow-window", 8 | "media": "(max-width: 30em)", 9 | "position": { 10 | "start": { 11 | "line": 1, 12 | "column": 1 13 | }, 14 | "end": { 15 | "line": 1, 16 | "column": 49 17 | }, 18 | "source": "input.css" 19 | } 20 | }, 21 | { 22 | "type": "custom-media", 23 | "name": "--wide-window", 24 | "media": "screen and (min-width: 40em)", 25 | "position": { 26 | "start": { 27 | "line": 2, 28 | "column": 1 29 | }, 30 | "end": { 31 | "line": 2, 32 | "column": 58 33 | }, 34 | "source": "input.css" 35 | } 36 | } 37 | ] 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/stringify/compiler.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Expose `Compiler`. 4 | */ 5 | 6 | module.exports = Compiler; 7 | 8 | /** 9 | * Initialize a compiler. 10 | * 11 | * @param {Type} name 12 | * @return {Type} 13 | * @api public 14 | */ 15 | 16 | function Compiler(opts) { 17 | this.options = opts || {}; 18 | } 19 | 20 | /** 21 | * Emit `str` 22 | */ 23 | 24 | Compiler.prototype.emit = function(str) { 25 | return str; 26 | }; 27 | 28 | /** 29 | * Visit `node`. 30 | */ 31 | 32 | Compiler.prototype.visit = function(node){ 33 | return this[node.type](node); 34 | }; 35 | 36 | /** 37 | * Map visit over array of `nodes`, optionally using a `delim` 38 | */ 39 | 40 | Compiler.prototype.mapVisit = function(nodes, delim){ 41 | var buf = ''; 42 | delim = delim || ''; 43 | 44 | for (var i = 0, length = nodes.length; i < length; i++) { 45 | buf += this.visit(nodes[i]); 46 | if (delim && i < length - 1) buf += this.emit(delim); 47 | } 48 | 49 | return buf; 50 | }; 51 | -------------------------------------------------------------------------------- /test/cases/rule/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [ 5 | { 6 | "type": "rule", 7 | "selectors": [ 8 | "foo" 9 | ], 10 | "declarations": [ 11 | { 12 | "type": "declaration", 13 | "property": "bar", 14 | "value": "'baz'", 15 | "position": { 16 | "start": { 17 | "line": 2, 18 | "column": 3 19 | }, 20 | "end": { 21 | "line": 2, 22 | "column": 13 23 | }, 24 | "source": "input.css" 25 | } 26 | } 27 | ], 28 | "position": { 29 | "start": { 30 | "line": 1, 31 | "column": 1 32 | }, 33 | "end": { 34 | "line": 3, 35 | "column": 2 36 | }, 37 | "source": "input.css" 38 | } 39 | } 40 | ] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/cases/page-linebreak/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [ 5 | { 6 | "type": "page", 7 | "selectors": [ 8 | "toc" 9 | ], 10 | "declarations": [ 11 | { 12 | "type": "declaration", 13 | "property": "color", 14 | "value": "black", 15 | "position": { 16 | "start": { 17 | "line": 4, 18 | "column": 9 19 | }, 20 | "end": { 21 | "line": 4, 22 | "column": 21 23 | }, 24 | "source": "input.css" 25 | } 26 | } 27 | ], 28 | "position": { 29 | "start": { 30 | "line": 1, 31 | "column": 1 32 | }, 33 | "end": { 34 | "line": 5, 35 | "column": 6 36 | }, 37 | "source": "input.css" 38 | } 39 | } 40 | ] 41 | } 42 | } -------------------------------------------------------------------------------- /test/cases/quoted/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [ 5 | { 6 | "type": "rule", 7 | "selectors": [ 8 | "body" 9 | ], 10 | "declarations": [ 11 | { 12 | "type": "declaration", 13 | "property": "background", 14 | "value": "url('some;stuff;here') 50% 50% no-repeat", 15 | "position": { 16 | "start": { 17 | "line": 2, 18 | "column": 3 19 | }, 20 | "end": { 21 | "line": 2, 22 | "column": 55 23 | }, 24 | "source": "input.css" 25 | } 26 | } 27 | ], 28 | "position": { 29 | "start": { 30 | "line": 1, 31 | "column": 1 32 | }, 33 | "end": { 34 | "line": 3, 35 | "column": 2 36 | }, 37 | "source": "input.css" 38 | } 39 | } 40 | ] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/cases/quote-escape/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [ 5 | { 6 | "type": "rule", 7 | "selectors": [ 8 | "p[qwe=\"a\\\",b\"]" 9 | ], 10 | "declarations": [ 11 | { 12 | "type": "declaration", 13 | "property": "color", 14 | "value": "red", 15 | "position": { 16 | "start": { 17 | "line": 1, 18 | "column": 18 19 | }, 20 | "end": { 21 | "line": 1, 22 | "column": 29 23 | }, 24 | "source": "input.css" 25 | } 26 | } 27 | ], 28 | "position": { 29 | "start": { 30 | "line": 1, 31 | "column": 1 32 | }, 33 | "end": { 34 | "line": 1, 35 | "column": 30 36 | }, 37 | "source": "input.css" 38 | } 39 | } 40 | ], 41 | "parsingErrors": [] 42 | } 43 | } -------------------------------------------------------------------------------- /test/cases/selectors/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [ 5 | { 6 | "type": "rule", 7 | "selectors": [ 8 | "foo", 9 | "bar", 10 | "baz" 11 | ], 12 | "declarations": [ 13 | { 14 | "type": "declaration", 15 | "property": "color", 16 | "value": "'black'", 17 | "position": { 18 | "start": { 19 | "line": 4, 20 | "column": 3 21 | }, 22 | "end": { 23 | "line": 4, 24 | "column": 17 25 | }, 26 | "source": "input.css" 27 | } 28 | } 29 | ], 30 | "position": { 31 | "start": { 32 | "line": 1, 33 | "column": 1 34 | }, 35 | "end": { 36 | "line": 5, 37 | "column": 2 38 | }, 39 | "source": "input.css" 40 | } 41 | } 42 | ] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2012 TJ Holowaychuk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /lib/stringify/index.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var Compressed = require('./compress'); 7 | var Identity = require('./identity'); 8 | 9 | /** 10 | * Stringfy the given AST `node`. 11 | * 12 | * Options: 13 | * 14 | * - `compress` space-optimized output 15 | * - `sourcemap` return an object with `.code` and `.map` 16 | * 17 | * @param {Object} node 18 | * @param {Object} [options] 19 | * @return {String} 20 | * @api public 21 | */ 22 | 23 | module.exports = function(node, options){ 24 | options = options || {}; 25 | 26 | var compiler = options.compress 27 | ? new Compressed(options) 28 | : new Identity(options); 29 | 30 | // source maps 31 | if (options.sourcemap) { 32 | var sourcemaps = require('./source-map-support'); 33 | sourcemaps(compiler); 34 | 35 | var code = compiler.compile(node); 36 | compiler.applySourceMaps(); 37 | 38 | var map = options.sourcemap === 'generator' 39 | ? compiler.map 40 | : compiler.map.toJSON(); 41 | 42 | return { code: code, map: map }; 43 | } 44 | 45 | var code = compiler.compile(node); 46 | return code; 47 | }; 48 | -------------------------------------------------------------------------------- /test/cases/escapes/compressed.css: -------------------------------------------------------------------------------- 1 | html{font:1.2em/1.6 Arial;}code{font-family:Consolas;}li code{background:rgba(255, 255, 255, .5);padding:.3em;}li{background:orange;}#♥{background:lime;}#©{background:lime;}#“‘’”{background:lime;}#☺☃{background:lime;}#⌘⌥{background:lime;}#𝄞♪♩♫♬{background:lime;}#\?{background:lime;}#\@{background:lime;}#\.{background:lime;}#\3A \){background:lime;}#\3A \`\({background:lime;}#\31 23{background:lime;}#\31 a2b3c{background:lime;}#\{background:lime;}#\<\>\<\<\<\>\>\<\>{background:lime;}#\+\+\+\+\+\+\+\+\+\+\[\>\+\+\+\+\+\+\+\>\+\+\+\+\+\+\+\+\+\+\>\+\+\+\>\+\<\<\<\<\-\]\>\+\+\.\>\+\.\+\+\+\+\+\+\+\.\.\+\+\+\.\>\+\+\.\<\<\+\+\+\+\+\+\+\+\+\+\+\+\+\+\+\.\>\.\+\+\+\.\-\-\-\-\-\-\.\-\-\-\-\-\-\-\-\.\>\+\.\>\.{background:lime;}#\#{background:lime;}#\#\#{background:lime;}#\#\.\#\.\#{background:lime;}#\_{background:lime;}#\.fake\-class{background:lime;}#foo\.bar{background:lime;}#\3A hover{background:lime;}#\3A hover\3A focus\3A active{background:lime;}#\[attr\=value\]{background:lime;}#f\/o\/o{background:lime;}#f\\o\\o{background:lime;}#f\*o\*o{background:lime;}#f\!o\!o{background:lime;}#f\'o\'o{background:lime;}#f\~o\~o{background:lime;}#f\+o\+o{background:lime;} 2 | -------------------------------------------------------------------------------- /test/cases/colon-space/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [ 5 | { 6 | "type": "rule", 7 | "selectors": [ 8 | "a" 9 | ], 10 | "declarations": [ 11 | { 12 | "type": "declaration", 13 | "property": "margin", 14 | "value": "auto", 15 | "position": { 16 | "start": { 17 | "line": 2, 18 | "column": 5 19 | }, 20 | "end": { 21 | "line": 2, 22 | "column": 19 23 | }, 24 | "source": "input.css" 25 | } 26 | }, 27 | { 28 | "type": "declaration", 29 | "property": "padding", 30 | "value": "0", 31 | "position": { 32 | "start": { 33 | "line": 3, 34 | "column": 5 35 | }, 36 | "end": { 37 | "line": 3, 38 | "column": 16 39 | }, 40 | "source": "input.css" 41 | } 42 | } 43 | ], 44 | "position": { 45 | "start": { 46 | "line": 1, 47 | "column": 1 48 | }, 49 | "end": { 50 | "line": 4, 51 | "column": 2 52 | }, 53 | "source": "input.css" 54 | } 55 | } 56 | ] 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /benchmark/LICENSES: -------------------------------------------------------------------------------- 1 | File: large.css 2 | Copyright: 2013 Contributors 3 | License: MIT 4 | Origin: https://raw.githubusercontent.com/Semantic-Org/Semantic-UI/0.16.1/build/packaged/css/semantic.css 5 | 6 | File: small.css 7 | Copyright: Nicolas Gallagher and Jonathan Neal 8 | License: MIT 9 | Origin: https://raw.githubusercontent.com/necolas/normalize.css/3.0.1/normalize.css 10 | 11 | 12 | License: MIT 13 | Permission is hereby granted, free of charge, to any person obtaining a 14 | copy of this software and associated documentation files (the 15 | 'Software'), to deal in the Software without restriction, including 16 | without limitation the rights to use, copy, modify, merge, publish, 17 | distribute, sublicense, and/or sell copies of the Software, and to 18 | permit persons to whom the Software is furnished to do so, subject to 19 | the following conditions: 20 | 21 | The above copyright notice and this permission notice shall be included 22 | in all copies or substantial portions of the Software. 23 | 24 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS 25 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 26 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 27 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 28 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 29 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 30 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 31 | -------------------------------------------------------------------------------- /test/cases/no-semi/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [ 5 | { 6 | "type": "rule", 7 | "selectors": [ 8 | "tobi loki jane" 9 | ], 10 | "declarations": [ 11 | { 12 | "type": "declaration", 13 | "property": "are", 14 | "value": "'all'", 15 | "position": { 16 | "start": { 17 | "line": 3, 18 | "column": 3 19 | }, 20 | "end": { 21 | "line": 3, 22 | "column": 13 23 | }, 24 | "source": "input.css" 25 | } 26 | }, 27 | { 28 | "type": "declaration", 29 | "property": "the-species", 30 | "value": "called \"ferrets\"", 31 | "position": { 32 | "start": { 33 | "line": 4, 34 | "column": 3 35 | }, 36 | "end": { 37 | "line": 5, 38 | "column": 1 39 | }, 40 | "source": "input.css" 41 | } 42 | } 43 | ], 44 | "position": { 45 | "start": { 46 | "line": 2, 47 | "column": 1 48 | }, 49 | "end": { 50 | "line": 5, 51 | "column": 2 52 | }, 53 | "source": "input.css" 54 | } 55 | } 56 | ] 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /test/cases.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var parse = require('../').parse; 4 | var stringify = require('../').stringify; 5 | 6 | var cases = fs.readdirSync(path.join(__dirname, 'cases')); 7 | cases.forEach(function(name) { 8 | describe('cases/' + name, function() { 9 | var dir = path.join(__dirname, 'cases', name); 10 | var inputFile = path.join(dir, 'input.css'); 11 | var astFile = path.join(dir, 'ast.json'); 12 | var outputFile = path.join(dir, 'output.css'); 13 | var compressedFile = path.join(dir, 'compressed.css'); 14 | 15 | it('should match ast.json', function() { 16 | var ast = parseInput(); 17 | ast.should.containDeep(JSON.parse(readFile(astFile))); 18 | }); 19 | 20 | it('should match output.css', function() { 21 | var output = stringify(parseInput()); 22 | output.should.equal(readFile(outputFile).trim()); 23 | }); 24 | 25 | it('should match compressed.css', function() { 26 | var compressed = stringify(parseInput(), { compress: true }); 27 | compressed.should.equal(readFile(compressedFile)); 28 | }); 29 | 30 | function parseInput() { 31 | return parse(readFile(inputFile), { source: 'input.css' }); 32 | } 33 | }); 34 | }); 35 | 36 | function readFile(file) { 37 | var src = fs.readFileSync(file, 'utf8'); 38 | // normalize line endings 39 | src = src.replace(/\r\n/, '\n'); 40 | // remove trailing newline 41 | src = src.replace(/\n$/, ''); 42 | 43 | return src; 44 | } 45 | -------------------------------------------------------------------------------- /test/cases/host/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [ 5 | { 6 | "type": "host", 7 | "rules": [ 8 | { 9 | "type": "rule", 10 | "selectors": [ 11 | ":scope" 12 | ], 13 | "declarations": [ 14 | { 15 | "type": "declaration", 16 | "property": "display", 17 | "value": "block", 18 | "position": { 19 | "start": { 20 | "line": 3, 21 | "column": 5 22 | }, 23 | "end": { 24 | "line": 3, 25 | "column": 19 26 | }, 27 | "source": "input.css" 28 | } 29 | } 30 | ], 31 | "position": { 32 | "start": { 33 | "line": 2, 34 | "column": 3 35 | }, 36 | "end": { 37 | "line": 4, 38 | "column": 4 39 | }, 40 | "source": "input.css" 41 | } 42 | } 43 | ], 44 | "position": { 45 | "start": { 46 | "line": 1, 47 | "column": 1 48 | }, 49 | "end": { 50 | "line": 5, 51 | "column": 2 52 | }, 53 | "source": "input.css" 54 | } 55 | } 56 | ] 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /test/cases/hose-linebreak/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [ 5 | { 6 | "type": "host", 7 | "rules": [ 8 | { 9 | "type": "rule", 10 | "selectors": [ 11 | ":scope" 12 | ], 13 | "declarations": [ 14 | { 15 | "type": "declaration", 16 | "property": "color", 17 | "value": "white", 18 | "position": { 19 | "start": { 20 | "line": 3, 21 | "column": 18 22 | }, 23 | "end": { 24 | "line": 3, 25 | "column": 30 26 | }, 27 | "source": "input.css" 28 | } 29 | } 30 | ], 31 | "position": { 32 | "start": { 33 | "line": 3, 34 | "column": 9 35 | }, 36 | "end": { 37 | "line": 3, 38 | "column": 33 39 | }, 40 | "source": "input.css" 41 | } 42 | } 43 | ], 44 | "position": { 45 | "start": { 46 | "line": 1, 47 | "column": 1 48 | }, 49 | "end": { 50 | "line": 4, 51 | "column": 6 52 | }, 53 | "source": "input.css" 54 | } 55 | } 56 | ] 57 | } 58 | } -------------------------------------------------------------------------------- /test/cases/supports-linebreak/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [ 5 | { 6 | "type": "supports", 7 | "supports": "(display: flex)", 8 | "rules": [ 9 | { 10 | "type": "rule", 11 | "selectors": [ 12 | ".test" 13 | ], 14 | "declarations": [ 15 | { 16 | "type": "declaration", 17 | "property": "display", 18 | "value": "flex", 19 | "position": { 20 | "start": { 21 | "line": 4, 22 | "column": 17 23 | }, 24 | "end": { 25 | "line": 4, 26 | "column": 30 27 | }, 28 | "source": "input.css" 29 | } 30 | } 31 | ], 32 | "position": { 33 | "start": { 34 | "line": 4, 35 | "column": 9 36 | }, 37 | "end": { 38 | "line": 4, 39 | "column": 33 40 | }, 41 | "source": "input.css" 42 | } 43 | } 44 | ], 45 | "position": { 46 | "start": { 47 | "line": 1, 48 | "column": 1 49 | }, 50 | "end": { 51 | "line": 5, 52 | "column": 6 53 | }, 54 | "source": "input.css" 55 | } 56 | } 57 | ] 58 | } 59 | } -------------------------------------------------------------------------------- /test/cases/media-linebreak/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [ 5 | { 6 | "type": "media", 7 | "media": "(\n min-width: 300px\n)", 8 | "rules": [ 9 | { 10 | "type": "rule", 11 | "selectors": [ 12 | ".test" 13 | ], 14 | "declarations": [ 15 | { 16 | "type": "declaration", 17 | "property": "width", 18 | "value": "100px", 19 | "position": { 20 | "start": { 21 | "line": 7, 22 | "column": 13 23 | }, 24 | "end": { 25 | "line": 7, 26 | "column": 25 27 | }, 28 | "source": "input.css" 29 | } 30 | } 31 | ], 32 | "position": { 33 | "start": { 34 | "line": 7, 35 | "column": 5 36 | }, 37 | "end": { 38 | "line": 7, 39 | "column": 28 40 | }, 41 | "source": "input.css" 42 | } 43 | } 44 | ], 45 | "position": { 46 | "start": { 47 | "line": 1, 48 | "column": 1 49 | }, 50 | "end": { 51 | "line": 8, 52 | "column": 2 53 | }, 54 | "source": "input.css" 55 | } 56 | } 57 | ] 58 | } 59 | } -------------------------------------------------------------------------------- /test/cases/document-linebreak/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [ 5 | { 6 | "type": "document", 7 | "document": "url-prefix()", 8 | "vendor": "", 9 | "rules": [ 10 | { 11 | "type": "rule", 12 | "selectors": [ 13 | ".test" 14 | ], 15 | "declarations": [ 16 | { 17 | "type": "declaration", 18 | "property": "color", 19 | "value": "blue", 20 | "position": { 21 | "start": { 22 | "line": 6, 23 | "column": 13 24 | }, 25 | "end": { 26 | "line": 6, 27 | "column": 24 28 | }, 29 | "source": "input.css" 30 | } 31 | } 32 | ], 33 | "position": { 34 | "start": { 35 | "line": 5, 36 | "column": 9 37 | }, 38 | "end": { 39 | "line": 7, 40 | "column": 10 41 | }, 42 | "source": "input.css" 43 | } 44 | } 45 | ], 46 | "position": { 47 | "start": { 48 | "line": 1, 49 | "column": 1 50 | }, 51 | "end": { 52 | "line": 9, 53 | "column": 6 54 | }, 55 | "source": "input.css" 56 | } 57 | } 58 | ] 59 | } 60 | } -------------------------------------------------------------------------------- /test/cases/charset/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [ 5 | { 6 | "type": "charset", 7 | "charset": "\"UTF-8\"", 8 | "position": { 9 | "start": { 10 | "line": 1, 11 | "column": 1 12 | }, 13 | "end": { 14 | "line": 1, 15 | "column": 18 16 | }, 17 | "source": "input.css" 18 | } 19 | }, 20 | { 21 | "type": "comment", 22 | "comment": " Set the encoding of the style sheet to Unicode UTF-8 ", 23 | "position": { 24 | "start": { 25 | "line": 1, 26 | "column": 25 27 | }, 28 | "end": { 29 | "line": 1, 30 | "column": 83 31 | }, 32 | "source": "input.css" 33 | } 34 | }, 35 | { 36 | "type": "charset", 37 | "charset": "'iso-8859-15'", 38 | "position": { 39 | "start": { 40 | "line": 2, 41 | "column": 1 42 | }, 43 | "end": { 44 | "line": 2, 45 | "column": 24 46 | }, 47 | "source": "input.css" 48 | } 49 | }, 50 | { 51 | "type": "comment", 52 | "comment": " Set the encoding of the style sheet to Latin-9 (Western European languages, with euro sign) ", 53 | "position": { 54 | "start": { 55 | "line": 2, 56 | "column": 25 57 | }, 58 | "end": { 59 | "line": 2, 60 | "column": 122 61 | }, 62 | "source": "input.css" 63 | } 64 | } 65 | ] 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /generate-tests.js: -------------------------------------------------------------------------------- 1 | // Generates missing output source and AST files for the test cases 2 | // IMPORTANT: Always verify the generated files when using this! 3 | 4 | var fs = require('fs'); 5 | var path = require('path'); 6 | var parse = require('./').parse; 7 | var stringify = require('./').stringify; 8 | 9 | var casesDir = path.join(__dirname, 'test', 'cases'); 10 | var cases = fs.readdirSync(casesDir) 11 | .map(function(f) { return path.join(casesDir, f); }); 12 | 13 | cases.forEach(function(dir) { 14 | var inputFile = path.join(dir, 'input.css'); 15 | if (!fs.existsSync(inputFile)) 16 | throw new Error('Missing input file ' + inputFile); 17 | 18 | var input = fs.readFileSync(inputFile, 'utf8'); 19 | var parsed; 20 | try { 21 | parsed = parse(input, { source: 'input.css' }); 22 | } catch(e) { 23 | console.log('Failed to parse', inputFile); 24 | throw e; 25 | } 26 | 27 | var outputFile = path.join(dir, 'output.css'); 28 | if (!fs.existsSync(outputFile)) { 29 | console.log('Generating', outputFile); 30 | var output = stringify(parsed); 31 | fs.writeFileSync(outputFile, output, 'utf8'); 32 | } 33 | 34 | var compressedFile = path.join(dir, 'compressed.css'); 35 | if (!fs.existsSync(compressedFile)) { 36 | console.log('Generating', compressedFile); 37 | var compressed = stringify(parsed, { compress: true }); 38 | fs.writeFileSync(compressedFile, compressed, 'utf8'); 39 | } 40 | 41 | var astFile = path.join(dir, 'ast.json'); 42 | if (!fs.existsSync(astFile)) { 43 | console.log('Generating', astFile); 44 | var ast = JSON.stringify(parsed, null, ' '); 45 | fs.writeFileSync(astFile, ast, 'utf8'); 46 | } 47 | }); 48 | -------------------------------------------------------------------------------- /test/cases/wtf/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [ 5 | { 6 | "type": "rule", 7 | "selectors": [ 8 | ".wtf" 9 | ], 10 | "declarations": [ 11 | { 12 | "type": "declaration", 13 | "property": "*overflow-x", 14 | "value": "hidden", 15 | "position": { 16 | "start": { 17 | "line": 2, 18 | "column": 3 19 | }, 20 | "end": { 21 | "line": 2, 22 | "column": 22 23 | }, 24 | "source": "input.css" 25 | } 26 | }, 27 | { 28 | "type": "declaration", 29 | "property": "//max-height", 30 | "value": "110px", 31 | "position": { 32 | "start": { 33 | "line": 3, 34 | "column": 3 35 | }, 36 | "end": { 37 | "line": 3, 38 | "column": 22 39 | }, 40 | "source": "input.css" 41 | } 42 | }, 43 | { 44 | "type": "declaration", 45 | "property": "#height", 46 | "value": "18px", 47 | "position": { 48 | "start": { 49 | "line": 4, 50 | "column": 3 51 | }, 52 | "end": { 53 | "line": 4, 54 | "column": 16 55 | }, 56 | "source": "input.css" 57 | } 58 | } 59 | ], 60 | "position": { 61 | "start": { 62 | "line": 1, 63 | "column": 1 64 | }, 65 | "end": { 66 | "line": 5, 67 | "column": 2 68 | }, 69 | "source": "input.css" 70 | } 71 | } 72 | ] 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2.2.1 / 2015-06-17 2 | ================== 3 | 4 | * fix parsing escaped quotes in quoted strings 5 | 6 | 2.2.0 / 2015-02-18 7 | ================== 8 | 9 | * add `parsingErrors` to list errors when parsing with `silent: true` 10 | * accept EOL characters and all other whitespace characters in `@` rules such 11 | as `@media` 12 | 13 | 2.1.0 / 2014-08-05 14 | ================== 15 | 16 | * change error message format and add `.reason` property to errors 17 | * add `inputSourcemaps` option to disable input source map processing 18 | * use `inherits` for inheritance (fixes some browsers) 19 | * add `sourcemap: 'generator'` option to return the `SourceMapGenerator` 20 | object 21 | 22 | 2.0.0 / 2014-06-18 23 | ================== 24 | 25 | * add non-enumerable parent reference to each node 26 | * drop Component(1) support 27 | * add support for @custom-media, @host, and @font-face 28 | * allow commas inside selector functions 29 | * allow empty property values 30 | * changed default options.position value to true 31 | * remove comments from properties and values 32 | * asserts when selectors are missing 33 | * added node.position.content property 34 | * absorb css-parse and css-stringify libraries 35 | * apply original source maps from source files 36 | 37 | 1.6.1 / 2014-01-02 38 | ================== 39 | 40 | * fix component.json 41 | 42 | 1.6.0 / 2013-12-21 43 | ================== 44 | 45 | * update deps 46 | 47 | 1.5.0 / 2013-12-03 48 | ================== 49 | 50 | * update deps 51 | 52 | 1.1.0 / 2013-04-04 53 | ================== 54 | 55 | * update deps 56 | 57 | 1.0.7 / 2012-11-21 58 | ================== 59 | 60 | * fix component.json 61 | 62 | 1.0.4 / 2012-11-15 63 | ================== 64 | 65 | * update css-stringify 66 | 67 | 1.0.3 / 2012-09-01 68 | ================== 69 | 70 | * add component support 71 | 72 | 0.0.1 / 2010-01-03 73 | ================== 74 | 75 | * Initial release 76 | -------------------------------------------------------------------------------- /test/cases/props/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [ 5 | { 6 | "type": "rule", 7 | "selectors": [ 8 | "tobi loki jane" 9 | ], 10 | "declarations": [ 11 | { 12 | "type": "declaration", 13 | "property": "are", 14 | "value": "'all'", 15 | "position": { 16 | "start": { 17 | "line": 3, 18 | "column": 3 19 | }, 20 | "end": { 21 | "line": 3, 22 | "column": 13 23 | }, 24 | "source": "input.css" 25 | } 26 | }, 27 | { 28 | "type": "declaration", 29 | "property": "the-species", 30 | "value": "called \"ferrets\"", 31 | "position": { 32 | "start": { 33 | "line": 4, 34 | "column": 3 35 | }, 36 | "end": { 37 | "line": 4, 38 | "column": 32 39 | }, 40 | "source": "input.css" 41 | } 42 | }, 43 | { 44 | "type": "declaration", 45 | "property": "*even", 46 | "value": "'ie crap'", 47 | "position": { 48 | "start": { 49 | "line": 5, 50 | "column": 3 51 | }, 52 | "end": { 53 | "line": 5, 54 | "column": 19 55 | }, 56 | "source": "input.css" 57 | } 58 | } 59 | ], 60 | "position": { 61 | "start": { 62 | "line": 2, 63 | "column": 1 64 | }, 65 | "end": { 66 | "line": 6, 67 | "column": 2 68 | }, 69 | "source": "input.css" 70 | } 71 | } 72 | ] 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /test/cases/escapes/input.css: -------------------------------------------------------------------------------- 1 | /* tests compressed for easy testing */ 2 | /* http://mathiasbynens.be/notes/css-escapes */ 3 | /* will match elements with class=":`(" */ 4 | .\3A \`\({} 5 | /* will match elements with class="1a2b3c" */ 6 | .\31 a2b3c{} 7 | /* will match the element with id="#fake-id" */ 8 | #\#fake-id{} 9 | /* will match the element with id="---" */ 10 | #\---{} 11 | /* will match the element with id="-a-b-c-" */ 12 | #-a-b-c-{} 13 | /* will match the element with id="©" */ 14 | #©{} 15 | /* More tests from http://mathiasbynens.be/demo/html5-id */ 16 | html{font:1.2em/1.6 Arial;} 17 | code{font-family:Consolas;} 18 | li code{background:rgba(255, 255, 255, .5);padding:.3em;} 19 | li{background:orange;} 20 | #♥{background:lime;} 21 | #©{background:lime;} 22 | #“‘’”{background:lime;} 23 | #☺☃{background:lime;} 24 | #⌘⌥{background:lime;} 25 | #𝄞♪♩♫♬{background:lime;} 26 | #\?{background:lime;} 27 | #\@{background:lime;} 28 | #\.{background:lime;} 29 | #\3A \){background:lime;} 30 | #\3A \`\({background:lime;} 31 | #\31 23{background:lime;} 32 | #\31 a2b3c{background:lime;} 33 | #\{background:lime;} 34 | #\<\>\<\<\<\>\>\<\>{background:lime;} 35 | #\+\+\+\+\+\+\+\+\+\+\[\>\+\+\+\+\+\+\+\>\+\+\+\+\+\+\+\+\+\+\>\+\+\+\>\+\<\<\<\<\-\]\>\+\+\.\>\+\.\+\+\+\+\+\+\+\.\.\+\+\+\.\>\+\+\.\<\<\+\+\+\+\+\+\+\+\+\+\+\+\+\+\+\.\>\.\+\+\+\.\-\-\-\-\-\-\.\-\-\-\-\-\-\-\-\.\>\+\.\>\.{background:lime;} 36 | #\#{background:lime;} 37 | #\#\#{background:lime;} 38 | #\#\.\#\.\#{background:lime;} 39 | #\_{background:lime;} 40 | #\.fake\-class{background:lime;} 41 | #foo\.bar{background:lime;} 42 | #\3A hover{background:lime;} 43 | #\3A hover\3A focus\3A active{background:lime;} 44 | #\[attr\=value\]{background:lime;} 45 | #f\/o\/o{background:lime;} 46 | #f\\o\\o{background:lime;} 47 | #f\*o\*o{background:lime;} 48 | #f\!o\!o{background:lime;} 49 | #f\'o\'o{background:lime;} 50 | #f\~o\~o{background:lime;} 51 | #f\+o\+o{background:lime;} 52 | 53 | /* css-parse does not yet pass this test */ 54 | /*#\{\}{background:lime;}*/ 55 | -------------------------------------------------------------------------------- /test/cases/import/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [ 5 | { 6 | "type": "import", 7 | "import": "url(\"fineprint.css\") print", 8 | "position": { 9 | "start": { 10 | "line": 1, 11 | "column": 1 12 | }, 13 | "end": { 14 | "line": 1, 15 | "column": 36 16 | }, 17 | "source": "input.css" 18 | } 19 | }, 20 | { 21 | "type": "import", 22 | "import": "url(\"bluish.css\") projection, tv", 23 | "position": { 24 | "start": { 25 | "line": 2, 26 | "column": 1 27 | }, 28 | "end": { 29 | "line": 2, 30 | "column": 42 31 | }, 32 | "source": "input.css" 33 | } 34 | }, 35 | { 36 | "type": "import", 37 | "import": "'custom.css'", 38 | "position": { 39 | "start": { 40 | "line": 3, 41 | "column": 1 42 | }, 43 | "end": { 44 | "line": 3, 45 | "column": 22 46 | }, 47 | "source": "input.css" 48 | } 49 | }, 50 | { 51 | "type": "import", 52 | "import": "\"common.css\" screen, projection", 53 | "position": { 54 | "start": { 55 | "line": 4, 56 | "column": 1 57 | }, 58 | "end": { 59 | "line": 4, 60 | "column": 41 61 | }, 62 | "source": "input.css" 63 | } 64 | }, 65 | { 66 | "type": "import", 67 | "import": "url('landscape.css') screen and (orientation:landscape)", 68 | "position": { 69 | "start": { 70 | "line": 5, 71 | "column": 1 72 | }, 73 | "end": { 74 | "line": 5, 75 | "column": 65 76 | }, 77 | "source": "input.css" 78 | } 79 | } 80 | ] 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /test/cases/import-messed/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [ 5 | { 6 | "type": "import", 7 | "import": "url(\"fineprint.css\") print", 8 | "position": { 9 | "start": { 10 | "line": 2, 11 | "column": 4 12 | }, 13 | "end": { 14 | "line": 2, 15 | "column": 39 16 | }, 17 | "source": "input.css" 18 | } 19 | }, 20 | { 21 | "type": "import", 22 | "import": "url(\"bluish.css\") projection, tv", 23 | "position": { 24 | "start": { 25 | "line": 3, 26 | "column": 3 27 | }, 28 | "end": { 29 | "line": 3, 30 | "column": 44 31 | }, 32 | "source": "input.css" 33 | } 34 | }, 35 | { 36 | "type": "import", 37 | "import": "'custom.css'", 38 | "position": { 39 | "start": { 40 | "line": 4, 41 | "column": 7 42 | }, 43 | "end": { 44 | "line": 4, 45 | "column": 28 46 | }, 47 | "source": "input.css" 48 | } 49 | }, 50 | { 51 | "type": "import", 52 | "import": "\"common.css\" screen, projection", 53 | "position": { 54 | "start": { 55 | "line": 5, 56 | "column": 3 57 | }, 58 | "end": { 59 | "line": 5, 60 | "column": 45 61 | }, 62 | "source": "input.css" 63 | } 64 | }, 65 | { 66 | "type": "import", 67 | "import": "url('landscape.css') screen and (orientation:landscape)", 68 | "position": { 69 | "start": { 70 | "line": 7, 71 | "column": 3 72 | }, 73 | "end": { 74 | "line": 7, 75 | "column": 67 76 | }, 77 | "source": "input.css" 78 | } 79 | } 80 | ] 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /test/cases/comma-selector-function/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [ 5 | { 6 | "type": "rule", 7 | "selectors": [ 8 | ".foo:matches(.bar,.baz)", 9 | ".foo:matches(.bar, .baz)", 10 | ".foo:matches(.bar , .baz)", 11 | ".foo:matches(.bar ,.baz)" 12 | ], 13 | "declarations": [ 14 | { 15 | "type": "declaration", 16 | "property": "prop", 17 | "value": "value", 18 | "position": { 19 | "start": { 20 | "line": 5, 21 | "column": 3 22 | }, 23 | "end": { 24 | "line": 5, 25 | "column": 14 26 | }, 27 | "source": "input.css" 28 | } 29 | } 30 | ], 31 | "position": { 32 | "start": { 33 | "line": 1, 34 | "column": 1 35 | }, 36 | "end": { 37 | "line": 6, 38 | "column": 2 39 | }, 40 | "source": "input.css" 41 | } 42 | }, 43 | { 44 | "type": "rule", 45 | "selectors": [ 46 | ".foo:matches(.bar,.baz,.foobar)", 47 | ".foo:matches(.bar, .baz,)", 48 | ".foo:matches(,.bar , .baz)" 49 | ], 50 | "declarations": [ 51 | { 52 | "type": "declaration", 53 | "property": "anotherprop", 54 | "value": "anothervalue", 55 | "position": { 56 | "start": { 57 | "line": 11, 58 | "column": 3 59 | }, 60 | "end": { 61 | "line": 11, 62 | "column": 28 63 | }, 64 | "source": "input.css" 65 | } 66 | } 67 | ], 68 | "position": { 69 | "start": { 70 | "line": 8, 71 | "column": 1 72 | }, 73 | "end": { 74 | "line": 12, 75 | "column": 2 76 | }, 77 | "source": "input.css" 78 | } 79 | } 80 | ] 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /test/cases/comment-in/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [ 5 | { 6 | "type": "rule", 7 | "selectors": [ 8 | "a" 9 | ], 10 | "declarations": [ 11 | { 12 | "type": "declaration", 13 | "property": "color", 14 | "value": "12px", 15 | "position": { 16 | "start": { 17 | "line": 2, 18 | "column": 5 19 | }, 20 | "end": { 21 | "line": 2, 22 | "column": 20 23 | }, 24 | "source": "input.css" 25 | } 26 | }, 27 | { 28 | "type": "declaration", 29 | "property": "padding", 30 | "value": "1px 2px 3px", 31 | "position": { 32 | "start": { 33 | "line": 3, 34 | "column": 5 35 | }, 36 | "end": { 37 | "line": 3, 38 | "column": 51 39 | }, 40 | "source": "input.css" 41 | } 42 | }, 43 | { 44 | "type": "declaration", 45 | "property": "border", 46 | "value": "solid", 47 | "position": { 48 | "start": { 49 | "line": 4, 50 | "column": 5 51 | }, 52 | "end": { 53 | "line": 4, 54 | "column": 24 55 | }, 56 | "source": "input.css" 57 | } 58 | }, 59 | { 60 | "type": "declaration", 61 | "property": "border-top", 62 | "value": "none\\9", 63 | "position": { 64 | "start": { 65 | "line": 4, 66 | "column": 26 67 | }, 68 | "end": { 69 | "line": 4, 70 | "column": 50 71 | }, 72 | "source": "input.css" 73 | } 74 | } 75 | ], 76 | "position": { 77 | "start": { 78 | "line": 1, 79 | "column": 1 80 | }, 81 | "end": { 82 | "line": 5, 83 | "column": 2 84 | }, 85 | "source": "input.css" 86 | } 87 | } 88 | ] 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /test/cases/font-face/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [ 5 | { 6 | "type": "font-face", 7 | "declarations": [ 8 | { 9 | "type": "declaration", 10 | "property": "font-family", 11 | "value": "\"Bitstream Vera Serif Bold\"", 12 | "position": { 13 | "start": { 14 | "line": 2, 15 | "column": 3 16 | }, 17 | "end": { 18 | "line": 2, 19 | "column": 43 20 | }, 21 | "source": "input.css" 22 | } 23 | }, 24 | { 25 | "type": "declaration", 26 | "property": "src", 27 | "value": "url(\"http://developer.mozilla.org/@api/deki/files/2934/=VeraSeBd.ttf\")", 28 | "position": { 29 | "start": { 30 | "line": 3, 31 | "column": 3 32 | }, 33 | "end": { 34 | "line": 3, 35 | "column": 78 36 | }, 37 | "source": "input.css" 38 | } 39 | } 40 | ], 41 | "position": { 42 | "start": { 43 | "line": 1, 44 | "column": 1 45 | }, 46 | "end": { 47 | "line": 4, 48 | "column": 2 49 | }, 50 | "source": "input.css" 51 | } 52 | }, 53 | { 54 | "type": "rule", 55 | "selectors": [ 56 | "body" 57 | ], 58 | "declarations": [ 59 | { 60 | "type": "declaration", 61 | "property": "font-family", 62 | "value": "\"Bitstream Vera Serif Bold\", serif", 63 | "position": { 64 | "start": { 65 | "line": 7, 66 | "column": 3 67 | }, 68 | "end": { 69 | "line": 7, 70 | "column": 50 71 | }, 72 | "source": "input.css" 73 | } 74 | } 75 | ], 76 | "position": { 77 | "start": { 78 | "line": 6, 79 | "column": 1 80 | }, 81 | "end": { 82 | "line": 8, 83 | "column": 2 84 | }, 85 | "source": "input.css" 86 | } 87 | } 88 | ] 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /test/cases/font-face-linebreak/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [ 5 | { 6 | "type": "font-face", 7 | "declarations": [ 8 | { 9 | "type": "declaration", 10 | "property": "font-family", 11 | "value": "\"Bitstream Vera Serif Bold\"", 12 | "position": { 13 | "start": { 14 | "line": 4, 15 | "column": 3 16 | }, 17 | "end": { 18 | "line": 4, 19 | "column": 43 20 | }, 21 | "source": "input.css" 22 | } 23 | }, 24 | { 25 | "type": "declaration", 26 | "property": "src", 27 | "value": "url(\"http://developer.mozilla.org/@api/deki/files/2934/=VeraSeBd.ttf\")", 28 | "position": { 29 | "start": { 30 | "line": 5, 31 | "column": 3 32 | }, 33 | "end": { 34 | "line": 5, 35 | "column": 78 36 | }, 37 | "source": "input.css" 38 | } 39 | } 40 | ], 41 | "position": { 42 | "start": { 43 | "line": 1, 44 | "column": 1 45 | }, 46 | "end": { 47 | "line": 6, 48 | "column": 2 49 | }, 50 | "source": "input.css" 51 | } 52 | }, 53 | { 54 | "type": "rule", 55 | "selectors": [ 56 | "body" 57 | ], 58 | "declarations": [ 59 | { 60 | "type": "declaration", 61 | "property": "font-family", 62 | "value": "\"Bitstream Vera Serif Bold\", serif", 63 | "position": { 64 | "start": { 65 | "line": 9, 66 | "column": 3 67 | }, 68 | "end": { 69 | "line": 9, 70 | "column": 50 71 | }, 72 | "source": "input.css" 73 | } 74 | } 75 | ], 76 | "position": { 77 | "start": { 78 | "line": 8, 79 | "column": 1 80 | }, 81 | "end": { 82 | "line": 10, 83 | "column": 2 84 | }, 85 | "source": "input.css" 86 | } 87 | } 88 | ] 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /test/cases/keyframes-linebreak/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [ 5 | { 6 | "type": "keyframes", 7 | "name": "test", 8 | "keyframes": [ 9 | { 10 | "type": "keyframe", 11 | "values": [ 12 | "from" 13 | ], 14 | "declarations": [ 15 | { 16 | "type": "declaration", 17 | "property": "opacity", 18 | "value": "1", 19 | "position": { 20 | "start": { 21 | "line": 4, 22 | "column": 16 23 | }, 24 | "end": { 25 | "line": 4, 26 | "column": 26 27 | }, 28 | "source": "input.css" 29 | } 30 | } 31 | ], 32 | "position": { 33 | "start": { 34 | "line": 4, 35 | "column": 9 36 | }, 37 | "end": { 38 | "line": 4, 39 | "column": 29 40 | }, 41 | "source": "input.css" 42 | } 43 | }, 44 | { 45 | "type": "keyframe", 46 | "values": [ 47 | "to" 48 | ], 49 | "declarations": [ 50 | { 51 | "type": "declaration", 52 | "property": "opacity", 53 | "value": "0", 54 | "position": { 55 | "start": { 56 | "line": 5, 57 | "column": 14 58 | }, 59 | "end": { 60 | "line": 5, 61 | "column": 24 62 | }, 63 | "source": "input.css" 64 | } 65 | } 66 | ], 67 | "position": { 68 | "start": { 69 | "line": 5, 70 | "column": 9 71 | }, 72 | "end": { 73 | "line": 5, 74 | "column": 27 75 | }, 76 | "source": "input.css" 77 | } 78 | } 79 | ], 80 | "position": { 81 | "start": { 82 | "line": 1, 83 | "column": 1 84 | }, 85 | "end": { 86 | "line": 6, 87 | "column": 6 88 | }, 89 | "source": "input.css" 90 | } 91 | } 92 | ] 93 | } 94 | } -------------------------------------------------------------------------------- /test/cases/keyframes-messed/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [ 5 | { 6 | "type": "keyframes", 7 | "name": "fade", 8 | "keyframes": [ 9 | { 10 | "type": "keyframe", 11 | "values": [ 12 | "from" 13 | ], 14 | "declarations": [ 15 | { 16 | "type": "declaration", 17 | "property": "opacity", 18 | "value": "0", 19 | "position": { 20 | "start": { 21 | "line": 2, 22 | "column": 4 23 | }, 24 | "end": { 25 | "line": 2, 26 | "column": 14 27 | }, 28 | "source": "input.css" 29 | } 30 | } 31 | ], 32 | "position": { 33 | "start": { 34 | "line": 1, 35 | "column": 18 36 | }, 37 | "end": { 38 | "line": 3, 39 | "column": 7 40 | }, 41 | "source": "input.css" 42 | } 43 | }, 44 | { 45 | "type": "keyframe", 46 | "values": [ 47 | "to" 48 | ], 49 | "declarations": [ 50 | { 51 | "type": "declaration", 52 | "property": "opacity", 53 | "value": "1", 54 | "position": { 55 | "start": { 56 | "line": 6, 57 | "column": 6 58 | }, 59 | "end": { 60 | "line": 6, 61 | "column": 16 62 | }, 63 | "source": "input.css" 64 | } 65 | } 66 | ], 67 | "position": { 68 | "start": { 69 | "line": 4, 70 | "column": 1 71 | }, 72 | "end": { 73 | "line": 6, 74 | "column": 18 75 | }, 76 | "source": "input.css" 77 | } 78 | } 79 | ], 80 | "position": { 81 | "start": { 82 | "line": 1, 83 | "column": 1 84 | }, 85 | "end": { 86 | "line": 6, 87 | "column": 19 88 | }, 89 | "source": "input.css" 90 | } 91 | } 92 | ] 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /test/cases/keyframes-vendor/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [ 5 | { 6 | "type": "keyframes", 7 | "name": "fade", 8 | "vendor": "-webkit-", 9 | "keyframes": [ 10 | { 11 | "type": "keyframe", 12 | "values": [ 13 | "from" 14 | ], 15 | "declarations": [ 16 | { 17 | "type": "declaration", 18 | "property": "opacity", 19 | "value": "0", 20 | "position": { 21 | "start": { 22 | "line": 2, 23 | "column": 10 24 | }, 25 | "end": { 26 | "line": 2, 27 | "column": 21 28 | }, 29 | "source": "input.css" 30 | } 31 | } 32 | ], 33 | "position": { 34 | "start": { 35 | "line": 2, 36 | "column": 3 37 | }, 38 | "end": { 39 | "line": 2, 40 | "column": 22 41 | }, 42 | "source": "input.css" 43 | } 44 | }, 45 | { 46 | "type": "keyframe", 47 | "values": [ 48 | "to" 49 | ], 50 | "declarations": [ 51 | { 52 | "type": "declaration", 53 | "property": "opacity", 54 | "value": "1", 55 | "position": { 56 | "start": { 57 | "line": 3, 58 | "column": 8 59 | }, 60 | "end": { 61 | "line": 3, 62 | "column": 19 63 | }, 64 | "source": "input.css" 65 | } 66 | } 67 | ], 68 | "position": { 69 | "start": { 70 | "line": 3, 71 | "column": 3 72 | }, 73 | "end": { 74 | "line": 3, 75 | "column": 20 76 | }, 77 | "source": "input.css" 78 | } 79 | } 80 | ], 81 | "position": { 82 | "start": { 83 | "line": 1, 84 | "column": 1 85 | }, 86 | "end": { 87 | "line": 4, 88 | "column": 2 89 | }, 90 | "source": "input.css" 91 | } 92 | } 93 | ] 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /test/cases/rules/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [ 5 | { 6 | "type": "rule", 7 | "selectors": [ 8 | "tobi" 9 | ], 10 | "declarations": [ 11 | { 12 | "type": "declaration", 13 | "property": "name", 14 | "value": "'tobi'", 15 | "position": { 16 | "start": { 17 | "line": 2, 18 | "column": 3 19 | }, 20 | "end": { 21 | "line": 2, 22 | "column": 15 23 | }, 24 | "source": "input.css" 25 | } 26 | }, 27 | { 28 | "type": "declaration", 29 | "property": "age", 30 | "value": "2", 31 | "position": { 32 | "start": { 33 | "line": 3, 34 | "column": 3 35 | }, 36 | "end": { 37 | "line": 3, 38 | "column": 9 39 | }, 40 | "source": "input.css" 41 | } 42 | } 43 | ], 44 | "position": { 45 | "start": { 46 | "line": 1, 47 | "column": 1 48 | }, 49 | "end": { 50 | "line": 4, 51 | "column": 2 52 | }, 53 | "source": "input.css" 54 | } 55 | }, 56 | { 57 | "type": "rule", 58 | "selectors": [ 59 | "loki" 60 | ], 61 | "declarations": [ 62 | { 63 | "type": "declaration", 64 | "property": "name", 65 | "value": "'loki'", 66 | "position": { 67 | "start": { 68 | "line": 7, 69 | "column": 3 70 | }, 71 | "end": { 72 | "line": 7, 73 | "column": 15 74 | }, 75 | "source": "input.css" 76 | } 77 | }, 78 | { 79 | "type": "declaration", 80 | "property": "age", 81 | "value": "1", 82 | "position": { 83 | "start": { 84 | "line": 8, 85 | "column": 3 86 | }, 87 | "end": { 88 | "line": 8, 89 | "column": 9 90 | }, 91 | "source": "input.css" 92 | } 93 | } 94 | ], 95 | "position": { 96 | "start": { 97 | "line": 6, 98 | "column": 1 99 | }, 100 | "end": { 101 | "line": 9, 102 | "column": 2 103 | }, 104 | "source": "input.css" 105 | } 106 | } 107 | ] 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /test/cases/escapes/output.css: -------------------------------------------------------------------------------- 1 | /* tests compressed for easy testing */ 2 | 3 | /* http://mathiasbynens.be/notes/css-escapes */ 4 | 5 | /* will match elements with class=":`(" */ 6 | 7 | 8 | 9 | /* will match elements with class="1a2b3c" */ 10 | 11 | 12 | 13 | /* will match the element with id="#fake-id" */ 14 | 15 | 16 | 17 | /* will match the element with id="---" */ 18 | 19 | 20 | 21 | /* will match the element with id="-a-b-c-" */ 22 | 23 | 24 | 25 | /* will match the element with id="©" */ 26 | 27 | 28 | 29 | /* More tests from http://mathiasbynens.be/demo/html5-id */ 30 | 31 | html { 32 | font: 1.2em/1.6 Arial; 33 | } 34 | 35 | code { 36 | font-family: Consolas; 37 | } 38 | 39 | li code { 40 | background: rgba(255, 255, 255, .5); 41 | padding: .3em; 42 | } 43 | 44 | li { 45 | background: orange; 46 | } 47 | 48 | #♥ { 49 | background: lime; 50 | } 51 | 52 | #© { 53 | background: lime; 54 | } 55 | 56 | #“‘’” { 57 | background: lime; 58 | } 59 | 60 | #☺☃ { 61 | background: lime; 62 | } 63 | 64 | #⌘⌥ { 65 | background: lime; 66 | } 67 | 68 | #𝄞♪♩♫♬ { 69 | background: lime; 70 | } 71 | 72 | #\? { 73 | background: lime; 74 | } 75 | 76 | #\@ { 77 | background: lime; 78 | } 79 | 80 | #\. { 81 | background: lime; 82 | } 83 | 84 | #\3A \) { 85 | background: lime; 86 | } 87 | 88 | #\3A \`\( { 89 | background: lime; 90 | } 91 | 92 | #\31 23 { 93 | background: lime; 94 | } 95 | 96 | #\31 a2b3c { 97 | background: lime; 98 | } 99 | 100 | #\ { 101 | background: lime; 102 | } 103 | 104 | #\<\>\<\<\<\>\>\<\> { 105 | background: lime; 106 | } 107 | 108 | #\+\+\+\+\+\+\+\+\+\+\[\>\+\+\+\+\+\+\+\>\+\+\+\+\+\+\+\+\+\+\>\+\+\+\>\+\<\<\<\<\-\]\>\+\+\.\>\+\.\+\+\+\+\+\+\+\.\.\+\+\+\.\>\+\+\.\<\<\+\+\+\+\+\+\+\+\+\+\+\+\+\+\+\.\>\.\+\+\+\.\-\-\-\-\-\-\.\-\-\-\-\-\-\-\-\.\>\+\.\>\. { 109 | background: lime; 110 | } 111 | 112 | #\# { 113 | background: lime; 114 | } 115 | 116 | #\#\# { 117 | background: lime; 118 | } 119 | 120 | #\#\.\#\.\# { 121 | background: lime; 122 | } 123 | 124 | #\_ { 125 | background: lime; 126 | } 127 | 128 | #\.fake\-class { 129 | background: lime; 130 | } 131 | 132 | #foo\.bar { 133 | background: lime; 134 | } 135 | 136 | #\3A hover { 137 | background: lime; 138 | } 139 | 140 | #\3A hover\3A focus\3A active { 141 | background: lime; 142 | } 143 | 144 | #\[attr\=value\] { 145 | background: lime; 146 | } 147 | 148 | #f\/o\/o { 149 | background: lime; 150 | } 151 | 152 | #f\\o\\o { 153 | background: lime; 154 | } 155 | 156 | #f\*o\*o { 157 | background: lime; 158 | } 159 | 160 | #f\!o\!o { 161 | background: lime; 162 | } 163 | 164 | #f\'o\'o { 165 | background: lime; 166 | } 167 | 168 | #f\~o\~o { 169 | background: lime; 170 | } 171 | 172 | #f\+o\+o { 173 | background: lime; 174 | } 175 | 176 | /* css-parse does not yet pass this test */ 177 | 178 | /*#\{\}{background:lime;}*/ 179 | -------------------------------------------------------------------------------- /test/cases/comment-url/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [ 5 | { 6 | "type": "comment", 7 | "comment": " http://foo.com/bar/baz.html ", 8 | "position": { 9 | "start": { 10 | "line": 1, 11 | "column": 1 12 | }, 13 | "end": { 14 | "line": 1, 15 | "column": 34 16 | }, 17 | "source": "input.css" 18 | } 19 | }, 20 | { 21 | "type": "comment", 22 | "comment": "", 23 | "position": { 24 | "start": { 25 | "line": 2, 26 | "column": 1 27 | }, 28 | "end": { 29 | "line": 2, 30 | "column": 5 31 | }, 32 | "source": "input.css" 33 | } 34 | }, 35 | { 36 | "type": "rule", 37 | "selectors": [ 38 | "foo" 39 | ], 40 | "declarations": [ 41 | { 42 | "type": "comment", 43 | "comment": "/", 44 | "position": { 45 | "start": { 46 | "line": 4, 47 | "column": 7 48 | }, 49 | "end": { 50 | "line": 4, 51 | "column": 12 52 | }, 53 | "source": "input.css" 54 | } 55 | }, 56 | { 57 | "type": "comment", 58 | "comment": " something ", 59 | "position": { 60 | "start": { 61 | "line": 5, 62 | "column": 3 63 | }, 64 | "end": { 65 | "line": 5, 66 | "column": 18 67 | }, 68 | "source": "input.css" 69 | } 70 | }, 71 | { 72 | "type": "declaration", 73 | "property": "bar", 74 | "value": "baz", 75 | "position": { 76 | "start": { 77 | "line": 6, 78 | "column": 3 79 | }, 80 | "end": { 81 | "line": 6, 82 | "column": 11 83 | }, 84 | "source": "input.css" 85 | } 86 | }, 87 | { 88 | "type": "comment", 89 | "comment": " http://foo.com/bar/baz.html ", 90 | "position": { 91 | "start": { 92 | "line": 6, 93 | "column": 13 94 | }, 95 | "end": { 96 | "line": 6, 97 | "column": 46 98 | }, 99 | "source": "input.css" 100 | } 101 | } 102 | ], 103 | "position": { 104 | "start": { 105 | "line": 4, 106 | "column": 1 107 | }, 108 | "end": { 109 | "line": 7, 110 | "column": 2 111 | }, 112 | "source": "input.css" 113 | } 114 | } 115 | ] 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /test/parse.js: -------------------------------------------------------------------------------- 1 | var parse = require('../').parse; 2 | var should = require('should'); 3 | 4 | describe('parse(str)', function() { 5 | it('should save the filename and source', function() { 6 | var css = 'booty {\n size: large;\n}\n'; 7 | var ast = parse(css, { 8 | source: 'booty.css' 9 | }); 10 | 11 | ast.stylesheet.source.should.equal('booty.css'); 12 | 13 | var position = ast.stylesheet.rules[0].position; 14 | position.start.should.be.ok; 15 | position.end.should.be.ok; 16 | position.source.should.equal('booty.css'); 17 | position.content.should.equal(css); 18 | }); 19 | 20 | it('should throw when a selector is missing', function() { 21 | should(function() { 22 | parse('{size: large}'); 23 | }).throw(); 24 | 25 | should(function() { 26 | parse('b { color: red; }\n{ color: green; }\na { color: blue; }'); 27 | }).throw(); 28 | }); 29 | 30 | it('should throw when a broken comment is found', function () { 31 | should(function() { 32 | parse('thing { color: red; } /* b { color: blue; }'); 33 | }).throw(); 34 | 35 | should(function() { 36 | parse('/*'); 37 | }).throw(); 38 | 39 | /* Nested comments should be fine */ 40 | should(function() { 41 | parse('/* /* */'); 42 | }).not.throw(); 43 | }); 44 | 45 | it('should allow empty property value', function() { 46 | should(function() { 47 | parse('p { color:; }'); 48 | }).not.throw(); 49 | }); 50 | 51 | it('should not throw with silent option', function () { 52 | should(function() { 53 | parse('thing { color: red; } /* b { color: blue; }', { silent: true }); 54 | }).not.throw(); 55 | }); 56 | 57 | it('should list the parsing errors and continue parsing', function() { 58 | var result = parse('foo { color= red; } bar { color: blue; } baz {}} boo { display: none}', { 59 | silent: true, 60 | source: 'foo.css' 61 | }); 62 | 63 | var rules = result.stylesheet.rules; 64 | rules.length.should.be.above(2); 65 | 66 | var errors = result.stylesheet.parsingErrors; 67 | errors.length.should.equal(2); 68 | 69 | errors[0].should.have.a.property('message'); 70 | errors[0].should.have.a.property('reason'); 71 | errors[0].should.have.a.property('filename'); 72 | errors[0].filename.should.equal('foo.css'); 73 | errors[0].should.have.a.property('line'); 74 | errors[0].should.have.a.property('column'); 75 | errors[0].should.have.a.property('source'); 76 | 77 | }); 78 | 79 | it('should set parent property', function() { 80 | var result = parse( 81 | 'thing { test: value; }\n' + 82 | '@media (min-width: 100px) { thing { test: value; } }'); 83 | 84 | should(result.parent).equal(null); 85 | 86 | var rules = result.stylesheet.rules; 87 | rules.length.should.equal(2); 88 | 89 | var rule = rules[0]; 90 | rule.parent.should.equal(result); 91 | rule.declarations.length.should.equal(1); 92 | 93 | var decl = rule.declarations[0]; 94 | decl.parent.should.equal(rule); 95 | 96 | var media = rules[1]; 97 | media.parent.should.equal(result); 98 | media.rules.length.should.equal(1); 99 | 100 | rule = media.rules[0]; 101 | rule.parent.should.equal(media); 102 | 103 | rule.declarations.length.should.equal(1); 104 | decl = rule.declarations[0]; 105 | decl.parent.should.equal(rule); 106 | }); 107 | 108 | }); 109 | -------------------------------------------------------------------------------- /test/cases/document/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [ 5 | { 6 | "type": "document", 7 | "document": "url-prefix()", 8 | "vendor": "-moz-", 9 | "rules": [ 10 | { 11 | "type": "comment", 12 | "comment": " ui above ", 13 | "position": { 14 | "start": { 15 | "line": 2, 16 | "column": 3 17 | }, 18 | "end": { 19 | "line": 2, 20 | "column": 17 21 | }, 22 | "source": "input.css" 23 | } 24 | }, 25 | { 26 | "type": "rule", 27 | "selectors": [ 28 | ".ui-select .ui-btn select" 29 | ], 30 | "declarations": [ 31 | { 32 | "type": "comment", 33 | "comment": " ui inside ", 34 | "position": { 35 | "start": { 36 | "line": 4, 37 | "column": 5 38 | }, 39 | "end": { 40 | "line": 4, 41 | "column": 20 42 | }, 43 | "source": "input.css" 44 | } 45 | }, 46 | { 47 | "type": "declaration", 48 | "property": "opacity", 49 | "value": ".0001", 50 | "position": { 51 | "start": { 52 | "line": 5, 53 | "column": 5 54 | }, 55 | "end": { 56 | "line": 6, 57 | "column": 3 58 | }, 59 | "source": "input.css" 60 | } 61 | } 62 | ], 63 | "position": { 64 | "start": { 65 | "line": 3, 66 | "column": 3 67 | }, 68 | "end": { 69 | "line": 6, 70 | "column": 4 71 | }, 72 | "source": "input.css" 73 | } 74 | }, 75 | { 76 | "type": "rule", 77 | "selectors": [ 78 | ".icon-spin" 79 | ], 80 | "declarations": [ 81 | { 82 | "type": "declaration", 83 | "property": "height", 84 | "value": ".9em", 85 | "position": { 86 | "start": { 87 | "line": 9, 88 | "column": 5 89 | }, 90 | "end": { 91 | "line": 9, 92 | "column": 17 93 | }, 94 | "source": "input.css" 95 | } 96 | } 97 | ], 98 | "position": { 99 | "start": { 100 | "line": 8, 101 | "column": 3 102 | }, 103 | "end": { 104 | "line": 10, 105 | "column": 4 106 | }, 107 | "source": "input.css" 108 | } 109 | } 110 | ], 111 | "position": { 112 | "start": { 113 | "line": 1, 114 | "column": 1 115 | }, 116 | "end": { 117 | "line": 11, 118 | "column": 2 119 | }, 120 | "source": "input.css" 121 | } 122 | } 123 | ] 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /lib/stringify/source-map-support.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var SourceMap = require('source-map').SourceMapGenerator; 7 | var SourceMapConsumer = require('source-map').SourceMapConsumer; 8 | var sourceMapResolve = require('source-map-resolve'); 9 | var fs = require('fs'); 10 | var path = require('path'); 11 | 12 | /** 13 | * Expose `mixin()`. 14 | */ 15 | 16 | module.exports = mixin; 17 | 18 | /** 19 | * Ensure Windows-style paths are formatted properly 20 | */ 21 | 22 | const makeFriendlyPath = function(aPath) { 23 | return path.sep === "\\" ? aPath.replace(/\\/g, "/").replace(/^[a-z]:\/?/i, "/") : aPath; 24 | } 25 | 26 | /** 27 | * Mixin source map support into `compiler`. 28 | * 29 | * @param {Compiler} compiler 30 | * @api public 31 | */ 32 | 33 | function mixin(compiler) { 34 | compiler._comment = compiler.comment; 35 | compiler.map = new SourceMap(); 36 | compiler.position = { line: 1, column: 1 }; 37 | compiler.files = {}; 38 | for (var k in exports) compiler[k] = exports[k]; 39 | } 40 | 41 | /** 42 | * Update position. 43 | * 44 | * @param {String} str 45 | * @api private 46 | */ 47 | 48 | exports.updatePosition = function(str) { 49 | var lines = str.match(/\n/g); 50 | if (lines) this.position.line += lines.length; 51 | var i = str.lastIndexOf('\n'); 52 | this.position.column = ~i ? str.length - i : this.position.column + str.length; 53 | }; 54 | 55 | /** 56 | * Emit `str`. 57 | * 58 | * @param {String} str 59 | * @param {Object} [pos] 60 | * @return {String} 61 | * @api private 62 | */ 63 | 64 | exports.emit = function(str, pos) { 65 | if (pos) { 66 | var sourceFile = makeFriendlyPath(pos.source || 'source.css'); 67 | 68 | this.map.addMapping({ 69 | source: sourceFile, 70 | generated: { 71 | line: this.position.line, 72 | column: Math.max(this.position.column - 1, 0) 73 | }, 74 | original: { 75 | line: pos.start.line, 76 | column: pos.start.column - 1 77 | } 78 | }); 79 | 80 | this.addFile(sourceFile, pos); 81 | } 82 | 83 | this.updatePosition(str); 84 | 85 | return str; 86 | }; 87 | 88 | /** 89 | * Adds a file to the source map output if it has not already been added 90 | * @param {String} file 91 | * @param {Object} pos 92 | */ 93 | 94 | exports.addFile = function(file, pos) { 95 | if (typeof pos.content !== 'string') return; 96 | if (Object.prototype.hasOwnProperty.call(this.files, file)) return; 97 | 98 | this.files[file] = pos.content; 99 | }; 100 | 101 | /** 102 | * Applies any original source maps to the output and embeds the source file 103 | * contents in the source map. 104 | */ 105 | 106 | exports.applySourceMaps = function() { 107 | Object.keys(this.files).forEach(function(file) { 108 | var content = this.files[file]; 109 | this.map.setSourceContent(file, content); 110 | 111 | if (this.options.inputSourcemaps !== false) { 112 | var originalMap = sourceMapResolve.resolveSync( 113 | content, file, fs.readFileSync); 114 | if (originalMap) { 115 | var map = new SourceMapConsumer(originalMap.map); 116 | var relativeTo = originalMap.sourcesRelativeTo; 117 | this.map.applySourceMap(map, file, makeFriendlyPath(path.dirname(relativeTo))); 118 | } 119 | } 120 | }, this); 121 | }; 122 | 123 | /** 124 | * Process comments, drops sourceMap comments. 125 | * @param {Object} node 126 | */ 127 | 128 | exports.comment = function(node) { 129 | if (/^# sourceMappingURL=/.test(node.comment)) 130 | return this.emit('', node.position); 131 | else 132 | return this._comment(node); 133 | }; 134 | -------------------------------------------------------------------------------- /test/cases/paged-media/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [ 5 | { 6 | "type": "comment", 7 | "comment": " toc above ", 8 | "position": { 9 | "start": { 10 | "line": 1, 11 | "column": 1 12 | }, 13 | "end": { 14 | "line": 1, 15 | "column": 16 16 | }, 17 | "source": "input.css" 18 | } 19 | }, 20 | { 21 | "type": "page", 22 | "selectors": [ 23 | "toc", 24 | "index:blank" 25 | ], 26 | "declarations": [ 27 | { 28 | "type": "comment", 29 | "comment": " toc inside ", 30 | "position": { 31 | "start": { 32 | "line": 3, 33 | "column": 3 34 | }, 35 | "end": { 36 | "line": 3, 37 | "column": 19 38 | }, 39 | "source": "input.css" 40 | } 41 | }, 42 | { 43 | "type": "declaration", 44 | "property": "color", 45 | "value": "green", 46 | "position": { 47 | "start": { 48 | "line": 4, 49 | "column": 3 50 | }, 51 | "end": { 52 | "line": 4, 53 | "column": 15 54 | }, 55 | "source": "input.css" 56 | } 57 | } 58 | ], 59 | "position": { 60 | "start": { 61 | "line": 2, 62 | "column": 1 63 | }, 64 | "end": { 65 | "line": 5, 66 | "column": 2 67 | }, 68 | "source": "input.css" 69 | } 70 | }, 71 | { 72 | "type": "page", 73 | "selectors": [], 74 | "declarations": [ 75 | { 76 | "type": "declaration", 77 | "property": "font-size", 78 | "value": "16pt", 79 | "position": { 80 | "start": { 81 | "line": 8, 82 | "column": 3 83 | }, 84 | "end": { 85 | "line": 8, 86 | "column": 18 87 | }, 88 | "source": "input.css" 89 | } 90 | } 91 | ], 92 | "position": { 93 | "start": { 94 | "line": 7, 95 | "column": 1 96 | }, 97 | "end": { 98 | "line": 9, 99 | "column": 2 100 | }, 101 | "source": "input.css" 102 | } 103 | }, 104 | { 105 | "type": "page", 106 | "selectors": [ 107 | ":left" 108 | ], 109 | "declarations": [ 110 | { 111 | "type": "declaration", 112 | "property": "margin-left", 113 | "value": "5cm", 114 | "position": { 115 | "start": { 116 | "line": 12, 117 | "column": 3 118 | }, 119 | "end": { 120 | "line": 12, 121 | "column": 19 122 | }, 123 | "source": "input.css" 124 | } 125 | } 126 | ], 127 | "position": { 128 | "start": { 129 | "line": 11, 130 | "column": 1 131 | }, 132 | "end": { 133 | "line": 13, 134 | "column": 2 135 | }, 136 | "source": "input.css" 137 | } 138 | } 139 | ] 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /test/cases/keyframes-advanced/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [ 5 | { 6 | "type": "keyframes", 7 | "name": "advanced", 8 | "keyframes": [ 9 | { 10 | "type": "keyframe", 11 | "values": [ 12 | "top" 13 | ], 14 | "declarations": [ 15 | { 16 | "type": "declaration", 17 | "property": "opacity[sqrt]", 18 | "value": "0", 19 | "position": { 20 | "start": { 21 | "line": 3, 22 | "column": 5 23 | }, 24 | "end": { 25 | "line": 3, 26 | "column": 21 27 | }, 28 | "source": "input.css" 29 | } 30 | } 31 | ], 32 | "position": { 33 | "start": { 34 | "line": 2, 35 | "column": 3 36 | }, 37 | "end": { 38 | "line": 4, 39 | "column": 4 40 | }, 41 | "source": "input.css" 42 | } 43 | }, 44 | { 45 | "type": "keyframe", 46 | "values": [ 47 | "100" 48 | ], 49 | "declarations": [ 50 | { 51 | "type": "declaration", 52 | "property": "opacity", 53 | "value": "0.5", 54 | "position": { 55 | "start": { 56 | "line": 7, 57 | "column": 5 58 | }, 59 | "end": { 60 | "line": 7, 61 | "column": 17 62 | }, 63 | "source": "input.css" 64 | } 65 | } 66 | ], 67 | "position": { 68 | "start": { 69 | "line": 6, 70 | "column": 3 71 | }, 72 | "end": { 73 | "line": 8, 74 | "column": 4 75 | }, 76 | "source": "input.css" 77 | } 78 | }, 79 | { 80 | "type": "keyframe", 81 | "values": [ 82 | "bottom" 83 | ], 84 | "declarations": [ 85 | { 86 | "type": "declaration", 87 | "property": "opacity", 88 | "value": "1", 89 | "position": { 90 | "start": { 91 | "line": 11, 92 | "column": 5 93 | }, 94 | "end": { 95 | "line": 11, 96 | "column": 15 97 | }, 98 | "source": "input.css" 99 | } 100 | } 101 | ], 102 | "position": { 103 | "start": { 104 | "line": 10, 105 | "column": 3 106 | }, 107 | "end": { 108 | "line": 12, 109 | "column": 4 110 | }, 111 | "source": "input.css" 112 | } 113 | } 114 | ], 115 | "position": { 116 | "start": { 117 | "line": 1, 118 | "column": 1 119 | }, 120 | "end": { 121 | "line": 13, 122 | "column": 2 123 | }, 124 | "source": "input.css" 125 | } 126 | } 127 | ] 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /test/cases/messed-up/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [ 5 | { 6 | "type": "rule", 7 | "selectors": [ 8 | "body" 9 | ], 10 | "declarations": [ 11 | { 12 | "type": "declaration", 13 | "property": "foo", 14 | "value": "'bar'", 15 | "position": { 16 | "start": { 17 | "line": 1, 18 | "column": 8 19 | }, 20 | "end": { 21 | "line": 3, 22 | "column": 9 23 | }, 24 | "source": "input.css" 25 | } 26 | } 27 | ], 28 | "position": { 29 | "start": { 30 | "line": 1, 31 | "column": 1 32 | }, 33 | "end": { 34 | "line": 3, 35 | "column": 10 36 | }, 37 | "source": "input.css" 38 | } 39 | }, 40 | { 41 | "type": "rule", 42 | "selectors": [ 43 | "body" 44 | ], 45 | "declarations": [ 46 | { 47 | "type": "declaration", 48 | "property": "foo", 49 | "value": "bar", 50 | "position": { 51 | "start": { 52 | "line": 5, 53 | "column": 9 54 | }, 55 | "end": { 56 | "line": 5, 57 | "column": 16 58 | }, 59 | "source": "input.css" 60 | } 61 | }, 62 | { 63 | "type": "declaration", 64 | "property": "bar", 65 | "value": "baz", 66 | "position": { 67 | "start": { 68 | "line": 5, 69 | "column": 17 70 | }, 71 | "end": { 72 | "line": 5, 73 | "column": 24 74 | }, 75 | "source": "input.css" 76 | } 77 | } 78 | ], 79 | "position": { 80 | "start": { 81 | "line": 5, 82 | "column": 4 83 | }, 84 | "end": { 85 | "line": 5, 86 | "column": 25 87 | }, 88 | "source": "input.css" 89 | } 90 | }, 91 | { 92 | "type": "rule", 93 | "selectors": [ 94 | "body" 95 | ], 96 | "declarations": [ 97 | { 98 | "type": "declaration", 99 | "property": "foo", 100 | "value": "bar", 101 | "position": { 102 | "start": { 103 | "line": 8, 104 | "column": 6 105 | }, 106 | "end": { 107 | "line": 11, 108 | "column": 6 109 | }, 110 | "source": "input.css" 111 | } 112 | }, 113 | { 114 | "type": "declaration", 115 | "property": "bar", 116 | "value": "baz", 117 | "position": { 118 | "start": { 119 | "line": 12, 120 | "column": 6 121 | }, 122 | "end": { 123 | "line": 15, 124 | "column": 6 125 | }, 126 | "source": "input.css" 127 | } 128 | } 129 | ], 130 | "position": { 131 | "start": { 132 | "line": 6, 133 | "column": 4 134 | }, 135 | "end": { 136 | "line": 15, 137 | "column": 7 138 | }, 139 | "source": "input.css" 140 | } 141 | } 142 | ] 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /test/cases/comment/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [ 5 | { 6 | "type": "comment", 7 | "comment": " 1 ", 8 | "position": { 9 | "start": { 10 | "line": 1, 11 | "column": 1 12 | }, 13 | "end": { 14 | "line": 1, 15 | "column": 8 16 | }, 17 | "source": "input.css" 18 | } 19 | }, 20 | { 21 | "type": "rule", 22 | "selectors": [ 23 | "head", 24 | "body" 25 | ], 26 | "declarations": [ 27 | { 28 | "type": "comment", 29 | "comment": " 2 ", 30 | "position": { 31 | "start": { 32 | "line": 3, 33 | "column": 37 34 | }, 35 | "end": { 36 | "line": 3, 37 | "column": 44 38 | }, 39 | "source": "input.css" 40 | } 41 | }, 42 | { 43 | "type": "comment", 44 | "comment": " 3 ", 45 | "position": { 46 | "start": { 47 | "line": 4, 48 | "column": 3 49 | }, 50 | "end": { 51 | "line": 4, 52 | "column": 10 53 | }, 54 | "source": "input.css" 55 | } 56 | }, 57 | { 58 | "type": "comment", 59 | "comment": "", 60 | "position": { 61 | "start": { 62 | "line": 5, 63 | "column": 3 64 | }, 65 | "end": { 66 | "line": 5, 67 | "column": 7 68 | }, 69 | "source": "input.css" 70 | } 71 | }, 72 | { 73 | "type": "declaration", 74 | "property": "foo", 75 | "value": "'bar'", 76 | "position": { 77 | "start": { 78 | "line": 5, 79 | "column": 7 80 | }, 81 | "end": { 82 | "line": 5, 83 | "column": 17 84 | }, 85 | "source": "input.css" 86 | } 87 | }, 88 | { 89 | "type": "comment", 90 | "comment": " 4 ", 91 | "position": { 92 | "start": { 93 | "line": 6, 94 | "column": 3 95 | }, 96 | "end": { 97 | "line": 6, 98 | "column": 10 99 | }, 100 | "source": "input.css" 101 | } 102 | } 103 | ], 104 | "position": { 105 | "start": { 106 | "line": 3, 107 | "column": 1 108 | }, 109 | "end": { 110 | "line": 7, 111 | "column": 2 112 | }, 113 | "source": "input.css" 114 | } 115 | }, 116 | { 117 | "type": "comment", 118 | "comment": " 5 ", 119 | "position": { 120 | "start": { 121 | "line": 7, 122 | "column": 3 123 | }, 124 | "end": { 125 | "line": 7, 126 | "column": 10 127 | }, 128 | "source": "input.css" 129 | } 130 | }, 131 | { 132 | "type": "comment", 133 | "comment": " 6 ", 134 | "position": { 135 | "start": { 136 | "line": 9, 137 | "column": 1 138 | }, 139 | "end": { 140 | "line": 9, 141 | "column": 8 142 | }, 143 | "source": "input.css" 144 | } 145 | } 146 | ] 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /test/cases/supports/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [ 5 | { 6 | "type": "supports", 7 | "supports": "(display: flex) or (display: box)", 8 | "rules": [ 9 | { 10 | "type": "comment", 11 | "comment": " flex above ", 12 | "position": { 13 | "start": { 14 | "line": 2, 15 | "column": 3 16 | }, 17 | "end": { 18 | "line": 2, 19 | "column": 19 20 | }, 21 | "source": "input.css" 22 | } 23 | }, 24 | { 25 | "type": "rule", 26 | "selectors": [ 27 | ".flex" 28 | ], 29 | "declarations": [ 30 | { 31 | "type": "comment", 32 | "comment": " flex inside ", 33 | "position": { 34 | "start": { 35 | "line": 4, 36 | "column": 5 37 | }, 38 | "end": { 39 | "line": 4, 40 | "column": 22 41 | }, 42 | "source": "input.css" 43 | } 44 | }, 45 | { 46 | "type": "declaration", 47 | "property": "display", 48 | "value": "box", 49 | "position": { 50 | "start": { 51 | "line": 5, 52 | "column": 5 53 | }, 54 | "end": { 55 | "line": 5, 56 | "column": 17 57 | }, 58 | "source": "input.css" 59 | } 60 | }, 61 | { 62 | "type": "declaration", 63 | "property": "display", 64 | "value": "flex", 65 | "position": { 66 | "start": { 67 | "line": 6, 68 | "column": 5 69 | }, 70 | "end": { 71 | "line": 6, 72 | "column": 18 73 | }, 74 | "source": "input.css" 75 | } 76 | } 77 | ], 78 | "position": { 79 | "start": { 80 | "line": 3, 81 | "column": 3 82 | }, 83 | "end": { 84 | "line": 7, 85 | "column": 4 86 | }, 87 | "source": "input.css" 88 | } 89 | }, 90 | { 91 | "type": "rule", 92 | "selectors": [ 93 | "div" 94 | ], 95 | "declarations": [ 96 | { 97 | "type": "declaration", 98 | "property": "something", 99 | "value": "else", 100 | "position": { 101 | "start": { 102 | "line": 10, 103 | "column": 5 104 | }, 105 | "end": { 106 | "line": 10, 107 | "column": 20 108 | }, 109 | "source": "input.css" 110 | } 111 | } 112 | ], 113 | "position": { 114 | "start": { 115 | "line": 9, 116 | "column": 3 117 | }, 118 | "end": { 119 | "line": 11, 120 | "column": 4 121 | }, 122 | "source": "input.css" 123 | } 124 | } 125 | ], 126 | "position": { 127 | "start": { 128 | "line": 1, 129 | "column": 1 130 | }, 131 | "end": { 132 | "line": 12, 133 | "column": 2 134 | }, 135 | "source": "input.css" 136 | } 137 | } 138 | ] 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /test/cases/keyframes/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [ 5 | { 6 | "type": "keyframes", 7 | "name": "fade", 8 | "keyframes": [ 9 | { 10 | "type": "comment", 11 | "comment": " from above ", 12 | "position": { 13 | "start": { 14 | "line": 2, 15 | "column": 3 16 | }, 17 | "end": { 18 | "line": 2, 19 | "column": 19 20 | }, 21 | "source": "input.css" 22 | } 23 | }, 24 | { 25 | "type": "keyframe", 26 | "values": [ 27 | "from" 28 | ], 29 | "declarations": [ 30 | { 31 | "type": "comment", 32 | "comment": " from inside ", 33 | "position": { 34 | "start": { 35 | "line": 4, 36 | "column": 5 37 | }, 38 | "end": { 39 | "line": 4, 40 | "column": 22 41 | }, 42 | "source": "input.css" 43 | } 44 | }, 45 | { 46 | "type": "declaration", 47 | "property": "opacity", 48 | "value": "0", 49 | "position": { 50 | "start": { 51 | "line": 5, 52 | "column": 5 53 | }, 54 | "end": { 55 | "line": 5, 56 | "column": 15 57 | }, 58 | "source": "input.css" 59 | } 60 | } 61 | ], 62 | "position": { 63 | "start": { 64 | "line": 3, 65 | "column": 3 66 | }, 67 | "end": { 68 | "line": 6, 69 | "column": 4 70 | }, 71 | "source": "input.css" 72 | } 73 | }, 74 | { 75 | "type": "comment", 76 | "comment": " to above ", 77 | "position": { 78 | "start": { 79 | "line": 8, 80 | "column": 3 81 | }, 82 | "end": { 83 | "line": 8, 84 | "column": 17 85 | }, 86 | "source": "input.css" 87 | } 88 | }, 89 | { 90 | "type": "keyframe", 91 | "values": [ 92 | "to" 93 | ], 94 | "declarations": [ 95 | { 96 | "type": "comment", 97 | "comment": " to inside ", 98 | "position": { 99 | "start": { 100 | "line": 10, 101 | "column": 5 102 | }, 103 | "end": { 104 | "line": 10, 105 | "column": 20 106 | }, 107 | "source": "input.css" 108 | } 109 | }, 110 | { 111 | "type": "declaration", 112 | "property": "opacity", 113 | "value": "1", 114 | "position": { 115 | "start": { 116 | "line": 11, 117 | "column": 5 118 | }, 119 | "end": { 120 | "line": 11, 121 | "column": 15 122 | }, 123 | "source": "input.css" 124 | } 125 | } 126 | ], 127 | "position": { 128 | "start": { 129 | "line": 9, 130 | "column": 3 131 | }, 132 | "end": { 133 | "line": 12, 134 | "column": 4 135 | }, 136 | "source": "input.css" 137 | } 138 | } 139 | ], 140 | "position": { 141 | "start": { 142 | "line": 1, 143 | "column": 1 144 | }, 145 | "end": { 146 | "line": 13, 147 | "column": 2 148 | }, 149 | "source": "input.css" 150 | } 151 | } 152 | ] 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /lib/stringify/compress.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var Base = require('./compiler'); 7 | var inherits = require('inherits'); 8 | 9 | /** 10 | * Expose compiler. 11 | */ 12 | 13 | module.exports = Compiler; 14 | 15 | /** 16 | * Initialize a new `Compiler`. 17 | */ 18 | 19 | function Compiler(options) { 20 | Base.call(this, options); 21 | } 22 | 23 | /** 24 | * Inherit from `Base.prototype`. 25 | */ 26 | 27 | inherits(Compiler, Base); 28 | 29 | /** 30 | * Compile `node`. 31 | */ 32 | 33 | Compiler.prototype.compile = function(node){ 34 | return node.stylesheet 35 | .rules.map(this.visit, this) 36 | .join(''); 37 | }; 38 | 39 | /** 40 | * Visit comment node. 41 | */ 42 | 43 | Compiler.prototype.comment = function(node){ 44 | return this.emit('', node.position); 45 | }; 46 | 47 | /** 48 | * Visit import node. 49 | */ 50 | 51 | Compiler.prototype.import = function(node){ 52 | return this.emit('@import ' + node.import + ';', node.position); 53 | }; 54 | 55 | /** 56 | * Visit media node. 57 | */ 58 | 59 | Compiler.prototype.media = function(node){ 60 | return this.emit('@media ' + node.media, node.position) 61 | + this.emit('{') 62 | + this.mapVisit(node.rules) 63 | + this.emit('}'); 64 | }; 65 | 66 | /** 67 | * Visit document node. 68 | */ 69 | 70 | Compiler.prototype.document = function(node){ 71 | var doc = '@' + (node.vendor || '') + 'document ' + node.document; 72 | 73 | return this.emit(doc, node.position) 74 | + this.emit('{') 75 | + this.mapVisit(node.rules) 76 | + this.emit('}'); 77 | }; 78 | 79 | /** 80 | * Visit charset node. 81 | */ 82 | 83 | Compiler.prototype.charset = function(node){ 84 | return this.emit('@charset ' + node.charset + ';', node.position); 85 | }; 86 | 87 | /** 88 | * Visit namespace node. 89 | */ 90 | 91 | Compiler.prototype.namespace = function(node){ 92 | return this.emit('@namespace ' + node.namespace + ';', node.position); 93 | }; 94 | 95 | /** 96 | * Visit supports node. 97 | */ 98 | 99 | Compiler.prototype.supports = function(node){ 100 | return this.emit('@supports ' + node.supports, node.position) 101 | + this.emit('{') 102 | + this.mapVisit(node.rules) 103 | + this.emit('}'); 104 | }; 105 | 106 | /** 107 | * Visit keyframes node. 108 | */ 109 | 110 | Compiler.prototype.keyframes = function(node){ 111 | return this.emit('@' 112 | + (node.vendor || '') 113 | + 'keyframes ' 114 | + node.name, node.position) 115 | + this.emit('{') 116 | + this.mapVisit(node.keyframes) 117 | + this.emit('}'); 118 | }; 119 | 120 | /** 121 | * Visit keyframe node. 122 | */ 123 | 124 | Compiler.prototype.keyframe = function(node){ 125 | var decls = node.declarations; 126 | 127 | return this.emit(node.values.join(','), node.position) 128 | + this.emit('{') 129 | + this.mapVisit(decls) 130 | + this.emit('}'); 131 | }; 132 | 133 | /** 134 | * Visit page node. 135 | */ 136 | 137 | Compiler.prototype.page = function(node){ 138 | var sel = node.selectors.length 139 | ? node.selectors.join(', ') 140 | : ''; 141 | 142 | return this.emit('@page ' + sel, node.position) 143 | + this.emit('{') 144 | + this.mapVisit(node.declarations) 145 | + this.emit('}'); 146 | }; 147 | 148 | /** 149 | * Visit font-face node. 150 | */ 151 | 152 | Compiler.prototype['font-face'] = function(node){ 153 | return this.emit('@font-face', node.position) 154 | + this.emit('{') 155 | + this.mapVisit(node.declarations) 156 | + this.emit('}'); 157 | }; 158 | 159 | /** 160 | * Visit host node. 161 | */ 162 | 163 | Compiler.prototype.host = function(node){ 164 | return this.emit('@host', node.position) 165 | + this.emit('{') 166 | + this.mapVisit(node.rules) 167 | + this.emit('}'); 168 | }; 169 | 170 | /** 171 | * Visit custom-media node. 172 | */ 173 | 174 | Compiler.prototype['custom-media'] = function(node){ 175 | return this.emit('@custom-media ' + node.name + ' ' + node.media + ';', node.position); 176 | }; 177 | 178 | /** 179 | * Visit rule node. 180 | */ 181 | 182 | Compiler.prototype.rule = function(node){ 183 | var decls = node.declarations; 184 | if (!decls.length) return ''; 185 | 186 | return this.emit(node.selectors.join(','), node.position) 187 | + this.emit('{') 188 | + this.mapVisit(decls) 189 | + this.emit('}'); 190 | }; 191 | 192 | /** 193 | * Visit declaration node. 194 | */ 195 | 196 | Compiler.prototype.declaration = function(node){ 197 | return this.emit(node.property + ':' + node.value, node.position) + this.emit(';'); 198 | }; 199 | 200 | -------------------------------------------------------------------------------- /test/stringify.js: -------------------------------------------------------------------------------- 1 | var stringify = require('../').stringify; 2 | var parse = require('../').parse; 3 | var path = require('path'); 4 | var read = require('fs').readFileSync; 5 | var SourceMapConsumer = require('source-map').SourceMapConsumer; 6 | var SourceMapGenerator = require('source-map').SourceMapGenerator; 7 | 8 | describe('stringify(obj, {sourcemap: true})', function() { 9 | var file = 'test/source-map/test.css'; 10 | var src = read(file, 'utf8'); 11 | var stylesheet = parse(src, { source: file }); 12 | function loc(line, column) { 13 | return { line: line, column: column, source: file, name: null }; 14 | } 15 | 16 | var locs = { 17 | tobiSelector: loc(1, 0), 18 | tobiNameName: loc(2, 2), 19 | tobiNameValue: loc(2, 2), 20 | mediaBlock: loc(11, 0), 21 | mediaOnly: loc(12, 2), 22 | comment: loc(17, 0), 23 | }; 24 | 25 | it('should generate source maps alongside when using identity compiler', function() { 26 | var result = stringify(stylesheet, { sourcemap: true }); 27 | result.should.have.property('code'); 28 | result.should.have.property('map'); 29 | var map = new SourceMapConsumer(result.map); 30 | map.originalPositionFor({ line: 1, column: 0 }).should.eql(locs.tobiSelector); 31 | map.originalPositionFor({ line: 2, column: 2 }).should.eql(locs.tobiNameName); 32 | map.originalPositionFor({ line: 2, column: 8 }).should.eql(locs.tobiNameValue); 33 | map.originalPositionFor({ line: 11, column: 0 }).should.eql(locs.mediaBlock); 34 | map.originalPositionFor({ line: 12, column: 2 }).should.eql(locs.mediaOnly); 35 | map.originalPositionFor({ line: 17, column: 0 }).should.eql(locs.comment); 36 | map.sourceContentFor(file).should.eql(src); 37 | }); 38 | 39 | it('should generate source maps alongside when using compress compiler', function() { 40 | var result = stringify(stylesheet, { compress: true, sourcemap: true }); 41 | result.should.have.property('code'); 42 | result.should.have.property('map'); 43 | var map = new SourceMapConsumer(result.map); 44 | map.originalPositionFor({ line: 1, column: 0 }).should.eql(locs.tobiSelector); 45 | map.originalPositionFor({ line: 1, column: 5 }).should.eql(locs.tobiNameName); 46 | map.originalPositionFor({ line: 1, column: 10 }).should.eql(locs.tobiNameValue); 47 | map.originalPositionFor({ line: 1, column: 50 }).should.eql(locs.mediaBlock); 48 | map.originalPositionFor({ line: 1, column: 64 }).should.eql(locs.mediaOnly); 49 | map.sourceContentFor(file).should.eql(src); 50 | }); 51 | 52 | it('should apply included source maps, with paths adjusted to CWD', function() { 53 | var file = 'test/source-map/apply.css'; 54 | var src = read(file, 'utf8'); 55 | var stylesheet = parse(src, { source: file }); 56 | var result = stringify(stylesheet, { sourcemap: true }); 57 | result.should.have.property('code'); 58 | result.should.have.property('map'); 59 | 60 | var map = new SourceMapConsumer(result.map); 61 | map.originalPositionFor({ line: 1, column: 0 }).should.eql({ 62 | column: 0, 63 | line: 1, 64 | name: null, 65 | source: 'test/source-map/apply.scss' 66 | }); 67 | 68 | map.originalPositionFor({ line: 2, column: 2 }).should.eql({ 69 | column: 7, 70 | line: 1, 71 | name: null, 72 | source: 'test/source-map/apply.scss' 73 | }); 74 | }); 75 | 76 | it('should not apply included source maps when inputSourcemap is false', function() { 77 | var file = 'test/source-map/apply.css'; 78 | var src = read(file, 'utf8'); 79 | var stylesheet = parse(src, { source: file }); 80 | var result = stringify(stylesheet, { sourcemap: true, inputSourcemaps: false }); 81 | 82 | var map = new SourceMapConsumer(result.map); 83 | map.originalPositionFor({ line: 1, column: 0 }).should.eql({ 84 | column: 0, 85 | line: 1, 86 | name: null, 87 | source: file 88 | }); 89 | }); 90 | 91 | it('should convert Windows-style paths to URLs', function() { 92 | var originalSep = path.sep; 93 | path.sep = '\\'; // Pretend we’re on Windows (if we aren’t already). 94 | 95 | var src = 'C:\\test\\source.css'; 96 | var css = 'a { color: black; }' 97 | var stylesheet = parse(css, { source: src }); 98 | var result = stringify(stylesheet, { sourcemap: true }); 99 | 100 | result.map.sources.should.eql(['/test/source.css']); 101 | 102 | path.sep = originalSep; 103 | }); 104 | 105 | it('should return source map generator when sourcemap: "generator"', function(){ 106 | var css = 'a { color: black; }'; 107 | var stylesheet = parse(css); 108 | var result = stringify(stylesheet, { sourcemap: 'generator' }); 109 | 110 | result.map.should.be.an.instanceOf(SourceMapGenerator); 111 | }); 112 | }); 113 | -------------------------------------------------------------------------------- /test/cases/comma-attribute/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [ 5 | { 6 | "type": "rule", 7 | "selectors": [ 8 | ".foo[bar=\"baz,quz\"]" 9 | ], 10 | "declarations": [ 11 | { 12 | "type": "declaration", 13 | "property": "foobar", 14 | "value": "123", 15 | "position": { 16 | "start": { 17 | "line": 2, 18 | "column": 3 19 | }, 20 | "end": { 21 | "line": 2, 22 | "column": 14 23 | }, 24 | "source": "input.css" 25 | } 26 | } 27 | ], 28 | "position": { 29 | "start": { 30 | "line": 1, 31 | "column": 1 32 | }, 33 | "end": { 34 | "line": 3, 35 | "column": 2 36 | }, 37 | "source": "input.css" 38 | } 39 | }, 40 | { 41 | "type": "rule", 42 | "selectors": [ 43 | ".bar", 44 | "#bar[baz=\"qux,foo\"]", 45 | "#qux" 46 | ], 47 | "declarations": [ 48 | { 49 | "type": "declaration", 50 | "property": "foobar", 51 | "value": "456", 52 | "position": { 53 | "start": { 54 | "line": 8, 55 | "column": 3 56 | }, 57 | "end": { 58 | "line": 8, 59 | "column": 14 60 | }, 61 | "source": "input.css" 62 | } 63 | } 64 | ], 65 | "position": { 66 | "start": { 67 | "line": 5, 68 | "column": 1 69 | }, 70 | "end": { 71 | "line": 9, 72 | "column": 2 73 | }, 74 | "source": "input.css" 75 | } 76 | }, 77 | { 78 | "type": "rule", 79 | "selectors": [ 80 | ".baz[qux=\",foo\"]", 81 | ".baz[qux=\"foo,\"]", 82 | ".baz[qux=\"foo,bar,baz\"]", 83 | ".baz[qux=\",foo,bar,baz,\"]", 84 | ".baz[qux=\" , foo , bar , baz , \"]" 85 | ], 86 | "declarations": [ 87 | { 88 | "type": "declaration", 89 | "property": "foobar", 90 | "value": "789", 91 | "position": { 92 | "start": { 93 | "line": 16, 94 | "column": 3 95 | }, 96 | "end": { 97 | "line": 16, 98 | "column": 14 99 | }, 100 | "source": "input.css" 101 | } 102 | } 103 | ], 104 | "position": { 105 | "start": { 106 | "line": 11, 107 | "column": 1 108 | }, 109 | "end": { 110 | "line": 17, 111 | "column": 2 112 | }, 113 | "source": "input.css" 114 | } 115 | }, 116 | { 117 | "type": "rule", 118 | "selectors": [ 119 | ".qux[foo='bar,baz']", 120 | ".qux[bar=\"baz,foo\"]", 121 | "#qux[foo=\"foobar\"]", 122 | "#qux[foo=',bar,baz, ']" 123 | ], 124 | "declarations": [ 125 | { 126 | "type": "declaration", 127 | "property": "foobar", 128 | "value": "012", 129 | "position": { 130 | "start": { 131 | "line": 23, 132 | "column": 3 133 | }, 134 | "end": { 135 | "line": 23, 136 | "column": 14 137 | }, 138 | "source": "input.css" 139 | } 140 | } 141 | ], 142 | "position": { 143 | "start": { 144 | "line": 19, 145 | "column": 1 146 | }, 147 | "end": { 148 | "line": 24, 149 | "column": 2 150 | }, 151 | "source": "input.css" 152 | } 153 | }, 154 | { 155 | "type": "rule", 156 | "selectors": [ 157 | "#foo[foo=\"\"]", 158 | "#foo[bar=\" \"]", 159 | "#foo[bar=\",\"]", 160 | "#foo[bar=\", \"]", 161 | "#foo[bar=\" ,\"]", 162 | "#foo[bar=\" , \"]", 163 | "#foo[baz='']", 164 | "#foo[qux=' ']", 165 | "#foo[qux=',']", 166 | "#foo[qux=', ']", 167 | "#foo[qux=' ,']", 168 | "#foo[qux=' , ']" 169 | ], 170 | "declarations": [ 171 | { 172 | "type": "declaration", 173 | "property": "foobar", 174 | "value": "345", 175 | "position": { 176 | "start": { 177 | "line": 38, 178 | "column": 3 179 | }, 180 | "end": { 181 | "line": 38, 182 | "column": 14 183 | }, 184 | "source": "input.css" 185 | } 186 | } 187 | ], 188 | "position": { 189 | "start": { 190 | "line": 26, 191 | "column": 1 192 | }, 193 | "end": { 194 | "line": 39, 195 | "column": 2 196 | }, 197 | "source": "input.css" 198 | } 199 | } 200 | ] 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /test/cases/keyframes-complex/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [ 5 | { 6 | "type": "keyframes", 7 | "name": "foo", 8 | "keyframes": [ 9 | { 10 | "type": "keyframe", 11 | "values": [ 12 | "0%" 13 | ], 14 | "declarations": [ 15 | { 16 | "type": "declaration", 17 | "property": "top", 18 | "value": "0", 19 | "position": { 20 | "start": { 21 | "line": 2, 22 | "column": 8 23 | }, 24 | "end": { 25 | "line": 2, 26 | "column": 14 27 | }, 28 | "source": "input.css" 29 | } 30 | }, 31 | { 32 | "type": "declaration", 33 | "property": "left", 34 | "value": "0", 35 | "position": { 36 | "start": { 37 | "line": 2, 38 | "column": 16 39 | }, 40 | "end": { 41 | "line": 2, 42 | "column": 24 43 | }, 44 | "source": "input.css" 45 | } 46 | } 47 | ], 48 | "position": { 49 | "start": { 50 | "line": 2, 51 | "column": 3 52 | }, 53 | "end": { 54 | "line": 2, 55 | "column": 25 56 | }, 57 | "source": "input.css" 58 | } 59 | }, 60 | { 61 | "type": "keyframe", 62 | "values": [ 63 | "30.50%" 64 | ], 65 | "declarations": [ 66 | { 67 | "type": "declaration", 68 | "property": "top", 69 | "value": "50px", 70 | "position": { 71 | "start": { 72 | "line": 3, 73 | "column": 12 74 | }, 75 | "end": { 76 | "line": 3, 77 | "column": 22 78 | }, 79 | "source": "input.css" 80 | } 81 | } 82 | ], 83 | "position": { 84 | "start": { 85 | "line": 3, 86 | "column": 3 87 | }, 88 | "end": { 89 | "line": 3, 90 | "column": 23 91 | }, 92 | "source": "input.css" 93 | } 94 | }, 95 | { 96 | "type": "keyframe", 97 | "values": [ 98 | ".68%", 99 | "72%", 100 | "85%" 101 | ], 102 | "declarations": [ 103 | { 104 | "type": "declaration", 105 | "property": "left", 106 | "value": "50px", 107 | "position": { 108 | "start": { 109 | "line": 6, 110 | "column": 15 111 | }, 112 | "end": { 113 | "line": 6, 114 | "column": 26 115 | }, 116 | "source": "input.css" 117 | } 118 | } 119 | ], 120 | "position": { 121 | "start": { 122 | "line": 4, 123 | "column": 3 124 | }, 125 | "end": { 126 | "line": 6, 127 | "column": 27 128 | }, 129 | "source": "input.css" 130 | } 131 | }, 132 | { 133 | "type": "keyframe", 134 | "values": [ 135 | "100%" 136 | ], 137 | "declarations": [ 138 | { 139 | "type": "declaration", 140 | "property": "top", 141 | "value": "100px", 142 | "position": { 143 | "start": { 144 | "line": 7, 145 | "column": 10 146 | }, 147 | "end": { 148 | "line": 7, 149 | "column": 20 150 | }, 151 | "source": "input.css" 152 | } 153 | }, 154 | { 155 | "type": "declaration", 156 | "property": "left", 157 | "value": "100%", 158 | "position": { 159 | "start": { 160 | "line": 7, 161 | "column": 22 162 | }, 163 | "end": { 164 | "line": 7, 165 | "column": 33 166 | }, 167 | "source": "input.css" 168 | } 169 | } 170 | ], 171 | "position": { 172 | "start": { 173 | "line": 7, 174 | "column": 3 175 | }, 176 | "end": { 177 | "line": 7, 178 | "column": 34 179 | }, 180 | "source": "input.css" 181 | } 182 | } 183 | ], 184 | "position": { 185 | "start": { 186 | "line": 1, 187 | "column": 1 188 | }, 189 | "end": { 190 | "line": 8, 191 | "column": 2 192 | }, 193 | "source": "input.css" 194 | } 195 | } 196 | ] 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /lib/stringify/identity.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var Base = require('./compiler'); 7 | var inherits = require('inherits'); 8 | 9 | /** 10 | * Expose compiler. 11 | */ 12 | 13 | module.exports = Compiler; 14 | 15 | /** 16 | * Initialize a new `Compiler`. 17 | */ 18 | 19 | function Compiler(options) { 20 | options = options || {}; 21 | Base.call(this, options); 22 | this.indentation = options.indent; 23 | } 24 | 25 | /** 26 | * Inherit from `Base.prototype`. 27 | */ 28 | 29 | inherits(Compiler, Base); 30 | 31 | /** 32 | * Compile `node`. 33 | */ 34 | 35 | Compiler.prototype.compile = function(node){ 36 | return this.stylesheet(node); 37 | }; 38 | 39 | /** 40 | * Visit stylesheet node. 41 | */ 42 | 43 | Compiler.prototype.stylesheet = function(node){ 44 | return this.mapVisit(node.stylesheet.rules, '\n\n'); 45 | }; 46 | 47 | /** 48 | * Visit comment node. 49 | */ 50 | 51 | Compiler.prototype.comment = function(node){ 52 | return this.emit(this.indent() + '/*' + node.comment + '*/', node.position); 53 | }; 54 | 55 | /** 56 | * Visit import node. 57 | */ 58 | 59 | Compiler.prototype.import = function(node){ 60 | return this.emit('@import ' + node.import + ';', node.position); 61 | }; 62 | 63 | /** 64 | * Visit media node. 65 | */ 66 | 67 | Compiler.prototype.media = function(node){ 68 | return this.emit('@media ' + node.media, node.position) 69 | + this.emit( 70 | ' {\n' 71 | + this.indent(1)) 72 | + this.mapVisit(node.rules, '\n\n') 73 | + this.emit( 74 | this.indent(-1) 75 | + '\n}'); 76 | }; 77 | 78 | /** 79 | * Visit document node. 80 | */ 81 | 82 | Compiler.prototype.document = function(node){ 83 | var doc = '@' + (node.vendor || '') + 'document ' + node.document; 84 | 85 | return this.emit(doc, node.position) 86 | + this.emit( 87 | ' ' 88 | + ' {\n' 89 | + this.indent(1)) 90 | + this.mapVisit(node.rules, '\n\n') 91 | + this.emit( 92 | this.indent(-1) 93 | + '\n}'); 94 | }; 95 | 96 | /** 97 | * Visit charset node. 98 | */ 99 | 100 | Compiler.prototype.charset = function(node){ 101 | return this.emit('@charset ' + node.charset + ';', node.position); 102 | }; 103 | 104 | /** 105 | * Visit namespace node. 106 | */ 107 | 108 | Compiler.prototype.namespace = function(node){ 109 | return this.emit('@namespace ' + node.namespace + ';', node.position); 110 | }; 111 | 112 | /** 113 | * Visit supports node. 114 | */ 115 | 116 | Compiler.prototype.supports = function(node){ 117 | return this.emit('@supports ' + node.supports, node.position) 118 | + this.emit( 119 | ' {\n' 120 | + this.indent(1)) 121 | + this.mapVisit(node.rules, '\n\n') 122 | + this.emit( 123 | this.indent(-1) 124 | + '\n}'); 125 | }; 126 | 127 | /** 128 | * Visit keyframes node. 129 | */ 130 | 131 | Compiler.prototype.keyframes = function(node){ 132 | return this.emit('@' + (node.vendor || '') + 'keyframes ' + node.name, node.position) 133 | + this.emit( 134 | ' {\n' 135 | + this.indent(1)) 136 | + this.mapVisit(node.keyframes, '\n') 137 | + this.emit( 138 | this.indent(-1) 139 | + '}'); 140 | }; 141 | 142 | /** 143 | * Visit keyframe node. 144 | */ 145 | 146 | Compiler.prototype.keyframe = function(node){ 147 | var decls = node.declarations; 148 | 149 | return this.emit(this.indent()) 150 | + this.emit(node.values.join(', '), node.position) 151 | + this.emit( 152 | ' {\n' 153 | + this.indent(1)) 154 | + this.mapVisit(decls, '\n') 155 | + this.emit( 156 | this.indent(-1) 157 | + '\n' 158 | + this.indent() + '}\n'); 159 | }; 160 | 161 | /** 162 | * Visit page node. 163 | */ 164 | 165 | Compiler.prototype.page = function(node){ 166 | var sel = node.selectors.length 167 | ? node.selectors.join(', ') + ' ' 168 | : ''; 169 | 170 | return this.emit('@page ' + sel, node.position) 171 | + this.emit('{\n') 172 | + this.emit(this.indent(1)) 173 | + this.mapVisit(node.declarations, '\n') 174 | + this.emit(this.indent(-1)) 175 | + this.emit('\n}'); 176 | }; 177 | 178 | /** 179 | * Visit font-face node. 180 | */ 181 | 182 | Compiler.prototype['font-face'] = function(node){ 183 | return this.emit('@font-face ', node.position) 184 | + this.emit('{\n') 185 | + this.emit(this.indent(1)) 186 | + this.mapVisit(node.declarations, '\n') 187 | + this.emit(this.indent(-1)) 188 | + this.emit('\n}'); 189 | }; 190 | 191 | /** 192 | * Visit host node. 193 | */ 194 | 195 | Compiler.prototype.host = function(node){ 196 | return this.emit('@host', node.position) 197 | + this.emit( 198 | ' {\n' 199 | + this.indent(1)) 200 | + this.mapVisit(node.rules, '\n\n') 201 | + this.emit( 202 | this.indent(-1) 203 | + '\n}'); 204 | }; 205 | 206 | /** 207 | * Visit custom-media node. 208 | */ 209 | 210 | Compiler.prototype['custom-media'] = function(node){ 211 | return this.emit('@custom-media ' + node.name + ' ' + node.media + ';', node.position); 212 | }; 213 | 214 | /** 215 | * Visit rule node. 216 | */ 217 | 218 | Compiler.prototype.rule = function(node){ 219 | var indent = this.indent(); 220 | var decls = node.declarations; 221 | if (!decls.length) return ''; 222 | 223 | return this.emit(node.selectors.map(function(s){ return indent + s }).join(',\n'), node.position) 224 | + this.emit(' {\n') 225 | + this.emit(this.indent(1)) 226 | + this.mapVisit(decls, '\n') 227 | + this.emit(this.indent(-1)) 228 | + this.emit('\n' + this.indent() + '}'); 229 | }; 230 | 231 | /** 232 | * Visit declaration node. 233 | */ 234 | 235 | Compiler.prototype.declaration = function(node){ 236 | return this.emit(this.indent()) 237 | + this.emit(node.property + ': ' + node.value, node.position) 238 | + this.emit(';'); 239 | }; 240 | 241 | /** 242 | * Increase, decrease or return current indentation. 243 | */ 244 | 245 | Compiler.prototype.indent = function(level) { 246 | this.level = this.level || 1; 247 | 248 | if (null != level) { 249 | this.level += level; 250 | return ''; 251 | } 252 | 253 | return Array(this.level).join(this.indentation || ' '); 254 | }; 255 | -------------------------------------------------------------------------------- /test/cases/media-messed/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [ 5 | { 6 | "type": "media", 7 | "media": "screen, projection", 8 | "rules": [ 9 | { 10 | "type": "rule", 11 | "selectors": [ 12 | "html" 13 | ], 14 | "declarations": [ 15 | { 16 | "type": "declaration", 17 | "property": "background", 18 | "value": "#fffef0", 19 | "position": { 20 | "start": { 21 | "line": 4, 22 | "column": 1 23 | }, 24 | "end": { 25 | "line": 4, 26 | "column": 20 27 | }, 28 | "source": "input.css" 29 | } 30 | }, 31 | { 32 | "type": "declaration", 33 | "property": "color", 34 | "value": "#300", 35 | "position": { 36 | "start": { 37 | "line": 5, 38 | "column": 5 39 | }, 40 | "end": { 41 | "line": 5, 42 | "column": 15 43 | }, 44 | "source": "input.css" 45 | } 46 | } 47 | ], 48 | "position": { 49 | "start": { 50 | "line": 1, 51 | "column": 28 52 | }, 53 | "end": { 54 | "line": 6, 55 | "column": 4 56 | }, 57 | "source": "input.css" 58 | } 59 | }, 60 | { 61 | "type": "rule", 62 | "selectors": [ 63 | "body" 64 | ], 65 | "declarations": [ 66 | { 67 | "type": "declaration", 68 | "property": "max-width", 69 | "value": "35em", 70 | "position": { 71 | "start": { 72 | "line": 10, 73 | "column": 5 74 | }, 75 | "end": { 76 | "line": 10, 77 | "column": 20 78 | }, 79 | "source": "input.css" 80 | } 81 | }, 82 | { 83 | "type": "declaration", 84 | "property": "margin", 85 | "value": "0 auto", 86 | "position": { 87 | "start": { 88 | "line": 11, 89 | "column": 5 90 | }, 91 | "end": { 92 | "line": 11, 93 | "column": 19 94 | }, 95 | "source": "input.css" 96 | } 97 | } 98 | ], 99 | "position": { 100 | "start": { 101 | "line": 7, 102 | "column": 3 103 | }, 104 | "end": { 105 | "line": 14, 106 | "column": 2 107 | }, 108 | "source": "input.css" 109 | } 110 | } 111 | ], 112 | "position": { 113 | "start": { 114 | "line": 1, 115 | "column": 1 116 | }, 117 | "end": { 118 | "line": 15, 119 | "column": 4 120 | }, 121 | "source": "input.css" 122 | } 123 | }, 124 | { 125 | "type": "media", 126 | "media": "print", 127 | "rules": [ 128 | { 129 | "type": "rule", 130 | "selectors": [ 131 | "html" 132 | ], 133 | "declarations": [ 134 | { 135 | "type": "declaration", 136 | "property": "background", 137 | "value": "#fff", 138 | "position": { 139 | "start": { 140 | "line": 20, 141 | "column": 15 142 | }, 143 | "end": { 144 | "line": 20, 145 | "column": 31 146 | }, 147 | "source": "input.css" 148 | } 149 | }, 150 | { 151 | "type": "declaration", 152 | "property": "color", 153 | "value": "#000", 154 | "position": { 155 | "start": { 156 | "line": 21, 157 | "column": 15 158 | }, 159 | "end": { 160 | "line": 21, 161 | "column": 26 162 | }, 163 | "source": "input.css" 164 | } 165 | } 166 | ], 167 | "position": { 168 | "start": { 169 | "line": 19, 170 | "column": 15 171 | }, 172 | "end": { 173 | "line": 22, 174 | "column": 16 175 | }, 176 | "source": "input.css" 177 | } 178 | }, 179 | { 180 | "type": "rule", 181 | "selectors": [ 182 | "body" 183 | ], 184 | "declarations": [ 185 | { 186 | "type": "declaration", 187 | "property": "padding", 188 | "value": "1in", 189 | "position": { 190 | "start": { 191 | "line": 24, 192 | "column": 15 193 | }, 194 | "end": { 195 | "line": 24, 196 | "column": 27 197 | }, 198 | "source": "input.css" 199 | } 200 | }, 201 | { 202 | "type": "declaration", 203 | "property": "border", 204 | "value": "0.5pt solid #666", 205 | "position": { 206 | "start": { 207 | "line": 25, 208 | "column": 15 209 | }, 210 | "end": { 211 | "line": 25, 212 | "column": 39 213 | }, 214 | "source": "input.css" 215 | } 216 | } 217 | ], 218 | "position": { 219 | "start": { 220 | "line": 23, 221 | "column": 15 222 | }, 223 | "end": { 224 | "line": 26, 225 | "column": 16 226 | }, 227 | "source": "input.css" 228 | } 229 | } 230 | ], 231 | "position": { 232 | "start": { 233 | "line": 17, 234 | "column": 1 235 | }, 236 | "end": { 237 | "line": 27, 238 | "column": 2 239 | }, 240 | "source": "input.css" 241 | } 242 | } 243 | ] 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /test/cases/media/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [ 5 | { 6 | "type": "media", 7 | "media": "screen, projection", 8 | "rules": [ 9 | { 10 | "type": "comment", 11 | "comment": " html above ", 12 | "position": { 13 | "start": { 14 | "line": 2, 15 | "column": 3 16 | }, 17 | "end": { 18 | "line": 2, 19 | "column": 19 20 | }, 21 | "source": "input.css" 22 | } 23 | }, 24 | { 25 | "type": "rule", 26 | "selectors": [ 27 | "html" 28 | ], 29 | "declarations": [ 30 | { 31 | "type": "comment", 32 | "comment": " html inside ", 33 | "position": { 34 | "start": { 35 | "line": 4, 36 | "column": 5 37 | }, 38 | "end": { 39 | "line": 4, 40 | "column": 22 41 | }, 42 | "source": "input.css" 43 | } 44 | }, 45 | { 46 | "type": "declaration", 47 | "property": "background", 48 | "value": "#fffef0", 49 | "position": { 50 | "start": { 51 | "line": 5, 52 | "column": 5 53 | }, 54 | "end": { 55 | "line": 5, 56 | "column": 24 57 | }, 58 | "source": "input.css" 59 | } 60 | }, 61 | { 62 | "type": "declaration", 63 | "property": "color", 64 | "value": "#300", 65 | "position": { 66 | "start": { 67 | "line": 6, 68 | "column": 5 69 | }, 70 | "end": { 71 | "line": 6, 72 | "column": 16 73 | }, 74 | "source": "input.css" 75 | } 76 | } 77 | ], 78 | "position": { 79 | "start": { 80 | "line": 3, 81 | "column": 3 82 | }, 83 | "end": { 84 | "line": 7, 85 | "column": 4 86 | }, 87 | "source": "input.css" 88 | } 89 | }, 90 | { 91 | "type": "comment", 92 | "comment": " body above ", 93 | "position": { 94 | "start": { 95 | "line": 9, 96 | "column": 3 97 | }, 98 | "end": { 99 | "line": 9, 100 | "column": 19 101 | }, 102 | "source": "input.css" 103 | } 104 | }, 105 | { 106 | "type": "rule", 107 | "selectors": [ 108 | "body" 109 | ], 110 | "declarations": [ 111 | { 112 | "type": "comment", 113 | "comment": " body inside ", 114 | "position": { 115 | "start": { 116 | "line": 11, 117 | "column": 5 118 | }, 119 | "end": { 120 | "line": 11, 121 | "column": 22 122 | }, 123 | "source": "input.css" 124 | } 125 | }, 126 | { 127 | "type": "declaration", 128 | "property": "max-width", 129 | "value": "35em", 130 | "position": { 131 | "start": { 132 | "line": 12, 133 | "column": 5 134 | }, 135 | "end": { 136 | "line": 12, 137 | "column": 20 138 | }, 139 | "source": "input.css" 140 | } 141 | }, 142 | { 143 | "type": "declaration", 144 | "property": "margin", 145 | "value": "0 auto", 146 | "position": { 147 | "start": { 148 | "line": 13, 149 | "column": 5 150 | }, 151 | "end": { 152 | "line": 13, 153 | "column": 19 154 | }, 155 | "source": "input.css" 156 | } 157 | } 158 | ], 159 | "position": { 160 | "start": { 161 | "line": 10, 162 | "column": 3 163 | }, 164 | "end": { 165 | "line": 14, 166 | "column": 4 167 | }, 168 | "source": "input.css" 169 | } 170 | } 171 | ], 172 | "position": { 173 | "start": { 174 | "line": 1, 175 | "column": 1 176 | }, 177 | "end": { 178 | "line": 15, 179 | "column": 2 180 | }, 181 | "source": "input.css" 182 | } 183 | }, 184 | { 185 | "type": "media", 186 | "media": "print", 187 | "rules": [ 188 | { 189 | "type": "rule", 190 | "selectors": [ 191 | "html" 192 | ], 193 | "declarations": [ 194 | { 195 | "type": "declaration", 196 | "property": "background", 197 | "value": "#fff", 198 | "position": { 199 | "start": { 200 | "line": 19, 201 | "column": 5 202 | }, 203 | "end": { 204 | "line": 19, 205 | "column": 21 206 | }, 207 | "source": "input.css" 208 | } 209 | }, 210 | { 211 | "type": "declaration", 212 | "property": "color", 213 | "value": "#000", 214 | "position": { 215 | "start": { 216 | "line": 20, 217 | "column": 5 218 | }, 219 | "end": { 220 | "line": 20, 221 | "column": 16 222 | }, 223 | "source": "input.css" 224 | } 225 | } 226 | ], 227 | "position": { 228 | "start": { 229 | "line": 18, 230 | "column": 3 231 | }, 232 | "end": { 233 | "line": 21, 234 | "column": 4 235 | }, 236 | "source": "input.css" 237 | } 238 | }, 239 | { 240 | "type": "rule", 241 | "selectors": [ 242 | "body" 243 | ], 244 | "declarations": [ 245 | { 246 | "type": "declaration", 247 | "property": "padding", 248 | "value": "1in", 249 | "position": { 250 | "start": { 251 | "line": 23, 252 | "column": 5 253 | }, 254 | "end": { 255 | "line": 23, 256 | "column": 17 257 | }, 258 | "source": "input.css" 259 | } 260 | }, 261 | { 262 | "type": "declaration", 263 | "property": "border", 264 | "value": "0.5pt solid #666", 265 | "position": { 266 | "start": { 267 | "line": 24, 268 | "column": 5 269 | }, 270 | "end": { 271 | "line": 24, 272 | "column": 29 273 | }, 274 | "source": "input.css" 275 | } 276 | } 277 | ], 278 | "position": { 279 | "start": { 280 | "line": 22, 281 | "column": 3 282 | }, 283 | "end": { 284 | "line": 25, 285 | "column": 4 286 | }, 287 | "source": "input.css" 288 | } 289 | } 290 | ], 291 | "position": { 292 | "start": { 293 | "line": 17, 294 | "column": 1 295 | }, 296 | "end": { 297 | "line": 26, 298 | "column": 2 299 | }, 300 | "source": "input.css" 301 | } 302 | } 303 | ] 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # css [![Build Status](https://travis-ci.org/reworkcss/css.svg?branch=master)](https://travis-ci.org/reworkcss/css) 2 | 3 | CSS parser / stringifier. 4 | 5 | ## Installation 6 | 7 | $ npm install css 8 | 9 | ## Usage 10 | 11 | ```js 12 | var css = require('css'); 13 | var obj = css.parse('body { font-size: 12px; }', options); 14 | css.stringify(obj, options); 15 | ``` 16 | 17 | ## API 18 | 19 | ### css.parse(code, [options]) 20 | 21 | Accepts a CSS string and returns an AST `object`. 22 | 23 | `options`: 24 | 25 | - silent: silently fail on parse errors. 26 | - source: the path to the file containing `css`. Makes errors and source 27 | maps more helpful, by letting them know where code comes from. 28 | 29 | ### css.stringify(object, [options]) 30 | 31 | Accepts an AST `object` (as `css.parse` produces) and returns a CSS string. 32 | 33 | `options`: 34 | 35 | - indent: the string used to indent the output. Defaults to two spaces. 36 | - compress: omit comments and extraneous whitespace. 37 | - sourcemap: return a sourcemap along with the CSS output. Using the `source` 38 | option of `css.parse` is strongly recommended when creating a source map. 39 | Specify `sourcemap: 'generator'` to return the SourceMapGenerator object 40 | instead of serializing the source map. 41 | - inputSourcemaps: (enabled by default, specify `false` to disable) reads any 42 | source maps referenced by the input files when generating the output source 43 | map. When enabled, file system access may be required for reading the 44 | referenced source maps. 45 | 46 | ### Example 47 | 48 | ```js 49 | var ast = css.parse('body { font-size: 12px; }', { source: 'source.css' }); 50 | 51 | var css = css.stringify(ast); 52 | 53 | var result = css.stringify(ast, { sourcemap: true }); 54 | result.code // string with CSS 55 | result.map // source map object 56 | ``` 57 | 58 | ### Errors 59 | 60 | Errors thrown during parsing have the following properties: 61 | 62 | - message: `String`. The full error message with the source position. 63 | - reason: `String`. The error message without position. 64 | - filename: `String` or `undefined`. The value of `options.source` if 65 | passed to `css.parse`. Otherwise `undefined`. 66 | - line: `Integer`. 67 | - column: `Integer`. 68 | - source: `String`. The portion of code that couldn't be parsed. 69 | 70 | When parsing with the `silent` option, errors are listed in the 71 | `parsingErrors` property of the [`stylesheet`](#stylesheet) node instead 72 | of being thrown. 73 | 74 | If you create any errors in plugins such as in 75 | [rework](https://github.com/reworkcss/rework), you __must__ set the same 76 | properties for consistency. 77 | 78 | ## AST 79 | 80 | Interactively explore the AST with . 81 | 82 | ### Common properties 83 | 84 | All nodes have the following properties. 85 | 86 | #### position 87 | 88 | Information about the position in the source string that corresponds to 89 | the node. 90 | 91 | `Object`: 92 | 93 | - start: `Object`: 94 | - line: `Number`. 95 | - column: `Number`. 96 | - end: `Object`: 97 | - line: `Number`. 98 | - column: `Number`. 99 | - source: `String` or `undefined`. The value of `options.source` if passed to 100 | `css.parse`. Otherwise `undefined`. 101 | - content: `String`. The full source string passed to `css.parse`. 102 | 103 | The line and column numbers are 1-based: The first line is 1 and the first 104 | column of a line is 1 (not 0). 105 | 106 | The `position` property lets you know from which source file the node comes 107 | from (if available), what that file contains, and what part of that file was 108 | parsed into the node. 109 | 110 | #### type 111 | 112 | `String`. The possible values are the ones listed in the Types section below. 113 | 114 | #### parent 115 | 116 | A reference to the parent node, or `null` if the node has no parent. 117 | 118 | ### Types 119 | 120 | The available values of `node.type` are listed below, as well as the available 121 | properties of each node (other than the common properties listed above.) 122 | 123 | #### stylesheet 124 | 125 | The root node returned by `css.parse`. 126 | 127 | - stylesheet: `Object`: 128 | - rules: `Array` of nodes with the types `rule`, `comment` and any of the 129 | at-rule types. 130 | - parsingErrors: `Array` of `Error`s. Errors collected during parsing when 131 | option `silent` is true. 132 | 133 | #### rule 134 | 135 | - selectors: `Array` of `String`s. The list of selectors of the rule, split 136 | on commas. Each selector is trimmed from whitespace and comments. 137 | - declarations: `Array` of nodes with the types `declaration` and `comment`. 138 | 139 | #### declaration 140 | 141 | - property: `String`. The property name, trimmed from whitespace and 142 | comments. May not be empty. 143 | - value: `String`. The value of the property, trimmed from whitespace and 144 | comments. Empty values are allowed. 145 | 146 | #### comment 147 | 148 | A rule-level or declaration-level comment. Comments inside selectors, 149 | properties and values etc. are lost. 150 | 151 | - comment: `String`. The part between the starting `/*` and the ending `*/` 152 | of the comment, including whitespace. 153 | 154 | #### charset 155 | 156 | The `@charset` at-rule. 157 | 158 | - charset: `String`. The part following `@charset `. 159 | 160 | #### custom-media 161 | 162 | The `@custom-media` at-rule. 163 | 164 | - name: `String`. The `--`-prefixed name. 165 | - media: `String`. The part following the name. 166 | 167 | #### document 168 | 169 | The `@document` at-rule. 170 | 171 | - document: `String`. The part following `@document `. 172 | - vendor: `String` or `undefined`. The vendor prefix in `@document`, or 173 | `undefined` if there is none. 174 | - rules: `Array` of nodes with the types `rule`, `comment` and any of the 175 | at-rule types. 176 | 177 | #### font-face 178 | 179 | The `@font-face` at-rule. 180 | 181 | - declarations: `Array` of nodes with the types `declaration` and `comment`. 182 | 183 | #### host 184 | 185 | The `@host` at-rule. 186 | 187 | - rules: `Array` of nodes with the types `rule`, `comment` and any of the 188 | at-rule types. 189 | 190 | #### import 191 | 192 | The `@import` at-rule. 193 | 194 | - import: `String`. The part following `@import `. 195 | 196 | #### keyframes 197 | 198 | The `@keyframes` at-rule. 199 | 200 | - name: `String`. The name of the keyframes rule. 201 | - vendor: `String` or `undefined`. The vendor prefix in `@keyframes`, or 202 | `undefined` if there is none. 203 | - keyframes: `Array` of nodes with the types `keyframe` and `comment`. 204 | 205 | #### keyframe 206 | 207 | - values: `Array` of `String`s. The list of “selectors” of the keyframe rule, 208 | split on commas. Each “selector” is trimmed from whitespace. 209 | - declarations: `Array` of nodes with the types `declaration` and `comment`. 210 | 211 | #### media 212 | 213 | The `@media` at-rule. 214 | 215 | - media: `String`. The part following `@media `. 216 | - rules: `Array` of nodes with the types `rule`, `comment` and any of the 217 | at-rule types. 218 | 219 | #### namespace 220 | 221 | The `@namespace` at-rule. 222 | 223 | - namespace: `String`. The part following `@namespace `. 224 | 225 | #### page 226 | 227 | The `@page` at-rule. 228 | 229 | - selectors: `Array` of `String`s. The list of selectors of the rule, split 230 | on commas. Each selector is trimmed from whitespace and comments. 231 | - declarations: `Array` of nodes with the types `declaration` and `comment`. 232 | 233 | #### supports 234 | 235 | The `@supports` at-rule. 236 | 237 | - supports: `String`. The part following `@supports `. 238 | - rules: `Array` of nodes with the types `rule`, `comment` and any of the 239 | at-rule types. 240 | 241 | ### Example 242 | 243 | CSS: 244 | 245 | ```css 246 | body { 247 | background: #eee; 248 | color: #888; 249 | } 250 | ``` 251 | 252 | Parse tree: 253 | 254 | ```json 255 | { 256 | "type": "stylesheet", 257 | "stylesheet": { 258 | "rules": [ 259 | { 260 | "type": "rule", 261 | "selectors": [ 262 | "body" 263 | ], 264 | "declarations": [ 265 | { 266 | "type": "declaration", 267 | "property": "background", 268 | "value": "#eee", 269 | "position": { 270 | "start": { 271 | "line": 2, 272 | "column": 3 273 | }, 274 | "end": { 275 | "line": 2, 276 | "column": 19 277 | } 278 | } 279 | }, 280 | { 281 | "type": "declaration", 282 | "property": "color", 283 | "value": "#888", 284 | "position": { 285 | "start": { 286 | "line": 3, 287 | "column": 3 288 | }, 289 | "end": { 290 | "line": 3, 291 | "column": 14 292 | } 293 | } 294 | } 295 | ], 296 | "position": { 297 | "start": { 298 | "line": 1, 299 | "column": 1 300 | }, 301 | "end": { 302 | "line": 4, 303 | "column": 2 304 | } 305 | } 306 | } 307 | ] 308 | } 309 | } 310 | ``` 311 | 312 | ## License 313 | 314 | MIT 315 | -------------------------------------------------------------------------------- /benchmark/small.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v3.0.1 | MIT License | git.io/normalize */ 2 | 3 | /** 4 | * 1. Set default font family to sans-serif. 5 | * 2. Prevent iOS text size adjust after orientation change, without disabling 6 | * user zoom. 7 | */ 8 | 9 | html { 10 | font-family: sans-serif; /* 1 */ 11 | -ms-text-size-adjust: 100%; /* 2 */ 12 | -webkit-text-size-adjust: 100%; /* 2 */ 13 | } 14 | 15 | /** 16 | * Remove default margin. 17 | */ 18 | 19 | body { 20 | margin: 0; 21 | } 22 | 23 | /* HTML5 display definitions 24 | ========================================================================== */ 25 | 26 | /** 27 | * Correct `block` display not defined for any HTML5 element in IE 8/9. 28 | * Correct `block` display not defined for `details` or `summary` in IE 10/11 and Firefox. 29 | * Correct `block` display not defined for `main` in IE 11. 30 | */ 31 | 32 | article, 33 | aside, 34 | details, 35 | figcaption, 36 | figure, 37 | footer, 38 | header, 39 | hgroup, 40 | main, 41 | nav, 42 | section, 43 | summary { 44 | display: block; 45 | } 46 | 47 | /** 48 | * 1. Correct `inline-block` display not defined in IE 8/9. 49 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. 50 | */ 51 | 52 | audio, 53 | canvas, 54 | progress, 55 | video { 56 | display: inline-block; /* 1 */ 57 | vertical-align: baseline; /* 2 */ 58 | } 59 | 60 | /** 61 | * Prevent modern browsers from displaying `audio` without controls. 62 | * Remove excess height in iOS 5 devices. 63 | */ 64 | 65 | audio:not([controls]) { 66 | display: none; 67 | height: 0; 68 | } 69 | 70 | /** 71 | * Address `[hidden]` styling not present in IE 8/9/10. 72 | * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. 73 | */ 74 | 75 | [hidden], 76 | template { 77 | display: none; 78 | } 79 | 80 | /* Links 81 | ========================================================================== */ 82 | 83 | /** 84 | * Remove the gray background color from active links in IE 10. 85 | */ 86 | 87 | a { 88 | background: transparent; 89 | } 90 | 91 | /** 92 | * Improve readability when focused and also mouse hovered in all browsers. 93 | */ 94 | 95 | a:active, 96 | a:hover { 97 | outline: 0; 98 | } 99 | 100 | /* Text-level semantics 101 | ========================================================================== */ 102 | 103 | /** 104 | * Address styling not present in IE 8/9/10/11, Safari, and Chrome. 105 | */ 106 | 107 | abbr[title] { 108 | border-bottom: 1px dotted; 109 | } 110 | 111 | /** 112 | * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. 113 | */ 114 | 115 | b, 116 | strong { 117 | font-weight: bold; 118 | } 119 | 120 | /** 121 | * Address styling not present in Safari and Chrome. 122 | */ 123 | 124 | dfn { 125 | font-style: italic; 126 | } 127 | 128 | /** 129 | * Address variable `h1` font-size and margin within `section` and `article` 130 | * contexts in Firefox 4+, Safari, and Chrome. 131 | */ 132 | 133 | h1 { 134 | font-size: 2em; 135 | margin: 0.67em 0; 136 | } 137 | 138 | /** 139 | * Address styling not present in IE 8/9. 140 | */ 141 | 142 | mark { 143 | background: #ff0; 144 | color: #000; 145 | } 146 | 147 | /** 148 | * Address inconsistent and variable font size in all browsers. 149 | */ 150 | 151 | small { 152 | font-size: 80%; 153 | } 154 | 155 | /** 156 | * Prevent `sub` and `sup` affecting `line-height` in all browsers. 157 | */ 158 | 159 | sub, 160 | sup { 161 | font-size: 75%; 162 | line-height: 0; 163 | position: relative; 164 | vertical-align: baseline; 165 | } 166 | 167 | sup { 168 | top: -0.5em; 169 | } 170 | 171 | sub { 172 | bottom: -0.25em; 173 | } 174 | 175 | /* Embedded content 176 | ========================================================================== */ 177 | 178 | /** 179 | * Remove border when inside `a` element in IE 8/9/10. 180 | */ 181 | 182 | img { 183 | border: 0; 184 | } 185 | 186 | /** 187 | * Correct overflow not hidden in IE 9/10/11. 188 | */ 189 | 190 | svg:not(:root) { 191 | overflow: hidden; 192 | } 193 | 194 | /* Grouping content 195 | ========================================================================== */ 196 | 197 | /** 198 | * Address margin not present in IE 8/9 and Safari. 199 | */ 200 | 201 | figure { 202 | margin: 1em 40px; 203 | } 204 | 205 | /** 206 | * Address differences between Firefox and other browsers. 207 | */ 208 | 209 | hr { 210 | -moz-box-sizing: content-box; 211 | box-sizing: content-box; 212 | height: 0; 213 | } 214 | 215 | /** 216 | * Contain overflow in all browsers. 217 | */ 218 | 219 | pre { 220 | overflow: auto; 221 | } 222 | 223 | /** 224 | * Address odd `em`-unit font size rendering in all browsers. 225 | */ 226 | 227 | code, 228 | kbd, 229 | pre, 230 | samp { 231 | font-family: monospace, monospace; 232 | font-size: 1em; 233 | } 234 | 235 | /* Forms 236 | ========================================================================== */ 237 | 238 | /** 239 | * Known limitation: by default, Chrome and Safari on OS X allow very limited 240 | * styling of `select`, unless a `border` property is set. 241 | */ 242 | 243 | /** 244 | * 1. Correct color not being inherited. 245 | * Known issue: affects color of disabled elements. 246 | * 2. Correct font properties not being inherited. 247 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. 248 | */ 249 | 250 | button, 251 | input, 252 | optgroup, 253 | select, 254 | textarea { 255 | color: inherit; /* 1 */ 256 | font: inherit; /* 2 */ 257 | margin: 0; /* 3 */ 258 | } 259 | 260 | /** 261 | * Address `overflow` set to `hidden` in IE 8/9/10/11. 262 | */ 263 | 264 | button { 265 | overflow: visible; 266 | } 267 | 268 | /** 269 | * Address inconsistent `text-transform` inheritance for `button` and `select`. 270 | * All other form control elements do not inherit `text-transform` values. 271 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. 272 | * Correct `select` style inheritance in Firefox. 273 | */ 274 | 275 | button, 276 | select { 277 | text-transform: none; 278 | } 279 | 280 | /** 281 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 282 | * and `video` controls. 283 | * 2. Correct inability to style clickable `input` types in iOS. 284 | * 3. Improve usability and consistency of cursor style between image-type 285 | * `input` and others. 286 | */ 287 | 288 | button, 289 | html input[type="button"], /* 1 */ 290 | input[type="reset"], 291 | input[type="submit"] { 292 | -webkit-appearance: button; /* 2 */ 293 | cursor: pointer; /* 3 */ 294 | } 295 | 296 | /** 297 | * Re-set default cursor for disabled elements. 298 | */ 299 | 300 | button[disabled], 301 | html input[disabled] { 302 | cursor: default; 303 | } 304 | 305 | /** 306 | * Remove inner padding and border in Firefox 4+. 307 | */ 308 | 309 | button::-moz-focus-inner, 310 | input::-moz-focus-inner { 311 | border: 0; 312 | padding: 0; 313 | } 314 | 315 | /** 316 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in 317 | * the UA stylesheet. 318 | */ 319 | 320 | input { 321 | line-height: normal; 322 | } 323 | 324 | /** 325 | * It's recommended that you don't attempt to style these elements. 326 | * Firefox's implementation doesn't respect box-sizing, padding, or width. 327 | * 328 | * 1. Address box sizing set to `content-box` in IE 8/9/10. 329 | * 2. Remove excess padding in IE 8/9/10. 330 | */ 331 | 332 | input[type="checkbox"], 333 | input[type="radio"] { 334 | box-sizing: border-box; /* 1 */ 335 | padding: 0; /* 2 */ 336 | } 337 | 338 | /** 339 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain 340 | * `font-size` values of the `input`, it causes the cursor style of the 341 | * decrement button to change from `default` to `text`. 342 | */ 343 | 344 | input[type="number"]::-webkit-inner-spin-button, 345 | input[type="number"]::-webkit-outer-spin-button { 346 | height: auto; 347 | } 348 | 349 | /** 350 | * 1. Address `appearance` set to `searchfield` in Safari and Chrome. 351 | * 2. Address `box-sizing` set to `border-box` in Safari and Chrome 352 | * (include `-moz` to future-proof). 353 | */ 354 | 355 | input[type="search"] { 356 | -webkit-appearance: textfield; /* 1 */ 357 | -moz-box-sizing: content-box; 358 | -webkit-box-sizing: content-box; /* 2 */ 359 | box-sizing: content-box; 360 | } 361 | 362 | /** 363 | * Remove inner padding and search cancel button in Safari and Chrome on OS X. 364 | * Safari (but not Chrome) clips the cancel button when the search input has 365 | * padding (and `textfield` appearance). 366 | */ 367 | 368 | input[type="search"]::-webkit-search-cancel-button, 369 | input[type="search"]::-webkit-search-decoration { 370 | -webkit-appearance: none; 371 | } 372 | 373 | /** 374 | * Define consistent border, margin, and padding. 375 | */ 376 | 377 | fieldset { 378 | border: 1px solid #c0c0c0; 379 | margin: 0 2px; 380 | padding: 0.35em 0.625em 0.75em; 381 | } 382 | 383 | /** 384 | * 1. Correct `color` not being inherited in IE 8/9/10/11. 385 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 386 | */ 387 | 388 | legend { 389 | border: 0; /* 1 */ 390 | padding: 0; /* 2 */ 391 | } 392 | 393 | /** 394 | * Remove default vertical scrollbar in IE 8/9/10/11. 395 | */ 396 | 397 | textarea { 398 | overflow: auto; 399 | } 400 | 401 | /** 402 | * Don't inherit the `font-weight` (applied by a rule above). 403 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. 404 | */ 405 | 406 | optgroup { 407 | font-weight: bold; 408 | } 409 | 410 | /* Tables 411 | ========================================================================== */ 412 | 413 | /** 414 | * Remove most spacing between table cells. 415 | */ 416 | 417 | table { 418 | border-collapse: collapse; 419 | border-spacing: 0; 420 | } 421 | 422 | td, 423 | th { 424 | padding: 0; 425 | } 426 | -------------------------------------------------------------------------------- /lib/parse/index.js: -------------------------------------------------------------------------------- 1 | // http://www.w3.org/TR/CSS21/grammar.html 2 | // https://github.com/visionmedia/css-parse/pull/49#issuecomment-30088027 3 | var commentre = /\/\*[^*]*\*+([^/*][^*]*\*+)*\//g 4 | 5 | module.exports = function(css, options){ 6 | options = options || {}; 7 | 8 | /** 9 | * Positional. 10 | */ 11 | 12 | var lineno = 1; 13 | var column = 1; 14 | 15 | /** 16 | * Update lineno and column based on `str`. 17 | */ 18 | 19 | function updatePosition(str) { 20 | var lines = str.match(/\n/g); 21 | if (lines) lineno += lines.length; 22 | var i = str.lastIndexOf('\n'); 23 | column = ~i ? str.length - i : column + str.length; 24 | } 25 | 26 | /** 27 | * Mark position and patch `node.position`. 28 | */ 29 | 30 | function position() { 31 | var start = { line: lineno, column: column }; 32 | return function(node){ 33 | node.position = new Position(start); 34 | whitespace(); 35 | return node; 36 | }; 37 | } 38 | 39 | /** 40 | * Store position information for a node 41 | */ 42 | 43 | function Position(start) { 44 | this.start = start; 45 | this.end = { line: lineno, column: column }; 46 | this.source = options.source; 47 | } 48 | 49 | /** 50 | * Non-enumerable source string 51 | */ 52 | 53 | Position.prototype.content = css; 54 | 55 | /** 56 | * Error `msg`. 57 | */ 58 | 59 | var errorsList = []; 60 | 61 | function error(msg) { 62 | var err = new Error(options.source + ':' + lineno + ':' + column + ': ' + msg); 63 | err.reason = msg; 64 | err.filename = options.source; 65 | err.line = lineno; 66 | err.column = column; 67 | err.source = css; 68 | 69 | if (options.silent) { 70 | errorsList.push(err); 71 | } else { 72 | throw err; 73 | } 74 | } 75 | 76 | /** 77 | * Parse stylesheet. 78 | */ 79 | 80 | function stylesheet() { 81 | var rulesList = rules(); 82 | 83 | return { 84 | type: 'stylesheet', 85 | stylesheet: { 86 | source: options.source, 87 | rules: rulesList, 88 | parsingErrors: errorsList 89 | } 90 | }; 91 | } 92 | 93 | /** 94 | * Opening brace. 95 | */ 96 | 97 | function open() { 98 | return match(/^{\s*/); 99 | } 100 | 101 | /** 102 | * Closing brace. 103 | */ 104 | 105 | function close() { 106 | return match(/^}/); 107 | } 108 | 109 | /** 110 | * Parse ruleset. 111 | */ 112 | 113 | function rules() { 114 | var node; 115 | var rules = []; 116 | whitespace(); 117 | comments(rules); 118 | while (css.length && css.charAt(0) != '}' && (node = atrule() || rule())) { 119 | if (node !== false) { 120 | rules.push(node); 121 | comments(rules); 122 | } 123 | } 124 | return rules; 125 | } 126 | 127 | /** 128 | * Match `re` and return captures. 129 | */ 130 | 131 | function match(re) { 132 | var m = re.exec(css); 133 | if (!m) return; 134 | var str = m[0]; 135 | updatePosition(str); 136 | css = css.slice(str.length); 137 | return m; 138 | } 139 | 140 | /** 141 | * Parse whitespace. 142 | */ 143 | 144 | function whitespace() { 145 | match(/^\s*/); 146 | } 147 | 148 | /** 149 | * Parse comments; 150 | */ 151 | 152 | function comments(rules) { 153 | var c; 154 | rules = rules || []; 155 | while (c = comment()) { 156 | if (c !== false) { 157 | rules.push(c); 158 | } 159 | } 160 | return rules; 161 | } 162 | 163 | /** 164 | * Parse comment. 165 | */ 166 | 167 | function comment() { 168 | var pos = position(); 169 | if ('/' != css.charAt(0) || '*' != css.charAt(1)) return; 170 | 171 | var i = 2; 172 | while ("" != css.charAt(i) && ('*' != css.charAt(i) || '/' != css.charAt(i + 1))) ++i; 173 | i += 2; 174 | 175 | if ("" === css.charAt(i-1)) { 176 | return error('End of comment missing'); 177 | } 178 | 179 | var str = css.slice(2, i - 2); 180 | column += 2; 181 | updatePosition(str); 182 | css = css.slice(i); 183 | column += 2; 184 | 185 | return pos({ 186 | type: 'comment', 187 | comment: str 188 | }); 189 | } 190 | 191 | /** 192 | * Parse selector. 193 | */ 194 | 195 | function selector() { 196 | var m = match(/^([^{]+)/); 197 | if (!m) return; 198 | /* @fix Remove all comments from selectors 199 | * http://ostermiller.org/findcomment.html */ 200 | return trim(m[0]) 201 | .replace(/\/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*\/+/g, '') 202 | .replace(/"(?:\\"|[^"])*"|'(?:\\'|[^'])*'/g, function(m) { 203 | return m.replace(/,/g, '\u200C'); 204 | }) 205 | .split(/\s*(?![^(]*\)),\s*/) 206 | .map(function(s) { 207 | return s.replace(/\u200C/g, ','); 208 | }); 209 | } 210 | 211 | /** 212 | * Parse declaration. 213 | */ 214 | 215 | function declaration() { 216 | var pos = position(); 217 | 218 | // prop 219 | var prop = match(/^(\*?[-#\/\*\\\w]+(\[[0-9a-z_-]+\])?)\s*/); 220 | if (!prop) return; 221 | prop = trim(prop[0]); 222 | 223 | // : 224 | if (!match(/^:\s*/)) return error("property missing ':'"); 225 | 226 | // val 227 | var val = match(/^((?:'(?:\\'|.)*?'|"(?:\\"|.)*?"|\([^\)]*?\)|[^};])+)/); 228 | 229 | var ret = pos({ 230 | type: 'declaration', 231 | property: prop.replace(commentre, ''), 232 | value: val ? trim(val[0]).replace(commentre, '') : '' 233 | }); 234 | 235 | // ; 236 | match(/^[;\s]*/); 237 | 238 | return ret; 239 | } 240 | 241 | /** 242 | * Parse declarations. 243 | */ 244 | 245 | function declarations() { 246 | var decls = []; 247 | 248 | if (!open()) return error("missing '{'"); 249 | comments(decls); 250 | 251 | // declarations 252 | var decl; 253 | while (decl = declaration()) { 254 | if (decl !== false) { 255 | decls.push(decl); 256 | comments(decls); 257 | } 258 | } 259 | 260 | if (!close()) return error("missing '}'"); 261 | return decls; 262 | } 263 | 264 | /** 265 | * Parse keyframe. 266 | */ 267 | 268 | function keyframe() { 269 | var m; 270 | var vals = []; 271 | var pos = position(); 272 | 273 | while (m = match(/^((\d+\.\d+|\.\d+|\d+)%?|[a-z]+)\s*/)) { 274 | vals.push(m[1]); 275 | match(/^,\s*/); 276 | } 277 | 278 | if (!vals.length) return; 279 | 280 | return pos({ 281 | type: 'keyframe', 282 | values: vals, 283 | declarations: declarations() 284 | }); 285 | } 286 | 287 | /** 288 | * Parse keyframes. 289 | */ 290 | 291 | function atkeyframes() { 292 | var pos = position(); 293 | var m = match(/^@([-\w]+)?keyframes\s*/); 294 | 295 | if (!m) return; 296 | var vendor = m[1]; 297 | 298 | // identifier 299 | var m = match(/^([-\w]+)\s*/); 300 | if (!m) return error("@keyframes missing name"); 301 | var name = m[1]; 302 | 303 | if (!open()) return error("@keyframes missing '{'"); 304 | 305 | var frame; 306 | var frames = comments(); 307 | while (frame = keyframe()) { 308 | frames.push(frame); 309 | frames = frames.concat(comments()); 310 | } 311 | 312 | if (!close()) return error("@keyframes missing '}'"); 313 | 314 | return pos({ 315 | type: 'keyframes', 316 | name: name, 317 | vendor: vendor, 318 | keyframes: frames 319 | }); 320 | } 321 | 322 | /** 323 | * Parse supports. 324 | */ 325 | 326 | function atsupports() { 327 | var pos = position(); 328 | var m = match(/^@supports *([^{]+)/); 329 | 330 | if (!m) return; 331 | var supports = trim(m[1]); 332 | 333 | if (!open()) return error("@supports missing '{'"); 334 | 335 | var style = comments().concat(rules()); 336 | 337 | if (!close()) return error("@supports missing '}'"); 338 | 339 | return pos({ 340 | type: 'supports', 341 | supports: supports, 342 | rules: style 343 | }); 344 | } 345 | 346 | /** 347 | * Parse host. 348 | */ 349 | 350 | function athost() { 351 | var pos = position(); 352 | var m = match(/^@host\s*/); 353 | 354 | if (!m) return; 355 | 356 | if (!open()) return error("@host missing '{'"); 357 | 358 | var style = comments().concat(rules()); 359 | 360 | if (!close()) return error("@host missing '}'"); 361 | 362 | return pos({ 363 | type: 'host', 364 | rules: style 365 | }); 366 | } 367 | 368 | /** 369 | * Parse media. 370 | */ 371 | 372 | function atmedia() { 373 | var pos = position(); 374 | var m = match(/^@media *([^{]+)/); 375 | 376 | if (!m) return; 377 | var media = trim(m[1]); 378 | 379 | if (!open()) return error("@media missing '{'"); 380 | 381 | var style = comments().concat(rules()); 382 | 383 | if (!close()) return error("@media missing '}'"); 384 | 385 | return pos({ 386 | type: 'media', 387 | media: media, 388 | rules: style 389 | }); 390 | } 391 | 392 | 393 | /** 394 | * Parse custom-media. 395 | */ 396 | 397 | function atcustommedia() { 398 | var pos = position(); 399 | var m = match(/^@custom-media\s+(--[^\s]+)\s*([^{;]+);/); 400 | if (!m) return; 401 | 402 | return pos({ 403 | type: 'custom-media', 404 | name: trim(m[1]), 405 | media: trim(m[2]) 406 | }); 407 | } 408 | 409 | /** 410 | * Parse paged media. 411 | */ 412 | 413 | function atpage() { 414 | var pos = position(); 415 | var m = match(/^@page */); 416 | if (!m) return; 417 | 418 | var sel = selector() || []; 419 | 420 | if (!open()) return error("@page missing '{'"); 421 | var decls = comments(); 422 | 423 | // declarations 424 | var decl; 425 | while (decl = declaration()) { 426 | decls.push(decl); 427 | decls = decls.concat(comments()); 428 | } 429 | 430 | if (!close()) return error("@page missing '}'"); 431 | 432 | return pos({ 433 | type: 'page', 434 | selectors: sel, 435 | declarations: decls 436 | }); 437 | } 438 | 439 | /** 440 | * Parse document. 441 | */ 442 | 443 | function atdocument() { 444 | var pos = position(); 445 | var m = match(/^@([-\w]+)?document *([^{]+)/); 446 | if (!m) return; 447 | 448 | var vendor = trim(m[1]); 449 | var doc = trim(m[2]); 450 | 451 | if (!open()) return error("@document missing '{'"); 452 | 453 | var style = comments().concat(rules()); 454 | 455 | if (!close()) return error("@document missing '}'"); 456 | 457 | return pos({ 458 | type: 'document', 459 | document: doc, 460 | vendor: vendor, 461 | rules: style 462 | }); 463 | } 464 | 465 | /** 466 | * Parse font-face. 467 | */ 468 | 469 | function atfontface() { 470 | var pos = position(); 471 | var m = match(/^@font-face\s*/); 472 | if (!m) return; 473 | 474 | if (!open()) return error("@font-face missing '{'"); 475 | var decls = comments(); 476 | 477 | // declarations 478 | var decl; 479 | while (decl = declaration()) { 480 | decls.push(decl); 481 | decls = decls.concat(comments()); 482 | } 483 | 484 | if (!close()) return error("@font-face missing '}'"); 485 | 486 | return pos({ 487 | type: 'font-face', 488 | declarations: decls 489 | }); 490 | } 491 | 492 | /** 493 | * Parse import 494 | */ 495 | 496 | var atimport = _compileAtrule('import'); 497 | 498 | /** 499 | * Parse charset 500 | */ 501 | 502 | var atcharset = _compileAtrule('charset'); 503 | 504 | /** 505 | * Parse namespace 506 | */ 507 | 508 | var atnamespace = _compileAtrule('namespace'); 509 | 510 | /** 511 | * Parse non-block at-rules 512 | */ 513 | 514 | 515 | function _compileAtrule(name) { 516 | var re = new RegExp('^@' + name + '\\s*([^;]+);'); 517 | return function() { 518 | var pos = position(); 519 | var m = match(re); 520 | if (!m) return; 521 | var ret = { type: name }; 522 | ret[name] = m[1].trim(); 523 | return pos(ret); 524 | } 525 | } 526 | 527 | /** 528 | * Parse at rule. 529 | */ 530 | 531 | function atrule() { 532 | if (css[0] != '@') return; 533 | 534 | return atkeyframes() 535 | || atmedia() 536 | || atcustommedia() 537 | || atsupports() 538 | || atimport() 539 | || atcharset() 540 | || atnamespace() 541 | || atdocument() 542 | || atpage() 543 | || athost() 544 | || atfontface(); 545 | } 546 | 547 | /** 548 | * Parse rule. 549 | */ 550 | 551 | function rule() { 552 | var pos = position(); 553 | var sel = selector(); 554 | 555 | if (!sel) return error('selector missing'); 556 | comments(); 557 | 558 | return pos({ 559 | type: 'rule', 560 | selectors: sel, 561 | declarations: declarations() 562 | }); 563 | } 564 | 565 | return addParent(stylesheet()); 566 | }; 567 | 568 | /** 569 | * Trim `str`. 570 | */ 571 | 572 | function trim(str) { 573 | return str ? str.replace(/^\s+|\s+$/g, '') : ''; 574 | } 575 | 576 | /** 577 | * Adds non-enumerable parent node reference to each node. 578 | */ 579 | 580 | function addParent(obj, parent) { 581 | var isNode = obj && typeof obj.type === 'string'; 582 | var childParent = isNode ? obj : parent; 583 | 584 | for (var k in obj) { 585 | var value = obj[k]; 586 | if (Array.isArray(value)) { 587 | value.forEach(function(v) { addParent(v, childParent); }); 588 | } else if (value && typeof value === 'object') { 589 | addParent(value, childParent); 590 | } 591 | } 592 | 593 | if (isNode) { 594 | Object.defineProperty(obj, 'parent', { 595 | configurable: true, 596 | writable: true, 597 | enumerable: false, 598 | value: parent || null 599 | }); 600 | } 601 | 602 | return obj; 603 | } 604 | --------------------------------------------------------------------------------