├── .gitattributes ├── .gitignore ├── .travis.yml ├── Makefile ├── README.md ├── bin ├── css.ml └── dune ├── css.opam ├── docs ├── README.md ├── camel-train.4e48f6b0.png ├── codemirror.c7cb63f8.css ├── codemirror.c7cb63f8.map ├── index.html ├── party-camel.0684fd20.jpeg ├── src.46f26b8e.map ├── src.c4c495dd.js ├── src │ ├── css.js │ ├── docs.html │ ├── images │ │ ├── .DS_Store │ │ ├── camel-train.png │ │ └── party-camel.jpeg │ ├── index.html │ ├── index.js │ ├── lib │ │ ├── code-mirror-modes │ │ │ ├── css.js │ │ │ └── javascript.js │ │ ├── codemirror.css │ │ ├── codemirror.js │ │ └── css-parser.js │ ├── package.json │ ├── styles.css │ └── yarn.lock ├── styles.a142ff0e.css └── styles.a142ff0e.map ├── dune ├── dune-project ├── esy.lock.json ├── js_export ├── Index.ml └── dune ├── lib ├── Index.ml ├── Lexer.mll ├── Parser.mly ├── Printer.ml ├── Types.ml └── dune ├── package.json └── test ├── cases.test.js ├── cases ├── at-namespace │ ├── ast.json │ ├── compressed.css │ ├── input.css │ └── output.css ├── charset-linebreak │ ├── ast.json │ ├── compressed.css │ ├── input.css │ └── output.css ├── charset │ ├── ast.json │ ├── compressed.css │ ├── input.css │ └── output.css ├── colon-space │ ├── ast.json │ ├── compressed.css │ ├── input.css │ └── output.css ├── comma-attribute │ ├── ast.json │ ├── compressed.css │ ├── input.css │ └── output.css ├── comma-selector-function │ ├── ast.json │ ├── compressed.css │ ├── input.css │ └── output.css ├── comment-in │ ├── ast.json │ ├── compressed.css │ ├── input.css │ └── output.css ├── comment-url │ ├── ast.json │ ├── compressed.css │ ├── input.css │ └── output.css ├── comment │ ├── ast.json │ ├── compressed.css │ ├── input.css │ └── output.css ├── custom-media-linebreak │ ├── ast.json │ ├── compressed.css │ ├── input.css │ └── output.css ├── custom-media │ ├── ast.json │ ├── compressed.css │ ├── input.css │ └── output.css ├── document-linebreak │ ├── ast.json │ ├── compressed.css │ ├── input.css │ └── output.css ├── document │ ├── ast.json │ ├── compressed.css │ ├── input.css │ └── output.css ├── empty │ ├── ast.json │ ├── compressed.css │ ├── input.css │ └── output.css ├── escapes │ ├── ast.json │ ├── compressed.css │ ├── input.css │ └── output.css ├── font-face-linebreak │ ├── ast.json │ ├── compressed.css │ ├── input.css │ └── output.css ├── font-face │ ├── ast.json │ ├── compressed.css │ ├── input.css │ └── output.css ├── hose-linebreak │ ├── ast.json │ ├── compressed.css │ ├── input.css │ └── output.css ├── host │ ├── ast.json │ ├── compressed.css │ ├── input.css │ └── output.css ├── import-linebreak │ ├── ast.json │ ├── compressed.css │ ├── input.css │ └── output.css ├── import-messed │ ├── ast.json │ ├── compressed.css │ ├── input.css │ └── output.css ├── import │ ├── ast.json │ ├── compressed.css │ ├── input.css │ └── output.css ├── keyframes-advanced │ ├── ast.json │ ├── compressed.css │ ├── input.css │ └── output.css ├── keyframes-complex │ ├── ast.json │ ├── compressed.css │ ├── input.css │ └── output.css ├── keyframes-linebreak │ ├── ast.json │ ├── compressed.css │ ├── input.css │ └── output.css ├── keyframes-messed │ ├── ast.json │ ├── compressed.css │ ├── input.css │ └── output.css ├── keyframes-vendor │ ├── ast.json │ ├── compressed.css │ ├── input.css │ └── output.css ├── keyframes │ ├── ast.json │ ├── compressed.css │ ├── input.css │ └── output.css ├── media-linebreak │ ├── ast.json │ ├── compressed.css │ ├── input.css │ └── output.css ├── media-messed │ ├── ast.json │ ├── compressed.css │ ├── input.css │ └── output.css ├── media │ ├── ast.json │ ├── compressed.css │ ├── input.css │ └── output.css ├── messed-up │ ├── ast.json │ ├── compressed.css │ ├── input.css │ └── output.css ├── namespace-linebreak │ ├── ast.json │ ├── compressed.css │ ├── input.css │ └── output.css ├── namespace │ ├── ast.json │ ├── compressed.css │ ├── input.css │ └── output.css ├── no-semi │ ├── ast.json │ ├── compressed.css │ ├── input.css │ └── output.css ├── page-linebreak │ ├── ast.json │ ├── compressed.css │ ├── input.css │ └── output.css ├── paged-media │ ├── ast.json │ ├── compressed.css │ ├── input.css │ └── output.css ├── props │ ├── ast.json │ ├── compressed.css │ ├── input.css │ └── output.css ├── quote-escape │ ├── ast.json │ ├── compressed.css │ ├── input.css │ └── output.css ├── quoted │ ├── ast.json │ ├── compressed.css │ ├── input.css │ └── output.css ├── rule │ ├── ast.json │ ├── compressed.css │ ├── input.css │ └── output.css ├── rules │ ├── ast.json │ ├── compressed.css │ ├── input.css │ └── output.css ├── selector-compound │ ├── ast.json │ ├── compressed.css │ ├── input.css │ └── output.css ├── selector-space │ ├── ast.json │ ├── compressed.css │ ├── input.css │ └── output.css ├── selectors │ ├── ast.json │ ├── compressed.css │ ├── input.css │ └── output.css ├── supports-linebreak │ ├── ast.json │ ├── compressed.css │ ├── input.css │ └── output.css ├── supports │ ├── ast.json │ ├── compressed.css │ ├── input.css │ └── output.css └── wtf │ ├── ast.json │ ├── compressed.css │ ├── input.css │ └── output.css ├── dune ├── parse.js ├── stringify.js └── test.ml /.gitattributes: -------------------------------------------------------------------------------- 1 | *.lock.json binary 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | npm-debug.log 2 | .merlin 3 | yarn-error.log 4 | node_modules/ 5 | _build 6 | _esybuild 7 | _esyinstall 8 | _release 9 | *.byte 10 | *.native 11 | *.install 12 | esy.lock 13 | Parser.mli 14 | Parser.ml 15 | Lexer.ml 16 | Parser.automaton 17 | Parser.conflicts 18 | .cache 19 | dist 20 | _esy 21 | node_modules 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 6 4 | - 8 5 | os: 6 | - linux 7 | - osx 8 | install: 9 | - npm install --global esy@latest 10 | - esy install 11 | script: 12 | - esy build 13 | - make test 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build: lexer #parser 2 | @esy build 3 | 4 | test: build 5 | ./_build/install/default/bin/test 6 | 7 | lexer: 8 | @ocamllex -q ./lib/Lexer.mll 9 | 10 | parser: 11 | #@esy menhir --table --trace --explain --dump ./lib/Parser.mly 12 | @esy menhir --table --explain ./lib/Parser.mly 13 | 14 | release: 15 | @esy release 16 | 17 | format: 18 | @esy dune build @fmt --auto-promote 19 | 20 | clean: 21 | rm -rf ./lib/Lexer.ml ./lib/Parser.mli ./lib/Parser.ml _build _release -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # css-parse 2 | [![Build Status](https://travis-ci.org/samouri/ocaml-css.svg?branch=master)](https://travis-ci.org/samouri/ocaml-css) 3 | 4 | parse, detect errors, and pretty-print css directly in terminal, natively in OCaml, or even from JavaScript. 5 | 6 | **this is under active development, please do not use yet** 7 | 8 | ### Overview 9 | 10 | `css-parse` is written in OCaml and utilizes ocamllex and [menhir](http://gallium.inria.fr/~fpottier/menhir/manual.html) for lexer and parser generation. A pretty good introduction and explanation of the tools can be found in [Real World Ocaml](https://v1.realworldocaml.org/v1/en/html/parsing-with-ocamllex-and-menhir.html). 11 | We generate a binary as well that can be run from the command line. 12 | 13 | ### Development 14 | 15 | You'll need to install esy as well as menhir. 16 | To bootstrap the project run: 17 | ``` 18 | esy install 19 | make 20 | ``` 21 | 22 | ### Testing Done 23 | 24 | In order to test this module, I've stolen test cases from the very well tested [reworkcss](https://github.com/reworkcss/css). 25 | All of the test cases are run through OUnit and can be executed with: 26 | 27 | ``` 28 | make test 29 | ``` 30 | -------------------------------------------------------------------------------- /bin/css.ml: -------------------------------------------------------------------------------- 1 | (* open Lib.Types *) 2 | open Lib.Index 3 | open Cmdliner 4 | 5 | type output_t = Pretty | Errors | AST 6 | 7 | exception ArgException of string 8 | 9 | let str_of_output = function 10 | | Pretty -> "pretty" 11 | | Errors -> "errors" 12 | | AST -> "ast" 13 | 14 | let output_of_str o = 15 | match String.lowercase_ascii o with 16 | | "pretty" -> Pretty 17 | | "errors" -> Errors 18 | | "ast" -> AST 19 | | s -> 20 | raise 21 | (ArgException (Printf.sprintf "invalid string for output type: %s" s)) 22 | 23 | let output = 24 | let doc = 25 | "Pick what ocaml-css should do: pretty print, astprint, or check for errors" 26 | in 27 | Arg.(value & opt string "pretty" & info ["o"; "output"] ~docv:"OUTPUT" ~doc) 28 | 29 | let file = 30 | let doc = "The file " in 31 | Arg.(required & pos 0 (some string) None & info [] ~docv:"FILE" ~doc) 32 | 33 | let info = 34 | let doc = "parse a css document in order to detect errors" in 35 | let man = 36 | [ `S Manpage.s_bugs 37 | ; `P 38 | "Open bug reports as issues on the GitHub repo at \ 39 | https://github.com/samouri/ocaml-css." ] 40 | in 41 | Term.info "css" ~version:"%%VERSION%%" ~doc ~exits:Term.default_exits ~man 42 | 43 | let load_file f = 44 | let ic = open_in f in 45 | let n = in_channel_length ic in 46 | let s = Bytes.create n in 47 | really_input ic s 0 n ; close_in ic ; Bytes.to_string s 48 | 49 | let run file output_str = 50 | let parsed = parse ~fp:file (load_file file) in 51 | let output_flag = output_of_str output_str in 52 | (* learn how to make the proper CMDLiner converter for this *) 53 | let output = 54 | if output_flag = Pretty then print parsed else astPrint parsed 55 | in 56 | print_endline output 57 | 58 | let css_t = Term.(const run $ file $ output) 59 | 60 | let () = Term.exit @@ Term.eval (css_t, Term.info "css-parse") 61 | -------------------------------------------------------------------------------- /bin/dune: -------------------------------------------------------------------------------- 1 | (executable 2 | (name css) 3 | (public_name css-parse) 4 | (libraries lib cmdliner yojson) 5 | (preprocess (pps ppx_deriving.std ppx_deriving_yojson)) 6 | ) 7 | -------------------------------------------------------------------------------- /css.opam: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samouri/ocaml-css/5fbc3482b1e665dc134eba0c8fb0a1558c9943e0/css.opam -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # css-parse-explorer 2 | 3 | [css-parse-explorer](https://samouri.github.io/ocaml-css/) is a tool to help individuals explore the AST generated by css-parse as well as to prove the viability of using an OCaml generated library in the browser. 4 | 5 | ### Development 6 | 7 | ```bash 8 | cd src 9 | npm install 10 | npm start 11 | ``` 12 | 13 | ## Deployment 14 | 15 | the current process is a hack. 16 | 17 | 1. delete all web files (keep src and this README) 18 | 2. run `npm run build` from within the `src` directory which should build the app and `mv` the new web files here. 19 | 3. commit and push! [GitHub Pages](https://pages.github.com/) should pick up the changes within seconds. -------------------------------------------------------------------------------- /docs/camel-train.4e48f6b0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samouri/ocaml-css/5fbc3482b1e665dc134eba0c8fb0a1558c9943e0/docs/camel-train.4e48f6b0.png -------------------------------------------------------------------------------- /docs/codemirror.c7cb63f8.css: -------------------------------------------------------------------------------- 1 | .CodeMirror{color:#000;direction:ltr;font-family:monospace;height:300px}.CodeMirror-lines{padding:4px 0}.CodeMirror pre{padding:0 4px}.CodeMirror-gutter-filler,.CodeMirror-scrollbar-filler{background-color:#fff}.CodeMirror-gutters{background-color:#f7f7f7;border-right:1px solid #ddd;white-space:nowrap}.CodeMirror-linenumber{color:#999;min-width:20px;padding:0 3px 0 5px;text-align:right;white-space:nowrap}.CodeMirror-guttermarker{color:#000}.CodeMirror-guttermarker-subtle{color:#999}.CodeMirror-cursor{border-left:1px solid #000;border-right:none;width:0}.CodeMirror div.CodeMirror-secondarycursor{border-left:1px solid silver}.cm-fat-cursor .CodeMirror-cursor{background:#7e7;border:0!important;width:auto}.cm-fat-cursor div.CodeMirror-cursors{z-index:1}.cm-fat-cursor-mark{background-color:rgba(20,255,20,.5)}.cm-animate-fat-cursor,.cm-fat-cursor-mark{-moz-animation:blink 1.06s steps(1) infinite;-webkit-animation:blink 1.06s steps(1) infinite;animation:blink 1.06s steps(1) infinite}.cm-animate-fat-cursor{background-color:#7e7;border:0;width:auto}@-moz-keyframes blink{50%{background-color:transparent}}@-webkit-keyframes blink{50%{background-color:transparent}}@keyframes blink{50%{background-color:transparent}}.cm-tab{display:inline-block;text-decoration:inherit}.CodeMirror-rulers{bottom:-20px;left:0;overflow:hidden;position:absolute;right:0;top:-50px}.CodeMirror-ruler{border-left:1px solid #ccc;bottom:0;position:absolute;top:0}.cm-s-default .cm-header{color:#00f}.cm-s-default .cm-quote{color:#090}.cm-negative{color:#d44}.cm-positive{color:#292}.cm-header,.cm-strong{font-weight:700}.cm-em{font-style:italic}.cm-link{text-decoration:underline}.cm-strikethrough{text-decoration:line-through}.cm-s-default .cm-keyword{color:#708}.cm-s-default .cm-atom{color:#219}.cm-s-default .cm-number{color:#164}.cm-s-default .cm-def{color:#00f}.cm-s-default .cm-variable-2{color:#05a}.cm-s-default .cm-type,.cm-s-default .cm-variable-3{color:#085}.cm-s-default .cm-comment{color:#a50}.cm-s-default .cm-string{color:#a11}.cm-s-default .cm-string-2{color:#f50}.cm-s-default .cm-meta,.cm-s-default .cm-qualifier{color:#555}.cm-s-default .cm-builtin{color:#30a}.cm-s-default .cm-bracket{color:#997}.cm-s-default .cm-tag{color:#170}.cm-s-default .cm-attribute{color:#00c}.cm-s-default .cm-hr{color:#999}.cm-s-default .cm-link{color:#00c}.cm-invalidchar,.cm-s-default .cm-error{color:red}.CodeMirror-composing{border-bottom:2px solid}div.CodeMirror span.CodeMirror-matchingbracket{color:#0b0}div.CodeMirror span.CodeMirror-nonmatchingbracket{color:#a22}.CodeMirror-matchingtag{background:rgba(255,150,0,.3)}.CodeMirror-activeline-background{background:#e8f2ff}.CodeMirror{background:#fff;overflow:hidden;position:relative}.CodeMirror-scroll{height:100%;margin-bottom:-30px;margin-right:-30px;outline:none;overflow:scroll!important;padding-bottom:30px;position:relative}.CodeMirror-sizer{border-right:30px solid transparent;position:relative}.CodeMirror-gutter-filler,.CodeMirror-hscrollbar,.CodeMirror-scrollbar-filler,.CodeMirror-vscrollbar{display:none;position:absolute;z-index:6}.CodeMirror-vscrollbar{overflow-x:hidden;overflow-y:scroll;right:0;top:0}.CodeMirror-hscrollbar{bottom:0;left:0;overflow-x:scroll;overflow-y:hidden}.CodeMirror-scrollbar-filler{bottom:0;right:0}.CodeMirror-gutter-filler{bottom:0;left:0}.CodeMirror-gutters{left:0;min-height:100%;position:absolute;top:0;z-index:3}.CodeMirror-gutter{display:inline-block;height:100%;margin-bottom:-30px;vertical-align:top;white-space:normal}.CodeMirror-gutter-wrapper{background:none!important;border:none!important;position:absolute;z-index:4}.CodeMirror-gutter-background{bottom:0;position:absolute;top:0;z-index:4}.CodeMirror-gutter-elt{cursor:default;position:absolute;z-index:4}.CodeMirror-gutter-wrapper ::selection{background-color:transparent}.CodeMirror-gutter-wrapper ::-moz-selection{background-color:transparent}.CodeMirror-lines{cursor:text;min-height:1px}.CodeMirror pre{-moz-border-radius:0;-webkit-border-radius:0;-webkit-font-variant-ligatures:contextual;-webkit-tap-highlight-color:transparent;background:transparent;border-radius:0;border-width:0;color:inherit;font-family:inherit;font-size:inherit;font-variant-ligatures:contextual;line-height:inherit;margin:0;overflow:visible;position:relative;white-space:pre;word-wrap:normal;z-index:2}.CodeMirror-wrap pre{white-space:pre-wrap;word-break:normal;word-wrap:break-word}.CodeMirror-linebackground{bottom:0;left:0;position:absolute;right:0;top:0;z-index:0}.CodeMirror-linewidget{padding:.1px;position:relative;z-index:2}.CodeMirror-rtl pre{direction:rtl}.CodeMirror-code{outline:none}.CodeMirror-gutter,.CodeMirror-gutters,.CodeMirror-linenumber,.CodeMirror-scroll,.CodeMirror-sizer{-moz-box-sizing:content-box;box-sizing:content-box}.CodeMirror-measure{height:0;overflow:hidden;position:absolute;visibility:hidden;width:100%}.CodeMirror-cursor{pointer-events:none;position:absolute}.CodeMirror-measure pre{position:static}div.CodeMirror-cursors{position:relative;visibility:hidden;z-index:3}.CodeMirror-focused div.CodeMirror-cursors,div.CodeMirror-dragcursors{visibility:visible}.CodeMirror-selected{background:#d9d9d9}.CodeMirror-focused .CodeMirror-selected{background:#d7d4f0}.CodeMirror-crosshair{cursor:crosshair}.CodeMirror-line::selection,.CodeMirror-line>span::selection,.CodeMirror-line>span>span::selection{background:#d7d4f0}.CodeMirror-line::-moz-selection,.CodeMirror-line>span::-moz-selection,.CodeMirror-line>span>span::-moz-selection{background:#d7d4f0}.cm-searching{background-color:#ffa;background-color:rgba(255,255,0,.4)}.cm-force-border{padding-right:.1px}@media print{.CodeMirror div.CodeMirror-cursors{visibility:hidden}}.cm-tab-wrap-hack:after{content:""}span.CodeMirror-selectedtext{background:none} -------------------------------------------------------------------------------- /docs/codemirror.c7cb63f8.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":[],"names":[],"mappings":"","file":"codemirror.c7cb63f8.map","sourceRoot":".."} -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 |
Docs GitHub

css-parse

Parse and print your css in OCaml and JavaScript

css goes in... json comes out
No competition If you want to parse css in OCaml, you don't have many options. you are stuck with this library.
Blazing fast 🔥🔥🔥 faster than alternatives in OCaml. once again, you don't have options.
(actual benchmarks coming soon)
-------------------------------------------------------------------------------- /docs/party-camel.0684fd20.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samouri/ocaml-css/5fbc3482b1e665dc134eba0c8fb0a1558c9943e0/docs/party-camel.0684fd20.jpeg -------------------------------------------------------------------------------- /docs/src/docs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 |
10 | Docs 11 | GitHub 12 |
13 |

css-parse docs

14 |

COMING SOON

15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /docs/src/images/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samouri/ocaml-css/5fbc3482b1e665dc134eba0c8fb0a1558c9943e0/docs/src/images/.DS_Store -------------------------------------------------------------------------------- /docs/src/images/camel-train.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samouri/ocaml-css/5fbc3482b1e665dc134eba0c8fb0a1558c9943e0/docs/src/images/camel-train.png -------------------------------------------------------------------------------- /docs/src/images/party-camel.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samouri/ocaml-css/5fbc3482b1e665dc134eba0c8fb0a1558c9943e0/docs/src/images/party-camel.jpeg -------------------------------------------------------------------------------- /docs/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 |
11 | Docs 12 | GitHub 13 |
14 |

css-parse

15 |

Parse and print your css in OCaml and JavaScript

16 |
17 | css goes in... 18 | json comes out 19 |
20 |
21 | 23 | 24 |
25 |
26 |
27 | No competition 28 | If you want to parse css in OCaml, you don't have many options. you are stuck with this library. 29 |
30 |
31 | Blazing fast 🔥🔥🔥 32 | faster than alternatives in OCaml. once again, you don't have options.
(actual benchmarks coming soon)
33 |
34 |
35 |
36 | 37 | 38 |
39 | 40 | 43 | 81 | 82 | -------------------------------------------------------------------------------- /docs/src/index.js: -------------------------------------------------------------------------------- 1 | import { pprint, astPrint } from './lib/css-parser'; 2 | import codemirror from './lib/codemirror'; 3 | import './lib/code-mirror-modes/javascript'; 4 | import './lib/code-mirror-modes/css'; 5 | 6 | window.pprint = pprint; 7 | window.astPrint = astPrint; 8 | window.CodeMirror = codemirror; -------------------------------------------------------------------------------- /docs/src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "css-parse-explorer", 3 | "version": "1.0.0", 4 | "description": "a site to explore css-parse built in the same vein as astexplorer.net", 5 | "main": "index.js", 6 | "directories": { 7 | "lib": "lib" 8 | }, 9 | "scripts": { 10 | "start": "parcel index.html", 11 | "build": "rm dist/* && parcel build --public-url '/ocaml-css/' index.html && cp dist/* ..", 12 | "test": "echo \"Error: no test specified\" && exit 1" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/samouri/ocaml-css/tree/master/docs" 17 | }, 18 | "author": "samouri", 19 | "license": "MIT", 20 | "dependencies": { 21 | "parcel": "^1.9.7" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /docs/src/styles.css: -------------------------------------------------------------------------------- 1 | html { 2 | background-color: rgb(245, 245, 245 ); 3 | font-family: 'Montserrat', sans-serif; 4 | } 5 | 6 | body { 7 | display: flex; 8 | flex-direction: column; 9 | margin: 0; 10 | padding: 0; 11 | } 12 | 13 | h1 { 14 | padding: 0; 15 | margin: 0; 16 | padding-bottom: 15px; 17 | font-size: 32px; 18 | } 19 | 20 | .header { 21 | display: flex; 22 | flex-direction: row; 23 | justify-content: flex-end; 24 | margin-right: 100px; 25 | } 26 | 27 | .top-background-wrapper { 28 | position: absolute; 29 | z-index: -1; 30 | width: 100%; 31 | height: 400px; 32 | background: url("./images/party-camel.jpeg") no-repeat center; 33 | background-size: cover; 34 | } 35 | 36 | .top-background { 37 | top: 0; 38 | left: 0; 39 | width: 100%; 40 | height: 100%; 41 | /* background-color: rgba(3, 142, 195, 0.83); */ 42 | background-color: rgb(22,142,192, 0.8); 43 | } 44 | 45 | .header a { 46 | margin-top: 5px; 47 | display: flex; 48 | font-size: 20px; 49 | color: white; 50 | margin-right: 30px; 51 | } 52 | 53 | .page-title { 54 | font-size: 40px; 55 | margin-top: 100px; 56 | color: white; 57 | } 58 | 59 | .page-subtitle { 60 | font-size: 24px; 61 | margin-top: 0; 62 | padding-top: 0; 63 | color: rgb(234,242,247); 64 | } 65 | 66 | .page-title, .page-subtitle { 67 | text-align: center; 68 | } 69 | 70 | .explorer-area-header { 71 | width: 760px; 72 | background-color: rgb(9,87,117); 73 | height: 30px; 74 | margin: 0 auto; 75 | margin-top: 30px; 76 | padding: 0; 77 | color: white; 78 | display: flex; 79 | flex-direction: row; 80 | border-top-left-radius: 5px; 81 | border-top-right-radius: 5px; 82 | } 83 | 84 | .explorer-area-header span { 85 | display: flex; 86 | margin-left: 40px; 87 | width: 200px; 88 | margin-top: 5px; 89 | } 90 | 91 | .explorer-area-header span:nth-child(1) { 92 | margin-right: 140px; 93 | } 94 | 95 | .explorer-area { 96 | display: flex; 97 | justify-content: space-around; 98 | width: 760px; 99 | margin: 0 auto; 100 | background-color: rgb(245, 245, 245 ); 101 | } 102 | 103 | .CodeMirror { 104 | font-size: 12px; 105 | width: 360px; 106 | } 107 | 108 | .brag-area { 109 | display: flex; 110 | flex-direction: row; 111 | margin: 0 auto; 112 | margin-top: 50px; 113 | } 114 | 115 | .brag { 116 | display: flex; 117 | flex-direction: column; 118 | width: 340px; 119 | padding: 10px; 120 | text-align: center; 121 | } 122 | 123 | .brag-title { 124 | color: rgb(51,51,51); 125 | } 126 | .brag-content { 127 | color: rbg(85,85,85); 128 | } 129 | 130 | .call-to-action { 131 | display: flex; 132 | width: 760px; 133 | margin: 0 auto; 134 | margin-top: 50px; 135 | padding-left: 50px; 136 | padding-bottom: 80px; 137 | } 138 | 139 | .call-to-action img { 140 | width: 340px; 141 | padding: 10px; 142 | } 143 | 144 | .call-to-action button { 145 | background-color: rgb(9,87, 117); 146 | border-radius: 25px; 147 | font-size: 24px; 148 | color: white; 149 | width: 200px; 150 | height: 50px; 151 | margin-left: 80px; 152 | padding: 10px; 153 | margin-top: 25px; 154 | cursor: pointer; 155 | } 156 | 157 | .call-to-action button:hover { 158 | background-color: rgb(12, 123, 167); 159 | } 160 | 161 | footer { 162 | height: 50px; 163 | width: 100%; 164 | color: white; 165 | background-color: rgb(22,142,192); 166 | } 167 | 168 | footer span { 169 | display: flex; 170 | margin-left: 20px; 171 | margin-top: 15px; 172 | } -------------------------------------------------------------------------------- /docs/styles.a142ff0e.css: -------------------------------------------------------------------------------- 1 | html{background-color:#f5f5f5;font-family:Montserrat,sans-serif}body{display:flex;flex-direction:column;padding:0}body,h1{margin:0}h1{font-size:32px;padding:0 0 15px}.header{display:flex;flex-direction:row;justify-content:flex-end;margin-right:100px}.top-background-wrapper{background:url(party-camel.0684fd20.jpeg) no-repeat 50%;background-size:cover;height:400px;position:absolute;width:100%;z-index:-1}.top-background{background-color:rgba(22,142,192,.8);height:100%;left:0;top:0;width:100%}.header a{color:#fff;display:flex;font-size:20px;margin-right:30px;margin-top:5px}.page-title{color:#fff;font-size:40px;margin-top:100px}.page-subtitle{color:#eaf2f7;font-size:24px;margin-top:0;padding-top:0}.page-subtitle,.page-title{text-align:center}.explorer-area-header{background-color:#095775;border-top-left-radius:5px;border-top-right-radius:5px;color:#fff;display:flex;flex-direction:row;height:30px;margin:30px auto 0;padding:0;width:760px}.explorer-area-header span{display:flex;margin-left:40px;margin-top:5px;width:200px}.explorer-area-header span:first-child{margin-right:140px}.explorer-area{background-color:#f5f5f5;display:flex;justify-content:space-around;margin:0 auto;width:760px}.CodeMirror{font-size:12px;width:360px}.brag-area{display:flex;flex-direction:row;margin:50px auto 0}.brag{display:flex;flex-direction:column;padding:10px;text-align:center;width:340px}.brag-title{color:#333}.brag-content{color:rbg(85,85,85)}.call-to-action{display:flex;margin:50px auto 0;padding-bottom:80px;padding-left:50px;width:760px}.call-to-action img{padding:10px;width:340px}.call-to-action button{background-color:#095775;border-radius:25px;color:#fff;cursor:pointer;font-size:24px;height:50px;margin-left:80px;margin-top:25px;padding:10px;width:200px}.call-to-action button:hover{background-color:#0c7ba7}footer{background-color:#168ec0;color:#fff;height:50px;width:100%}footer span{display:flex;margin-left:20px;margin-top:15px} -------------------------------------------------------------------------------- /docs/styles.a142ff0e.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":[],"names":[],"mappings":"","file":"styles.a142ff0e.map","sourceRoot":".."} -------------------------------------------------------------------------------- /dune: -------------------------------------------------------------------------------- 1 | (ignored_subdirs (node_modules)) 2 | -------------------------------------------------------------------------------- /dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 1.0) 2 | (using menhir 2.0) 3 | (using fmt 1.0) 4 | -------------------------------------------------------------------------------- /js_export/Index.ml: -------------------------------------------------------------------------------- 1 | let parse s = Lib.Index.parse (Js.to_string s) 2 | 3 | let _ = 4 | Js.export_all 5 | (object%js 6 | method pprint s = Js.string (Lib.Index.print (parse s)) 7 | 8 | method astPrint s = Js.string (Lib.Index.astPrint (parse s)) 9 | end) 10 | -------------------------------------------------------------------------------- /js_export/dune: -------------------------------------------------------------------------------- 1 | (executable 2 | (name Index) 3 | (public_name index.js) 4 | (libraries lib js_of_ocaml) 5 | (preprocess (pps js_of_ocaml-ppx)) 6 | ) 7 | -------------------------------------------------------------------------------- /lib/Index.ml: -------------------------------------------------------------------------------- 1 | open Types 2 | 3 | let print = Printer.prettyPrint 4 | 5 | let astPrint = Printer.astPrint 6 | 7 | let getLast lst = List.nth_opt (List.rev lst) 0 8 | 9 | let getRulesetItemPos (t : Types.declaration orComment) = 10 | match t with Comment {pos} | Else {pos} -> pos 11 | 12 | let getStyleSheetItemPos (t : Types.rule) = 13 | match t with Comment {pos} | AtRule {pos} | StyleRule {pos} -> pos 14 | 15 | let isBefore (pos1 : Lexing.position) (pos2 : Lexing.position) = 16 | pos1.pos_lnum < pos2.pos_lnum 17 | || (pos1.pos_lnum = pos2.pos_lnum && pos1.pos_cnum < pos2.pos_cnum) 18 | 19 | let isAfter p1 p2 = not (isBefore p1 p2) 20 | 21 | let insertComment (stylesheet : Types.stylesheet) (comment : Types.comment) = 22 | (* does comment start before the node starts? *) 23 | let cStartP, cEndP = comment.pos in 24 | let nodeIsBeforeComment node = 25 | let startPos, _ = getStyleSheetItemPos node in 26 | isBefore startPos cStartP 27 | in 28 | let prevNodes, postNodes = List.partition nodeIsBeforeComment stylesheet in 29 | (* 30 | * there are three potential cases: 31 | * 1: comment occurs after all of the code. 32 | * 2: comment occurs in between rulesets. then insert comments in between. 33 | * 3: comment occurs in between rules. must dive into the ruleset. 34 | *) 35 | (* List.iter (fun x -> print_endline (show_stylesheet_item x)) prevNodes; 36 | List.iter (fun x -> print_endline (show_stylesheet_item x)) postNodes; *) 37 | match getLast prevNodes with 38 | | None -> Comment comment :: postNodes 39 | | Some node -> 40 | let _, endPos = getStyleSheetItemPos node in 41 | if isAfter cEndP endPos then prevNodes @ (Comment comment :: postNodes) 42 | else 43 | (* mid ruleset case*) 44 | let newNode = 45 | match node with 46 | (*TODO: atrules should handle comments *) 47 | | AtRule _ | Comment _ -> 48 | failwith "should never see nested comments in css" 49 | | StyleRule {prelude; declarations; pos} -> 50 | let rulesIsBeforeComments (rule : Types.declaration orComment) = 51 | let startPos, _ = 52 | match rule with Else {pos} -> pos | Comment {pos} -> pos 53 | in 54 | isBefore startPos cStartP 55 | in 56 | let prevRules, postRules = 57 | List.partition rulesIsBeforeComments declarations 58 | in 59 | { prelude 60 | ; declarations= prevRules @ (Comment comment :: postRules) 61 | ; pos } 62 | in 63 | List.rev (List.tl (List.rev prevNodes)) 64 | @ (StyleRule newNode :: postNodes) 65 | 66 | let parse ?(fp = "") (source : string) = 67 | let lexbuf = Lexing.from_string source in 68 | let pos_fname = 69 | match getLast (String.split_on_char '/' fp) with None -> "" | Some s -> s 70 | in 71 | lexbuf.lex_curr_p <- {lexbuf.lex_curr_p with pos_fname} ; 72 | try 73 | let stylesheet = Parser.stylesheet Lexer.css lexbuf in 74 | let comments = Lexer.comments () in 75 | let _ = Lexer.clearComments () in 76 | (* List.iter (fun (c,pos) -> print_endline (c ^ (show_position pos))) comments; *) 77 | List.fold_left insertComment stylesheet comments 78 | with exn -> 79 | let _ = Lexer.clearComments () in 80 | let curr = lexbuf.Lexing.lex_curr_p in 81 | let line = curr.Lexing.pos_lnum in 82 | let cnum = curr.Lexing.pos_cnum - curr.Lexing.pos_bol + 1 in 83 | let tok = Lexing.lexeme lexbuf in 84 | print_endline 85 | (Printf.sprintf "Error at location (%d:%d) with token: %s!" line cnum tok) ; 86 | raise exn 87 | -------------------------------------------------------------------------------- /lib/Lexer.mll: -------------------------------------------------------------------------------- 1 | { 2 | open Parser 3 | open Types 4 | 5 | (* 6 | * Some code taken from Real World OCaml v2: https://dev.realworldocaml.org/parsing-with-ocamllex-and-menhir.html 7 | *) 8 | exception SyntaxError of string 9 | 10 | let next_line (lexbuf:Lexing.lexbuf) = 11 | let pos = lexbuf.lex_curr_p in 12 | lexbuf.lex_curr_p <- 13 | { pos with pos_bol = lexbuf.lex_curr_pos; 14 | pos_lnum = pos.pos_lnum + 1; 15 | } 16 | ;; 17 | 18 | let tail1 s = String.sub s 1 ((String.length s) - 1) 19 | and head1 s = String.sub s 0 ((String.length s) - 1) 20 | and inner s = String.sub s 1 ((String.length s) - 2) 21 | 22 | let split_dimension s = 23 | let div = ref 0 24 | and len = String.length s 25 | and num = (function '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '+' | '-' | '.' -> true | _ -> false) in 26 | while !div < len && num s.[!div] do 27 | incr div 28 | done; 29 | DIMENSION(float_of_string(String.sub s 0 !div), String.sub s !div (len - !div)) 30 | 31 | (* Save space by sharing copies of the most common kewords. *) 32 | (* XXX CSS unquoted words are case-insensitive. XML element names are 33 | case-sensitive. Is String.lowercase_ascii inappropriate for XML? *) 34 | let intern s = 35 | (match String.lowercase_ascii s with "none" -> "none" | "normal" -> "normal" | s -> s) 36 | 37 | let doident lexeme = 38 | let l = String.length lexeme in 39 | if lexeme.[l-1] = '(' then FUNCTION(head1 lexeme) else IDENT(intern lexeme) 40 | 41 | let donumber lexeme = 42 | let l = String.length lexeme in 43 | if lexeme.[l-1] = '%' then 44 | PERCENTAGE(float_of_string(String.sub lexeme 0 (l-1))) 45 | else 46 | NUMBER(lexeme) 47 | 48 | let atkeyword lexeme = ATKEYWORD(String.lowercase_ascii(tail1 lexeme));; 49 | 50 | (* remove first two characters as well as the last two *) 51 | let extractComment (comment:string) : string = 52 | String.sub comment 2 ((String.length comment) - 4);; 53 | 54 | 55 | let last_comments = ref [];; 56 | let comments () = List.rev !last_comments;; 57 | let clearComments () = last_comments := [];; 58 | 59 | let decode_unicode s = 60 | let l = String.length s in 61 | let rv = int_of_string ("0x" ^ (String.sub s 1 (l-1))) in 62 | assert (rv >= 0 && rv < 256); 63 | char_of_int rv 64 | 65 | (* XXX Skip whitespace? *) 66 | let uri lexeme = 67 | let l = String.length lexeme in 68 | URI(String.sub lexeme 4 (l-5)) 69 | } 70 | let hex = [ '0' - '9' 'a' - 'f' ] 71 | let HEX = [ '0' - '9' 'A' - 'F' ] 72 | (* Technically only 1-6 hex characters are allowed but the lex tables 73 | are much smaller when + is used. *) 74 | let css_unicode = '\\' (HEX | hex)+ ['\n' '\r' '\t' '\012']? 75 | let css_escape = css_unicode | '\\' [ ' ' - '~' ] (* \200-\4177777 omitted *) 76 | let css_nonascii = [^ '\000' - '\255' ] 77 | let css_nmstart = ['a' - 'z' 'A' - 'Z' ] | css_nonascii | css_unicode 78 | let css_nmchar = ['a' - 'z' 'A' - 'Z' '0' - '9' '-' ] | css_nonascii | css_unicode 79 | (* let css_name = css_nmchar + *) 80 | let css_ident = css_nmstart css_nmchar* 81 | let css_num = ['0' - '9']+ | ['0' - '9']* '.' ['0' - '9']+ 82 | let css_nl = '\n' | '\r' '\n' | '\r' | '\012' 83 | let css_string1 = '\034' ( [ '\t' ' ' '!' '#' '$' '%' '&' '(' - '~' ] | '\\' css_nl | '\'' | css_nonascii | css_escape )* '\034' 84 | let css_string2 = '\039' ( [ '\t' ' ' '!' '#' '$' '%' '&' '(' - '~' ] | '\\' css_nl | '\034' | css_nonascii | css_escape )* '\039' 85 | let css_string = css_string1 | css_string2 86 | let css_w = [' ' '\t' '\r' '\012']* 87 | let css_s = [' ' '\t' '\r' '\012']+ 88 | let css_comment = '/' '*' [^ '*']* '*'+ ([^'/''*'][^'*']* '*'+)* '/' 89 | let css_number = ['+' '-']? css_num '%'? 90 | let css_dimension = ['+' '-']? css_num css_ident 91 | 92 | (* The CSS spec defines a grammar which allows just about any combination 93 | of tokens and advises against using the real grammar in appendix D. *) 94 | 95 | rule css = 96 | parse 97 | (* Skip over CDO, CDC, and comments. *) 98 | | "" | css_comment { 99 | let commentText = extractComment (Lexing.lexeme lexbuf) in 100 | let newComment = {value=commentText; pos=(Lexing.lexeme_start_p lexbuf, Lexing.lexeme_end_p lexbuf);} in 101 | last_comments := newComment :: !last_comments; 102 | css lexbuf 103 | } 104 | (*| '#' css_ident { HASH(tail1(Lexing.lexeme lexbuf)) }*) 105 | | '@' css_ident { atkeyword(Lexing.lexeme lexbuf) } 106 | | css_ident '(' [^')']* ')' { FUNCTION(Lexing.lexeme(lexbuf)) } 107 | | css_ident '('? { doident (Lexing.lexeme lexbuf) } 108 | (* XXX String processing: strip quotes; delete backslash-newline *) 109 | | css_string { 110 | (* store which character was used to create the string. either '' or "" *) 111 | let str = Lexing.lexeme lexbuf in 112 | STRING(str.[0], inner(str)) 113 | } 114 | | css_nl { next_line lexbuf; css lexbuf } 115 | | css_number { donumber(Lexing.lexeme lexbuf) } 116 | | css_dimension css_num css_ident { split_dimension(Lexing.lexeme lexbuf) } 117 | (* XXX Should be case-insentive? *) 118 | | "url(" css_w css_ident css_w ')' 119 | | "url(" css_w (['!' '#' '$' '%' '&' - '~' ] | css_nonascii | css_escape)* css_w ')' { uri(Lexing.lexeme lexbuf) } 120 | | "U+" (HEX | '?')+ ('-' HEX+)? { UNICODE(0,0) } 121 | | '*' { STAR } 122 | | '.' { DOT } 123 | | '#' { HASH } 124 | | ',' { COMMA } 125 | | '+' { PLUS } 126 | | '-' { MINUS } 127 | | '[' { LSQUARE } 128 | | ']' { RSQUARE } 129 | | ':' { COLON } 130 | | '>' { CHILD } 131 | | '{' { LBRACE } 132 | | '}' { RBRACE } 133 | | ';' { SEMICOLON } 134 | (* | '=' { EQUALS } *) 135 | | '(' { LPAREN } 136 | | ')' { RPAREN } 137 | | '/' { SLASH } 138 | (* Only in font: line-height declaration *) 139 | | '!' { EXCLAMATION } 140 | (* Only followed by "important" *) 141 | (* XXX Parentheses? *) 142 | | css_s { S } 143 | | "~=" { CONTAINS } 144 | | "|=" { PREFIX } 145 | | eof { EOF } 146 | | _ { DELIM(Lexing.lexeme lexbuf) } 147 | -------------------------------------------------------------------------------- /lib/Parser.mly: -------------------------------------------------------------------------------- 1 | %{ 2 | open Types 3 | %} 4 | 5 | %token EOF 6 | %token RPAREN LPAREN EQUALS SEMICOLON CHILD COLOR RSQUARE LSQUARE DOT STAR EXCLAMATION CONTAINS 7 | %token S CDO CDC INCLUDES DASHMATCH LBRACE RBRACE PLUS MINUS GREATER COMMA COLON PREFIX HASH 8 | %token SLASH ATIMPORT ATCHARSET ATMEDIA ATPAGE ATFONTFACE ATNAMESPACE 9 | %token IDENT NUMBER URI CHAR FUNCTION ATKEYWORD COMMENT DELIM 10 | %token STRING /* character representing either " or '*/ 11 | %token UNICODE 12 | %token PERCENTAGE 13 | %token DIMENSION 14 | %token ERROR 15 | %token INVALID 16 | %token IMPORT_SYM IMPORTANT_SYM PAGE_SYM MEDIA_SYM CHARSET_SYM 17 | 18 | %start stylesheet /* the entry point */ 19 | %% 20 | 21 | /* stylesheet */ 22 | stylesheet: 23 | | S* r=rule* S* EOF { r } 24 | ; 25 | 26 | rule: 27 | | at_rule { $1 } 28 | | style_rule { $1 } 29 | 30 | at_rule: 31 | | k=ATKEYWORD S* prefix=at_rule_prelude S* semi=SEMICOLON? S* { 32 | AtRule({ 33 | name = k; 34 | prelude = prefix; 35 | block = None; (*Some {token=Brace; value=[]; pos=$loc}; *) 36 | pos = ($startpos(k), $endpos(semi)); 37 | }) 38 | }; 39 | 40 | style_rule: 41 | | s=selectors LBRACE S* r=declaration_w* e=RBRACE { 42 | StyleRule({ 43 | prelude=s; 44 | declarations=r; 45 | pos= ($startpos(s), $endpos(e)); 46 | }) 47 | } 48 | ; 49 | 50 | selectors: 51 | | s=selector S* { [s] } 52 | | s=selector S* COMMA S* cvs=selectors { s :: cvs } 53 | 54 | selector: 55 | | cv=component_value_wplus+ { Simple cv } 56 | | cv=component_value+ { Compound cv } 57 | | s1=selector s2=selector { Complex( [s1; s2]) } 58 | 59 | at_rule_prelude: 60 | | cv=component_value_w* { [cv] } 61 | | cv1 = component_value_w* S* COMMA S* cvs=at_rule_prelude { cv1 :: cvs } 62 | ;; 63 | 64 | declaration_w: d=declaration S* { d }; 65 | declaration: 66 | | star=STAR? p=IDENT S* COLON S* t=component_value_w+ e=SEMICOLON? { 67 | let prefix = match star with None -> "" | Some _ -> "*" in 68 | Else({ 69 | name = prefix ^ p; 70 | value = t; 71 | important = false; 72 | pos=($startpos(star), $startpos(e)); 73 | }) 74 | } 75 | ; 76 | 77 | block: 78 | | LPAREN value=component_value_w* RPAREN { 79 | Block({token=Paren; value; pos=$loc}) 80 | } 81 | | LSQUARE value=component_value_w* RSQUARE { 82 | Block({token=SquareBracket; value; pos=$loc}) 83 | } 84 | 85 | component_value_wplus: cv=component_value S+ { cv }; 86 | component_value_w: cv=component_value S* { cv }; 87 | component_value: 88 | | IDENT { Ident $1 } 89 | | FUNCTION { Func $1 } 90 | | DOT IDENT { Ident( "." ^ $2) } 91 | | HASH IDENT { Hash($2) } 92 | | NUMBER { Number $1 } 93 | | DELIM { Delim $1 } 94 | | COLON { Delim ":" } 95 | | DIMENSION { match $1 with (f,u) -> Dimension(f,u) } 96 | | PERCENTAGE { Percentage $1 } 97 | | STRING { match $1 with (c, s) -> String(c,s) } 98 | | URI { Uri $1 } 99 | | block { $1 } 100 | (* | UnicodeRange of string *) 101 | ; 102 | 103 | (*selectors: 104 | | s=selector S* { [ s ] } 105 | | s=selector S* COMMA S* ss=selectors { s :: ss } 106 | ; 107 | 108 | (* simple_selector [ combinator selector | S+ [ combinator? selector ]? ]? *) 109 | selector: 110 | | simple_selector { $1 } 111 | | s1=selector S s2=simple_selector { s1 ^ " " ^ s2 } 112 | | s1=selector s2=simple_selector { s1 ^ s2 } 113 | | s=simple_selector LSQUARE S* ident=IDENT EQUALS str=STRING S* RSQUARE { s ^ "[" ^ ident ^ "=" ^ "'" ^ str ^ "'" ^ "]" } 114 | ; 115 | 116 | simple_selector: 117 | | element_name { $1 } 118 | | dotclass { $1 } 119 | | hash { $1 } 120 | *) -------------------------------------------------------------------------------- /lib/Printer.ml: -------------------------------------------------------------------------------- 1 | open Types 2 | 3 | let hasFractionalPart (num : float) : bool = 4 | match modf num with 0., _ -> false | _ -> true 5 | 6 | let printOpenerBlockType = function 7 | | Paren -> "(" 8 | | SquareBracket -> "[" 9 | | Brace -> "{" 10 | 11 | let printClosingBlockType = function 12 | | Paren -> ")" 13 | | SquareBracket -> "]" 14 | | Brace -> "}" 15 | 16 | type blockType = Paren | SquareBracket | Brace [@@deriving yojson, show] 17 | 18 | let rec print_cvalue ?(wrap = true) = function 19 | | String (ch, str) -> if wrap then Printf.sprintf "%c%s%c" ch str ch else str 20 | | Ident str -> str 21 | | Number i -> i 22 | | Percentage i -> 23 | if hasFractionalPart i then string_of_float i ^ "%" 24 | else string_of_int (int_of_float i) ^ "%" 25 | | Uri str -> "url(" ^ str ^ ")" 26 | | HexColor str -> str 27 | | Delim d -> d 28 | | Dimension (num, str) -> Printf.sprintf "%f%s" num str 29 | | Func f -> f 30 | | UnicodeRange str -> str 31 | | Hash str -> "#" ^ str 32 | | Block {token; value; pos} -> 33 | String.concat "" 34 | [ printOpenerBlockType token 35 | ; List.fold_left (fun a b -> a ^ print_cvalue b) "" value 36 | ; printClosingBlockType token ] 37 | 38 | (*Gross hack here rn. Basically almost all cvalues get pprinted by space separators except for blocks which 39 | should not be separated. So if we are at last elem we know no space, and also if we know next elem isblock then no space 40 | *) 41 | let rec printCValueList ?(sep = " ") (expressions : component_value list) = 42 | let isNotParens (token : Types.blockType) = 43 | match token with Paren -> false | _ -> true 44 | in 45 | let isBlock = function Block {token} -> isNotParens token | _ -> false in 46 | let isDelim = function Delim _ -> true | _ -> false in 47 | let next i lst = List.nth lst (i + 1) in 48 | expressions 49 | |> List.mapi (fun (i : int) (cval : component_value) -> 50 | print_cvalue cval 51 | ^ 52 | if 53 | i + 1 >= List.length expressions 54 | || isBlock (next i expressions) 55 | || isDelim (next i expressions) 56 | || isDelim cval 57 | then "" 58 | else sep ) 59 | |> String.concat "" 60 | 61 | let print_comment ({value} : comment) = "/*" ^ value ^ "*/" 62 | 63 | let print_declaration ({name; value; important} : declaration) = 64 | Printf.sprintf " %s: %s%s;" name (printCValueList value) 65 | (if important then " !important" else "") 66 | 67 | let print_declarations (rules : declaration orComment list) = 68 | let printDeclarationOrComment (term : declaration orComment) = 69 | match term with 70 | | Comment c -> " " ^ print_comment c 71 | | Else declaration -> print_declaration declaration 72 | in 73 | rules |> List.map printDeclarationOrComment |> String.concat "\n" 74 | 75 | let printAtRule {name; prelude; block} = 76 | let preludeStr = prelude |> List.map printCValueList |> String.concat ", " in 77 | Printf.sprintf "@%s %s;" name preludeStr 78 | 79 | let rec printSelector = function 80 | | Simple selector -> printCValueList selector 81 | | Compound selector -> printCValueList ~sep:"" selector 82 | | Complex selectors -> 83 | selectors |> List.map printSelector |> String.concat " " 84 | 85 | let printSelectors selectors = 86 | selectors |> List.map printSelector |> String.concat ",\n" 87 | 88 | let printStyleRule {prelude : Types.selector list; declarations} = 89 | let selectors = printSelectors prelude in 90 | let declarationsS = print_declarations declarations in 91 | Printf.sprintf "%s {\n%s\n}" selectors declarationsS 92 | 93 | let printRule (rule : rule) = 94 | match rule with 95 | | Comment c -> print_comment c 96 | | AtRule r -> printAtRule r 97 | | StyleRule r -> printStyleRule r 98 | 99 | let optToStr = function None -> "" | Some s -> "" ^ s ^ " " 100 | 101 | let positionToJson (position : position) : Yojson.Safe.json = 102 | match position with start, finish -> 103 | `Assoc 104 | [ ( "start" 105 | , `Assoc 106 | [ ("line", `Int start.pos_lnum) 107 | ; ( "column" 108 | , `Int (start.Lexing.pos_cnum - start.Lexing.pos_bol + 1) ) ] ) 109 | ; ( "end" 110 | , `Assoc 111 | [ ("line", `Int finish.pos_lnum) 112 | ; ( "column" 113 | , `Int (finish.Lexing.pos_cnum - finish.Lexing.pos_bol + 1) ) ] 114 | ) 115 | ; ("source", `String start.pos_fname) ] 116 | 117 | let termsToJson (value : component_value list) = 118 | match value with _ -> 119 | `String (String.concat " " (List.map print_cvalue value)) 120 | 121 | let commentToJson ({value; pos} : comment) = 122 | `Assoc 123 | [ ("type", `String "comment") 124 | ; ("comment", `String value) 125 | ; ("position", positionToJson pos) ] 126 | 127 | let ruleToJson (rule : declaration orComment) : Yojson.Safe.json = 128 | match rule with 129 | | Comment c -> commentToJson c 130 | | Else {name; value; pos} -> 131 | `Assoc 132 | [ ("type", `String "declaration") 133 | ; ("property", `String name) 134 | ; ("value", termsToJson value) 135 | ; ("position", positionToJson pos) ] 136 | 137 | let rulesetToJson (ruleset : rule) : Yojson.Safe.json = 138 | match ruleset with 139 | | Comment c -> commentToJson c 140 | | AtRule {name; prelude; block; pos} -> 141 | `Assoc 142 | [ ("type", `String name) 143 | ; ( name 144 | , `String (prelude |> List.map printCValueList |> String.concat ", ") 145 | ) 146 | ; ("position", positionToJson pos) ] 147 | | StyleRule {prelude; declarations; pos} -> 148 | `Assoc 149 | [ ("type", `String "rule") 150 | ; ( "selectors" 151 | , `List (List.map (fun s -> `String (printSelector s)) prelude) ) 152 | ; ("declarations", `List (List.map ruleToJson declarations)) 153 | ; ("position", positionToJson pos) ] 154 | 155 | let rec rulesetsToJson (stylesheet : stylesheet) : Yojson.Safe.json = 156 | match stylesheet with 157 | | [] -> `List [] 158 | | hd :: tl -> `List (rulesetToJson hd :: List.map rulesetToJson tl) 159 | 160 | let stylesheetToJson (stylesheet : stylesheet) : Yojson.Safe.json = 161 | `Assoc 162 | [ ("type", `String "stylesheet") 163 | ; ("stylesheet", `Assoc [("rules", rulesetsToJson stylesheet)]) ] 164 | 165 | let astPrint (rulesets : stylesheet) : string = 166 | Yojson.Safe.pretty_to_string ~std:true (stylesheetToJson rulesets) 167 | 168 | let prettyPrint (stylesheet : stylesheet) = 169 | ( stylesheet 170 | |> List.map (fun ruleset -> printRule ruleset) 171 | |> String.concat "\n\n" ) 172 | ^ "\n" 173 | -------------------------------------------------------------------------------- /lib/Types.ml: -------------------------------------------------------------------------------- 1 | type operator = NoOp | Slash | Comma [@@deriving show, yojson] 2 | 3 | type lexing_position = Lexing.position = 4 | {pos_fname: string; pos_lnum: int; pos_bol: int; pos_cnum: int} 5 | [@@deriving yojson, show] 6 | 7 | type position = lexing_position * lexing_position [@@deriving yojson, show] 8 | 9 | type blockType = Paren | SquareBracket | Brace [@@deriving yojson, show] 10 | 11 | type block = {token: blockType; value: component_value list; pos: position} 12 | [@@deriving yojson, show] 13 | 14 | and component_value = 15 | | Ident of string 16 | | Func of string 17 | | Hash of string 18 | | Number of string 19 | | Dimension of float * string 20 | | Delim of string 21 | | Percentage of float 22 | | UnicodeRange of string 23 | | String of char * string 24 | | Uri of string 25 | | HexColor of string 26 | | Block of block 27 | [@@deriving show, yojson] 28 | 29 | type atPrelude = component_value list list [@@deriving show, yojson] 30 | 31 | (* need to differentiate between single and complex because the components of 32 | compound are not space separated *) 33 | type selector = 34 | (* Simple or Combinator *) 35 | | Simple of component_value list 36 | | Compound of component_value list 37 | | Complex of selector list 38 | [@@deriving show, yojson] 39 | 40 | type comment = {value: string; pos: position} [@@deriving show, yojson] 41 | 42 | type 'a orComment = Comment of comment | Else of 'a [@@deriving show, yojson] 43 | 44 | type atrule = 45 | {name: string; prelude: atPrelude; block: block option; pos: position} 46 | [@@deriving show, yojson] 47 | 48 | type declaration = 49 | {name: string; value: component_value list; important: bool; pos: position} 50 | [@@deriving show, yojson] 51 | 52 | (* type declarationOrComment = 53 | | Comment of comment 54 | | Declaration of declaration [@@deriving show, yojson];; *) 55 | type styleRule = 56 | { prelude: selector list 57 | ; declarations: declaration orComment list 58 | ; pos: position } 59 | [@@deriving show, yojson] 60 | 61 | type rule = Comment of comment | StyleRule of styleRule | AtRule of atrule 62 | [@@deriving show, yojson] 63 | 64 | type stylesheet = rule list [@@deriving show, yojson] 65 | -------------------------------------------------------------------------------- /lib/dune: -------------------------------------------------------------------------------- 1 | (menhir 2 | (infer false) 3 | (modules Parser) 4 | (flags --explain --table --cmly) 5 | ) 6 | 7 | (library 8 | (name lib) 9 | (libraries yojson ppx_deriving_yojson.runtime menhirLib) 10 | (preprocess (pps ppx_deriving.show ppx_deriving_yojson)) 11 | (flags (-w -39)) 12 | ) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "css-parse", 3 | "version": "0.1.0", 4 | "description": "Parse CSS", 5 | "license": "MIT", 6 | "esy": { 7 | "build": [ 8 | "dune build", 9 | "dune build --profile release js_export/Index.bc.js" 10 | ], 11 | "install": [ 12 | "dune install --prefix=#{self.install}" 13 | ], 14 | "release": { 15 | "releasedBinaries": [ "css-parse", "index.js" ] 16 | }, 17 | "buildsInSource": "_build" 18 | }, 19 | "resolutions": { 20 | "**/@opam/lwt": "4.0.0", 21 | "**/@opam/lwt_log": "1.1.0" 22 | }, 23 | "dependencies": { 24 | "@esy-ocaml/esy-installer": "^0.0.0", 25 | "@opam/jbuilder": "*", 26 | "@opam/js_of_ocaml": "3.2.0", 27 | "@opam/js_of_ocaml-ppx": "3.2.0", 28 | "@opam/menhir": "20181113", 29 | "@opam/cmdliner": "1.0.2", 30 | "@opam/ppx_deriving": "^4.2.1", 31 | "@opam/ppx_deriving_yojson": "3.1", 32 | "@opam/ocamlformat": "0.8", 33 | "@opam/yojson": "1.4.0", 34 | "@opam/ounit": "2.0.8" 35 | }, 36 | "devDependencies": { 37 | "@opam/merlin": "^3.0.3", 38 | "ocaml": "~4.6.0" 39 | }, 40 | "scripts": { 41 | "test": "make test" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test/cases.test.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var cssParse = require('../_build/default/js_export/Index.bc.js'); 4 | var stringify = cssParse.print; 5 | var parse = cssParse.parse; 6 | 7 | var cases = fs.readdirSync(path.join(__dirname, 'cases')); 8 | 9 | let casesLimited = [ 10 | // "colon-space", 11 | // "comma-attribute", 12 | // "empty", 13 | // "no-semi", 14 | // "props", 15 | // "quote-escape", 16 | // "quoted", 17 | "rule", 18 | // "rules", 19 | // "selectors", 20 | ] 21 | 22 | casesLimited.forEach(function(name) { 23 | describe('cases/' + name, function() { 24 | var dir = path.join(__dirname, 'cases', name); 25 | var inputFile = path.join(dir, 'input.css'); 26 | var astFile = path.join(dir, 'ast.json'); 27 | var outputFile = path.join(dir, 'output.css'); 28 | var compressedFile = path.join(dir, 'compressed.css'); 29 | 30 | it('should match ast.json', function() { 31 | var ast = parseInput(); 32 | ast.should.containDeep(JSON.parse(readFile(astFile))); 33 | }); 34 | 35 | it('should match output.css', function() { 36 | var output = stringify(parseInput()); 37 | output.should.equal(readFile(outputFile).trim()); 38 | }); 39 | 40 | function parseInput() { 41 | let fileContents = readFile(inputFile); 42 | return parse(fileContents); 43 | } 44 | }); 45 | }); 46 | 47 | function readFile(file) { 48 | var src = fs.readFileSync(file, 'utf8'); 49 | // normalize line endings 50 | src = src.replace(/\r\n/, '\n'); 51 | // remove trailing newline 52 | src = src.replace(/\n$/, ''); 53 | 54 | return src; 55 | } 56 | -------------------------------------------------------------------------------- /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/at-namespace/compressed.css: -------------------------------------------------------------------------------- 1 | @namespace svg "http://www.w3.org/2000/svg"; 2 | -------------------------------------------------------------------------------- /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/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/charset-linebreak/compressed.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; -------------------------------------------------------------------------------- /test/cases/charset-linebreak/input.css: -------------------------------------------------------------------------------- 1 | @charset 2 | "UTF-8" 3 | ; 4 | -------------------------------------------------------------------------------- /test/cases/charset-linebreak/output.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/cases/charset/compressed.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8";@charset 'iso-8859-15'; 2 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /test/cases/colon-space/compressed.css: -------------------------------------------------------------------------------- 1 | a{margin:auto;padding:0;} 2 | -------------------------------------------------------------------------------- /test/cases/colon-space/input.css: -------------------------------------------------------------------------------- 1 | a { 2 | margin : auto; 3 | padding : 0; 4 | } 5 | -------------------------------------------------------------------------------- /test/cases/colon-space/output.css: -------------------------------------------------------------------------------- 1 | a { 2 | margin: auto; 3 | padding: 0; 4 | } 5 | -------------------------------------------------------------------------------- /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/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/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/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/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/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/comment-in/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [ 5 | { 6 | "type": "rule", 7 | "selectors": [ "a" ], 8 | "declarations": [ 9 | { 10 | "type": "declaration", 11 | "property": "color", 12 | "value": "12 px", 13 | "position": { 14 | "start": { "line": 2, "column": 5 }, 15 | "end": { "line": 2, "column": 20 }, 16 | "source": "input.css" 17 | } 18 | }, 19 | { 20 | "type": "comment", 21 | "comment": "", 22 | "position": { 23 | "start": { "line": 2, "column": 10 }, 24 | "end": { "line": 2, "column": 14 }, 25 | "source": "input.css" 26 | } 27 | }, 28 | { 29 | "type": "declaration", 30 | "property": "padding", 31 | "value": "1 px 2 px 3 px", 32 | "position": { 33 | "start": { "line": 3, "column": 5 }, 34 | "end": { "line": 3, "column": 51 }, 35 | "source": "input.css" 36 | } 37 | }, 38 | { 39 | "type": "comment", 40 | "comment": "4815162342", 41 | "position": { 42 | "start": { "line": 3, "column": 12 }, 43 | "end": { "line": 3, "column": 26 }, 44 | "source": "input.css" 45 | } 46 | }, 47 | { 48 | "type": "comment", 49 | "comment": "", 50 | "position": { 51 | "start": { "line": 3, "column": 32 }, 52 | "end": { "line": 3, "column": 36 }, 53 | "source": "input.css" 54 | } 55 | }, 56 | { 57 | "type": "comment", 58 | "comment": "13", 59 | "position": { 60 | "start": { "line": 3, "column": 41 }, 61 | "end": { "line": 3, "column": 47 }, 62 | "source": "input.css" 63 | } 64 | }, 65 | { 66 | "type": "declaration", 67 | "property": "border", 68 | "value": "solid", 69 | "position": { 70 | "start": { "line": 4, "column": 5 }, 71 | "end": { "line": 4, "column": 24 }, 72 | "source": "input.css" 73 | } 74 | }, 75 | { 76 | "type": "comment", 77 | "comment": "\\*", 78 | "position": { 79 | "start": { "line": 4, "column": 11 }, 80 | "end": { "line": 4, "column": 17 }, 81 | "source": "input.css" 82 | } 83 | }, 84 | { 85 | "type": "declaration", 86 | "property": "border-top", 87 | "value": "none\\9", 88 | "position": { 89 | "start": { "line": 4, "column": 26 }, 90 | "end": { "line": 4, "column": 50 }, 91 | "source": "input.css" 92 | } 93 | }, 94 | { 95 | "type": "comment", 96 | "comment": "\\*", 97 | "position": { 98 | "start": { "line": 4, "column": 36 }, 99 | "end": { "line": 4, "column": 42 }, 100 | "source": "input.css" 101 | } 102 | } 103 | ], 104 | "position": { 105 | "start": { "line": 1, "column": 1 }, 106 | "end": { "line": 5, "column": 2 }, 107 | "source": "input.css" 108 | } 109 | } 110 | ] 111 | } 112 | } -------------------------------------------------------------------------------- /test/cases/comment-in/compressed.css: -------------------------------------------------------------------------------- 1 | a{color:12px;padding:1px 2px 3px;border:solid;border-top:none\9;} 2 | -------------------------------------------------------------------------------- /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-in/output.css: -------------------------------------------------------------------------------- 1 | a { 2 | color: 12 px; 3 | /**/ 4 | padding: 1 px 2 px 3 px; 5 | /*4815162342*/ 6 | /**/ 7 | /*13*/ 8 | border: solid; 9 | /*\**/ 10 | border-top: none\9; 11 | /*\**/ 12 | } 13 | -------------------------------------------------------------------------------- /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/cases/comment-url/compressed.css: -------------------------------------------------------------------------------- 1 | foo{bar:baz;} 2 | -------------------------------------------------------------------------------- /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/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/comment/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [ 5 | { 6 | "type": "comment", 7 | "comment": " 1 ", 8 | "position": { 9 | "start": { "line": 1, "column": 1 }, 10 | "end": { "line": 1, "column": 8 }, 11 | "source": "input.css" 12 | } 13 | }, 14 | { 15 | "type": "rule", 16 | "selectors": [ "head", "body" ], 17 | "declarations": [ 18 | { 19 | "type": "comment", 20 | "comment": " footer, ", 21 | "position": { 22 | "start": { "line": 3, "column": 7 }, 23 | "end": { "line": 3, "column": 20 }, 24 | "source": "input.css" 25 | } 26 | }, 27 | { 28 | "type": "comment", 29 | "comment": ", nav ", 30 | "position": { 31 | "start": { "line": 3, "column": 24 }, 32 | "end": { "line": 3, "column": 34 }, 33 | "source": "input.css" 34 | } 35 | }, 36 | { 37 | "type": "comment", 38 | "comment": " 2 ", 39 | "position": { 40 | "start": { "line": 3, "column": 37 }, 41 | "end": { "line": 3, "column": 44 }, 42 | "source": "input.css" 43 | } 44 | }, 45 | { 46 | "type": "comment", 47 | "comment": " 3 ", 48 | "position": { 49 | "start": { "line": 4, "column": 3 }, 50 | "end": { "line": 4, "column": 10 }, 51 | "source": "input.css" 52 | } 53 | }, 54 | { 55 | "type": "comment", 56 | "comment": "", 57 | "position": { 58 | "start": { "line": 5, "column": 3 }, 59 | "end": { "line": 5, "column": 7 }, 60 | "source": "input.css" 61 | } 62 | }, 63 | { 64 | "type": "declaration", 65 | "property": "foo", 66 | "value": "'bar'", 67 | "position": { 68 | "start": { "line": 5, "column": 3 }, 69 | "end": { "line": 5, "column": 17 }, 70 | "source": "input.css" 71 | } 72 | }, 73 | { 74 | "type": "comment", 75 | "comment": " 4 ", 76 | "position": { 77 | "start": { "line": 6, "column": 3 }, 78 | "end": { "line": 6, "column": 10 }, 79 | "source": "input.css" 80 | } 81 | } 82 | ], 83 | "position": { 84 | "start": { "line": 3, "column": 1 }, 85 | "end": { "line": 7, "column": 2 }, 86 | "source": "input.css" 87 | } 88 | }, 89 | { 90 | "type": "comment", 91 | "comment": " 5 ", 92 | "position": { 93 | "start": { "line": 7, "column": 3 }, 94 | "end": { "line": 7, "column": 10 }, 95 | "source": "input.css" 96 | } 97 | }, 98 | { 99 | "type": "comment", 100 | "comment": " 6 ", 101 | "position": { 102 | "start": { "line": 9, "column": 1 }, 103 | "end": { "line": 9, "column": 8 }, 104 | "source": "input.css" 105 | } 106 | } 107 | ] 108 | } 109 | } 110 | 111 | -------------------------------------------------------------------------------- /test/cases/comment/compressed.css: -------------------------------------------------------------------------------- 1 | head,body{foo:'bar';} 2 | -------------------------------------------------------------------------------- /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/comment/output.css: -------------------------------------------------------------------------------- 1 | /* 1 */ 2 | 3 | head, 4 | body { 5 | /* footer, */ 6 | /*, nav */ 7 | /* 2 */ 8 | /* 3 */ 9 | /**/ 10 | foo: 'bar'; 11 | /* 4 */ 12 | } 13 | 14 | /* 5 */ 15 | 16 | /* 6 */ 17 | -------------------------------------------------------------------------------- /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/custom-media-linebreak/compressed.css: -------------------------------------------------------------------------------- 1 | @custom-media --test (min-width: 200px); -------------------------------------------------------------------------------- /test/cases/custom-media-linebreak/input.css: -------------------------------------------------------------------------------- 1 | @custom-media 2 | --test 3 | (min-width: 200px) 4 | ; 5 | -------------------------------------------------------------------------------- /test/cases/custom-media-linebreak/output.css: -------------------------------------------------------------------------------- 1 | @custom-media --test (min-width: 200px); -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/document-linebreak/compressed.css: -------------------------------------------------------------------------------- 1 | @document url-prefix(){.test{color:blue;}} -------------------------------------------------------------------------------- /test/cases/document-linebreak/input.css: -------------------------------------------------------------------------------- 1 | @document 2 | url-prefix() 3 | { 4 | 5 | .test { 6 | color: blue; 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /test/cases/document-linebreak/output.css: -------------------------------------------------------------------------------- 1 | @document url-prefix() { 2 | .test { 3 | color: blue; 4 | } 5 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/cases/document/compressed.css: -------------------------------------------------------------------------------- 1 | @-moz-document url-prefix(){.ui-select .ui-btn select{opacity:.0001;}.icon-spin{height:.9em;}} 2 | -------------------------------------------------------------------------------- /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/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/cases/empty/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/cases/empty/compressed.css: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/cases/empty/input.css: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/cases/empty/output.css: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /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/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/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/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/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/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/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/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/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/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/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/hose-linebreak/compressed.css: -------------------------------------------------------------------------------- 1 | @host{:scope{color:white;}} -------------------------------------------------------------------------------- /test/cases/hose-linebreak/input.css: -------------------------------------------------------------------------------- 1 | @host 2 | { 3 | :scope { color: white; } 4 | } 5 | -------------------------------------------------------------------------------- /test/cases/hose-linebreak/output.css: -------------------------------------------------------------------------------- 1 | @host { 2 | :scope { 3 | color: white; 4 | } 5 | } -------------------------------------------------------------------------------- /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/host/compressed.css: -------------------------------------------------------------------------------- 1 | @host{:scope{display:block;}} 2 | -------------------------------------------------------------------------------- /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/import-linebreak/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [ 5 | { 6 | "type": "import", 7 | "import": "url(test.css) 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/import-linebreak/compressed.css: -------------------------------------------------------------------------------- 1 | @import url(test.css) 2 | screen; -------------------------------------------------------------------------------- /test/cases/import-linebreak/input.css: -------------------------------------------------------------------------------- 1 | @import 2 | url(test.css) 3 | screen 4 | ; 5 | -------------------------------------------------------------------------------- /test/cases/import-linebreak/output.css: -------------------------------------------------------------------------------- 1 | @import url(test.css) screen; 2 | -------------------------------------------------------------------------------- /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/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-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-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/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/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/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/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/keyframes-advanced/compressed.css: -------------------------------------------------------------------------------- 1 | @keyframes advanced{top{opacity[sqrt]:0;}100{opacity:0.5;}bottom{opacity:1;}} 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/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 | -------------------------------------------------------------------------------- /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/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/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/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-linebreak/compressed.css: -------------------------------------------------------------------------------- 1 | @keyframes test{from{opacity:1;}to{opacity:0;}} -------------------------------------------------------------------------------- /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/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-messed/compressed.css: -------------------------------------------------------------------------------- 1 | @keyframes fade{from{opacity:0;}to{opacity:1;}} 2 | -------------------------------------------------------------------------------- /test/cases/keyframes-messed/input.css: -------------------------------------------------------------------------------- 1 | @keyframes fade {from 2 | {opacity: 0; 3 | } 4 | to 5 | { 6 | opacity: 1;}} 7 | -------------------------------------------------------------------------------- /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/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/keyframes-vendor/compressed.css: -------------------------------------------------------------------------------- 1 | @-webkit-keyframes fade{from{opacity:0;}to{opacity:1;}} 2 | -------------------------------------------------------------------------------- /test/cases/keyframes-vendor/input.css: -------------------------------------------------------------------------------- 1 | @-webkit-keyframes fade { 2 | from { opacity: 0 } 3 | to { opacity: 1 } 4 | } 5 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /test/cases/keyframes/compressed.css: -------------------------------------------------------------------------------- 1 | @keyframes fade{from{opacity:0;}to{opacity:1;}} 2 | -------------------------------------------------------------------------------- /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/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/media-linebreak/compressed.css: -------------------------------------------------------------------------------- 1 | @media ( 2 | min-width: 300px 3 | ){.test{width:100px;}} -------------------------------------------------------------------------------- /test/cases/media-linebreak/input.css: -------------------------------------------------------------------------------- 1 | @media 2 | 3 | ( 4 | min-width: 300px 5 | ) 6 | { 7 | .test { width: 100px; } 8 | } 9 | -------------------------------------------------------------------------------- /test/cases/media-linebreak/output.css: -------------------------------------------------------------------------------- 1 | @media ( 2 | min-width: 300px 3 | ) { 4 | .test { 5 | width: 100px; 6 | } 7 | } -------------------------------------------------------------------------------- /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-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/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/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/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 | -------------------------------------------------------------------------------- /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/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/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/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/messed-up/compressed.css: -------------------------------------------------------------------------------- 1 | body{foo:'bar';}body{foo:bar;bar:baz;}body{foo:bar;bar:baz;} 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/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/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/namespace-linebreak/compressed.css: -------------------------------------------------------------------------------- 1 | @namespace "http://www.w3.org/1999/xhtml"; -------------------------------------------------------------------------------- /test/cases/namespace-linebreak/input.css: -------------------------------------------------------------------------------- 1 | @namespace 2 | "http://www.w3.org/1999/xhtml" 3 | ; 4 | -------------------------------------------------------------------------------- /test/cases/namespace-linebreak/output.css: -------------------------------------------------------------------------------- 1 | @namespace "http://www.w3.org/1999/xhtml"; 2 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/namespace/output.css: -------------------------------------------------------------------------------- 1 | @namespace "http://www.w3.org/1999/xhtml"; 2 | 3 | @namespace svg "http://www.w3.org/2000/svg"; 4 | -------------------------------------------------------------------------------- /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": 4, 38 | "column": 32 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/no-semi/compressed.css: -------------------------------------------------------------------------------- 1 | tobi loki jane{are:'all';the-species:called "ferrets";} 2 | -------------------------------------------------------------------------------- /test/cases/no-semi/input.css: -------------------------------------------------------------------------------- 1 | 2 | tobi loki jane { 3 | are: 'all'; 4 | the-species: called "ferrets" 5 | } 6 | -------------------------------------------------------------------------------- /test/cases/no-semi/output.css: -------------------------------------------------------------------------------- 1 | tobi loki jane { 2 | are: 'all'; 3 | the-species: called "ferrets"; 4 | } 5 | -------------------------------------------------------------------------------- /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/page-linebreak/compressed.css: -------------------------------------------------------------------------------- 1 | @page toc{color:black;} -------------------------------------------------------------------------------- /test/cases/page-linebreak/input.css: -------------------------------------------------------------------------------- 1 | @page 2 | toc 3 | { 4 | color: black; 5 | } 6 | -------------------------------------------------------------------------------- /test/cases/page-linebreak/output.css: -------------------------------------------------------------------------------- 1 | @page toc { 2 | color: black; 3 | } -------------------------------------------------------------------------------- /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/paged-media/compressed.css: -------------------------------------------------------------------------------- 1 | @page toc, index:blank{color:green;}@page {font-size:16pt;}@page :left{margin-left:5cm;} 2 | -------------------------------------------------------------------------------- /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/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/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/props/compressed.css: -------------------------------------------------------------------------------- 1 | tobi loki jane{are:'all';the-species:called "ferrets";*even:'ie crap';} 2 | -------------------------------------------------------------------------------- /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/props/output.css: -------------------------------------------------------------------------------- 1 | tobi loki jane { 2 | are: 'all'; 3 | the-species: called "ferrets"; 4 | *even: 'ie crap'; 5 | } 6 | -------------------------------------------------------------------------------- /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 | } 42 | } -------------------------------------------------------------------------------- /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/quote-escape/output.css: -------------------------------------------------------------------------------- 1 | p[qwe="a\",b"] { 2 | color: red; 3 | } 4 | -------------------------------------------------------------------------------- /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/quoted/compressed.css: -------------------------------------------------------------------------------- 1 | body{background:url('some;stuff;here') 50% 50% no-repeat;} 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/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/rule/compressed.css: -------------------------------------------------------------------------------- 1 | foo{bar:'baz';} 2 | -------------------------------------------------------------------------------- /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/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/rules/compressed.css: -------------------------------------------------------------------------------- 1 | tobi{name:'tobi';age:2;}loki{name:'loki';age:1;} 2 | -------------------------------------------------------------------------------- /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/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/selector-compound/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [ 5 | { 6 | "type": "rule", 7 | "selectors": [ "foo.bar" ], 8 | "declarations": [ 9 | { 10 | "type": "declaration", 11 | "property": "color", 12 | "value": "'black'", 13 | "position": { 14 | "start": { "line": 2, "column": 3 }, 15 | "end": { "line": 2, "column": 17 }, 16 | "source": "input.css" 17 | } 18 | } 19 | ], 20 | "position": { 21 | "start": { "line": 1, "column": 1 }, 22 | "end": { "line": 3, "column": 2 }, 23 | "source": "input.css" 24 | } 25 | } 26 | ] 27 | } 28 | } -------------------------------------------------------------------------------- /test/cases/selector-compound/compressed.css: -------------------------------------------------------------------------------- 1 | foo,bar,baz{color:'black';} 2 | -------------------------------------------------------------------------------- /test/cases/selector-compound/input.css: -------------------------------------------------------------------------------- 1 | foo.bar { 2 | color: 'black'; 3 | } 4 | -------------------------------------------------------------------------------- /test/cases/selector-compound/output.css: -------------------------------------------------------------------------------- 1 | foo.bar { 2 | color: 'black'; 3 | } 4 | -------------------------------------------------------------------------------- /test/cases/selector-space/ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stylesheet", 3 | "stylesheet": { 4 | "rules": [ 5 | { 6 | "type": "rule", 7 | "selectors": [ "foo bar" ], 8 | "declarations": [ 9 | { 10 | "type": "declaration", 11 | "property": "color", 12 | "value": "'black'", 13 | "position": { 14 | "start": { "line": 2, "column": 3 }, 15 | "end": { "line": 2, "column": 17 }, 16 | "source": "input.css" 17 | } 18 | } 19 | ], 20 | "position": { 21 | "start": { "line": 1, "column": 1 }, 22 | "end": { "line": 3, "column": 2 }, 23 | "source": "input.css" 24 | } 25 | } 26 | ] 27 | } 28 | } -------------------------------------------------------------------------------- /test/cases/selector-space/compressed.css: -------------------------------------------------------------------------------- 1 | foo bar{color:'black';} 2 | -------------------------------------------------------------------------------- /test/cases/selector-space/input.css: -------------------------------------------------------------------------------- 1 | foo bar { 2 | color: 'black'; 3 | } 4 | -------------------------------------------------------------------------------- /test/cases/selector-space/output.css: -------------------------------------------------------------------------------- 1 | foo bar { 2 | color: 'black'; 3 | } 4 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/cases/selectors/compressed.css: -------------------------------------------------------------------------------- 1 | foo,bar,baz{color:'black';} 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/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/supports-linebreak/compressed.css: -------------------------------------------------------------------------------- 1 | @supports (display: flex){.test{display:flex;}} -------------------------------------------------------------------------------- /test/cases/supports-linebreak/input.css: -------------------------------------------------------------------------------- 1 | @supports 2 | (display: flex) 3 | { 4 | .test { display: flex; } 5 | } 6 | -------------------------------------------------------------------------------- /test/cases/supports-linebreak/output.css: -------------------------------------------------------------------------------- 1 | @supports (display: flex) { 2 | .test { 3 | display: flex; 4 | } 5 | } -------------------------------------------------------------------------------- /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/supports/compressed.css: -------------------------------------------------------------------------------- 1 | @supports (display: flex) or (display: box){.flex{display:box;display:flex;}div{something:else;}} 2 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /test/cases/wtf/compressed.css: -------------------------------------------------------------------------------- 1 | .wtf{*overflow-x:hidden;//max-height:110px;#height:18px;} 2 | -------------------------------------------------------------------------------- /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/dune: -------------------------------------------------------------------------------- 1 | (executable 2 | (name test) 3 | (public_name test) 4 | (libraries lib cmdliner yojson ounit) 5 | (preprocess (pps ppx_deriving.std ppx_deriving_yojson)) 6 | ) -------------------------------------------------------------------------------- /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/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/test.ml: -------------------------------------------------------------------------------- 1 | open Lib.Index 2 | open OUnit2 3 | 4 | let dir_contents dir = Sys.readdir dir |> Array.to_list 5 | 6 | let load_file f = 7 | let ic = open_in f in 8 | let n = in_channel_length ic in 9 | let s = Bytes.create n in 10 | really_input ic s 0 n ; close_in ic ; Bytes.to_string s 11 | 12 | let rec printlst = function 13 | | [] -> () 14 | | hd :: tl -> print_endline hd ; printlst tl 15 | 16 | let test_path = "./test/cases" 17 | 18 | let test_dirs = dir_contents test_path 19 | 20 | let get_test_for_dir dir = 21 | let filename = test_path ^ "/" ^ dir ^ "/input.css" in 22 | let inputStr = load_file filename in 23 | let expectedStr = load_file (test_path ^ "/" ^ dir ^ "/output.css") in 24 | let expectedAst = 25 | Yojson.Safe.prettify (load_file (test_path ^ "/" ^ dir ^ "/ast.json")) 26 | in 27 | dir 28 | >:: fun _ -> 29 | let actualAst = astPrint (parse ~fp:filename inputStr) in 30 | let actualStr = print (parse ~fp:filename inputStr) in 31 | assert_equal ~msg:"pretty-print" ~printer:(fun x -> x) expectedStr actualStr ; 32 | assert_equal ~msg:"ast-print" ~printer:(fun x -> x) expectedAst actualAst 33 | 34 | let suite = 35 | "suite" 36 | >::: List.map 37 | (fun dir -> get_test_for_dir dir) 38 | [ "at-namespace" 39 | ; "charset" 40 | ; "charset-linebreak" 41 | ; "colon-space" 42 | ; "comma-attribute" 43 | ; "comma-selector-function" 44 | ; "comment" 45 | ; "comment-in" 46 | ; "comment-url" 47 | (* ; "custom-media" 48 | ; "custom-media-linebreak" 49 | ; "document" 50 | ; "document-linebreak" *) 51 | ; "empty" 52 | (* ; "escapes" 53 | ; "font-face" 54 | ; "font-face-linebreak" 55 | ; "hose-linebreak" 56 | ; "host" *) 57 | ; "import" 58 | ; "import-linebreak" 59 | ; "import-messed" 60 | (* ; "keyframes" 61 | ; "keyframes-advanced" 62 | ; "keyframes-complex" 63 | ; "keyframes-linebreak" 64 | ; "keyframes-messed" 65 | ; "keyframes-vendor" 66 | ; "media" 67 | ; "media-linebreak" 68 | ; "media-messed" 69 | ; "messed-up" *) 70 | ; "namespace" 71 | ; "namespace-linebreak" 72 | ; "no-semi" 73 | (* ; "page-linebreak" 74 | ; "paged-media" *) 75 | ; "props" 76 | ; "quote-escape" 77 | ; "quoted" 78 | ; "rule" 79 | ; "rules" 80 | ; "selectors" 81 | ; "selector-space" 82 | ; "selector-compound" 83 | (* ; "supports" 84 | ; "supports-linebreak" 85 | ; "wtf" *) 86 | ] 87 | 88 | ;; 89 | run_test_tt_main suite 90 | --------------------------------------------------------------------------------