├── .babelrc ├── .editorconfig ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE-MIT ├── README.md ├── bin ├── cmd.js └── usage.txt ├── package.json └── src ├── __tests__ ├── api.js ├── cli.js ├── fixtures │ ├── block-comment.compact.css │ ├── block-comment.compressed.css │ ├── block-comment.expanded.css │ ├── block-comment.fixture.css │ ├── border-and-shadow.compact.css │ ├── border-and-shadow.compressed.css │ ├── border-and-shadow.expanded.css │ ├── border-and-shadow.fixture.css │ ├── border-radius.compact.css │ ├── border-radius.compressed.css │ ├── border-radius.expanded.css │ ├── border-radius.fixture.css │ ├── box-shadow.compact.css │ ├── box-shadow.compressed.css │ ├── box-shadow.expanded.css │ ├── box-shadow.fixture.css │ ├── braces-in-content.compact.css │ ├── braces-in-content.compressed.css │ ├── braces-in-content.expanded.css │ ├── braces-in-content.fixture.css │ ├── color-case.compact.css │ ├── color-case.compressed.css │ ├── color-case.expanded.css │ ├── color-case.fixture.css │ ├── color-shorthand.compact.css │ ├── color-shorthand.compressed.css │ ├── color-shorthand.expanded.css │ ├── color-shorthand.fixture.css │ ├── comment-between.compact.css │ ├── comment-between.compressed.css │ ├── comment-between.expanded.css │ ├── comment-between.fixture.css │ ├── comment-eol.compact.css │ ├── comment-eol.compressed.css │ ├── comment-eol.expanded.css │ ├── comment-eol.fixture.css │ ├── comment-in-selector.compact.css │ ├── comment-in-selector.compressed.css │ ├── comment-in-selector.expanded.css │ ├── comment-in-selector.fixture.css │ ├── comment-newline.compact.css │ ├── comment-newline.compressed.css │ ├── comment-newline.expanded.css │ ├── comment-newline.fixture.css │ ├── comments-in-values.compact.css │ ├── comments-in-values.compressed.css │ ├── comments-in-values.expanded.css │ ├── comments-in-values.fixture.css │ ├── ending-comment.compact.css │ ├── ending-comment.compressed.css │ ├── ending-comment.expanded.css │ ├── ending-comment.fixture.css │ ├── exclamation-value.compact.css │ ├── exclamation-value.compressed.css │ ├── exclamation-value.expanded.css │ ├── exclamation-value.fixture.css │ ├── flex-order.compact.css │ ├── flex-order.compressed.css │ ├── flex-order.expanded.css │ ├── flex-order.fixture.css │ ├── font-face.compact.css │ ├── font-face.compressed.css │ ├── font-face.expanded.css │ ├── font-face.fixture.css │ ├── handle-fractions.compact.css │ ├── handle-fractions.compressed.css │ ├── handle-fractions.expanded.css │ ├── handle-fractions.fixture.css │ ├── import.compact.css │ ├── import.compressed.css │ ├── import.expanded.css │ ├── import.fixture.css │ ├── large-media-query.compact.css │ ├── large-media-query.compressed.css │ ├── large-media-query.expanded.css │ ├── large-media-query.fixture.css │ ├── long-selector-media.compact.css │ ├── long-selector-media.compressed.css │ ├── long-selector-media.expanded.css │ ├── long-selector-media.fixture.css │ ├── long-selector.compact.css │ ├── long-selector.compressed.css │ ├── long-selector.expanded.css │ ├── long-selector.fixture.css │ ├── media-query.compact.css │ ├── media-query.compressed.css │ ├── media-query.expanded.css │ ├── media-query.fixture.css │ ├── medium-media.compact.css │ ├── medium-media.compressed.css │ ├── medium-media.expanded.css │ ├── medium-media.fixture.css │ ├── multiline-selectors.compact.css │ ├── multiline-selectors.compressed.css │ ├── multiline-selectors.expanded.css │ ├── multiline-selectors.fixture.css │ ├── nested-media.compact.css │ ├── nested-media.compressed.css │ ├── nested-media.expanded.css │ ├── nested-media.fixture.css │ ├── nested.compact.css │ ├── nested.compressed.css │ ├── nested.expanded.css │ ├── nested.fixture.css │ ├── quoted-image-url.compact.css │ ├── quoted-image-url.compressed.css │ ├── quoted-image-url.expanded.css │ ├── quoted-image-url.fixture.css │ ├── simple.compact.css │ ├── simple.compressed.css │ ├── simple.expanded.css │ ├── simple.fixture.css │ ├── trim-leading-zero.compact.css │ ├── trim-leading-zero.compressed.css │ ├── trim-leading-zero.expanded.css │ ├── trim-leading-zero.fixture.css │ ├── trim-trailing-zeros.compact.css │ ├── trim-trailing-zeros.compressed.css │ ├── trim-trailing-zeros.expanded.css │ ├── trim-trailing-zeros.fixture.css │ ├── zero-length-no-unit.compact.css │ ├── zero-length-no-unit.compressed.css │ ├── zero-length-no-unit.expanded.css │ └── zero-length-no-unit.fixture.css ├── index.js └── options.js ├── applyCompact.js ├── applyCompressed.js ├── applyExpanded.js ├── applyTransformFeatures.js ├── blank.js ├── deeplyNested.js ├── getIndent.js ├── index.js ├── isSassVariable.js ├── longest.js ├── maxSelectorLength.js ├── prefixedDecls.js ├── sameLine.js ├── space.js └── walk.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015-loose", "stage-0"], 3 | "plugins": ["add-module-exports"], 4 | "env": { 5 | "development": { 6 | "sourceMaps": "inline" 7 | }, 8 | "test": { 9 | "plugins": ["istanbul"] 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [**/__tests__/**/**.expected.css] 13 | insert_final_newline = false 14 | 15 | [**/__tests__/**/**.compressed.css] 16 | insert_final_newline = false 17 | 18 | [*.{json,yml}] 19 | indent_size = 2 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .nyc_output 2 | coverage 3 | node_modules 4 | npm-debug.log 5 | dist 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | matrix: 5 | include: 6 | - node_js: '5' 7 | - node_js: '4' 8 | - node_js: '0.12' 9 | env: NO_ESLINT=true 10 | 11 | script: "[[ $NO_ESLINT == true ]] && npm run test-012 || npm test" 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 2.4.0 2 | 3 | * Adds support for tab indentation (thanks to @solidflows). 4 | 5 | # 2.3.1 6 | 7 | * Better handling of trailing/leading zeroes & hex colour case transformations 8 | (thanks to @vansosnin). 9 | * Now parses values rather than applying regular expressions to values, using 10 | postcss-value-parser. 11 | * Resolves an issue where fractions were being incorrectly transformed. 12 | * Resolves an issue where parentheses inside string literals were being 13 | erroneously transformed. 14 | * Resolves an issue where base64 content was being incorrectly transformed 15 | to lower case. 16 | 17 | # 2.3.0 18 | 19 | * Add options to handle zeroes - trim leading/trailing zeroes, and removing 20 | units from zero lengths (thanks to @vansosnin). 21 | 22 | # 2.2.0 23 | 24 | * Add options to format hex colours (thanks to @vansosnin). 25 | 26 | # 2.1.4 27 | 28 | * Replaced internal vendor prefixes list with the vendors module. 29 | * Uses Babel's object assign. 30 | * Ensures that the expanded format is used when the option is set to undefined. 31 | 32 | # 2.1.3 33 | 34 | * Fixes an issue where extra space was being added to commas inside strings, 35 | mangling base64 URLs (thanks to @Mottie, @silverwind, @denji). 36 | 37 | # 2.1.2 38 | 39 | * Fixes an integration issue with PostCSS 5.0.6. 40 | 41 | # 2.1.1 42 | 43 | * Fixes a plugin integration issue where `undefined` was appearing in 44 | the output. 45 | 46 | # 2.1.0 47 | 48 | * Adds scss syntax support. 49 | * Better formatting of functions; `rgb(0,0,0)` becomes `rgba(0, 0, 0)` 50 | (thanks to @yisibl). 51 | 52 | # 2.0.0 53 | 54 | * Upgrade to PostCSS 5.x. 55 | 56 | # 1.4.1 57 | 58 | * Fixed an issue where perfectionist would add an extra space in between 59 | at rules with no child nodes (for example, `@import`). 60 | 61 | # 1.4.0 62 | 63 | * Added an option to disable the visual cascade of properties. 64 | * Fixed an issue where at-rules were not getting an appropriate amount of 65 | newlines following the rule. 66 | * Fixed an issue where comments in values were being removed. 67 | * Where possible, perfectionist will condense multi-line selectors into 68 | a single line. 69 | 70 | # 1.3.1 71 | 72 | * perfectionist will now not remove comments within selector strings. 73 | 74 | # 1.3.0 75 | 76 | * Better formatting of comments inside rules. 77 | * Better formatting of selectors inside at-rules. 78 | * Added an option to configure the indent size. 79 | 80 | # 1.2.2 81 | 82 | * Fixes a crash when a comment ended the file. 83 | 84 | # 1.2.1 85 | 86 | * Fixes an issue where comments were being removed from inside nodes. 87 | 88 | # 1.2.0 89 | 90 | * Adds support for configurable wrapping of property values over multiple lines. 91 | * Adds support for configurable wrapping of at-rule parameters. 92 | 93 | # 1.1.0 94 | 95 | * Adds support for newlines around block comments in both `expanded` and 96 | `compact` formats. 97 | 98 | # 1.0.2 99 | 100 | * Fixes a crash on comments inside rules. 101 | 102 | # 1.0.1 103 | 104 | * Fixes a behaviour where the module was trying to add negative space to a 105 | property when re-aligning. 106 | 107 | # 1.0.0 108 | 109 | * Initial release. 110 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) (http://beneb.info) 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # perfectionist [![Build Status](https://travis-ci.org/ben-eb/perfectionist.svg?branch=master)][ci] [![NPM version](https://badge.fury.io/js/perfectionist.svg)][npm] [![Dependency Status](https://gemnasium.com/ben-eb/perfectionist.svg)][deps] 2 | 3 | > Beautify CSS files. 4 | 5 | ## Install 6 | 7 | With [npm](https://npmjs.org/package/perfectionist) do: 8 | 9 | ``` 10 | npm install perfectionist --save 11 | ``` 12 | 13 | ## Example 14 | 15 | ### Input 16 | 17 | ```css 18 | h1 { 19 | color : red } 20 | ``` 21 | 22 | ### Expanded output 23 | 24 | ```css 25 | h1 { 26 | color: red; 27 | } 28 | ``` 29 | 30 | ### Compact output 31 | 32 | ```css 33 | h1 { color: red; } 34 | ``` 35 | 36 | ### Compressed output 37 | 38 | ```css 39 | h1{color:red} 40 | ``` 41 | 42 | ## API 43 | 44 | ### perfectionist.process(css, [options]) 45 | 46 | #### css 47 | 48 | Type: `string` 49 | *Required option.* 50 | 51 | Pass a CSS string to beautify it. 52 | 53 | #### options 54 | 55 | ##### cascade 56 | 57 | Type: `boolean` 58 | Default: `true` 59 | 60 | Set this to `false` to disable visual cascading of vendor prefixed properties. 61 | Note that this transform only applies to the `expanded` format. 62 | 63 | ```css 64 | /* true */ 65 | h1 { 66 | -webkit-border-radius: 12px; 67 | border-radius: 12px; 68 | } 69 | 70 | /* false */ 71 | h1 { 72 | -webkit-border-radius: 12px; 73 | border-radius: 12px; 74 | } 75 | ``` 76 | 77 | ##### colorCase 78 | 79 | Type: `string` 80 | Default: `lower` 81 | 82 | Set either `lower` or `upper` to transform hexadecimal colors to the according case. 83 | 84 | ```css 85 | /* upper */ 86 | p { color: #C8C8C8 } 87 | 88 | /* lower */ 89 | p { color: #c8c8c8 } 90 | ``` 91 | 92 | ##### colorShorthand 93 | 94 | Type: `boolean` 95 | Default: `true` 96 | 97 | Set this to `true` to shorten hexadecimal colors. 98 | 99 | ```css 100 | /* true */ 101 | p { color: #fff } 102 | 103 | /* false */ 104 | p { color: #ffffff } 105 | ``` 106 | 107 | ##### format 108 | 109 | Type: `string` 110 | Default: `expanded` 111 | 112 | Pass either `expanded`, `compact` or `compressed`. Note that the `compressed` 113 | format only facilitates simple whitespace compression around selectors & 114 | declarations. For more powerful compression, see [cssnano]. 115 | 116 | ##### indentChar 117 | 118 | Type: `string` 119 | Default: ` ` (space) 120 | 121 | Specify `\t` here instead if you would like to use tabs for indentation. 122 | 123 | ##### indentSize 124 | 125 | Type: `number` 126 | Default: `4` 127 | 128 | This number will be used as a basis for all indent levels, using the `expanded` 129 | format. 130 | 131 | ##### trimLeadingZero 132 | 133 | Type: `boolean` 134 | Default: `true` 135 | 136 | Set this to `true` to trim leading zero for fractional numbers less than 1. 137 | 138 | ```css 139 | /* true */ 140 | p { line-height: .8 } 141 | 142 | /* false */ 143 | p { line-height: 0.8 } 144 | ``` 145 | 146 | ##### trimTrailingZeros 147 | 148 | Type: `boolean` 149 | Default: `true` 150 | 151 | Set this to `true` to traim trailing zeros in numbers. 152 | 153 | ```css 154 | /* true */ 155 | div { top: 50px } 156 | 157 | /* false */ 158 | div { top: 50.000px } 159 | ``` 160 | 161 | ##### maxAtRuleLength 162 | 163 | Type: `boolean|number` 164 | Default: `80` 165 | 166 | If set to a positive integer, set a maximum width for at-rule parameters; if 167 | they exceed this, they will be split up over multiple lines. If false, this 168 | behaviour will not be performed. Note that this transform only applies to 169 | the `expanded` format. 170 | 171 | ##### maxSelectorLength 172 | 173 | Type: `boolean|number` 174 | Default: `80` 175 | 176 | If set to a positive integer, set a maximum width for a selector string; if 177 | it exceeds this, it will be split up over multiple lines. If false, this 178 | behaviour will not be performed. Note that this transform is excluded from the 179 | `compressed` format. 180 | 181 | ##### maxValueLength 182 | 183 | Type: `boolean|number` 184 | Default: `80` 185 | 186 | If set to a positive integer, set a maximum width for a property value; if 187 | it exceeds this, it will be split up over multiple lines. If false, this 188 | behaviour will not be performed. Note that this transform only applies to 189 | the `expanded` format. 190 | 191 | ##### sourcemap 192 | 193 | Type: `boolean` 194 | Default: `false` 195 | 196 | Generate a sourcemap with the transformed CSS. 197 | 198 | ##### syntax 199 | 200 | Type: `string` 201 | 202 | Specify `scss` if you would like to also format SCSS-style single line comments. 203 | This loads the [postcss-scss](https://github.com/postcss/postcss-scss) plugin. 204 | 205 | ##### zeroLengthNoUnit 206 | 207 | Type: `boolean` 208 | Default: `true` 209 | 210 | Set this to `true` to trim units after zero length. 211 | 212 | ```css 213 | /* true */ 214 | div { padding: 0 } 215 | 216 | /* false */ 217 | div { padding: 0px } 218 | ``` 219 | 220 | ### `postcss([ perfectionist(opts) ])` 221 | 222 | perfectionist can also be consumed as a PostCSS plugin. See the 223 | [documentation](https://github.com/postcss/postcss#usage) for examples for 224 | your environment. 225 | 226 | ### CLI 227 | 228 | perfectionist also ships with a CLI app. To see the available options, just run: 229 | 230 | ```sh 231 | $ perfectionist --help 232 | ``` 233 | 234 | ## Usage 235 | 236 | See the [PostCSS documentation](https://github.com/postcss/postcss#usage) for 237 | examples for your environment. 238 | 239 | ## Integrations 240 | 241 | - [Sublime Text plugin](https://github.com/yisibl/sublime-perfectionist) 242 | - [Atom plugin](https://github.com/sindresorhus/atom-perfectionist) 243 | 244 | ## Contributing 245 | 246 | Pull requests are welcome. If you add functionality, then please add unit tests 247 | to cover it. 248 | 249 | ## License 250 | 251 | MIT © [Ben Briggs](http://beneb.info) 252 | 253 | [ci]: https://travis-ci.org/ben-eb/perfectionist 254 | [cssnano]: https://github.com/ben-eb/cssnano 255 | [deps]: https://gemnasium.com/ben-eb/perfectionist 256 | [npm]: http://badge.fury.io/js/perfectionist 257 | [postcss]: https://github.com/postcss/postcss 258 | -------------------------------------------------------------------------------- /bin/cmd.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var fs = require('fs'); 4 | var perfectionist = require('../dist'); 5 | var read = require('read-file-stdin'); 6 | var write = require('write-file-stdout'); 7 | 8 | var opts = require('minimist')(process.argv.slice(2), { 9 | alias: { 10 | f: 'format', 11 | h: 'help', 12 | s: 'sourcemap', 13 | t: 'syntax', 14 | v: 'version' 15 | } 16 | }); 17 | 18 | if (opts.version) { 19 | return console.log(require('../package.json').version); 20 | } 21 | 22 | var file = opts._[0]; 23 | var out = opts._[1]; 24 | 25 | if (file === 'help' || opts.help) { 26 | return fs.createReadStream(__dirname + '/usage.txt') 27 | .pipe(process.stdout) 28 | .on('close', function () { process.exit(1); }); 29 | } 30 | 31 | read(file, function (err, buf) { 32 | if (err) { 33 | throw err; 34 | } 35 | if (file) { 36 | opts.from = file; 37 | } 38 | if (out) { 39 | opts.to = out; 40 | } 41 | write(out, String(perfectionist.process(String(buf), opts))); 42 | }); 43 | -------------------------------------------------------------------------------- /bin/usage.txt: -------------------------------------------------------------------------------- 1 | Usage: perfectionist [input] [output] {OPTIONS} 2 | 3 | Options: 4 | 5 | --format -f Choose how the CSS should be formatted: 6 | expanded (default), compact, compressed 7 | 8 | --syntax -t Pass scss to beautify scss source. 9 | 10 | --sourcemap -s Output a sourcemap with the CSS. 11 | 12 | --version, -v Outputs the version number. 13 | 14 | --help, -h Outputs this help screen. 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "perfectionist", 3 | "version": "2.4.0", 4 | "description": "Beautify CSS files.", 5 | "main": "dist/index.js", 6 | "bin": { 7 | "perfectionist": "bin/cmd.js" 8 | }, 9 | "files": [ 10 | "bin", 11 | "LICENSE-MIT", 12 | "dist" 13 | ], 14 | "scripts": { 15 | "pretest": "eslint src", 16 | "prepublish": "del-cli dist && cross-env BABEL_ENV=publish babel src --out-dir dist --ignore /__tests__/", 17 | "report": "nyc report --reporter=html", 18 | "test": "cross-env BABEL_ENV=test nyc ava", 19 | "test-012": "cross-env BABEL_ENV=test nyc ava" 20 | }, 21 | "keywords": [ 22 | "beautify", 23 | "css", 24 | "format", 25 | "postcss", 26 | "postcss-plugin", 27 | "pretty" 28 | ], 29 | "license": "MIT", 30 | "homepage": "https://github.com/ben-eb/perfectionist", 31 | "author": { 32 | "name": "Ben Briggs", 33 | "email": "beneb.info@gmail.com", 34 | "url": "http://beneb.info" 35 | }, 36 | "repository": "ben-eb/perfectionist", 37 | "dependencies": { 38 | "comment-regex": "^1.0.0", 39 | "defined": "^1.0.0", 40 | "minimist": "^1.2.0", 41 | "postcss": "^5.0.8", 42 | "postcss-scss": "^0.3.0", 43 | "postcss-value-parser": "^3.3.0", 44 | "read-file-stdin": "^0.2.0", 45 | "string.prototype.repeat": "^0.2.0", 46 | "vendors": "^1.0.0", 47 | "write-file-stdout": "0.0.2" 48 | }, 49 | "devDependencies": { 50 | "ava": "^0.17.0", 51 | "babel-cli": "^6.3.17", 52 | "babel-core": "^6.3.26", 53 | "babel-plugin-add-module-exports": "^0.2.1", 54 | "babel-plugin-istanbul": "^2.0.0", 55 | "babel-preset-es2015": "^6.3.13", 56 | "babel-preset-es2015-loose": "^7.0.0", 57 | "babel-preset-stage-0": "^6.3.13", 58 | "babel-register": "^6.9.0", 59 | "cross-env": "^2.0.0", 60 | "del-cli": "^0.2.0", 61 | "eslint": "^3.0.0", 62 | "eslint-config-cssnano": "^3.0.0", 63 | "eslint-plugin-babel": "^3.3.0", 64 | "eslint-plugin-import": "^2.0.1", 65 | "execa": "^0.4.0", 66 | "nyc": "^10.0.0" 67 | }, 68 | "ava": { 69 | "babel": "inherit", 70 | "require": "babel-register" 71 | }, 72 | "eslintConfig": { 73 | "extends": "cssnano" 74 | }, 75 | "nyc": { 76 | "exclude": [ 77 | "node_modules", 78 | "**/__tests__" 79 | ], 80 | "sourceMap": false, 81 | "instrument": false 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/__tests__/api.js: -------------------------------------------------------------------------------- 1 | import postcss from 'postcss'; 2 | import ava from 'ava'; 3 | import perfectionist from '../'; 4 | import {name} from '../../package.json'; 5 | 6 | function usage (t, instance) { 7 | const input = 'h1 { color: #fff }'; 8 | return instance.process(input).then(({css}) => { 9 | t.deepEqual(css, 'h1 {\n color: #fff;\n}\n', 'should be consumed'); 10 | }); 11 | } 12 | 13 | ava('can be used as a postcss plugin', usage, postcss().use(perfectionist())); 14 | ava('can be used as a postcss plugin (2)', usage, postcss([ perfectionist() ])); 15 | ava('can be used as a postcss plugin (3)', usage, postcss([ perfectionist ])); 16 | 17 | ava('should use the postcss plugin api', t => { 18 | t.truthy(perfectionist().postcssVersion, 'should be able to access version'); 19 | t.deepEqual(perfectionist().postcssPlugin, name, 'should be able to access name'); 20 | }); 21 | -------------------------------------------------------------------------------- /src/__tests__/cli.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import {readFileSync as read} from 'fs'; 3 | import ava from 'ava'; 4 | import execa from 'execa'; 5 | 6 | const fixture = path.join(__dirname, './fixtures/nested.fixture.css'); 7 | 8 | function setup (args) { 9 | process.chdir(__dirname); 10 | return execa(path.resolve(__dirname, '../../bin/cmd.js'), args, {stripEof: false}); 11 | } 12 | 13 | ava('cli: defaults', t => { 14 | return setup([fixture]).then(({stdout}) => { 15 | t.deepEqual(stdout, read('./fixtures/nested.expanded.css', 'utf-8'), 'should transform the css'); 16 | }); 17 | }); 18 | 19 | ava('cli: formatter', t => { 20 | return setup([fixture, '--format', 'compressed']).then(({stdout}) => { 21 | t.deepEqual(stdout, read('./fixtures/nested.compressed.css', 'utf-8'), 'should transform the css'); 22 | }); 23 | }); 24 | 25 | ava('cli: sourcemaps', t => { 26 | return setup([fixture, '--sourcemap']).then(({stdout}) => { 27 | const hasMap = /sourceMappingURL=data:application\/json;base64/.test(stdout); 28 | t.truthy(hasMap, 'should generate a sourcemap'); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/block-comment.compact.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v3.0.2 | MIT License | git.io/normalize */ 2 | /** 3 | * 1. Set default font family to sans-serif. 4 | * 2. Prevent iOS text size adjust after orientation change, without disabling 5 | * user zoom. 6 | */ 7 | html { font-family: sans-serif; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; } 8 | /** 9 | * Remove default margin. 10 | */ 11 | body { margin: 0; } 12 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/block-comment.compressed.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v3.0.2 | MIT License | git.io/normalize *//** 2 | * 1. Set default font family to sans-serif. 3 | * 2. Prevent iOS text size adjust after orientation change, without disabling 4 | * user zoom. 5 | */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}/** 6 | * Remove default margin. 7 | */body{margin:0} -------------------------------------------------------------------------------- /src/__tests__/fixtures/block-comment.expanded.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v3.0.2 | MIT License | git.io/normalize */ 2 | 3 | /** 4 | * 1. Set default font family to sans-serif. 5 | * 2. Prevent iOS text size adjust after orientation change, without disabling 6 | * user zoom. 7 | */ 8 | 9 | html { 10 | font-family: sans-serif; 11 | -ms-text-size-adjust: 100%; 12 | -webkit-text-size-adjust: 100%; 13 | } 14 | 15 | /** 16 | * Remove default margin. 17 | */ 18 | 19 | body { 20 | margin: 0; 21 | } 22 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/block-comment.fixture.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v3.0.2 | MIT License | git.io/normalize *//** 2 | * 1. Set default font family to sans-serif. 3 | * 2. Prevent iOS text size adjust after orientation change, without disabling 4 | * user zoom. 5 | */html {font-family: sans-serif;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;}/** 6 | * Remove default margin. 7 | */body {margin: 0;} 8 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/border-and-shadow.compact.css: -------------------------------------------------------------------------------- 1 | h1 { -moz-border-radius: 12px; -webkit-border-radius: 12px; border-radius: 12px; -webkit-box-shadow: 0 1px 3px black; -moz-box-shadow: 0 1px 3px black; box-shadow: 0 1px 3px black; } 2 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/border-and-shadow.compressed.css: -------------------------------------------------------------------------------- 1 | h1{-moz-border-radius:12px;-webkit-border-radius:12px;border-radius:12px;-webkit-box-shadow:0 1px 3px black;-moz-box-shadow:0 1px 3px black;box-shadow:0 1px 3px black} -------------------------------------------------------------------------------- /src/__tests__/fixtures/border-and-shadow.expanded.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | -moz-border-radius: 12px; 3 | -webkit-border-radius: 12px; 4 | border-radius: 12px; 5 | -webkit-box-shadow: 0 1px 3px black; 6 | -moz-box-shadow: 0 1px 3px black; 7 | box-shadow: 0 1px 3px black; 8 | } 9 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/border-and-shadow.fixture.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | -moz-border-radius: 12px; 3 | -webkit-border-radius: 12px; 4 | border-radius: 12px; 5 | -webkit-box-shadow: 0 1px 3px black; 6 | -moz-box-shadow: 0 1px 3px black; 7 | box-shadow: 0 1px 3px black; 8 | } 9 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/border-radius.compact.css: -------------------------------------------------------------------------------- 1 | h1 { -moz-border-radius: 12px; -webkit-border-radius: 12px; border-radius: 12px; } 2 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/border-radius.compressed.css: -------------------------------------------------------------------------------- 1 | h1{-moz-border-radius:12px;-webkit-border-radius:12px;border-radius:12px} -------------------------------------------------------------------------------- /src/__tests__/fixtures/border-radius.expanded.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | -moz-border-radius: 12px; 3 | -webkit-border-radius: 12px; 4 | border-radius: 12px; 5 | } 6 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/border-radius.fixture.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | -moz-border-radius: 12px; 3 | -webkit-border-radius: 12px; 4 | border-radius: 12px; 5 | } 6 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/box-shadow.compact.css: -------------------------------------------------------------------------------- 1 | h1 { box-shadow: 0 1px 3px black, 1px 0 6px red; } 2 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/box-shadow.compressed.css: -------------------------------------------------------------------------------- 1 | h1{box-shadow:0 1px 3px black,1px 0 6px red} -------------------------------------------------------------------------------- /src/__tests__/fixtures/box-shadow.expanded.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | box-shadow: 0 1px 3px black, 1px 0 6px red; 3 | } 4 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/box-shadow.fixture.css: -------------------------------------------------------------------------------- 1 | h1 { box-shadow: 0 1px 3px black, 1px 0 6px red; 2 | } 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/braces-in-content.compact.css: -------------------------------------------------------------------------------- 1 | h1:before { content: "("; } 2 | h1:after { content: ")"; } 3 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/braces-in-content.compressed.css: -------------------------------------------------------------------------------- 1 | h1:before{content:"("}h1:after{content:")"} -------------------------------------------------------------------------------- /src/__tests__/fixtures/braces-in-content.expanded.css: -------------------------------------------------------------------------------- 1 | h1:before { 2 | content: "("; 3 | } 4 | 5 | h1:after { 6 | content: ")"; 7 | } 8 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/braces-in-content.fixture.css: -------------------------------------------------------------------------------- 1 | h1:before { 2 | content: "("; 3 | } 4 | 5 | h1:after { 6 | content: ")"; 7 | } 8 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/color-case.compact.css: -------------------------------------------------------------------------------- 1 | p { color: #aa93fe; background: #fff url( /ImagesDir/loader.gif ) center center no-repeat; } 2 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/color-case.compressed.css: -------------------------------------------------------------------------------- 1 | p{color:#aa93fe;background:#fff url(/ImagesDir/loader.gif) center center no-repeat} -------------------------------------------------------------------------------- /src/__tests__/fixtures/color-case.expanded.css: -------------------------------------------------------------------------------- 1 | p { 2 | color: #aa93fe; 3 | background: #fff url(/ImagesDir/loader.gif) center center no-repeat; 4 | } 5 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/color-case.fixture.css: -------------------------------------------------------------------------------- 1 | p { 2 | color: #AA93FE; 3 | background: #FFF url(/ImagesDir/loader.gif) center center no-repeat; 4 | } 5 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/color-shorthand.compact.css: -------------------------------------------------------------------------------- 1 | p { color: #a8e; box-shadow: 0 0 5px 0 #ccc; } 2 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/color-shorthand.compressed.css: -------------------------------------------------------------------------------- 1 | p{color:#a8e;box-shadow:0 0 5px 0 #ccc} -------------------------------------------------------------------------------- /src/__tests__/fixtures/color-shorthand.expanded.css: -------------------------------------------------------------------------------- 1 | p { 2 | color: #a8e; 3 | box-shadow: 0 0 5px 0 #ccc; 4 | } 5 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/color-shorthand.fixture.css: -------------------------------------------------------------------------------- 1 | p { 2 | color: #aa88ee; 3 | box-shadow: 0 0 5px 0 #cccccc; 4 | } 5 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/comment-between.compact.css: -------------------------------------------------------------------------------- 1 | .selector { color: /*[[placeholder]]*/ white; } 2 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/comment-between.compressed.css: -------------------------------------------------------------------------------- 1 | .selector{color: /*[[placeholder]]*/ white} -------------------------------------------------------------------------------- /src/__tests__/fixtures/comment-between.expanded.css: -------------------------------------------------------------------------------- 1 | .selector { 2 | color: /*[[placeholder]]*/ white; 3 | } 4 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/comment-between.fixture.css: -------------------------------------------------------------------------------- 1 | .selector { 2 | color: /*[[placeholder]]*/ white; 3 | } 4 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/comment-eol.compact.css: -------------------------------------------------------------------------------- 1 | html { font-family: sans-serif; /* 1 */ -ms-text-size-adjust: 100%; /* 2 */ -webkit-text-size-adjust: 100%; /* 2 */ } 2 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/comment-eol.compressed.css: -------------------------------------------------------------------------------- 1 | html{font-family:sans-serif;/* 1 */-ms-text-size-adjust:100%;/* 2 */-webkit-text-size-adjust:100%/* 2 */} -------------------------------------------------------------------------------- /src/__tests__/fixtures/comment-eol.expanded.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-family: sans-serif; /* 1 */ 3 | -ms-text-size-adjust: 100%; /* 2 */ 4 | -webkit-text-size-adjust: 100%; /* 2 */ 5 | } 6 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/comment-eol.fixture.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-family: sans-serif;/* 1 */ 3 | -ms-text-size-adjust: 100%;/* 2 */ 4 | -webkit-text-size-adjust: 100%;/* 2 */ 5 | } 6 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/comment-in-selector.compact.css: -------------------------------------------------------------------------------- 1 | /* 4 selectors + 2 comments + 3 selectors */ 2 | .selectorA, .selectorB, .selectorC, .selectorX, 3 | /* comment 1 */ 4 | /* comment 2 */ 5 | .selectorD, .selectorE, .selectorF { color: white; } 6 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/comment-in-selector.compressed.css: -------------------------------------------------------------------------------- 1 | /* 4 selectors + 2 comments + 3 selectors */.selectorA, .selectorB, .selectorC, .selectorX, 2 | /* comment 1 */ 3 | /* comment 2 */ 4 | .selectorD, .selectorE, .selectorF{color:white} -------------------------------------------------------------------------------- /src/__tests__/fixtures/comment-in-selector.expanded.css: -------------------------------------------------------------------------------- 1 | /* 4 selectors + 2 comments + 3 selectors */ 2 | 3 | .selectorA, .selectorB, .selectorC, .selectorX, 4 | /* comment 1 */ 5 | /* comment 2 */ 6 | .selectorD, .selectorE, .selectorF { 7 | color: white; 8 | } 9 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/comment-in-selector.fixture.css: -------------------------------------------------------------------------------- 1 | /* 4 selectors + 2 comments + 3 selectors */ 2 | .selectorA, .selectorB, .selectorC, .selectorX, 3 | /* comment 1 */ 4 | /* comment 2 */ 5 | .selectorD, .selectorE, .selectorF { 6 | color: white; 7 | } 8 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/comment-newline.compact.css: -------------------------------------------------------------------------------- 1 | h1 { color: black; /* ^^^^^^ */ } 2 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/comment-newline.compressed.css: -------------------------------------------------------------------------------- 1 | h1{color:black/* ^^^^^^ */} -------------------------------------------------------------------------------- /src/__tests__/fixtures/comment-newline.expanded.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | color: black; 3 | /* ^^^^^^ */ 4 | } 5 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/comment-newline.fixture.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | color: black; 3 | /* ^^^^^^ */ 4 | } 5 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/comments-in-values.compact.css: -------------------------------------------------------------------------------- 1 | h1 { margin: 10px /* top/bottom */ 20px /* right/left */; } 2 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/comments-in-values.compressed.css: -------------------------------------------------------------------------------- 1 | h1{margin:10px /* top/bottom */ 20px /* right/left */} -------------------------------------------------------------------------------- /src/__tests__/fixtures/comments-in-values.expanded.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | margin: 10px /* top/bottom */ 20px /* right/left */; 3 | } 4 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/comments-in-values.fixture.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | margin: 10px /* top/bottom */ 20px /* right/left */; 3 | } 4 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/ending-comment.compact.css: -------------------------------------------------------------------------------- 1 | .selector { color: white; } 2 | /* comment */ 3 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/ending-comment.compressed.css: -------------------------------------------------------------------------------- 1 | .selector{color:white}/* comment */ -------------------------------------------------------------------------------- /src/__tests__/fixtures/ending-comment.expanded.css: -------------------------------------------------------------------------------- 1 | .selector { 2 | color: white; 3 | } 4 | 5 | /* comment */ 6 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/ending-comment.fixture.css: -------------------------------------------------------------------------------- 1 | .selector { 2 | color: white; 3 | } 4 | /* comment */ 5 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/exclamation-value.compact.css: -------------------------------------------------------------------------------- 1 | $border: 1px solid rgba( 68, 120, 10, .5 ); 2 | $color: #f80; 3 | .foo { color: rgba( 170, 136, 153, .6 ) !important; display: inline-block !default; position: absolute !global; left: 20px !foooo; } 4 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/exclamation-value.compressed.css: -------------------------------------------------------------------------------- 1 | $border:1px solid rgba(68,120,10,.5);$color:#f80;.foo{color:rgba(170,136,153,.6)!important;display:inline-block!default;position:absolute!global;left:20px!foooo} -------------------------------------------------------------------------------- /src/__tests__/fixtures/exclamation-value.expanded.css: -------------------------------------------------------------------------------- 1 | $border: 1px solid rgba(68, 120, 10, .5); 2 | $color: #f80; 3 | 4 | .foo { 5 | color: rgba(170, 136, 153, .6) !important; 6 | display: inline-block !default; 7 | position: absolute !global; 8 | left: 20px !foooo; 9 | } 10 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/exclamation-value.fixture.css: -------------------------------------------------------------------------------- 1 | $border: 1px solid rgba( 68, 120,10, .5 ) ; 2 | 3 | $color:#f80; 4 | 5 | .foo { 6 | color: rgba( 170, 136, 153, 0.6 ) ! important; 7 | display: inline-block ! default ; 8 | position: absolute ! global ; 9 | left: 20px ! foooo; 10 | } 11 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/flex-order.compact.css: -------------------------------------------------------------------------------- 1 | .box { -ms-flex-order: 1; -webkit-order: 1; order: 1; } 2 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/flex-order.compressed.css: -------------------------------------------------------------------------------- 1 | .box{-ms-flex-order:1;-webkit-order:1;order:1} -------------------------------------------------------------------------------- /src/__tests__/fixtures/flex-order.expanded.css: -------------------------------------------------------------------------------- 1 | .box { 2 | -ms-flex-order: 1; 3 | -webkit-order: 1; 4 | order: 1; 5 | } 6 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/flex-order.fixture.css: -------------------------------------------------------------------------------- 1 | .box { 2 | -ms-flex-order: 1; 3 | -webkit-order: 1; 4 | order: 1; 5 | } 6 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/font-face.compact.css: -------------------------------------------------------------------------------- 1 | @font-face { src: url( "webfont.eot" ); src: url( "webfont.eot?#iefix" ) format( "embedded-opentype" ), url( "webfont.woff2" ) format( "woff2" ), url( "webfont.woff" ) format( "woff" ), url( "webfont.ttf" ) format( "truetype" ), url( "webfont.svg#svgFontName" ) format( "svg" ); } 2 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/font-face.compressed.css: -------------------------------------------------------------------------------- 1 | @font-face{src:url("webfont.eot");src:url("webfont.eot?#iefix") format("embedded-opentype"),url("webfont.woff2") format("woff2"),url("webfont.woff") format("woff"),url("webfont.ttf") format("truetype"),url("webfont.svg#svgFontName") format("svg")} -------------------------------------------------------------------------------- /src/__tests__/fixtures/font-face.expanded.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | src: url("webfont.eot"); 3 | src: url("webfont.eot?#iefix") format("embedded-opentype"), 4 | url("webfont.woff2") format("woff2"), 5 | url("webfont.woff") format("woff"), 6 | url("webfont.ttf") format("truetype"), 7 | url("webfont.svg#svgFontName") format("svg"); 8 | } 9 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/font-face.fixture.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | src: url("webfont.eot"); src: url("webfont.eot?#iefix") format("embedded-opentype"), url("webfont.woff2") format("woff2"), url("webfont.woff") format("woff"), url("webfont.ttf") format("truetype"), url("webfont.svg#svgFontName") format("svg"); 3 | } 4 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/handle-fractions.compact.css: -------------------------------------------------------------------------------- 1 | .foo { transform: scale( 1.05 ); } 2 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/handle-fractions.compressed.css: -------------------------------------------------------------------------------- 1 | .foo{transform:scale(1.05)} -------------------------------------------------------------------------------- /src/__tests__/fixtures/handle-fractions.expanded.css: -------------------------------------------------------------------------------- 1 | .foo { 2 | transform: scale(1.05); 3 | } 4 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/handle-fractions.fixture.css: -------------------------------------------------------------------------------- 1 | .foo { 2 | transform: scale(1.05); 3 | } 4 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/import.compact.css: -------------------------------------------------------------------------------- 1 | @import 'import-p-green.css'; 2 | @import url(import-p-green.css); 3 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/import.compressed.css: -------------------------------------------------------------------------------- 1 | @import 'import-p-green.css';@import url(import-p-green.css); -------------------------------------------------------------------------------- /src/__tests__/fixtures/import.expanded.css: -------------------------------------------------------------------------------- 1 | @import 'import-p-green.css'; 2 | @import url(import-p-green.css); 3 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/import.fixture.css: -------------------------------------------------------------------------------- 1 | @import 'import-p-green.css'; 2 | @import url(import-p-green.css); 3 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/large-media-query.compact.css: -------------------------------------------------------------------------------- 1 | @media 2 | only screen and (-o-min-device-pixel-ratio: 2/1), 3 | only screen and (min--moz-device-pixel-ratio: 2), 4 | only screen and (-moz-min-device-pixel-ratio: 2), 5 | only screen and (-webkit-min-device-pixel-ratio: 2), 6 | only screen and (min-resolution: 192dpi), 7 | only screen and (min-resolution: 2dppx) { 8 | .selector { background-size: 30px 50px; } 9 | } 10 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/large-media-query.compressed.css: -------------------------------------------------------------------------------- 1 | @media 2 | only screen and (-o-min-device-pixel-ratio: 2/1), 3 | only screen and (min--moz-device-pixel-ratio: 2), 4 | only screen and (-moz-min-device-pixel-ratio: 2), 5 | only screen and (-webkit-min-device-pixel-ratio: 2), 6 | only screen and (min-resolution: 192dpi), 7 | only screen and (min-resolution: 2dppx){.selector{background-size:30px 50px}} -------------------------------------------------------------------------------- /src/__tests__/fixtures/large-media-query.expanded.css: -------------------------------------------------------------------------------- 1 | @media only screen and (-o-min-device-pixel-ratio: 2/1), 2 | only screen and (min--moz-device-pixel-ratio: 2), 3 | only screen and (-moz-min-device-pixel-ratio: 2), 4 | only screen and (-webkit-min-device-pixel-ratio: 2), 5 | only screen and (min-resolution: 192dpi), 6 | only screen and (min-resolution: 2dppx) { 7 | .selector { 8 | background-size: 30px 50px; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/large-media-query.fixture.css: -------------------------------------------------------------------------------- 1 | @media 2 | only screen and (-o-min-device-pixel-ratio: 2/1), 3 | only screen and (min--moz-device-pixel-ratio: 2), 4 | only screen and (-moz-min-device-pixel-ratio: 2), 5 | only screen and (-webkit-min-device-pixel-ratio: 2), 6 | only screen and (min-resolution: 192dpi), 7 | only screen and (min-resolution: 2dppx) { 8 | .selector { 9 | background-size: 30px 50px; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/long-selector-media.compact.css: -------------------------------------------------------------------------------- 1 | @media all and (min-width: 480px) and (max-width: 767px) { 2 | .selector 1, .selector 2, .selector 3, .selector 4, .selector 5, .selector 6, 3 | .selector 7, .selector 8, .selector 9, .selector 10, .selector 11, .selector 12, 4 | .selector 13, .selector 14, .selector 15, .selector 16, .selector 17, 5 | .selector 18, .selector 19, .selector 20 { background: #000; } 6 | } 7 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/long-selector-media.compressed.css: -------------------------------------------------------------------------------- 1 | @media all and (min-width: 480px) and (max-width: 767px){.selector 1, .selector 2, .selector 3, .selector 4, .selector 5, .selector 6, .selector 7, .selector 8, .selector 9, .selector 10, .selector 11, .selector 12, .selector 13, .selector 14, .selector 15, .selector 16, .selector 17, .selector 18, .selector 19, .selector 20{background:#000}} -------------------------------------------------------------------------------- /src/__tests__/fixtures/long-selector-media.expanded.css: -------------------------------------------------------------------------------- 1 | @media all and (min-width: 480px) and (max-width: 767px) { 2 | .selector 1, .selector 2, .selector 3, .selector 4, .selector 5, .selector 6, 3 | .selector 7, .selector 8, .selector 9, .selector 10, .selector 11, 4 | .selector 12, .selector 13, .selector 14, .selector 15, .selector 16, 5 | .selector 17, .selector 18, .selector 19, .selector 20 { 6 | background: #000; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/long-selector-media.fixture.css: -------------------------------------------------------------------------------- 1 | @media all and (min-width: 480px) and (max-width: 767px) { 2 | .selector 1, .selector 2, .selector 3, .selector 4, .selector 5, .selector 6, .selector 7, .selector 8, .selector 9, .selector 10, .selector 11, .selector 12, .selector 13, .selector 14, .selector 15, .selector 16, .selector 17, .selector 18, .selector 19, .selector 20 { 3 | background: #000; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/long-selector.compact.css: -------------------------------------------------------------------------------- 1 | a, abbr, p, ul, ol, li, h1, h2, h3, h4, h5, h6, cite, blockquote, hr, div, 2 | section, article, span, time, code, pre, embed, object, body, aside, video, 3 | audio, header, footer, main, small, b, em, .hidden { display: none; } 4 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/long-selector.compressed.css: -------------------------------------------------------------------------------- 1 | a, abbr, p, ul, ol, li, h1, h2, h3, h4, h5, h6, cite, blockquote, hr, div, section, article, span, time, code, pre, embed, object, body, aside, video, audio, header, footer, main, small, b, em, .hidden{display:none} -------------------------------------------------------------------------------- /src/__tests__/fixtures/long-selector.expanded.css: -------------------------------------------------------------------------------- 1 | a, abbr, p, ul, ol, li, h1, h2, h3, h4, h5, h6, cite, blockquote, hr, div, 2 | section, article, span, time, code, pre, embed, object, body, aside, video, 3 | audio, header, footer, main, small, b, em, .hidden { 4 | display: none; 5 | } 6 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/long-selector.fixture.css: -------------------------------------------------------------------------------- 1 | a, abbr, p, ul, ol, li, h1, h2, h3, h4, h5, h6, cite, blockquote, hr, div, section, article, span, time, code, pre, embed, object, body, aside, video, audio, header, footer, main, small, b, em, .hidden { 2 | display: none } 3 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/media-query.compact.css: -------------------------------------------------------------------------------- 1 | @media screen and (min-width: 500px) { 2 | h1 { color: navy; background: #fff; border-radius: 12px; } 3 | h2 { text-decoration: underline; } 4 | } 5 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/media-query.compressed.css: -------------------------------------------------------------------------------- 1 | @media screen and (min-width: 500px){h1{color:navy;background:#fff;border-radius:12px}h2{text-decoration:underline}} -------------------------------------------------------------------------------- /src/__tests__/fixtures/media-query.expanded.css: -------------------------------------------------------------------------------- 1 | @media screen and (min-width: 500px) { 2 | h1 { 3 | color: navy; 4 | background: #fff; 5 | border-radius: 12px; 6 | } 7 | 8 | h2 { 9 | text-decoration: underline; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/media-query.fixture.css: -------------------------------------------------------------------------------- 1 | @media screen and (min-width: 500px){h1 {color: navy;background: #fff;border-radius: 12px;} 2 | h2 { 3 | text-decoration: underline; 4 | }} 5 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/medium-media.compact.css: -------------------------------------------------------------------------------- 1 | @media only screen and (min-width: 500px), only screen and (max-width: 800px), only screen and (min-height: 500px) { 2 | .selector { height: 100%; } 3 | } 4 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/medium-media.compressed.css: -------------------------------------------------------------------------------- 1 | @media only screen and (min-width: 500px), only screen and (max-width: 800px), only screen and (min-height: 500px){.selector{height:100%}} -------------------------------------------------------------------------------- /src/__tests__/fixtures/medium-media.expanded.css: -------------------------------------------------------------------------------- 1 | @media only screen and (min-width: 500px), 2 | only screen and (max-width: 800px), 3 | only screen and (min-height: 500px) { 4 | .selector { 5 | height: 100%; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/medium-media.fixture.css: -------------------------------------------------------------------------------- 1 | @media only screen and (min-width: 500px), only screen and (max-width: 800px), only screen and (min-height: 500px) { 2 | .selector { 3 | height: 100% 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/multiline-selectors.compact.css: -------------------------------------------------------------------------------- 1 | h1, h2, h3 { color: red; } 2 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/multiline-selectors.compressed.css: -------------------------------------------------------------------------------- 1 | h1, 2 | h2, 3 | h3{color:red} -------------------------------------------------------------------------------- /src/__tests__/fixtures/multiline-selectors.expanded.css: -------------------------------------------------------------------------------- 1 | h1, h2, h3 { 2 | color: red; 3 | } 4 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/multiline-selectors.fixture.css: -------------------------------------------------------------------------------- 1 | h1, 2 | h2, 3 | h3 { 4 | color: red 5 | } 6 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/nested-media.compact.css: -------------------------------------------------------------------------------- 1 | @media (min-width: 992px) { 2 | @media (max-width: 1200px) { 3 | .container { position: absolute; } 4 | } 5 | } 6 | .container-fluid { padding-right: 15px; padding-left: 15px; margin-right: auto; margin-left: auto; } 7 | @media (min-width: 1200px) { 8 | .container { width: 1170px; } 9 | } 10 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/nested-media.compressed.css: -------------------------------------------------------------------------------- 1 | @media (min-width: 992px){@media (max-width: 1200px){.container{position:absolute}}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width: 1200px){.container{width:1170px}} -------------------------------------------------------------------------------- /src/__tests__/fixtures/nested-media.expanded.css: -------------------------------------------------------------------------------- 1 | @media (min-width: 992px) { 2 | @media (max-width: 1200px) { 3 | .container { 4 | position: absolute; 5 | } 6 | } 7 | } 8 | 9 | .container-fluid { 10 | padding-right: 15px; 11 | padding-left: 15px; 12 | margin-right: auto; 13 | margin-left: auto; 14 | } 15 | 16 | @media (min-width: 1200px) { 17 | .container { 18 | width: 1170px; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/nested-media.fixture.css: -------------------------------------------------------------------------------- 1 | @media (min-width: 992px) { 2 | @media (max-width: 1200px) { 3 | .container { 4 | position: absolute; 5 | } 6 | } 7 | } 8 | 9 | .container-fluid { 10 | padding-right: 15px; 11 | padding-left: 15px; 12 | margin-right: auto; 13 | margin-left: auto; 14 | } 15 | 16 | @media (min-width: 1200px) { 17 | .container { 18 | width: 1170px; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/nested.compact.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | color: red; 3 | & h2 { 4 | color: yellow; 5 | & h3 { color: green; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/nested.compressed.css: -------------------------------------------------------------------------------- 1 | h1{color:red;& h2{color:yellow;& h3{color:green}}} -------------------------------------------------------------------------------- /src/__tests__/fixtures/nested.expanded.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | color: red; 3 | 4 | & h2 { 5 | color: yellow; 6 | 7 | & h3 { 8 | color: green; 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/nested.fixture.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | color: red; 3 | & h2 { 4 | color: yellow; 5 | & h3 { color: green; 6 | }}} 7 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/quoted-image-url.compact.css: -------------------------------------------------------------------------------- 1 | .double-quotes { background-image: url( "data:image/gif;base64,R0lGODlhFQAJAIAAACMtMP///yH5BAEAAAEALAAAAAAVAAkAAAIXjI+AywnaYnhUMoqt3gZXPmVg94yJVQAAOw==" ); } 2 | .single-quotes { background-image: url( 'data:image/gif;base64,R0lGODlhCgAKAIAAABoaGgAAACwAAAAACgAKAAACEYQdmYcaDNxjEspKndVZbc8UADs=' ); } 3 | .no-quotes { background-image: url( data:image/gif;base64,R0lGODlhFQAEAIAAACMtMP///yH5BAEAAAEALAAAAAAVAAQAAAINjI8Bya2wnINUMopZAQA7 ); } 4 | /* http://codepen.io/silverwind/pen/WQqVBa */ 5 | #square0 { background-image: url( 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAiIGhlaWdodD0iMTAwIj48cmVjdCBmaWxsPSIjMDBCMUZGIiB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIvPjwvc3ZnPg==' ); } 6 | #square1 { background-image: url( 'data:image/svg+xml;charset=utf-8,' ); } 7 | #square2 { background-image: url( 'data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22100%22%20height%3D%22100%22%3E%3Crect%20fill%3D%22%2300B1FF%22%20width%3D%22100%22%20height%3D%22100%22%2F%3E%3C%2Fsvg%3E' ); } 8 | #square3 { background-image: url( data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAiIGhlaWdodD0iMTAwIj48cmVjdCBmaWxsPSIjMDBCMUZGIiB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIvPjwvc3ZnPg== ); } 9 | #square4 { background-image: url( data:image/svg+xml;charset=utf-8, ); } 10 | #square5 { background-image: url( data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22100%22%20height%3D%22100%22%3E%3Crect%20fill%3D%22%2300B1FF%22%20width%3D%22100%22%20height%3D%22100%22%2F%3E%3C%2Fsvg%3E ); } 11 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/quoted-image-url.compressed.css: -------------------------------------------------------------------------------- 1 | .double-quotes{background-image:url("data:image/gif;base64,R0lGODlhFQAJAIAAACMtMP///yH5BAEAAAEALAAAAAAVAAkAAAIXjI+AywnaYnhUMoqt3gZXPmVg94yJVQAAOw==")}.single-quotes{background-image:url('data:image/gif;base64,R0lGODlhCgAKAIAAABoaGgAAACwAAAAACgAKAAACEYQdmYcaDNxjEspKndVZbc8UADs=')}.no-quotes{background-image:url(data:image/gif;base64,R0lGODlhFQAEAIAAACMtMP///yH5BAEAAAEALAAAAAAVAAQAAAINjI8Bya2wnINUMopZAQA7)}/* http://codepen.io/silverwind/pen/WQqVBa */#square0{background-image:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAiIGhlaWdodD0iMTAwIj48cmVjdCBmaWxsPSIjMDBCMUZGIiB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIvPjwvc3ZnPg==')}#square1{background-image:url('data:image/svg+xml;charset=utf-8,')}#square2{background-image:url('data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22100%22%20height%3D%22100%22%3E%3Crect%20fill%3D%22%2300B1FF%22%20width%3D%22100%22%20height%3D%22100%22%2F%3E%3C%2Fsvg%3E')}#square3{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAiIGhlaWdodD0iMTAwIj48cmVjdCBmaWxsPSIjMDBCMUZGIiB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIvPjwvc3ZnPg==)}#square4{background-image:url(data:image/svg+xml;charset=utf-8,)}#square5{background-image:url(data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22100%22%20height%3D%22100%22%3E%3Crect%20fill%3D%22%2300B1FF%22%20width%3D%22100%22%20height%3D%22100%22%2F%3E%3C%2Fsvg%3E)} -------------------------------------------------------------------------------- /src/__tests__/fixtures/quoted-image-url.expanded.css: -------------------------------------------------------------------------------- 1 | .double-quotes { 2 | background-image: url("data:image/gif;base64,R0lGODlhFQAJAIAAACMtMP///yH5BAEAAAEALAAAAAAVAAkAAAIXjI+AywnaYnhUMoqt3gZXPmVg94yJVQAAOw=="); 3 | } 4 | 5 | .single-quotes { 6 | background-image: url('data:image/gif;base64,R0lGODlhCgAKAIAAABoaGgAAACwAAAAACgAKAAACEYQdmYcaDNxjEspKndVZbc8UADs='); 7 | } 8 | 9 | .no-quotes { 10 | background-image: url(data:image/gif;base64,R0lGODlhFQAEAIAAACMtMP///yH5BAEAAAEALAAAAAAVAAQAAAINjI8Bya2wnINUMopZAQA7); 11 | } 12 | 13 | /* http://codepen.io/silverwind/pen/WQqVBa */ 14 | 15 | #square0 { 16 | background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAiIGhlaWdodD0iMTAwIj48cmVjdCBmaWxsPSIjMDBCMUZGIiB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIvPjwvc3ZnPg=='); 17 | } 18 | 19 | #square1 { 20 | background-image: url('data:image/svg+xml;charset=utf-8,'); 21 | } 22 | 23 | #square2 { 24 | background-image: url('data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22100%22%20height%3D%22100%22%3E%3Crect%20fill%3D%22%2300B1FF%22%20width%3D%22100%22%20height%3D%22100%22%2F%3E%3C%2Fsvg%3E'); 25 | } 26 | 27 | #square3 { 28 | background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAiIGhlaWdodD0iMTAwIj48cmVjdCBmaWxsPSIjMDBCMUZGIiB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIvPjwvc3ZnPg==); 29 | } 30 | 31 | #square4 { 32 | background-image: url(data:image/svg+xml;charset=utf-8,); 33 | } 34 | 35 | #square5 { 36 | background-image: url(data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22100%22%20height%3D%22100%22%3E%3Crect%20fill%3D%22%2300B1FF%22%20width%3D%22100%22%20height%3D%22100%22%2F%3E%3C%2Fsvg%3E); 37 | } 38 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/quoted-image-url.fixture.css: -------------------------------------------------------------------------------- 1 | .double-quotes{ background-image:url( "data:image/gif;base64,R0lGODlhFQAJAIAAACMtMP///yH5BAEAAAEALAAAAAAVAAkAAAIXjI+AywnaYnhUMoqt3gZXPmVg94yJVQAAOw==" ); }.single-quotes{background-image:url('data:image/gif;base64,R0lGODlhCgAKAIAAABoaGgAAACwAAAAACgAKAAACEYQdmYcaDNxjEspKndVZbc8UADs=' );} .no-quotes{ background-image:url(data:image/gif;base64,R0lGODlhFQAEAIAAACMtMP///yH5BAEAAAEALAAAAAAVAAQAAAINjI8Bya2wnINUMopZAQA7); } 2 | /* http://codepen.io/silverwind/pen/WQqVBa */ 3 | #square0 { background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAiIGhlaWdodD0iMTAwIj48cmVjdCBmaWxsPSIjMDBCMUZGIiB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIvPjwvc3ZnPg=='); } 4 | #square1 { background-image: url('data:image/svg+xml;charset=utf-8,'); } 5 | #square2 { background-image: url('data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22100%22%20height%3D%22100%22%3E%3Crect%20fill%3D%22%2300B1FF%22%20width%3D%22100%22%20height%3D%22100%22%2F%3E%3C%2Fsvg%3E'); } 6 | #square3 { background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAiIGhlaWdodD0iMTAwIj48cmVjdCBmaWxsPSIjMDBCMUZGIiB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIvPjwvc3ZnPg==); } 7 | #square4 { background-image: url(data:image/svg+xml;charset=utf-8,); } 8 | #square5 { background-image: url(data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22100%22%20height%3D%22100%22%3E%3Crect%20fill%3D%22%2300B1FF%22%20width%3D%22100%22%20height%3D%22100%22%2F%3E%3C%2Fsvg%3E); } 9 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/simple.compact.css: -------------------------------------------------------------------------------- 1 | h1 { color: navy; font-size: 2em; font-weight: normal; } 2 | h2 { text-decoration: underline; } 3 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/simple.compressed.css: -------------------------------------------------------------------------------- 1 | h1{color:navy;font-size:2em;font-weight:normal}h2{text-decoration:underline} -------------------------------------------------------------------------------- /src/__tests__/fixtures/simple.expanded.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | color: navy; 3 | font-size: 2em; 4 | font-weight: normal; 5 | } 6 | 7 | h2 { 8 | text-decoration: underline; 9 | } 10 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/simple.fixture.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | color: navy; 3 | font-size:2em; 4 | font-weight: normal 5 | }h2 {text-decoration: underline; 6 | } 7 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/trim-leading-zero.compact.css: -------------------------------------------------------------------------------- 1 | p { transition: .4s cubic-bezier( .23, 1, .32, 1 ); } 2 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/trim-leading-zero.compressed.css: -------------------------------------------------------------------------------- 1 | p{transition:.4s cubic-bezier(.23,1,.32,1)} -------------------------------------------------------------------------------- /src/__tests__/fixtures/trim-leading-zero.expanded.css: -------------------------------------------------------------------------------- 1 | p { 2 | transition: .4s cubic-bezier(.23, 1, .32, 1); 3 | } 4 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/trim-leading-zero.fixture.css: -------------------------------------------------------------------------------- 1 | p { 2 | transition: 0.4s cubic-bezier(0.23, 1, 0.32, 1); 3 | } 4 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/trim-trailing-zeros.compact.css: -------------------------------------------------------------------------------- 1 | div { top: 50px; } 2 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/trim-trailing-zeros.compressed.css: -------------------------------------------------------------------------------- 1 | div{top:50px} -------------------------------------------------------------------------------- /src/__tests__/fixtures/trim-trailing-zeros.expanded.css: -------------------------------------------------------------------------------- 1 | div { 2 | top: 50px; 3 | } 4 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/trim-trailing-zeros.fixture.css: -------------------------------------------------------------------------------- 1 | div { 2 | top: 50.000px; 3 | } 4 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/zero-length-no-unit.compact.css: -------------------------------------------------------------------------------- 1 | div { height: 0; } 2 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/zero-length-no-unit.compressed.css: -------------------------------------------------------------------------------- 1 | div{height:0} -------------------------------------------------------------------------------- /src/__tests__/fixtures/zero-length-no-unit.expanded.css: -------------------------------------------------------------------------------- 1 | div { 2 | height: 0; 3 | } 4 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/zero-length-no-unit.fixture.css: -------------------------------------------------------------------------------- 1 | div { 2 | height: 0px; 3 | } 4 | -------------------------------------------------------------------------------- /src/__tests__/index.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs'; 3 | import ava from 'ava'; 4 | import postcss from 'postcss'; 5 | import plugin from '../'; 6 | 7 | let base = path.join(__dirname, 'fixtures'); 8 | 9 | function perfectionist (css, options) { 10 | return plugin.process(css, options).css; 11 | } 12 | 13 | let specs = fs.readdirSync(base).reduce((tests, css) => { 14 | let [spec, style] = css.split('.'); 15 | if (!tests[spec]) { 16 | tests[spec] = {}; 17 | } 18 | tests[spec][style] = fs.readFileSync(path.join(base, css), 'utf-8'); 19 | return tests; 20 | }, {}); 21 | 22 | Object.keys(specs).forEach(name => { 23 | let spec = specs[name]; 24 | ava(`fixture: ${name}`, t => { 25 | t.plan(3); 26 | Object.keys(spec).slice(0, 3).forEach(s => { 27 | let result = perfectionist(spec.fixture, {format: s}); 28 | t.deepEqual(result, spec[s], `should output the expected result (${s})`); 29 | }); 30 | }); 31 | }); 32 | 33 | const scss = (css, format) => { 34 | return plugin.process(css, { 35 | format: format, 36 | syntax: 'scss', 37 | }).css; 38 | }; 39 | 40 | ava('should handle single line comments', t => { 41 | const input = 'h1{\n // test \n color: red;\n}\n'; 42 | t.deepEqual(scss(input, 'expanded'), 'h1 {\n // test \n color: red;\n}\n'); 43 | t.deepEqual(scss(input, 'compact'), 'h1 {/* test */ color: red; }\n'); 44 | t.deepEqual(scss(input, 'compressed'), 'h1{/* test */color:red}'); 45 | }); 46 | 47 | ava('should handle single line comments in @import', t => { 48 | const css = 'a, a:visited {\n //@include border-radius(5px);\n @include transition(background-color 0.2s ease);\n}\n'; 49 | t.deepEqual(scss(css), css); 50 | }); 51 | 52 | let ensureRed = postcss.plugin('ensure-red', () => { 53 | return css => { 54 | let rule = postcss.rule({selector: '*'}); 55 | rule.append(postcss.decl({ 56 | prop: 'color', 57 | value: 'red', 58 | important: true, 59 | })); 60 | css.append(rule); 61 | }; 62 | }); 63 | 64 | function handleRaws (t, opts = {}) { 65 | return postcss([ensureRed, plugin(opts)]).process('h1 { color: blue }').then(({css}) => { 66 | t.falsy(!!~css.indexOf('undefined')); 67 | }); 68 | } 69 | 70 | ava('should handle declarations added without raw properties (default)', handleRaws); 71 | ava('should handle declarations added without raw properties (compact)', handleRaws, {format: 'compact'}); 72 | ava('should handle declarations added without raw properties (compressed)', handleRaws, {format: 'compressed'}); 73 | -------------------------------------------------------------------------------- /src/__tests__/options.js: -------------------------------------------------------------------------------- 1 | import ava from 'ava'; 2 | import plugin from '../'; 3 | 4 | let tests = [{ 5 | message: 'should have a configurable indent size', 6 | fixture: 'h1{color:black}', 7 | expected: 'h1 {\n color: black;\n}\n', 8 | options: {indentSize: 2}, 9 | }, { 10 | message: 'should have a configurable indent type', 11 | fixture: 'h1{color:black}', 12 | expected: 'h1 {\n color: black;\n}\n', 13 | options: {indentChar: '\t', indentSize: 1}, 14 | }, { 15 | message: 'should allow disabling of the cascade', 16 | fixture: 'h1{-webkit-border-radius: 12px; border-radius: 12px; }', 17 | expected: 'h1 {\n -webkit-border-radius: 12px;\n border-radius: 12px;\n}\n', 18 | options: {cascade: false}, 19 | }, { 20 | message: 'should convert hex colours to uppercase', 21 | fixture: 'h1{color:#fff}', 22 | expected: 'h1 {\n color: #FFF;\n}\n', 23 | options: {colorCase: 'upper'}, 24 | }, { 25 | message: 'should expand shorthand hex', 26 | fixture: 'h1{color:#fff}', 27 | expected: 'h1 {\n color: #ffffff;\n}\n', 28 | options: {colorShorthand: false}, 29 | }, { 30 | message: 'should expand shorthand hex', 31 | fixture: 'h1{color:#ffffff}', 32 | expected: 'h1 {\n color: #ffffff;\n}\n', 33 | options: {colorShorthand: false}, 34 | }, { 35 | message: 'should not remove units from zero lengths', 36 | fixture: 'h1{width:0px}', 37 | expected: 'h1 {\n width: 0px;\n}\n', 38 | options: {zeroLengthNoUnit: false}, 39 | }, { 40 | message: 'should add leading zeroes', 41 | fixture: 'h1{width:.5px}', 42 | expected: 'h1 {\n width: 0.5px;\n}\n', 43 | options: {trimLeadingZero: false}, 44 | }, { 45 | message: 'should not trim trailing zeroes', 46 | fixture: 'h1{width:10.000px}', 47 | expected: 'h1 {\n width: 10.000px;\n}\n', 48 | options: {trimTrailingZeros: false}, 49 | }]; 50 | 51 | function perfectionist (css, options) { 52 | return plugin.process(css, options).css; 53 | } 54 | 55 | ava('perfectionist options', (t) => { 56 | tests.forEach(({fixture, expected, options}) => { 57 | t.deepEqual(perfectionist(fixture, options || {}), expected); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /src/applyCompact.js: -------------------------------------------------------------------------------- 1 | import {block as commentRegex} from 'comment-regex'; 2 | import valueParser from 'postcss-value-parser'; 3 | import applyTransformFeatures from './applyTransformFeatures'; 4 | import blank from './blank'; 5 | import deeplyNested from './deeplyNested'; 6 | import getIndent from './getIndent'; 7 | import isSassVariable from './isSassVariable'; 8 | import {maxSelectorLength} from './maxSelectorLength'; 9 | import walk from './walk'; 10 | 11 | export default function applyCompact (css, opts) { 12 | css.walk(rule => { 13 | if (rule.type === 'decl') { 14 | if (rule.raws.value) { 15 | rule.value = rule.raws.value.raw.trim(); 16 | } 17 | // Format sass variable `$size: 30em;` 18 | if (isSassVariable(rule)) { 19 | rule.raws.before = ''; 20 | rule.raws.between = ': '; 21 | } 22 | 23 | const ast = valueParser(rule.value); 24 | 25 | walk(ast, (node, index, parent) => { 26 | const next = parent.nodes[index + 1]; 27 | if (node.type === 'div' && node.value === ',') { 28 | node.before = ''; 29 | node.after = ' '; 30 | } 31 | if (node.type === 'function') { 32 | node.before = node.after = ' '; 33 | } 34 | if (node.type === 'space') { 35 | node.value = ' '; 36 | } 37 | if ( 38 | node.type === 'word' && 39 | node.value === '!' && 40 | parent.nodes[index + 2] && 41 | next.type === 'space' && 42 | parent.nodes[index + 2].type === 'word' 43 | ) { 44 | next.type = 'word'; 45 | next.value = ''; 46 | } 47 | if (node.type === 'word') { 48 | applyTransformFeatures(node, opts); 49 | } 50 | }); 51 | 52 | rule.value = ast.toString(); 53 | 54 | // Format `!important` 55 | if (rule.important) { 56 | rule.raws.important = ' !important'; 57 | } 58 | 59 | if (rule.raws.value) { 60 | rule.raws.value.raw = rule.value; 61 | } 62 | } 63 | opts.indentSize = 1; 64 | if (rule.type === 'comment') { 65 | if (rule.raws.inline) { 66 | rule.raws.inline = null; 67 | } 68 | let prev = rule.prev(); 69 | if (prev && prev.type === 'decl') { 70 | rule.raws.before = ' ' + blank(rule.raws.before); 71 | } 72 | if (rule.parent && rule.parent.type === 'root') { 73 | let next = rule.next(); 74 | if (next) { 75 | next.raws.before = '\n'; 76 | } 77 | if (rule !== css.first) { 78 | rule.raws.before = '\n'; 79 | } 80 | } 81 | return; 82 | } 83 | let indent = getIndent(rule, opts.indentChar, opts.indentSize); 84 | let deep = deeplyNested(rule); 85 | if (rule.type === 'rule' || rule.type === 'atrule') { 86 | if (!rule.nodes) { 87 | rule.raws.between = ''; 88 | } else { 89 | rule.raws.between = ' '; 90 | } 91 | rule.raws.after = ' '; 92 | rule.raws.before = indent + blank(rule.raws.before); 93 | rule.raws.semicolon = true; 94 | } 95 | if (rule.raws.selector && rule.raws.selector.raw) { 96 | rule.selector = rule.raws.selector.raw; 97 | } 98 | maxSelectorLength(rule, opts); 99 | if (rule.type === 'decl') { 100 | if (deeplyNested(rule.parent)) { 101 | let newline = rule === css.first ? '' : '\n'; 102 | rule.raws.before = newline + indent + blank(rule.raws.before); 103 | } else { 104 | rule.raws.before = ' ' + blank(rule.raws.before); 105 | } 106 | if (!commentRegex().test(rule.raws.between)) { 107 | rule.raws.between = ': '; 108 | } 109 | } 110 | if ((deep || rule.nodes) && rule !== css.first) { 111 | rule.raws.before = '\n '; 112 | } 113 | if (deep) { 114 | rule.raws.after = '\n' + indent; 115 | } 116 | if (rule.parent && rule !== rule.parent.first && (rule.type === 'rule' || rule.type === 'atrule')) { 117 | rule.raws.before = '\n' + indent; 118 | } 119 | }); 120 | css.raws.after = '\n'; 121 | } 122 | -------------------------------------------------------------------------------- /src/applyCompressed.js: -------------------------------------------------------------------------------- 1 | import {block as commentRegex} from 'comment-regex'; 2 | import valueParser from 'postcss-value-parser'; 3 | import applyTransformFeatures from './applyTransformFeatures'; 4 | import isSassVariable from './isSassVariable'; 5 | import walk from './walk'; 6 | 7 | export default function applyCompressed (css, opts) { 8 | css.walk(rule => { 9 | const {raws, type} = rule; 10 | rule.raws.semicolon = false; 11 | if (type === 'comment' && raws.inline) { 12 | rule.raws.inline = null; 13 | } 14 | if (type === 'rule' || type === 'atrule') { 15 | rule.raws.between = rule.raws.after = ''; 16 | } 17 | if (type === 'decl' && !commentRegex().test(raws.between)) { 18 | rule.raws.between = ':'; 19 | } 20 | if (rule.type === 'decl') { 21 | if (raws.value) { 22 | rule.value = raws.value.raw.trim(); 23 | } 24 | 25 | const ast = valueParser(rule.value); 26 | 27 | walk(ast, (node, index, parent) => { 28 | const next = parent.nodes[index + 1]; 29 | if (node.type === 'div' && node.value === ',' || node.type === 'function') { 30 | node.before = node.after = ''; 31 | } 32 | if (node.type === 'space') { 33 | node.value = ' '; 34 | if (next.type === 'word' && next.value[0] === '!') { 35 | node.value = ''; 36 | } 37 | } 38 | if ( 39 | node.type === 'word' && 40 | node.value === '!' && 41 | parent.nodes[index + 2] && 42 | next.type === 'space' && 43 | parent.nodes[index + 2].type === 'word' 44 | ) { 45 | next.type = 'word'; 46 | next.value = ''; 47 | } 48 | if (node.type === 'word') { 49 | applyTransformFeatures(node, opts); 50 | } 51 | }); 52 | 53 | rule.value = ast.toString(); 54 | 55 | if (isSassVariable(rule)) { 56 | rule.raws.before = ''; 57 | } 58 | 59 | // Format `!important` 60 | if (rule.important) { 61 | rule.raws.important = '!important'; 62 | } 63 | 64 | if (raws.value) { 65 | rule.raws.value.raw = rule.value; 66 | } 67 | } 68 | }); 69 | // Remove final newline 70 | css.raws.after = ''; 71 | } 72 | -------------------------------------------------------------------------------- /src/applyExpanded.js: -------------------------------------------------------------------------------- 1 | import postcss from 'postcss'; 2 | import valueParser from 'postcss-value-parser'; 3 | import {block as commentRegex} from 'comment-regex'; 4 | import applyTransformFeatures from './applyTransformFeatures'; 5 | import blank from './blank'; 6 | import getIndent from './getIndent'; 7 | import isSassVariable from './isSassVariable'; 8 | import longest from './longest'; 9 | import {maxAtRuleLength, maxSelectorLength, maxValueLength} from './maxSelectorLength'; 10 | import prefixedDecls from './prefixedDecls'; 11 | import space from './space'; 12 | import sameLine from './sameLine'; 13 | import walk from './walk'; 14 | 15 | const {unprefixed} = postcss.vendor; 16 | 17 | export default function applyExpanded (css, opts) { 18 | css.walk(rule => { 19 | const {raws, type} = rule; 20 | if (type === 'decl') { 21 | if (raws.value) { 22 | rule.value = raws.value.raw.trim(); 23 | } 24 | // Format sass variable `$size: 30em;` 25 | if (isSassVariable(rule)) { 26 | if (rule !== css.first) { 27 | rule.raws.before = '\n'; 28 | } 29 | rule.raws.between = ': '; 30 | } 31 | 32 | const ast = valueParser(rule.value); 33 | 34 | walk(ast, (node, index, parent) => { 35 | const next = parent.nodes[index + 1]; 36 | if (node.type === 'function') { 37 | node.before = node.after = ''; 38 | } 39 | if (node.type === 'div' && node.value === ',') { 40 | node.before = ''; 41 | node.after = ' '; 42 | } 43 | if (node.type === 'space') { 44 | node.value = ' '; 45 | } 46 | if ( 47 | node.type === 'word' && 48 | node.value === '!' && 49 | parent.nodes[index + 2] && 50 | next.type === 'space' && 51 | parent.nodes[index + 2].type === 'word' 52 | ) { 53 | next.type = 'word'; 54 | next.value = ''; 55 | } 56 | if (node.type === 'word') { 57 | applyTransformFeatures(node, opts); 58 | } 59 | }); 60 | 61 | rule.value = ast.toString(); 62 | 63 | // Format `!important` 64 | if (rule.important) { 65 | rule.raws.important = ' !important'; 66 | } 67 | 68 | if (raws.value) { 69 | rule.raws.value.raw = rule.value; 70 | } 71 | } 72 | let indent = getIndent(rule, opts.indentChar, opts.indentSize); 73 | if (type === 'comment') { 74 | let prev = rule.prev(); 75 | if (prev && prev.type === 'decl') { 76 | if (sameLine(prev, rule)) { 77 | rule.raws.before = ' ' + blank(rule.raws.before); 78 | } else { 79 | rule.raws.before = '\n' + indent + blank(rule.raws.before); 80 | } 81 | } 82 | if (!prev && rule !== css.first) { 83 | rule.raws.before = '\n' + indent + blank(rule.raws.before); 84 | } 85 | if (rule.parent && rule.parent.type === 'root') { 86 | let next = rule.next(); 87 | if (next) { 88 | next.raws.before = '\n\n'; 89 | } 90 | if (rule !== css.first) { 91 | rule.raws.before = '\n\n'; 92 | } 93 | } 94 | return; 95 | } 96 | rule.raws.before = indent + blank(rule.raws.before); 97 | if (type === 'rule' || type === 'atrule') { 98 | if (!rule.nodes) { 99 | rule.raws.between = ''; 100 | } else { 101 | rule.raws.between = ' '; 102 | } 103 | rule.raws.semicolon = true; 104 | if (rule.nodes) { 105 | rule.raws.after = '\n'; 106 | } 107 | } 108 | // visual cascade of vendor prefixed properties 109 | if (opts.cascade && type === 'rule' && rule.nodes.length > 1) { 110 | let props = []; 111 | let prefixed = prefixedDecls(rule).sort(longest).filter(({prop}) => { 112 | let base = unprefixed(prop); 113 | if (!~props.indexOf(base)) { 114 | return props.push(base); 115 | } 116 | return false; 117 | }); 118 | prefixed.forEach(prefix => { 119 | let base = unprefixed(prefix.prop); 120 | let vendor = prefix.prop.replace(base, '').length; 121 | rule.nodes.filter(({prop}) => prop && ~prop.indexOf(base)).forEach(decl => { 122 | let thisVendor = decl.prop.replace(base, '').length; 123 | let extraSpace = vendor - thisVendor; 124 | if (extraSpace > 0) { 125 | decl.raws.before = space(extraSpace) + blank(decl.raws.before); 126 | } 127 | }); 128 | }); 129 | } 130 | if (raws.selector && raws.selector.raw) { 131 | rule.selector = rule.raws.selector.raw; 132 | } 133 | maxSelectorLength(rule, opts); 134 | if (type === 'atrule') { 135 | if (rule.params) { 136 | rule.raws.afterName = ' '; 137 | } 138 | maxAtRuleLength(rule, opts); 139 | } 140 | if (type === 'decl') { 141 | if (!commentRegex().test(rule.raws.between)) { 142 | rule.raws.between = ': '; 143 | } 144 | maxValueLength(rule, opts); 145 | } 146 | if (rule.parent && rule.parent.type !== 'root') { 147 | rule.raws.before = '\n' + blank(rule.raws.before); 148 | rule.raws.after = '\n' + indent; 149 | } 150 | if (rule.parent && rule !== rule.parent.first && (type === 'rule' || type === 'atrule')) { 151 | if (type === 'atrule' && !rule.nodes) { 152 | rule.raws.before = '\n' + indent; 153 | return; 154 | } 155 | rule.raws.before = '\n\n' + indent; 156 | } 157 | }); 158 | css.raws.after = '\n'; 159 | } 160 | -------------------------------------------------------------------------------- /src/applyTransformFeatures.js: -------------------------------------------------------------------------------- 1 | import {unit} from 'postcss-value-parser'; 2 | 3 | function isHex (node) { 4 | if (node.value[0] !== '#') { 5 | return false; 6 | } 7 | const range = node.value.slice(1); 8 | return ~[3, 4, 6, 8].indexOf(range.length) && !isNaN(parseInt(range, 16)); 9 | } 10 | 11 | function toShorthand (hex) { 12 | if ( 13 | hex.length === 7 && 14 | hex[1] === hex[2] && 15 | hex[3] === hex[4] && 16 | hex[5] === hex[6] 17 | ) { 18 | return '#' + hex[2] + hex[4] + hex[6]; 19 | } 20 | return hex; 21 | } 22 | 23 | function toLonghand (hex) { 24 | if (hex.length !== 4) { 25 | return hex; 26 | } 27 | 28 | const r = hex[1]; 29 | const g = hex[2]; 30 | const b = hex[3]; 31 | return '#' + r + r + g + g + b + b; 32 | }; 33 | 34 | const lengths = [ 35 | 'px', 36 | 'em', 37 | 'rem', 38 | 'ex', 39 | 'ch', 40 | 'vh', 41 | 'vw', 42 | 'cm', 43 | 'mm', 44 | 'in', 45 | 'pt', 46 | 'pc', 47 | 'vmin', 48 | 'vmax', 49 | ]; 50 | 51 | export default function applyTransformFeatures (node, opts) { 52 | if (isHex(node)) { 53 | if (opts.colorCase === 'upper') { 54 | node.value = node.value.toUpperCase(); 55 | } 56 | if (opts.colorCase === 'lower') { 57 | node.value = node.value.toLowerCase(); 58 | } 59 | if (opts.colorShorthand === true) { 60 | node.value = toShorthand(node.value); 61 | } 62 | if (opts.colorShorthand === false) { 63 | node.value = toLonghand(node.value); 64 | } 65 | } 66 | const pair = unit(node.value); 67 | if (pair) { 68 | if ( 69 | opts.zeroLengthNoUnit === true && 70 | ~lengths.indexOf(pair.unit.toLowerCase()) && 71 | Number(pair.number) === 0 72 | ) { 73 | node.value = '0'; 74 | return; 75 | } 76 | 77 | const parts = pair.number.split('.'); 78 | let pre = parts[0]; 79 | let post = parts.slice(1).join('.'); 80 | 81 | if (opts.trimLeadingZero === true && parts[1]) { 82 | pre = pre.replace(/^0+/, ''); 83 | } else if (opts.trimLeadingZero === false && !pre.length) { 84 | pre = 0; 85 | } 86 | 87 | if (opts.trimTrailingZeros === true && parts[1]) { 88 | const rounded = String(Number(pre + '.' + post)).split('.')[1]; 89 | post = rounded ? '.' + rounded : ''; 90 | } else if (opts.trimTrailingZeros === false && parts[1]) { 91 | post = '.' + parts[1]; 92 | } 93 | 94 | node.value = pre + post + pair.unit; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/blank.js: -------------------------------------------------------------------------------- 1 | import defined from 'defined'; 2 | 3 | export default function blank (value) { 4 | return defined(value, ''); 5 | } 6 | -------------------------------------------------------------------------------- /src/deeplyNested.js: -------------------------------------------------------------------------------- 1 | export default function deeplyNested ({nodes}) { 2 | return nodes && nodes.some(({nodes: children}) => children); 3 | } 4 | -------------------------------------------------------------------------------- /src/getIndent.js: -------------------------------------------------------------------------------- 1 | import space from './space'; 2 | 3 | export default function getIndent (node, indent = ' ', base = 4) { 4 | let level = 0; 5 | let {parent} = node; 6 | while (parent && parent.type !== 'root') { 7 | level++; 8 | parent = parent.parent; 9 | } 10 | return space(level * base, indent); 11 | } 12 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import postcss from 'postcss'; 2 | import applyCompact from './applyCompact'; 3 | import applyCompressed from './applyCompressed'; 4 | import applyExpanded from './applyExpanded'; 5 | 6 | const perfectionist = postcss.plugin('perfectionist', opts => { 7 | opts = { 8 | format: 'expanded', 9 | indentSize: 4, 10 | indentChar: ' ', 11 | maxAtRuleLength: 80, 12 | maxSelectorLength: 80, 13 | maxValueLength: 80, 14 | trimLeadingZero: true, 15 | trimTrailingZeros: true, 16 | cascade: true, 17 | colorCase: 'lower', 18 | colorShorthand: true, 19 | zeroLengthNoUnit: true, 20 | ...opts, 21 | }; 22 | 23 | return css => { 24 | css.walk(node => { 25 | if (node.raws.before) { 26 | node.raws.before = node.raws.before.replace(/[;\s]/g, ''); 27 | } 28 | }); 29 | switch (opts.format) { 30 | case 'compact': 31 | applyCompact(css, opts); 32 | break; 33 | case 'compressed': 34 | applyCompressed(css, opts); 35 | break; 36 | case 'expanded': 37 | default: 38 | applyExpanded(css, opts); 39 | break; 40 | } 41 | }; 42 | }); 43 | 44 | perfectionist.process = (css, opts = {}) => { 45 | opts.map = opts.map || (opts.sourcemap ? true : null); 46 | if (opts.syntax === 'scss') { 47 | opts.syntax = require('postcss-scss'); 48 | } 49 | let processor = postcss([ perfectionist(opts) ]); 50 | return processor.process(css, opts); 51 | }; 52 | 53 | export default perfectionist; 54 | -------------------------------------------------------------------------------- /src/isSassVariable.js: -------------------------------------------------------------------------------- 1 | export default function isSassVariable ({parent, prop}) { 2 | return parent.type === 'root' && prop[0] === '$'; 3 | } 4 | -------------------------------------------------------------------------------- /src/longest.js: -------------------------------------------------------------------------------- 1 | export default (a, b) => b.prop.length - a.prop.length; 2 | -------------------------------------------------------------------------------- /src/maxSelectorLength.js: -------------------------------------------------------------------------------- 1 | import {list} from 'postcss'; 2 | import space from './space'; 3 | import getIndent from './getIndent'; 4 | 5 | function splitProperty (rule, prop, opts) { 6 | const {breakEvery, reindent, reduce, max} = { 7 | reindent: false, 8 | ...opts, 9 | }; 10 | const property = rule[prop]; 11 | if (!max || !property) { 12 | return; 13 | } 14 | const exploded = list.comma(property); 15 | if (property.length > max || reduce) { 16 | let indent = 0; 17 | if (typeof reindent === 'function') { 18 | indent = reindent(rule); 19 | } 20 | rule[prop] = exploded.reduce((lines, chunk) => { 21 | if (breakEvery) { 22 | lines.push(chunk); 23 | return lines; 24 | } 25 | if (lines[lines.length - 1].length + indent <= max) { 26 | const merged = `${lines[lines.length - 1]}, ${chunk}`; 27 | if (indent + merged.length <= max) { 28 | lines[lines.length - 1] = merged; 29 | return lines; 30 | } 31 | } 32 | lines.push(chunk); 33 | return lines; 34 | }, [exploded.shift()]).join(',\n' + space(indent)); 35 | } 36 | } 37 | 38 | export function maxAtRuleLength (rule, {maxAtRuleLength: max}) { 39 | return splitProperty(rule, 'params', { 40 | max, 41 | breakEvery: true, 42 | reindent: function (r) { 43 | return r.name.length + 2; 44 | }, 45 | }); 46 | } 47 | 48 | export function maxSelectorLength (rule, opts) { 49 | return splitProperty(rule, 'selector', { 50 | max: opts.maxSelectorLength, 51 | reduce: true, // where possible reduce to one line 52 | reindent: function (r) { 53 | return getIndent(r, opts.indentChar, opts.indentSize).length; 54 | }, 55 | }); 56 | } 57 | 58 | export function maxValueLength (rule, {maxValueLength: max}) { 59 | if (rule.raws.value && rule.raws.value.raw) { 60 | rule.value = rule.raws.value.raw; 61 | } 62 | return splitProperty(rule, 'value', { 63 | max, 64 | breakEvery: true, 65 | reindent: function (r) { 66 | return getIndent(r).length + r.prop.length + 2; 67 | }, 68 | }); 69 | } 70 | -------------------------------------------------------------------------------- /src/prefixedDecls.js: -------------------------------------------------------------------------------- 1 | import vendors from 'vendors'; 2 | 3 | const prefixes = vendors.map(vendor => `-${vendor}-`); 4 | 5 | export default function prefixedDeclarations ({nodes}) { 6 | const prefix = node => prefixes.some(p => node.prop && !node.prop.indexOf(p)); 7 | return nodes.filter(prefix); 8 | } 9 | -------------------------------------------------------------------------------- /src/sameLine.js: -------------------------------------------------------------------------------- 1 | export default (a, b) => a.source.end.line === b.source.start.line; 2 | -------------------------------------------------------------------------------- /src/space.js: -------------------------------------------------------------------------------- 1 | import 'string.prototype.repeat'; 2 | 3 | export default function space (amount, indent = ' ') { 4 | return indent.repeat(amount); 5 | } 6 | -------------------------------------------------------------------------------- /src/walk.js: -------------------------------------------------------------------------------- 1 | export default function walk (parent, callback) { 2 | parent.nodes.forEach((node, index) => { 3 | const bubble = callback(node, index, parent); 4 | if (node.nodes && bubble !== false) { 5 | walk(node, callback); 6 | } 7 | }); 8 | } 9 | --------------------------------------------------------------------------------